@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
|
@@ -43,15 +43,15 @@ module.exports = defineCases({
|
|
|
43
43
|
run: async (ctx) => {
|
|
44
44
|
const { root, projectRoot, distRoot, emDefaultsRoot } = stage();
|
|
45
45
|
try {
|
|
46
|
-
fakePng(path.join(emDefaultsRoot, 'icons', '
|
|
46
|
+
fakePng(path.join(emDefaultsRoot, 'icons', 'mac', 'icon.png'));
|
|
47
47
|
fakePng(path.join(emDefaultsRoot, 'icons', 'windows', 'icon.png'));
|
|
48
48
|
|
|
49
49
|
const { resolveAndCopy } = require(path.join(__dirname, '..', '..', '..', 'lib', 'sign-helpers', 'resolve-icons.js'));
|
|
50
50
|
const out = await resolveAndCopy({ config: {}, projectRoot, distRoot, emDefaultsRoot });
|
|
51
51
|
|
|
52
|
-
ctx.expect(out.
|
|
52
|
+
ctx.expect(out.mac.app).toBe(path.join(distRoot, 'config', 'icons', 'mac', 'icon.png'));
|
|
53
53
|
ctx.expect(out.windows.app).toBe(path.join(distRoot, 'config', 'icons', 'windows', 'icon.png'));
|
|
54
|
-
ctx.expect(jetpack.exists(out.
|
|
54
|
+
ctx.expect(jetpack.exists(out.mac.app)).toBeTruthy();
|
|
55
55
|
ctx.expect(jetpack.exists(out.windows.app)).toBeTruthy();
|
|
56
56
|
} finally {
|
|
57
57
|
fs.rmSync(root, { recursive: true, force: true });
|
|
@@ -63,15 +63,15 @@ module.exports = defineCases({
|
|
|
63
63
|
run: async (ctx) => {
|
|
64
64
|
const { root, projectRoot, distRoot, emDefaultsRoot } = stage();
|
|
65
65
|
try {
|
|
66
|
-
fakePng(path.join(emDefaultsRoot, 'icons', '
|
|
67
|
-
const consumerIcon = path.join(projectRoot, 'config', 'icons', '
|
|
66
|
+
fakePng(path.join(emDefaultsRoot, 'icons', 'mac', 'icon.png'));
|
|
67
|
+
const consumerIcon = path.join(projectRoot, 'config', 'icons', 'mac', 'icon.png');
|
|
68
68
|
fakePng(consumerIcon);
|
|
69
69
|
fs.writeFileSync(consumerIcon, Buffer.from('consumer-bytes'));
|
|
70
70
|
|
|
71
71
|
const { resolveAndCopy } = require(path.join(__dirname, '..', '..', '..', 'lib', 'sign-helpers', 'resolve-icons.js'));
|
|
72
72
|
const out = await resolveAndCopy({ config: {}, projectRoot, distRoot, emDefaultsRoot });
|
|
73
73
|
|
|
74
|
-
const bytes = fs.readFileSync(out.
|
|
74
|
+
const bytes = fs.readFileSync(out.mac.app);
|
|
75
75
|
ctx.expect(bytes.toString()).toBe('consumer-bytes');
|
|
76
76
|
} finally {
|
|
77
77
|
fs.rmSync(root, { recursive: true, force: true });
|
|
@@ -84,7 +84,7 @@ module.exports = defineCases({
|
|
|
84
84
|
const { root, projectRoot, distRoot, emDefaultsRoot } = stage();
|
|
85
85
|
try {
|
|
86
86
|
// Bundled defaults exist for all three platforms — but consumer-global wins.
|
|
87
|
-
fakePng(path.join(emDefaultsRoot, 'icons', '
|
|
87
|
+
fakePng(path.join(emDefaultsRoot, 'icons', 'mac', 'icon.png'));
|
|
88
88
|
fakePng(path.join(emDefaultsRoot, 'icons', 'windows', 'icon.png'));
|
|
89
89
|
|
|
90
90
|
// Consumer ships ONE file at the global path. All three platforms pick it up.
|
|
@@ -95,7 +95,7 @@ module.exports = defineCases({
|
|
|
95
95
|
const { resolveAndCopy } = require(path.join(__dirname, '..', '..', '..', 'lib', 'sign-helpers', 'resolve-icons.js'));
|
|
96
96
|
const out = await resolveAndCopy({ config: {}, projectRoot, distRoot, emDefaultsRoot });
|
|
97
97
|
|
|
98
|
-
for (const platform of ['
|
|
98
|
+
for (const platform of ['mac', 'windows', 'linux']) {
|
|
99
99
|
ctx.expect(out[platform].app).toBeDefined();
|
|
100
100
|
const bytes = fs.readFileSync(out[platform].app);
|
|
101
101
|
ctx.expect(bytes.toString()).toBe('global-bytes');
|
|
@@ -118,14 +118,14 @@ module.exports = defineCases({
|
|
|
118
118
|
fs.writeFileSync(globalIcon, Buffer.from('global-bytes'));
|
|
119
119
|
|
|
120
120
|
// Mac-specific override exists — should win for macOS.
|
|
121
|
-
const macIcon = path.join(projectRoot, 'config', 'icons', '
|
|
121
|
+
const macIcon = path.join(projectRoot, 'config', 'icons', 'mac', 'icon.png');
|
|
122
122
|
fakePng(macIcon);
|
|
123
123
|
fs.writeFileSync(macIcon, Buffer.from('mac-specific-bytes'));
|
|
124
124
|
|
|
125
125
|
const { resolveAndCopy } = require(path.join(__dirname, '..', '..', '..', 'lib', 'sign-helpers', 'resolve-icons.js'));
|
|
126
126
|
const out = await resolveAndCopy({ config: {}, projectRoot, distRoot, emDefaultsRoot });
|
|
127
127
|
|
|
128
|
-
ctx.expect(fs.readFileSync(out.
|
|
128
|
+
ctx.expect(fs.readFileSync(out.mac.app).toString()).toBe('mac-specific-bytes');
|
|
129
129
|
ctx.expect(fs.readFileSync(out.windows.app).toString()).toBe('global-bytes');
|
|
130
130
|
ctx.expect(fs.readFileSync(out.linux.app).toString()).toBe('global-bytes');
|
|
131
131
|
} finally {
|
|
@@ -200,37 +200,37 @@ module.exports = defineCases({
|
|
|
200
200
|
run: async (ctx) => {
|
|
201
201
|
const { root, projectRoot, distRoot, emDefaultsRoot } = stage();
|
|
202
202
|
try {
|
|
203
|
-
fakePng(path.join(emDefaultsRoot, 'icons', '
|
|
203
|
+
fakePng(path.join(emDefaultsRoot, 'icons', 'mac', 'icon.png'));
|
|
204
204
|
// Input source files at retina (native) size. @omega.js/desktop should emit both @1x + @2x in dist.
|
|
205
205
|
// Tray input is `tray.png`; @omega.js/desktop renames the dist output to `trayTemplate.png` (macOS magic).
|
|
206
|
-
await realPng(path.join(emDefaultsRoot, 'icons', '
|
|
207
|
-
await realPng(path.join(emDefaultsRoot, 'icons', '
|
|
206
|
+
await realPng(path.join(emDefaultsRoot, 'icons', 'mac', 'tray.png'), 32, 32);
|
|
207
|
+
await realPng(path.join(emDefaultsRoot, 'icons', 'mac', 'dmg.png'), 1080, 760);
|
|
208
208
|
|
|
209
209
|
const { resolveAndCopy } = require(path.join(__dirname, '..', '..', '..', 'lib', 'sign-helpers', 'resolve-icons.js'));
|
|
210
210
|
const out = await resolveAndCopy({ config: {}, projectRoot, distRoot, emDefaultsRoot });
|
|
211
211
|
|
|
212
212
|
// Both @1x and @2x slots populated.
|
|
213
|
-
ctx.expect(out.
|
|
214
|
-
ctx.expect(out.
|
|
215
|
-
ctx.expect(out.
|
|
216
|
-
ctx.expect(out.
|
|
213
|
+
ctx.expect(out.mac.tray).toBeDefined();
|
|
214
|
+
ctx.expect(out.mac.tray2x).toBeDefined();
|
|
215
|
+
ctx.expect(out.mac.dmg).toBeDefined();
|
|
216
|
+
ctx.expect(out.mac.dmg2x).toBeDefined();
|
|
217
217
|
// app slot has no retina flag.
|
|
218
|
-
ctx.expect(out.
|
|
218
|
+
ctx.expect(out.mac.app2x).toBeUndefined();
|
|
219
219
|
|
|
220
220
|
// Tray output filename is trayTemplate.png (renamed for macOS auto-inversion magic).
|
|
221
|
-
ctx.expect(path.basename(out.
|
|
222
|
-
ctx.expect(path.basename(out.
|
|
221
|
+
ctx.expect(path.basename(out.mac.tray)).toBe('trayTemplate.png');
|
|
222
|
+
ctx.expect(path.basename(out.mac.tray2x)).toBe('trayTemplate@2x.png');
|
|
223
223
|
|
|
224
224
|
// @1x derived at half the @2x dimensions.
|
|
225
|
-
const tray1xMeta = await sharp(out.
|
|
226
|
-
const tray2xMeta = await sharp(out.
|
|
225
|
+
const tray1xMeta = await sharp(out.mac.tray).metadata();
|
|
226
|
+
const tray2xMeta = await sharp(out.mac.tray2x).metadata();
|
|
227
227
|
ctx.expect(tray1xMeta.width).toBe(16);
|
|
228
228
|
ctx.expect(tray1xMeta.height).toBe(16);
|
|
229
229
|
ctx.expect(tray2xMeta.width).toBe(32);
|
|
230
230
|
ctx.expect(tray2xMeta.height).toBe(32);
|
|
231
231
|
|
|
232
|
-
const dmg1xMeta = await sharp(out.
|
|
233
|
-
const dmg2xMeta = await sharp(out.
|
|
232
|
+
const dmg1xMeta = await sharp(out.mac.dmg).metadata();
|
|
233
|
+
const dmg2xMeta = await sharp(out.mac.dmg2x).metadata();
|
|
234
234
|
ctx.expect(dmg1xMeta.width).toBe(540);
|
|
235
235
|
ctx.expect(dmg1xMeta.height).toBe(380);
|
|
236
236
|
ctx.expect(dmg2xMeta.width).toBe(1080);
|
|
@@ -245,20 +245,20 @@ module.exports = defineCases({
|
|
|
245
245
|
run: async (ctx) => {
|
|
246
246
|
const { root, projectRoot, distRoot, emDefaultsRoot } = stage();
|
|
247
247
|
try {
|
|
248
|
-
fakePng(path.join(emDefaultsRoot, 'icons', '
|
|
249
|
-
await realPng(path.join(emDefaultsRoot, 'icons', '
|
|
248
|
+
fakePng(path.join(emDefaultsRoot, 'icons', 'mac', 'icon.png'));
|
|
249
|
+
await realPng(path.join(emDefaultsRoot, 'icons', 'mac', 'tray.png'), 32, 32);
|
|
250
250
|
// Consumer ships ONE file as `tray.png` (at @2x native size).
|
|
251
|
-
const consumerTray = path.join(projectRoot, 'config', 'icons', '
|
|
251
|
+
const consumerTray = path.join(projectRoot, 'config', 'icons', 'mac', 'tray.png');
|
|
252
252
|
await realPng(consumerTray, 32, 32);
|
|
253
253
|
|
|
254
254
|
const { resolveAndCopy } = require(path.join(__dirname, '..', '..', '..', 'lib', 'sign-helpers', 'resolve-icons.js'));
|
|
255
255
|
const out = await resolveAndCopy({ config: {}, projectRoot, distRoot, emDefaultsRoot });
|
|
256
256
|
|
|
257
|
-
ctx.expect(out.
|
|
258
|
-
ctx.expect(out.
|
|
257
|
+
ctx.expect(out.mac.tray).toBeDefined();
|
|
258
|
+
ctx.expect(out.mac.tray2x).toBeDefined();
|
|
259
259
|
// Output is still trayTemplate.png regardless of input name.
|
|
260
|
-
ctx.expect(path.basename(out.
|
|
261
|
-
const tray1xMeta = await sharp(out.
|
|
260
|
+
ctx.expect(path.basename(out.mac.tray)).toBe('trayTemplate.png');
|
|
261
|
+
const tray1xMeta = await sharp(out.mac.tray).metadata();
|
|
262
262
|
ctx.expect(tray1xMeta.width).toBe(16);
|
|
263
263
|
} finally {
|
|
264
264
|
fs.rmSync(root, { recursive: true, force: true });
|
|
@@ -270,7 +270,7 @@ module.exports = defineCases({
|
|
|
270
270
|
run: async (ctx) => {
|
|
271
271
|
const { root, projectRoot, distRoot, emDefaultsRoot } = stage();
|
|
272
272
|
try {
|
|
273
|
-
fakePng(path.join(emDefaultsRoot, 'icons', '
|
|
273
|
+
fakePng(path.join(emDefaultsRoot, 'icons', 'mac', 'icon.png'));
|
|
274
274
|
fakePng(path.join(emDefaultsRoot, 'icons', 'windows', 'icon.png'));
|
|
275
275
|
|
|
276
276
|
// Consumer ships ONE tray file under global/.
|
|
@@ -279,13 +279,13 @@ module.exports = defineCases({
|
|
|
279
279
|
const { resolveAndCopy } = require(path.join(__dirname, '..', '..', '..', 'lib', 'sign-helpers', 'resolve-icons.js'));
|
|
280
280
|
const out = await resolveAndCopy({ config: {}, projectRoot, distRoot, emDefaultsRoot });
|
|
281
281
|
|
|
282
|
-
ctx.expect(out.
|
|
283
|
-
ctx.expect(out.
|
|
282
|
+
ctx.expect(out.mac.tray).toBeDefined();
|
|
283
|
+
ctx.expect(out.mac.tray2x).toBeDefined();
|
|
284
284
|
ctx.expect(out.windows.tray).toBeDefined();
|
|
285
285
|
ctx.expect(out.linux.tray).toBeDefined();
|
|
286
286
|
|
|
287
287
|
// macOS @1x derived at half size from the global @2x source.
|
|
288
|
-
const macTrayMeta = await sharp(out.
|
|
288
|
+
const macTrayMeta = await sharp(out.mac.tray).metadata();
|
|
289
289
|
ctx.expect(macTrayMeta.width).toBe(16);
|
|
290
290
|
} finally {
|
|
291
291
|
fs.rmSync(root, { recursive: true, force: true });
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// The self-hosted signer's job guard
|
|
2
|
+
// ([#875](https://github.com/Omega-JS-Stack/omega/issues/875)): the hook the
|
|
3
|
+
// runner runs before a job's first step, and the three files the runner install
|
|
4
|
+
// writes beside the runner so it has an allow list to read.
|
|
5
|
+
//
|
|
6
|
+
// The hook is run here the way the runner runs it (a node process with the
|
|
7
|
+
// job's environment and nothing else), because the EXIT CODE is the whole
|
|
8
|
+
// mechanism: nonzero and the job dies with the script's output, before the
|
|
9
|
+
// checkout, before npm, before signtool ever sees the token.
|
|
10
|
+
//
|
|
11
|
+
// Nothing in this file touches a real box: every write goes to a scratch home
|
|
12
|
+
// under the OS temp dir, and no verb of `commands/runner.js` is called at all.
|
|
13
|
+
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const os = require('os');
|
|
17
|
+
const { spawnSync } = require('child_process');
|
|
18
|
+
const defineCases = require('../../../vendor/devkit/test/define-cases.js');
|
|
19
|
+
|
|
20
|
+
const guard = require('../../../utils/runner-job-guard.js');
|
|
21
|
+
const hook = require('../../../runner/job-started.js');
|
|
22
|
+
const { ensureRunnerDirEnv } = require('../../../utils/runner-env.js');
|
|
23
|
+
|
|
24
|
+
const scratchHome = () => fs.mkdtempSync(path.join(os.tmpdir(), 'omega-job-guard-'));
|
|
25
|
+
|
|
26
|
+
// One job, as the runner delivers it: the three names the hook reads, and no
|
|
27
|
+
// inherited GITHUB_* (a run of this suite inside a job carries its own).
|
|
28
|
+
function runHook(hookPath, job) {
|
|
29
|
+
return spawnSync(process.execPath, [hookPath], {
|
|
30
|
+
env: { PATH: process.env.PATH, SystemRoot: process.env.SystemRoot || '', ...job },
|
|
31
|
+
encoding: 'utf8',
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = defineCases({
|
|
36
|
+
type: 'suite',
|
|
37
|
+
layer: 'build',
|
|
38
|
+
description: 'runner job guard (#875): the box refuses any job that is not a trusted dispatch',
|
|
39
|
+
tests: [
|
|
40
|
+
{
|
|
41
|
+
name: 'a dispatch from a listed repo by a listed actor is allowed, and every other job is refused with one line naming the check and the value',
|
|
42
|
+
run: (ctx) => {
|
|
43
|
+
const home = scratchHome();
|
|
44
|
+
const { hook: hookPath } = guard.ensureRunnerJobGuard(home, { orgs: ['Omega-JS-Stack'], actor: 'ianwieds' });
|
|
45
|
+
|
|
46
|
+
// The passing case, on the ONE trusted event (#923).
|
|
47
|
+
const ok = runHook(hookPath, { GITHUB_EVENT_NAME: 'workflow_dispatch', GITHUB_REPOSITORY: 'Omega-JS-Stack/omega-omega', GITHUB_ACTOR: 'ianwieds' });
|
|
48
|
+
ctx.expect(ok.status).toBe(0);
|
|
49
|
+
ctx.expect(ok.stdout.trim()).toBe('Allowing this job: workflow_dispatch on Omega-JS-Stack/omega-omega by ianwieds.');
|
|
50
|
+
|
|
51
|
+
// Each failing check: nonzero, and ONE line carrying the env name that
|
|
52
|
+
// failed and the value seen.
|
|
53
|
+
const cases = [
|
|
54
|
+
[{ GITHUB_EVENT_NAME: 'pull_request', GITHUB_REPOSITORY: 'Omega-JS-Stack/omega-omega', GITHUB_ACTOR: 'ianwieds' }, 'GITHUB_EVENT_NAME', 'pull_request'],
|
|
55
|
+
[{ GITHUB_EVENT_NAME: 'repository_dispatch', GITHUB_REPOSITORY: 'Omega-JS-Stack/omega-omega', GITHUB_ACTOR: 'ianwieds' }, 'GITHUB_EVENT_NAME', 'repository_dispatch'],
|
|
56
|
+
[{ GITHUB_EVENT_NAME: 'push', GITHUB_REPOSITORY: 'Omega-JS-Stack/omega-omega', GITHUB_ACTOR: 'ianwieds' }, 'GITHUB_EVENT_NAME', 'push'],
|
|
57
|
+
[{ GITHUB_EVENT_NAME: 'workflow_dispatch', GITHUB_REPOSITORY: 'someone-else/their-app', GITHUB_ACTOR: 'ianwieds' }, 'GITHUB_REPOSITORY', 'someone-else/their-app'],
|
|
58
|
+
[{ GITHUB_EVENT_NAME: 'workflow_dispatch', GITHUB_REPOSITORY: 'Omega-JS-Stack/omega-omega', GITHUB_ACTOR: 'mallory' }, 'GITHUB_ACTOR', 'mallory'],
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
for (const [job, name, value] of cases) {
|
|
62
|
+
const refused = runHook(hookPath, job);
|
|
63
|
+
ctx.expect(refused.status).toBe(1);
|
|
64
|
+
ctx.expect(refused.stdout.trim().split('\n').length).toBe(1);
|
|
65
|
+
ctx.expect(refused.stdout).toContain(name);
|
|
66
|
+
ctx.expect(refused.stdout).toContain(value);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
fs.rmSync(home, { recursive: true, force: true });
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'a job with no environment at all is refused, and so is one whose allow list is missing',
|
|
74
|
+
run: (ctx) => {
|
|
75
|
+
const home = scratchHome();
|
|
76
|
+
const { hook: hookPath, repos } = guard.ensureRunnerJobGuard(home, { orgs: ['Omega-JS-Stack'], actor: 'ianwieds' });
|
|
77
|
+
|
|
78
|
+
// The runner always sets these; a hook run without them is a hook run
|
|
79
|
+
// that cannot answer the question, which is a refusal.
|
|
80
|
+
ctx.expect(runHook(hookPath, {}).status).toBe(1);
|
|
81
|
+
|
|
82
|
+
// An allow list that is not there allows nothing: the guard never opens
|
|
83
|
+
// the box up by failing to read its own configuration.
|
|
84
|
+
fs.rmSync(repos, { force: true });
|
|
85
|
+
const refused = runHook(hookPath, { GITHUB_EVENT_NAME: 'workflow_dispatch', GITHUB_REPOSITORY: 'Omega-JS-Stack/omega-omega', GITHUB_ACTOR: 'ianwieds' });
|
|
86
|
+
ctx.expect(refused.status).toBe(1);
|
|
87
|
+
ctx.expect(refused.stdout).toContain('allowed-repos.txt');
|
|
88
|
+
|
|
89
|
+
fs.rmSync(home, { recursive: true, force: true });
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: 'the allow list reads `owner/*` and single repos alike, case-insensitively, and comments and blanks are not entries',
|
|
94
|
+
run: (ctx) => {
|
|
95
|
+
const lists = { repos: ['Omega-JS-Stack/*', 'other-org/one-app'], actors: ['ianwieds'], reposFile: 'allowed-repos.txt', actorsFile: 'allowed-actors.txt' };
|
|
96
|
+
const dispatch = (repository) => hook.checkJob({ GITHUB_EVENT_NAME: 'workflow_dispatch', GITHUB_REPOSITORY: repository, GITHUB_ACTOR: 'IanWieds' }, lists).allowed;
|
|
97
|
+
|
|
98
|
+
ctx.expect(dispatch('omega-js-stack/anything')).toBe(true);
|
|
99
|
+
ctx.expect(dispatch('other-org/one-app')).toBe(true);
|
|
100
|
+
ctx.expect(dispatch('other-org/another-app')).toBe(false);
|
|
101
|
+
// `owner/*` is one owner, never a prefix of another owner's name.
|
|
102
|
+
ctx.expect(dispatch('Omega-JS-Stack-evil/app')).toBe(false);
|
|
103
|
+
|
|
104
|
+
const home = scratchHome();
|
|
105
|
+
const file = path.join(home, 'allowed-repos.txt');
|
|
106
|
+
fs.writeFileSync(file, '# a comment\n\n Omega-JS-Stack/omega-omega # trailing\n');
|
|
107
|
+
ctx.expect(hook.readList(file)).toEqual(['Omega-JS-Stack/omega-omega']);
|
|
108
|
+
|
|
109
|
+
fs.rmSync(home, { recursive: true, force: true });
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: 'the setup writes the hook and both allow lists beside the runner, and rerunning writes the same bytes',
|
|
114
|
+
run: (ctx) => {
|
|
115
|
+
const home = scratchHome();
|
|
116
|
+
const first = guard.ensureRunnerJobGuard(home, { orgs: ['Omega-JS-Stack'], actor: 'ianwieds' });
|
|
117
|
+
|
|
118
|
+
ctx.expect(first.hook).toBe(path.join(home, 'job-started.js'));
|
|
119
|
+
ctx.expect(first.repos).toBe(path.join(home, 'allowed-repos.txt'));
|
|
120
|
+
ctx.expect(first.actors).toBe(path.join(home, 'allowed-actors.txt'));
|
|
121
|
+
ctx.expect(first.added.repos).toEqual(['Omega-JS-Stack/*']);
|
|
122
|
+
ctx.expect(first.added.actors).toEqual(['ianwieds']);
|
|
123
|
+
|
|
124
|
+
// The hook on the box is this package's file, so a framework update
|
|
125
|
+
// reaches the box on its next install or start.
|
|
126
|
+
ctx.expect(fs.readFileSync(first.hook, 'utf8')).toBe(fs.readFileSync(path.join(__dirname, '..', '..', '..', 'runner', 'job-started.js'), 'utf8'));
|
|
127
|
+
|
|
128
|
+
const bytes = () => [first.hook, first.repos, first.actors].map((file) => fs.readFileSync(file, 'utf8'));
|
|
129
|
+
const before = bytes();
|
|
130
|
+
const again = guard.ensureRunnerJobGuard(home, { orgs: ['Omega-JS-Stack'], actor: 'ianwieds' });
|
|
131
|
+
ctx.expect(again.added).toEqual({ repos: [], actors: [] });
|
|
132
|
+
ctx.expect(bytes()).toEqual(before);
|
|
133
|
+
|
|
134
|
+
// A second org registered later gets its line; the first one is not
|
|
135
|
+
// duplicated and the file the operator reads keeps its header.
|
|
136
|
+
const third = guard.ensureRunnerJobGuard(home, { orgs: ['Omega-JS-Stack', 'ITW-Creative-Works'], actor: 'ianwieds' });
|
|
137
|
+
ctx.expect(third.added.repos).toEqual(['ITW-Creative-Works/*']);
|
|
138
|
+
ctx.expect(hook.readList(third.repos)).toEqual(['Omega-JS-Stack/*', 'ITW-Creative-Works/*']);
|
|
139
|
+
ctx.expect(fs.readFileSync(third.repos, 'utf8').startsWith('# The repositories this box will sign for')).toBe(true);
|
|
140
|
+
|
|
141
|
+
fs.rmSync(home, { recursive: true, force: true });
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: 'an allow list narrowed by hand is never widened back by a later setup',
|
|
146
|
+
run: (ctx) => {
|
|
147
|
+
const home = scratchHome();
|
|
148
|
+
const { repos } = guard.ensureRunnerJobGuard(home, { orgs: ['Omega-JS-Stack'], actor: 'ianwieds' });
|
|
149
|
+
|
|
150
|
+
// The operator narrows the org wildcard to the one repo that signs.
|
|
151
|
+
fs.writeFileSync(repos, '# mine\nOmega-JS-Stack/omega-omega\n');
|
|
152
|
+
const again = guard.ensureRunnerJobGuard(home, { orgs: ['Omega-JS-Stack'], actor: 'ianwieds' });
|
|
153
|
+
|
|
154
|
+
ctx.expect(again.added.repos).toEqual([]);
|
|
155
|
+
ctx.expect(hook.readList(repos)).toEqual(['Omega-JS-Stack/omega-omega']);
|
|
156
|
+
|
|
157
|
+
fs.rmSync(home, { recursive: true, force: true });
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
name: "the hook path reaches the listener through the runner dir's own .env, beside HOME",
|
|
162
|
+
run: (ctx) => {
|
|
163
|
+
const home = scratchHome();
|
|
164
|
+
const runnerDir = path.join(home, 'actions-runner-omega-js-stack');
|
|
165
|
+
const { hook: hookPath } = guard.ensureRunnerJobGuard(home, { orgs: ['Omega-JS-Stack'], actor: 'ianwieds' });
|
|
166
|
+
|
|
167
|
+
// The file GitHub documents for runner env, which the listener reads at
|
|
168
|
+
// startup and applies to every job: the same one the private HOME
|
|
169
|
+
// travels in (#807), written once for both.
|
|
170
|
+
const file = ensureRunnerDirEnv(runnerDir, { HOME: path.join(home, 'home'), ACTIONS_RUNNER_HOOK_JOB_STARTED: hookPath });
|
|
171
|
+
const lines = fs.readFileSync(file, 'utf8').trim().split(/\r?\n/);
|
|
172
|
+
|
|
173
|
+
ctx.expect(lines).toContain(`ACTIONS_RUNNER_HOOK_JOB_STARTED=${hookPath}`);
|
|
174
|
+
ctx.expect(lines).toContain(`HOME=${path.join(home, 'home')}`);
|
|
175
|
+
// Bare, never quoted: the listener takes the rest of the line verbatim.
|
|
176
|
+
ctx.expect(fs.readFileSync(file, 'utf8')).not.toContain('"');
|
|
177
|
+
|
|
178
|
+
fs.rmSync(home, { recursive: true, force: true });
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
});
|
|
@@ -1155,11 +1155,23 @@ module.exports = defineCases({
|
|
|
1155
1155
|
jetpack.write(path.join(home, 'actions-runner-foo', 'config.cmd'), 'rem foo');
|
|
1156
1156
|
jetpack.write(path.join(home, '_template', 'config.cmd'), 'rem template');
|
|
1157
1157
|
|
|
1158
|
+
// The job guard's allow lists are configuration too
|
|
1159
|
+
// ([#875](https://github.com/Omega-JS-Stack/omega/issues/875)): a list
|
|
1160
|
+
// narrowed by hand outlives an uninstall exactly as the `.env` does.
|
|
1161
|
+
// The hook SCRIPT is install state, and the next install writes it back.
|
|
1162
|
+
jetpack.write(path.join(home, 'allowed-repos.txt'), 'Omega-JS-Stack/omega-omega\n');
|
|
1163
|
+
jetpack.write(path.join(home, 'allowed-actors.txt'), 'ianwieds\n');
|
|
1164
|
+
jetpack.write(path.join(home, 'job-started.js'), '// stale copy');
|
|
1165
|
+
|
|
1158
1166
|
await runner.removeRunnerHomeWithRetry(new Set(), home);
|
|
1159
1167
|
|
|
1160
|
-
ctx.expect(jetpack.list(home).sort()).toEqual(['.env', 'logs']);
|
|
1168
|
+
ctx.expect(jetpack.list(home).sort()).toEqual(['.env', 'allowed-actors.txt', 'allowed-repos.txt', 'logs']);
|
|
1161
1169
|
ctx.expect(jetpack.read(path.join(home, '.env'))).toBe('WIN_CSC_KEY_PASSWORD=keep-me');
|
|
1162
1170
|
ctx.expect(jetpack.read(runnerEnv.runnerLogFile(home))).toBe('# omega log\n');
|
|
1171
|
+
ctx.expect(jetpack.read(path.join(home, 'allowed-repos.txt'))).toBe('Omega-JS-Stack/omega-omega\n');
|
|
1172
|
+
|
|
1173
|
+
jetpack.remove(path.join(home, 'allowed-repos.txt'));
|
|
1174
|
+
jetpack.remove(path.join(home, 'allowed-actors.txt'));
|
|
1163
1175
|
|
|
1164
1176
|
// A kept org dir (its deregistration failed) survives beside them.
|
|
1165
1177
|
jetpack.write(path.join(home, 'actions-runner-bar', 'config.cmd'), 'rem bar');
|
|
@@ -2584,10 +2596,21 @@ module.exports = defineCases({
|
|
|
2584
2596
|
});
|
|
2585
2597
|
|
|
2586
2598
|
ctx.expect(fs.readFileSync(path.join(home, 'home', '.gitconfig'), 'utf8')).toBe('[core]\n\tautocrlf = false\n');
|
|
2599
|
+
// The job guard is healed on the same pass
|
|
2600
|
+
// ([#875](https://github.com/Omega-JS-Stack/omega/issues/875)): a box
|
|
2601
|
+
// that installed before it would otherwise come online signing for
|
|
2602
|
+
// anything. Both lines, in one `<runner dir>\.env`.
|
|
2603
|
+
const hook = path.join(home, 'job-started.js');
|
|
2604
|
+
ctx.expect(jetpack.exists(hook)).toBe('file');
|
|
2587
2605
|
for (const org of orgs) {
|
|
2588
|
-
ctx.expect(fs.readFileSync(path.join(home, `actions-runner-${org}`, '.env'), 'utf8')).toBe(`HOME=${path.join(home, 'home')}\n`);
|
|
2606
|
+
ctx.expect(fs.readFileSync(path.join(home, `actions-runner-${org}`, '.env'), 'utf8')).toBe(`HOME=${path.join(home, 'home')}\nACTIONS_RUNNER_HOOK_JOB_STARTED=${hook}\n`);
|
|
2589
2607
|
ctx.expect(logged.join('\n')).toContain(`HOME for ${org}: ${path.join(home, 'home')}`);
|
|
2608
|
+
ctx.expect(logged.join('\n')).toContain(`Job guard for ${org}: ${hook}`);
|
|
2590
2609
|
}
|
|
2610
|
+
// `start` asks GitHub nothing, so the actor list is the operator's to
|
|
2611
|
+
// fill: the file is there with its header, and an empty list refuses.
|
|
2612
|
+
ctx.expect(jetpack.exists(path.join(home, 'allowed-repos.txt'))).toBe('file');
|
|
2613
|
+
ctx.expect(jetpack.exists(path.join(home, 'allowed-actors.txt'))).toBe('file');
|
|
2591
2614
|
} finally {
|
|
2592
2615
|
if (origForce === undefined) delete process.env.OMEGA_RUNNER_FORCE; else process.env.OMEGA_RUNNER_FORCE = origForce;
|
|
2593
2616
|
for (const file of shortcuts) jetpack.remove(file);
|
|
@@ -63,15 +63,20 @@ module.exports = defineCases({
|
|
|
63
63
|
// in the sibling `package` key.
|
|
64
64
|
name: 'the bundle bake folds the app version into the config the renderer hands the client',
|
|
65
65
|
run: (ctx) => {
|
|
66
|
-
const { composeBuildConfig } = require(path.join(FRAMEWORK_ROOT, 'src', 'gulp', 'tasks', 'bundle.js'));
|
|
66
|
+
const { composeBuildConfig, buildFacts } = require(path.join(FRAMEWORK_ROOT, 'src', 'gulp', 'tasks', 'bundle.js'));
|
|
67
67
|
|
|
68
|
-
const
|
|
68
|
+
const pkg = { version: '3.1.4' };
|
|
69
|
+
const mode = { environment: 'production', build: true, publish: false };
|
|
70
|
+
const packaged = composeBuildConfig({ brand: { id: 'paperloom' } }, buildFacts({ pkg, mode, dev: null }));
|
|
69
71
|
ctx.expect(packaged.version).toBe('3.1.4');
|
|
70
72
|
ctx.expect(packaged.brand.id).toBe('paperloom');
|
|
71
73
|
ctx.expect(packaged.dev).toBeUndefined();
|
|
72
74
|
|
|
73
75
|
// A dev build carries the resolved local-stack map beside it (#300).
|
|
74
|
-
const local = composeBuildConfig(
|
|
76
|
+
const local = composeBuildConfig(
|
|
77
|
+
{ brand: { id: 'paperloom' } },
|
|
78
|
+
buildFacts({ pkg, mode: { ...mode, environment: 'development', build: false }, dev: { ports: { auth: 9099 } } }),
|
|
79
|
+
);
|
|
75
80
|
ctx.expect(local.version).toBe('3.1.4');
|
|
76
81
|
ctx.expect(local.dev.ports.auth).toBe(9099);
|
|
77
82
|
},
|
|
@@ -72,6 +72,9 @@ module.exports = defineCases({
|
|
|
72
72
|
const written = JSON.parse(contents);
|
|
73
73
|
ctx.expect(written.scripts.build).toBe('omega build');
|
|
74
74
|
ctx.expect(written.private).toBe(true);
|
|
75
|
+
// Every OMEGA target states its license when it states none of its
|
|
76
|
+
// own (#884): npm's word for closed-source commercial code
|
|
77
|
+
ctx.expect(written.license).toBe('UNLICENSED');
|
|
75
78
|
ctx.expect(written.main).toBe('dist/main.bundle.js');
|
|
76
79
|
ctx.expect(contents).toContain('\n "name": "staged-app"');
|
|
77
80
|
});
|
|
@@ -14,7 +14,7 @@ module.exports = defineCases({
|
|
|
14
14
|
description: 'sign-windows — signtool command construction + retry policy',
|
|
15
15
|
tests: [
|
|
16
16
|
{
|
|
17
|
-
name: 'this suite can never sign for real: every
|
|
17
|
+
name: 'this suite can never sign for real: every call that reaches signtool injects its exec',
|
|
18
18
|
run: (ctx) => {
|
|
19
19
|
// The box runs this lane too. `signWithRetry` shells out to signtool by
|
|
20
20
|
// DEFAULT, so a case that forgets `exec:` would drive the real EV token
|
|
@@ -22,13 +22,17 @@ module.exports = defineCases({
|
|
|
22
22
|
// lives in sign-windows-e2e.test.js, gated. This pins the invariant.
|
|
23
23
|
const fs = require('fs');
|
|
24
24
|
const source = fs.readFileSync(__filename, 'utf8');
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
// Both entry points reach signtool: the retry wrapper directly, and the
|
|
26
|
+
// signing loop through it plus its own verify call.
|
|
27
|
+
for (const entry of ['signWithRetry', 'signWithSigntool']) {
|
|
28
|
+
// The needle is assembled so this case's own text is not one of its hits.
|
|
29
|
+
const calls = source.split(`${entry}${'('}`).slice(1);
|
|
30
|
+
ctx.expect(calls.length).toBeGreaterThan(0);
|
|
31
|
+
for (const call of calls) {
|
|
32
|
+
const body = call.slice(0, call.indexOf('});'));
|
|
33
|
+
if (!body.includes('exec:')) {
|
|
34
|
+
throw new Error(`a ${entry} call in this suite does not inject exec, it would run the real signtool:\n${body.slice(0, 200)}`);
|
|
35
|
+
}
|
|
32
36
|
}
|
|
33
37
|
}
|
|
34
38
|
},
|
|
@@ -143,6 +147,59 @@ module.exports = defineCases({
|
|
|
143
147
|
.toBe('"signtool" verify /pa "C:\\out\\App.exe"');
|
|
144
148
|
},
|
|
145
149
|
},
|
|
150
|
+
{
|
|
151
|
+
name: 'a signed artifact prints the proof line in the mac shape, after verify passes (#918)',
|
|
152
|
+
run: async (ctx) => {
|
|
153
|
+
// The mac lane ends with `Stapled and verified: ...` (hooks/notarize.js).
|
|
154
|
+
// Windows runs the same kind of proof (`signtool verify /pa`, a failure
|
|
155
|
+
// aborts the job), so it says so in the same shape, naming the check it
|
|
156
|
+
// ran. Driven here through the injected signtool: the ORDER (sign, then
|
|
157
|
+
// verify, THEN the line) is what makes the line true.
|
|
158
|
+
const os = require('os');
|
|
159
|
+
const jetpack = require('fs-jetpack');
|
|
160
|
+
|
|
161
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'omega-sign-proof-'));
|
|
162
|
+
const inDir = path.join(tmp, 'release');
|
|
163
|
+
const outDir = path.join(inDir, 'signed');
|
|
164
|
+
const target = path.join(inDir, 'App-windows.msi');
|
|
165
|
+
// .msi on purpose: it needs no latest.yml, so the loop is the whole test.
|
|
166
|
+
jetpack.write(target, 'unsigned bytes');
|
|
167
|
+
|
|
168
|
+
const commands = [];
|
|
169
|
+
const lines = [];
|
|
170
|
+
const fakeLogger = {
|
|
171
|
+
log: (m) => lines.push(String(m)),
|
|
172
|
+
warn: (m) => lines.push(String(m)),
|
|
173
|
+
error: (m) => lines.push(String(m)),
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const origToken = process.env.WIN_EV_TOKEN_PATH;
|
|
177
|
+
const origPassword = process.env.WIN_CSC_KEY_PASSWORD;
|
|
178
|
+
try {
|
|
179
|
+
// Thumbprint mode with no PIN: SafeNet owns it, so no console-lock check.
|
|
180
|
+
process.env.WIN_EV_TOKEN_PATH = 'f'.repeat(40);
|
|
181
|
+
delete process.env.WIN_CSC_KEY_PASSWORD;
|
|
182
|
+
|
|
183
|
+
await signWindows.signWithSigntool([target], inDir, outDir, {
|
|
184
|
+
exec: (cmd) => { commands.push(cmd); return Promise.resolve('Successfully signed'); },
|
|
185
|
+
logger: fakeLogger,
|
|
186
|
+
});
|
|
187
|
+
} finally {
|
|
188
|
+
if (origToken === undefined) delete process.env.WIN_EV_TOKEN_PATH; else process.env.WIN_EV_TOKEN_PATH = origToken;
|
|
189
|
+
if (origPassword === undefined) delete process.env.WIN_CSC_KEY_PASSWORD; else process.env.WIN_CSC_KEY_PASSWORD = origPassword;
|
|
190
|
+
jetpack.remove(tmp);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
ctx.expect(commands.length).toBe(2);
|
|
194
|
+
ctx.expect(commands[0]).toContain(' sign /sha1 ');
|
|
195
|
+
ctx.expect(commands[1]).toContain('verify /pa');
|
|
196
|
+
// The proof line is the LAST thing this file's turn prints, and it names
|
|
197
|
+
// the check as well as the artifact: mac punctuation, basename, period.
|
|
198
|
+
ctx.expect(lines[lines.length - 1]).toBe('Signed and verified: signtool verify /pa accepts App-windows.msi.');
|
|
199
|
+
// The old line claimed only a signature, which is what sent the owner asking.
|
|
200
|
+
ctx.expect(lines.some((line) => line.includes('Signed: '))).toBe(false);
|
|
201
|
+
},
|
|
202
|
+
},
|
|
146
203
|
{
|
|
147
204
|
name: 'WIN_EV_TOKEN_PATH is the ONE cert-reference key — WIN_CSC_LINK is not read',
|
|
148
205
|
run: (ctx) => {
|
|
@@ -309,5 +366,32 @@ module.exports = defineCases({
|
|
|
309
366
|
ctx.expect(signWindows.isTransientSignFailure(new Error('The specified network password is not correct'))).toBe(false);
|
|
310
367
|
},
|
|
311
368
|
},
|
|
369
|
+
{
|
|
370
|
+
name: 'a terminated attempt reads as transient, so the next one gets a fresh PIN watcher',
|
|
371
|
+
run: async (ctx) => {
|
|
372
|
+
// The wording is execWithLimit's (exec-with-limit.test.js proves the
|
|
373
|
+
// mechanism); here it only matters that nothing classifies it as final.
|
|
374
|
+
const limitError = new Error('signtool produced no verdict within 180s and was terminated');
|
|
375
|
+
ctx.expect(signWindows.isTransientSignFailure(limitError)).toBe(true);
|
|
376
|
+
ctx.expect(signWindows.SIGN_TIME_LIMIT_MS).toBe(3 * 60 * 1000);
|
|
377
|
+
|
|
378
|
+
let watchers = 0;
|
|
379
|
+
let calls = 0;
|
|
380
|
+
let threw;
|
|
381
|
+
try {
|
|
382
|
+
await signWindows.signWithRetry('signtool sign ...', {
|
|
383
|
+
file: 'App.exe',
|
|
384
|
+
exec: () => { calls += 1; throw limitError; },
|
|
385
|
+
sleep: () => Promise.resolve(),
|
|
386
|
+
emit: () => {},
|
|
387
|
+
startUnlock: () => { watchers += 1; return { stop: () => {} }; },
|
|
388
|
+
});
|
|
389
|
+
} catch (e) { threw = e; }
|
|
390
|
+
|
|
391
|
+
ctx.expect(calls).toBe(3);
|
|
392
|
+
ctx.expect(watchers).toBe(3);
|
|
393
|
+
ctx.expect(threw.message).toMatch(/no verdict within 180s/);
|
|
394
|
+
},
|
|
395
|
+
},
|
|
312
396
|
],
|
|
313
397
|
});
|
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
// invisible and non-intrusive". Consumed by window-manager (stealth surfacing),
|
|
3
3
|
// main.js (macOS app-activation suppression), and the test harness.
|
|
4
4
|
//
|
|
5
|
-
// Build layer runs in plain Node, so the env-var contract (
|
|
5
|
+
// Build layer runs in plain Node, so the env-var contract (OMEGA_ENVIRONMENT,
|
|
6
|
+
// the ONE environment input since
|
|
7
|
+
// [#817](https://github.com/Omega-JS-Stack/omega/issues/817), plus
|
|
6
8
|
// OMEGA_TEST_SHOW) is exercised directly with save/restore around each case.
|
|
7
9
|
|
|
8
10
|
const isTestStealth = require('../../../utils/test-stealth.js');
|
|
9
11
|
const defineCases = require('../../../vendor/devkit/test/define-cases.js');
|
|
10
12
|
|
|
11
|
-
// Run fn with
|
|
13
|
+
// Run fn with OMEGA_ENVIRONMENT / OMEGA_TEST_SHOW set to the given values (undefined = unset),
|
|
12
14
|
// restoring the real environment afterwards so other build suites are unaffected.
|
|
13
15
|
function withEnv(vars, fn) {
|
|
14
16
|
const saved = {};
|
|
@@ -41,7 +43,7 @@ module.exports = defineCases({
|
|
|
41
43
|
{
|
|
42
44
|
name: 'true in testing mode without OMEGA_TEST_SHOW',
|
|
43
45
|
run: (ctx) => {
|
|
44
|
-
withEnv({
|
|
46
|
+
withEnv({ OMEGA_ENVIRONMENT: 'testing', OMEGA_TEST_SHOW: undefined }, () => {
|
|
45
47
|
ctx.expect(isTestStealth()).toBe(true);
|
|
46
48
|
});
|
|
47
49
|
},
|
|
@@ -49,7 +51,7 @@ module.exports = defineCases({
|
|
|
49
51
|
{
|
|
50
52
|
name: 'OMEGA_TEST_SHOW=1 opts out even in testing mode',
|
|
51
53
|
run: (ctx) => {
|
|
52
|
-
withEnv({
|
|
54
|
+
withEnv({ OMEGA_ENVIRONMENT: 'testing', OMEGA_TEST_SHOW: '1' }, () => {
|
|
53
55
|
ctx.expect(isTestStealth()).toBe(false);
|
|
54
56
|
});
|
|
55
57
|
},
|
|
@@ -57,24 +59,28 @@ module.exports = defineCases({
|
|
|
57
59
|
{
|
|
58
60
|
name: 'false outside testing mode regardless of OMEGA_TEST_SHOW',
|
|
59
61
|
run: (ctx) => {
|
|
60
|
-
withEnv({
|
|
62
|
+
withEnv({ OMEGA_ENVIRONMENT: 'development', OMEGA_TEST_SHOW: undefined }, () => {
|
|
63
|
+
ctx.expect(isTestStealth()).toBe(false);
|
|
64
|
+
});
|
|
65
|
+
withEnv({ OMEGA_ENVIRONMENT: 'production', OMEGA_TEST_SHOW: '1' }, () => {
|
|
61
66
|
ctx.expect(isTestStealth()).toBe(false);
|
|
62
67
|
});
|
|
63
68
|
},
|
|
64
69
|
},
|
|
65
70
|
{
|
|
66
|
-
name: 'a provided manager
|
|
71
|
+
name: 'a provided manager answers from the config IT was baked with',
|
|
67
72
|
run: (ctx) => {
|
|
68
|
-
//
|
|
69
|
-
//
|
|
73
|
+
// The build-time Manager carries the same mixin as the runtime Managers,
|
|
74
|
+
// and since #817 they all read the ONE input: the process variable, or
|
|
75
|
+
// the baked `config.environment` when the process has none.
|
|
70
76
|
const Manager = require('../../../build.js');
|
|
71
|
-
withEnv({
|
|
77
|
+
withEnv({ OMEGA_ENVIRONMENT: undefined, OMEGA_TEST_SHOW: undefined }, () => {
|
|
72
78
|
const testing = new Manager();
|
|
73
|
-
testing.config = {
|
|
79
|
+
testing.config = { environment: 'testing' };
|
|
74
80
|
ctx.expect(isTestStealth(testing)).toBe(true);
|
|
75
81
|
|
|
76
82
|
const production = new Manager();
|
|
77
|
-
production.config = {
|
|
83
|
+
production.config = { environment: 'production' };
|
|
78
84
|
ctx.expect(isTestStealth(production)).toBe(false);
|
|
79
85
|
});
|
|
80
86
|
},
|