@omega.js/desktop 0.50.0 → 0.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. package/README.md +2 -2
  2. package/dist/assets/css/tokens/_index.scss +1 -1
  3. package/dist/assets/themes/base/_includes/frontend/sections/account-section-header.html +4 -1
  4. package/dist/assets/themes/base/_includes/frontend/sections/footer.html +13 -6
  5. package/dist/assets/themes/base/_includes/frontend/sections/nav.html +10 -8
  6. package/dist/assets/themes/base/_includes/global/sections/account.html +3 -1
  7. package/dist/assets/themes/base/_includes/global/sections/app-sidebar.html +12 -8
  8. package/dist/assets/themes/base/_includes/global/sections/app-topbar.html +10 -6
  9. package/dist/assets/themes/base/_includes/global/sections/page-header.html +8 -4
  10. package/dist/assets/themes/base/_layouts/backend/pages/dashboard/index.html +24 -24
  11. package/dist/assets/themes/base/_layouts/frontend/pages/about.html +10 -10
  12. package/dist/assets/themes/base/_layouts/frontend/pages/account/index.html +35 -35
  13. package/dist/assets/themes/base/_layouts/frontend/pages/alternatives/index.html +4 -4
  14. package/dist/assets/themes/base/_layouts/frontend/pages/auth/signin.html +1 -1
  15. package/dist/assets/themes/base/_layouts/frontend/pages/auth/signup.html +4 -4
  16. package/dist/assets/themes/base/_layouts/frontend/pages/contact.html +3 -3
  17. package/dist/assets/themes/base/_layouts/frontend/pages/download.html +34 -33
  18. package/dist/assets/themes/base/_layouts/frontend/pages/extension/index.html +11 -11
  19. package/dist/assets/themes/base/_layouts/frontend/pages/legal/document.html +1 -1
  20. package/dist/assets/themes/base/_layouts/frontend/pages/status.html +1 -1
  21. package/dist/assets/themes/base/_layouts/frontend/pages/team/index.html +9 -7
  22. package/dist/assets/themes/base/_layouts/frontend/pages/team/member.html +5 -3
  23. package/dist/assets/themes/base/_sections/about/letter/section.html +1 -1
  24. package/dist/assets/themes/base/_sections/about/letter/section.json5 +4 -4
  25. package/dist/assets/themes/base/_sections/marketing/bento/section.html +1 -1
  26. package/dist/assets/themes/base/_sections/marketing/bento/section.json5 +15 -15
  27. package/dist/assets/themes/base/_sections/marketing/cta/section.json5 +1 -1
  28. package/dist/assets/themes/base/_sections/marketing/hero/section.html +6 -6
  29. package/dist/assets/themes/base/_sections/marketing/hero/section.json5 +10 -10
  30. package/dist/assets/themes/base/_sections/marketing/product-demo/section.html +1 -1
  31. package/dist/assets/themes/base/_sections/marketing/product-demo/section.json5 +2 -2
  32. package/dist/assets/themes/base/_sections/marketing/stats/section.html +1 -1
  33. package/dist/assets/themes/base/_sections/marketing/stats/section.json5 +5 -5
  34. package/dist/assets/themes/base/_sections/marketing/trusted-by/section.html +1 -1
  35. package/dist/assets/themes/base/_sections/marketing/trusted-by/section.json5 +2 -2
  36. package/dist/assets/themes/neobrutalism/_layouts/frontend/pages/index.html +10 -10
  37. package/dist/assets/themes/newsflash/_layouts/frontend/pages/index.html +10 -10
  38. package/dist/assets/themes/newsflash/_sections/marketing/desks/section.html +2 -2
  39. package/dist/assets/themes/newsflash/_sections/marketing/desks/section.json5 +4 -4
  40. package/dist/build.js +69 -28
  41. package/dist/cli-run.js +20 -13
  42. package/dist/cli.js +12 -7
  43. package/dist/commands/build.js +0 -2
  44. package/dist/commands/deploy.js +116 -44
  45. package/dist/commands/finalize-release.js +5 -4
  46. package/dist/commands/launch.js +12 -7
  47. package/dist/commands/lib/deploy-precheck.js +66 -34
  48. package/dist/commands/lib/ensure-target.js +71 -11
  49. package/dist/commands/package.js +0 -1
  50. package/dist/commands/publish.js +11 -4
  51. package/dist/commands/release.js +99 -252
  52. package/dist/commands/runner.js +176 -42
  53. package/dist/commands/sign-windows.js +46 -15
  54. package/dist/commands/test.js +4 -3
  55. package/dist/commands/validate-certs.js +291 -115
  56. package/dist/config/page-template.html +4 -0
  57. package/dist/defaults/.github/workflows/build.yml +146 -64
  58. package/dist/defaults/AGENTS.md +3 -2
  59. package/dist/defaults/_.gitignore +4 -2
  60. package/dist/defaults/config/certs/README.md +25 -45
  61. package/dist/defaults/config/omega.json5 +66 -32
  62. package/dist/defaults/hooks/deploy/pre.js +10 -0
  63. package/dist/defaults/src/assets/scss/main.scss +12 -2
  64. package/dist/defaults/src/integrations/tray/index.js +1 -1
  65. package/dist/gulp/main.js +13 -25
  66. package/dist/gulp/tasks/audit.js +18 -6
  67. package/dist/gulp/tasks/build-config.js +181 -60
  68. package/dist/gulp/tasks/bundle.js +108 -36
  69. package/dist/gulp/tasks/release.js +86 -4
  70. package/dist/gulp/tasks/sass.js +11 -0
  71. package/dist/hooks/lib/notarize-tools.js +137 -0
  72. package/dist/hooks/notarize-artifacts.js +57 -0
  73. package/dist/hooks/notarize.js +59 -8
  74. package/dist/lib/auth-persistence.js +25 -0
  75. package/dist/lib/client-bridge.js +11 -8
  76. package/dist/lib/deep-link.js +15 -8
  77. package/dist/lib/protocol.js +7 -1
  78. package/dist/lib/restart-manager/install.js +6 -3
  79. package/dist/lib/sign-helpers/auto-unlock.js +65 -27
  80. package/dist/lib/sign-helpers/console-lock.js +34 -0
  81. package/dist/lib/sign-helpers/exec-with-limit.js +68 -0
  82. package/dist/lib/sign-helpers/resolve-icons.js +6 -4
  83. package/dist/lib/tray.js +7 -6
  84. package/dist/main.js +16 -0
  85. package/dist/preload.js +8 -0
  86. package/dist/renderer.js +16 -4
  87. package/dist/runner/job-started.js +104 -0
  88. package/dist/test/fixtures/consumer-app/config/omega.json5 +7 -0
  89. package/dist/test/fixtures/consumer-app/package.json +1 -1
  90. package/dist/test/fixtures/consumer-app/src/assets/js/components/main/index.js +7 -1
  91. package/dist/test/harness/main-entry.js +2 -1
  92. package/dist/test/harness/renderer-preload.js +12 -3
  93. package/dist/test/runners/boot.js +101 -11
  94. package/dist/test/runners/electron.js +1 -1
  95. package/dist/test/suites/boot/consumer-app-boots.test.js +54 -0
  96. package/dist/test/suites/build/audit.test.js +45 -6
  97. package/dist/test/suites/build/auth-persistence-resolve.test.js +119 -0
  98. package/dist/test/suites/build/auto-unlock.test.js +105 -0
  99. package/dist/test/suites/build/boot-runner-timeout.test.js +253 -0
  100. package/dist/test/suites/build/brand-scss.test.js +106 -0
  101. package/dist/test/suites/build/build-config.test.js +146 -36
  102. package/dist/test/suites/build/build-json-bake.test.js +245 -0
  103. package/dist/test/suites/build/build-verbs.test.js +48 -15
  104. package/dist/test/suites/build/build-workflow-jobs.test.js +190 -0
  105. package/dist/test/suites/build/cli.test.js +32 -10
  106. package/dist/test/suites/build/config-schema.test.js +4 -4
  107. package/dist/test/suites/build/console-lock.test.js +51 -0
  108. package/dist/test/suites/build/defaults-scaffold.test.js +71 -2
  109. package/dist/test/suites/build/deploy-direct.test.js +241 -0
  110. package/dist/test/suites/build/deploy-dispatch.test.js +287 -0
  111. package/dist/test/suites/build/deploy-hook.test.js +169 -0
  112. package/dist/test/suites/build/ensure-target.test.js +14 -2
  113. package/dist/test/suites/build/env-delivery.test.js +19 -10
  114. package/dist/test/suites/build/env-watch.test.js +18 -7
  115. package/dist/test/suites/build/esm-only-dependency.test.js +127 -0
  116. package/dist/test/suites/build/exec-with-limit.test.js +53 -0
  117. package/dist/test/suites/build/finalize-release.test.js +2 -2
  118. package/dist/test/suites/build/get-config.test.js +120 -8
  119. package/dist/test/suites/build/github-utils.test.js +12 -6
  120. package/dist/test/suites/build/license-stamp.test.js +6 -4
  121. package/dist/test/suites/build/manager.test.js +88 -68
  122. package/dist/test/suites/build/manifest-deps.test.js +116 -0
  123. package/dist/test/suites/build/merge-line-files.test.js +27 -4
  124. package/dist/test/suites/build/notarize-artifacts.test.js +135 -0
  125. package/dist/test/suites/build/notarize-tools.test.js +38 -0
  126. package/dist/test/suites/build/notarize.test.js +207 -0
  127. package/dist/test/suites/build/release-pipeline.test.js +38 -60
  128. package/dist/test/suites/build/release-skipped-upload.test.js +107 -0
  129. package/dist/test/suites/build/resolve-icons.test.js +35 -35
  130. package/dist/test/suites/build/runner-job-guard.test.js +182 -0
  131. package/dist/test/suites/build/runner.test.js +635 -5
  132. package/dist/test/suites/build/sentry.test.js +8 -3
  133. package/dist/test/suites/build/setup-scripts.test.js +3 -0
  134. package/dist/test/suites/build/sign-windows.test.js +92 -8
  135. package/dist/test/suites/build/test-stealth.test.js +17 -11
  136. package/dist/test/suites/build/url-helpers.test.js +37 -17
  137. package/dist/test/suites/build/validate-certs.test.js +428 -55
  138. package/dist/test/suites/build/validate-config.test.js +3 -3
  139. package/dist/test/suites/main/auth-flow.test.js +12 -0
  140. package/dist/test/suites/main/auth-persistence.test.js +14 -17
  141. package/dist/test/suites/main/auto-updater.test.js +2 -2
  142. package/dist/test/suites/main/boot-sequence.test.js +1 -1
  143. package/dist/test/suites/main/client-bridge.integration.test.js +5 -82
  144. package/dist/test/suites/main/client-bridge.test.js +19 -6
  145. package/dist/test/suites/main/deep-link.test.js +54 -0
  146. package/dist/test/suites/main/startup-paths-and-ua.test.js +1 -1
  147. package/dist/test/suites/main/url-helpers.test.js +81 -72
  148. package/dist/test/suites/renderer/cross-context-helpers.test.js +19 -16
  149. package/dist/utils/build-pipeline.js +10 -10
  150. package/dist/utils/github.js +12 -51
  151. package/dist/utils/load-env.js +66 -0
  152. package/dist/utils/mode-helpers.js +43 -111
  153. package/dist/utils/platform.js +37 -0
  154. package/dist/utils/runner-env.js +2 -1
  155. package/dist/utils/runner-job-guard.js +149 -0
  156. package/dist/utils/ship-keys.js +52 -0
  157. package/dist/utils/test-stealth.js +5 -3
  158. package/dist/utils/url-helpers.js +33 -17
  159. package/dist/vendor/config/bundle-id.js +53 -0
  160. package/dist/vendor/config/client-config.js +141 -0
  161. package/dist/vendor/config/company.js +334 -15
  162. package/dist/vendor/config/dev-facts.js +48 -0
  163. package/dist/vendor/config/env-delivery.js +231 -9
  164. package/dist/vendor/config/env-retired.js +137 -0
  165. package/dist/vendor/config/env-rules.js +22 -3
  166. package/dist/vendor/config/env-schema.js +234 -117
  167. package/dist/vendor/config/env.js +55 -26
  168. package/dist/vendor/config/environment.js +189 -0
  169. package/dist/vendor/config/hooks.js +13 -11
  170. package/dist/vendor/config/index.js +125 -44
  171. package/dist/vendor/config/load.js +366 -115
  172. package/dist/vendor/config/merge.js +2 -2
  173. package/dist/vendor/config/order.js +3 -3
  174. package/dist/vendor/config/platforms.js +276 -0
  175. package/dist/vendor/config/repo.js +249 -103
  176. package/dist/vendor/config/retired-keys.js +232 -27
  177. package/dist/vendor/config/schema.js +525 -99
  178. package/dist/vendor/config/site-global.js +63 -53
  179. package/dist/vendor/config/targets.js +187 -0
  180. package/dist/vendor/config/validate.js +119 -59
  181. package/dist/vendor/devkit/argv.js +118 -0
  182. package/dist/vendor/devkit/attach-log-file.js +21 -13
  183. package/dist/vendor/devkit/brand-tokens.js +278 -0
  184. package/dist/vendor/devkit/brand-version.js +425 -0
  185. package/dist/vendor/devkit/build-json.js +91 -0
  186. package/dist/vendor/devkit/bundle.js +48 -0
  187. package/dist/vendor/devkit/certificate-expiry.js +108 -0
  188. package/dist/vendor/devkit/certs.js +16 -194
  189. package/dist/vendor/devkit/ci-workflows.js +124 -8
  190. package/dist/vendor/devkit/cli-router.js +3 -3
  191. package/dist/vendor/devkit/defaults-engine.js +69 -7
  192. package/dist/vendor/devkit/deploy-follow.js +297 -0
  193. package/dist/vendor/devkit/deploy-precheck.js +23 -8
  194. package/dist/vendor/devkit/deploy-record.js +11 -27
  195. package/dist/vendor/devkit/deploy-snapshot.js +661 -0
  196. package/dist/vendor/devkit/deploy.js +505 -75
  197. package/dist/vendor/devkit/git-auth.js +73 -0
  198. package/dist/vendor/devkit/git-remote.js +172 -0
  199. package/dist/vendor/devkit/github-repo.js +290 -0
  200. package/dist/vendor/devkit/local.js +98 -3
  201. package/dist/vendor/devkit/lockfile.js +127 -0
  202. package/dist/vendor/devkit/merge-line-files.js +23 -16
  203. package/dist/vendor/devkit/omega-bin.js +18 -3
  204. package/dist/vendor/devkit/pack-local.js +388 -0
  205. package/dist/vendor/devkit/preludes/index.js +120 -0
  206. package/dist/vendor/devkit/preludes/origin-heal.js +142 -0
  207. package/dist/vendor/devkit/service-account.js +43 -0
  208. package/dist/vendor/devkit/ship-plan.js +112 -0
  209. package/dist/vendor/devkit/signing-env.js +180 -0
  210. package/dist/vendor/devkit/signing-tree.js +92 -0
  211. package/dist/vendor/devkit/target-seams.js +142 -0
  212. package/dist/vendor/devkit/target-secrets.js +231 -57
  213. package/dist/vendor/devkit/test/esm-only-fixture.js +48 -0
  214. package/dist/vendor/devkit/test/fixtures/esm-only-package/browser.js +19 -0
  215. package/dist/vendor/devkit/test/fixtures/esm-only-package/index.js +20 -0
  216. package/dist/vendor/devkit/test/fixtures/esm-only-package/package.json +13 -0
  217. package/dist/vendor/monitoring/env.js +20 -10
  218. package/dist/vendor/monitoring/main.js +1 -1
  219. package/dist/vendor/monitoring/preload.js +1 -1
  220. package/dist/vendor/monitoring/renderer.js +1 -1
  221. package/docs/analytics.md +1 -1
  222. package/docs/auto-updater.md +5 -5
  223. package/docs/boot-sequence.md +1 -1
  224. package/docs/build-system.md +15 -7
  225. package/docs/client-bridge.md +9 -7
  226. package/docs/config-schema.md +4 -4
  227. package/docs/css.md +8 -2
  228. package/docs/deep-link.md +12 -4
  229. package/docs/environment-detection.md +32 -24
  230. package/docs/hooks.md +3 -1
  231. package/docs/icons.md +7 -7
  232. package/docs/index.md +61 -23
  233. package/docs/installer-options.md +24 -21
  234. package/docs/logging.md +5 -5
  235. package/docs/releasing.md +29 -19
  236. package/docs/runner.md +51 -14
  237. package/docs/shared/brands.md +12 -6
  238. package/docs/shared/breaking-changes.md +375 -21
  239. package/docs/shared/config.md +764 -199
  240. package/docs/shared/deploys.md +217 -91
  241. package/docs/shared/icons.md +18 -0
  242. package/docs/shared/local-dev.md +28 -8
  243. package/docs/shared/logging.md +9 -6
  244. package/docs/shared/monitoring.md +27 -13
  245. package/docs/shared/publishing.md +3 -3
  246. package/docs/shared/rulings.md +2 -2
  247. package/docs/shared/testing.md +1 -1
  248. package/docs/shared/theming.md +26 -1
  249. package/docs/shared/translation.md +49 -7
  250. package/docs/shared/updates.md +1 -1
  251. package/docs/signing.md +60 -34
  252. package/docs/test-framework.md +10 -5
  253. package/docs/themes.md +15 -1
  254. package/package.json +18 -12
  255. package/bin/omega-desktop +0 -2
  256. package/dist/commands/push-secrets.js +0 -141
  257. package/dist/test/suites/build/deliver-certs.test.js +0 -95
  258. package/dist/test/suites/build/derive-signing-env.test.js +0 -122
  259. package/dist/test/suites/build/push-secrets.test.js +0 -226
  260. package/dist/test/suites/build/resolve-signing-cert.test.js +0 -342
  261. package/dist/utils/deliver-certs.js +0 -69
  262. package/dist/utils/derive-signing-env.js +0 -56
  263. package/dist/utils/resolve-signing-cert.js +0 -175
  264. package/dist/vendor/config/desktop-artifacts.js +0 -110
  265. package/dist/vendor/config/instances.js +0 -208
  266. /package/dist/defaults/config/icons/{macos → mac}/dmg.png +0 -0
  267. /package/dist/defaults/config/icons/{macos → mac}/icon.png +0 -0
  268. /package/dist/defaults/config/icons/{macos → mac}/tray.png +0 -0
