@omega.js/desktop 0.50.0 → 0.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. package/README.md +2 -2
  2. package/dist/assets/css/tokens/_index.scss +1 -1
  3. package/dist/assets/themes/base/_includes/frontend/sections/account-section-header.html +4 -1
  4. package/dist/assets/themes/base/_includes/frontend/sections/footer.html +13 -6
  5. package/dist/assets/themes/base/_includes/frontend/sections/nav.html +10 -8
  6. package/dist/assets/themes/base/_includes/global/sections/account.html +3 -1
  7. package/dist/assets/themes/base/_includes/global/sections/app-sidebar.html +12 -8
  8. package/dist/assets/themes/base/_includes/global/sections/app-topbar.html +10 -6
  9. package/dist/assets/themes/base/_includes/global/sections/page-header.html +8 -4
  10. package/dist/assets/themes/base/_layouts/backend/pages/dashboard/index.html +24 -24
  11. package/dist/assets/themes/base/_layouts/frontend/pages/about.html +10 -10
  12. package/dist/assets/themes/base/_layouts/frontend/pages/account/index.html +35 -35
  13. package/dist/assets/themes/base/_layouts/frontend/pages/alternatives/index.html +4 -4
  14. package/dist/assets/themes/base/_layouts/frontend/pages/auth/signin.html +1 -1
  15. package/dist/assets/themes/base/_layouts/frontend/pages/auth/signup.html +4 -4
  16. package/dist/assets/themes/base/_layouts/frontend/pages/contact.html +3 -3
  17. package/dist/assets/themes/base/_layouts/frontend/pages/download.html +34 -33
  18. package/dist/assets/themes/base/_layouts/frontend/pages/extension/index.html +11 -11
  19. package/dist/assets/themes/base/_layouts/frontend/pages/legal/document.html +1 -1
  20. package/dist/assets/themes/base/_layouts/frontend/pages/status.html +1 -1
  21. package/dist/assets/themes/base/_layouts/frontend/pages/team/index.html +9 -7
  22. package/dist/assets/themes/base/_layouts/frontend/pages/team/member.html +5 -3
  23. package/dist/assets/themes/base/_sections/about/letter/section.html +1 -1
  24. package/dist/assets/themes/base/_sections/about/letter/section.json5 +4 -4
  25. package/dist/assets/themes/base/_sections/marketing/bento/section.html +1 -1
  26. package/dist/assets/themes/base/_sections/marketing/bento/section.json5 +15 -15
  27. package/dist/assets/themes/base/_sections/marketing/cta/section.json5 +1 -1
  28. package/dist/assets/themes/base/_sections/marketing/hero/section.html +6 -6
  29. package/dist/assets/themes/base/_sections/marketing/hero/section.json5 +10 -10
  30. package/dist/assets/themes/base/_sections/marketing/product-demo/section.html +1 -1
  31. package/dist/assets/themes/base/_sections/marketing/product-demo/section.json5 +2 -2
  32. package/dist/assets/themes/base/_sections/marketing/stats/section.html +1 -1
  33. package/dist/assets/themes/base/_sections/marketing/stats/section.json5 +5 -5
  34. package/dist/assets/themes/base/_sections/marketing/trusted-by/section.html +1 -1
  35. package/dist/assets/themes/base/_sections/marketing/trusted-by/section.json5 +2 -2
  36. package/dist/assets/themes/neobrutalism/_layouts/frontend/pages/index.html +10 -10
  37. package/dist/assets/themes/newsflash/_layouts/frontend/pages/index.html +10 -10
  38. package/dist/assets/themes/newsflash/_sections/marketing/desks/section.html +2 -2
  39. package/dist/assets/themes/newsflash/_sections/marketing/desks/section.json5 +4 -4
  40. package/dist/build.js +69 -28
  41. package/dist/cli-run.js +20 -13
  42. package/dist/cli.js +12 -7
  43. package/dist/commands/build.js +0 -2
  44. package/dist/commands/deploy.js +116 -44
  45. package/dist/commands/finalize-release.js +5 -4
  46. package/dist/commands/launch.js +12 -7
  47. package/dist/commands/lib/deploy-precheck.js +66 -34
  48. package/dist/commands/lib/ensure-target.js +71 -11
  49. package/dist/commands/package.js +0 -1
  50. package/dist/commands/publish.js +11 -4
  51. package/dist/commands/release.js +99 -252
  52. package/dist/commands/runner.js +176 -42
  53. package/dist/commands/sign-windows.js +46 -15
  54. package/dist/commands/test.js +4 -3
  55. package/dist/commands/validate-certs.js +291 -115
  56. package/dist/config/page-template.html +4 -0
  57. package/dist/defaults/.github/workflows/build.yml +146 -64
  58. package/dist/defaults/AGENTS.md +3 -2
  59. package/dist/defaults/_.gitignore +4 -2
  60. package/dist/defaults/config/certs/README.md +25 -45
  61. package/dist/defaults/config/omega.json5 +66 -32
  62. package/dist/defaults/hooks/deploy/pre.js +10 -0
  63. package/dist/defaults/src/assets/scss/main.scss +12 -2
  64. package/dist/defaults/src/integrations/tray/index.js +1 -1
  65. package/dist/gulp/main.js +13 -25
  66. package/dist/gulp/tasks/audit.js +18 -6
  67. package/dist/gulp/tasks/build-config.js +181 -60
  68. package/dist/gulp/tasks/bundle.js +108 -36
  69. package/dist/gulp/tasks/release.js +86 -4
  70. package/dist/gulp/tasks/sass.js +11 -0
  71. package/dist/hooks/lib/notarize-tools.js +137 -0
  72. package/dist/hooks/notarize-artifacts.js +57 -0
  73. package/dist/hooks/notarize.js +59 -8
  74. package/dist/lib/auth-persistence.js +25 -0
  75. package/dist/lib/client-bridge.js +11 -8
  76. package/dist/lib/deep-link.js +15 -8
  77. package/dist/lib/protocol.js +7 -1
  78. package/dist/lib/restart-manager/install.js +6 -3
  79. package/dist/lib/sign-helpers/auto-unlock.js +65 -27
  80. package/dist/lib/sign-helpers/console-lock.js +34 -0
  81. package/dist/lib/sign-helpers/exec-with-limit.js +68 -0
  82. package/dist/lib/sign-helpers/resolve-icons.js +6 -4
  83. package/dist/lib/tray.js +7 -6
  84. package/dist/main.js +16 -0
  85. package/dist/preload.js +8 -0
  86. package/dist/renderer.js +16 -4
  87. package/dist/runner/job-started.js +104 -0
  88. package/dist/test/fixtures/consumer-app/config/omega.json5 +7 -0
  89. package/dist/test/fixtures/consumer-app/package.json +1 -1
  90. package/dist/test/fixtures/consumer-app/src/assets/js/components/main/index.js +7 -1
  91. package/dist/test/harness/main-entry.js +2 -1
  92. package/dist/test/harness/renderer-preload.js +12 -3
  93. package/dist/test/runners/boot.js +101 -11
  94. package/dist/test/runners/electron.js +1 -1
  95. package/dist/test/suites/boot/consumer-app-boots.test.js +54 -0
  96. package/dist/test/suites/build/audit.test.js +45 -6
  97. package/dist/test/suites/build/auth-persistence-resolve.test.js +119 -0
  98. package/dist/test/suites/build/auto-unlock.test.js +105 -0
  99. package/dist/test/suites/build/boot-runner-timeout.test.js +253 -0
  100. package/dist/test/suites/build/brand-scss.test.js +106 -0
  101. package/dist/test/suites/build/build-config.test.js +146 -36
  102. package/dist/test/suites/build/build-json-bake.test.js +245 -0
  103. package/dist/test/suites/build/build-verbs.test.js +48 -15
  104. package/dist/test/suites/build/build-workflow-jobs.test.js +190 -0
  105. package/dist/test/suites/build/cli.test.js +32 -10
  106. package/dist/test/suites/build/config-schema.test.js +4 -4
  107. package/dist/test/suites/build/console-lock.test.js +51 -0
  108. package/dist/test/suites/build/defaults-scaffold.test.js +71 -2
  109. package/dist/test/suites/build/deploy-direct.test.js +241 -0
  110. package/dist/test/suites/build/deploy-dispatch.test.js +287 -0
  111. package/dist/test/suites/build/deploy-hook.test.js +169 -0
  112. package/dist/test/suites/build/ensure-target.test.js +14 -2
  113. package/dist/test/suites/build/env-delivery.test.js +19 -10
  114. package/dist/test/suites/build/env-watch.test.js +18 -7
  115. package/dist/test/suites/build/esm-only-dependency.test.js +127 -0
  116. package/dist/test/suites/build/exec-with-limit.test.js +53 -0
  117. package/dist/test/suites/build/finalize-release.test.js +2 -2
  118. package/dist/test/suites/build/get-config.test.js +120 -8
  119. package/dist/test/suites/build/github-utils.test.js +12 -6
  120. package/dist/test/suites/build/license-stamp.test.js +6 -4
  121. package/dist/test/suites/build/manager.test.js +88 -68
  122. package/dist/test/suites/build/manifest-deps.test.js +116 -0
  123. package/dist/test/suites/build/merge-line-files.test.js +27 -4
  124. package/dist/test/suites/build/notarize-artifacts.test.js +135 -0
  125. package/dist/test/suites/build/notarize-tools.test.js +38 -0
  126. package/dist/test/suites/build/notarize.test.js +207 -0
  127. package/dist/test/suites/build/release-pipeline.test.js +38 -60
  128. package/dist/test/suites/build/release-skipped-upload.test.js +107 -0
  129. package/dist/test/suites/build/resolve-icons.test.js +35 -35
  130. package/dist/test/suites/build/runner-job-guard.test.js +182 -0
  131. package/dist/test/suites/build/runner.test.js +635 -5
  132. package/dist/test/suites/build/sentry.test.js +8 -3
  133. package/dist/test/suites/build/setup-scripts.test.js +3 -0
  134. package/dist/test/suites/build/sign-windows.test.js +92 -8
  135. package/dist/test/suites/build/test-stealth.test.js +17 -11
  136. package/dist/test/suites/build/url-helpers.test.js +37 -17
  137. package/dist/test/suites/build/validate-certs.test.js +428 -55
  138. package/dist/test/suites/build/validate-config.test.js +3 -3
  139. package/dist/test/suites/main/auth-flow.test.js +12 -0
  140. package/dist/test/suites/main/auth-persistence.test.js +14 -17
  141. package/dist/test/suites/main/auto-updater.test.js +2 -2
  142. package/dist/test/suites/main/boot-sequence.test.js +1 -1
  143. package/dist/test/suites/main/client-bridge.integration.test.js +5 -82
  144. package/dist/test/suites/main/client-bridge.test.js +19 -6
  145. package/dist/test/suites/main/deep-link.test.js +54 -0
  146. package/dist/test/suites/main/startup-paths-and-ua.test.js +1 -1
  147. package/dist/test/suites/main/url-helpers.test.js +81 -72
  148. package/dist/test/suites/renderer/cross-context-helpers.test.js +19 -16
  149. package/dist/utils/build-pipeline.js +10 -10
  150. package/dist/utils/github.js +12 -51
  151. package/dist/utils/load-env.js +66 -0
  152. package/dist/utils/mode-helpers.js +43 -111
  153. package/dist/utils/platform.js +37 -0
  154. package/dist/utils/runner-env.js +2 -1
  155. package/dist/utils/runner-job-guard.js +149 -0
  156. package/dist/utils/ship-keys.js +52 -0
  157. package/dist/utils/test-stealth.js +5 -3
  158. package/dist/utils/url-helpers.js +33 -17
  159. package/dist/vendor/config/bundle-id.js +53 -0
  160. package/dist/vendor/config/client-config.js +141 -0
  161. package/dist/vendor/config/company.js +334 -15
  162. package/dist/vendor/config/dev-facts.js +48 -0
  163. package/dist/vendor/config/env-delivery.js +231 -9
  164. package/dist/vendor/config/env-retired.js +137 -0
  165. package/dist/vendor/config/env-rules.js +22 -3
  166. package/dist/vendor/config/env-schema.js +234 -117
  167. package/dist/vendor/config/env.js +55 -26
  168. package/dist/vendor/config/environment.js +189 -0
  169. package/dist/vendor/config/hooks.js +13 -11
  170. package/dist/vendor/config/index.js +125 -44
  171. package/dist/vendor/config/load.js +366 -115
  172. package/dist/vendor/config/merge.js +2 -2
  173. package/dist/vendor/config/order.js +3 -3
  174. package/dist/vendor/config/platforms.js +276 -0
  175. package/dist/vendor/config/repo.js +249 -103
  176. package/dist/vendor/config/retired-keys.js +232 -27
  177. package/dist/vendor/config/schema.js +525 -99
  178. package/dist/vendor/config/site-global.js +63 -53
  179. package/dist/vendor/config/targets.js +187 -0
  180. package/dist/vendor/config/validate.js +119 -59
  181. package/dist/vendor/devkit/argv.js +118 -0
  182. package/dist/vendor/devkit/attach-log-file.js +21 -13
  183. package/dist/vendor/devkit/brand-tokens.js +278 -0
  184. package/dist/vendor/devkit/brand-version.js +425 -0
  185. package/dist/vendor/devkit/build-json.js +91 -0
  186. package/dist/vendor/devkit/bundle.js +48 -0
  187. package/dist/vendor/devkit/certificate-expiry.js +108 -0
  188. package/dist/vendor/devkit/certs.js +16 -194
  189. package/dist/vendor/devkit/ci-workflows.js +124 -8
  190. package/dist/vendor/devkit/cli-router.js +3 -3
  191. package/dist/vendor/devkit/defaults-engine.js +69 -7
  192. package/dist/vendor/devkit/deploy-follow.js +297 -0
  193. package/dist/vendor/devkit/deploy-precheck.js +23 -8
  194. package/dist/vendor/devkit/deploy-record.js +11 -27
  195. package/dist/vendor/devkit/deploy-snapshot.js +661 -0
  196. package/dist/vendor/devkit/deploy.js +505 -75
  197. package/dist/vendor/devkit/git-auth.js +73 -0
  198. package/dist/vendor/devkit/git-remote.js +172 -0
  199. package/dist/vendor/devkit/github-repo.js +290 -0
  200. package/dist/vendor/devkit/local.js +98 -3
  201. package/dist/vendor/devkit/lockfile.js +127 -0
  202. package/dist/vendor/devkit/merge-line-files.js +23 -16
  203. package/dist/vendor/devkit/omega-bin.js +18 -3
  204. package/dist/vendor/devkit/pack-local.js +388 -0
  205. package/dist/vendor/devkit/preludes/index.js +120 -0
  206. package/dist/vendor/devkit/preludes/origin-heal.js +142 -0
  207. package/dist/vendor/devkit/service-account.js +43 -0
  208. package/dist/vendor/devkit/ship-plan.js +112 -0
  209. package/dist/vendor/devkit/signing-env.js +180 -0
  210. package/dist/vendor/devkit/signing-tree.js +92 -0
  211. package/dist/vendor/devkit/target-seams.js +142 -0
  212. package/dist/vendor/devkit/target-secrets.js +231 -57
  213. package/dist/vendor/devkit/test/esm-only-fixture.js +48 -0
  214. package/dist/vendor/devkit/test/fixtures/esm-only-package/browser.js +19 -0
  215. package/dist/vendor/devkit/test/fixtures/esm-only-package/index.js +20 -0
  216. package/dist/vendor/devkit/test/fixtures/esm-only-package/package.json +13 -0
  217. package/dist/vendor/monitoring/env.js +20 -10
  218. package/dist/vendor/monitoring/main.js +1 -1
  219. package/dist/vendor/monitoring/preload.js +1 -1
  220. package/dist/vendor/monitoring/renderer.js +1 -1
  221. package/docs/analytics.md +1 -1
  222. package/docs/auto-updater.md +5 -5
  223. package/docs/boot-sequence.md +1 -1
  224. package/docs/build-system.md +15 -7
  225. package/docs/client-bridge.md +9 -7
  226. package/docs/config-schema.md +4 -4
  227. package/docs/css.md +8 -2
  228. package/docs/deep-link.md +12 -4
  229. package/docs/environment-detection.md +32 -24
  230. package/docs/hooks.md +3 -1
  231. package/docs/icons.md +7 -7
  232. package/docs/index.md +61 -23
  233. package/docs/installer-options.md +24 -21
  234. package/docs/logging.md +5 -5
  235. package/docs/releasing.md +29 -19
  236. package/docs/runner.md +51 -14
  237. package/docs/shared/brands.md +12 -6
  238. package/docs/shared/breaking-changes.md +375 -21
  239. package/docs/shared/config.md +764 -199
  240. package/docs/shared/deploys.md +217 -91
  241. package/docs/shared/icons.md +18 -0
  242. package/docs/shared/local-dev.md +28 -8
  243. package/docs/shared/logging.md +9 -6
  244. package/docs/shared/monitoring.md +27 -13
  245. package/docs/shared/publishing.md +3 -3
  246. package/docs/shared/rulings.md +2 -2
  247. package/docs/shared/testing.md +1 -1
  248. package/docs/shared/theming.md +26 -1
  249. package/docs/shared/translation.md +49 -7
  250. package/docs/shared/updates.md +1 -1
  251. package/docs/signing.md +60 -34
  252. package/docs/test-framework.md +10 -5
  253. package/docs/themes.md +15 -1
  254. package/package.json +18 -12
  255. package/bin/omega-desktop +0 -2
  256. package/dist/commands/push-secrets.js +0 -141
  257. package/dist/test/suites/build/deliver-certs.test.js +0 -95
  258. package/dist/test/suites/build/derive-signing-env.test.js +0 -122
  259. package/dist/test/suites/build/push-secrets.test.js +0 -226
  260. package/dist/test/suites/build/resolve-signing-cert.test.js +0 -342
  261. package/dist/utils/deliver-certs.js +0 -69
  262. package/dist/utils/derive-signing-env.js +0 -56
  263. package/dist/utils/resolve-signing-cert.js +0 -175
  264. package/dist/vendor/config/desktop-artifacts.js +0 -110
  265. package/dist/vendor/config/instances.js +0 -208
  266. /package/dist/defaults/config/icons/{macos → mac}/dmg.png +0 -0
  267. /package/dist/defaults/config/icons/{macos → mac}/icon.png +0 -0
  268. /package/dist/defaults/config/icons/{macos → mac}/tray.png +0 -0
