@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,44 @@
1
+ // user-agent: the global user agent fallback every @omega.js/desktop app carries,
2
+ // so web requests (BrowserWindow loads, fetch, electron-updater downloads) send a
3
+ // branded UA: Mozilla parsers see a normal Chrome UA and the app's own
4
+ // server-side telemetry sees `{brand.name}/{app.version}`. Merge tags use
5
+ // node-powertools.template (single-curly syntax). A consumer overrides it by
6
+ // re-setting `app.userAgentFallback` from its main.js after initialize().
7
+
8
+ const TEMPLATES = {
9
+ darwin: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) {brand.name}/{app.version} Chrome/{chrome} Safari/537.36',
10
+ win32: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) {brand.name}/{app.version} Chrome/{chrome} Safari/537.36',
11
+ linux: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) {brand.name}/{app.version} Chrome/{chrome} Safari/537.36',
12
+ };
13
+
14
+ /**
15
+ * Set `app.userAgentFallback` from the brand and this process's versions.
16
+ * @param {object} omega - the main-process instance (its config, getVersion() and logger).
17
+ * @returns {string} the user agent it set.
18
+ */
19
+ function applyUserAgent(omega) {
20
+ const { template } = require('node-powertools');
21
+ const { app } = require('electron');
22
+
23
+ const ua = template(TEMPLATES[process.platform] || TEMPLATES.linux, {
24
+ brand: {
25
+ name: omega.config.brand.name,
26
+ id: omega.config.brand.id,
27
+ },
28
+ app: {
29
+ version: omega.getVersion(),
30
+ },
31
+ chrome: process.versions.chrome,
32
+ electron: process.versions.electron,
33
+ node: process.versions.node,
34
+ platform: process.platform,
35
+ arch: process.arch,
36
+ });
37
+
38
+ app.userAgentFallback = ua;
39
+ omega.logger.log(`userAgent: ${ua}`);
40
+
41
+ return ua;
42
+ }
43
+
44
+ module.exports = applyUserAgent;
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Schema resolution engine — extracted verbatim from @omega.js/backend's
3
- * src/manager/helpers/user.js, with ONE change: the '$uuid'/'$randomId'/'$apiKey'
2
+ * Schema resolution engine, extracted verbatim from @omega.js/backend's former
3
+ * user helper with ONE change: the '$uuid'/'$randomId'/'$apiKey'
4
4
  * value generators are injected via ctx.generators instead of being required
5
- * in-module. Hosts that can generate them (@omega.js/backend, Node) inject real generators;
5
+ * in-module. Hosts that can generate them (@omega.js/backend, Node) inject real generators (src/omega/services/user.js);
6
6
  * hosts that can't (@omega.js/client, browser) pass none and the fields resolve to
7
7
  * null — the doc's real values always come from the backend.
8
8
  *
@@ -1,53 +1,22 @@
1
1
  /**
2
- * @omega.js/account — the single source of truth for the OMEGA user/account schema.
2
+ * @omega.js/account: the single source of truth for the OMEGA user/account schema.
3
3
  *
4
- * Today the account shape lives in two drifted places: @omega.js/backend's
5
- * src/manager/helpers/user.js (canonical — schema engine + tokens) and
6
- * @omega.js/client's DEFAULT_ACCOUNT in modules/auth.js (hand-rolled deep-merge,
7
- * missing flags/activity/personal branches and `everPaid`). This package
8
- * unifies them; each framework becomes a thin wrapper (@omega.js/backend keeps its
9
- * `new User(Manager, settings).properties` API, @omega.js/client keeps
10
- * `resolveAccount(rawData, firebaseUser)`).
4
+ * The account shape once lived in two drifted places: @omega.js/backend's user
5
+ * helper (canonical, schema engine + tokens) and @omega.js/client's hand-rolled
6
+ * DEFAULT_ACCOUNT (missing flags/activity/personal branches and `everPaid`).
7
+ * This package unifies them; each framework is a thin wrapper (the backend's
8
+ * src/omega/services/user.js, the client's modules/auth.js).
11
9
  *
12
- * resolveAccount(data, options):
13
- * options.generators — { uuid, randomId, apiKey } value generators for the
14
- * '$uuid'/'$randomId'/'$apiKey' schema tokens. @omega.js/backend injects real ones
15
- * (uuid v4, Utilities().randomId, uid-generator); the frontend passes none
16
- * and the fields resolve to null (real values always come from the backend).
17
- * options.user — optional auth-user overlay ({ uid, email }): fills
18
- * account.auth identity when the doc doesn't carry it (@omega.js/client's
19
- * resolveAccount(rawData, firebaseUser) semantic).
10
+ * Exports:
11
+ * User: the account as a class (user.js), the stored document as own fields
12
+ * plus the computed getters.
13
+ * resolveAccount(data, options): the plain resolved document (resolve-account.js).
14
+ * resolveSubscription(account): the subscription math (subscription.js).
20
15
  */
