@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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// bundle — three bundles (main / preload / renderer), built in parallel.
|
|
2
|
-
//
|
|
2
|
+
// The NODE pair (main / preload) gets `OMEGA_BUILD_JSON` baked in as a define;
|
|
3
|
+
// the renderer reads the ONE `dist/build.js` the page template loads (#743).
|
|
3
4
|
//
|
|
4
5
|
// The bundler is esbuild since [#737](https://github.com/Omega-JS-Stack/omega/issues/737),
|
|
5
6
|
// and the file, the gulp task, the log tag and `npm run gulp -- bundle` were
|
|
@@ -19,11 +20,12 @@ const logger = Manager.logger('bundle');
|
|
|
19
20
|
const path = require('path');
|
|
20
21
|
const glob = require('glob').globSync;
|
|
21
22
|
const jetpack = require('fs-jetpack');
|
|
22
|
-
const { readSiblingPorts, readSiblingOrigin, envPorts } = require('../../vendor/config/index.js');
|
|
23
|
-
const { bakeKeys } = require('../../vendor/config/env-delivery.js');
|
|
23
|
+
const { CLASSIC_PORTS, CLASSIC_DEV_ORIGIN, readSiblingPorts, readSiblingOrigin, envPorts, targetNameFromDir } = require('../../vendor/config/index.js');
|
|
24
|
+
const { bakeKeys, bakeSourceKeys } = require('../../vendor/config/env-delivery.js');
|
|
24
25
|
const { checkEnvRules } = require('../../vendor/config/env-rules.js');
|
|
25
26
|
const { resolveLicenseStamp } = require('../../vendor/devkit/license.js');
|
|
26
27
|
const { bundle, formatBytes } = require('../../vendor/devkit/bundle.js');
|
|
28
|
+
const buildJsonKit = require('../../vendor/devkit/build-json.js');
|
|
27
29
|
const { emptyModulesPlugin } = require('../../vendor/devkit/empty-modules-plugin.js');
|
|
28
30
|
const electronTargets = require('../../utils/electron-targets.js');
|
|
29
31
|
|
|
@@ -49,33 +51,72 @@ const RENDERER_EMPTY_MODULES = [
|
|
|
49
51
|
'querystring', 'string_decoder', 'electron',
|
|
50
52
|
];
|
|
51
53
|
|
|
52
|
-
// The
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
// back to the build stamp without one (#380), and it never sees the
|
|
57
|
-
// `package` key.
|
|
58
|
-
|
|
59
|
-
|
|
54
|
+
// The build FACTS this artifact carries, composed onto the resolved config by
|
|
55
|
+
// both halves ([#894](https://github.com/Omega-JS-Stack/omega/issues/894)) and
|
|
56
|
+
// spelled the way web and the extension spell them. The app's VERSION rides
|
|
57
|
+
// INSIDE the config: the client tags every error report `<brand.id>@<version>`
|
|
58
|
+
// and falls back to the build stamp without one (#380), and it never sees the
|
|
59
|
+
// sibling `package` key. `runtime` is baked because the client's own sniff has
|
|
60
|
+
// no Electron signal to find in a renderer and would answer `'web'`
|
|
61
|
+
// ([#896](https://github.com/Omega-JS-Stack/omega/issues/896)). The dev map
|
|
62
|
+
// (#300) is a dev-build key only.
|
|
63
|
+
function buildFacts({ pkg, mode, dev }) {
|
|
64
|
+
return {
|
|
65
|
+
runtime: 'electron',
|
|
66
|
+
environment: mode.environment,
|
|
67
|
+
version: pkg.version,
|
|
68
|
+
buildTime: Date.now(),
|
|
69
|
+
// WHICH target this artifact IS, by name (#887)
|
|
70
|
+
target: targetNameFromDir(projectRoot) || 'desktop',
|
|
71
|
+
...(dev ? { dev } : {}),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// The wrapper's `mode`, the same three keys on every OMEGA surface (#894).
|
|
76
|
+
// Desktop's own `server` verdict stays inside the Manager, which is the only
|
|
77
|
+
// thing that reads it: an artifact records what it WAS BUILT as.
|
|
78
|
+
function artifactMode(mode) {
|
|
79
|
+
return { environment: mode.environment, build: mode.build, publish: mode.publish };
|
|
60
80
|
}
|
|
61
81
|
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
//
|
|
69
|
-
|
|
82
|
+
// The config blob the NODE bundles read as OMEGA_BUILD_JSON.config (main and
|
|
83
|
+
// preload): the WHOLE resolved config plus this build's facts.
|
|
84
|
+
function composeBuildConfig(config, facts) {
|
|
85
|
+
return { ...config, ...facts };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// OMEGA_BUILD_JSON for the NODE half: the build's own record of what it
|
|
89
|
+
// produced. The `license` stamp
|
|
90
|
+
// ([#320](https://github.com/Omega-JS-Stack/omega/issues/320)) rides HERE and
|
|
91
|
+
// not inside `config`: it is a fact about the build, not part of the client
|
|
92
|
+
// contract the renderer hands @omega.js/client. Nothing in the app reads it
|
|
93
|
+
// today (a packaged app has no attribution surface, and payments ride the
|
|
94
|
+
// backend's own gate); it is what makes an artifact say which verdict it was
|
|
95
|
+
// packaged under.
|
|
96
|
+
//
|
|
97
|
+
// Main and preload keep the whole resolved config because the main process
|
|
98
|
+
// BOOTS from it (main.js reads OMEGA_BUILD_JSON.config in a packaged app, where
|
|
99
|
+
// config/omega.json5 is inside the asar), and a preload is isolated from the
|
|
100
|
+
// page. Both are Node, neither is a public surface.
|
|
101
|
+
function composeBuildJson({ config, facts, pkg, mode, license }) {
|
|
70
102
|
return {
|
|
71
|
-
config: composeBuildConfig(config,
|
|
103
|
+
config: composeBuildConfig(config, facts),
|
|
72
104
|
package: pkg,
|
|
73
|
-
mode,
|
|
105
|
+
mode: artifactMode(mode),
|
|
74
106
|
license,
|
|
75
107
|
builtAt: new Date().toISOString(),
|
|
76
108
|
};
|
|
77
109
|
}
|
|
78
110
|
|
|
111
|
+
// The BROWSER half's snapshot ([#743](https://github.com/Omega-JS-Stack/omega/issues/743)):
|
|
112
|
+
// @omega.js/devkit composes it (through @omega.js/config's browser subset) and
|
|
113
|
+
// writes it to `dist/build.js`, which every view's page template loads with one
|
|
114
|
+
// script tag ahead of its own bundle. One file, one shape and one consumption
|
|
115
|
+
// on web, desktop and the extension alike.
|
|
116
|
+
function composeClientBuildJson({ config, facts, pkg, mode, license }) {
|
|
117
|
+
return buildJsonKit.composeBuildJson({ config, pkg, mode, license, facts });
|
|
118
|
+
}
|
|
119
|
+
|
|
79
120
|
module.exports = function bundleTask(done) {
|
|
80
121
|
const mode = Manager.getMode();
|
|
81
122
|
const isProd = mode.environment === 'production';
|
|
@@ -88,13 +129,20 @@ module.exports = function bundleTask(done) {
|
|
|
88
129
|
// channel, resolved on every build so a rebuild picks up a restarted
|
|
89
130
|
// emulator ([#300](https://github.com/Omega-JS-Stack/omega/issues/300)).
|
|
90
131
|
// The sibling website's published dev ORIGIN rides the same bake
|
|
91
|
-
// ([#262](https://github.com/Omega-JS-Stack/omega/issues/262))
|
|
92
|
-
//
|
|
93
|
-
//
|
|
132
|
+
// ([#262](https://github.com/Omega-JS-Stack/omega/issues/262)). Never in
|
|
133
|
+
// production: a packaged app has no local stack, so its `dev` is null and
|
|
134
|
+
// every dev read throws by name rather than reaching a localhost port.
|
|
94
135
|
const devOrigin = isProd ? null : readSiblingOrigin(projectRoot);
|
|
95
136
|
const dev = isProd ? null : {
|
|
96
|
-
|
|
97
|
-
|
|
137
|
+
// The classic map is the FLOOR, from @omega.js/config, the one place the
|
|
138
|
+
// numbers are defined ([#834](https://github.com/Omega-JS-Stack/omega/issues/834)).
|
|
139
|
+
// A live stack's resolved (possibly bumped) numbers land on top of it, so a
|
|
140
|
+
// dev artifact always carries a COMPLETE map and no browser-side code needs
|
|
141
|
+
// a second copy of the defaults to fall back to. The same floor goes under
|
|
142
|
+
// the origin: the classic dev origin is `omega dev`'s own default, and a
|
|
143
|
+
// live website's published one wins over it.
|
|
144
|
+
ports: { ...CLASSIC_PORTS, ...readSiblingPorts(projectRoot), ...envPorts() },
|
|
145
|
+
origin: devOrigin || CLASSIC_DEV_ORIGIN,
|
|
98
146
|
};
|
|
99
147
|
|
|
100
148
|
// The license check (#320), once per build: a PRODUCTION build asks the
|
|
@@ -104,8 +152,14 @@ module.exports = function bundleTask(done) {
|
|
|
104
152
|
const projectPackage = Manager.getPackage('project');
|
|
105
153
|
resolveLicenseStamp({ config, production: isProd })
|
|
106
154
|
.then((license) => {
|
|
107
|
-
// OMEGA_BUILD_JSON
|
|
108
|
-
|
|
155
|
+
// OMEGA_BUILD_JSON: frozen at build time. The NODE pair reads it as a
|
|
156
|
+
// define; the renderer reads the file written below.
|
|
157
|
+
const input = { config, facts: buildFacts({ pkg: projectPackage, mode, dev }), pkg: projectPackage, mode, license };
|
|
158
|
+
const buildJson = composeBuildJson(input);
|
|
159
|
+
|
|
160
|
+
// The ONE build.js, at dist/: the web root every view's shell resolves
|
|
161
|
+
// `../../build.js` against, inside a packaged asar exactly as on disk (#743).
|
|
162
|
+
buildJsonKit.writeBuildJs(outputRoot, composeClientBuildJson(input));
|
|
109
163
|
|
|
110
164
|
logger.log(`bundling — environment=${mode.environment}, license=${license.status}`);
|
|
111
165
|
runBundles(buildJson, isProd).then(() => done()).catch(done);
|
|
@@ -130,10 +184,16 @@ async function runBundles(buildJson, isProd) {
|
|
|
130
184
|
dev: !isProd,
|
|
131
185
|
};
|
|
132
186
|
|
|
187
|
+
// The renderer takes the same defines MINUS the snapshot: it reads the one
|
|
188
|
+
// build.js the page template loads ahead of it (#743), so nothing is copied
|
|
189
|
+
// into a bundle a user can read.
|
|
190
|
+
const { OMEGA_BUILD_JSON, ...rendererDefine } = shared.define;
|
|
191
|
+
const { banner, ...rendererShared } = { ...shared, define: rendererDefine };
|
|
192
|
+
|
|
133
193
|
const builds = [
|
|
134
194
|
mainBundle(shared),
|
|
135
195
|
preloadBundle(shared),
|
|
136
|
-
rendererBundle(
|
|
196
|
+
rendererBundle(rendererShared),
|
|
137
197
|
].filter(Boolean);
|
|
138
198
|
|
|
139
199
|
if (builds.length === 0) {
|
|
@@ -172,7 +232,7 @@ function resolveTargets() {
|
|
|
172
232
|
}
|
|
173
233
|
}
|
|
174
234
|
|
|
175
|
-
// Inject OMEGA_BUILD_JSON into
|
|
235
|
+
// Inject OMEGA_BUILD_JSON into the NODE bundles (main and preload) two ways:
|
|
176
236
|
// 1. `define` replaces the bare `OMEGA_BUILD_JSON` identifier with the literal at build time
|
|
177
237
|
// (so framework code can reference it without globals).
|
|
178
238
|
// 2. `banner` prepends a tiny IIFE that assigns the same value to globalThis.OMEGA_BUILD_JSON
|
|
@@ -198,8 +258,9 @@ function buildJsonDefines(buildJson, isProd) {
|
|
|
198
258
|
}
|
|
199
259
|
|
|
200
260
|
/**
|
|
201
|
-
* The globalThis/window assignment
|
|
202
|
-
*
|
|
261
|
+
* The globalThis/window assignment the NODE bundles carry. The renderer takes
|
|
262
|
+
* no banner at all: its snapshot is the one `dist/build.js` its shell loads (#743).
|
|
263
|
+
* @param {object} buildJson - The blob the node bundles bake.
|
|
203
264
|
* @returns {object} an esbuild banner
|
|
204
265
|
*/
|
|
205
266
|
function buildJsonBanner(buildJson) {
|
|
@@ -214,6 +275,8 @@ function buildJsonBanner(buildJson) {
|
|
|
214
275
|
// GOOGLE_ANALYTICS_SECRET read this replaced was one of three hand-kept lists
|
|
215
276
|
// for one concern. A new baked key is one schema entry and nothing here.
|
|
216
277
|
const BAKED_KEYS = bakeKeys('desktop');
|
|
278
|
+
// The same set at the level a HUMAN sets it: what the bake guard judges (#891)
|
|
279
|
+
const BAKED_SOURCE_KEYS = bakeSourceKeys('desktop');
|
|
217
280
|
|
|
218
281
|
/**
|
|
219
282
|
* The schema's presence rules at the BAKE seam
|
|
@@ -228,6 +291,12 @@ const BAKED_KEYS = bakeKeys('desktop');
|
|
|
228
291
|
* warns and keeps going, because a half-configured brand is a normal step on
|
|
229
292
|
* the way to a configured one.
|
|
230
293
|
*
|
|
294
|
+
* Only keys this build BAKES are its business
|
|
295
|
+
* ([#891](https://github.com/Omega-JS-Stack/omega/issues/891)): the schema's
|
|
296
|
+
* other desktop rules are CI-delivered signing credentials, which the deploy
|
|
297
|
+
* lane owns (the deploy precheck refuses to publish a half set) and which a local
|
|
298
|
+
* unsigned build has no need of at all.
|
|
299
|
+
*
|
|
231
300
|
* @param {object} config - The target's resolved omega.json5 config.
|
|
232
301
|
* @param {object} env - The build env.
|
|
233
302
|
* @param {object} [options]
|
|
@@ -241,7 +310,7 @@ function assertBakeRules(config, env, options) {
|
|
|
241
310
|
const warn = (options.logger || logger).warn.bind(options.logger || logger);
|
|
242
311
|
|
|
243
312
|
const violations = checkEnvRules(config, env, { target: 'desktop' })
|
|
244
|
-
.filter((violation) => violation.rule === 'requiredWhen');
|
|
313
|
+
.filter((violation) => violation.rule === 'requiredWhen' && BAKED_SOURCE_KEYS.includes(violation.key));
|
|
245
314
|
if (violations.length === 0) return;
|
|
246
315
|
|
|
247
316
|
// The BRAND-LEVEL key name (GOOGLE_ANALYTICS_SECRET_DESKTOP, not the
|
|
@@ -249,7 +318,7 @@ function assertBakeRules(config, env, options) {
|
|
|
249
318
|
// in the brand .env and in the repo's Actions secrets.
|
|
250
319
|
const named = violations.map(({ key, path }) => `${key} (required by ${path})`).join(', ');
|
|
251
320
|
const message = `${violations.length} env ${violations.length === 1 ? 'key this brand\'s config requires is' : 'keys this brand\'s config requires are'} missing from the build env: ${named}. `
|
|
252
|
-
+ 'Set it in the brand .env (
|
|
321
|
+
+ 'Set it in the brand .env (a CI build reads it as a repo Actions secret, which `omega deploy`\'s precheck pushes), then build again.';
|
|
253
322
|
|
|
254
323
|
if (mode.build || mode.publish) {
|
|
255
324
|
throw new Error(message);
|
|
@@ -365,7 +434,7 @@ function rendererEntryKey(rel) {
|
|
|
365
434
|
/**
|
|
366
435
|
* One browser bundle per `src/assets/js/components/<name>/index.js`, loaded by
|
|
367
436
|
* the page template as a plain `<script src>` — so iife, not esm.
|
|
368
|
-
* @param {object} shared - `{ define,
|
|
437
|
+
* @param {object} shared - `{ define, targets, dev }`, composed once per run (no banner: #743)
|
|
369
438
|
* @returns {object|null} `{ name, options }`, or null when the consumer has no entries
|
|
370
439
|
*/
|
|
371
440
|
function rendererBundle(shared) {
|
|
@@ -412,7 +481,8 @@ function rendererBundle(shared) {
|
|
|
412
481
|
// a browser bundle leaves it undefined, so it is rewritten textually.
|
|
413
482
|
global: 'globalThis',
|
|
414
483
|
},
|
|
415
|
-
banner:
|
|
484
|
+
// No banner and no snapshot define: the renderer reads the ONE
|
|
485
|
+
// `dist/build.js` its page template loads ahead of it (#743).
|
|
416
486
|
plugins: [emptyModulesPlugin(RENDERER_EMPTY_MODULES)],
|
|
417
487
|
},
|
|
418
488
|
};
|
|
@@ -443,8 +513,10 @@ function nativeExternals() {
|
|
|
443
513
|
// The list behind the renderer's Node-builtin shim (#737) — pinned by
|
|
444
514
|
// src/test/suites/build/renderer-node-shims.test.js.
|
|
445
515
|
module.exports.RENDERER_EMPTY_MODULES = RENDERER_EMPTY_MODULES;
|
|
516
|
+
module.exports.buildFacts = buildFacts;
|
|
446
517
|
module.exports.composeBuildConfig = composeBuildConfig;
|
|
447
518
|
module.exports.composeBuildJson = composeBuildJson;
|
|
519
|
+
module.exports.composeClientBuildJson = composeClientBuildJson;
|
|
448
520
|
module.exports.rendererEntryKey = rendererEntryKey;
|
|
449
521
|
// The schema-derived bake set and its reader (#627).
|
|
450
522
|
module.exports.BAKED_KEYS = BAKED_KEYS;
|
|
@@ -18,6 +18,11 @@ const Manager = new (require('../../build.js'));
|
|
|
18
18
|
|
|
19
19
|
const logger = Manager.logger('release');
|
|
20
20
|
|
|
21
|
+
// The two warnings electron-publish prints when it uploads nothing: one per
|
|
22
|
+
// release (`gitHubPublisher.getOrCreateRelease` returns null with its reason)
|
|
23
|
+
// and one per file that never left the machine.
|
|
24
|
+
const SKIP_WARNINGS = ['skipped publishing', 'GitHub release not created'];
|
|
25
|
+
|
|
21
26
|
module.exports = function release(done) {
|
|
22
27
|
const projectRoot = process.cwd();
|
|
23
28
|
const config = path.join(require('../../utils/dist-root.js')(projectRoot), 'electron-builder.yml');
|
|
@@ -40,12 +45,25 @@ module.exports = function release(done) {
|
|
|
40
45
|
const relConfig = path.relative(projectRoot, config);
|
|
41
46
|
logger.log(`Releasing via electron-builder (config=${relConfig}, publish=always)...`);
|
|
42
47
|
|
|
43
|
-
|
|
48
|
+
// electron-publish reports a skipped upload through THIS logger and nowhere
|
|
49
|
+
// else, and a release that uploaded nothing must never print a success line,
|
|
50
|
+
// so the logger is a requirement of the task, not a nicety. It comes off
|
|
51
|
+
// electron-builder's OWN export, which re-exports the one `builder-util`
|
|
52
|
+
// instance electron-publish writes to: a bare require of `builder-util` names
|
|
53
|
+
// a package this manifest never declares (#872) and could resolve a SECOND
|
|
54
|
+
// copy whose log nothing ever writes to.
|
|
55
|
+
recordSkippedPublishes(builder.log, () => builder.build({
|
|
44
56
|
config,
|
|
45
57
|
publish: 'always',
|
|
46
|
-
})
|
|
47
|
-
.then((
|
|
48
|
-
const
|
|
58
|
+
}))
|
|
59
|
+
.then(({ result, skips }) => {
|
|
60
|
+
const skipped = skippedUploadError(skips);
|
|
61
|
+
if (skipped) {
|
|
62
|
+
logger.error(skipped.message);
|
|
63
|
+
return done(skipped);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const list = (result || []).map((a) => path.relative(projectRoot, a));
|
|
49
67
|
logger.log(`Released ${list.length} artifact(s):`);
|
|
50
68
|
list.forEach((a) => logger.log(` • ${a}`));
|
|
51
69
|
done();
|
|
@@ -55,3 +73,67 @@ module.exports = function release(done) {
|
|
|
55
73
|
done(e);
|
|
56
74
|
});
|
|
57
75
|
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Run the build with electron-builder's OWN logger watched, and hand back what
|
|
79
|
+
* it published NOTHING for. The promise `build()` returns resolves with the
|
|
80
|
+
* local artifact paths whether or not a byte was uploaded, so the resolve value
|
|
81
|
+
* says nothing about the release: the skip is announced only as a warning on
|
|
82
|
+
* electron-builder's log (the `builder-util` instance it re-exports), carrying
|
|
83
|
+
* the reason and the tag as fields. The wrapper
|
|
84
|
+
* is put back in a `finally`, so a build that throws leaves the logger the way
|
|
85
|
+
* it found it. Exported for tests.
|
|
86
|
+
*
|
|
87
|
+
* @param {{ warn: Function }} log - electron-builder's logger.
|
|
88
|
+
* @param {Function} fn - The build to run.
|
|
89
|
+
* @returns {Promise<{ result: *, skips: object[] }>} What the build resolved with, and every skip it logged.
|
|
90
|
+
*/
|
|
91
|
+
async function recordSkippedPublishes(log, fn) {
|
|
92
|
+
const skips = [];
|
|
93
|
+
const owned = Object.prototype.hasOwnProperty.call(log, 'warn');
|
|
94
|
+
const original = log.warn;
|
|
95
|
+
|
|
96
|
+
log.warn = function (messageOrFields, message) {
|
|
97
|
+
// `warn(message)` and `warn(fields, message)` are both legal on this logger.
|
|
98
|
+
const text = String(message === undefined ? messageOrFields : message);
|
|
99
|
+
const fields = message === undefined ? {} : (messageOrFields || {});
|
|
100
|
+
if (SKIP_WARNINGS.some((warning) => text.includes(warning))) {
|
|
101
|
+
skips.push({ message: text, ...fields });
|
|
102
|
+
}
|
|
103
|
+
return original.call(this, messageOrFields, message);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const result = await fn();
|
|
108
|
+
return { result, skips };
|
|
109
|
+
} finally {
|
|
110
|
+
if (owned) {
|
|
111
|
+
log.warn = original;
|
|
112
|
+
} else {
|
|
113
|
+
delete log.warn;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The verdict on a build's recorded skips: a release that uploaded nothing is a
|
|
120
|
+
* failed release, named by electron-builder's own reason. Exported for tests.
|
|
121
|
+
*
|
|
122
|
+
* @param {object[]} skips - What recordSkippedPublishes collected.
|
|
123
|
+
* @returns {Error|null} The failure, or null when everything published.
|
|
124
|
+
*/
|
|
125
|
+
function skippedUploadError(skips) {
|
|
126
|
+
if (skips.length === 0) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const reason = skips.find((skip) => skip.reason)?.reason || 'electron-builder gave no reason';
|
|
131
|
+
const tag = skips.find((skip) => skip.tag)?.tag || 'the configured tag';
|
|
132
|
+
const files = skips.map((skip) => skip.file).filter(Boolean);
|
|
133
|
+
const what = files.length > 0 ? `${files.length} artifact(s) (${files.join(', ')})` : 'every artifact';
|
|
134
|
+
|
|
135
|
+
return new Error(`Nothing was uploaded: electron-builder skipped publishing ${what} to ${tag} (${reason}). Bump the version in package.json: a release that already exists is never re-uploaded.`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
module.exports.recordSkippedPublishes = recordSkippedPublishes;
|
|
139
|
+
module.exports.skippedUploadError = skippedUploadError;
|
package/dist/gulp/tasks/sass.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// - <omega-package-root>/dist/assets/css → resolves bare 'omega-desktop' to omega-desktop.scss
|
|
8
8
|
// - <omega-package-root>/dist/assets/themes/<active-theme> → resolves bare 'theme' to <theme>/_theme.scss
|
|
9
9
|
// - <omega-package-root>/dist/assets/themes → so themes can reference each other via '../<sibling>'
|
|
10
|
+
// - <output-root>/assets/scss → resolves bare 'brand' to the generated _brand.scss (#912)
|
|
10
11
|
// - <consumer>/src/assets/scss → consumer's own modules
|
|
11
12
|
|
|
12
13
|
const Manager = new (require('../../build.js'));
|
|
@@ -15,6 +16,7 @@ const path = require('path');
|
|
|
15
16
|
const jetpack = require('fs-jetpack');
|
|
16
17
|
const sass = require('sass');
|
|
17
18
|
const { formatBytes } = require('../../vendor/devkit/bundle.js');
|
|
19
|
+
const { composeBrandTokens, renderBrandScss } = require('../../vendor/devkit/brand-tokens.js');
|
|
18
20
|
|
|
19
21
|
const projectRoot = Manager.getRootPath('project');
|
|
20
22
|
const packageRoot = Manager.getRootPath('main');
|
|
@@ -25,10 +27,19 @@ module.exports = function sassTask(done) {
|
|
|
25
27
|
const config = Manager.getConfig() || {};
|
|
26
28
|
const themeId = config.theme?.id || 'classy';
|
|
27
29
|
|
|
30
|
+
// The brand partial ([#912](https://github.com/Omega-JS-Stack/omega/issues/912)):
|
|
31
|
+
// `brand.color` is the ONE accent hex, and it reaches the css through a
|
|
32
|
+
// GENERATED partial rather than a literal a consumer keeps in sync by hand.
|
|
33
|
+
// Written before every compile (no color resolves to the framework default,
|
|
34
|
+
// whose one home is the renderer), so a config edit is the only edit.
|
|
35
|
+
const brandDir = path.join(outputRoot, 'assets', 'scss');
|
|
36
|
+
jetpack.write(path.join(brandDir, '_brand.scss'), renderBrandScss(composeBrandTokens(config.brand?.color)));
|
|
37
|
+
|
|
28
38
|
const loadPaths = [
|
|
29
39
|
path.join(packageRoot, 'dist', 'assets', 'css'), // for `@use 'omega-desktop'`
|
|
30
40
|
path.join(packageRoot, 'dist', 'assets', 'themes', themeId), // for `@use 'theme'`
|
|
31
41
|
path.join(packageRoot, 'dist', 'assets', 'themes'), // for sibling-theme references
|
|
42
|
+
brandDir, // for `@use 'brand'` (generated above)
|
|
32
43
|
path.join(projectRoot, 'src', 'assets', 'scss'), // consumer's own scss tree
|
|
33
44
|
];
|
|
34
45
|
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// The notarization TOOLS: submit, staple, and PROVE, in one place
|
|
2
|
+
// ([#891](https://github.com/Omega-JS-Stack/omega/issues/891)).
|
|
3
|
+
//
|
|
4
|
+
// Proof run one shipped an unsigned, unnotarized mac app on a green build
|
|
5
|
+
// because every rung assumed instead of checking. These three do the opposite:
|
|
6
|
+
// each one reads the tool's own report and throws unless it says the words that
|
|
7
|
+
// mean success. A hook that returns without throwing has PROVED its artifact is
|
|
8
|
+
// notarized, stapled, and accepted by Gatekeeper.
|
|
9
|
+
//
|
|
10
|
+
// Both hooks (afterSign for the .app, artifactBuildCompleted for each .dmg) run
|
|
11
|
+
// every command through the `run` seam a test replaces with a fake `xcrun` /
|
|
12
|
+
// `spctl`; nothing here is global state.
|
|
13
|
+
|
|
14
|
+
const { spawn } = require('child_process');
|
|
15
|
+
|
|
16
|
+
// What Gatekeeper is asked about each artifact: an app is EXECUTED, a disk
|
|
17
|
+
// image is OPENED, and a DMG's verdict must come from its own signature rather
|
|
18
|
+
// than from a quarantine-less filesystem (`--context context:primary-signature`).
|
|
19
|
+
const ASSESS_FLAGS = {
|
|
20
|
+
app: '--type execute',
|
|
21
|
+
dmg: '--type open --context context:primary-signature',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The default command runner: stdout + stderr of a shell command, in the order
|
|
26
|
+
* they arrived, WHATEVER the exit code. These tools write their verdict to the
|
|
27
|
+
* console and exit non-zero when they refuse, so the failure text is the
|
|
28
|
+
* answer, never an exception to lose (a runner that rejects on exit code
|
|
29
|
+
* threw the verdict away with it).
|
|
30
|
+
*
|
|
31
|
+
* @returns {function(string): Promise<string>}
|
|
32
|
+
*/
|
|
33
|
+
function toolRunner() {
|
|
34
|
+
return (command) => new Promise((resolve) => {
|
|
35
|
+
const child = spawn(command, { shell: true, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
36
|
+
let output = '';
|
|
37
|
+
child.stdout.on('data', (data) => { output += data; });
|
|
38
|
+
child.stderr.on('data', (data) => { output += data; });
|
|
39
|
+
child.on('close', () => resolve(output));
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Run a command and hand back its REPORT. A runner that throws (a test's fake)
|
|
45
|
+
* reports its message.
|
|
46
|
+
*
|
|
47
|
+
* @param {function} run - The command runner.
|
|
48
|
+
* @param {string} command - The command.
|
|
49
|
+
* @returns {Promise<string>}
|
|
50
|
+
*/
|
|
51
|
+
async function report(run, command) {
|
|
52
|
+
try {
|
|
53
|
+
return String(await run(command) || '');
|
|
54
|
+
} catch (e) {
|
|
55
|
+
return String(e.message || '');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Run a command and require its report to SAY it worked.
|
|
61
|
+
*
|
|
62
|
+
* @param {object} input
|
|
63
|
+
* @param {function} input.run - The command runner.
|
|
64
|
+
* @param {string} input.command - The command.
|
|
65
|
+
* @param {RegExp} input.expect - What success reads like in the tool's report.
|
|
66
|
+
* @param {string} input.what - What failed, for the thrown message.
|
|
67
|
+
* @returns {Promise<string>} The report.
|
|
68
|
+
* @throws {Error} When the report does not say it worked.
|
|
69
|
+
*/
|
|
70
|
+
async function demand(input) {
|
|
71
|
+
const { run, command, expect, what } = input;
|
|
72
|
+
const output = await report(run, command);
|
|
73
|
+
|
|
74
|
+
if (!expect.test(output)) {
|
|
75
|
+
throw new Error(`[notarize] ${what}: ${output.trim() || 'no output'}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return output;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Submit an artifact to Apple and wait for the verdict. Used for the DMG (the
|
|
83
|
+
* .app rides @electron/notarize's own submit inside the afterSign hook).
|
|
84
|
+
*
|
|
85
|
+
* @param {object} input
|
|
86
|
+
* @param {string} input.filePath - The artifact.
|
|
87
|
+
* @param {object} input.credentials - `{ apiKeyPath, apiKeyId, apiIssuer }`.
|
|
88
|
+
* @param {function} [input.run] - Command runner (test seam).
|
|
89
|
+
* @returns {Promise<string>} notarytool's report.
|
|
90
|
+
*/
|
|
91
|
+
function submit({ filePath, credentials, run = toolRunner() }) {
|
|
92
|
+
const { apiKeyPath, apiKeyId, apiIssuer } = credentials;
|
|
93
|
+
|
|
94
|
+
return demand({
|
|
95
|
+
run,
|
|
96
|
+
command: `xcrun notarytool submit "${filePath}" --key "${apiKeyPath}" --key-id "${apiKeyId}" --issuer "${apiIssuer}" --wait`,
|
|
97
|
+
expect: /status:\s*Accepted/i,
|
|
98
|
+
what: `notarization of ${filePath} was not accepted`,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Staple the notarization ticket to an artifact and PROVE it took: the staple
|
|
104
|
+
* itself, the stapler's own re-read, and Gatekeeper's assessment. A user's Mac
|
|
105
|
+
* checks exactly this, offline, before it will open the thing.
|
|
106
|
+
*
|
|
107
|
+
* @param {object} input
|
|
108
|
+
* @param {string} input.filePath - The .app or .dmg.
|
|
109
|
+
* @param {'app'|'dmg'} input.kind - Which assessment Gatekeeper is asked for.
|
|
110
|
+
* @param {function} [input.run] - Command runner (test seam).
|
|
111
|
+
* @returns {Promise<void>}
|
|
112
|
+
* @throws {Error} When stapling, validation or the assessment fails.
|
|
113
|
+
*/
|
|
114
|
+
async function stapleAndProve({ filePath, kind, run = toolRunner() }) {
|
|
115
|
+
await demand({
|
|
116
|
+
run,
|
|
117
|
+
command: `xcrun stapler staple "${filePath}"`,
|
|
118
|
+
expect: /The staple and validate action worked!/i,
|
|
119
|
+
what: `could not staple the notarization ticket to ${filePath}`,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
await demand({
|
|
123
|
+
run,
|
|
124
|
+
command: `xcrun stapler validate "${filePath}"`,
|
|
125
|
+
expect: /The validate action worked!/i,
|
|
126
|
+
what: `${filePath} carries no valid notarization ticket after stapling`,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
await demand({
|
|
130
|
+
run,
|
|
131
|
+
command: `spctl --assess ${ASSESS_FLAGS[kind]} -vv "${filePath}"`,
|
|
132
|
+
expect: /: accepted/i,
|
|
133
|
+
what: `Gatekeeper refused ${filePath}`,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
module.exports = { submit, stapleAndProve, toolRunner, report, ASSESS_FLAGS };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// The DMG's own notarization, stapling and proof (electron-builder's
|
|
2
|
+
// `artifactBuildCompleted`, [#891](https://github.com/Omega-JS-Stack/omega/issues/891)).
|
|
3
|
+
//
|
|
4
|
+
// The afterSign hook notarizes and staples the .app, and that ticket rides
|
|
5
|
+
// INSIDE the disk image, not on it: a user who downloads the .dmg has Gatekeeper
|
|
6
|
+
// assess the IMAGE first, and an unstapled image is refused on a Mac that is
|
|
7
|
+
// offline. So every .dmg this build produces is submitted with the same App
|
|
8
|
+
// Store Connect key, stapled, and PROVED (stapler validate + spctl --assess).
|
|
9
|
+
//
|
|
10
|
+
// Per artifact, not after all of them: electron-builder awaits this hook and
|
|
11
|
+
// THEN emits the `artifactCreated` its publisher uploads on, whereas
|
|
12
|
+
// `afterAllArtifactBuild` runs after every upload is already queued (run
|
|
13
|
+
// 34738410986 published a signed, notarized image with no staple that way).
|
|
14
|
+
//
|
|
15
|
+
// Non-mac legs produce no .dmg, so this hook is a no-op there by construction:
|
|
16
|
+
// it reads the artifact's extension, never the platform.
|
|
17
|
+
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
const Logger = require('../lib/logger');
|
|
21
|
+
const { submit, stapleAndProve, toolRunner } = require('./lib/notarize-tools.js');
|
|
22
|
+
|
|
23
|
+
const logger = new Logger('notarize-artifacts');
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {object} event - electron-builder's artifactBuildCompleted event
|
|
27
|
+
* (`{ file, target, arch, packager, ... }`).
|
|
28
|
+
* @param {object} [options]
|
|
29
|
+
* @param {function} [options.run] - Command runner (test seam for xcrun/spctl).
|
|
30
|
+
* @returns {Promise<void>}
|
|
31
|
+
*/
|
|
32
|
+
module.exports = async function notarizeArtifacts(event, { run = toolRunner() } = {}) {
|
|
33
|
+
const dmg = event.file;
|
|
34
|
+
|
|
35
|
+
if (path.extname(dmg).toLowerCase() !== '.dmg') {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const apiKey = process.env.APPLE_API_KEY;
|
|
40
|
+
const apiKeyId = process.env.APPLE_API_KEY_ID;
|
|
41
|
+
const apiIssuer = process.env.APPLE_API_ISSUER;
|
|
42
|
+
|
|
43
|
+
if (!apiKey || !apiKeyId || !apiIssuer) {
|
|
44
|
+
throw new Error('[notarize] APPLE_API_KEY, APPLE_API_KEY_ID and APPLE_API_ISSUER are required to notarize the DMG. Run `omega deploy` (its precheck pushes them for CI) or set them in the target .env.');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const apiKeyPath = path.isAbsolute(apiKey) ? apiKey : path.resolve(process.cwd(), apiKey);
|
|
48
|
+
if (!fs.existsSync(apiKeyPath)) {
|
|
49
|
+
throw new Error(`[notarize] APPLE_API_KEY file not found at ${apiKeyPath}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
logger.log(`Notarizing ${path.basename(dmg)} via App Store Connect API key (${apiKeyId})...`);
|
|
53
|
+
await submit({ filePath: dmg, credentials: { apiKeyPath, apiKeyId, apiIssuer }, run });
|
|
54
|
+
|
|
55
|
+
await stapleAndProve({ filePath: dmg, kind: 'dmg', run });
|
|
56
|
+
logger.log(`Stapled and verified: stapler validate + spctl --assess both accept ${path.basename(dmg)}.`);
|
|
57
|
+
};
|