@omega.js/desktop 0.52.0 → 0.54.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 (345) hide show
  1. package/README.md +53 -48
  2. package/dist/assets/css/core/_initialize.scss +1 -1
  3. package/dist/assets/js/core/app-shell.js +14 -15
  4. package/dist/assets/themes/_template/_theme.js +6 -6
  5. package/dist/assets/themes/base/_includes/global/sections/account.html +4 -4
  6. package/dist/assets/themes/base/_includes/global/sections/app-sidebar.html +5 -5
  7. package/dist/assets/themes/base/_layouts/frontend/pages/account/index.html +3 -3
  8. package/dist/assets/themes/base/_layouts/frontend/pages/alternatives/index.html +1 -1
  9. package/dist/assets/themes/base/_layouts/frontend/pages/blog/tags/tag.html +1 -1
  10. package/dist/assets/themes/base/_layouts/frontend/pages/payment/confirmation.html +1 -1
  11. package/dist/assets/themes/base/_sections/marketing/newsletter-cta/section.js +2 -3
  12. package/dist/assets/themes/base/_sections/verts/unit/section.html +1 -1
  13. package/dist/assets/themes/base/_sections/verts/unit/section.js +3 -4
  14. package/dist/assets/themes/base/_theme.js +4 -3
  15. package/dist/assets/themes/bootstrap/_theme.js +2 -2
  16. package/dist/assets/themes/bootstrap/overrides/_links.scss +1 -1
  17. package/dist/assets/themes/classy/_theme.js +8 -6
  18. package/dist/assets/themes/classy/css/marketing/_sections.scss +1 -2
  19. package/dist/assets/themes/classy/js/hero-demo-form.js +3 -2
  20. package/dist/assets/themes/neobrutalism/_theme.js +5 -5
  21. package/dist/assets/themes/neobrutalism/js/pages/test/libraries/layers/index.js +1 -1
  22. package/dist/assets/themes/newsflash/_theme.js +6 -6
  23. package/dist/assets/themes/newsflash/js/pages/test/libraries/layers/index.js +1 -1
  24. package/dist/build.js +87 -111
  25. package/dist/cli-run.js +4 -1
  26. package/dist/cli.js +3 -3
  27. package/dist/commands/build.js +2 -2
  28. package/dist/commands/cdp/capture.js +2 -2
  29. package/dist/commands/cdp/client.js +1 -1
  30. package/dist/commands/cdp/quit.js +2 -2
  31. package/dist/commands/cdp/relaunch.js +2 -2
  32. package/dist/commands/cdp/theme.js +1 -1
  33. package/dist/commands/cdp.js +1 -1
  34. package/dist/commands/clean.js +4 -5
  35. package/dist/commands/deploy.js +4 -4
  36. package/dist/commands/dev.js +25 -0
  37. package/dist/commands/finalize-release.js +4 -4
  38. package/dist/commands/launch.js +2 -2
  39. package/dist/commands/lib/deploy-precheck.js +3 -3
  40. package/dist/commands/lib/ensure-target.js +18 -23
  41. package/dist/commands/lib/migrate.js +17 -0
  42. package/dist/commands/logs.js +1 -1
  43. package/dist/commands/package.js +2 -2
  44. package/dist/commands/publish.js +2 -2
  45. package/dist/commands/release.js +4 -4
  46. package/dist/commands/runner.js +11 -11
  47. package/dist/commands/sign-windows.js +5 -5
  48. package/dist/commands/test.js +8 -8
  49. package/dist/commands/update.js +7 -6
  50. package/dist/commands/validate-certs.js +4 -4
  51. package/dist/commands/version.js +3 -3
  52. package/dist/defaults/.github/workflows/build.yml +18 -18
  53. package/dist/defaults/_.gitignore +0 -2
  54. package/dist/defaults/_mas/README.md +3 -3
  55. package/dist/defaults/config/certs/README.md +1 -1
  56. package/dist/defaults/config/omega.json5 +43 -43
  57. package/dist/defaults/docs/README.md +3 -3
  58. package/dist/defaults/gulpfile.js +1 -1
  59. package/dist/defaults/hooks/build/post.js +2 -2
  60. package/dist/defaults/hooks/build/pre.js +2 -2
  61. package/dist/defaults/hooks/deploy/pre.js +1 -1
  62. package/dist/defaults/hooks/notarize/post.js +2 -2
  63. package/dist/defaults/hooks/release/post.js +2 -2
  64. package/dist/defaults/hooks/release/pre.js +2 -2
  65. package/dist/defaults/src/assets/js/components/about/index.js +3 -5
  66. package/dist/defaults/src/assets/js/components/main/index.js +3 -5
  67. package/dist/defaults/src/assets/js/components/settings/index.js +3 -5
  68. package/dist/defaults/src/assets/scss/pages/about.scss +1 -1
  69. package/dist/defaults/src/assets/scss/pages/main.scss +1 -1
  70. package/dist/defaults/src/assets/scss/pages/settings.scss +1 -1
  71. package/dist/defaults/src/integrations/context-menu/index.js +13 -13
  72. package/dist/defaults/src/integrations/menu/index.js +8 -8
  73. package/dist/defaults/src/integrations/tray/index.js +12 -12
  74. package/dist/defaults/src/main.js +5 -7
  75. package/dist/defaults/src/preload.js +4 -6
  76. package/dist/defaults/test/README.md +5 -5
  77. package/dist/defaults/test/_init.js +1 -1
  78. package/dist/gulp/main.js +9 -10
  79. package/dist/gulp/tasks/audit.js +11 -14
  80. package/dist/gulp/tasks/build-config.js +8 -8
  81. package/dist/gulp/tasks/bundle.js +16 -16
  82. package/dist/gulp/tasks/defaults.js +3 -3
  83. package/dist/gulp/tasks/distribute.js +2 -2
  84. package/dist/gulp/tasks/html.js +9 -9
  85. package/dist/gulp/tasks/package-quick.js +3 -3
  86. package/dist/gulp/tasks/package.js +3 -3
  87. package/dist/gulp/tasks/release.js +3 -3
  88. package/dist/gulp/tasks/sass.js +6 -6
  89. package/dist/gulp/tasks/serve.js +4 -4
  90. package/dist/hooks/notarize-artifacts.js +1 -1
  91. package/dist/hooks/notarize.js +1 -1
  92. package/dist/index.js +5 -8
  93. package/dist/lib/_environment-mixin.js +50 -0
  94. package/dist/lib/_lifecycle-mixin.js +45 -0
  95. package/dist/lib/analytics.js +33 -35
  96. package/dist/lib/app-state.js +14 -14
  97. package/dist/lib/auth-flow.js +18 -18
  98. package/dist/lib/auth-persistence.js +12 -12
  99. package/dist/lib/auth.js +421 -0
  100. package/dist/lib/auto-updater.js +49 -49
  101. package/dist/lib/context-menu.js +13 -13
  102. package/dist/lib/context.js +19 -19
  103. package/dist/lib/deep-link.js +34 -34
  104. package/dist/lib/fontawesome.js +5 -5
  105. package/dist/lib/ipc.js +4 -4
  106. package/dist/lib/menu.js +25 -25
  107. package/dist/lib/protocol.js +5 -5
  108. package/dist/lib/remote-config.js +22 -22
  109. package/dist/lib/remote-scripts.js +21 -21
  110. package/dist/lib/restart-manager/index.js +29 -29
  111. package/dist/lib/restart-manager/install.js +1 -1
  112. package/dist/lib/restart-manager/protocol.js +1 -1
  113. package/dist/lib/sign-helpers/exec-with-limit.js +1 -1
  114. package/dist/lib/sign-helpers/sign-events.js +1 -1
  115. package/dist/lib/startup.js +18 -13
  116. package/dist/lib/storage.js +10 -10
  117. package/dist/lib/templating.js +16 -16
  118. package/dist/lib/theme.js +10 -10
  119. package/dist/lib/tray.js +27 -27
  120. package/dist/lib/usage.js +11 -11
  121. package/dist/lib/window-manager.js +26 -26
  122. package/dist/main.js +399 -483
  123. package/dist/preload.js +236 -176
  124. package/dist/renderer.js +417 -391
  125. package/dist/test/fixtures/consumer-app/config/omega.json5 +1 -1
  126. package/dist/test/fixtures/consumer-app/src/assets/js/components/main/index.js +8 -11
  127. package/dist/test/fixtures/consumer-app/src/main.js +5 -7
  128. package/dist/test/fixtures/consumer-app/src/preload.js +2 -2
  129. package/dist/test/harness/boot-entry.js +22 -20
  130. package/dist/test/harness/main-entry.js +31 -30
  131. package/dist/test/harness/renderer-entry.js +5 -5
  132. package/dist/test/harness/renderer-preload.js +137 -141
  133. package/dist/test/index.js +10 -10
  134. package/dist/test/runner.js +2 -2
  135. package/dist/test/runners/boot.js +7 -6
  136. package/dist/test/runners/electron.js +3 -2
  137. package/dist/test/runners/render-event.js +2 -2
  138. package/dist/test/suites/boot/consumer-app-boots.test.js +57 -26
  139. package/dist/test/suites/boot/restart-manager.test.js +2 -2
  140. package/dist/test/suites/boot/storage-bundled.test.js +5 -5
  141. package/dist/test/suites/boot/theme.test.js +13 -13
  142. package/dist/test/suites/build/audit.test.js +21 -8
  143. package/dist/test/suites/build/auth-persistence-resolve.test.js +6 -6
  144. package/dist/test/suites/build/boot-fixture.test.js +2 -2
  145. package/dist/test/suites/build/boot-runner-timeout.test.js +6 -5
  146. package/dist/test/suites/build/brand-scss.test.js +1 -1
  147. package/dist/test/suites/build/build-json-bake.test.js +1 -1
  148. package/dist/test/suites/build/build-workflow-jobs.test.js +2 -2
  149. package/dist/test/suites/build/cli.test.js +30 -2
  150. package/dist/test/suites/build/config-schema.test.js +5 -5
  151. package/dist/test/suites/build/defaults-em-dash.test.js +22 -0
  152. package/dist/test/suites/build/defaults-scaffold.test.js +21 -7
  153. package/dist/test/suites/build/deploy-direct.test.js +7 -5
  154. package/dist/test/suites/build/deploy-dispatch.test.js +2 -1
  155. package/dist/test/suites/build/deploy-hook.test.js +7 -5
  156. package/dist/test/suites/build/dev-verb.test.js +67 -0
  157. package/dist/test/suites/build/ensure-target.test.js +13 -5
  158. package/dist/test/suites/build/env-delivery.test.js +2 -2
  159. package/dist/test/suites/build/esm-only-dependency.test.js +2 -2
  160. package/dist/test/suites/build/exports.test.js +9 -8
  161. package/dist/test/suites/build/get-config.test.js +4 -4
  162. package/dist/test/suites/build/manifest-deps.test.js +1 -1
  163. package/dist/test/suites/build/merge-line-files.test.js +7 -7
  164. package/dist/test/suites/build/migrate.test.js +29 -0
  165. package/dist/test/suites/build/omega-shell.test.js +34 -2
  166. package/dist/test/suites/build/omega.test.js +350 -0
  167. package/dist/test/suites/build/project-scripts-deps.test.js +6 -10
  168. package/dist/test/suites/build/renderer-auth-bridge.test.js +211 -78
  169. package/dist/test/suites/build/runner-env-write.test.js +73 -0
  170. package/dist/test/suites/build/runner.test.js +11 -10
  171. package/dist/test/suites/build/sentry.test.js +2 -2
  172. package/dist/test/suites/build/setup-scripts.test.js +27 -0
  173. package/dist/test/suites/build/sign-windows-e2e.test.js +2 -2
  174. package/dist/test/suites/build/templating.test.js +3 -3
  175. package/dist/test/suites/build/test-stealth.test.js +7 -9
  176. package/dist/test/suites/build/url-helpers.test.js +55 -56
  177. package/dist/test/suites/build/validate-config.test.js +15 -4
  178. package/dist/test/suites/build/verb-logs.test.js +20 -0
  179. package/dist/test/suites/build/wave5-pins.test.js +2 -2
  180. package/dist/test/suites/main/analytics.test.js +59 -59
  181. package/dist/test/suites/main/app-state.test.js +66 -66
  182. package/dist/test/suites/main/auth-flow.test.js +41 -41
  183. package/dist/test/suites/main/auth-persistence.test.js +54 -43
  184. package/dist/test/suites/main/{client-bridge.integration.test.js → auth.integration.test.js} +10 -9
  185. package/dist/test/suites/main/auth.test.js +336 -0
  186. package/dist/test/suites/main/auto-updater.test.js +134 -134
  187. package/dist/test/suites/main/boot-sequence.test.js +37 -49
  188. package/dist/test/suites/main/context-menu.test.js +51 -50
  189. package/dist/test/suites/main/context.test.js +25 -25
  190. package/dist/test/suites/main/deep-link.test.js +74 -74
  191. package/dist/test/suites/main/fontawesome.test.js +27 -27
  192. package/dist/test/suites/main/ipc.test.js +35 -35
  193. package/dist/test/suites/main/menu.test.js +101 -100
  194. package/dist/test/suites/main/protocol.test.js +19 -19
  195. package/dist/test/suites/main/remote-config.test.js +63 -63
  196. package/dist/test/suites/main/remote-scripts.test.js +103 -103
  197. package/dist/test/suites/main/request.test.js +71 -0
  198. package/dist/test/suites/main/restart-manager.test.js +33 -33
  199. package/dist/test/suites/main/startup-paths-and-ua.test.js +4 -4
  200. package/dist/test/suites/main/startup.test.js +26 -26
  201. package/dist/test/suites/main/stealth-window.test.js +1 -1
  202. package/dist/test/suites/main/storage.test.js +25 -25
  203. package/dist/test/suites/main/theme.test.js +34 -34
  204. package/dist/test/suites/main/tray.test.js +79 -79
  205. package/dist/test/suites/main/url-helpers.test.js +133 -133
  206. package/dist/test/suites/main/usage.test.js +25 -25
  207. package/dist/test/suites/main/window-bounds.test.js +27 -27
  208. package/dist/test/suites/main/window-manager.test.js +44 -44
  209. package/dist/test/suites/renderer/analytics-bridge.test.js +5 -5
  210. package/dist/test/suites/renderer/cross-context-helpers.test.js +37 -31
  211. package/dist/test/suites/renderer/round-trip.test.js +3 -3
  212. package/dist/test/suites/renderer/tooltips.test.js +15 -15
  213. package/dist/test/suites/renderer/{window-em-surface.test.js → window-desktop-surface.test.js} +28 -8
  214. package/dist/test/utils/extended-mode-warning.js +1 -1
  215. package/dist/utils/boot-harness.js +56 -0
  216. package/dist/utils/build-pipeline.js +4 -4
  217. package/dist/utils/mode-helpers.js +2 -15
  218. package/dist/utils/runner-env.js +13 -28
  219. package/dist/utils/ship-keys.js +3 -3
  220. package/dist/utils/signing-status.js +51 -0
  221. package/dist/utils/test-events.js +7 -0
  222. package/dist/utils/test-stealth.js +6 -6
  223. package/dist/utils/url-helpers.js +52 -42
  224. package/dist/utils/user-agent.js +44 -0
  225. package/dist/vendor/account/engine.js +3 -3
  226. package/dist/vendor/account/index.js +14 -45
  227. package/dist/vendor/account/resolve-account.js +44 -0
  228. package/dist/vendor/account/schema.js +1 -1
  229. package/dist/vendor/account/user.js +99 -0
  230. package/dist/vendor/config/client-config.js +1 -1
  231. package/dist/vendor/config/company.js +46 -14
  232. package/dist/vendor/config/defaults.js +30 -7
  233. package/dist/vendor/config/edit.js +25 -3
  234. package/dist/vendor/config/env-delivery.js +1 -1
  235. package/dist/vendor/config/env-schema.js +3 -6
  236. package/dist/vendor/config/env.js +34 -22
  237. package/dist/vendor/config/environment.js +11 -30
  238. package/dist/vendor/config/index.js +21 -28
  239. package/dist/vendor/config/load.js +16 -9
  240. package/dist/vendor/config/platforms.js +1 -1
  241. package/dist/vendor/config/repo.js +10 -27
  242. package/dist/vendor/config/schema-client.js +64 -0
  243. package/dist/vendor/config/schema-cloud.js +38 -0
  244. package/dist/vendor/config/schema-manager.js +118 -0
  245. package/dist/vendor/config/schema-overrides.js +68 -0
  246. package/dist/vendor/config/schema.js +104 -160
  247. package/dist/vendor/config/site-global.js +2 -3
  248. package/dist/vendor/config/validate.js +97 -78
  249. package/dist/vendor/config/winback.js +1 -1
  250. package/dist/vendor/devkit/actions-secrets.js +1 -1
  251. package/dist/vendor/devkit/agents-md.js +233 -0
  252. package/dist/vendor/devkit/attach-log-file.js +16 -2
  253. package/dist/vendor/devkit/build-json.js +1 -1
  254. package/dist/vendor/devkit/ci-workflows.js +30 -30
  255. package/dist/vendor/devkit/cli-router.js +16 -11
  256. package/dist/vendor/devkit/defaults-engine.js +15 -51
  257. package/dist/vendor/devkit/deploy-snapshot.js +44 -9
  258. package/dist/vendor/devkit/env-lines.js +183 -0
  259. package/dist/vendor/devkit/local.js +64 -10
  260. package/dist/vendor/devkit/lockfile.js +32 -13
  261. package/dist/vendor/devkit/logger.js +7 -2
  262. package/dist/vendor/devkit/merge-line-files.js +219 -177
  263. package/dist/vendor/devkit/omega-bin.js +208 -111
  264. package/dist/vendor/devkit/preludes/docs-sync.js +52 -0
  265. package/dist/vendor/devkit/preludes/index.js +1 -0
  266. package/dist/vendor/devkit/target-picker.js +45 -0
  267. package/dist/vendor/devkit/test/dashed-files.js +37 -0
  268. package/dist/vendor/devkit/test/run-verb-under-tee.js +71 -0
  269. package/dist/vendor/devkit/test/runner-core.js +6 -6
  270. package/dist/vendor/devkit/update.js +15 -15
  271. package/dist/vendor/devkit/verb-scripts.js +40 -0
  272. package/dist/vendor/devkit/verbs.js +170 -0
  273. package/dist/vendor/monitoring/env.js +2 -2
  274. package/dist/vendor/monitoring/index.js +1 -1
  275. package/dist/vendor/monitoring/main.js +1 -1
  276. package/dist/vendor/monitoring/preload.js +1 -1
  277. package/dist/vendor/monitoring/renderer.js +1 -1
  278. package/package.json +19 -26
  279. package/dist/commands/install.js +0 -37
  280. package/dist/defaults/AGENTS.md +0 -110
  281. package/dist/defaults/CLAUDE.md +0 -1
  282. package/dist/lib/client-bridge.js +0 -374
  283. package/dist/lib/logger.js +0 -4
  284. package/dist/test/suites/build/manager.test.js +0 -213
  285. package/dist/test/suites/main/client-bridge.test.js +0 -262
  286. package/dist/vendor/config/env-retired.js +0 -137
  287. package/dist/vendor/config/retired-keys.js +0 -635
  288. package/docs/analytics.md +0 -140
  289. package/docs/app-state.md +0 -92
  290. package/docs/audit.md +0 -69
  291. package/docs/auto-updater.md +0 -243
  292. package/docs/boot-sequence.md +0 -39
  293. package/docs/build-system.md +0 -169
  294. package/docs/cdp-debugging.md +0 -169
  295. package/docs/client-bridge.md +0 -269
  296. package/docs/common-mistakes.md +0 -21
  297. package/docs/config-schema.md +0 -120
  298. package/docs/context-menu.md +0 -112
  299. package/docs/context.md +0 -81
  300. package/docs/css.md +0 -90
  301. package/docs/deep-link.md +0 -186
  302. package/docs/environment-detection.md +0 -112
  303. package/docs/fontawesome.md +0 -107
  304. package/docs/hooks.md +0 -89
  305. package/docs/icons.md +0 -79
  306. package/docs/index.md +0 -317
  307. package/docs/installer-options.md +0 -165
  308. package/docs/ipc.md +0 -61
  309. package/docs/lib-modules.md +0 -53
  310. package/docs/logging.md +0 -229
  311. package/docs/menu.md +0 -160
  312. package/docs/releasing.md +0 -239
  313. package/docs/remote-config.md +0 -118
  314. package/docs/remote-scripts.md +0 -144
  315. package/docs/restart-manager.md +0 -144
  316. package/docs/runner.md +0 -290
  317. package/docs/sentry.md +0 -97
  318. package/docs/shared/agent-docs.md +0 -89
  319. package/docs/shared/analytics.md +0 -612
  320. package/docs/shared/brands.md +0 -57
  321. package/docs/shared/breaking-changes.md +0 -851
  322. package/docs/shared/config.md +0 -1952
  323. package/docs/shared/deploys.md +0 -341
  324. package/docs/shared/icons.md +0 -219
  325. package/docs/shared/local-dev.md +0 -167
  326. package/docs/shared/logging.md +0 -205
  327. package/docs/shared/monitoring.md +0 -167
  328. package/docs/shared/publishing.md +0 -187
  329. package/docs/shared/rulings.md +0 -34
  330. package/docs/shared/testing.md +0 -147
  331. package/docs/shared/theming.md +0 -629
  332. package/docs/shared/translation.md +0 -333
  333. package/docs/shared/updates.md +0 -61
  334. package/docs/signing.md +0 -293
  335. package/docs/startup.md +0 -142
  336. package/docs/storage.md +0 -59
  337. package/docs/templating.md +0 -101
  338. package/docs/test-boot-layer.md +0 -157
  339. package/docs/test-framework.md +0 -362
  340. package/docs/themes.md +0 -149
  341. package/docs/tooltips.md +0 -99
  342. package/docs/tray.md +0 -164
  343. package/docs/usage.md +0 -58
  344. package/docs/verts.md +0 -62
  345. package/docs/windows.md +0 -149
