@aaqu/fromcubes-portal-react 0.1.0-alpha.28 → 0.1.0-alpha.29

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.
@@ -427,6 +427,44 @@ function hasFreshBuild(state) {
427
427
  return !!state && !state.building && !!state.compiled && !state.compiled.error;
428
428
  }
429
429
 
430
+ /**
431
+ * Decide whether the `preInstall` hook may veto an npm install because the
432
+ * module is already on disk. Vetoing is ONLY safe for plain installs of an
433
+ * already-present package (the offline/Docker case). It must never swallow:
434
+ *
435
+ * - **upgrades** (`event.isUpgrade`) — the palette manager's "update"
436
+ * button routes through the same install path; vetoing it makes Node-RED
437
+ * mark the module `pendingUpdated` and demand a restart while the old
438
+ * files stay on disk — the update silently never lands,
439
+ * - **explicit version requests** that differ from the installed version
440
+ * (e.g. a `libs` entry bumped from `^4.4.0`) — npm itself no-ops when
441
+ * the range is already satisfied, so letting it run is the safe side.
442
+ *
443
+ * An unreadable/corrupt package.json also proceeds with the install (npm
444
+ * repairs what we cannot verify).
445
+ *
446
+ * @param {{dir: string, module: string, version?: string, isUpgrade?: boolean}} event
447
+ * `preInstall` hook payload from `@node-red/registry`.
448
+ * @returns {boolean} true → hook should return false (skip npm install).
449
+ */
450
+ function shouldSkipInstall(event) {
451
+ if (event.isUpgrade) return false;
452
+ const modDir = path.join(event.dir, "node_modules", event.module);
453
+ if (!fs.existsSync(modDir)) return false;
454
+ if (event.version) {
455
+ let installed;
456
+ try {
457
+ installed = JSON.parse(
458
+ fs.readFileSync(path.join(modDir, "package.json"), "utf8"),
459
+ ).version;
460
+ } catch (_) {
461
+ return false;
462
+ }
463
+ if (installed !== event.version) return false;
464
+ }
465
+ return true;
466
+ }
467
+
430
468
  module.exports = function (RED) {
431
469
  return createHelpers(RED);
432
470
  };
@@ -440,6 +478,7 @@ module.exports.findMissingComponentRefs = findMissingComponentRefs;
440
478
  module.exports.identifierRe = identifierRe;
441
479
  module.exports.serveableHash = serveableHash;
442
480
  module.exports.hasFreshBuild = hasFreshBuild;
481
+ module.exports.shouldSkipInstall = shouldSkipInstall;
443
482
  module.exports.NAME_MAX_LEN = NAME_MAX_LEN;
444
483
  module.exports.MAX_GROUPS_HEADER_BYTES = MAX_GROUPS_HEADER_BYTES;
445
484
 
@@ -461,20 +500,22 @@ function createHelpers(RED) {
461
500
  * `preInstall` hook (Node-RED 1.3+) — vetoes an npm install when the
462
501
  * package is already on disk. Useful for offline/Docker setups where
463
502
  * Node-RED's auto-install pass would otherwise try to hit the registry
464
- * and fail. Hook itself is *optional by design*: any unexpected error
503
+ * and fail. Upgrades and mismatched explicit versions are never vetoed
504
+ * (see {@link shouldSkipInstall}) — vetoing an upgrade leaves the old
505
+ * files on disk while Node-RED demands a restart, so the update never
506
+ * installs. Hook itself is *optional by design*: any unexpected error
465
507
  * inside the body MUST NOT bubble up (it would cancel an install path
466
508
  * the user actually expected to run). We log at `trace` level so the
467
509
  * developer can opt into diagnostics via `logging.level = trace` in
468
510
  * `settings.js`, without spamming default-level logs.
469
511
  *
470
- * @param {{dir: string, module: string}} event
512
+ * @param {{dir: string, module: string, version?: string, isUpgrade?: boolean}} event
471
513
  * @returns {boolean|void} `false` skips the install; anything else proceeds.
472
514
  * @listens RED.hooks#preInstall.portalReact
473
515
  */
474
516
  RED.hooks.add("preInstall.portalReact", (event) => {
475
517
  try {
476
- const modDir = path.join(event.dir, "node_modules", event.module);
477
- if (fs.existsSync(modDir)) {
518
+ if (shouldSkipInstall(event)) {
478
519
  RED.log.info(
479
520
  `[portal-react] ${event.module} already in node_modules, skipping install`,
480
521
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aaqu/fromcubes-portal-react",
3
- "version": "0.1.0-alpha.28",
3
+ "version": "0.1.0-alpha.29",
4
4
  "description": "Fromcubes Portal - React for Node-RED with Tailwind CSS and auto complete",
5
5
  "type": "commonjs",
6
6
  "keywords": [