@omega.js/desktop 0.50.0 → 0.51.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 (267) hide show
  1. package/README.md +1 -1
  2. package/dist/assets/css/tokens/_index.scss +1 -1
  3. package/dist/assets/themes/base/_includes/frontend/sections/account-section-header.html +4 -1
  4. package/dist/assets/themes/base/_includes/frontend/sections/footer.html +13 -6
  5. package/dist/assets/themes/base/_includes/frontend/sections/nav.html +10 -8
  6. package/dist/assets/themes/base/_includes/global/sections/account.html +3 -1
  7. package/dist/assets/themes/base/_includes/global/sections/app-sidebar.html +12 -8
  8. package/dist/assets/themes/base/_includes/global/sections/app-topbar.html +10 -6
  9. package/dist/assets/themes/base/_includes/global/sections/page-header.html +8 -4
  10. package/dist/assets/themes/base/_layouts/backend/pages/dashboard/index.html +24 -24
  11. package/dist/assets/themes/base/_layouts/frontend/pages/about.html +10 -10
  12. package/dist/assets/themes/base/_layouts/frontend/pages/account/index.html +35 -35
  13. package/dist/assets/themes/base/_layouts/frontend/pages/alternatives/index.html +4 -4
  14. package/dist/assets/themes/base/_layouts/frontend/pages/auth/signin.html +1 -1
  15. package/dist/assets/themes/base/_layouts/frontend/pages/auth/signup.html +4 -4
  16. package/dist/assets/themes/base/_layouts/frontend/pages/contact.html +3 -3
  17. package/dist/assets/themes/base/_layouts/frontend/pages/download.html +34 -33
  18. package/dist/assets/themes/base/_layouts/frontend/pages/extension/index.html +11 -11
  19. package/dist/assets/themes/base/_layouts/frontend/pages/legal/document.html +1 -1
  20. package/dist/assets/themes/base/_layouts/frontend/pages/status.html +1 -1
  21. package/dist/assets/themes/base/_layouts/frontend/pages/team/index.html +9 -7
  22. package/dist/assets/themes/base/_layouts/frontend/pages/team/member.html +5 -3
  23. package/dist/assets/themes/base/_sections/about/letter/section.html +1 -1
  24. package/dist/assets/themes/base/_sections/about/letter/section.json5 +4 -4
  25. package/dist/assets/themes/base/_sections/marketing/bento/section.html +1 -1
  26. package/dist/assets/themes/base/_sections/marketing/bento/section.json5 +15 -15
  27. package/dist/assets/themes/base/_sections/marketing/cta/section.json5 +1 -1
  28. package/dist/assets/themes/base/_sections/marketing/hero/section.html +6 -6
  29. package/dist/assets/themes/base/_sections/marketing/hero/section.json5 +10 -10
  30. package/dist/assets/themes/base/_sections/marketing/product-demo/section.html +1 -1
  31. package/dist/assets/themes/base/_sections/marketing/product-demo/section.json5 +2 -2
  32. package/dist/assets/themes/base/_sections/marketing/stats/section.html +1 -1
  33. package/dist/assets/themes/base/_sections/marketing/stats/section.json5 +5 -5
  34. package/dist/assets/themes/base/_sections/marketing/trusted-by/section.html +1 -1
  35. package/dist/assets/themes/base/_sections/marketing/trusted-by/section.json5 +2 -2
  36. package/dist/assets/themes/neobrutalism/_layouts/frontend/pages/index.html +10 -10
  37. package/dist/assets/themes/newsflash/_layouts/frontend/pages/index.html +10 -10
  38. package/dist/assets/themes/newsflash/_sections/marketing/desks/section.html +2 -2
  39. package/dist/assets/themes/newsflash/_sections/marketing/desks/section.json5 +4 -4
  40. package/dist/build.js +69 -28
  41. package/dist/cli-run.js +20 -13
  42. package/dist/cli.js +12 -7
  43. package/dist/commands/build.js +0 -2
  44. package/dist/commands/deploy.js +116 -44
  45. package/dist/commands/finalize-release.js +5 -4
  46. package/dist/commands/launch.js +12 -7
  47. package/dist/commands/lib/deploy-precheck.js +66 -34
  48. package/dist/commands/lib/ensure-target.js +71 -11
  49. package/dist/commands/package.js +0 -1
  50. package/dist/commands/publish.js +11 -4
  51. package/dist/commands/release.js +99 -252
  52. package/dist/commands/runner.js +42 -5
  53. package/dist/commands/sign-windows.js +46 -15
  54. package/dist/commands/test.js +4 -3
  55. package/dist/commands/validate-certs.js +291 -115
  56. package/dist/config/page-template.html +4 -0
  57. package/dist/defaults/.github/workflows/build.yml +144 -64
  58. package/dist/defaults/AGENTS.md +3 -2
  59. package/dist/defaults/_.gitignore +4 -2
  60. package/dist/defaults/config/certs/README.md +25 -45
  61. package/dist/defaults/config/omega.json5 +66 -32
  62. package/dist/defaults/hooks/deploy/pre.js +10 -0
  63. package/dist/defaults/src/assets/scss/main.scss +12 -2
  64. package/dist/defaults/src/integrations/tray/index.js +1 -1
  65. package/dist/gulp/main.js +13 -25
  66. package/dist/gulp/tasks/audit.js +18 -6
  67. package/dist/gulp/tasks/build-config.js +181 -60
  68. package/dist/gulp/tasks/bundle.js +108 -36
  69. package/dist/gulp/tasks/release.js +86 -4
  70. package/dist/gulp/tasks/sass.js +11 -0
  71. package/dist/hooks/lib/notarize-tools.js +137 -0
  72. package/dist/hooks/notarize-artifacts.js +57 -0
  73. package/dist/hooks/notarize.js +59 -8
  74. package/dist/lib/auth-persistence.js +25 -0
  75. package/dist/lib/client-bridge.js +11 -8
  76. package/dist/lib/deep-link.js +15 -8
  77. package/dist/lib/protocol.js +7 -1
  78. package/dist/lib/restart-manager/install.js +6 -3
  79. package/dist/lib/sign-helpers/auto-unlock.js +65 -27
  80. package/dist/lib/sign-helpers/console-lock.js +34 -0
  81. package/dist/lib/sign-helpers/exec-with-limit.js +68 -0
  82. package/dist/lib/sign-helpers/resolve-icons.js +6 -4
  83. package/dist/lib/tray.js +7 -6
  84. package/dist/main.js +16 -0
  85. package/dist/preload.js +8 -0
  86. package/dist/renderer.js +16 -4
  87. package/dist/runner/job-started.js +104 -0
  88. package/dist/test/fixtures/consumer-app/config/omega.json5 +7 -0
  89. package/dist/test/fixtures/consumer-app/package.json +1 -1
  90. package/dist/test/fixtures/consumer-app/src/assets/js/components/main/index.js +7 -1
  91. package/dist/test/harness/main-entry.js +2 -1
  92. package/dist/test/harness/renderer-preload.js +12 -3
  93. package/dist/test/runners/boot.js +101 -11
  94. package/dist/test/runners/electron.js +1 -1
  95. package/dist/test/suites/boot/consumer-app-boots.test.js +54 -0
  96. package/dist/test/suites/build/audit.test.js +45 -6
  97. package/dist/test/suites/build/auth-persistence-resolve.test.js +119 -0
  98. package/dist/test/suites/build/auto-unlock.test.js +105 -0
  99. package/dist/test/suites/build/boot-runner-timeout.test.js +253 -0
  100. package/dist/test/suites/build/brand-scss.test.js +106 -0
  101. package/dist/test/suites/build/build-config.test.js +146 -36
  102. package/dist/test/suites/build/build-json-bake.test.js +245 -0
  103. package/dist/test/suites/build/build-verbs.test.js +48 -15
  104. package/dist/test/suites/build/build-workflow-jobs.test.js +190 -0
  105. package/dist/test/suites/build/cli.test.js +32 -10
  106. package/dist/test/suites/build/config-schema.test.js +4 -4
  107. package/dist/test/suites/build/console-lock.test.js +51 -0
  108. package/dist/test/suites/build/defaults-scaffold.test.js +71 -2
  109. package/dist/test/suites/build/deploy-direct.test.js +241 -0
  110. package/dist/test/suites/build/deploy-dispatch.test.js +287 -0
  111. package/dist/test/suites/build/deploy-hook.test.js +169 -0
  112. package/dist/test/suites/build/ensure-target.test.js +14 -2
  113. package/dist/test/suites/build/env-delivery.test.js +19 -10
  114. package/dist/test/suites/build/env-watch.test.js +18 -7
  115. package/dist/test/suites/build/esm-only-dependency.test.js +127 -0
  116. package/dist/test/suites/build/exec-with-limit.test.js +53 -0
  117. package/dist/test/suites/build/finalize-release.test.js +2 -2
  118. package/dist/test/suites/build/get-config.test.js +120 -8
  119. package/dist/test/suites/build/github-utils.test.js +12 -6
  120. package/dist/test/suites/build/license-stamp.test.js +6 -4
  121. package/dist/test/suites/build/manager.test.js +88 -68
  122. package/dist/test/suites/build/manifest-deps.test.js +116 -0
  123. package/dist/test/suites/build/merge-line-files.test.js +27 -4
  124. package/dist/test/suites/build/notarize-artifacts.test.js +135 -0
  125. package/dist/test/suites/build/notarize-tools.test.js +38 -0
  126. package/dist/test/suites/build/notarize.test.js +207 -0
  127. package/dist/test/suites/build/release-pipeline.test.js +38 -60
  128. package/dist/test/suites/build/release-skipped-upload.test.js +107 -0
  129. package/dist/test/suites/build/resolve-icons.test.js +35 -35
  130. package/dist/test/suites/build/runner-job-guard.test.js +182 -0
  131. package/dist/test/suites/build/runner.test.js +25 -2
  132. package/dist/test/suites/build/sentry.test.js +8 -3
  133. package/dist/test/suites/build/setup-scripts.test.js +3 -0
  134. package/dist/test/suites/build/sign-windows.test.js +92 -8
  135. package/dist/test/suites/build/test-stealth.test.js +17 -11
  136. package/dist/test/suites/build/url-helpers.test.js +37 -17
  137. package/dist/test/suites/build/validate-certs.test.js +428 -55
  138. package/dist/test/suites/build/validate-config.test.js +3 -3
  139. package/dist/test/suites/main/auth-flow.test.js +12 -0
  140. package/dist/test/suites/main/auth-persistence.test.js +14 -17
  141. package/dist/test/suites/main/auto-updater.test.js +2 -2
  142. package/dist/test/suites/main/boot-sequence.test.js +1 -1
  143. package/dist/test/suites/main/client-bridge.integration.test.js +5 -82
  144. package/dist/test/suites/main/client-bridge.test.js +19 -6
  145. package/dist/test/suites/main/deep-link.test.js +54 -0
  146. package/dist/test/suites/main/startup-paths-and-ua.test.js +1 -1
  147. package/dist/test/suites/main/url-helpers.test.js +81 -72
  148. package/dist/test/suites/renderer/cross-context-helpers.test.js +19 -16
  149. package/dist/utils/build-pipeline.js +10 -10
  150. package/dist/utils/github.js +12 -51
  151. package/dist/utils/load-env.js +66 -0
  152. package/dist/utils/mode-helpers.js +43 -111
  153. package/dist/utils/platform.js +37 -0
  154. package/dist/utils/runner-env.js +2 -1
  155. package/dist/utils/runner-job-guard.js +149 -0
  156. package/dist/utils/ship-keys.js +52 -0
  157. package/dist/utils/test-stealth.js +5 -3
  158. package/dist/utils/url-helpers.js +33 -17
  159. package/dist/vendor/config/bundle-id.js +53 -0
  160. package/dist/vendor/config/client-config.js +141 -0
  161. package/dist/vendor/config/company.js +334 -15
  162. package/dist/vendor/config/dev-facts.js +48 -0
  163. package/dist/vendor/config/env-delivery.js +231 -9
  164. package/dist/vendor/config/env-retired.js +137 -0
  165. package/dist/vendor/config/env-rules.js +22 -3
  166. package/dist/vendor/config/env-schema.js +234 -117
  167. package/dist/vendor/config/env.js +55 -26
  168. package/dist/vendor/config/environment.js +189 -0
  169. package/dist/vendor/config/hooks.js +13 -11
  170. package/dist/vendor/config/index.js +121 -44
  171. package/dist/vendor/config/load.js +366 -115
  172. package/dist/vendor/config/merge.js +2 -2
  173. package/dist/vendor/config/order.js +3 -3
  174. package/dist/vendor/config/platforms.js +276 -0
  175. package/dist/vendor/config/repo.js +226 -104
  176. package/dist/vendor/config/retired-keys.js +232 -27
  177. package/dist/vendor/config/schema.js +525 -99
  178. package/dist/vendor/config/site-global.js +63 -53
  179. package/dist/vendor/config/targets.js +187 -0
  180. package/dist/vendor/config/validate.js +119 -59
  181. package/dist/vendor/devkit/argv.js +118 -0
  182. package/dist/vendor/devkit/attach-log-file.js +21 -13
  183. package/dist/vendor/devkit/brand-tokens.js +278 -0
  184. package/dist/vendor/devkit/brand-version.js +264 -0
  185. package/dist/vendor/devkit/build-json.js +91 -0
  186. package/dist/vendor/devkit/bundle.js +48 -0
  187. package/dist/vendor/devkit/certificate-expiry.js +108 -0
  188. package/dist/vendor/devkit/certs.js +16 -194
  189. package/dist/vendor/devkit/ci-workflows.js +124 -8
  190. package/dist/vendor/devkit/cli-router.js +3 -3
  191. package/dist/vendor/devkit/defaults-engine.js +69 -7
  192. package/dist/vendor/devkit/deploy-follow.js +297 -0
  193. package/dist/vendor/devkit/deploy-precheck.js +23 -8
  194. package/dist/vendor/devkit/deploy-record.js +11 -27
  195. package/dist/vendor/devkit/deploy-snapshot.js +661 -0
  196. package/dist/vendor/devkit/deploy.js +445 -75
  197. package/dist/vendor/devkit/git-auth.js +73 -0
  198. package/dist/vendor/devkit/git-remote.js +95 -0
  199. package/dist/vendor/devkit/github-repo.js +290 -0
  200. package/dist/vendor/devkit/local.js +47 -0
  201. package/dist/vendor/devkit/merge-line-files.js +23 -16
  202. package/dist/vendor/devkit/omega-bin.js +18 -3
  203. package/dist/vendor/devkit/pack-local.js +391 -0
  204. package/dist/vendor/devkit/preludes/index.js +120 -0
  205. package/dist/vendor/devkit/preludes/origin-heal.js +156 -0
  206. package/dist/vendor/devkit/service-account.js +43 -0
  207. package/dist/vendor/devkit/ship-plan.js +112 -0
  208. package/dist/vendor/devkit/signing-env.js +180 -0
  209. package/dist/vendor/devkit/signing-tree.js +92 -0
  210. package/dist/vendor/devkit/target-seams.js +142 -0
  211. package/dist/vendor/devkit/target-secrets.js +235 -50
  212. package/dist/vendor/devkit/test/esm-only-fixture.js +48 -0
  213. package/dist/vendor/devkit/test/fixtures/esm-only-package/browser.js +19 -0
  214. package/dist/vendor/devkit/test/fixtures/esm-only-package/index.js +20 -0
  215. package/dist/vendor/devkit/test/fixtures/esm-only-package/package.json +13 -0
  216. package/dist/vendor/monitoring/env.js +20 -10
  217. package/dist/vendor/monitoring/main.js +1 -1
  218. package/dist/vendor/monitoring/preload.js +1 -1
  219. package/dist/vendor/monitoring/renderer.js +1 -1
  220. package/docs/analytics.md +1 -1
  221. package/docs/auto-updater.md +5 -5
  222. package/docs/boot-sequence.md +1 -1
  223. package/docs/build-system.md +15 -7
  224. package/docs/client-bridge.md +9 -7
  225. package/docs/config-schema.md +4 -4
  226. package/docs/css.md +8 -2
  227. package/docs/deep-link.md +12 -4
  228. package/docs/environment-detection.md +32 -24
  229. package/docs/hooks.md +3 -1
  230. package/docs/icons.md +7 -7
  231. package/docs/index.md +61 -23
  232. package/docs/installer-options.md +24 -21
  233. package/docs/logging.md +5 -5
  234. package/docs/releasing.md +25 -17
  235. package/docs/runner.md +40 -5
  236. package/docs/shared/brands.md +12 -6
  237. package/docs/shared/breaking-changes.md +375 -21
  238. package/docs/shared/config.md +757 -199
  239. package/docs/shared/deploys.md +194 -91
  240. package/docs/shared/icons.md +18 -0
  241. package/docs/shared/local-dev.md +24 -6
  242. package/docs/shared/logging.md +9 -6
  243. package/docs/shared/monitoring.md +27 -13
  244. package/docs/shared/publishing.md +3 -3
  245. package/docs/shared/rulings.md +2 -2
  246. package/docs/shared/testing.md +1 -1
  247. package/docs/shared/theming.md +26 -1
  248. package/docs/shared/translation.md +49 -7
  249. package/docs/shared/updates.md +1 -1
  250. package/docs/signing.md +59 -33
  251. package/docs/test-framework.md +10 -5
  252. package/docs/themes.md +15 -1
  253. package/package.json +18 -12
  254. package/bin/omega-desktop +0 -2
  255. package/dist/commands/push-secrets.js +0 -141
  256. package/dist/test/suites/build/deliver-certs.test.js +0 -95
  257. package/dist/test/suites/build/derive-signing-env.test.js +0 -122
  258. package/dist/test/suites/build/push-secrets.test.js +0 -226
  259. package/dist/test/suites/build/resolve-signing-cert.test.js +0 -342
  260. package/dist/utils/deliver-certs.js +0 -69
  261. package/dist/utils/derive-signing-env.js +0 -56
  262. package/dist/utils/resolve-signing-cert.js +0 -175
  263. package/dist/vendor/config/desktop-artifacts.js +0 -110
  264. package/dist/vendor/config/instances.js +0 -208
  265. /package/dist/defaults/config/icons/{macos → mac}/dmg.png +0 -0
  266. /package/dist/defaults/config/icons/{macos → mac}/icon.png +0 -0
  267. /package/dist/defaults/config/icons/{macos → mac}/tray.png +0 -0
