@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,142 +1,264 @@
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.
30
50
  *
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.
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).
55
+ *
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).
47
- *
48
- * @param {object} config - Composed omega config (brand + local layers).
49
- * @returns {object} Merged `{ org, repo, … }` keys.
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}
50
85
  */
51
- function githubKeys(config) {
52
- return { ...(config?.repo?.providers?.github || {}), ...(config?.github || {}) };
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}` };
53
94
  }
54
95
 
55
96
  /**
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.
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.
58
100
  *
59
- * @param {object} config - Composed omega config (brand + local layers).
60
- * @returns {string} Repo name ('' when nothing in the chain resolves).
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.
61
106
  */
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` : '');
107
+ function sourceRepo(config) {
108
+ return derivedRepo(config, 'omega');
70
109
  }
71
110
 
72
111
  /**
73
- * The brand repo's owner (GitHub org or user).
112
+ * The brand's ONE public releases repo: `<brand.id>-releases` under the same
113
+ * org, ALWAYS public, because the desktop updater polls it with no token
114
+ * ([#620](https://github.com/Omega-JS-Stack/omega/issues/620),
115
+ * [#799](https://github.com/Omega-JS-Stack/omega/issues/799)).
74
116
  *
75
- * @param {object} config - Composed omega config (brand + local layers).
76
- * @returns {string} Owner ('' when nothing in the chain resolves).
117
+ * It is the release channel for EVERY target's built artifacts (desktop
118
+ * installers and the extension's zips alike, tagged per target) and the one
119
+ * home of that address: the site's download links, @omega.js/desktop's
120
+ * electron-builder publish block and its finalize-release upload all read it
121
+ * here, so the feed a shipped app polls can never disagree with the URL a
122
+ * download button carries.
123
+ *
124
+ * @param {object} config - Composed omega config.
125
+ * @returns {{ owner: string, name: string, slug: string }|null} Null when the config names no org or no brand id.
126
+ */
127
+ function releasesRepo(config) {
128
+ return derivedRepo(config, 'releases');
129
+ }
130
+
131
+ /**
132
+ * A declared target's entry, by name.
133
+ * @param {object} config - Composed omega config carrying `targets`.
134
+ * @param {string} name - The target name.
135
+ * @returns {object} The entry.
136
+ * @throws {Error} When the config declares no target of that name.
137
+ */
138
+ function targetEntry(config, name) {
139
+ const targets = config && config.targets;
140
+ const entry = isPlainObject(targets) ? targets[name] : undefined;
141
+
142
+ if (!isPlainObject(entry)) {
143
+ const declared = isPlainObject(targets) ? Object.keys(targets) : [];
144
+ throw new Error(`No target "${name}" is declared: this brand declares [${declared.join(', ')}]`);
145
+ }
146
+
147
+ return entry;
148
+ }
149
+
150
+ /**
151
+ * Where a WEB target is served from: its own `hosting.provider`, else the
152
+ * default `github`.
153
+ * @param {object} config - Composed omega config carrying `targets`.
154
+ * @param {string} name - The target name.
155
+ * @returns {string} A HOSTING_PROVIDERS value.
156
+ * @throws {Error} When the config declares no target of that name.
77
157
  */
