@omega.js/desktop 0.50.0 → 0.52.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.
Files changed (268) hide show
  1. package/README.md +2 -2
  2. package/dist/assets/css/tokens/_index.scss +1 -1
  3. package/dist/assets/themes/base/_includes/frontend/sections/account-section-header.html +4 -1
  4. package/dist/assets/themes/base/_includes/frontend/sections/footer.html +13 -6
  5. package/dist/assets/themes/base/_includes/frontend/sections/nav.html +10 -8
  6. package/dist/assets/themes/base/_includes/global/sections/account.html +3 -1
  7. package/dist/assets/themes/base/_includes/global/sections/app-sidebar.html +12 -8
  8. package/dist/assets/themes/base/_includes/global/sections/app-topbar.html +10 -6
  9. package/dist/assets/themes/base/_includes/global/sections/page-header.html +8 -4
  10. package/dist/assets/themes/base/_layouts/backend/pages/dashboard/index.html +24 -24
  11. package/dist/assets/themes/base/_layouts/frontend/pages/about.html +10 -10
  12. package/dist/assets/themes/base/_layouts/frontend/pages/account/index.html +35 -35
  13. package/dist/assets/themes/base/_layouts/frontend/pages/alternatives/index.html +4 -4
  14. package/dist/assets/themes/base/_layouts/frontend/pages/auth/signin.html +1 -1
  15. package/dist/assets/themes/base/_layouts/frontend/pages/auth/signup.html +4 -4
  16. package/dist/assets/themes/base/_layouts/frontend/pages/contact.html +3 -3
  17. package/dist/assets/themes/base/_layouts/frontend/pages/download.html +34 -33
  18. package/dist/assets/themes/base/_layouts/frontend/pages/extension/index.html +11 -11
  19. package/dist/assets/themes/base/_layouts/frontend/pages/legal/document.html +1 -1
  20. package/dist/assets/themes/base/_layouts/frontend/pages/status.html +1 -1
  21. package/dist/assets/themes/base/_layouts/frontend/pages/team/index.html +9 -7
  22. package/dist/assets/themes/base/_layouts/frontend/pages/team/member.html +5 -3
  23. package/dist/assets/themes/base/_sections/about/letter/section.html +1 -1
  24. package/dist/assets/themes/base/_sections/about/letter/section.json5 +4 -4
  25. package/dist/assets/themes/base/_sections/marketing/bento/section.html +1 -1
  26. package/dist/assets/themes/base/_sections/marketing/bento/section.json5 +15 -15
  27. package/dist/assets/themes/base/_sections/marketing/cta/section.json5 +1 -1
  28. package/dist/assets/themes/base/_sections/marketing/hero/section.html +6 -6
  29. package/dist/assets/themes/base/_sections/marketing/hero/section.json5 +10 -10
  30. package/dist/assets/themes/base/_sections/marketing/product-demo/section.html +1 -1
  31. package/dist/assets/themes/base/_sections/marketing/product-demo/section.json5 +2 -2
  32. package/dist/assets/themes/base/_sections/marketing/stats/section.html +1 -1
  33. package/dist/assets/themes/base/_sections/marketing/stats/section.json5 +5 -5
  34. package/dist/assets/themes/base/_sections/marketing/trusted-by/section.html +1 -1
  35. package/dist/assets/themes/base/_sections/marketing/trusted-by/section.json5 +2 -2
  36. package/dist/assets/themes/neobrutalism/_layouts/frontend/pages/index.html +10 -10
  37. package/dist/assets/themes/newsflash/_layouts/frontend/pages/index.html +10 -10
  38. package/dist/assets/themes/newsflash/_sections/marketing/desks/section.html +2 -2
  39. package/dist/assets/themes/newsflash/_sections/marketing/desks/section.json5 +4 -4
  40. package/dist/build.js +69 -28
  41. package/dist/cli-run.js +20 -13
  42. package/dist/cli.js +12 -7
  43. package/dist/commands/build.js +0 -2
  44. package/dist/commands/deploy.js +116 -44
  45. package/dist/commands/finalize-release.js +5 -4
  46. package/dist/commands/launch.js +12 -7
  47. package/dist/commands/lib/deploy-precheck.js +66 -34
  48. package/dist/commands/lib/ensure-target.js +71 -11
  49. package/dist/commands/package.js +0 -1
  50. package/dist/commands/publish.js +11 -4
  51. package/dist/commands/release.js +99 -252
  52. package/dist/commands/runner.js +176 -42
  53. package/dist/commands/sign-windows.js +46 -15
  54. package/dist/commands/test.js +4 -3
  55. package/dist/commands/validate-certs.js +291 -115
  56. package/dist/config/page-template.html +4 -0
  57. package/dist/defaults/.github/workflows/build.yml +146 -64
  58. package/dist/defaults/AGENTS.md +3 -2
  59. package/dist/defaults/_.gitignore +4 -2
  60. package/dist/defaults/config/certs/README.md +25 -45
  61. package/dist/defaults/config/omega.json5 +66 -32
  62. package/dist/defaults/hooks/deploy/pre.js +10 -0
  63. package/dist/defaults/src/assets/scss/main.scss +12 -2
  64. package/dist/defaults/src/integrations/tray/index.js +1 -1
  65. package/dist/gulp/main.js +13 -25
  66. package/dist/gulp/tasks/audit.js +18 -6
  67. package/dist/gulp/tasks/build-config.js +181 -60
  68. package/dist/gulp/tasks/bundle.js +108 -36
  69. package/dist/gulp/tasks/release.js +86 -4
  70. package/dist/gulp/tasks/sass.js +11 -0
  71. package/dist/hooks/lib/notarize-tools.js +137 -0
  72. package/dist/hooks/notarize-artifacts.js +57 -0
  73. package/dist/hooks/notarize.js +59 -8
  74. package/dist/lib/auth-persistence.js +25 -0
  75. package/dist/lib/client-bridge.js +11 -8
  76. package/dist/lib/deep-link.js +15 -8
  77. package/dist/lib/protocol.js +7 -1
  78. package/dist/lib/restart-manager/install.js +6 -3
  79. package/dist/lib/sign-helpers/auto-unlock.js +65 -27
  80. package/dist/lib/sign-helpers/console-lock.js +34 -0
  81. package/dist/lib/sign-helpers/exec-with-limit.js +68 -0
  82. package/dist/lib/sign-helpers/resolve-icons.js +6 -4
  83. package/dist/lib/tray.js +7 -6
  84. package/dist/main.js +16 -0
  85. package/dist/preload.js +8 -0
  86. package/dist/renderer.js +16 -4
  87. package/dist/runner/job-started.js +104 -0
  88. package/dist/test/fixtures/consumer-app/config/omega.json5 +7 -0
  89. package/dist/test/fixtures/consumer-app/package.json +1 -1
  90. package/dist/test/fixtures/consumer-app/src/assets/js/components/main/index.js +7 -1
  91. package/dist/test/harness/main-entry.js +2 -1
  92. package/dist/test/harness/renderer-preload.js +12 -3
  93. package/dist/test/runners/boot.js +101 -11
  94. package/dist/test/runners/electron.js +1 -1
  95. package/dist/test/suites/boot/consumer-app-boots.test.js +54 -0
  96. package/dist/test/suites/build/audit.test.js +45 -6
  97. package/dist/test/suites/build/auth-persistence-resolve.test.js +119 -0
  98. package/dist/test/suites/build/auto-unlock.test.js +105 -0
  99. package/dist/test/suites/build/boot-runner-timeout.test.js +253 -0
  100. package/dist/test/suites/build/brand-scss.test.js +106 -0
  101. package/dist/test/suites/build/build-config.test.js +146 -36
  102. package/dist/test/suites/build/build-json-bake.test.js +245 -0
  103. package/dist/test/suites/build/build-verbs.test.js +48 -15
  104. package/dist/test/suites/build/build-workflow-jobs.test.js +190 -0
  105. package/dist/test/suites/build/cli.test.js +32 -10
  106. package/dist/test/suites/build/config-schema.test.js +4 -4
  107. package/dist/test/suites/build/console-lock.test.js +51 -0
  108. package/dist/test/suites/build/defaults-scaffold.test.js +71 -2
  109. package/dist/test/suites/build/deploy-direct.test.js +241 -0
  110. package/dist/test/suites/build/deploy-dispatch.test.js +287 -0
  111. package/dist/test/suites/build/deploy-hook.test.js +169 -0
  112. package/dist/test/suites/build/ensure-target.test.js +14 -2
  113. package/dist/test/suites/build/env-delivery.test.js +19 -10
  114. package/dist/test/suites/build/env-watch.test.js +18 -7
  115. package/dist/test/suites/build/esm-only-dependency.test.js +127 -0
  116. package/dist/test/suites/build/exec-with-limit.test.js +53 -0
  117. package/dist/test/suites/build/finalize-release.test.js +2 -2
  118. package/dist/test/suites/build/get-config.test.js +120 -8
  119. package/dist/test/suites/build/github-utils.test.js +12 -6
  120. package/dist/test/suites/build/license-stamp.test.js +6 -4
  121. package/dist/test/suites/build/manager.test.js +88 -68
  122. package/dist/test/suites/build/manifest-deps.test.js +116 -0
  123. package/dist/test/suites/build/merge-line-files.test.js +27 -4
  124. package/dist/test/suites/build/notarize-artifacts.test.js +135 -0
  125. package/dist/test/suites/build/notarize-tools.test.js +38 -0
  126. package/dist/test/suites/build/notarize.test.js +207 -0
  127. package/dist/test/suites/build/release-pipeline.test.js +38 -60
  128. package/dist/test/suites/build/release-skipped-upload.test.js +107 -0
  129. package/dist/test/suites/build/resolve-icons.test.js +35 -35
  130. package/dist/test/suites/build/runner-job-guard.test.js +182 -0
  131. package/dist/test/suites/build/runner.test.js +635 -5
  132. package/dist/test/suites/build/sentry.test.js +8 -3
  133. package/dist/test/suites/build/setup-scripts.test.js +3 -0
  134. package/dist/test/suites/build/sign-windows.test.js +92 -8
  135. package/dist/test/suites/build/test-stealth.test.js +17 -11
  136. package/dist/test/suites/build/url-helpers.test.js +37 -17
  137. package/dist/test/suites/build/validate-certs.test.js +428 -55
  138. package/dist/test/suites/build/validate-config.test.js +3 -3
  139. package/dist/test/suites/main/auth-flow.test.js +12 -0
  140. package/dist/test/suites/main/auth-persistence.test.js +14 -17
  141. package/dist/test/suites/main/auto-updater.test.js +2 -2
  142. package/dist/test/suites/main/boot-sequence.test.js +1 -1
  143. package/dist/test/suites/main/client-bridge.integration.test.js +5 -82
  144. package/dist/test/suites/main/client-bridge.test.js +19 -6
  145. package/dist/test/suites/main/deep-link.test.js +54 -0
  146. package/dist/test/suites/main/startup-paths-and-ua.test.js +1 -1
  147. package/dist/test/suites/main/url-helpers.test.js +81 -72
  148. package/dist/test/suites/renderer/cross-context-helpers.test.js +19 -16
  149. package/dist/utils/build-pipeline.js +10 -10
  150. package/dist/utils/github.js +12 -51
  151. package/dist/utils/load-env.js +66 -0
  152. package/dist/utils/mode-helpers.js +43 -111
  153. package/dist/utils/platform.js +37 -0
  154. package/dist/utils/runner-env.js +2 -1
  155. package/dist/utils/runner-job-guard.js +149 -0
  156. package/dist/utils/ship-keys.js +52 -0
  157. package/dist/utils/test-stealth.js +5 -3
  158. package/dist/utils/url-helpers.js +33 -17
  159. package/dist/vendor/config/bundle-id.js +53 -0
  160. package/dist/vendor/config/client-config.js +141 -0
  161. package/dist/vendor/config/company.js +334 -15
  162. package/dist/vendor/config/dev-facts.js +48 -0
  163. package/dist/vendor/config/env-delivery.js +231 -9
  164. package/dist/vendor/config/env-retired.js +137 -0
  165. package/dist/vendor/config/env-rules.js +22 -3
  166. package/dist/vendor/config/env-schema.js +234 -117
  167. package/dist/vendor/config/env.js +55 -26
  168. package/dist/vendor/config/environment.js +189 -0
  169. package/dist/vendor/config/hooks.js +13 -11
  170. package/dist/vendor/config/index.js +125 -44
  171. package/dist/vendor/config/load.js +366 -115
  172. package/dist/vendor/config/merge.js +2 -2
  173. package/dist/vendor/config/order.js +3 -3
  174. package/dist/vendor/config/platforms.js +276 -0
  175. package/dist/vendor/config/repo.js +249 -103
  176. package/dist/vendor/config/retired-keys.js +232 -27
  177. package/dist/vendor/config/schema.js +525 -99
  178. package/dist/vendor/config/site-global.js +63 -53
  179. package/dist/vendor/config/targets.js +187 -0
  180. package/dist/vendor/config/validate.js +119 -59
  181. package/dist/vendor/devkit/argv.js +118 -0
  182. package/dist/vendor/devkit/attach-log-file.js +21 -13
  183. package/dist/vendor/devkit/brand-tokens.js +278 -0
  184. package/dist/vendor/devkit/brand-version.js +425 -0
  185. package/dist/vendor/devkit/build-json.js +91 -0
  186. package/dist/vendor/devkit/bundle.js +48 -0
  187. package/dist/vendor/devkit/certificate-expiry.js +108 -0
  188. package/dist/vendor/devkit/certs.js +16 -194
  189. package/dist/vendor/devkit/ci-workflows.js +124 -8
  190. package/dist/vendor/devkit/cli-router.js +3 -3
  191. package/dist/vendor/devkit/defaults-engine.js +69 -7
  192. package/dist/vendor/devkit/deploy-follow.js +297 -0
  193. package/dist/vendor/devkit/deploy-precheck.js +23 -8
  194. package/dist/vendor/devkit/deploy-record.js +11 -27
  195. package/dist/vendor/devkit/deploy-snapshot.js +661 -0
  196. package/dist/vendor/devkit/deploy.js +505 -75
  197. package/dist/vendor/devkit/git-auth.js +73 -0
  198. package/dist/vendor/devkit/git-remote.js +172 -0
  199. package/dist/vendor/devkit/github-repo.js +290 -0
  200. package/dist/vendor/devkit/local.js +98 -3
  201. package/dist/vendor/devkit/lockfile.js +127 -0
  202. package/dist/vendor/devkit/merge-line-files.js +23 -16
  203. package/dist/vendor/devkit/omega-bin.js +18 -3
  204. package/dist/vendor/devkit/pack-local.js +388 -0
  205. package/dist/vendor/devkit/preludes/index.js +120 -0
  206. package/dist/vendor/devkit/preludes/origin-heal.js +142 -0
  207. package/dist/vendor/devkit/service-account.js +43 -0
  208. package/dist/vendor/devkit/ship-plan.js +112 -0
  209. package/dist/vendor/devkit/signing-env.js +180 -0
  210. package/dist/vendor/devkit/signing-tree.js +92 -0
  211. package/dist/vendor/devkit/target-seams.js +142 -0
  212. package/dist/vendor/devkit/target-secrets.js +231 -57
  213. package/dist/vendor/devkit/test/esm-only-fixture.js +48 -0
  214. package/dist/vendor/devkit/test/fixtures/esm-only-package/browser.js +19 -0
  215. package/dist/vendor/devkit/test/fixtures/esm-only-package/index.js +20 -0
  216. package/dist/vendor/devkit/test/fixtures/esm-only-package/package.json +13 -0
  217. package/dist/vendor/monitoring/env.js +20 -10
  218. package/dist/vendor/monitoring/main.js +1 -1
  219. package/dist/vendor/monitoring/preload.js +1 -1
  220. package/dist/vendor/monitoring/renderer.js +1 -1
  221. package/docs/analytics.md +1 -1
  222. package/docs/auto-updater.md +5 -5
  223. package/docs/boot-sequence.md +1 -1
  224. package/docs/build-system.md +15 -7
  225. package/docs/client-bridge.md +9 -7
  226. package/docs/config-schema.md +4 -4
  227. package/docs/css.md +8 -2
  228. package/docs/deep-link.md +12 -4
  229. package/docs/environment-detection.md +32 -24
  230. package/docs/hooks.md +3 -1
  231. package/docs/icons.md +7 -7
  232. package/docs/index.md +61 -23
  233. package/docs/installer-options.md +24 -21
  234. package/docs/logging.md +5 -5
  235. package/docs/releasing.md +29 -19
  236. package/docs/runner.md +51 -14
  237. package/docs/shared/brands.md +12 -6
  238. package/docs/shared/breaking-changes.md +375 -21
  239. package/docs/shared/config.md +764 -199
  240. package/docs/shared/deploys.md +217 -91
  241. package/docs/shared/icons.md +18 -0
  242. package/docs/shared/local-dev.md +28 -8
  243. package/docs/shared/logging.md +9 -6
  244. package/docs/shared/monitoring.md +27 -13
  245. package/docs/shared/publishing.md +3 -3
  246. package/docs/shared/rulings.md +2 -2
  247. package/docs/shared/testing.md +1 -1
  248. package/docs/shared/theming.md +26 -1
  249. package/docs/shared/translation.md +49 -7
  250. package/docs/shared/updates.md +1 -1
  251. package/docs/signing.md +60 -34
  252. package/docs/test-framework.md +10 -5
  253. package/docs/themes.md +15 -1
  254. package/package.json +18 -12
  255. package/bin/omega-desktop +0 -2
  256. package/dist/commands/push-secrets.js +0 -141
  257. package/dist/test/suites/build/deliver-certs.test.js +0 -95
  258. package/dist/test/suites/build/derive-signing-env.test.js +0 -122
  259. package/dist/test/suites/build/push-secrets.test.js +0 -226
  260. package/dist/test/suites/build/resolve-signing-cert.test.js +0 -342
  261. package/dist/utils/deliver-certs.js +0 -69
  262. package/dist/utils/derive-signing-env.js +0 -56
  263. package/dist/utils/resolve-signing-cert.js +0 -175
  264. package/dist/vendor/config/desktop-artifacts.js +0 -110
  265. package/dist/vendor/config/instances.js +0 -208
  266. /package/dist/defaults/config/icons/{macos → mac}/dmg.png +0 -0
  267. /package/dist/defaults/config/icons/{macos → mac}/icon.png +0 -0
  268. /package/dist/defaults/config/icons/{macos → mac}/tray.png +0 -0
