@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
@@ -3,8 +3,7 @@
3
3
  * `site.*` global, so existing template references port verbatim to the new
4
4
  * SSG (plan §2: toSiteGlobal()).
5
5
  *
6
- * Scoped by the real-usage audit (2026-07-06) of UJM's theme/blueprints +
7
- * somiibo: config-owned keys referenced in templates are the resolved
6
+ * Config-owned keys referenced in templates are the resolved
8
7
  * config's own shape, so this is an identity mapping plus: machinery keys
9
8
  * stripped (`targets`, `enabled`), `url` derived (explicit url, else
10
9
  * brand.url), `baseurl` defaulted. ENGINE-owned keys (posts, pages, data,
@@ -24,7 +23,7 @@
24
23
  * ([#610](https://github.com/Omega-JS-Stack/omega/issues/610)): the
25
24
  * `/download` page, the `/extension` page and the shortlink generator all
26
25
  * read `site.targets.desktop.releasesUrl` and
27
- * `site.targets.extension.listings` directly. The legacy UJM-shaped
26
+ * `site.targets.extension.listings` directly. The hand-written
28
27
  * `download`/`extension` page maps — and the derivation that filled them —
29
28
  * are gone; the validator refuses a config still carrying either key.
30
29
  *
@@ -1,40 +1,19 @@
1
1
  /**
2
- * Schema-driven validation for resolved omega.json5 configs.
3
- *
4
- * The rule walker (runSchema) is electron-manager's proven validate-config
5
- * engine ported verbatim: required/type/match/enum semantics, where match +
6
- * enum (and itemEnum, the same check per member of an array value) only run on
7
- * PRESENT values and a conditional `required` function
8
- * receives the full config. See schema.js for the rule format.
9
- *
10
- * validateConfig() layers on top of the walker:
11
- * - SHARED_SCHEMA always runs; TARGET_SCHEMAS[options.target] adds that
12
- * target's refinements against the same resolved top-level namespace
13
- * - `targets` sanity (#886): every key is a target NAME, so it must be a
14
- * dir-safe slug, and every entry must be an object declaring a `type` the
15
- * framework list knows (an ARRAY is the retired multi-instance form, whose
16
- * replacement is a sibling key; >1 backend target is a WARNING)
17
- * - the `repo` block (#883): one `provider` from REPO_PROVIDERS, and an `org`
18
- * that is a real non-empty string whenever the block is there, because the
19
- * org is the owner of every repo the brand's names derive under
20
- * - `hosting` (#883) is a WEB target's key: a non-web target carrying it says
21
- * something nothing serves, and its provider comes from HOSTING_PROVIDERS
22
- * - `cloud.config.authDomain`, when set, must be the brand's OWN host (the
23
- * resolved target url, else brand.url): a firebaseapp.com value or a
24
- * mismatch is an error, and demo-* (emulator-only) projects are exempt
25
- * - retired keys are always errors (see retired-keys.js) — a name that was
26
- * renamed outright reads as nothing at all, so it fails loudly instead of
27
- * losing its settings silently
28
- * - secret-shaped keys are always errors (see secrets.js) — loadConfig
29
- * additionally hard-fails on them before any merge happens
30
- * - keys the schema does not declare are WARNINGS (#636): the walker only
31
- * ever visits declared paths, so a typo — or a live path nobody declared —
32
- * used to pass in silence
2
+ * Schema-driven validation for resolved omega.json5 configs. runSchema walks
3
+ * the rules (required/type/match/enum/itemEnum, the value checks only on a
4
+ * PRESENT value; schema.js has the rule format). validateConfig() adds the
5
+ * target's refinements, the `targets` sanity (each key a dir-safe NAME, each
6
+ * entry a known `type`), the `repo` block and web-only `hosting`, the brand's
7
+ * own authDomain, the price and feature shapes, and secret-shaped keys. The
8
+ * schema is STRICT: a path no rule declares is an error, one line per path,
9
+ * naming `omega migrate`. The validator knows only the present shape; the
10
+ * migrate verb is the one code that knows an old name.
33
11
  */
34
12
 
35
- const { TARGETS, SHARED_SCHEMA, TARGET_SCHEMAS } = require('./schema.js');
13
+ const { TARGETS, SHARED_SCHEMA, TARGET_SCHEMAS, isCustomTargetEntry } = require('./schema.js');
36
14
  const { findSecretKeys } = require('./secrets.js');
37
- const { findRetiredKeys } = require('./retired-keys.js');
15
+ const { CLIENT_FACT_KEYS } = require('./client-config.js');
16
+ const { RESOLVED_COMPANY_KEYS } = require('./company.js');
38
17
  const { isPlainObject } = require('./merge.js');
39
18
  const { TARGET_NAME_PATTERN, TARGET_TYPES } = require('./targets.js');
40
19
  const { REPO_PROVIDERS, HOSTING_PROVIDERS } = require('./repo.js');
@@ -44,6 +23,14 @@ const { isCountedFeature } = require('../account/features.js');
44
23
  // Firebase's own default authDomain shape: a third-party host by definition
45
24
  const FIREBASE_AUTH_DOMAIN = /\.firebaseapp\.com$/;
46
25
 
26
+ // The company facts the loader fills: a staged compose output carries them on
27
+ // purpose, and the loader refuses a TYPED one in a brand or company file.
28
+ const RESOLVED_PATHS = RESOLVED_COMPANY_KEYS.map((key) => `company.${key}`);
29
+
30
+ // The build facts a surface bakes AFTER the load: declared only for
31
+ // `decorated: true`, the re-validation of a baked config (a desktop boot).
32
+ const BUILD_FACT_PATHS = CLIENT_FACT_KEYS;
33
+
47
34
  /**
48
35
  * Read a dotted path out of a config object — the path resolver the schema
49
36
  * walk and the env presence checker (env-rules.js) share.
@@ -420,24 +407,23 @@ function validateFeatures(config) {
420
407
  }
421
408
 
422
409
  /**
423
- * The LEAF paths of a resolved config the schema does not declare (#636).
424
- *
425
- * A rule declares its own path AND everything beneath it — an `object`/`array`
426
- * rule is a declared subtree (brand.address's postal fields, an open provider
427
- * map), which is what keeps a brand's own data out of this list. An empty
428
- * object/array is itself a leaf, declared when the schema declares anything
429
- * below it (`certificates: {}` against certificates.enabled).
430
- *
431
- * The `targets` subtree is skipped whole: those keys belong to a framework or,
432
- * for a custom target (#603), to the brand — the targets-sanity check above is
433
- * what polices that namespace.
434
- *
435
- * @param {object} config - The resolved config object.
410
+ * The LEAF paths of a config the schema does not declare. A rule declares its
411
+ * own path; an `object`/`array` rule opens its whole subtree only when no rule
412
+ * is declared beneath it (or it says `open: true`), so a section with typed
413
+ * keys stays closed. An empty object/array (or a `false` off switch) is a leaf,
414
+ * declared when the schema declares anything below it. `targets` is skipped
415
+ * whole: each target's own load judges its entry.
416
+ * @param {object} config - The config object.
436
417
  * @param {object[]} schema - Rule array in the schema.js entry format.
418
+ * @param {string[]} [written] - Paths framework code wrote onto a decorated config, each open.
437
419
  * @returns {string[]} Undeclared leaf paths, in config order.
438
420
  */
439
- function findUndeclaredPaths(config, schema) {
440
- const declared = schema.map((rule) => rule.path);
421
+ function findUndeclaredPaths(config, schema, written = []) {
422
+ const paths = [...schema.map((rule) => rule.path), ...written];
423
+ const open = schema
424
+ .filter((rule) => /\b(object|array)\b/.test(rule.type || '') && (rule.open || !paths.some((other) => other.startsWith(`${rule.path}.`))))
425
+ .map((rule) => rule.path)
426
+ .concat(written);
441
427
  const found = [];
442
428
 
443
429
  const walk = (value, path) => {
@@ -445,9 +431,10 @@ function findUndeclaredPaths(config, schema) {
445
431
  Object.keys(value).forEach((key) => walk(value[key], `${path}.${key}`));
446
432
  return;
447
433
  }
448
- if (!declared.some((rule) => rule === path || path.startsWith(`${rule}.`) || rule.startsWith(`${path}.`))) {
449
- found.push(path);
450
- }
434
+ const declared = paths.includes(path)
435
+ || open.some((rule) => path.startsWith(`${rule}.`))
436
+ || paths.some((rule) => rule.startsWith(`${path}.`));
437
+ if (!declared) found.push(path);
451
438
  };
452
439
 
453
440
  Object.keys(isPlainObject(config) ? config : {})
@@ -457,25 +444,69 @@ function findUndeclaredPaths(config, schema) {
457
444
  return found;
458
445
  }
459
446
 
447
+ /**
448
+ * The rules a target's resolved config answers to: shared + its refinements.
449
+ * @param {string} [target] - Canonical target name; omitted = shared only.
450
+ * @returns {object[]}
451
+ */
452
+ function schemaFor(target) {
453
+ if (target && !TARGETS.includes(target)) {
454
+ throw new Error(`Unknown target "${target}": must be one of [${TARGETS.join(', ')}]`);
455
+ }
456
+
457
+ return target ? [...SHARED_SCHEMA, ...TARGET_SCHEMAS[target]] : SHARED_SCHEMA;
458
+ }
459
+
460
+ /**
461
+ * The paths a config carries that no rule declares, for one target's view:
462
+ * what the strict check fails, for a caller (a converter) that drops them.
463
+ * @param {object} config - A resolved config (a target's keys at the top level).
464
+ * @param {object} [options]
465
+ * @param {string} [options.target]
466
+ * @returns {string[]} Undeclared leaf paths, in config order.
467
+ */
468
+ function undeclaredPaths(config, options) {
469
+ return findUndeclaredPaths(config, schemaFor((options || {}).target), RESOLVED_PATHS);
470
+ }
471
+
472
+ /**
473
+ * Every path of an AUTHORED config (one omega.json5 as written) the strict
474
+ * schema refuses: the whole file, `targets` left to its targets, then each
475
+ * framework target entry the way its own load sees it (hoisted to the top
476
+ * level). A target's OWN file names its type, since its top level is that
477
+ * target's layer. A custom target's keys are its own; an unknown type is the
478
+ * targets check's to report.
479
+ * @param {object} config - One file's parsed config.
480
+ * @param {object} [options]
481
+ * @param {string} [options.target] - The type whose own file this is.
482
+ * @returns {string[]} Dotted paths as the file spells them.
483
+ */
484
+ function undeclaredAuthoredPaths(config, options) {
485
+ const found = undeclaredPaths(config, { target: (options || {}).target });
486
+ const targets = isPlainObject(config) && isPlainObject(config.targets) ? config.targets : {};
487
+
488
+ for (const [name, entry] of Object.entries(targets)) {
489
+ if (!isPlainObject(entry) || isCustomTargetEntry(entry) || !TARGETS.includes(entry.type)) continue;
490
+ found.push(...undeclaredPaths(entry, { target: entry.type }).map((dotted) => `targets.${name}.${dotted}`));
491
+ }
492
+
493
+ return found;
494
+ }
495
+
460
496
  /**
461
497
  * Validate a resolved config: shared schema + optional target refinements +
462
498
  * targets-key sanity + secret-shaped-key detection.
463
499
  * @param {object} config - The resolved config object.
464
500
  * @param {object} [options]
465
501
  * @param {string} [options.target] - Canonical target name; adds TARGET_SCHEMAS[target].
502
+ * @param {boolean} [options.decorated] - The config already carries a build's
503
+ * facts (a baked config, validated again at boot).
466
504
  * @returns {{ errors: string[], warnings: string[] }}
467
505
  */
468
506
  function validateConfig(config, options) {
469
507
  options = options || {};
470
508
 
471
- if (options.target && !TARGETS.includes(options.target)) {
472
- throw new Error(`Unknown target "${options.target}" — must be one of [${TARGETS.join(', ')}]`);
473
- }
474
-
475
- const schema = options.target
476
- ? [...SHARED_SCHEMA, ...TARGET_SCHEMAS[options.target]]
477
- : SHARED_SCHEMA;
478
-
509
+ const schema = schemaFor(options.target);
479
510
  const errors = runSchema(config, schema);
480
511
  const warnings = [];
481
512
 
@@ -574,25 +605,13 @@ function validateConfig(config, options) {
574
605
  // ─── the features catalog and the values products name (#647) ──────────
575
606
  validateFeatures(config).forEach((error) => errors.push(error));
576
607
 
577
- // ─── undeclared paths (#636) ───────────────────────────────────────────
578
- // A warning, never an error: a brand config outliving one framework version
579
- // must still build, and the finding is what closes the gap — either the key
580
- // is dead, or the schema owes it a rule.
581
- const undeclared = findUndeclaredPaths(config, schema);
582
- if (undeclared.length > 0) {
583
- warnings.push(
584
- 'config carries keys the schema does not declare — nothing reads them, so a typo looks exactly like a '
585
- + `feature. Remove them, or give each one a rule in @omega.js/config's schema.js `
586
- + `(docs/shared/config.md → Validation): ${undeclared.join(', ')}`,
587
- );
588
- }
589
-
590
- // ─── retired keys (#142) ───────────────────────────────────────────────
591
- findRetiredKeys(config).forEach(({ path, replacement, why }) => {
608
+ // ─── the schema is strict ──────────────────────────────────────────────
609
+ // A key nothing declares is a key nothing reads: a typo or a legacy shape.
610
+ // One line per path, so a brand sees every key it has to move or delete.
611
+ findUndeclaredPaths(config, schema, options.decorated ? [...RESOLVED_PATHS, ...BUILD_FACT_PATHS] : RESOLVED_PATHS).forEach((path) => {
592
612
  errors.push(
593
- `config.${path} is retired — the key is now "${replacement}" (${why}). `
594
- + `Rename it; there is no dual-read, so the old name is silently ignored `
595
- + `(docs/shared/config.md → "Migration — legacy configs")`,
613
+ `config.${path} is not a key the schema declares. Remove it, or if it is a legacy key run `
614
+ + '`npx omega migrate` at the brand root (report) and `--execute` to convert (docs/shared/config.md → Validation)',
596
615
  );
597
616
  });
598
617
 
@@ -611,4 +630,4 @@ function formatErrors(errors) {
611
630
  return errors.map((e, i) => ` ${i + 1}. ${e}`).join('\n');
612
631
  }
613
632
 
614
- module.exports = { validateConfig, runSchema, formatErrors, getPath, resolvedBrandHost };
633
+ module.exports = { validateConfig, undeclaredPaths, undeclaredAuthoredPaths, runSchema, formatErrors, getPath, resolvedBrandHost };
@@ -9,7 +9,7 @@
9
9
  * nothing at all still makes one: 50% off the next cycle, one cycle only.
10
10
  *
11
11
  * That default lives HERE and nowhere else. The backend's apply route resolves
12
- * `Manager.config.payment` through this function, and the web build bakes the
12
+ * `omega.config.payment` through this function, and the web build bakes the
13
13
  * SAME call into the client blob it emits — so the dialog the customer reads and
14
14
  * the coupon the provider creates can never disagree about the number.
15
15
  *
@@ -5,7 +5,7 @@
5
5
  * repo secrets: hand it a repo (`owner/name`) and a key → value map, it sets
6
6
  * each one. The transport is the `gh` CLI (`gh secret set KEY --repo o/n`,
7
7
  * which encrypts locally with the repo's public key before sending) — there is
8
- * NO libsodium/REST fallback here (UJM's path): without a usable `gh` this
8
+ * NO libsodium/REST fallback here: without a usable `gh` this
9
9
  * FAILS LOUDLY with install/auth instructions rather than growing a crypto
10
10
  * dependency to do what the CLI already does.
11
11
  *
@@ -0,0 +1,233 @@
1
+ /**
2
+ * The ONE builder of a project-root AGENTS.md, a brand root and a framework
3
+ * used alone alike, written through the one marker engine: the Default section
4
+ * imports the installed manager's AGENTS.md (which imports the omega map from
5
+ * the docs the manager ships), the Custom section holds the project's own
6
+ * notes, verbatim. Every older shape converges once, its notes kept under
7
+ * Custom. A brand target carries none: its brand root is the doc home.
8
+ */
9
+ const { join } = require('node:path');
10
+ const fs = require('node:fs');
11
+ const jetpack = require('fs-jetpack');
12
+ const { mergeLineBasedFiles, hasSectionMarkers, getCustomSection, isShipped, sectionMarkers, DEFAULT_MARKER } = require('./merge-line-files');
13
+
14
+ const FILE_NAME = 'AGENTS.md';
15
+ const GUIDE_SUBPATH = 'node_modules/@omega.js/manager/AGENTS.md';
16
+ const IMPORT_LINE = `@${GUIDE_SUBPATH}`;
17
+ // The retired scope-level link: an import of it is healed away and the link
18
+ // itself removed.
19
+ const RETIRED_SUBPATH = 'node_modules/@omega.js/AGENTS.md';
20
+
21
+ const SCOPE_PREFIXES = ['', '../', '../../', '../../../'];
22
+
23
+ // The skeleton heading earlier builders wrote under the import.
24
+ const NOTES_HEADING = /^# .+: (project|brand) notes$/;
25
+
26
+ /**
27
+ * Is this line an import of the manager's AGENTS.md or the retired scope link
28
+ * (any relative depth)?
29
+ *
30
+ * @param {string} line - A single file line
31
+ * @returns {boolean}
32
+ */
33
+ function isImportLine(line) {
34
+ const trimmed = line.trim();
35
+ return trimmed.startsWith('@') && (trimmed.endsWith(GUIDE_SUBPATH) || trimmed.endsWith(RETIRED_SUBPATH));
36
+ }
37
+
38
+ /**
39
+ * The @omega.js scope directory serving this project: its own node_modules
40
+ * normally, an ancestor's when hoisted (npm workspaces). Only a scope holding
41
+ * the manager counts: an empty dir from a partial install would leave the
42
+ * import dangling while the real scope sits a level up.
43
+ *
44
+ * @param {string} root - Absolute project root
45
+ * @returns {{prefix: string, scopeDir: string}|null}
46
+ */
47
+ function findScope(root) {
48
+ for (const prefix of SCOPE_PREFIXES) {
49
+ const scopeDir = join(root, prefix, 'node_modules', '@omega.js');
50
+ if (jetpack.exists(join(scopeDir, 'manager')) === 'dir') {
51
+ return { prefix, scopeDir };
52
+ }
53
+ }
54
+ return null;
55
+ }
56
+
57
+ /**
58
+ * Resolve the import line for THIS project: the depth that reaches the scope
59
+ * holding the manager. Falls back to the canonical project-local path when
60
+ * the manager is not installed, so the line still names where the docs live.
61
+ *
62
+ * @param {string} root - Absolute project root
63
+ * @returns {string} - The `@<relative path>` import line
64
+ */
65
+ function resolveImportLine(root) {
66
+ const scope = findScope(root);
67
+ return scope ? `@${scope.prefix}${GUIDE_SUBPATH}` : IMPORT_LINE;
68
+ }
69
+
70
+ /**
71
+ * Remove the retired `node_modules/@omega.js/AGENTS.md` link (or a stale file
72
+ * in its place) from the scope serving this brand.
73
+ *
74
+ * @param {string} brandRoot - Absolute brand monorepo root
75
+ * @param {{ dryRun?: boolean }} [options] - dryRun: the same verdict, nothing removed
76
+ * @returns {'removed'|'absent'} - What happened
77
+ */
78
+ function removeRetiredLink(brandRoot, { dryRun = false } = {}) {
79
+ const scope = findScope(brandRoot);
80
+ const link = scope && join(scope.scopeDir, 'AGENTS.md');
81
+ if (!link || !fs.lstatSync(link, { throwIfNoEntry: false })) {
82
+ return 'absent';
83
+ }
84
+ if (!dryRun) {
85
+ fs.rmSync(link, { force: true });
86
+ }
87
+ return 'removed';
88
+ }
89
+
90
+ /**
91
+ * Render a fresh project-root AGENTS.md: the import under the Default marker,
92
+ * then the Custom marker and nothing below it.
93
+ *
94
+ * @param {string} [importLine] - The resolved import line
95
+ * @returns {string} - Full file content
96
+ */
97
+ function renderAgentsMd(importLine = IMPORT_LINE) {
98
+ const { defaultMarker, customMarker } = sectionMarkers(FILE_NAME);
99
+ return `${defaultMarker}\n${importLine}\n\n${customMarker}\n`;
100
+ }
101
+
102
+ /**
103
+ * Is this the retired per-framework template? It OPENS with the `#` Default
104
+ * marker; a file that merely quotes the markers in its notes is not one.
105
+ *
106
+ * @param {string} content - The AGENTS.md content
107
+ * @returns {boolean}
108
+ */
109
+ function isRetiredTemplate(content) {
110
+ return content.split('\n')[0].trim() === DEFAULT_MARKER && hasSectionMarkers(content);
111
+ }
112
+
113
+ /**
114
+ * The notes a consumer wrote in an AGENTS.md of any shape, trimmed: the Custom
115
+ * section of a marked or retired-template file, else the whole file, minus
116
+ * every line a framework wrote (the import, the skeleton heading, shipped
117
+ * boilerplate).
118
+ *
119
+ * @param {string} content - The AGENTS.md content
120
+ * @returns {string} - '' when the file holds nothing the framework did not write
121
+ */
122
+ function consumerNotes(content) {
123
+ const source = hasSectionMarkers(content, FILE_NAME)
124
+ ? getCustomSection(content, FILE_NAME)
125
+ : isRetiredTemplate(content) ? getCustomSection(content) : content;
126
+ return source.split('\n')
127
+ .filter((line) => !isImportLine(line) && !NOTES_HEADING.test(line.trim()) && !isShipped(line, FILE_NAME))
128
+ .join('\n')
129
+ .trim();
130
+ }
131
+
132
+ /**
133
+ * Ensure the project-root AGENTS.md is the marked shape with the resolved
134
+ * import. A marked file goes through the engine (the Default section healed,
135
+ * the Custom section verbatim); any other shape converges, its notes landing
136
+ * under Custom in order.
137
+ *
138
+ * @param {string} root - Absolute project root
139
+ * @param {{ dryRun?: boolean }} [options] - dryRun: the same verdict, nothing written
140
+ * @returns {'present'|'created'|'healed'|'converged'} - What happened
141
+ */
142
+ function ensureAgentsMd(root, { dryRun = false } = {}) {
143
+ const file = join(root, FILE_NAME);
144
+ const template = renderAgentsMd(resolveImportLine(root));
145
+ const existing = jetpack.read(file);
146
+
147
+ if (existing === undefined) {
148
+ if (!dryRun) jetpack.write(file, template);
149
+ return 'created';
150
+ }
151
+
152
+ const marked = hasSectionMarkers(existing, FILE_NAME);
153
+ const next = mergeLineBasedFiles(marked ? existing : consumerNotes(existing), template, FILE_NAME);
154
+ if (next === existing) {
155
+ return 'present';
156
+ }
157
+ if (!dryRun) jetpack.write(file, next);
158
+ return marked ? 'healed' : 'converged';
159
+ }
160
+
161
+ /**
162
+ * Keep a brand target free of any AGENTS.md: the brand root is its doc home. A
163
+ * copy holding nothing the consumer wrote is removed; one with notes stays.
164
+ *
165
+ * @param {string} targetDir - Absolute brand target root
166
+ * @returns {'absent'|'removed'|'kept'} - What happened
167
+ */
168
+ function retireAgentsMd(targetDir) {
169
+ const file = join(targetDir, 'AGENTS.md');
170
+ const existing = jetpack.read(file);
171
+
172
+ if (existing === undefined) {
173
+ return 'absent';
174
+ }
175
+ if (consumerNotes(existing)) {
176
+ return 'kept';
177
+ }
178
+ jetpack.remove(file);
179
+ return 'removed';
180
+ }
181
+
182
+ /**
183
+ * A framework scaffold's AGENTS.md step, recorded into the defaults engine's
184
+ * result and logged the way the engine logs: a standalone project root gets
185
+ * the builder's file; a brand target gets none.
186
+ *
187
+ * @param {object} options
188
+ * @param {string} options.outputDir - Absolute project or target root
189
+ * @param {boolean} options.standalone - True when no brand root sits above it
190
+ * @param {{written: string[], merged: string[], skipped: string[], removed: string[]}} options.result - The engine result to record into
191
+ * @param {object} [options.logger] - `{ log, warn }` (defaults to console)
192
+ */
193
+ function scaffoldAgentsMd({ outputDir, standalone, result, logger = console }) {
194
+ if (!standalone) {
195
+ const verdict = retireAgentsMd(outputDir);
196
+ if (verdict === 'removed') {
197
+ result.removed.push('AGENTS.md');
198
+ logger.warn('Retired AGENTS.md: a brand target carries none, the brand root AGENTS.md is the one doc home');
199
+ } else if (verdict === 'kept') {
200
+ result.skipped.push('AGENTS.md');
201
+ logger.warn('Kept AGENTS.md: it carries consumer content. A brand target carries no AGENTS.md: move those notes under the Custom marker of the brand root AGENTS.md, then delete the file');
202
+ }
203
+ return;
204
+ }
205
+
206
+ const verdict = ensureAgentsMd(outputDir);
207
+ if (verdict === 'created') {
208
+ result.written.push('AGENTS.md');
209
+ logger.log('Scaffolded → AGENTS.md');
210
+ } else if (verdict === 'healed') {
211
+ result.merged.push('AGENTS.md');
212
+ logger.log('Healed → AGENTS.md: the Default section imports the manager, your Custom section kept');
213
+ } else if (verdict === 'converged') {
214
+ result.merged.push('AGENTS.md');
215
+ logger.warn('Converged AGENTS.md to the marker sections: the manager import under Default, your notes under Custom');
216
+ } else {
217
+ result.skipped.push('AGENTS.md');
218
+ }
219
+ }
220
+
221
+ module.exports = {
222
+ GUIDE_SUBPATH,
223
+ IMPORT_LINE,
224
+ RETIRED_SUBPATH,
225
+ isImportLine,
226
+ findScope,
227
+ resolveImportLine,
228
+ removeRetiredLink,
229
+ renderAgentsMd,
230
+ ensureAgentsMd,
231
+ retireAgentsMd,
232
+ scaffoldAgentsMd,
233
+ };
@@ -25,7 +25,8 @@
25
25
  // Two attaches on ONE tee stack the same way: a second attach of a DIFFERENT path pushes a
26
26
  // layer on top of the first instead of replacing it, so a verb that runs another verb in
27
27
  // process (`omega test` running `omega deploy`) keeps teeing both files. Detach order is
28
- // LIFO, and the module-level `detach()` pops the newest layer.
28
+ // LIFO, and the module-level `detach()` pops the newest layer. A harness that runs a verb
29
+ // takes `mark()` first and calls the restore it returns after, never a blind `detach()`.
29
30
  //
30
31
  // Skipped in CI: a runner has its own log capture and wants no logs/ left in the workspace.
31
32
  // `attachInCI: true` opts out of that skip, for a sink whose file is the POINT rather than
@@ -142,8 +143,20 @@ function createTee() {
142
143
  return detach;
143
144
  }
144
145
 
146
+ // A harness around a verb restores to the mark instead of popping blind: a run can
147
+ // push one layer, several, or none, and only what it pushed comes off, newest first.
148
+ function mark() {
149
+ const kept = layers.slice();
150
+ return function restore() {
151
+ for (const layer of layers.slice().reverse()) {
152
+ if (!kept.includes(layer)) { layer.detach(); }
153
+ }
154
+ };
155
+ }
156
+
145
157
  return {
146
158
  attach,
159
+ mark,
147
160
  detach: () => {
148
161
  const newest = layers[layers.length - 1];
149
162
  if (newest) { newest.detach(); }
@@ -161,7 +174,7 @@ function createTee() {
161
174
  // The roll is what the tee has no need for: a child that stays up for days (a
162
175
  // Firebase emulator) has to be truncatable MID-RUN, so a sibling process asks
163
176
  // for a fresh log by touching `resetPath` and the poll below honors it within
164
- // `pollMs`. Ported from BEM's emulator/serve commands, which carried a copy each.
177
+ // `pollMs`. The backend's emulator and serve commands share it.
165
178
  //
166
179
  // Writes go to an open fd synchronously, for the same reason the tee's do: the
167
180
  // lines describing a crash are the ones a buffered stream drops.
@@ -265,6 +278,7 @@ function attachLogFile(filePath, options) {
265
278
 
266
279
  module.exports = attachLogFile;
267
280
  module.exports.detach = singleton.detach;
281
+ module.exports.mark = singleton.mark;
268
282
  module.exports.stripAnsi = stripAnsi;
269
283
  module.exports.createTee = createTee;
270
284
  module.exports.createChildLog = createChildLog;
@@ -23,7 +23,7 @@ const { clientConfig } = require('../config/index.js');
23
23
  // ([#894](https://github.com/Omega-JS-Stack/omega/issues/894)): the build's
24
24
  // verdict (`environment`), whether it was a BUILD rather than a dev/watch run,
25
25
  // and whether it publishes. A surface's own extra verdicts (desktop's
26
- // `server`) stay inside that surface's Manager and never reach the artifact.
26
+ // `server`) stay inside that surface's Omega instance and never reach the artifact.
27
27
  const MODE_KEYS = ['environment', 'build', 'publish'];
28
28
 
29
29
  /**