@omega.js/desktop 0.50.0 → 0.52.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 (268) hide show
  1. package/README.md +2 -2
  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 +176 -42
  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 +146 -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 +635 -5
  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 +125 -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 +249 -103
  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 +425 -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 +505 -75
  197. package/dist/vendor/devkit/git-auth.js +73 -0
  198. package/dist/vendor/devkit/git-remote.js +172 -0
  199. package/dist/vendor/devkit/github-repo.js +290 -0
  200. package/dist/vendor/devkit/local.js +98 -3
  201. package/dist/vendor/devkit/lockfile.js +127 -0
  202. package/dist/vendor/devkit/merge-line-files.js +23 -16
  203. package/dist/vendor/devkit/omega-bin.js +18 -3
  204. package/dist/vendor/devkit/pack-local.js +388 -0
  205. package/dist/vendor/devkit/preludes/index.js +120 -0
  206. package/dist/vendor/devkit/preludes/origin-heal.js +142 -0
  207. package/dist/vendor/devkit/service-account.js +43 -0
  208. package/dist/vendor/devkit/ship-plan.js +112 -0
  209. package/dist/vendor/devkit/signing-env.js +180 -0
  210. package/dist/vendor/devkit/signing-tree.js +92 -0
  211. package/dist/vendor/devkit/target-seams.js +142 -0
  212. package/dist/vendor/devkit/target-secrets.js +231 -57
  213. package/dist/vendor/devkit/test/esm-only-fixture.js +48 -0
  214. package/dist/vendor/devkit/test/fixtures/esm-only-package/browser.js +19 -0
  215. package/dist/vendor/devkit/test/fixtures/esm-only-package/index.js +20 -0
  216. package/dist/vendor/devkit/test/fixtures/esm-only-package/package.json +13 -0
  217. package/dist/vendor/monitoring/env.js +20 -10
  218. package/dist/vendor/monitoring/main.js +1 -1
  219. package/dist/vendor/monitoring/preload.js +1 -1
  220. package/dist/vendor/monitoring/renderer.js +1 -1
  221. package/docs/analytics.md +1 -1
  222. package/docs/auto-updater.md +5 -5
  223. package/docs/boot-sequence.md +1 -1
  224. package/docs/build-system.md +15 -7
  225. package/docs/client-bridge.md +9 -7
  226. package/docs/config-schema.md +4 -4
  227. package/docs/css.md +8 -2
  228. package/docs/deep-link.md +12 -4
  229. package/docs/environment-detection.md +32 -24
  230. package/docs/hooks.md +3 -1
  231. package/docs/icons.md +7 -7
  232. package/docs/index.md +61 -23
  233. package/docs/installer-options.md +24 -21
  234. package/docs/logging.md +5 -5
  235. package/docs/releasing.md +29 -19
  236. package/docs/runner.md +51 -14
  237. package/docs/shared/brands.md +12 -6
  238. package/docs/shared/breaking-changes.md +375 -21
  239. package/docs/shared/config.md +764 -199
  240. package/docs/shared/deploys.md +217 -91
  241. package/docs/shared/icons.md +18 -0
  242. package/docs/shared/local-dev.md +28 -8
  243. package/docs/shared/logging.md +9 -6
  244. package/docs/shared/monitoring.md +27 -13
  245. package/docs/shared/publishing.md +3 -3
  246. package/docs/shared/rulings.md +2 -2
  247. package/docs/shared/testing.md +1 -1
  248. package/docs/shared/theming.md +26 -1
  249. package/docs/shared/translation.md +49 -7
  250. package/docs/shared/updates.md +1 -1
  251. package/docs/signing.md +60 -34
  252. package/docs/test-framework.md +10 -5
  253. package/docs/themes.md +15 -1
  254. package/package.json +18 -12
  255. package/bin/omega-desktop +0 -2
  256. package/dist/commands/push-secrets.js +0 -141
  257. package/dist/test/suites/build/deliver-certs.test.js +0 -95
  258. package/dist/test/suites/build/derive-signing-env.test.js +0 -122
  259. package/dist/test/suites/build/push-secrets.test.js +0 -226
  260. package/dist/test/suites/build/resolve-signing-cert.test.js +0 -342
  261. package/dist/utils/deliver-certs.js +0 -69
  262. package/dist/utils/derive-signing-env.js +0 -56
  263. package/dist/utils/resolve-signing-cert.js +0 -175
  264. package/dist/vendor/config/desktop-artifacts.js +0 -110
  265. package/dist/vendor/config/instances.js +0 -208
  266. /package/dist/defaults/config/icons/{macos → mac}/dmg.png +0 -0
  267. /package/dist/defaults/config/icons/{macos → mac}/icon.png +0 -0
  268. /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,10 +28,13 @@
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
- * `.env` to a stranger's Actions.
34
+ * `.env` to a stranger's Actions. A checkout that could be the brand's own
35
+ * and whose origin names another repo REFUSES on `repoDrift`'s one line
36
+ * ([#934](https://github.com/Omega-JS-Stack/omega/issues/934)), the
37
+ * same refusal the deploy lane and the manage walk make.
30
38
  * 4. PUBLISH — each collected key becomes a repo Actions secret via devkit's
31
39
  * `gh` boundary (values on stdin, never logged).
32
40
  *
@@ -34,20 +42,81 @@
34
42
  * a skip — it throws the boundary's instructions (install, `gh auth login`, or
35
43
  * `omega deploy --no-secrets`).
36
44
  *
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.
45
+ * A key the SCHEMA says this target's config requires (`required` /
46
+ * `requiredWhen`, @omega.js/config's env-rules) and the cascade resolves EMPTY
47
+ * stops the publish outright ([#891](https://github.com/Omega-JS-Stack/omega/issues/891)):
48
+ * the run that reads those secrets is the one that signs a release, and half a
49
+ * signing set on a runner is a green build nobody can install. Nothing is
50
+ * pushed on that path, so the fix and the re-run are one step, not a partial
51
+ * state to reason about. A key that is merely absent and NOT required gets one
52
+ * line naming it.
53
+ *
54
+ * There is no per-framework BIND any more
55
+ * ([#891](https://github.com/Omega-JS-Stack/omega/issues/891)): the four files
56
+ * that passed a target string and a seam were four places to forget a rule, and
57
+ * the standalone `omega push-secrets` verb they backed is gone with them. The
58
+ * seams live HERE, in one table keyed by target type, and every caller (each
59
+ * framework's deploy precheck, the manager's repo walk) makes the same call.
60
+ *
61
+ * desktop `resolveValue` base64-encodes a secret that names a FILE
62
+ * (#682: `CSC_LINK` points at a `.p12` and CI needs the bytes), and
63
+ * `deriveValues` fills the signing paths from the signing tree, by
64
+ * the ONE derivation the desktop env load runs
65
+ * (`@omega.js/devkit/signing-env`), so no operator pastes a path.
66
+ * backend `extraSecrets`: the deploy credential is a service-account FILE
67
+ * the cloud manage service mints (#872), so there is nothing in the
68
+ * cascade to compose it from and it arrives already valued.
69
+ * web, extension plain strings, no seam.
70
+ *
71
+ * `dryRun` ([#895](https://github.com/Omega-JS-Stack/omega/issues/895)) prints
72
+ * the key NAMES that would publish, the derived lines and any refusal, and
73
+ * sends nothing. The refusal still THROWS in a dry run: a plan that cannot be
74
+ * made is loud.
42
75
  */
43
- const { composeTargetEnv } = require('../config/index.js');
76
+ const { composeTargetEnv, loadConfig, sourceRepo } = require('../config/index.js');
44
77
  const { publishSecretKeys } = require('../config/env-delivery.js');
78
+ const { checkEnvRules } = require('../config/env-rules.js');
45
79
  const { publishActionsSecrets } = require('./actions-secrets.js');
46
- const { resolveRepo } = require('./deploy.js');
80
+ const { assertOriginMatches } = require('./git-remote.js');
81
+ const { findBrandRoot } = require('./local.js');
82
+ const { targetSeams } = require('./target-seams.js');
47
83
 
48
84
  /**
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.
85
+ * The target's COMPOSED env, with the bind's own derivations applied to it.
86
+ *
87
+ * PRODUCTION, named rather than sniffed: these values fuel the runner's release
88
+ * build, and the composer would otherwise default to whatever environment this
89
+ * shell happens to be
90
+ * ([#586](https://github.com/Omega-JS-Stack/omega/issues/586)): the same
91
+ * determinism the "FILES only, never the shell" rule above buys.
92
+ *
93
+ * Everything that reads the cascade reads it through HERE (the required-key
94
+ * check and the collection both), so a derived value is as real as a typed one
95
+ * to each of them ([#891](https://github.com/Omega-JS-Stack/omega/issues/891)).
96
+ *
97
+ * @param {object} options
98
+ * @param {string} options.targetDir - The target root.
99
+ * @param {string} options.target - Target name.
100
+ * @param {function} [options.deriveValues] - The derive seam (see collectTargetSecrets).
101
+ * @param {object} [options.logger] - `{ log, warn, error }`; each derivation prints one line.
102
+ * @returns {{ values: object, fixes: Object<string, string> }}
103
+ */
104
+ function composeValues({ targetDir, target, deriveValues, logger }) {
105
+ const { values } = composeTargetEnv({ targetDir, target, environment: 'production' });
106
+
107
+ const { derived = [], fixes = {} } = (deriveValues ? deriveValues(values) : null) || {};
108
+ for (const { key, value } of derived) {
109
+ if (logger) logger.log(`${key} derived from ${value}`);
110
+ }
111
+
112
+ return { values, fixes };
113
+ }
114
+
115
+ /**
116
+ * The publishable secrets for a target: the delivery set the ONE primitive
117
+ * answers for it (schema names, family members, the consumer's own keys),
118
+ * valued from the same composed env the set is read off. An empty value never
119
+ * claims a key.
51
120
  *
52
121
  * @param {object} options
53
122
  * @param {string} options.targetDir - The target root (its .env is the local layer).
@@ -56,20 +125,32 @@ const { resolveRepo } = require('./deploy.js');
56
125
  * applied to each collected value before it travels (desktop base64-encodes
57
126
  * its file-path signing secrets). A falsy return DROPS the key, exactly as an
58
127
  * empty composed value does.
128
+ * @param {function} [options.deriveValues] - `(values) => { derived, fixes }`, the
129
+ * seam applied to the COMPOSED map before anything reads it: a bind may VALUE
130
+ * a key the cascade left empty (desktop derives its signing paths from the
131
+ * dispersed artifacts) and may name, per key it could NOT value, the fix line
132
+ * the refusal should carry. `derived` is `[{ key, value }]` (already written
133
+ * into the map); `fixes` is `key → sentence`.
59
134
  * @returns {Object<string, string>} key → value, ready to publish.
60
135
  */
61
136
  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' });
137
+ const { targetDir, target, logger } = options;
138
+ const seams = targetSeams({ target, targetDir });
139
+ const resolveValue = 'resolveValue' in options ? options.resolveValue : seams.resolveValue;
140
+ const deriveValues = 'deriveValues' in options ? options.deriveValues : seams.deriveValues;
141
+ const { values } = composeValues({ targetDir, target, deriveValues, logger });
69
142
 
70
143
  const secrets = {};
71
- for (const key of publishSecretKeys(target)) {
72
- if (!values[key]) continue;
144
+ // The key set and the values come from ONE composition (#835, #876): a
145
+ // family member or a consumer's own key exists only in the composed map, so
146
+ // the set has to be read off the same map it is valued from.
147
+ for (const key of publishSecretKeys(target, { values })) {
148
+ if (!values[key]) {
149
+ // Never silence: a key with no value in the cascade is a key CI will not
150
+ // have. Whether that MATTERS is the schema's answer, below.
151
+ if (logger) logger.log(`No value for ${key} in the .env cascade: not published`);
152
+ continue;
153
+ }
73
154
  const value = resolveValue ? resolveValue(values[key], key) : values[key];
74
155
  if (value) secrets[key] = value;
75
156
  }
@@ -78,25 +159,63 @@ function collectTargetSecrets(options) {
78
159
  }
79
160
 
80
161
  /**
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.
162
+ * The keys this target's own config REQUIRES that the cascade cannot value.
163
+ *
164
+ * The rules are the schema's, evaluated by the ONE checker (#626) against the
165
+ * target's resolved config and its COMPOSED env, narrowed to the keys this
166
+ * publisher would send: a required key CI never reads is not the publish's
167
+ * business, and a rule nobody delivers cannot be fixed by pushing anything.
168
+ *
169
+ * The bind's derivations run FIRST (`deriveValues`), because a key the brand
170
+ * never typed but the dispersed files answer is not missing, and a key the
171
+ * derivation could not value carries that seam's own fix line
172
+ * ([#891](https://github.com/Omega-JS-Stack/omega/issues/891)).
85
173
  *
86
174
  * @param {object} options
87
175
  * @param {string} options.targetDir - The target root.
88
- * @param {string} options.target - Target name ('web', 'extension', …).
89
- * @returns {string|null}
176
+ * @param {string} options.target - Target name.
177
+ * @param {function} [options.deriveValues] - The derive seam (see collectTargetSecrets).
178
+ * @returns {Array<{ key: string, rule: string, path: string|null, fix: string|null }>}
90
179
  */
91
- function declaredBrandRepo(options) {
180
+ function missingRequiredSecrets(options) {
181
+ const { targetDir, target } = options;
182
+ const deriveValues = 'deriveValues' in options
183
+ ? options.deriveValues
184
+ : targetSeams({ target, targetDir }).deriveValues;
185
+
186
+ let config;
92
187
  try {
93
- const { loadConfig, brandRepoOwner, brandRepoName } = require('../config/index.js');
94
- const { config } = loadConfig(options.targetDir, options.target);
95
- if (!config) return null;
188
+ // PRODUCTION, the same environment `composeValues` composes for: the rules
189
+ // and the values have to be read off ONE config, or a developer machine's
190
+ // `config/omega.development.json5` decides whether a release publish
191
+ // refuses ([#895](https://github.com/Omega-JS-Stack/omega/issues/895)).
192
+ config = loadConfig(targetDir, target, { environment: 'production' }).config;
193
+ } catch (e) {
194
+ // No loadable config: there is no declaration to owe anything to.
195
+ return [];
196
+ }
197
+
198
+ const { values, fixes } = composeValues({ targetDir, target, deriveValues });
199
+ const delivered = new Set(publishSecretKeys(target, { values }));
96
200
 
97
- const owner = brandRepoOwner(config);
98
- const name = brandRepoName(config);
99
- return owner && name ? `${owner}/${name}` : null;
201
+ return checkEnvRules(config, values, { target })
202
+ .filter((violation) => delivered.has(violation.key))
203
+ .map((violation) => ({ ...violation, fix: fixes[violation.key] || null }));
204
+ }
205
+
206
+ /**
207
+ * The target's resolved PRODUCTION config, like every other read this publish
208
+ * makes (#895): the repo a release's secrets belong to is the one the
209
+ * production config names. Null when it doesn't load.
210
+ *
211
+ * @param {object} options
212
+ * @param {string} options.targetDir - The target root.
213
+ * @param {string} options.target - Target name ('web', 'extension', …).
214
+ * @returns {object|null}
215
+ */
216
+ function productionConfig(options) {
217
+ try {
218
+ return loadConfig(options.targetDir, options.target, { environment: 'production' }).config || null;
100
219
  } catch (e) {
101
220
  return null;
102
221
  }
@@ -111,16 +230,27 @@ function declaredBrandRepo(options) {
111
230
  * @param {string} options.target - Target name ('web', 'extension', …).
112
231
  * @param {object} options.logger - `{ log, warn, error }`.
113
232
  * @param {object} [options.env] - Env map (default: process.env).
114
- * @param {function} [options.resolveValue] - Value seam (see collectTargetSecrets).
233
+ * @param {boolean} [options.dryRun] - Print the plan (key NAMES, the derived
234
+ * lines, any refusal) and send nothing (#895).
235
+ * @param {function} [options.resolveValue] - Value seam override (tests).
236
+ * @param {function} [options.deriveValues] - Derive seam override (tests).
237
+ * @param {Object<string, string>} [options.extraSecrets] - Override for the
238
+ * target's already-valued keys, merged OVER the composed set (tests).
115
239
  * @param {function} [options.execFn] - Injectable `gh` exec (tests).
116
- * @param {function} [options.gitExecFn] - Injectable `git` exec for remote discovery (tests).
117
- * @returns {{ skipped: string }|{ published: string[], failed: Array<object> }}
240
+ * @returns {{ skipped: string }|{ planned: string[] }|{ published: string[], failed: Array<object> }}
118
241
  */
119
242
  function publishTargetSecrets(options) {
120
243
  options = options || {};
121
- const { targetDir, target, logger } = options;
244
+ const { targetDir, target, logger, dryRun } = options;
122
245
  const env = options.env || process.env;
123
246
 
247
+ // The target type's own shape differences, in ONE table (#891). A caller
248
+ // passes a seam only to stub it.
249
+ const seams = targetSeams({ target, targetDir });
250
+ const resolveValue = 'resolveValue' in options ? options.resolveValue : seams.resolveValue;
251
+ const deriveValues = 'deriveValues' in options ? options.deriveValues : seams.deriveValues;
252
+ const extraSecrets = 'extraSecrets' in options ? options.extraSecrets : seams.extraSecrets;
253
+
124
254
  // CI runs the generated workflow as its build/publish step — the secrets
125
255
  // already exist there and the runner token can't write them.
126
256
  if (env.GITHUB_ACTIONS === 'true' || env.CI === 'true') {
@@ -128,30 +258,74 @@ function publishTargetSecrets(options) {
128
258
  return { skipped: 'ci' };
129
259
  }
130
260
 
131
- const secrets = collectTargetSecrets({ targetDir, target, resolveValue: options.resolveValue });
261
+ // The schema's set, plus the caller's own already-valued keys. An extra has
262
+ // no `.env` home to compose from (backend's deploy credential is a FILE the
263
+ // firebase service mints, #872), so it arrives valued and travels verbatim:
264
+ // `resolveValue` is the COMPOSED half's seam and would only mangle it. An
265
+ // empty extra claims no key, exactly as an empty composed value does.
266
+ // The config's OWN requirements first: a half set is never published (#891)
267
+ const missing = missingRequiredSecrets({ targetDir, target, deriveValues });
268
+ if (missing.length > 0) {
269
+ // ONE line shape per key, whatever answers it: `<KEY> (required by <path>):
270
+ // <the fix>`. A key a bind can DERIVE names the producer that delivers the
271
+ // file (`omega manage --service certificates`, then disperse) instead of
272
+ // asking for a path to paste; everything else names the .env to set.
273
+ const lines = missing.map(({ key, path, fix }) => {
274
+ const detail = fix || `set it in the ${target} target's .env or the brand's, then re-run.`;
275
+
276
+ return ` ${key}${path ? ` (required by ${path})` : ''}: ${detail}`;
277
+ });
278
+ throw new Error(
279
+ `${missing.length} secret(s) this brand's config requires are empty in the .env cascade (company/brand/target). `
280
+ + `Nothing was published.\n${lines.join('\n')}`,
281
+ );
282
+ }
283
+
284
+ const secrets = { ...collectTargetSecrets({ targetDir, target, resolveValue, deriveValues, logger }) };
285
+ for (const [key, value] of Object.entries(extraSecrets || {})) {
286
+ if (value) secrets[key] = value;
287
+ }
288
+
132
289
  const keys = Object.keys(secrets);
133
290
  if (!keys.length) {
134
291
  logger.warn('Skipping secret publication — no keys composed for this target from the .env cascade (company/brand/target)');
135
292
  return { skipped: 'no-secrets' };
136
293
  }
137
294
 
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' };
295
+ const config = productionConfig({ targetDir, target });
296
+ const source = config ? sourceRepo(config) : null;
297
+ if (!source) {
298
+ logger.warn('Skipping secret publication: this brand names no GitHub repo in config (repo.org). Set it, then re-run `omega deploy`.');
299
+ return { skipped: 'no-declared-repo' };
145
300
  }
146
301
 
147
- const declared = declaredBrandRepo({ targetDir, target });
148
- 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`.');
150
- return { skipped: 'no-declared-repo' };
302
+ // The ONE origin gate the deploy lane and the manage walk use
303
+ // ([#934](https://github.com/Omega-JS-Stack/omega/issues/934)): the brand
304
+ // root's own origin (the local remote, which the boot prelude has already
305
+ // healed onto GitHub's redirect) must BE the derived source repo, or the
306
+ // publish REFUSES on the one drift line, because arming a repo that is not
307
+ // the derived one is the harm itself. No `.git` AT the brand root is nothing
308
+ // to compare: a NESTED brand's remote is the enclosing repo's by
309
+ // construction, and its deploy pushes to the derived repo regardless
310
+ // ([#872](https://github.com/Omega-JS-Stack/omega/issues/872)). A checkout
311
+ // with no GitHub origin (nobody pushed it yet, or it is hosted elsewhere) has
312
+ // no repo to arm, so it skips.
313
+ const origin = assertOriginMatches({ dir: findBrandRoot(targetDir), config });
314
+ if (origin.reason === 'no-origin' || origin.reason === 'foreign-remote') {
315
+ logger.warn(`Skipping secret publication: no GitHub remote here (${origin.reason})`);
316
+ return { skipped: 'no-remote' };
151
317
  }
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' };
318
+
319
+ const repo = source.slug;
320
+
321
+ // The PLAN, and nothing else (#895): what the composition answered, by NAME.
322
+ // It runs after the refusal and the two guards above on purpose, so a dry run
323
+ // is a real preview of whether the deploy would publish at all: every one of
324
+ // them is a READ, and a plan naming keys the real run would skip is a promise
325
+ // the deploy cannot keep.
326
+ if (dryRun) {
327
+ logger.log(`DRY RUN, would publish ${keys.length} secret(s) for ${target}: ${keys.join(', ')}`);
328
+ return { planned: keys };
155
329
  }
156
330
 
157
331
  logger.log(`Publishing ${keys.length} secret(s) to ${repo}: ${keys.join(', ')}`);
@@ -162,4 +336,4 @@ function publishTargetSecrets(options) {
162
336
  return result;
163
337
  }
164
338
 
165
- module.exports = { collectTargetSecrets, declaredBrandRepo, publishTargetSecrets };
339
+ module.exports = { collectTargetSecrets, 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.