@omega.js/desktop 0.1.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 +19 -15
  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 +3 -3
  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
@@ -13,9 +13,10 @@ module.exports = async function (options) {
13
13
  // This process is a TEST RUN, and everything it spawns inherits that. The box's
14
14
  // mutating verbs read it and refuse to touch a real runner home
15
15
  // ([#337](https://github.com/Omega-JS-Stack/omega/issues/337): a suite once ran
16
- // a real `runner install` on the signing box). Deliberately NOT the canonical
17
- // OMEGA_TEST_MODE, which means "the app under test runs in testing mode" and
18
- // would change what the boot lane's production build compiles.
16
+ // a real `runner install` on the signing box). Deliberately a marker about THIS
17
+ // process, never a word about the app under test: the environment the thing
18
+ // being tested RUNS in is `OMEGA_ENVIRONMENT`, and the runners name it on their
19
+ // own children alone (#925).
19
20
  process.env.OMEGA_TEST_RUNNER = '1';
20
21
 
21
22
  // Tee all test output to <projectRoot>/logs/test.log (ANSI-stripped) — mirrors
@@ -1,16 +1,42 @@
1
- // Validate code-signing prerequisites (certs, profiles, env vars).
1
+ // Validate code-signing prerequisites (certs, env vars).
2
2
  //
3
- // Runs as an `omega deploy` precheck (non-fatal — warns and continues so the deploy
4
- // finishes even if certs aren't ready yet) and standalone via `npx omega validate-certs`.
3
+ // Runs as the `omega deploy` precheck and inside `omega publish`, both STRICT
4
+ // ([#891](https://github.com/Omega-JS-Stack/omega/issues/891)): a deploy is how
5
+ // a signed, notarized release ships, so missing material stops it. Standalone
6
+ // (`npx omega validate-certs`) it reports and exits 0 unless `--strict`.
7
+ //
8
+ // It READS the env, it never derives: the boot populated `CSC_LINK` and
9
+ // `APPLE_API_KEY` from the signing tree through the ONE derivation
10
+ // (utils/load-env.js, `@omega.js/devkit/signing-env`), so this step and the
11
+ // build that follows it judge the same answer. Scanning `config/certs/` for a
12
+ // file nothing pointed at is exactly the second rule that stopped a deploy the
13
+ // build would have signed fine, and it is gone.
14
+ //
15
+ // The mac rungs run on the MAC LEG and nowhere else
16
+ // ([#891](https://github.com/Omega-JS-Stack/omega/issues/891)): the linux and
17
+ // windows legs of the build workflow blank `CSC_LINK` and `APPLE_API_KEY` on
18
+ // purpose, because neither can sign for mac, so checking them there only
19
+ // printed two warnings about material nobody put on that leg. What is an ERROR
20
+ // vs a warning, once the run IS the mac leg:
21
+ // error no signing certificate at all, no Keychain identity on the leg that
22
+ // has no CSC_LINK, a .p12 whose password is missing, an EXPIRED
23
+ // certificate
24
+ // warning a certificate with under 30 days left (it still signs)
25
+ //
26
+ // Expiry is read by the ONE reader every surface uses (`@omega.js/devkit/certs`
27
+ // certificateExpiry, #892), so the manager's certificates walk and this step
28
+ // answer with the same date and the same three rungs.
5
29
  //
6
30
  // Exit codes:
7
31
  // 0 all checks passed
8
32
  // 1 failures (only when --strict is passed; otherwise we always exit 0 and just warn)
9
33
 
10
34
  const path = require('path');
11
- const fs = require('fs');
12
35
  const jetpack = require('fs-jetpack');
13
36
  const { execute } = require('node-powertools');
37
+ const { certificateExpiry, EXPIRY_WARN_DAYS } = require('../vendor/devkit/certs.js');
38
+ const { signingPathCandidates } = require('../vendor/devkit/signing-env.js');
39
+ const { requiredWhenHolds } = require('../vendor/config/env-rules.js');
14
40
 
15
41
  const Manager = new (require('../build.js'));
16
42
  const logger = Manager.logger('validate-certs');
@@ -27,8 +53,8 @@ module.exports = async function (options) {
27
53
 
28
54
  const issues = [];
29
55
 
30
- // macOS checks (run on any platform if cert files are present in config/certs/, since
31
- // CI builds happen cross-platform — useful to flag bad files even when running on win/linux).
56
+ // The mac rungs, on the mac leg only (#891): checkMac itself says so and
57
+ // prints the skip line off darwin.
32
58
  await checkMac(issues, config);
33
59
 
34
60
  if (platform === 'win32') {
@@ -56,123 +82,244 @@ module.exports = async function (options) {
56
82
  return { ok: false, issues };
57
83
  };
58
84
 
59
- async function checkMac(issues, config) {
60
- const projectRoot = process.cwd();
61
- const certsDir = path.join(projectRoot, 'config', 'certs');
62
- const expectedAppId = config.app?.appId
63
- || `com.itwcw.${config.brand?.id || 'app'}`;
85
+ /**
86
+ * The mac rungs, in order: the certificate, the notarization key, the team id,
87
+ * and the Keychain identity the build falls back to when the env names no
88
+ * certificate at all. DARWIN ONLY (#891): no other leg ships mac, and the
89
+ * credentials are blanked on the ones that do not.
90
+ *
91
+ * @param {object[]} issues - The run's issue list, appended to.
92
+ * @param {object} config - The resolved omega config.
93
+ * @param {object} [options] - `{ execFn }`, the Keychain query seam.
94
+ */
95
+ async function checkMac(issues, config, options = {}) {
96
+ if (process.platform !== 'darwin') {
97
+ logger.log('Skipping the macOS signing checks: this leg does not ship mac.');
98
+ return;
99
+ }
64
100
 
65
- // 1. Developer ID Application .p12
101
+ const projectRoot = process.cwd();
102
+ const execFn = options.execFn || execute;
103
+
104
+ // The gate the env schema already uses for the whole mac set (its
105
+ // `requiredWhen`): a brand that DECLARES Apple signing owes these
106
+ // credentials, so a missing one is an error on the leg that signs (#891). A
107
+ // brand that declares none is simply unsigned, and every finding here is a
108
+ // note instead, the Keychain rung included.
109
+ const declaresApple = requiredWhenHolds(config || {}, 'certificates.providers.apple');
110
+ const signs = declaresApple ? 'error' : 'warn';
111
+
112
+ // 1. Developer ID Application .p12. The boot derived CSC_LINK from the
113
+ // signing tree when the tree holds one, so an unset key here means the tree
114
+ // does NOT, and the line names where it looked and the walk that fills it.
66
115
  const cscLink = process.env.CSC_LINK;
67
116
  if (cscLink) {
68
- const p12Path = path.isAbsolute(cscLink) ? cscLink : path.join(projectRoot, cscLink);
69
- if (!jetpack.exists(p12Path)) {
70
- issues.push({ severity: 'error', message: `CSC_LINK points to a missing file: ${p12Path}` });
71
- } else if (!process.env.CSC_KEY_PASSWORD) {
72
- issues.push({ severity: 'warn', message: 'CSC_KEY_PASSWORD not set — signing will fail.' });
73
- } else {
74
- logger.log(logger.format.green(`✓ Developer ID Application cert at ${path.relative(projectRoot, p12Path)}`));
75
- }
76
- } else if (jetpack.exists(certsDir)) {
77
- // Look for the file even if env not pointed
78
- const p12Files = jetpack.find(certsDir, { matching: '*.p12', recursive: false }) || [];
79
- if (p12Files.length === 0) {
80
- issues.push({ severity: 'warn', message: `No .p12 found in config/certs/ and CSC_LINK not set. (Required for macOS signing.)` });
81
- } else {
82
- issues.push({ severity: 'warn', message: `Found .p12 in config/certs/ but CSC_LINK env var is not set. Set CSC_LINK=${path.relative(projectRoot, p12Files[0])}` });
83
- }
117
+ checkSigningCert(issues, cscLink, projectRoot);
84
118
  } else {
85
- issues.push({ severity: 'warn', message: 'No config/certs/ directory and no CSC_LINK env var. Skipping mac cert check.' });
119
+ issues.push({ severity: signs, message: `CSC_LINK is not set${treeNote(projectRoot, 'CSC_LINK')}, so this build cannot be signed for macOS. Run \`omega manage --service certificates\` to produce the signing material.` });
86
120
  }
87
121
 
88
- // 2. Provisioning profile (optional — only some apps need one)
89
- const provisionFiles = jetpack.exists(certsDir)
90
- ? (jetpack.find(certsDir, { matching: '*.provisionprofile', recursive: false }) || [])
91
- : [];
92
- for (const profPath of provisionFiles) {
93
- try {
94
- const parsed = parseProvision(profPath);
95
- const rel = path.relative(projectRoot, profPath);
96
-
97
- if (!parsed.parsed || !parsed.raw) {
98
- issues.push({ severity: 'error', message: `${rel}: could not parse plist payload.` });
99
- continue;
100
- }
101
-
102
- const exp = parsed.parsed.ExpirationDate;
103
- if (exp && new Date(exp) < new Date()) {
104
- issues.push({ severity: 'error', message: `${rel}: EXPIRED on ${new Date(exp).toISOString()}.` });
105
- continue;
106
- }
107
-
108
- // Days until expiry — warn if < 30 days
109
- if (exp) {
110
- const daysLeft = Math.floor((new Date(exp) - new Date()) / (1000 * 60 * 60 * 24));
111
- if (daysLeft < 30) {
112
- issues.push({ severity: 'warn', message: `${rel}: expires in ${daysLeft} day(s) — renew soon.` });
113
- }
114
- }
115
-
116
- // App ID match — provisioning profiles embed the app-id-prefix.bundle-id.
117
- // Be lenient: just check the bundle id appears anywhere in the raw plist text.
118
- if (expectedAppId && !parsed.raw.includes(expectedAppId)) {
119
- issues.push({ severity: 'error', message: `${rel}: does not contain expected appId "${expectedAppId}". This profile is for a different app.` });
120
- continue;
121
- }
122
-
123
- logger.log(logger.format.green(`✓ ${rel} (expires ${exp ? new Date(exp).toISOString().slice(0,10) : 'unknown'})`));
124
- } catch (e) {
125
- issues.push({ severity: 'error', message: `${path.relative(projectRoot, profPath)}: parse failed: ${e.message}` });
126
- }
127
- }
128
-
129
- // 3. Notarization API key (.p8)
122
+ // 2. Notarization API key (.p8): derived from the same tree, judged the same
123
+ // way. The non-mac legs of the build workflow carry the base64 secret itself.
130
124
  const apiKeyEnv = process.env.APPLE_API_KEY;
131
125
  if (apiKeyEnv) {
132
- const p8Path = path.isAbsolute(apiKeyEnv) ? apiKeyEnv : path.join(projectRoot, apiKeyEnv);
133
- if (!jetpack.exists(p8Path)) {
134
- issues.push({ severity: 'error', message: `APPLE_API_KEY points to a missing file: ${p8Path}` });
135
- } else {
136
- // Check the filename includes the key id (Apple's filename convention is AuthKey_<KEY_ID>.p8).
137
- const keyId = process.env.APPLE_API_KEY_ID;
138
- const issuer = process.env.APPLE_API_ISSUER;
139
- const filenameKeyId = path.basename(p8Path).match(/AuthKey_([A-Z0-9]+)\.p8$/i)?.[1];
140
-
141
- if (!keyId) {
142
- issues.push({ severity: 'warn', message: 'APPLE_API_KEY set but APPLE_API_KEY_ID is missing.' });
143
- } else if (filenameKeyId && filenameKeyId !== keyId) {
144
- issues.push({ severity: 'warn', message: `APPLE_API_KEY filename suggests key ID "${filenameKeyId}" but APPLE_API_KEY_ID is "${keyId}". Likely a typo.` });
145
- }
146
-
147
- if (!issuer) {
148
- issues.push({ severity: 'warn', message: 'APPLE_API_KEY set but APPLE_API_ISSUER is missing.' });
149
- }
150
-
151
- logger.log(logger.format.green(`✓ Notarization API key at ${path.relative(projectRoot, p8Path)}`));
152
- }
126
+ checkNotarizationKey(issues, apiKeyEnv, projectRoot);
153
127
  } else {
154
- issues.push({ severity: 'warn', message: 'No APPLE_API_KEY set. Required for notarization (`npm run release`).' });
128
+ issues.push({ severity: signs, message: `APPLE_API_KEY is not set${treeNote(projectRoot, 'APPLE_API_KEY')}, so this build cannot be notarized. Set APPLE_API_KEY_ID in the .env and run \`omega manage --service certificates\` to produce the key.` });
155
129
  }
156
130
 
157
- // 4. Apple Team ID format check
131
+ // 3. Apple Team ID format check
158
132
  const teamId = process.env.APPLE_TEAM_ID;
159
133
  if (teamId && !/^[A-Z0-9]{10}$/.test(teamId)) {
160
134
  issues.push({ severity: 'warn', message: `APPLE_TEAM_ID="${teamId}" doesn't match the expected 10-character format.` });
161
135
  }
162
136
 
163
- // 5. macOS Keychain identity (only when running on macOS — irrelevant on win/linux)
164
- if (process.platform === 'darwin') {
137
+ // 4. macOS Keychain identity: the NO-CSC_LINK path only. A CSC_LINK .p12 is
138
+ // imported by electron-builder into its OWN temporary keychain, so the login
139
+ // keychain says nothing about whether that build can sign. Asking it anyway
140
+ // is what stopped a runner whose file rungs had both just passed (#891).
141
+ // With no CSC_LINK, identity discovery IS the signer, so the missing identity
142
+ // is the same finding an unset key is.
143
+ if (!cscLink) {
165
144
  try {
166
- const out = await execute('security find-identity -v -p codesigning', { log: false });
145
+ const out = await execFn('security find-identity -v -p codesigning', { log: false });
167
146
  const identities = String(out || '');
168
147
  if (!identities.includes('Developer ID Application')) {
169
- issues.push({ severity: 'warn', message: 'No "Developer ID Application" identity in macOS Keychain. Import your .p12 via Keychain Access.' });
148
+ issues.push({ severity: signs, message: 'No "Developer ID Application" identity in the macOS Keychain, so a local signed build has nothing to sign with. Run `omega manage --service certificates` (it imports the .p12) or import it via Keychain Access.' });
170
149
  } else {
171
150
  logger.log(logger.format.green('✓ Keychain has Developer ID Application identity.'));
172
151
  }
173
152
  } catch (e) {
174
- issues.push({ severity: 'warn', message: `Could not query macOS Keychain: ${e.message}` });
153
+ issues.push({ severity: 'error', message: `Could not query the macOS Keychain: ${e.message}` });
175
154
  }
155
+ } else {
156
+ logger.log('CSC_LINK names the certificate, so electron-builder imports it into its own keychain: no Keychain identity is read.');
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Where the derivation LOOKED for a key's file, as a clause to append to the
162
+ * "not set" line. The paths are the signing tree's, company tier first, from
163
+ * the ONE home of them; a target outside a brand has no tree and gets nothing.
164
+ *
165
+ * @param {string} projectRoot - The target root.
166
+ * @param {string} key - 'CSC_LINK' or 'APPLE_API_KEY'.
167
+ * @returns {string} ` (no file at <path> or <path>)`, or ''.
168
+ */
169
+ function treeNote(projectRoot, key) {
170
+ const candidate = signingPathCandidates({ env: process.env, targetDir: projectRoot })
171
+ .find((entry) => entry.key === key);
172
+
173
+ return candidate ? ` and no file at ${candidate.paths.join(' or ')}` : '';
174
+ }
175
+
176
+ /**
177
+ * What a signing credential's env value CARRIES. Both of the mac ones take three
178
+ * shapes: a path to the file, an https URL, or the file ITSELF as inline base64,
179
+ * and the build workflow ships two of them, because only the mac job decodes the
180
+ * base64 secrets to disk before the build
181
+ * ([#872](https://github.com/Omega-JS-Stack/omega/issues/872)). Statting an inline
182
+ * value as a path is what killed the linux leg: a base64 .p12 (or .p8) is
183
+ * thousands of characters and the stat throws ENAMETOOLONG.
184
+ *
185
+ * @param {string} value - The raw env value (CSC_LINK, APPLE_API_KEY).
186
+ * @param {string} projectRoot - The target root, for resolving a relative path.
187
+ * @returns {{ kind: 'url'|'inline'|'file'|'unreadable', path?: string, exists?: boolean, reason?: string }}
188
+ */
189
+ function describeCertRef(value, projectRoot) {
190
+ if (/^https?:\/\//i.test(value)) {
191
+ return { kind: 'url' };
192
+ }
193
+
194
+ const candidate = path.isAbsolute(value) ? value : path.join(projectRoot, value);
195
+
196
+ let exists;
197
+ try {
198
+ exists = !!jetpack.exists(candidate);
199
+ } catch (e) {
200
+ // A value no filesystem can even answer for. Base64 is the known case (a
201
+ // name past the OS limit); anything else is reported, never thrown.
202
+ if (looksInline(value)) {
203
+ return { kind: 'inline' };
204
+ }
205
+ return { kind: 'unreadable', reason: e.message };
206
+ }
207
+
208
+ if (!exists && looksInline(value)) {
209
+ return { kind: 'inline' };
210
+ }
211
+
212
+ return { kind: 'file', path: candidate, exists };
213
+ }
214
+
215
+ // A value that is no path at all: one long unbroken run of characters. A real
216
+ // .p12/.p8 path is short and carries a separator; base64 carries neither.
217
+ function looksInline(value) {
218
+ return value.length > 200 && !value.includes('/') && !value.includes(path.sep);
219
+ }
220
+
221
+ /**
222
+ * Report on one credential reference, and hand the caller back what it turned out
223
+ * to be so its OWN checks (the cert's password, the key's id and issuer) can run
224
+ * whatever the shape is. A shape with no file skips the file check, because there
225
+ * is no file. Null means it was already reported as a hard failure.
226
+ *
227
+ * @param {object[]} issues - The run's issue list, appended to.
228
+ * @param {object} options - `{ key, value, projectRoot, describe }`.
229
+ * @returns {object|null} the described reference, or null when it failed.
230
+ */
231
+ function checkCertRef(issues, options) {
232
+ const { key, value, projectRoot, describe } = options;
233
+ const ref = describeCertRef(value, projectRoot);
234
+
235
+ if (ref.kind === 'unreadable') {
236
+ issues.push({ severity: 'error', message: `${key} could not be read as a path: ${ref.reason}` });
237
+ return null;
238
+ }
239
+
240
+ if (ref.kind === 'file' && !ref.exists) {
241
+ issues.push({ severity: 'error', message: `${key} points to a missing file: ${ref.path}` });
242
+ return null;
243
+ }
244
+
245
+ if (ref.kind === 'inline') {
246
+ logger.log(`${key} carries an inline base64 ${describe} (${value.length} chars), so there is no file to check.`);
247
+ } else if (ref.kind === 'url') {
248
+ logger.log(`${key} carries a ${describe} URL, so there is no file to check.`);
249
+ }
250
+
251
+ return ref;
252
+ }
253
+
254
+ /**
255
+ * The Developer ID Application certificate (CSC_LINK), whatever shape it arrives
256
+ * in. Only a file is checkable as a file; every shape still needs the password
257
+ * that decrypts it.
258
+ */
259
+ function checkSigningCert(issues, value, projectRoot) {
260
+ const cert = checkCertRef(issues, { key: 'CSC_LINK', value, projectRoot, describe: 'certificate' });
261
+ if (!cert) {
262
+ return;
263
+ }
264
+
265
+ if (!process.env.CSC_KEY_PASSWORD) {
266
+ issues.push({ severity: 'error', message: 'CSC_KEY_PASSWORD is not set, so the signing certificate cannot be opened and signing WILL fail.' });
267
+ return;
268
+ }
269
+
270
+ if (cert.kind !== 'file') {
271
+ return;
272
+ }
273
+
274
+ const rel = pathLabel(projectRoot, cert.path);
275
+
276
+ // The same three rungs the manager's certificates walk reports, over the same
277
+ // reader (#892): fine, under 30 days warns, expired errors.
278
+ let expiry;
279
+ try {
280
+ expiry = certificateExpiry(cert.path, { password: process.env.CSC_KEY_PASSWORD });
281
+ } catch (e) {
282
+ issues.push({ severity: 'error', message: `${rel}: ${e.message}` });
283
+ return;
284
+ }
285
+
286
+ const rung = expiryRung(rel, expiry);
287
+ if (rung) {
288
+ issues.push(rung);
289
+ return;
290
+ }
291
+
292
+ logger.log(logger.format.green(`✓ Developer ID Application cert at ${rel} (expires ${expiry.expiresAt.toISOString().slice(0, 10)}, ${expiry.daysLeft} days)`));
293
+ }
294
+
295
+ /**
296
+ * The App Store Connect API key (APPLE_API_KEY). Apple's filename convention is
297
+ * `AuthKey_<KEY_ID>.p8`, so the id/filename cross-check only exists for a FILE:
298
+ * an inline key has no filename. The id and the issuer are wanted either way,
299
+ * because notarytool takes all three.
300
+ */
301
+ function checkNotarizationKey(issues, value, projectRoot) {
302
+ const apiKey = checkCertRef(issues, { key: 'APPLE_API_KEY', value, projectRoot, describe: 'key' });
303
+ if (!apiKey) {
304
+ return;
305
+ }
306
+
307
+ const keyId = process.env.APPLE_API_KEY_ID;
308
+ const issuer = process.env.APPLE_API_ISSUER;
309
+ const filenameKeyId = apiKey.kind === 'file' ? path.basename(apiKey.path).match(/AuthKey_([A-Z0-9]+)\.p8$/i)?.[1] : null;
310
+
311
+ if (!keyId) {
312
+ issues.push({ severity: 'warn', message: 'APPLE_API_KEY set but APPLE_API_KEY_ID is missing.' });
313
+ } else if (filenameKeyId && filenameKeyId !== keyId) {
314
+ issues.push({ severity: 'warn', message: `APPLE_API_KEY filename suggests key ID "${filenameKeyId}" but APPLE_API_KEY_ID is "${keyId}". Likely a typo.` });
315
+ }
316
+
317
+ if (!issuer) {
318
+ issues.push({ severity: 'warn', message: 'APPLE_API_KEY set but APPLE_API_ISSUER is missing.' });
319
+ }
320
+
321
+ if (apiKey.kind === 'file') {
322
+ logger.log(logger.format.green(`✓ Notarization API key at ${pathLabel(projectRoot, apiKey.path)}`));
176
323
  }
177
324
  }
178
325
 
@@ -193,9 +340,11 @@ function checkWindows(issues, strategy, config) {
193
340
  }
194
341
 
195
342
  if (strategy === 'cloud') {
196
- const provider = config.targets?.win?.signing?.cloud?.provider;
343
+ // The same key sign-windows and the build read: a platform setting, so it
344
+ // sits under `platforms`, never under `targets` (#872).
345
+ const provider = config.platforms?.windows?.signing?.cloud?.provider;
197
346
  if (!provider) {
198
- issues.push({ severity: 'error', message: 'Cloud signing strategy selected but no provider set (config.targets.win.signing.cloud.provider).' });
347
+ issues.push({ severity: 'error', message: 'Cloud signing strategy selected but no provider set (platforms.windows.signing.cloud.provider in omega.json5).' });
199
348
  return;
200
349
  }
201
350
  const required = {
@@ -216,23 +365,50 @@ function checkWindows(issues, strategy, config) {
216
365
  issues.push({ severity: 'error', message: `Unknown Windows signing strategy: ${strategy}` });
217
366
  }
218
367
 
219
- // Parse a provisioning profile (a CMS-signed plist). Extract the embedded plist payload.
220
- function parseProvision(filepath) {
221
- const raw = jetpack.read(filepath);
222
- if (!raw) return { parsed: null, raw: null };
368
+ /**
369
+ * What to CALL a file in a message: target-relative when it sits inside the
370
+ * target, absolute otherwise. The signing tree lives outside the target, so a
371
+ * relative label there would be a run of `../` nobody can read.
372
+ *
373
+ * @param {string} projectRoot - The target root.
374
+ * @param {string} filePath - Absolute path to the file.
375
+ * @returns {string}
376
+ */
377
+ function pathLabel(projectRoot, filePath) {
378
+ const rel = path.relative(projectRoot, filePath);
379
+ return rel.startsWith('..') ? filePath : rel;
380
+ }
223
381
 
224
- const match = raw.match(/<\?xml(.|\n)*?<\/plist>/);
225
- if (!match) return { parsed: null, raw: null };
382
+ /**
383
+ * The ONE verdict on a date: expired errors, under the shared renewal window
384
+ * warns, anything else is fine. The same three rungs the manager's certificates
385
+ * walk prints, from the same constant (#892).
386
+ *
387
+ * @param {string} label - What the message names (a target-relative path).
388
+ * @param {{ expiresAt: Date, daysLeft: number }|null} expiry
389
+ * @returns {{ severity: 'error'|'warn', message: string }|null} null when it is fine.
390
+ */
391
+ function expiryRung(label, expiry) {
392
+ if (!expiry) {
393
+ return null;
394
+ }
226
395
 
227
- const xml = match[0];
228
- let parsed;
229
- try {
230
- parsed = require('plist').parse(xml);
231
- } catch (e) {
232
- return { parsed: null, raw: xml };
396
+ if (expiry.daysLeft < 0) {
397
+ return { severity: 'error', message: `${label}: EXPIRED on ${expiry.expiresAt.toISOString().slice(0, 10)} (${Math.abs(expiry.daysLeft)} days ago). Run \`omega manage --service certificates\` to renew it.` };
233
398
  }
234
- return { parsed, raw: xml };
399
+
400
+ if (expiry.daysLeft < EXPIRY_WARN_DAYS) {
401
+ return { severity: 'warn', message: `${label}: expires in ${expiry.daysLeft} day(s), renew soon.` };
402
+ }
403
+
404
+ return null;
235
405
  }
236
406
 
237
407
  // Exported for tests.
238
- module.exports.parseProvision = parseProvision;
408
+ module.exports.describeCertRef = describeCertRef;
409
+ module.exports.checkCertRef = checkCertRef;
410
+ module.exports.checkSigningCert = checkSigningCert;
411
+ module.exports.checkNotarizationKey = checkNotarizationKey;
412
+ module.exports.checkWindows = checkWindows;
413
+ module.exports.checkMac = checkMac;
414
+ module.exports.expiryRung = expiryRung;
@@ -10,6 +10,10 @@
10
10
 
11
11
  <!-- Per-page CSS — present only on this view (compiled from src/assets/scss/pages/{{ page.name }}.scss). -->
12
12
  <link href="../../assets/css/components/{{ page.name }}.bundle.css?cb={{ cacheBust }}" rel="stylesheet">
13
+
14
+ <!-- The ONE build snapshot (#743): dist/build.js, the same file every OMEGA
15
+ browser surface loads, ahead of any bundle that reads OMEGA_BUILD_JSON. -->
16
+ <script src="../../build.js?cb={{ cacheBust }}"></script>
13
17
  </head>
14
18
  <body>
15
19
  <!--