@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
@@ -1,31 +1,350 @@
1
1
  /**
2
- * The company layer's ONE discovery rule.
2
+ * The company layer's ONE resolver
3
+ * ([#677](https://github.com/Omega-JS-Stack/omega/issues/677)).
3
4
  *
4
- * A brand managed by a company workspace carries `.omega/company.json` at its
5
- * root (stamped idempotently by company manage runs) pointing at the company
6
- * root. Everything that cascades — the config chain (load.js), the .env chain
7
- * (env.js), owner hooks (hooks.js) — resolves the company root through here,
8
- * so the stamp is read exactly one way.
5
+ * A brand joins its company with ONE typed key, outside `brand` (Ian
6
+ * 2026-09-12: "brand key is for things about this brand, and the
7
+ * parent/company/organization key is OUTSIDE of that"):
8
+ *
9
+ * company: { id: 'itw-creative-works' } // a sub-brand: the parent's brand.id
10
+ * company: { id: 'self' } // the company brand itself
11
+ *
12
+ * The resolver fills that SAME object at load (`{ id, name, url, images,
13
+ * webhooks }`), so one name carries one shape at every level and no reader
14
+ * needs a fallback: a brand with no `company` key resolves to `{ id: null,
15
+ * name: brand.name, url: brand.url, images: {}, webhooks: true }`.
16
+ *
17
+ * `webhooks` is the one key beside `id` a brand TYPES (#677): `false` says the
18
+ * provider ACCOUNT is somebody else's and the manage walk never repoints its
19
+ * one account-level webhook. It is the brand's own statement, so it is read
20
+ * from the brand config at every branch, never from the company.
21
+ *
22
+ * The COMPANY TREE is a `company/` folder INSIDE the parent brand's repo,
23
+ * shaped like a brand (`company/config/omega.json5`, `company/.env`,
24
+ * `company/.omega/certificates/apple/`). Inheritance is ONE rule: a
25
+ * brand-level file the child lacks resolves from the parent's `company/` at the
26
+ * same relative path, through `file(relPath)`. Everything that cascades (the
27
+ * config chain in load.js, the .env chain in env.js, owner hooks in hooks.js,
28
+ * the desktop signing tree) asks that one function, so a NEW kind of inherited
29
+ * file costs zero code.
30
+ *
31
+ * WHERE the parent lives on this machine is a cache, never configuration: the
32
+ * machine registry `~/.omega/brands.json` (`OMEGA_HOME` overrides the home),
33
+ * keyed by `brand.id`, which every loadConfig() refreshes for its own brand.
34
+ * Nobody maintains it, and a parent that has never been loaded here is simply
35
+ * inheritance-off with one loud line.
36
+ *
37
+ * OFF-LAPTOP the tree never travels: the machine that dispatches a deploy
38
+ * resolves the company and writes `config/company-resolved.json5` beside the
39
+ * brand config (@omega.js/devkit's deploy snapshot), and a runner reads that
40
+ * generated layer instead of a registry it has no lines in.
9
41
  */
10
42
 
11
43
  const fs = require('node:fs');
44
+ const os = require('node:os');
12
45
  const path = require('node:path');
46
+ const JSON5 = require('json5');
13
47
 
14
- // A brand's pointer at its company root.
15
- const COMPANY_MARKER = path.join('.omega', 'company.json');
48
+ // The company TREE inside the parent brand's repo, and the file an off-laptop
49
+ // run reads the resolved layer from: GENERATED beside the brand config by the
50
+ // deploy snapshot, which is the only place a brand-wide generated file can sit
51
+ // and still ride a mirror (git cannot re-include a path under an excluded
52
+ // directory, and every brand ignores `.omega/` whole).
53
+ const COMPANY_DIR = 'company';
54
+ const COMPANY_RESOLVED_FILE = path.join('config', 'company-resolved.json5');
55
+
56
+ // The machine registry: brand id to { root, name, url, updatedAt }. One file,
57
+ // no other company-shaped state.
58
+ const REGISTRY_FILE = 'brands.json';
59
+
60
+ // `company: { id: 'self' }` is the company brand itself, kept visible on
61
+ // purpose (Ian 2026-09-12: "dont retire parent field for parent brands, i like
62
+ // it visible").
63
+ const SELF = 'self';
64
+
65
+ // The legacy stamp #677 retired, warned about where an owner still carries one.
66
+ const RETIRED_MARKER = path.join('.omega', 'company.json');
67
+
68
+ // One warning per company id per process: a missing parent is a state a whole
69
+ // build runs in, never a per-call event.
70
+ const warned = new Set();
16
71
 
