@node-red/registry 5.0.0-beta.5 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/externalModules.js +12 -5
- package/lib/installer.js +29 -17
- package/lib/loader.js +56 -29
- package/lib/util.js +1 -0
- package/package.json +5 -3
package/lib/externalModules.js
CHANGED
|
@@ -12,11 +12,13 @@ const log = require("@node-red/util").log;
|
|
|
12
12
|
const hooks = require("@node-red/util").hooks;
|
|
13
13
|
const url = require("url");
|
|
14
14
|
const { createRequire } = require("module");
|
|
15
|
+
const validateNpmPackageName = require("validate-npm-package-name");
|
|
15
16
|
|
|
16
17
|
const BUILTIN_MODULES = require('module').builtinModules;
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
const
|
|
19
|
+
const npmPackageJson = require.resolve('npm/package.json')
|
|
20
|
+
const npmRoot = path.dirname(npmPackageJson);
|
|
21
|
+
const npmCli = path.join(npmRoot, 'bin', 'npm-cli.js');
|
|
20
22
|
|
|
21
23
|
let registeredTypes = {};
|
|
22
24
|
let subflowTypes = {};
|
|
@@ -246,6 +248,11 @@ async function installModule(moduleDetails) {
|
|
|
246
248
|
e.code = "install_not_allowed";
|
|
247
249
|
throw e;
|
|
248
250
|
}
|
|
251
|
+
if (!validateNpmPackageName(moduleDetails.module).validForOldPackages) {
|
|
252
|
+
const e = new Error("Invalid module name");
|
|
253
|
+
e.code = "install_not_allowed";
|
|
254
|
+
throw e;
|
|
255
|
+
}
|
|
249
256
|
if (moduleDetails.version) {
|
|
250
257
|
installSpec = installSpec+"@"+moduleDetails.version;
|
|
251
258
|
}
|
|
@@ -265,9 +272,9 @@ async function installModule(moduleDetails) {
|
|
|
265
272
|
// - run install
|
|
266
273
|
if (result !== false) {
|
|
267
274
|
let extraArgs = triggerPayload.args || [];
|
|
268
|
-
let args = ['install', ...extraArgs, installSpec]
|
|
269
|
-
log.trace(
|
|
270
|
-
return exec.run(
|
|
275
|
+
let args = [npmCli, 'install', ...extraArgs, '--', installSpec]
|
|
276
|
+
log.trace(`${process.execPath} ${npmCli} ${JSON.stringify(args)}`);
|
|
277
|
+
return exec.run(process.execPath, args, { cwd: installDir },true)
|
|
271
278
|
} else {
|
|
272
279
|
log.trace("skipping npm install");
|
|
273
280
|
}
|
package/lib/installer.js
CHANGED
|
@@ -29,12 +29,16 @@ const child_process = require('child_process');
|
|
|
29
29
|
const plugins = require("./plugins");
|
|
30
30
|
|
|
31
31
|
const isWindows = process.platform === 'win32'
|
|
32
|
-
|
|
32
|
+
|
|
33
|
+
const npmPackageJson = require.resolve('npm/package.json')
|
|
34
|
+
const npmRoot = path.dirname(npmPackageJson);
|
|
35
|
+
const npmCli = path.join(npmRoot, 'bin', 'npm-cli.js');
|
|
36
|
+
|
|
37
|
+
const validateNpmPackageName = require("validate-npm-package-name");
|
|
33
38
|
|
|
34
39
|
let installerEnabled = false;
|
|
35
40
|
let settings;
|
|
36
41
|
|
|
37
|
-
const moduleRe = /^(@[^/@]+?[/])?[^/@]+?$/;
|
|
38
42
|
const slashRe = isWindows ? /\\|[/]/ : /[/]/;
|
|
39
43
|
const pkgurlRe = /^(https?|git(|\+https?|\+ssh|\+file)):\/\//;
|
|
40
44
|
const localtgzRe = /^([a-zA-Z]:|\/).+tgz$/;
|
|
@@ -143,8 +147,7 @@ async function installModule(module,version,url) {
|
|
|
143
147
|
var isExisting = false;
|
|
144
148
|
if (url) {
|
|
145
149
|
if (pkgurlRe.test(url) || localtgzRe.test(url)) {
|
|
146
|
-
|
|
147
|
-
installName = localtgzRe.test(url) && slashRe.test(url) ? `"${url}"` : url;
|
|
150
|
+
installName = url;
|
|
148
151
|
isRegistryPackage = false;
|
|
149
152
|
} else {
|
|
150
153
|
log.warn(log._("server.install.install-failed-url",{name:module,url:url}));
|
|
@@ -152,8 +155,8 @@ async function installModule(module,version,url) {
|
|
|
152
155
|
e.code = "invalid_module_url";
|
|
153
156
|
throw e;
|
|
154
157
|
}
|
|
155
|
-
} else if (
|
|
156
|
-
//
|
|
158
|
+
} else if (validateNpmPackageName(module).validForOldPackages) {
|
|
159
|
+
// A valid npm module name
|
|
157
160
|
if (version) {
|
|
158
161
|
installName += "@"+version;
|
|
159
162
|
}
|
|
@@ -169,9 +172,12 @@ async function installModule(module,version,url) {
|
|
|
169
172
|
e.code = "invalid_module_name";
|
|
170
173
|
throw e;
|
|
171
174
|
}
|
|
175
|
+
|
|
172
176
|
if (!installAllAllowed) {
|
|
173
177
|
let installVersion = version;
|
|
174
|
-
if (
|
|
178
|
+
if (isRegistryPackage) {
|
|
179
|
+
// For an registry module, this will validate the version exists before trying to install it
|
|
180
|
+
// It will throw an error if the version doesn't exist.
|
|
175
181
|
installVersion = await getModuleVersionFromNPM(module, version);
|
|
176
182
|
}
|
|
177
183
|
|
|
@@ -228,9 +234,9 @@ async function installModule(module,version,url) {
|
|
|
228
234
|
// - run install
|
|
229
235
|
if (result !== false) {
|
|
230
236
|
let extraArgs = triggerPayload.args || [];
|
|
231
|
-
let args = ['install', ...extraArgs, installName]
|
|
232
|
-
log.trace(
|
|
233
|
-
return exec.run(
|
|
237
|
+
let args = [npmCli, 'install', ...extraArgs, '--', installName]
|
|
238
|
+
log.trace(`${process.execPath} ${npmCli} ${JSON.stringify(args)}`);
|
|
239
|
+
return exec.run(process.execPath, args, { cwd: installDir }, true)
|
|
234
240
|
} else {
|
|
235
241
|
log.trace("skipping npm install");
|
|
236
242
|
}
|
|
@@ -265,7 +271,7 @@ async function installModule(module,version,url) {
|
|
|
265
271
|
log.warn("------------------------------------------");
|
|
266
272
|
e = new Error(log._("server.install.install-failed")+": "+err.toString());
|
|
267
273
|
if (err.hook === "postInstall") {
|
|
268
|
-
return exec.run(
|
|
274
|
+
return exec.run(process.execPath, [npmCli, "remove", module],{ cwd: installDir }, false).finally(() => {
|
|
269
275
|
throw e;
|
|
270
276
|
})
|
|
271
277
|
}
|
|
@@ -370,7 +376,7 @@ async function getModuleVersionFromNPM(module, version) {
|
|
|
370
376
|
|
|
371
377
|
|
|
372
378
|
return new Promise((resolve, reject) => {
|
|
373
|
-
child_process.execFile(
|
|
379
|
+
child_process.execFile(process.execPath, [npmCli, 'info', '--json', '--', installName], function(err,stdout,stderr) {
|
|
374
380
|
try {
|
|
375
381
|
if (!stdout) {
|
|
376
382
|
log.warn(log._("server.install.install-failed-not-found",{name:module}));
|
|
@@ -381,6 +387,7 @@ async function getModuleVersionFromNPM(module, version) {
|
|
|
381
387
|
}
|
|
382
388
|
const response = JSON.parse(stdout);
|
|
383
389
|
if (response.error) {
|
|
390
|
+
console.log(response)
|
|
384
391
|
if (response.error.code === "E404") {
|
|
385
392
|
log.warn(log._("server.install.install-failed-not-found",{name:module}));
|
|
386
393
|
e = new Error("Module not found");
|
|
@@ -495,9 +502,14 @@ async function getTarballModuleInfo(tarball) {
|
|
|
495
502
|
}
|
|
496
503
|
|
|
497
504
|
function uninstallModule(module) {
|
|
505
|
+
// This function is called when the admin api is called to remove a module.
|
|
506
|
+
// A check in nodes.js#removeModule verifies the module being removed is already in the registry
|
|
507
|
+
// as a user module. As such, we can have confidence that the `module` passed here is
|
|
508
|
+
// a valid and recognised module name. But lets not rely 100% on that.
|
|
498
509
|
activePromise = activePromise.then(() => {
|
|
499
510
|
return new Promise((resolve,reject) => {
|
|
500
|
-
|
|
511
|
+
// Ensure its a valid package name and nothing has slipped through other checks
|
|
512
|
+
if (!validateNpmPackageName(module).validForOldPackages) {
|
|
501
513
|
reject(new Error(log._("server.install.invalid")));
|
|
502
514
|
return;
|
|
503
515
|
}
|
|
@@ -527,9 +539,9 @@ function uninstallModule(module) {
|
|
|
527
539
|
// - run uninstall
|
|
528
540
|
if (result !== false) {
|
|
529
541
|
let extraArgs = triggerPayload.args || [];
|
|
530
|
-
let args = ['remove', ...extraArgs, module]
|
|
531
|
-
log.trace(
|
|
532
|
-
return exec.run(
|
|
542
|
+
let args = [npmCli, 'remove', ...extraArgs, '--', module]
|
|
543
|
+
log.trace(`${process.execPath} ${npmCli} ${JSON.stringify(args)}`);
|
|
544
|
+
return exec.run(process.execPath, args, { cwd: installDir }, true)
|
|
533
545
|
} else {
|
|
534
546
|
log.trace("skipping npm uninstall");
|
|
535
547
|
}
|
|
@@ -596,7 +608,7 @@ async function checkPrereq() {
|
|
|
596
608
|
installerEnabled = false;
|
|
597
609
|
} else {
|
|
598
610
|
return new Promise(resolve => {
|
|
599
|
-
child_process.execFile(
|
|
611
|
+
child_process.execFile(process.execPath, [npmCli, '-v'], function(err,stdout) {
|
|
600
612
|
if (err) {
|
|
601
613
|
log.info(log._("server.palette-editor.npm-not-found"));
|
|
602
614
|
installerEnabled = false;
|
package/lib/loader.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
var fs = require("fs-extra");
|
|
18
18
|
var path = require("path");
|
|
19
19
|
var semver = require("semver");
|
|
20
|
+
const { pathToFileURL } = require('node:url');
|
|
20
21
|
|
|
21
22
|
var localfilesystem = require("./localfilesystem");
|
|
22
23
|
var registry = require("./registry");
|
|
@@ -66,7 +67,7 @@ function loadModuleTypeFiles(module, type) {
|
|
|
66
67
|
moduleFn = parts.slice(0,nmi+2).join(path.sep);
|
|
67
68
|
}
|
|
68
69
|
if (!moduleFn) {
|
|
69
|
-
// shortcut - skip calling statSync on empty string
|
|
70
|
+
// shortcut - skip calling statSync on empty string
|
|
70
71
|
break; // Module not found, don't attempt to load its nodes
|
|
71
72
|
}
|
|
72
73
|
try {
|
|
@@ -356,34 +357,23 @@ function loadNodeSet(node) {
|
|
|
356
357
|
} else {
|
|
357
358
|
}
|
|
358
359
|
try {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
return node;
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
if (loadPromise == null) {
|
|
378
|
-
node.enabled = true;
|
|
379
|
-
node.loaded = true;
|
|
380
|
-
loadPromise = Promise.resolve(node);
|
|
381
|
-
}
|
|
382
|
-
return loadPromise;
|
|
360
|
+
const modulePath = pathToFileURL(node.file).href;
|
|
361
|
+
const importPromise = import(modulePath);
|
|
362
|
+
return importPromise
|
|
363
|
+
.then(function(moduleNamespaceObject) {
|
|
364
|
+
// CJS will always have default, ESM might have default. (details in note below)
|
|
365
|
+
let r = moduleNamespaceObject.default ? moduleNamespaceObject.default : moduleNamespaceObject;
|
|
366
|
+
// Babel related workaround, references in comment below.
|
|
367
|
+
r = r.__esModule ? r.default : r;
|
|
368
|
+
return createLoadPromise(node, r);
|
|
369
|
+
})
|
|
370
|
+
.catch(function(err) {
|
|
371
|
+
node.err = err;
|
|
372
|
+
return Promise.resolve(node);
|
|
373
|
+
});
|
|
383
374
|
} catch(err) {
|
|
384
375
|
node.err = err;
|
|
385
376
|
var stack = err.stack;
|
|
386
|
-
var message;
|
|
387
377
|
if (stack) {
|
|
388
378
|
var filePath = node.file;
|
|
389
379
|
try {
|
|
@@ -403,6 +393,45 @@ function loadNodeSet(node) {
|
|
|
403
393
|
}
|
|
404
394
|
return Promise.resolve(node);
|
|
405
395
|
}
|
|
396
|
+
|
|
397
|
+
// Notes on default:
|
|
398
|
+
// https://nodejs.org/api/esm.html#import-expressions
|
|
399
|
+
// "When importing CommonJS modules, the module.exports object is provided as the default export."
|
|
400
|
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import
|
|
401
|
+
// "The module namespace object is a sealed object ... with the default export available as a key called `default`."
|
|
402
|
+
|
|
403
|
+
// Notes on __esModule:
|
|
404
|
+
// Workaround originally added in commit 326f3469, merged in pull request #3669.
|
|
405
|
+
// Related links:
|
|
406
|
+
// https://github.com/nodejs/node/issues/40891
|
|
407
|
+
// https://github.com/evanw/esbuild/issues/1591
|
|
408
|
+
// https://babeljs.io/docs/babel-plugin-transform-modules-commonjs
|
|
409
|
+
// https://stackoverflow.com/questions/50943704/whats-the-purpose-of-object-definepropertyexports-esmodule-value-0
|
|
410
|
+
|
|
411
|
+
function createLoadPromise(node, r) {
|
|
412
|
+
var loadPromise = null;
|
|
413
|
+
if (typeof r === "function") {
|
|
414
|
+
|
|
415
|
+
var red = registryUtil.createNodeApi(node);
|
|
416
|
+
var promise = r(red);
|
|
417
|
+
if (promise != null && typeof promise.then === "function") {
|
|
418
|
+
loadPromise = promise.then(function () {
|
|
419
|
+
node.enabled = true;
|
|
420
|
+
node.loaded = true;
|
|
421
|
+
return node;
|
|
422
|
+
}).catch(function (err) {
|
|
423
|
+
node.err = err;
|
|
424
|
+
return node;
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if (loadPromise == null) {
|
|
429
|
+
node.enabled = true;
|
|
430
|
+
node.loaded = true;
|
|
431
|
+
loadPromise = Promise.resolve(node);
|
|
432
|
+
}
|
|
433
|
+
return loadPromise;
|
|
434
|
+
}
|
|
406
435
|
}
|
|
407
436
|
|
|
408
437
|
async function loadPlugin(plugin) {
|
|
@@ -435,7 +464,6 @@ async function loadPlugin(plugin) {
|
|
|
435
464
|
console.log(err);
|
|
436
465
|
plugin.err = err;
|
|
437
466
|
var stack = err.stack;
|
|
438
|
-
var message;
|
|
439
467
|
if (stack) {
|
|
440
468
|
var i = stack.indexOf(plugin.file);
|
|
441
469
|
if (i > -1) {
|
|
@@ -449,7 +477,7 @@ async function loadPlugin(plugin) {
|
|
|
449
477
|
return plugin;
|
|
450
478
|
}
|
|
451
479
|
}
|
|
452
|
-
|
|
480
|
+
|
|
453
481
|
function loadNodeSetList(nodes) {
|
|
454
482
|
var promises = [];
|
|
455
483
|
nodes.forEach(function(node) {
|
|
@@ -471,7 +499,6 @@ function addModule(module) {
|
|
|
471
499
|
if (!settings.available()) {
|
|
472
500
|
throw new Error("Settings unavailable");
|
|
473
501
|
}
|
|
474
|
-
var nodes = [];
|
|
475
502
|
var existingInfo = registry.getModuleInfo(module);
|
|
476
503
|
if (existingInfo) {
|
|
477
504
|
// TODO: nls
|
package/lib/util.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@node-red/registry",
|
|
3
|
-
"version": "5.0.0
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"repository": {
|
|
@@ -16,11 +16,13 @@
|
|
|
16
16
|
}
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@node-red/util": "5.0.0
|
|
19
|
+
"@node-red/util": "5.0.0",
|
|
20
20
|
"clone": "2.1.2",
|
|
21
21
|
"fs-extra": "11.3.0",
|
|
22
|
+
"npm": "11.16.0",
|
|
22
23
|
"semver": "7.7.4",
|
|
23
24
|
"tar": "7.5.12",
|
|
24
|
-
"uglify-js": "3.19.3"
|
|
25
|
+
"uglify-js": "3.19.3",
|
|
26
|
+
"validate-npm-package-name": "7.0.2"
|
|
25
27
|
}
|
|
26
28
|
}
|