@@ -1,100 +1,473 @@
1
- // Build-layer tests for commands/validate-certs.js — provisioning profile parsing logic.
1
+ // Build-layer tests for commands/validate-certs.js: what CSC_LINK is allowed to
2
+ // carry, the three validity rungs every surface shares
3
+ // ([#892](https://github.com/Omega-JS-Stack/omega/issues/892)), and the rung an
4
+ // UNSET key is now ([#891](https://github.com/Omega-JS-Stack/omega/issues/891)):
5
+ // the step READS the env the boot derived, and the `config/certs/` scan that
6
+ // used to stop a deploy the build would have signed fine is gone.
2
7
  // The full validate flow (Keychain query, env vars) is exercised via real `npx omega deploy` prechecks.
3
8
 
4
9
  const path = require('path');
5
10
  const fs = require('fs');
6
11
  const os = require('os');
12
+ const { execFileSync } = require('child_process');
7
13
 
8
14
  const validateCerts = require(path.join(__dirname, '..', '..', '..', 'commands', 'validate-certs.js'));
9
15
  const defineCases = require('../../../vendor/devkit/test/define-cases.js');
10
16
 
11
- // A minimal mock provisioning profile (CMS-wrapped XML plist payload).
12
- function makeMockProvision({ appId, expirationDate }) {
13
- const appIdLine = appId
14
- ? `\n <key>application-identifier</key>\n <string>${appId}</string>`
15
- : '';
16
- const expLine = expirationDate
17
- ? `\n <key>ExpirationDate</key>\n <date>${expirationDate.toISOString()}</date>`
18
- : '';
19
-
20
- // The function strips outer CMS wrapping by regex-matching the inner plist; we just
21
- // wrap our test XML in some CMS-like junk so the regex still works.
22
- const inner = `<?xml version="1.0" encoding="UTF-8"?>
23
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
24
- <plist version="1.0">
25
- <dict>${appIdLine}${expLine}
26
- </dict>
27
- </plist>`;
28
-
29
- return `XX-CMS-WRAPPER-XX${inner}YY-CMS-WRAPPER-YY`;
17
+ // A REAL signing certificate: a self-signed cert exported to a .p12 with a
18
+ // password, so the expiry read is the same openssl path a build takes.
19
+ function stageSigningCert({ days = 365 } = {}) {
20
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'desktop-p12-'));
21
+ const keyPath = path.join(dir, 'private.key');
22
+ const certPath = path.join(dir, 'cert.cer');
23
+ const p12Path = path.join(dir, 'dev-id.p12');
24
+ const password = 'fixture-pass';
25
+
26
+ execFileSync('openssl', [
27
+ 'req', '-x509', '-nodes', '-newkey', 'rsa:2048', '-keyout', keyPath, '-out', certPath,
28
+ '-days', String(days), '-subj', '/CN=Developer ID Application: Fixture/C=US', '-outform', 'DER',
29
+ ], { stdio: 'pipe' });
30
+ execFileSync('openssl', [
31
+ 'pkcs12', '-export', '-legacy', '-inkey', keyPath, '-in', certPath, '-out', p12Path,
32
+ '-passout', 'env:OMEGA_P12_PASSWORD',
33
+ ], { stdio: 'pipe', env: { ...process.env, OMEGA_P12_PASSWORD: password } });
34
+
35
+ return { dir, p12Path, password };
36
+ }
37
+
38
+ // Run one call with exactly these env vars set (undefined deletes the key), then
39
+ // put the environment back the way it was. An ASYNC call holds the window until
40
+ // it settles: checkMac awaits the Keychain query, and a restore that fired at
41
+ // the first await would hand the rest of the run this machine's real signing env.
42
+ function withEnv(vars, run) {
43
+ const previous = {};
44
+ for (const [key, value] of Object.entries(vars)) {
45
+ previous[key] = process.env[key];
46
+ if (value === undefined) {
47
+ delete process.env[key];
48
+ } else {
49
+ process.env[key] = value;
50
+ }
51
+ }
52
+
53
+ const restore = () => {
54
+ for (const [key, value] of Object.entries(previous)) {
55
+ if (value === undefined) {
56
+ delete process.env[key];
57
+ } else {
58
+ process.env[key] = value;
59
+ }
60
+ }
61
+ };
62
+
63
+ let result;
64
+ try {
65
+ result = run();
66
+ } catch (error) {
67
+ restore();
68
+ throw error;
69
+ }
70
+
71
+ if (result && typeof result.then === 'function') {
72
+ return result.finally(restore);
73
+ }
74
+
75
+ restore();
76
+ return result;
77
+ }
78
+
79
+ // Run one call as if this leg were `platform`, then put the real value back.
80
+ // The mac rungs are darwin-only (#891), so a case about them has to say which
81
+ // leg it is rather than inherit the machine the suite happens to run on.
82
+ function withPlatform(platform, run) {
83
+ const descriptor = Object.getOwnPropertyDescriptor(process, 'platform');
84
+ Object.defineProperty(process, 'platform', { value: platform, configurable: true });
85
+
86
+ const restore = () => Object.defineProperty(process, 'platform', descriptor);
87
+
88
+ let result;
89
+ try {
90
+ result = run();
91
+ } catch (error) {
92
+ restore();
93
+ throw error;
94
+ }
95
+
96
+ if (result && typeof result.then === 'function') {
97
+ return result.finally(restore);
98
+ }
99
+
100
+ restore();
101
+ return result;
30
102
  }