@@ -261,6 +261,33 @@ function relativeSpecPath(fromDir, target) {
261
261
  return path.relative(fs.realpathSync(fromDir), fs.realpathSync(target)).split(path.sep).join('/');
262
262
  }
263
263
 
264
+ /**
265
+ * Regenerate the install root's OWN lockfile after a flip (#938): inside the
266
+ * omega monorepo the tree install climbs to the monorepo root and writes that
267
+ * lockfile, so the brand's would stay stale. Loaded here rather than at the top:
268
+ * this module runs at every CLI boot (freshnessBoot), and the lockfile read
269
+ * pulls in @omega.js/config, which the boot has no use for.
270
+ * @param {string} installRoot - The brand root (or standalone target).
271
+ * @param {object} [logger] - Logger with log (silent when omitted).
272
+ * @returns {Promise<void>}
273
+ */
274
+ function regenerateBrandLockfile(installRoot, logger) {
275
+ const { regenerateLockfile } = require('./lockfile.js');
276
+ return regenerateLockfile({ root: installRoot, log: logger ? (line) => logger.log(line) : undefined });
277
+ }
278
+
279
+ /**
280
+ * Whether the install root's lockfile is missing or disagrees with its
281
+ * manifests' @omega.js registry specs: the deploy gate's own read (#938).
282
+ * @param {string} installRoot - The brand root (or standalone target).
283
+ * @returns {boolean}
284
+ */
285
+ function brandLockfileStale(installRoot) {
286
+ const { brandLockfileDrift } = require('./brand-version.js');
287
+ const { lock, drift } = brandLockfileDrift({ root: installRoot });
288
+ return !lock || drift.length > 0;
289
+ }
290
+
264
291
  /**
265
292
  * file:-install a brand's @omega.js dependencies from the local monorepo.
266
293
  *
@@ -275,7 +302,8 @@ function relativeSpecPath(fromDir, target) {
275
302
  * targets degenerate to themselves.
276
303
  *
277
304
  * Idempotent: dependencies already resolving to the monorepo copy are
278
- * skipped, and when nothing needs linking no install runs.
305
+ * skipped, and when nothing needs linking no install runs. An install is
306
+ * followed by the brand's OWN lockfile regeneration (#938).
279
307
  * @param {object} options
280
308
  * @param {string} options.dir - Target directory to link from (any target in the brand).
281
309
  * @param {string} options.monorepoRoot - Monorepo root path.
@@ -347,6 +375,11 @@ async function linkLocalPackages(options) {
347
375
  logger && logger.warn(`install failed — restored ${manifestBackups.size} manifest(s) to their pre-link specs`);
348
376
  throw error;
349
377
  }
378
+
379
+ // Outside the rollback: the install succeeded, so the manifests and the
380
+ // tree already agree, and restoring the manifests now would split them.
381
+ // A failed regeneration puts the lock back itself and throws as what it is.
382
+ await regenerateBrandLockfile(installRoot, logger);
350
383
  }
351
384
 
352
385
  return actions;
@@ -364,7 +397,9 @@ async function linkLocalPackages(options) {
364
397
  * @omega.js/client in one brand. An explicit `range` still wins verbatim — the
365
398
  * caller owns that string. Idempotent: registry-spec'd entries are untouched;
366
399
  * nothing to flip → no install. Same transactional manifest restore as the
367
- * linker when the install fails.
400
+ * linker when the install fails. Either way the brand's OWN lockfile ends up
401
+ * agreeing with the manifests (#938): regenerated after the install, and
402
+ * regenerated alone when nothing flipped but the lock still disagrees.
368
403
  * @param {object} options
369
404
  * @param {string} options.dir - Any directory inside the brand.
370
405
  * @param {object} [options.logger] - Logger with log/warn (silent when omitted).
@@ -416,7 +451,11 @@ async function restoreRegistrySpecs(options) {
416
451
  }
417
452
  }
418
453
 
419
- if (installNeeded && !dryRun) {
454
+ if (dryRun) {
455
+ return actions;
456
+ }
457
+
458
+ if (installNeeded) {
420
459
  try {
421
460
  await safeInstall('npm install', { log: true, config: { cwd: installRoot } });
422
461
  } catch (error) {
@@ -426,6 +465,15 @@ async function restoreRegistrySpecs(options) {
426
465
  logger && logger.warn(`install failed — restored ${manifestBackups.size} manifest(s) to their file: specs`);
427
466
  throw error;
428
467
  }
468
+
469
+ // Outside the rollback, as in linkLocalPackages: the flipped manifests and
470
+ // the installed tree agree, so only the lock is left to heal.
471
+ await regenerateBrandLockfile(installRoot, logger);
472
+ } else if (brandLockfileStale(installRoot)) {
473
+ // Nothing to flip, but the lock still disagrees (the specs were flipped
474
+ // before the lock was ever regenerated): this verb is the fix the deploy's
475
+ // lockfile gate names, so it heals the lock here too (#938).
476
+ await regenerateBrandLockfile(installRoot, logger);
429
477
  }
430
478
 
431
479
  return actions;
@@ -1548,6 +1596,34 @@ function healFailedMessage(result) {
1548
1596
  ].join('\n');
1549
1597
  }
1550
1598
 
1599
+ /**
1600
+ * Leave the #350 prepare gate wired on a package this process just prepared
1601
+ * ([#870](https://github.com/Omega-JS-Stack/omega/issues/870)). prepare-package
1602
+ * rewrites `scripts.prepare` to its own UNGUARDED string on the way through, and
1603
+ * a heal that left it there hands a consumer install the door back. The prepare
1604
+ * script restores it in a `finally`, so this is the net for the case a finally
1605
+ * cannot reach: a prepare killed outright, or one that never ran the gate at all.
1606
+ * The guard is loaded from the devkit BESIDE the prepared package, the same hop
1607
+ * the gated script's own relative require makes, and its path is built at runtime
1608
+ * rather than written as a literal specifier, because devkit's vendored copies
1609
+ * carry `src/` only and a relative require of `tools/` would send the vendor walk
1610
+ * outside the module root.
1611
+ * @param {object} options
1612
+ * @param {string} options.monorepoRoot - The monorepo containing the package.
1613
+ * @param {string} options.packageDir - The package that was prepared.
1614
+ * @returns {boolean} Whether the manifest holds the gate now (true when the
1615
+ * checkout carries no guard to enforce).
1616
+ */
1617
+ function keepPrepareGuarded(options) {
1618
+ const { monorepoRoot, packageDir } = options;
1619
+ const guardPath = path.join(monorepoRoot, 'packages', 'devkit', 'tools', 'prepare-guard.js');
1620
+ if (!fs.existsSync(guardPath)) {
1621
+ return true;
1622
+ }
1623
+
1624
+ return require(guardPath).ensureGuarded({ packageDir }).guarded;
1625
+ }
1626
+
1551
1627
  /**
1552
1628
  * Check that a locally-linked framework's dist is at least as new as its src —
1553
1629
  * reporting or rebuilding it when a src edit landed without a prepare, so
@@ -1661,6 +1737,10 @@ function ensureFreshLocalDist(options) {
1661
1737
  stdio: 'inherit',
1662
1738
  shell: process.platform === 'win32',
1663
1739
  });
1740
+
1741
+ // Whatever that prepare did, it does not get to leave the gate off (#870)
1742
+ const guarded = inMonorepo ? keepPrepareGuarded({ monorepoRoot, packageDir: realDir }) : true;
1743
+
1664
1744
  if (result.status !== 0) {
1665
1745
  const failure = `npm run prepare exited ${result.status === null ? String(result.error && result.error.message || 'spawn error') : result.status}`;
1666
1746
  if (inMonorepo) {
@@ -1671,6 +1751,21 @@ function ensureFreshLocalDist(options) {
1671
1751
  console.warn(`omega: rebuild failed (${failure} in ${realDir}) — continuing on the stale dist`);
1672
1752
  return { status: 'rebuild-failed', packageName, dir: realDir };
1673
1753
  }
1754
+
1755
+ // A prepare that ended with the gate off hands a consumer install the #350
1756
+ // door back, so a heal that could not put it back is a FAILED heal like any
1757
+ // other: the boot stops on it rather than running on an ungated package.
1758
+ if (!guarded) {
1759
+ return {
1760
+ status: 'heal-failed',
1761
+ packageName,
1762
+ dir: realDir,
1763
+ reason,
1764
+ failure: 'npm run prepare left scripts.prepare off the #350 gate',
1765
+ monorepoRoot,
1766
+ };
1767
+ }
1768
+
1674
1769
  return { status: 'rebuilt', by: 'self', packageName, dir: realDir };
1675
1770
  });
1676
1771
  }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * lockfile: regenerate an install root's OWN package-lock.json
3
+ * ([#938](https://github.com/Omega-JS-Stack/omega/issues/938)).
4
+ *
5
+ * ONE helper for its three callers: the pack step (`pack-local`, the staged
6
+ * shape), `omega i local` (`linkLocalPackages`) and `omega i live`
7
+ * (`restoreRegistrySpecs`).
8
+ *
9
+ * Two things a plain `npm install` gets wrong here, and this answers both:
10
+ * - `--prefix` makes npm treat `root` as the install root. Without it, npm in
11
+ * a directory that is itself a WORKSPACE of an outer root (every in-repo
12
+ * brand, under the omega monorepo's `brands/*`) climbs to that outer root
13
+ * and writes ITS lockfile, leaving the brand's own untouched.
14
+ * - npm KEEPS a locked link whose target's version satisfies a registry spec,
15
+ * so a lock from the local era (`link: true` into a checkout already at the
16
+ * published number) would survive the flip. Every @omega.js entry the
17
+ * deploy gate would refuse (`brandLockfileDrift`) is dropped first, with its
18
+ * link target, so npm resolves those afresh and keeps every other pin.
19
+ * - npm also honors a STALE path entry, a folder the lock names that is no
20
+ * longer on disk (a renamed target), and re-creates whatever link its
21
+ * dependencies declare. Those entries go first, with everything under them
22
+ * and every `node_modules/` link pointing into them.
23
+ *
24
+ * Transactional: a failed npm run writes the lock back byte for byte.
25
+ */
26
+ const fs = require('fs');
27
+ const path = require('path');
28
+ const jetpack = require('fs-jetpack');
29
+
30
+ const { safeInstall } = require('./safe-install.js');
31
+ const { brandLockfileDrift, staleLockPaths } = require('./brand-version.js');
32
+
33
+ /** A path equal to base, or nested beneath it. */
34
+ const under = (value, base) => value === base || value.startsWith(`${base}/`);
35
+
36
+ /**
37
+ * Drop every stale path entry (`staleLockPaths`), everything nested under it,
38
+ * and every `node_modules/` entry whose `resolved` points into it.
39
+ *
40
+ * @param {object} lock - The parsed lock (mutated).
41
+ * @param {string} root - The install root the keys are relative to.
42
+ * @returns {string[]} The stale paths dropped (the outermost of each tree).
43
+ */
44
+ function dropStalePaths(lock, root) {
45
+ const stale = staleLockPaths({ root, packages: lock.packages });
46
+ const outermost = stale.filter((key) => !stale.some((other) => other !== key && under(key, other)));
47
+
48
+ for (const [key, entry] of Object.entries(lock.packages)) {
49
+ const pointsInto = entry && typeof entry.resolved === 'string' && outermost.some((base) => under(entry.resolved, base));
50
+ if (outermost.some((base) => under(key, base)) || pointsInto) delete lock.packages[key];
51
+ }
52
+
53
+ return outermost;
54
+ }
55
+
56
+ /**
57
+ * Drop every lock entry `drift` names, plus a dropped link's target entry and
58
+ * everything nested under it (the checkout's own dependencies).
59
+ *
60
+ * @param {object} lock - The parsed lock (mutated).
61
+ * @param {Array<{ key: string|null, entry: object|undefined }>} drift - What the gate refused.
62
+ * @returns {string[]} The names whose entries were dropped.
63
+ */
64
+ function dropDrift(lock, drift) {
65
+ const dropped = [];
66
+
67
+ for (const { name, key, entry } of drift) {
68
+ // An absent entry has nothing to drop (npm adds it from the manifest), and
69
+ // one inside a stale path is already gone with it.
70
+ if (!key || !lock.packages[key]) continue;
71
+
72
+ delete lock.packages[key];
73
+ dropped.push(name);
74
+
75
+ if (entry.link && entry.resolved) {
76
+ for (const other of Object.keys(lock.packages)) {
77
+ if (under(other, entry.resolved)) delete lock.packages[other];
78
+ }
79
+ }
80
+ }
81
+
82
+ return dropped;
83
+ }
84
+
85
+ /**
86
+ * Regenerate `root`'s package-lock.json from its manifests, lock-only: no
87
+ * scripts, no node_modules writes.
88
+ *
89
+ * @param {object} options
90
+ * @param {string} options.root - The install root whose lock is regenerated.
91
+ * @param {function} [options.log] - Line sink (silent when omitted).
92
+ * @returns {Promise<void>}
93
+ */
94
+ async function regenerateLockfile({ root, log }) {
95
+ const { lockPath, lock, drift } = brandLockfileDrift({ root });
96
+ const original = lock ? jetpack.read(lockPath) : null;
97
+
98
+ if (lock && lock.packages) {
99
+ const pruned = dropStalePaths(lock, root);
100
+ const dropped = dropDrift(lock, drift);
101
+
102
+ if (pruned.length || dropped.length) {
103
+ jetpack.write(lockPath, `${JSON.stringify(lock, null, 2)}\n`);
104
+ }
105
+ if (log && pruned.length) log(`Pruned the lock entries of folders no longer on disk (${pruned.join(', ')}) so npm stops honoring what they declare`);
106
+ if (log && dropped.length) log(`Dropped the stale lock entries of ${dropped.join(', ')} so npm resolves them from the manifests`);
107
+ }
108
+
109
+ if (log) log(`Regenerating ${path.basename(root)}/package-lock.json...`);
110
+
111
+ // The REAL path: npm writes a link's `resolved` relative to the physical
112
+ // directory, so a prefix spelled through an alias (macOS /var for
113
+ // /private/var, a symlinked brand dir) reads those paths from the wrong place.
114
+ const prefix = fs.realpathSync(root);
115
+
116
+ try {
117
+ await safeInstall(
118
+ `npm install --package-lock-only --ignore-scripts --no-audit --no-fund --prefix "${prefix}"`,
119
+ { log: false, config: { cwd: prefix } },
120
+ );
121
+ } catch (error) {
122
+ if (original !== null) jetpack.write(lockPath, original);
123
+ throw error;
124
+ }
125
+ }
126
+
127
+ module.exports = { regenerateLockfile };
@@ -23,8 +23,16 @@
23
23
  // - The Default section is replaced with the new framework's defaults.
