@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.
- package/README.md +1 -1
- package/dist/assets/css/tokens/_index.scss +1 -1
- package/dist/assets/themes/base/_includes/frontend/sections/account-section-header.html +4 -1
- package/dist/assets/themes/base/_includes/frontend/sections/footer.html +13 -6
- package/dist/assets/themes/base/_includes/frontend/sections/nav.html +10 -8
- package/dist/assets/themes/base/_includes/global/sections/account.html +3 -1
- package/dist/assets/themes/base/_includes/global/sections/app-sidebar.html +12 -8
- package/dist/assets/themes/base/_includes/global/sections/app-topbar.html +10 -6
- package/dist/assets/themes/base/_includes/global/sections/page-header.html +8 -4
- package/dist/assets/themes/base/_layouts/backend/pages/dashboard/index.html +24 -24
- package/dist/assets/themes/base/_layouts/frontend/pages/about.html +10 -10
- package/dist/assets/themes/base/_layouts/frontend/pages/account/index.html +35 -35
- package/dist/assets/themes/base/_layouts/frontend/pages/alternatives/index.html +4 -4
- package/dist/assets/themes/base/_layouts/frontend/pages/auth/signin.html +1 -1
- package/dist/assets/themes/base/_layouts/frontend/pages/auth/signup.html +4 -4
- package/dist/assets/themes/base/_layouts/frontend/pages/contact.html +3 -3
- package/dist/assets/themes/base/_layouts/frontend/pages/download.html +34 -33
- package/dist/assets/themes/base/_layouts/frontend/pages/extension/index.html +11 -11
- package/dist/assets/themes/base/_layouts/frontend/pages/legal/document.html +1 -1
- package/dist/assets/themes/base/_layouts/frontend/pages/status.html +1 -1
- package/dist/assets/themes/base/_layouts/frontend/pages/team/index.html +9 -7
- package/dist/assets/themes/base/_layouts/frontend/pages/team/member.html +5 -3
- package/dist/assets/themes/base/_sections/about/letter/section.html +1 -1
- package/dist/assets/themes/base/_sections/about/letter/section.json5 +4 -4
- package/dist/assets/themes/base/_sections/marketing/bento/section.html +1 -1
- package/dist/assets/themes/base/_sections/marketing/bento/section.json5 +15 -15
- package/dist/assets/themes/base/_sections/marketing/cta/section.json5 +1 -1
- package/dist/assets/themes/base/_sections/marketing/hero/section.html +6 -6
- package/dist/assets/themes/base/_sections/marketing/hero/section.json5 +10 -10
- package/dist/assets/themes/base/_sections/marketing/product-demo/section.html +1 -1
- package/dist/assets/themes/base/_sections/marketing/product-demo/section.json5 +2 -2
- package/dist/assets/themes/base/_sections/marketing/stats/section.html +1 -1
- package/dist/assets/themes/base/_sections/marketing/stats/section.json5 +5 -5
- package/dist/assets/themes/base/_sections/marketing/trusted-by/section.html +1 -1
- package/dist/assets/themes/base/_sections/marketing/trusted-by/section.json5 +2 -2
- package/dist/assets/themes/neobrutalism/_layouts/frontend/pages/index.html +10 -10
- package/dist/assets/themes/newsflash/_layouts/frontend/pages/index.html +10 -10
- package/dist/assets/themes/newsflash/_sections/marketing/desks/section.html +2 -2
- package/dist/assets/themes/newsflash/_sections/marketing/desks/section.json5 +4 -4
- package/dist/build.js +69 -28
- package/dist/cli-run.js +20 -13
- package/dist/cli.js +12 -7
- package/dist/commands/build.js +0 -2
- package/dist/commands/deploy.js +116 -44
- package/dist/commands/finalize-release.js +5 -4
- package/dist/commands/launch.js +12 -7
- package/dist/commands/lib/deploy-precheck.js +66 -34
- package/dist/commands/lib/ensure-target.js +71 -11
- package/dist/commands/package.js +0 -1
- package/dist/commands/publish.js +11 -4
- package/dist/commands/release.js +99 -252
- package/dist/commands/runner.js +42 -5
- package/dist/commands/sign-windows.js +46 -15
- package/dist/commands/test.js +4 -3
- package/dist/commands/validate-certs.js +291 -115
- package/dist/config/page-template.html +4 -0
- package/dist/defaults/.github/workflows/build.yml +144 -64
- package/dist/defaults/AGENTS.md +3 -2
- package/dist/defaults/_.gitignore +4 -2
- package/dist/defaults/config/certs/README.md +25 -45
- package/dist/defaults/config/omega.json5 +66 -32
- package/dist/defaults/hooks/deploy/pre.js +10 -0
- package/dist/defaults/src/assets/scss/main.scss +12 -2
- package/dist/defaults/src/integrations/tray/index.js +1 -1
- package/dist/gulp/main.js +13 -25
- package/dist/gulp/tasks/audit.js +18 -6
- package/dist/gulp/tasks/build-config.js +181 -60
- package/dist/gulp/tasks/bundle.js +108 -36
- package/dist/gulp/tasks/release.js +86 -4
- package/dist/gulp/tasks/sass.js +11 -0
- package/dist/hooks/lib/notarize-tools.js +137 -0
- package/dist/hooks/notarize-artifacts.js +57 -0
- package/dist/hooks/notarize.js +59 -8
- package/dist/lib/auth-persistence.js +25 -0
- package/dist/lib/client-bridge.js +11 -8
- package/dist/lib/deep-link.js +15 -8
- package/dist/lib/protocol.js +7 -1
- package/dist/lib/restart-manager/install.js +6 -3
- package/dist/lib/sign-helpers/auto-unlock.js +65 -27
- package/dist/lib/sign-helpers/console-lock.js +34 -0
- package/dist/lib/sign-helpers/exec-with-limit.js +68 -0
- package/dist/lib/sign-helpers/resolve-icons.js +6 -4
- package/dist/lib/tray.js +7 -6
- package/dist/main.js +16 -0
- package/dist/preload.js +8 -0
- package/dist/renderer.js +16 -4
- package/dist/runner/job-started.js +104 -0
- package/dist/test/fixtures/consumer-app/config/omega.json5 +7 -0
- package/dist/test/fixtures/consumer-app/package.json +1 -1
- package/dist/test/fixtures/consumer-app/src/assets/js/components/main/index.js +7 -1
- package/dist/test/harness/main-entry.js +2 -1
- package/dist/test/harness/renderer-preload.js +12 -3
- package/dist/test/runners/boot.js +101 -11
- package/dist/test/runners/electron.js +1 -1
- package/dist/test/suites/boot/consumer-app-boots.test.js +54 -0
- package/dist/test/suites/build/audit.test.js +45 -6
- package/dist/test/suites/build/auth-persistence-resolve.test.js +119 -0
- package/dist/test/suites/build/auto-unlock.test.js +105 -0
- package/dist/test/suites/build/boot-runner-timeout.test.js +253 -0
- package/dist/test/suites/build/brand-scss.test.js +106 -0
- package/dist/test/suites/build/build-config.test.js +146 -36
- package/dist/test/suites/build/build-json-bake.test.js +245 -0
- package/dist/test/suites/build/build-verbs.test.js +48 -15
- package/dist/test/suites/build/build-workflow-jobs.test.js +190 -0
- package/dist/test/suites/build/cli.test.js +32 -10
- package/dist/test/suites/build/config-schema.test.js +4 -4
- package/dist/test/suites/build/console-lock.test.js +51 -0
- package/dist/test/suites/build/defaults-scaffold.test.js +71 -2
- package/dist/test/suites/build/deploy-direct.test.js +241 -0
- package/dist/test/suites/build/deploy-dispatch.test.js +287 -0
- package/dist/test/suites/build/deploy-hook.test.js +169 -0
- package/dist/test/suites/build/ensure-target.test.js +14 -2
- package/dist/test/suites/build/env-delivery.test.js +19 -10
- package/dist/test/suites/build/env-watch.test.js +18 -7
- package/dist/test/suites/build/esm-only-dependency.test.js +127 -0
- package/dist/test/suites/build/exec-with-limit.test.js +53 -0
- package/dist/test/suites/build/finalize-release.test.js +2 -2
- package/dist/test/suites/build/get-config.test.js +120 -8
- package/dist/test/suites/build/github-utils.test.js +12 -6
- package/dist/test/suites/build/license-stamp.test.js +6 -4
- package/dist/test/suites/build/manager.test.js +88 -68
- package/dist/test/suites/build/manifest-deps.test.js +116 -0
- package/dist/test/suites/build/merge-line-files.test.js +27 -4
- package/dist/test/suites/build/notarize-artifacts.test.js +135 -0
- package/dist/test/suites/build/notarize-tools.test.js +38 -0
- package/dist/test/suites/build/notarize.test.js +207 -0
- package/dist/test/suites/build/release-pipeline.test.js +38 -60
- package/dist/test/suites/build/release-skipped-upload.test.js +107 -0
- package/dist/test/suites/build/resolve-icons.test.js +35 -35
- package/dist/test/suites/build/runner-job-guard.test.js +182 -0
- package/dist/test/suites/build/runner.test.js +25 -2
- package/dist/test/suites/build/sentry.test.js +8 -3
- package/dist/test/suites/build/setup-scripts.test.js +3 -0
- package/dist/test/suites/build/sign-windows.test.js +92 -8
- package/dist/test/suites/build/test-stealth.test.js +17 -11
- package/dist/test/suites/build/url-helpers.test.js +37 -17
- package/dist/test/suites/build/validate-certs.test.js +428 -55
- package/dist/test/suites/build/validate-config.test.js +3 -3
- package/dist/test/suites/main/auth-flow.test.js +12 -0
- package/dist/test/suites/main/auth-persistence.test.js +14 -17
- package/dist/test/suites/main/auto-updater.test.js +2 -2
- package/dist/test/suites/main/boot-sequence.test.js +1 -1
- package/dist/test/suites/main/client-bridge.integration.test.js +5 -82
- package/dist/test/suites/main/client-bridge.test.js +19 -6
- package/dist/test/suites/main/deep-link.test.js +54 -0
- package/dist/test/suites/main/startup-paths-and-ua.test.js +1 -1
- package/dist/test/suites/main/url-helpers.test.js +81 -72
- package/dist/test/suites/renderer/cross-context-helpers.test.js +19 -16
- package/dist/utils/build-pipeline.js +10 -10
- package/dist/utils/github.js +12 -51
- package/dist/utils/load-env.js +66 -0
- package/dist/utils/mode-helpers.js +43 -111
- package/dist/utils/platform.js +37 -0
- package/dist/utils/runner-env.js +2 -1
- package/dist/utils/runner-job-guard.js +149 -0
- package/dist/utils/ship-keys.js +52 -0
- package/dist/utils/test-stealth.js +5 -3
- package/dist/utils/url-helpers.js +33 -17
- package/dist/vendor/config/bundle-id.js +53 -0
- package/dist/vendor/config/client-config.js +141 -0
- package/dist/vendor/config/company.js +334 -15
- package/dist/vendor/config/dev-facts.js +48 -0
- package/dist/vendor/config/env-delivery.js +231 -9
- package/dist/vendor/config/env-retired.js +137 -0
- package/dist/vendor/config/env-rules.js +22 -3
- package/dist/vendor/config/env-schema.js +234 -117
- package/dist/vendor/config/env.js +55 -26
- package/dist/vendor/config/environment.js +189 -0
- package/dist/vendor/config/hooks.js +13 -11
- package/dist/vendor/config/index.js +121 -44
- package/dist/vendor/config/load.js +366 -115
- package/dist/vendor/config/merge.js +2 -2
- package/dist/vendor/config/order.js +3 -3
- package/dist/vendor/config/platforms.js +276 -0
- package/dist/vendor/config/repo.js +226 -104
- package/dist/vendor/config/retired-keys.js +232 -27
- package/dist/vendor/config/schema.js +525 -99
- package/dist/vendor/config/site-global.js +63 -53
- package/dist/vendor/config/targets.js +187 -0
- package/dist/vendor/config/validate.js +119 -59
- package/dist/vendor/devkit/argv.js +118 -0
- package/dist/vendor/devkit/attach-log-file.js +21 -13
- package/dist/vendor/devkit/brand-tokens.js +278 -0
- package/dist/vendor/devkit/brand-version.js +264 -0
- package/dist/vendor/devkit/build-json.js +91 -0
- package/dist/vendor/devkit/bundle.js +48 -0
- package/dist/vendor/devkit/certificate-expiry.js +108 -0
- package/dist/vendor/devkit/certs.js +16 -194
- package/dist/vendor/devkit/ci-workflows.js +124 -8
- package/dist/vendor/devkit/cli-router.js +3 -3
- package/dist/vendor/devkit/defaults-engine.js +69 -7
- package/dist/vendor/devkit/deploy-follow.js +297 -0
- package/dist/vendor/devkit/deploy-precheck.js +23 -8
- package/dist/vendor/devkit/deploy-record.js +11 -27
- package/dist/vendor/devkit/deploy-snapshot.js +661 -0
- package/dist/vendor/devkit/deploy.js +445 -75
- package/dist/vendor/devkit/git-auth.js +73 -0
- package/dist/vendor/devkit/git-remote.js +95 -0
- package/dist/vendor/devkit/github-repo.js +290 -0
- package/dist/vendor/devkit/local.js +47 -0
- package/dist/vendor/devkit/merge-line-files.js +23 -16
- package/dist/vendor/devkit/omega-bin.js +18 -3
- package/dist/vendor/devkit/pack-local.js +391 -0
- package/dist/vendor/devkit/preludes/index.js +120 -0
- package/dist/vendor/devkit/preludes/origin-heal.js +156 -0
- package/dist/vendor/devkit/service-account.js +43 -0
- package/dist/vendor/devkit/ship-plan.js +112 -0
- package/dist/vendor/devkit/signing-env.js +180 -0
- package/dist/vendor/devkit/signing-tree.js +92 -0
- package/dist/vendor/devkit/target-seams.js +142 -0
- package/dist/vendor/devkit/target-secrets.js +235 -50
- package/dist/vendor/devkit/test/esm-only-fixture.js +48 -0
- package/dist/vendor/devkit/test/fixtures/esm-only-package/browser.js +19 -0
- package/dist/vendor/devkit/test/fixtures/esm-only-package/index.js +20 -0
- package/dist/vendor/devkit/test/fixtures/esm-only-package/package.json +13 -0
- package/dist/vendor/monitoring/env.js +20 -10
- package/dist/vendor/monitoring/main.js +1 -1
- package/dist/vendor/monitoring/preload.js +1 -1
- package/dist/vendor/monitoring/renderer.js +1 -1
- package/docs/analytics.md +1 -1
- package/docs/auto-updater.md +5 -5
- package/docs/boot-sequence.md +1 -1
- package/docs/build-system.md +15 -7
- package/docs/client-bridge.md +9 -7
- package/docs/config-schema.md +4 -4
- package/docs/css.md +8 -2
- package/docs/deep-link.md +12 -4
- package/docs/environment-detection.md +32 -24
- package/docs/hooks.md +3 -1
- package/docs/icons.md +7 -7
- package/docs/index.md +61 -23
- package/docs/installer-options.md +24 -21
- package/docs/logging.md +5 -5
- package/docs/releasing.md +25 -17
- package/docs/runner.md +40 -5
- package/docs/shared/brands.md +12 -6
- package/docs/shared/breaking-changes.md +375 -21
- package/docs/shared/config.md +757 -199
- package/docs/shared/deploys.md +194 -91
- package/docs/shared/icons.md +18 -0
- package/docs/shared/local-dev.md +24 -6
- package/docs/shared/logging.md +9 -6
- package/docs/shared/monitoring.md +27 -13
- package/docs/shared/publishing.md +3 -3
- package/docs/shared/rulings.md +2 -2
- package/docs/shared/testing.md +1 -1
- package/docs/shared/theming.md +26 -1
- package/docs/shared/translation.md +49 -7
- package/docs/shared/updates.md +1 -1
- package/docs/signing.md +59 -33
- package/docs/test-framework.md +10 -5
- package/docs/themes.md +15 -1
- package/package.json +18 -12
- package/bin/omega-desktop +0 -2
- package/dist/commands/push-secrets.js +0 -141
- package/dist/test/suites/build/deliver-certs.test.js +0 -95
- package/dist/test/suites/build/derive-signing-env.test.js +0 -122
- package/dist/test/suites/build/push-secrets.test.js +0 -226
- package/dist/test/suites/build/resolve-signing-cert.test.js +0 -342
- package/dist/utils/deliver-certs.js +0 -69
- package/dist/utils/derive-signing-env.js +0 -56
- package/dist/utils/resolve-signing-cert.js +0 -175
- package/dist/vendor/config/desktop-artifacts.js +0 -110
- package/dist/vendor/config/instances.js +0 -208
- /package/dist/defaults/config/icons/{macos → mac}/dmg.png +0 -0
- /package/dist/defaults/config/icons/{macos → mac}/icon.png +0 -0
- /package/dist/defaults/config/icons/{macos → mac}/tray.png +0 -0
|
@@ -69,14 +69,15 @@ const NO_ENV = {
|
|
|
69
69
|
module.exports = defineCases({
|
|
70
70
|
type: 'group', // independent tests; run all even on failure
|
|
71
71
|
layer: 'build',
|
|
72
|
-
description: 'utils/url-helpers: every local helper resolves env
|
|
72
|
+
description: 'utils/url-helpers: every local helper resolves env, then the baked dev.ports, then throws',
|
|
73
73
|
tests: [
|
|
74
74
|
{
|
|
75
|
-
name: 'exports { attachTo, getApiUrl, localPort }',
|
|
75
|
+
name: 'exports { attachTo, getApiUrl, localPort, requiredPort }',
|
|
76
76
|
run: (ctx) => {
|
|
77
77
|
ctx.expect(typeof helpers.attachTo).toBe('function');
|
|
78
78
|
ctx.expect(typeof helpers.getApiUrl).toBe('function');
|
|
79
79
|
ctx.expect(typeof helpers.localPort).toBe('function');
|
|
80
|
+
ctx.expect(typeof helpers.requiredPort).toBe('function');
|
|
80
81
|
},
|
|
81
82
|
},
|
|
82
83
|
{
|
|
@@ -107,11 +108,19 @@ module.exports = defineCases({
|
|
|
107
108
|
},
|
|
108
109
|
},
|
|
109
110
|
{
|
|
110
|
-
|
|
111
|
+
// The classic 5002 used to answer here (#834). It is gone: a dev build
|
|
112
|
+
// always bakes the resolved map, so neither channel answering means the
|
|
113
|
+
// artifact is broken, and a wrong API base is a silent connection refusal
|
|
114
|
+
// or a hit on a neighbouring project's stack.
|
|
115
|
+
name: 'neither channel → a throw naming the port and the bundle task',
|
|
111
116
|
run: (ctx) => {
|
|
112
117
|
withEnv(NO_ENV, () => {
|
|
113
|
-
|
|
114
|
-
|
|
118
|
+
for (const environment of ['testing', 'development']) {
|
|
119
|
+
ctx.expect(() => helpers.getApiUrl.call(fakeManager(environment)))
|
|
120
|
+
.toThrow(/dev port for `hosting`/);
|
|
121
|
+
ctx.expect(() => helpers.getApiUrl.call(fakeManager(environment)))
|
|
122
|
+
.toThrow(/bundle task/);
|
|
123
|
+
}
|
|
115
124
|
});
|
|
116
125
|
},
|
|
117
126
|
},
|
|
@@ -163,11 +172,11 @@ module.exports = defineCases({
|
|
|
163
172
|
},
|
|
164
173
|
},
|
|
165
174
|
{
|
|
166
|
-
name: 'getFunctionsUrl: neither channel →
|
|
175
|
+
name: 'getFunctionsUrl: neither channel → a throw naming the port (#834)',
|
|
167
176
|
run: (ctx) => {
|
|
168
177
|
withEnv(NO_ENV, () => {
|
|
169
|
-
ctx.expect(helpers.getFunctionsUrl.call(fakeManager('development')))
|
|
170
|
-
.
|
|
178
|
+
ctx.expect(() => helpers.getFunctionsUrl.call(fakeManager('development')))
|
|
179
|
+
.toThrow(/dev port for `functions`/);
|
|
171
180
|
});
|
|
172
181
|
},
|
|
173
182
|
},
|
|
@@ -200,10 +209,11 @@ module.exports = defineCases({
|
|
|
200
209
|
},
|
|
201
210
|
},
|
|
202
211
|
{
|
|
203
|
-
name: 'getWebsiteUrl: neither channel →
|
|
212
|
+
name: 'getWebsiteUrl: neither channel → a throw naming the port (#834)',
|
|
204
213
|
run: (ctx) => {
|
|
205
214
|
withEnv(NO_ENV, () => {
|
|
206
|
-
ctx.expect(helpers.getWebsiteUrl.call(fakeManager('development')))
|
|
215
|
+
ctx.expect(() => helpers.getWebsiteUrl.call(fakeManager('development')))
|
|
216
|
+
.toThrow(/dev port for `website`/);
|
|
207
217
|
});
|
|
208
218
|
},
|
|
209
219
|
},
|
|
@@ -228,12 +238,18 @@ module.exports = defineCases({
|
|
|
228
238
|
},
|
|
229
239
|
},
|
|
230
240
|
{
|
|
231
|
-
//
|
|
232
|
-
//
|
|
233
|
-
// the
|
|
234
|
-
|
|
241
|
+
// There is no lockstep copy to keep honest any more (#834): the classic
|
|
242
|
+
// numbers live ONLY in @omega.js/config, the bundle task bakes them into
|
|
243
|
+
// the artifact as the floor of the resolved map, and this module reads
|
|
244
|
+
// that map. A copy here would be the exact defect the issue closed.
|
|
245
|
+
name: 'no classic constants live in this module (#834)',
|
|
235
246
|
run: (ctx) => {
|
|
236
|
-
ctx.expect(helpers.CLASSIC_DEV_ORIGIN).toBe(
|
|
247
|
+
ctx.expect(helpers.CLASSIC_DEV_ORIGIN).toBe(undefined);
|
|
248
|
+
const source = require('fs').readFileSync(require.resolve('../../../utils/url-helpers.js'), 'utf8');
|
|
249
|
+
const code = source.split('\n').filter((line) => !line.trim().startsWith('//')).join('\n');
|
|
250
|
+
for (const classic of ['4000', '5001', '5002', '9099']) {
|
|
251
|
+
ctx.expect(code.includes(classic)).toBe(false);
|
|
252
|
+
}
|
|
237
253
|
},
|
|
238
254
|
},
|
|
239
255
|
{
|
|
@@ -244,7 +260,7 @@ module.exports = defineCases({
|
|
|
244
260
|
withEnv(NO_ENV, () => {
|
|
245
261
|
// getAuthUrl calls `this.getWebsiteUrl()`, which attachTo() supplies on a
|
|
246
262
|
// real Manager — hand the stand-in the same helper.
|
|
247
|
-
const manager = fakeManager('development');
|
|
263
|
+
const manager = fakeManager('development', { website: 4000 });
|
|
248
264
|
manager.getWebsiteUrl = helpers.getWebsiteUrl;
|
|
249
265
|
const url = new URL(helpers.getAuthUrl.call(manager));
|
|
250
266
|
ctx.expect(url.origin).toBe('https://localhost:4000');
|
|
@@ -274,8 +290,12 @@ module.exports = defineCases({
|
|
|
274
290
|
withBridgeManager(fakeManager('testing', { auth: 9109 }), () => {
|
|
275
291
|
ctx.expect(clientBridge._authEmulatorPort()).toBe(9109);
|
|
276
292
|
});
|
|
293
|
+
// The classic 9099 used to answer here (#834): nothing identity-checks
|
|
294
|
+
// what holds that port, so a neighbouring project's emulator read as
|
|
295
|
+
// an auth mystery instead of a port problem.
|
|
277
296
|
withBridgeManager(fakeManager('testing'), () => {
|
|
278
|
-
ctx.expect(clientBridge._authEmulatorPort()).
|
|
297
|
+
ctx.expect(() => clientBridge._authEmulatorPort()).toThrow(/dev port for `auth`/);
|
|
298
|
+
ctx.expect(() => clientBridge._authEmulatorPort()).toThrow(/bundle task/);
|
|
279
299
|
});
|
|
280
300
|
});
|
|
281
301
|
withEnv({ OMEGA_AUTH_PORT: '9119' }, () => {
|
|
@@ -1,100 +1,473 @@
|
|
|
1
|
-
// Build-layer tests for commands/validate-certs.js
|
|
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
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return
|
|
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
|
|
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: '
|
|
228
|
+
name: 'under 30 days WARNS, expired ERRORS, and a healthy date says nothing',
|
|
39
229
|
run: (ctx) => {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
49
|
-
|
|
50
|
-
ctx.expect(
|
|
51
|
-
ctx.expect(
|
|
52
|
-
|
|
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: '
|
|
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-
|
|
62
|
-
const
|
|
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
|
|
67
|
-
|
|
68
|
-
|
|
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: '
|
|
304
|
+
name: 'APPLE_API_KEY naming a missing file is still the error it always was',
|
|
76
305
|
run: (ctx) => {
|
|
77
|
-
const
|
|
78
|
-
|
|
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: '
|
|
323
|
+
name: 'a real AuthKey file still gets the filename/key-id cross-check',
|
|
83
324
|
run: (ctx) => {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
|
95
|
-
|
|
96
|
-
|
|
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
|
},
|