@@ -0,0 +1,183 @@
1
+ /**
2
+ * The .env line grammar the marker engine merges by and every in-place key
3
+ * writer sets keys by: logical lines (a quoted value spanning several physical
4
+ * lines is one unit), the key a line assigns, the double-quote normalization,
5
+ * and the replace-or-append of a key's lines. dotenv is the reader every OMEGA loader
6
+ * uses, so it is the judge: a normalized line must read back to the same value,
7
+ * and a key's winning line is its LAST one, as dotenv reads it.
8
+ */
9
+ const dotenv = require('dotenv');
10
+ const { envLine } = require('../config/index.js');
11
+
12
+ // An assignment dotenv accepts: optional indent and `export `, then KEY=
13
+ const ASSIGNMENT = /^(\s*(?:export\s+)?)([\w.-]+)\s*=(.*)$/s;
14
+ const QUOTED_OPEN = /^\s*(?:export\s+)?[\w.-]+\s*=\s*(["'`])/;
15
+
16
+ /**
17
+ * The index of the quote closing a value opened at `from`, as dotenv picks it:
18
+ * a quote after a backslash may close the value or be passed, the first quote
19
+ * after no backslash cannot be passed, and dotenv takes the LAST closing
20
+ * candidate followed on its line by only whitespace or a `# comment`, so
21
+ * `K="x\"` reads `x\`. -1 when none closes it.
22
+ *
23
+ * @param {string} text
24
+ * @param {string} quote - `"`, `'` or a backtick
25
+ * @param {number} from - First index after the opening quote
26
+ * @returns {number}
27
+ */
28
+ function closingQuote(text, quote, from) {
29
+ const lineRest = /[^\S\n]*(?:#[^\n]*)?(?:\n|$)/y;
30
+ let close = -1;
31
+ for (let i = from; i < text.length; i += 1) {
32
+ if (text[i] !== quote) continue;
33
+ lineRest.lastIndex = i + 1;
34
+ if (lineRest.test(text)) close = i;
35
+ if (text[i - 1] !== '\\') break;
36
+ }
37
+ return close;
38
+ }
39
+
40
+ /**
41
+ * Group physical lines into logical ones: a quoted value that closes on a later
42
+ * line joins those lines (newlines kept) into one unit, so it moves and
43
+ * survives whole. An unterminated quote stays line by line, as dotenv reads it.
44
+ *
45
+ * @param {string[]} lines - The file split on `\n`
46
+ * @returns {string[]}
47
+ */
48
+ function joinEnvUnits(lines) {
49
+ const units = [];
50
+ for (let i = 0; i < lines.length; i += 1) {
51
+ const open = lines[i].match(QUOTED_OPEN);
52
+ const rest = open ? lines.slice(i).join('\n') : '';
53
+ const close = open ? closingQuote(rest, open[1], open[0].length) : -1;
54
+ if (close < 0) {
55
+ units.push(lines[i]);
56
+ continue;
57
+ }
58
+
59
+ const end = i + rest.slice(0, close).split('\n').length - 1;
60
+ units.push(lines.slice(i, end + 1).join('\n'));
61
+ i = end;
62
+ }
63
+ return units;
64
+ }
65
+
66
+ /**
67
+ * The key a line assigns, or null for a comment, a blank, or anything else.
68
+ *
69
+ * @param {string} line
70
+ * @returns {string|null}
71
+ */
72
+ function envKey(line) {
73
+ const match = line.match(ASSIGNMENT);
74
+ return match ? match[2] : null;
75
+ }
76
+
77
+ // The inline comment after a raw value: past the closing quote of a quoted
78
+ // value, from the first `#` of an unquoted one.
79
+ function trailingComment(raw) {
80
+ let rest = raw;
81
+ if (raw[0] === "'" || raw[0] === '`') {
82
+ const close = closingQuote(raw, raw[0], 1);
83
+ if (close >= 0) rest = raw.slice(close + 1);
84
+ }
85
+ const hash = rest.indexOf('#');
86
+ return hash < 0 ? '' : rest.slice(hash).trim();
87
+ }
88
+
89
+ /**
90
+ * Normalize one .env line to the double-quoted form: `KEY=raw` and `KEY='x'`
91
+ * become `KEY="..."`, an inline comment kept; an empty value becomes `KEY=""`;
92
+ * a double-quoted value, a comment and a blank are left alone. The rewrite
93
+ * happens only when dotenv reads the same value back; otherwise the line stays.
94
+ *
95
+ * @param {string} line - One logical line
96
+ * @returns {string}
97
+ */
98
+ function normalizeEnvLine(line) {
99
+ const match = line.match(ASSIGNMENT);
100
+ if (!match) return line;
101
+ const [, prefix, key, rawValue] = match;
102
+ const raw = rawValue.trim();
103
+
104
+ if (raw === '') return `${prefix}${key}=""`;
105
+ if (raw.startsWith('"')) return line;
106
+
107
+ const value = dotenv.parse(line)[key];
108
+ if (value === undefined) return line;
109
+
110
+ let quoted;
111
+ try {
112
+ quoted = envLine(key, value);
113
+ } catch (e) {
114
+ // envLine refuses a value dotenv would read back changed: the line stays
115
+ return line;
116
+ }
117
+ const comment = trailingComment(raw);
118
+ const candidate = `${prefix}${quoted}${comment ? ` ${comment}` : ''}`;
119
+ return dotenv.parse(candidate)[key] === value ? candidate : line;
120
+ }
121
+
122
+ /**
123
+ * The line that sets `key`: the LAST one, the value dotenv reads.
124
+ *
125
+ * @param {string[]} lines
126
+ * @param {string} key
127
+ * @returns {string} - `KEY=""` when no line sets it
128
+ */
129
+ function findKeyLine(lines, key) {
130
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
131
+ if (envKey(lines[i]) === key) return lines[i];
132
+ }
133
+ return `${key}=""`;
134
+ }
135
+
136
+ // A placeholder, `# KEY=""` or a bare `# KEY=`, → KEY; any other comment → null.
137
+ function parsePlaceholderKey(trimmed) {
138
+ const match = trimmed.match(/^#\s*([A-Za-z_][A-Za-z0-9_]*)=\s*(?:"")?\s*$/);
139
+ return match ? match[1] : null;
140
+ }
141
+
142
+ /**
143
+ * Set keys in .env content by logical line: the first unit assigning a key, or
144
+ * its `# KEY=""` placeholder, becomes the given line and every later one drops,
145
+ * so dotenv reads the new value; a key the content never had is appended.
146
+ * Every other line keeps its place.
147
+ *
148
+ * @param {string} content - Current file content ('' for none)
149
+ * @param {Object<string, string>} lineFor - KEY to its whole line, e.g. envLine's
150
+ * @returns {string} The new content, ending in exactly one newline
151
+ */
152
+ function setEnvLines(content, lineFor) {
153
+ const body = content.replace(/(?:\r?\n)+$/, '');
154
+ const seen = new Set();
155
+
156
+ const out = joinEnvUnits(body === '' ? [] : body.split(/\r?\n/)).flatMap((unit) => {
157
+ const key = envKey(unit) ?? parsePlaceholderKey(unit.trim());
158
+ if (key === null || !Object.hasOwn(lineFor, key)) return [unit];
159
+ if (seen.has(key)) return [];
160
+ seen.add(key);
161
+ return [lineFor[key]];
162
+ });
163
+ for (const key of Object.keys(lineFor)) {
164
+ if (!seen.has(key)) out.push(lineFor[key]);
165
+ }
166
+ return `${out.join('\n')}\n`;
167
+ }
168
+
169
+ // The framework's own .env comment grammar: a boxed group header or a
170
+ // `# KEY=""` placeholder. Regenerated from the template, never a consumer's note.
171
+ function isMachineComment(trimmed) {
172
+ return /^# ── .* ──$/.test(trimmed) || parsePlaceholderKey(trimmed) !== null;
173
+ }
174
+
175
+ // KEY= / KEY="" / KEY='' (whitespace tolerated) count as empty.
176
+ function envValueIsEmpty(line) {
177
+ const eqIdx = line.indexOf('=');
178
+ if (eqIdx < 0) return true;
179
+ const value = line.slice(eqIdx + 1).trim();
180
+ return value === '' || value === '""' || value === "''";
181
+ }
182
+
183
+ module.exports = { joinEnvUnits, envKey, normalizeEnvLine, findKeyLine, parsePlaceholderKey, isMachineComment, envValueIsEmpty, setEnvLines };
@@ -16,9 +16,9 @@
16
16
  * into the monorepo is read-only and stops the boot loudly, any other local
17
17
  * checkout is rebuilt and the invocation re-execs once
18
18
  *
19
- * Consumers: `omega dev --local` (@omega.js/web), `mgr i local`
20
- * (@omega.js/backend, @omega.js/desktop, @omega.js/extension), and the monorepo's
21
- * own scripts/watch-all.js (lock helpers + vendor propagation).
19
+ * Consumers: `omega dev --local` (@omega.js/web), @omega.js/manager's
20
+ * brand-root `omega i local` / `omega i live` (src/commands/install.js), and the
21
+ * monorepo's own scripts/watch-all.js (lock helpers + vendor propagation).
22
22
  */
23
23
 
24
24
  // Libraries
@@ -276,6 +276,43 @@ function regenerateBrandLockfile(installRoot, logger) {
276
276
  return regenerateLockfile({ root: installRoot, log: logger ? (line) => logger.log(line) : undefined });
277
277
  }
278
278
 
279
+ /**
280
+ * Prune the install root's lockfile before a tree install (#862): npm's install
281
+ * honors a locked link just as its lock-only run does. Loaded lazily, as above.
282
+ * @param {string} installRoot - The brand root (or standalone target).
283
+ * @param {object} [logger] - Logger with log (silent when omitted).
284
+ */
285
+ function pruneBrandLockfile(installRoot, logger) {
286
+ const { pruneLockfile } = require('./lockfile.js');
287
+ pruneLockfile({ root: installRoot, log: logger ? (line) => logger.log(line) : undefined });
288
+ }
289
+
290
+ /**
291
+ * Throw when any @omega.js package of the tree still resolves into a checkout
292
+ * (#862): a symlink under the install root's or any target's node_modules, or
293
+ * a resolution that lands in a monorepo. The one check `omega i live` ends on.
294
+ * @param {string} installRoot - The brand root (or standalone target).
295
+ */
296
+ function assertRegistryTree(installRoot) {
297
+ for (const targetDir of discoverTargets(installRoot)) {
298
+ const scopeDir = path.join(targetDir, 'node_modules', SCOPE);
299
+ // No scope folder here: this target's copies are hoisted, or it has none.
300
+ if (!fs.existsSync(scopeDir)) continue;
301
+
302
+ for (const name of fs.readdirSync(scopeDir)) {
303
+ const installed = path.join(scopeDir, name);
304
+ if (fs.lstatSync(installed).isSymbolicLink()) {
305
+ throw new Error(`omega i live left ${installed} linked into ${path.resolve(scopeDir, fs.readlinkSync(installed))}`);
306
+ }
307
+ }
308
+ }
309
+
310
+ const monorepoRoot = resolveLinkedMonorepo(installRoot);
311
+ if (monorepoRoot) {
312
+ throw new Error(`omega i live left ${installRoot} linked into ${monorepoRoot}`);
313
+ }
314
+ }
315
+
279
316
  /**
280
317
  * Whether the install root's lockfile is missing or disagrees with its
281
318
  * manifests' @omega.js registry specs: the deploy gate's own read (#938).
@@ -301,9 +338,10 @@ function brandLockfileStale(installRoot) {
301
338
  * ONE `npm install` materializes the links for the whole tree. Standalone
302
339
  * targets degenerate to themselves.
303
340
  *
304
- * Idempotent: dependencies already resolving to the monorepo copy are
305
- * skipped, and when nothing needs linking no install runs. An install is
306
- * followed by the brand's OWN lockfile regeneration (#938).
341
+ * Idempotent: dependencies already resolving to the monorepo copy AND
342
+ * already declaring its file: spec are skipped, and when nothing needs
343
+ * linking no install runs. An install is followed by the brand's OWN
344
+ * lockfile regeneration (#938).
307
345
  * @param {object} options
308
346
  * @param {string} options.dir - Target directory to link from (any target in the brand).
309
347
  * @param {string} options.monorepoRoot - Monorepo root path.
@@ -339,13 +377,13 @@ async function linkLocalPackages(options) {
339
377
  continue;
340
378
  }
341
379
 
342
- if (isLinkedTo(entry.dir, entry.name, target)) {
380
+ const spec = `file:${relativeSpecPath(entry.dir, target)}`;
381
+ if (isLinkedTo(entry.dir, entry.name, target) && entry.spec === spec) {
343
382
  actions.push({ name: entry.name, dir: entry.dir, target, action: 'skip' });
344
383
  logger && logger.log(`${entry.name}: already linked to the monorepo`);
345
384
  continue;
346
385
  }
347
386
 
348
- const spec = `file:${relativeSpecPath(entry.dir, target)}`;
349
387
  if (entry.spec === spec) {
350
388
  // Spec already correct, only the install is missing — no rewrite,
351
389
  // so a committed relative spec is never churned to a new layout.
@@ -398,8 +436,9 @@ async function linkLocalPackages(options) {
398
436
  * caller owns that string. Idempotent: registry-spec'd entries are untouched;
399
437
  * nothing to flip → no install. Same transactional manifest restore as the
400
438
  * 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.
439
+ * agreeing with the manifests (#938): pruned before the install and
440
+ * regenerated after it, and regenerated alone when nothing flipped but the lock
441
+ * still disagrees. Every run but a dryRun ends on assertRegistryTree() (#862).
403
442
  * @param {object} options
404
443
  * @param {string} options.dir - Any directory inside the brand.
405
444
  * @param {object} [options.logger] - Logger with log/warn (silent when omitted).
@@ -456,12 +495,21 @@ async function restoreRegistrySpecs(options) {
456
495
  }
457
496
 
458
497
  if (installNeeded) {
498
+ // The prune below rewrites the lock, so a failed install puts it back too.
499
+ const lockPath = path.join(installRoot, 'package-lock.json');
500
+ const lockBackup = fs.existsSync(lockPath) ? fs.readFileSync(lockPath, 'utf8') : null;
501
+
459
502
  try {
503
+ // BEFORE the install (#862): npm keeps a locked link whose checkout
504
+ // satisfies the flipped spec, in the lock and in node_modules' hidden lock.
505
+ pruneBrandLockfile(installRoot, logger);
506
+ fs.rmSync(path.join(installRoot, 'node_modules', '.package-lock.json'), { force: true });
460
507
  await safeInstall('npm install', { log: true, config: { cwd: installRoot } });
461
508
  } catch (error) {
462
509
  for (const [manifestPath, contents] of manifestBackups) {
463
510
  fs.writeFileSync(manifestPath, contents);
464
511
  }
512
+ if (lockBackup !== null) fs.writeFileSync(lockPath, lockBackup);
465
513
  logger && logger.warn(`install failed — restored ${manifestBackups.size} manifest(s) to their file: specs`);
466
514
  throw error;
467
515
  }
@@ -476,6 +524,10 @@ async function restoreRegistrySpecs(options) {
476
524
  await regenerateBrandLockfile(installRoot, logger);
477
525
  }
478
526
 
527
+ // Every run, not only after an install: a brand already on registry specs
528
+ // can still be linked, which is the state this verb exists to end.
529
+ assertRegistryTree(installRoot);
530
+
479
531
  return actions;
480
532
  }
481
533
 
@@ -1981,6 +2033,8 @@ module.exports = {
1981
2033
  findBrandRoot,
1982
2034
  discoverTargets,
1983
2035
  frameworkPackagesOf,
2036
+ resolvePackageRealDir,
2037
+ isLocalCheckout,
1984
2038
  linkLocalPackages,
1985
2039
  restoreRegistrySpecs,
1986
2040
  resolveLinkedMonorepo,
@@ -82,6 +82,34 @@ function dropDrift(lock, drift) {
82
82
  return dropped;
83
83
  }
84
84
 
85
+ /**
86
+ * Prune `root`'s package-lock.json in place: the stale path entries, then the
87
+ * @omega.js entries the deploy gate refuses. The lock-only regeneration runs it
88
+ * first, and so does `omega i live` before its tree install
89
+ * ([#862](https://github.com/Omega-JS-Stack/omega/issues/862)), since that
90
+ * install honors the lock's old links just the same.
91
+ *
92
+ * @param {object} options
93
+ * @param {string} options.root - The install root whose lock is pruned.
94
+ * @param {function} [options.log] - Line sink (silent when omitted).
95
+ * @returns {void}
96
+ */
97
+ function pruneLockfile({ root, log }) {
98
+ const { lockPath, lock, drift } = brandLockfileDrift({ root });
99
+
100
+ // No lock, or one without a `packages` map, has no entry to prune.
101
+ if (!lock || !lock.packages) return;
102
+
103
+ const pruned = dropStalePaths(lock, root);
104
+ const dropped = dropDrift(lock, drift);
105
+
106
+ if (pruned.length || dropped.length) {
107
+ jetpack.write(lockPath, `${JSON.stringify(lock, null, 2)}\n`);
108
+ }
109
+ if (log && pruned.length) log(`Pruned the lock entries of folders no longer on disk (${pruned.join(', ')}) so npm stops honoring what they declare`);
110
+ if (log && dropped.length) log(`Dropped the stale lock entries of ${dropped.join(', ')} so npm resolves them from the manifests`);
111
+ }
112
+
85
113
  /**
86
114
  * Regenerate `root`'s package-lock.json from its manifests, lock-only: no
87
115
  * scripts, no node_modules writes.
@@ -92,19 +120,10 @@ function dropDrift(lock, drift) {
92
120
  * @returns {Promise<void>}
93
121
  */
94
122
  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);
123
+ const lockPath = path.join(root, 'package-lock.json');
124
+ const original = jetpack.exists(lockPath) === 'file' ? jetpack.read(lockPath) : null;
101
125
 
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
- }
126
+ pruneLockfile({ root, log });
108
127
 
109
128
  if (log) log(`Regenerating ${path.basename(root)}/package-lock.json...`);
110
129
 
@@ -124,4 +143,4 @@ async function regenerateLockfile({ root, log }) {
124
143
  }
125
144
  }