31
103
 
32
104
  module.exports = defineCases({
33
105
  type: 'suite',
34
106
  layer: 'build',
35
- description: 'validate-certs — provisioning profile parsing',
107
+ description: 'validate-certs: the shapes CSC_LINK takes, the three validity rungs, and the unset-key rung',
36
108
  tests: [
109
+ // BOTH mac credentials take three shapes
110
+ // ([#872](https://github.com/Omega-JS-Stack/omega/issues/872)): a path, an https
111
+ // URL, or the file itself as inline base64. The build workflow ships two of them,
112
+ // because only the mac job decodes the base64 secrets to files, and treating that
113
+ // base64 as a path threw ENAMETOOLONG out of the precheck: first on CSC_LINK,
114
+ // then, once that was fixed, on APPLE_API_KEY.
115
+ // The cloud provider is read where sign-windows and the build read it,
116
+ // `platforms.windows.signing.cloud.provider` (#872): the checker used to look
117
+ // under `targets`, so a configured provider was always "not set" and its
118
+ // env vars were never checked.
119
+ {
120
+ name: 'the Windows cloud provider is read from platforms.windows, and its env vars are checked',
121
+ run: (ctx) => {
122
+ const config = { platforms: { windows: { signing: { strategy: 'cloud', cloud: { provider: 'azure' } } } } };
123
+ const azure = { AZURE_TENANT_ID: 't', AZURE_CLIENT_ID: 'c', AZURE_CLIENT_SECRET: 's', AZURE_TRUSTED_SIGNING_ENDPOINT: 'e' };
124
+ const none = Object.fromEntries(Object.keys(azure).map((key) => [key, undefined]));
125
+
126
+ const configured = [];
127
+ withEnv(azure, () => validateCerts.checkWindows(configured, 'cloud', config));
128
+ ctx.expect(configured).toEqual([]);
129
+
130
+ const bare = [];
131
+ withEnv(none, () => validateCerts.checkWindows(bare, 'cloud', config));
132
+ ctx.expect(bare.length).toBe(1);
133
+ ctx.expect(bare[0].message).toContain('Missing cloud-signing env vars for azure');
134
+ },
135
+ },
136
+ {
137
+ name: 'CSC_LINK carrying an inline base64 cert is reported, not statted',
138
+ run: (ctx) => {
139
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'desktop-csc-'));
140
+ const inline = 'MIIKvwIBAzCCCn8GCSqGSIb3DQEHAaCCCnAEggpsMIIKaDCCBR8'.repeat(60);
141
+
142
+ try {
143
+ ctx.expect(validateCerts.describeCertRef(inline, tmpDir).kind).toBe('inline');
144
+
145
+ const issues = [];
146
+ withEnv({ CSC_KEY_PASSWORD: 'hunter2' }, () => validateCerts.checkSigningCert(issues, inline, tmpDir));
147
+ ctx.expect(issues).toEqual([]);
148
+ } finally {
149
+ fs.rmSync(tmpDir, { recursive: true, force: true });
150
+ }
151
+ },
152
+ },
153
+ {
154
+ name: 'CSC_LINK carrying a URL is reported, not statted',
155
+ run: (ctx) => {
156
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'desktop-csc-'));
157
+ const url = 'https://certs.example.com/dev-id.p12';
158
+
159
+ try {
160
+ ctx.expect(validateCerts.describeCertRef(url, tmpDir).kind).toBe('url');
161
+
162
+ const issues = [];
163
+ withEnv({ CSC_KEY_PASSWORD: 'hunter2' }, () => validateCerts.checkSigningCert(issues, url, tmpDir));
164
+ ctx.expect(issues).toEqual([]);
165
+ } finally {
166
+ fs.rmSync(tmpDir, { recursive: true, force: true });
167
+ }
168
+ },
169
+ },
170
+ {
171
+ name: 'CSC_LINK naming a missing file is still the error it always was',
172
+ run: (ctx) => {
173
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'desktop-csc-'));
174
+
175
+ try {
176
+ const described = validateCerts.describeCertRef('config/certs/dev-id.p12', tmpDir);
177
+ ctx.expect(described.kind).toBe('file');
178
+ ctx.expect(described.exists).toBe(false);
179
+
180
+ const issues = [];
181
+ withEnv({ CSC_KEY_PASSWORD: 'hunter2' }, () => validateCerts.checkSigningCert(issues, 'config/certs/dev-id.p12', tmpDir));
182
+ ctx.expect(issues.length).toBe(1);
183
+ ctx.expect(issues[0].severity).toBe('error');
184
+ ctx.expect(issues[0].message).toContain('CSC_LINK points to a missing file');
185
+ } finally {
186
+ fs.rmSync(tmpDir, { recursive: true, force: true });
187
+ }
188
+ },
189
+ },
190
+ {
191
+ // Flipped by #891: a certificate nobody can open signs nothing, and the
192
+ // run that discovered this shipped an unsigned app on a warning.
193
+ name: 'a cert of any shape without CSC_KEY_PASSWORD is an ERROR',
194
+ run: (ctx) => {
195
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'desktop-csc-'));
196
+ const inline = 'MIIKvwIBAzCCCn8GCSqGSIb3DQEHAaCCCnAEggpsMIIKaDCCBR8'.repeat(60);
197
+
198
+ try {
199
+ const issues = [];
200
+ withEnv({ CSC_KEY_PASSWORD: undefined }, () => validateCerts.checkSigningCert(issues, inline, tmpDir));
201
+ ctx.expect(issues.length).toBe(1);
202
+ ctx.expect(issues[0].severity).toBe('error');
203
+ ctx.expect(issues[0].message).toContain('CSC_KEY_PASSWORD');
204
+ } finally {
205
+ fs.rmSync(tmpDir, { recursive: true, force: true });
206
+ }
207
+ },
208
+ },
209
+ // ── The three validity rungs, one rule (#892) ───────────────────────────
210
+ // The manager's certificates walk and this step read the SAME function and
211
+ // apply the same window, so a cert is never "fine" in one and stale in the
212
+ // other.
213
+ {
214
+ name: 'a real .p12 reports its expiry through the shared reader, and a healthy one raises nothing',
215
+ run: (ctx) => {
216
+ const { dir, p12Path, password } = stageSigningCert();
217
+
218
+ try {
219
+ const issues = [];
220
+ withEnv({ CSC_KEY_PASSWORD: password }, () => validateCerts.checkSigningCert(issues, p12Path, dir));
221
+ ctx.expect(issues).toEqual([]);
222
+ } finally {
223
+ fs.rmSync(dir, { recursive: true, force: true });
224
+ }
225
+ },
226
+ },
37
227
  {
38
- name: 'parseProvision extracts plist from CMS-wrapped file',
228
+ name: 'under 30 days WARNS, expired ERRORS, and a healthy date says nothing',
39
229
  run: (ctx) => {
40
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'desktop-test-'));
41
- const profPath = path.join(tmpDir, 'test.provisionprofile');
42
- fs.writeFileSync(profPath, makeMockProvision({
43
- appId: 'TEAMID.com.itwcw.testapp',
44
- expirationDate: new Date('2099-01-01T00:00:00Z'),
45
- }));
230
+ const days = (n) => ({ expiresAt: new Date(Date.now() + n * 86400000), daysLeft: n });
231
+
232
+ ctx.expect(validateCerts.expiryRung('config/certs/dev-id.p12', days(200))).toBeNull();
233
+
234
+ const soon = validateCerts.expiryRung('config/certs/dev-id.p12', days(10));
235
+ ctx.expect(soon.severity).toBe('warn');
236
+ ctx.expect(soon.message).toContain('expires in 10 day(s)');
237
+
238
+ const expired = validateCerts.expiryRung('config/certs/dev-id.p12', days(-5));
239
+ ctx.expect(expired.severity).toBe('error');
240
+ ctx.expect(expired.message).toContain('EXPIRED on');
241
+ ctx.expect(expired.message).toContain('omega manage --service certificates');
242
+
243
+ // A profile with no date at all is not a finding
244
+ ctx.expect(validateCerts.expiryRung('x.provisionprofile', null)).toBeNull();
245
+ },
246
+ },
247
+ {
248
+ name: 'a .p12 whose password does not open it is an error, not a silent pass',
249
+ run: (ctx) => {
250
+ const { dir, p12Path } = stageSigningCert();
46
251
 
47
252
  try {
48
- const result = validateCerts.parseProvision(profPath);
49
- ctx.expect(result.parsed).toBeTruthy();
50
- ctx.expect(result.parsed['application-identifier']).toBe('TEAMID.com.itwcw.testapp');
51
- ctx.expect(result.parsed.ExpirationDate).toBeInstanceOf(Date);
52
- ctx.expect(result.raw).toContain('<plist');
253
+ const issues = [];
254
+ withEnv({ CSC_KEY_PASSWORD: 'the-wrong-password' }, () => validateCerts.checkSigningCert(issues, p12Path, dir));
255
+ ctx.expect(issues.length).toBe(1);
256
+ ctx.expect(issues[0].severity).toBe('error');
257
+ } finally {
258
+ fs.rmSync(dir, { recursive: true, force: true });
259
+ }
260
+ },
261
+ },
262
+ {
263
+ name: 'APPLE_API_KEY carrying an inline base64 key is reported, not statted',
264
+ run: (ctx) => {
265
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'desktop-key-'));
266
+ const inline = 'LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tTUlHVEFnRUFNQk1HQnlxR1NNNDk'.repeat(50);
267
+
268
+ try {
269
+ ctx.expect(validateCerts.describeCertRef(inline, tmpDir).kind).toBe('inline');
270
+
271
+ // An inline key has no filename, so the AuthKey_<id>.p8 cross-check has
272
+ // nothing to read, while the id and issuer are still wanted.
273
+ const issues = [];
274
+ withEnv({ APPLE_API_KEY_ID: 'ABCDE12345', APPLE_API_ISSUER: '00000000-0000-0000-0000-000000000000' }, () => {
275
+ validateCerts.checkNotarizationKey(issues, inline, tmpDir);
276
+ });
277
+ ctx.expect(issues).toEqual([]);
53
278
  } finally {
54
279
  fs.rmSync(tmpDir, { recursive: true, force: true });
55
280
  }
