@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,199 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The signing-tree READERS, in one place.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* a
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
4
|
+
* There is no delivery here any more
|
|
5
|
+
* ([#891](https://github.com/Omega-JS-Stack/omega/issues/891)): nothing COPIES
|
|
6
|
+
* signing material into a target. The tree is read IN PLACE (company tier
|
|
7
|
+
* first, `signing-tree.js`, #892), and the paths into it are derived once, at
|
|
8
|
+
* the desktop env load, by `signing-env.js`. The copy step and its rule table
|
|
9
|
+
* had two homes for one fact (which file a key points at) and produced the
|
|
10
|
+
* dispersed duplicates a build could silently sign with; the manager's disperse
|
|
11
|
+
* `certs` operation went with it.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* so a delete-on-missing pass would eat hand-placed signing material on the
|
|
18
|
-
* next build.
|
|
19
|
-
*
|
|
20
|
-
* This module DELIVERS and reports; it logs nothing and classifies nothing.
|
|
21
|
-
* Which misses are tolerable (`optional`) is on the rules, which callers read
|
|
22
|
-
* through resolveCertRules — the manager's disperse writer turns them into its
|
|
23
|
-
* per-rule lines, a desktop build into whatever its own voice is.
|
|
24
|
-
*/
|
|
25
|
-
const { join, dirname } = require('node:path');
|
|
26
|
-
const jetpack = require('fs-jetpack');
|
|
27
|
-
|
|
28
|
-
// Paths: source relative to .omega/certificates/apple/, dest relative to the
|
|
29
|
-
// target dir. `{env.VAR}` and `{brand.id}` placeholders supported on both sides.
|
|
30
|
-
const CERT_FILE_MAP = {
|
|
31
|
-
desktop: [
|
|
32
|
-
// macOS code signing — Developer ID Application (.p12 with private key)
|
|
33
|
-
{
|
|
34
|
-
source: 'certificates/DEVELOPER_ID_APPLICATION_G2.p12',
|
|
35
|
-
dest: 'config/certs/developer-id-application.p12',
|
|
36
|
-
},
|
|
37
|
-
// macOS installer signing (only used if shipping a .pkg installer)
|
|
38
|
-
{
|
|
39
|
-
source: 'certificates/DEVELOPER_ID_INSTALLER_G2.p12',
|
|
40
|
-
dest: 'config/certs/developer-id-installer.p12',
|
|
41
|
-
optional: true,
|
|
42
|
-
},
|
|
43
|
-
// Notarization API key — App Store Connect .p8
|
|
44
|
-
{
|
|
45
|
-
source: 'AuthKey_{env.APPLE_API_KEY_ID}.p8',
|
|
46
|
-
dest: 'config/certs/AuthKey_{env.APPLE_API_KEY_ID}.p8',
|
|
47
|
-
},
|
|
48
|
-
// Per-brand provisioning profile (only present if cert generation produced one)
|
|
49
|
-
{
|
|
50
|
-
source: 'profiles/{brand.id}/DEVELOPER_ID_APPLICATION_G2/MACOS.mobileprovision',
|
|
51
|
-
dest: 'config/certs/{brand.id}.provisionprofile',
|
|
52
|
-
optional: true,
|
|
53
|
-
},
|
|
54
|
-
],
|
|
55
|
-
mobile: [
|
|
56
|
-
// iOS App Store distribution — used by xcodebuild
|
|
57
|
-
{
|
|
58
|
-
source: 'certificates/IOS_DISTRIBUTION.p12',
|
|
59
|
-
dest: 'build/certs/ios-distribution.p12',
|
|
60
|
-
},
|
|
61
|
-
// Notarization / API access from CI
|
|
62
|
-
{
|
|
63
|
-
source: 'AuthKey_{env.APPLE_API_KEY_ID}.p8',
|
|
64
|
-
dest: 'build/certs/AuthKey_{env.APPLE_API_KEY_ID}.p8',
|
|
65
|
-
},
|
|
66
|
-
// Per-brand iOS provisioning profile
|
|
67
|
-
{
|
|
68
|
-
source: 'profiles/{brand.id}/IOS_DISTRIBUTION/IOS.mobileprovision',
|
|
69
|
-
dest: 'build/certs/{brand.id}.mobileprovision',
|
|
70
|
-
optional: true,
|
|
71
|
-
},
|
|
72
|
-
],
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Resolve `{env.VAR}` and `{brand.key}` placeholders. Returns null when an
|
|
77
|
-
* env placeholder is unset — the rule can't produce a real path, and a
|
|
78
|
-
* literal `AuthKey_.p8` miss would only confuse.
|
|
79
|
-
*/
|
|
80
|
-
function resolveTemplate(template, brand) {
|
|
81
|
-
let missing = false;
|
|
82
|
-
|
|
83
|
-
const resolved = template.replace(/\{([^}]+)\}/g, (match, path) => {
|
|
84
|
-
const [source, ...keyParts] = path.split('.');
|
|
85
|
-
const key = keyParts.join('.');
|
|
86
|
-
if (source === 'env') {
|
|
87
|
-
const value = process.env[key] || '';
|
|
88
|
-
if (!value) missing = true;
|
|
89
|
-
return value;
|
|
90
|
-
}
|
|
91
|
-
if (source === 'brand') return brand?.[key] || '';
|
|
92
|
-
return match;
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
return missing ? null : resolved;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Ensure the certs dir ignores everything it holds — signing material must
|
|
100
|
-
* never land in git, regardless of whether the framework's own .gitignore
|
|
101
|
-
* exists yet.
|
|
102
|
-
*/
|
|
103
|
-
function ensureCertsIgnore(destDir) {
|
|
104
|
-
const ignorePath = join(destDir, '.gitignore');
|
|
105
|
-
if (!jetpack.exists(ignorePath)) {
|
|
106
|
-
jetpack.write(ignorePath, '*\n!.gitignore\n');
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* The signing tree a source root holds — the ONE home of that path.
|
|
112
|
-
*
|
|
113
|
-
* @param {string} sourceRoot - Company root (company-managed brands) or brand root.
|
|
114
|
-
* @returns {string} Absolute path to .omega/certificates/apple/.
|
|
115
|
-
*/
|
|
116
|
-
function certsSourceDir(sourceRoot) {
|
|
117
|
-
return join(sourceRoot, '.omega', 'certificates', 'apple');
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* A target's signing rules with their placeholders resolved.
|
|
122
|
-
*
|
|
123
|
-
* @param {object} input
|
|
124
|
-
* @param {string} input.target - Target type ('desktop'/'mobile'); anything
|
|
125
|
-
* else has no signing rules and returns [].
|
|
126
|
-
* @param {object} [input.brand] - Brand config block (`{brand.id}` resolves from it).
|
|
127
|
-
* @returns {Array<{ source, dest, optional, sourceRel, destRel }>} `sourceRel`/
|
|
128
|
-
* `destRel` are null when an env placeholder is unset; `dest` is the template.
|
|
129
|
-
*/
|
|
130
|
-
function resolveCertRules({ target, brand = null }) {
|
|
131
|
-
return (CERT_FILE_MAP[target] || []).map((rule) => {
|
|
132
|
-
const sourceRel = resolveTemplate(rule.source, brand);
|
|
133
|
-
const destRel = sourceRel === null ? null : resolveTemplate(rule.dest, brand);
|
|
134
|
-
|
|
135
|
-
return {
|
|
136
|
-
source: rule.source,
|
|
137
|
-
dest: rule.dest,
|
|
138
|
-
optional: !!rule.optional,
|
|
139
|
-
sourceRel,
|
|
140
|
-
destRel,
|
|
141
|
-
};
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Copy a target's signing artifacts out of the signing tree.
|
|
147
|
-
*
|
|
148
|
-
* @param {object} input
|
|
149
|
-
* @param {string} input.sourceRoot - Company root or brand root holding .omega/certificates/apple/.
|
|
150
|
-
* @param {string} input.targetDir - The target's own directory (destinations resolve inside it).
|
|
151
|
-
* @param {string} input.target - Target type ('desktop'/'mobile').
|
|
152
|
-
* @param {object} [input.brand] - Brand config block, for `{brand.id}` rules.
|
|
153
|
-
* @param {boolean} [input.dryRun] - Plan only: `copied` says what WOULD be
|
|
154
|
-
* written, nothing is written.
|
|
155
|
-
* @returns {{ copied: string[], current: string[], missing: string[] }}
|
|
156
|
-
* Target-relative paths — copied (written this pass), current (already
|
|
157
|
-
* byte-identical), missing (no source, or an unresolved placeholder: that rule
|
|
158
|
-
* reports its dest TEMPLATE).
|
|
13
|
+
* What remains is what every surface asks the tree: WHERE it is (signingTree)
|
|
14
|
+
* and WHEN what it holds expires (certificateExpiry), so the certificates walk,
|
|
15
|
+
* `validate-certs` and a build all answer with the same date and the same three
|
|
16
|
+
* rungs.
|
|
159
17
|
*/
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const copied = [];
|
|
163
|
-
const current = [];
|
|
164
|
-
const missing = [];
|
|
165
|
-
|
|
166
|
-
for (const rule of resolveCertRules({ target, brand })) {
|
|
167
|
-
// An unset env placeholder (e.g. APPLE_API_KEY_ID) means the rule has no
|
|
168
|
-
// real path to resolve to — it reports its template, which names the gap
|
|
169
|
-
if (rule.destRel === null) {
|
|
170
|
-
missing.push(rule.dest);
|
|
171
|
-
continue;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const destPath = join(targetDir, rule.destRel);
|
|
175
|
-
const sourcePath = join(appleDir, rule.sourceRel);
|
|
176
|
-
if (!jetpack.exists(sourcePath)) {
|
|
177
|
-
missing.push(rule.destRel);
|
|
178
|
-
continue;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const sourceBytes = jetpack.read(sourcePath, 'buffer');
|
|
182
|
-
const destBytes = jetpack.exists(destPath) ? jetpack.read(destPath, 'buffer') : null;
|
|
183
|
-
|
|
184
|
-
if (destBytes && sourceBytes.equals(destBytes)) {
|
|
185
|
-
current.push(rule.destRel);
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
copied.push(rule.destRel);
|
|
190
|
-
if (dryRun) continue;
|
|
191
|
-
|
|
192
|
-
jetpack.copy(sourcePath, destPath, { overwrite: true });
|
|
193
|
-
ensureCertsIgnore(dirname(destPath));
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return { copied, current, missing };
|
|
197
|
-
}
|
|
18
|
+
const { signingTree, certsSourceDir } = require('./signing-tree.js');
|
|
19
|
+
const { certificateExpiry, EXPIRY_WARN_DAYS } = require('./certificate-expiry.js');
|
|
198
20
|
|
|
199
|
-
module.exports = {
|
|
21
|
+
module.exports = { signingTree, certsSourceDir, certificateExpiry, EXPIRY_WARN_DAYS };
|
|
@@ -29,11 +29,43 @@
|
|
|
29
29
|
const path = require('path');
|
|
30
30
|
const jetpack = require('fs-jetpack');
|
|
31
31
|
|
|
32
|
+
// The ONE pinned Socket Firewall action. A workflow installs the firewall
|
|
33
|
+
// through Socket's own action rather than through npm ([#871](https://github.com/Omega-JS-Stack/omega/issues/871)):
|
|
34
|
+
// the action downloads the binary with the job's token, so a hosted runner's
|
|
35
|
+
// shared anonymous GitHub API quota never fails the step, and it caches it.
|
|
36
|
+
// Pinning a new version is this one edit, and every brand picks it up on its
|
|
37
|
+
// next verb ([#872](https://github.com/Omega-JS-Stack/omega/issues/872)).
|
|
38
|
+
const FIREWALL_ACTION = 'SocketDev/action@v1.3.2';
|
|
39
|
+
|
|
40
|
+
// The token every framework template carries where that step belongs, on its
|
|
41
|
+
// own line, indented as the list item it becomes.
|
|
42
|
+
const FIREWALL_TOKEN = '{{ installFirewall }}';
|
|
43
|
+
|
|
44
|
+
// The action step's `id`, so the Windows shim below can read the binary path
|
|
45
|
+
// the action reports (`firewall-path-binary`) instead of guessing at it.
|
|
46
|
+
const FIREWALL_STEP_ID = 'omega-firewall';
|
|
47
|
+
|
|
48
|
+
// The token every framework template carries at the END of its install command,
|
|
49
|
+
// and what a COMPOSED job renders it into ([#898](https://github.com/Omega-JS-Stack/omega/issues/898)).
|
|
50
|
+
// At a brand root npm installs every target's workspace, so the desktop job on
|
|
51
|
+
// Electron's node installed the backend workspace whose engines.node is the
|
|
52
|
+
// Firebase runtime pin and warned EBADENGINE on every leg. Each job installs
|
|
53
|
+
// only the workspace it belongs to instead.
|
|
54
|
+
//
|
|
55
|
+
// `.`, not the target's path: npm resolves `--workspace` against the CWD, and
|
|
56
|
+
// every composed run step already carries `working-directory: targets/<name>`,
|
|
57
|
+
// so `--workspace targets/desktop` from inside targets/desktop looks for
|
|
58
|
+
// targets/desktop/targets/desktop and refuses with "No workspaces found". The
|
|
59
|
+
// dot is the target's real directory whatever the brand named it ([#886](https://github.com/Omega-JS-Stack/omega/issues/886)),
|
|
60
|
+
// and no template ever spells a path.
|
|
61
|
+
const INSTALL_WORKSPACE_TOKEN = '{{ installWorkspace }}';
|
|
62
|
+
const INSTALL_WORKSPACE_FLAG = '--workspace .';
|
|
63
|
+
|
|
32
64
|
/**
|
|
33
65
|
* Action inputs that name a path inside the checkout, per action. `uses:` steps
|
|
34
66
|
* ignore `working-directory:` entirely (it is a `run:` key), so a composed
|
|
35
|
-
* workflow has to scope these itself
|
|
36
|
-
*
|
|
67
|
+
* workflow has to scope these itself: an unscoped cache key hashes nothing and
|
|
68
|
+
* an unscoped artifact upload collects an empty repo-root path.
|
|
37
69
|
*
|
|
38
70
|
* Named per action on purpose: a blanket "any input called path" rule would
|
|
39
71
|
* rewrite inputs that mean something else — `actions/checkout`'s `path:` names
|
|
@@ -44,9 +76,72 @@ const SCOPED_ACTION_INPUTS = {
|
|
|
44
76
|
'actions/cache': ['path'],
|
|
45
77
|
'actions/upload-artifact': ['path'],
|
|
46
78
|
'actions/download-artifact': ['path'],
|
|
47
|
-
'peaceiris/actions-gh-pages': ['publish_dir'],
|
|
48
79
|
};
|
|
49
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Render every `{{ installFirewall }}` token into the pinned Socket Firewall
|
|
83
|
+
* step, at the indentation the token itself stands at.
|
|
84
|
+
*
|
|
85
|
+
* The step is written ONCE, here, instead of four times across the framework
|
|
86
|
+
* templates: the action and its version live in `FIREWALL_ACTION` above, so a
|
|
87
|
+
* version bump is one edit rather than a sweep of every template that happens
|
|
88
|
+
* to install the firewall ([#872](https://github.com/Omega-JS-Stack/omega/issues/872)).
|
|
89
|
+
* A template carrying no token is returned untouched.
|
|
90
|
+
*
|
|
91
|
+
* @param {string} contents - The workflow template.
|
|
92
|
+
* @returns {string} The same text with every token replaced.
|
|
93
|
+
*/
|
|
94
|
+
function renderInstallFirewall(contents) {
|
|
95
|
+
return contents.replace(new RegExp(`^([ \\t]*)${escapeRegExp(FIREWALL_TOKEN)}[ \\t]*$`, 'gm'), (full, indent) => [
|
|
96
|
+
`${indent}# The firewall binary comes through Socket's own action (#871): it downloads`,
|
|
97
|
+
`${indent}# with the job's token, so the shared anonymous GitHub API quota of a hosted`,
|
|
98
|
+
`${indent}# runner never fails the step, and it caches the binary between runs.`,
|
|
99
|
+
`${indent}- name: Install Socket Firewall`,
|
|
100
|
+
`${indent} id: ${FIREWALL_STEP_ID}`,
|
|
101
|
+
`${indent} uses: ${FIREWALL_ACTION}`,
|
|
102
|
+
`${indent} with:`,
|
|
103
|
+
`${indent} mode: firewall-free`,
|
|
104
|
+
// The action caches the Windows binary under the extension-less name `sfw`
|
|
105
|
+
// and puts its directory on PATH. cmd.exe cannot execute a file with no
|
|
106
|
+
// extension, and the desktop build forces `shell: cmd` on Windows, so every
|
|
107
|
+
// windows leg died on `'sfw' is not recognized` (#872). One copy next to the
|
|
108
|
+
// original, named `sfw.exe`, and cmd finds it through the same PATH entry.
|
|
109
|
+
// Rendered on every template: the `if` makes it a no-op everywhere else.
|
|
110
|
+
// Under cmd, not bash: a self-hosted Windows box (the EV signer) has no bash
|
|
111
|
+
// on the runner's PATH, and cmd is the one shell every Windows runner has.
|
|
112
|
+
`${indent}- name: Expose Socket Firewall to cmd (Windows)`,
|
|
113
|
+
`${indent} if: runner.os == 'Windows'`,
|
|
114
|
+
`${indent} shell: cmd`,
|
|
115
|
+
`${indent} run: copy "\${{ steps.${FIREWALL_STEP_ID}.outputs.firewall-path-binary }}" "\${{ steps.${FIREWALL_STEP_ID}.outputs.firewall-path-binary }}.exe"`,
|
|
116
|
+
].join('\n'));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Render every `{{ installWorkspace }}` token: into the workspace flag on a
|
|
121
|
+
* COMPOSED brand-root copy, into nothing at all on a standalone one.
|
|
122
|
+
*
|
|
123
|
+
* Both lanes go through this one function, like the firewall step above, so the
|
|
124
|
+
* flag lives in ONE place instead of four templates. A standalone target IS its
|
|
125
|
+
* own repo root and declares no workspaces, so the flag there would refuse the
|
|
126
|
+
* install by name ("No workspaces found: --workspace=."); the token and the
|
|
127
|
+
* space before it are dropped and the command is the plain install it always was.
|
|
128
|
+
*
|
|
129
|
+
* @param {string} contents - The workflow template.
|
|
130
|
+
* @param {object} [options]
|
|
131
|
+
* @param {boolean} [options.composed] - Composing into a brand root.
|
|
132
|
+
* @returns {string} The same text with every token replaced.
|
|
133
|
+
*/
|
|
134
|
+
function renderInstallWorkspace(contents, options) {
|
|
135
|
+
const composed = !!(options && options.composed);
|
|
136
|
+
|
|
137
|
+
return contents.replace(new RegExp(`[ \\t]*${escapeRegExp(INSTALL_WORKSPACE_TOKEN)}`, 'g'), composed ? ` ${INSTALL_WORKSPACE_FLAG}` : '');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** The literal, safe to drop into a RegExp. */
|
|
141
|
+
function escapeRegExp(value) {
|
|
142
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
143
|
+
}
|
|
144
|
+
|
|
50
145
|
/**
|
|
51
146
|
* Compose one framework workflow template for a target inside a brand monorepo.
|
|
52
147
|
* Pure: text in, text out.
|
|
@@ -60,13 +155,20 @@ function composeWorkflow(contents, options) {
|
|
|
60
155
|
const targetPath = options.targetPath;
|
|
61
156
|
const targetName = options.targetName;
|
|
62
157
|
|
|
63
|
-
|
|
158
|
+
// Before anything else, so the scoping pass below sees the rendered step for
|
|
159
|
+
// what it is: a `uses:` step, which is never scoped to the target dir.
|
|
160
|
+
let composed = renderInstallFirewall(contents);
|
|
161
|
+
|
|
162
|
+
// This job installs ONLY the target it belongs to (#898): the composed copy
|
|
163
|
+
// is the one that runs inside a brand, where every other target's workspace
|
|
164
|
+
// (and its own engines.node pin) sits beside it.
|
|
165
|
+
composed = renderInstallWorkspace(composed, { composed: true });
|
|
64
166
|
|
|
65
167
|
// Display name carries the target — two targets' runs are told apart in the
|
|
66
168
|
// Actions list, where only the workflow name shows.
|
|
67
169
|
composed = composed.replace(/^name:[ \t]*(.*)$/m, (full, value) => `name: ${value.trim()} (${targetPath})`);
|
|
68
170
|
|
|
69
|
-
// Per-target concurrency: `${{ github.ref }}` alone means the
|
|
171
|
+
// Per-target concurrency: `${{ github.ref }}` alone means the web target's deploy
|
|
70
172
|
// cancels the extension's.
|
|
71
173
|
composed = composed.replace(/^(concurrency:\n(?:[ \t]+.*\n)*?[ \t]+group:[ \t]*)(.*)$/m, (full, prefix, value) => `${prefix}${targetName}-${value.trim()}`);
|
|
72
174
|
|
|
@@ -149,7 +251,7 @@ function composeTargetWorkflows(options) {
|
|
|
149
251
|
* @param {object} options
|
|
150
252
|
* @param {string} options.brandRoot - The brand (repo) root
|
|
151
253
|
* @param {string[]} options.liveTargets - The target DIR names the brand's config
|
|
152
|
-
* still enables (`
|
|
254
|
+
* still enables (`web`, `extension`, …). A composed file owned by anything
|
|
153
255
|
* else is a leftover.
|
|
154
256
|
* @param {boolean} [options.dryRun] - Report what would go, delete nothing
|
|
155
257
|
* @param {object} [options.logger] - Logger with `log`/`warn` (defaults to console)
|
|
@@ -201,7 +303,21 @@ function composedWorkflowName(options) {
|
|
|
201
303
|
if (!options.brandRoot || path.resolve(options.brandRoot) === path.resolve(options.targetDir)) {
|
|
202
304
|
return options.workflow;
|
|
203
305
|
}
|
|
204
|
-
return
|
|
306
|
+
return composedWorkflowNameFor(path.basename(options.targetDir), options.workflow);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* The same name from the target's NAME, for a caller that holds the name and no
|
|
311
|
+
* path at all (the backend's deploy dispatch reads the target out of a request,
|
|
312
|
+
* never off a disk it does not have). The path-taking form above is this one
|
|
313
|
+
* plus a basename, so the two can never spell the rule differently.
|
|
314
|
+
*
|
|
315
|
+
* @param {string} name - The target name (e.g. web)
|
|
316
|
+
* @param {string} workflow - The framework's workflow file name (e.g. build.yml)
|
|
317
|
+
* @returns {string} the composed workflow file name
|
|
318
|
+
*/
|
|
319
|
+
function composedWorkflowNameFor(name, workflow) {
|
|
320
|
+
return `${name}-${workflow}`;
|
|
205
321
|
}
|
|
206
322
|
|
|
207
323
|
// Scope every `run:` step that executes AFTER its job's checkout to the target dir.
|
|
@@ -517,4 +633,4 @@ function header(targetPath) {
|
|
|
517
633
|
].join('\n');
|
|
518
634
|
}
|
|
519
635
|
|
|
520
|
-
module.exports = { composeWorkflow, composeTargetWorkflows, composedWorkflowName, reconcileComposedWorkflows };
|
|
636
|
+
module.exports = { composeWorkflow, composeTargetWorkflows, composedWorkflowName, composedWorkflowNameFor, reconcileComposedWorkflows, renderInstallFirewall, renderInstallWorkspace, FIREWALL_ACTION, FIREWALL_STEP_ID, FIREWALL_TOKEN, INSTALL_WORKSPACE_TOKEN, INSTALL_WORKSPACE_FLAG };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// createCliRouter(config) — the shared OMEGA framework CLI dispatcher.
|
|
2
2
|
//
|
|
3
|
-
// Every framework bin parses argv (
|
|
3
|
+
// Every framework bin parses argv (src/argv.js) and hands the result to a Main class
|
|
4
4
|
// whose process() resolves a command name and runs commands/<name>.js from the
|
|
5
5
|
// framework's dist. That router was copy-pasted per framework (UJM/BXM/EM,
|
|
6
6
|
// drift-identical); this is the single implementation. @omega.js/backend's CLI is a different
|
|
@@ -46,8 +46,8 @@ function createCliRouter(config) {
|
|
|
46
46
|
// `--help`/`-h` route to the built-in help FIRST — before positionals, so
|
|
47
47
|
// `omega deploy --help` prints help instead of RUNNING deploy, and never
|
|
48
48
|
// the default command (the old fallthrough ran setup/manage on a help
|
|
49
|
-
// request).
|
|
50
|
-
//
|
|
49
|
+
// request). No parse may claim `--help` for itself: the flag has to reach
|
|
50
|
+
// the router.
|
|
51
51
|
if (options.help || options.h) {
|
|
52
52
|
return 'help';
|
|
53
53
|
}
|
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
// template object render `{{ key.path }}` tokens with this data
|
|
16
16
|
// (tolerant: unknown keys survive verbatim, so GitHub
|
|
17
17
|
// Actions' `${{ secrets.X }}` passes through)
|
|
18
|
-
// merge bool JSON5 defaults-merge with the existing file
|
|
18
|
+
// merge bool JSON5 defaults-merge with the existing file, written
|
|
19
|
+
// through @omega.js/config's comment-preserving editor
|
|
20
|
+
// (only the differing keys change; comments stay)
|
|
19
21
|
// mergeLines bool OMEGA marker-section line merge (.env/.gitignore/AGENTS.md)
|
|
20
22
|
// retire bool brand-context per-target doc retirement: NEVER scaffold
|
|
21
23
|
// the file; an existing framework-owned-only copy is
|
|
@@ -38,6 +40,7 @@
|
|
|
38
40
|
|
|
39
41
|
const path = require('path');
|
|
40
42
|
const fs = require('fs');
|
|
43
|
+
const { isDeepStrictEqual } = require('node:util');
|
|
41
44
|
const jetpack = require('fs-jetpack');
|
|
42
45
|
const { minimatch } = require('minimatch');
|
|
43
46
|
const { mergeLineBasedFiles, hasSectionMarkers, getCustomSection } = require('./merge-line-files');
|
|
@@ -206,9 +209,7 @@ function applyDefaults(config) {
|
|
|
206
209
|
|
|
207
210
|
if (options.merge && exists) {
|
|
208
211
|
try {
|
|
209
|
-
|
|
210
|
-
const merged = mergeJson5Defaults(JSON5.parse(jetpack.read(destination)), JSON5.parse(contents));
|
|
211
|
-
contents = JSON5.stringify(merged, null, 2);
|
|
212
|
+
contents = mergeJson5Text(jetpack.read(destination), contents);
|
|
212
213
|
didMerge = true;
|
|
213
214
|
} catch (error) {
|
|
214
215
|
logger.error(`Error merging config file ${finalRelative}: ${error.message}`);
|
|
@@ -335,7 +336,10 @@ function renderTemplate(content, context) {
|
|
|
335
336
|
|
|
336
337
|
// JSON5 defaults merge (BXM's mergeConfigs): framework defaults provide shape and
|
|
337
338
|
// order; the consumer's values win unless they're the literal string 'default';
|
|
338
|
-
// consumer-only keys survive at every nesting level
|
|
339
|
+
// consumer-only keys survive at every nesting level, EXCEPT one holding the
|
|
340
|
+
// 'default' sentinel, which is the framework's own unset marker and drops (#926).
|
|
341
|
+
// A JSON5 file carries no framework-block marker, so that sentinel is the only
|
|
342
|
+
// signal telling a key the framework retired from a key the consumer added.
|
|
339
343
|
function mergeJson5Defaults(existingConfig, newConfig) {
|
|
340
344
|
const merged = { ...newConfig };
|
|
341
345
|
|
|
@@ -356,9 +360,11 @@ function mergeJson5Defaults(existingConfig, newConfig) {
|
|
|
356
360
|
}
|
|
357
361
|
}
|
|
358
362
|
|
|
359
|
-
// Consumer-only keys survive at every level
|
|
363
|
+
// Consumer-only keys survive at every level, except a leftover 'default'
|
|
364
|
+
// sentinel: that is the framework's unset marker for a key it no longer
|
|
365
|
+
// declares, so it leaves with the key (#926).
|
|
360
366
|
for (const key in source) {
|
|
361
|
-
if (!Object.prototype.hasOwnProperty.call(newDefaults, key)) {
|
|
367
|
+
if (!Object.prototype.hasOwnProperty.call(newDefaults, key) && source[key] !== 'default') {
|
|
362
368
|
target[key] = source[key];
|
|
363
369
|
}
|
|
364
370
|
}
|
|
@@ -369,4 +375,60 @@ function mergeJson5Defaults(existingConfig, newConfig) {
|
|
|
369
375
|
return merged;
|
|
370
376
|
}
|
|
371
377
|
|
|
378
|
+
// The merge WRITE: the consumer's JSON5 is hand-edited, so the merged object is
|
|
379
|
+
// never re-stringified over it (that stripped every comment and the trailing
|
|
380
|
+
// newline, #928). The merge result is diffed against the file on disk and the
|
|
381
|
+
// differences go through @omega.js/config's comment-preserving editor, so only
|
|
382
|
+
// the changed value spans move, every other byte survives, and an unchanged merge
|
|
383
|
+
// returns the source untouched so the engine's idempotency check skips the write.
|
|
384
|
+
// Output always ends with exactly one newline.
|
|
385
|
+
function mergeJson5Text(existingText, defaultsText) {
|
|
386
|
+
const JSON5 = require('json5');
|
|
387
|
+
const { applyConfigEdits, applyConfigRemovals } = require('../config/index.js');
|
|
388
|
+
|
|
389
|
+
const existing = JSON5.parse(existingText);
|
|
390
|
+
const merged = mergeJson5Defaults(existing, JSON5.parse(defaultsText));
|
|
391
|
+
|
|
392
|
+
const edits = {};
|
|
393
|
+
const removals = [];
|
|
394
|
+
diffConfigPaths(existing, merged, '', edits, removals);
|
|
395
|
+
|
|
396
|
+
// Nothing to change is not a write: the file goes back byte for byte, final
|
|
397
|
+
// newline included or not, so a rerun never touches it.
|
|
398
|
+
if (removals.length === 0 && Object.keys(edits).length === 0) {
|
|
399
|
+
return existingText;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const written = applyConfigEdits(applyConfigRemovals(existingText, removals), edits);
|
|
403
|
+
|
|
404
|
+
return written.replace(/\n*$/, '\n');
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Diff the merged object against the one on disk into editor operations: dot-paths
|
|
408
|
+
// to set (a leaf whose value differs, plus a key or whole branch the file lacks)
|
|
409
|
+
// and dot-paths to delete (a key the merge dropped, #926's `default` sentinel).
|
|
410
|
+
// Two objects at the same path recurse; anything else (primitive, array, or a
|
|
411
|
+
// branch landing where the file has no object) is one value the editor writes whole.
|
|
412
|
+
function diffConfigPaths(existing, merged, prefix, edits, removals) {
|
|
413
|
+
const isObject = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
414
|
+
|
|
415
|
+
for (const key of Object.keys(merged)) {
|
|
416
|
+
const keyPath = prefix ? `${prefix}.${key}` : key;
|
|
417
|
+
const nextValue = merged[key];
|
|
418
|
+
const priorValue = existing[key];
|
|
419
|
+
|
|
420
|
+
if (isObject(nextValue) && isObject(priorValue)) {
|
|
421
|
+
diffConfigPaths(priorValue, nextValue, keyPath, edits, removals);
|
|
422
|
+
} else if (!isDeepStrictEqual(priorValue, nextValue)) {
|
|
423
|
+
edits[keyPath] = nextValue;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
for (const key of Object.keys(existing)) {
|
|
428
|
+
if (!Object.prototype.hasOwnProperty.call(merged, key)) {
|
|
429
|
+
removals.push(prefix ? `${prefix}.${key}` : key);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
372
434
|
module.exports = { applyDefaults, mergeJson5Defaults, renderTemplate };
|