@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.
@@ -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
- // TODO: outsource running npm to a plugin
19
- const NPM_COMMAND = (process.platform === "win32") ? "npm.cmd" : "npm";
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(NPM_COMMAND + JSON.stringify(args));
270
- return exec.run(NPM_COMMAND, args, { cwd: installDir, shell: true },true)
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
- const npmCommand = isWindows ? 'npm.cmd' : 'npm';
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
- // Git remote url or Tarball url - check the valid package url
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 (moduleRe.test(module)) {
156
- // Simple module name - assume it can be npm installed
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 (installVersionRestricted && isRegistryPackage) {
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(npmCommand + JSON.stringify(args));
233
- return exec.run(npmCommand,args,{ cwd: installDir, shell: true }, true)
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(npmCommand,["remove",module],{ cwd: installDir, shell: true }, false).finally(() => {
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(`${npmCommand} info --json ${installName}`, { shell: true },function(err,stdout,stderr) {
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
- if (/[\s;]/.test(module)) {
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(npmCommand + JSON.stringify(args));
532
- return exec.run(npmCommand,args,{ cwd: installDir, shell: true }, true)
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(`${npmCommand} -v`,{ shell: true },function(err,stdout) {
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
- var loadPromise = null;
360
- var r = require(node.file);
361
- r = r.__esModule ? r.default : r
362
- if (typeof r === "function") {
363
-
364
- var red = registryUtil.createNodeApi(node);
365
- var promise = r(red);
366
- if (promise != null && typeof promise.then === "function") {
367
- loadPromise = promise.then(function() {
368
- node.enabled = true;
369
- node.loaded = true;
370
- return node;
371
- }).catch(function(err) {
372
- node.err = err;
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
- let invocation = 0
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
@@ -128,6 +128,7 @@ function createNodeApi(node) {
128
128
  }
129
129
  return i18n._.apply(null,args);
130
130
  }
131
+ red.nodes.linkcallTargets = runtime.nodes.linkcallTargets;
131
132
  return red;
132
133
  }
133
134
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node-red/registry",
3
- "version": "5.0.0-beta.5",
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-beta.5",
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
  }