mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Compare commits
26 Commits
delay-node
...
rerorder-i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19d391fa05 | ||
|
|
d1aa1fd4d8 | ||
|
|
4133f9c56f | ||
|
|
53055064e1 | ||
|
|
06090d8de1 | ||
|
|
d6ccae38f8 | ||
|
|
f7210effec | ||
|
|
ea50ba16f9 | ||
|
|
b62e4f6662 | ||
|
|
62f2a552ea | ||
|
|
b053e02174 | ||
|
|
3798167908 | ||
|
|
56fe2014e1 | ||
|
|
be2e64433f | ||
|
|
8732e89e55 | ||
|
|
fdd0a93bad | ||
|
|
dd12572b1d | ||
|
|
5cc791690b | ||
|
|
250005ad16 | ||
|
|
b4a03a56b4 | ||
|
|
d2432716ea | ||
|
|
52ef85cba3 | ||
|
|
8140057bea | ||
|
|
22df59e229 | ||
|
|
ed351eee54 | ||
|
|
aac2a8f830 |
@@ -13,6 +13,7 @@ matrix:
|
||||
- node_js: "12"
|
||||
script:
|
||||
- ./node_modules/.bin/grunt no-coverage
|
||||
allow_failures:
|
||||
- node_js: "16"
|
||||
script:
|
||||
- ./node_modules/.bin/grunt no-coverage
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"cookie": "0.4.1",
|
||||
"cookie-parser": "1.4.5",
|
||||
"cors": "2.8.5",
|
||||
"cron": "1.7.2",
|
||||
"cronosjs": "1.7.1",
|
||||
"denque": "1.5.0",
|
||||
"express": "4.17.1",
|
||||
"express-session": "1.17.1",
|
||||
|
||||
@@ -455,7 +455,7 @@
|
||||
var propertyValue = $('<input/>',{class:"node-input-prop-property-value",type:"text"})
|
||||
.css("width","calc(70% - 30px)")
|
||||
.appendTo(row)
|
||||
.typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','bin','date','jsonata','env']});
|
||||
.typedInput({default:'str',types:['flow','global','str','num','bool','json','bin','date','jsonata','env','msg']});
|
||||
|
||||
propertyName.typedInput('value',prop.p);
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var cron = require("cron");
|
||||
const {scheduleTask} = require("cronosjs");
|
||||
|
||||
function InjectNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
@@ -85,7 +85,7 @@ module.exports = function(RED) {
|
||||
if (RED.settings.verbose) {
|
||||
this.log(RED._("inject.crontab", this));
|
||||
}
|
||||
this.cronjob = new cron.CronJob(this.crontab, function() { node.emit("input", {}); }, null, true);
|
||||
this.cronjob = scheduleTask(this.crontab,() => { node.emit("input", {})});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -51,6 +51,10 @@ module.exports = function(RED) {
|
||||
|
||||
function processMsg(msg,nodeSend, done) {
|
||||
var filename = node.filename || msg.filename || "";
|
||||
var fullFilename = filename;
|
||||
if (filename && RED.settings.fileWorkingDirectory && !path.isAbsolute(filename)) {
|
||||
fullFilename = path.resolve(path.join(RED.settings.fileWorkingDirectory,filename));
|
||||
}
|
||||
if ((!node.filename) && (!node.tout)) {
|
||||
node.tout = setTimeout(function() {
|
||||
node.status({fill:"grey",shape:"dot",text:filename});
|
||||
@@ -62,7 +66,7 @@ module.exports = function(RED) {
|
||||
node.warn(RED._("file.errors.nofilename"));
|
||||
done();
|
||||
} else if (node.overwriteFile === "delete") {
|
||||
fs.unlink(filename, function (err) {
|
||||
fs.unlink(fullFilename, function (err) {
|
||||
if (err) {
|
||||
node.error(RED._("file.errors.deletefail",{error:err.toString()}),msg);
|
||||
} else {
|
||||
@@ -74,7 +78,7 @@ module.exports = function(RED) {
|
||||
done();
|
||||
});
|
||||
} else if (msg.hasOwnProperty("payload") && (typeof msg.payload !== "undefined")) {
|
||||
var dir = path.dirname(filename);
|
||||
var dir = path.dirname(fullFilename);
|
||||
if (node.createDir) {
|
||||
try {
|
||||
fs.ensureDirSync(dir);
|
||||
@@ -94,7 +98,7 @@ module.exports = function(RED) {
|
||||
if ((node.appendNewline) && (!Buffer.isBuffer(data))) { data += os.EOL; }
|
||||
var buf = encode(data, node.encoding);
|
||||
if (node.overwriteFile === "true") {
|
||||
var wstream = fs.createWriteStream(filename, { encoding:'binary', flags:'w', autoClose:true });
|
||||
var wstream = fs.createWriteStream(fullFilename, { encoding:'binary', flags:'w', autoClose:true });
|
||||
node.wstream = wstream;
|
||||
wstream.on("error", function(err) {
|
||||
node.error(RED._("file.errors.writefail",{error:err.toString()}),msg);
|
||||
@@ -116,7 +120,7 @@ module.exports = function(RED) {
|
||||
// of the file. Check the file hasn't been deleted
|
||||
// or deleted and recreated.
|
||||
try {
|
||||
var stat = fs.statSync(filename);
|
||||
var stat = fs.statSync(fullFilename);
|
||||
// File exists - check the inode matches
|
||||
if (stat.ino !== node.wstreamIno) {
|
||||
// The file has been recreated. Close the current
|
||||
@@ -135,10 +139,10 @@ module.exports = function(RED) {
|
||||
}
|
||||
}
|
||||
if (recreateStream) {
|
||||
node.wstream = fs.createWriteStream(filename, { encoding:'binary', flags:'a', autoClose:true });
|
||||
node.wstream = fs.createWriteStream(fullFilename, { encoding:'binary', flags:'a', autoClose:true });
|
||||
node.wstream.on("open", function(fd) {
|
||||
try {
|
||||
var stat = fs.statSync(filename);
|
||||
var stat = fs.statSync(fullFilename);
|
||||
node.wstreamIno = stat.ino;
|
||||
} catch(err) {
|
||||
}
|
||||
@@ -258,6 +262,10 @@ module.exports = function(RED) {
|
||||
|
||||
this.on("input",function(msg, nodeSend, nodeDone) {
|
||||
var filename = (node.filename || msg.filename || "").replace(/\t|\r|\n/g,'');
|
||||
var fullFilename = filename;
|
||||
if (filename && RED.settings.fileWorkingDirectory && !path.isAbsolute(filename)) {
|
||||
fullFilename = path.resolve(path.join(RED.settings.fileWorkingDirectory,filename));
|
||||
}
|
||||
if (!node.filename) {
|
||||
node.status({fill:"grey",shape:"dot",text:filename});
|
||||
}
|
||||
@@ -279,7 +287,7 @@ module.exports = function(RED) {
|
||||
var hwm;
|
||||
var getout = false;
|
||||
|
||||
var rs = fs.createReadStream(filename)
|
||||
var rs = fs.createReadStream(fullFilename)
|
||||
.on('readable', function () {
|
||||
var chunk;
|
||||
var hwm = rs._readableState.highWaterMark;
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"cookie-parser": "1.4.5",
|
||||
"cookie": "0.4.1",
|
||||
"cors": "2.8.5",
|
||||
"cron": "1.7.2",
|
||||
"cronosjs": "1.7.1",
|
||||
"denque": "1.5.0",
|
||||
"fs-extra": "9.1.0",
|
||||
"fs.notify": "0.0.4",
|
||||
|
||||
@@ -9,6 +9,7 @@ const path = require("path");
|
||||
const clone = require("clone");
|
||||
const exec = require("@node-red/util").exec;
|
||||
const log = require("@node-red/util").log;
|
||||
const hooks = require("@node-red/util").hooks;
|
||||
|
||||
const BUILTIN_MODULES = require('module').builtinModules;
|
||||
const EXTERNAL_MODULES_DIR = "externalModules";
|
||||
@@ -189,13 +190,29 @@ async function installModule(moduleDetails) {
|
||||
|
||||
await ensureModuleDir();
|
||||
|
||||
var args = ["install", installSpec, "--production"];
|
||||
return exec.run(NPM_COMMAND, args, {
|
||||
cwd: installDir
|
||||
},true).then(result => {
|
||||
let triggerPayload = {
|
||||
"module": moduleDetails.module,
|
||||
"version": moduleDetails.version,
|
||||
"dir": installDir,
|
||||
"args": ["--production"]
|
||||
}
|
||||
return hooks.trigger("preInstall", triggerPayload).then((result) => {
|
||||
// preInstall passed
|
||||
// - run install
|
||||
if (result !== false) {
|
||||
let extraArgs = triggerPayload.args || [];
|
||||
let args = ['install', ...extraArgs, installSpec]
|
||||
log.trace(NPM_COMMAND + JSON.stringify(args));
|
||||
return exec.run(NPM_COMMAND, args, { cwd: installDir },true)
|
||||
} else {
|
||||
log.trace("skipping npm install");
|
||||
}
|
||||
}).then(() => {
|
||||
return hooks.trigger("postInstall", triggerPayload)
|
||||
}).then(() => {
|
||||
log.info(log._("server.install.installed", { name: installSpec }));
|
||||
}).catch(result => {
|
||||
var output = result.stderr;
|
||||
var output = result.stderr || result.toString();
|
||||
var e;
|
||||
if (/E404/.test(output) || /ETARGET/.test(output)) {
|
||||
log.error(log._("server.install.install-failed-not-found",{name:installSpec}));
|
||||
|
||||
@@ -23,7 +23,7 @@ const tar = require("tar");
|
||||
const registry = require("./registry");
|
||||
const registryUtil = require("./util");
|
||||
const library = require("./library");
|
||||
const {exec,log,events} = require("@node-red/util");
|
||||
const {exec,log,events,hooks} = require("@node-red/util");
|
||||
const child_process = require('child_process');
|
||||
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
||||
let installerEnabled = false;
|
||||
@@ -168,11 +168,30 @@ async function installModule(module,version,url) {
|
||||
}
|
||||
|
||||
var installDir = settings.userDir || process.env.NODE_RED_HOME || ".";
|
||||
var args = ['install','--no-audit','--no-update-notifier','--no-fund','--save','--save-prefix=~','--production',installName];
|
||||
log.trace(npmCommand + JSON.stringify(args));
|
||||
return exec.run(npmCommand,args,{
|
||||
cwd: installDir
|
||||
}, true).then(result => {
|
||||
let triggerPayload = {
|
||||
"module": module,
|
||||
"version": version,
|
||||
"url": url,
|
||||
"dir": installDir,
|
||||
"isExisting": isExisting,
|
||||
"isUpgrade": isUpgrade,
|
||||
"args": ['--no-audit','--no-update-notifier','--no-fund','--save','--save-prefix=~','--production']
|
||||
}
|
||||
|
||||
return hooks.trigger("preInstall", triggerPayload).then((result) => {
|
||||
// preInstall passed
|
||||
// - run install
|
||||
if (result !== false) {
|
||||
let extraArgs = triggerPayload.args || [];
|
||||
let args = ['install', ...extraArgs, installName]
|
||||
log.trace(npmCommand + JSON.stringify(args));
|
||||
return exec.run(npmCommand,args,{ cwd: installDir}, true)
|
||||
} else {
|
||||
log.trace("skipping npm install");
|
||||
}
|
||||
}).then(() => {
|
||||
return hooks.trigger("postInstall", triggerPayload)
|
||||
}).then(() => {
|
||||
if (isExisting) {
|
||||
// This is a module we already have installed as a non-user module.
|
||||
// That means it was discovered when loading, but was not listed
|
||||
@@ -191,29 +210,45 @@ async function installModule(module,version,url) {
|
||||
events.emit("runtime-event",{id:"restart-required",payload:{type:"warning",text:"notification.warnings.restartRequired"},retain:true});
|
||||
return require("./registry").setModulePendingUpdated(module,version);
|
||||
}
|
||||
}).catch(result => {
|
||||
var output = result.stderr;
|
||||
var e;
|
||||
var lookFor404 = new RegExp(" 404 .*"+module,"m");
|
||||
var lookForVersionNotFound = new RegExp("version not found: "+module+"@"+version,"m");
|
||||
if (lookFor404.test(output)) {
|
||||
log.warn(log._("server.install.install-failed-not-found",{name:module}));
|
||||
e = new Error("Module not found");
|
||||
e.code = 404;
|
||||
throw e;
|
||||
} else if (isUpgrade && lookForVersionNotFound.test(output)) {
|
||||
log.warn(log._("server.install.upgrade-failed-not-found",{name:module}));
|
||||
e = new Error("Module not found");
|
||||
e.code = 404;
|
||||
throw e;
|
||||
} else {
|
||||
}).catch(err => {
|
||||
let e;
|
||||
if (err.hook) {
|
||||
// preInstall failed
|
||||
log.warn(log._("server.install.install-failed-long",{name:module}));
|
||||
log.warn("------------------------------------------");
|
||||
log.warn(output);
|
||||
log.warn(err.toString());
|
||||
log.warn("------------------------------------------");
|
||||
throw new Error(log._("server.install.install-failed"));
|
||||
e = new Error(log._("server.install.install-failed")+": "+err.toString());
|
||||
if (err.hook === "postInstall") {
|
||||
return exec.run(npmCommand,["remove",module],{ cwd: installDir}, false).finally(() => {
|
||||
throw e;
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// npm install failed
|
||||
let output = err.stderr;
|
||||
let lookFor404 = new RegExp(" 404 .*"+module,"m");
|
||||
let lookForVersionNotFound = new RegExp("version not found: "+module+"@"+version,"m");
|
||||
if (lookFor404.test(output)) {
|
||||
log.warn(log._("server.install.install-failed-not-found",{name:module}));
|
||||
e = new Error("Module not found");
|
||||
e.code = 404;
|
||||
} else if (isUpgrade && lookForVersionNotFound.test(output)) {
|
||||
log.warn(log._("server.install.upgrade-failed-not-found",{name:module}));
|
||||
e = new Error("Module not found");
|
||||
e.code = 404;
|
||||
} else {
|
||||
log.warn(log._("server.install.install-failed-long",{name:module}));
|
||||
log.warn("------------------------------------------");
|
||||
log.warn(output);
|
||||
log.warn("------------------------------------------");
|
||||
e = new Error(log._("server.install.install-failed"));
|
||||
}
|
||||
}
|
||||
})
|
||||
if (e) {
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}).catch(err => {
|
||||
// In case of error, reset activePromise to be resolvable
|
||||
activePromise = Promise.resolve();
|
||||
@@ -412,17 +447,29 @@ function uninstallModule(module) {
|
||||
log.info(log._("server.install.uninstalling",{name:module}));
|
||||
|
||||
var args = ['remove','--no-audit','--no-update-notifier','--no-fund','--save',module];
|
||||
log.trace(npmCommand + JSON.stringify(args));
|
||||
|
||||
exec.run(npmCommand,args,{
|
||||
cwd: installDir,
|
||||
},true).then(result => {
|
||||
let triggerPayload = {
|
||||
"module": module,
|
||||
"dir": installDir,
|
||||
}
|
||||
return hooks.trigger("preUninstall", triggerPayload).then(() => {
|
||||
// preUninstall passed
|
||||
// - run uninstall
|
||||
log.trace(npmCommand + JSON.stringify(args));
|
||||
return exec.run(npmCommand,args,{ cwd: installDir}, true)
|
||||
}).then(() => {
|
||||
log.info(log._("server.install.uninstalled",{name:module}));
|
||||
reportRemovedModules(list);
|
||||
library.removeExamplesDir(module);
|
||||
resolve(list);
|
||||
return hooks.trigger("postUninstall", triggerPayload).catch((err)=>{
|
||||
log.warn("------------------------------------------");
|
||||
log.warn(err.toString());
|
||||
log.warn("------------------------------------------");
|
||||
}).finally(() => {
|
||||
resolve(list);
|
||||
})
|
||||
}).catch(result => {
|
||||
var output = result.stderr;
|
||||
let output = result.stderr || result;
|
||||
log.warn(log._("server.install.uninstall-failed-long",{name:module}));
|
||||
log.warn("------------------------------------------");
|
||||
log.warn(output.toString());
|
||||
|
||||
@@ -19,7 +19,7 @@ var redUtil = require("@node-red/util").util;
|
||||
const events = require("@node-red/util").events;
|
||||
var flowUtil = require("./util");
|
||||
const context = require('../nodes/context');
|
||||
const hooks = require("../hooks");
|
||||
const hooks = require("@node-red/util").hooks;
|
||||
|
||||
var Subflow;
|
||||
var Log;
|
||||
|
||||
@@ -20,7 +20,6 @@ var redNodes = require("./nodes");
|
||||
var flows = require("./flows");
|
||||
var storage = require("./storage");
|
||||
var library = require("./library");
|
||||
var hooks = require("./hooks");
|
||||
var plugins = require("./plugins");
|
||||
var settings = require("./settings");
|
||||
|
||||
@@ -29,7 +28,7 @@ var path = require('path');
|
||||
var fs = require("fs");
|
||||
var os = require("os");
|
||||
|
||||
const {log,i18n,events,exec,util} = require("@node-red/util");
|
||||
const {log,i18n,events,exec,util,hooks} = require("@node-red/util");
|
||||
|
||||
var runtimeMetricInterval = null;
|
||||
|
||||
@@ -181,6 +180,9 @@ function start() {
|
||||
if (settings.settingsFile) {
|
||||
log.info(log._("runtime.paths.settings",{path:settings.settingsFile}));
|
||||
}
|
||||
if (settings.httpRoot !== undefined) {
|
||||
log.warn(log._("server.deprecatedOption",{old:"httpRoot", new: "httpNodeRoot/httpAdminRoot"}));
|
||||
}
|
||||
if (settings.httpStatic) {
|
||||
log.info(log._("runtime.paths.httpStatic",{path:path.resolve(settings.httpStatic)}));
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ var redUtil = require("@node-red/util").util;
|
||||
var Log = require("@node-red/util").log;
|
||||
var context = require("./context");
|
||||
var flows = require("../flows");
|
||||
const hooks = require("../hooks");
|
||||
const hooks = require("@node-red/util").hooks;
|
||||
|
||||
|
||||
const NOOP_SEND = function() {}
|
||||
|
||||
@@ -38,6 +38,8 @@ var activeProject;
|
||||
|
||||
var globalGitUser = false;
|
||||
|
||||
var usingHostName = false;
|
||||
|
||||
function init(_settings, _runtime) {
|
||||
settings = _settings;
|
||||
runtime = _runtime;
|
||||
@@ -77,6 +79,7 @@ function init(_settings, _runtime) {
|
||||
} else {
|
||||
flowsFile = 'flows_'+require('os').hostname()+'.json';
|
||||
flowsFullPath = fspath.join(settings.userDir,flowsFile);
|
||||
usingHostName = true;
|
||||
}
|
||||
var ffExt = fspath.extname(flowsFullPath);
|
||||
var ffBase = fspath.basename(flowsFullPath,ffExt);
|
||||
@@ -526,7 +529,7 @@ async function getFlows() {
|
||||
if (projectsEnabled) {
|
||||
log.info(log._("storage.localfilesystem.projects.projects-directory", {projectsDirectory: projectsDir}));
|
||||
}
|
||||
|
||||
|
||||
if (activeProject) {
|
||||
// At this point activeProject will be a string, so go load it and
|
||||
// swap in an instance of Project
|
||||
@@ -541,6 +544,7 @@ async function getFlows() {
|
||||
} else {
|
||||
projectLogMessages.forEach(log.warn);
|
||||
}
|
||||
if (usingHostName) { log.warn(log._("storage.localfilesystem.warn_name")) };
|
||||
log.info(log._("storage.localfilesystem.flows-file",{path:flowsFullPath}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"install-failed-not-found": "$t(server.install.install-failed-long) module not found",
|
||||
"install-failed-name": "$t(server.install.install-failed-long) invalid module name: __name__",
|
||||
"install-failed-url": "$t(server.install.install-failed-long) invalid url: __url__",
|
||||
"post-install-error": "Error running 'postInstall' hook:",
|
||||
"upgrading": "Upgrading module: __name__ to version: __version__",
|
||||
"upgraded": "Upgraded module: __name__. Restart Node-RED to use the new version",
|
||||
"upgrade-failed-not-found": "$t(server.install.install-failed-long) version not found",
|
||||
@@ -42,7 +43,7 @@
|
||||
"uninstall-failed-long": "Uninstall of module __name__ failed:",
|
||||
"uninstalled": "Uninstalled module: __name__"
|
||||
},
|
||||
"deprecatedOption": "Use of __old__ is deprecated. Use __new__ instead",
|
||||
"deprecatedOption": "Use of __old__ is DEPRECATED. Use __new__ instead",
|
||||
"unable-to-listen": "Unable to listen on __listenpath__",
|
||||
"port-in-use": "Error: port in use",
|
||||
"uncaught-exception": "Uncaught Exception:",
|
||||
@@ -50,7 +51,7 @@
|
||||
"now-running": "Server now running at __listenpath__",
|
||||
"failed-to-start": "Failed to start server:",
|
||||
"headless-mode": "Running in headless mode",
|
||||
"httpadminauth-deprecated": "use of httpAdminAuth is deprecated. Use adminAuth instead",
|
||||
"httpadminauth-deprecated": "Use of httpAdminAuth is DEPRECATED. Use adminAuth instead",
|
||||
"https": {
|
||||
"refresh-interval": "Refreshing https settings every __interval__ hours",
|
||||
"settings-refreshed": "Server https settings have been refreshed",
|
||||
@@ -159,6 +160,7 @@
|
||||
"restore": "Restoring __type__ file backup : __path__",
|
||||
"restore-fail": "Restoring __type__ file backup failed : __message__",
|
||||
"fsync-fail": "Flushing file __path__ to disk failed : __message__",
|
||||
"warn_name": "Flows file name not set. Generating name using hostname.",
|
||||
"projects": {
|
||||
"changing-project": "Setting active project : __project__",
|
||||
"active-project": "Active project : __project__",
|
||||
|
||||
10
packages/node_modules/@node-red/util/index.js
vendored
10
packages/node_modules/@node-red/util/index.js
vendored
@@ -19,6 +19,7 @@ const i18n = require("./lib/i18n");
|
||||
const util = require("./lib/util");
|
||||
const events = require("./lib/events");
|
||||
const exec = require("./lib/exec");
|
||||
const hooks = require("./lib/hooks");
|
||||
|
||||
/**
|
||||
* This module provides common utilities for the Node-RED runtime and editor
|
||||
@@ -69,5 +70,12 @@ module.exports = {
|
||||
* @mixes @node-red/util_exec
|
||||
* @memberof @node-red/util
|
||||
*/
|
||||
exec: exec
|
||||
exec: exec,
|
||||
|
||||
/**
|
||||
* Runtime hooks
|
||||
* @mixes @node-red/util_hooks
|
||||
* @memberof @node-red/util
|
||||
*/
|
||||
hooks: hooks
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const Log = require("@node-red/util").log;
|
||||
const Log = require("./log.js");
|
||||
|
||||
const VALID_HOOKS = [
|
||||
// Message Routing Path
|
||||
@@ -8,14 +8,28 @@ const VALID_HOOKS = [
|
||||
"postDeliver",
|
||||
"onReceive",
|
||||
"postReceive",
|
||||
"onComplete"
|
||||
"onComplete",
|
||||
// Module install hooks
|
||||
"preInstall",
|
||||
"postInstall",
|
||||
"preUninstall",
|
||||
"postUninstall"
|
||||
]
|
||||
|
||||
|
||||
// Flags for what hooks have handlers registered
|
||||
let states = { }
|
||||
|
||||
// Hooks by id
|
||||
// Doubly-LinkedList of hooks by id.
|
||||
// - hooks[id] points to head of list
|
||||
// - each list item looks like:
|
||||
// {
|
||||
// cb: the callback function
|
||||
// location: filename/line of code that added the hook
|
||||
// previousHook: reference to previous hook in list
|
||||
// nextHook: reference to next hook in list
|
||||
// removed: a flag that is set if the item was removed
|
||||
// }
|
||||
let hooks = { }
|
||||
|
||||
// Hooks by label
|
||||
@@ -35,12 +49,12 @@ let labelledHooks = { }
|
||||
* - `postReceive` - passed a `ReceiveEvent` when the message has been given to the node's `input` handler(s)
|
||||
* - `onComplete` - passed a `CompleteEvent` when the node has completed with a message or logged an error
|
||||
*
|
||||
* @mixin @node-red/runtime_hooks
|
||||
* @mixin @node-red/util_hooks
|
||||
*/
|
||||
|
||||
/**
|
||||
* Register a handler to a named hook
|
||||
* @memberof @node-red/runtime_hooks
|
||||
* @memberof @node-red/util_hooks
|
||||
* @param {String} hookId - the name of the hook to attach to
|
||||
* @param {Function} callback - the callback function for the hook
|
||||
*/
|
||||
@@ -49,26 +63,39 @@ function add(hookId, callback) {
|
||||
if (VALID_HOOKS.indexOf(id) === -1) {
|
||||
throw new Error(`Invalid hook '${id}'`);
|
||||
}
|
||||
if (label) {
|
||||
if (labelledHooks[label] && labelledHooks[label][id]) {
|
||||
throw new Error("Hook "+hookId+" already registered")
|
||||
}
|
||||
labelledHooks[label] = labelledHooks[label]||{};
|
||||
labelledHooks[label][id] = callback;
|
||||
if (label && labelledHooks[label] && labelledHooks[label][id]) {
|
||||
throw new Error("Hook "+hookId+" already registered")
|
||||
}
|
||||
// Get location of calling code
|
||||
const stack = new Error().stack;
|
||||
const callModule = stack.split("\n")[2].split("(")[1].slice(0,-1);
|
||||
Log.debug(`Adding hook '${hookId}' from ${callModule}`);
|
||||
|
||||
hooks[id] = hooks[id] || [];
|
||||
hooks[id].push({cb:callback,location:callModule});
|
||||
const hookItem = {cb:callback, location: callModule, previousHook: null, nextHook: null }
|
||||
|
||||
let tailItem = hooks[id];
|
||||
if (tailItem === undefined) {
|
||||
hooks[id] = hookItem;
|
||||
} else {
|
||||
while(tailItem.nextHook !== null) {
|
||||
tailItem = tailItem.nextHook
|
||||
}
|
||||
tailItem.nextHook = hookItem;
|
||||
hookItem.previousHook = tailItem;
|
||||
}
|
||||
|
||||
if (label) {
|
||||
labelledHooks[label] = labelledHooks[label]||{};
|
||||
labelledHooks[label][id] = hookItem;
|
||||
}
|
||||
|
||||
// TODO: get rid of this;
|
||||
states[id] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a handled from a named hook
|
||||
* @memberof @node-red/runtime_hooks
|
||||
* @memberof @node-red/util_hooks
|
||||
* @param {String} hookId - the name of the hook event to remove - must be `name.label`
|
||||
*/
|
||||
function remove(hookId) {
|
||||
@@ -95,33 +122,66 @@ function remove(hookId) {
|
||||
}
|
||||
}
|
||||
|
||||
function removeHook(id,callback) {
|
||||
let i = hooks[id].findIndex(hook => hook.cb === callback);
|
||||
if (i !== -1) {
|
||||
hooks[id].splice(i,1);
|
||||
if (hooks[id].length === 0) {
|
||||
delete hooks[id];
|
||||
delete states[id];
|
||||
}
|
||||
function removeHook(id,hookItem) {
|
||||
let previousHook = hookItem.previousHook;
|
||||
let nextHook = hookItem.nextHook;
|
||||
|
||||
if (previousHook) {
|
||||
previousHook.nextHook = nextHook;
|
||||
} else {
|
||||
hooks[id] = nextHook;
|
||||
}
|
||||
if (nextHook) {
|
||||
nextHook.previousHook = previousHook;
|
||||
}
|
||||
hookItem.removed = true;
|
||||
if (!previousHook && !nextHook) {
|
||||
delete hooks[id];
|
||||
delete states[id];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function trigger(hookId, payload, done) {
|
||||
const hookStack = hooks[hookId];
|
||||
if (!hookStack || hookStack.length === 0) {
|
||||
done();
|
||||
return;
|
||||
let hookItem = hooks[hookId];
|
||||
if (!hookItem) {
|
||||
if (done) {
|
||||
done();
|
||||
return;
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
let i = 0;
|
||||
|
||||
if (!done) {
|
||||
return new Promise((resolve,reject) => {
|
||||
invokeStack(hookItem,payload,function(err) {
|
||||
if (err !== undefined && err !== false) {
|
||||
if (!(err instanceof Error)) {
|
||||
err = new Error(err);
|
||||
}
|
||||
err.hook = hookId
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(err);
|
||||
}
|
||||
})
|
||||
});
|
||||
} else {
|
||||
invokeStack(hookItem,payload,done)
|
||||
}
|
||||
}
|
||||
function invokeStack(hookItem,payload,done) {
|
||||
function callNextHook(err) {
|
||||
if (i === hookStack.length || err) {
|
||||
if (!hookItem || err) {
|
||||
done(err);
|
||||
return;
|
||||
}
|
||||
const hook = hookStack[i++];
|
||||
const callback = hook.cb;
|
||||
if (hookItem.removed) {
|
||||
hookItem = hookItem.nextHook;
|
||||
callNextHook();
|
||||
return;
|
||||
}
|
||||
const callback = hookItem.cb;
|
||||
if (callback.length === 1) {
|
||||
try {
|
||||
let result = callback(payload);
|
||||
@@ -134,6 +194,7 @@ function trigger(hookId, payload, done) {
|
||||
result.then(handleResolve, callNextHook)
|
||||
return;
|
||||
}
|
||||
hookItem = hookItem.nextHook;
|
||||
callNextHook();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
@@ -148,15 +209,15 @@ function trigger(hookId, payload, done) {
|
||||
}
|
||||
}
|
||||
}
|
||||
callNextHook();
|
||||
|
||||
function handleResolve(result) {
|
||||
if (result === undefined) {
|
||||
hookItem = hookItem.nextHook;
|
||||
callNextHook();
|
||||
} else {
|
||||
done(result);
|
||||
}
|
||||
}
|
||||
callNextHook();
|
||||
}
|
||||
|
||||
function clear() {
|
||||
@@ -179,4 +240,4 @@ module.exports = {
|
||||
add,
|
||||
remove,
|
||||
trigger
|
||||
}
|
||||
}
|
||||
1
packages/node_modules/node-red/red.js
vendored
1
packages/node_modules/node-red/red.js
vendored
@@ -263,7 +263,6 @@ httpsPromise.then(function(startupHttps) {
|
||||
settings.httpAdminRoot = false;
|
||||
settings.httpNodeRoot = false;
|
||||
} else {
|
||||
settings.httpRoot = settings.httpRoot||"/";
|
||||
settings.disableEditor = settings.disableEditor||false;
|
||||
}
|
||||
|
||||
|
||||
24
packages/node_modules/node-red/settings.js
vendored
24
packages/node_modules/node-red/settings.js
vendored
@@ -12,6 +12,13 @@
|
||||
**/
|
||||
|
||||
module.exports = {
|
||||
// The file containing the flows. If not set, it defaults to flows_<hostname>.json
|
||||
flowFile: 'flows.json',
|
||||
|
||||
// To enabled pretty-printing of the flow within the flow file, set the following
|
||||
// property to true:
|
||||
//flowFilePretty: true,
|
||||
|
||||
// the tcp port that the Node-RED web server is listening on
|
||||
uiPort: process.env.PORT || 1880,
|
||||
|
||||
@@ -46,6 +53,10 @@ module.exports = {
|
||||
// defaults to 10Mb
|
||||
//execMaxBufferSize: 10000000,
|
||||
|
||||
// The working directory to handle relative file paths from within the File nodes
|
||||
// defaults to the working directory of the Node-RED process.
|
||||
//fileWorkingDirectory: "",
|
||||
|
||||
// The maximum length, in characters, of any message sent to the debug sidebar tab
|
||||
debugMaxLength: 1000,
|
||||
|
||||
@@ -61,13 +72,6 @@ module.exports = {
|
||||
// Colourise the console output of the debug node
|
||||
//debugUseColors: true,
|
||||
|
||||
// The file containing the flows. If not set, it defaults to flows_<hostname>.json
|
||||
//flowFile: 'flows.json',
|
||||
|
||||
// To enabled pretty-printing of the flow within the flow file, set the following
|
||||
// property to true:
|
||||
//flowFilePretty: true,
|
||||
|
||||
// By default, credentials are encrypted in storage using a generated key. To
|
||||
// specify your own secret, set the following property.
|
||||
// If you want to disable encryption of credentials, set this property to false.
|
||||
@@ -96,10 +100,6 @@ module.exports = {
|
||||
// disabled.
|
||||
//httpNodeRoot: '/red-nodes',
|
||||
|
||||
// The following property can be used in place of 'httpAdminRoot' and 'httpNodeRoot',
|
||||
// to apply the same root to both parts.
|
||||
//httpRoot: '/red',
|
||||
|
||||
// When httpAdminRoot is used to move the UI to a different root path, the
|
||||
// following property can be used to identify a directory of static content
|
||||
// that should be served at http://localhost:1880/.
|
||||
@@ -110,7 +110,7 @@ module.exports = {
|
||||
//apiMaxLength: '5mb',
|
||||
|
||||
// If you installed the optional node-red-dashboard you can set it's path
|
||||
// relative to httpRoot
|
||||
// relative to httpNodeRoot
|
||||
// Other optional properties include
|
||||
// readOnly:{boolean},
|
||||
// middleware:{function or array}, (req,res,next) - http middleware
|
||||
|
||||
@@ -22,6 +22,7 @@ var sinon = require("sinon");
|
||||
var iconv = require("iconv-lite");
|
||||
var fileNode = require("nr-test-utils").require("@node-red/nodes/core/storage/10-file.js");
|
||||
var helper = require("node-red-node-test-helper");
|
||||
var RED = require("nr-test-utils").require("node-red/lib/red");
|
||||
|
||||
describe('file Nodes', function() {
|
||||
|
||||
@@ -41,8 +42,9 @@ describe('file Nodes', function() {
|
||||
|
||||
describe('file out Node', function() {
|
||||
|
||||
var relativePathToFile = "50-file-test-file.txt";
|
||||
var resourcesDir = path.join(__dirname,"..","..","..","resources");
|
||||
var fileToTest = path.join(resourcesDir,"50-file-test-file.txt");
|
||||
var fileToTest = path.join(resourcesDir,relativePathToFile);
|
||||
var wait = 250;
|
||||
|
||||
beforeEach(function(done) {
|
||||
@@ -51,6 +53,7 @@ describe('file Nodes', function() {
|
||||
});
|
||||
|
||||
afterEach(function(done) {
|
||||
delete RED.settings.fileWorkingDirectory;
|
||||
fs.removeSync(path.join(resourcesDir,"file-out-node"));
|
||||
helper.unload().then(function() {
|
||||
//fs.unlinkSync(fileToTest);
|
||||
@@ -94,6 +97,30 @@ describe('file Nodes', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should write to a file using RED.settings.fileWorkingDirectory', function(done) {
|
||||
var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":relativePathToFile, "appendNewline":false, "overwriteFile":true, wires: [["helperNode1"]]},
|
||||
{id:"helperNode1", type:"helper"}];
|
||||
helper.load(fileNode, flow, function() {
|
||||
RED.settings.fileWorkingDirectory = resourcesDir;
|
||||
var n1 = helper.getNode("fileNode1");
|
||||
var n2 = helper.getNode("helperNode1");
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
var f = fs.readFileSync(fileToTest);
|
||||
f.should.have.length(4);
|
||||
fs.unlinkSync(fileToTest);
|
||||
msg.should.have.property("payload", "test");
|
||||
done();
|
||||
}
|
||||
catch (e) {
|
||||
done(e);
|
||||
}
|
||||
});
|
||||
n1.receive({payload:"test"});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should write multi-byte string to a file', function(done) {
|
||||
var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":false, "overwriteFile":true, wires: [["helperNode1"]]},
|
||||
{id:"helperNode1", type:"helper"}];
|
||||
@@ -1036,9 +1063,10 @@ describe('file Nodes', function() {
|
||||
|
||||
describe('file in Node', function() {
|
||||
|
||||
var relativePathToFile = "50-file-test-file.txt";
|
||||
var resourcesDir = path.join(__dirname,"..","..","..","resources");
|
||||
var fileToTest = path.join(resourcesDir,"50-file-test-file.txt");
|
||||
var fileToTest2 = "\t"+path.join(resourcesDir,"50-file-test-file.txt")+"\r\n";
|
||||
var fileToTest = path.join(resourcesDir,relativePathToFile);
|
||||
var fileToTest2 = "\t"+path.join(resourcesDir,relativePathToFile)+"\r\n";
|
||||
var wait = 150;
|
||||
|
||||
beforeEach(function(done) {
|
||||
@@ -1047,6 +1075,7 @@ describe('file Nodes', function() {
|
||||
});
|
||||
|
||||
afterEach(function(done) {
|
||||
delete RED.settings.fileWorkingDirectory;
|
||||
helper.unload().then(function() {
|
||||
fs.unlinkSync(fileToTest);
|
||||
helper.stopServer(done);
|
||||
@@ -1100,6 +1129,30 @@ describe('file Nodes', function() {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should read in a file using fileWorkingDirectory to set cwd', function(done) {
|
||||
var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", "filename":relativePathToFile, "format":"utf8", wires:[["n2"]]},
|
||||
{id:"n2", type:"helper"}];
|
||||
helper.load(fileNode, flow, function() {
|
||||
RED.settings.fileWorkingDirectory = resourcesDir;
|
||||
var n1 = helper.getNode("fileInNode1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
msg.should.have.property('payload');
|
||||
msg.payload.should.be.a.String();
|
||||
msg.payload.should.have.length(40)
|
||||
msg.payload.should.equal("File message line 1\nFile message line 2\n");
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
n1.receive({payload:""});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should read in a file ending in cr and output a utf8 string', function(done) {
|
||||
var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", "filename":fileToTest2, "format":"utf8", wires:[["n2"]]},
|
||||
{id:"n2", type:"helper"}];
|
||||
|
||||
@@ -14,6 +14,7 @@ const os = require("os");
|
||||
const NR_TEST_UTILS = require("nr-test-utils");
|
||||
const externalModules = NR_TEST_UTILS.require("@node-red/registry/lib/externalModules");
|
||||
const exec = NR_TEST_UTILS.require("@node-red/util/lib/exec");
|
||||
const hooks = NR_TEST_UTILS.require("@node-red/util/lib/hooks");
|
||||
|
||||
let homeDir;
|
||||
|
||||
@@ -40,19 +41,20 @@ describe("externalModules api", function() {
|
||||
await createUserDir()
|
||||
})
|
||||
afterEach(async function() {
|
||||
hooks.clear();
|
||||
await fs.remove(homeDir);
|
||||
})
|
||||
describe("checkFlowDependencies", function() {
|
||||
beforeEach(function() {
|
||||
sinon.stub(exec,"run").callsFake(async function(cmd, args, options) {
|
||||
let error;
|
||||
if (args[1] === "moduleNotFound") {
|
||||
if (args[2] === "moduleNotFound") {
|
||||
error = new Error();
|
||||
error.stderr = "E404";
|
||||
} else if (args[1] === "moduleVersionNotFound") {
|
||||
} else if (args[2] === "moduleVersionNotFound") {
|
||||
error = new Error();
|
||||
error.stderr = "ETARGET";
|
||||
} else if (args[1] === "moduleFail") {
|
||||
} else if (args[2] === "moduleFail") {
|
||||
error = new Error();
|
||||
error.stderr = "Some unexpected install error";
|
||||
}
|
||||
@@ -102,6 +104,45 @@ describe("externalModules api", function() {
|
||||
fs.existsSync(path.join(homeDir,"externalModules")).should.be.true();
|
||||
})
|
||||
|
||||
|
||||
it("calls pre/postInstall hooks", async function() {
|
||||
externalModules.init({userDir: homeDir});
|
||||
externalModules.register("function", "libs");
|
||||
let receivedPreEvent,receivedPostEvent;
|
||||
hooks.add("preInstall", function(event) { event.args = ["a"]; receivedPreEvent = event; })
|
||||
hooks.add("postInstall", function(event) { receivedPostEvent = event; })
|
||||
|
||||
await externalModules.checkFlowDependencies([
|
||||
{type: "function", libs:[{module: "foo"}]}
|
||||
])
|
||||
exec.run.called.should.be.true();
|
||||
// exec.run.lastCall.args[1].should.eql([ 'install', 'a', 'foo' ]);
|
||||
receivedPreEvent.should.have.property("module","foo")
|
||||
receivedPreEvent.should.have.property("version")
|
||||
receivedPreEvent.should.have.property("dir")
|
||||
receivedPreEvent.should.eql(receivedPostEvent)
|
||||
fs.existsSync(path.join(homeDir,"externalModules")).should.be.true();
|
||||
})
|
||||
|
||||
it("skips npm install if preInstall returns false", async function() {
|
||||
externalModules.init({userDir: homeDir});
|
||||
externalModules.register("function", "libs");
|
||||
let receivedPreEvent,receivedPostEvent;
|
||||
hooks.add("preInstall", function(event) { receivedPreEvent = event; return false })
|
||||
hooks.add("postInstall", function(event) { receivedPostEvent = event; })
|
||||
|
||||
await externalModules.checkFlowDependencies([
|
||||
{type: "function", libs:[{module: "foo"}]}
|
||||
])
|
||||
exec.run.called.should.be.false();
|
||||
receivedPreEvent.should.have.property("module","foo")
|
||||
receivedPreEvent.should.have.property("version")
|
||||
receivedPreEvent.should.have.property("dir")
|
||||
receivedPreEvent.should.eql(receivedPostEvent)
|
||||
fs.existsSync(path.join(homeDir,"externalModules")).should.be.true();
|
||||
})
|
||||
|
||||
|
||||
it("installs missing modules from inside subflow module", async function() {
|
||||
externalModules.init({userDir: homeDir});
|
||||
externalModules.register("function", "libs");
|
||||
@@ -299,4 +340,4 @@ describe("externalModules api", function() {
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@ var NR_TEST_UTILS = require("nr-test-utils");
|
||||
var installer = NR_TEST_UTILS.require("@node-red/registry/lib/installer");
|
||||
var registry = NR_TEST_UTILS.require("@node-red/registry/lib/index");
|
||||
var typeRegistry = NR_TEST_UTILS.require("@node-red/registry/lib/registry");
|
||||
const { events, exec, log } = NR_TEST_UTILS.require("@node-red/util");
|
||||
const { events, exec, log, hooks } = NR_TEST_UTILS.require("@node-red/util");
|
||||
|
||||
describe('nodes/registry/installer', function() {
|
||||
|
||||
@@ -68,6 +68,7 @@ describe('nodes/registry/installer', function() {
|
||||
fs.statSync.restore();
|
||||
}
|
||||
exec.run.restore();
|
||||
hooks.clear();
|
||||
});
|
||||
|
||||
describe("installs module", function() {
|
||||
@@ -251,6 +252,70 @@ describe('nodes/registry/installer', function() {
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it("triggers preInstall and postInstall hooks", function(done) {
|
||||
let receivedPreEvent,receivedPostEvent;
|
||||
hooks.add("preInstall", function(event) { event.args = ["a"]; receivedPreEvent = event; })
|
||||
hooks.add("postInstall", function(event) { receivedPostEvent = event; })
|
||||
var nodeInfo = {nodes:{module:"foo",types:["a"]}};
|
||||
var res = {code: 0,stdout:"",stderr:""}
|
||||
var p = Promise.resolve(res);
|
||||
p.catch((err)=>{});
|
||||
execResponse = p;
|
||||
|
||||
var addModule = sinon.stub(registry,"addModule").callsFake(function(md) {
|
||||
return Promise.resolve(nodeInfo);
|
||||
});
|
||||
|
||||
installer.installModule("this_wont_exist","1.2.3").then(function(info) {
|
||||
exec.run.called.should.be.true();
|
||||
exec.run.lastCall.args[1].should.eql([ 'install', 'a', 'this_wont_exist@1.2.3' ]);
|
||||
info.should.eql(nodeInfo);
|
||||
should.exist(receivedPreEvent)
|
||||
receivedPreEvent.should.have.property("module","this_wont_exist")
|
||||
receivedPreEvent.should.have.property("version","1.2.3")
|
||||
receivedPreEvent.should.have.property("dir")
|
||||
receivedPreEvent.should.have.property("url")
|
||||
receivedPreEvent.should.have.property("isExisting")
|
||||
receivedPreEvent.should.have.property("isUpgrade")
|
||||
receivedPreEvent.should.eql(receivedPostEvent)
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it("fails install if preInstall hook fails", function(done) {
|
||||
let receivedEvent;
|
||||
hooks.add("preInstall", function(event) { throw new Error("preInstall-error"); })
|
||||
var nodeInfo = {nodes:{module:"foo",types:["a"]}};
|
||||
|
||||
installer.installModule("this_wont_exist","1.2.3").catch(function(err) {
|
||||
exec.run.called.should.be.false();
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it("skips invoking npm if preInstall returns false", function(done) {
|
||||
let receivedEvent;
|
||||
hooks.add("preInstall", function(event) { return false })
|
||||
hooks.add("postInstall", function(event) { receivedEvent = event; })
|
||||
var nodeInfo = {nodes:{module:"foo",types:["a"]}};
|
||||
|
||||
installer.installModule("this_wont_exist","1.2.3").catch(function(err) {
|
||||
exec.run.called.should.be.false();
|
||||
should.exist(receivedEvent);
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it("rollsback install if postInstall hook fails", function(done) {
|
||||
hooks.add("postInstall", function(event) { throw new Error("fail"); })
|
||||
installer.installModule("this_wont_exist","1.2.3").catch(function(err) {
|
||||
exec.run.calledTwice.should.be.true();
|
||||
exec.run.firstCall.args[1].includes("install").should.be.true();
|
||||
exec.run.secondCall.args[1].includes("remove").should.be.true();
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
});
|
||||
describe("uninstalls module", function() {
|
||||
it("rejects invalid module names", function(done) {
|
||||
|
||||
@@ -26,7 +26,7 @@ var flowUtils = NR_TEST_UTILS.require("@node-red/runtime/lib/flows/util");
|
||||
var Flow = NR_TEST_UTILS.require("@node-red/runtime/lib/flows/Flow");
|
||||
var flows = NR_TEST_UTILS.require("@node-red/runtime/lib/flows");
|
||||
var Node = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/Node");
|
||||
var hooks = NR_TEST_UTILS.require("@node-red/runtime/lib/hooks");
|
||||
var hooks = NR_TEST_UTILS.require("@node-red/util/lib/hooks");
|
||||
var typeRegistry = NR_TEST_UTILS.require("@node-red/registry");
|
||||
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ var sinon = require('sinon');
|
||||
var NR_TEST_UTILS = require("nr-test-utils");
|
||||
var RedNode = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/Node");
|
||||
var Log = NR_TEST_UTILS.require("@node-red/util").log;
|
||||
var hooks = NR_TEST_UTILS.require("@node-red/runtime/lib/hooks");
|
||||
var hooks = NR_TEST_UTILS.require("@node-red/util/lib/hooks");
|
||||
var flows = NR_TEST_UTILS.require("@node-red/runtime/lib/flows");
|
||||
|
||||
describe('Node', function() {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
const should = require("should");
|
||||
const NR_TEST_UTILS = require("nr-test-utils");
|
||||
|
||||
const hooks = NR_TEST_UTILS.require("@node-red/runtime/lib/hooks");
|
||||
const hooks = NR_TEST_UTILS.require("@node-red/util/lib/hooks");
|
||||
|
||||
describe("runtime/hooks", function() {
|
||||
describe("util/hooks", function() {
|
||||
afterEach(function() {
|
||||
hooks.clear();
|
||||
})
|
||||
@@ -81,7 +81,7 @@ describe("runtime/hooks", function() {
|
||||
hooks.has("onSend.A").should.be.false();
|
||||
hooks.has("onSend.B").should.be.false();
|
||||
hooks.has("onSend").should.be.false();
|
||||
|
||||
|
||||
done(err);
|
||||
} catch(err2) {
|
||||
done(err2);
|
||||
@@ -121,7 +121,46 @@ describe("runtime/hooks", function() {
|
||||
})
|
||||
})
|
||||
})
|
||||
it("allows a hook to remove itself whilst being called", function(done) {
|
||||
let data = { order: [] }
|
||||
hooks.add("onSend.A", function(payload) { payload.order.push("A") } )
|
||||
hooks.add("onSend.B", function(payload) {
|
||||
hooks.remove("*.B");
|
||||
})
|
||||
hooks.add("onSend.C", function(payload) { payload.order.push("C") } )
|
||||
hooks.add("onSend.D", function(payload) { payload.order.push("D") } )
|
||||
|
||||
hooks.trigger("onSend", data, err => {
|
||||
try {
|
||||
should.not.exist(err);
|
||||
data.order.should.eql(["A","C","D"])
|
||||
done();
|
||||
} catch(e) {
|
||||
done(e);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
it("allows a hook to remove itself and others whilst being called", function(done) {
|
||||
let data = { order: [] }
|
||||
hooks.add("onSend.A", function(payload) { payload.order.push("A") } )
|
||||
hooks.add("onSend.B", function(payload) {
|
||||
hooks.remove("*.B");
|
||||
hooks.remove("*.C");
|
||||
})
|
||||
hooks.add("onSend.C", function(payload) { payload.order.push("C") } )
|
||||
hooks.add("onSend.D", function(payload) { payload.order.push("D") } )
|
||||
|
||||
hooks.trigger("onSend", data, err => {
|
||||
try {
|
||||
should.not.exist(err);
|
||||
data.order.should.eql(["A","D"])
|
||||
done();
|
||||
} catch(e) {
|
||||
done(e);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
it("halts execution on return false", function(done) {
|
||||
hooks.add("onSend.A", function(payload) { payload.order.push("A"); return false } )
|
||||
@@ -249,4 +288,51 @@ describe("runtime/hooks", function() {
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
it("handler can use callback function - promise API", function(done) {
|
||||
hooks.add("onSend.A", function(payload, done) {
|
||||
setTimeout(function() {
|
||||
payload.order.push("A")
|
||||
done()
|
||||
},30)
|
||||
})
|
||||
hooks.add("onSend.B", function(payload) { payload.order.push("B") } )
|
||||
|
||||
let data = { order:[] };
|
||||
hooks.trigger("onSend",data).then(() => {
|
||||
data.order.should.eql(["A","B"])
|
||||
done()
|
||||
}).catch(done)
|
||||
})
|
||||
|
||||
it("handler can halt execution - promise API", function(done) {
|
||||
hooks.add("onSend.A", function(payload, done) {
|
||||
setTimeout(function() {
|
||||
payload.order.push("A")
|
||||
done(false)
|
||||
},30)
|
||||
})
|
||||
hooks.add("onSend.B", function(payload) { payload.order.push("B") } )
|
||||
|
||||
let data = { order:[] };
|
||||
hooks.trigger("onSend",data).then(() => {
|
||||
data.order.should.eql(["A"])
|
||||
done()
|
||||
}).catch(done)
|
||||
})
|
||||
|
||||
it("handler can halt execution on error - promise API", function(done) {
|
||||
hooks.add("onSend.A", function(payload, done) {
|
||||
throw new Error("error");
|
||||
})
|
||||
hooks.add("onSend.B", function(payload) { payload.order.push("B") } )
|
||||
|
||||
let data = { order:[] };
|
||||
hooks.trigger("onSend",data).then(() => {
|
||||
done("hooks.trigger resolved unexpectedly")
|
||||
}).catch(err => {
|
||||
done();
|
||||
})
|
||||
})
|
||||
});
|
||||
Reference in New Issue
Block a user