56
281
  },
57
282
  },
58
283
  {
59
- name: 'parseProvision returns nulls for malformed file',
284
+ name: 'an inline APPLE_API_KEY still wants its key id and issuer',
60
285
  run: (ctx) => {
61
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'desktop-test-'));
62
- const profPath = path.join(tmpDir, 'bad.provisionprofile');
63
- fs.writeFileSync(profPath, 'this is not a plist at all');
286
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'desktop-key-'));
287
+ const inline = 'LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tTUlHVEFnRUFNQk1HQnlxR1NNNDk'.repeat(50);
64
288
 
65
289
  try {
66
- const result = validateCerts.parseProvision(profPath);
67
- ctx.expect(result.parsed).toBeNull();
68
- ctx.expect(result.raw).toBeNull();
290
+ const issues = [];
291
+ withEnv({ APPLE_API_KEY_ID: undefined, APPLE_API_ISSUER: undefined }, () => {
292
+ validateCerts.checkNotarizationKey(issues, inline, tmpDir);
293
+ });
294
+
295
+ ctx.expect(issues.length).toBe(2);
296
+ ctx.expect(issues.map((issue) => issue.message).join(' ')).toContain('APPLE_API_KEY_ID is missing');
297
+ ctx.expect(issues.map((issue) => issue.message).join(' ')).toContain('APPLE_API_ISSUER is missing');
69
298
  } finally {
70
299
  fs.rmSync(tmpDir, { recursive: true, force: true });
71
300
  }