78
- function brandRepoOwner(config) {
79
- const github = githubKeys(config);
80
- return parseRepoSlug(github.repo).owner || github.org || '';
158
+ function hostingProvider(config, name) {
159
+ const entry = targetEntry(config, name);
160
+ const hosting = isPlainObject(entry.hosting) ? entry.hosting : {};
161
+
162
+ return hosting.provider || DEFAULT_HOSTING_PROVIDER;
81
163
  }
82
164
 
83
165
  /**
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.
166
+ * A web target's own repo: `<brand.id>-<target name>` under the brand's org,
167
+ * holding the BUILT site only (one force-orphan commit on `gh-pages`, served
168
+ * by Pages at the target's url). The source monorepo stays private in a free
169
+ * org this way, which is what a shared `gh-pages` branch on it made impossible.
89
170
  *
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).
171
+ * Only the target's NAME distinguishes it, per the `<brand.id>-<role>` rule, so
172
+ * a second web target named `community` publishes to `<brand.id>-community`.
173
+ *
174
+ * @param {object} config - Composed omega config carrying `targets`.
175
+ * @param {string} name - The web target's name.
176
+ * @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).
177
+ * @throws {Error} When the name is not declared, or names a target that is not web.
92
178
  */
93
- function brandRepo(config) {
94
- const owner = brandRepoOwner(config);
95
- const name = brandRepoName(config);
179
+ function websiteRepo(config, name) {
180
+ const entry = targetEntry(config, name);
96
181
 
97
- return { owner, name, repo: owner && name ? `${owner}/${name}` : '' };
182
+ if (entry.type !== 'web') {
183
+ throw new Error(`targets.${name} is a ${entry.type} target: only a web target is served from its own website repo`);
184
+ }
185
+
186
+ if (hostingProvider(config, name) !== 'github') return null;
187
+
188
+ return derivedRepo(config, name);
98
189
  }
99
190
 
100
191
  /**
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.
192
+ * The GitHub Pages custom domain a web target serves at: the bare host of its
193
+ * `targetUrl`. The ONE derivation of it, asked by the manage walk (which sets
194
+ * the domain on the repo) and by the web deploy (which writes the CNAME file
195
+ * the push publishes) alike, so the two can never claim different domains for
196
+ * one site.
197
+ *
198
+ * A `*.github.io` host is NOT a custom domain
199
+ * ([#366](https://github.com/Omega-JS-Stack/omega/issues/366)): a project
200
+ * site's url names its PAGES address, and reading that as a domain would have
201
+ * the deploy claim `<owner>.github.io` in a CNAME and mount the build at `/`,
202
+ * where every asset 404s.
203
+ *
204
+ * @param {object} config - Composed omega config carrying `brand` and `targets`.
205
+ * @param {string} name - The web target's name.
206
+ * @returns {string} The bare host, or '' when the target has no custom domain (or no url at all).
110
207
  */
111
- function releasesKeys(config) {
112
- const target = config?.targets?.desktop;
113
- const entry = target && typeof target === 'object' && !Array.isArray(target) ? target.releases : undefined;
208
+ function pagesHost(config, name) {
209
+ const targets = config && config.targets;
210
+ // A target the config does not declare has no derivable url of its own: a
211
+ // standalone project scaffolded before its first manage walk is the whole
212
+ // site, so the brand's own url is the answer rather than a `<name>.` host
213
+ // invented for a sibling that does not exist.
214
+ const url = isPlainObject(targets) && isPlainObject(targets[name])
215
+ ? targetUrl(config, name)
216
+ : (config && config.brand ? config.brand.url : '');
217
+
218
+ const host = brandHost(String(url || ''));
114
219
 
115
- return { ...(entry || {}), ...(config?.releases || {}) };
220
+ return DEFAULT_PAGES_HOST.test(host) ? '' : host;
116
221
  }
117
222
 
118
223
  /**
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).
224
+ * The brand monorepo's visibility, from the ONE place that states it: the brand
225
+ * root's `package.json` `private` field. `true` or ABSENT is private (every
226
+ * monorepo is private by default, Ian 2026-09-11); only a literal `false` is a
227
+ * public brand. The manage walk reconciles the repo to this in both directions.
228
+ *
229
+ * @param {string} brandRoot - The brand root directory.
230
+ * @returns {string} 'private' | 'public'.
133
231
  */
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` : '');
232
+ function brandVisibility(brandRoot) {
233
+ let contents;
234
+
235
+ try {
236
+ contents = fs.readFileSync(path.join(brandRoot, 'package.json'), 'utf8');
237
+ } catch (e) {
238
+ // A brand with no manifest at all is private like any other: the default is
239
+ // the safe half of the question, and the walk never guesses public. Any
240
+ // OTHER read failure (a permission error, a directory in its place) is this
241
+ // machine being broken, and answering 'private' for it would hide the break
242
+ // behind a plausible answer.
243
+ if (e.code === 'ENOENT') return 'private';
244
+ throw e;
245
+ }
246
+
247
+ // A manifest that does not parse is a brand nobody can build: it fails here,
248
+ // loudly, rather than reporting a visibility read off a file nothing read.
249
+ const manifest = JSON.parse(contents);
138
250
 
139
- return { owner, name, repo: owner && name ? `${owner}/${name}` : '' };
251
+ return manifest && manifest.private === false ? 'public' : 'private';
140
252
  }
141
253
 
142
- module.exports = { parseRepoSlug, brandRepoName, brandRepoOwner, brandRepo, releasesRepo };
254
+ module.exports = {
255
+ REPO_PROVIDERS,
256
+ HOSTING_PROVIDERS,
257
+ repoBlock,
258
+ sourceRepo,
259
+ releasesRepo,
260
+ websiteRepo,
261
+ hostingProvider,
262
+ pagesHost,
263
+ brandVisibility,
264
+ };