@@ -1,142 +1,288 @@
1
1
  /**
2
- * @omega.js/config repo — the SINGLE derivation of the brand's GitHub repo
3
- * (owner + name), shared by every surface that targets the brand repo (web
4
- * `omega deploy --direct`, the manager's github service setup/repo/pages,
5
- * the backend CMS routes that commit posts).
6
- *
7
- * One optional key: `repo.providers.github.repo` (or, on a backend load, the
8
- * target-overlaid `targets.backend.github.repo`, which wins) — either a bare name
9
- * ("omega-brand") or an "owner/name" slug ("itw-creative-works/omega-brand").
10
- * Defaults: name falls to `<brand.id>-omega`, owner falls to `repo.providers.github.org`.
11
- * That name is the `<brand.id>-<role>` repo rule (Ian 2026-09-07,
12
- * [#809](https://github.com/Omega-JS-Stack/omega/issues/809)): every repo a brand
13
- * owns is its id plus the role it plays, `omega` for the source monorepo beside
14
- * the `releases` one below, so nobody types a repo name to get the right one.
15
- * The slug's owner slot carries the legacy omega-manager orgMain/orgWebsite
16
- * split (Ian 2026-07-19): most ITW brands house the brand repo under the paid
17
- * company org (itw-creative-works) while `repo.providers.github.org` keeps
18
- * naming the brand's own org for org-profile reconciliation. The retired `repoWebsite` URL key
19
- * (2026-07-19, Ian: "we no longer need it — that was for when the website
20
- * lived NOT in the monorepo") is superseded by this slug: one repo per
21
- * brand, named by the brand.
22
- *
23
- * Born from the 2026-07-18 launch-night collision: the bare brand-id
24
- * fallback resolved brand "omega" to Omega-JS-Stack/omega — the framework
25
- * MONOREPO — instead of the brand repo.
2
+ * @omega.js/config repo: the ONE derivation of every repo a brand owns
3
+ * ([#883](https://github.com/Omega-JS-Stack/omega/issues/883)).
4
+ *
5
+ * ONE config block says where a brand hosts its code:
6
+ *
7
+ * repo: { provider: 'github', org: 'Acme-Org' }
8
+ *
9
+ * and nothing else. Presence of the block enables the repo service, exactly
10
+ * as a target's key presence enables a target; `provider` defaults to `github`
11
+ * and `org` is the only typed value. No repo NAME is ever configured: every
12
+ * name derives from the `<brand.id>-<role>` rule (Ian 2026-09-07,
13
+ * [#809](https://github.com/Omega-JS-Stack/omega/issues/809)), one function per
14
+ * role, so nobody types a repo name to get the right one:
15
+ *
16
+ * - `<brand.id>-omega` the SOURCE monorepo (`sourceRepo`)
17
+ * - `<brand.id>-releases` the public release channel (`releasesRepo`)
18
+ * - `<brand.id>-<name>` one per web target that GitHub hosts (`websiteRepo`)
19
+ *
20
+ * A repo name that must differ is a brand id that must differ: the override
21
+ * keys (`repo.providers.github.repo`, the top-level `github` block,
22
+ * `targets.<name>.github.repo`, `targets.desktop.releases.owner/repo`) are
23
+ * retired, each with a row in retired-keys.js.
24
+ *
25
+ * Visibility is NOT in omega.json5 either: the brand root's package.json
26
+ * `private` field is the one statement of it (`brandVisibility`), absent
27
+ * meaning private, because every brand monorepo is private by default
28
+ * (Ian 2026-09-11).
26
29
  */