72
301
  },
73
302
  },
74
303
  {
75
- name: 'parseProvision handles missing file',
304
+ name: 'APPLE_API_KEY naming a missing file is still the error it always was',
76
305
  run: (ctx) => {
77
- const result = validateCerts.parseProvision('/nonexistent/path.provisionprofile');
78
- ctx.expect(result.parsed).toBeNull();
306
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'desktop-key-'));
307
+
308
+ try {
309
+ const issues = [];
310
+ withEnv({ APPLE_API_KEY_ID: 'ABCDE12345', APPLE_API_ISSUER: 'x' }, () => {
311
+ validateCerts.checkNotarizationKey(issues, 'config/certs/AuthKey.p8', tmpDir);
312
+ });
313
+
314
+ ctx.expect(issues.length).toBe(1);
315
+ ctx.expect(issues[0].severity).toBe('error');
316
+ ctx.expect(issues[0].message).toContain('APPLE_API_KEY points to a missing file');
317
+ } finally {
318
+ fs.rmSync(tmpDir, { recursive: true, force: true });
319
+ }
79
320
  },
80
321
  },
81
322
  {
82
- name: 'parseProvision raw string contains the appId for substring match logic',
323
+ name: 'a real AuthKey file still gets the filename/key-id cross-check',
83
324
  run: (ctx) => {
84
- // The validator checks `parsed.raw.includes(expectedAppId)` — confirm the raw is
85
- // searchable for that.
86
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'desktop-test-'));
87
- const profPath = path.join(tmpDir, 'app.provisionprofile');
88
- fs.writeFileSync(profPath, makeMockProvision({
89
- appId: 'TEAMID.com.itwcw.somiibo',
90
- expirationDate: new Date('2099-01-01T00:00:00Z'),
91
- }));
325
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'desktop-key-'));
326
+ fs.mkdirSync(path.join(tmpDir, 'config', 'certs'), { recursive: true });
327
+ fs.writeFileSync(path.join(tmpDir, 'config', 'certs', 'AuthKey_ABCDE12345.p8'), 'key');
92
328
 
