@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
@@ -5,8 +5,8 @@
5
5
  *
6
6
  * Same walk as the config cascade (load.js owns it): a target inside a brand
7
7
  * monorepo ({brand}/targets/{target}) layers the brand root's .env under its own,
8
- * and a brand stamped with .omega/company.json layers its company root's
9
- * .env underneath that. Loading uses dotenv's no-override semantics — keys
8
+ * and a brand naming a company (`company: { id }`) layers that company's own
9
+ * `company/.env` underneath that (#677). Loading uses dotenv's no-override semantics: keys
10
10
  * already in process.env (the shell) always win, and files apply
11
11
  * innermost-first, so local beats brand beats company.
12
12
  *
@@ -30,32 +30,40 @@ const fs = require('node:fs');
30
30
  const path = require('node:path');
31
31
 
32
32
  const { findBrandRoot } = require('./load.js');
33
- const { readCompanyRoot } = require('./company.js');
34
- const { ENV_SCHEMA, envFileGroups } = require('./env-schema.js');
35
-
36
- // The ONE environment vocabulary, strongest signal first: every `.env.<name>`
37
- // overlay is suffixed with one of these, every framework's environment() answers
38
- // one of these, and nothing anywhere spells a fourth
39
- // ([#586](https://github.com/Omega-JS-Stack/omega/issues/586)).
40
- const ENV_ENVIRONMENTS = ['development', 'testing', 'production'];
33
+ const { resolveCompany } = require('./company.js');
34
+ const { ENV_SCHEMA, envFileGroups, envSchemaEntry } = require('./env-schema.js');
35
+ const { assertNoRetiredEnvKeys } = require('./env-retired.js');
36
+ // The one vocabulary lives with the one environment module (#817); this file
37
+ // re-exports it so the .env overlay names and the runtime answer stay one list.
38
+ const { ENV_ENVIRONMENTS } = require('./environment.js');
41
39
 
42
40
  /**
43
- * The runtime environment — the SINGLE SOURCE OF TRUTH for the one vocabulary,
44
- * shared by the env overlay above and by every framework's own environment
45
- * answer (@omega.js/backend's `env.environment()` / `Manager.getEnvironment()`
46
- * delegate here). Exactly ONE of three mutually-exclusive values: testing wins,
47
- * then production, else development.
41
+ * The AMBIENT environment answer: what a lane resolves when nothing named one
42
+ * for it. It is the PRODUCER of the one input, never a second reader of it:
43
+ * `environment.js` owns the runtime answer (one input, `OMEGA_ENVIRONMENT`, no
44
+ * guessing), and this is the sniff a lane runs ONCE to decide what to set
45
+ * ([#817](https://github.com/Omega-JS-Stack/omega/issues/817)). It is also the
46
+ * default for the two overlay selections below (`.env.<environment>` and
47
+ * `config/omega.<environment>.json5`), so a compose that was told nothing
48
+ * follows the same answer the runtime will give.
49
+ *
50
+ * An already-set `OMEGA_ENVIRONMENT` wins outright, so the producer and the
51
+ * reader can never disagree inside one process. Otherwise: testing wins, then
52
+ * production, else development.
48
53
  *
49
54
  * The final `else` is PRODUCTION on purpose: a deployed Cloud Function has no
50
55
  * FUNCTIONS_EMULATOR and often no ENVIRONMENT var, so "no signal" IS the normal
51
56
  * production state. Defaulting to development would make every deployed
52
- * function skip real side effects (emails/analytics/webhooks). (Contrast
53
- * UJM/BXM, whose deployed artifacts always carry their signal.)
57
+ * function skip real side effects (emails/analytics/webhooks).
54
58
  *
55
59
  * @returns {'testing'|'production'|'development'} The environment.
56
60
  */
57
61
  function envEnvironment() {
58
- // Testing takes precedence — set by the test runner / emulator (OMEGA_TEST_MODE=true).
62
+ // The one input, when a lane already named it (#817).
63
+ if (ENV_ENVIRONMENTS.includes(process.env.OMEGA_ENVIRONMENT)) {
64
+ return process.env.OMEGA_ENVIRONMENT;
65
+ }
66
+ // Testing takes precedence, set by the test runner / emulator (OMEGA_TEST_MODE=true).
59
67
  if (process.env.OMEGA_TEST_MODE === 'true') {
60
68
  return 'testing';
61
69
  }
@@ -104,14 +112,17 @@ function envLayerFiles(envPath, environment) {
104
112
  function resolveEnvChain(startDir) {
105
113
  const targetDir = path.resolve(startDir);
106
114
  const brandRoot = findBrandRoot(targetDir);
107
- // The marker sits at the brand root; when startDir IS a brand root (no
108
- // targets/ walk above it), its own marker supplies the company layer.
109
- const companyRoot = readCompanyRoot(brandRoot || targetDir);
115
+ // The company is named by the BRAND's config; when startDir IS a brand root
116
+ // (no targets/ walk above it), its own config names it. The layer is the
117
+ // company TREE's `.env`, named whether or not it exists: this resolves paths,
118
+ // and an `.env.<environment>` overlay (#586) derives from the base name, so a
119
+ // company that ships only an overlay still layers.
120
+ const company = resolveCompany(brandRoot || targetDir);
110
121
 
111
122
  return {
112
123
  local: path.join(targetDir, '.env'),
113
124
  brand: brandRoot ? path.join(brandRoot, '.env') : null,
114
- company: companyRoot ? path.join(companyRoot, '.env') : null,
125
+ company: company.dir ? path.join(company.dir, '.env') : null,
115
126
  };
116
127
  }
117
128
 
@@ -163,7 +174,7 @@ function loadEnvChain(envPaths) {
163
174
  for (const envPath of envPaths) {
164
175
  if (!envPath || !fs.existsSync(envPath)) continue;
165
176
 
166
- const parsed = require('dotenv').parse(fs.readFileSync(envPath, 'utf8'));
177
+ const parsed = parseEnvFile(envPath);
167
178
  for (const [key, value] of Object.entries(parsed)) {
168
179
  if (value === '' || key in process.env) continue;
169
180
  process.env[key] = value;
@@ -311,13 +322,22 @@ function reloadEnv(startDir, { target, environment = envEnvironment() } = {}) {
311
322
  /**
312
323
  * Parse one .env file into a plain map. Missing files read as empty.
313
324
  *
325
+ * The ONE place a `.env` layer is read, so it is also where a RETIRED key is
326
+ * refused ([#893](https://github.com/Omega-JS-Stack/omega/issues/893)): there
327
+ * is no dual-read, so a line for a key that moved into config is a value
328
+ * nothing consults, and both readers below (the process cascade and the
329
+ * artifact composer) fail on it naming the move.
330
+ *
314
331
  * @param {string|null} envPath
315
332
  * @returns {Object<string, string>} Parsed key → value.
316
333
  */
317
334
  function parseEnvFile(envPath) {
318
335
  if (!envPath || !fs.existsSync(envPath)) return {};
319
336
 
320
- return require('dotenv').parse(fs.readFileSync(envPath, 'utf8'));
337
+ const parsed = require('dotenv').parse(fs.readFileSync(envPath, 'utf8'));
338
+ assertNoRetiredEnvKeys(parsed, envPath);
339
+
340
+ return parsed;
321
341
  }
322
342
 
323
343
  /**
@@ -358,7 +378,11 @@ function deliveringEntry(key, target) {
358
378
  * there is): a key rides down when some entry claims it — by name or by
359
379
  * pattern — names this target, and sits in a file group. The TARGET layer
360
380
  * passes through unfiltered: placing a key in the target's own .env IS the
361
- * targeting. `deliverAs` renames on arrival in EVERY layer (the per-target GA4
381
+ * targeting. A key NO entry claims at all is the consumer's own
382
+ * ([#835](https://github.com/Omega-JS-Stack/omega/issues/835)) and rides down
383
+ * to every target: the schema never heard of it, so it names no target to be
384
+ * filtered by, and a consumer who wants one on a single target has that
385
+ * target's own .env. `deliverAs` renames on arrival in EVERY layer (the per-target GA4
362
386
  * secrets), so a human writing the brand-level name in the target's own .env
363
387
  * gets the one delivered key, overriding the brand's.
364
388
  *
@@ -398,7 +422,12 @@ function composeTargetEnv({ targetDir, target, environment = envEnvironment() })
398
422
  for (const { file, layer, filtered } of files) {
399
423
  const claimed = {};
400
424
  for (const [key, value] of Object.entries(parseEnvFile(file))) {
401
- if (filtered && !deliveringEntry(key, target)) continue;
425
+ // The filter is on DECLARED keys: an entry that does not name this
426
+ // target keeps its key home. A key the schema knows nothing about is the
427
+ // CONSUMER's own (#835), has no target of its own to be judged by, and
428
+ // rides down to every target; a consumer who wants one on a single
429
+ // target puts it in that target's .env, which passes unfiltered anyway.
430
+ if (filtered && envSchemaEntry(key) && !deliveringEntry(key, target)) continue;
402
431
  claimed[key] = value;
403
432
  }
404
433
 
@@ -0,0 +1,189 @@
1
+ /**
2
+ * environment.js, the ONE environment module every OMEGA target answers from
3
+ * ([#817](https://github.com/Omega-JS-Stack/omega/issues/817)).
4
+ *
5
+ * Four calls, one implementation, one call form everywhere: `getEnvironment()`
6
+ * plus `isDevelopment()` / `isProduction()` / `isTesting()`, mixed into a
7
+ * framework's Manager with `attachTo()`. @omega.js/backend, @omega.js/desktop,
8
+ * @omega.js/extension and @omega.js/web each carried their own copy of this
9
+ * logic, each with its own signal list and its own DEFAULT, and the copies
10
+ * disagreed: desktop answered `production` with no signal while the extension
11
+ * answered `development`, so a desktop dev boot baked itself as a production
12
+ * artifact.
13
+ *
14
+ * The fix is by construction, and it is the whole contract:
15
+ *
16
+ * ONE INPUT. On Node it is `process.env.OMEGA_ENVIRONMENT`. In a browser
17
+ * context (a desktop renderer, an extension view, a web page), which can read
18
+ * neither env nor files, it is `config.environment`, the build fact every
19
+ * surface already bakes into `OMEGA_BUILD_JSON.config`
20
+ * ([#896](https://github.com/Omega-JS-Stack/omega/issues/896)), reached as
21
+ * `this.config.environment` off the Manager the call is made on. Nothing else
22
+ * is consulted: no `app.isPackaged`, no `manifest.update_url`, no `NODE_ENV`,
23
+ * no terminal sniffing.
24
+ *
25
+ * NO DEFAULT. A context with no input does not guess a safe answer, it throws
26
+ * and names the variable. A guessed environment is how a dev build ships as
27
+ * production and how a production build talks to an emulator, and both of
28
+ * those failures are silent until a user finds them.
29
+ *
30
+ * Who SETS the one input is each target's own business, at the one place it
31
+ * already resolves its config, through `setEnvironment()` below (the only
32
+ * writer, so a fourth word can never reach the variable): the backend's boot
33
+ * takes the .env cascade's ambient answer, the desktop and extension build
34
+ * Managers take `production` under their build-mode flag and the ambient answer
35
+ * otherwise, and web's verbs name their own (`omega build` is production,
36
+ * `omega dev` is development).
37
+ *
38
+ * This module requires NOTHING. It is bundled into browser artifacts (the
39
+ * desktop renderer, every extension bundle) and vendored into
40
+ * @omega.js/client, so a single `node:fs` at the top would break all three.
41
+ */
42
+
43
+ // The ONE environment vocabulary, and the home of it. Every `.env.<name>`
44
+ // overlay is suffixed with one of these, every `config/omega.<name>.json5`
45
+ // overlay is named with one of these, every framework's environment answer is
46
+ // one of these, and nothing anywhere spells a fourth
47
+ // ([#586](https://github.com/Omega-JS-Stack/omega/issues/586)).
48
+ const ENV_ENVIRONMENTS = ['development', 'testing', 'production'];
49
+
50
+ // The ONE input's name, spelled once so every error message and every writer
51
+ // reads it from here.
52
+ const ENVIRONMENT_VAR = 'OMEGA_ENVIRONMENT';
53
+
54
+ /**
55
+ * The Node input: the environment variable, when this context has a `process`
56
+ * at all (a browser bundle does not).
57
+ * @returns {string|undefined} the raw value.
58
+ */
59
+ function fromProcess() {
60
+ return typeof process !== 'undefined' && process.env
61
+ ? process.env[ENVIRONMENT_VAR]
62
+ : undefined;
63
+ }
64
+
65
+ /**
66
+ * The browser input: the build fact baked into `OMEGA_BUILD_JSON.config`,
67
+ * reached through the Manager the call was made on.
68
+ * @param {object} [context] - the `this` of the call.
69
+ * @returns {string|undefined} the raw value.
70
+ */
71
+ function fromBakedConfig(context) {
72
+ return context && context.config ? context.config.environment : undefined;
73
+ }
74
+
75
+ /**
76
+ * The running environment, from the one input.
77
+ *
78
+ * @this {object} [context] - a Manager carrying the baked `config`.
79
+ * @returns {'development'|'testing'|'production'} exactly one of the three.
80
+ * @throws {Error} when neither input names one of the three.
81
+ */
82
+ function getEnvironment() {
83
+ const value = fromProcess() || fromBakedConfig(this);
84
+
85
+ if (!ENV_ENVIRONMENTS.includes(value)) {
86
+ // Loud by design: every lane sets the input, so reaching here is a broken
87
+ // lane, and any answer invented here would be wrong somewhere it matters.
88
+ throw new Error(`${ENVIRONMENT_VAR} is not set to one of ${ENV_ENVIRONMENTS.join(' | ')} (got ${value === undefined ? 'nothing' : JSON.stringify(value)}). On Node the lane that boots or builds this target sets it; in a browser context it is the baked OMEGA_BUILD_JSON.config.environment, which every OMEGA build writes.`);
89
+ }
90
+
91
+ return value;
92
+ }
93
+
94
+ /**
95
+ * @this {object} [context] - a Manager carrying the baked `config`.
96
+ * @returns {boolean} true only in development (never in testing).
97
+ */
98
+ function isDevelopment() {
99
+ return getEnvironment.call(this) === 'development';
100
+ }
101
+
102
+ /**
103
+ * @this {object} [context] - a Manager carrying the baked `config`.
104
+ * @returns {boolean} true only in production, a real positive check.
105
+ */
106
+ function isProduction() {
107
+ return getEnvironment.call(this) === 'production';
108
+ }
109
+
110
+ /**
111
+ * @this {object} [context] - a Manager carrying the baked `config`.
112
+ * @returns {boolean} true only while a test lane runs this process.
113
+ */
114
+ function isTesting() {
115
+ return getEnvironment.call(this) === 'testing';
116
+ }
117
+
118
+ /**
119
+ * The ONE writer of the one input, so every lane that names an environment
120
+ * spells it the same way and a fourth word is refused at the moment it is
121
+ * written rather than at the moment it is read.
122
+ *
123
+ * @param {string} value - one of the three names.
124
+ * @returns {string} the same value, so a caller can name it and pass it on.
125
+ * @throws {Error} when it is not one of the three.
126
+ */
127
+ function setEnvironment(value) {
128
+ if (!ENV_ENVIRONMENTS.includes(value)) {
129
+ throw new Error(`${ENVIRONMENT_VAR} cannot be set to ${value === undefined ? 'nothing' : JSON.stringify(value)}: it is one of ${ENV_ENVIRONMENTS.join(' | ')}.`);
130
+ }
131
+
132
+ process.env[ENVIRONMENT_VAR] = value;
133
+
134
+ return value;
135
+ }
136
+
137
+ /**
138
+ * The environment a NODE BUILD LANE is for, decided once for the two frameworks
139
+ * whose build Manager names it at load (@omega.js/desktop and
140
+ * @omega.js/extension, which carried byte-identical copies of this expression).
141
+ * Two rules, no sniffing:
142
+ *
143
+ * 1. The build-mode flag is the lane SAYING it produces a production artifact
144
+ * (`omega build` sets `OMEGA_BUILD_MODE`), and it wins over anything
145
+ * inherited, so a production build spawned from a test run still bakes
146
+ * production.
147
+ * 2. Otherwise a lane that already named one keeps it (the test runners spawn
148
+ * their children with `testing`), and a bare dev boot is `development`.
149
+ *
150
+ * It DECIDES; the caller writes it with `setEnvironment()`, which stays the one
151
+ * writer. This is the only default anywhere near this module, and it belongs to
152
+ * a build lane alone: a READER still gets no default at all.
153
+ *
154
+ * @param {boolean} buildMode - The lane's build-mode flag (`Manager.isBuildMode()`).
155
+ * @returns {'development'|'testing'|'production'} the word that lane is for.
156
+ */
157
+ function buildLaneEnvironment(buildMode) {
158
+ return buildMode ? 'production' : (fromProcess() || 'development');
159
+ }
160
+
161
+ /**
162
+ * Mix the surface into a Manager constructor's prototype AND the constructor
163
+ * itself, so `Manager.isTesting()` works statically too. The idiom every
164
+ * framework already attaches with.
165
+ *
166
+ * @param {Function} Manager - the constructor to extend.
167
+ */
168
+ function attachTo(Manager) {
169
+ Manager.prototype.getEnvironment = getEnvironment;
170
+ Manager.prototype.isDevelopment = isDevelopment;
171
+ Manager.prototype.isProduction = isProduction;
172
+ Manager.prototype.isTesting = isTesting;
173
+ Manager.getEnvironment = getEnvironment;
174
+ Manager.isDevelopment = isDevelopment;
175
+ Manager.isProduction = isProduction;
176
+ Manager.isTesting = isTesting;
177
+ }
178
+
179
+ module.exports = {
180
+ ENV_ENVIRONMENTS,
181
+ ENVIRONMENT_VAR,
182
+ getEnvironment,
183
+ isDevelopment,
184
+ isProduction,
185
+ isTesting,
186
+ setEnvironment,
187
+ buildLaneEnvironment,
188
+ attachTo,
189
+ };
@@ -19,9 +19,11 @@
19
19
  * add their own `config/hooks/` ignore line.
20
20
  *
21
21
  * Resolution walks the same hierarchy as the .env cascade: the brand root's
22
- * own `config/hooks/` first, then the company root's (via the
23
- * .omega/company.json stamp) — so a company-wide hook covers every brand and
24
- * a single brand can still override it. Hooks are plain CJS modules whose
22
+ * own `config/hooks/` first, then the company tree's (the parent brand's
23
+ * `company/config/hooks/`, resolved through company.js), so a company-wide hook
24
+ * covers every brand and a single brand can still override it. It is the ONE
25
+ * inheritance rule (#677) applied to one more relative path, and costs no code
26
+ * of its own. Hooks are plain CJS modules whose
25
27
  * `module.exports` IS the hook function; call-site docs define each hook's
26
28
  * signature and return contract.
27
29
  */
@@ -29,7 +31,7 @@
29
31
  const fs = require('node:fs');
30
32
  const path = require('node:path');
31
33
 
32
- const { readCompanyRoot } = require('./company.js');
34
+ const { resolveCompany } = require('./company.js');
33
35
 
34
36
  // Hook points are code-owned kebab-case path constants ('account/password') —
35
37
  // enforce the shape so a typo'd or traversal-shaped path fails loudly.
@@ -44,7 +46,7 @@ function hookFile(root, hookPath) {
44
46
 
45
47
  /**
46
48
  * Resolve a hook point to the file that defines it: the brand root's own
47
- * `config/hooks/<hookPath>.js`, else the company root's (company.json stamp).
49
+ * `config/hooks/<hookPath>.js`, else the company tree's.
48
50
  *
49
51
  * @param {string} startRoot - Brand (or standalone-project) root
50
52
  * @param {string} hookPath - Call-site-mirroring hook point, e.g. 'account/password'
@@ -55,13 +57,13 @@ function resolveHook(startRoot, hookPath) {
55
57
  throw new Error(`Invalid hook path ${JSON.stringify(hookPath)} — kebab-case segments joined by '/', e.g. 'account/password'`);
56
58
  }
57
59
 
58
- const roots = [path.resolve(startRoot)];
59
- const companyRoot = readCompanyRoot(roots[0]);
60
- if (companyRoot && companyRoot !== roots[0]) {
61
- roots.push(companyRoot);
62
- }
60
+ const brandRoot = path.resolve(startRoot);
61
+ const own = hookFile(brandRoot, hookPath);
62
+ if (fs.existsSync(own)) return own;
63
63
 
64
- return roots.map((root) => hookFile(root, hookPath)).find((file) => fs.existsSync(file)) || null;
64
+ // One relative path, one rule: whatever the child lacks resolves from the
65
+ // company tree at the same place.
66
+ return resolveCompany(brandRoot).file(path.relative(brandRoot, own));
65
67
  }
66
68
 
67
69
  /**