17
72
  /**
18
- * The company root a brand points at via its .omega/company.json marker.
19
- * @param {string} brandRoot
20
- * @returns {string|null} Absolute company root, or null when unstamped/unreadable.
73
+ * The machine home OMEGA keeps its per-machine state in. `OMEGA_HOME` moves it
74
+ * (the test lanes point it at a temp dir so a fixture never writes a real line).
75
+ * @returns {string} Absolute path.
21
76
  */
22
- function readCompanyRoot(brandRoot) {
77
+ function omegaHome() {
78
+ return process.env.OMEGA_HOME || path.join(os.homedir(), '.omega');
79
+ }
80
+
81
+ /**
82
+ * The machine registry file.
83
+ * @returns {string} Absolute path to `<home>/brands.json`.
84
+ */
85
+ function registryFile() {
86
+ return path.join(omegaHome(), REGISTRY_FILE);
87
+ }
88
+
89
+ /**
90
+ * Parse a JSON/JSON5 file, tolerantly: this whole module is a CACHE plus a
91
+ * parent's own public facts, and neither may ever fail a brand's load.
92
+ * @param {string} file - Absolute path.
93
+ * @returns {object|null} The parsed object, or null.
94
+ */
95
+ function readJson(file) {
23
96
  try {
24
- const marker = JSON.parse(fs.readFileSync(path.join(brandRoot, COMPANY_MARKER), 'utf8'));
25
- return typeof marker.root === 'string' && marker.root ? marker.root : null;
97
+ const parsed = JSON5.parse(fs.readFileSync(file, 'utf8'));
98
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
26
99
  } catch {
27
100
  return null;
28
101
  }
29
102
  }
30
103
 
31
- module.exports = { readCompanyRoot, COMPANY_MARKER };
104
+ /**
105
+ * A brand root's authored omega.json5.
106
+ * @param {string|null} root - Brand root.
107
+ * @returns {object|null}
108
+ */
109
+ function readBrandConfig(root) {
110
+ return root ? readJson(path.join(root, 'config', 'omega.json5')) : null;
111
+ }
112
+
113
+ /**
114
+ * The whole machine registry.
115
+ * @returns {Object<string, { root: string, name: string, url: string, updatedAt: string }>}
116
+ */
117
+ function readRegistry() {
118
+ return readJson(registryFile()) || {};
119
+ }
120
+
121
+ /**
122
+ * Record (or refresh) ONE brand's line in the machine registry, which is what
123
+ * every `loadConfig()` does for the brand it just loaded: the map of where the
124
+ * brands are on this machine maintains itself.
125
+ *
126
+ * IDEMPOTENT: a line whose root, name and url already match is left untouched
127
+ * (`updatedAt` is not churn). It NEVER throws: a read-only home, a racing
128
+ * writer, or a machine with no home directory at all is a registry that misses,
129
+ * which is inheritance-off, never a failed load.
130
+ *
131
+ * A write also PRUNES the lines whose root is gone: the file is a map of what
132
+ * is on this machine, so a brand that was deleted or moved (and re-recorded
133
+ * under its new root) leaves nothing behind to resolve into.
134
+ *
135
+ * @param {object} entry - The brand's own facts.
136
+ * @param {string} entry.id - `brand.id` (the key).
137
+ * @param {string} entry.root - The brand root on this machine.
138
+ * @param {string} [entry.name] - `brand.name`.
139
+ * @param {string} [entry.url] - `brand.url`.
140
+ * @returns {boolean} True when a line was written.
141
+ */
142
+ function recordBrand({ id, root, name, url }) {
143
+ if (!id || !root) return false;
144
+
145
+ try {
146
+ const registry = readRegistry();
147
+ const current = registry[id];
148
+ const line = { root, name: name || null, url: url || null };
149
+
150
+ if (current && current.root === line.root && (current.name || null) === line.name && (current.url || null) === line.url) {
151
+ return false;
152
+ }
153
+
154
+ registry[id] = { ...line, updatedAt: new Date().toISOString() };
155
+
156
+ const live = Object.fromEntries(Object.entries(registry).filter(([, entry]) => entry && isDir(entry.root)));
157
+
158
+ fs.mkdirSync(omegaHome(), { recursive: true });
159
+ fs.writeFileSync(registryFile(), `${JSON.stringify(live, null, 2)}\n`);
160
+ return true;
161
+ } catch {
162
+ return false;
163
+ }
164
+ }
165
+
166
+ /**
167
+ * The generated layer an off-laptop run reads: what the dispatching machine
168
+ * resolved, written beside the brand config and carried by the deploy snapshot.
169
+ * @param {string|null} brandRoot
170
+ * @returns {{ config: object, company: object }|null}
171
+ */
172
+ function readResolvedLayer(brandRoot) {
173
+ const generated = brandRoot ? readJson(path.join(brandRoot, COMPANY_RESOLVED_FILE)) : null;
174
+ return generated && generated.company ? generated : null;
175
+ }
176
+
177
+ /** A directory that is really there. */
178
+ function isDir(dir) {
179
+ try {
180
+ return fs.statSync(dir).isDirectory();
181
+ } catch {
182
+ return false;
183
+ }
184
+ }
185
+
186
+ /**
187
+ * The one line a missing parent prints, once per company id per process.
188
+ * @param {string} id
189
+ */
190
+ function warnMissing(id) {
191
+ if (warned.has(id)) return;
192
+ warned.add(id);
193
+ console.warn(`Company ${id} is not on this machine: inheritance off. Clone it and run any omega verb inside it once.`);
194
+ }
195
+
196
+ /**
197
+ * The company tree (`<root>/company`), when the root has one.
198
+ * @param {string|null} root
199
+ * @returns {string|null}
200
+ */
201
+ function companyDir(root) {
202
+ const dir = root ? path.join(root, COMPANY_DIR) : null;
203
+ return dir && isDir(dir) ? dir : null;
204
+ }
205
+
206
+ /**
207
+ * The wordmark a company hands its brands: the company brand's OWN wordmark.
208
+ * @param {object} brand - A brand block.
209
+ * @returns {{ wordmark?: string }}
210
+ */
211
+ function wordmarkOf(brand) {
212
+ const wordmark = brand && brand.images ? brand.images.wordmark : null;
213
+ return wordmark ? { wordmark } : {};
214
+ }
215
+
216
+ /**
217
+ * The resolver's answer, built around ONE inheritance rule.
218
+ * @param {object} facts - The company's id/name/url/images/root, the brand's
219
+ * own `webhooks` statement, plus the generated layer's config when there is
220
+ * no tree to read one from.
221
+ * @returns {object} The resolveCompany() shape.
222
+ */
223
+ function answer({ id = null, name = null, url = null, images = {}, root = null, config = null, configFile = null, webhooks = true }) {
224
+ const dir = companyDir(root);
225
+
226
+ // The WHOLE inheritance rule: a brand-level file the child lacks resolves
227
+ // from the parent's company/ at the same relative path. Nothing per-kind.
228
+ const file = (relPath) => {
229
+ const candidate = dir && relPath ? path.join(dir, relPath) : null;
230
+ return candidate && fs.existsSync(candidate) ? candidate : null;
231
+ };
232
+
233
+ const layerFile = configFile || file(path.join('config', 'omega.json5'));
234
+
235
+ return {
236
+ id,
237
+ name: name || null,
238
+ url: url || null,
239
+ images: images || {},
240
+ // Typed boolean, default true: only an explicit `false` opts out.
241
+ webhooks: webhooks !== false,
242
+ root: root || null,
243
+ dir,
244
+ file,
245
+ // The company CONFIG layer itself, already parsed: read from the parent's
246
+ // company/config/omega.json5 here, handed over by the generated layer on a
247
+ // runner.
248
+ config: config || (layerFile ? readJson(layerFile) : null),
249
+ configFile: layerFile || null,
250
+ };
251
+ }
252
+
253
+ /**
254
+ * The company a brand belongs to, filled from its typed `company.id`.
255
+ *
256
+ * @param {string} brandRoot - The brand (or standalone-project) root.
257
+ * @param {object} [brandConfig] - The brand's authored config, when the caller
258
+ * already has it (loadConfig does); read from the brand root otherwise.
259
+ * @returns {{ id: string|null, name: string|null, url: string|null, images: object, webhooks: boolean, root: string|null, dir: string|null, file: Function, config: object|null, configFile: string|null }}
260
+ * `root` is the parent brand's root ON THIS MACHINE (null = inheritance off),
261
+ * `file(relPath)` that path inside the parent's `company/` when it exists.
262
+ */
263
+ function resolveCompany(brandRoot, brandConfig) {
264
+ const root = brandRoot ? path.resolve(brandRoot) : null;
265
+ const config = brandConfig || readBrandConfig(root) || {};
266
+ const brand = config.brand || {};
267
+ const id = config.company && typeof config.company.id === 'string' ? config.company.id.trim() : '';
268
+
269
+ // The brand's OWN statement, on every branch: whose provider account the
270
+ // manage walk may repoint (#677). Never the company's to answer.
271
+ const webhooks = !(config.company && config.company.webhooks === false);
272
+ const reply = (facts) => answer({ ...facts, webhooks });
273
+
274
+ // A brand still carrying the retired stamp hears about it once: the folder it
275
+ // points at is not read any more, by anything.
276
+ if (root && fs.existsSync(path.join(root, RETIRED_MARKER)) && !warned.has(RETIRED_MARKER)) {
277
+ warned.add(RETIRED_MARKER);
278
+ console.warn(`${path.join(root, RETIRED_MARKER)} is retired (#677): delete it, and name the company with company: { id: '<parent brand.id>' } in omega.json5.`);
279
+ }
280
+
281
+ // No company: the brand IS the whole entity, so the company facts are its own
282
+ // and every reader works without a fallback.
283
+ if (!id) {
284
+ return reply({ id: null, name: brand.name, url: brand.url });
285
+ }
286
+
287
+ // The company brand itself: its own company/ tree, its own public facts.
288
+ if (id === SELF) {
289
+ return reply({ id: SELF, name: brand.name, url: brand.url, images: wordmarkOf(brand), root });
290
+ }
291
+
292
+ const line = readRegistry()[id];
293
+ const parentRoot = line && typeof line.root === 'string' && isDir(line.root) ? line.root : null;
294
+
295
+ if (parentRoot) {
296
+ const parentBrand = (readBrandConfig(parentRoot) || {}).brand || {};
297
+
298
+ return reply({
299
+ id,
300
+ name: parentBrand.name || line.name,
301
+ url: parentBrand.url || line.url,
302
+ images: wordmarkOf(parentBrand),
303
+ root: parentRoot,
304
+ });
305
+ }
306
+
307
+ // Off this laptop: what the dispatching machine resolved rides beside the
308
+ // brand config. Never a tree, so `file()` answers null for everything.
309
+ const generated = readResolvedLayer(root);
310
+ if (generated) {
311
+ const company = generated.company || {};
312
+ return reply({
313
+ id: company.id || id,
314
+ name: company.name,
315
+ url: company.url,
316
+ images: company.images || {},
317
+ config: generated.config || {},
318
+ configFile: path.join(root, COMPANY_RESOLVED_FILE),
319
+ });
320
+ }
321
+
322
+ // A config that already CARRIES the facts: composeTargetConfig freezes the
323
+ // resolved company into a target's upload (the deployed backend has no
324
+ // registry, no company tree and no brand walk-up above it), so what the
325
+ // dispatching machine resolved is the answer here. A human never authors
326
+ // these keys: the loader refuses them in a brand file.
327
+ const frozen = config.company || {};
328
+ if (frozen.name || frozen.url || frozen.images) {
329
+ return reply({ id, name: frozen.name, url: frozen.url, images: frozen.images || {} });
330
+ }
331
+
332
+ // Known in the registry but the folder is gone: the cached public facts still
333
+ // stand, the tree does not.
334
+ if (line) {
335
+ return reply({ id, name: line.name, url: line.url });
336
+ }
337
+
338
+ warnMissing(id);
339
+ return reply({ id });
340
+ }
341
+
342
+ module.exports = {
343
+ resolveCompany,
344
+ recordBrand,
345
+ readRegistry,
346
+ registryFile,
347
+ COMPANY_DIR,
348
+ COMPANY_RESOLVED_FILE,
349
+ COMPANY_SELF: SELF,
350
+ };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * dev-facts.js, the ONE missing-dev-fact error
3
+ * ([#834](https://github.com/Omega-JS-Stack/omega/issues/834)).
4
+ *
5
+ * The classic port numbers are DEFINED once, in `CLASSIC_PORTS` beside this
6
+ * file, and the allocator resolves a live map from them at boot (N7). Browser
7
+ * code can read neither env nor files, so every surface BAKES that resolved map
8
+ * into its build json under one key, `OMEGA_BUILD_JSON.config.dev`
9
+ * ([#300](https://github.com/Omega-JS-Stack/omega/issues/300),
10
+ * [#262](https://github.com/Omega-JS-Stack/omega/issues/262)).
11
+ *
12
+ * What this file exists to stop is the OTHER half of that arrangement: the same
13
+ * numbers hand-typed a second time as a browser-side fallback for "a build made
14
+ * with no stack up". @omega.js/client carried four of them plus a classic dev
15
+ * origin, @omega.js/desktop's url-helpers and client-bridge carried three more,
16
+ * and @omega.js/extension's url-helpers and background worker two. Every one of
17
+ * them was a guess about a port nothing identity-checks, so a neighbouring
18
+ * project's emulator holding 9099 read as an auth mystery for hours rather than
19
+ * as a port problem, and a wrong API base read as a silent connection refusal.
20
+ *
21
+ * So there is no fallback anywhere any more. A real build always carries the
22
+ * map; a read that finds none throws THIS error, which names the fact it wanted
23
+ * and the build step that writes it.
24
+ *
25
+ * This module requires NOTHING: it is bundled into browser artifacts (the
26
+ * desktop renderer, every extension bundle) and vendored into @omega.js/client.
27
+ */
28
+
29
+ // The ONE key every surface bakes the resolved map under, spelled once.
30
+ const DEV_FACT_CHANNEL = 'OMEGA_BUILD_JSON.config.dev';
31
+
32
+ /**
33
+ * The error a surface throws when a dev fact it needs was never baked.
34
+ *
35
+ * @param {string} what - the fact, named the way the caller wanted it
36
+ * ("dev port for `auth`", "dev website origin").
37
+ * @param {string} writer - the build step that writes the map on THIS surface,
38
+ * so the message says where to go and not just what is absent.
39
+ * @returns {Error} the error to throw.
40
+ */
41
+ function devFactMissing(what, writer) {
42
+ return new Error(`No resolved ${what} in ${DEV_FACT_CHANNEL}. It is written by ${writer}. A build made without it is not a build, so nothing here assumes a classic port.`);
43
+ }
44
+
45
+ module.exports = {
46
+ DEV_FACT_CHANNEL,
47
+ devFactMissing,
48
+ };