@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,5 +1,6 @@
1
1
  // bundle — three bundles (main / preload / renderer), built in parallel.
2
- // Each one gets `OMEGA_BUILD_JSON` baked in as a define.
2
+ // The NODE pair (main / preload) gets `OMEGA_BUILD_JSON` baked in as a define;
3
+ // the renderer reads the ONE `dist/build.js` the page template loads (#743).
3
4
  //
4
5
  // The bundler is esbuild since [#737](https://github.com/Omega-JS-Stack/omega/issues/737),
5
6
  // and the file, the gulp task, the log tag and `npm run gulp -- bundle` were
@@ -19,11 +20,12 @@ const logger = Manager.logger('bundle');
19
20
  const path = require('path');
20
21
  const glob = require('glob').globSync;
21
22
  const jetpack = require('fs-jetpack');
22
- const { readSiblingPorts, readSiblingOrigin, envPorts } = require('../../vendor/config/index.js');
23
- const { bakeKeys } = require('../../vendor/config/env-delivery.js');
23
+ const { CLASSIC_PORTS, CLASSIC_DEV_ORIGIN, readSiblingPorts, readSiblingOrigin, envPorts, targetNameFromDir } = require('../../vendor/config/index.js');
24
+ const { bakeKeys, bakeSourceKeys } = require('../../vendor/config/env-delivery.js');
24
25
  const { checkEnvRules } = require('../../vendor/config/env-rules.js');
25
26
  const { resolveLicenseStamp } = require('../../vendor/devkit/license.js');
26
27
  const { bundle, formatBytes } = require('../../vendor/devkit/bundle.js');
28
+ const buildJsonKit = require('../../vendor/devkit/build-json.js');
27
29
  const { emptyModulesPlugin } = require('../../vendor/devkit/empty-modules-plugin.js');
28
30
  const electronTargets = require('../../utils/electron-targets.js');
29
31
 
@@ -49,33 +51,72 @@ const RENDERER_EMPTY_MODULES = [
49
51
  'querystring', 'string_decoder', 'electron',
50
52
  ];
51
53
 
