Use npm info to check pending install version

This commit is contained in:
Nick O'Leary 2021-01-06 20:03:22 +00:00
parent af19536222
commit 8a87f93741
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
1 changed files with 153 additions and 86 deletions

View File

@ -37,7 +37,8 @@ const localtgzRe = /^([a-zA-Z]:|\/).+tgz$/;
// Default allow/deny lists // Default allow/deny lists
let installAllowList = ['*']; let installAllowList = ['*'];
let installDenyList = []; let installDenyList = [];
let installAllAllowed = true;
let installVersionRestricted = false;
function init(_settings) { function init(_settings) {
settings = _settings; settings = _settings;
@ -51,6 +52,18 @@ function init(_settings) {
} }
installAllowList = registryUtil.parseModuleList(installAllowList); installAllowList = registryUtil.parseModuleList(installAllowList);
installDenyList = registryUtil.parseModuleList(installDenyList); 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();
@ -95,99 +108,101 @@ 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 {
log.warn(log._("server.install.install-failed-url",{name:module,url:url}));
const e = new Error("Invalid url");
e.code = "invalid_module_url";
reject(e);
return;
}
} else if (moduleRe.test(module)) {
// Simple module name - assume it can be npm installed
if (version) {
installName += "@"+version;
}
} else if (slashRe.test(module)) {
// A path - check if there's a valid package.json
installName = module;
let info = checkModulePath(module);
module = info.name;
} else {
log.warn(log._("server.install.install-failed-name",{name:module}));
const e = new Error("Invalid module name");
e.code = "invalid_module_name";
reject(e);
return;
}
if (!registryUtil.checkModuleAllowed(module,version,installAllowList,installDenyList)) {
const e = new Error("Install not allowed");
e.code = "install_not_allowed";
reject(e);
return
}
isUpgrade = checkExistingModule(module,version);
} catch(err) {
return reject(err);
}
if (!isUpgrade) {
log.info(log._("server.install.installing",{name: module,version: version||"latest"}));
} else { } else {
log.info(log._("server.install.upgrading",{name: module,version: version||"latest"})); log.warn(log._("server.install.install-failed-url",{name:module,url:url}));
const e = new Error("Invalid url");
e.code = "invalid_module_url";
throw e;
}
} else if (moduleRe.test(module)) {
// Simple module name - assume it can be npm installed
if (version) {
installName += "@"+version;
}
} else if (slashRe.test(module)) {
// A path - check if there's a valid package.json
installName = module;
let info = checkModulePath(module);
module = info.name;
isRegistryPackage = false;
} else {
log.warn(log._("server.install.install-failed-name",{name:module}));
const e = new Error("Invalid module name");
e.code = "invalid_module_name";
throw e;
}
if (!installAllAllowed) {
let installVersion = version;
if (installVersionRestricted && isRegistryPackage) {
installVersion = await getModuleVersionFromNPM(module, version);
} }
var installDir = settings.userDir || process.env.NODE_RED_HOME || "."; if (!registryUtil.checkModuleAllowed(module,installVersion,installAllowList,installDenyList)) {
var args = ['install','--no-audit','--no-update-notifier','--no-fund','--save','--save-prefix=~','--production',installName]; const e = new Error("Install not allowed");
log.trace(npmCommand + JSON.stringify(args)); e.code = "install_not_allowed";
exec.run(npmCommand,args,{ throw e;
cwd: installDir }
}, true).then(result => { }
if (!isUpgrade) { isUpgrade = checkExistingModule(module,version);
log.info(log._("server.install.installed",{name:module}));
resolve(require("./index").addModule(module).then(reportAddedModules)); if (!isUpgrade) {
} else { log.info(log._("server.install.installing",{name: module,version: version||"latest"}));
log.info(log._("server.install.upgraded",{name:module, version:version})); } else {
events.emit("runtime-event",{id:"restart-required",payload:{type:"warning",text:"notification.warnings.restartRequired"},retain:true}); log.info(log._("server.install.upgrading",{name: module,version: version||"latest"}));
resolve(require("./registry").setModulePendingUpdated(module,version)); }
}
}).catch(result => { var installDir = settings.userDir || process.env.NODE_RED_HOME || ".";
var output = result.stderr; var args = ['install','--no-audit','--no-update-notifier','--no-fund','--save','--save-prefix=~','--production',installName];
var e; log.trace(npmCommand + JSON.stringify(args));
var lookFor404 = new RegExp(" 404 .*"+module,"m"); return exec.run(npmCommand,args,{
var lookForVersionNotFound = new RegExp("version not found: "+module+"@"+version,"m"); cwd: installDir
if (lookFor404.test(output)) { }, true).then(result => {
log.warn(log._("server.install.install-failed-not-found",{name:module})); if (!isUpgrade) {
e = new Error("Module not found"); log.info(log._("server.install.installed",{name:module}));
e.code = 404; return require("./index").addModule(module).then(reportAddedModules);
reject(e); } else {
} else if (isUpgrade && lookForVersionNotFound.test(output)) { log.info(log._("server.install.upgraded",{name:module, version:version}));
log.warn(log._("server.install.upgrade-failed-not-found",{name:module})); events.emit("runtime-event",{id:"restart-required",payload:{type:"warning",text:"notification.warnings.restartRequired"},retain:true});
e = new Error("Module not found"); return require("./registry").setModulePendingUpdated(module,version);
e.code = 404; }
reject(e); }).catch(result => {
} else { var output = result.stderr;
log.warn(log._("server.install.install-failed-long",{name:module})); var e;
log.warn("------------------------------------------"); var lookFor404 = new RegExp(" 404 .*"+module,"m");
log.warn(output); var lookForVersionNotFound = new RegExp("version not found: "+module+"@"+version,"m");
log.warn("------------------------------------------"); if (lookFor404.test(output)) {
reject(new Error(log._("server.install.install-failed"))); log.warn(log._("server.install.install-failed-not-found",{name:module}));
} e = new Error("Module not found");
}) e.code = 404;
}); throw e;
} else if (isUpgrade && lookForVersionNotFound.test(output)) {
log.warn(log._("server.install.upgrade-failed-not-found",{name:module}));
e = new Error("Module not found");
e.code = 404;
throw e;
} else {
log.warn(log._("server.install.install-failed-long",{name:module}));
log.warn("------------------------------------------");
log.warn(output);
log.warn("------------------------------------------");
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();
@ -236,6 +251,58 @@ 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) { if (settings.externalModules && settings.externalModules.palette && settings.externalModules.palette.allowUpload === false) {
throw new Error("Module upload disabled") throw new Error("Module upload disabled")