21
16
  const USER_SCHEMA = require('./schema.js');
22
- const { resolve } = require('./engine.js');
17
+ const resolveAccount = require('./resolve-account.js');
23
18
  const resolveSubscription = require('./subscription.js');
19
+ const User = require('./user.js');
24
20
  const features = require('./features.js');
25
21
 
26
- function resolveAccount(data, options) {
27
- options = options || {};
28
-
29
- // Time context — byte-identical to @omega.js/backend's node-powertools timestamps:
30
- // powertools.timestamp(d, { output: 'string' }) === d.toISOString(),
31
- // powertools.timestamp(d, { output: 'unix' }) === floor(ms / 1000).
32
- const now = new Date();
33
- const ctx = {
34
- generators: options.generators || {},
35
- now: now.toISOString(),
36
- nowUNIX: Math.floor(now.getTime() / 1000),
37
- oldDate: new Date(0).toISOString(),
38
- oldDateUNIX: 0,
39
- };
40
-
41
- const account = resolve(USER_SCHEMA, data || {}, ctx);
42
-
43
- // Auth-user overlay (frontend semantic): identity from the signed-in Firebase
44
- // user when the stored doc doesn't already carry it
45
- if (options.user) {
46
- account.auth.uid = account.auth.uid || options.user.uid || null;
47
- account.auth.email = account.auth.email || options.user.email || null;
48
- }
49
-
50
- return account;
51
- }
52
-
53
- module.exports = { USER_SCHEMA, resolveAccount, resolveSubscription, ...features };
22
+ module.exports = { USER_SCHEMA, User, resolveAccount, resolveSubscription, ...features };
@@ -0,0 +1,44 @@
1
+ /**
2
+ * resolveAccount: the ONE document resolution, shared by the User class and
3
+ * every caller that still wants the plain resolved object.
4
+ *
5
+ * resolveAccount(data, options):
6
+ * options.generators: { uuid, randomId, apiKey } value generators for the
7
+ * '$uuid'/'$randomId'/'$apiKey' schema tokens. @omega.js/backend injects real ones
8
+ * (uuid v4, Utilities().randomId, uid-generator); the frontend passes none
9
+ * and the fields resolve to null (real values always come from the backend).
10
+ * options.user: optional auth-user overlay ({ uid, email }): fills
11
+ * account.auth identity when the doc doesn't carry it (@omega.js/client's
12
+ * resolveAccount(rawData, firebaseUser) semantic).
13
+ */
14
+ const USER_SCHEMA = require('./schema.js');
15
+ const { resolve } = require('./engine.js');
16
+
17
+ function resolveAccount(data, options) {
18
+ options = options || {};
19
+
20
+ // Time context, byte-identical to @omega.js/backend's node-powertools timestamps:
21
+ // powertools.timestamp(d, { output: 'string' }) === d.toISOString(),
22
+ // powertools.timestamp(d, { output: 'unix' }) === floor(ms / 1000).
23
+ const now = new Date();
24
+ const ctx = {
25
+ generators: options.generators || {},
26
+ now: now.toISOString(),
27
+ nowUNIX: Math.floor(now.getTime() / 1000),
28
+ oldDate: new Date(0).toISOString(),
29
+ oldDateUNIX: 0,
30
+ };
31
+
32
+ const account = resolve(USER_SCHEMA, data || {}, ctx);
33
+
34
+ // Auth-user overlay (frontend semantic): identity from the signed-in Firebase
35
+ // user when the stored doc doesn't already carry it
36
+ if (options.user) {
37
+ account.auth.uid = account.auth.uid || options.user.uid || null;
38
+ account.auth.email = account.auth.email || options.user.email || null;
39
+ }
40
+
41
+ return account;
42
+ }
43
+
44
+ module.exports = resolveAccount;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * USER_SCHEMA — the canonical OMEGA user/account schema (pure data, no logic).
3
3
  *
