@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
@@ -0,0 +1,425 @@
1
+ /**
2
+ * brand-version: the brand's ONE version
3
+ * ([#869](https://github.com/Omega-JS-Stack/omega/issues/869)).
4
+ *
5
+ * A brand root's `package.json` `version` is the master and every
6
+ * `targets/<name>/package.json` follows it. Targets keep a real `version`
7
+ * field because every tool reads it there (electron-builder, the extension
8
+ * manifest, npm), so nothing needs plumbing in dev or CI; what they never do
9
+ * is move on their own.
10
+ *
11
+ * Two halves, one rule:
12
+ * - `bumpBrandVersion` is the ONE writer, behind `omega bump` at the brand
13
+ * root. It never commits and never tags: the ship flow owns the
14
+ * `chore(release)` commit.
15
+ * - `assertBrandVersion` is the gate every framework's `omega deploy` runs
16
+ * before its secrets precheck, so a target that drifted is refused before
17
+ * anything is published or published to.
18
+ *
19
+ * Twin in shape of the manager's `assertFamilyVersions`
20
+ * ([#794](https://github.com/Omega-JS-Stack/omega/issues/794)), which refuses a
21
+ * mixed @omega.js FAMILY. Same verdict, different subject: that one is about
22
+ * the framework packages a brand installed, this one about the brand's own
23
+ * release number.
24
+ *
25
+ * Beside it, the LOCKFILE gate
26
+ * ([#938](https://github.com/Omega-JS-Stack/omega/issues/938)): the registry
27
+ * deploy lane ships the brand's own `package-lock.json`, and the runner's
28
+ * `npm ci` installs exactly what it says, so `assertBrandLockfile` refuses a
29
+ * lock that disagrees with the manifests' @omega.js specs before anything is
30
+ * pushed. `brandLockfileDrift` is the read both the gate and the lock
31
+ * regeneration (`lockfile.js`) share.
32
+ */
33
+ const path = require('path');
34
+ const jetpack = require('fs-jetpack');
35
+
36
+ const { resolveBrandRoot } = require('../config/index.js');
37
+ const Logger = require('./logger.js');
38
+ const { isRegistrySpec, resolveWanted } = require('./update.js');
39
+
40
+ const defaultLogger = new Logger('brand-version');
41
+
42
+ // What `omega bump` takes, in the order it prints them.
43
+ const KINDS = ['patch', 'minor', 'major'];
44
+
45
+ // A plain release number and nothing else: a prerelease tag is refused by name
46
+ // rather than guessed at (which digit does `patch` move on `1.0.0-beta.2`?).
47
+ const RELEASE = /^(\d+)\.(\d+)\.(\d+)$/;
48
+
49
+ /**
50
+ * A refusal: crafted text a human is meant to read, flagged so every surface
51
+ * prints it the same way (the message alone, nothing added), the same shape
52
+ * scaffold-guard's refusals take.
53
+ *
54
+ * @param {string} message - The lines to print verbatim.
55
+ * @returns {Error} The flagged error to throw.
56
+ */
57
+ function refusal(message) {
58
+ const error = new Error(message);
59
+ error.refusal = true;
60
+ return error;
61
+ }
62
+
63
+ /**
64
+ * The brand's version, resolved from anywhere inside the brand.
65
+ *
66
+ * @param {object} options
67
+ * @param {string} options.dir - Any directory inside the brand (root or target).
68
+ * @returns {{ brandRoot: string, version: string }|null} null when `dir` is not inside a brand.
69
+ * @throws {Error} A refusal when the brand root carries no `version`: the
70
+ * master number cannot be defaulted, or every target would follow a guess.
71
+ */
72
+ function readBrandVersion({ dir }) {
73
+ const brandRoot = resolveBrandRoot(dir);
74
+ if (!brandRoot) return null;
75
+
76
+ const manifest = path.join(brandRoot, 'package.json');
77
+ const version = (jetpack.read(manifest, 'json') || {}).version;
78
+
79
+ if (!version) {
80
+ throw refusal(
81
+ `${manifest} carries no "version", and the brand root is the one version every target follows (#869).\n`
82
+ + ` fix: add "version" to ${manifest}, then run \`omega bump\` at the brand root`,
83
+ );
84
+ }
85
+
86
+ return { brandRoot, version };
87
+ }
88
+
89
+ /**
90
+ * Every target that carries a package.json, by name. A directory under
91
+ * `targets/` with no manifest is not a target (the manager's `discoverTargets`
92
+ * rule), and the list is sorted so a bump's output reads the same every run.
93
+ *
94
+ * @param {object} options
95
+ * @param {string} options.brandRoot - Brand monorepo root.
96
+ * @returns {Array<{ name: string, dir: string, manifest: string }>}
97
+ */
98
+ function listTargetPackages({ brandRoot }) {
99
+ const targetsDir = path.join(brandRoot, 'targets');
100
+
101
+ return (jetpack.list(targetsDir) || [])
102
+ .filter((name) => !name.startsWith('.'))
103
+ .sort()
104
+ .map((name) => ({
105
+ name,
106
+ dir: path.join(targetsDir, name),
107
+ manifest: path.join(targetsDir, name, 'package.json'),
108
+ }))
109
+ .filter((entry) => jetpack.exists(entry.manifest) === 'file');
110
+ }
111
+
112
+ /**
113
+ * The next release number for a kind.
114
+ *
115
+ * @param {string} previous - The brand's current version.
116
+ * @param {'patch'|'minor'|'major'} kind - Which digit moves.
117
+ * @returns {string} The next version.
118
+ * @throws {Error} A refusal when `previous` is not a plain release number.
119
+ */
120
+ function nextVersion(previous, kind) {
121
+ const parts = RELEASE.exec(String(previous));
122
+
123
+ if (!parts) {
124
+ throw refusal(
125
+ `the brand version "${previous}" is not a plain major.minor.patch, and \`omega bump\` moves whole releases with no prerelease handling.\n`
126
+ + ' fix: set a plain version in the brand root package.json first',
127
+ );
128
+ }
129
+
130
+ const [major, minor, patch] = parts.slice(1).map(Number);
131
+
132
+ if (kind === 'major') return `${major + 1}.0.0`;
133
+ if (kind === 'minor') return `${major}.${minor + 1}.0`;
134
+ return `${major}.${minor}.${patch + 1}`;
135
+ }
136
+
137
+ /**
138
+ * The same content with `version` set, gaining the key after `name` when the
139
+ * file never had one, so a target's first bump reads like every sibling's
140
+ * manifest instead of appending a stray key at the end.
141
+ *
142
+ * @param {object} content - The parsed manifest (or lock entry).
143
+ * @param {string} next - The version to write.
144
+ * @returns {object} The content to write back.
145
+ */
146
+ function withVersion(content, next) {
147
+ if (content.version !== undefined) return { ...content, version: next };
148
+
149
+ const updated = content.name === undefined ? { version: next } : {};
150
+ for (const [key, value] of Object.entries(content)) {
151
+ updated[key] = value;
152
+ if (key === 'name') updated.version = next;
153
+ }
154
+
155
+ return updated;
156
+ }
157
+
158
+ /**
159
+ * Write a JSON file the way npm writes them: 2-space indent, trailing newline.
160
+ *
161
+ * @param {string} file - Where to write.
162
+ * @param {object} content - What to write.
163
+ * @returns {void}
164
+ */
165
+ function writeJson(file, content) {
166
+ jetpack.write(file, `${JSON.stringify(content, null, 2)}\n`);
167
+ }
168
+
169
+ /**
170
+ * Move the brand and every target onto the next version. The ONE writer.
171
+ *
172
+ * Writes the brand root manifest, every target manifest (a target with no
173
+ * `version` GAINS one: every target follows), and the brand lock's entry for
174
+ * each target plus its own root version where the lock carries one, since a
175
+ * lock left behind would have `npm ci` install the old number.
176
+ *
177
+ * @param {object} options
178
+ * @param {string} options.brandRoot - Brand monorepo root.
179
+ * @param {'patch'|'minor'|'major'} options.kind - Which digit moves.
180
+ * @param {object} [options.logger] - Where the per-file lines go.
181
+ * @returns {{ previous: string, next: string, files: string[] }} Paths relative to the brand root.
182
+ * @throws {Error} A refusal on an unknown kind, a missing root version, or a prerelease one.
183
+ */
184
+ function bumpBrandVersion({ brandRoot, kind, logger = defaultLogger }) {
185
+ if (!KINDS.includes(kind)) {
186
+ throw refusal(`\`omega bump\` takes ${KINDS.join(', ')}, and "${kind}" is none of them.`);
187
+ }
188
+
189
+ const previous = readBrandVersion({ dir: brandRoot }).version;
190
+ const next = nextVersion(previous, kind);
191
+ const files = [];
192
+
193
+ // One line per file, always `<old> -> <new>`; a file that had no version
194
+ // says so rather than printing an empty left-hand side.
195
+ const write = (file, old) => {
196
+ const relative = path.relative(brandRoot, file);
197
+ files.push(relative);
198
+ logger.log(`${relative} ${old || '(none)'} -> ${next}`);
199
+ };
200
+
201
+ const rootManifest = path.join(brandRoot, 'package.json');
202
+ writeJson(rootManifest, withVersion(jetpack.read(rootManifest, 'json'), next));
203
+ write(rootManifest, previous);
204
+
205
+ const targets = listTargetPackages({ brandRoot });
206
+
207
+ for (const target of targets) {
208
+ const content = jetpack.read(target.manifest, 'json');
209
+ writeJson(target.manifest, withVersion(content, next));
210
+ write(target.manifest, content.version);
211
+ }
212
+
213
+ const lockPath = path.join(brandRoot, 'package-lock.json');
214
+ const lock = jetpack.exists(lockPath) === 'file' ? jetpack.read(lockPath, 'json') : null;
215
+
216
+ if (lock && lock.packages) {
217
+ for (const target of targets) {
218
+ const key = `targets/${target.name}`;
219
+ if (lock.packages[key]) lock.packages[key] = withVersion(lock.packages[key], next);
220
+ }
221
+
222
+ // The lock's own root number exists only when npm wrote one, and npm
223
+ // writes it from the root manifest: where it is absent, the next install
224
+ // is what puts it there.
225
+ if (lock.version) lock.version = next;
226
+ if (lock.packages[''] && lock.packages[''].version) lock.packages[''].version = next;
227
+
228
+ writeJson(lockPath, lock);
229
+ write(lockPath, previous);
230
+ }
231
+
232
+ return { previous, next, files };
233
+ }
234
+
235
+ /**
236
+ * The deploy gate: refuse a target whose version has drifted from the brand's.
237
+ *
238
+ * Read-only, so every lane runs it (`--direct` and `--dry-run` included), and
239
+ * it runs BEFORE the secrets precheck: a drifted target must not push its
240
+ * secrets and dispatch a build of the wrong number. A match says nothing.
241
+ *
242
+ * @param {object} options
243
+ * @param {string} options.dir - The target directory being deployed.
244
+ * @returns {{ brandRoot: string, name: string, version: string }|null} null when there is nothing to compare.
245
+ * @throws {Error} A refusal naming the target and `omega bump`.
246
+ */
247
+ function assertBrandVersion({ dir }) {
248
+ const brandRoot = resolveBrandRoot(dir);
249
+ if (!brandRoot) return null;
250
+
251
+ // Only a target follows the brand. A brand root, or any other directory
252
+ // inside the brand, has nothing of its own to compare.
253
+ const segments = path.relative(brandRoot, path.resolve(dir)).split(path.sep);
254
+ if (segments[0] !== 'targets' || !segments[1]) return null;
255
+
256
+ const name = segments[1];
257
+ const manifest = path.join(brandRoot, 'targets', name, 'package.json');
258
+ if (jetpack.exists(manifest) !== 'file') return null;
259
+
260
+ const found = (jetpack.read(manifest, 'json') || {}).version;
261
+ const { version } = readBrandVersion({ dir: brandRoot });
262
+
263
+ if (found !== version) {
264
+ throw refusal(
265
+ `targets/${name} ${found ? `is ${found}` : 'has no version'} but the brand is ${version}; `
266
+ + 'run `omega bump` at the brand root (#869).',
267
+ );
268
+ }
269
+
270
+ return { brandRoot, name, version };
271
+ }
272
+
273
+ // The family's scope: the only dependencies the lockfile gate reads.
274
+ const SCOPE = '@omega.js/';
275
+
276
+ // A registry entry's `resolved` is a URL; a link target, a packed tarball or a
277
+ // directory is a path, which a runner has nothing at.
278
+ const REGISTRY_RESOLVED = /^https?:\/\//;
279
+
280
+ /**
281
+ * The lock entry npm would use for `name` as declared by the manifest at
282
+ * `where`: its own nested copy first, then the hoisted one at the root, the
283
+ * order node resolves in.
284
+ *
285
+ * @param {object} packages - The lock's `packages` map.
286
+ * @param {string} where - The manifest's dir relative to the root ('' for the root).
287
+ * @param {string} name - The dependency name.
288
+ * @returns {string|null} The lock key, or null when neither slot exists.
289
+ */
290
+ function lockKeyFor(packages, where, name) {
291
+ const keys = where ? [`${where}/node_modules/${name}`, `node_modules/${name}`] : [`node_modules/${name}`];
292
+ return keys.find((key) => packages[key]) || null;
293
+ }
294
+
295
+ /**
296
+ * Why a lock entry cannot satisfy a registry spec on a runner, or null when it
297
+ * does.
298
+ *
299
+ * @param {object|undefined} entry - The lock entry.
300
+ * @param {string} spec - The manifest's registry spec.
301
+ * @returns {string|null} The reason, worded to follow `<name> <spec> `.
302
+ */
303
+ function entryDrift(entry, spec) {
304
+ if (!entry) return 'is not in the lock';
305
+ if (entry.link) return `is locked as a link to ${entry.resolved}`;
306
+ if (entry.resolved && !REGISTRY_RESOLVED.test(entry.resolved)) return `is locked at the path ${entry.resolved}`;
307
+ if (resolveWanted(spec, [entry.version]) !== entry.version) return `is locked at ${entry.version || 'no version'}`;
308
+ return null;
309
+ }
310
+
311
+ /**
312
+ * The lock's STALE path entries: a key that names a folder under the root (a
313
+ * workspace, or a link target) rather than a `node_modules/` slot, where that
314
+ * folder is not on disk. A renamed target leaves one behind (`targets/website`
315
+ * after the move to `targets/web`), and npm keeps honoring the dependencies it
316
+ * declares on every lock-only run.
317
+ *
318
+ * @param {object} options
319
+ * @param {string} options.root - The install root the lock's keys are relative to.
320
+ * @param {object} options.packages - The lock's `packages` map.
321
+ * @returns {string[]} The stale keys, nested ones included.
322
+ */
323
+ function staleLockPaths({ root, packages }) {
324
+ return Object.keys(packages).filter((key) => key !== ''
325
+ && !key.split('/').includes('node_modules')
326
+ && !jetpack.exists(path.join(root, key)));
327
+ }
328
+
329
+ /**
330
+ * Every @omega.js registry spec in the brand's manifests (the root and each
331
+ * `targets/<name>/package.json`) that the brand's OWN lockfile does not
332
+ * satisfy. Read-only. `file:`/`link:`/git specs are the linked lane's, which
333
+ * regenerates its lock in the pack step, so only registry specs are read. A
334
+ * STALE lock entry (`staleLockPaths`) declaring an @omega.js spec that is NOT a
335
+ * registry one is drift too: npm would re-create the link it names on the next
336
+ * lock-only run.
337
+ *
338
+ * @param {object} options
339
+ * @param {string} options.root - The install root (a brand root, or a standalone target).
340
+ * @returns {{ lockPath: string, lock: object|null, drift: Array<{ manifest: string, name: string, spec: string, key: string|null, entry: object|undefined, reason: string }> }}
341
+ * `lock` is null when the root has none; `manifest` is the declaring
342
+ * manifest's dir relative to the root, `package.json` for the root itself.
343
+ */
344
+ function brandLockfileDrift({ root }) {
345
+ const lockPath = path.join(root, 'package-lock.json');
346
+ const lock = jetpack.exists(lockPath) === 'file' ? jetpack.read(lockPath, 'json') : null;
347
+ const packages = (lock && lock.packages) || {};
348
+
349
+ const manifests = [
350
+ { where: '', file: path.join(root, 'package.json') },
351
+ ...listTargetPackages({ brandRoot: root }).map((target) => ({ where: `targets/${target.name}`, file: target.manifest })),
352
+ ];
353
+
354
+ const drift = [];
355
+
356
+ for (const { where, file } of manifests) {
357
+ const pkg = jetpack.read(file, 'json') || {};
358
+ const declared = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
359
+
360
+ for (const [name, spec] of Object.entries(declared)) {
361
+ if (!name.startsWith(SCOPE) || !isRegistrySpec(spec)) continue;
362
+
363
+ const key = lockKeyFor(packages, where, name);
364
+ const entry = key ? packages[key] : undefined;
365
+ const reason = entryDrift(entry, spec);
366
+
367
+ if (reason) drift.push({ manifest: where || 'package.json', name, spec, key, entry, reason });
368
+ }
369
+ }
370
+
371
+ for (const stale of staleLockPaths({ root, packages })) {
372
+ const declared = { ...(packages[stale].dependencies || {}), ...(packages[stale].devDependencies || {}) };
373
+
374
+ for (const [name, spec] of Object.entries(declared)) {
375
+ if (!name.startsWith(SCOPE) || isRegistrySpec(spec)) continue;
376
+
377
+ // No key: nothing here is the dependency's own entry, and the pruning
378
+ // of stale paths (lockfile.js) is what removes this one.
379
+ drift.push({ manifest: stale, name, spec, key: null, entry: undefined, reason: `is declared by a stale lock entry (${stale} is not on disk)` });
380
+ }
381
+ }
382
+
383
+ return { lockPath, lock, drift };
384
+ }
385
+
386
+ /**
387
+ * The deploy gate on the registry lane: the brand's lockfile must exist and
388
+ * carry every @omega.js registry spec of its manifests as a registry entry at
389
+ * a version that satisfies it. Read-only, so a dry run runs it too. Agreement
390
+ * says nothing.
391
+ *
392
+ * @param {object} options
393
+ * @param {string} options.root - The brand root the lane snapshots.
394
+ * @returns {void}
395
+ * @throws {Error} A refusal naming each disagreeing entry and `omega i live`.
396
+ */
397
+ function assertBrandLockfile({ root }) {
398
+ const { lockPath, lock, drift } = brandLockfileDrift({ root });
399
+ const fix = ' fix: run `omega i live` in the brand, which regenerates the brand\'s own lockfile from its manifests (#938)';
400
+
401
+ if (!lock) {
402
+ throw refusal(
403
+ `${root} has no package-lock.json, and the deploy runner installs with \`npm ci\`, which needs one.\n${fix}`,
404
+ );
405
+ }
406
+
407
+ if (drift.length) {
408
+ throw refusal(
409
+ `${lockPath} disagrees with the manifests, and the deploy runner's \`npm ci\` installs the lock:\n`
410
+ + drift.map((item) => ` ${item.manifest}: ${item.name} ${item.spec} ${item.reason}\n`).join('')
411
+ + fix,
412
+ );
413
+ }
414
+ }
415
+
416
+ module.exports = {
417
+ readBrandVersion,
418
+ listTargetPackages,
419
+ bumpBrandVersion,
420
+ assertBrandVersion,
421
+ staleLockPaths,
422
+ brandLockfileDrift,
423
+ assertBrandLockfile,
424
+ KINDS,
425
+ };
@@ -0,0 +1,91 @@
1
+ /**
2
+ * build-json: the ONE composer and the ONE writer of `OMEGA_BUILD_JSON`
3
+ * ([#743](https://github.com/Omega-JS-Stack/omega/issues/743)).
4
+ *
5
+ * Every browser surface OMEGA builds (web pages and its service worker,
6
+ * desktop's renderer windows, every extension page and the extension's service
7
+ * worker) reads the same snapshot under the same name, so it is composed once
8
+ * here and delivered once: a single `build.js` at the artifact's web root that
9
+ * every shell loads with one script tag and every worker with one
10
+ * `importScripts` line. The banners and defines each framework grew for itself
11
+ * are gone with it (Ian 2026-09-12: "make it same shape and consumption
12
+ * everywhere as much as possible").
13
+ *
14
+ * Desktop's main and preload bundles are the one exception, and not to this
15
+ * file: they are NODE, they boot from the WHOLE resolved config inside the
16
+ * asar, and they keep carrying it in their own bundle.
17
+ */
18
+
19
+ const jetpack = require('fs-jetpack');
20
+ const { clientConfig } = require('../config/index.js');
21
+
22
+ // The wrapper's `mode`, one shape on every surface
23
+ // ([#894](https://github.com/Omega-JS-Stack/omega/issues/894)): the build's
24
+ // verdict (`environment`), whether it was a BUILD rather than a dev/watch run,
25
+ // and whether it publishes. A surface's own extra verdicts (desktop's
26
+ // `server`) stay inside that surface's Manager and never reach the artifact.
27
+ const MODE_KEYS = ['environment', 'build', 'publish'];
28
+
29
+ /**
30
+ * Compose the snapshot a browser artifact carries.
31
+ * @param {object} input
32
+ * @param {object} input.config - the target's resolved omega.json5.
33
+ * @param {object} input.pkg - the project package (`{ name, version, … }`), the wrapper's `package`.
34
+ * @param {object} input.mode - `{ environment, build, publish }`.
35
+ * @param {object} input.license - the build's license stamp (#320).
36
+ * @param {object} input.facts - the build facts that ride on the config (`runtime`, `environment`, `version`, `buildTime`, `target`, `dev`).
37
+ * @returns {object} `{ config, package, mode, license, builtAt }`.
38
+ * @throws {Error} when `mode` is missing one of its three keys: a half-stated verdict is a build bug, never a default.
39
+ */
40
+ function composeBuildJson({ config, pkg, mode, license, facts }) {
41
+ const missing = MODE_KEYS.filter((key) => !(mode && key in mode));
42
+ if (missing.length) {
43
+ throw new Error(
44
+ `[@omega.js/devkit] OMEGA_BUILD_JSON needs a mode carrying ${MODE_KEYS.join(', ')}, missing: ${missing.join(', ')}.`,
45
+ );
46
+ }
47
+
48
+ return {
49
+ config: clientConfig({ ...config, ...facts }),
50
+ package: pkg,
51
+ mode: { environment: mode.environment, build: mode.build, publish: mode.publish },
52
+ license,
53
+ builtAt: new Date().toISOString(),
54
+ };
55
+ }
56
+
57
+ /**
58
+ * The text of the `build.js` file every surface writes.
59
+ *
60
+ * TWO statements, the same two everywhere. `self` is the one global name that
61
+ * answers in a window and in a worker alike, so there is no IIFE and no
62
+ * `globalThis` dance to read past. The `dev` map is its OWN statement because
63
+ * `omega dev` REWRITES that line per request
64
+ * ([#346](https://github.com/Omega-JS-Stack/omega/issues/346)), so the file on
65
+ * disk stays the build's own snapshot while a served page gets the map of the
66
+ * stack running right now. A build with no local stack writes `null`, the
67
+ * value @omega.js/client already reads as "assume the classics".
68
+ * @param {object} buildJson - composeBuildJson()'s answer.
69
+ * @returns {string} the file text.
70
+ */
71
+ function buildJsSource(buildJson) {
72
+ const { dev = null, ...config } = buildJson.config || {};
73
+
74
+ return `self.OMEGA_BUILD_JSON = ${JSON.stringify({ ...buildJson, config })};\n`
75
+ + `self.OMEGA_BUILD_JSON.config.dev = ${JSON.stringify(dev)};\n`;
76
+ }
77
+
78
+ /**
79
+ * Write `<webRoot>/build.js`.
80
+ * @param {string} webRoot - the artifact's web root (what `/` resolves to for its pages).
81
+ * @param {object} buildJson - composeBuildJson()'s answer.
82
+ * @returns {string} the path written.
83
+ */
84
+ function writeBuildJs(webRoot, buildJson) {
85
+ const file = jetpack.path(webRoot, 'build.js');
86
+ jetpack.write(file, buildJsSource(buildJson));
87
+
88
+ return file;
89
+ }
90
+
91
+ module.exports = { composeBuildJson, buildJsSource, writeBuildJs, MODE_KEYS };
@@ -13,6 +13,8 @@
13
13
  * - the `@dev-only` strip (#18), registered for PRODUCTION builds only
14
14
  * - minify / sourcemap by mode, and `bundle` + `metafile` on
15
15
  * - one timing line per build, watch rebuilds included
16
+ * - `import.meta.url`, kept answering in CommonJS output (#906), so an
17
+ * ESM-only dependency runs bundled
16
18
  *
17
19
  * esbuild is REQUIRED LAZILY, from the CALLING FRAMEWORK's installation rather
18
20
  * than devkit's. Devkit is vendored into each framework's dist at prepare time
@@ -52,6 +54,24 @@ const RESERVED_OPTIONS = ['bundle', 'metafile', 'minify', 'sourcemap', 'plugins'
52
54
  // when it tries to shut down.
53
55
  const MODES = ['build', 'watch'];
54
56
 
57
+ // The `import.meta.url` a CommonJS bundle keeps
58
+ // ([#906](https://github.com/Omega-JS-Stack/omega/issues/906)). esbuild rewrites
59
+ // `import.meta` to `{}` in `format: 'cjs'` output (its documented limitation:
60
+ // `import.meta` only exists in an ES module), so an ESM-only dependency that
61
+ // opens a require of its own with `createRequire(import.meta.url)`, which
62
+ // yargs-parser and a growing share of the registry do, is handed `undefined`
63
+ // and throws ERR_INVALID_ARG_VALUE the moment the app boots. Node's own answer
64
+ // for a CommonJS file is `pathToFileURL(__filename)`, so that is what the
65
+ // reference becomes.
66
+ //
67
+ // It is a define onto an IDENTIFIER plus a banner declaring it, rather than one
68
+ // define carrying the expression, because esbuild refuses an expression as a
69
+ // define value ("must be an entity name or JS literal"). The banner rides every
70
+ // emitted file, so each answers with its OWN path.
71
+ const IMPORT_META_URL = 'import.meta.url';
72
+ const IMPORT_META_URL_NAME = '__omegaImportMetaUrl';
73
+ const IMPORT_META_URL_SHIM = `var ${IMPORT_META_URL_NAME} = require('url').pathToFileURL(__filename).href;`;
74
+
55
75
  /**
56
76
  * esbuild as the CALLING FRAMEWORK resolves it (see the header).
57
77
  * @param {string} frameworkRoot - the framework package's root (the dir holding its package.json)
@@ -130,6 +150,32 @@ function timingPlugin(destination, entryCount, watching) {
130
150
  };
131
151
  }
132
152
 
153
+ /**
154
+ * Keep `import.meta.url` answering in a CommonJS bundle (see IMPORT_META_URL).
155
+ * Composed from the OUTPUT SHAPE rather than wired per caller: every cjs+node
156
+ * build has the hole, and no other output format has it (a browser bundle is
157
+ * iife or esm, and esm keeps `import.meta` natively).
158
+ * @param {object} config - the composed esbuild options, edited in place
159
+ * @throws {Error} when the caller defines the same key, the RESERVED_OPTIONS rule:
160
+ * two answers for one reference is a programmer error, not a merge
161
+ */
162
+ function keepImportMetaUrl(config) {
163
+ if (config.format !== 'cjs' || config.platform !== 'node') return;
164
+
165
+ if (config.define && IMPORT_META_URL in config.define) {
166
+ throw new Error(`[devkit bundle] \`${IMPORT_META_URL}\` is composed by bundle() for CommonJS output and cannot be defined by a caller: a cjs+node build always gets \`pathToFileURL(__filename)\`, so a second answer would silently win or lose depending on merge order`);
167
+ }
168
+
169
+ config.define = { ...config.define, [IMPORT_META_URL]: IMPORT_META_URL_NAME };
170
+ // The shim is declared FIRST and the caller's banner rides below it,
171
+ // unchanged: anything the banner itself does could read the reference, and a
172
+ // declaration has to come before its readers.
173
+ config.banner = {
174
+ ...config.banner,
175
+ js: [IMPORT_META_URL_SHIM, config.banner && config.banner.js].filter(Boolean).join('\n'),
176
+ };
177
+ }
178
+
133
179
  /**
134
180
  * One esbuild message as a line: what, and where.
135
181
  * @param {object} message - an esbuild warning or error
@@ -248,6 +294,8 @@ async function bundle(options) {
248
294
  if (value != null) config[key] = value;
249
295
  }
250
296
 
297
+ keepImportMetaUrl(config);
298
+
251
299
  if (mode !== 'watch') {
252
300
  return esbuild.build(config);
253
301
  }