@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
@@ -1,73 +1,87 @@
1
- // Merge line-based files (.env, .gitignore, AGENTS.md) during framework setup —
2
- // the OMEGA marker-section protocol. Canonical version is EM's (it added .env
3
- // double-quote normalization and order-safe key substitution over the older
4
- // BXM/UJM inline copies) plus @omega.js/backend's custom-key promotion (a key the framework
5
- // newly adopts into its Default section is promoted UP from the user's Custom
6
- // section with their value, instead of appearing empty in Default and set in
7
- // Custom).
8
- //
9
- // Convention (the ONE OMEGA marker grammar — `<comment> ========== <Label> ==========`,
10
- // comment token per file type; the rules-file managed block is the `//` flavor of the
11
- // same family, owned by @omega.js/backend — see _attic/plans/archive/marker-harmonization.md):
12
- //
13
- // # ========== Default Values ==========
14
- // # framework-managed; overwritten on every setup
15
- // KEY1=
16
- // KEY2="value with spaces"
17
- //
18
- // # ========== Custom Values ==========
19
- // # user's section; preserved verbatim across setups
20
- // USER_SECRET="my-secret"
21
- //
22
- // Behavior:
23
- // - The Default section is replaced with the new framework's defaults.
24
- // - Keys that already had values (in either Default or Custom) keep those values
25
- // in the same section they were in.
26
- // - The Default section is COMPLETELY managed ([#926](https://github.com/Omega-JS-Stack/omega/issues/926)):
27
- // a line the new framework block no longer carries LEAVES the consumer, so a
28
- // rule the framework retires or rewrites actually takes effect everywhere. A
29
- // retired framework line and a line a user typed inside the framework block
30
- // cannot be told apart, and the block header already says it is overwritten on
31
- // every setup, so `.gitignore` and `AGENTS.md` drop it. Lines a user owns
32
- // belong under the Custom marker, which stays verbatim.
33
- // - .env is the one exception: a retired key holding a NON-EMPTY value migrates
34
- // to the Custom section (that value is data, not a rule), while an empty
35
- // retired key (`KEY=` / `KEY=""`) drops like any other retired line.
36
- // - .env values are normalized to **double-quoted** form on every merge:
37
- // KEY=raw-value → KEY="raw-value"
38
- // KEY="already-quoted" → KEY="already-quoted" (left alone)
39
- // KEY= → KEY= (empty stays empty/unquoted)
40
- // This protects values containing spaces, #, $, or other shell-meaningful chars.
41
- // - .env COMMENTED PLACEHOLDERS (`# KEY=` in the template's Default section)
42
- // mark keys the framework knows but ships no value for (the env cascade
43
- // supplies values from stronger layers — dogfood friction #20). On merge:
44
- // an existing NON-EMPTY value for that key (either section) keeps its line
45
- // in Default; an existing EMPTY value (`KEY=` / `KEY=""`) converges to the
46
- // commented placeholder instead of migrating to Custom.
47
- // - .gitignore: same logic, line-based instead of key-based (no quoting).
48
- // - AGENTS.md: same logic as .gitignore (line-based, no quoting). The markers
49
- // render as visible H1 headings in markdown — that's intentional UX.
50
- // - First setup (no existing file): the framework template lands as-is.
1
+ // Merge line-based files (.env, .gitignore, .gitattributes, AGENTS.md) through
2
+ // the OMEGA marker sections: the framework rewrites Default on every run, the
3
+ // consumer's Custom stays verbatim, and a file with no markers yet converges
4
+ // once, its consumer lines landing under Custom. The protocol (the grammar and its
5
+ // flavors, the .env value rules, placeholders, promotion, the first converge)
6
+ // lives in docs/devkit/index.md.
7
+
8
+ const { joinEnvUnits, envKey, normalizeEnvLine, findKeyLine, parsePlaceholderKey, isMachineComment, envValueIsEmpty } = require('./env-lines');
51
9
 
52
10
  const DEFAULT_MARKER = '# ========== Default Values ==========';