93
329
  try {
94
- const result = validateCerts.parseProvision(profPath);
95
- ctx.expect(result.raw.includes('com.itwcw.somiibo')).toBe(true);
96
- ctx.expect(result.raw.includes('com.itwcw.different-app')).toBe(false);
330
+ const issues = [];
331
+ withEnv({ APPLE_API_KEY_ID: 'ZZZZZ99999', APPLE_API_ISSUER: 'x' }, () => {
332
+ validateCerts.checkNotarizationKey(issues, 'config/certs/AuthKey_ABCDE12345.p8', tmpDir);
333
+ });
334
+
335
+ ctx.expect(issues.length).toBe(1);
336
+ ctx.expect(issues[0].message).toContain('filename suggests key ID "ABCDE12345"');
337
+ } finally {
338
+ fs.rmSync(tmpDir, { recursive: true, force: true });
339
+ }
340
+ },
341
+ },
342
+ {
343
+ // #891: the boot derives CSC_LINK from the signing tree, so an unset key
344
+ // here means the TREE has none. The line names where it looked and the
345
+ // walk that fills it, and there is no `config/certs/` scan left to
346
+ // contradict the build beside it.
347
+ name: 'an unset CSC_LINK names the signing tree it looked in, never a certs-dir scan (#891)',
348
+ run: async (ctx) => {
349
+ const brand = fs.mkdtempSync(path.join(os.tmpdir(), 'desktop-brand-'));
350
+ fs.mkdirSync(path.join(brand, 'config'), { recursive: true });
351
+ fs.writeFileSync(path.join(brand, 'config', 'omega.json5'), '{ brand: { id: "b" } }');
352
+ const target = path.join(brand, 'targets', 'desktop');
353
+ fs.mkdirSync(target, { recursive: true });
354
+ // A .p12 sitting in the OLD dispersed location: nothing may read it.
355
+ fs.mkdirSync(path.join(target, 'config', 'certs'), { recursive: true });
356
+ fs.writeFileSync(path.join(target, 'config', 'certs', 'developer-id-application.p12'), 'stale');
357
+
358
+ const cwd = process.cwd();
359
+ process.chdir(target);
360
+ try {
361
+ const issues = [];
362
+ await withPlatform('darwin', () => withEnv({ CSC_LINK: undefined, APPLE_API_KEY: undefined, APPLE_API_KEY_ID: undefined, CSC_KEY_PASSWORD: undefined }, () => (
363
+ validateCerts.checkMac(issues, { certificates: { providers: { apple: { teamId: 'TEAMTEST12' } } } }, { execFn: () => Promise.resolve('') })
364
+ )));
365
+
366
+ const csc = issues.find((issue) => issue.message.includes('CSC_LINK'));
367
+ ctx.expect(csc.message).toContain('.omega/certificates/apple/certificates/DEVELOPER_ID_APPLICATION_G2.p12');
368
+ ctx.expect(csc.message).toContain('omega manage --service certificates');
369
+ ctx.expect(issues.map((issue) => issue.message).join(' ')).not.toContain('Found .p12 in config/certs/');
370
+ } finally {
371
+ process.chdir(cwd);
372
+ fs.rmSync(brand, { recursive: true, force: true });
373
+ }
374
+ },
375
+ },
376
+ {
377
+ // #891: electron-builder imports a CSC_LINK .p12 into its OWN temporary
378
+ // keychain, so the login keychain answers nothing about that build. The
379
+ // rung that read it anyway failed a runner whose file rungs had both just
380
+ // passed, and it now belongs to the no-CSC_LINK path alone.
381
+ name: 'a CSC_LINK cert never consults the Keychain, and an unset one still does (#891)',
382
+ run: async (ctx) => {
383
+ const { dir, p12Path, password } = stageSigningCert();
384
+ const apple = { certificates: { providers: { apple: { teamId: 'TEAMTEST12' } } } };
385
+ // A machine with no Developer ID identity at all.
386
+ const queried = [];
387
+ const execFn = (command) => { queried.push(command); return Promise.resolve(''); };
388
+ const cwd = process.cwd();
389
+ process.chdir(dir);
390
+
391
+ try {
392
+ const withCert = [];
393
+ await withPlatform('darwin', () => withEnv({ CSC_LINK: p12Path, CSC_KEY_PASSWORD: password, APPLE_API_KEY: p12Path, APPLE_API_KEY_ID: 'ABCDE12345', APPLE_API_ISSUER: 'x' }, () => (
394
+ validateCerts.checkMac(withCert, apple, { execFn })
395
+ )));
396
+
397
+ ctx.expect(queried).toEqual([]);
398
+ ctx.expect(withCert.filter((issue) => issue.severity === 'error')).toEqual([]);
399
+
400
+ const without = [];
401
+ await withPlatform('darwin', () => withEnv({ CSC_LINK: undefined, CSC_KEY_PASSWORD: undefined, APPLE_API_KEY: undefined, APPLE_API_KEY_ID: undefined }, () => (
402
+ validateCerts.checkMac(without, apple, { execFn })
403
+ )));
404
+
405
+ const keychain = without.find((issue) => issue.message.includes('macOS Keychain'));
406
+ ctx.expect(queried.length).toBe(1);
407
+ ctx.expect(keychain.severity).toBe('error');
408
+ } finally {
409
+ process.chdir(cwd);
410
+ fs.rmSync(dir, { recursive: true, force: true });
411
+ }
412
+ },
413
+ },
414
+ {
415
+ // The gate the env schema already uses for the whole mac set: a brand
416
+ // that declares no Apple signing owes no credential, so the finding is a
417
+ // note rather than the rung that stops a deploy.
418
+ name: 'the unsigned findings are ERRORS only when the config declares Apple signing (#891)',
419
+ run: async (ctx) => {
420
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'desktop-gate-'));
421
+ const cwd = process.cwd();
422
+ process.chdir(tmpDir);
423
+
424
+ try {
425
+ const declared = [];
426
+ const undeclared = [];
427
+ const execFn = () => Promise.resolve('');
428
+ await withPlatform('darwin', () => withEnv({ CSC_LINK: undefined, APPLE_API_KEY: undefined, APPLE_API_KEY_ID: undefined }, async () => {
429
+ await validateCerts.checkMac(declared, { certificates: { providers: { apple: { teamId: 'TEAMTEST12' } } } }, { execFn });
430
+ await validateCerts.checkMac(undeclared, {}, { execFn });
431
+ }));
432
+
433
+ const severityOf = (issues, key) => issues.find((issue) => issue.message.includes(key)).severity;
434
+ ctx.expect(severityOf(declared, 'CSC_LINK')).toBe('error');
435
+ ctx.expect(severityOf(declared, 'APPLE_API_KEY')).toBe('error');
436
+ ctx.expect(severityOf(undeclared, 'CSC_LINK')).toBe('warn');
437
+ ctx.expect(severityOf(undeclared, 'APPLE_API_KEY')).toBe('warn');
438
+ } finally {
439
+ process.chdir(cwd);
440
+ fs.rmSync(tmpDir, { recursive: true, force: true });
441
+ }
442
+ },
443
+ },
444
+ {
445
+ // #891: the linux and windows legs of the build workflow blank the mac
446
+ // credentials on purpose (they cannot sign for mac), so running the mac
447
+ // rungs there only prints two warnings about material nobody put on that
448
+ // leg. The rungs belong to the leg that ships mac, and to no other.
449
+ name: 'off darwin the mac rungs do not run at all (#891)',
450
+ run: async (ctx) => {
451
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'desktop-leg-'));
452
+ const cwd = process.cwd();
453
+ process.chdir(tmpDir);
454
+
455
+ try {
456
+ const queried = [];
457
+ const execFn = (command) => { queried.push(command); return Promise.resolve(''); };
458
+
459
+ for (const platform of ['linux', 'win32']) {
460
+ const issues = [];
461
+ await withPlatform(platform, () => withEnv({ CSC_LINK: undefined, APPLE_API_KEY: undefined, APPLE_API_KEY_ID: undefined, CSC_KEY_PASSWORD: undefined }, () => (
462
+ validateCerts.checkMac(issues, { certificates: { providers: { apple: { teamId: 'TEAMTEST12' } } } }, { execFn })
463
+ )));
464
+
465
+ ctx.expect(issues).toEqual([]);
466
+ }
467
+
468
+ ctx.expect(queried).toEqual([]);
97
469
  } finally {
470
+ process.chdir(cwd);
98
471
  fs.rmSync(tmpDir, { recursive: true, force: true });
99
472
  }