24
24
  // - Keys that already had values (in either Default or Custom) keep those values
25
25
  // in the same section they were in.
26
- // - Keys the user added to the Default section that are NOT in the new framework's
27
- // defaults migrate to the Custom section (so framework cleanups don't lose user data).
26
+ // - The Default section is COMPLETELY managed ([#926](https://github.com/Omega-JS-Stack/omega/issues/926)):
27
+ // a line the new framework block no longer carries LEAVES the consumer, so a
28
+ // rule the framework retires or rewrites actually takes effect everywhere. A
29
+ // retired framework line and a line a user typed inside the framework block
30
+ // cannot be told apart, and the block header already says it is overwritten on
31
+ // every setup, so `.gitignore` and `AGENTS.md` drop it. Lines a user owns
32
+ // belong under the Custom marker, which stays verbatim.
33
+ // - .env is the one exception: a retired key holding a NON-EMPTY value migrates
34
+ // to the Custom section (that value is data, not a rule), while an empty
35
+ // retired key (`KEY=` / `KEY=""`) drops like any other retired line.
28
36
  // - .env values are normalized to **double-quoted** form on every merge:
29
37
  // KEY=raw-value → KEY="raw-value"
30
38
  // KEY="already-quoted" → KEY="already-quoted" (left alone)
