mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Rework Function node module integration
This commit is contained in:
parent
4a1d66f210
commit
9c09ee3b71
@ -67,11 +67,6 @@ module.exports = {
|
|||||||
adminApp.get(/^\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.read"),nodes.getSet,apiUtil.errorHandler);
|
adminApp.get(/^\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.read"),nodes.getSet,apiUtil.errorHandler);
|
||||||
adminApp.put(/^\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.write"),nodes.putSet,apiUtil.errorHandler);
|
adminApp.put(/^\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.write"),nodes.putSet,apiUtil.errorHandler);
|
||||||
|
|
||||||
// NPM Modules
|
|
||||||
adminApp.get("/modules", needsPermission("nodes.read"), nodes.listNPMModules, apiUtil.errorHandler);
|
|
||||||
adminApp.delete("/modules/:spec", needsPermission("nodes.write"), nodes.uninstallNPMModule, apiUtil.errorHandler);
|
|
||||||
adminApp.post("/modules", needsPermission("nodes.write"), nodes.updateNPMModule, apiUtil.errorHandler);
|
|
||||||
|
|
||||||
// Context
|
// Context
|
||||||
adminApp.get("/context/:scope(global)",needsPermission("context.read"),context.get,apiUtil.errorHandler);
|
adminApp.get("/context/:scope(global)",needsPermission("context.read"),context.get,apiUtil.errorHandler);
|
||||||
adminApp.get("/context/:scope(global)/*",needsPermission("context.read"),context.get,apiUtil.errorHandler);
|
adminApp.get("/context/:scope(global)/*",needsPermission("context.read"),context.get,apiUtil.errorHandler);
|
||||||
|
@ -191,45 +191,5 @@ module.exports = {
|
|||||||
runtimeAPI.nodes.getIconList(opts).then(function(list) {
|
runtimeAPI.nodes.getIconList(opts).then(function(list) {
|
||||||
res.json(list);
|
res.json(list);
|
||||||
});
|
});
|
||||||
},
|
|
||||||
|
|
||||||
listNPMModules: function(req, res) {
|
|
||||||
var opts = {
|
|
||||||
user: req.user,
|
|
||||||
req: apiUtils.getRequestLogObject(req)
|
|
||||||
}
|
|
||||||
runtimeAPI.nodes.listNPMModules(opts).then(function(list) {
|
|
||||||
res.json(list);
|
|
||||||
}).catch(err => {
|
|
||||||
apiUtils.rejectHandler(req,res,err);
|
|
||||||
});;
|
|
||||||
},
|
|
||||||
|
|
||||||
uninstallNPMModule: function(req, res) {
|
|
||||||
var opts = {
|
|
||||||
user: req.user,
|
|
||||||
spec: req.params.spec,
|
|
||||||
req: apiUtils.getRequestLogObject(req)
|
|
||||||
}
|
|
||||||
runtimeAPI.nodes.uninstallNPMModule(opts).then(function(result) {
|
|
||||||
res.json(result);
|
|
||||||
}).catch(err => {
|
|
||||||
apiUtils.rejectHandler(req,res,err);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
updateNPMModule: function(req, res) {
|
|
||||||
var body = req.body;
|
|
||||||
var opts = {
|
|
||||||
user: req.user,
|
|
||||||
spec: body.spec,
|
|
||||||
update: body.update,
|
|
||||||
req: apiUtils.getRequestLogObject(req)
|
|
||||||
}
|
|
||||||
runtimeAPI.nodes.updateNPMModule(opts).then(function(result) {
|
|
||||||
res.json(result);
|
|
||||||
}).catch(err => {
|
|
||||||
apiUtils.rejectHandler(req,res,err);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -142,6 +142,7 @@
|
|||||||
"nodeActionDisabled": "node actions disabled",
|
"nodeActionDisabled": "node actions disabled",
|
||||||
"nodeActionDisabledSubflow": "node actions disabled within subflow",
|
"nodeActionDisabledSubflow": "node actions disabled within subflow",
|
||||||
"missing-types": "<p>Flows stopped due to missing node types.</p>",
|
"missing-types": "<p>Flows stopped due to missing node types.</p>",
|
||||||
|
"missing-modules": "<p>Flows stopped due to missing modules.</p>",
|
||||||
"safe-mode":"<p>Flows stopped in safe mode.</p><p>You can modify your flows and deploy the changes to restart.</p>",
|
"safe-mode":"<p>Flows stopped in safe mode.</p><p>You can modify your flows and deploy the changes to restart.</p>",
|
||||||
"restartRequired": "Node-RED must be restarted to enable upgraded modules",
|
"restartRequired": "Node-RED must be restarted to enable upgraded modules",
|
||||||
"credentials_load_failed": "<p>Flows stopped as the credentials could not be decrypted.</p><p>The flow credential file is encrypted, but the project's encryption key is missing or invalid.</p>",
|
"credentials_load_failed": "<p>Flows stopped as the credentials could not be decrypted.</p><p>The flow credential file is encrypted, but the project's encryption key is missing or invalid.</p>",
|
||||||
|
@ -248,6 +248,7 @@ var RED = (function() {
|
|||||||
id: notificationId
|
id: notificationId
|
||||||
}
|
}
|
||||||
if (notificationId === "runtime-state") {
|
if (notificationId === "runtime-state") {
|
||||||
|
RED.events.emit("runtime-state",msg);
|
||||||
if (msg.error === "safe-mode") {
|
if (msg.error === "safe-mode") {
|
||||||
options.buttons = [
|
options.buttons = [
|
||||||
{
|
{
|
||||||
@ -280,6 +281,16 @@ var RED = (function() {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
} else if (msg.error === "missing-modules") {
|
||||||
|
text+="<ul><li>"+msg.modules.map(function(m) { return RED.utils.sanitize(m.module)+(m.error?(" - <small>"+RED.utils.sanitize(""+m.error)+"</small>"):"")}).join("</li><li>")+"</li></ul>";
|
||||||
|
options.buttons = [
|
||||||
|
{
|
||||||
|
text: RED._("common.label.close"),
|
||||||
|
click: function() {
|
||||||
|
persistentNotifications[notificationId].hideNotification();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
} else if (msg.error === "credentials_load_failed") {
|
} else if (msg.error === "credentials_load_failed") {
|
||||||
if (RED.settings.theme("projects.enabled",false)) {
|
if (RED.settings.theme("projects.enabled",false)) {
|
||||||
// projects enabled
|
// projects enabled
|
||||||
@ -370,6 +381,9 @@ var RED = (function() {
|
|||||||
} else if (persistentNotifications.hasOwnProperty(notificationId)) {
|
} else if (persistentNotifications.hasOwnProperty(notificationId)) {
|
||||||
persistentNotifications[notificationId].close();
|
persistentNotifications[notificationId].close();
|
||||||
delete persistentNotifications[notificationId];
|
delete persistentNotifications[notificationId];
|
||||||
|
if (notificationId === 'runtime-state') {
|
||||||
|
RED.events.emit("runtime-state",msg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
RED.comms.subscribe("status/#",function(topic,msg) {
|
RED.comms.subscribe("status/#",function(topic,msg) {
|
||||||
|
@ -509,8 +509,8 @@ RED.tabs = (function() {
|
|||||||
li.attr('id',"red-ui-tab-"+(tab.id.replace(".","-")));
|
li.attr('id',"red-ui-tab-"+(tab.id.replace(".","-")));
|
||||||
li.data("tabId",tab.id);
|
li.data("tabId",tab.id);
|
||||||
|
|
||||||
if (options.maximumTabWidth) {
|
if (options.maximumTabWidth || tab.maximumTabWidth) {
|
||||||
li.css("maxWidth",options.maximumTabWidth+"px");
|
li.css("maxWidth",(options.maximumTabWidth || tab.maximumTabWidth) +"px");
|
||||||
}
|
}
|
||||||
var link = $("<a/>",{href:"#"+tab.id, class:"red-ui-tab-label"}).appendTo(li);
|
var link = $("<a/>",{href:"#"+tab.id, class:"red-ui-tab-label"}).appendTo(li);
|
||||||
if (tab.icon) {
|
if (tab.icon) {
|
||||||
|
@ -331,7 +331,7 @@ RED.palette.editor = (function() {
|
|||||||
if (v.modules) {
|
if (v.modules) {
|
||||||
var a = false;
|
var a = false;
|
||||||
v.modules = v.modules.filter(function(m) {
|
v.modules = v.modules.filter(function(m) {
|
||||||
if (checkModuleAllowed(m.id,m.version,installAllowList,installDenyList)) {
|
if (RED.utils.checkModuleAllowed(m.id,m.version,installAllowList,installDenyList)) {
|
||||||
loadedIndex[m.id] = m;
|
loadedIndex[m.id] = m;
|
||||||
m.index = [m.id];
|
m.index = [m.id];
|
||||||
if (m.keywords) {
|
if (m.keywords) {
|
||||||
@ -445,68 +445,6 @@ RED.palette.editor = (function() {
|
|||||||
var installAllowList = ['*'];
|
var installAllowList = ['*'];
|
||||||
var installDenyList = [];
|
var installDenyList = [];
|
||||||
|
|
||||||
function parseModuleList(list) {
|
|
||||||
list = list || ["*"];
|
|
||||||
return list.map(function(rule) {
|
|
||||||
var m = /^(.+?)(?:@(.*))?$/.exec(rule);
|
|
||||||
var wildcardPos = m[1].indexOf("*");
|
|
||||||
wildcardPos = wildcardPos===-1?Infinity:wildcardPos;
|
|
||||||
|
|
||||||
return {
|
|
||||||
module: new RegExp("^"+m[1].replace(/\*/g,".*")+"$"),
|
|
||||||
version: m[2],
|
|
||||||
wildcardPos: wildcardPos
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkAgainstList(module,version,list) {
|
|
||||||
for (var i=0;i<list.length;i++) {
|
|
||||||
var rule = list[i];
|
|
||||||
if (rule.module.test(module)) {
|
|
||||||
// Without a full semver library in the editor,
|
|
||||||
// we skip the version check.
|
|
||||||
// Not ideal - but will get caught in the runtime
|
|
||||||
// if the user tries to install.
|
|
||||||
return rule;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkModuleAllowed(module,version,allowList,denyList) {
|
|
||||||
if (!allowList && !denyList) {
|
|
||||||
// Default to allow
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (allowList.length === 0 && denyList.length === 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var allowedRule = checkAgainstList(module,version,allowList);
|
|
||||||
var deniedRule = checkAgainstList(module,version,denyList);
|
|
||||||
// console.log("A",allowedRule)
|
|
||||||
// console.log("D",deniedRule)
|
|
||||||
|
|
||||||
if (allowedRule && !deniedRule) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!allowedRule && deniedRule) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!allowedRule && !deniedRule) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (allowedRule.wildcardPos !== deniedRule.wildcardPos) {
|
|
||||||
return allowedRule.wildcardPos > deniedRule.wildcardPos
|
|
||||||
} else {
|
|
||||||
// First wildcard in same position.
|
|
||||||
// Go with the longer matching rule. This isn't going to be 100%
|
|
||||||
// right, but we are deep into edge cases at this point.
|
|
||||||
return allowedRule.module.toString().length > deniedRule.module.toString().length
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
if (RED.settings.get('externalModules.palette.allowInstall', true) === false) {
|
if (RED.settings.get('externalModules.palette.allowInstall', true) === false) {
|
||||||
return;
|
return;
|
||||||
@ -517,8 +455,8 @@ RED.palette.editor = (function() {
|
|||||||
installAllowList = settingsAllowList;
|
installAllowList = settingsAllowList;
|
||||||
installDenyList = settingsDenyList
|
installDenyList = settingsDenyList
|
||||||
}
|
}
|
||||||
installAllowList = parseModuleList(installAllowList);
|
installAllowList = RED.utils.parseModuleList(installAllowList);
|
||||||
installDenyList = parseModuleList(installDenyList);
|
installDenyList = RED.utils.parseModuleList(installDenyList);
|
||||||
|
|
||||||
createSettingsPane();
|
createSettingsPane();
|
||||||
|
|
||||||
|
@ -1122,6 +1122,67 @@ RED.utils = (function() {
|
|||||||
return '#'+'000000'.slice(0, 6-s.length)+s;
|
return '#'+'000000'.slice(0, 6-s.length)+s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseModuleList(list) {
|
||||||
|
list = list || ["*"];
|
||||||
|
return list.map(function(rule) {
|
||||||
|
var m = /^(.+?)(?:@(.*))?$/.exec(rule);
|
||||||
|
var wildcardPos = m[1].indexOf("*");
|
||||||
|
wildcardPos = wildcardPos===-1?Infinity:wildcardPos;
|
||||||
|
|
||||||
|
return {
|
||||||
|
module: new RegExp("^"+m[1].replace(/\*/g,".*")+"$"),
|
||||||
|
version: m[2],
|
||||||
|
wildcardPos: wildcardPos
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkAgainstList(module,version,list) {
|
||||||
|
for (var i=0;i<list.length;i++) {
|
||||||
|
var rule = list[i];
|
||||||
|
if (rule.module.test(module)) {
|
||||||
|
// Without a full semver library in the editor,
|
||||||
|
// we skip the version check.
|
||||||
|
// Not ideal - but will get caught in the runtime
|
||||||
|
// if the user tries to install.
|
||||||
|
return rule;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkModuleAllowed(module,version,allowList,denyList) {
|
||||||
|
if (!allowList && !denyList) {
|
||||||
|
// Default to allow
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (allowList.length === 0 && denyList.length === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var allowedRule = checkAgainstList(module,version,allowList);
|
||||||
|
var deniedRule = checkAgainstList(module,version,denyList);
|
||||||
|
// console.log("A",allowedRule)
|
||||||
|
// console.log("D",deniedRule)
|
||||||
|
|
||||||
|
if (allowedRule && !deniedRule) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!allowedRule && deniedRule) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!allowedRule && !deniedRule) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (allowedRule.wildcardPos !== deniedRule.wildcardPos) {
|
||||||
|
return allowedRule.wildcardPos > deniedRule.wildcardPos
|
||||||
|
} else {
|
||||||
|
// First wildcard in same position.
|
||||||
|
// Go with the longer matching rule. This isn't going to be 100%
|
||||||
|
// right, but we are deep into edge cases at this point.
|
||||||
|
return allowedRule.module.toString().length > deniedRule.module.toString().length
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
createObjectElement: buildMessageElement,
|
createObjectElement: buildMessageElement,
|
||||||
getMessageProperty: getMessageProperty,
|
getMessageProperty: getMessageProperty,
|
||||||
@ -1141,6 +1202,8 @@ RED.utils = (function() {
|
|||||||
sanitize: sanitize,
|
sanitize: sanitize,
|
||||||
renderMarkdown: renderMarkdown,
|
renderMarkdown: renderMarkdown,
|
||||||
createNodeIcon: createNodeIcon,
|
createNodeIcon: createNodeIcon,
|
||||||
getDarkerColor: getDarkerColor
|
getDarkerColor: getDarkerColor,
|
||||||
|
parseModuleList: parseModuleList,
|
||||||
|
checkModuleAllowed: checkModuleAllowed
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
@ -143,6 +143,13 @@
|
|||||||
background-size: contain
|
background-size: contain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.red-ui-font-code {
|
||||||
|
font-family: $monospace-font;
|
||||||
|
font-size: $primary-font-size;
|
||||||
|
color: $info-text-code-color;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
font-family: $monospace-font;
|
font-family: $monospace-font;
|
||||||
font-size: $primary-font-size;
|
font-size: $primary-font-size;
|
||||||
|
@ -1,20 +1,67 @@
|
|||||||
<script type="text/html" data-template-name="function">
|
<script type="text/html" data-template-name="function">
|
||||||
<div class="form-row">
|
<style>
|
||||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
#node-input-libs-container-row .red-ui-editableList-container {
|
||||||
<div style="display: inline-block; width: calc(100% - 105px)"><input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name"></div>
|
padding: 0px;
|
||||||
</div>
|
}
|
||||||
|
#node-input-libs-container-row .red-ui-editableList-container li {
|
||||||
|
padding:5px;
|
||||||
|
}
|
||||||
|
#node-input-libs-container-row .red-ui-editableList-item-remove {
|
||||||
|
right: 5px;
|
||||||
|
}
|
||||||
|
.node-libs-entry {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.node-libs-entry .node-input-libs-var, .node-libs-entry .red-ui-typedInput-container {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
.node-libs-entry > code,.node-libs-entry > span {
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
.node-libs-entry > input[type=text] {
|
||||||
|
border-radius: 0;
|
||||||
|
border-left: none;
|
||||||
|
border-top: none;
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
<div class="form-row">
|
.node-libs-entry > span > i {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.node-libs-entry > span.input-error > i {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<input type="hidden" id="node-input-func">
|
||||||
|
<input type="hidden" id="node-input-noerr">
|
||||||
|
<input type="hidden" id="node-input-finalize">
|
||||||
|
<input type="hidden" id="node-input-initialize">
|
||||||
|
|
||||||
|
<div class="form-row" style="margin-top: -20px">
|
||||||
<ul style="min-width: 600px; margin-bottom: 20px;" id="func-tabs"></ul>
|
<ul style="min-width: 600px; margin-bottom: 20px;" id="func-tabs"></ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="func-tabs-content" style="min-height: calc(100% - 95px);">
|
<div id="func-tabs-content" style="min-height: calc(100% - 95px);">
|
||||||
|
|
||||||
<div id="func-tab-init" style="display:none">
|
<div id="func-tab-config" style="display:none">
|
||||||
<div class="form-row" style="margin-bottom: 0px;">
|
<div class="form-row">
|
||||||
<input type="hidden" id="node-input-initialize" autofocus="autofocus">
|
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||||
|
<div style="display: inline-block; width: calc(100% - 105px)"><input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name"></div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="node-input-outputs"><i class="fa fa-random"></i> <span data-i18n="function.label.outputs"></span></label>
|
||||||
|
<input id="node-input-outputs" style="width: 60px;" value="1">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row node-input-libs-row hide" style="margin-bottom: 0px;">
|
||||||
|
<label><i class="fa fa-cubes"></i> <span>Modules</span></label>
|
||||||
|
</div>
|
||||||
|
<div class="form-row node-input-libs-row hide" id="node-input-libs-container-row">
|
||||||
|
<ol id="node-input-libs-container"></ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="func-tab-init" style="display:none">
|
||||||
<div class="form-row node-text-editor-row" style="position:relative">
|
<div class="form-row node-text-editor-row" style="position:relative">
|
||||||
<div style="position: absolute; right:0; bottom: calc(100% + 3px);"><button id="node-init-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
|
<div style="position: absolute; right:0; bottom: calc(100% + 3px);"><button id="node-init-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
|
||||||
<div style="height: 250px; min-height:150px; margin-top: 30px;" class="node-text-editor" id="node-input-init-editor" ></div>
|
<div style="height: 250px; min-height:150px; margin-top: 30px;" class="node-text-editor" id="node-input-init-editor" ></div>
|
||||||
@ -22,46 +69,77 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="func-tab-body" style="display:none">
|
<div id="func-tab-body" style="display:none">
|
||||||
<div class="form-row" style="margin-bottom: 0px;">
|
|
||||||
<input type="hidden" id="node-input-func" autofocus="autofocus">
|
|
||||||
<input type="hidden" id="node-input-noerr">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-row node-text-editor-row" style="position:relative">
|
<div class="form-row node-text-editor-row" style="position:relative">
|
||||||
<div style="position: absolute; right:0; bottom: calc(100% + 3px);"><button id="node-function-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
|
<div style="position: absolute; right:0; bottom: calc(100% + 3px);"><button id="node-function-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
|
||||||
<div style="height: 220px; min-height:120px; margin-top: 30px;" class="node-text-editor" id="node-input-func-editor" ></div>
|
<div style="height: 220px; min-height:120px; margin-top: 30px;" class="node-text-editor" id="node-input-func-editor" ></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-row" style="margin-bottom: 0px">
|
|
||||||
<label for="node-input-outputs"><i class="fa fa-random"></i> <span data-i18n="function.label.outputs"></span></label>
|
|
||||||
<input id="node-input-outputs" style="width: 60px;" value="1">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="func-tab-finalize" style="display:none">
|
<div id="func-tab-finalize" style="display:none">
|
||||||
<div class="form-row" style="margin-bottom: 0px;">
|
|
||||||
<input type="hidden" id="node-input-finalize" autofocus="autofocus">
|
|
||||||
</div>
|
|
||||||
<div class="form-row node-text-editor-row" style="position:relative">
|
<div class="form-row node-text-editor-row" style="position:relative">
|
||||||
<div style="position: absolute; right:0; bottom: calc(100% + 3px);"><button id="node-finalize-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
|
<div style="position: absolute; right:0; bottom: calc(100% + 3px);"><button id="node-finalize-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
|
||||||
<div style="height: 250px; min-height:150px; margin-top: 30px;" class="node-text-editor" id="node-input-finalize-editor" ></div>
|
<div style="height: 250px; min-height:150px; margin-top: 30px;" class="node-text-editor" id="node-input-finalize-editor" ></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="func-tab-config" style="display:none">
|
|
||||||
<div class="form-row" style="margin-bottom: 0px;">
|
|
||||||
<label><i class="fa fa-wrench"></i> <span>Use Library</span></label>
|
|
||||||
</div>
|
|
||||||
<div class="form-row" style="height: 250px; min-height: 150px">
|
|
||||||
<ol id="node-input-libs-container"></ol>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
var knownFunctionNodes = {};
|
||||||
|
RED.events.on("nodes:add", function(n) {
|
||||||
|
if (n.type === "function") {
|
||||||
|
knownFunctionNodes[n.id] = n;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
RED.events.on("nodes:remove", function(n) {
|
||||||
|
if (n.type === "function") {
|
||||||
|
delete knownFunctionNodes[n.id];
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
var missingModules = [];
|
||||||
|
var missingModuleReasons = {};
|
||||||
|
RED.events.on("runtime-state", function(event) {
|
||||||
|
if (event.error === "missing-modules") {
|
||||||
|
missingModules = event.modules.map(function(m) { missingModuleReasons[m.module] = m.error; return m.module });
|
||||||
|
for (var id in knownFunctionNodes) {
|
||||||
|
if (knownFunctionNodes.hasOwnProperty(id) && knownFunctionNodes[id].libs && knownFunctionNodes[id].libs.length > 0) {
|
||||||
|
RED.editor.validateNode(knownFunctionNodes[id])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!event.text) {
|
||||||
|
missingModuleReasons = {};
|
||||||
|
missingModules = [];
|
||||||
|
for (var id in knownFunctionNodes) {
|
||||||
|
if (knownFunctionNodes.hasOwnProperty(id) && knownFunctionNodes[id].libs && knownFunctionNodes[id].libs.length > 0) {
|
||||||
|
RED.editor.validateNode(knownFunctionNodes[id])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RED.view.redraw();
|
||||||
|
});
|
||||||
|
|
||||||
|
var installAllowList = ['*'];
|
||||||
|
var installDenyList = [];
|
||||||
|
|
||||||
|
var modulesEnabled = true;
|
||||||
|
if (RED.settings.get('externalModules.modules.allowInstall', true) === false) {
|
||||||
|
modulesEnabled = false;
|
||||||
|
}
|
||||||
|
var settingsAllowList = RED.settings.get("externalModules.modules.allowList")
|
||||||
|
var settingsDenyList = RED.settings.get("externalModules.modules.denyList")
|
||||||
|
if (settingsAllowList || settingsDenyList) {
|
||||||
|
installAllowList = settingsAllowList;
|
||||||
|
installDenyList = settingsDenyList
|
||||||
|
}
|
||||||
|
installAllowList = RED.utils.parseModuleList(installAllowList);
|
||||||
|
installDenyList = RED.utils.parseModuleList(installDenyList);
|
||||||
|
|
||||||
|
|
||||||
// object that maps from library name to its descriptor
|
// object that maps from library name to its descriptor
|
||||||
var allLibs = [];
|
var allLibs = [];
|
||||||
|
|
||||||
@ -73,523 +151,123 @@
|
|||||||
return [module, undefined];
|
return [module, undefined];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function getAllUsedModules() {
|
||||||
* Add library descriptor
|
var moduleSet = new Set();
|
||||||
* @param {string} name - library name
|
for (var id in knownFunctionNodes) {
|
||||||
*/
|
if (knownFunctionNodes.hasOwnProperty(id)) {
|
||||||
function addLib(info) {
|
if (knownFunctionNodes[id].libs) {
|
||||||
allLibs.push(info);
|
for (var i=0, l=knownFunctionNodes[id].libs.length; i<l; i++) {
|
||||||
}
|
if (RED.utils.checkModuleAllowed(knownFunctionNodes[id].libs[i].module,null,installAllowList,installDenyList)) {
|
||||||
|
moduleSet.add(knownFunctionNodes[id].libs[i].module);
|
||||||
/**
|
|
||||||
* Validate library spec including conflicts with other specs
|
|
||||||
* @param {string} name - library name
|
|
||||||
*/
|
|
||||||
function checkLib(spec) {
|
|
||||||
var [name, ver] = moduleName(spec);
|
|
||||||
var libs = $("#node-input-libs-container").editableList("items");
|
|
||||||
var count = 0;
|
|
||||||
libs.each(function(i) {
|
|
||||||
if (count > 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var item = $(this);
|
|
||||||
var n = item.find(".node-input-libs-val").val();
|
|
||||||
if (n && (n !== "")) {
|
|
||||||
var [name1, ver1] = moduleName(n);
|
|
||||||
if (name1 === name) {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return (count <= 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get library list
|
|
||||||
*/
|
|
||||||
function getModules() {
|
|
||||||
var root = RED.settings.apiRootUrl || "";
|
|
||||||
var promise = $.ajax({
|
|
||||||
url: root + "/modules",
|
|
||||||
type: "GET",
|
|
||||||
cache: false
|
|
||||||
});
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check and update in progress state of modules
|
|
||||||
*/
|
|
||||||
function checkInProgress(allLibs) {
|
|
||||||
var waitTime = 1000;
|
|
||||||
|
|
||||||
function update() {
|
|
||||||
map = {};
|
|
||||||
allLibs.forEach(function (lib) {
|
|
||||||
if (lib.inProgress) {
|
|
||||||
map[lib.name] = lib;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
getModules().then(function (data,textStatus,xhr) {
|
|
||||||
var count = 0;
|
|
||||||
data.forEach(lib => {
|
|
||||||
var name = lib.name;
|
|
||||||
if (lib.inProgress) {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
else if (name in map) {
|
|
||||||
var lib2 = map[name];
|
|
||||||
if (lib2) {
|
|
||||||
delete map[name];
|
|
||||||
var cb = lib2.onReady;
|
|
||||||
if (cb) {
|
|
||||||
lib2.version = lib.version;
|
|
||||||
lib2.status["installed"] = true;
|
|
||||||
lib2.inProgress = false;
|
|
||||||
cb();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
if (count > 0) {
|
|
||||||
setTimeout(update, waitTime);
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
setTimeout(update, waitTime);
|
var modules = Array.from(moduleSet);
|
||||||
}
|
modules.sort();
|
||||||
|
return modules;
|
||||||
|
|
||||||
/**
|
|
||||||
* Update library list
|
|
||||||
*/
|
|
||||||
function updateLib(node, done) {
|
|
||||||
var names = {};
|
|
||||||
var specs = {};
|
|
||||||
var libs = node.libs || [];
|
|
||||||
allLibs = [];
|
|
||||||
libs.forEach(lib => {
|
|
||||||
var vname = lib.vname;
|
|
||||||
var spec = lib.spec;
|
|
||||||
var [name, ver] = moduleName(spec);
|
|
||||||
var item = {
|
|
||||||
vname: vname,
|
|
||||||
name: name,
|
|
||||||
spec: spec,
|
|
||||||
status: { "edit":true }
|
|
||||||
};
|
|
||||||
addLib(item);
|
|
||||||
specs[spec] = true;
|
|
||||||
names[name] = true;
|
|
||||||
});
|
|
||||||
RED.nodes.eachNode(function(n) {
|
|
||||||
if ((n.type === "function") && (n !== node)) {
|
|
||||||
var libs = n.libs || [];
|
|
||||||
libs.forEach(lib => {
|
|
||||||
var spec = lib.spec;
|
|
||||||
var [name, ver] = moduleName(spec);
|
|
||||||
if (name in names) {
|
|
||||||
var items = allLibs.filter(item => (item.name === name));
|
|
||||||
items.forEach(item => {
|
|
||||||
item.status["used"] = true;
|
|
||||||
});
|
|
||||||
if (spec in specs) {
|
|
||||||
// olready found
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// spec conflict
|
|
||||||
}
|
|
||||||
var item = {
|
|
||||||
name: name,
|
|
||||||
spec: spec,
|
|
||||||
status: { "used": true }
|
|
||||||
};
|
|
||||||
addLib(item);
|
|
||||||
names[name] = true;
|
|
||||||
specs[spec] = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
var inProgressCount = 0;
|
|
||||||
getModules().then(function(data,textStatus,xhr) {
|
|
||||||
data.forEach(lib => {
|
|
||||||
var name = lib.name;
|
|
||||||
var version = lib.version;
|
|
||||||
var preinstalled = (lib.status == "preinstalled");
|
|
||||||
var spec = lib.spec;
|
|
||||||
var items = allLibs.filter(item => (item.name === name));
|
|
||||||
if (items.length > 0) {
|
|
||||||
items.forEach(item => {
|
|
||||||
item.version = version;
|
|
||||||
item.status["installed"] = true;
|
|
||||||
item.status["preinstalled"] = preinstalled;
|
|
||||||
item.inProgress = lib.inProgress;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var item = {
|
|
||||||
name: name,
|
|
||||||
spec: spec,
|
|
||||||
version: version,
|
|
||||||
status: {
|
|
||||||
"installed": true,
|
|
||||||
"preinstalled": preinstalled
|
|
||||||
},
|
|
||||||
inProgress: lib.inProgress
|
|
||||||
};
|
|
||||||
addLib(item);
|
|
||||||
names[name] = true;
|
|
||||||
specs[spec] = true;
|
|
||||||
}
|
|
||||||
if (lib.inProgress) {
|
|
||||||
inProgressCount++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$("#node-input-libs-container").editableList("empty");
|
|
||||||
allLibs.forEach(function(lib) {
|
|
||||||
$("#node-input-libs-container").editableList("addItem", lib);
|
|
||||||
});
|
|
||||||
if (inProgressCount > 0) {
|
|
||||||
checkInProgress(allLibs);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (done) {
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uninstall NPM module
|
|
||||||
*/
|
|
||||||
function uninstallModule(node, spec, name, version,
|
|
||||||
moduleInfo, installButton,
|
|
||||||
installInfoRow, installedInfo,
|
|
||||||
removeButton,
|
|
||||||
progressRow,
|
|
||||||
done) {
|
|
||||||
var root = RED.settings.apiRootUrl || "";
|
|
||||||
progressRow.show();
|
|
||||||
$.ajax({
|
|
||||||
url: root + "/modules/" +spec,
|
|
||||||
type: "DELETE",
|
|
||||||
}).then(function(data,textStatus,xhr) {
|
|
||||||
moduleInfo.attr("disabled", false);
|
|
||||||
installButton.attr("disabled", false);
|
|
||||||
installInfoRow.hide();
|
|
||||||
installedInfo.val("");
|
|
||||||
removeButton.show();
|
|
||||||
progressRow.hide();
|
|
||||||
|
|
||||||
RED.notify("Successfully uninstalled:" +name);
|
|
||||||
if(done) {
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
}).fail(function(xhr,textStatus,err) {
|
|
||||||
progressRow.hide();
|
|
||||||
RED.notify("Failed to uninstall: " +name);
|
|
||||||
if (done) {
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updaate or Install NPM module
|
|
||||||
*/
|
|
||||||
function updateModule(node, spec, update,
|
|
||||||
moduleInfo, installButton,
|
|
||||||
installInfoRow, installedInfo, removeButton,
|
|
||||||
progressRow,
|
|
||||||
done) {
|
|
||||||
var [name, version] = moduleName(spec);
|
|
||||||
var root = RED.settings.apiRootUrl || "";
|
|
||||||
var errorMessage = "failed to "+(update ? "update": "install") +": " +name;
|
|
||||||
progressRow.show();
|
|
||||||
$.ajax({
|
|
||||||
url: root + "/modules",
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
cache: false,
|
|
||||||
data: JSON.stringify({
|
|
||||||
spec: spec,
|
|
||||||
update: update
|
|
||||||
})
|
|
||||||
}).then(function(data) {
|
|
||||||
getModules().then(function(data,textStatus,xhr) {
|
|
||||||
var item = data.find(lib => (lib.name === name));
|
|
||||||
progressRow.hide();
|
|
||||||
if (item) {
|
|
||||||
moduleInfo.attr("disabled", true);
|
|
||||||
installButton.attr("disabled", true);
|
|
||||||
installInfoRow.show();
|
|
||||||
installedInfo.val(item.name+"@"+item.version);
|
|
||||||
removeButton.hide();
|
|
||||||
|
|
||||||
var msg = "Successfully "
|
|
||||||
+(update ? "updated": "installed")
|
|
||||||
+": " +name;
|
|
||||||
|
|
||||||
RED.notify(msg);
|
|
||||||
}
|
|
||||||
if (done) {
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
}).fail(function(xhr,textStatus,err) {
|
|
||||||
progressRow.hide();
|
|
||||||
RED.notify(errorMessage);
|
|
||||||
if (done) {
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}).fail(function(xhr,textStatus,err) {
|
|
||||||
progressRow.hide();
|
|
||||||
RED.notify(errorMessage);
|
|
||||||
if (done) {
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
function withNotification(msg, text, cb) {
|
|
||||||
var notification = RED.notify(msg, {
|
|
||||||
modal: true,
|
|
||||||
fixed: true,
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
text: RED._("common.label.cancel"),
|
|
||||||
click: function() {
|
|
||||||
notification.close();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: text,
|
|
||||||
class: "primary red-ui-palette-module-install-confirm-button-update",
|
|
||||||
click: function() {
|
|
||||||
notification.close();
|
|
||||||
cb();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareLibraryConfig(node) {
|
function prepareLibraryConfig(node) {
|
||||||
$("#node-input-libs-container").css('min-height','250px').css('min-width','450px').editableList({
|
$(".node-input-libs-row").show();
|
||||||
|
var usedModules = getAllUsedModules();
|
||||||
|
var typedModules = usedModules.map(function(l) {
|
||||||
|
return {icon:"fa fa-cube", value:l,label:l,hasValue:false}
|
||||||
|
})
|
||||||
|
typedModules.push({
|
||||||
|
value:"_custom_", label:RED._("editor:subflow.licenseOther"), icon:"red/images/typedInput/az.svg"
|
||||||
|
})
|
||||||
|
|
||||||
|
var libList = $("#node-input-libs-container").css('min-height','100px').css('min-width','450px').editableList({
|
||||||
addItem: function(container,i,opt) {
|
addItem: function(container,i,opt) {
|
||||||
var parent = container.parent();
|
var parent = container.parent();
|
||||||
var removeButton = parent.find(".red-ui-editableList-item-remove");
|
var row0 = $("<div/>").addClass("node-libs-entry").appendTo(container);
|
||||||
var disabled = (opt && !!opt.unused);
|
|
||||||
var row0 = $("<div/>").appendTo(container);
|
|
||||||
var fvar = $("<input/>", {
|
|
||||||
class: "node-input-libs-var",
|
|
||||||
placeholder: RED._("node-red:function.require.var"),
|
|
||||||
type: "text",
|
|
||||||
disabled: disabled
|
|
||||||
}).css({
|
|
||||||
width: "90px",
|
|
||||||
"margin-left": "5px"
|
|
||||||
}).appendTo(row0);
|
|
||||||
var fieldWidth = "260px";
|
var fieldWidth = "260px";
|
||||||
|
$('<code>const </code>').appendTo(row0);
|
||||||
|
var fvar = $("<input/>", {
|
||||||
|
class: "node-input-libs-var red-ui-font-code",
|
||||||
|
placeholder: RED._("node-red:function.require.var"),
|
||||||
|
type: "text"
|
||||||
|
}).css({
|
||||||
|
width: "120px",
|
||||||
|
"margin-left": "5px"
|
||||||
|
}).appendTo(row0).val(opt.var);
|
||||||
|
$('<code> = require(</code>').appendTo(row0);
|
||||||
var fmodule = $("<input/>", {
|
var fmodule = $("<input/>", {
|
||||||
class: "node-input-libs-val",
|
class: "node-input-libs-val",
|
||||||
placeholder: RED._("node-red:function.require.module"),
|
placeholder: RED._("node-red:function.require.module"),
|
||||||
type: "text",
|
type: "text"
|
||||||
disabled: disabled
|
|
||||||
}).css({
|
}).css({
|
||||||
width: fieldWidth,
|
width: "180px",
|
||||||
"margin-left": "5px"
|
}).appendTo(row0).typedInput({
|
||||||
}).appendTo(row0);
|
types: typedModules,
|
||||||
|
default: usedModules.indexOf(opt.module) > -1 ? opt.module : "_custom_"
|
||||||
|
});
|
||||||
|
if (usedModules.indexOf(opt.module) === -1) {
|
||||||
|
fmodule.typedInput('value', opt.module);
|
||||||
|
}
|
||||||
|
|
||||||
var buttonWidth = "70px";
|
$('<code>)</code>').appendTo(row0);
|
||||||
var install = $("<button/>", {
|
|
||||||
class: "red-ui-button red-ui-button-small"
|
|
||||||
}).css({
|
|
||||||
"margin-left": "10px",
|
|
||||||
width: buttonWidth
|
|
||||||
}).appendTo(row0);;
|
|
||||||
$("<i/>", {
|
|
||||||
class: "fa fa-cube"
|
|
||||||
}).appendTo(install);
|
|
||||||
$("<span/>").css({
|
|
||||||
"margin-left": "5px"
|
|
||||||
}).text("Install").appendTo(install);
|
|
||||||
|
|
||||||
var warn = $("<span/>").css({
|
var warning = $('<span style="display:inline-block; width: 16px;"><i class="fa fa-warning"></i></span>').appendTo(row0);
|
||||||
"margin-left": "10px"
|
RED.popover.tooltip(warning.find("i"),function() {
|
||||||
}).appendTo(row0);
|
var val = fmodule.typedInput("type");
|
||||||
$("<i/>", {
|
if (val === "_custom_") {
|
||||||
class: "fa fa-warning"
|
val = fmodule.val();
|
||||||
}).appendTo(warn);
|
}
|
||||||
RED.popover.tooltip(warn, "module spec conflict");
|
if (!RED.utils.checkModuleAllowed(val,null,installAllowList,installDenyList)) {
|
||||||
warn.hide();
|
return "Module not allowed"
|
||||||
|
} else {
|
||||||
var row1 = $("<div/>").css({
|
return "Module not installed: "+missingModuleReasons[val]
|
||||||
"margin-top": "5px"
|
}
|
||||||
}).appendTo(container);
|
})
|
||||||
|
|
||||||
var linstalled = $("<label/>").css({
|
|
||||||
width: "90px",
|
|
||||||
"text-align": "right",
|
|
||||||
"margin-right": "10px"
|
|
||||||
}).appendTo(row1);
|
|
||||||
$("<i/>", {
|
|
||||||
class: "fa fa-cube"
|
|
||||||
}).appendTo(linstalled);
|
|
||||||
var status = "Installed";
|
|
||||||
$("<span/>").css({
|
|
||||||
"margin-left": "5px"
|
|
||||||
}).text(status).appendTo(linstalled);
|
|
||||||
|
|
||||||
var finstalled = $("<input/>", {
|
|
||||||
type: "text",
|
|
||||||
disabled: true
|
|
||||||
}).css({
|
|
||||||
width: fieldWidth
|
|
||||||
}).appendTo(row1);
|
|
||||||
|
|
||||||
var uninstall = $("<button/>", {
|
|
||||||
class: "red-ui-button red-ui-button-small"
|
|
||||||
}).css({
|
|
||||||
"margin-left": "10px",
|
|
||||||
width: buttonWidth
|
|
||||||
}).appendTo(row1);
|
|
||||||
$("<i/>", {
|
|
||||||
class: "fa fa-trash"
|
|
||||||
}).appendTo(uninstall);
|
|
||||||
$("<span/>").css({
|
|
||||||
"margin-left": "5px"
|
|
||||||
}).text("Uninstall").appendTo(uninstall);
|
|
||||||
|
|
||||||
var update = $("<button/>", {
|
|
||||||
class: "red-ui-button red-ui-button-small"
|
|
||||||
}).css({
|
|
||||||
"margin-left": "10px",
|
|
||||||
width: buttonWidth
|
|
||||||
}).appendTo(row1);
|
|
||||||
$("<i/>", {
|
|
||||||
class: "fa fa-refresh"
|
|
||||||
}).appendTo(update);
|
|
||||||
$("<span/>").css({
|
|
||||||
"margin-left": "5px"
|
|
||||||
}).text("Update").appendTo(update);
|
|
||||||
|
|
||||||
var row2 = $("<div/>", {
|
|
||||||
class: "red-ui-palette-module-shade"
|
|
||||||
}).appendTo(container);
|
|
||||||
$("<img/>", {
|
|
||||||
src: "red/images/spin.svg",
|
|
||||||
class: "red-ui-palette-spinner"
|
|
||||||
}).appendTo(row2);
|
|
||||||
row2.hide();
|
|
||||||
|
|
||||||
fvar.on("change", function (e) {
|
fvar.on("change", function (e) {
|
||||||
opt.vname = $(this).val();
|
var v = $(this).val().trim();
|
||||||
|
if (v === "" || / /.test(v)) {
|
||||||
|
fvar.addClass("input-error");
|
||||||
|
} else {
|
||||||
|
fvar.removeClass("input-error");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
fmodule.on("change", function (e) {
|
fmodule.on("change", function (e) {
|
||||||
var val = $(this).val();
|
var val = $(this).typedInput("type");
|
||||||
if (!checkLib(val, opt.id)) {
|
if (val === "_custom_") {
|
||||||
$(this).addClass("input-error");
|
val = $(this).val();
|
||||||
warn.show();
|
|
||||||
}
|
}
|
||||||
else {
|
var varName = val.trim().replace(/^@/,"").replace(/@.*$/,"").replace(/[-_/]./g, function(v) { return v[1].toUpperCase() });
|
||||||
$(this).removeClass("input-error");
|
fvar.val(varName);
|
||||||
warn.hide();
|
fvar.trigger("change");
|
||||||
if (val && (val !== "") &&
|
|
||||||
(!opt.version || (opt.version === ""))) {
|
|
||||||
install.attr("disabled", false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
opt.spec = val;
|
|
||||||
});
|
|
||||||
|
|
||||||
install.on("click", function (e) {
|
if (RED.utils.checkModuleAllowed(val,null,installAllowList,installDenyList) && (missingModules.indexOf(val) === -1)) {
|
||||||
withNotification(
|
fmodule.removeClass("input-error");
|
||||||
"Install NPM Module " +opt.spec,
|
warning.removeClass("input-error");
|
||||||
"Install",
|
} else {
|
||||||
function (cb) {
|
fmodule.addClass("input-error");
|
||||||
updateModule(
|
warning.addClass("input-error");
|
||||||
node, opt.spec, false,
|
|
||||||
fmodule, install,
|
|
||||||
row1, finstalled, removeButton,
|
|
||||||
row2,
|
|
||||||
cb);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
uninstall.on("click", function (e) {
|
|
||||||
withNotification(
|
|
||||||
"Uninstall NPM Module "+opt.spec,
|
|
||||||
"Uninstall",
|
|
||||||
function (cb) {
|
|
||||||
uninstallModule(
|
|
||||||
node, opt.spec, opt.name, opt.version,
|
|
||||||
fmodule, install,
|
|
||||||
row1, finstalled,
|
|
||||||
removeButton,
|
|
||||||
row2,
|
|
||||||
cb);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
update.on("click", function (e) {
|
|
||||||
withNotification(
|
|
||||||
"Update NPM Module " +opt.spec,
|
|
||||||
"Update",
|
|
||||||
function (cb) {
|
|
||||||
updateModule(
|
|
||||||
node, opt.spec, true,
|
|
||||||
fmodule, install,
|
|
||||||
row1, finstalled, removeButton,
|
|
||||||
row2,
|
|
||||||
cb);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
install.attr("disabled", true);
|
|
||||||
if (opt) {
|
|
||||||
function updateData(data) {
|
|
||||||
if (data.vname && (data.vname !== "")) {
|
|
||||||
fvar.val(data.vname);
|
|
||||||
}
|
|
||||||
if (data.spec && (data.spec !== "")) {
|
|
||||||
fmodule.val(data.spec);
|
|
||||||
}
|
|
||||||
if (data.version && (data.version !== "")) {
|
|
||||||
fmodule.prop("disabled", true);
|
|
||||||
row1.show();
|
|
||||||
removeButton.hide();
|
|
||||||
finstalled.val(data.name+"@"+data.version)
|
|
||||||
if (data.status["preinstalled"]) {
|
|
||||||
uninstall.attr("disabled", true);
|
|
||||||
update.attr("disabled", true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
install.attr("disabled", false);
|
|
||||||
row1.hide();
|
|
||||||
removeButton.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateData(opt);
|
|
||||||
if (opt.inProgress) {
|
|
||||||
row2.show();
|
|
||||||
opt.onReady = function () {
|
|
||||||
row2.hide();
|
|
||||||
updateData(opt);
|
|
||||||
opt.onReady = null;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
if (RED.utils.checkModuleAllowed(opt.module,null,installAllowList,installDenyList) && (missingModules.indexOf(opt.module) === -1)) {
|
||||||
|
fmodule.removeClass("input-error");
|
||||||
|
warning.removeClass("input-error");
|
||||||
|
} else {
|
||||||
|
fmodule.addClass("input-error");
|
||||||
|
warning.addClass("input-error");
|
||||||
|
}
|
||||||
|
if (opt.var) {
|
||||||
|
fvar.trigger("change");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
removable: true,
|
removable: true
|
||||||
sortable: true
|
|
||||||
});
|
|
||||||
updateLib(node, function () {
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var libs = node.libs || [];
|
||||||
|
for (var i=0,l=libs.length;i<l; i++) {
|
||||||
|
libList.editableList('addItem',libs[i])
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RED.nodes.registerType('function',{
|
RED.nodes.registerType('function',{
|
||||||
@ -602,7 +280,22 @@
|
|||||||
noerr: {value:0,required:true,validate:function(v) { return !v; }},
|
noerr: {value:0,required:true,validate:function(v) { return !v; }},
|
||||||
initialize: {value:""},
|
initialize: {value:""},
|
||||||
finalize: {value:""},
|
finalize: {value:""},
|
||||||
libs: {value: []}
|
libs: {value: [], validate: function(v) {
|
||||||
|
if (!v) { return true; }
|
||||||
|
for (var i=0,l=v.length;i<l;i++) {
|
||||||
|
var m = v[i];
|
||||||
|
if (!RED.utils.checkModuleAllowed(m.module,null,installAllowList,installDenyList)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (m.var === "" || / /.test(m.var)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (missingModules.indexOf(m.module) > -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}}
|
||||||
},
|
},
|
||||||
inputs:1,
|
inputs:1,
|
||||||
outputs:1,
|
outputs:1,
|
||||||
@ -623,21 +316,24 @@
|
|||||||
$("#" + tab.id).show();
|
$("#" + tab.id).show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
tabs.addTab({
|
||||||
|
id: "func-tab-config",
|
||||||
|
iconClass: "fa fa-cog",
|
||||||
|
maximumTabWidth: 38
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
tabs.addTab({
|
tabs.addTab({
|
||||||
id: "func-tab-init",
|
id: "func-tab-init",
|
||||||
label: that._("function.label.initialize")
|
label: "On Start", //that._("function.label.initialize")
|
||||||
});
|
});
|
||||||
tabs.addTab({
|
tabs.addTab({
|
||||||
id: "func-tab-body",
|
id: "func-tab-body",
|
||||||
label: that._("function.label.function")
|
label: "On Message"//that._("function.label.function")
|
||||||
});
|
});
|
||||||
tabs.addTab({
|
tabs.addTab({
|
||||||
id: "func-tab-finalize",
|
id: "func-tab-finalize",
|
||||||
label: that._("function.label.finalize")
|
label: "On Stop"//that._("function.label.finalize")
|
||||||
});
|
|
||||||
tabs.addTab({
|
|
||||||
id: "func-tab-config",
|
|
||||||
label: "Config"
|
|
||||||
});
|
});
|
||||||
|
|
||||||
tabs.activateTab("func-tab-body");
|
tabs.activateTab("func-tab-body");
|
||||||
@ -748,7 +444,9 @@
|
|||||||
RED.popover.tooltip($("#node-function-expand-js"), RED._("node-red:common.label.expand"));
|
RED.popover.tooltip($("#node-function-expand-js"), RED._("node-red:common.label.expand"));
|
||||||
RED.popover.tooltip($("#node-finalize-expand-js"), RED._("node-red:common.label.expand"));
|
RED.popover.tooltip($("#node-finalize-expand-js"), RED._("node-red:common.label.expand"));
|
||||||
|
|
||||||
prepareLibraryConfig(that);
|
if (RED.settings.functionExternalModules !== false) {
|
||||||
|
prepareLibraryConfig(that);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
oneditsave: function() {
|
oneditsave: function() {
|
||||||
var node = this;
|
var node = this;
|
||||||
@ -780,24 +478,28 @@
|
|||||||
|
|
||||||
$("#node-input-noerr").val(noerr);
|
$("#node-input-noerr").val(noerr);
|
||||||
this.noerr = noerr;
|
this.noerr = noerr;
|
||||||
|
if (RED.settings.functionExternalModules !== false) {
|
||||||
var libs = $("#node-input-libs-container").editableList("items");
|
var libs = $("#node-input-libs-container").editableList("items");
|
||||||
var oldLibs = node.libs || [];
|
node.libs = [];
|
||||||
var newLibs = [];
|
libs.each(function(i) {
|
||||||
node.libs = newLibs;
|
var item = $(this);
|
||||||
libs.each(function(i) {
|
var v = item.find(".node-input-libs-var").val();
|
||||||
var item = $(this);
|
var n = item.find(".node-input-libs-val").typedInput("type");
|
||||||
var v = item.find(".node-input-libs-var").val();
|
if (n === "_custom_") {
|
||||||
var n = item.find(".node-input-libs-val").val();
|
n = item.find(".node-input-libs-val").val();
|
||||||
if ((!v || (v === "")) ||
|
}
|
||||||
(!n || (n === ""))) {
|
if ((!v || (v === "")) ||
|
||||||
return;
|
(!n || (n === ""))) {
|
||||||
}
|
return;
|
||||||
newLibs.push({
|
}
|
||||||
vname: v,
|
node.libs.push({
|
||||||
spec: n
|
var: v,
|
||||||
|
module: n
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
} else {
|
||||||
|
node.libs = [];
|
||||||
|
}
|
||||||
},
|
},
|
||||||
oneditcancel: function() {
|
oneditcancel: function() {
|
||||||
var node = this;
|
var node = this;
|
||||||
@ -823,15 +525,16 @@
|
|||||||
this.editor.resize();
|
this.editor.resize();
|
||||||
|
|
||||||
var height = size.height;
|
var height = size.height;
|
||||||
$("#node-input-init-editor").css("height", (height -105)+"px");
|
$("#node-input-init-editor").css("height", (height -45)+"px");
|
||||||
$("#node-input-func-editor").css("height", (height -145)+"px");
|
$("#node-input-func-editor").css("height", (height -45)+"px");
|
||||||
$("#node-input-finalize-editor").css("height", (height -105)+"px");
|
$("#node-input-finalize-editor").css("height", (height -45)+"px");
|
||||||
|
|
||||||
this.initEditor.resize();
|
this.initEditor.resize();
|
||||||
this.editor.resize();
|
this.editor.resize();
|
||||||
this.finalizeEditor.resize();
|
this.finalizeEditor.resize();
|
||||||
|
|
||||||
$("#node-input-libs-container").css("height", (height -155)+"px");
|
$("#node-input-libs-container").css("height", (height - 185)+"px");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
@ -91,13 +91,17 @@ module.exports = function(RED) {
|
|||||||
function FunctionNode(n) {
|
function FunctionNode(n) {
|
||||||
var libs = n.libs || [];
|
var libs = n.libs || [];
|
||||||
n.modules = libs.map(x => x.spec).filter(x => (x && (x !== "")));
|
n.modules = libs.map(x => x.spec).filter(x => (x && (x !== "")));
|
||||||
var loadPromise = RED.nodes.createNode(this,n);
|
RED.nodes.createNode(this,n);
|
||||||
var node = this;
|
var node = this;
|
||||||
node.name = n.name;
|
node.name = n.name;
|
||||||
node.func = n.func;
|
node.func = n.func;
|
||||||
node.ini = n.initialize ? n.initialize.trim() : "";
|
node.ini = n.initialize ? n.initialize.trim() : "";
|
||||||
node.fin = n.finalize ? n.finalize.trim() : "";
|
node.fin = n.finalize ? n.finalize.trim() : "";
|
||||||
node.libs = libs;
|
node.libs = libs || [];
|
||||||
|
|
||||||
|
if (RED.settings.functionExternalModules === false && node.libs.length > 0) {
|
||||||
|
throw new Error("Function node not allowed to load external modules");
|
||||||
|
}
|
||||||
|
|
||||||
var handleNodeDoneCall = true;
|
var handleNodeDoneCall = true;
|
||||||
|
|
||||||
@ -287,173 +291,173 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// wait for module installation
|
if (node.hasOwnProperty("libs")) {
|
||||||
loadPromise.catch(()=>{
|
var modules = node.libs;
|
||||||
}).finally(function () {
|
modules.forEach(module => {
|
||||||
if (node.hasOwnProperty("libs")) {
|
var vname = module.hasOwnProperty("var") ? module.var : null;
|
||||||
var modules = node.libs;
|
if (vname && (vname !== "")) {
|
||||||
modules.forEach(module => {
|
sandbox[vname] = null;
|
||||||
var vname = module.hasOwnProperty("vname") ? module.vname : null;
|
try {
|
||||||
if (vname && (vname !== "")) {
|
var spec = module.spec;
|
||||||
sandbox[vname] = null;
|
if (spec && (spec !== "")) {
|
||||||
try {
|
var lib = RED.require(module.spec);
|
||||||
var spec = module.spec;
|
sandbox[vname] = lib;
|
||||||
if (spec && (spec !== "")) {
|
}
|
||||||
var lib = RED.require(module.spec);
|
}
|
||||||
sandbox[vname] = lib;
|
catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
node.warn("failed to load library: "+ module.spec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
var context = vm.createContext(sandbox);
|
||||||
|
try {
|
||||||
|
var iniScript = null;
|
||||||
|
var iniOpt = null;
|
||||||
|
if (node.ini && (node.ini !== "")) {
|
||||||
|
var iniText = `
|
||||||
|
(async function(__send__) {
|
||||||
|
var node = {
|
||||||
|
id:__node__.id,
|
||||||
|
name:__node__.name,
|
||||||
|
log:__node__.log,
|
||||||
|
error:__node__.error,
|
||||||
|
warn:__node__.warn,
|
||||||
|
debug:__node__.debug,
|
||||||
|
trace:__node__.trace,
|
||||||
|
status:__node__.status,
|
||||||
|
send: function(msgs, cloneMsg) {
|
||||||
|
__node__.send(__send__, RED.util.generateId(), msgs, cloneMsg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
`+ node.ini +`
|
||||||
|
})(__initSend__);`;
|
||||||
|
iniOpt = createVMOpt(node, " setup");
|
||||||
|
iniScript = new vm.Script(iniText, iniOpt);
|
||||||
|
}
|
||||||
|
node.script = vm.createScript(functionText, createVMOpt(node, ""));
|
||||||
|
if (node.fin && (node.fin !== "")) {
|
||||||
|
var finText = "(function () {\n"+node.fin +"\n})();";
|
||||||
|
finOpt = createVMOpt(node, " cleanup");
|
||||||
|
finScript = new vm.Script(finText, finOpt);
|
||||||
|
}
|
||||||
|
var promise = Promise.resolve();
|
||||||
|
if (iniScript) {
|
||||||
|
context.__initSend__ = function(msgs) { node.send(msgs); };
|
||||||
|
promise = iniScript.runInContext(context, iniOpt);
|
||||||
|
}
|
||||||
|
|
||||||
|
processMessage = function (msg, send, done) {
|
||||||
|
var start = process.hrtime();
|
||||||
|
context.msg = msg;
|
||||||
|
context.__send__ = send;
|
||||||
|
context.__done__ = done;
|
||||||
|
|
||||||
|
node.script.runInContext(context);
|
||||||
|
context.results.then(function(results) {
|
||||||
|
sendResults(node,send,msg._msgid,results,false);
|
||||||
|
if (handleNodeDoneCall) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
|
||||||
|
var duration = process.hrtime(start);
|
||||||
|
var converted = Math.floor((duration[0] * 1e9 + duration[1])/10000)/100;
|
||||||
|
node.metric("duration", msg, converted);
|
||||||
|
if (process.env.NODE_RED_FUNCTION_TIME) {
|
||||||
|
node.status({fill:"yellow",shape:"dot",text:""+converted});
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
if ((typeof err === "object") && err.hasOwnProperty("stack")) {
|
||||||
|
//remove unwanted part
|
||||||
|
var index = err.stack.search(/\n\s*at ContextifyScript.Script.runInContext/);
|
||||||
|
err.stack = err.stack.slice(0, index).split('\n').slice(0,-1).join('\n');
|
||||||
|
var stack = err.stack.split(/\r?\n/);
|
||||||
|
|
||||||
|
//store the error in msg to be used in flows
|
||||||
|
msg.error = err;
|
||||||
|
|
||||||
|
var line = 0;
|
||||||
|
var errorMessage;
|
||||||
|
if (stack.length > 0) {
|
||||||
|
while (line < stack.length && stack[line].indexOf("ReferenceError") !== 0) {
|
||||||
|
line++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line < stack.length) {
|
||||||
|
errorMessage = stack[line];
|
||||||
|
var m = /:(\d+):(\d+)$/.exec(stack[line+1]);
|
||||||
|
if (m) {
|
||||||
|
var lineno = Number(m[1])-1;
|
||||||
|
var cha = m[2];
|
||||||
|
errorMessage += " (line "+lineno+", col "+cha+")";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (e) {
|
if (!errorMessage) {
|
||||||
node.warn("failed to load library: "+ module.spec);
|
errorMessage = err.toString();
|
||||||
}
|
}
|
||||||
|
done(errorMessage);
|
||||||
|
}
|
||||||
|
else if (typeof err === "string") {
|
||||||
|
done(err);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
done(JSON.stringify(err));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
var context = vm.createContext(sandbox);
|
|
||||||
try {
|
node.on("close", function() {
|
||||||
var iniScript = null;
|
if (finScript) {
|
||||||
var iniOpt = null;
|
try {
|
||||||
if (node.ini && (node.ini !== "")) {
|
finScript.runInContext(context, finOpt);
|
||||||
var iniText = `
|
}
|
||||||
(async function(__send__) {
|
catch (err) {
|
||||||
var node = {
|
node.error(err);
|
||||||
id:__node__.id,
|
}
|
||||||
name:__node__.name,
|
|
||||||
log:__node__.log,
|
|
||||||
error:__node__.error,
|
|
||||||
warn:__node__.warn,
|
|
||||||
debug:__node__.debug,
|
|
||||||
trace:__node__.trace,
|
|
||||||
status:__node__.status,
|
|
||||||
send: function(msgs, cloneMsg) {
|
|
||||||
__node__.send(__send__, RED.util.generateId(), msgs, cloneMsg);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
`+ node.ini +`
|
|
||||||
})(__initSend__);`;
|
|
||||||
iniOpt = createVMOpt(node, " setup");
|
|
||||||
iniScript = new vm.Script(iniText, iniOpt);
|
|
||||||
}
|
}
|
||||||
node.script = vm.createScript(functionText, createVMOpt(node, ""));
|
while (node.outstandingTimers.length > 0) {
|
||||||
if (node.fin && (node.fin !== "")) {
|
clearTimeout(node.outstandingTimers.pop());
|
||||||
var finText = "(function () {\n"+node.fin +"\n})();";
|
|
||||||
finOpt = createVMOpt(node, " cleanup");
|
|
||||||
finScript = new vm.Script(finText, finOpt);
|
|
||||||
}
|
}
|
||||||
var promise = Promise.resolve();
|
while (node.outstandingIntervals.length > 0) {
|
||||||
if (iniScript) {
|
clearInterval(node.outstandingIntervals.pop());
|
||||||
context.__initSend__ = function(msgs) { node.send(msgs); };
|
|
||||||
promise = iniScript.runInContext(context, iniOpt);
|
|
||||||
}
|
}
|
||||||
|
if (node.clearStatus) {
|
||||||
|
node.status({});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
processMessage = function (msg, send, done) {
|
promise.then(function (v) {
|
||||||
var start = process.hrtime();
|
var msgs = messages;
|
||||||
context.msg = msg;
|
messages = [];
|
||||||
context.__send__ = send;
|
while (msgs.length > 0) {
|
||||||
context.__done__ = done;
|
msgs.forEach(function (s) {
|
||||||
|
processMessage(s.msg, s.send, s.done);
|
||||||
node.script.runInContext(context);
|
|
||||||
context.results.then(function(results) {
|
|
||||||
sendResults(node,send,msg._msgid,results,false);
|
|
||||||
if (handleNodeDoneCall) {
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
|
|
||||||
var duration = process.hrtime(start);
|
|
||||||
var converted = Math.floor((duration[0] * 1e9 + duration[1])/10000)/100;
|
|
||||||
node.metric("duration", msg, converted);
|
|
||||||
if (process.env.NODE_RED_FUNCTION_TIME) {
|
|
||||||
node.status({fill:"yellow",shape:"dot",text:""+converted});
|
|
||||||
}
|
|
||||||
}).catch(err => {
|
|
||||||
if ((typeof err === "object") && err.hasOwnProperty("stack")) {
|
|
||||||
//remove unwanted part
|
|
||||||
var index = err.stack.search(/\n\s*at ContextifyScript.Script.runInContext/);
|
|
||||||
err.stack = err.stack.slice(0, index).split('\n').slice(0,-1).join('\n');
|
|
||||||
var stack = err.stack.split(/\r?\n/);
|
|
||||||
|
|
||||||
//store the error in msg to be used in flows
|
|
||||||
msg.error = err;
|
|
||||||
|
|
||||||
var line = 0;
|
|
||||||
var errorMessage;
|
|
||||||
if (stack.length > 0) {
|
|
||||||
while (line < stack.length && stack[line].indexOf("ReferenceError") !== 0) {
|
|
||||||
line++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line < stack.length) {
|
|
||||||
errorMessage = stack[line];
|
|
||||||
var m = /:(\d+):(\d+)$/.exec(stack[line+1]);
|
|
||||||
if (m) {
|
|
||||||
var lineno = Number(m[1])-1;
|
|
||||||
var cha = m[2];
|
|
||||||
errorMessage += " (line "+lineno+", col "+cha+")";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!errorMessage) {
|
|
||||||
errorMessage = err.toString();
|
|
||||||
}
|
|
||||||
done(errorMessage);
|
|
||||||
}
|
|
||||||
else if (typeof err === "string") {
|
|
||||||
done(err);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
done(JSON.stringify(err));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
msgs = messages;
|
||||||
|
messages = [];
|
||||||
}
|
}
|
||||||
|
state = RESOLVED;
|
||||||
|
}).catch((error) => {
|
||||||
|
messages = [];
|
||||||
|
state = ERROR;
|
||||||
|
node.error(error);
|
||||||
|
});
|
||||||
|
|
||||||
node.on("close", function() {
|
}
|
||||||
if (finScript) {
|
catch(err) {
|
||||||
try {
|
// eg SyntaxError - which v8 doesn't include line number information
|
||||||
finScript.runInContext(context, finOpt);
|
// so we can't do better than this
|
||||||
}
|
updateErrorInfo(err);
|
||||||
catch (err) {
|
node.error(err);
|
||||||
node.error(err);
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
while (node.outstandingTimers.length > 0) {
|
|
||||||
clearTimeout(node.outstandingTimers.pop());
|
|
||||||
}
|
|
||||||
while (node.outstandingIntervals.length > 0) {
|
|
||||||
clearInterval(node.outstandingIntervals.pop());
|
|
||||||
}
|
|
||||||
if (node.clearStatus) {
|
|
||||||
node.status({});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
promise.then(function (v) {
|
|
||||||
var msgs = messages;
|
|
||||||
messages = [];
|
|
||||||
while (msgs.length > 0) {
|
|
||||||
msgs.forEach(function (s) {
|
|
||||||
processMessage(s.msg, s.send, s.done);
|
|
||||||
});
|
|
||||||
msgs = messages;
|
|
||||||
messages = [];
|
|
||||||
}
|
|
||||||
state = RESOLVED;
|
|
||||||
}).catch((error) => {
|
|
||||||
messages = [];
|
|
||||||
state = ERROR;
|
|
||||||
node.error(error);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
catch(err) {
|
|
||||||
// eg SyntaxError - which v8 doesn't include line number information
|
|
||||||
// so we can't do better than this
|
|
||||||
updateErrorInfo(err);
|
|
||||||
node.error(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
RED.nodes.registerType("function",FunctionNode, {
|
RED.nodes.registerType("function",FunctionNode, {
|
||||||
dynamicModuleList: "modules"
|
dynamicModuleList: "libs",
|
||||||
|
settings: {
|
||||||
|
functionExternalModules: { value: true, exportable: true }
|
||||||
|
}
|
||||||
});
|
});
|
||||||
RED.library.register("functions");
|
RED.library.register("functions");
|
||||||
};
|
};
|
||||||
|
@ -220,7 +220,7 @@
|
|||||||
"finalize": "// Code added here will be run when the\n// node is being stopped or re-deployed.\n"
|
"finalize": "// Code added here will be run when the\n// node is being stopped or re-deployed.\n"
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"var": "name",
|
"var": "variable",
|
||||||
"module": "module"
|
"module": "module"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
|
209
packages/node_modules/@node-red/registry/lib/externalModules.js
vendored
Normal file
209
packages/node_modules/@node-red/registry/lib/externalModules.js
vendored
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
// This module handles the management of modules required by the runtime and flows.
|
||||||
|
// Essentially this means keeping track of what extra modules a flow requires,
|
||||||
|
// ensuring those modules are installed and providing a standard way for nodes
|
||||||
|
// to require those modules safely.
|
||||||
|
|
||||||
|
const fs = require("fs-extra");
|
||||||
|
const registryUtil = require("./util");
|
||||||
|
const path = require("path");
|
||||||
|
const exec = require("@node-red/util").exec;
|
||||||
|
const log = require("@node-red/util").log;
|
||||||
|
|
||||||
|
const BUILTIN_MODULES = require('module').builtinModules;
|
||||||
|
const EXTERNAL_MODULES_DIR = "externalModules";
|
||||||
|
|
||||||
|
// TODO: outsource running npm to a plugin
|
||||||
|
const NPM_COMMAND = (process.platform === "win32") ? "npm.cmd" : "npm";
|
||||||
|
|
||||||
|
let registeredTypes = {};
|
||||||
|
let settings;
|
||||||
|
|
||||||
|
let knownExternalModules = {};
|
||||||
|
|
||||||
|
let installEnabled = true;
|
||||||
|
let installAllowList = ['*'];
|
||||||
|
let installDenyList = [];
|
||||||
|
|
||||||
|
function getInstallDir() {
|
||||||
|
return path.resolve(path.join(settings.userDir || process.env.NODE_RED_HOME || ".", "externalModules"));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshExternalModules() {
|
||||||
|
const externalModuleDir = path.resolve(path.join(settings.userDir || process.env.NODE_RED_HOME || ".", EXTERNAL_MODULES_DIR));
|
||||||
|
try {
|
||||||
|
const pkgFile = JSON.parse(await fs.readFile(path.join(externalModuleDir,"package.json"),"utf-8"));
|
||||||
|
knownExternalModules = pkgFile.dependencies;
|
||||||
|
} catch(err) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function init(_settings) {
|
||||||
|
settings = _settings;
|
||||||
|
path.resolve(settings.userDir || process.env.NODE_RED_HOME || ".");
|
||||||
|
|
||||||
|
if (settings.externalModules && settings.externalModules.modules) {
|
||||||
|
if (settings.externalModules.modules.allowList || settings.externalModules.modules.denyList) {
|
||||||
|
installAllowList = settings.externalModules.modules.allowList;
|
||||||
|
installDenyList = settings.externalModules.modules.denyList;
|
||||||
|
}
|
||||||
|
if (settings.externalModules.modules.hasOwnProperty("allowInstall")) {
|
||||||
|
installEnabled = settings.externalModules.modules.allowInstall
|
||||||
|
}
|
||||||
|
}
|
||||||
|
installAllowList = registryUtil.parseModuleList(installAllowList);
|
||||||
|
installDenyList = registryUtil.parseModuleList(installDenyList);
|
||||||
|
}
|
||||||
|
|
||||||
|
function register(type, dynamicModuleListProperty) {
|
||||||
|
registeredTypes[type] = dynamicModuleListProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
function requireModule(module) {
|
||||||
|
if (!registryUtil.checkModuleAllowed( module, null,installAllowList,installDenyList)) {
|
||||||
|
const e = new Error("Module not allowed");
|
||||||
|
e.code = "module_not_allowed";
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
if (BUILTIN_MODULES.indexOf(module) !== -1) {
|
||||||
|
return require(module);
|
||||||
|
}
|
||||||
|
if (!knownExternalModules[module]) {
|
||||||
|
const e = new Error("Module not allowed");
|
||||||
|
e.code = "module_not_allowed";
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
const externalModuleDir = path.resolve(path.join(settings.userDir || process.env.NODE_RED_HOME || ".", EXTERNAL_MODULES_DIR));
|
||||||
|
const moduleDir = path.join(externalModuleDir,"node_modules",module);
|
||||||
|
return require(moduleDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseModuleName(module) {
|
||||||
|
var match = /((?:@[^/]+\/)?[^/@]+)(?:@([\s\S]+))?/.exec(module);
|
||||||
|
if (match) {
|
||||||
|
return {
|
||||||
|
spec: module,
|
||||||
|
module: match[1],
|
||||||
|
version: match[2],
|
||||||
|
builtin: BUILTIN_MODULES.indexOf(match[1]) !== -1,
|
||||||
|
known: !!knownExternalModules[match[1]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isInstalled(moduleDetails) {
|
||||||
|
return moduleDetails.builtin || moduleDetails.known;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkFlowDependencies(flowConfig) {
|
||||||
|
await refreshExternalModules();
|
||||||
|
|
||||||
|
const checkedModules = {};
|
||||||
|
const promises = [];
|
||||||
|
const errors = [];
|
||||||
|
|
||||||
|
flowConfig.forEach(n => {
|
||||||
|
if (registeredTypes[n.type]) {
|
||||||
|
let nodeModules = n[registeredTypes[n.type]] || [];
|
||||||
|
if (!Array.isArray(nodeModules)) {
|
||||||
|
nodeModules = [nodeModules]
|
||||||
|
}
|
||||||
|
nodeModules.forEach(module => {
|
||||||
|
if (typeof module !== 'string') {
|
||||||
|
module = module.module || "";
|
||||||
|
}
|
||||||
|
if (module) {
|
||||||
|
let moduleDetails = parseModuleName(module)
|
||||||
|
if (moduleDetails && checkedModules[moduleDetails.module] === undefined) {
|
||||||
|
checkedModules[moduleDetails.module] = isInstalled(moduleDetails)
|
||||||
|
if (!checkedModules[moduleDetails.module]) {
|
||||||
|
if (installEnabled) {
|
||||||
|
promises.push(installModule(moduleDetails).catch(err => {
|
||||||
|
errors.push({module: moduleDetails,error:err});
|
||||||
|
}))
|
||||||
|
} else if (!installEnabled) {
|
||||||
|
const e = new Error("Module install disabled - externalModules.modules.allowInstall=false");
|
||||||
|
e.code = "install_not_allowed";
|
||||||
|
errors.push({module: moduleDetails,error:e});
|
||||||
|
}
|
||||||
|
} else if (!registryUtil.checkModuleAllowed( moduleDetails.module, moduleDetails.version,installAllowList,installDenyList)) {
|
||||||
|
const e = new Error("Module not allowed");
|
||||||
|
e.code = "module_not_allowed";
|
||||||
|
errors.push({module: moduleDetails,error:e});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return Promise.all(promises).then(() => {
|
||||||
|
if (errors.length > 0) {
|
||||||
|
throw errors;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function ensureModuleDir() {
|
||||||
|
const installDir = getInstallDir();
|
||||||
|
|
||||||
|
if (!fs.existsSync(installDir)) {
|
||||||
|
await fs.ensureDir(installDir);
|
||||||
|
}
|
||||||
|
const pkgFile = path.join(installDir,"package.json");
|
||||||
|
if (!fs.existsSync(pkgFile)) {
|
||||||
|
await fs.writeFile(path.join(installDir,"package.json"),`{
|
||||||
|
"name": "Node-RED External Modules",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {}
|
||||||
|
}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function installModule(moduleDetails) {
|
||||||
|
let installSpec = moduleDetails.module;
|
||||||
|
if (!registryUtil.checkModuleAllowed( moduleDetails.module, moduleDetails.version,installAllowList,installDenyList)) {
|
||||||
|
const e = new Error("Install not allowed");
|
||||||
|
e.code = "install_not_allowed";
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
if (moduleDetails.version) {
|
||||||
|
installSpec = installSpec+"@"+moduleDetails.version;
|
||||||
|
}
|
||||||
|
log.info(log._("server.install.installing",{name: moduleDetails.module,version: moduleDetails.version||"latest"}));
|
||||||
|
const installDir = getInstallDir();
|
||||||
|
|
||||||
|
await ensureModuleDir();
|
||||||
|
|
||||||
|
var args = ["install", installSpec, "--production"];
|
||||||
|
return exec.run(NPM_COMMAND, args, {
|
||||||
|
cwd: installDir
|
||||||
|
},true).then(result => {
|
||||||
|
log.info("successfully installed: "+installSpec);
|
||||||
|
}).catch(result => {
|
||||||
|
var output = result.stderr;
|
||||||
|
var e;
|
||||||
|
var lookForVersionNotFound = new RegExp("version not found: ","m");
|
||||||
|
if (/E404/.test(output) || /ETARGET/.test(output)) {
|
||||||
|
log.error(log._("server.install.install-failed-not-found",{name:installSpec}));
|
||||||
|
e = new Error("Module not found");
|
||||||
|
e.code = 404;
|
||||||
|
throw e;
|
||||||
|
} else {
|
||||||
|
log.error(log._("server.install.install-failed-long",{name:installSpec}));
|
||||||
|
log.error("------------------------------------------");
|
||||||
|
log.error(output);
|
||||||
|
log.error("------------------------------------------");
|
||||||
|
throw new Error(log._("server.install.install-failed"));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init: init,
|
||||||
|
register: register,
|
||||||
|
checkFlowDependencies: checkFlowDependencies,
|
||||||
|
require: requireModule
|
||||||
|
}
|
@ -28,6 +28,7 @@ var registry = require("./registry");
|
|||||||
var loader = require("./loader");
|
var loader = require("./loader");
|
||||||
var installer = require("./installer");
|
var installer = require("./installer");
|
||||||
var library = require("./library");
|
var library = require("./library");
|
||||||
|
const externalModules = require("./externalModules")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialise the registry with a reference to a runtime object
|
* Initialise the registry with a reference to a runtime object
|
||||||
@ -42,6 +43,7 @@ function init(runtime) {
|
|||||||
loader.init(runtime);
|
loader.init(runtime);
|
||||||
registry.init(runtime.settings,loader);
|
registry.init(runtime.settings,loader);
|
||||||
library.init();
|
library.init();
|
||||||
|
externalModules.init(runtime.settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -297,6 +299,8 @@ module.exports = {
|
|||||||
*/
|
*/
|
||||||
getNodeExampleFlowPath: library.getExampleFlowPath,
|
getNodeExampleFlowPath: library.getExampleFlowPath,
|
||||||
|
|
||||||
|
checkFlowDependencies: externalModules.checkFlowDependencies,
|
||||||
|
|
||||||
deprecated: require("./deprecated")
|
deprecated: require("./deprecated")
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -21,6 +21,7 @@ var fs = require("fs");
|
|||||||
var library = require("./library");
|
var library = require("./library");
|
||||||
const {events} = require("@node-red/util")
|
const {events} = require("@node-red/util")
|
||||||
var subflows = require("./subflow");
|
var subflows = require("./subflow");
|
||||||
|
var externalModules = require("./externalModules")
|
||||||
var settings;
|
var settings;
|
||||||
var loader;
|
var loader;
|
||||||
|
|
||||||
@ -28,6 +29,7 @@ var nodeConfigCache = {};
|
|||||||
var moduleConfigs = {};
|
var moduleConfigs = {};
|
||||||
var nodeList = [];
|
var nodeList = [];
|
||||||
var nodeConstructors = {};
|
var nodeConstructors = {};
|
||||||
|
var nodeOptions = {};
|
||||||
var subflowModules = {};
|
var subflowModules = {};
|
||||||
|
|
||||||
var nodeTypeToId = {};
|
var nodeTypeToId = {};
|
||||||
@ -36,12 +38,7 @@ var moduleNodes = {};
|
|||||||
function init(_settings,_loader) {
|
function init(_settings,_loader) {
|
||||||
settings = _settings;
|
settings = _settings;
|
||||||
loader = _loader;
|
loader = _loader;
|
||||||
moduleNodes = {};
|
clear();
|
||||||
nodeTypeToId = {};
|
|
||||||
nodeConstructors = {};
|
|
||||||
subflowModules = {};
|
|
||||||
nodeList = [];
|
|
||||||
nodeConfigCache = {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function load() {
|
function load() {
|
||||||
@ -234,6 +231,7 @@ function removeNode(id) {
|
|||||||
if (typeId === id) {
|
if (typeId === id) {
|
||||||
delete subflowModules[t];
|
delete subflowModules[t];
|
||||||
delete nodeConstructors[t];
|
delete nodeConstructors[t];
|
||||||
|
delete nodeOptions[t];
|
||||||
delete nodeTypeToId[t];
|
delete nodeTypeToId[t];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -411,7 +409,7 @@ function getCaller(){
|
|||||||
return stack[0].getFileName();
|
return stack[0].getFileName();
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerNodeConstructor(nodeSet,type,constructor) {
|
function registerNodeConstructor(nodeSet,type,constructor,options) {
|
||||||
if (nodeConstructors.hasOwnProperty(type)) {
|
if (nodeConstructors.hasOwnProperty(type)) {
|
||||||
throw new Error(type+" already registered");
|
throw new Error(type+" already registered");
|
||||||
}
|
}
|
||||||
@ -431,6 +429,12 @@ function registerNodeConstructor(nodeSet,type,constructor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
nodeConstructors[type] = constructor;
|
nodeConstructors[type] = constructor;
|
||||||
|
nodeOptions[type] = options;
|
||||||
|
if (options) {
|
||||||
|
if (options.dynamicModuleList) {
|
||||||
|
externalModules.register(type,options.dynamicModuleList);
|
||||||
|
}
|
||||||
|
}
|
||||||
events.emit("type-registered",type);
|
events.emit("type-registered",type);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -525,6 +529,7 @@ function clear() {
|
|||||||
moduleConfigs = {};
|
moduleConfigs = {};
|
||||||
nodeList = [];
|
nodeList = [];
|
||||||
nodeConstructors = {};
|
nodeConstructors = {};
|
||||||
|
nodeOptions = {};
|
||||||
subflowModules = {};
|
subflowModules = {};
|
||||||
nodeTypeToId = {};
|
nodeTypeToId = {};
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
const semver = require("semver");
|
const semver = require("semver");
|
||||||
const {events,i18n,log} = require("@node-red/util");
|
const {events,i18n,log} = require("@node-red/util");
|
||||||
|
|
||||||
var runtime;
|
var runtime;
|
||||||
|
|
||||||
function copyObjectProperties(src,dst,copyList,blockList) {
|
function copyObjectProperties(src,dst,copyList,blockList) {
|
||||||
@ -45,13 +46,8 @@ function requireModule(name) {
|
|||||||
var relPath = path.relative(__dirname, moduleInfo.path);
|
var relPath = path.relative(__dirname, moduleInfo.path);
|
||||||
return require(relPath);
|
return require(relPath);
|
||||||
} else {
|
} else {
|
||||||
var npm = runtime.nodes.loadNPMModule(name);
|
// Require it here to avoid the circular dependency
|
||||||
if (npm) {
|
return require("./externalModules").require(name);
|
||||||
return npm;
|
|
||||||
}
|
|
||||||
var err = new Error(`Cannot find module '${name}'`);
|
|
||||||
err.code = "MODULE_NOT_FOUND";
|
|
||||||
throw err;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,7 +125,6 @@ function checkAgainstList(module,version,list) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function checkModuleAllowed(module,version,allowList,denyList) {
|
function checkModuleAllowed(module,version,allowList,denyList) {
|
||||||
// console.log("checkModuleAllowed",module,version);//,allowList,denyList)
|
|
||||||
if (!allowList && !denyList) {
|
if (!allowList && !denyList) {
|
||||||
// Default to allow
|
// Default to allow
|
||||||
return true;
|
return true;
|
||||||
|
@ -447,47 +447,5 @@ var api = module.exports = {
|
|||||||
} else {
|
} else {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets list of NPM modules
|
|
||||||
* @param {Object} opts
|
|
||||||
* @param {User} opts.user - the user calling the api
|
|
||||||
* @param {Object} opts.req - the request to log (optional)
|
|
||||||
* @return {Promise<Buffer>} - list of installed NPM modules
|
|
||||||
* @memberof @node-red/runtime_nodes
|
|
||||||
*/
|
|
||||||
listNPMModules: async function(opts) {
|
|
||||||
var promise = runtime.nodes.listNPMModules();
|
|
||||||
return promise;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uninstall NPM modules
|
|
||||||
* @param {Object} opts
|
|
||||||
* @param {User} opts.user - the user calling the api
|
|
||||||
* @param {Object} opts.req - the request to log (optional)
|
|
||||||
* @return {Promise<Object>} - object for request result
|
|
||||||
* @memberof @node-red/runtime_nodes
|
|
||||||
*/
|
|
||||||
uninstallNPMModule: async function(opts) {
|
|
||||||
var spec = opts.spec;
|
|
||||||
var promise = runtime.nodes.uninstallNPMModule(spec);
|
|
||||||
return promise;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update NPM modules
|
|
||||||
* @param {Object} opts
|
|
||||||
* @param {User} opts.user - the user calling the api
|
|
||||||
* @param {Object} opts.req - the request to log (optional)
|
|
||||||
* @return {Promise<Object>} - object for request result
|
|
||||||
* @memberof @node-red/runtime_nodes
|
|
||||||
*/
|
|
||||||
updateNPMModule: async function(opts) {
|
|
||||||
var spec = opts.spec;
|
|
||||||
var isUpdate = opts.update;
|
|
||||||
var promise = runtime.nodes.updateNPMModule(spec, isUpdate);
|
|
||||||
return promise;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -187,35 +187,35 @@ function setFlows(_config,_credentials,type,muteLog,forceStart,user) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return configSavePromise
|
|
||||||
.then(function(flowRevision) {
|
|
||||||
if (!isLoad) {
|
|
||||||
log.debug("saved flow revision: "+flowRevision);
|
|
||||||
}
|
|
||||||
activeConfig = {
|
|
||||||
flows:config,
|
|
||||||
rev:flowRevision
|
|
||||||
};
|
|
||||||
activeFlowConfig = newFlowConfig;
|
|
||||||
if (forceStart || started) {
|
|
||||||
// Flows are running (or should be)
|
|
||||||
|
|
||||||
// Stop the active flows (according to deploy type and the diff)
|
return configSavePromise.then(flowRevision => {
|
||||||
return stop(type,diff,muteLog).then(() => {
|
if (!isLoad) {
|
||||||
// Once stopped, allow context to remove anything no longer needed
|
log.debug("saved flow revision: "+flowRevision);
|
||||||
return context.clean(activeFlowConfig)
|
}
|
||||||
}).then(() => {
|
activeConfig = {
|
||||||
// Start the active flows
|
flows:config,
|
||||||
start(type,diff,muteLog).then(() => {
|
rev:flowRevision
|
||||||
events.emit("runtime-event",{id:"runtime-deploy",payload:{revision:flowRevision},retain: true});
|
};
|
||||||
});
|
activeFlowConfig = newFlowConfig;
|
||||||
// Return the new revision asynchronously to the actual start
|
if (forceStart || started) {
|
||||||
return flowRevision;
|
// Flows are running (or should be)
|
||||||
}).catch(function(err) { })
|
|
||||||
} else {
|
// Stop the active flows (according to deploy type and the diff)
|
||||||
events.emit("runtime-event",{id:"runtime-deploy",payload:{revision:flowRevision},retain: true});
|
return stop(type,diff,muteLog).then(() => {
|
||||||
}
|
// Once stopped, allow context to remove anything no longer needed
|
||||||
});
|
return context.clean(activeFlowConfig)
|
||||||
|
}).then(() => {
|
||||||
|
// Start the active flows
|
||||||
|
start(type,diff,muteLog).then(() => {
|
||||||
|
events.emit("runtime-event",{id:"runtime-deploy",payload:{revision:flowRevision},retain: true});
|
||||||
|
});
|
||||||
|
// Return the new revision asynchronously to the actual start
|
||||||
|
return flowRevision;
|
||||||
|
}).catch(function(err) { })
|
||||||
|
} else {
|
||||||
|
events.emit("runtime-event",{id:"runtime-deploy",payload:{revision:flowRevision},retain: true});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNode(id) {
|
function getNode(id) {
|
||||||
@ -246,7 +246,7 @@ function getFlows() {
|
|||||||
return activeConfig;
|
return activeConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
function start(type,diff,muteLog) {
|
async function start(type,diff,muteLog) {
|
||||||
type = type||"full";
|
type = type||"full";
|
||||||
started = true;
|
started = true;
|
||||||
var i;
|
var i;
|
||||||
@ -271,7 +271,21 @@ function start(type,diff,muteLog) {
|
|||||||
log.info(" "+settings.userDir);
|
log.info(" "+settings.userDir);
|
||||||
}
|
}
|
||||||
events.emit("runtime-event",{id:"runtime-state",payload:{error:"missing-types", type:"warning",text:"notification.warnings.missing-types",types:activeFlowConfig.missingTypes},retain:true});
|
events.emit("runtime-event",{id:"runtime-state",payload:{error:"missing-types", type:"warning",text:"notification.warnings.missing-types",types:activeFlowConfig.missingTypes},retain:true});
|
||||||
return Promise.resolve();
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await typeRegistry.checkFlowDependencies(activeConfig.flows);
|
||||||
|
} catch(err) {
|
||||||
|
log.info("Failed to load external modules required by this flow:");
|
||||||
|
const missingModules = [];
|
||||||
|
for (i=0;i<err.length;i++) {
|
||||||
|
let errMessage = err[i].error.toString()
|
||||||
|
missingModules.push({module:err[i].module.module, error: err[i].error.code || err[i].error.toString()})
|
||||||
|
log.info(` - ${err[i].module.spec} [${err[i].error.code || "unknown_error"}]`);
|
||||||
|
}
|
||||||
|
events.emit("runtime-event",{id:"runtime-state",payload:{error:"missing-modules", type:"warning",text:"notification.warnings.missing-modules",modules:missingModules},retain:true});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// In safe mode, don't actually start anything, emit the necessary runtime event and return
|
// In safe mode, don't actually start anything, emit the necessary runtime event and return
|
||||||
@ -280,7 +294,7 @@ function start(type,diff,muteLog) {
|
|||||||
log.info(log._("nodes.flows.safe-mode"));
|
log.info(log._("nodes.flows.safe-mode"));
|
||||||
log.info("*****************************************************************")
|
log.info("*****************************************************************")
|
||||||
events.emit("runtime-event",{id:"runtime-state",payload:{error:"safe-mode", type:"warning",text:"notification.warnings.safe-mode"},retain:true});
|
events.emit("runtime-event",{id:"runtime-state",payload:{error:"safe-mode", type:"warning",text:"notification.warnings.safe-mode"},retain:true});
|
||||||
return Promise.resolve();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!muteLog) {
|
if (!muteLog) {
|
||||||
@ -370,7 +384,7 @@ function start(type,diff,muteLog) {
|
|||||||
log.info(log._("nodes.flows.started-flows"));
|
log.info(log._("nodes.flows.started-flows"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function stop(type,diff,muteLog) {
|
function stop(type,diff,muteLog) {
|
||||||
|
@ -26,7 +26,6 @@ var flows = require("../flows");
|
|||||||
var flowUtil = require("../flows/util")
|
var flowUtil = require("../flows/util")
|
||||||
var context = require("./context");
|
var context = require("./context");
|
||||||
var Node = require("./Node");
|
var Node = require("./Node");
|
||||||
var npmModule = require("./npmModule");
|
|
||||||
var log;
|
var log;
|
||||||
|
|
||||||
const events = require("@node-red/util").events;
|
const events = require("@node-red/util").events;
|
||||||
@ -50,7 +49,6 @@ function registerType(nodeSet,type,constructor,opts) {
|
|||||||
type = nodeSet;
|
type = nodeSet;
|
||||||
nodeSet = "";
|
nodeSet = "";
|
||||||
}
|
}
|
||||||
var dynModule = null;
|
|
||||||
if (opts) {
|
if (opts) {
|
||||||
if (opts.credentials) {
|
if (opts.credentials) {
|
||||||
credentials.register(type,opts.credentials);
|
credentials.register(type,opts.credentials);
|
||||||
@ -62,11 +60,7 @@ function registerType(nodeSet,type,constructor,opts) {
|
|||||||
log.warn("["+type+"] "+err.message);
|
log.warn("["+type+"] "+err.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (opts.dynamicModuleList) {
|
|
||||||
dynModule = opts.dynamicModuleList;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
npmModule.register(type, dynModule);
|
|
||||||
if(!(constructor.prototype instanceof Node)) {
|
if(!(constructor.prototype instanceof Node)) {
|
||||||
if(Object.getPrototypeOf(constructor.prototype) === Object.prototype) {
|
if(Object.getPrototypeOf(constructor.prototype) === Object.prototype) {
|
||||||
util.inherits(constructor,Node);
|
util.inherits(constructor,Node);
|
||||||
@ -87,7 +81,7 @@ function registerType(nodeSet,type,constructor,opts) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
registry.registerType(nodeSet,type,constructor);
|
registry.registerType(nodeSet,type,constructor,opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -116,7 +110,6 @@ function createNode(node,def) {
|
|||||||
} else if (credentials.getDefinition(node.type)) {
|
} else if (credentials.getDefinition(node.type)) {
|
||||||
node.credentials = {};
|
node.credentials = {};
|
||||||
}
|
}
|
||||||
return npmModule.checkInstall(def);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerSubflow(nodeSet, subflow) {
|
function registerSubflow(nodeSet, subflow) {
|
||||||
@ -145,7 +138,6 @@ function init(runtime) {
|
|||||||
flows.init(runtime);
|
flows.init(runtime);
|
||||||
registry.init(runtime);
|
registry.init(runtime);
|
||||||
context.init(runtime.settings);
|
context.init(runtime.settings);
|
||||||
npmModule.init(runtime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function disableNode(id) {
|
function disableNode(id) {
|
||||||
@ -270,10 +262,4 @@ module.exports = {
|
|||||||
loadContextsPlugin: context.load,
|
loadContextsPlugin: context.load,
|
||||||
closeContextsPlugin: context.close,
|
closeContextsPlugin: context.close,
|
||||||
listContextStores: context.listStores,
|
listContextStores: context.listStores,
|
||||||
|
|
||||||
// NPM modules
|
|
||||||
listNPMModules: npmModule.list,
|
|
||||||
uninstallNPMModule: npmModule.uninstall,
|
|
||||||
updateNPMModule: npmModule.update,
|
|
||||||
loadNPMModule: npmModule.load
|
|
||||||
};
|
};
|
||||||
|
@ -1,460 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
**/
|
|
||||||
|
|
||||||
var path = require("path");
|
|
||||||
var fs = require("fs-extra");
|
|
||||||
var os = require("os");
|
|
||||||
var util = require("@node-red/registry/lib/util");
|
|
||||||
|
|
||||||
var api;
|
|
||||||
|
|
||||||
var runtime;
|
|
||||||
var settings;
|
|
||||||
var exec;
|
|
||||||
var log;
|
|
||||||
|
|
||||||
var npmCommand = (process.platform === "win32") ? "npm.cmd" : "npm";
|
|
||||||
|
|
||||||
var metadataFileName = "npm-modules.json";
|
|
||||||
|
|
||||||
var moduleProp = {};
|
|
||||||
var moduleBase = null;
|
|
||||||
|
|
||||||
var allowInstall = true;
|
|
||||||
var allowList = ["*"];
|
|
||||||
var denyList = [];
|
|
||||||
|
|
||||||
var inProgress = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialise npm install module.
|
|
||||||
* @param {Object} _runtime - runtime object
|
|
||||||
*/
|
|
||||||
function init(_runtime) {
|
|
||||||
runtime = _runtime;
|
|
||||||
settings = _runtime.settings;
|
|
||||||
exec = _runtime.exec;
|
|
||||||
log = _runtime.log;
|
|
||||||
|
|
||||||
moduleProp = {};
|
|
||||||
inProgress = {};
|
|
||||||
|
|
||||||
moduleBase = settings.userDir || process.env.NODE_RED_HOME || ".";
|
|
||||||
|
|
||||||
if (settings.hasOwnProperty("externalModules")) {
|
|
||||||
var em = settings.externalModules;
|
|
||||||
if (em && em.hasOwnProperty("modules")) {
|
|
||||||
var mod = em.modules;
|
|
||||||
if (mod.hasOwnProperty("allowInstall")) {
|
|
||||||
allowInstall = mod.allowInstall;
|
|
||||||
}
|
|
||||||
if (mod.hasOwnProperty("allowList")) {
|
|
||||||
var alist = mod.allowList;
|
|
||||||
if (Array.isArray(alist)) {
|
|
||||||
allowList = alist;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
log.warn("unexpected value of externalModule.allowList in settings.js");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mod.hasOwnProperty("denyList")) {
|
|
||||||
var dlist = mod.denyList;
|
|
||||||
if (Array.isArray(dlist)) {
|
|
||||||
denyList = dlist;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
log.warn("unexpected value of externalModule.denyList in settings.js");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register dynamic module installation property.
|
|
||||||
* @param {string} type - node type
|
|
||||||
* @param {string} prop - property name
|
|
||||||
*/
|
|
||||||
function register(type, prop) {
|
|
||||||
if (prop) {
|
|
||||||
moduleProp[type] = prop;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
delete moduleProp[prop]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get path to install modules
|
|
||||||
*/
|
|
||||||
function modulePath() { // takes variable length arguments in `arguments`
|
|
||||||
var result = moduleBase;
|
|
||||||
for(var i = 0; i < arguments.length; i++) {
|
|
||||||
result = path.join(result, arguments[i]);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decompose NPM module specification string
|
|
||||||
* @param {string} module - module specification
|
|
||||||
* @return {Object} array [name, version], where name is name part and version is version part of the module
|
|
||||||
*/
|
|
||||||
function moduleName(module) {
|
|
||||||
var match = /^([^@]+)@(.+)/.exec(module);
|
|
||||||
if (match) {
|
|
||||||
return [match[1], match[2]];
|
|
||||||
}
|
|
||||||
return [module, undefined];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get NPM module info
|
|
||||||
* @param {string} name - module name
|
|
||||||
* @return {Object} package.json for specified NPM module
|
|
||||||
*/
|
|
||||||
function infoNPM(name) {
|
|
||||||
var path = modulePath("node_modules", name, "package.json");
|
|
||||||
try {
|
|
||||||
var pkg = fs.readFileSync(path);
|
|
||||||
return JSON.parse(pkg);
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load NPM module metadata
|
|
||||||
* @return {object} module metadata object
|
|
||||||
*/
|
|
||||||
function loadMetadata() {
|
|
||||||
var path = modulePath(metadataFileName);
|
|
||||||
try {
|
|
||||||
var pkg = fs.readFileSync(path);
|
|
||||||
return JSON.parse(pkg);
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
modules: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save NPM module metadata
|
|
||||||
* @param {string} data - module metadata object
|
|
||||||
*/
|
|
||||||
function saveMetadata(data) {
|
|
||||||
var path = modulePath(metadataFileName);
|
|
||||||
var str = JSON.stringify(data, null, 4);
|
|
||||||
fs.writeFileSync(path, str);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find item in metadata
|
|
||||||
* @param {Object} meta - metadata
|
|
||||||
* @param {string} name - module name
|
|
||||||
* @return {object} metadata item
|
|
||||||
*/
|
|
||||||
function findModule(meta, name) {
|
|
||||||
var modules = meta.modules;
|
|
||||||
var item = modules.find(item => (item.name === name));
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setInProgress(name) {
|
|
||||||
inProgress[name] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearInProgress(name) {
|
|
||||||
inProgress[name] = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Install NPM module
|
|
||||||
* @param {string} module - module specification
|
|
||||||
*/
|
|
||||||
function installNPM(module) {
|
|
||||||
var [name, ver] = moduleName(module);
|
|
||||||
setInProgress(name);
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
var pkg = infoNPM(name);
|
|
||||||
if (!pkg) {
|
|
||||||
var args = ["install", module];
|
|
||||||
var dir = modulePath();
|
|
||||||
return exec.run(npmCommand, args, {
|
|
||||||
cwd: dir
|
|
||||||
}, true).then(result => {
|
|
||||||
if (result && (result.code === 0)) {
|
|
||||||
pkg = infoNPM(name);
|
|
||||||
var spec = name +(pkg ? "@"+pkg.version : "");
|
|
||||||
log.info("successfully installed: "+spec);
|
|
||||||
var meta = loadMetadata();
|
|
||||||
var item = {
|
|
||||||
name: name,
|
|
||||||
spec: module,
|
|
||||||
status: "installed",
|
|
||||||
};
|
|
||||||
meta.modules.push(item);
|
|
||||||
saveMetadata(meta);
|
|
||||||
clearInProgress(name);
|
|
||||||
resolve(true);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
clearInProgress(name);
|
|
||||||
var msg = "failed to install: "+name;
|
|
||||||
log.warn(msg);
|
|
||||||
reject(msg);
|
|
||||||
}
|
|
||||||
}).catch(e => {
|
|
||||||
clearInProgress(name);
|
|
||||||
var msg = "failed to install: "+name
|
|
||||||
log.warn(msg);
|
|
||||||
reject(msg);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var meta = loadMetadata();
|
|
||||||
if (!findModule(meta, name)) {
|
|
||||||
var item = {
|
|
||||||
name: name,
|
|
||||||
spec: module,
|
|
||||||
status: "preinstalled",
|
|
||||||
};
|
|
||||||
meta.modules.push(item);
|
|
||||||
saveMetadata(meta);
|
|
||||||
}
|
|
||||||
clearInProgress(name);
|
|
||||||
var spec = name +(pkg ? ("@"+pkg.version) : "");
|
|
||||||
log.info("already installed: "+spec);
|
|
||||||
}
|
|
||||||
resolve(true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check allowance of NPM module installation
|
|
||||||
* @param {string} name - module specification
|
|
||||||
*/
|
|
||||||
function isAllowed(name) {
|
|
||||||
if (!allowInstall) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var [module, ver] = moduleName(name);
|
|
||||||
var aList = util.parseModuleList(allowList);
|
|
||||||
var dList = util.parseModuleList(denyList);
|
|
||||||
return util.checkModuleAllowed(module, ver, aList, dList);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check and install NPM module according to dynamic module specification
|
|
||||||
* @param {Object} node - node object
|
|
||||||
*/
|
|
||||||
function checkInstall(node) {
|
|
||||||
var name = null;
|
|
||||||
if(moduleProp.hasOwnProperty(node.type)) {
|
|
||||||
name = moduleProp[node.type];
|
|
||||||
}
|
|
||||||
var promises = [];
|
|
||||||
if (name && node.hasOwnProperty(name)) {
|
|
||||||
var modules = node[name];
|
|
||||||
modules.forEach(module => {
|
|
||||||
var name = module;
|
|
||||||
if ((typeof module === "object") &&
|
|
||||||
module &&
|
|
||||||
module.hasOwnProperty("name")) {
|
|
||||||
name = module.name;
|
|
||||||
}
|
|
||||||
if (isAllowed(name)) {
|
|
||||||
var [n, v] = moduleName(name);
|
|
||||||
setInProgress(name);
|
|
||||||
promises.push(installNPM(name));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
log.info("installation not allowed: "+name);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return Promise.all(promises);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load NPM module
|
|
||||||
* @param {string} module - module to load
|
|
||||||
*/
|
|
||||||
function load(module) {
|
|
||||||
try {
|
|
||||||
var [name, ver] = moduleName(module);
|
|
||||||
var path = modulePath("node_modules", name);
|
|
||||||
var npm = require(path);
|
|
||||||
return npm;
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get list of installed modules
|
|
||||||
*/
|
|
||||||
function listModules() {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
var meta = loadMetadata();
|
|
||||||
var modules = meta.modules;
|
|
||||||
modules.forEach(item => {
|
|
||||||
var name = item.name;
|
|
||||||
var info = infoNPM(name);
|
|
||||||
if (info) {
|
|
||||||
item.version = info.version;
|
|
||||||
}
|
|
||||||
item.inProgress = ((name in inProgress) && inProgress[name]);
|
|
||||||
});
|
|
||||||
Object.keys(inProgress).forEach(name => {
|
|
||||||
if (inProgress[name] &&
|
|
||||||
!modules.find(item => (item.name === name))) {
|
|
||||||
modules.push({
|
|
||||||
name: name,
|
|
||||||
spec: name,
|
|
||||||
state: "inprogress",
|
|
||||||
inProgress: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
resolve(meta.modules);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uninstall NPM modules
|
|
||||||
*/
|
|
||||||
function uninstall(module) {
|
|
||||||
var [name, ver] = moduleName(module);
|
|
||||||
setInProgress(name);
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
var pkg = infoNPM(name);
|
|
||||||
var meta = loadMetadata();
|
|
||||||
var item = findModule(meta, name);
|
|
||||||
if (pkg && item) {
|
|
||||||
if (item.status === "preinstalled") {
|
|
||||||
clearInProgress(name);
|
|
||||||
var msg = "can't uninstall preinstalled: "+name;
|
|
||||||
log.warn(msg);
|
|
||||||
reject(msg);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var args = ["uninstall", module];
|
|
||||||
var dir = modulePath();
|
|
||||||
return exec.run(npmCommand, args, {
|
|
||||||
cwd: dir
|
|
||||||
}, true).then(result => {
|
|
||||||
if (result && (result.code === 0)) {
|
|
||||||
log.info("successfully uninstalled: "+name);
|
|
||||||
var meta = loadMetadata();
|
|
||||||
var items = meta.modules.filter(item => (item.name !== name));
|
|
||||||
meta.modules = items;
|
|
||||||
saveMetadata(meta);
|
|
||||||
clearInProgress(name);
|
|
||||||
resolve(true);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
clearInProgress(name);
|
|
||||||
var msg = "failed to uninstall: "+name;
|
|
||||||
log.warn(msg);
|
|
||||||
reject(msg);
|
|
||||||
}
|
|
||||||
}).catch(e => {
|
|
||||||
clearInProgress(name);
|
|
||||||
var msg = "failed to uninstall: "+name;
|
|
||||||
log.warn(msg);
|
|
||||||
reject(msg);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
clearInProgress(name);
|
|
||||||
var msg = "module not installed: "+name;
|
|
||||||
log.info(msg);
|
|
||||||
reject(msg);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update NPM modules
|
|
||||||
*/
|
|
||||||
function update(module, isUpdate) {
|
|
||||||
var act = (isUpdate ? "updated": "install")
|
|
||||||
var acted = (isUpdate ? "updated": "installed")
|
|
||||||
var [name, ver] = moduleName(module);
|
|
||||||
setInProgress(name);
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
var pkg = infoNPM(name);
|
|
||||||
if (!pkg || isUpdate) {
|
|
||||||
var args = ["install", module];
|
|
||||||
var dir = modulePath();
|
|
||||||
return exec.run(npmCommand, args, {
|
|
||||||
cwd: dir
|
|
||||||
}, true).then(result => {
|
|
||||||
if (result && (result.code === 0)) {
|
|
||||||
pkg = infoNPM(name);
|
|
||||||
var spec = name +(pkg ? "@"+pkg.version : "");
|
|
||||||
log.info("successfully "+acted+": "+spec);
|
|
||||||
var meta = loadMetadata();
|
|
||||||
var items = meta.modules.filter(item => (item.name !== name));
|
|
||||||
var item = {
|
|
||||||
name: name,
|
|
||||||
spec: module,
|
|
||||||
status: "installed",
|
|
||||||
};
|
|
||||||
items.push(item);
|
|
||||||
meta.modules = items;
|
|
||||||
saveMetadata(meta);
|
|
||||||
clearInProgress(name);
|
|
||||||
resolve(true);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
clearInProgress(name);
|
|
||||||
var msg = "failed to "+act+": "+name;
|
|
||||||
log.warn(msg);
|
|
||||||
reject(msg);
|
|
||||||
}
|
|
||||||
}).catch(e => {
|
|
||||||
clearInProgress(name);
|
|
||||||
var msg = "failed to "+act+": "+name;
|
|
||||||
log.warn(msg);
|
|
||||||
reject(msg);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
clearInProgress(name);
|
|
||||||
var msg = "not "+acted+": "+name;
|
|
||||||
log.info(msg);
|
|
||||||
reject(msg);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
api = {
|
|
||||||
init: init,
|
|
||||||
register: register,
|
|
||||||
checkInstall: checkInstall,
|
|
||||||
load: load,
|
|
||||||
list: listModules,
|
|
||||||
uninstall: uninstall,
|
|
||||||
update: update
|
|
||||||
};
|
|
||||||
module.exports = api;
|
|
20
test/unit/@node-red/registry/lib/externalModules_spec.js
Normal file
20
test/unit/@node-red/registry/lib/externalModules_spec.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// init: init,
|
||||||
|
// register: register,
|
||||||
|
// checkFlowDependencies: checkFlowDependencies,
|
||||||
|
// require: requireModule
|
||||||
|
//
|
||||||
|
|
||||||
|
const should = require("should");
|
||||||
|
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
var NR_TEST_UTILS = require("nr-test-utils");
|
||||||
|
|
||||||
|
var externalModules = NR_TEST_UTILS.require("@node-red/registry/lib/externalModules");
|
||||||
|
|
||||||
|
|
||||||
|
describe("externalModules api", function() {
|
||||||
|
|
||||||
|
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user