mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Merge pull request #2797 from node-red/externalModules
Add support for settings.externalModules
This commit is contained in:
commit
87c9ed6356
@ -50,13 +50,15 @@ module.exports = {
|
|||||||
// Nodes
|
// Nodes
|
||||||
adminApp.get("/nodes",needsPermission("nodes.read"),nodes.getAll,apiUtil.errorHandler);
|
adminApp.get("/nodes",needsPermission("nodes.read"),nodes.getAll,apiUtil.errorHandler);
|
||||||
|
|
||||||
if (!settings.editorTheme || !settings.editorTheme.palette || settings.editorTheme.palette.upload !== false) {
|
if (!settings.externalModules || !settings.externalModules.palette || settings.externalModules.palette.allowInstall !== false) {
|
||||||
|
if (!settings.externalModules || !settings.externalModules.palette || settings.externalModules.palette.allowUpload !== false) {
|
||||||
const multer = require('multer');
|
const multer = require('multer');
|
||||||
const upload = multer({ storage: multer.memoryStorage() });
|
const upload = multer({ storage: multer.memoryStorage() });
|
||||||
adminApp.post("/nodes",needsPermission("nodes.write"),upload.single("tarball"),nodes.post,apiUtil.errorHandler);
|
adminApp.post("/nodes",needsPermission("nodes.write"),upload.single("tarball"),nodes.post,apiUtil.errorHandler);
|
||||||
} else {
|
} else {
|
||||||
adminApp.post("/nodes",needsPermission("nodes.write"),nodes.post,apiUtil.errorHandler);
|
adminApp.post("/nodes",needsPermission("nodes.write"),nodes.post,apiUtil.errorHandler);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
adminApp.get(/^\/nodes\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalogs,apiUtil.errorHandler);
|
adminApp.get(/^\/nodes\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalogs,apiUtil.errorHandler);
|
||||||
adminApp.get(/^\/nodes\/((@[^\/]+\/)?[^\/]+\/[^\/]+)\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalog,apiUtil.errorHandler);
|
adminApp.get(/^\/nodes\/((@[^\/]+\/)?[^\/]+\/[^\/]+)\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalog,apiUtil.errorHandler);
|
||||||
adminApp.get(/^\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.read"),nodes.getModule,apiUtil.errorHandler);
|
adminApp.get(/^\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.read"),nodes.getModule,apiUtil.errorHandler);
|
||||||
|
@ -269,7 +269,7 @@ var RED = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
// } else if (RED.settings.theme('palette.editable') !== false) {
|
// } else if (RED.settings.get('externalModules.palette.allowInstall', true) !== false) {
|
||||||
} else {
|
} else {
|
||||||
options.buttons = [
|
options.buttons = [
|
||||||
{
|
{
|
||||||
@ -509,7 +509,7 @@ var RED = (function() {
|
|||||||
]});
|
]});
|
||||||
|
|
||||||
menuOptions.push(null);
|
menuOptions.push(null);
|
||||||
if (RED.settings.theme('palette.editable') !== false) {
|
if (RED.settings.get('externalModules.palette.allowInstall', true) !== false) {
|
||||||
menuOptions.push({id:"menu-item-edit-palette",label:RED._("menu.label.editPalette"),onselect:"core:manage-palette"});
|
menuOptions.push({id:"menu-item-edit-palette",label:RED._("menu.label.editPalette"),onselect:"core:manage-palette"});
|
||||||
menuOptions.push(null);
|
menuOptions.push(null);
|
||||||
}
|
}
|
||||||
@ -544,7 +544,7 @@ var RED = (function() {
|
|||||||
RED.palette.init();
|
RED.palette.init();
|
||||||
RED.eventLog.init();
|
RED.eventLog.init();
|
||||||
|
|
||||||
if (RED.settings.theme('palette.editable') !== false) {
|
if (RED.settings.get('externalModules.palette.allowInstall', true) !== false) {
|
||||||
RED.palette.editor.init();
|
RED.palette.editor.init();
|
||||||
} else {
|
} else {
|
||||||
console.log("Palette editor disabled");
|
console.log("Palette editor disabled");
|
||||||
|
@ -57,12 +57,11 @@ RED.settings = (function () {
|
|||||||
return JSON.parse(localStorage.getItem(key));
|
return JSON.parse(localStorage.getItem(key));
|
||||||
} else {
|
} else {
|
||||||
var v;
|
var v;
|
||||||
try {
|
try { v = RED.utils.getMessageProperty(userSettings,key); } catch(err) {}
|
||||||
v = RED.utils.getMessageProperty(userSettings,key);
|
|
||||||
if (v === undefined) {
|
if (v === undefined) {
|
||||||
v = defaultIfUndefined;
|
try { v = RED.utils.getMessageProperty(RED.settings,key); } catch(err) {}
|
||||||
}
|
}
|
||||||
} catch(err) {
|
if (v === undefined) {
|
||||||
v = defaultIfUndefined;
|
v = defaultIfUndefined;
|
||||||
}
|
}
|
||||||
return v;
|
return v;
|
||||||
|
@ -329,7 +329,9 @@ RED.palette.editor = (function() {
|
|||||||
catalogueLoadStatus.push(err||v);
|
catalogueLoadStatus.push(err||v);
|
||||||
if (!err) {
|
if (!err) {
|
||||||
if (v.modules) {
|
if (v.modules) {
|
||||||
v.modules.forEach(function(m) {
|
var a = false;
|
||||||
|
v.modules = v.modules.filter(function(m) {
|
||||||
|
if (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) {
|
||||||
@ -344,6 +346,9 @@ RED.palette.editor = (function() {
|
|||||||
m.timestamp = 0;
|
m.timestamp = 0;
|
||||||
}
|
}
|
||||||
m.index = m.index.join(",").toLowerCase();
|
m.index = m.index.join(",").toLowerCase();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
})
|
})
|
||||||
loadedList = loadedList.concat(v.modules);
|
loadedList = loadedList.concat(v.modules);
|
||||||
}
|
}
|
||||||
@ -437,11 +442,84 @@ RED.palette.editor = (function() {
|
|||||||
return -1 * (A.info.timestamp-B.info.timestamp);
|
return -1 * (A.info.timestamp-B.info.timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var installAllowList = ['*'];
|
||||||
|
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.theme('palette.editable') === false) {
|
if (RED.settings.get('externalModules.palette.allowInstall', true) === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
var settingsAllowList = RED.settings.get("externalModules.palette.allowList")
|
||||||
|
var settingsDenyList = RED.settings.get("externalModules.palette.denyList")
|
||||||
|
if (settingsAllowList || settingsDenyList) {
|
||||||
|
installAllowList = settingsAllowList;
|
||||||
|
installDenyList = settingsDenyList
|
||||||
|
}
|
||||||
|
installAllowList = parseModuleList(installAllowList);
|
||||||
|
installDenyList = parseModuleList(installDenyList);
|
||||||
|
|
||||||
createSettingsPane();
|
createSettingsPane();
|
||||||
|
|
||||||
RED.userSettings.add({
|
RED.userSettings.add({
|
||||||
@ -880,7 +958,7 @@ RED.palette.editor = (function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (RED.settings.theme('palette.upload') !== false) {
|
if (RED.settings.get('externalModules.palette.allowUpload', true) !== false) {
|
||||||
var uploadSpan = $('<span class="button-group">').prependTo(toolBar);
|
var uploadSpan = $('<span class="button-group">').prependTo(toolBar);
|
||||||
var uploadButton = $('<button type="button" class="red-ui-sidebar-header-button red-ui-palette-editor-upload-button"><label><i class="fa fa-upload"></i><form id="red-ui-palette-editor-upload-form" enctype="multipart/form-data"><input name="tarball" type="file" accept=".tgz"></label></button>').appendTo(uploadSpan);
|
var uploadButton = $('<button type="button" class="red-ui-sidebar-header-button red-ui-palette-editor-upload-button"><label><i class="fa fa-upload"></i><form id="red-ui-palette-editor-upload-form" enctype="multipart/form-data"><input name="tarball" type="file" accept=".tgz"></label></button>').appendTo(uploadSpan);
|
||||||
|
|
||||||
@ -962,7 +1040,7 @@ RED.palette.editor = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function update(entry,version,url,container,done) {
|
function update(entry,version,url,container,done) {
|
||||||
if (RED.settings.theme('palette.editable') === false) {
|
if (RED.settings.get('externalModules.palette.allowInstall', true) === false) {
|
||||||
done(new Error('Palette not editable'));
|
done(new Error('Palette not editable'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1021,7 +1099,7 @@ RED.palette.editor = (function() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
function remove(entry,container,done) {
|
function remove(entry,container,done) {
|
||||||
if (RED.settings.theme('palette.editable') === false) {
|
if (RED.settings.get('externalModules.palette.allowInstall', true) === false) {
|
||||||
done(new Error('Palette not editable'));
|
done(new Error('Palette not editable'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1078,7 +1156,7 @@ RED.palette.editor = (function() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
function install(entry,container,done) {
|
function install(entry,container,done) {
|
||||||
if (RED.settings.theme('palette.editable') === false) {
|
if (RED.settings.get('externalModules.palette.allowInstall', true) === false) {
|
||||||
done(new Error('Palette not editable'));
|
done(new Error('Palette not editable'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -465,7 +465,7 @@ RED.projects.settings = (function() {
|
|||||||
metaRow = $('<div class="red-ui-palette-module-meta"></div>').appendTo(headerRow);
|
metaRow = $('<div class="red-ui-palette-module-meta"></div>').appendTo(headerRow);
|
||||||
var buttons = $('<div class="red-ui-palette-module-button-group"></div>').appendTo(metaRow);
|
var buttons = $('<div class="red-ui-palette-module-button-group"></div>').appendTo(metaRow);
|
||||||
if (RED.user.hasPermission("projects.write")) {
|
if (RED.user.hasPermission("projects.write")) {
|
||||||
if (!entry.installed && RED.settings.theme('palette.editable') !== false) {
|
if (!entry.installed && RED.settings.get('externalModules.palette.allowInstall', true) !== false) {
|
||||||
$('<a href="#" class="red-ui-button red-ui-button-small">' + RED._("sidebar.project.projectSettings.install") + '</a>').appendTo(buttons)
|
$('<a href="#" class="red-ui-button red-ui-button-small">' + RED._("sidebar.project.projectSettings.install") + '</a>').appendTo(buttons)
|
||||||
.on("click", function(evt) {
|
.on("click", function(evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
|
@ -254,7 +254,8 @@ module.exports = {
|
|||||||
* Update to internal list of available modules based on what has been actually
|
* Update to internal list of available modules based on what has been actually
|
||||||
* loaded.
|
* loaded.
|
||||||
*
|
*
|
||||||
* The `autoInstallModules` runtime option means the runtime may try to install
|
* The `externalModules.autoInstall` (previously `autoInstallModules`)
|
||||||
|
* runtime option means the runtime may try to install
|
||||||
* missing modules after the initial load is complete. If that flag is not set
|
* missing modules after the initial load is complete. If that flag is not set
|
||||||
* this function is used to remove the modules from the registry's saved list.
|
* this function is used to remove the modules from the registry's saved list.
|
||||||
* @function
|
* @function
|
||||||
|
@ -15,26 +15,55 @@
|
|||||||
**/
|
**/
|
||||||
|
|
||||||
|
|
||||||
var path = require("path");
|
const path = require("path");
|
||||||
var os = require("os");
|
const os = require("os");
|
||||||
var fs = require("fs-extra");
|
const fs = require("fs-extra");
|
||||||
var tar = require("tar");
|
const tar = require("tar");
|
||||||
|
|
||||||
var registry = require("./registry");
|
const registry = require("./registry");
|
||||||
var library = require("./library");
|
const registryUtil = require("./util");
|
||||||
|
const library = require("./library");
|
||||||
const {exec,log,events} = require("@node-red/util");
|
const {exec,log,events} = require("@node-red/util");
|
||||||
var child_process = require('child_process');
|
const child_process = require('child_process');
|
||||||
var npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
||||||
var installerEnabled = false;
|
let installerEnabled = false;
|
||||||
|
|
||||||
var settings;
|
let settings;
|
||||||
const moduleRe = /^(@[^/@]+?[/])?[^/@]+?$/;
|
const moduleRe = /^(@[^/@]+?[/])?[^/@]+?$/;
|
||||||
const slashRe = process.platform === "win32" ? /\\|[/]/ : /[/]/;
|
const slashRe = process.platform === "win32" ? /\\|[/]/ : /[/]/;
|
||||||
const pkgurlRe = /^(https?|git(|\+https?|\+ssh|\+file)):\/\//;
|
const pkgurlRe = /^(https?|git(|\+https?|\+ssh|\+file)):\/\//;
|
||||||
const localtgzRe = /^([a-zA-Z]:|\/).+tgz$/;
|
const localtgzRe = /^([a-zA-Z]:|\/).+tgz$/;
|
||||||
|
|
||||||
|
// Default allow/deny lists
|
||||||
|
let installAllowList = ['*'];
|
||||||
|
let installDenyList = [];
|
||||||
|
let installAllAllowed = true;
|
||||||
|
let installVersionRestricted = false;
|
||||||
|
|
||||||
function init(_settings) {
|
function init(_settings) {
|
||||||
settings = _settings;
|
settings = _settings;
|
||||||
|
// TODO: This is duplicated in localfilesystem.js
|
||||||
|
// Should it *all* be managed by util?
|
||||||
|
if (settings.externalModules && settings.externalModules.palette) {
|
||||||
|
if (settings.externalModules.palette.allowList || settings.externalModules.palette.denyList) {
|
||||||
|
installAllowList = settings.externalModules.palette.allowList;
|
||||||
|
installDenyList = settings.externalModules.palette.denyList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
installAllowList = registryUtil.parseModuleList(installAllowList);
|
||||||
|
installDenyList = registryUtil.parseModuleList(installDenyList);
|
||||||
|
installAllAllowed = installDenyList.length === 0;
|
||||||
|
if (!installAllAllowed) {
|
||||||
|
installAllowList.forEach(function(rule) {
|
||||||
|
installVersionRestricted = installVersionRestricted || (!!rule.version);
|
||||||
|
})
|
||||||
|
if (!installVersionRestricted) {
|
||||||
|
installDenyList.forEach(function(rule) {
|
||||||
|
installVersionRestricted = installVersionRestricted || (!!rule.version);
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var activePromise = Promise.resolve();
|
var activePromise = Promise.resolve();
|
||||||
@ -79,27 +108,26 @@ function checkExistingModule(module,version) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function installModule(module,version,url) {
|
async function installModule(module,version,url) {
|
||||||
if (Buffer.isBuffer(module)) {
|
if (Buffer.isBuffer(module)) {
|
||||||
return installTarball(module)
|
return installTarball(module)
|
||||||
}
|
}
|
||||||
module = module || "";
|
module = module || "";
|
||||||
activePromise = activePromise.then(() => {
|
activePromise = activePromise.then(async function() {
|
||||||
//TODO: ensure module is 'safe'
|
//TODO: ensure module is 'safe'
|
||||||
return new Promise((resolve,reject) => {
|
|
||||||
var installName = module;
|
var installName = module;
|
||||||
|
let isRegistryPackage = true;
|
||||||
var isUpgrade = false;
|
var isUpgrade = false;
|
||||||
try {
|
|
||||||
if (url) {
|
if (url) {
|
||||||
if (pkgurlRe.test(url) || localtgzRe.test(url)) {
|
if (pkgurlRe.test(url) || localtgzRe.test(url)) {
|
||||||
// Git remote url or Tarball url - check the valid package url
|
// Git remote url or Tarball url - check the valid package url
|
||||||
installName = url;
|
installName = url;
|
||||||
|
isRegistryPackage = false;
|
||||||
} else {
|
} else {
|
||||||
log.warn(log._("server.install.install-failed-url",{name:module,url:url}));
|
log.warn(log._("server.install.install-failed-url",{name:module,url:url}));
|
||||||
const e = new Error("Invalid url");
|
const e = new Error("Invalid url");
|
||||||
e.code = "invalid_module_url";
|
e.code = "invalid_module_url";
|
||||||
reject(e);
|
throw e;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
} else if (moduleRe.test(module)) {
|
} else if (moduleRe.test(module)) {
|
||||||
// Simple module name - assume it can be npm installed
|
// Simple module name - assume it can be npm installed
|
||||||
@ -111,17 +139,27 @@ function installModule(module,version,url) {
|
|||||||
installName = module;
|
installName = module;
|
||||||
let info = checkModulePath(module);
|
let info = checkModulePath(module);
|
||||||
module = info.name;
|
module = info.name;
|
||||||
|
isRegistryPackage = false;
|
||||||
} else {
|
} else {
|
||||||
log.warn(log._("server.install.install-failed-name",{name:module}));
|
log.warn(log._("server.install.install-failed-name",{name:module}));
|
||||||
const e = new Error("Invalid module name");
|
const e = new Error("Invalid module name");
|
||||||
e.code = "invalid_module_name";
|
e.code = "invalid_module_name";
|
||||||
reject(e);
|
throw e;
|
||||||
return;
|
}
|
||||||
|
if (!installAllAllowed) {
|
||||||
|
let installVersion = version;
|
||||||
|
if (installVersionRestricted && isRegistryPackage) {
|
||||||
|
installVersion = await getModuleVersionFromNPM(module, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!registryUtil.checkModuleAllowed(module,installVersion,installAllowList,installDenyList)) {
|
||||||
|
const e = new Error("Install not allowed");
|
||||||
|
e.code = "install_not_allowed";
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
isUpgrade = checkExistingModule(module,version);
|
isUpgrade = checkExistingModule(module,version);
|
||||||
} catch(err) {
|
|
||||||
return reject(err);
|
|
||||||
}
|
|
||||||
if (!isUpgrade) {
|
if (!isUpgrade) {
|
||||||
log.info(log._("server.install.installing",{name: module,version: version||"latest"}));
|
log.info(log._("server.install.installing",{name: module,version: version||"latest"}));
|
||||||
} else {
|
} else {
|
||||||
@ -131,16 +169,16 @@ function installModule(module,version,url) {
|
|||||||
var installDir = settings.userDir || process.env.NODE_RED_HOME || ".";
|
var installDir = settings.userDir || process.env.NODE_RED_HOME || ".";
|
||||||
var args = ['install','--no-audit','--no-update-notifier','--no-fund','--save','--save-prefix=~','--production',installName];
|
var args = ['install','--no-audit','--no-update-notifier','--no-fund','--save','--save-prefix=~','--production',installName];
|
||||||
log.trace(npmCommand + JSON.stringify(args));
|
log.trace(npmCommand + JSON.stringify(args));
|
||||||
exec.run(npmCommand,args,{
|
return exec.run(npmCommand,args,{
|
||||||
cwd: installDir
|
cwd: installDir
|
||||||
}, true).then(result => {
|
}, true).then(result => {
|
||||||
if (!isUpgrade) {
|
if (!isUpgrade) {
|
||||||
log.info(log._("server.install.installed",{name:module}));
|
log.info(log._("server.install.installed",{name:module}));
|
||||||
resolve(require("./index").addModule(module).then(reportAddedModules));
|
return require("./index").addModule(module).then(reportAddedModules);
|
||||||
} else {
|
} else {
|
||||||
log.info(log._("server.install.upgraded",{name:module, version:version}));
|
log.info(log._("server.install.upgraded",{name:module, version:version}));
|
||||||
events.emit("runtime-event",{id:"restart-required",payload:{type:"warning",text:"notification.warnings.restartRequired"},retain:true});
|
events.emit("runtime-event",{id:"restart-required",payload:{type:"warning",text:"notification.warnings.restartRequired"},retain:true});
|
||||||
resolve(require("./registry").setModulePendingUpdated(module,version));
|
return require("./registry").setModulePendingUpdated(module,version);
|
||||||
}
|
}
|
||||||
}).catch(result => {
|
}).catch(result => {
|
||||||
var output = result.stderr;
|
var output = result.stderr;
|
||||||
@ -151,21 +189,20 @@ function installModule(module,version,url) {
|
|||||||
log.warn(log._("server.install.install-failed-not-found",{name:module}));
|
log.warn(log._("server.install.install-failed-not-found",{name:module}));
|
||||||
e = new Error("Module not found");
|
e = new Error("Module not found");
|
||||||
e.code = 404;
|
e.code = 404;
|
||||||
reject(e);
|
throw e;
|
||||||
} else if (isUpgrade && lookForVersionNotFound.test(output)) {
|
} else if (isUpgrade && lookForVersionNotFound.test(output)) {
|
||||||
log.warn(log._("server.install.upgrade-failed-not-found",{name:module}));
|
log.warn(log._("server.install.upgrade-failed-not-found",{name:module}));
|
||||||
e = new Error("Module not found");
|
e = new Error("Module not found");
|
||||||
e.code = 404;
|
e.code = 404;
|
||||||
reject(e);
|
throw e;
|
||||||
} else {
|
} else {
|
||||||
log.warn(log._("server.install.install-failed-long",{name:module}));
|
log.warn(log._("server.install.install-failed-long",{name:module}));
|
||||||
log.warn("------------------------------------------");
|
log.warn("------------------------------------------");
|
||||||
log.warn(output);
|
log.warn(output);
|
||||||
log.warn("------------------------------------------");
|
log.warn("------------------------------------------");
|
||||||
reject(new Error(log._("server.install.install-failed")));
|
throw new Error(log._("server.install.install-failed"));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
// In case of error, reset activePromise to be resolvable
|
// In case of error, reset activePromise to be resolvable
|
||||||
activePromise = Promise.resolve();
|
activePromise = Promise.resolve();
|
||||||
@ -214,7 +251,63 @@ async function getExistingPackageVersion(moduleName) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getModuleVersionFromNPM(module, version) {
|
||||||
|
let installName = module;
|
||||||
|
if (version) {
|
||||||
|
installName += "@" + version;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
child_process.execFile(npmCommand,['info','--json',installName],function(err,stdout,stderr) {
|
||||||
|
try {
|
||||||
|
if (!stdout) {
|
||||||
|
log.warn(log._("server.install.install-failed-not-found",{name:module}));
|
||||||
|
e = new Error("Version not found");
|
||||||
|
e.code = 404;
|
||||||
|
reject(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const response = JSON.parse(stdout);
|
||||||
|
if (response.error) {
|
||||||
|
if (response.error.code === "E404") {
|
||||||
|
log.warn(log._("server.install.install-failed-not-found",{name:module}));
|
||||||
|
e = new Error("Module not found");
|
||||||
|
e.code = 404;
|
||||||
|
reject(e);
|
||||||
|
} else {
|
||||||
|
log.warn(log._("server.install.install-failed-long",{name:module}));
|
||||||
|
log.warn("------------------------------------------");
|
||||||
|
log.warn(response.error.summary);
|
||||||
|
log.warn("------------------------------------------");
|
||||||
|
reject(new Error(log._("server.install.install-failed")));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
resolve(response.version);
|
||||||
|
}
|
||||||
|
} catch(err) {
|
||||||
|
log.warn(log._("server.install.install-failed-long",{name:module}));
|
||||||
|
log.warn("------------------------------------------");
|
||||||
|
if (stdout) {
|
||||||
|
log.warn(stdout);
|
||||||
|
}
|
||||||
|
if (stderr) {
|
||||||
|
log.warn(stderr);
|
||||||
|
}
|
||||||
|
log.warn(err);
|
||||||
|
log.warn("------------------------------------------");
|
||||||
|
reject(new Error(log._("server.install.install-failed")));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async function installTarball(tarball) {
|
async function installTarball(tarball) {
|
||||||
|
if (settings.externalModules && settings.externalModules.palette && settings.externalModules.palette.allowUpload === false) {
|
||||||
|
throw new Error("Module upload disabled")
|
||||||
|
}
|
||||||
|
|
||||||
// Check this tarball contains a valid node-red module.
|
// Check this tarball contains a valid node-red module.
|
||||||
// Get its module name/version
|
// Get its module name/version
|
||||||
const moduleInfo = await getTarballModuleInfo(tarball);
|
const moduleInfo = await getTarballModuleInfo(tarball);
|
||||||
@ -335,7 +428,32 @@ function uninstallModule(module) {
|
|||||||
return activePromise;
|
return activePromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkPrereq() {
|
async function checkPrereq() {
|
||||||
|
if (settings.editorTheme && settings.editorTheme.palette) {
|
||||||
|
if (settings.editorTheme.palette.hasOwnProperty("editable")) {
|
||||||
|
log.warn(log._("server.deprecatedOption",{old:"editorTheme.palette.editable", new:"externalModules.palette.allowInstall"}));
|
||||||
|
}
|
||||||
|
if (settings.editorTheme.palette.hasOwnProperty("upload")) {
|
||||||
|
log.warn(log._("server.deprecatedOption",{old:"editorTheme.palette.upload", new:"externalModules.palette.allowUpload"}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (settings.editorTheme.palette.editable === false) {
|
||||||
|
log.info(log._("server.palette-editor.disabled"));
|
||||||
|
installerEnabled = false;
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch(err) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (settings.externalModules.palette.allowInstall === false) {
|
||||||
|
log.info(log._("server.palette-editor.disabled"));
|
||||||
|
installerEnabled = false;
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch(err) {}
|
||||||
|
|
||||||
if (settings.hasOwnProperty('editorTheme') &&
|
if (settings.hasOwnProperty('editorTheme') &&
|
||||||
settings.editorTheme.hasOwnProperty('palette') &&
|
settings.editorTheme.hasOwnProperty('palette') &&
|
||||||
settings.editorTheme.palette.hasOwnProperty('editable') &&
|
settings.editorTheme.palette.hasOwnProperty('editable') &&
|
||||||
@ -343,7 +461,6 @@ function checkPrereq() {
|
|||||||
) {
|
) {
|
||||||
log.info(log._("server.palette-editor.disabled"));
|
log.info(log._("server.palette-editor.disabled"));
|
||||||
installerEnabled = false;
|
installerEnabled = false;
|
||||||
return Promise.resolve();
|
|
||||||
} else {
|
} else {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
child_process.execFile(npmCommand,['-v'],function(err,stdout) {
|
child_process.execFile(npmCommand,['-v'],function(err,stdout) {
|
||||||
|
@ -14,10 +14,16 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
var fs = require("fs");
|
const fs = require("fs");
|
||||||
var path = require("path");
|
const path = require("path");
|
||||||
var log = require("@node-red/util").log;
|
const log = require("@node-red/util").log;
|
||||||
var i18n = require("@node-red/util").i18n;
|
const i18n = require("@node-red/util").i18n;
|
||||||
|
const registryUtil = require("./util");
|
||||||
|
|
||||||
|
// Default allow/deny lists
|
||||||
|
let loadAllowList = ['*'];
|
||||||
|
let loadDenyList = [];
|
||||||
|
|
||||||
|
|
||||||
var settings;
|
var settings;
|
||||||
var disableNodePathScan = false;
|
var disableNodePathScan = false;
|
||||||
@ -25,6 +31,16 @@ var iconFileExtensions = [".png", ".gif", ".svg"];
|
|||||||
|
|
||||||
function init(_settings) {
|
function init(_settings) {
|
||||||
settings = _settings;
|
settings = _settings;
|
||||||
|
// TODO: This is duplicated in installer.js
|
||||||
|
// Should it *all* be managed by util?
|
||||||
|
if (settings.externalModules && settings.externalModules.palette) {
|
||||||
|
if (settings.externalModules.palette.allowList || settings.externalModules.palette.denyList) {
|
||||||
|
loadAllowList = settings.externalModules.palette.allowList;
|
||||||
|
loadDenyList = settings.externalModules.palette.denyList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadAllowList = registryUtil.parseModuleList(loadAllowList);
|
||||||
|
loadDenyList = registryUtil.parseModuleList(loadDenyList);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isIncluded(name) {
|
function isIncluded(name) {
|
||||||
@ -137,9 +153,13 @@ function scanDirForNodesModules(dir,moduleName) {
|
|||||||
try {
|
try {
|
||||||
var pkg = require(pkgfn);
|
var pkg = require(pkgfn);
|
||||||
if (pkg['node-red']) {
|
if (pkg['node-red']) {
|
||||||
|
if (!registryUtil.checkModuleAllowed(pkg.name,pkg.version,loadAllowList,loadDenyList)) {
|
||||||
|
log.debug("! Module: "+pkg.name+" "+pkg.version+ " *ignored due to denyList*");
|
||||||
|
} else {
|
||||||
var moduleDir = path.join(dir,fn);
|
var moduleDir = path.join(dir,fn);
|
||||||
results.push({dir:moduleDir,package:pkg});
|
results.push({dir:moduleDir,package:pkg});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
if (err.code != "MODULE_NOT_FOUND") {
|
if (err.code != "MODULE_NOT_FOUND") {
|
||||||
// TODO: handle unexpected error
|
// TODO: handle unexpected error
|
||||||
@ -308,8 +328,7 @@ function getNodeFiles(disableNodePathScan) {
|
|||||||
} else {
|
} else {
|
||||||
result = false;
|
result = false;
|
||||||
}
|
}
|
||||||
log.debug("Module: "+mod.package.name+" "+mod.package.version+(result?"":" *ignored due to local copy*"));
|
log.debug((result?"":"! ")+"Module: "+mod.package.name+" "+mod.package.version+" "+mod.dir+(result?"":" *ignored due to local copy*"));
|
||||||
log.debug(" "+mod.dir);
|
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
**/
|
**/
|
||||||
|
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
const semver = require("semver");
|
||||||
const {events,i18n,log} = require("@node-red/util");
|
const {events,i18n,log} = require("@node-red/util");
|
||||||
var runtime;
|
var runtime;
|
||||||
|
|
||||||
@ -104,9 +105,78 @@ function createNodeApi(node) {
|
|||||||
return red;
|
return red;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function checkAgainstList(module,version,list) {
|
||||||
|
for (let i=0;i<list.length;i++) {
|
||||||
|
let rule = list[i];
|
||||||
|
if (rule.module.test(module)) {
|
||||||
|
if (version && rule.version) {
|
||||||
|
if (semver.satisfies(version,rule.version)) {
|
||||||
|
return rule;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return rule;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkModuleAllowed(module,version,allowList,denyList) {
|
||||||
|
// console.log("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 parseModuleList(list) {
|
||||||
|
list = list || ["*"];
|
||||||
|
return list.map(rule => {
|
||||||
|
let m = /^(.+?)(?:@(.*))?$/.exec(rule);
|
||||||
|
let wildcardPos = m[1].indexOf("*");
|
||||||
|
wildcardPos = wildcardPos===-1?Infinity:wildcardPos;
|
||||||
|
|
||||||
|
return {
|
||||||
|
module: new RegExp("^"+m[1].replace(/\*/g,".*")+"$"),
|
||||||
|
version: m[2],
|
||||||
|
wildcardPos: wildcardPos
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
init: function(_runtime) {
|
init: function(_runtime) {
|
||||||
runtime = _runtime;
|
runtime = _runtime;
|
||||||
},
|
},
|
||||||
createNodeApi: createNodeApi
|
createNodeApi: createNodeApi,
|
||||||
|
parseModuleList: parseModuleList,
|
||||||
|
checkModuleAllowed: checkModuleAllowed
|
||||||
}
|
}
|
||||||
|
@ -164,7 +164,7 @@ var api = module.exports = {
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
if (opts.tarball) {
|
if (opts.tarball) {
|
||||||
if (runtime.settings.editorTheme && runtime.settings.editorTheme.palette && runtime.settings.editorTheme.palette.upload === false) {
|
if (runtime.settings.externalModules && runtime.settings.externalModules.palette && runtime.settings.externalModules.palette.upload === false) {
|
||||||
runtime.log.audit({event: "nodes.install",tarball:opts.tarball.file,error:"invalid_request"}, opts.req);
|
runtime.log.audit({event: "nodes.install",tarball:opts.tarball.file,error:"invalid_request"}, opts.req);
|
||||||
var err = new Error("Invalid request");
|
var err = new Error("Invalid request");
|
||||||
err.code = "invalid_request";
|
err.code = "invalid_request";
|
||||||
@ -406,10 +406,12 @@ var api = module.exports = {
|
|||||||
var lang = opts.lang;
|
var lang = opts.lang;
|
||||||
var prevLang = runtime.i18n.i.language;
|
var prevLang = runtime.i18n.i.language;
|
||||||
// Trigger a load from disk of the language if it is not the default
|
// Trigger a load from disk of the language if it is not the default
|
||||||
return runtime.i18n.i.changeLanguage(lang, function(){
|
return new Promise(resolve => {
|
||||||
|
runtime.i18n.i.changeLanguage(lang, function() {
|
||||||
var catalog = runtime.i18n.i.getResourceBundle(lang, namespace);
|
var catalog = runtime.i18n.i.getResourceBundle(lang, namespace);
|
||||||
runtime.i18n.i.changeLanguage(prevLang);
|
runtime.i18n.i.changeLanguage(prevLang);
|
||||||
return catalog||{};
|
resolve(catalog||{});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -90,10 +90,28 @@ var api = module.exports = {
|
|||||||
safeSettings.flowFilePretty = runtime.settings.flowFilePretty;
|
safeSettings.flowFilePretty = runtime.settings.flowFilePretty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (runtime.settings.editorTheme && runtime.settings.editorTheme.palette) {
|
||||||
|
if (runtime.settings.editorTheme.palette.upload === false || runtime.settings.editorTheme.palette.editable === false) {
|
||||||
|
safeSettings.externalModules = {palette: { } }
|
||||||
|
}
|
||||||
|
if (runtime.settings.editorTheme.palette.upload === false) {
|
||||||
|
safeSettings.externalModules.palette.allowUpload = false;
|
||||||
|
}
|
||||||
|
if (runtime.settings.editorTheme.palette.editable === false) {
|
||||||
|
safeSettings.externalModules.palette.allowInstall = false;
|
||||||
|
safeSettings.externalModules.palette.allowUpload = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runtime.settings.externalModules) {
|
||||||
|
safeSettings.externalModules = extend(safeSettings.externalModules||{},runtime.settings.externalModules);
|
||||||
|
}
|
||||||
|
|
||||||
if (!runtime.nodes.installerEnabled()) {
|
if (!runtime.nodes.installerEnabled()) {
|
||||||
safeSettings.editorTheme = safeSettings.editorTheme || {};
|
safeSettings.externalModules = safeSettings.externalModules || {};
|
||||||
safeSettings.editorTheme.palette = safeSettings.editorTheme.palette || {};
|
safeSettings.externalModules.palette = safeSettings.externalModules.palette || {};
|
||||||
safeSettings.editorTheme.palette.editable = false;
|
safeSettings.externalModules.palette.allowInstall = false;
|
||||||
|
safeSettings.externalModules.palette.allowUpload = false;
|
||||||
}
|
}
|
||||||
if (runtime.storage.projects) {
|
if (runtime.storage.projects) {
|
||||||
var activeProject = runtime.storage.projects.getActiveProject();
|
var activeProject = runtime.storage.projects.getActiveProject();
|
||||||
|
@ -123,7 +123,15 @@ function start() {
|
|||||||
}
|
}
|
||||||
log.info(os.type()+" "+os.release()+" "+os.arch()+" "+os.endianness());
|
log.info(os.type()+" "+os.release()+" "+os.arch()+" "+os.endianness());
|
||||||
return redNodes.load().then(function() {
|
return redNodes.load().then(function() {
|
||||||
|
let autoInstallModules = false;
|
||||||
|
if (settings.hasOwnProperty('autoInstallModules')) {
|
||||||
|
log.warn(log._("server.deprecatedOption",{old:"autoInstallModules", new:"externalModules.autoInstall"}));
|
||||||
|
autoInstallModules = true;
|
||||||
|
}
|
||||||
|
if (settings.externalModules) {
|
||||||
|
// autoInstallModules = autoInstall enabled && (no palette setting || palette install not disabled)
|
||||||
|
autoInstallModules = settings.externalModules.autoInstall && (!settings.externalModules.palette || settings.externalModules.palette.allowInstall !== false) ;
|
||||||
|
}
|
||||||
var i;
|
var i;
|
||||||
var nodeErrors = redNodes.getNodeList(function(n) { return n.err!=null;});
|
var nodeErrors = redNodes.getNodeList(function(n) { return n.err!=null;});
|
||||||
var nodeMissing = redNodes.getNodeList(function(n) { return n.module && n.enabled && !n.loaded && !n.err;});
|
var nodeMissing = redNodes.getNodeList(function(n) { return n.module && n.enabled && !n.loaded && !n.err;});
|
||||||
@ -156,12 +164,12 @@ function start() {
|
|||||||
for (i in missingModules) {
|
for (i in missingModules) {
|
||||||
if (missingModules.hasOwnProperty(i)) {
|
if (missingModules.hasOwnProperty(i)) {
|
||||||
log.warn(" - "+i+" ("+missingModules[i].version+"): "+missingModules[i].types.join(", "));
|
log.warn(" - "+i+" ("+missingModules[i].version+"): "+missingModules[i].types.join(", "));
|
||||||
if (settings.autoInstallModules && i != "node-red") {
|
if (autoInstallModules && i != "node-red") {
|
||||||
installingModules.push({id:i,version:missingModules[i].version});
|
installingModules.push({id:i,version:missingModules[i].version});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!settings.autoInstallModules) {
|
if (!autoInstallModules) {
|
||||||
log.info(log._("server.removing-modules"));
|
log.info(log._("server.removing-modules"));
|
||||||
redNodes.cleanModuleList();
|
redNodes.cleanModuleList();
|
||||||
} else if (installingModules.length > 0) {
|
} else if (installingModules.length > 0) {
|
||||||
@ -186,11 +194,18 @@ function start() {
|
|||||||
var reinstallAttempts = 0;
|
var reinstallAttempts = 0;
|
||||||
var reinstallTimeout;
|
var reinstallTimeout;
|
||||||
function reinstallModules(moduleList) {
|
function reinstallModules(moduleList) {
|
||||||
var promises = [];
|
const promises = [];
|
||||||
var reinstallList = [];
|
const reinstallList = [];
|
||||||
|
const installRetry = 30000;
|
||||||
|
if (settings.hasOwnProperty('autoInstallModulesRetry')) {
|
||||||
|
log.warn(log._("server.deprecatedOption",{old:"autoInstallModulesRetry", new:"externalModules.autoInstallRetry"}));
|
||||||
|
installRetry = settings.autoInstallModulesRetry;
|
||||||
|
}
|
||||||
|
if (settings.externalModules && settings.externalModules.hasOwnProperty('autoInstallRetry')) {
|
||||||
|
installRetry = settings.externalModules.autoInstallRetry * 1000;
|
||||||
|
}
|
||||||
for (var i=0;i<moduleList.length;i++) {
|
for (var i=0;i<moduleList.length;i++) {
|
||||||
if (settings.autoInstallModules && moduleList[i].id != "node-red") {
|
if (moduleList[i].id != "node-red") {
|
||||||
(function(mod) {
|
(function(mod) {
|
||||||
promises.push(redNodes.installModule(mod.id,mod.version).then(m => {
|
promises.push(redNodes.installModule(mod.id,mod.version).then(m => {
|
||||||
events.emit("runtime-event",{id:"node/added",retain:false,payload:m.nodes});
|
events.emit("runtime-event",{id:"node/added",retain:false,payload:m.nodes});
|
||||||
@ -204,7 +219,7 @@ function reinstallModules(moduleList) {
|
|||||||
if (reinstallList.length > 0) {
|
if (reinstallList.length > 0) {
|
||||||
reinstallAttempts++;
|
reinstallAttempts++;
|
||||||
// First 5 at 1x timeout, next 5 at 2x, next 5 at 4x, then 8x
|
// First 5 at 1x timeout, next 5 at 2x, next 5 at 4x, then 8x
|
||||||
var timeout = (settings.autoInstallModulesRetry||30000) * Math.pow(2,Math.min(Math.floor(reinstallAttempts/5),3));
|
var timeout = installRetry * Math.pow(2,Math.min(Math.floor(reinstallAttempts/5),3));
|
||||||
reinstallTimeout = setTimeout(function() {
|
reinstallTimeout = setTimeout(function() {
|
||||||
reinstallModules(reinstallList);
|
reinstallModules(reinstallList);
|
||||||
},timeout);
|
},timeout);
|
||||||
|
@ -42,6 +42,7 @@
|
|||||||
"uninstall-failed-long": "Uninstall of module __name__ failed:",
|
"uninstall-failed-long": "Uninstall of module __name__ failed:",
|
||||||
"uninstalled": "Uninstalled module: __name__"
|
"uninstalled": "Uninstalled module: __name__"
|
||||||
},
|
},
|
||||||
|
"deprecatedOption": "Use of __old__ is deprecated. Use __new__ instead",
|
||||||
"unable-to-listen": "Unable to listen on __listenpath__",
|
"unable-to-listen": "Unable to listen on __listenpath__",
|
||||||
"port-in-use": "Error: port in use",
|
"port-in-use": "Error: port in use",
|
||||||
"uncaught-exception": "Uncaught Exception:",
|
"uncaught-exception": "Uncaught Exception:",
|
||||||
|
@ -14,7 +14,54 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
|
const should = require("should");
|
||||||
|
const NR_TEST_UTILS = require("nr-test-utils");
|
||||||
|
const registryUtil = NR_TEST_UTILS.require("@node-red/registry/lib/util");
|
||||||
|
|
||||||
|
|
||||||
describe("red/nodes/registry/util",function() {
|
describe("red/nodes/registry/util",function() {
|
||||||
it.skip("NEEDS TESTS");
|
describe("createNodeApi", function() {
|
||||||
|
it.skip("needs tests");
|
||||||
|
});
|
||||||
|
describe("checkModuleAllowed", function() {
|
||||||
|
function checkList(module, version, allowList, denyList) {
|
||||||
|
return registryUtil.checkModuleAllowed(
|
||||||
|
module,
|
||||||
|
version,
|
||||||
|
registryUtil.parseModuleList(allowList),
|
||||||
|
registryUtil.parseModuleList(denyList)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
it("allows module with no allow/deny list provided", function() {
|
||||||
|
checkList("abc","1.2.3",[],[]).should.be.true();
|
||||||
|
})
|
||||||
|
it("defaults allow to * when only deny list is provided", function() {
|
||||||
|
checkList("abc","1.2.3",["*"],["def"]).should.be.true();
|
||||||
|
checkList("def","1.2.3",["*"],["def"]).should.be.false();
|
||||||
|
})
|
||||||
|
it("uses most specific matching rule", function() {
|
||||||
|
checkList("abc","1.2.3",["ab*"],["a*"]).should.be.true();
|
||||||
|
checkList("def","1.2.3",["d*"],["de*"]).should.be.false();
|
||||||
|
})
|
||||||
|
it("checks version string using semver rules", function() {
|
||||||
|
// Deny
|
||||||
|
checkList("abc","1.2.3",["abc@1.2.2"],["*"]).should.be.false();
|
||||||
|
checkList("abc","1.2.3",["abc@1.2.4"],["*"]).should.be.false();
|
||||||
|
checkList("abc","1.2.3",["abc@>1.2.3"],["*"]).should.be.false();
|
||||||
|
checkList("abc","1.2.3",["abc@>=1.2.3"],["abc"]).should.be.false();
|
||||||
|
|
||||||
|
|
||||||
|
checkList("node-red-contrib-foo","1.2.3",["*"],["*contrib*"]).should.be.false();
|
||||||
|
|
||||||
|
|
||||||
|
// Allow
|
||||||
|
checkList("abc","1.2.3",["abc@1.2.3"],["*"]).should.be.true();
|
||||||
|
checkList("abc","1.2.3",["abc@<1.2.4"],["*"]).should.be.true();
|
||||||
|
checkList("abc","1.2.3",["abc"],["abc@>1.2.3"]).should.be.true();
|
||||||
|
checkList("abc","1.2.3",["abc"],["abc@<1.2.3||>1.2.3"]).should.be.true();
|
||||||
|
checkList("node-red-contrib-foo","1.2.3",["*contrib*"],["*"]).should.be.true();
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
@ -61,8 +61,8 @@ describe("runtime-api/settings", function() {
|
|||||||
result.should.not.have.property("foo",123);
|
result.should.not.have.property("foo",123);
|
||||||
result.should.have.property("flowEncryptionType","test-key-type");
|
result.should.have.property("flowEncryptionType","test-key-type");
|
||||||
result.should.not.have.property("user");
|
result.should.not.have.property("user");
|
||||||
result.should.have.property("editorTheme");
|
result.should.have.property("externalModules");
|
||||||
result.editorTheme.should.eql({palette:{editable:false}});
|
result.externalModules.should.eql({palette:{allowInstall:false, allowUpload: false}});
|
||||||
|
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user