@@ -110,24 +118,23 @@ function mergeLineBasedFiles(existingContent, newContent, fileName) {
110
118
  }
111
119
  }
112
120
 
113
- // User-added stuff in their Default section that the new framework doesn't know about
114
- // → migrate to Custom so it's preserved without being clobbered next setup.
121
+ // Anything in the existing Default section the new framework block no longer
122
+ // carries. The block is COMPLETELY managed (#926), so it DROPS: a retired
123
+ // framework line and a line the user typed into the framework block cannot be
124
+ // told apart, and the block header already says it is overwritten on every
125
+ // setup. The one exception is an .env key holding a real value, which is the
126
+ // user's DATA rather than a rule, so it migrates down to Custom; an empty one
127
+ // drops with the rest. .gitignore and AGENTS.md migrate nothing.
115
128
  const migratedToCustom = [];
116
- for (const line of existingDefault) {
117
- const trimmed = line.trim();
118
- if (!trimmed || trimmed.startsWith('#')) continue;
129
+ if (isEnvFile) {
130
+ for (const line of existingDefault) {
131
+ const trimmed = line.trim();
132
+ if (!trimmed || trimmed.startsWith('#')) continue;
119
133
 
120
- if (isEnvFile) {
121
134
  const key = trimmed.split('=')[0].trim();
122
- if (key && !newDefaultKeys.has(key) && !existingCustomKeys.has(key)) {
135
+ if (key && !newDefaultKeys.has(key) && !existingCustomKeys.has(key) && !envValueIsEmpty(trimmed)) {
123
136
  migratedToCustom.push(normalizeEnvLine(line));
124
137
  }
125
- } else {
126
- // .gitignore: line not in new defaults → migrate.
127
- const inNew = newLines.some((nl) => nl.trim() === trimmed);
128
- if (!inNew) {
129
- migratedToCustom.push(line);
130
- }
131
138
  }
132
139
  }
133
140
 
@@ -186,7 +193,7 @@ function normalizeEnvLine(line) {
186
193
  if (eqIdx < 0) return line; // no `=` — not a KEY=VALUE line
187
194
 
188
195
  const key = trimmed.slice(0, eqIdx).trim();
189
- let value = trimmed.slice(eqIdx + 1);
196
+ const value = trimmed.slice(eqIdx + 1);
190
197
 
191
198
  // Strip trailing whitespace + inline comment-after-value (rare; we only strip a # that follows a space).
192
199
  // We do NOT strip # inside quoted values. Detect that by checking if value starts with a quote.
@@ -253,9 +253,10 @@ async function run({ hostName, hostRun, argv = process.argv.slice(2) }) {
253
253
  const target = findTarget(process.cwd());
254
254
 
255
255
  // No context — the bootstrap case (a verb run in a fresh directory has
256
- // no framework dep yet, by definition). Run the HOST framework's CLI, exactly
257
- // like the pre-dispatcher bins did, and say which one so a hoist-winner at a
258
- // brand root is never a silent mystery.
256
+ // no framework dep yet, by definition). Run the INSTALLED @omega.js/manager
257
+ // when one resolves, else the HOST framework's CLI exactly like the
258
+ // pre-dispatcher bins did, and say which one so a hoist-winner at a brand
259
+ // root is never a silent mystery.
259
260
  if (!target) {
260
261
  // …but ONLY for a verb that cannot write. The fallback hands a mutating verb
261
262
  // to whichever framework won npm's bin link, and that verb scaffolds its own
@@ -279,6 +280,20 @@ async function run({ hostName, hostRun, argv = process.argv.slice(2) }) {
279
280
  process.exit(1);
280
281
  }
281
282
 
283
+ // The manager wins a contextless run whenever the install carries one: the
284
+ // verbs that survive the refusal above are the ones that run where no
285
+ // target exists yet (onboard, create, new), and those are the manager's own.
286
+ // Without this, a fresh brand-template clone that installed ONLY
287
+ // @omega.js/manager still ran the backend, because the manager DEPENDS on
288
+ // the backend and npm's arbitrary hoist winner linked the backend's bin:
289
+ // `npx omega onboard` answered `Unknown command`
290
+ // ([#908](https://github.com/Omega-JS-Stack/omega/issues/908)).
291
+ const manager = tryResolveCli(MANAGER, process.cwd());
292
+ if (manager.cliPath) {
293
+ console.error(`omega: no target context found from ${process.cwd()}: running ${MANAGER}`);
294
+ return require(manager.cliPath).run();
295
+ }
296
+
282
297
  console.error(`omega: no target context found from ${process.cwd()} — running ${hostName}`);
283
298
  return hostRun();
284
299
  }