@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
@@ -0,0 +1,264 @@
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
+ const path = require('path');
26
+ const jetpack = require('fs-jetpack');
27
+
28
+ const { resolveBrandRoot } = require('../config/index.js');
29
+ const Logger = require('./logger.js');
30
+
31
+ const defaultLogger = new Logger('brand-version');
32
+
33
+ // What `omega bump` takes, in the order it prints them.
34
+ const KINDS = ['patch', 'minor', 'major'];
35
+
36
+ // A plain release number and nothing else: a prerelease tag is refused by name
37
+ // rather than guessed at (which digit does `patch` move on `1.0.0-beta.2`?).
38
+ const RELEASE = /^(\d+)\.(\d+)\.(\d+)$/;
39
+
40
+ /**
41
+ * A refusal: crafted text a human is meant to read, flagged so every surface
42
+ * prints it the same way (the message alone, nothing added), the same shape
43
+ * scaffold-guard's refusals take.
44
+ *
45
+ * @param {string} message - The lines to print verbatim.
46
+ * @returns {Error} The flagged error to throw.
47
+ */
48
+ function refusal(message) {
49
+ const error = new Error(message);
50
+ error.refusal = true;
51
+ return error;
52
+ }
53
+
54
+ /**
55
+ * The brand's version, resolved from anywhere inside the brand.
56
+ *
57
+ * @param {object} options
58
+ * @param {string} options.dir - Any directory inside the brand (root or target).
59
+ * @returns {{ brandRoot: string, version: string }|null} null when `dir` is not inside a brand.
60
+ * @throws {Error} A refusal when the brand root carries no `version`: the
61
+ * master number cannot be defaulted, or every target would follow a guess.
62
+ */
63
+ function readBrandVersion({ dir }) {
64
+ const brandRoot = resolveBrandRoot(dir);
65
+ if (!brandRoot) return null;
66
+
67
+ const manifest = path.join(brandRoot, 'package.json');
68
+ const version = (jetpack.read(manifest, 'json') || {}).version;
69
+
70
+ if (!version) {
71
+ throw refusal(
72
+ `${manifest} carries no "version", and the brand root is the one version every target follows (#869).\n`
73
+ + ` fix: add "version" to ${manifest}, then run \`omega bump\` at the brand root`,
74
+ );
75
+ }
76
+
77
+ return { brandRoot, version };
78
+ }
79
+
80
+ /**
81
+ * Every target that carries a package.json, by name. A directory under
82
+ * `targets/` with no manifest is not a target (the manager's `discoverTargets`
83
+ * rule), and the list is sorted so a bump's output reads the same every run.
84
+ *
85
+ * @param {object} options
86
+ * @param {string} options.brandRoot - Brand monorepo root.
87
+ * @returns {Array<{ name: string, dir: string, manifest: string }>}
88
+ */
89
+ function listTargetPackages({ brandRoot }) {
90
+ const targetsDir = path.join(brandRoot, 'targets');
91
+
92
+ return (jetpack.list(targetsDir) || [])
93
+ .filter((name) => !name.startsWith('.'))
94
+ .sort()
95
+ .map((name) => ({
96
+ name,
97
+ dir: path.join(targetsDir, name),
98
+ manifest: path.join(targetsDir, name, 'package.json'),
99
+ }))
100
+ .filter((entry) => jetpack.exists(entry.manifest) === 'file');
101
+ }
102
+
103
+ /**
104
+ * The next release number for a kind.
105
+ *
106
+ * @param {string} previous - The brand's current version.
107
+ * @param {'patch'|'minor'|'major'} kind - Which digit moves.
108
+ * @returns {string} The next version.
109
+ * @throws {Error} A refusal when `previous` is not a plain release number.
110
+ */
111
+ function nextVersion(previous, kind) {
112
+ const parts = RELEASE.exec(String(previous));
113
+
114
+ if (!parts) {
115
+ throw refusal(
116
+ `the brand version "${previous}" is not a plain major.minor.patch, and \`omega bump\` moves whole releases with no prerelease handling.\n`
117
+ + ' fix: set a plain version in the brand root package.json first',
118
+ );
119
+ }
120
+
121
+ const [major, minor, patch] = parts.slice(1).map(Number);
122
+
123
+ if (kind === 'major') return `${major + 1}.0.0`;
124
+ if (kind === 'minor') return `${major}.${minor + 1}.0`;
125
+ return `${major}.${minor}.${patch + 1}`;
126
+ }
127
+
128
+ /**
129
+ * The same content with `version` set, gaining the key after `name` when the
130
+ * file never had one, so a target's first bump reads like every sibling's
131
+ * manifest instead of appending a stray key at the end.
132
+ *
133
+ * @param {object} content - The parsed manifest (or lock entry).
134
+ * @param {string} next - The version to write.
135
+ * @returns {object} The content to write back.
136
+ */
137
+ function withVersion(content, next) {
138
+ if (content.version !== undefined) return { ...content, version: next };
139
+
140
+ const updated = content.name === undefined ? { version: next } : {};
141
+ for (const [key, value] of Object.entries(content)) {
142
+ updated[key] = value;
143
+ if (key === 'name') updated.version = next;
144
+ }
145
+
146
+ return updated;
147
+ }
148
+
149
+ /**
150
+ * Write a JSON file the way npm writes them: 2-space indent, trailing newline.
151
+ *
152
+ * @param {string} file - Where to write.
153
+ * @param {object} content - What to write.
154
+ * @returns {void}
155
+ */
156
+ function writeJson(file, content) {
157
+ jetpack.write(file, `${JSON.stringify(content, null, 2)}\n`);
158
+ }
159
+
160
+ /**
161
+ * Move the brand and every target onto the next version. The ONE writer.
162
+ *
163
+ * Writes the brand root manifest, every target manifest (a target with no
164
+ * `version` GAINS one: every target follows), and the brand lock's entry for
165
+ * each target plus its own root version where the lock carries one, since a
166
+ * lock left behind would have `npm ci` install the old number.
167
+ *
168
+ * @param {object} options
169
+ * @param {string} options.brandRoot - Brand monorepo root.
170
+ * @param {'patch'|'minor'|'major'} options.kind - Which digit moves.
171
+ * @param {object} [options.logger] - Where the per-file lines go.
172
+ * @returns {{ previous: string, next: string, files: string[] }} Paths relative to the brand root.
173
+ * @throws {Error} A refusal on an unknown kind, a missing root version, or a prerelease one.
174
+ */
175
+ function bumpBrandVersion({ brandRoot, kind, logger = defaultLogger }) {
176
+ if (!KINDS.includes(kind)) {
177
+ throw refusal(`\`omega bump\` takes ${KINDS.join(', ')}, and "${kind}" is none of them.`);
178
+ }
179
+
180
+ const previous = readBrandVersion({ dir: brandRoot }).version;
181
+ const next = nextVersion(previous, kind);
182
+ const files = [];
183
+
184
+ // One line per file, always `<old> -> <new>`; a file that had no version
185
+ // says so rather than printing an empty left-hand side.
186
+ const write = (file, old) => {
187
+ const relative = path.relative(brandRoot, file);
188
+ files.push(relative);
189
+ logger.log(`${relative} ${old || '(none)'} -> ${next}`);
190
+ };
191
+
192
+ const rootManifest = path.join(brandRoot, 'package.json');
193
+ writeJson(rootManifest, withVersion(jetpack.read(rootManifest, 'json'), next));
194
+ write(rootManifest, previous);
195
+
196
+ const targets = listTargetPackages({ brandRoot });
197
+
198
+ for (const target of targets) {
199
+ const content = jetpack.read(target.manifest, 'json');
200
+ writeJson(target.manifest, withVersion(content, next));
201
+ write(target.manifest, content.version);
202
+ }
203
+
204
+ const lockPath = path.join(brandRoot, 'package-lock.json');
205
+ const lock = jetpack.exists(lockPath) === 'file' ? jetpack.read(lockPath, 'json') : null;
206
+
207
+ if (lock && lock.packages) {
208
+ for (const target of targets) {
209
+ const key = `targets/${target.name}`;
210
+ if (lock.packages[key]) lock.packages[key] = withVersion(lock.packages[key], next);
211
+ }
212
+
213
+ // The lock's own root number exists only when npm wrote one, and npm
214
+ // writes it from the root manifest: where it is absent, the next install
215
+ // is what puts it there.
216
+ if (lock.version) lock.version = next;
217
+ if (lock.packages[''] && lock.packages[''].version) lock.packages[''].version = next;
218
+
219
+ writeJson(lockPath, lock);
220
+ write(lockPath, previous);
221
+ }
222
+
223
+ return { previous, next, files };
224
+ }
225
+
226
+ /**
227
+ * The deploy gate: refuse a target whose version has drifted from the brand's.
228
+ *
229
+ * Read-only, so every lane runs it (`--direct` and `--dry-run` included), and
230
+ * it runs BEFORE the secrets precheck: a drifted target must not push its
231
+ * secrets and dispatch a build of the wrong number. A match says nothing.
232
+ *
233
+ * @param {object} options
234
+ * @param {string} options.dir - The target directory being deployed.
235
+ * @returns {{ brandRoot: string, name: string, version: string }|null} null when there is nothing to compare.
236
+ * @throws {Error} A refusal naming the target and `omega bump`.
237
+ */
238
+ function assertBrandVersion({ dir }) {
239
+ const brandRoot = resolveBrandRoot(dir);
240
+ if (!brandRoot) return null;
241
+
242
+ // Only a target follows the brand. A brand root, or any other directory
243
+ // inside the brand, has nothing of its own to compare.
244
+ const segments = path.relative(brandRoot, path.resolve(dir)).split(path.sep);
245
+ if (segments[0] !== 'targets' || !segments[1]) return null;
246
+
247
+ const name = segments[1];
248
+ const manifest = path.join(brandRoot, 'targets', name, 'package.json');
249
+ if (jetpack.exists(manifest) !== 'file') return null;
250
+
251
+ const found = (jetpack.read(manifest, 'json') || {}).version;
252
+ const { version } = readBrandVersion({ dir: brandRoot });
253
+
254
+ if (found !== version) {
255
+ throw refusal(
256
+ `targets/${name} ${found ? `is ${found}` : 'has no version'} but the brand is ${version}; `
257
+ + 'run `omega bump` at the brand root (#869).',
258
+ );
259
+ }
260
+
261
+ return { brandRoot, name, version };
262
+ }
263
+
264
+ module.exports = { readBrandVersion, listTargetPackages, bumpBrandVersion, assertBrandVersion, KINDS };
@@ -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
  }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * How long a signing certificate has left: the ONE expiry reader