@@ -9,10 +9,15 @@
9
9
  * the ONE copy; each framework binds it with its own target string and nothing
10
10
  * else.
11
11
  *
12
- * 1. DERIVE — the KEY SET is the env schema's: every entry whose `delivery`
13
- * names the target (`publishSecretKeys(target)`, @omega.js/config). Never
14
- * "whatever the composed .env holds", which published a brand's unrelated
15
- * keys to a runner.
12
+ * 1. DERIVE: the KEY SET is @omega.js/config'sONE delivery primitive, read
13
+ * off the schema AND the composed values (`publishSecretKeys(target,
14
+ * { values })`): every entry whose `delivery` names the target, plus the
15
+ * `match` family members this brand actually configured
16
+ * ([#876](https://github.com/Omega-JS-Stack/omega/issues/876)) and the
17
+ * keys the schema does not know at all, a consumer's own
18
+ * ([#835](https://github.com/Omega-JS-Stack/omega/issues/835)). Still not
19
+ * "whatever the composed .env holds": a declared key this target does not
20
+ * deliver, and every `machineLocal` one, stay home.
16
21
  * 2. COLLECT — the VALUES come from the target's COMPOSED env
17
22
  * (`composeTargetEnv`, [#678](https://github.com/Omega-JS-Stack/omega/issues/678)):
18
23
  * company ← brand ← target `.env`, the brand-side layers schema-filtered
@@ -23,7 +28,7 @@
23
28
  * schema drops them from the delivered set itself.
24
29
  * 3. GUARD — secrets belong to the repo whose Actions run the workflow, and
25
30
  * the only acceptable proof of which repo that is, is the brand's own
26
- * config (`repo.providers.github`). An inferred git remote is not proof: a
31
+ * config (`repo.org`, the SOURCE repo). An inferred git remote is not proof: a
27
32
  * target vendored into a framework/test monorepo, a cloned starter still
28
33
  * pointing at the template author, or any fork would publish this brand's
29
34
  * `.env` to a stranger's Actions.
@@ -34,20 +39,80 @@
34
39
  * a skip — it throws the boundary's instructions (install, `gh auth login`, or
35
40
  * `omega deploy --no-secrets`).
36
41
  *
37
- * Desktop binds it too ([#682](https://github.com/Omega-JS-Stack/omega/issues/682)),
38
- * over the `resolveValue` seam: its signing secrets are named by PATH in the
39
- * cascade (`CSC_LINK=config/certs/dev-id.p12`) and what CI needs is the FILE, so
40
- * it base64-encodes an existing file between COLLECT and PUBLISH. That seam is
41
- * the only shape difference between the three binds — the transport is one.
42
+ * A key the SCHEMA says this target's config requires (`required` /
43
+ * `requiredWhen`, @omega.js/config's env-rules) and the cascade resolves EMPTY
44
+ * stops the publish outright ([#891](https://github.com/Omega-JS-Stack/omega/issues/891)):
45
+ * the run that reads those secrets is the one that signs a release, and half a
46
+ * signing set on a runner is a green build nobody can install. Nothing is
47
+ * pushed on that path, so the fix and the re-run are one step, not a partial
48
+ * state to reason about. A key that is merely absent and NOT required gets one
49
+ * line naming it.
50
+ *
51
+ * There is no per-framework BIND any more
52
+ * ([#891](https://github.com/Omega-JS-Stack/omega/issues/891)): the four files
53
+ * that passed a target string and a seam were four places to forget a rule, and
54
+ * the standalone `omega push-secrets` verb they backed is gone with them. The
55
+ * seams live HERE, in one table keyed by target type, and every caller (each
56
+ * framework's deploy precheck, the manager's repo walk) makes the same call.
57
+ *
58
+ * desktop `resolveValue` base64-encodes a secret that names a FILE
59
+ * (#682: `CSC_LINK` points at a `.p12` and CI needs the bytes), and
60
+ * `deriveValues` fills the signing paths from the signing tree, by
61
+ * the ONE derivation the desktop env load runs
62
+ * (`@omega.js/devkit/signing-env`), so no operator pastes a path.
63
+ * backend `extraSecrets`: the deploy credential is a service-account FILE
64
+ * the cloud manage service mints (#872), so there is nothing in the
65
+ * cascade to compose it from and it arrives already valued.
66
+ * web, extension plain strings, no seam.
67
+ *
68
+ * `dryRun` ([#895](https://github.com/Omega-JS-Stack/omega/issues/895)) prints
69
+ * the key NAMES that would publish, the derived lines and any refusal, and
70
+ * sends nothing. The refusal still THROWS in a dry run: a plan that cannot be
71
+ * made is loud.
42
72
  */
43
- const { composeTargetEnv } = require('../config/index.js');
73
+ const { composeTargetEnv, loadConfig } = require('../config/index.js');
44
74
  const { publishSecretKeys } = require('../config/env-delivery.js');
75
+ const { checkEnvRules } = require('../config/env-rules.js');
45
76
  const { publishActionsSecrets } = require('./actions-secrets.js');
46
- const { resolveRepo } = require('./deploy.js');
77
+ const { resolveRepo, resolveDeployLane } = require('./deploy.js');
78
+ const { targetSeams } = require('./target-seams.js');
47
79
 
48
80
  /**
49
- * The publishable secrets for a target: the schema's delivery set for it,
50
- * valued from the composed env. An empty value never claims a key.
81
+ * The target's COMPOSED env, with the bind's own derivations applied to it.
82
+ *
83
+ * PRODUCTION, named rather than sniffed: these values fuel the runner's release
84
+ * build, and the composer would otherwise default to whatever environment this
85
+ * shell happens to be
86
+ * ([#586](https://github.com/Omega-JS-Stack/omega/issues/586)): the same
87
+ * determinism the "FILES only, never the shell" rule above buys.
88
+ *
89
+ * Everything that reads the cascade reads it through HERE (the required-key
90
+ * check and the collection both), so a derived value is as real as a typed one
91
+ * to each of them ([#891](https://github.com/Omega-JS-Stack/omega/issues/891)).
92
+ *
93
+ * @param {object} options
94
+ * @param {string} options.targetDir - The target root.
95
+ * @param {string} options.target - Target name.
96
+ * @param {function} [options.deriveValues] - The derive seam (see collectTargetSecrets).
97
+ * @param {object} [options.logger] - `{ log, warn, error }`; each derivation prints one line.
98
+ * @returns {{ values: object, fixes: Object<string, string> }}
99
+ */
100
+ function composeValues({ targetDir, target, deriveValues, logger }) {
101
+ const { values } = composeTargetEnv({ targetDir, target, environment: 'production' });
102
+
103
+ const { derived = [], fixes = {} } = (deriveValues ? deriveValues(values) : null) || {};
104
+ for (const { key, value } of derived) {
105
+ if (logger) logger.log(`${key} derived from ${value}`);
106
+ }
107
+
108
+ return { values, fixes };
109
+ }
110
+
111
+ /**
112
+ * The publishable secrets for a target: the delivery set the ONE primitive
113
+ * answers for it (schema names, family members, the consumer's own keys),
114
+ * valued from the same composed env the set is read off. An empty value never
115
+ * claims a key.
51
116
  *
52
117
  * @param {object} options
53
118
  * @param {string} options.targetDir - The target root (its .env is the local layer).
@@ -56,20 +121,32 @@ const { resolveRepo } = require('./deploy.js');
56
121
  * applied to each collected value before it travels (desktop base64-encodes
57
122
  * its file-path signing secrets). A falsy return DROPS the key, exactly as an
58
123
  * empty composed value does.
124
+ * @param {function} [options.deriveValues] - `(values) => { derived, fixes }`, the
125
+ * seam applied to the COMPOSED map before anything reads it: a bind may VALUE
126
+ * a key the cascade left empty (desktop derives its signing paths from the
127
+ * dispersed artifacts) and may name, per key it could NOT value, the fix line
128
+ * the refusal should carry. `derived` is `[{ key, value }]` (already written
129
+ * into the map); `fixes` is `key → sentence`.
59
130
  * @returns {Object<string, string>} key → value, ready to publish.
60
131
  */
61
132
  function collectTargetSecrets(options) {
62
- const { targetDir, target, resolveValue } = options;
63
- // PRODUCTION, named rather than sniffed: these values fuel the runner's
64
- // release build, and the composer would otherwise default to whatever
65
- // environment this shell happens to be
66
- // ([#586](https://github.com/Omega-JS-Stack/omega/issues/586)) — the same
67
- // determinism the "FILES only, never the shell" rule above buys.
68
- const { values } = composeTargetEnv({ targetDir, target, environment: 'production' });
133
+ const { targetDir, target, logger } = options;
134
+ const seams = targetSeams({ target, targetDir });
135
+ const resolveValue = 'resolveValue' in options ? options.resolveValue : seams.resolveValue;
136
+ const deriveValues = 'deriveValues' in options ? options.deriveValues : seams.deriveValues;
137
+ const { values } = composeValues({ targetDir, target, deriveValues, logger });
69
138
 
70
139
  const secrets = {};
71
- for (const key of publishSecretKeys(target)) {
72
- if (!values[key]) continue;
140
+ // The key set and the values come from ONE composition (#835, #876): a
141
+ // family member or a consumer's own key exists only in the composed map, so
142
+ // the set has to be read off the same map it is valued from.
143
+ for (const key of publishSecretKeys(target, { values })) {
144
+ if (!values[key]) {
145
+ // Never silence: a key with no value in the cascade is a key CI will not
146
+ // have. Whether that MATTERS is the schema's answer, below.
147
+ if (logger) logger.log(`No value for ${key} in the .env cascade: not published`);
148
+ continue;
149
+ }
73
150
  const value = resolveValue ? resolveValue(values[key], key) : values[key];
74
151
  if (value) secrets[key] = value;
75
152
  }
@@ -78,10 +155,55 @@ function collectTargetSecrets(options) {
78
155
  }
79
156
 
80
157
  /**
81
- * The brand's OWN repo as `owner/name`, from the target's resolved config — the
82
- * same derivation `omega deploy --direct` uses (`repo.providers.github`, name
83
- * falling back to brand.id). Null when the config declares nothing usable or
84
- * doesn't load.
158
+ * The keys this target's own config REQUIRES that the cascade cannot value.
159
+ *
160
+ * The rules are the schema's, evaluated by the ONE checker (#626) against the
161
+ * target's resolved config and its COMPOSED env, narrowed to the keys this
162
+ * publisher would send: a required key CI never reads is not the publish's
163
+ * business, and a rule nobody delivers cannot be fixed by pushing anything.
164
+ *
165
+ * The bind's derivations run FIRST (`deriveValues`), because a key the brand
166
+ * never typed but the dispersed files answer is not missing, and a key the
167
+ * derivation could not value carries that seam's own fix line
168
+ * ([#891](https://github.com/Omega-JS-Stack/omega/issues/891)).
169
+ *
170
+ * @param {object} options
171
+ * @param {string} options.targetDir - The target root.
172
+ * @param {string} options.target - Target name.
173
+ * @param {function} [options.deriveValues] - The derive seam (see collectTargetSecrets).
174
+ * @returns {Array<{ key: string, rule: string, path: string|null, fix: string|null }>}
175
+ */
176
+ function missingRequiredSecrets(options) {
177
+ const { targetDir, target } = options;
178
+ const deriveValues = 'deriveValues' in options
179
+ ? options.deriveValues
180
+ : targetSeams({ target, targetDir }).deriveValues;
181
+
182
+ let config;
183
+ try {
184
+ // PRODUCTION, the same environment `composeValues` composes for: the rules
185
+ // and the values have to be read off ONE config, or a developer machine's
186
+ // `config/omega.development.json5` decides whether a release publish
187
+ // refuses ([#895](https://github.com/Omega-JS-Stack/omega/issues/895)).
188
+ config = loadConfig(targetDir, target, { environment: 'production' }).config;
189
+ } catch (e) {
190
+ // No loadable config: there is no declaration to owe anything to.
191
+ return [];
192
+ }
193
+
194
+ const { values, fixes } = composeValues({ targetDir, target, deriveValues });
195
+ const delivered = new Set(publishSecretKeys(target, { values }));
196
+
197
+ return checkEnvRules(config, values, { target })
198
+ .filter((violation) => delivered.has(violation.key))
199
+ .map((violation) => ({ ...violation, fix: fixes[violation.key] || null }));
200
+ }
201
+
202
+ /**
203
+ * The brand's SOURCE repo as `owner/name`, from the target's resolved config:
204
+ * the one the workflows and their secrets live on, derived as
205
+ * `<brand.id>-omega` under `repo.org` (#883). Null when the config declares
206
+ * nothing usable or doesn't load.
85
207
  *
86
208
  * @param {object} options
87
209
  * @param {string} options.targetDir - The target root.
@@ -90,13 +212,14 @@ function collectTargetSecrets(options) {
90
212
  */
91
213
  function declaredBrandRepo(options) {
92
214
  try {
93
- const { loadConfig, brandRepoOwner, brandRepoName } = require('../config/index.js');
94
- const { config } = loadConfig(options.targetDir, options.target);
215
+ const { loadConfig, sourceRepo } = require('../config/index.js');
216
+ // PRODUCTION, like every other read this publish makes (#895): the repo a
217
+ // release's secrets belong to is the one the production config names.
218
+ const { config } = loadConfig(options.targetDir, options.target, { environment: 'production' });
95
219
  if (!config) return null;
96
220
 
97
- const owner = brandRepoOwner(config);
98
- const name = brandRepoName(config);
99
- return owner && name ? `${owner}/${name}` : null;
221
+ const source = sourceRepo(config);
222
+ return source ? source.slug : null;
100
223
  } catch (e) {
101
224
  return null;
102
225
  }
@@ -111,16 +234,28 @@ function declaredBrandRepo(options) {
111
234
  * @param {string} options.target - Target name ('web', 'extension', …).
112
235
  * @param {object} options.logger - `{ log, warn, error }`.
113
236
  * @param {object} [options.env] - Env map (default: process.env).
114
- * @param {function} [options.resolveValue] - Value seam (see collectTargetSecrets).
237
+ * @param {boolean} [options.dryRun] - Print the plan (key NAMES, the derived
238
+ * lines, any refusal) and send nothing (#895).
239
+ * @param {function} [options.resolveValue] - Value seam override (tests).
240
+ * @param {function} [options.deriveValues] - Derive seam override (tests).
241
+ * @param {Object<string, string>} [options.extraSecrets] - Override for the
242
+ * target's already-valued keys, merged OVER the composed set (tests).
115
243
  * @param {function} [options.execFn] - Injectable `gh` exec (tests).
116
244
  * @param {function} [options.gitExecFn] - Injectable `git` exec for remote discovery (tests).
117
- * @returns {{ skipped: string }|{ published: string[], failed: Array<object> }}
245
+ * @returns {{ skipped: string }|{ planned: string[] }|{ published: string[], failed: Array<object> }}
118
246
  */
119
247
  function publishTargetSecrets(options) {
120
248
  options = options || {};
121
- const { targetDir, target, logger } = options;
249
+ const { targetDir, target, logger, dryRun } = options;
122
250
  const env = options.env || process.env;
123
251
 
252
+ // The target type's own shape differences, in ONE table (#891). A caller
253
+ // passes a seam only to stub it.
254
+ const seams = targetSeams({ target, targetDir });
255
+ const resolveValue = 'resolveValue' in options ? options.resolveValue : seams.resolveValue;
256
+ const deriveValues = 'deriveValues' in options ? options.deriveValues : seams.deriveValues;
257
+ const extraSecrets = 'extraSecrets' in options ? options.extraSecrets : seams.extraSecrets;
258
+
124
259
  // CI runs the generated workflow as its build/publish step — the secrets
125
260
  // already exist there and the runner token can't write them.
126
261
  if (env.GITHUB_ACTIONS === 'true' || env.CI === 'true') {
@@ -128,30 +263,80 @@ function publishTargetSecrets(options) {
128
263
  return { skipped: 'ci' };
129
264
  }
130
265
 
131
- const secrets = collectTargetSecrets({ targetDir, target, resolveValue: options.resolveValue });
266
+ // The schema's set, plus the caller's own already-valued keys. An extra has
267
+ // no `.env` home to compose from (backend's deploy credential is a FILE the
268
+ // firebase service mints, #872), so it arrives valued and travels verbatim:
269
+ // `resolveValue` is the COMPOSED half's seam and would only mangle it. An
270
+ // empty extra claims no key, exactly as an empty composed value does.
271
+ // The config's OWN requirements first: a half set is never published (#891)
272
+ const missing = missingRequiredSecrets({ targetDir, target, deriveValues });
273
+ if (missing.length > 0) {
274
+ // ONE line shape per key, whatever answers it: `<KEY> (required by <path>):
275
+ // <the fix>`. A key a bind can DERIVE names the producer that delivers the
276
+ // file (`omega manage --service certificates`, then disperse) instead of
277
+ // asking for a path to paste; everything else names the .env to set.
278
+ const lines = missing.map(({ key, path, fix }) => {
279
+ const detail = fix || `set it in the ${target} target's .env or the brand's, then re-run.`;
280
+
281
+ return ` ${key}${path ? ` (required by ${path})` : ''}: ${detail}`;
282
+ });
283
+ throw new Error(
284
+ `${missing.length} secret(s) this brand's config requires are empty in the .env cascade (company/brand/target). `
285
+ + `Nothing was published.\n${lines.join('\n')}`,
286
+ );
287
+ }
288
+
289
+ const secrets = { ...collectTargetSecrets({ targetDir, target, resolveValue, deriveValues, logger }) };
290
+ for (const [key, value] of Object.entries(extraSecrets || {})) {
291
+ if (value) secrets[key] = value;
292
+ }
293
+
132
294
  const keys = Object.keys(secrets);
133
295
  if (!keys.length) {
134
296
  logger.warn('Skipping secret publication — no keys composed for this target from the .env cascade (company/brand/target)');
135
297
  return { skipped: 'no-secrets' };
136
298
  }
137
299
 
138
- let repo;
139
- try {
140
- const { owner, repo: name } = resolveRepo({ cwd: targetDir, execFn: options.gitExecFn });
141
- repo = `${owner}/${name}`;
142
- } catch (e) {
143
- logger.warn(`Skipping secret publication — no GitHub remote here (${e.message})`);
144
- return { skipped: 'no-remote' };
145
- }
146
-
147
300
  const declared = declaredBrandRepo({ targetDir, target });
148
301
  if (!declared) {
149
- logger.warn('Skipping secret publication — this brand names no GitHub repo in config (repo.providers.github). Set it, then re-run `omega deploy`.');
302
+ logger.warn('Skipping secret publication: this brand names no GitHub repo in config (repo.org). Set it, then re-run `omega deploy`.');
150
303
  return { skipped: 'no-declared-repo' };
151
304
  }
152
- if (declared.toLowerCase() !== repo.toLowerCase()) {
153
- logger.warn(`Skipping secret publication — the git remote here is ${repo}, but this brand's repo is ${declared}. Run \`omega deploy\` from the brand's own checkout.`);
154
- return { skipped: 'repo-mismatch' };
305
+
306
+ // A NESTED brand (its root is not the toplevel of the git repo it sits in)
307
+ // has a remote that answers the ENCLOSING repo by construction, and its
308
+ // deploy pushes a snapshot to the DECLARED repo regardless
309
+ // ([#872](https://github.com/Omega-JS-Stack/omega/issues/872)), so the
310
+ // mismatch guard would only ever skip a publish that is correct. Every other
311
+ // guard stays: what the guard protects against is a checkout that COULD have
312
+ // been the brand's own and is not.
313
+ const { nested } = resolveDeployLane({ dir: targetDir, execFn: options.gitExecFn });
314
+ const repo = declared;
315
+
316
+ if (!nested) {
317
+ let remote;
318
+ try {
319
+ const { owner, repo: name } = resolveRepo({ cwd: targetDir, execFn: options.gitExecFn });
320
+ remote = `${owner}/${name}`;
321
+ } catch (e) {
322
+ logger.warn(`Skipping secret publication — no GitHub remote here (${e.message})`);
323
+ return { skipped: 'no-remote' };
324
+ }
325
+
326
+ if (declared.toLowerCase() !== remote.toLowerCase()) {
327
+ logger.warn(`Skipping secret publication — the git remote here is ${remote}, but this brand's repo is ${declared}. Run \`omega deploy\` from the brand's own checkout.`);
328
+ return { skipped: 'repo-mismatch' };
329
+ }
330
+ }
331
+
332
+ // The PLAN, and nothing else (#895): what the composition answered, by NAME.
333
+ // It runs after the refusal and the two guards above on purpose, so a dry run
334
+ // is a real preview of whether the deploy would publish at all: every one of
335
+ // them is a READ, and a plan naming keys the real run would skip is a promise
336
+ // the deploy cannot keep.
337
+ if (dryRun) {
338
+ logger.log(`DRY RUN, would publish ${keys.length} secret(s) for ${target}: ${keys.join(', ')}`);
339
+ return { planned: keys };
155
340
  }
156
341
 
157
342
  logger.log(`Publishing ${keys.length} secret(s) to ${repo}: ${keys.join(', ')}`);
@@ -162,4 +347,4 @@ function publishTargetSecrets(options) {
162
347
  return result;
163
348
  }
164
349
 
165
- module.exports = { collectTargetSecrets, declaredBrandRepo, publishTargetSecrets };
350
+ module.exports = { collectTargetSecrets, declaredBrandRepo, missingRequiredSecrets, publishTargetSecrets };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The ONE ESM-only dependency every OMEGA target proves its bundle lane against
3
+ * ([#906](https://github.com/Omega-JS-Stack/omega/issues/906)).
4
+ *
5
+ * `fixtures/esm-only-package/` is a real package (`"type": "module"`, no
6
+ * CommonJS build) with one entry per environment, chosen by export condition:
7
+ * the NODE entry opens a require of its own with
8
+ * `createRequire(import.meta.url)`, the shape that threw in a packaged desktop
9
+ * app, and the BROWSER entry touches no Node built-in. One package, so a target
10
+ * asks for it by its own name and gets the half its bundle is built for.
11
+ *
12
+ * It is INSTALLED rather than pointed at: `installEsmOnlyFixture(projectRoot)`
13
+ * copies it into the project's `node_modules`, which is where a bundler's normal
14
+ * resolution looks and the only shape that is the same in this monorepo and in a
15
+ * consumer running a framework's self-test off its packaged dist (desktop and
16
+ * extension carry the directory there through `omega.vendorAssets`).
17
+ */
18
+ const path = require('path');
19
+ const jetpack = require('fs-jetpack');
20
+
21
+ // The name a consumer entry imports, and what the fixture's two entries say
22
+ // about themselves. Exported because a test asserting the bundled copy ran must
23
+ // compare against the ONE spelling, not a retyped twin.
24
+ const FIXTURE_NAME = 'esm-only-fixture';
25
+ const FIXTURE_DIR = path.join(__dirname, 'fixtures', 'esm-only-package');
26
+ const NODE_MARKER = 'ESM_ONLY_FIXTURE_NODE';
27
+ const BROWSER_MARKER = 'ESM_ONLY_FIXTURE_BROWSER';
28
+
29
+ /**
30
+ * Install the fixture into a project, the way npm installs a dependency.
31
+ * @param {string} projectRoot - the project that will import it
32
+ * @returns {string} where it landed (`<projectRoot>/node_modules/esm-only-fixture`)
33
+ * @throws {Error} when the fixture is not where this module ships it: in a
34
+ * packaged framework that means the vendor step did not carry it, and a test
35
+ * that silently skipped the dependency would prove nothing
36
+ */
37
+ function installEsmOnlyFixture(projectRoot) {
38
+ if (!jetpack.exists(FIXTURE_DIR)) {
39
+ throw new Error(`[devkit test] the ESM-only fixture is missing from ${FIXTURE_DIR}. A framework running this off its packaged dist needs the omega.vendorAssets entry that carries it`);
40
+ }
41
+
42
+ const destination = path.join(projectRoot, 'node_modules', FIXTURE_NAME);
43
+ jetpack.copy(FIXTURE_DIR, destination, { overwrite: true });
44
+
45
+ return destination;
46
+ }
47
+
48
+ module.exports = { installEsmOnlyFixture, FIXTURE_NAME, FIXTURE_DIR, NODE_MARKER, BROWSER_MARKER };
@@ -0,0 +1,19 @@
1
+ // The BROWSER half of the fixture (the `browser` export condition), for the
2
+ // targets whose bundles are browser output: @omega.js/web's esm chunks and
3
+ // service worker, @omega.js/extension's iife bundles. It reaches for no Node
4
+ // built-in and no `import.meta` (esbuild rewrites `import.meta` in iife output
5
+ // too, and warns), because the thing a browser bundle has to prove is the other
6
+ // half of [#906](https://github.com/Omega-JS-Stack/omega/issues/906): an
7
+ // ESM-only package resolves, bundles and RUNS, with a real value on the other
8
+ // side.
9
+ export const flavor = 'browser';
10
+ export const marker = 'ESM_ONLY_FIXTURE_BROWSER';
11
+
12
+ /**
13
+ * A value the bundled copy has to compute at RUNTIME, so a test asserting it
14
+ * proves the module evaluated rather than proving its text survived.
15
+ * @returns {string} the fixture's own name for itself
16
+ */
17
+ export function describeFixture() {
18
+ return `${marker}:${flavor}`;
19
+ }
@@ -0,0 +1,20 @@
1
+ // The NODE half of the fixture (the `default` export condition): what an
2
+ // ESM-only dependency does the moment it needs a require of its own, which is
3
+ // the shape that broke a packaged desktop app
4
+ // ([#906](https://github.com/Omega-JS-Stack/omega/issues/906)). yargs-parser is
5
+ // the live case: `createRequire(import.meta.url)` with an `import.meta.url` of
6
+ // `undefined` throws ERR_INVALID_ARG_VALUE before a single line of app code
7
+ // runs.
8
+ //
9
+ // `viaRequire` requires a BUILT-IN rather than a file beside this one: a
10
+ // relative require resolves against the caller's own directory, which for a
11
+ // bundled copy is the bundle's, so a relative path would be testing where the
12
+ // output landed instead of whether the require works at all.
13
+ import { createRequire } from 'node:module';
14
+
15
+ const require = createRequire(import.meta.url);
16
+
17
+ export const flavor = 'node';
18
+ export const fileUrl = import.meta.url;
19
+ export const viaRequire = typeof require('node:path').join === 'function';
20
+ export const marker = 'ESM_ONLY_FIXTURE_NODE';
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "esm-only-fixture",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "description": "An ESM-only dependency, as every OMEGA target's bundle lane has to be able to carry one (#906)",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": {
9
+ "browser": "./browser.js",
10
+ "default": "./index.js"
11
+ }
12
+ }
13
+ }
@@ -8,20 +8,30 @@
8
8
  * OMEGA_SENTRY_ENABLED=false kill switch — nothing reports, ever
9
9
  * OMEGA_TEST_RUNNER a test run never pollutes a live project
10
10
  * OMEGA_SENTRY_FORCE=true report from a non-production run (local proving)
11
- * OMEGA_BUILD_MODE=true the DEFAULT production signal, for hosts that
12
- * have no runtime one
11
+ * the ONE environment the DEFAULT production signal, for hosts that
12
+ * pass no runtime one
13
13
  *
14
- * The production signal is the host's to supply. @omega.js/desktop has no
15
- * runtime answer — "should we ship telemetry" is a property of its BUILD, so it
16
- * falls through to OMEGA_BUILD_MODE. @omega.js/backend does have one
17
- * (`Manager.isProduction()`, env-derived and stable for the life of the
18
- * process) and passes it in.
14
+ * The production signal is the host's to supply, and the DEFAULT is the one
15
+ * environment every OMEGA target answers from
16
+ * ([#817](https://github.com/Omega-JS-Stack/omega/issues/817)):
17
+ * `@omega.js/config/environment`'s `isProduction()`, read off the host the gates
18
+ * were asked for (`process.env.OMEGA_ENVIRONMENT` on Node, the host's baked
19
+ * `config.environment` in a browser-ish context such as a renderer bundle).
20
+ * OMEGA_BUILD_MODE used to be that default, which made it a FIFTH production
21
+ * signal with its own opinion: a build-mode run of a development artifact
22
+ * reported as production, and a packaged production app whose lane did not
23
+ * carry the flag reported as development. @omega.js/backend still passes its
24
+ * own answer in (`Manager.isProduction()`), and a boolean a host supplies
25
+ * always wins.
19
26
  */
20
27
 
28
+ const { isProduction } = require('../config/environment.js');
29
+
21
30
  /**
22
31
  * Read the environment gates for core.resolveConfig().
23
- * @param {object} [host] - the host's own signals
24
- * @param {boolean} [host.isProduction] - overrides the OMEGA_BUILD_MODE default
32
+ * @param {object} [host] - the host's own signals, and the context the one
33
+ * environment is read off (a Manager carrying the baked `config`)
34
+ * @param {boolean} [host.isProduction] - overrides the one environment's answer
25
35
  * @param {boolean} [host.allowInDev] - the host's own dev opt-in (e.g. @omega.js/backend's `reportErrorsInDev`)
26
36
  * @returns {{ killed: boolean, killedReason: string|null, isProduction: boolean, allowInDev: boolean }}
27
37
  */
@@ -41,7 +51,7 @@ function readGates(host) {
41
51
  killedReason,
42
52
  isProduction: typeof host.isProduction === 'boolean'
43
53
  ? host.isProduction
44
- : env.OMEGA_BUILD_MODE === 'true',
54
+ : isProduction.call(host),
45
55
  allowInDev: !!host.allowInDev || env.OMEGA_SENTRY_FORCE === 'true',
46
56
  };
47
57
  }
@@ -30,7 +30,7 @@ const main = {
30
30
  initialize(host) {
31
31
  main._initialized = true;
32
32
 
33
- const { shouldEnable, options, reason } = resolveConfig(host && host.config && host.config.monitoring, readGates());
33
+ const { shouldEnable, options, reason } = resolveConfig(host && host.config && host.config.monitoring, readGates(host));
34
34
  main._options = options;
35
35
 
36
36
  if (!shouldEnable) {
@@ -21,7 +21,7 @@ const preload = {
21
21
  * @param {object} host - the desktop Manager: { config }
22
22
  */
23
23
  initialize(host) {
24
- const { shouldEnable, reason } = resolveConfig(host && host.config && host.config.monitoring, readGates());
24
+ const { shouldEnable, reason } = resolveConfig(host && host.config && host.config.monitoring, readGates(host));
25
25
  preload._enabled = shouldEnable;
26
26
  if (!shouldEnable) logger.log(`disabled — ${reason}`);
27
27
  },
@@ -26,7 +26,7 @@ const renderer = {
26
26
  initialize(host) {
27
27
  renderer._initialized = true;
28
28
 
29
- const { shouldEnable, options, reason } = resolveConfig(host && host.config && host.config.monitoring, readGates());
29
+ const { shouldEnable, options, reason } = resolveConfig(host && host.config && host.config.monitoring, readGates(host));
30
30
  renderer._options = options;
31
31
 
32
32
  if (!shouldEnable) {
package/docs/analytics.md CHANGED
@@ -46,7 +46,7 @@ Mint the secret in GA4 Admin → Data Streams → your stream → **Measurement
46
46
 
47
47
  ### Production builds
48
48
 
49
- The `bundle` task's esbuild `define` bakes `process.env.GOOGLE_ANALYTICS_SECRET` into the bundled main process at build time, so packaged apps don't need `.env` at runtime. The build runs with the secret set (CI does this via the GitHub Actions secret pushed by `mgr push-secrets`).
49
+ The `bundle` task's esbuild `define` bakes `process.env.GOOGLE_ANALYTICS_SECRET` into the bundled main process at build time, so packaged apps don't need `.env` at runtime. The build runs with the secret set (CI does this via the GitHub Actions secret `omega deploy`'s precheck pushes).
50
50
 
51
51
  ## API
52
52
 
@@ -110,13 +110,13 @@ Long enough that an actively-used app won't surprise-quit mid-task. Short enough
110
110
 
111
111
  ### Test mode behavior
112
112
 
113
- When `manager.isTesting() === true` (canonical signal: `OMEGA_TEST_MODE=true`), the auto-updater swaps in test-friendly defaults so a real download → idle wait → install can complete in seconds instead of minutes:
113
+ When `manager.isTesting() === true` (the one input: `OMEGA_ENVIRONMENT=testing`), the auto-updater swaps in test-friendly defaults so a real download → idle wait → install can complete in seconds instead of minutes:
114
114
 
115
115
  - **Idle threshold**: `IDLE_INSTALL_THRESHOLD_MS_TESTING = 3000ms` (3 sec) instead of 15 min.
116
116
  - **Both timers**: `IDLE_TICK_MS_TESTING = 500ms` replaces `feedCheckIntervalMs` and `idleEvalIntervalMs`.
117
117
  - **`_promptToInstall` short-circuits** before invoking `dialog.showMessageBox`. The native dialog is modal + blocking + would pop a window the test process can't dismiss programmatically. In test mode the prompt logs `[testing] _promptToInstall(...) — skipped native dialog.` and returns. Tests that want to assert prompt behavior override `_promptToInstall` per-test (see `auto-updater.test.js`).
118
118
 
119
- This lets the framework's own integration tests drive the full sequence (`OMEGA_DEV_UPDATE=available` → state machine → 500ms tick → 3s idle threshold elapses → stubbed `installNow` fires) in ~5s. Consumers running their own tests should set `OMEGA_TEST_MODE=true` to inherit the same defaults.
119
+ This lets the framework's own integration tests drive the full sequence (`OMEGA_DEV_UPDATE=available` → state machine → 500ms tick → 3s idle threshold elapses → stubbed `installNow` fires) in ~5s. Consumers running their own tests should name `OMEGA_ENVIRONMENT=testing` to inherit the same defaults.
120
120
 
121
121
  ## Menu integration
122
122
 
@@ -224,8 +224,8 @@ The submenu is dev-only (same gate as `view/developer/toggle-devtools`) and is a
224
224
 
225
225
  ```
226
226
  provider: github
227
- owner: <releases.owner ?? the brand repo's owner>
228
- repo: <releases.repo ?? `${brand.id}-releases`>
227
+ owner: <repo.org>
228
+ repo: `${brand.id}-releases`
229
229
  releaseType: release
230
230
  ```
231
231
 
@@ -233,7 +233,7 @@ So your private app repo and your public release repo are completely decoupled
233
233
 
234
234
  ## Failure modes
235
235
 
236
- - **Update repo isn't public** → `electron-updater` gets 404 or 401 against a private repo. Fix: ensure the releases repo (`<owner>/<brand.id>-releases` by default) is public.
236
+ - **Update repo isn't public** → `electron-updater` gets 404 or 401 against a private repo. Fix: ensure the releases repo (`<repo.org>/<brand.id>-releases`) is public; `omega deploy`'s precheck creates it that way.
237
237
  - **Token rotates / expires for `releases` repo** — `electron-updater` doesn't authenticate downloads (anonymous public reads). So tokens don't apply on the consumer side.
238
238
  - **`app-update.yml` missing in the packaged app** → look at the build log for `electron-builder`'s "creating updates yml" line. If it's skipped, your `publish` block didn't materialize correctly into `dist/electron-builder.yml`.
239
239
  - **`error` status with code `ERR_UPDATER_CHANNEL_FILE_NOT_FOUND`** → there's no `latest-mac.yml` (or `latest.yml` / `latest-linux.yml`) at the configured channel. Run a release first.
@@ -5,7 +5,7 @@
5
5
  ## Order
6
6
 
7
7
  1. **`startup.applyEarly()`** — first thing, before `whenReady`. Calls `app.dock.hide()` for `mode: 'hidden'` (zero-bounce production via `LSUIElement` baked at build time).
8
- 1b. **userData path isolation** — appends an environment suffix to `app.getPath('userData')` so each environment's session data, logs, and `electron-store` files stay separate on the same machine: production untouched, development gets ` (Development)`, testing (`OMEGA_TEST_MODE=true`) gets ` (Testing)`. The testing dir is **wiped at boot** so every test run starts from a clean slate (post-run state stays on disk for inspection until the next run; set `OMEGA_TEST_KEEP_USERDATA=1` to skip the wipe). **Must run before `storage.initialize()`** (which constructs `electron-store` against the path).
8
+ 1b. **userData path isolation** — appends an environment suffix to `app.getPath('userData')` so each environment's session data, logs, and `electron-store` files stay separate on the same machine: production untouched, development gets ` (Development)`, testing (`OMEGA_ENVIRONMENT=testing`) gets ` (Testing)`. The testing dir is **wiped at boot** so every test run starts from a clean slate (post-run state stays on disk for inspection until the next run; set `OMEGA_TEST_KEEP_USERDATA=1` to skip the wipe). **Must run before `storage.initialize()`** (which constructs `electron-store` against the path).
9
9
  1c. **Global user-agent fallback** — sets `app.userAgentFallback` to a branded template via `node-powertools.template`. Default per-platform templates: `Mozilla/5.0 (... <platform-specific> ...) AppleWebKit/537.36 (KHTML, like Gecko) {brand.name}/{app.version} Chrome/{chrome} Safari/537.36`. Merge tags resolve from `{ brand: { name, id }, app: { version }, chrome, electron, node, platform, arch }`. Every BrowserWindow load + electron-updater fetch + node-fetch via the renderer carries the branded UA. Consumers can override post-init by re-setting `app.userAgentFallback` from their main.js.
10
10
  2. **`app.on('before-quit')`** wired — sets `manager._isQuitting = true` so any quit path (Cmd+Q, role:'quit' menu, programmatic `app.quit()`, OS shutdown) bypasses the window-manager's hide-on-close trap.
11
11
  3. **`ipc`** — typed channel bus online before any feature can register handlers.