27
30
 
31
+ const fs = require('node:fs');
32
+ const path = require('node:path');
33
+
34
+ const { isPlainObject } = require('./merge.js');
35
+ const { targetUrl, brandHost } = require('./targets.js');
36
+ // The two provider lists live in schema.js, with the rules that enumerate
37
+ // them (the WINBACK_DURATIONS pattern), and are re-exported here: this module
38
+ // is where every reader takes them from.
39
+ const { REPO_PROVIDERS, HOSTING_PROVIDERS } = require('./schema.js');
40
+
41
+ const DEFAULT_REPO_PROVIDER = 'github';
42
+
43
+ // Every GitHub default Pages host (`<owner>.github.io`, and the bare form): a
44
+ // url naming one is an ADDRESS, never a custom domain (#366).
45
+ const DEFAULT_PAGES_HOST = /(^|\.)github\.io$/i;
46
+ const DEFAULT_HOSTING_PROVIDER = 'github';
47
+
28
48
  /**
29
- * Parse a `repo.providers.github.repo` value: "owner/name" slug or bare "name".
49
+ * The brand's repo block: `{ provider, org }`, with the provider defaulted.
50
+ *
51
+ * Null when the config carries no `repo` block, and null when the block names
52
+ * no org: half an address addresses nothing, and the validator fails an
53
+ * org-less block on its own (an error here would be a second, quieter copy of
54
+ * that rule).
30
55
  *
31
- * @param {string} value - e.g. "itw-creative-works/omega-brand" or "omega-brand"
32
- * @returns {{ owner: string, name: string }} owner is '' for bare names.
56
+ * @param {object} config - Composed omega config (brand + local layers).
57
+ * @returns {{ provider: string, org: string }|null}
33
58
  */