100
473
  },
@@ -56,11 +56,11 @@ module.exports = defineCases({
56
56
  },
57
57
  },
58
58
  {
59
- name: 'platforms.win.signing.strategy enum enforced under desktop',
59
+ name: 'platforms.windows.signing.strategy enum enforced under desktop',
60
60
  run: (ctx) => {
61
- const cfg = { ...VALID, platforms: { win: { signing: { strategy: 'banana' } } } };
61
+ const cfg = { ...VALID, platforms: { windows: { signing: { strategy: 'banana' } } } };
62
62
  const { errors } = validateConfig(cfg, { target: 'desktop' });
63
- ctx.expect(errors.some((e) => e.includes('platforms.win.signing.strategy'))).toBe(true);
63
+ ctx.expect(errors.some((e) => e.includes('platforms.windows.signing.strategy'))).toBe(true);
64
64
  },
65
65
  },
66
66
  {
@@ -20,17 +20,28 @@ function get(url) {
20
20
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
21
21
 
22
22
  // Stub the externals for one test body. Returns { opened, tokens, shown, restore }.
23
+ //
24
+ // The baked `dev` block is staged too: the sign-in chain starts at
25
+ // getWebsiteUrl(), which since
26
+ // [#834](https://github.com/Omega-JS-Stack/omega/issues/834) answers from the
27
+ // resolved dev map the bundle task bakes (whose floor is @omega.js/config's
28
+ // classic numbers) and throws when an artifact carries none. The harness manager
29
+ // loads src/defaults' config, which has no build behind it, so a real dev
30
+ // artifact's block is stated here.
23
31
  function stubExternals(ctx) {
24
32
  const electron = require('electron');
33
+ const { CLASSIC_PORTS } = require('../../../vendor/config/index.js');
25
34
  const opened = [];
26
35
  const tokens = [];
27
36
  const shown = [];
28
37
  const origOpen = electron.shell.openExternal;
29
38
  const origHandle = ctx.manager.omega.handleAuthToken;
30
39
  const origShow = ctx.manager.windows.show;
40
+ const origDev = ctx.manager.config.dev;
31
41
  electron.shell.openExternal = async (url) => { opened.push(url); };
32
42
  ctx.manager.omega.handleAuthToken = (t) => { tokens.push(t); };
33
43
  ctx.manager.windows.show = (name) => { shown.push(name); };
44
+ ctx.manager.config.dev = { ports: { ...CLASSIC_PORTS } };
34
45
  return {
35
46
  opened,
36
47
  tokens,
@@ -39,6 +50,7 @@ function stubExternals(ctx) {
39
50
  electron.shell.openExternal = origOpen;
40
51
  ctx.manager.omega.handleAuthToken = origHandle;
41
52
  ctx.manager.windows.show = origShow;
53
+ if (origDev !== undefined) ctx.manager.config.dev = origDev; else delete ctx.manager.config.dev;
42
54
  ctx.manager.authFlow.cancel();
43
55
  },
44
56
  };
@@ -25,22 +25,24 @@ module.exports = defineCases({
25
25
  description: 'auth-persistence (main)',
26
26
  tests: [
27
27
  {
28
- name: 'resolve(): default is safeStorage; explicit none disables; unknown warns to null',
28
+ name: 'resolve(): a test run is none, whatever the config says',
29
29
  run: async (ctx) => {
30
30
  const m = ctx.manager;
31
+ ctx.expect(m.isTesting()).toBe(true);
31
32
  m.config.omega = m.config.omega || {};
32
33
  const orig = m.config.omega.authPersistence;
33
34
  try {
35
+ // Unset, and explicitly asking for the vault, both land on none: the harness
36
+ // never signs a real user in, so it never asks the OS keychain
37
+ // ([#907](https://github.com/Omega-JS-Stack/omega/issues/907)).
34
38
  delete m.config.omega.authPersistence;
35
- const def = await authPersistence.resolve(m);
36
- // Default resolves to safeStorage when the OS vault is available, null otherwise —
37
- // both are valid environments; assert it never picks something else.
38
- if (def) ctx.expect(def.name).toBe('safeStorage');
39
+ ctx.expect(await authPersistence.resolve(m)).toBeNull();
40
+ ctx.expect(authPersistence.getActive()).toBeNull();
39
41
 
40
- m.config.omega.authPersistence = 'none';
42
+ m.config.omega.authPersistence = 'safeStorage';
41
43
  ctx.expect(await authPersistence.resolve(m)).toBeNull();
42
44
 
43
- m.config.omega.authPersistence = 'does-not-exist';
45
+ m.config.omega.authPersistence = 'none';
44
46
  ctx.expect(await authPersistence.resolve(m)).toBeNull();
45
47
  } finally {
46
48
  if (orig !== undefined) m.config.omega.authPersistence = orig;
@@ -50,23 +52,18 @@ module.exports = defineCases({
50
52
  },
51
53
  },
52
54
  {
53
- name: 'register(): validates the strategy surface and makes it selectable',
55
+ name: 'register(): validates the strategy surface and adds it to the registry',
54
56
  run: async (ctx) => {
55
- const m = ctx.manager;
56
57
  ctx.expect(() => authPersistence.register('bad', {})).toThrow(/must implement/);
57
58
 
58
59
  authPersistence.register('desktop-test-custom', fakeStrategy());
59
- m.config.omega = m.config.omega || {};
60
- const orig = m.config.omega.authPersistence;
61
60
  try {
62
- m.config.omega.authPersistence = 'desktop-test-custom';
63
- const active = await authPersistence.resolve(m);
64
- ctx.expect(active.name).toBe('desktop-test-custom');
61
+ // Selection by config is proven at the build layer, on a production manager:
62
+ // under test mode resolve() answers none before it reads a thing.
63
+ ctx.expect(authPersistence._strategies['desktop-test-custom'].name).toBe('desktop-test-custom');
64
+ ctx.expect(await authPersistence._strategies['desktop-test-custom'].available()).toBe(true);
65
65
  } finally {
66
- if (orig !== undefined) m.config.omega.authPersistence = orig;
67
- else delete m.config.omega.authPersistence;
68
66
  delete authPersistence._strategies['desktop-test-custom'];
69
- await authPersistence.resolve(m);
70
67
  }
71
68
  },
72
69
  },