53
11
  const CUSTOM_MARKER = '# ========== Custom Values ==========';
54
12
 
13
+ // The markdown flavor of the one grammar: an HTML comment renders as nothing.
14
+ const MARKDOWN_DEFAULT_MARKER = '<!-- ========== Default Values ========== -->';
15
+ const MARKDOWN_CUSTOM_MARKER = '<!-- ========== Custom Values ========== -->';
16
+
17
+ // Framework text earlier generations wrote, one pattern per line, each block
18
+ // scoped to the file type that shipped it: Custom boilerplate (the retired
19
+ // per-framework AGENTS.md's included), and old .env headers and group notes,
20
+ // which only an unmarked .env's first converge strips.
21
+ const SHIPPED = [
22
+ { file: '.gitignore', lines: [/^# Add your custom ignore patterns below this line$/, /^# \.\.\.$/] },
23
+ { file: '.gitignore', lines: [/^# Add your own ignores below\. This section is preserved across (framework re-syncs|`npx omega setup` runs|`npx mgr setup` runs)\.$/] },
24
+ { file: '.gitignore', lines: [/^# \.\.\.$/] },
25
+ { file: 'AGENTS.md', lines: [/^## Project-specific notes$/, /^Add anything specific to THIS project here\. Edits below this line are preserved across (runs|framework re-syncs|`npx omega setup` runs)\.$/] },
26
+ { file: 'AGENTS.md', lines: [/^<!-- Add your project-specific notes below this line -->$/] },
27
+ {
28
+ file: '.env',
29
+ lines: [
30
+ /^# .+(?: \u2014|:) brand secrets \(gitignored; loaded before every omega(?:-manager)? run\)\.$/,
31
+ /^# Uncomment and fill what this brand uses\. Services without their credentials$/,
32
+ /^# skip cleanly, so add these as the brand adopts each service\.$/,
33
+ ],
34
+ },
35
+ {
36
+ file: '.env',
37
+ lines: [
38
+ /^# .+(?: \u2014|:) COMPANY secrets \(gitignored; loaded UNDER every (?:managed brand's own \.env|brand of this company)\)\.$/,
39
+ /^# Precedence: shell env > brand \.env > this file\. Put here only what every brand$/,
40
+ /^# shares(?: \u2014 anything|\. Anything) brand-specific belongs in that brand's \.env, never here\.$/,
41
+ ],
42
+ },
43
+ {
44
+ file: '.env',
45
+ lines: [
46
+ /^# one per stream; disperse composes each target its own GOOGLE_ANALYTICS_SECRET\), and the$/,
47
+ /^# Auto-generated and persisted here on the first real run \u2014 leave unset:$/,
48
+ /^# ACCOUNT_PASSWORD_SEED, CSC_KEY_PASSWORD$/,
49
+ ],
50
+ },
51
+ ];
52
+
55
53
  function mergeLineBasedFiles(existingContent, newContent, fileName) {
56
54
  const isEnvFile = fileName === '.env';
55
+ const markers = sectionMarkers(fileName);
57
56
 
58
- const existingLines = existingContent.split('\n');
59
- const newLines = newContent.split('\n');
60
-
61
- // Parse existing into default + custom sections.
62
- const { defaultLines: existingDefault, customLines: existingCustom, existingDefaultKeys, existingCustomKeys }
63
- = splitSections(existingLines, isEnvFile);
57
+ const hasMarkers = hasSectionMarkers(existingContent, fileName);
58
+ // .env merges by logical line: a quoted value spanning lines is one unit
59
+ const toLines = (content) => (isEnvFile ? joinEnvUnits(content.split('\n')) : content.split('\n'));
60
+ const existingLines = toLines(existingContent);
61
+ const newLines = toLines(newContent);
64
62
 
65
63
  // Parse new content. We only use its default section (custom is the user's domain).
66
- const { defaultLines: newDefault, customLines: newCustom } = splitSections(newLines, isEnvFile);
64
+ const { defaultLines: newDefault } = splitSections(newLines, markers);
65
+ const newDefaultKeys = new Set(isEnvFile ? newDefault.map((line) => envKey(line) || parsePlaceholderKey(line.trim())).filter(Boolean) : []);
66
+
67
+ let existingDefault = [];
68
+ let existingCustom = [];
69
+ if (hasMarkers) {
70
+ const sections = splitSections(existingLines, markers);
71
+ existingDefault = sections.defaultLines;
72
+ const shipped = !isEnvFile && isShippedBlock(sections.customLines, fileName);
73
+ existingCustom = withPreamble(sections.preamble, shipped ? [''] : sections.customLines);
74
+ } else if (isEnvFile) {
75
+ existingDefault = existingLines.filter((line) => !isMarkerLine(line, markers));
76
+ } else {
77
+ existingCustom = convergeUnmarked(existingLines, newDefault, markers, fileName);
78
+ }
79
+ const keysOf = (lines) => new Set(isEnvFile ? lines.map(envKey).filter(Boolean) : []);
80
+ const existingDefaultKeys = keysOf(existingDefault);
81
+ const existingCustomKeys = keysOf(existingCustom);
67
82
 
68
83
  // Build the merged default section: walk new defaults in order, substituting the
69
84
  // user's existing value for any key they had set in either section.
70
- const newDefaultKeys = new Set();
71
85
  const mergedDefault = [];
72
86
  const emit = (line) => mergedDefault.push(isEnvFile ? normalizeEnvLine(line) : line);
73
87
 
@@ -75,9 +89,8 @@ function mergeLineBasedFiles(existingContent, newContent, fileName) {
75
89
  const trimmed = line.trim();
76
90
 
77
91
  if (isEnvFile && trimmed && !trimmed.startsWith('#')) {
78
- const key = trimmed.split('=')[0].trim();
92
+ const key = envKey(line);
79
93
  if (key) {
80
- newDefaultKeys.add(key);
81
94
  if (existingDefaultKeys.has(key)) {
82
95
  emit(findKeyLine(existingDefault, key));
83
96
  continue;
@@ -95,13 +108,11 @@ function mergeLineBasedFiles(existingContent, newContent, fileName) {
95
108
  // .gitignore: just keep the new line.
96
109
  mergedDefault.push(line);
97
110
  } else {
98
- // Comment / blank — for .env, a `# KEY=` placeholder claims the key:
111
+ // Comment or blank. For .env, a `# KEY=""` placeholder claims the key:
99
112
  // an existing set value keeps its line, an empty one converges away.
100
113
  const placeholderKey = isEnvFile ? parsePlaceholderKey(trimmed) : null;
101
114
 
102
115
  if (placeholderKey) {
103
- newDefaultKeys.add(placeholderKey);
104
-
105
116
  const existingLine = existingDefaultKeys.has(placeholderKey)
106
117
  ? findKeyLine(existingDefault, placeholderKey)
107
118
  : existingCustomKeys.has(placeholderKey)
@@ -118,159 +129,175 @@ function mergeLineBasedFiles(existingContent, newContent, fileName) {
118
129
  }
119
130
  }
120
131
 
121
- // Anything in the existing Default section the new framework block no longer
122
- // carries. The block is COMPLETELY managed (#926), so it DROPS: a retired
123
- // framework line and a line the user typed into the framework block cannot be
124
- // told apart, and the block header already says it is overwritten on every
125
- // setup. The one exception is an .env key holding a real value, which is the
126
- // user's DATA rather than a rule, so it migrates down to Custom; an empty one
127
- // drops with the rest. .gitignore and AGENTS.md migrate nothing.
128
- const migratedToCustom = [];
129
- if (isEnvFile) {
130
- for (const line of existingDefault) {
131
- const trimmed = line.trim();
132
- if (!trimmed || trimmed.startsWith('#')) continue;
133
-
134
- const key = trimmed.split('=')[0].trim();
135
- if (key && !newDefaultKeys.has(key) && !existingCustomKeys.has(key) && !envValueIsEmpty(trimmed)) {
136
- migratedToCustom.push(normalizeEnvLine(line));
137
- }
138
- }
139
- }
140
-
141
- // The user's Custom section is preserved — except (a) .env values get normalized
142
- // to double-quoted form for consistent quoting, and (b) any key that was promoted
143
- // UP into the Default section above is dropped here so it isn't duplicated. Keys
144
- // present in BOTH sections are NOT dropped (the Default copy's value won there,
145
- // so removing the Custom copy would silently change dotenv's effective value).
132
+ // A line the new framework block does not carry drops, except an .env key
133
+ // holding a value: the consumer's data, so it moves under Custom. An unmarked
134
+ // .env's first converge also carries every consumer comment there.
135
+ const known = new Set([...newDefaultKeys, ...existingCustomKeys]);
136
+ const migratedToCustom = !isEnvFile
137
+ ? []
138
+ : hasMarkers
139
+ ? existingDefault.filter((line) => isValuedUnknown(line, known)).map(normalizeEnvLine)
140
+ : convergeUnmarkedEnv(existingDefault, newDefault, known);
141
+
142
+ // Custom stays verbatim but for .env quoting and dropping a key promoted UP
143
+ // into Default. A key set in both sections keeps both lines: dotenv reads the
144
+ // last one, the Custom line, so dropping it would change the value.
146
145
  const finalCustom = isEnvFile
147
146
  ? existingCustom
148
147
  .filter((line) => {
149
148
  const trimmed = line.trim();
150
149
  if (!trimmed || trimmed.startsWith('#')) return true;
151
- const key = trimmed.split('=')[0].trim();
150
+ const key = envKey(line);
152
151
  return !(key && newDefaultKeys.has(key) && !existingDefaultKeys.has(key));
153
152
  })
154
153
  .map((line) => normalizeEnvLine(line))
155
154
  : existingCustom;
156
155
 
157
156
  const result = [];
158
- result.push(DEFAULT_MARKER);
157
+ result.push(markers.defaultMarker);
159
158
  result.push(...mergedDefault);
160
159
  // Insert a single blank line before CUSTOM_MARKER, but only if the merged default
161
160
  // doesn't already end with one (otherwise we'd accumulate an extra blank line on
162
- // every merge — breaking idempotency on the first re-run after a fresh `jetpack.copy`).
161
+ // every merge, breaking idempotency on the first re-run after a fresh `jetpack.copy`).
163
162
  if (mergedDefault.length === 0 || mergedDefault[mergedDefault.length - 1].trim() !== '') {
164
163
  result.push('');
165
164
  }
166
- result.push(CUSTOM_MARKER);
165
+ result.push(markers.customMarker);
167
166
  if (migratedToCustom.length > 0) {
168
167
  result.push(...migratedToCustom);
169
168
  }
170
169
  result.push(...finalCustom);
170
+ // A converged file ends on a newline like every marked one
171
+ if (!hasMarkers && result[result.length - 1] !== '') {
172
+ result.push('');
173
+ }
171
174
 
172
175
  return result.join('\n');
173
176
  }
174
177
 
175
- // Normalize a single .env line:
176
- // - Comments / blanks unchanged
177
- // - KEY= (empty value) unchanged
178
- // - KEY="..." (already double-quoted) unchanged
179
- // - KEY=raw-value → KEY="raw-value" (with embedded " and \ escaped)
180
- // - KEY='single' → KEY="single" (canonicalize single → double)
181
- function normalizeEnvLine(line) {
182
- if (typeof line !== 'string') return line;
178
+ // Split a file on its marker lines: the lines above the first marker, the
179
+ // Default section and the Custom section.
180
+ function splitSections(lines, markers) {
181
+ const sections = { preamble: [], defaultLines: [], customLines: [] };
182
+ let into = sections.preamble;
183
+ for (const line of lines) {
184
+ const trimmed = line.trim();
185
+ if (trimmed === markers.defaultMarker) { into = sections.defaultLines; continue; }
186
+ if (trimmed === markers.customMarker) { into = sections.customLines; continue; }
187
+ into.push(line);
188
+ }
189
+ return sections;
190
+ }
183
191
 
184
- // Preserve comments and blank lines verbatim.
192
+ function isMarkerLine(line, markers) {
185
193
  const trimmed = line.trim();
186
- if (!trimmed || trimmed.startsWith('#')) return line;
187
-
188
- // Capture leading whitespace so we don't lose indentation.
189
- const leadingMatch = line.match(/^(\s*)/);
190
- const leading = leadingMatch ? leadingMatch[1] : '';
194
+ return trimmed === markers.defaultMarker || trimmed === markers.customMarker;
195
+ }
191
196
 
192
- const eqIdx = trimmed.indexOf('=');
193
- if (eqIdx < 0) return line; // no `=` — not a KEY=VALUE line
197
+ // Blank lines at either end dropped, a run of blanks inside collapsed to one.
198
+ function tidyParagraphs(lines) {
199
+ const tidy = [];
200
+ for (const line of lines) {
201
+ if (!line.trim() && (tidy.length === 0 || !tidy[tidy.length - 1].trim())) continue;
202
+ tidy.push(line);
203
+ }
204
+ if (tidy.length > 0 && !tidy[tidy.length - 1].trim()) tidy.pop();
205
+ return tidy;
206
+ }
194
207
 
195
- const key = trimmed.slice(0, eqIdx).trim();
196
- const value = trimmed.slice(eqIdx + 1);
208
+ // A marked file's lines above its Default marker open its Custom section.
209
+ function withPreamble(preamble, custom) {
210
+ const lead = tidyParagraphs(preamble);
211
+ if (lead.length === 0) return custom;
212
+ const start = custom.findIndex((line) => line.trim());
213
+ return start < 0 ? [...lead, ''] : [...lead, '', ...custom.slice(start)];
214
+ }
197
215
 
198
- // Strip trailing whitespace + inline comment-after-value (rare; we only strip a # that follows a space).
199
- // We do NOT strip # inside quoted values. Detect that by checking if value starts with a quote.
200
- if (value.length === 0) {
201
- return `${leading}${key}=`;
216
+ // A marker-less file's first converge: paragraph by paragraph, a line the new
217
+ // framework block carries is the framework's, a paragraph whose every entry is
218
+ // one goes whole (its comments were a framework header), and the rest land
219
+ // under Custom in order, one blank line between paragraphs.
220
+ function convergeUnmarked(lines, newDefault, markers, fileName) {
221
+ const framework = new Set(newDefault.map((line) => line.trim()).filter(Boolean));
222
+ // The grammar's comment token opens every marker: `#`, or `<!--` in markdown
223
+ const commentToken = markers.defaultMarker.split(' ')[0];
224
+ const paragraphs = [[]];
225
+ for (const line of lines) {
226
+ const trimmed = line.trim();
227
+ if (isMarkerLine(line, markers)) continue;
228
+ if (trimmed) paragraphs[paragraphs.length - 1].push(line);
229
+ else if (paragraphs[paragraphs.length - 1].length > 0) paragraphs.push([]);
202
230
  }
203
231
 
204
- // Already double-quoted? Leave alone (preserves user's exact contents).
205
- if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
206
- return `${leading}${key}=${value}`;
207
- }
232
+ const custom = [];
233
+ for (const paragraph of paragraphs) {
234
+ const entries = paragraph.filter((line) => !line.trim().startsWith(commentToken));
235
+ if (entries.length > 0 && entries.every((line) => framework.has(line.trim()))) continue;
208
236
 
209
- // Single-quoted → canonicalize to double-quoted.
210
- if (value.startsWith("'") && value.endsWith("'") && value.length >= 2) {
211
- const inner = value.slice(1, -1);
212
- return `${leading}${key}="${escapeForDoubleQuote(inner)}"`;
237
+ const kept = paragraph.filter((line) => !framework.has(line.trim()) && !isShipped(line, fileName));
238
+ if (kept.length === 0) continue;
239
+ if (custom.length > 0) custom.push('');
240
+ custom.push(...kept);
213
241
  }
214
-
215
- // Raw value → wrap.
216
- return `${leading}${key}="${escapeForDoubleQuote(value)}"`;
242
+ custom.push('');
243
+ return custom;
217
244
  }
218
245
 
219
- function escapeForDoubleQuote(s) {
220
- return String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
246
+ // An .env key the framework block does not know, holding a value.
247
+ function isValuedUnknown(line, known) {
248
+ const key = envKey(line);
249
+ return Boolean(key) && !known.has(key) && !envValueIsEmpty(line.trim());
221
250
  }
222
251
 
223
- function splitSections(lines, isEnvFile) {
224
- const defaultLines = [];
225
- const customLines = [];
226
- const existingDefaultKeys = new Set();
227
- const existingCustomKeys = new Set();
228
-
229
- let mode = null; // null | 'default' | 'custom'
252
+ // An unmarked .env's first converge, in file order with its paragraphs: every
253
+ // comment but the framework's own and a run directly above a known key or its
254
+ // placeholder, and every valued key the framework block does not know.
255
+ function convergeUnmarkedEnv(lines, newDefault, known) {
256
+ const framework = new Set(newDefault.map((line) => line.trim()).filter(Boolean));
257
+ const kept = [];
258
+ let run = [];
230
259
  for (const line of lines) {
231
260
  const trimmed = line.trim();
232
- if (trimmed === DEFAULT_MARKER) { mode = 'default'; continue; }
233
- if (trimmed === CUSTOM_MARKER) { mode = 'custom'; continue; }
234
-
235
- // Lines before any marker are treated as default (legacy / fresh files).
236
- if (mode === 'custom') {
237
- customLines.push(line);
238
- if (isEnvFile && trimmed && !trimmed.startsWith('#')) {
239
- const key = trimmed.split('=')[0].trim();
240
- if (key) existingCustomKeys.add(key);
241
- }
242
- } else {
243
- defaultLines.push(line);
244
- if (isEnvFile && trimmed && !trimmed.startsWith('#')) {
245
- const key = trimmed.split('=')[0].trim();
246
- if (key) existingDefaultKeys.add(key);
247
- }
261
+ if (trimmed.startsWith('#') && !isMachineComment(trimmed) && !framework.has(trimmed)) {
262
+ if (!isShipped(line, '.env')) run.push(line);
263
+ continue;
248
264
  }
265
+ const key = envKey(line) || parsePlaceholderKey(trimmed);
266
+ if (!key || !known.has(key)) kept.push(...run);
267
+ if (isValuedUnknown(line, known)) kept.push(normalizeEnvLine(line));
268
+ if (!trimmed) kept.push('');
269
+ run = [];
249
270
  }
250
-
251
- return { defaultLines, customLines, existingDefaultKeys, existingCustomKeys };
271
+ kept.push(...run);
272
+ return tidyParagraphs(kept);
252
273
  }
253
274
 
254
- function findKeyLine(lines, key) {
255
- const re = new RegExp(`^\\s*${key}\\s*=`);
256
- for (const line of lines) {
257
- if (re.test(line)) return line;
258
- }
259
- return `${key}=`;
275
+ /**
276
+ * Is this line framework text an earlier generation shipped in this file type?
277
+ * @param {string} line
278
+ * @param {string} fileName - The file's name (e.g. `.gitignore`, `AGENTS.md`, `.env`)
279
+ * @returns {boolean}
280
+ */
281
+ function isShipped(line, fileName) {
282
+ return SHIPPED.some((entry) => entry.file === fileName && entry.lines.some((shipped) => shipped.test(line.trim())));
260
283
  }
261
284
 
262
- // `# KEY=` (nothing after the =) → KEY; any other comment → null.
263
- function parsePlaceholderKey(trimmed) {
264
- const match = trimmed.match(/^#\s*([A-Za-z_][A-Za-z0-9_]*)=\s*$/);
265
- return match ? match[1] : null;
285
+ // Is this Custom section exactly one block this file type shipped?
286
+ function isShippedBlock(lines, fileName) {
287
+ const body = lines.map((line) => line.trim()).filter(Boolean);
288
+ return SHIPPED.some((entry) => entry.file === fileName && entry.lines.length === body.length && entry.lines.every((shipped, i) => shipped.test(body[i])));
266
289
  }
267
290
 
268
- // KEY= / KEY="" / KEY='' (whitespace tolerated) count as empty.
269
- function envValueIsEmpty(line) {
270
- const eqIdx = line.indexOf('=');
271
- if (eqIdx < 0) return true;
272
- const value = line.slice(eqIdx + 1).trim();
273
- return value === '' || value === '""' || value === "''";
291
+ /**
292
+ * The marker pair a file speaks, by file type: markdown takes the HTML-comment
293
+ * flavor of the one grammar, every other line file the `#` flavor.
294
+ * @param {string} [fileName] - The file's name (e.g. `AGENTS.md`, `.gitignore`)
295
+ * @returns {{ defaultMarker: string, customMarker: string }}
296
+ */
297
+ function sectionMarkers(fileName) {
298
+ return typeof fileName === 'string' && fileName.endsWith('.md')
299
+ ? { defaultMarker: MARKDOWN_DEFAULT_MARKER, customMarker: MARKDOWN_CUSTOM_MARKER }
300
+ : { defaultMarker: DEFAULT_MARKER, customMarker: CUSTOM_MARKER };
274
301
  }
275
302
 
276
303
  /**
@@ -278,26 +305,41 @@ function envValueIsEmpty(line) {
278
305
  * through the merge protocol at least once). Setup validators use this to
279
306
  * distinguish legacy/no-marker files from protocol-managed ones.
280
307
  * @param {string} content
308
+ * @param {string} [fileName] - Selects the marker flavor (default: the `#` flavor)
281
309
  * @returns {boolean}
282
310
  */
283
- function hasSectionMarkers(content) {
284
- return typeof content === 'string'
285
- && content.includes(DEFAULT_MARKER)
286
- && content.includes(CUSTOM_MARKER);
311
+ function hasSectionMarkers(content, fileName) {
312
+ if (typeof content !== 'string') return false;
313
+ const { defaultMarker, customMarker } = sectionMarkers(fileName);
314
+ const lines = content.split('\n').map((line) => line.trim());
315
+ const at = lines.indexOf(defaultMarker);
316
+ return at >= 0 && lines.indexOf(customMarker, at + 1) > at;
287
317
  }
288
318
 
289
319
  /**
290
320
  * Extract the consumer-owned Custom section of a marker file (everything after
291
- * the Custom marker), or '' when there is no marker. The defaults engine's
321
+ * the Custom marker line), or '' when there is no marker. The defaults engine's
292
322
  * `retire` rule uses this to judge whether a per-target doc carries consumer
293
323
  * content or is framework-owned-only.
294
324
  * @param {string} content
325
+ * @param {string} [fileName] - Selects the marker flavor (default: the `#` flavor)
295
326
  * @returns {string}
296
327
  */
297
- function getCustomSection(content) {
328
+ function getCustomSection(content, fileName) {
298
329
  if (typeof content !== 'string') return '';
299
- const idx = content.indexOf(CUSTOM_MARKER);
300
- return idx < 0 ? '' : content.slice(idx + CUSTOM_MARKER.length);
330
+ const { customMarker } = sectionMarkers(fileName);
331
+ const lines = content.split('\n');
332
+ const at = lines.findIndex((line) => line.trim() === customMarker);
333
+ return at < 0 ? '' : ['', ...lines.slice(at + 1)].join('\n');
301
334
  }
302
335
 
303
- module.exports = { mergeLineBasedFiles, normalizeEnvLine, hasSectionMarkers, getCustomSection, DEFAULT_MARKER, CUSTOM_MARKER };
336
+ module.exports = {
337
+ mergeLineBasedFiles,
338
+ normalizeEnvLine,
339
+ hasSectionMarkers,
340
+ getCustomSection,
341
+ sectionMarkers,
342
+ isShipped,
343
+ DEFAULT_MARKER,
344
+ CUSTOM_MARKER,
345
+ };