126
145
 
127
- module.exports = { regenerateLockfile };
146
+ module.exports = { pruneLockfile, regenerateLockfile };
@@ -1,5 +1,5 @@
1
1
  // Build-time logger — timestamped, tag-scoped console output for CLI commands, gulp
2
- // tasks, and test runners. `new Logger('setup')` then logger.log/error/warn/info,
2
+ // tasks, and test runners. `new Logger('setup')` then logger.log/error/warn/info/debug,
3
3
  // which prints `[HH:MM:SS] [@omega.js/<package>:setup] message`.
4
4
  //
5
5
  // NOT for app/runtime logging — frameworks with runtime logging needs (e.g. desktop's
@@ -87,8 +87,13 @@ function Logger(name) {
87
87
  }
88
88
 
89
89
  // Make methods that log to console with the tag and time: [xx:xx:xx] [@omega.js/pkg:name] message
90
- ['log', 'error', 'warn', 'info'].forEach((method) => {
90
+ ['log', 'error', 'warn', 'info', 'debug'].forEach((method) => {
91
91
  Logger.prototype[method] = function () {
92
+ // debug is the one opt-in level (docs/shared/logging.md): OMEGA_DEBUG, read live.
93
+ if (method === 'debug' && !process.env.OMEGA_DEBUG) {
94
+ return;
95
+ }
96
+
92
97
  const time = new Date().toLocaleTimeString('en-US', {
93
98
  hour12: false,
94
99
  hour: '2-digit',