3
+ * ([#892](https://github.com/Omega-JS-Stack/omega/issues/892)).
4
+ *
5
+ * Two places used to parse `openssl x509 -enddate` with their own regex and
6
+ * their own date math (the manager's `validateManualCertificate`, the desktop's
7
+ * `validate-certs` provisioning-profile block), and neither could read a `.p12`
8
+ * at all, so the walk that PRODUCES the signing material never said how long it
9
+ * had left. One function answers for both container shapes:
10
+ *
11
+ * .cer / .pem openssl x509 -enddate (openssl 3 auto-detects DER/PEM)
12
+ * .p12 openssl pkcs12 -nokeys + x509 (the cert half, then the same read)
13
+ *
14
+ * The password rides an env var (`-passin env:`), never argv and never a shell
15
+ * string, exactly as the manager's export and the desktop's signing lookup pass
16
+ * theirs.
17
+ *
18
+ * It THROWS on a file it cannot read: an unreadable certificate is not "zero
19
+ * days left", it is a broken invariant the caller must report as its own error.
20
+ */
21
+ const { execFileSync } = require('node:child_process');
22
+ const { extname } = require('node:path');
23
+ const jetpack = require('fs-jetpack');
24
+
25
+ const PASSWORD_ENV = 'OMEGA_P12_PASSWORD';
26
+ const MS_PER_DAY = 24 * 60 * 60 * 1000;
27
+
28
+ // The ONE renewal window: under this many days left, every reader of this
29
+ // module warns (the manager walk per type, the desktop's validate-certs step);
30
+ // past zero, every one of them errors.
31
+ const EXPIRY_WARN_DAYS = 30;
32
+
33
+ /**
34
+ * The certificate half of a .p12, as PEM.
35
+ *
36
+ * Two openssl families answer differently: OpenSSL 3 needs `-legacy` for the
37
+ * legacy-format containers the manager exports (macOS `security import` needs
38
+ * that format), while stock macOS /usr/bin/openssl is LibreSSL, which rejects
39
+ * the `-legacy` FLAG but reads legacy containers natively. A flag rejection is
40
+ * retried without it; anything else is the caller's error.
41
+ *
42
+ * @param {string} filePath - Path to the .p12.
43
+ * @param {string} password - Password that opens it.
44
+ * @returns {string} PEM text.
45
+ */
46
+ function p12Certificate(filePath, password) {
47
+ const run = (extra) => execFileSync(
48
+ 'openssl',
49
+ ['pkcs12', ...extra, '-in', filePath, '-nokeys', '-passin', `env:${PASSWORD_ENV}`],
50
+ { encoding: 'utf8', stdio: 'pipe', env: { ...process.env, [PASSWORD_ENV]: password } },
51
+ );
52
+
53
+ try {
54
+ return run(['-legacy']);
55
+ } catch (error) {
56
+ if (String(error.stderr || '').includes('unknown option')) {
57
+ return run([]);
58
+ }
59
+ throw error;
60
+ }
61
+ }
62
+
63
+ /**
64
+ * When a certificate expires, and how many days that is from now.
65
+ *
66
+ * @param {string} filePath - A .cer, .pem or .p12 on disk.
67
+ * @param {object} [options]
68
+ * @param {string} [options.password] - The .p12 password (ignored for a .cer/.pem).
69
+ * @returns {{ expiresAt: Date, daysLeft: number }} `daysLeft` is negative for an
70
+ * expired certificate.
71
+ * @throws {Error} When the file is missing, unopenable, or carries no date.
72
+ */
73
+ function certificateExpiry(filePath, { password = '' } = {}) {
74
+ if (!filePath || !jetpack.exists(filePath)) {
75
+ throw new Error(`certificateExpiry: no file at ${filePath}`);
76
+ }
77
+
78
+ const isP12 = extname(filePath).toLowerCase() === '.p12';
79
+ let output;
80
+
81
+ try {
82
+ output = isP12
83
+ ? execFileSync('openssl', ['x509', '-enddate', '-noout'], {
84
+ encoding: 'utf8',
85
+ stdio: 'pipe',
86
+ input: p12Certificate(filePath, password),
87
+ })
88
+ : execFileSync('openssl', ['x509', '-enddate', '-noout', '-in', filePath], {
89
+ encoding: 'utf8',
90
+ stdio: 'pipe',
91
+ });
92
+ } catch (error) {
93
+ throw new Error(`certificateExpiry: could not read ${filePath}: ${error.message}`);
94
+ }
95
+
96
+ const match = String(output).match(/notAfter=(.+)/);
97
+ const expiresAt = match ? new Date(match[1].trim()) : null;
98
+ if (!expiresAt || Number.isNaN(expiresAt.getTime())) {
99
+ throw new Error(`certificateExpiry: no expiration date in ${filePath}`);
100
+ }
101
+
102
+ return {
103
+ expiresAt,
104
+ daysLeft: Math.floor((expiresAt.getTime() - Date.now()) / MS_PER_DAY),
105
+ };
106
+ }
107
+
108
+ module.exports = { certificateExpiry, EXPIRY_WARN_DAYS };