@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,122 +0,0 @@
1
- // derive-signing-env tests — the .env stamp the manager's disperse service used
2
- // to write is derived at build time instead
3
- // ([#678](https://github.com/Omega-JS-Stack/omega/issues/678)).
4
-
5
- const path = require('path');
6
- const fs = require('fs');
7
- const os = require('os');
8
- const jetpack = require('fs-jetpack');
9
-
10
- const deriveSigningEnv = require('../../../utils/derive-signing-env.js');
11
- const defineCases = require('../../../vendor/devkit/test/define-cases.js');
12
-
13
- function stageTarget(files) {
14
- const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'desktop-derive-'));
15
- for (const file of files) {
16
- jetpack.write(path.join(tmp, 'config', 'certs', file), 'artifact');
17
- }
18
- return tmp;
19
- }
20
-
21
- module.exports = defineCases({
22
- type: 'group',
23
- layer: 'build',
24
- description: 'derive-signing-env',
25
- tests: [
26
- {
27
- name: 'unset + the file is delivered → the target-relative path is set',
28
- run: (ctx) => {
29
- const tmp = stageTarget(['developer-id-application.p12', 'AuthKey_ABCDE12345.p8']);
30
-
31
- try {
32
- const env = { APPLE_API_KEY_ID: 'ABCDE12345' };
33
- const derived = deriveSigningEnv({ env, projectDir: tmp });
34
-
35
- ctx.expect(env.CSC_LINK).toBe('config/certs/developer-id-application.p12');
36
- ctx.expect(env.APPLE_API_KEY).toBe('config/certs/AuthKey_ABCDE12345.p8');
37
- ctx.expect(derived.map((entry) => entry.key).sort()).toEqual(['APPLE_API_KEY', 'CSC_LINK']);
38
- } finally {
39
- fs.rmSync(tmp, { recursive: true, force: true });
40
- }
41
- },
42
- },
43
- {
44
- name: 'already set → never overridden (an explicit answer wins)',
45
- run: (ctx) => {
46
- const tmp = stageTarget(['developer-id-application.p12', 'AuthKey_ABCDE12345.p8']);
47
-
48
- try {
49
- const env = {
50
- APPLE_API_KEY_ID: 'ABCDE12345',
51
- CSC_LINK: '/keychain/mine.p12',
52
- APPLE_API_KEY: '/secrets/AuthKey.p8',
53
- };
54
- const derived = deriveSigningEnv({ env, projectDir: tmp });
55
-
56
- ctx.expect(env.CSC_LINK).toBe('/keychain/mine.p12');
57
- ctx.expect(env.APPLE_API_KEY).toBe('/secrets/AuthKey.p8');
58
- ctx.expect(derived).toEqual([]);
59
- } finally {
60
- fs.rmSync(tmp, { recursive: true, force: true });
61
- }
62
- },
63
- },
64
- {
65
- name: 'the file is absent → the key stays unset (electron-builder skips/auto-discovers)',
66
- run: (ctx) => {
67
- const tmp = stageTarget([]);
68
-
69
- try {
70
- const env = { APPLE_API_KEY_ID: 'ABCDE12345' };
71
- const derived = deriveSigningEnv({ env, projectDir: tmp });
72
-
73
- ctx.expect('CSC_LINK' in env).toBe(false);
74
- ctx.expect('APPLE_API_KEY' in env).toBe(false);
75
- ctx.expect(derived).toEqual([]);
76
- } finally {
77
- fs.rmSync(tmp, { recursive: true, force: true });
78
- }
79
- },
80
- },
81
- {
82
- name: 'no APPLE_API_KEY_ID → no notarization path to derive',
83
- run: (ctx) => {
84
- const tmp = stageTarget(['AuthKey_ABCDE12345.p8']);
85
-
86
- try {
87
- const env = {};
88
- deriveSigningEnv({ env, projectDir: tmp });
89
-
90
- ctx.expect('APPLE_API_KEY' in env).toBe(false);
91
- } finally {
92
- fs.rmSync(tmp, { recursive: true, force: true });
93
- }
94
- },
95
- },
96
- {
97
- // The gulp boot is too heavy to require here (it pulls in Manager, gulp and
98
- // every task), so the call site is checked statically: the option name has
99
- // to be the one the deriver reads, and the value it passes has to be a
100
- // binding main.js actually declares. Shipped bound to an undeclared
101
- // `projectDir` once, which is a ReferenceError on every desktop build.
102
- name: 'the gulp main call site passes a declared binding as projectDir',
103
- run: (ctx) => {
104
- const source = fs.readFileSync(path.join(__dirname, '..', '..', '..', 'gulp', 'main.js'), 'utf8');
105
- const call = source.match(/require\('\.\.\/utils\/derive-signing-env\.js'\)\(\{([^}]*)\}\)/);
106
- ctx.expect(call).toBeTruthy();
107
-
108
- const options = {};
109
- for (const part of call[1].split(',')) {
110
- const [name, value] = part.split(':').map((piece) => piece.trim());
111
- options[name] = value === undefined ? name : value;
112
- }
113
-
114
- ctx.expect(Object.keys(options).sort()).toEqual(['env', 'projectDir']);
115
-
116
- const binding = options.projectDir;
117
- const declared = new RegExp(`\\b(?:const|let|var)\\s+${binding}\\s*=`).test(source);
118
- ctx.expect(declared).toBe(true);
119
- },
120
- },
121
- ],
122
- });
@@ -1,226 +0,0 @@
1
- // Build-layer tests for commands/push-secrets.js — the composed source, desktop's
2
- // file → base64 value seam, and the publish itself.
3
- //
4
- // Offline by construction ([#682](https://github.com/Omega-JS-Stack/omega/issues/682)):
5
- // the brand and target are temp dirs and the `gh`/`git` boundaries are injected,
6
- // so the suite proves the real send shape without a repo, a credential or a
7
- // network. What the shared publisher owns (the five loud skips, the declared-repo
8
- // guard) is pinned once in @omega.js/devkit's target-secrets suite.
9
-
10
- const path = require('path');
11
- const fs = require('fs');
12
- const os = require('os');
13
-
14
- const pushSecrets = require(path.join(__dirname, '..', '..', '..', 'commands', 'push-secrets.js'));
15
- const defineCases = require('../../../vendor/devkit/test/define-cases.js');
16
-
17
- const quiet = { log() {}, warn() {}, error() {} };
18
-
19
- // A brand root (config/omega.json5) with a targets/desktop target under it.
20
- function tmpBrand({ brandEnv, targetEnv, repo } = {}) {
21
- const brand = fs.mkdtempSync(path.join(os.tmpdir(), 'desktop-brand-'));
22
- fs.mkdirSync(path.join(brand, 'config'), { recursive: true });
23
- fs.writeFileSync(path.join(brand, 'config', 'omega.json5'), [
24
- '{',
25
- ' brand: { id: "b" },',
26
- ...(repo ? [` repo: { providers: { github: { repo: '${repo}' } } },`] : []),
27
- '}',
28
- ].join('\n'));
29
- if (brandEnv !== undefined) fs.writeFileSync(path.join(brand, '.env'), brandEnv);
30
-
31
- const target = path.join(brand, 'targets', 'desktop');
32
- fs.mkdirSync(target, { recursive: true });
33
- if (targetEnv !== undefined) fs.writeFileSync(path.join(target, '.env'), targetEnv);
34
-
35
- return { brand, target };
36
- }
37
-
38
- module.exports = defineCases({
39
- type: 'suite',
40
- layer: 'build',
41
- description: 'push-secrets — composed env source + value resolution',
42
- tests: [
43
- {
44
- name: 'publishEnvSecrets: the schema set over the gh boundary, file-path secrets base64\'d, values on stdin',
45
- run: (ctx) => {
46
- const { brand, target } = tmpBrand({
47
- repo: 'acme/app',
48
- brandEnv: [
49
- 'GH_TOKEN=brand-token',
50
- 'APPLE_TEAM_ID=BRANDTEAM',
51
- 'CSC_LINK=config/certs/dev-id.p12',
52
- 'MY_CUSTOM_THING=custom',
53
- '',
54
- ].join('\n'),
55
- });
56
- // A brand-level certificate: named by a path the target root does not
57
- // hold, resolved from the brand root.
58
- const cert = path.join(brand, 'config', 'certs', 'dev-id.p12');
59
- fs.mkdirSync(path.dirname(cert), { recursive: true });
60
- fs.writeFileSync(cert, Buffer.from('FAKE-CERT-BYTES'));
61
-
62
- const gh = [];
63
- try {
64
- const result = pushSecrets.publishEnvSecrets({
65
- targetDir: target,
66
- logger: quiet,
67
- env: {},
68
- gitExecFn: () => 'git@github.com:acme/app.git\n',
69
- execFn: (file, args, options) => { gh.push({ file, args, input: options.input }); return ''; },
70
- });
71
-
72
- ctx.expect(result.published).toEqual(['APPLE_TEAM_ID', 'CSC_LINK', 'GH_TOKEN']);
73
- ctx.expect(gh.map((c) => c.args.join(' '))).toEqual([
74
- 'auth status',
75
- 'secret set APPLE_TEAM_ID --repo acme/app',
76
- 'secret set CSC_LINK --repo acme/app',
77
- 'secret set GH_TOKEN --repo acme/app',
78
- ]);
79
- // The CERTIFICATE travels, never this laptop's path to it — and every
80
- // value goes on stdin, never in argv.
81
- ctx.expect(gh.slice(1).map((c) => c.input)).toEqual([
82
- 'BRANDTEAM',
83
- Buffer.from('FAKE-CERT-BYTES').toString('base64'),
84
- 'brand-token',
85
- ]);
86
- ctx.expect(gh.every((c) => c.file === 'gh')).toBe(true);
87
- } finally {
88
- fs.rmSync(brand, { recursive: true, force: true });
89
- }
90
- },
91
- },
92
- {
93
- name: 'publishEnvSecrets: a checkout that is not the brand\'s DECLARED repo skips loudly, never touching gh',
94
- run: (ctx) => {
95
- // The declared repo is the only proof of where these certificates
96
- // belong — an inferred remote is a fork, a template clone or a vendored
97
- // target away from arming a stranger's Actions. Desktop pushes the most
98
- // dangerous payload of the three binds.
99
- const { brand, target } = tmpBrand({ repo: 'acme/app', brandEnv: 'GH_TOKEN=brand-token\n' });
100
- const undeclared = tmpBrand({ brandEnv: 'GH_TOKEN=brand-token\n' });
101
- const said = [];
102
- const loud = { log: (m) => said.push(m), warn: (m) => said.push(m), error: (m) => said.push(m) };
103
- const noGh = () => { throw new Error('gh must not run'); };
104
-
105
- try {
106
- ctx.expect(pushSecrets.publishEnvSecrets({
107
- targetDir: target,
108
- logger: loud,
109
- env: {},
110
- execFn: noGh,
111
- gitExecFn: () => 'git@github.com:Omega-JS-Stack/omega.git\n',
112
- })).toEqual({ skipped: 'repo-mismatch' });
113
-
114
- ctx.expect(pushSecrets.publishEnvSecrets({
115
- targetDir: undeclared.target,
116
- logger: loud,
117
- env: {},
118
- execFn: noGh,
119
- gitExecFn: () => 'git@github.com:acme/app.git\n',
120
- })).toEqual({ skipped: 'no-declared-repo' });
121
-
122
- const heard = said.join('\n');
123
- ctx.expect(heard).toContain("this brand's repo is acme/app");
124
- ctx.expect(heard).toContain('names no GitHub repo in config');
125
- } finally {
126
- fs.rmSync(brand, { recursive: true, force: true });
127
- fs.rmSync(undeclared.brand, { recursive: true, force: true });
128
- }
129
- },
130
- },
131
- {
132
- name: 'collectEnvSecrets: the brand-root .env supplies the target — no target .env exists (#678)',
133
- run: (ctx) => {
134
- const { brand, target } = tmpBrand({
135
- brandEnv: [
136
- 'GH_TOKEN=brand-token',
137
- 'APPLE_TEAM_ID=BRANDTEAM',
138
- 'STRIPE_SECRET_KEY=sk_live_backend_only',
139
- 'GOOGLE_ANALYTICS_SECRET_DESKTOP=desktop-stream',
140
- 'MY_CUSTOM_THING=custom',
141
- 'OMEGA_FONTAWESOME_ROOT=/Users/dev/.omega/fontawesome',
142
- '',
143
- ].join('\n'),
144
- });
145
-
146
- try {
147
- ctx.expect(fs.existsSync(path.join(target, '.env'))).toBe(false);
148
-
149
- ctx.expect(pushSecrets.collectEnvSecrets(target)).toEqual({
150
- APPLE_TEAM_ID: 'BRANDTEAM',
151
- GH_TOKEN: 'brand-token',
152
- GOOGLE_ANALYTICS_SECRET: 'desktop-stream',
153
- });
154
- } finally {
155
- fs.rmSync(brand, { recursive: true, force: true });
156
- }
157
- },
158
- },
159
- {
160
- name: 'collectEnvSecrets: a target .env overrides the brand root per key (#678)',
161
- run: (ctx) => {
162
- const { brand, target } = tmpBrand({
163
- brandEnv: 'GH_TOKEN=brand-token\nAPPLE_TEAM_ID=BRANDTEAM\n',
164
- targetEnv: 'APPLE_TEAM_ID=TARGETTEAM\nCSC_KEY_PASSWORD=target-pass\n',
165
- });
166
-
167
- try {
168
- ctx.expect(pushSecrets.collectEnvSecrets(target)).toEqual({
169
- APPLE_TEAM_ID: 'TARGETTEAM',
170
- CSC_KEY_PASSWORD: 'target-pass',
171
- GH_TOKEN: 'brand-token',
172
- });
173
- } finally {
174
- fs.rmSync(brand, { recursive: true, force: true });
175
- }
176
- },
177
- },
178
- {
179
- name: 'collectEnvSecrets: --only narrows the composed set',
180
- run: (ctx) => {
181
- const { brand, target } = tmpBrand({ brandEnv: 'GH_TOKEN=brand-token\nAPPLE_TEAM_ID=BRANDTEAM\n' });
182
-
183
- try {
184
- ctx.expect(pushSecrets.collectEnvSecrets(target, { only: 'GH_TOKEN' })).toEqual({ GH_TOKEN: 'brand-token' });
185
- } finally {
186
- fs.rmSync(brand, { recursive: true, force: true });
187
- }
188
- },
189
- },
190
- {
191
- name: 'fileValueResolver: a plain string travels as itself, a path that names no file too',
192
- run: (ctx) => {
193
- const resolve = pushSecrets.fileValueResolver({ targetDir: '/tmp' });
194
-
195
- ctx.expect(resolve('plain-string-value', 'CSC_KEY_PASSWORD')).toBe('plain-string-value');
196
- // Path-shaped but nowhere on disk: it is a string, not a certificate.
197
- ctx.expect(resolve('config/certs/does-not-exist.p12', 'CSC_LINK')).toBe('config/certs/does-not-exist.p12');
198
- },
199
- },
200
- {
201
- name: 'fileValueResolver: an existing file travels as base64 — absolute, target-relative, or brand-relative',
202
- run: (ctx) => {
203
- const { brand, target } = tmpBrand();
204
-
205
- const absolute = path.join(brand, 'cert.p12');
206
- fs.writeFileSync(absolute, Buffer.from('ABS'));
207
- fs.mkdirSync(path.join(target, 'config', 'certs'), { recursive: true });
208
- fs.writeFileSync(path.join(target, 'config', 'certs', 'target.pem'), Buffer.from('TARGET'));
209
- fs.mkdirSync(path.join(brand, '.omega', 'secrets'), { recursive: true });
210
- fs.writeFileSync(path.join(brand, '.omega', 'secrets', 'brand-cert.p8'), Buffer.from('BRAND'));
211
-
212
- try {
213
- const resolve = pushSecrets.fileValueResolver({ targetDir: target });
214
-
215
- ctx.expect(resolve(absolute, 'CSC_LINK')).toBe(Buffer.from('ABS').toString('base64'));
216
- ctx.expect(resolve('config/certs/target.pem', 'CSC_LINK')).toBe(Buffer.from('TARGET').toString('base64'));
217
- // Brand-level material: the path is relative to the BRAND root, which
218
- // the target root does not hold.
219
- ctx.expect(resolve('.omega/secrets/brand-cert.p8', 'APPLE_API_KEY')).toBe(Buffer.from('BRAND').toString('base64'));
220
- } finally {
221
- fs.rmSync(brand, { recursive: true, force: true });
222
- }
223
- },
224
- },
225
- ],
226
- });
@@ -1,342 +0,0 @@
1
- // resolve-signing-cert tests ([#28](https://github.com/Omega-JS-Stack/omega/issues/28)) —
2
- // the signing lookup ORDER: an explicit CSC_LINK wins, then the brand's
3
- // gitignored .omega/certificates/apple/ tree, then the company's tree (reached
4
- // through the brand's .omega/company.json stamp — a brand is NEVER physically
5
- // inside its company folder), then the Keychain.
6
- //
7
- // The Keychain branch is today's default and MUST stay it: with no cert in the
8
- // tree the resolver hands electron-builder nothing, exactly as before.
9
- //
10
- // The tree branch is only taken when the password actually OPENS the .p12 —
11
- // the adoption flow can pair an adopted legacy .p12 with a freshly generated
12
- // CSC_KEY_PASSWORD, and handing electron-builder an unopenable file is the
13
- // failure this lookup exists to avoid. Real openssl material is generated in a
14
- // temp dir for that check; nothing here is stubbed.
15
-
16
- const { execFileSync } = require('node:child_process');
17
- const { mkdtempSync } = require('node:fs');
18
- const { homedir, tmpdir } = require('node:os');
19
- const { join } = require('node:path');
20
- const jetpack = require('fs-jetpack');
21
-
22
- const resolveSigningCert = require('../../../utils/resolve-signing-cert.js');
23
- const defineCases = require('../../../vendor/devkit/test/define-cases.js');
24
-
25
- const CERT_REL = join('.omega', 'certificates', 'apple', 'certificates', 'DEVELOPER_ID_APPLICATION_G2.p12');
26
- const REAL_PASSWORD = 'fixture-p12-password';
27
-
28
- // One real self-signed key/cert exported to a .p12, the same way the manager's
29
- // certificate-manager.js exports one (`pkcs12 -export -legacy`).
30
- let realP12 = null;
31
- function realP12Bytes() {
32
- if (realP12) {
33
- return realP12;
34
- }
35
-
36
- const dir = mkdtempSync(join(tmpdir(), 'omega-signing-material-'));
37
- execFileSync('openssl', [
38
- 'req', '-x509', '-nodes', '-newkey', 'rsa:2048',
39
- '-keyout', join(dir, 'private.key'), '-out', join(dir, 'cert.cer'),
40
- '-days', '2', '-subj', '/CN=Signing Fixture/C=US', '-outform', 'DER',
41
- ], { stdio: 'pipe' });
42
- execFileSync('openssl', [
43
- 'pkcs12', '-export', '-legacy',
44
- '-inkey', join(dir, 'private.key'), '-in', join(dir, 'cert.cer'),
45
- '-out', join(dir, 'fixture.p12'), '-passout', 'env:OMEGA_P12_PASSWORD',
46
- ], { stdio: 'pipe', env: { ...process.env, OMEGA_P12_PASSWORD: REAL_PASSWORD } });
47
-
48
- realP12 = jetpack.read(join(dir, 'fixture.p12'), 'buffer');
49
- return realP12;
50
- }
51
-
52
- // A brand root with a desktop app under it, mirroring a real brand monorepo.
53
- // `cert: 'real'` writes an openable .p12; `cert: 'dummy'` writes bytes that are
54
- // not a certificate at all. A brand root ALWAYS carries a `.omega/` — that is
55
- // what identifies it to the walk.
56
- function stageBrand({ cert = null, root = null } = {}) {
57
- const brandRoot = root || mkdtempSync(join(tmpdir(), 'omega-signing-'));
58
- const targetRoot = join(brandRoot, 'targets', 'desktop');
59
-
60
- jetpack.dir(targetRoot);
61
- jetpack.dir(join(brandRoot, '.omega'));
62
- if (cert === 'real') {
63
- jetpack.write(join(brandRoot, CERT_REL), realP12Bytes());
64
- } else if (cert === 'dummy') {
65
- jetpack.write(join(brandRoot, CERT_REL), 'p12-bytes');
66
- }
67
-
68
- return { brandRoot, targetRoot };
69
- }
70
-
71
- // Stamp a brand as company-managed — the ONE membership rule
72
- // (@omega.js/config's readCompanyRoot / .omega/company.json).
73
- function stampCompany(brandRoot, companyRoot) {
74
- jetpack.write(join(brandRoot, '.omega', 'company.json'), JSON.stringify({ root: companyRoot }));
75
- }
76
-
77
- module.exports = defineCases({
78
- type: 'group',
79
- layer: 'build',
80
- description: 'resolve-signing-cert',
81
- tests: [
82
- {
83
- name: 'no cert anywhere → keychain (today\'s default, unchanged)',
84
- run: (ctx) => {
85
- const { targetRoot } = stageBrand();
86
-
87
- const resolved = resolveSigningCert({ projectRoot: targetRoot, env: {} });
88
-
89
- ctx.expect(resolved.source).toBe('keychain');
90
- ctx.expect(resolved.cscLink).toBe(null);
91
- },
92
- },
93
- {
94
- name: 'an explicit CSC_LINK always wins',
95
- run: (ctx) => {
96
- const { targetRoot } = stageBrand({ cert: 'real' });
97
-
98
- const resolved = resolveSigningCert({
99
- projectRoot: targetRoot,
100
- env: { CSC_LINK: 'config/certs/developer-id-application.p12', CSC_KEY_PASSWORD: REAL_PASSWORD },
101
- });
102
-
103
- ctx.expect(resolved.source).toBe('env');
104
- ctx.expect(resolved.cscLink).toBe('config/certs/developer-id-application.p12');
105
- },
106
- },
107
- {
108
- name: 'the brand certificates tree is preferred over the keychain',
109
- run: (ctx) => {
110
- const { brandRoot, targetRoot } = stageBrand({ cert: 'real' });
111
-
112
- const resolved = resolveSigningCert({ projectRoot: targetRoot, env: { CSC_KEY_PASSWORD: REAL_PASSWORD } });
113
-
114
- ctx.expect(resolved.source).toBe('certificates');
115
- ctx.expect(resolved.cscLink).toBe(join(brandRoot, CERT_REL));
116
- },
117
- },
118
- {
119
- name: 'a stamped brand reaches its company tree — a SIBLING directory, never an ancestor',
120
- run: (ctx) => {
121
- // No nesting: the company workspace lives beside the brand, and the
122
- // brand's .omega/company.json is the only thing pointing at it.
123
- const { brandRoot, targetRoot } = stageBrand();
124
- const companyRoot = mkdtempSync(join(tmpdir(), 'omega-company-'));
125
- jetpack.write(join(companyRoot, CERT_REL), realP12Bytes());
126
- stampCompany(brandRoot, companyRoot);
127
-
128
- const resolved = resolveSigningCert({ projectRoot: targetRoot, env: { CSC_KEY_PASSWORD: REAL_PASSWORD } });
129
-
130
- ctx.expect(resolved.source).toBe('certificates');
131
- ctx.expect(resolved.cscLink).toBe(join(companyRoot, CERT_REL));
132
- },
133
- },
134
- {
135
- name: 'the brand tree wins over the company tree',
136
- run: (ctx) => {
137
- const { brandRoot, targetRoot } = stageBrand({ cert: 'real' });
138
- const companyRoot = mkdtempSync(join(tmpdir(), 'omega-company-'));
139
- jetpack.write(join(companyRoot, CERT_REL), realP12Bytes());
140
- stampCompany(brandRoot, companyRoot);
141
-
142
- const resolved = resolveSigningCert({ projectRoot: targetRoot, env: { CSC_KEY_PASSWORD: REAL_PASSWORD } });
143
-
144
- ctx.expect(resolved.cscLink).toBe(join(brandRoot, CERT_REL));
145
- },
146
- },
147
- {
148
- name: 'an unstamped brand with no tree stays on the keychain — no company is guessed',
149
- run: (ctx) => {
150
- const { targetRoot } = stageBrand();
151
- // A company workspace exists on disk but nothing points at it.
152
- const companyRoot = mkdtempSync(join(tmpdir(), 'omega-company-'));
153
- jetpack.write(join(companyRoot, CERT_REL), realP12Bytes());
154
-
155
- const resolved = resolveSigningCert({ projectRoot: targetRoot, env: { CSC_KEY_PASSWORD: REAL_PASSWORD } });
156
-
157
- ctx.expect(resolved.source).toBe('keychain');
158
- ctx.expect(resolved.cscLink).toBe(null);
159
- },
160
- },
161
- {
162
- name: 'a stamp pointing at a company with no tree stays on the keychain',
163
- run: (ctx) => {
164
- const { brandRoot, targetRoot } = stageBrand();
165
- stampCompany(brandRoot, mkdtempSync(join(tmpdir(), 'omega-company-')));
166
-
167
- const resolved = resolveSigningCert({ projectRoot: targetRoot, env: { CSC_KEY_PASSWORD: REAL_PASSWORD } });
168
-
169
- ctx.expect(resolved.source).toBe('keychain');
170
- },
171
- },
172
- {
173
- name: 'a cert with no CSC_KEY_PASSWORD stays on the keychain — an unopenable .p12 would fail the build',
174
- run: (ctx) => {
175
- const { targetRoot } = stageBrand({ cert: 'real' });
176
-
177
- const resolved = resolveSigningCert({ projectRoot: targetRoot, env: {} });
178
-
179
- ctx.expect(resolved.source).toBe('keychain');
180
- ctx.expect(resolved.cscLink).toBe(null);
181
- ctx.expect(resolved.reason.includes('CSC_KEY_PASSWORD')).toBe(true);
182
- },
183
- },
184
- {
185
- name: 'a password that does NOT open the .p12 stays on the keychain',
186
- run: (ctx) => {
187
- const { targetRoot } = stageBrand({ cert: 'real' });
188
-
189
- const resolved = resolveSigningCert({ projectRoot: targetRoot, env: { CSC_KEY_PASSWORD: 'not-the-password' } });
190
-
191
- ctx.expect(resolved.source).toBe('keychain');
192
- ctx.expect(resolved.cscLink).toBe(null);
193
- ctx.expect(resolved.reason.includes('does not open')).toBe(true);
194
- },
195
- },
196
- {
197
- name: 'a file that is not a .p12 at all stays on the keychain',
198
- run: (ctx) => {
199
- const { targetRoot } = stageBrand({ cert: 'dummy' });
200
-
201
- const resolved = resolveSigningCert({ projectRoot: targetRoot, env: { CSC_KEY_PASSWORD: REAL_PASSWORD } });
202
-
203
- ctx.expect(resolved.source).toBe('keychain');
204
- ctx.expect(resolved.reason.includes('does not open')).toBe(true);
205
- },
206
- },
207
- {
208
- name: 'an empty CSC_LINK is not an answer — the tree still wins',
209
- run: (ctx) => {
210
- const { brandRoot, targetRoot } = stageBrand({ cert: 'real' });
211
-
212
- const resolved = resolveSigningCert({ projectRoot: targetRoot, env: { CSC_LINK: '', CSC_KEY_PASSWORD: REAL_PASSWORD } });
213
-
214
- ctx.expect(resolved.source).toBe('certificates');
215
- ctx.expect(resolved.cscLink).toBe(join(brandRoot, CERT_REL));
216
- },
217
- },
218
- {
219
- name: 'a tree AT the home directory never signs a build (the ~/.omega overlay)',
220
- run: (ctx) => {
221
- // ~/.omega is a real personal overlay on developer machines. A stand-in
222
- // home carries the tree here; without the bound the walk would take it.
223
- const fakeHome = mkdtempSync(join(tmpdir(), 'omega-fake-home-'));
224
- const targetRoot = join(fakeHome, 'brand', 'targets', 'desktop');
225
- jetpack.dir(targetRoot);
226
- jetpack.write(join(fakeHome, CERT_REL), realP12Bytes());
227
-
228
- const resolved = resolveSigningCert({
229
- projectRoot: targetRoot,
230
- env: { CSC_KEY_PASSWORD: REAL_PASSWORD },
231
- homeDir: fakeHome,
232
- });
233
-
234
- ctx.expect(resolved.source).toBe('keychain');
235
- ctx.expect(resolved.cscLink).toBe(null);
236
- },
237
- },
238
- {
239
- name: 'a tree ABOVE the home directory is out of bounds too',
240
- run: (ctx) => {
241
- const above = mkdtempSync(join(tmpdir(), 'omega-above-home-'));
242
- const fakeHome = join(above, 'home');
243
- const targetRoot = join(fakeHome, 'brand', 'targets', 'desktop');
244
- jetpack.dir(targetRoot);
245
- jetpack.write(join(above, CERT_REL), realP12Bytes());
246
-
247
- const resolved = resolveSigningCert({
248
- projectRoot: targetRoot,
249
- env: { CSC_KEY_PASSWORD: REAL_PASSWORD },
250
- homeDir: fakeHome,
251
- });
252
-
253
- ctx.expect(resolved.source).toBe('keychain');
254
- },
255
- },
256
- {
257
- name: 'a tree BELOW home still resolves — the bound is the home dir, not "anywhere under it"',
258
- run: (ctx) => {
259
- const fakeHome = mkdtempSync(join(tmpdir(), 'omega-under-home-'));
260
- const brandRoot = join(fakeHome, 'Developer', 'brand');
261
- const targetRoot = join(brandRoot, 'targets', 'desktop');
262
- jetpack.dir(targetRoot);
263
- jetpack.write(join(brandRoot, CERT_REL), realP12Bytes());
264
-
265
- const resolved = resolveSigningCert({
266
- projectRoot: targetRoot,
267
- env: { CSC_KEY_PASSWORD: REAL_PASSWORD },
268
- homeDir: fakeHome,
269
- });
270
-
271
- ctx.expect(resolved.source).toBe('certificates');
272
- ctx.expect(resolved.cscLink).toBe(join(brandRoot, CERT_REL));
273
- },
274
- },
275
- {
276
- name: 'a LibreSSL openssl (no -legacy flag) still verifies the cert — the check retries without the flag',
277
- run: (ctx) => {
278
- // Stock macOS /usr/bin/openssl is LibreSSL: it rejects the -legacy FLAG
279
- // but reads legacy containers natively. A stub front presents exactly
280
- // that behavior, delegating the flagless retry to the system binary.
281
- const { brandRoot, targetRoot } = stageBrand({ cert: 'real' });
282
- const stubDir = mkdtempSync(join(tmpdir(), 'omega-libressl-stub-'));
283
- const stub = join(stubDir, 'openssl');
284
- jetpack.write(stub, [
285
- '#!/bin/sh',
286
- 'for a in "$@"; do',
287
- ' if [ "$a" = "-legacy" ]; then echo "pkcs12: unknown option \'-legacy\'" >&2; exit 1; fi',
288
- 'done',
289
- 'if [ -x /usr/bin/openssl ]; then exec /usr/bin/openssl "$@"; fi',
290
- 'exit 0',
291
- '',
292
- ].join('\n'));
293
- execFileSync('chmod', ['+x', stub]);
294
-
295
- const realPath = process.env.PATH;
296
- process.env.PATH = `${stubDir}:${realPath}`;
297
- try {
298
- const resolved = resolveSigningCert({ projectRoot: targetRoot, env: { ...process.env, CSC_KEY_PASSWORD: REAL_PASSWORD } });
299
-
300
- ctx.expect(resolved.source).toBe('certificates');
301
- ctx.expect(resolved.cscLink).toBe(join(brandRoot, CERT_REL));
302
- } finally {
303
- process.env.PATH = realPath;
304
- }
305
- },
306
- },
307
- {
308
- name: 'no openssl at all names the tooling, not the password, and stays on the keychain',
309
- run: (ctx) => {
310
- const { targetRoot } = stageBrand({ cert: 'real' });
311
- const emptyDir = mkdtempSync(join(tmpdir(), 'omega-no-openssl-'));
312
-
313
- const realPath = process.env.PATH;
314
- process.env.PATH = emptyDir;
315
- try {
316
- const resolved = resolveSigningCert({ projectRoot: targetRoot, env: { ...process.env, CSC_KEY_PASSWORD: REAL_PASSWORD } });
317
-
318
- ctx.expect(resolved.source).toBe('keychain');
319
- ctx.expect(resolved.reason.includes('openssl is not available')).toBe(true);
320
- ctx.expect(resolved.reason.includes('does not open')).toBe(false);
321
- } finally {
322
- process.env.PATH = realPath;
323
- }
324
- },
325
- },
326
- {
327
- name: 'the default boundary is the REAL home — a project under it picks up no stray tree',
328
- run: (ctx) => {
329
- const projectRoot = mkdtempSync(join(homedir(), '.omega-signing-probe-'));
330
-
331
- // No homeDir passed: the default must apply, and nothing in the real
332
- // home (including its ~/.omega overlay) may be reached.
333
- const resolved = resolveSigningCert({ projectRoot, env: { CSC_KEY_PASSWORD: REAL_PASSWORD } });
334
-
335
- ctx.expect(resolved.source).toBe('keychain');
336
- ctx.expect(resolved.cscLink).toBe(null);
337
-
338
- jetpack.remove(projectRoot);
339
- },
340
- },
341
- ],
342
- });