@omega.js/desktop 0.50.0 → 0.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- 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 +176 -42
- 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 +146 -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 +635 -5
- 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 +125 -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 +249 -103
- 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 +425 -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 +505 -75
- package/dist/vendor/devkit/git-auth.js +73 -0
- package/dist/vendor/devkit/git-remote.js +172 -0
- package/dist/vendor/devkit/github-repo.js +290 -0
- package/dist/vendor/devkit/local.js +98 -3
- package/dist/vendor/devkit/lockfile.js +127 -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 +388 -0
- package/dist/vendor/devkit/preludes/index.js +120 -0
- package/dist/vendor/devkit/preludes/origin-heal.js +142 -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 +231 -57
- 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 +29 -19
- package/docs/runner.md +51 -14
- package/docs/shared/brands.md +12 -6
- package/docs/shared/breaking-changes.md +375 -21
- package/docs/shared/config.md +764 -199
- package/docs/shared/deploys.md +217 -91
- package/docs/shared/icons.md +18 -0
- package/docs/shared/local-dev.md +28 -8
- 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 +60 -34
- 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
package/dist/hooks/notarize.js
CHANGED
|
@@ -9,17 +9,32 @@
|
|
|
9
9
|
// APPLE_API_KEY_ID — 10-char Key ID (matches the XXXXXXXXXX in the filename)
|
|
10
10
|
// APPLE_API_ISSUER — issuer UUID from App Store Connect → Users and Access → Keys
|
|
11
11
|
//
|
|
12
|
-
// Skipped
|
|
13
|
-
//
|
|
14
|
-
//
|
|
12
|
+
// Skipped ONLY when building for a non-darwin platform. Every other state is a
|
|
13
|
+
// THROW ([#891](https://github.com/Omega-JS-Stack/omega/issues/891)): a mac
|
|
14
|
+
// build that reaches this hook is a mac build this framework ships, and a build
|
|
15
|
+
// that cannot be signed and notarized must not become a release.
|
|
16
|
+
// - missing APPLE_API_KEY / _ID / _ISSUER: nothing can notarize it
|
|
17
|
+
// - no Developer ID signature (#872): electron-builder SKIPS signing when it
|
|
18
|
+
// finds no identity and leaves an ad-hoc signature behind, which notarytool
|
|
19
|
+
// rejects ("Failed to codesign your application"). That state used to warn
|
|
20
|
+
// and carry on, and the release published an app Gatekeeper refuses on every
|
|
21
|
+
// other Mac.
|
|
22
|
+
//
|
|
23
|
+
// After notarization the ticket is STAPLED and the result PROVED (stapler
|
|
24
|
+
// validate + spctl --assess): the hook returns only when the .app on disk is
|
|
25
|
+
// one a user's Mac will open offline. The DMG gets the same treatment in its
|
|
26
|
+
// own hook (notarize-artifacts.js, artifactBuildCompleted), because the ticket
|
|
27
|
+
// stapled to the app inside an image is not stapled to the image.
|
|
15
28
|
|
|
16
29
|
const path = require('path');
|
|
17
30
|
const fs = require('fs');
|
|
31
|
+
const { execute } = require('node-powertools');
|
|
18
32
|
const Logger = require('../lib/logger');
|
|
33
|
+
const { stapleAndProve, toolRunner } = require('./lib/notarize-tools.js');
|
|
19
34
|
|
|
20
35
|
const logger = new Logger('notarize');
|
|
21
36
|
|
|
22
|
-
module.exports = async function notarize(context) {
|
|
37
|
+
module.exports = async function notarize(context, { run = toolRunner() } = {}) {
|
|
23
38
|
const { electronPlatformName, appOutDir } = context;
|
|
24
39
|
|
|
25
40
|
if (electronPlatformName !== 'darwin') {
|
|
@@ -31,8 +46,7 @@ module.exports = async function notarize(context) {
|
|
|
31
46
|
const appleApiIssuer = process.env.APPLE_API_ISSUER;
|
|
32
47
|
|
|
33
48
|
if (!appleApiKey || !appleApiKeyId || !appleApiIssuer) {
|
|
34
|
-
|
|
35
|
-
return;
|
|
49
|
+
throw new Error('[notarize] APPLE_API_KEY, APPLE_API_KEY_ID and APPLE_API_ISSUER are required to notarize a mac build. Run `omega deploy` (its precheck pushes them for CI) or set them in the target .env.');
|
|
36
50
|
}
|
|
37
51
|
|
|
38
52
|
const apiKeyPath = path.isAbsolute(appleApiKey)
|
|
@@ -43,10 +57,18 @@ module.exports = async function notarize(context) {
|
|
|
43
57
|
throw new Error(`[notarize] APPLE_API_KEY file not found at ${apiKeyPath}`);
|
|
44
58
|
}
|
|
45
59
|
|
|
46
|
-
const { notarize } = require('@electron/notarize');
|
|
47
60
|
const appName = context.packager.appInfo.productFilename;
|
|
48
61
|
const appPath = path.join(appOutDir, `${appName}.app`);
|
|
49
62
|
|
|
63
|
+
// Notarization verifies a Developer ID signature; an ad-hoc one (what
|
|
64
|
+
// electron-builder leaves when it finds no identity) fails the submission
|
|
65
|
+
// instead ([#872](https://github.com/Omega-JS-Stack/omega/issues/872)).
|
|
66
|
+
if (!isDeveloperIdSigned(await readCodesign(appPath, run))) {
|
|
67
|
+
throw new Error(`[notarize] ${appName}.app is NOT Developer ID signed, so it cannot be notarized and every other Mac would refuse it. Set CSC_LINK + CSC_KEY_PASSWORD (or install a Developer ID Application identity) and build again.`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const { notarize } = require('@electron/notarize');
|
|
71
|
+
|
|
50
72
|
logger.log(`Notarizing ${appName} via App Store Connect API key (${appleApiKeyId})...`);
|
|
51
73
|
const start = Date.now();
|
|
52
74
|
|
|
@@ -59,7 +81,12 @@ module.exports = async function notarize(context) {
|
|
|
59
81
|
});
|
|
60
82
|
|
|
61
83
|
const duration = Math.round((Date.now() - start) / 1000);
|
|
62
|
-
logger.log(`
|
|
84
|
+
logger.log(`Notarized in ${duration}s.`);
|
|
85
|
+
|
|
86
|
+
// The ticket, stapled and PROVED: a notarized app whose ticket never landed
|
|
87
|
+
// still fails on a Mac that is offline when the user opens it (#891).
|
|
88
|
+
await stapleAndProve({ filePath: appPath, kind: 'app', run });
|
|
89
|
+
logger.log(`Stapled and verified: stapler validate + spctl --assess both accept ${appName}.app.`);
|
|
63
90
|
|
|
64
91
|
// After @omega.js/desktop's real notarization, optionally invoke the consumer's hooks/notarize/post.js as
|
|
65
92
|
// an extension point. The consumer hook can do post-notarize work (custom stapling,
|
|
@@ -68,3 +95,27 @@ module.exports = async function notarize(context) {
|
|
|
68
95
|
const runConsumerHook = require('../utils/run-consumer-hook.js');
|
|
69
96
|
await runConsumerHook('notarize/post', context);
|
|
70
97
|
};
|
|
98
|
+
|
|
99
|
+
// Pure: does codesign's own report describe a REAL Developer ID signature? An
|
|
100
|
+
// unsigned app has no report at all and an ad-hoc one ("Signature=adhoc",
|
|
101
|
+
// "TeamIdentifier=not set") carries no Authority line, so the one line
|
|
102
|
+
// notarization actually depends on is the one that is read.
|
|
103
|
+
function isDeveloperIdSigned(codesignOutput) {
|
|
104
|
+
return /^Authority=Developer ID Application/m.test(String(codesignOutput || ''));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// codesign writes its report to STDERR and exits non-zero for an unsigned app,
|
|
108
|
+
// so the report is folded into stdout and a failure is read as the answer it is.
|
|
109
|
+
async function readCodesign(appPath, exec) {
|
|
110
|
+
const run = exec || ((cmd) => execute(cmd, { log: false }));
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
return String(await run(`codesign -dv --verbose=2 "${appPath}" 2>&1`) || '');
|
|
114
|
+
} catch (e) {
|
|
115
|
+
return String(e.message || '');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Exported for tests.
|
|
120
|
+
module.exports.isDeveloperIdSigned = isDeveloperIdSigned;
|
|
121
|
+
module.exports.readCodesign = readCodesign;
|
|
@@ -25,6 +25,10 @@
|
|
|
25
25
|
// than "store it in a browser session".
|
|
26
26
|
// 'none' — explicit opt-out; Firebase stays in-memory (pre-1.12 behavior).
|
|
27
27
|
//
|
|
28
|
+
// A TEST RUN is always 'none', whatever the config says: the test lanes never sign a
|
|
29
|
+
// real user in, so the harness never touches the OS keychain. Both lanes, on the one
|
|
30
|
+
// signal isTestRun() reads below ([#907](https://github.com/Omega-JS-Stack/omega/issues/907)).
|
|
31
|
+
//
|
|
28
32
|
// Select via config `omega.authPersistence` ('safeStorage' | 'none' | a custom
|
|
29
33
|
// registered name). Register custom strategies BEFORE manager.initialize():
|
|
30
34
|
// require('@omega.js/desktop/lib/auth-persistence').register('keytar', {...})
|
|
@@ -110,6 +114,15 @@ const noneStrategy = {
|
|
|
110
114
|
async removeItem() {},
|
|
111
115
|
};
|
|
112
116
|
|
|
117
|
+
// Is this process a TEST RUN, in either lane that reaches this lib? The manager
|
|
118
|
+
// answers it, in both: each lane spawns its child with `OMEGA_ENVIRONMENT=testing`
|
|
119
|
+
// and the baked word never writes over it
|
|
120
|
+
// ([#925](https://github.com/Omega-JS-Stack/omega/issues/925)), so a booted
|
|
121
|
+
// production artifact reports the lane it is running in too.
|
|
122
|
+
function isTestRun(manager) {
|
|
123
|
+
return !!manager?.isTesting?.();
|
|
124
|
+
}
|
|
125
|
+
|
|
113
126
|
const authPersistence = {
|
|
114
127
|
_initialized: false,
|
|
115
128
|
_manager: null,
|
|
@@ -140,6 +153,18 @@ const authPersistence = {
|
|
|
140
153
|
// Pick + availability-check the configured strategy. Returns the active strategy
|
|
141
154
|
// or null (→ caller falls back to firebase's in-memory default).
|
|
142
155
|
async resolve(manager) {
|
|
156
|
+
// A TEST RUN is `none`, before any config is read: the test lanes never sign a
|
|
157
|
+
// real user in, so the OS keychain is never asked. This overrides an explicit
|
|
158
|
+
// consumer value too, because the boot layer boots the brand's REAL config through
|
|
159
|
+
// the staged test app, and an unsigned Electron binary asking macOS for a keychain
|
|
160
|
+
// item parks the whole run behind a SecurityAgent prompt
|
|
161
|
+
// ([#907](https://github.com/Omega-JS-Stack/omega/issues/907)).
|
|
162
|
+
if (isTestRun(manager)) {
|
|
163
|
+
logger.log('auth persistence: none (test mode)');
|
|
164
|
+
authPersistence._active = null;
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
143
168
|
const wanted = manager.config?.omega?.authPersistence || 'safeStorage';
|
|
144
169
|
const strategy = authPersistence._strategies[wanted];
|
|
145
170
|
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
const LoggerLite = require('./logger-lite.js');
|
|
20
20
|
const authPersistence = require('./auth-persistence.js');
|
|
21
|
-
const {
|
|
21
|
+
const { requiredPort } = require('../utils/url-helpers.js');
|
|
22
22
|
|
|
23
23
|
const logger = new LoggerLite('client-bridge');
|
|
24
24
|
|
|
@@ -117,8 +117,8 @@ const bridge = {
|
|
|
117
117
|
|
|
118
118
|
// A TESTING run talks to the LOCAL stack, never real auth — the same move
|
|
119
119
|
// getApiUrl() makes when it maps testing to localhost, and the same one
|
|
120
|
-
// @omega.js/extension's background SW makes for its emulator runs. Only
|
|
121
|
-
//
|
|
120
|
+
// @omega.js/extension's background SW makes for its emulator runs. Only an
|
|
121
|
+
// OMEGA_ENVIRONMENT=testing run reaches here; dev and production are untouched.
|
|
122
122
|
if (bridge._manager.isTesting()) {
|
|
123
123
|
const { connectAuthEmulator } = bridge._firebaseModule;
|
|
124
124
|
const port = bridge._authEmulatorPort();
|
|
@@ -129,12 +129,15 @@ const bridge = {
|
|
|
129
129
|
return bridge._firebaseAuth;
|
|
130
130
|
},
|
|
131
131
|
|
|
132
|
-
// The auth emulator's port, on the same
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
// ([#745](https://github.com/Omega-JS-Stack/omega/issues/745)).
|
|
132
|
+
// The auth emulator's port, on the same chain getApiUrl() walks: the
|
|
133
|
+
// resolved-port env channel (N7), then the `dev.ports` map the bundle baked
|
|
134
|
+
// into OMEGA_BUILD_JSON
|
|
135
|
+
// ([#745](https://github.com/Omega-JS-Stack/omega/issues/745)). The classic
|
|
136
|
+
// 9099 used to sit under those two as a last resort; it is gone (#834),
|
|
137
|
+
// because nothing identity-checks what answers on it and a neighbouring
|
|
138
|
+
// project's emulator reads as an auth mystery rather than a port problem.
|
|
136
139
|
_authEmulatorPort() {
|
|
137
|
-
return
|
|
140
|
+
return requiredPort(bridge._manager, 'OMEGA_AUTH_PORT', 'auth');
|
|
138
141
|
},
|
|
139
142
|
|
|
140
143
|
_registerIpc() {
|
package/dist/lib/deep-link.js
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
// Cross-platform plumbing handled internally:
|
|
4
4
|
// - macOS: the OS routes `<scheme>://...` URLs via `app.on('open-url')`. Cold-start opens may queue
|
|
5
5
|
// them before whenReady; we drain them after.
|
|
6
|
-
// - Windows/Linux: deep links arrive as the LAST entry in `process.argv` on cold-start, and
|
|
7
|
-
//
|
|
6
|
+
// - Windows/Linux: deep links arrive as the LAST entry in `process.argv` on cold-start, and on
|
|
7
|
+
// warm-start via `app.on('second-instance')` (read from the `additionalData` argument,
|
|
8
|
+
// which carries the duplicate's real argv; the event's own `argv` is Chromium-processed).
|
|
8
9
|
//
|
|
9
10
|
// Public API on manager.deepLink:
|
|
10
11
|
//
|
|
@@ -23,8 +24,8 @@
|
|
|
23
24
|
// ctx.params // { id: '42' }
|
|
24
25
|
// ctx.query // { ref: 'abc' }
|
|
25
26
|
// ctx.source // 'cold-start' | 'warm-start' | 'manual'
|
|
26
|
-
// ctx.argv // process.argv (cold) or
|
|
27
|
-
// ctx.cwd // process.cwd() or
|
|
27
|
+
// ctx.argv // process.argv (cold) or the duplicate's real argv (warm, via additionalData)
|
|
28
|
+
// ctx.cwd // process.cwd() or the duplicate's cwd (warm)
|
|
28
29
|
// ctx.handled // mutate: set true to suppress fall-through to '*' catch-all
|
|
29
30
|
// });
|
|
30
31
|
//
|
|
@@ -117,8 +118,14 @@ const deepLink = {
|
|
|
117
118
|
// Windows/Linux warm-start: another instance launched (e.g. user double-clicked the
|
|
118
119
|
// app icon while it's already running, or a deep link arrived). The OS gave us the
|
|
119
120
|
// lock, killed the duplicate, and forwarded its argv here.
|
|
120
|
-
app.on('second-instance', (_event, argv, cwd) => {
|
|
121
|
-
|
|
121
|
+
app.on('second-instance', (_event, argv, cwd, additionalData) => {
|
|
122
|
+
// The event's own argv is Chromium-processed (switches first, the values detached
|
|
123
|
+
// at the end), so the duplicate's REAL command line rides along in the object it
|
|
124
|
+
// passed to requestSingleInstanceLock (#921). The fallback covers a duplicate
|
|
125
|
+
// started by a build from before that object existed.
|
|
126
|
+
const realArgv = Array.isArray(additionalData?.argv) ? additionalData.argv : argv;
|
|
127
|
+
const realCwd = additionalData?.cwd || cwd;
|
|
128
|
+
logger.log(`second-instance argv=${JSON.stringify(realArgv)} eventArgv=${JSON.stringify(argv)} cwd=${realCwd}`);
|
|
122
129
|
|
|
123
130
|
// Surface the main window. With hidden-mode apps, the consumer typically created
|
|
124
131
|
// `main` with `show: false` at boot, so it's in the registry but invisible — we
|
|
@@ -134,9 +141,9 @@ const deepLink = {
|
|
|
134
141
|
logger.log('second-instance — no main window in registry; ignoring');
|
|
135
142
|
}
|
|
136
143
|
|
|
137
|
-
const url = deepLink._extractUrlFromArgv(
|
|
144
|
+
const url = deepLink._extractUrlFromArgv(realArgv);
|
|
138
145
|
if (url) {
|
|
139
|
-
deepLink._handle(url, 'warm-start', { argv, cwd });
|
|
146
|
+
deepLink._handle(url, 'warm-start', { argv: realArgv, cwd: realCwd });
|
|
140
147
|
}
|
|
141
148
|
});
|
|
142
149
|
},
|
package/dist/lib/protocol.js
CHANGED
|
@@ -41,7 +41,13 @@ const protocol = {
|
|
|
41
41
|
// initialize() and quits the duplicate WITHOUT running the rest of the boot
|
|
42
42
|
// (two live instances would fight over storage, servers, and — for agents like
|
|
43
43
|
// restart-manager — shared runtime files).
|
|
44
|
-
|
|
44
|
+
//
|
|
45
|
+
// The object rides along because the event's OWN argv is Chromium-processed
|
|
46
|
+
// (switches first, Chromium's own switches spliced in, the values detached at
|
|
47
|
+
// the end), so a brand parsing it reads its flags empty (#921). `additionalData`
|
|
48
|
+
// arrives untouched as the fourth argument of second-instance, so every brand
|
|
49
|
+
// gets the duplicate's real command line, in order, for free.
|
|
50
|
+
protocol._hasLock = app.requestSingleInstanceLock({ argv: process.argv, cwd: process.cwd() });
|
|
45
51
|
if (!protocol._hasLock) {
|
|
46
52
|
logger.warn('single-instance lock lost — another copy of the app is running.');
|
|
47
53
|
} else {
|
|
@@ -94,7 +94,8 @@ function parseFeed(ymlText) {
|
|
|
94
94
|
/**
|
|
95
95
|
* Pick the install artifact for a platform from a parsed feed.
|
|
96
96
|
* darwin — zip, preferring `-<arch>-mac.zip` → `-universal-mac.zip` →
|
|
97
|
-
* `-mac-universal.zip` (the
|
|
97
|
+
* `-mac-universal.zip` (the #620 name) → `-mac.zip` (the name
|
|
98
|
+
* @omega.js/desktop packages the auto-update zip under since #867)
|
|
98
99
|
* win32 — the NSIS setup .exe (run silently with /S; NSIS is what lets RM
|
|
99
100
|
* self-update via electron-updater on Windows)
|
|
100
101
|
* linux — the .AppImage entry
|
|
@@ -111,8 +112,10 @@ function pickArtifact(feed, platform, arch) {
|
|
|
111
112
|
if (platform === 'darwin') {
|
|
112
113
|
picked = names.find((n) => n.endsWith(`-${arch}-mac.zip`))
|
|
113
114
|
|| names.find((n) => n.endsWith('-universal-mac.zip'))
|
|
114
|
-
//
|
|
115
|
-
//
|
|
115
|
+
// A feed published before #867 still carries the arch-shaped name; the
|
|
116
|
+
// last form is what @omega.js/desktop packages the mac auto-update zip
|
|
117
|
+
// under now. This reads the RM app's OWN feed, so every published form
|
|
118
|
+
// stays answerable.
|
|
116
119
|
|| names.find((n) => n.endsWith('-mac-universal.zip'))
|
|
117
120
|
|| names.find((n) => n.endsWith('-mac.zip'));
|
|
118
121
|
} else if (platform === 'win32') {
|
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
//
|
|
4
4
|
// Strategy: in parallel with the signtool invocation, poll the Win32 window
|
|
5
5
|
// list (via automately.getWindows() — a single native call, ~1-5ms) looking
|
|
6
|
-
// for a "Token Logon" window. When detected,
|
|
7
|
-
//
|
|
6
|
+
// for a "Token Logon" window. When detected, type the password + Enter via
|
|
7
|
+
// automately (a maintained nutjs fork), then watch the dialog for a few seconds:
|
|
8
|
+
// a dialog that stays open after typing means the keystrokes did not reach it
|
|
9
|
+
// (a locked console does exactly that, [#864](https://github.com/Omega-JS-Stack/omega/issues/864)),
|
|
10
|
+
// and that is said out loud instead of leaving signtool's silence unexplained.
|
|
8
11
|
//
|
|
9
12
|
// Falls back to a no-op if:
|
|
10
13
|
// - automately isn't installed (optional dep, may have failed to compile)
|
|
@@ -20,30 +23,44 @@ try {
|
|
|
20
23
|
automately = null;
|
|
21
24
|
}
|
|
22
25
|
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
const TIMINGS = {
|
|
27
|
+
pollIntervalMs: 500, // automately.getWindows() is fast — poll twice a second
|
|
28
|
+
pollTimeoutMs: 60000,
|
|
29
|
+
preTypeDelayMs: 2000, // safety wait after detecting dialog before typing
|
|
30
|
+
perCharDelayMs: 60, // small delay between keystrokes — some dialogs drop chars on fast input
|
|
31
|
+
confirmMs: 10000, // how long a typed-into dialog gets to close before that is reported
|
|
32
|
+
};
|
|
27
33
|
|
|
28
34
|
function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }
|
|
29
35
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
// The two things the helper does to the desktop, behind one seam so the loop
|
|
37
|
+
// above them is provable without a desktop.
|
|
38
|
+
function automatelyDriver() {
|
|
39
|
+
return {
|
|
40
|
+
findTokenLogonWindow: async () => {
|
|
41
|
+
const windows = await automately.getWindows();
|
|
42
|
+
for (const w of windows) {
|
|
43
|
+
let title = '';
|
|
44
|
+
try { title = await w.title; } catch (e) { continue; }
|
|
45
|
+
if (title && title.startsWith('Token Logon')) return title;
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
},
|
|
49
|
+
typeText: (text) => automately.keyboard.type(text),
|
|
50
|
+
pressEnter: () => automately.keyboard.type(automately.Key.Enter),
|
|
51
|
+
};
|
|
38
52
|
}
|
|
39
53
|
|
|
40
|
-
function startAutoUnlock({ password, logger }) {
|
|
41
|
-
|
|
54
|
+
function startAutoUnlock({ password, logger, driver, timings, platform }) {
|
|
55
|
+
platform = platform || process.platform;
|
|
56
|
+
if (platform !== 'win32') return { stop: () => {} };
|
|
42
57
|
if (!password) return { stop: () => {} };
|
|
43
|
-
if (!automately) {
|
|
58
|
+
if (!driver && !automately) {
|
|
44
59
|
if (logger) logger.warn('automately not installed — SafeNet password prompt will need manual entry. Run `npm install` in the @omega.js/desktop repo to enable auto-unlock.');
|
|
45
60
|
return { stop: () => {} };
|
|
46
61
|
}
|
|
62
|
+
driver = driver || automatelyDriver();
|
|
63
|
+
timings = { ...TIMINGS, ...(timings || {}) };
|
|
47
64
|
|
|
48
65
|
let stopped = false;
|
|
49
66
|
let typed = false;
|
|
@@ -51,42 +68,63 @@ function startAutoUnlock({ password, logger }) {
|
|
|
51
68
|
|
|
52
69
|
(async function poll() {
|
|
53
70
|
let attempt = 0;
|
|
54
|
-
while (!stopped && !typed && Date.now() - startedAt <
|
|
71
|
+
while (!stopped && !typed && Date.now() - startedAt < timings.pollTimeoutMs) {
|
|
55
72
|
attempt += 1;
|
|
56
73
|
const elapsed = Math.round((Date.now() - startedAt) / 1000);
|
|
57
74
|
const pollStart = Date.now();
|
|
58
75
|
let title = null;
|
|
59
76
|
try {
|
|
60
|
-
title = await findTokenLogonWindow();
|
|
77
|
+
title = await driver.findTokenLogonWindow();
|
|
61
78
|
} catch (e) {
|
|
62
79
|
if (logger) logger.log(`auto-unlock: poll ${attempt} (${elapsed}s) — getWindows error: ${e.message}`);
|
|
63
80
|
}
|
|
64
81
|
const pollMs = Date.now() - pollStart;
|
|
65
82
|
|
|
66
83
|
if (title) {
|
|
67
|
-
if (logger) logger.log(`auto-unlock: SafeNet Token Logon dialog detected (attempt ${attempt}, ${elapsed}s, poll took ${pollMs}ms) — title: "${title}" — waiting ${
|
|
68
|
-
await sleep(
|
|
84
|
+
if (logger) logger.log(`auto-unlock: SafeNet Token Logon dialog detected (attempt ${attempt}, ${elapsed}s, poll took ${pollMs}ms) — title: "${title}" — waiting ${timings.preTypeDelayMs}ms before typing...`);
|
|
85
|
+
await sleep(timings.preTypeDelayMs);
|
|
69
86
|
|
|
70
|
-
if (logger) logger.log(`auto-unlock: typing password (${password.length} chars, ~${
|
|
87
|
+
if (logger) logger.log(`auto-unlock: typing password (${password.length} chars, ~${timings.perCharDelayMs}ms each)...`);
|
|
71
88
|
for (const ch of password) {
|
|
72
89
|
if (stopped) return;
|
|
73
|
-
await
|
|
74
|
-
await sleep(
|
|
90
|
+
await driver.typeText(ch);
|
|
91
|
+
await sleep(timings.perCharDelayMs);
|
|
75
92
|
}
|
|
76
|
-
await
|
|
93
|
+
await driver.pressEnter();
|
|
77
94
|
typed = true;
|
|
78
95
|
if (logger) logger.log('auto-unlock: password typed + Enter pressed.');
|
|
96
|
+
await confirmDialogClosed();
|
|
79
97
|
return;
|
|
80
98
|
}
|
|
81
99
|
|
|
82
100
|
if (logger) logger.log(`auto-unlock: poll ${attempt} (${elapsed}s, took ${pollMs}ms) — no Token Logon window`);
|
|
83
|
-
await sleep(
|
|
101
|
+
await sleep(timings.pollIntervalMs);
|
|
84
102
|
}
|
|
85
|
-
if (!typed && !stopped && logger) logger.warn(`auto-unlock: timed out after ${Math.round(
|
|
103
|
+
if (!typed && !stopped && logger) logger.warn(`auto-unlock: timed out after ${Math.round(timings.pollTimeoutMs / 1000)}s without seeing the Token Logon dialog`);
|
|
86
104
|
})().catch((e) => {
|
|
87
105
|
if (logger) logger.warn(`auto-unlock: poll error: ${e.message}`);
|
|
88
106
|
});
|
|
89
107
|
|
|
108
|
+
// The dialog closing is the only proof the keystrokes landed in it.
|
|
109
|
+
async function confirmDialogClosed() {
|
|
110
|
+
const typedAt = Date.now();
|
|
111
|
+
while (!stopped && Date.now() - typedAt < timings.confirmMs) {
|
|
112
|
+
await sleep(timings.pollIntervalMs);
|
|
113
|
+
let title = null;
|
|
114
|
+
try {
|
|
115
|
+
title = await driver.findTokenLogonWindow();
|
|
116
|
+
} catch (e) {
|
|
117
|
+
if (logger) logger.log(`auto-unlock: confirm poll — getWindows error: ${e.message}`);
|
|
118
|
+
title = 'unknown';
|
|
119
|
+
}
|
|
120
|
+
if (!title) {
|
|
121
|
+
if (logger) logger.log('auto-unlock: Token Logon dialog closed.');
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (!stopped && logger) logger.warn(`auto-unlock: the Token Logon dialog is still open ${Math.round(timings.confirmMs / 1000)}s after typing — the keystrokes did not reach it. Is the console session locked?`);
|
|
126
|
+
}
|
|
127
|
+
|
|
90
128
|
return {
|
|
91
129
|
stop: () => { stopped = true; },
|
|
92
130
|
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Is the box's console session locked? The Token Logon dialog the SafeNet
|
|
2
|
+
// driver raises renders on the interactive desktop, and the auto-unlock helper
|
|
3
|
+
// types the PIN into whatever holds keyboard focus there. Behind a lock screen
|
|
4
|
+
// the dialog still exists (the window poll enumerates it) but every keystroke
|
|
5
|
+
// lands on the lock screen, so signtool waits for a PIN that never arrives
|
|
6
|
+
// ([#864](https://github.com/Omega-JS-Stack/omega/issues/864): a Windows Update
|
|
7
|
+
// restart auto-signed the box in and locked it, and the sign job hung six hours).
|
|
8
|
+
//
|
|
9
|
+
// Windows shows the lock screen through LogonUI.exe, which exits on unlock, so
|
|
10
|
+
// its presence in the process list IS the locked state.
|
|
11
|
+
|
|
12
|
+
const { execute } = require('node-powertools');
|
|
13
|
+
|
|
14
|
+
const TASKLIST_CMD = 'tasklist /fi "IMAGENAME eq LogonUI.exe" /fo csv /nh';
|
|
15
|
+
|
|
16
|
+
// Pure: read tasklist's CSV for the lock-screen process.
|
|
17
|
+
function isConsoleLocked(tasklistOutput) {
|
|
18
|
+
return /"LogonUI\.exe"/i.test(String(tasklistOutput || ''));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Throw, naming the cause and the fix, when the console is locked. Off Windows
|
|
22
|
+
// there is no lock screen to check.
|
|
23
|
+
async function assertConsoleUnlocked(options) {
|
|
24
|
+
options = options || {};
|
|
25
|
+
const platform = options.platform || process.platform;
|
|
26
|
+
const exec = options.exec || ((cmd) => execute(cmd, { log: false }));
|
|
27
|
+
if (platform !== 'win32') return;
|
|
28
|
+
|
|
29
|
+
if (isConsoleLocked(await exec(TASKLIST_CMD))) {
|
|
30
|
+
throw new Error('The console session is locked (LogonUI.exe is running), so the PIN cannot reach the Token Logon dialog. Unlock the box (a Windows Update restart leaves it locked) and re-run.');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = { isConsoleLocked, assertConsoleUnlocked };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Run a shell command with a hard time limit. node-powertools' `execute` is the
|
|
2
|
+
// runner every sign call already uses (shell, piped stdio, rejects with stderr),
|
|
3
|
+
// and it settles only on the child's `close` event. So the limit lives on a
|
|
4
|
+
// timer of its own: when it fires the attempt is rejected THEN, whatever the
|
|
5
|
+
// termination below manages, instead of holding the job until GitHub's six-hour
|
|
6
|
+
// default does it ([#864](https://github.com/Omega-JS-Stack/omega/issues/864)).
|
|
7
|
+
//
|
|
8
|
+
// `execute` spawns through a shell, so the program is the shell's child. On
|
|
9
|
+
// Windows `taskkill /T` takes the tree, the same call `runner.js` makes on a
|
|
10
|
+
// listener. Elsewhere the child is spawned detached, which puts the shell at
|
|
11
|
+
// the head of its own process group, and the negative pid signals the group.
|
|
12
|
+
|
|
13
|
+
const { spawnSync } = require('child_process');
|
|
14
|
+
const { execute } = require('node-powertools');
|
|
15
|
+
|
|
16
|
+
const logger = new (require('../../build.js'))().logger('exec-with-limit');
|
|
17
|
+
|
|
18
|
+
// A kill that failed is exactly the state the operator needs to see, so every
|
|
19
|
+
// failure is said, never swallowed.
|
|
20
|
+
function terminateTree(child, platform) {
|
|
21
|
+
if (platform === 'win32') {
|
|
22
|
+
const k = spawnSync('taskkill', ['/F', '/PID', String(child.pid), '/T'], { encoding: 'utf8' });
|
|
23
|
+
if (k.status !== 0) logger.warn(`taskkill /PID ${child.pid} /T exited ${k.status}: ${String(k.stderr || k.stdout || '').trim()}`);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
process.kill(-child.pid, 'SIGKILL');
|
|
28
|
+
} catch (e) {
|
|
29
|
+
logger.warn(`kill of process group ${child.pid} failed: ${e.message}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Resolves with the command's stdout; rejects with the command's own error, or
|
|
34
|
+
// with one naming the limit when the limit fired first.
|
|
35
|
+
function execWithLimit(cmd, options) {
|
|
36
|
+
options = options || {};
|
|
37
|
+
const { limitMs, label = 'command' } = options;
|
|
38
|
+
if (!(limitMs > 0)) throw new Error(`execWithLimit needs a positive limitMs (got ${limitMs})`);
|
|
39
|
+
|
|
40
|
+
const platform = process.platform;
|
|
41
|
+
let child;
|
|
42
|
+
let timedOut = false;
|
|
43
|
+
|
|
44
|
+
// `detached` on Windows would open a console window for the child; on POSIX
|
|
45
|
+
// it is the process group the kill above needs. `execute` runs the setup
|
|
46
|
+
// callback synchronously, so `child` is set before this returns.
|
|
47
|
+
const run = execute(cmd, { log: false, config: { detached: platform !== 'win32' } }, (c) => { child = c; });
|
|
48
|
+
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
const timer = setTimeout(() => {
|
|
51
|
+
timedOut = true;
|
|
52
|
+
reject(new Error(`${label} produced no verdict within ${Math.round(limitMs / 1000)}s and was terminated`));
|
|
53
|
+
terminateTree(child, platform);
|
|
54
|
+
}, limitMs);
|
|
55
|
+
|
|
56
|
+
run.then(
|
|
57
|
+
(output) => { clearTimeout(timer); resolve(output); },
|
|
58
|
+
(e) => {
|
|
59
|
+
clearTimeout(timer);
|
|
60
|
+
// After the limit fired, the child's own verdict is the termination we
|
|
61
|
+
// asked for; the rejection above already said what happened.
|
|
62
|
+
if (!timedOut) reject(e);
|
|
63
|
+
},
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = { execWithLimit };
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// Retina (@2x) variants are DERIVED. Consumers ship ONE file at the @2x (native) size.
|
|
15
15
|
// @omega.js/desktop downscales it to write the @1x sibling. So `tray.png` is the 32x32 native retina
|
|
16
16
|
// source (consumer-facing input name), and @omega.js/desktop emits both `trayTemplate.png` (16x16)
|
|
17
|
-
// and `trayTemplate@2x.png` (32x32) into `dist/config/icons/
|
|
17
|
+
// and `trayTemplate@2x.png` (32x32) into `dist/config/icons/mac/`. The output name
|
|
18
18
|
// diverges from the input on macOS because the `Template` suffix is a system magic
|
|
19
19
|
// marker that triggers auto-inversion in dark mode. Same retina derivation for
|
|
20
20
|
// `dmg.png` (1080x760 native → emits 540x380 + 1080x760, no name change).
|
|
@@ -28,7 +28,7 @@ const jetpack = require('fs-jetpack');
|
|
|
28
28
|
const sharp = require('sharp');
|
|
29
29
|
|
|
30
30
|
// Slot defs: per platform, a list of { slot, file, outFile, retina } entries.
|
|
31
|
-
// slot = key under resolved[platform] (e.g. 'app' → resolved.
|
|
31
|
+
// slot = key under resolved[platform] (e.g. 'app' → resolved.mac.app)
|
|
32
32
|
// file = INPUT filename — what consumers ship in config/icons/<platform>/ (or global/)
|
|
33
33
|
// outFile = OUTPUT filename written into dist/config/icons/<platform>/.
|
|
34
34
|
// Defaults to `file`. Diverges only when the on-disk runtime name
|
|
@@ -37,8 +37,10 @@ const sharp = require('sharp');
|
|
|
37
37
|
// @omega.js/desktop owns that detail so consumers can just call it `tray.png`).
|
|
38
38
|
// retina = if set, the SOURCE file is treated as @2x (native). @omega.js/desktop downscales it
|
|
39
39
|
// to produce the @1x sibling. Both files are written into dist/.
|
|
40
|
+
// The platform keys are OMEGA's vocabulary (#867): `mac`, never `macos`, both
|
|
41
|
+
// as the consumer's `config/icons/<platform>/` dir and as the dist output dir.
|
|
40
42
|
const SLOTS = {
|
|
41
|
-
|
|
43
|
+
mac: [
|
|
42
44
|
{ slot: 'app', file: 'icon.png' },
|
|
43
45
|
{ slot: 'tray', file: 'tray.png', outFile: 'trayTemplate.png', retina: true },
|
|
44
46
|
{ slot: 'dmg', file: 'dmg.png', retina: true },
|
|
@@ -123,7 +125,7 @@ async function resolveAndCopy({ config, projectRoot, distRoot, emDefaultsRoot })
|
|
|
123
125
|
const distIconsRoot = path.join(distRoot, 'config', 'icons');
|
|
124
126
|
jetpack.dir(distIconsRoot);
|
|
125
127
|
|
|
126
|
-
const resolved = {
|
|
128
|
+
const resolved = { mac: {}, windows: {}, linux: {} };
|
|
127
129
|
|
|
128
130
|
// First pass: resolve each platform/slot. For retina slots, source file is treated as
|
|
129
131
|
// @2x; @omega.js/desktop emits both <slot>.png (downscaled) and <slot>@2x.png (the source) into dist.
|
package/dist/lib/tray.js
CHANGED
|
@@ -166,15 +166,16 @@ const tray = {
|
|
|
166
166
|
//
|
|
167
167
|
// Returns null if nothing is found (caller logs).
|
|
168
168
|
_defaultIconPath() {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
169
|
+
// The icon dirs are OMEGA's vocabulary (mac/windows/linux), so the OS is
|
|
170
|
+
// read through the one translation point (#867). Electron runs nowhere else,
|
|
171
|
+
// so an unknown OS falling back to linux is the safe read.
|
|
172
|
+
const platform = require('../utils/platform.js').desktopPlatform() || 'linux';
|
|
172
173
|
// INPUT name (what consumers ship in config/icons/<platform>/ or global/) is always tray.png.
|
|
173
|
-
// OUTPUT name (what gulp/build-config writes into dist/config/icons/
|
|
174
|
-
// trayTemplate.png on mac
|
|
174
|
+
// OUTPUT name (what gulp/build-config writes into dist/config/icons/mac/) is
|
|
175
|
+
// trayTemplate.png on mac: the `Template` suffix is a macOS magic marker that
|
|
175
176
|
// makes the OS auto-invert the icon in dark mode.
|
|
176
177
|
const inFile = 'tray.png';
|
|
177
|
-
const outFile = platform === '
|
|
178
|
+
const outFile = platform === 'mac' ? 'trayTemplate.png' : 'tray.png';
|
|
178
179
|
const fallbackFile = 'icon.png'; // tray → app icon if absent
|
|
179
180
|
|
|
180
181
|
const projectRoot = require('../utils/app-root.js')();
|
package/dist/main.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// Boot sequence below — each step delegates to a `lib/*.js` module. Stubs today, real impls land in pass 2.
|
|
4
4
|
|
|
5
5
|
const LoggerLite = require('./lib/logger-lite.js');
|
|
6
|
+
const { setEnvironment, ENVIRONMENT_VAR } = require('./vendor/config/environment.js');
|
|
6
7
|
|
|
7
8
|
const storage = require('./lib/storage.js');
|
|
8
9
|
const theme = require('./lib/theme.js');
|
|
@@ -146,6 +147,21 @@ Manager.prototype.initialize = async function (consumerConfig, options) {
|
|
|
146
147
|
self.config = consumerConfig || {};
|
|
147
148
|
self._options = options || {};
|
|
148
149
|
|
|
150
|
+
// The environment's ONE input is the `OMEGA_ENVIRONMENT` variable
|
|
151
|
+
// ([#817](https://github.com/Omega-JS-Stack/omega/issues/817)), and a lane
|
|
152
|
+
// that named one is NEVER overridden here
|
|
153
|
+
// ([#925](https://github.com/Omega-JS-Stack/omega/issues/925)). The baked word
|
|
154
|
+
// is the FALLBACK, for the one case with no parent lane to inherit from: a
|
|
155
|
+
// packaged app, where this is the input every later read answers (the
|
|
156
|
+
// context-free ones, like test-stealth's, included). A dev boot inherits the
|
|
157
|
+
// word from the gulp lane that spawned this electron, and the test lanes spawn
|
|
158
|
+
// their child with `testing` while booting a PRODUCTION artifact, so writing
|
|
159
|
+
// the baked word over theirs turned every isTesting() gate in a booted app
|
|
160
|
+
// silently off.
|
|
161
|
+
if (!process.env[ENVIRONMENT_VAR] && self.config.environment) {
|
|
162
|
+
setEnvironment(self.config.environment);
|
|
163
|
+
}
|
|
164
|
+
|
|
149
165
|
{
|
|
150
166
|
const fs = require('fs');
|
|
151
167
|
const _path = require('path');
|