@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
@@ -6,7 +6,7 @@
6
6
  * chatsy settings: nothing reads that name.
7
7
  *
8
8
  * Like secrets.js this is a key-NAME test, walked at every depth (shared
9
- * level, inside a target entry, inside an instance array) — the rename moved
9
+ * level, inside a target entry, inside an array item): the rename moved
10
10
  * the key, not its home. Only unambiguous renames belong here: a name that
11
11
  * still exists as a legitimate key somewhere in the schema (`google`/`meta`
12
12
  * under `analytics.providers`) would false-positive and is left to the mapping
@@ -20,16 +20,33 @@
20
20
  * name (`slapform` → `forms.providers.slapform`), and folded two keys into
21
21
  * `cloud`. A name test can't express those — `slapform` is legitimate again
22
22
  * one level down, and `firebase` lives on as `client.firebase` — so these
23
- * match one EXACT path from the root.
24
- *
25
- * `github` is deliberately NOT here: the brand's own `github` (content
26
- * identity, unchanged; a shared key since #277) lives at the top level, and
27
- * a `targets.backend.github` override is overlaid there too by the resolved
28
- * view, so a name test would false-positive. Its rename to
29
- * `repo.providers.github` is carried by the mapping tables in
30
- * docs/shared/config.md instead.
23
+ * match one EXACT path from the root, with the target NAME read as its TYPE
24
+ * (#886) so a `targets.web.*` row fires on every web target a brand runs.
31
25
  */
32
26
 
27
+ const { TARGETS, CUSTOM_TARGET_TYPE } = require('./schema.js');
28
+
29
+ // Every legal target `type`, so a per-type row set covers all of them (#883).
30
+ const TARGET_TYPES = [...TARGETS, CUSTOM_TARGET_TYPE];
31
+
32
+ /**
33
+ * The `translation.exclude` → `translation.include` move (#858): every route
34
+ * the brand skipped becomes a negation, in the order it was written, on top
35
+ * of `**`. The FRAMEWORK default (`['**', '!blog/**']`) is deliberately not
36
+ * what a carrying brand lands on: it was translating its blog, and a
37
+ * conversion that changed which pages reach a provider would be a behavior
38
+ * change dressed as a rename. Routes are normalized the way the translation
39
+ * pass normalizes them (no leading or trailing slash), so `/changelog/` lands
40
+ * as `!changelog`.
41
+ * @param {*} value - the authored `exclude` list
42
+ * @returns {string[]} the `include` list that means the same thing
43
+ */
44
+ function excludeToInclude(value) {
45
+ const routes = Array.isArray(value) ? value : [];
46
+
47
+ return ['**', ...routes.map((entry) => `!${String(entry).replace(/^\/+|\/+$/g, '')}`)];
48
+ }
49
+
33
50
  // key name → { replacement, why } (docs/shared/config.md carries the rows)
34
51
  const RETIRED_KEYS = {
35
52
  web_manager: {
@@ -45,16 +62,16 @@ const RETIRED_KEYS = {
45
62
  why: 'the banner became a real consent gate (#383) — the block names the DECISION, not the cookie, and its palette/theme/type keys are gone (tokens paint it, the visitor\'s region picks opt-in vs opt-out)',
46
63
  },
47
64
 
48
- // The web instance IS the subdomain (#588). `brand.subdomains` was read by
65
+ // The web TARGET is the subdomain (#588). `brand.subdomains` was read by
49
66
  // ONE thing (the cloud hosting op, which ensured an api.{sub}.{domain} per
50
67
  // entry) and declared by nothing: no schema rule, no default, never
51
68
  // materialized. Ian's 2026-09-01 call gave the fact a real home. A NAME test
52
69
  // by the rule above: `subdomains` exists nowhere else in the schema (the
53
70
  // legacy searchConsole.subdomains was dropped outright), so the walk catches
54
- // it at every depth, instance arrays included.
71
+ // it at every depth, target entries included.
55
72
  subdomains: {
56
73
  replacement: 'targets.web',
57
- why: "each subdomain is a web INSTANCE (#588): [\"admin\", \"cdn\"] becomes web: [{ id: 'main' }, { id: 'admin' }, { id: 'cdn' }], where the id IS the subdomain (https://admin.<brand host>), an entry's own `url` overrides it for a custom host, and the subdomains share ONE api.<domain>",
74
+ why: "each subdomain is its own web TARGET (#588/#886): [\"admin\", \"cdn\"] becomes sibling keys beside the main site, `admin: { type: 'web' }, cdn: { type: 'web' }`, where the NAME is the subdomain (https://admin.<brand host>), an entry's own `url` overrides it for a custom host, and the targets share ONE api.<domain>",
58
75
  },
59
76
 
60
77
  // The feature is `connections` now (#788, Ian 2026-09-03): a user connection
@@ -67,6 +84,16 @@ const RETIRED_KEYS = {
67
84
  replacement: 'connections',
68
85
  why: "the product concept is a CONNECTION (#788) — the per-provider block is unchanged, the credentials are the CONNECTIONS_<PROVIDER>_CLIENT_ID/_SECRET env pair now, and a brand's own provider lives at targets/backend/src/connections/<name>.js",
69
86
  },
87
+
88
+ // The company block names the parent (#677), so `parent` is retired
89
+ // OUTRIGHT: the topology moved to `company: { id }` first, and the last
90
+ // meaning it carried, the webhook opt-out, is `company: { webhooks: false }`
91
+ // now. A NAME test by the rule above: `parent` exists nowhere else in the
92
+ // schema.
93
+ parent: {
94
+ replacement: 'company.webhooks',
95
+ why: "the company block names the parent (#677): the topology is `company: { id: '<parent brand.id>' }` (or 'self'), and the only other thing `parent` ever said, `false` for \"the provider ACCOUNT is shared and its one account-level webhook is owned elsewhere\", is `company: { webhooks: false }`",
96
+ },
70
97
  };
71
98
 
72
99
  // exact dotted path → { replacement, why } (docs/shared/config.md carries the rows)
@@ -201,7 +228,7 @@ const RETIRED_PATHS = {
201
228
  },
202
229
  'monitoring.dsn': {
203
230
  replacement: 'monitoring.providers.sentry.dsn',
204
- why: 'provider-hung Sentry settings live under the provider that reads them (#425) — per-surface DSNs are targets.<type>.monitoring.providers.sentry.dsn',
231
+ why: 'provider-hung Sentry settings live under the provider that reads them (#425): per-surface DSNs are targets.<name>.monitoring.providers.sentry.dsn',
205
232
  },
206
233
  'monitoring.environment': {
207
234
  replacement: 'monitoring.providers.sentry.environment',
@@ -310,6 +337,32 @@ const RETIRED_PATHS = {
310
337
  why: 'two homes for one fact (#610) — the /extension page and its shortlinks read the extension target\'s store listings, curated onto site.targets.extension.listings',
311
338
  },
312
339
 
340
+ // ─── the four schema-less web sections (#850) ─────────────────────────
341
+ // Everything the build processes has a schema home (Ian 2026-09-09). These
342
+ // four were the exception: legacy UJM presentation blocks the converter
343
+ // wrote under `targets.web`, which @omega.js/web carried in a PRIVATE list
344
+ // (`WEB_ONLY_SECTIONS`) purely to let its own `config:` guard pass them.
345
+ // The list is gone, so each one is a registered path instead of a key that
346
+ // validates clean and reaches nothing. Matched at their AUTHORED path, the
347
+ // one place a carrying brand has them (a root spelling on a web load is the
348
+ // same key overlaid, exactly as with the #610 pair above).
349
+ 'targets.web.favicon': {
350
+ replacement: 'nothing for the path; brand.images.favicon for the source image',
351
+ why: 'the favicon set is MINTED from the brand images (#850): the manager assets service mints it and the web build bridges it to /assets/images/favicon, so a path override pointed the whole site at an unminted folder; theme-color comes from `brand.color` now, the one place a brand states its hex',
352
+ },
353
+ 'targets.web.manifest': {
354
+ replacement: 'nothing: the minted set ships site.webmanifest',
355
+ why: 'no reader anywhere in @omega.js/web (#850): the web app manifest that ships is the minted set\'s own site.webmanifest, so every key under this block was a value nothing consulted',
356
+ },
357
+ 'targets.web.icons': {
358
+ replacement: 'the `icon` on the link itself, as Font Awesome classes',
359
+ why: 'one icon mechanism (#619): a footer link carries its own `fa-*` classes (`icon: \'fa-brands fa-github\'`), so the name-to-markup map is gone; the legacy block only ever held a `style`, which the map lookup could never resolve',
360
+ },
361
+ 'targets.web.currency': {
362
+ replacement: 'payment.currency',
363
+ why: 'two homes for one fact (#850): the price currency belongs to the payment section every other surface reads it from, and the pricing JSON-LD reads it there',
364
+ },
365
+
313
366
  // ─── redirects are not web config (#466) ──────────────────────────────
314
367
  // The block shipped for one wave (0.45.0) and was withdrawn: static hosting
315
368
  // has no server, so the map could only be answered CLIENT-side off the built
@@ -378,20 +431,172 @@ const RETIRED_PATHS = {
378
431
  replacement: 'nothing: the versionless assets live on the `v<x.y.z>` release',
379
432
  why: 'one public releases repo per brand (#620/#799): a stable mirror tag is what `/releases/latest/download/<asset>` replaced, so no tag is configured anywhere',
380
433
  },
434
+
435
+ // ─── one repo block, and no repo name anywhere (#883) ──────────────────
436
+ // A brand's repo hosting used to be spelled in four places: the
437
+ // `repo.providers.github` block, a separate top-level `github` identity, a
438
+ // `targets.<name>.github.repo` override, and the desktop releases
439
+ // owner/repo. One block says it now (`repo: { provider, org }`), every repo
440
+ // name derives from `<brand.id>-<role>`, and visibility is the brand root
441
+ // package.json's `private` field. Matched at their AUTHORED paths, one row
442
+ // per LEAF: each of these validated clean under the new shape and silently
443
+ // addressed a repo nothing publishes to.
444
+ 'repo.providers.github.org': {
445
+ replacement: 'repo.org',
446
+ why: 'ONE repo block (#883): `repo: { provider: \'github\', org }` is the whole declaration, and the org owns every repo the brand derives',
447
+ },
448
+ 'repo.providers.github.repo': {
449
+ replacement: 'nothing: the source repo IS `<brand.id>-omega`',
450
+ why: 'no repo NAME is configurable anywhere (#883): a repo name that must differ is a brand id that must differ, so rename the GitHub repo to `<brand.id>-omega` instead',
451
+ },
452
+ 'repo.providers.github.private': {
453
+ replacement: 'the brand root package.json `private` field',
454
+ why: 'visibility has ONE statement (#883): `private: true` (or absent) is a private brand, `false` a public one, and the manage walk reconciles the repo to it in both directions',
455
+ },
456
+ 'repo.providers.github.shared': {
457
+ replacement: 'nothing: an org may host many brands, and no brand rewrites an org profile',
458
+ why: 'the org-profile reconcile is gone (#883), so there is no shared-org exception left to declare',
459
+ },
460
+ 'repo.providers.github.enabled': {
461
+ replacement: 'the presence of the `repo` block',
462
+ why: 'presence is the switch (#883), exactly as a target key\'s presence enables that target: a brand that hosts its source somewhere the manager does not touch omits the block',
463
+ },
464
+ 'github.user': {
465
+ replacement: 'nothing: the org is `repo.org`',
466
+ why: 'the separate GitHub identity block is gone (#883): nothing read `user`, and every repo address derives from `repo.org` plus `brand.id`',
467
+ },
468
+ 'github.website': {
469
+ replacement: 'nothing: a web target\'s site repo is `<brand.id>-<target name>`',
470
+ why: 'the website repo derives now (#883): each GitHub-hosted web target publishes its built site to its own `<brand.id>-<name>` repo, Pages serving it at the target\'s url',
471
+ },
472
+ // ─── the company is ONE key, outside `brand` (#677) ────────────────────
473
+ // `brand.company` was a typed DISPLAY NAME (Ian 2026-09-12: "brand.company
474
+ // hardcoded was a workaround"): the parent's name is the parent's to state,
475
+ // so it resolves now. Registered at its authored path, because `company` is
476
+ // a legitimate key one level up and at the top level.
477
+ 'brand.company': {
478
+ replacement: 'company.name (resolved from `company: { id }`)',
479
+ why: "the company is ONE top-level key now (#677): type `company: { id: '<parent brand.id>' }` (or 'self') and the loader fills company.name/url/images from the parent's own config, so no brand restates its parent's facts",
480
+ },
481
+ 'brand.images.companyWordmark': {
482
+ replacement: 'company.images.wordmark (resolved from `company: { id }`)',
483
+ why: "the parent's wordmark is the parent's own `brand.images.wordmark` (#677), resolved through `company: { id }` for every sub-brand instead of pasted into each one",
484
+ },
485
+
486
+ // ─── the ONE platform vocabulary (#867) ───────────────────────────────
487
+ // The client's words everywhere (`mac`, `windows`, `linux`), and what a
488
+ // target ships is DECLARED per format. Both of these validated clean while
489
+ // nothing read them: a brand still saying `win` got @omega.js/desktop's
490
+ // default Windows settings and its own were ignored, and a brand still
491
+ // saying `snap.enabled: true` published no snap at all. Registered at both
492
+ // shapes a brand can write them in: inside the target entry (the brand file)
493
+ // and at the top level (a target's own config/omega.json5).
494
+ 'platforms.win': {
495
+ replacement: 'platforms.windows (with its formats inside: platforms.windows.formats.nsis)',
496
+ why: "ONE platform vocabulary, the client's (#867): run `npx omega manage --migration=platform-names --execute` at the brand root to rewrite it (it also renames config/icons/macos/ to config/icons/mac/). Run the migration BEFORE `omega migrate`, which deletes a retired key rather than moving it",
497
+ },
498
+ 'targets.desktop.platforms.win': {
499
+ replacement: 'targets.desktop.platforms.windows (with its formats inside: platforms.windows.formats.nsis)',
500
+ why: "ONE platform vocabulary, the client's (#867): run `npx omega manage --migration=platform-names --execute` at the brand root to rewrite it (it also renames config/icons/macos/ to config/icons/mac/). Run the migration BEFORE `omega migrate`, which deletes a retired key rather than moving it",
501
+ },
502
+ 'platforms.linux.snap': {
503
+ replacement: 'platforms.linux.formats.snap (presence IS the switch, so the `enabled` flag is gone)',
504
+ why: 'what a target ships is one declaration now (#867): every platform and format defaults ON and `platforms.linux.formats.snap: false` is the only way to drop the snap. Its settings (channels, confinement, grade, autoStart) move inside the format. `npx omega manage --migration=platform-names --execute` performs the move',
505
+ },
506
+ 'targets.desktop.platforms.linux.snap': {
507
+ replacement: 'targets.desktop.platforms.linux.formats.snap (presence IS the switch, so the `enabled` flag is gone)',
508
+ why: 'what a target ships is one declaration now (#867): every platform and format defaults ON and `platforms.linux.formats.snap: false` is the only way to drop the snap. Its settings (channels, confinement, grade, autoStart) move inside the format. `npx omega manage --migration=platform-names --execute` performs the move',
509
+ },
510
+
511
+ // ─── the translation list says what to TRANSLATE (#858) ───────────────
512
+ // Ian 2026-09-13: an exclude list defaulted to "translate everything", so a
513
+ // brand that never thought about it paid a provider for its whole blog. The
514
+ // list is `translation.include` now: globs with `!` negation, defaulting to
515
+ // ['**', '!blog/**'] in the DEFAULTS layer, and the page half is the same
516
+ // key one level down (`translation.include: true`/`false` in frontmatter),
517
+ // which is Ian's 2026-09-09 same-name ruling. The row carries a `convert`,
518
+ // so `omega migrate` MOVES the setting instead of only deleting it: each
519
+ // excluded route becomes a negation on top of the default. Registered at its
520
+ // authored path AND at the `targets.web` overlay, the two places a brand can
521
+ // write it; `translation` is a legitimate key name at both.
522
+ 'translation.exclude': {
523
+ replacement: 'translation.include',
524
+ why: "the route list says what to TRANSLATE now (#858, Ian 2026-09-13): globs with `!` negation, read in .gitignore order, defaulting to ['**', '!blog/**'], so `exclude: ['docs']` becomes `include: ['**', '!docs']`, and a page overrides it for itself with `translation.include: true`/`false` in its own frontmatter. `omega migrate` performs the move",
525
+ convert: excludeToInclude,
526
+ },
527
+ 'targets.web.translation.exclude': {
528
+ replacement: 'targets.web.translation.include',
529
+ why: "the route list says what to TRANSLATE now (#858, Ian 2026-09-13): globs with `!` negation, read in .gitignore order, defaulting to ['**', '!blog/**'], so `exclude: ['docs']` becomes `include: ['**', '!docs']`, and a page overrides it for itself with `translation.include: true`/`false` in its own frontmatter. `omega migrate` performs the move",
530
+ convert: excludeToInclude,
531
+ },
532
+
533
+ 'targets.desktop.releases.owner': {
534
+ replacement: 'nothing: the releases repo is `<brand.id>-releases` under `repo.org`',
535
+ why: 'one public releases repo per brand (#883), with no override: `releases: {}` stays the presence switch for the site\'s download links',
536
+ },
537
+ 'targets.desktop.releases.repo': {
538
+ replacement: 'nothing: the releases repo is `<brand.id>-releases` under `repo.org`',
539
+ why: 'one public releases repo per brand (#883), with no override: `releases: {}` stays the presence switch for the site\'s download links',
540
+ },
381
541
  };
382
542
 
383
- // A multi-instance target is an ARRAY, so the walked path carries the position
384
- // (`targets.web.0.meta`) while every RETIRED_PATHS row names the shape
385
- // (`targets.web.meta`) — without this, each `targets.<type>.*` row missed the
386
- // array form outright (#732). Matching only: the REPORTED path keeps the index,
387
- // which is where the author finds the key.
388
- function withoutIndices(keyPath) {
389
- return keyPath.split('.').filter((segment) => !/^\d+$/.test(segment)).join('.');
543
+ // The per-target `github` override, one row per TYPE (#883). The type-row rule
544
+ // above (shapePath reads a target's NAME as its TYPE) makes each row fire on
545
+ // every target of that type a brand runs, and enumerating TARGET_TYPES is what
546
+ // keeps a type nobody thought of from slipping through: the backend's CMS
547
+ // content repo is the override that existed, but the key validated clean on any
548
+ // target and pointed the commits at a repo nothing else addressed.
549
+ for (const type of TARGET_TYPES) {
550
+ RETIRED_PATHS[`targets.${type}.github.repo`] = {
551
+ replacement: 'nothing: every repo the brand owns is `<brand.id>-<role>` under `repo.org`',
552
+ why: 'no repo NAME is configurable anywhere (#883): the CMS commits to the SOURCE repo `<brand.id>-omega`, so a content repo of its own is a brand of its own',
553
+ };
554
+ }
555
+
556
+ // The walked path is not always the SHAPE a RETIRED_PATHS row names, two ways:
557
+ // a value inside an array carries its position (`redirects.0.from`), and a
558
+ // target carries its NAME while the rows name its TYPE (#886). Both are
559
+ // normalized for MATCHING only: the REPORTED path stays the real one, which is
560
+ // where the author finds the key.
561
+ //
562
+ // The name normalization is what makes a `targets.web.*` row fire on EVERY web
563
+ // target a brand runs (`targets.community.meta.title` when community is
564
+ // `type: 'web'`), which is the shape a brand with two sites has (#732, #886).
565
+ function shapePath(keyPath, targetTypes) {
566
+ const parts = keyPath.split('.').filter((segment) => !/^\d+$/.test(segment));
567
+
568
+ if (parts[0] === 'targets' && parts.length > 1 && targetTypes[parts[1]]) {
569
+ parts[1] = targetTypes[parts[1]];
570
+ }
571
+
572
+ return parts.join('.');
573
+ }
574
+
575
+ /**
576
+ * Every declared target's name → type, read off the config being walked. A
577
+ * sub-tree with no `targets` map (or entries with no `type`) yields {}, so the
578
+ * rows then match by the key as written.
579
+ * @param {object} object - Parsed config (or any sub-tree of one).
580
+ * @returns {object} name → type.
581
+ */
582
+ function targetTypes(object) {
583
+ const targets = object && object.targets;
584
+ if (!targets || typeof targets !== 'object' || Array.isArray(targets)) return {};
585
+
586
+ const types = {};
587
+ Object.keys(targets).forEach((name) => {
588
+ const entry = targets[name];
589
+ if (entry && typeof entry === 'object' && !Array.isArray(entry) && typeof entry.type === 'string') {
590
+ types[name] = entry.type;
591
+ }
592
+ });
593
+
594
+ return types;
390
595
  }
391
596
 
392
- function walk(node, path, found) {
597
+ function walk(node, path, found, types) {
393
598
  if (Array.isArray(node)) {
394
- node.forEach((item, index) => walk(item, path ? `${path}.${index}` : String(index), found));
599
+ node.forEach((item, index) => walk(item, path ? `${path}.${index}` : String(index), found, types));
395
600
  return;
396
601
  }
397
602
 
@@ -406,13 +611,13 @@ function walk(node, path, found) {
406
611
  found.push({ path: keyPath, key, ...RETIRED_KEYS[key] });
407
612
  }
408
613
 
409
- const shapePath = withoutIndices(keyPath);
614
+ const shape = shapePath(keyPath, types);
410
615
 
411
- if (RETIRED_PATHS[shapePath]) {
412
- found.push({ path: keyPath, key, ...RETIRED_PATHS[shapePath] });
616
+ if (RETIRED_PATHS[shape]) {
617
+ found.push({ path: keyPath, key, ...RETIRED_PATHS[shape] });
413
618
  }
414
619
 
415
- walk(node[key], keyPath, found);
620
+ walk(node[key], keyPath, found, types);
416
621
  });
417
622
  }
418
623
 
@@ -423,7 +628,7 @@ function walk(node, path, found) {
423
628
  */
424
629
  function findRetiredKeys(object) {
425
630
  const found = [];
426
- walk(object, '', found);
631
+ walk(object, '', found, targetTypes(object));
427
632
  return found;
428
633
  }
429
634