@node-red/runtime
Advanced tools
Comparing version 1.1.3 to 1.2.0-beta.1
@@ -37,2 +37,4 @@ /** | ||
var runtime; | ||
var Mutex = require('async-mutex').Mutex; | ||
const mutex = new Mutex(); | ||
@@ -51,7 +53,5 @@ var api = module.exports = { | ||
*/ | ||
getFlows: function(opts) { | ||
return new Promise(function(resolve,reject) { | ||
runtime.log.audit({event: "flows.get"}, opts.req); | ||
return resolve(runtime.nodes.getFlows()); | ||
}); | ||
getFlows: async function(opts) { | ||
runtime.log.audit({event: "flows.get"}, opts.req); | ||
return runtime.flows.getFlows(); | ||
}, | ||
@@ -68,5 +68,4 @@ /** | ||
*/ | ||
setFlows: function(opts) { | ||
return new Promise(function(resolve,reject) { | ||
setFlows: async function(opts) { | ||
return mutex.runExclusive(async function() { | ||
var flows = opts.flows; | ||
@@ -78,6 +77,6 @@ var deploymentType = opts.deploymentType||"full"; | ||
if (deploymentType === 'reload') { | ||
apiPromise = runtime.nodes.loadFlows(true); | ||
apiPromise = runtime.flows.loadFlows(true); | ||
} else { | ||
if (flows.hasOwnProperty('rev')) { | ||
var currentVersion = runtime.nodes.getFlows().rev; | ||
var currentVersion = runtime.flows.getFlows().rev; | ||
if (currentVersion !== flows.rev) { | ||
@@ -89,13 +88,13 @@ var err; | ||
//TODO: log warning | ||
return reject(err); | ||
throw err; | ||
} | ||
} | ||
apiPromise = runtime.nodes.setFlows(flows.flows,flows.credentials,deploymentType); | ||
apiPromise = runtime.flows.setFlows(flows.flows,flows.credentials,deploymentType,null,null,opts.user); | ||
} | ||
apiPromise.then(function(flowId) { | ||
return resolve({rev:flowId}); | ||
return apiPromise.then(function(flowId) { | ||
return {rev:flowId}; | ||
}).catch(function(err) { | ||
runtime.log.warn(runtime.log._("api.flows.error-"+(deploymentType === 'reload'?'reload':'save'),{message:err.message})); | ||
runtime.log.warn(err.stack); | ||
return reject(err); | ||
throw err | ||
}); | ||
@@ -114,16 +113,18 @@ }); | ||
*/ | ||
addFlow: function(opts) { | ||
return new Promise(function(resolve,reject) { | ||
addFlow: async function(opts) { | ||
return mutex.runExclusive(async function() { | ||
var flow = opts.flow; | ||
runtime.nodes.addFlow(flow).then(function(id) { | ||
runtime.log.audit({event: "flow.add",id:id}, opts.req); | ||
return resolve(id); | ||
}).catch(function(err) { | ||
runtime.log.audit({event: "flow.add",error:err.code||"unexpected_error",message:err.toString()}, opts.req); | ||
return runtime.flows.addFlow(flow, opts.user).then(function (id) { | ||
runtime.log.audit({event: "flow.add", id: id}, opts.req); | ||
return id; | ||
}).catch(function (err) { | ||
runtime.log.audit({ | ||
event: "flow.add", | ||
error: err.code || "unexpected_error", | ||
message: err.toString() | ||
}, opts.req); | ||
err.status = 400; | ||
return reject(err); | ||
throw err; | ||
}) | ||
}) | ||
}); | ||
}, | ||
@@ -140,17 +141,14 @@ | ||
*/ | ||
getFlow: function(opts) { | ||
return new Promise(function (resolve,reject) { | ||
var flow = runtime.nodes.getFlow(opts.id); | ||
if (flow) { | ||
runtime.log.audit({event: "flow.get",id:opts.id}, opts.req); | ||
return resolve(flow); | ||
} else { | ||
runtime.log.audit({event: "flow.get",id:opts.id,error:"not_found"}, opts.req); | ||
var err = new Error(); | ||
err.code = "not_found"; | ||
err.status = 404; | ||
return reject(err); | ||
} | ||
}) | ||
getFlow: async function(opts) { | ||
var flow = runtime.flows.getFlow(opts.id); | ||
if (flow) { | ||
runtime.log.audit({event: "flow.get",id:opts.id}, opts.req); | ||
return flow; | ||
} else { | ||
runtime.log.audit({event: "flow.get",id:opts.id,error:"not_found"}, opts.req); | ||
var err = new Error(); | ||
err.code = "not_found"; | ||
err.status = 404; | ||
throw err; | ||
} | ||
}, | ||
@@ -167,30 +165,26 @@ /** | ||
*/ | ||
updateFlow: function(opts) { | ||
return new Promise(function (resolve,reject) { | ||
updateFlow: async function(opts) { | ||
return mutex.runExclusive(async function() { | ||
var flow = opts.flow; | ||
var id = opts.id; | ||
try { | ||
runtime.nodes.updateFlow(id,flow).then(function() { | ||
runtime.log.audit({event: "flow.update",id:id}, opts.req); | ||
return resolve(id); | ||
}).catch(function(err) { | ||
runtime.log.audit({event: "flow.update",error:err.code||"unexpected_error",message:err.toString()}, opts.req); | ||
err.status = 400; | ||
return reject(err); | ||
}) | ||
} catch(err) { | ||
return runtime.flows.updateFlow(id, flow, opts.user).then(function () { | ||
runtime.log.audit({event: "flow.update", id: id}, opts.req); | ||
return id; | ||
}).catch(function (err) { | ||
if (err.code === 404) { | ||
runtime.log.audit({event: "flow.update",id:id,error:"not_found"}, opts.req); | ||
runtime.log.audit({event: "flow.update", id: id, error: "not_found"}, opts.req); | ||
// TODO: this swap around of .code and .status isn't ideal | ||
err.status = 404; | ||
err.code = "not_found"; | ||
return reject(err); | ||
} else { | ||
runtime.log.audit({event: "flow.update",error:err.code||"unexpected_error",message:err.toString()}, opts.req); | ||
runtime.log.audit({ | ||
event: "flow.update", | ||
error: err.code || "unexpected_error", | ||
message: err.toString() | ||
}, opts.req); | ||
err.status = 400; | ||
return reject(err); | ||
} | ||
} | ||
throw err; | ||
}) | ||
}); | ||
}, | ||
@@ -206,27 +200,25 @@ /** | ||
*/ | ||
deleteFlow: function(opts) { | ||
return new Promise(function (resolve,reject) { | ||
deleteFlow: async function(opts) { | ||
return mutex.runExclusive(function() { | ||
var id = opts.id; | ||
try { | ||
runtime.nodes.removeFlow(id).then(function() { | ||
runtime.log.audit({event: "flow.remove",id:id}, opts.req); | ||
return resolve(); | ||
}).catch(function(err) { | ||
runtime.log.audit({event: "flow.remove",id:id,error:err.code||"unexpected_error",message:err.toString()}, opts.req); | ||
err.status = 400; | ||
return reject(err); | ||
}); | ||
} catch(err) { | ||
return runtime.flows.removeFlow(id, opts.user).then(function () { | ||
runtime.log.audit({event: "flow.remove", id: id}, opts.req); | ||
return; | ||
}).catch(function (err) { | ||
if (err.code === 404) { | ||
runtime.log.audit({event: "flow.remove",id:id,error:"not_found"}, opts.req); | ||
runtime.log.audit({event: "flow.remove", id: id, error: "not_found"}, opts.req); | ||
// TODO: this swap around of .code and .status isn't ideal | ||
err.status = 404; | ||
err.code = "not_found"; | ||
return reject(err); | ||
} else { | ||
runtime.log.audit({event: "flow.remove",id:id,error:err.code||"unexpected_error",message:err.toString()}, opts.req); | ||
runtime.log.audit({ | ||
event: "flow.remove", | ||
id: id, | ||
error: err.code || "unexpected_error", | ||
message: err.toString() | ||
}, opts.req); | ||
err.status = 400; | ||
return reject(err); | ||
} | ||
} | ||
throw err; | ||
}); | ||
}); | ||
@@ -245,34 +237,31 @@ }, | ||
*/ | ||
getNodeCredentials: function(opts) { | ||
return new Promise(function(resolve,reject) { | ||
runtime.log.audit({event: "credentials.get",type:opts.type,id:opts.id}, opts.req); | ||
var credentials = runtime.nodes.getCredentials(opts.id); | ||
if (!credentials) { | ||
return resolve({}); | ||
getNodeCredentials: async function(opts) { | ||
runtime.log.audit({event: "credentials.get",type:opts.type,id:opts.id}, opts.req); | ||
var credentials = runtime.nodes.getCredentials(opts.id); | ||
if (!credentials) { | ||
return {}; | ||
} | ||
var sendCredentials = {}; | ||
var cred; | ||
if (/^subflow(:|$)/.test(opts.type)) { | ||
for (cred in credentials) { | ||
if (credentials.hasOwnProperty(cred)) { | ||
sendCredentials['has_'+cred] = credentials[cred] != null && credentials[cred] !== ''; | ||
} | ||
} | ||
var sendCredentials = {}; | ||
var cred; | ||
if (/^subflow(:|$)/.test(opts.type)) { | ||
for (cred in credentials) { | ||
if (credentials.hasOwnProperty(cred)) { | ||
sendCredentials['has_'+cred] = credentials[cred] != null && credentials[cred] !== ''; | ||
} else { | ||
var definition = runtime.nodes.getCredentialDefinition(opts.type) || {}; | ||
for (cred in definition) { | ||
if (definition.hasOwnProperty(cred)) { | ||
if (definition[cred].type == "password") { | ||
var key = 'has_' + cred; | ||
sendCredentials[key] = credentials[cred] != null && credentials[cred] !== ''; | ||
continue; | ||
} | ||
sendCredentials[cred] = credentials[cred] || ''; | ||
} | ||
} else { | ||
var definition = runtime.nodes.getCredentialDefinition(opts.type) || {}; | ||
for (cred in definition) { | ||
if (definition.hasOwnProperty(cred)) { | ||
if (definition[cred].type == "password") { | ||
var key = 'has_' + cred; | ||
sendCredentials[key] = credentials[cred] != null && credentials[cred] !== ''; | ||
continue; | ||
} | ||
sendCredentials[cred] = credentials[cred] || ''; | ||
} | ||
} | ||
} | ||
resolve(sendCredentials); | ||
}) | ||
} | ||
return sendCredentials; | ||
} | ||
} |
@@ -162,2 +162,3 @@ /** | ||
* @param {String} opts.version - (optional) the version of the module to install | ||
* @param {Object} opts.tarball - (optional) a tarball file to install. Object has properties `name`, `size` and `buffer`. | ||
* @param {String} opts.url - (optional) url to install | ||
@@ -177,2 +178,33 @@ * @param {Object} opts.req - the request to log (optional) | ||
} | ||
if (opts.tarball) { | ||
if (runtime.settings.editorTheme && runtime.settings.editorTheme.palette && runtime.settings.editorTheme.palette.upload === false) { | ||
runtime.log.audit({event: "nodes.install",tarball:opts.tarball.file,error:"invalid_request"}, opts.req); | ||
var err = new Error("Invalid request"); | ||
err.code = "invalid_request"; | ||
err.status = 400; | ||
return reject(err); | ||
} | ||
if (opts.module || opts.version || opts.url) { | ||
runtime.log.audit({event: "nodes.install",tarball:opts.tarball.file,module:opts.module,error:"invalid_request"}, opts.req); | ||
var err = new Error("Invalid request"); | ||
err.code = "invalid_request"; | ||
err.status = 400; | ||
return reject(err); | ||
} | ||
runtime.nodes.installModule(opts.tarball.buffer).then(function(info) { | ||
runtime.log.audit({event: "nodes.install",tarball:opts.tarball.file,module:info.id}, opts.req); | ||
return resolve(info); | ||
}).catch(function(err) { | ||
if (err.code) { | ||
err.status = 400; | ||
runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url,error:err.code}, opts.req); | ||
} else { | ||
err.status = 400; | ||
runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url,error:err.code||"unexpected_error",message:err.toString()}, opts.req); | ||
} | ||
return reject(err); | ||
}) | ||
return; | ||
} | ||
if (opts.module) { | ||
@@ -179,0 +211,0 @@ var existingModule = runtime.nodes.getModuleInfo(opts.module); |
@@ -17,6 +17,31 @@ /*! | ||
var events = require("events"); | ||
const events = new (require("events")).EventEmitter(); | ||
module.exports = new events.EventEmitter(); | ||
const deprecatedEvents = { | ||
"nodes-stopped": "flows:stopped", | ||
"nodes-started": "flows:started" | ||
} | ||
function wrapEventFunction(obj,func) { | ||
events["_"+func] = events[func]; | ||
return function(eventName, listener) { | ||
if (deprecatedEvents.hasOwnProperty(eventName)) { | ||
const log = require("@node-red/util").log; | ||
const stack = (new Error().stack).split("\n")[2].split("(")[1].slice(0,-1); | ||
log.warn(`[RED.events] Deprecated use of "${eventName}" event from "${stack}". Use "${deprecatedEvents[eventName]}" instead.`) | ||
} | ||
return events["_"+func].call(events,eventName,listener) | ||
} | ||
} | ||
events.on = wrapEventFunction(events,"on"); | ||
events.once = wrapEventFunction(events,"once"); | ||
events.addListener = events.on; | ||
module.exports = events; | ||
/** | ||
@@ -23,0 +48,0 @@ * Runtime events emitter |
@@ -22,5 +22,7 @@ /*! | ||
var redNodes = require("./nodes"); | ||
var flows = require("./flows"); | ||
var storage = require("./storage"); | ||
var library = require("./library"); | ||
var events = require("./events"); | ||
var hooks = require("./hooks"); | ||
var settings = require("./settings"); | ||
@@ -276,3 +278,5 @@ var exec = require("./exec"); | ||
events: events, | ||
hooks: hooks, | ||
nodes: redNodes, | ||
flows: flows, | ||
library: library, | ||
@@ -360,2 +364,3 @@ exec: exec, | ||
events: events, | ||
hooks: hooks, | ||
util: require("@node-red/util").util, | ||
@@ -362,0 +367,0 @@ get httpNode() { return nodeApp }, |
@@ -21,3 +21,2 @@ /** | ||
var memory = require("./memory"); | ||
var flows; | ||
@@ -52,3 +51,2 @@ var settings; | ||
function init(_settings) { | ||
flows = require("../flows"); | ||
settings = _settings; | ||
@@ -518,35 +516,2 @@ contexts = {}; | ||
// | ||
// function getContext(localId,flowId,parent) { | ||
// var contextId = localId; | ||
// if (flowId) { | ||
// contextId = localId+":"+flowId; | ||
// } | ||
// console.log("getContext",localId,flowId,"known?",contexts.hasOwnProperty(contextId)); | ||
// if (contexts.hasOwnProperty(contextId)) { | ||
// return contexts[contextId]; | ||
// } | ||
// var newContext = createContext(contextId,undefined,parent); | ||
// if (flowId) { | ||
// var node = flows.get(flowId); | ||
// console.log("flows,get",flowId,node&&node.type) | ||
// var parent = undefined; | ||
// if (node && node.type.startsWith("subflow:")) { | ||
// parent = node.context().flow; | ||
// } | ||
// else { | ||
// parent = createRootContext(); | ||
// } | ||
// var flowContext = getContext(flowId,undefined,parent); | ||
// Object.defineProperty(newContext, 'flow', { | ||
// value: flowContext | ||
// }); | ||
// } | ||
// Object.defineProperty(newContext, 'global', { | ||
// value: contexts['global'] | ||
// }) | ||
// contexts[contextId] = newContext; | ||
// return newContext; | ||
// } | ||
function deleteContext(id,flowId) { | ||
@@ -553,0 +518,0 @@ if(!hasConfiguredStore){ |
@@ -26,4 +26,4 @@ /** | ||
var credentials = require("./credentials"); | ||
var flows = require("./flows"); | ||
var flowUtil = require("./flows/util") | ||
var flows = require("../flows"); | ||
var flowUtil = require("../flows/util") | ||
var context = require("./context"); | ||
@@ -155,7 +155,5 @@ var Node = require("./Node"); | ||
function installModule(module,version,url) { | ||
var existingModule = registry.getModuleInfo(module); | ||
var isUpgrade = !!existingModule; | ||
return registry.installModule(module,version,url).then(function(info) { | ||
if (isUpgrade) { | ||
events.emit("runtime-event",{id:"node/upgraded",retain:false,payload:{module:module,version:version}}); | ||
if (info.pending_version) { | ||
events.emit("runtime-event",{id:"node/upgraded",retain:false,payload:{module:info.name,version:info.pending_version}}); | ||
} else { | ||
@@ -162,0 +160,0 @@ events.emit("runtime-event",{id:"node/added",retain:false,payload:info.nodes}); |
@@ -23,4 +23,6 @@ /** | ||
var context = require("./context"); | ||
var flows = require("./flows"); | ||
var flows = require("../flows"); | ||
const hooks = require("../hooks"); | ||
const NOOP_SEND = function() {} | ||
@@ -59,7 +61,3 @@ | ||
Object.defineProperty(this,'_flow', {value: n._flow, enumerable: false, writable: true }) | ||
this._asyncDelivery = n._flow.asyncMessageDelivery; | ||
} | ||
if (this._asyncDelivery === undefined) { | ||
this._asyncDelivery = true; | ||
} | ||
this.updateWires(n.wires); | ||
@@ -129,2 +127,8 @@ } | ||
Node.prototype._complete = function(msg,error) { | ||
this.metric("done",msg); | ||
hooks.trigger("onComplete",{ msg: msg, error: error, node: { id: this.id, node: this }}, err => { | ||
if (err) { | ||
this.error(err); | ||
} | ||
}) | ||
if (error) { | ||
@@ -178,11 +182,3 @@ // For now, delegate this to this.error | ||
if (event === "input") { | ||
// When Pluggable Message Routing arrives, this will be called from | ||
// that and will already be sync/async depending on the router. | ||
if (this._asyncDelivery) { | ||
setImmediate(function() { | ||
node._emitInput.apply(node,args); | ||
}); | ||
} else { | ||
this._emitInput.apply(this,args); | ||
} | ||
this._emitInput.apply(this,args); | ||
} else { | ||
@@ -201,37 +197,49 @@ this._emit.apply(this,arguments); | ||
this.metric("receive", arg); | ||
if (node._inputCallback) { | ||
// Just one callback registered. | ||
try { | ||
node._inputCallback( | ||
arg, | ||
function() { node.send.apply(node,arguments) }, | ||
function(err) { node._complete(arg,err); } | ||
); | ||
} catch(err) { | ||
node.error(err,arg); | ||
} | ||
} else if (node._inputCallbacks) { | ||
// Multiple callbacks registered. Call each one, tracking eventual completion | ||
var c = node._inputCallbacks.length; | ||
for (var i=0;i<c;i++) { | ||
var cb = node._inputCallbacks[i]; | ||
if (cb.length === 2) { | ||
c++; | ||
} | ||
try { | ||
node._inputCallbacks[i]( | ||
arg, | ||
function() { node.send.apply(node,arguments) }, | ||
function(err) { | ||
c--; | ||
if (c === 0) { | ||
node._complete(arg,err); | ||
} | ||
let receiveEvent = { msg:arg, destination: { id: this.id, node: this } } | ||
// onReceive - a node is about to receive a message | ||
hooks.trigger("onReceive",receiveEvent,(err) => { | ||
if (err) { | ||
node.error(err); | ||
return | ||
} else if (err !== false) { | ||
if (node._inputCallback) { | ||
// Just one callback registered. | ||
try { | ||
node._inputCallback( | ||
arg, | ||
function() { node.send.apply(node,arguments) }, | ||
function(err) { node._complete(arg,err); } | ||
); | ||
} catch(err) { | ||
node.error(err,arg); | ||
} | ||
} else if (node._inputCallbacks) { | ||
// Multiple callbacks registered. Call each one, tracking eventual completion | ||
var c = node._inputCallbacks.length; | ||
for (var i=0;i<c;i++) { | ||
var cb = node._inputCallbacks[i]; | ||
if (cb.length === 2) { | ||
c++; | ||
} | ||
); | ||
} catch(err) { | ||
node.error(err,arg); | ||
try { | ||
cb.call( | ||
node, | ||
arg, | ||
function() { node.send.apply(node,arguments) }, | ||
function(err) { | ||
c--; | ||
if (c === 0) { | ||
node._complete(arg,err); | ||
} | ||
} | ||
); | ||
} catch(err) { | ||
node.error(err,arg); | ||
} | ||
} | ||
} | ||
// postReceive - the message has been passed to the node's input handler | ||
hooks.trigger("postReceive",receiveEvent,(err) => {if (err) { node.error(err) }}); | ||
} | ||
} | ||
}); | ||
} | ||
@@ -372,7 +380,15 @@ | ||
this.metric("send",msg); | ||
node = this._flow.getNode(this._wire); | ||
/* istanbul ignore else */ | ||
if (node) { | ||
node.receive(msg); | ||
} | ||
this._flow.send([{ | ||
msg: msg, | ||
source: { | ||
id: this.id, | ||
node: this, | ||
port: 0 | ||
}, | ||
destination: { | ||
id: this._wire, | ||
node: undefined | ||
}, | ||
cloneMessage: false | ||
}]); | ||
return; | ||
@@ -391,3 +407,3 @@ } else { | ||
var sentMessageId = null; | ||
var hasMissingIds = false; | ||
// for each output of node eg. [msgs to output 0, msgs to output 1, ...] | ||
@@ -406,20 +422,27 @@ for (var i = 0; i < numOutputs; i++) { | ||
for (var j = 0; j < wires.length; j++) { | ||
node = this._flow.getNode(wires[j]); // node at end of wire j | ||
if (node) { | ||
// for each msg to send eg. [[m1, m2, ...], ...] | ||
for (k = 0; k < msgs.length; k++) { | ||
var m = msgs[k]; | ||
if (m !== null && m !== undefined) { | ||
/* istanbul ignore else */ | ||
if (!sentMessageId) { | ||
sentMessageId = m._msgid; | ||
} | ||
if (msgSent) { | ||
var clonedmsg = redUtil.cloneMessage(m); | ||
sendEvents.push({n:node,m:clonedmsg}); | ||
} else { | ||
sendEvents.push({n:node,m:m}); | ||
msgSent = true; | ||
} | ||
// for each msg to send eg. [[m1, m2, ...], ...] | ||
for (k = 0; k < msgs.length; k++) { | ||
var m = msgs[k]; | ||
if (m !== null && m !== undefined) { | ||
if (!m._msgid) { | ||
hasMissingIds = true; | ||
} | ||
/* istanbul ignore else */ | ||
if (!sentMessageId) { | ||
sentMessageId = m._msgid; | ||
} | ||
sendEvents.push({ | ||
msg: m, | ||
source: { | ||
id: this.id, | ||
node: this, | ||
port: i | ||
}, | ||
destination: { | ||
id: wires[j], | ||
node: undefined | ||
}, | ||
cloneMessage: msgSent | ||
}); | ||
msgSent = true; | ||
} | ||
@@ -437,10 +460,12 @@ } | ||
for (i=0;i<sendEvents.length;i++) { | ||
var ev = sendEvents[i]; | ||
/* istanbul ignore else */ | ||
if (!ev.m._msgid) { | ||
ev.m._msgid = sentMessageId; | ||
if (hasMissingIds) { | ||
for (i=0;i<sendEvents.length;i++) { | ||
var ev = sendEvents[i]; | ||
/* istanbul ignore else */ | ||
if (!ev.msg._msgid) { | ||
ev.msg._msgid = sentMessageId; | ||
} | ||
} | ||
ev.n.receive(ev.m); | ||
} | ||
this._flow.send(sendEvents); | ||
}; | ||
@@ -452,3 +477,2 @@ | ||
* This will emit the `input` event with the provided message. | ||
* As of 1.0, this will return *before* any 'input' callback handler is invoked. | ||
*/ | ||
@@ -455,0 +479,0 @@ Node.prototype.receive = function(msg) { |
@@ -95,3 +95,3 @@ /** | ||
} catch(err) { | ||
return storage.saveSettings(globalSettings); | ||
return storage.saveSettings(clone(globalSettings)); | ||
} | ||
@@ -108,3 +108,3 @@ }, | ||
delete globalSettings[prop]; | ||
return storage.saveSettings(globalSettings); | ||
return storage.saveSettings(clone(globalSettings)); | ||
} | ||
@@ -188,3 +188,3 @@ return when.resolve(); | ||
globalSettings.users = userSettings; | ||
return storage.saveSettings(globalSettings); | ||
return storage.saveSettings(clone(globalSettings)); | ||
} | ||
@@ -191,0 +191,0 @@ } |
@@ -85,3 +85,3 @@ /** | ||
}, | ||
saveFlows: function(config) { | ||
saveFlows: function(config, user) { | ||
var flows = config.flows; | ||
@@ -98,3 +98,3 @@ var credentials = config.credentials; | ||
return credentialSavePromise.then(function() { | ||
return storageModule.saveFlows(flows).then(function() { | ||
return storageModule.saveFlows(flows, user).then(function() { | ||
return crypto.createHash('md5').update(JSON.stringify(config.flows)).digest("hex"); | ||
@@ -101,0 +101,0 @@ }) |
@@ -18,3 +18,2 @@ /** | ||
var fs = require('fs-extra'); | ||
var when = require('when'); | ||
var fspath = require("path"); | ||
@@ -33,2 +32,7 @@ | ||
function checkForConfigFile(dir) { | ||
return fs.existsSync(fspath.join(dir,".config.json")) || | ||
fs.existsSync(fspath.join(dir,".config.nodes.json")) | ||
} | ||
var localfilesystem = { | ||
@@ -41,25 +45,15 @@ init: function(_settings, runtime) { | ||
if (!settings.userDir) { | ||
try { | ||
fs.statSync(fspath.join(process.env.NODE_RED_HOME,".config.json")); | ||
settings.userDir = process.env.NODE_RED_HOME; | ||
} catch(err) { | ||
try { | ||
// Consider compatibility for older versions | ||
if (process.env.HOMEPATH) { | ||
fs.statSync(fspath.join(process.env.HOMEPATH,".node-red",".config.json")); | ||
settings.userDir = fspath.join(process.env.HOMEPATH,".node-red"); | ||
} | ||
} catch(err) { | ||
} | ||
if (!settings.userDir) { | ||
settings.userDir = fspath.join(process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH || process.env.NODE_RED_HOME,".node-red"); | ||
if (!settings.readOnly) { | ||
promises.push(fs.ensureDir(fspath.join(settings.userDir,"node_modules"))); | ||
} | ||
} | ||
if (checkForConfigFile(process.env.NODE_RED_HOME)) { | ||
settings.userDir = process.env.NODE_RED_HOME | ||
} else if (process.env.HOMEPATH && checkForConfigFile(fspath.join(process.env.HOMEPATH,".node-red"))) { | ||
settings.userDir = fspath.join(process.env.HOMEPATH,".node-red"); | ||
} else { | ||
settings.userDir = fspath.join(process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH || process.env.NODE_RED_HOME,".node-red"); | ||
} | ||
} | ||
if (!settings.readOnly) { | ||
promises.push(fs.ensureDir(fspath.join(settings.userDir,"node_modules"))); | ||
} | ||
sessions.init(settings); | ||
runtimeSettings.init(settings); | ||
promises.push(runtimeSettings.init(settings)); | ||
promises.push(library.init(settings)); | ||
@@ -69,3 +63,3 @@ promises.push(projects.init(settings, runtime)); | ||
var packageFile = fspath.join(settings.userDir,"package.json"); | ||
var packagePromise = when.resolve(); | ||
var packagePromise = Promise.resolve(); | ||
@@ -88,3 +82,3 @@ if (!settings.readOnly) { | ||
} | ||
return when.all(promises).then(packagePromise); | ||
return Promise.all(promises).then(packagePromise); | ||
}, | ||
@@ -91,0 +85,0 @@ |
@@ -21,5 +21,6 @@ /** | ||
var os = require("os"); | ||
const crypto = require("crypto"); | ||
function getListenPath() { | ||
var seed = (0x100000+Math.random()*0x999999).toString(16); | ||
var seed = crypto.randomBytes(8).toString('hex'); | ||
var fn = 'node-red-git-askpass-'+seed+'-sock'; | ||
@@ -26,0 +27,0 @@ var listenPath; |
@@ -193,3 +193,9 @@ /** | ||
function getUserGitSettings(user) { | ||
var userSettings = settings.getUserSettings(user)||{}; | ||
var username; | ||
if (!user) { | ||
username = "_"; | ||
} else { | ||
username = user.username; | ||
} | ||
var userSettings = settings.getUserSettings(username)||{}; | ||
return userSettings.git; | ||
@@ -366,3 +372,2 @@ } | ||
function createProject(user, metadata) { | ||
// var userSettings = getUserGitSettings(user); | ||
if (metadata.files && metadata.migrateFiles) { | ||
@@ -554,3 +559,3 @@ // We expect there to be no active project in this scenario | ||
function saveFlows(flows) { | ||
function saveFlows(flows, user) { | ||
if (settings.readOnly) { | ||
@@ -569,3 +574,4 @@ return when.resolve(); | ||
if (settings.flowFilePretty) { | ||
if (settings.flowFilePretty || (activeProject && settings.flowFilePretty !== false) ) { | ||
// Pretty format if option enabled, or using Projects and not explicitly disabled | ||
flowData = JSON.stringify(flows,null,4); | ||
@@ -575,3 +581,11 @@ } else { | ||
} | ||
return util.writeFile(flowsFullPath, flowData, flowsFileBackup); | ||
return util.writeFile(flowsFullPath, flowData, flowsFileBackup).then(() => { | ||
var gitSettings = getUserGitSettings(user) || {}; | ||
var workflowMode = (gitSettings.workflow||{}).mode || "manual"; | ||
if (activeProject && workflowMode === 'auto') { | ||
return activeProject.stageFile([flowsFullPath, credentialsFile]).then(() => { | ||
return activeProject.commit(user,{message:"Update flow files"}) | ||
}) | ||
} | ||
}); | ||
} | ||
@@ -589,3 +603,4 @@ | ||
var credentialData; | ||
if (settings.flowFilePretty) { | ||
if (settings.flowFilePretty || (activeProject && settings.flowFilePretty !== false) ) { | ||
// Pretty format if option enabled, or using Projects and not explicitly disabled | ||
credentialData = JSON.stringify(credentials,null,4); | ||
@@ -592,0 +607,0 @@ } else { |
@@ -43,3 +43,4 @@ /** | ||
} | ||
function getGitUser(user) { | ||
function getUserGitSettings(user) { | ||
var username; | ||
@@ -51,5 +52,10 @@ if (!user) { | ||
} | ||
var userSettings = settings.getUserSettings(username); | ||
if (userSettings && userSettings.git) { | ||
return userSettings.git.user; | ||
var userSettings = settings.getUserSettings(username)||{}; | ||
return userSettings.git; | ||
} | ||
function getGitUser(user) { | ||
var gitSettings = getUserGitSettings(user); | ||
if (gitSettings) { | ||
return gitSettings.user; | ||
} | ||
@@ -177,3 +183,3 @@ return null; | ||
return when.all(promises).then(function() { | ||
return Promise.all(promises).then(function() { | ||
return gitTools.stageFile(project.path,files); | ||
@@ -349,2 +355,6 @@ }).then(function() { | ||
} | ||
if (data.hasOwnProperty('version')) { | ||
savePackage = true; | ||
this.package.version = data.version; | ||
} | ||
@@ -397,7 +407,11 @@ if (data.hasOwnProperty('git')) { | ||
} | ||
var modifiedFiles = []; | ||
if (saveREADME) { | ||
promises.push(util.writeFile(fspath.join(this.path,this.paths['README.md']), this.description)); | ||
modifiedFiles.push('README.md'); | ||
} | ||
if (savePackage) { | ||
promises.push(fs.readFile(fspath.join(project.path,project.paths['package.json']),"utf8").then(content => { | ||
promises.push(fs.readFile(fspath.join(this.path,this.paths['package.json']),"utf8").then(content => { | ||
var currentPackage = {}; | ||
@@ -411,8 +425,18 @@ try { | ||
})); | ||
modifiedFiles.push('package.json'); | ||
} | ||
return when.settle(promises).then(res => { | ||
return when.settle(promises).then(function(res) { | ||
var gitSettings = getUserGitSettings(user) || {}; | ||
var workflowMode = (gitSettings.workflow||{}).mode || "manual"; | ||
if (workflowMode === 'auto') { | ||
return project.stageFile(modifiedFiles.map(f => project.paths[f])).then(() => { | ||
return project.commit(user,{message:"Update "+modifiedFiles.join(", ")}) | ||
}) | ||
} | ||
}).then(res => { | ||
if (reloadProject) { | ||
return this.load() | ||
} | ||
}).then(() => { return { | ||
}).then(function() { | ||
return { | ||
flowFilesChanged: flowFilesChanged, | ||
@@ -818,2 +842,3 @@ credentialSecretChanged: credentialSecretChanged | ||
summary: this.package.description, | ||
version: this.package.version, | ||
description: this.description, | ||
@@ -920,3 +945,3 @@ dependencies: this.package.dependencies||{}, | ||
return when.all(promises).then(function() { | ||
return Promise.all(promises).then(function() { | ||
return gitTools.stageFile(projectPath,files); | ||
@@ -923,0 +948,0 @@ }).then(function() { |
@@ -17,9 +17,12 @@ /** | ||
var when = require('when'); | ||
var fs = require('fs-extra'); | ||
var fspath = require("path"); | ||
const fs = require('fs-extra'); | ||
const fspath = require("path"); | ||
var log = require("@node-red/util").log; // TODO: separate module | ||
var util = require("./util"); | ||
const log = require("@node-red/util").log; | ||
const util = require("./util"); | ||
const configSections = ['nodes','users','projects']; | ||
const settingsCache = {}; | ||
var globalSettingsFile; | ||
@@ -29,2 +32,81 @@ var globalSettingsBackup; | ||
async function migrateToMultipleConfigFiles() { | ||
const nodesFilename = getSettingsFilename("nodes"); | ||
if (fs.existsSync(nodesFilename)) { | ||
// We have both .config.json and .config.nodes.json | ||
// Use the more recently modified. This handles users going back to pre1.2 | ||
// and up again. | ||
// We can remove this logic in 1.3+ and remove the old .config.json file entirely | ||
// | ||
const fsStatNodes = await fs.stat(nodesFilename); | ||
const fsStatGlobal = await fs.stat(globalSettingsFile); | ||
if (fsStatNodes.mtimeMs > fsStatGlobal.mtimeMs) { | ||
// .config.nodes.json is newer than .config.json - no migration needed | ||
return; | ||
} | ||
} | ||
const data = await util.readFile(globalSettingsFile,globalSettingsBackup,{}); | ||
// In a later release we should remove the old settings file. But don't do | ||
// that *yet* otherwise users won't be able to downgrade easily. | ||
return writeSettings(data) // .then( () => fs.remove(globalSettingsFile) ); | ||
} | ||
/** | ||
* Takes the single settings object and splits it into separate files. This makes | ||
* it easier to backup selected parts of the settings and also helps reduce the blast | ||
* radius if a file is lost. | ||
* | ||
* The settings are written to four files: | ||
* - .config.nodes.json - the node registry | ||
* - .config.users.json - user specific settings (eg editor settings) | ||
* - .config.projects.json - project settings, including the active project | ||
* - .config.runtime.json - everything else - most notable _credentialSecret | ||
*/ | ||
function writeSettings(data) { | ||
const configKeys = Object.keys(data); | ||
const writePromises = []; | ||
configSections.forEach(key => { | ||
const sectionData = data[key] || {}; | ||
delete data[key]; | ||
const sectionFilename = getSettingsFilename(key); | ||
const sectionContent = JSON.stringify(sectionData,null,4); | ||
if (sectionContent !== settingsCache[key]) { | ||
settingsCache[key] = sectionContent; | ||
writePromises.push(util.writeFile(sectionFilename,sectionContent,sectionFilename+".backup")) | ||
} | ||
}) | ||
// Having extracted nodes/users/projects, write whatever is left to the runtime config | ||
const sectionFilename = getSettingsFilename("runtime"); | ||
const sectionContent = JSON.stringify(data,null,4); | ||
if (sectionContent !== settingsCache["runtime"]) { | ||
settingsCache["runtime"] = sectionContent; | ||
writePromises.push(util.writeFile(sectionFilename,sectionContent,sectionFilename+".backup")); | ||
} | ||
return Promise.all(writePromises); | ||
} | ||
async function readSettings() { | ||
// Read the 'runtime' settings file first | ||
const runtimeFilename = getSettingsFilename("runtime"); | ||
const result = await util.readFile(runtimeFilename,runtimeFilename+".backup",{}); | ||
settingsCache["runtime"] = JSON.stringify(result, null ,4); | ||
const readPromises = []; | ||
// Read the other settings files and add them into the runtime settings | ||
configSections.forEach(key => { | ||
const sectionFilename = getSettingsFilename(key); | ||
readPromises.push(util.readFile(sectionFilename,sectionFilename+".backup",{}).then(sectionData => { | ||
settingsCache[key] = JSON.stringify(sectionData, null ,4); | ||
if (Object.keys(sectionData).length > 0) { | ||
result[key] = sectionData; | ||
} | ||
})) | ||
}); | ||
return Promise.all(readPromises).then(() => result); | ||
} | ||
function getSettingsFilename(section) { | ||
return fspath.join(settings.userDir,`.config.${section}.json`); | ||
} | ||
module.exports = { | ||
@@ -35,23 +117,18 @@ init: function(_settings) { | ||
globalSettingsBackup = fspath.join(settings.userDir,".config.json.backup"); | ||
if (fs.existsSync(globalSettingsFile) && !settings.readOnly) { | ||
return migrateToMultipleConfigFiles(); | ||
} else { | ||
return Promise.resolve(); | ||
} | ||
}, | ||
getSettings: function() { | ||
return when.promise(function(resolve,reject) { | ||
fs.readFile(globalSettingsFile,'utf8',function(err,data) { | ||
if (!err) { | ||
try { | ||
return resolve(util.parseJSON(data)); | ||
} catch(err2) { | ||
log.trace("Corrupted config detected - resetting"); | ||
} | ||
} | ||
return resolve({}); | ||
}) | ||
}) | ||
return readSettings() | ||
}, | ||
saveSettings: function(newSettings) { | ||
if (settings.readOnly) { | ||
return when.resolve(); | ||
return Promise.resolve(); | ||
} | ||
return util.writeFile(globalSettingsFile,JSON.stringify(newSettings,null,1),globalSettingsBackup); | ||
return writeSettings(newSettings); | ||
} | ||
} |
@@ -17,8 +17,6 @@ /** | ||
var fs = require('fs-extra'); | ||
var fspath = require('path'); | ||
var when = require('when'); | ||
var nodeFn = require('when/node/function'); | ||
const fs = require('fs-extra'); | ||
const fspath = require('path'); | ||
var log = require("@node-red/util").log; // TODO: separate module | ||
const log = require("@node-red/util").log; | ||
@@ -32,3 +30,3 @@ function parseJSON(data) { | ||
function readFile(path,backupPath,emptyResponse,type) { | ||
return when.promise(function(resolve) { | ||
return new Promise(function(resolve) { | ||
fs.readFile(path,'utf8',function(err,data) { | ||
@@ -80,19 +78,25 @@ if (!err) { | ||
/** | ||
* Write content to a file using UTF8 encoding. | ||
* This forces a fsync before completing to ensure | ||
* the write hits disk. | ||
*/ | ||
writeFile: function(path,content,backupPath) { | ||
if (backupPath) { | ||
if (fs.existsSync(path)) { | ||
fs.renameSync(path,backupPath); | ||
* Write content to a file using UTF8 encoding. | ||
* This forces a fsync before completing to ensure | ||
* the write hits disk. | ||
*/ | ||
writeFile: function(path,content,backupPath) { | ||
var backupPromise; | ||
if (backupPath && fs.existsSync(path)) { | ||
backupPromise = fs.copy(path,backupPath); | ||
} else { | ||
backupPromise = Promise.resolve(); | ||
} | ||
const dirname = fspath.dirname(path); | ||
const tempFile = `${path}.$$$`; | ||
return backupPromise.then(() => { | ||
if (backupPath) { | ||
log.trace(`utils.writeFile - copied ${path} TO ${backupPath}`) | ||
} | ||
} | ||
return when.promise(function(resolve,reject) { | ||
fs.ensureDir(fspath.dirname(path), (err)=>{ | ||
if (err) { | ||
reject(err); | ||
return; | ||
} | ||
var stream = fs.createWriteStream(path); | ||
return fs.ensureDir(dirname) | ||
}).then(() => { | ||
return new Promise(function(resolve,reject) { | ||
var stream = fs.createWriteStream(tempFile); | ||
stream.on('open',function(fd) { | ||
@@ -102,3 +106,3 @@ stream.write(content,'utf8',function() { | ||
if (err) { | ||
log.warn(log._("storage.localfilesystem.fsync-fail",{path: path, message: err.toString()})); | ||
log.warn(log._("storage.localfilesystem.fsync-fail",{path: tempFile, message: err.toString()})); | ||
} | ||
@@ -110,7 +114,20 @@ stream.end(resolve); | ||
stream.on('error',function(err) { | ||
log.warn(log._("storage.localfilesystem.fsync-fail",{path: tempFile, message: err.toString()})); | ||
reject(err); | ||
}); | ||
}); | ||
}).then(() => { | ||
log.trace(`utils.writeFile - written content to ${tempFile}`) | ||
return new Promise(function(resolve,reject) { | ||
fs.rename(tempFile,path,err => { | ||
if (err) { | ||
log.warn(log._("storage.localfilesystem.fsync-fail",{path: path, message: err.toString()})); | ||
return reject(err); | ||
} | ||
log.trace(`utils.writeFile - renamed ${tempFile} to ${path}`) | ||
resolve(); | ||
}) | ||
}); | ||
}); | ||
}, | ||
}, | ||
readFile: readFile, | ||
@@ -117,0 +134,0 @@ |
{ | ||
"name": "@node-red/runtime", | ||
"version": "1.1.3", | ||
"version": "1.2.0-beta.1", | ||
"license": "Apache-2.0", | ||
@@ -19,4 +19,5 @@ "main": "./lib/index.js", | ||
"dependencies": { | ||
"@node-red/registry": "1.1.3", | ||
"@node-red/util": "1.1.3", | ||
"@node-red/registry": "1.2.0-beta.1", | ||
"@node-red/util": "1.2.0-beta.1", | ||
"async-mutex": "0.2.4", | ||
"clone": "2.1.2", | ||
@@ -23,0 +24,0 @@ "express": "4.17.1", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
504753
52
12050
8
2
+ Addedasync-mutex@0.2.4
+ Added@node-red/registry@1.2.0-beta.1(transitive)
+ Added@node-red/util@1.2.0-beta.1(transitive)
+ Addedasync-mutex@0.2.4(transitive)
+ Addedchownr@2.0.0(transitive)
+ Addedfs-minipass@2.1.0(transitive)
+ Addedminipass@3.3.6(transitive)
+ Addedminizlib@2.1.2(transitive)
+ Addedmkdirp@1.0.4(transitive)
+ Addedtar@6.0.5(transitive)
+ Addedtslib@2.7.0(transitive)
+ Addeduglify-js@3.11.0(transitive)
+ Addedyallist@4.0.0(transitive)
- Removed@node-red/registry@1.1.3(transitive)
- Removed@node-red/util@1.1.3(transitive)
- Removeduglify-js@3.10.0(transitive)
Updated@node-red/util@1.2.0-beta.1