4
- * Extracted verbatim from @omega.js/backend's src/manager/helpers/user.js, which is
4
+ * Extracted verbatim from @omega.js/backend's former user helper, which was
5
5
  * the authoritative shape (@omega.js/client's DEFAULT_ACCOUNT had drifted from it).
6
6
  *
7
7
  * Each leaf field is { type, default, nullable }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * User: the OMEGA account as one class, the same on the backend and in the browser.
3
+ *
4
+ * The own enumerable fields are the resolved stored document (every schema
5
+ * branch, in schema order), so `user.auth.uid` and `user.subscription.status`
6
+ * read as they always have and `toJSON()` is the one stored shape a Firestore
7
+ * write or an API response carries. Everything derived lives on the prototype
8
+ * as getters, computed on every read, so it never reaches the stored shape and
9
+ * never goes stale when code mutates `user.subscription` in place.
10
+ *
11
+ * `new User()` is the signed-out user: `authenticated` false, `plan` basic.
12
+ */
13
+ const resolveAccount = require('./resolve-account.js');
14
+ const resolveSubscription = require('./subscription.js');
15
+
16
+ /**
17
+ * An account: the stored document as own fields, identity-derived and
18
+ * subscription-derived facts as getters.
19
+ */
20
+ class User {
21
+ // The '$uuid'/'$randomId'/'$apiKey' token generators. Static because there is one host per process:
22
+ // the Node backend assigns { uuid, randomId, apiKey } once at boot; the browser never does, so those fields resolve to null.
23
+ static generators = {};
24
+
25
+ // The schema's own EMPTY discount node, which is what a CLEARED discount is
26
+ // ([#333](https://github.com/Omega-JS-Stack/omega/issues/333)). The webhook
27
+ // pipeline clears the node with a MERGE write, so every field has to be named or
28
+ // half of the old claim survives it. Read off the resolver rather than spelled
29
+ // out again: the node's shape keeps its one home in the schema, and a field
30
+ // added there is cleared without anyone remembering a second list.
31
+ static EMPTY_DISCOUNT = resolveAccount({}).subscription.discount;
32
+
33
+ /**
34
+ * @param {object} [document] - the stored account document (any partial object)
35
+ * @param {object|null} [identity] - { uid, email, displayName, photoURL, emailVerified }; a Firebase user satisfies it as-is
36
+ */
37
+ constructor(document = {}, identity = null) {
38
+ Object.assign(this, resolveAccount(document, { generators: User.generators, user: identity }));
39
+
40
+ // Non-enumerable so it never reaches toJSON() or Object.keys(): the profile
41
+ // comes from the sign-in, never from the stored document
42
+ const source = identity || {};
43
+ Object.defineProperty(this, 'profile', {
44
+ value: {
45
+ displayName: source.displayName || null,
46
+ photoURL: source.photoURL || null,
47
+ emailVerified: source.emailVerified === true,
48
+ },
49
+ writable: true,
50
+ });
51
+ }
52
+
53
+ /** True only when the account carries a non-empty uid. */
54
+ get authenticated() {
55
+ return typeof this.auth.uid === 'string' && this.auth.uid.length > 0;
56
+ }
57
+
58
+ /** The account's uid, or null when signed out. */
59
+ get uid() {
60
+ return this.auth.uid;
61
+ }
62
+
63
+ /** The account's email, or null when signed out. */
64
+ get email() {
65
+ return this.auth.email;
66
+ }
67
+
68
+ /** The plan the user has access to right now ('basic' when cancelled or suspended). */
69
+ get plan() {
70
+ return resolveSubscription(this).plan;
71
+ }
72
+
73
+ /** True when a paid plan is active (including trialing and cancelling). */
74
+ get active() {
75
+ return resolveSubscription(this).active;
76
+ }
77
+
78
+ /** True while a claimed trial on an active paid plan has not yet expired. */
79
+ get trialing() {
80
+ return resolveSubscription(this).trialing;
81
+ }
82
+
83
+ /** True when an active paid plan (not trialing) has a cancellation pending. */
84
+ get cancelling() {
85
+ return resolveSubscription(this).cancelling;
86
+ }
87
+
88
+ /** True when the user has ever paid (a payment start date exists). */
89
+ get everPaid() {
90
+ return resolveSubscription(this).everPaid;
91
+ }
92
+
93
+ /** The stored document alone: own enumerable fields, never getters or profile. */
94
+ toJSON() {
95
+ return { ...this };
96
+ }
97
+ }
98
+
99
+ module.exports = User;
@@ -88,7 +88,7 @@ function pickPaths(section, paths) {
88
88
 
89
89
  /**
90
90
  * The subset of a resolved config a browser may see.
91
- * @param {object} [resolved] - a resolved config (Manager.getConfig() output),
91
+ * @param {object} [resolved] - a resolved config (build.getConfig() output),
92
92
  * with this build's facts composed onto it.
93
93
  * @returns {object} the blob every browser surface bakes as OMEGA_BUILD_JSON.config.
94
94
  * @throws {Error} when a section that is about to ship carries a secret-shaped key.
@@ -44,6 +44,9 @@ const fs = require('node:fs');
44
44
  const os = require('node:os');
45
45
  const path = require('node:path');
46
46
  const JSON5 = require('json5');
47
+ const Logger = require('../devkit/logger.js');
48
+
49
+ const logger = new Logger('company');
47
50
 
48
51
  // The company TREE inside the parent brand's repo, and the file an off-laptop
49
52
  // run reads the resolved layer from: GENERATED beside the brand config by the
@@ -62,13 +65,16 @@ const REGISTRY_FILE = 'brands.json';
62
65
  // it visible").
63
66
  const SELF = 'self';
64
67
 
65
- // The legacy stamp #677 retired, warned about where an owner still carries one.
66
- const RETIRED_MARKER = path.join('.omega', 'company.json');
68
+ // The `company` keys the LOADER fills: never typed, always resolved.
69
+ const RESOLVED_COMPANY_KEYS = ['name', 'url', 'images'];
67
70
 
68
71
  // One warning per company id per process: a missing parent is a state a whole
69
72
  // build runs in, never a per-call event.
70
73
  const warned = new Set();
71
74
 
75
+ // One warning per process for a registry file that is there but will not parse.
76
+ let warnedUnreadable = false;
77
+
72
78
  /**
73
79
  * The machine home OMEGA keeps its per-machine state in. `OMEGA_HOME` moves it
74
80
  * (the test lanes point it at a temp dir so a fixture never writes a real line).
@@ -118,6 +124,17 @@ function readRegistry() {
118
124
  return readJson(registryFile()) || {};
119
125
  }
120
126
 
127
+ /**
128
+ * A registry line's root when it is on this machine now, else null: the ONE
129
+ * test both the reader and the write's prune apply, so a line the reader would
130
+ * resolve is never a line the prune drops.
131
+ * @param {object} line - A registry line.
132
+ * @returns {string|null}
133
+ */
134
+ function liveRoot(line) {
135
+ return line && typeof line.root === 'string' && isDir(line.root) ? line.root : null;
136
+ }
137
+
121
138
  /**
122
139
  * Record (or refresh) ONE brand's line in the machine registry, which is what
123
140
  * every `loadConfig()` does for the brand it just loaded: the map of where the
@@ -130,7 +147,13 @@ function readRegistry() {
130
147
  *
131
148
  * A write also PRUNES the lines whose root is gone: the file is a map of what
132
149
  * is on this machine, so a brand that was deleted or moved (and re-recorded
133
- * under its new root) leaves nothing behind to resolve into.
150
+ * under its new root) leaves nothing behind to resolve into. A line whose root
151
+ * is on disk is never pruned (`liveRoot`, the reader's own test).
152
+ *
153
+ * A registry file that is THERE but will not parse is never rewritten: writing
154
+ * over it would keep one line and drop every other brand. The write itself is
155
+ * a rename of a finished temp file, so a racing reader never sees half a file.
156
+ * Every write logs its caller and the kept and pruned ids at debug level.
134
157
  *
135
158
  * @param {object} entry - The brand's own facts.
136
159
  * @param {string} entry.id - `brand.id` (the key).
@@ -143,7 +166,17 @@ function recordBrand({ id, root, name, url }) {
143
166
  if (!id || !root) return false;
144
167
 
145
168
  try {
146
- const registry = readRegistry();
169
+ const file = registryFile();
170
+ const registry = fs.existsSync(file) ? readJson(file) : {};
171
+
172
+ if (!registry) {
173
+ if (!warnedUnreadable) {
174
+ warnedUnreadable = true;
175
+ logger.warn(`Machine registry ${file} does not parse: left as it is, no brand recorded. Delete it and any omega verb rebuilds it.`);
176
+ }
177
+ return false;
178
+ }
179
+
147
180
  const current = registry[id];
148
181
  const line = { root, name: name || null, url: url || null };
149
182
 
@@ -153,10 +186,15 @@ function recordBrand({ id, root, name, url }) {
153
186
 
154
187
  registry[id] = { ...line, updatedAt: new Date().toISOString() };
155
188
 
156
- const live = Object.fromEntries(Object.entries(registry).filter(([, entry]) => entry && isDir(entry.root)));
189
+ const live = Object.fromEntries(Object.entries(registry).filter(([, entry]) => liveRoot(entry)));
190
+ const pruned = Object.keys(registry).filter((key) => !(key in live));
157
191
 
158
192
  fs.mkdirSync(omegaHome(), { recursive: true });
159
- fs.writeFileSync(registryFile(), `${JSON.stringify(live, null, 2)}\n`);
193
+ const temp = `${file}.${process.pid}.tmp`;
194
+ fs.writeFileSync(temp, `${JSON.stringify(live, null, 2)}\n`);
195
+ fs.renameSync(temp, file);
196
+
197
+ logger.debug(`Registry write by ${process.argv[1] || process.argv0} (pid ${process.pid}): kept [${Object.keys(live).join(', ')}], pruned [${pruned.join(', ')}]`);
160
198
  return true;
161
199
  } catch {
162
200
  return false;
@@ -271,13 +309,6 @@ function resolveCompany(brandRoot, brandConfig) {
271
309
  const webhooks = !(config.company && config.company.webhooks === false);
272
310
  const reply = (facts) => answer({ ...facts, webhooks });
273
311
 
274
- // A brand still carrying the retired stamp hears about it once: the folder it
275
- // points at is not read any more, by anything.
276
- if (root && fs.existsSync(path.join(root, RETIRED_MARKER)) && !warned.has(RETIRED_MARKER)) {
277
- warned.add(RETIRED_MARKER);
278
- console.warn(`${path.join(root, RETIRED_MARKER)} is retired (#677): delete it, and name the company with company: { id: '<parent brand.id>' } in omega.json5.`);
279
- }
280
-
281
312
  // No company: the brand IS the whole entity, so the company facts are its own
282
313
  // and every reader works without a fallback.
283
314
  if (!id) {
@@ -290,7 +321,7 @@ function resolveCompany(brandRoot, brandConfig) {
290
321
  }
291
322
 
292
323
  const line = readRegistry()[id];
293
- const parentRoot = line && typeof line.root === 'string' && isDir(line.root) ? line.root : null;
324
+ const parentRoot = liveRoot(line);
294
325
 
295
326
  if (parentRoot) {
296
327
  const parentBrand = (readBrandConfig(parentRoot) || {}).brand || {};
@@ -347,4 +378,5 @@ module.exports = {
347
378
  COMPANY_DIR,
348
379
  COMPANY_RESOLVED_FILE,
349
380
  COMPANY_SELF: SELF,
381
+ RESOLVED_COMPANY_KEYS,
350
382
  };
@@ -27,6 +27,8 @@
27
27
  * brand config is a copy that drifts from the thing it came from.
28
28
  */
29
29
 
30
+ const { isDeepStrictEqual } = require('node:util');
31
+
30
32
  const { SHARED_SCHEMA, TARGET_SCHEMAS } = require('./schema.js');
31
33
  const { isPlainObject } = require('./merge.js');
32
34
 
@@ -59,6 +61,12 @@ function defaultRules(target) {
59
61
  return rules.filter(hasDefault);
60
62
  }
61
63
 
64
+ /**
65
+ * Set a value at a dot-path, creating the objects on the way.
66
+ * @param {object} target
67
+ * @param {string} dotted
68
+ * @param {*} value
69
+ */
62
70
  function setAtPath(target, dotted, value) {
63
71
  const names = dotted.split('.');
64
72
  const leaf = names.pop();
@@ -127,13 +135,26 @@ function defaultComments(target) {
127
135
  return comments;
128
136
  }
129
137
 
130
- function collectMissing(defaults, present, prefix, target, found) {
131
- for (const [key, value] of Object.entries(defaults)) {
138
+ /**
139
+ * What filling `present` from `incoming` does, key by key, never overwriting:
140
+ * a path `present` lacks is ADDED at its highest missing point (one block,
141
+ * not one edit per key inside it), and a path `present` already answers
142
+ * differently is KEPT. An equal value is neither.
143
+ * @param {object} incoming - The values on offer.
144
+ * @param {object} present - The authored config they would fill.
145
+ * @returns {{ added: Array<{ path: string, value: * }>, kept: Array<{ path: string, value: *, incoming: * }> }}
146
+ */
147
+ function planMerge(incoming, present) {
148
+ return collectPlan(incoming, present, '', { added: [], kept: [] });
149
+ }
150
+
151
+ function collectPlan(incoming, present, prefix, plan) {
152
+ for (const [key, value] of Object.entries(incoming)) {
132
153
  const dotted = prefix ? `${prefix}.${key}` : key;
133
154
  const authored = isPlainObject(present) && Object.prototype.hasOwnProperty.call(present, key);
134
155
 
135
156
  if (!authored) {
136
- found.push({ path: dotted, value });
157
+ plan.added.push({ path: dotted, value });
137
158
  continue;
138
159
  }
139
160
 
@@ -141,11 +162,13 @@ function collectMissing(defaults, present, prefix, target, found) {
141
162
  // A brand that authored anything else at this path (a scalar, false, null)
142
163
  // made a decision: never dive in, never overwrite.
143
164
  if (isPlainObject(value) && isPlainObject(present[key])) {
144
- collectMissing(value, present[key], dotted, target, found);
165
+ collectPlan(value, present[key], dotted, plan);
166
+ } else if (!isDeepStrictEqual(value, present[key])) {
167
+ plan.kept.push({ path: dotted, value: present[key], incoming: value });
145
168
  }
146
169
  }
147
170
 
148
- return found;
171
+ return plan;
149
172
  }
150
173
 
151
174
  /**
@@ -167,7 +190,7 @@ function missingDefaults(config, target) {
167
190
  // copy to drift ([#793](https://github.com/Omega-JS-Stack/omega/issues/793))
168
191
  const writable = defaultsFrom(defaultRules(target).filter(isMaterialized));
169
192
 
170
- return collectMissing(writable, config || {}, '', target, []);
193
+ return planMerge(writable, config || {}).added;
171
194
  }
172
195
 
173
- module.exports = { schemaDefaults, missingDefaults, defaultComments, defaultRules };
196
+ module.exports = { schemaDefaults, missingDefaults, defaultComments, defaultRules, planMerge, setAtPath };
@@ -579,12 +579,23 @@ function applyConfigEdits(source, edits, { comments = {} } = {}) {
579
579
  * @param {{ dryRun?: boolean, comments?: Object<string, string> }} [options]
580
580
  * @returns {{ path: string, changed: boolean, applied: string[] }}
581
581
  */
582
- function writeConfigValues(projectDir, edits, { dryRun = false, comments = {} } = {}) {
582
+ function writeConfigValues(projectDir, edits, options) {
583
583
  const configPath = resolveConfigPath(projectDir);
584
584
  if (!configPath) {
585
585
  throw new Error(`No ${FILE_NAME} found under ${projectDir} — cannot write config values`);
586
586
  }
587
587
 
588
+ return writeConfigFileValues(configPath, edits, options);
589
+ }
590
+
591
+ /**
592
+ * writeConfigValues on one named omega file (an overlay, a target's own file).
593
+ * @param {string} configPath - The file to edit.
594
+ * @param {Object<string, *>} edits - Dot-path → value.
595
+ * @param {{ dryRun?: boolean, comments?: Object<string, string> }} [options]
596
+ * @returns {{ path: string, changed: boolean, applied: string[] }}
597
+ */
598
+ function writeConfigFileValues(configPath, edits, { dryRun = false, comments = {} } = {}) {
588
599
  const source = fs.readFileSync(configPath, 'utf8');
589
600
  const applied = pendingEdits(JSON5.parse(source), edits).map(([path]) => path);
590
601
  // Every writeback also normalizes top-level key order (comments travel
@@ -748,12 +759,23 @@ function applyConfigRemovals(source, paths) {
748
759
  * @param {{ dryRun?: boolean }} [options]
749
760
  * @returns {{ path: string, changed: boolean, removed: string[] }}
750
761
  */
751
- function removeConfigValues(projectDir, paths, { dryRun = false } = {}) {
762
+ function removeConfigValues(projectDir, paths, options) {
752
763
  const configPath = resolveConfigPath(projectDir);
753
764
  if (!configPath) {
754
765
  throw new Error(`No ${FILE_NAME} found under ${projectDir} — cannot remove config values`);
755
766
  }
756
767
 
768
+ return removeConfigFileValues(configPath, paths, options);
769
+ }
770
+
771
+ /**
772
+ * removeConfigValues on one named omega file (an overlay, a target's own file).
773
+ * @param {string} configPath - The file to edit.
774
+ * @param {string[]} paths - Dot-paths to delete.
775
+ * @param {{ dryRun?: boolean }} [options]
776
+ * @returns {{ path: string, changed: boolean, removed: string[] }}
777
+ */
778
+ function removeConfigFileValues(configPath, paths, { dryRun = false } = {}) {
757
779
  const source = fs.readFileSync(configPath, 'utf8');
758
780
  const parsed = JSON5.parse(source);
759
781
  const removed = paths.filter((path) => getAtPath(parsed, path) !== undefined);
@@ -766,4 +788,4 @@ function removeConfigValues(projectDir, paths, { dryRun = false } = {}) {
766
788
  return { path: configPath, changed: next !== source, removed };
767
789
  }
768
790
 
769
- module.exports = { applyConfigEdits, writeConfigValues, applyConfigRemovals, removeConfigValues, parseRoot };
791
+ module.exports = { applyConfigEdits, writeConfigValues, writeConfigFileValues, applyConfigRemovals, removeConfigValues, removeConfigFileValues, parseRoot };
@@ -54,7 +54,7 @@ const WORKFLOW_OWNED_KEYS = ['GH_TOKEN', 'CLOUDFLARE_TOKEN', 'NODE_VERSION', 'NO
54
54
 
55
55
  // Rendered in place of the block when a target delivers nothing, so the
56
56
  // generated region is always a valid, self-explaining line of YAML.
57
- const EMPTY_BLOCK = '# (no CI-delivered keys for this target — the next omega verb regenerates this block)';
57
+ const EMPTY_BLOCK = '# (no CI-delivered keys for this target: the next omega verb regenerates this block)';
58
58
 
59
59
  // What a target's generated workflow carries in the runner env, per target.
60
60
  //
@@ -874,12 +874,9 @@ const ENV_SCHEMA = [
874
874
  description: 'Edge Add-ons API key the publish request authenticates with.',
875
875
  },
876
876
 
877
- // No test-lane credentials live here any more
878
- // ([#819](https://github.com/Omega-JS-Stack/omega/issues/819), Ian
879
- // 2026-09-13): web, desktop and extension each test their own sign-in
880
- // against a persona the backend emulator seeds, so a suite never asks a
881
- // brand for a key. The pair that used to sit here is retired outright
882
- // (env-retired.js carries both rows); #904 owns the replacement.
877
+ // No test-lane credentials live here: web, desktop and extension each sign
878
+ // in as a persona the backend emulator seeds, so a suite never asks a brand
879
+ // for a key. The retired pair's rows are @omega.js/manager's migrate table.
883
880
 
884
881
  {
885
882
  name: 'OMEGA_FONTAWESOME_ROOT',