52
- // The blob every bundle reads as OMEGA_BUILD_JSON.config — and the ONLY thing
53
- // the renderer hands @omega.js/client (renderer.js merges `buildJson.config`
54
- // with the runtime overrides). The app's VERSION rides INSIDE it for that
55
- // reason: the client tags every error report `<brand.id>@<version>` and falls
56
- // back to the build stamp without one (#380), and it never sees the sibling
57
- // `package` key. The dev map (#300) is a dev-build key only.
58
- function composeBuildConfig(config, dev, pkg) {
59
- return { ...config, version: pkg.version, ...(dev ? { dev } : {}) };
54
+ // The build FACTS this artifact carries, composed onto the resolved config by
55
+ // both halves ([#894](https://github.com/Omega-JS-Stack/omega/issues/894)) and
56
+ // spelled the way web and the extension spell them. The app's VERSION rides
57
+ // INSIDE the config: the client tags every error report `<brand.id>@<version>`
58
+ // and falls back to the build stamp without one (#380), and it never sees the
59
+ // sibling `package` key. `runtime` is baked because the client's own sniff has
60
+ // no Electron signal to find in a renderer and would answer `'web'`
61
+ // ([#896](https://github.com/Omega-JS-Stack/omega/issues/896)). The dev map
62
+ // (#300) is a dev-build key only.
63
+ function buildFacts({ pkg, mode, dev }) {
64
+ return {
65
+ runtime: 'electron',
66
+ environment: mode.environment,
67
+ version: pkg.version,
68
+ buildTime: Date.now(),
69
+ // WHICH target this artifact IS, by name (#887)
70
+ target: targetNameFromDir(projectRoot) || 'desktop',
71
+ ...(dev ? { dev } : {}),
72
+ };
73
+ }
74
+
75
+ // The wrapper's `mode`, the same three keys on every OMEGA surface (#894).
76
+ // Desktop's own `server` verdict stays inside the Manager, which is the only
77
+ // thing that reads it: an artifact records what it WAS BUILT as.
78
+ function artifactMode(mode) {
79
+ return { environment: mode.environment, build: mode.build, publish: mode.publish };
60
80
  }
61
81
 
62
- // OMEGA_BUILD_JSON itself — the build's own record of what it produced. The
63
- // `license` stamp ([#320](https://github.com/Omega-JS-Stack/omega/issues/320))
64
- // rides HERE and not inside `config`: it is a fact about the build, not part
65
- // of the client contract the renderer hands @omega.js/client. Nothing in the
66
- // app reads it today (a packaged app has no attribution surface, and payments
67
- // ride the backend's own gate) — it is what makes an artifact say which
68
- // verdict it was packaged under.
69
- function composeBuildJson({ config, dev, pkg, mode, license }) {
82
+ // The config blob the NODE bundles read as OMEGA_BUILD_JSON.config (main and
83
+ // preload): the WHOLE resolved config plus this build's facts.
84
+ function composeBuildConfig(config, facts) {
85
+ return { ...config, ...facts };
86
+ }
87
+
88
+ // OMEGA_BUILD_JSON for the NODE half: the build's own record of what it
89
+ // produced. The `license` stamp
90
+ // ([#320](https://github.com/Omega-JS-Stack/omega/issues/320)) rides HERE and
91
+ // not inside `config`: it is a fact about the build, not part of the client
92
+ // contract the renderer hands @omega.js/client. Nothing in the app reads it
93
+ // today (a packaged app has no attribution surface, and payments ride the
94
+ // backend's own gate); it is what makes an artifact say which verdict it was
95
+ // packaged under.
96
+ //
97
+ // Main and preload keep the whole resolved config because the main process
98
+ // BOOTS from it (main.js reads OMEGA_BUILD_JSON.config in a packaged app, where
99
+ // config/omega.json5 is inside the asar), and a preload is isolated from the
100
+ // page. Both are Node, neither is a public surface.
101
+ function composeBuildJson({ config, facts, pkg, mode, license }) {
70
102
  return {
71
- config: composeBuildConfig(config, dev, pkg),
103
+ config: composeBuildConfig(config, facts),
72
104
  package: pkg,
73
- mode,
105
+ mode: artifactMode(mode),
74
106
  license,
75
107
  builtAt: new Date().toISOString(),
76
108
  };
77
109
  }
78
110
 
111
+ // The BROWSER half's snapshot ([#743](https://github.com/Omega-JS-Stack/omega/issues/743)):
112
+ // @omega.js/devkit composes it (through @omega.js/config's browser subset) and
113
+ // writes it to `dist/build.js`, which every view's page template loads with one
114
+ // script tag ahead of its own bundle. One file, one shape and one consumption
115
+ // on web, desktop and the extension alike.
116
+ function composeClientBuildJson({ config, facts, pkg, mode, license }) {
117
+ return buildJsonKit.composeBuildJson({ config, pkg, mode, license, facts });
118
+ }
119
+
79
120
  module.exports = function bundleTask(done) {
80
121
  const mode = Manager.getMode();
81
122
  const isProd = mode.environment === 'production';
@@ -88,13 +129,20 @@ module.exports = function bundleTask(done) {
88
129
  // channel, resolved on every build so a rebuild picks up a restarted
89
130
  // emulator ([#300](https://github.com/Omega-JS-Stack/omega/issues/300)).
90
131
  // The sibling website's published dev ORIGIN rides the same bake
91
- // ([#262](https://github.com/Omega-JS-Stack/omega/issues/262)); a key
92
- // present is a resolved fact, absent means the client assumes and warns.
93
- // Never in production: a packaged app has no local stack.
132
+ // ([#262](https://github.com/Omega-JS-Stack/omega/issues/262)). Never in
133
+ // production: a packaged app has no local stack, so its `dev` is null and
134
+ // every dev read throws by name rather than reaching a localhost port.
94
135
  const devOrigin = isProd ? null : readSiblingOrigin(projectRoot);
95
136
  const dev = isProd ? null : {
96
- ports: { ...readSiblingPorts(projectRoot), ...envPorts() },
97
- ...(devOrigin ? { origin: devOrigin } : {}),
137
+ // The classic map is the FLOOR, from @omega.js/config, the one place the
138
+ // numbers are defined ([#834](https://github.com/Omega-JS-Stack/omega/issues/834)).
139
+ // A live stack's resolved (possibly bumped) numbers land on top of it, so a
140
+ // dev artifact always carries a COMPLETE map and no browser-side code needs
141
+ // a second copy of the defaults to fall back to. The same floor goes under
142
+ // the origin: the classic dev origin is `omega dev`'s own default, and a
143
+ // live website's published one wins over it.
144
+ ports: { ...CLASSIC_PORTS, ...readSiblingPorts(projectRoot), ...envPorts() },
145
+ origin: devOrigin || CLASSIC_DEV_ORIGIN,
98
146
  };
99
147
 
100
148
  // The license check (#320), once per build: a PRODUCTION build asks the
@@ -104,8 +152,14 @@ module.exports = function bundleTask(done) {
104
152
  const projectPackage = Manager.getPackage('project');
105
153
  resolveLicenseStamp({ config, production: isProd })
106
154
  .then((license) => {
107
- // OMEGA_BUILD_JSON — frozen at build time, accessible at runtime as window/globalThis.OMEGA_BUILD_JSON.
108
- const buildJson = composeBuildJson({ config, dev, pkg: projectPackage, mode, license });
155
+ // OMEGA_BUILD_JSON: frozen at build time. The NODE pair reads it as a
156
+ // define; the renderer reads the file written below.
157
+ const input = { config, facts: buildFacts({ pkg: projectPackage, mode, dev }), pkg: projectPackage, mode, license };
158
+ const buildJson = composeBuildJson(input);
159
+
160
+ // The ONE build.js, at dist/: the web root every view's shell resolves
161
+ // `../../build.js` against, inside a packaged asar exactly as on disk (#743).
162
+ buildJsonKit.writeBuildJs(outputRoot, composeClientBuildJson(input));
109
163
 
110
164
  logger.log(`bundling — environment=${mode.environment}, license=${license.status}`);
111
165
  runBundles(buildJson, isProd).then(() => done()).catch(done);
@@ -130,10 +184,16 @@ async function runBundles(buildJson, isProd) {
130
184
  dev: !isProd,
131
185
  };
132
186
 
187
+ // The renderer takes the same defines MINUS the snapshot: it reads the one
188
+ // build.js the page template loads ahead of it (#743), so nothing is copied
189
+ // into a bundle a user can read.
190
+ const { OMEGA_BUILD_JSON, ...rendererDefine } = shared.define;
191
+ const { banner, ...rendererShared } = { ...shared, define: rendererDefine };
192
+
133
193
  const builds = [
134
194
  mainBundle(shared),
135
195
  preloadBundle(shared),
136
- rendererBundle(shared),
196
+ rendererBundle(rendererShared),
137
197
  ].filter(Boolean);
138
198
 
139
199
  if (builds.length === 0) {
@@ -172,7 +232,7 @@ function resolveTargets() {
172
232
  }
173
233
  }
174
234
 
175
- // Inject OMEGA_BUILD_JSON into every bundle two ways:
235
+ // Inject OMEGA_BUILD_JSON into the NODE bundles (main and preload) two ways:
176
236
  // 1. `define` replaces the bare `OMEGA_BUILD_JSON` identifier with the literal at build time
177
237
  // (so framework code can reference it without globals).
178
238
  // 2. `banner` prepends a tiny IIFE that assigns the same value to globalThis.OMEGA_BUILD_JSON
@@ -198,8 +258,9 @@ function buildJsonDefines(buildJson, isProd) {
198
258
  }
199
259
 
200
260
  /**
201
- * The globalThis/window assignment every bundle carries.
202
- * @param {object} buildJson - The blob every bundle bakes.
261
+ * The globalThis/window assignment the NODE bundles carry. The renderer takes
262
+ * no banner at all: its snapshot is the one `dist/build.js` its shell loads (#743).
263
+ * @param {object} buildJson - The blob the node bundles bake.
203
264
  * @returns {object} an esbuild banner
204
265
  */
205
266
  function buildJsonBanner(buildJson) {
@@ -214,6 +275,8 @@ function buildJsonBanner(buildJson) {
214
275
  // GOOGLE_ANALYTICS_SECRET read this replaced was one of three hand-kept lists
215
276
  // for one concern. A new baked key is one schema entry and nothing here.
216
277
  const BAKED_KEYS = bakeKeys('desktop');
278
+ // The same set at the level a HUMAN sets it: what the bake guard judges (#891)
279
+ const BAKED_SOURCE_KEYS = bakeSourceKeys('desktop');
217
280
 
218
281
  /**
219
282
  * The schema's presence rules at the BAKE seam
@@ -228,6 +291,12 @@ const BAKED_KEYS = bakeKeys('desktop');
228
291
  * warns and keeps going, because a half-configured brand is a normal step on
229
292
  * the way to a configured one.
230
293
  *
294
+ * Only keys this build BAKES are its business
295
+ * ([#891](https://github.com/Omega-JS-Stack/omega/issues/891)): the schema's
296
+ * other desktop rules are CI-delivered signing credentials, which the deploy
297
+ * lane owns (the deploy precheck refuses to publish a half set) and which a local
298
+ * unsigned build has no need of at all.
299
+ *
231
300
  * @param {object} config - The target's resolved omega.json5 config.
232
301
  * @param {object} env - The build env.
233
302
  * @param {object} [options]
@@ -241,7 +310,7 @@ function assertBakeRules(config, env, options) {
241
310
  const warn = (options.logger || logger).warn.bind(options.logger || logger);
242
311
 
243
312
  const violations = checkEnvRules(config, env, { target: 'desktop' })
244
- .filter((violation) => violation.rule === 'requiredWhen');
313
+ .filter((violation) => violation.rule === 'requiredWhen' && BAKED_SOURCE_KEYS.includes(violation.key));
245
314
  if (violations.length === 0) return;
246
315
 
247
316
  // The BRAND-LEVEL key name (GOOGLE_ANALYTICS_SECRET_DESKTOP, not the
@@ -249,7 +318,7 @@ function assertBakeRules(config, env, options) {
249
318
  // in the brand .env and in the repo's Actions secrets.
250
319
  const named = violations.map(({ key, path }) => `${key} (required by ${path})`).join(', ');
251
320
  const message = `${violations.length} env ${violations.length === 1 ? 'key this brand\'s config requires is' : 'keys this brand\'s config requires are'} missing from the build env: ${named}. `
252
- + 'Set it in the brand .env (and as a repo Actions secret for a CI build — `omega push-secrets` sends them), then build again.';
321
+ + 'Set it in the brand .env (a CI build reads it as a repo Actions secret, which `omega deploy`\'s precheck pushes), then build again.';
253
322
 
254
323
  if (mode.build || mode.publish) {
255
324
  throw new Error(message);
@@ -365,7 +434,7 @@ function rendererEntryKey(rel) {
365
434
  /**
366
435
  * One browser bundle per `src/assets/js/components/<name>/index.js`, loaded by
367
436
  * the page template as a plain `<script src>` — so iife, not esm.
368
- * @param {object} shared - `{ define, banner, targets, dev }`, composed once per run
437
+ * @param {object} shared - `{ define, targets, dev }`, composed once per run (no banner: #743)
369
438
  * @returns {object|null} `{ name, options }`, or null when the consumer has no entries
370
439
  */
371
440
  function rendererBundle(shared) {
@@ -412,7 +481,8 @@ function rendererBundle(shared) {
412
481
  // a browser bundle leaves it undefined, so it is rewritten textually.
413
482
  global: 'globalThis',
414
483
  },
415
- banner: shared.banner,
484
+ // No banner and no snapshot define: the renderer reads the ONE
485
+ // `dist/build.js` its page template loads ahead of it (#743).
416
486
  plugins: [emptyModulesPlugin(RENDERER_EMPTY_MODULES)],
417
487
  },
418
488
  };
@@ -443,8 +513,10 @@ function nativeExternals() {
443
513
  // The list behind the renderer's Node-builtin shim (#737) — pinned by
444
514
  // src/test/suites/build/renderer-node-shims.test.js.
445
515
  module.exports.RENDERER_EMPTY_MODULES = RENDERER_EMPTY_MODULES;
516
+ module.exports.buildFacts = buildFacts;
446
517
  module.exports.composeBuildConfig = composeBuildConfig;
447
518
  module.exports.composeBuildJson = composeBuildJson;
519
+ module.exports.composeClientBuildJson = composeClientBuildJson;
448
520
  module.exports.rendererEntryKey = rendererEntryKey;
449
521
  // The schema-derived bake set and its reader (#627).
450
522
  module.exports.BAKED_KEYS = BAKED_KEYS;
@@ -18,6 +18,11 @@ const Manager = new (require('../../build.js'));
18
18
 
19
19
  const logger = Manager.logger('release');
20
20
 
21
+ // The two warnings electron-publish prints when it uploads nothing: one per
22
+ // release (`gitHubPublisher.getOrCreateRelease` returns null with its reason)
23
+ // and one per file that never left the machine.
24
+ const SKIP_WARNINGS = ['skipped publishing', 'GitHub release not created'];
25
+
21
26
  module.exports = function release(done) {
22
27
  const projectRoot = process.cwd();
23
28
  const config = path.join(require('../../utils/dist-root.js')(projectRoot), 'electron-builder.yml');
@@ -40,12 +45,25 @@ module.exports = function release(done) {
40
45
  const relConfig = path.relative(projectRoot, config);
41
46
  logger.log(`Releasing via electron-builder (config=${relConfig}, publish=always)...`);
42
47
 
43
- builder.build({
48
+ // electron-publish reports a skipped upload through THIS logger and nowhere
49
+ // else, and a release that uploaded nothing must never print a success line,
50
+ // so the logger is a requirement of the task, not a nicety. It comes off
51
+ // electron-builder's OWN export, which re-exports the one `builder-util`
52
+ // instance electron-publish writes to: a bare require of `builder-util` names
53
+ // a package this manifest never declares (#872) and could resolve a SECOND
54
+ // copy whose log nothing ever writes to.
55
+ recordSkippedPublishes(builder.log, () => builder.build({
44
56
  config,
45
57
  publish: 'always',
46
- })
47
- .then((artifacts) => {
48
- const list = (artifacts || []).map((a) => path.relative(projectRoot, a));
58
+ }))
59
+ .then(({ result, skips }) => {
60
+ const skipped = skippedUploadError(skips);
61
+ if (skipped) {
62
+ logger.error(skipped.message);
63
+ return done(skipped);
64
+ }
65
+
66
+ const list = (result || []).map((a) => path.relative(projectRoot, a));
49
67
  logger.log(`Released ${list.length} artifact(s):`);
50
68
  list.forEach((a) => logger.log(` • ${a}`));
51
69
  done();
@@ -55,3 +73,67 @@ module.exports = function release(done) {
55
73
  done(e);
56
74
  });
57
75
  };
76
+
77
+ /**
78
+ * Run the build with electron-builder's OWN logger watched, and hand back what
79
+ * it published NOTHING for. The promise `build()` returns resolves with the
80
+ * local artifact paths whether or not a byte was uploaded, so the resolve value
81
+ * says nothing about the release: the skip is announced only as a warning on
82
+ * electron-builder's log (the `builder-util` instance it re-exports), carrying
83
+ * the reason and the tag as fields. The wrapper
84
+ * is put back in a `finally`, so a build that throws leaves the logger the way
85
+ * it found it. Exported for tests.
86
+ *
87
+ * @param {{ warn: Function }} log - electron-builder's logger.
88
+ * @param {Function} fn - The build to run.
89
+ * @returns {Promise<{ result: *, skips: object[] }>} What the build resolved with, and every skip it logged.
90
+ */
91
+ async function recordSkippedPublishes(log, fn) {
92
+ const skips = [];
93
+ const owned = Object.prototype.hasOwnProperty.call(log, 'warn');
94
+ const original = log.warn;
95
+
96
+ log.warn = function (messageOrFields, message) {
97
+ // `warn(message)` and `warn(fields, message)` are both legal on this logger.
98
+ const text = String(message === undefined ? messageOrFields : message);
99
+ const fields = message === undefined ? {} : (messageOrFields || {});
100
+ if (SKIP_WARNINGS.some((warning) => text.includes(warning))) {
101
+ skips.push({ message: text, ...fields });
102
+ }
103
+ return original.call(this, messageOrFields, message);
104
+ };
105
+
106
+ try {
107
+ const result = await fn();
108
+ return { result, skips };
109
+ } finally {
110
+ if (owned) {
111
+ log.warn = original;
112
+ } else {
113
+ delete log.warn;
114
+ }
115
+ }
116
+ }
117
+
118
+ /**
119
+ * The verdict on a build's recorded skips: a release that uploaded nothing is a
120
+ * failed release, named by electron-builder's own reason. Exported for tests.
121
+ *
122
+ * @param {object[]} skips - What recordSkippedPublishes collected.
123
+ * @returns {Error|null} The failure, or null when everything published.
124
+ */
125
+ function skippedUploadError(skips) {
126
+ if (skips.length === 0) {
127
+ return null;
128
+ }
129
+
130
+ const reason = skips.find((skip) => skip.reason)?.reason || 'electron-builder gave no reason';
131
+ const tag = skips.find((skip) => skip.tag)?.tag || 'the configured tag';
132
+ const files = skips.map((skip) => skip.file).filter(Boolean);
133
+ const what = files.length > 0 ? `${files.length} artifact(s) (${files.join(', ')})` : 'every artifact';
134
+
135
+ return new Error(`Nothing was uploaded: electron-builder skipped publishing ${what} to ${tag} (${reason}). Bump the version in package.json: a release that already exists is never re-uploaded.`);
136
+ }
137
+
138
+ module.exports.recordSkippedPublishes = recordSkippedPublishes;
139
+ module.exports.skippedUploadError = skippedUploadError;
@@ -7,6 +7,7 @@
7
7
  // - <omega-package-root>/dist/assets/css → resolves bare 'omega-desktop' to omega-desktop.scss
8
8
  // - <omega-package-root>/dist/assets/themes/<active-theme> → resolves bare 'theme' to <theme>/_theme.scss
9
9
  // - <omega-package-root>/dist/assets/themes → so themes can reference each other via '../<sibling>'
10
+ // - <output-root>/assets/scss → resolves bare 'brand' to the generated _brand.scss (#912)
10
11
  // - <consumer>/src/assets/scss → consumer's own modules
11
12
 
12
13
  const Manager = new (require('../../build.js'));
@@ -15,6 +16,7 @@ const path = require('path');
15
16
  const jetpack = require('fs-jetpack');
16
17
  const sass = require('sass');
17
18
  const { formatBytes } = require('../../vendor/devkit/bundle.js');
19
+ const { composeBrandTokens, renderBrandScss } = require('../../vendor/devkit/brand-tokens.js');
18
20
 
19
21
  const projectRoot = Manager.getRootPath('project');
20
22
  const packageRoot = Manager.getRootPath('main');
@@ -25,10 +27,19 @@ module.exports = function sassTask(done) {
25
27
  const config = Manager.getConfig() || {};
26
28
  const themeId = config.theme?.id || 'classy';
27
29
 
30
+ // The brand partial ([#912](https://github.com/Omega-JS-Stack/omega/issues/912)):
31
+ // `brand.color` is the ONE accent hex, and it reaches the css through a
32
+ // GENERATED partial rather than a literal a consumer keeps in sync by hand.
33
+ // Written before every compile (no color resolves to the framework default,
34
+ // whose one home is the renderer), so a config edit is the only edit.
35
+ const brandDir = path.join(outputRoot, 'assets', 'scss');
36
+ jetpack.write(path.join(brandDir, '_brand.scss'), renderBrandScss(composeBrandTokens(config.brand?.color)));
37
+
28
38
  const loadPaths = [
29
39
  path.join(packageRoot, 'dist', 'assets', 'css'), // for `@use 'omega-desktop'`
30
40
  path.join(packageRoot, 'dist', 'assets', 'themes', themeId), // for `@use 'theme'`
31
41
  path.join(packageRoot, 'dist', 'assets', 'themes'), // for sibling-theme references
42
+ brandDir, // for `@use 'brand'` (generated above)
32
43
  path.join(projectRoot, 'src', 'assets', 'scss'), // consumer's own scss tree
33
44
  ];
34
45
 
@@ -0,0 +1,137 @@
1
+ // The notarization TOOLS: submit, staple, and PROVE, in one place
2
+ // ([#891](https://github.com/Omega-JS-Stack/omega/issues/891)).
3
+ //
4
+ // Proof run one shipped an unsigned, unnotarized mac app on a green build
5
+ // because every rung assumed instead of checking. These three do the opposite:
6
+ // each one reads the tool's own report and throws unless it says the words that
7
+ // mean success. A hook that returns without throwing has PROVED its artifact is
8
+ // notarized, stapled, and accepted by Gatekeeper.
9
+ //
10
+ // Both hooks (afterSign for the .app, artifactBuildCompleted for each .dmg) run
11
+ // every command through the `run` seam a test replaces with a fake `xcrun` /
12
+ // `spctl`; nothing here is global state.
13
+
14
+ const { spawn } = require('child_process');
15
+
16
+ // What Gatekeeper is asked about each artifact: an app is EXECUTED, a disk
17
+ // image is OPENED, and a DMG's verdict must come from its own signature rather
18
+ // than from a quarantine-less filesystem (`--context context:primary-signature`).
19
+ const ASSESS_FLAGS = {
20
+ app: '--type execute',
21
+ dmg: '--type open --context context:primary-signature',
22
+ };
23
+
24
+ /**
25
+ * The default command runner: stdout + stderr of a shell command, in the order
26
+ * they arrived, WHATEVER the exit code. These tools write their verdict to the
27
+ * console and exit non-zero when they refuse, so the failure text is the
28
+ * answer, never an exception to lose (a runner that rejects on exit code
29
+ * threw the verdict away with it).
30
+ *
31
+ * @returns {function(string): Promise<string>}
32
+ */
33
+ function toolRunner() {
34
+ return (command) => new Promise((resolve) => {
35
+ const child = spawn(command, { shell: true, stdio: ['ignore', 'pipe', 'pipe'] });
36
+ let output = '';
37
+ child.stdout.on('data', (data) => { output += data; });
38
+ child.stderr.on('data', (data) => { output += data; });
39
+ child.on('close', () => resolve(output));
40
+ });
41
+ }
42
+
43
+ /**
44
+ * Run a command and hand back its REPORT. A runner that throws (a test's fake)
45
+ * reports its message.
46
+ *
47
+ * @param {function} run - The command runner.
48
+ * @param {string} command - The command.
49
+ * @returns {Promise<string>}
50
+ */
51
+ async function report(run, command) {
52
+ try {
53
+ return String(await run(command) || '');
54
+ } catch (e) {
55
+ return String(e.message || '');
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Run a command and require its report to SAY it worked.
61
+ *
62
+ * @param {object} input
63
+ * @param {function} input.run - The command runner.
64
+ * @param {string} input.command - The command.
65
+ * @param {RegExp} input.expect - What success reads like in the tool's report.
66
+ * @param {string} input.what - What failed, for the thrown message.
67
+ * @returns {Promise<string>} The report.
68
+ * @throws {Error} When the report does not say it worked.
69
+ */
70
+ async function demand(input) {
71
+ const { run, command, expect, what } = input;
72
+ const output = await report(run, command);
73
+
74
+ if (!expect.test(output)) {
75
+ throw new Error(`[notarize] ${what}: ${output.trim() || 'no output'}`);
76
+ }
77
+
78
+ return output;
79
+ }
80
+
81
+ /**
82
+ * Submit an artifact to Apple and wait for the verdict. Used for the DMG (the
83
+ * .app rides @electron/notarize's own submit inside the afterSign hook).
84
+ *
85
+ * @param {object} input
86
+ * @param {string} input.filePath - The artifact.
87
+ * @param {object} input.credentials - `{ apiKeyPath, apiKeyId, apiIssuer }`.
88
+ * @param {function} [input.run] - Command runner (test seam).
89
+ * @returns {Promise<string>} notarytool's report.
90
+ */
91
+ function submit({ filePath, credentials, run = toolRunner() }) {
92
+ const { apiKeyPath, apiKeyId, apiIssuer } = credentials;
93
+
94
+ return demand({
95
+ run,
96
+ command: `xcrun notarytool submit "${filePath}" --key "${apiKeyPath}" --key-id "${apiKeyId}" --issuer "${apiIssuer}" --wait`,
97
+ expect: /status:\s*Accepted/i,
98
+ what: `notarization of ${filePath} was not accepted`,
99
+ });
100
+ }
101
+
102
+ /**
103
+ * Staple the notarization ticket to an artifact and PROVE it took: the staple
104
+ * itself, the stapler's own re-read, and Gatekeeper's assessment. A user's Mac
105
+ * checks exactly this, offline, before it will open the thing.
106
+ *
107
+ * @param {object} input
108
+ * @param {string} input.filePath - The .app or .dmg.
109
+ * @param {'app'|'dmg'} input.kind - Which assessment Gatekeeper is asked for.
110
+ * @param {function} [input.run] - Command runner (test seam).
111
+ * @returns {Promise<void>}
112
+ * @throws {Error} When stapling, validation or the assessment fails.
113
+ */
114
+ async function stapleAndProve({ filePath, kind, run = toolRunner() }) {
115
+ await demand({
116
+ run,
117
+ command: `xcrun stapler staple "${filePath}"`,
118
+ expect: /The staple and validate action worked!/i,
119
+ what: `could not staple the notarization ticket to ${filePath}`,
120
+ });
121
+
122
+ await demand({
123
+ run,
124
+ command: `xcrun stapler validate "${filePath}"`,
125
+ expect: /The validate action worked!/i,
126
+ what: `${filePath} carries no valid notarization ticket after stapling`,
127
+ });
128
+
129
+ await demand({
130
+ run,
131
+ command: `spctl --assess ${ASSESS_FLAGS[kind]} -vv "${filePath}"`,
132
+ expect: /: accepted/i,
133
+ what: `Gatekeeper refused ${filePath}`,
134
+ });
135
+ }
136
+
137
+ module.exports = { submit, stapleAndProve, toolRunner, report, ASSESS_FLAGS };
@@ -0,0 +1,57 @@
1
+ // The DMG's own notarization, stapling and proof (electron-builder's
2
+ // `artifactBuildCompleted`, [#891](https://github.com/Omega-JS-Stack/omega/issues/891)).
3
+ //
4
+ // The afterSign hook notarizes and staples the .app, and that ticket rides
5
+ // INSIDE the disk image, not on it: a user who downloads the .dmg has Gatekeeper
6
+ // assess the IMAGE first, and an unstapled image is refused on a Mac that is
7
+ // offline. So every .dmg this build produces is submitted with the same App
8
+ // Store Connect key, stapled, and PROVED (stapler validate + spctl --assess).
9
+ //
10
+ // Per artifact, not after all of them: electron-builder awaits this hook and
11
+ // THEN emits the `artifactCreated` its publisher uploads on, whereas
12
+ // `afterAllArtifactBuild` runs after every upload is already queued (run
13
+ // 34738410986 published a signed, notarized image with no staple that way).
14
+ //
15
+ // Non-mac legs produce no .dmg, so this hook is a no-op there by construction:
16
+ // it reads the artifact's extension, never the platform.
17
+
18
+ const fs = require('fs');
19
+ const path = require('path');
20
+ const Logger = require('../lib/logger');
21
+ const { submit, stapleAndProve, toolRunner } = require('./lib/notarize-tools.js');
22
+
23
+ const logger = new Logger('notarize-artifacts');
24
+
25
+ /**
26
+ * @param {object} event - electron-builder's artifactBuildCompleted event
27
+ * (`{ file, target, arch, packager, ... }`).
28
+ * @param {object} [options]
29
+ * @param {function} [options.run] - Command runner (test seam for xcrun/spctl).
30
+ * @returns {Promise<void>}
31
+ */
32
+ module.exports = async function notarizeArtifacts(event, { run = toolRunner() } = {}) {
33
+ const dmg = event.file;
34
+
35
+ if (path.extname(dmg).toLowerCase() !== '.dmg') {
36
+ return;
37
+ }
38
+
39
+ const apiKey = process.env.APPLE_API_KEY;
40
+ const apiKeyId = process.env.APPLE_API_KEY_ID;
41
+ const apiIssuer = process.env.APPLE_API_ISSUER;
42
+
43
+ if (!apiKey || !apiKeyId || !apiIssuer) {
44
+ throw new Error('[notarize] APPLE_API_KEY, APPLE_API_KEY_ID and APPLE_API_ISSUER are required to notarize the DMG. Run `omega deploy` (its precheck pushes them for CI) or set them in the target .env.');
45
+ }
46
+
47
+ const apiKeyPath = path.isAbsolute(apiKey) ? apiKey : path.resolve(process.cwd(), apiKey);
48
+ if (!fs.existsSync(apiKeyPath)) {
49
+ throw new Error(`[notarize] APPLE_API_KEY file not found at ${apiKeyPath}`);
50
+ }
51
+
52
+ logger.log(`Notarizing ${path.basename(dmg)} via App Store Connect API key (${apiKeyId})...`);
53
+ await submit({ filePath: dmg, credentials: { apiKeyPath, apiKeyId, apiIssuer }, run });
54
+
55
+ await stapleAndProve({ filePath: dmg, kind: 'dmg', run });
56
+ logger.log(`Stapled and verified: stapler validate + spctl --assess both accept ${path.basename(dmg)}.`);
57
+ };