34
- function parseRepoSlug(value) {
35
- const trimmed = (value || '').trim();
36
- if (!trimmed) return { owner: '', name: '' };
37
- const slash = trimmed.indexOf('/');
38
- if (slash === -1) return { owner: '', name: trimmed };
39
- return { owner: trimmed.slice(0, slash), name: trimmed.slice(slash + 1) };
59
+ function repoBlock(config) {
60
+ const repo = config ? config.repo : undefined;
61
+ if (!isPlainObject(repo)) return null;
62
+
63
+ const org = typeof repo.org === 'string' ? repo.org.trim() : '';
64
+ if (!org) return null;
65
+
66
+ return { provider: repo.provider || DEFAULT_REPO_PROVIDER, org };
40
67
  }
41
68
 
42
69
  /**
43
- * The github keys the derivation reads: the shared `repo.providers.github`
44
- * block, overlaid by the target's own `github` entry (a backend load composes
45
- * `targets.backend.github` — the CMS's content identity — onto the top level,
46
- * and per the merge chain the target wins).
70
+ * The brand's id, trimmed: the role suffix is appended to it, so untrimmed
71
+ * whitespace would sit INSIDE the derived name.
72
+ * @param {object} config - Composed omega config.
73
+ * @returns {string} '' when the config names no brand id.
74
+ */
75
+ function brandId(config) {
76
+ const id = config && config.brand ? config.brand.id : undefined;
77
+ return typeof id === 'string' ? id.trim() : '';
78
+ }
79
+
80
+ /**
81
+ * A derived repo as one finished value, or null when either half is missing.
82
+ * @param {object} config - Composed omega config.
83
+ * @param {string} role - The role suffix ('omega', 'releases', a target name).
84
+ * @returns {{ owner: string, name: string, slug: string }|null}
85
+ */
86
+ function derivedRepo(config, role) {
87
+ const block = repoBlock(config);
88
+ const id = brandId(config);
89
+ if (!block || !id) return null;
90
+
91
+ const name = `${id}-${role}`;
92
+
93
+ return { owner: block.org, name, slug: `${block.org}/${name}` };
94
+ }
95
+
96
+ /**
97
+ * The brand's SOURCE monorepo: `<brand.id>-omega` under the declared org. This
98
+ * is the repo every SOURCE fact addresses: the workflow dispatch, the repo
99
+ * Actions secrets, the CMS content commits, the scaffolded workflows.
47
100
  *
48
- * @param {object} config - Composed omega config (brand + local layers).
49
- * @returns {object} Merged `{ org, repo, … }` keys.
101
+ * Its visibility is the brand root's `private` field (`brandVisibility`), not a
102
+ * config key.
103
+ *
104
+ * @param {object} config - Composed omega config.
105
+ * @returns {{ owner: string, name: string, slug: string }|null} Null when the config names no org or no brand id.
50
106
  */
51
- function githubKeys(config) {
52
- return { ...(config?.repo?.providers?.github || {}), ...(config?.github || {}) };
107
+ function sourceRepo(config) {
108
+ return derivedRepo(config, 'omega');
53
109
  }
54
110
 
55
111
  /**
56
- * The brand repo's bare name (no owner): a typed slug or bare name, else the
57
- * `<brand.id>-omega` default of the `<brand.id>-<role>` rule.
112
+ * Whether the repo a brand's `origin` names IS the source repo its config
113
+ * derives ([#934](https://github.com/Omega-JS-Stack/omega/issues/934)): the ONE
114
+ * comparison, and the ONE wording of its answer, for every reader that holds
115
+ * the two side by side. The boot prelude states it in one line; the manage walk
116
+ * and the deploy, which act ON the derived repo, refuse with it.
58
117
  *
59
- * @param {object} config - Composed omega config (brand + local layers).
60
- * @returns {string} Repo name ('' when nothing in the chain resolves).
118
+ * The WHOLE slug is compared, case-insensitively (GitHub's own comparison), so
119
+ * a rename drifts as surely as a transfer: an owner-only compare let a renamed
120
+ * repo through while every reader kept deriving the old name.
121
+ *
122
+ * @param {string} originSlug - The origin's `Owner/name`.
123
+ * @param {object} config - Composed omega config.
124
+ * @returns {string|null} The drift line, or null when the two agree or the
125
+ * config derives no source repo (no org, or no brand id: nothing to compare).
61
126
  */
62
- function brandRepoName(config) {
63
- const github = githubKeys(config);
64
- // The id half is trimmed like a typed slug: the role suffix is appended to
65
- // it, so untrimmed whitespace would sit INSIDE the composed name.
66
- const id = (config?.brand?.id || '').trim();
67
-
68
- return parseRepoSlug(github.repo).name
69
- || (id ? `${id}-omega` : '');
127
+ function repoDrift(originSlug, config) {
128
+ const derived = sourceRepo(config);
129
+ if (!derived || derived.slug.toLowerCase() === originSlug.toLowerCase()) return null;
130
+
131
+ return `origin is ${originSlug} but config derives ${derived.slug}: fix repo.org in config/omega.json5 or move the repo`;
70
132
  }
71
133
 
72
134
  /**
73
- * The brand repo's owner (GitHub org or user).
135
+ * The brand's ONE public releases repo: `<brand.id>-releases` under the same
136
+ * org, ALWAYS public, because the desktop updater polls it with no token
137
+ * ([#620](https://github.com/Omega-JS-Stack/omega/issues/620),
138
+ * [#799](https://github.com/Omega-JS-Stack/omega/issues/799)).
74
139
  *
75
- * @param {object} config - Composed omega config (brand + local layers).
76
- * @returns {string} Owner ('' when nothing in the chain resolves).
140
+ * It is the release channel for EVERY target's built artifacts (desktop
141
+ * installers and the extension's zips alike, tagged per target) and the one
142
+ * home of that address: the site's download links, @omega.js/desktop's
143
+ * electron-builder publish block and its finalize-release upload all read it
144
+ * here, so the feed a shipped app polls can never disagree with the URL a
145
+ * download button carries.
146
+ *
147
+ * @param {object} config - Composed omega config.
148
+ * @returns {{ owner: string, name: string, slug: string }|null} Null when the config names no org or no brand id.
149
+ */
150
+ function releasesRepo(config) {
151
+ return derivedRepo(config, 'releases');
152
+ }
153
+
154
+ /**
155
+ * A declared target's entry, by name.
156
+ * @param {object} config - Composed omega config carrying `targets`.
157
+ * @param {string} name - The target name.
158
+ * @returns {object} The entry.
159
+ * @throws {Error} When the config declares no target of that name.
77
160
  */
78
- function brandRepoOwner(config) {
79
- const github = githubKeys(config);
80
- return parseRepoSlug(github.repo).owner || github.org || '';
161
+ function targetEntry(config, name) {
162
+ const targets = config && config.targets;
163
+ const entry = isPlainObject(targets) ? targets[name] : undefined;
164
+
165
+ if (!isPlainObject(entry)) {
166
+ const declared = isPlainObject(targets) ? Object.keys(targets) : [];
167
+ throw new Error(`No target "${name}" is declared: this brand declares [${declared.join(', ')}]`);
168
+ }
169
+
170
+ return entry;
81
171
  }
82
172
 
83
173
  /**
84
- * The brand repo as ONE finished value: owner, name, and the "owner/name" slug
85
- * the GitHub API takes. This is the form a FRAMEWORK hands to consumer code —
86
- * @omega.js/backend exposes it as `config.resolved.github` ([#290](https://github.com/Omega-JS-Stack/omega/issues/290)),
87
- * because a brand target cannot require this private package and must never
88
- * re-derive the merge rules for itself.
174
+ * Where a WEB target is served from: its own `hosting.provider`, else the
175
+ * default `github`.
176
+ * @param {object} config - Composed omega config carrying `targets`.
177
+ * @param {string} name - The target name.
178
+ * @returns {string} A HOSTING_PROVIDERS value.
179
+ * @throws {Error} When the config declares no target of that name.
180
+ */
181
+ function hostingProvider(config, name) {
182
+ const entry = targetEntry(config, name);
183
+ const hosting = isPlainObject(entry.hosting) ? entry.hosting : {};
184
+
185
+ return hosting.provider || DEFAULT_HOSTING_PROVIDER;
186
+ }
187
+
188
+ /**
189
+ * A web target's own repo: `<brand.id>-<target name>` under the brand's org,
190
+ * holding the BUILT site only (one force-orphan commit on `gh-pages`, served
191
+ * by Pages at the target's url). The source monorepo stays private in a free
192
+ * org this way, which is what a shared `gh-pages` branch on it made impossible.
89
193
  *
90
- * @param {object} config - Composed omega config (brand + local layers).
91
- * @returns {{ owner: string, name: string, repo: string }} `repo` is the slug, '' unless BOTH halves resolve (half an address addresses nothing).
194
+ * Only the target's NAME distinguishes it, per the `<brand.id>-<role>` rule, so
195
+ * a second web target named `community` publishes to `<brand.id>-community`.
196
+ *
197
+ * @param {object} config - Composed omega config carrying `targets`.
198
+ * @param {string} name - The web target's name.
199
+ * @returns {{ owner: string, name: string, slug: string }|null} Null when the config names no org or no brand id, and null when another provider hosts this target (there is no GitHub repo to address then).
200
+ * @throws {Error} When the name is not declared, or names a target that is not web.
92
201
  */
93
- function brandRepo(config) {
94
- const owner = brandRepoOwner(config);
95
- const name = brandRepoName(config);
202
+ function websiteRepo(config, name) {
203
+ const entry = targetEntry(config, name);
96
204
 
97
- return { owner, name, repo: owner && name ? `${owner}/${name}` : '' };
205
+ if (entry.type !== 'web') {
206
+ throw new Error(`targets.${name} is a ${entry.type} target: only a web target is served from its own website repo`);
207
+ }
208
+
209
+ if (hostingProvider(config, name) !== 'github') return null;
210
+
211
+ return derivedRepo(config, name);
98
212
  }
99
213
 
100
214
  /**
101
- * The releases keys the derivation reads: the desktop target's own `releases`
102
- * block, overlaid by the top-level one a desktop-resolved config carries (the
103
- * merge chain overlays `targets.desktop` onto the top level, so the overlay is
104
- * the resolved value). Both shapes are live: the site global reads a raw config,
105
- * where the block sits under the target, while every desktop verb reads the
106
- * resolved one.
107
- *
108
- * @param {object} config - Composed omega config (raw or desktop-resolved).
109
- * @returns {object} Merged `{ enabled, owner, repo }` keys.
215
+ * The GitHub Pages custom domain a web target serves at: the bare host of its
216
+ * `targetUrl`. The ONE derivation of it, asked by the manage walk (which sets
217
+ * the domain on the repo) and by the web deploy (which writes the CNAME file
218
+ * the push publishes) alike, so the two can never claim different domains for
219
+ * one site.
220
+ *
221
+ * A `*.github.io` host is NOT a custom domain
222
+ * ([#366](https://github.com/Omega-JS-Stack/omega/issues/366)): a project
223
+ * site's url names its PAGES address, and reading that as a domain would have
224
+ * the deploy claim `<owner>.github.io` in a CNAME and mount the build at `/`,
225
+ * where every asset 404s.
226
+ *
227
+ * @param {object} config - Composed omega config carrying `brand` and `targets`.
228
+ * @param {string} name - The web target's name.
229
+ * @returns {string} The bare host, or '' when the target has no custom domain (or no url at all).
110
230
  */
111
- function releasesKeys(config) {
112
- const target = config?.targets?.desktop;
113
- const entry = target && typeof target === 'object' && !Array.isArray(target) ? target.releases : undefined;
231
+ function pagesHost(config, name) {
232
+ const targets = config && config.targets;
233
+ // A target the config does not declare has no derivable url of its own: a
234
+ // standalone project scaffolded before its first manage walk is the whole
235
+ // site, so the brand's own url is the answer rather than a `<name>.` host
236
+ // invented for a sibling that does not exist.
237
+ const url = isPlainObject(targets) && isPlainObject(targets[name])
238
+ ? targetUrl(config, name)
239
+ : (config && config.brand ? config.brand.url : '');
240
+
241
+ const host = brandHost(String(url || ''));
114
242
 
115
- return { ...(entry || {}), ...(config?.releases || {}) };
243
+ return DEFAULT_PAGES_HOST.test(host) ? '' : host;
116
244
  }
117
245
 
118
246
  /**
119
- * The brand's ONE public releases repo: where the versioned `v<x.y.z>` releases
120
- * that feed electron-updater live, and where the website's versionless
121
- * `/releases/latest/download/<asset>` links point ([#620](https://github.com/Omega-JS-Stack/omega/issues/620),
122
- * [#799](https://github.com/Omega-JS-Stack/omega/issues/799)). One repo per
123
- * brand, named after it: `<brand.id>-releases` by default, under the brand
124
- * repo's own owner, so a brand that declares nothing still has an address.
125
- *
126
- * This is the ONE home of that rule. Every reader takes it from here (the site
127
- * global's download links, @omega.js/desktop's electron-builder publish block,
128
- * its release-repo provisioning, its finalize-release upload), so the feed a
129
- * shipped app polls and the URL a download button carries can never disagree.
130
- *
131
- * @param {object} config - Composed omega config (raw or desktop-resolved).
132
- * @returns {{ owner: string, name: string, repo: string }} `repo` is the slug, '' unless BOTH halves resolve (half an address addresses nothing).
247
+ * The brand monorepo's visibility, from the ONE place that states it: the brand
248
+ * root's `package.json` `private` field. `true` or ABSENT is private (every
249
+ * monorepo is private by default, Ian 2026-09-11); only a literal `false` is a
250
+ * public brand. The manage walk reconciles the repo to this in both directions.
251
+ *
252
+ * @param {string} brandRoot - The brand root directory.
253
+ * @returns {string} 'private' | 'public'.
133
254
  */
134
- function releasesRepo(config) {
135
- const releases = releasesKeys(config);
136
- const owner = releases.owner || brandRepoOwner(config);
137
- const name = releases.repo || (config?.brand?.id ? `${config.brand.id}-releases` : '');
255
+ function brandVisibility(brandRoot) {
256
+ let contents;
257
+
258
+ try {
259
+ contents = fs.readFileSync(path.join(brandRoot, 'package.json'), 'utf8');
260
+ } catch (e) {
261
+ // A brand with no manifest at all is private like any other: the default is
262
+ // the safe half of the question, and the walk never guesses public. Any
263
+ // OTHER read failure (a permission error, a directory in its place) is this
264
+ // machine being broken, and answering 'private' for it would hide the break
265
+ // behind a plausible answer.
266
+ if (e.code === 'ENOENT') return 'private';
267
+ throw e;
268
+ }
269
+
270
+ // A manifest that does not parse is a brand nobody can build: it fails here,
271
+ // loudly, rather than reporting a visibility read off a file nothing read.
272
+ const manifest = JSON.parse(contents);
138
273
 
139
- return { owner, name, repo: owner && name ? `${owner}/${name}` : '' };
274
+ return manifest && manifest.private === false ? 'public' : 'private';
140
275
  }
141
276
 
142
- module.exports = { parseRepoSlug, brandRepoName, brandRepoOwner, brandRepo, releasesRepo };
277
+ module.exports = {
278
+ REPO_PROVIDERS,
279
+ HOSTING_PROVIDERS,
280
+ repoBlock,
281
+ sourceRepo,
282
+ repoDrift,
283
+ releasesRepo,
284
+ websiteRepo,
285
+ hostingProvider,
286
+ pagesHost,
287
+ brandVisibility,
288
+ };