@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
|
@@ -13,47 +13,94 @@ const path = require('node:path');
|
|
|
13
13
|
const { execSync, execFileSync } = require('node:child_process');
|
|
14
14
|
|
|
15
15
|
const { findBrandRoot, discoverTargets, frameworkPackagesOf } = require('./local.js');
|
|
16
|
+
const { stageLocalPackages, STAGING_DIR } = require('./pack-local.js');
|
|
17
|
+
const { assertBrandLockfile } = require('./brand-version.js');
|
|
18
|
+
const {
|
|
19
|
+
pushSnapshot,
|
|
20
|
+
pushWorkflowFiles,
|
|
21
|
+
composedWorkflowFiles,
|
|
22
|
+
defaultBranchOf,
|
|
23
|
+
healDefaultBranch,
|
|
24
|
+
waitForWorkflow,
|
|
25
|
+
waitForRef,
|
|
26
|
+
ghHeaders,
|
|
27
|
+
shortSha,
|
|
28
|
+
} = require('./deploy-snapshot.js');
|
|
29
|
+
// The remote-url parse this module published before the boot prelude needed it
|
|
30
|
+
// too (#890): it lives in the remote boundary now, re-exported here unchanged.
|
|
31
|
+
// The origin gate (#934) lives there too, beside the one origin read.
|
|
32
|
+
const { parseRemoteUrl, assertOriginMatches } = require('./git-remote.js');
|
|
16
33
|
|
|
17
34
|
const API_BASE = 'https://api.github.com';
|
|
18
35
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
function parseRemoteUrl(url) {
|
|
27
|
-
const match = (url || '').trim().match(/github\.com[/:]([^/]+)\/([^/\s]+?)(?:\.git)?$/);
|
|
28
|
-
if (!match) return null;
|
|
29
|
-
return { owner: match[1], repo: match[2] };
|
|
30
|
-
}
|
|
36
|
+
// The ONE branch a deploy ever builds from
|
|
37
|
+
// ([#915](https://github.com/Omega-JS-Stack/omega/issues/915)): every brand's
|
|
38
|
+
// snapshot lands here, nested or not, linked or not, and CI only ever runs what
|
|
39
|
+
// this branch holds. A brand's real history never carries packed tarballs, and
|
|
40
|
+
// whoever starts a deploy (a laptop, the admin publish) puts the files it needs
|
|
41
|
+
// on the same branch. Exported, so the backend's admin publish never types it.
|
|
42
|
+
const SNAPSHOT_REF = 'omega-deploy';
|
|
31
43
|
|
|
32
44
|
/**
|
|
33
|
-
* The repo a CI dispatch addresses: the
|
|
34
|
-
* ([#799](https://github.com/Omega-JS-Stack/omega/issues/799)
|
|
45
|
+
* The repo a CI dispatch addresses: the brand's SOURCE monorepo, from its
|
|
46
|
+
* config ([#799](https://github.com/Omega-JS-Stack/omega/issues/799),
|
|
47
|
+
* [#883](https://github.com/Omega-JS-Stack/omega/issues/883)). Every framework's
|
|
35
48
|
* deploy verb asks this instead of `resolveRepo` above, because a git remote
|
|
36
49
|
* answers the repo the working tree sits in: inside a brand monorepo nested in
|
|
37
50
|
* another repo (a brand inside the framework monorepo, a target checked out
|
|
38
51
|
* under someone else's tree) that is the ENCLOSING repo, so the dispatch went to
|
|
39
|
-
* a workflow that was never there.
|
|
52
|
+
* a workflow that was never there. The workflows live on the SOURCE repo in
|
|
53
|
+
* every case, whatever a target publishes to.
|
|
40
54
|
*
|
|
41
55
|
* Half an address addresses nothing, so it throws rather than POST to
|
|
42
56
|
* `undefined/<name>`.
|
|
43
57
|
*
|
|
44
58
|
* @param {object} config - Composed omega config for the target being deployed.
|
|
45
|
-
* @returns {{ owner: string, repo: string }} the
|
|
59
|
+
* @returns {{ owner: string, repo: string }} the source repo's owner and bare name
|
|
46
60
|
* @throws {Error} when the config names no repo
|
|
47
61
|
*/
|
|
48
62
|
function dispatchRepo(config) {
|
|
49
|
-
const {
|
|
50
|
-
const
|
|
63
|
+
const { sourceRepo } = require('../config/index.js');
|
|
64
|
+
const source = sourceRepo(config);
|
|
51
65
|
|
|
52
|
-
if (!
|
|
53
|
-
throw new Error('Could not determine the brand repo to dispatch on. Set repo.
|
|
66
|
+
if (!source) {
|
|
67
|
+
throw new Error('Could not determine the brand repo to dispatch on. Set repo.org (and brand.id) in config/omega.json5: the source repo is <brand.id>-omega under that org.');
|
|
54
68
|
}
|
|
55
69
|
|
|
56
|
-
return { owner, repo: name };
|
|
70
|
+
return { owner: source.owner, repo: source.name };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The whole CI dispatch ADDRESS for a target: the repo above, plus the workflow
|
|
75
|
+
* file the target's scaffold actually wrote
|
|
76
|
+
* ([#847](https://github.com/Omega-JS-Stack/omega/issues/847)). Inside a brand
|
|
77
|
+
* monorepo the target's CI lives in the BRAND ROOT's workflows dir under a
|
|
78
|
+
* per-target name (`desktop-build.yml`, #265), and standalone it keeps the
|
|
79
|
+
* framework's own name, so every deploy verb composed `dispatchRepo` with
|
|
80
|
+
* `composedWorkflowName` by hand and desktop kept a local helper its siblings
|
|
81
|
+
* lacked. One helper now, called by all four.
|
|
82
|
+
*
|
|
83
|
+
* @param {object} options
|
|
84
|
+
* @param {string} options.projectRoot - The target dir being deployed.
|
|
85
|
+
* @param {object} options.config - Composed omega config for that target.
|
|
86
|
+
* @param {string} options.workflow - The framework's workflow file name (e.g. publish.yml).
|
|
87
|
+
* @returns {{ owner: string, repo: string, workflow: string }} the repo and the workflow to dispatch
|
|
88
|
+
* @throws {Error} when the config names no repo
|
|
89
|
+
*/
|
|
90
|
+
function dispatchTarget({ projectRoot, config, workflow }) {
|
|
91
|
+
const { composedWorkflowName } = require('./ci-workflows.js');
|
|
92
|
+
const { resolveSeedMode } = require('../config/index.js');
|
|
93
|
+
const { owner, repo } = dispatchRepo(config);
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
owner,
|
|
97
|
+
repo,
|
|
98
|
+
workflow: composedWorkflowName({
|
|
99
|
+
targetDir: projectRoot,
|
|
100
|
+
brandRoot: resolveSeedMode(projectRoot).brandRoot,
|
|
101
|
+
workflow,
|
|
102
|
+
}),
|
|
103
|
+
};
|
|
57
104
|
}
|
|
58
105
|
|
|
59
106
|
/**
|
|
@@ -139,12 +186,7 @@ async function dispatchWorkflow(plan, options = {}) {
|
|
|
139
186
|
const fetchFn = options.fetchFn || fetch;
|
|
140
187
|
const response = await fetchFn(plan.url, {
|
|
141
188
|
method: plan.method,
|
|
142
|
-
headers:
|
|
143
|
-
Accept: 'application/vnd.github+json',
|
|
144
|
-
Authorization: `Bearer ${options.token}`,
|
|
145
|
-
'X-GitHub-Api-Version': '2022-11-28',
|
|
146
|
-
'User-Agent': 'omega-deploy',
|
|
147
|
-
},
|
|
189
|
+
headers: ghHeaders(options.token),
|
|
148
190
|
body: JSON.stringify(plan.body),
|
|
149
191
|
});
|
|
150
192
|
|
|
@@ -156,51 +198,374 @@ async function dispatchWorkflow(plan, options = {}) {
|
|
|
156
198
|
}
|
|
157
199
|
|
|
158
200
|
/**
|
|
159
|
-
* The
|
|
160
|
-
*
|
|
201
|
+
* The LANE half of the two lines a dispatch prints, one spelling for all four
|
|
202
|
+
* verbs: `<mode> lane, ref <ref>`, plus the snapshot sha when the brand root
|
|
203
|
+
* pushed one for this run ([#901](https://github.com/Omega-JS-Stack/omega/issues/901)),
|
|
204
|
+
* so a fan-out's log shows every target dispatching the SAME snapshot.
|
|
205
|
+
*
|
|
206
|
+
* @param {{ mode: string, ref: string }} lane - The resolved lane.
|
|
207
|
+
* @param {string} [snapshot] - The sha the run's snapshot sits at.
|
|
208
|
+
* @returns {string} the parenthesised label, without its parentheses
|
|
209
|
+
*/
|
|
210
|
+
function laneLabel(lane, snapshot) {
|
|
211
|
+
return `${lane.mode} lane, ref ${lane.ref}${snapshot ? ` @ ${shortSha(snapshot)}` : ''}`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// The things a lane DOES, in one injectable seam (the `steps` shape
|
|
215
|
+
// `deploy-precheck` uses): the tests drive the order without a push, a pack or
|
|
216
|
+
// a network call, and the defaults are the real modules.
|
|
217
|
+
const LANE_STEPS = {
|
|
218
|
+
origin: (options) => assertOriginMatches({ dir: options.root }),
|
|
219
|
+
lockfile: assertBrandLockfile,
|
|
220
|
+
defaultBranch: defaultBranchOf,
|
|
221
|
+
heal: healDefaultBranch,
|
|
222
|
+
behind: assertNotBehind,
|
|
223
|
+
workflows: pushWorkflowFiles,
|
|
224
|
+
stage: stageLocalPackages,
|
|
225
|
+
push: pushSnapshot,
|
|
226
|
+
waitRef: waitForRef,
|
|
227
|
+
wait: waitForWorkflow,
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* The origin gate ([#934](https://github.com/Omega-JS-Stack/omega/issues/934)):
|
|
232
|
+
* a brand whose `origin` names another repo than the source repo its config
|
|
233
|
+
* derives refuses, because every lane acts on the DERIVED repo (the snapshot
|
|
234
|
+
* push, the workflow compose, the dispatch). It runs on every lane, the
|
|
235
|
+
* dispatch-only one included: a brand outside git has no origin to disagree,
|
|
236
|
+
* which the gate answers as nothing to compare. Read-only, so the dry run runs
|
|
237
|
+
* it too.
|
|
238
|
+
*
|
|
239
|
+
* @param {object} lane - The resolved lane.
|
|
240
|
+
* @param {object} steps - The lane steps (`origin` is the gate).
|
|
241
|
+
* @returns {void}
|
|
242
|
+
* @throws {Error} The drift line, naming `repo.org`.
|
|
243
|
+
*/
|
|
244
|
+
function checkLaneOrigin(lane, steps) {
|
|
245
|
+
steps.origin({ root: lane.brandRoot });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* The lockfile gate, where the lane ships the brand's OWN lock
|
|
250
|
+
* ([#938](https://github.com/Omega-JS-Stack/omega/issues/938)): a registry
|
|
251
|
+
* (unlinked) snapshot. A linked lane regenerates its lock in the pack step, and
|
|
252
|
+
* a lane with no snapshot ships nothing, so neither has a lock of its own to
|
|
253
|
+
* check. Read-only, so the dry run runs it too.
|
|
254
|
+
*
|
|
255
|
+
* @param {object} lane - The resolved lane.
|
|
256
|
+
* @param {object} steps - The lane steps (`lockfile` is the gate).
|
|
257
|
+
* @returns {void}
|
|
258
|
+
* @throws {Error} The gate's refusal, naming `omega i live`.
|
|
259
|
+
*/
|
|
260
|
+
function checkLaneLockfile(lane, steps) {
|
|
261
|
+
if (lane.mode === 'snapshot' && !lane.linked) {
|
|
262
|
+
steps.lockfile({ root: lane.brandRoot });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* The DELIVERY half of a lane: how this brand's code reaches GitHub before the
|
|
268
|
+
* dispatch that runs it. ONE implementation for its two callers
|
|
269
|
+
* ([#901](https://github.com/Omega-JS-Stack/omega/issues/901)): a target's own
|
|
270
|
+
* `deployViaDispatch` below, and the brand-root fan-out, which runs it once for
|
|
271
|
+
* the whole run rather than letting every target of a concurrent group perform
|
|
272
|
+
* it at the same brand root at the same moment.
|
|
273
|
+
*
|
|
274
|
+
* What the lane delivers, in order
|
|
275
|
+
* ([#915](https://github.com/Omega-JS-Stack/omega/issues/915)):
|
|
276
|
+
* 0. on EVERY lane, the ORIGIN gate
|
|
277
|
+
* ([#934](https://github.com/Omega-JS-Stack/omega/issues/934)): a checkout
|
|
278
|
+
* whose `origin` is not the derived source repo refuses before this lane
|
|
279
|
+
* writes to that repo; then, on a registry (unlinked) lane, the LOCKFILE gate
|
|
280
|
+
* ([#938](https://github.com/Omega-JS-Stack/omega/issues/938)): the runner's
|
|
281
|
+
* `npm ci` installs the brand's lock as pushed, so one that disagrees with
|
|
282
|
+
* the manifests refuses here, before a branch is healed or a file written;
|
|
283
|
+
* 1. the DEFAULT branch read once, and healed when published output has taken
|
|
284
|
+
* it over ([#922](https://github.com/Omega-JS-Stack/omega/issues/922)): a
|
|
285
|
+
* `gh-pages` default moves back to `main` here, before step 2 writes a
|
|
286
|
+
* workflow file to a branch the next web deploy force-pushes over;
|
|
287
|
+
* 2. the BEHIND check, for a brand that is its own repo's toplevel: a checkout
|
|
288
|
+
* behind the remote default branch refuses, because the force-push below
|
|
289
|
+
* would overwrite the deploy branch with a tree that lacks what the admin
|
|
290
|
+
* publish committed there;
|
|
291
|
+
* 3. the composed WORKFLOW FILES to the default branch, and only when they
|
|
292
|
+
* differ, because GitHub registers a workflow from that branch alone. This
|
|
293
|
+
* is the one write a deploy makes outside the deploy branch: the local tree
|
|
294
|
+
* is never committed, and what the developer has not committed stays theirs;
|
|
295
|
+
* 4. the linked packages PACKED (tarballs are files in the tree, so they ride
|
|
296
|
+
* the snapshot like anything else untracked and not ignored), the brand
|
|
297
|
+
* folder force-pushed to the deploy branch, and the tree put back.
|
|
298
|
+
*
|
|
299
|
+
* A brand outside git (`repo: false`) delivers nothing at all: there is no
|
|
300
|
+
* index to snapshot from, so the dispatch runs on whatever the branch holds.
|
|
301
|
+
*
|
|
302
|
+
* @param {object} options - Options.
|
|
303
|
+
* @param {object} options.lane - The lane `resolveDeployLane` returned.
|
|
304
|
+
* @param {string} [options.owner] - The snapshot's repo owner (snapshot lane).
|
|
305
|
+
* @param {string} [options.repo] - The snapshot's repo name (snapshot lane).
|
|
306
|
+
* @param {string} [options.ref] - The ref the snapshot lands on (default: the lane's).
|
|
307
|
+
* @param {string} [options.token] - GitHub token for the snapshot push.
|
|
308
|
+
* @param {string} [options.message] - Snapshot commit message.
|
|
309
|
+
* @param {object} [options.logger] - Logger with `log` (silent when omitted).
|
|
310
|
+
* @param {function} [options.fetchFn] - Injectable fetch (the ref wait, tests).
|
|
311
|
+
* @param {object} [options.steps] - Lane step overrides (tests).
|
|
312
|
+
* @returns {Promise<{ sha: string|null }>} the sha the snapshot landed at, and
|
|
313
|
+
* null on a lane that pushes none
|
|
314
|
+
*/
|
|
315
|
+
async function deliverLane(options) {
|
|
316
|
+
const lane = options.lane;
|
|
317
|
+
const logger = options.logger;
|
|
318
|
+
const steps = { ...LANE_STEPS, ...(options.steps || {}) };
|
|
319
|
+
const ref = options.ref || lane.ref;
|
|
320
|
+
|
|
321
|
+
checkLaneOrigin(lane, steps);
|
|
322
|
+
|
|
323
|
+
if (lane.mode !== 'snapshot') {
|
|
324
|
+
return { sha: null };
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
checkLaneLockfile(lane, steps);
|
|
328
|
+
|
|
329
|
+
// Read ONCE, used twice: the branch a behind checkout is measured against,
|
|
330
|
+
// and the branch the composed workflows are pushed to. Null is a repo GitHub
|
|
331
|
+
// does not have yet, and the snapshot push below is where that says itself.
|
|
332
|
+
// A dry run returns before this whole function, so it reads nothing here and
|
|
333
|
+
// heals nothing below ([#922](https://github.com/Omega-JS-Stack/omega/issues/922)).
|
|
334
|
+
const current = await steps.defaultBranch({
|
|
335
|
+
owner: options.owner,
|
|
336
|
+
repo: options.repo,
|
|
337
|
+
token: options.token,
|
|
338
|
+
fetchFn: options.fetchFn,
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// The gh-pages HEAL, on the name just read and before anything is written to
|
|
342
|
+
// it (#922): published output is where the next web deploy's force-push wipes
|
|
343
|
+
// whatever the compose step put there, so the default branch moves back to
|
|
344
|
+
// main first and the rest of the lane uses the healed name.
|
|
345
|
+
const branch = await steps.heal({
|
|
346
|
+
owner: options.owner,
|
|
347
|
+
repo: options.repo,
|
|
348
|
+
current,
|
|
349
|
+
token: options.token,
|
|
350
|
+
fetchFn: options.fetchFn,
|
|
351
|
+
logger,
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
if (branch) {
|
|
355
|
+
// A NESTED brand's git toplevel is somebody else's repo (this monorepo, for
|
|
356
|
+
// the playground), so there is no checkout of the brand's own repo to be
|
|
357
|
+
// behind: nothing to compare, and nothing to refuse.
|
|
358
|
+
if (!lane.nested) {
|
|
359
|
+
steps.behind({ cwd: lane.brandRoot, branch, logger });
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
await steps.workflows({
|
|
363
|
+
brandRoot: lane.brandRoot,
|
|
364
|
+
owner: options.owner,
|
|
365
|
+
repo: options.repo,
|
|
366
|
+
branch,
|
|
367
|
+
token: options.token,
|
|
368
|
+
fetchFn: options.fetchFn,
|
|
369
|
+
logger,
|
|
370
|
+
});
|
|
371
|
+
} else if (logger) {
|
|
372
|
+
logger.log(`${options.owner}/${options.repo} answered no default branch (a repo nobody has created yet): no workflow compare, and the snapshot push speaks for itself.`);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (logger) {
|
|
376
|
+
logger.log(`Snapshotting ${lane.brandRoot} to ${options.owner}/${options.repo}#${ref}${lane.linked ? ' (packing its linked packages)' : ''}`);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const staging = lane.linked
|
|
380
|
+
? await steps.stage({ dir: lane.brandRoot, log: logger ? (line) => logger.log(line) : undefined })
|
|
381
|
+
: null;
|
|
382
|
+
|
|
383
|
+
let sha;
|
|
384
|
+
|
|
385
|
+
try {
|
|
386
|
+
sha = steps.push({
|
|
387
|
+
brandRoot: lane.brandRoot,
|
|
388
|
+
owner: options.owner,
|
|
389
|
+
repo: options.repo,
|
|
390
|
+
ref,
|
|
391
|
+
token: options.token,
|
|
392
|
+
message: options.message,
|
|
393
|
+
// What the stage just wrote has to reach the runner, so the push
|
|
394
|
+
// refuses a tree whose ignore rules would drop it.
|
|
395
|
+
require: staging ? [STAGING_DIR, 'package-lock.json'] : [],
|
|
396
|
+
});
|
|
397
|
+
} finally {
|
|
398
|
+
// ALWAYS, and as early as possible: the push carried the staged shape, so
|
|
399
|
+
// the developer's tree goes back before anything else can fail.
|
|
400
|
+
if (staging) {
|
|
401
|
+
await staging.restore();
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Then the ref itself (#902): GitHub resolves the branch on its side, so a
|
|
406
|
+
// dispatch sent in the same second as the force-push can still resolve to the
|
|
407
|
+
// PREVIOUS commit and build a tree this deploy never pushed. Both callers get
|
|
408
|
+
// the wait from here, so every target of a run dispatches a settled ref.
|
|
409
|
+
await steps.waitRef({
|
|
410
|
+
owner: options.owner,
|
|
411
|
+
repo: options.repo,
|
|
412
|
+
ref,
|
|
413
|
+
sha,
|
|
414
|
+
token: options.token,
|
|
415
|
+
fetchFn: options.fetchFn,
|
|
416
|
+
logger,
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
return { sha };
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* The one deploy path: resolve repo + token, resolve the LANE, carry the code
|
|
424
|
+
* to GitHub the way that lane says, then dispatch. A dryRun returns the plan
|
|
425
|
+
* untouched instead.
|
|
426
|
+
*
|
|
427
|
+
* The lane runs only when the caller names a `dir` (a local tree). A
|
|
428
|
+
* server-side dispatch (the admin post route) has no working tree at all and
|
|
429
|
+
* addresses code GitHub already has, so it dispatches and nothing else.
|
|
430
|
+
*
|
|
161
431
|
* @param {object} options
|
|
162
432
|
* @param {string} options.workflow - workflow file name
|
|
433
|
+
* @param {string} [options.dir] - any dir inside the brand (turns the lane on)
|
|
163
434
|
* @param {string} [options.cwd] - repo dir for remote resolution
|
|
164
435
|
* @param {string} [options.owner] - explicit owner (skips git resolution)
|
|
165
436
|
* @param {string} [options.repo] - explicit repo (skips git resolution)
|
|
166
|
-
* @param {string} [options.ref] - branch (default main)
|
|
437
|
+
* @param {string} [options.ref] - branch (default: the lane's, else main)
|
|
167
438
|
* @param {object} [options.inputs] - workflow inputs
|
|
168
439
|
* @param {boolean} [options.dryRun] - build the plan but never send
|
|
440
|
+
* @param {string} [options.message] - snapshot commit message
|
|
441
|
+
* @param {string} [options.snapshot] - the sha a snapshot the CALLER already
|
|
442
|
+
* pushed to this lane's ref sits at (the brand-root fan-out's one push,
|
|
443
|
+
* [#901](https://github.com/Omega-JS-Stack/omega/issues/901)): the lane's
|
|
444
|
+
* delivery is done, so this run waits and dispatches against it
|
|
445
|
+
* @param {object} [options.logger] - logger with `log` (silent when omitted)
|
|
169
446
|
* @param {object} [options.env] - env map for token resolution
|
|
170
447
|
* @param {function} [options.fetchFn] - injectable fetch
|
|
171
448
|
* @param {function} [options.execFn] - injectable exec
|
|
172
|
-
* @
|
|
449
|
+
* @param {object} [options.steps] - lane step overrides (tests)
|
|
450
|
+
* @returns {Promise<{ plan: object, dispatched: boolean, lane: object|null, sha: string|null }>}
|
|
451
|
+
* the plan, whether it was sent, the lane, and the sha this deploy dispatched
|
|
452
|
+
* against (the caller's `snapshot`, else the one the lane pushed, else null on
|
|
453
|
+
* a lane that pushes none, [#902](https://github.com/Omega-JS-Stack/omega/issues/902))
|
|
173
454
|
*/
|
|
174
455
|
async function deployViaDispatch(options) {
|
|
175
456
|
const target = options.owner && options.repo
|
|
176
457
|
? { owner: options.owner, repo: options.repo }
|
|
177
458
|
: resolveRepo({ cwd: options.cwd, execFn: options.execFn });
|
|
178
459
|
|
|
460
|
+
const lane = options.dir ? resolveDeployLane({ dir: options.dir, execFn: options.execFn }) : null;
|
|
461
|
+
const logger = options.logger;
|
|
462
|
+
// What this deploy dispatches against, for the line it prints and for the
|
|
463
|
+
// follower's head check (#902): the caller's snapshot when it has one, else
|
|
464
|
+
// whatever the lane's own delivery pushed below.
|
|
465
|
+
let sha = options.snapshot || null;
|
|
466
|
+
|
|
467
|
+
// A sha only means something on the lane that pushes one (#901). A brand
|
|
468
|
+
// with no repo snapshots nothing, so a sha claiming to be on its ref is a
|
|
469
|
+
// ref this run never wrote: loud, at the boundary, before a step has run.
|
|
470
|
+
if (options.snapshot && !(lane && lane.mode === 'snapshot')) {
|
|
471
|
+
throw new Error(`--snapshot=${options.snapshot} is the snapshot lane's alone: this deploy takes the ${lane ? `${lane.mode} lane` : 'dispatch-only lane'}, which pushes no snapshot to skip.`);
|
|
472
|
+
}
|
|
473
|
+
|
|
179
474
|
const plan = buildDispatch({
|
|
180
475
|
owner: target.owner,
|
|
181
476
|
repo: target.repo,
|
|
182
477
|
workflow: options.workflow,
|
|
183
|
-
ref: options.ref,
|
|
478
|
+
ref: options.ref || (lane ? lane.ref : null),
|
|
184
479
|
inputs: options.inputs,
|
|
185
480
|
});
|
|
186
481
|
|
|
482
|
+
const steps = { ...LANE_STEPS, ...(options.steps || {}) };
|
|
483
|
+
|
|
187
484
|
if (options.dryRun) {
|
|
188
|
-
|
|
485
|
+
// The origin and lockfile gates only read, so a dry run refuses what the
|
|
486
|
+
// real run would (#934, #938), as the version gate already does.
|
|
487
|
+
if (lane) {
|
|
488
|
+
checkLaneOrigin(lane, steps);
|
|
489
|
+
checkLaneLockfile(lane, steps);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// The plan IS the dry run, the lane included: what would happen, with
|
|
493
|
+
// nothing written to git and the network untouched (the gates above only
|
|
494
|
+
// read). The workflow half of it is read off
|
|
495
|
+
// DISK (the scaffold has already composed this run's files), so the preview
|
|
496
|
+
// names them and the branch they would go to without asking GitHub which of
|
|
497
|
+
// them differ (#915).
|
|
498
|
+
if (logger && lane && lane.mode === 'snapshot') {
|
|
499
|
+
const names = composedWorkflowFiles(lane.brandRoot).map((file) => file.name);
|
|
500
|
+
|
|
501
|
+
logger.log(names.length
|
|
502
|
+
? `Would compare ${names.join(', ')} with ${target.owner}/${target.repo}'s default branch and push the ones missing or changed there in one \`chore(ci): compose\` commit (a dry run reads nothing, so which differ is unknown here); the brand folder itself goes to ${plan.body.ref}.`
|
|
503
|
+
: `No composed workflow in ${lane.brandRoot}/.github/workflows: the default branch would receive nothing, and the brand folder goes to ${plan.body.ref}.`);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return { plan, dispatched: false, lane, sha };
|
|
189
507
|
}
|
|
190
508
|
|
|
191
509
|
const token = options.token || resolveToken({ env: options.env, execFn: options.execFn });
|
|
510
|
+
|
|
511
|
+
if (lane && lane.mode === 'snapshot' && options.snapshot) {
|
|
512
|
+
// The brand root pushed this run's snapshot ONCE, before it spawned a
|
|
513
|
+
// target (#901), so every step of the delivery is already done: the
|
|
514
|
+
// workflow compare, the packing and the push would only overwrite the very
|
|
515
|
+
// ref the other targets of this run are dispatching against. The wait and
|
|
516
|
+
// the dispatch below are the whole lane from here.
|
|
517
|
+
if (logger) {
|
|
518
|
+
logger.log(`Dispatching against the run's snapshot ${shortSha(options.snapshot)} on ${target.owner}/${target.repo}#${plan.body.ref} (the brand root pushed it once for every target)`);
|
|
519
|
+
}
|
|
520
|
+
} else if (lane) {
|
|
521
|
+
// Whatever this lane delivers, it is delivered in ONE place (#901), so a
|
|
522
|
+
// brand-root fan-out can run the very same delivery once for every target
|
|
523
|
+
// of a run instead of once per target.
|
|
524
|
+
const delivered = await deliverLane({
|
|
525
|
+
lane,
|
|
526
|
+
owner: target.owner,
|
|
527
|
+
repo: target.repo,
|
|
528
|
+
ref: plan.body.ref,
|
|
529
|
+
token,
|
|
530
|
+
message: options.message,
|
|
531
|
+
logger,
|
|
532
|
+
fetchFn: options.fetchFn,
|
|
533
|
+
steps,
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
sha = delivered.sha;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (lane) {
|
|
540
|
+
// Both lanes wait the same way: GitHub indexes a workflow it has just
|
|
541
|
+
// received a few seconds late, and a brand's FIRST deploy is exactly the
|
|
542
|
+
// push that carries the composed file. The wait reads the default branch,
|
|
543
|
+
// which is where a dispatch reads the workflow from.
|
|
544
|
+
await steps.wait({
|
|
545
|
+
owner: target.owner,
|
|
546
|
+
repo: target.repo,
|
|
547
|
+
workflow: options.workflow,
|
|
548
|
+
ref: plan.body.ref,
|
|
549
|
+
token,
|
|
550
|
+
fetchFn: options.fetchFn,
|
|
551
|
+
logger,
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
|
|
192
555
|
await dispatchWorkflow(plan, { token, fetchFn: options.fetchFn });
|
|
193
|
-
return { plan, dispatched: true };
|
|
556
|
+
return { plan, dispatched: true, lane, sha };
|
|
194
557
|
}
|
|
195
558
|
|
|
196
559
|
/**
|
|
197
560
|
* Find every `file:` @omega.js spec in the brand tree. npm resolves the
|
|
198
561
|
* WHOLE workspace tree on any install, so one linked sibling target breaks a
|
|
199
562
|
* CI install even when the deploying target is clean (the cp194 lesson —
|
|
200
|
-
* linking is tree-wide, so detection is too).
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
563
|
+
* linking is tree-wide, so detection is too). `resolveDeployLane` reads it
|
|
564
|
+
* to pick the SNAPSHOT lane, which packs the linked frameworks so the runner
|
|
565
|
+
* installs the same code this tree runs
|
|
566
|
+
* ([#872](https://github.com/Omega-JS-Stack/omega/issues/872); this replaces
|
|
567
|
+
* the 2026-07-20 rule that a linked brand built and shipped the artifact from
|
|
568
|
+
* this machine, which is `--direct` now).
|
|
204
569
|
* @param {object} [options]
|
|
205
570
|
* @param {string} [options.dir] - Any directory inside the brand (default cwd).
|
|
206
571
|
* @returns {string[]} One line per offender: `<manifest> → <name>: <spec>`.
|
|
@@ -222,65 +587,130 @@ function findLocalSpecs(options = {}) {
|
|
|
222
587
|
}
|
|
223
588
|
|
|
224
589
|
/**
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
590
|
+
* The LANE a deploy takes, derived once for every target
|
|
591
|
+
* ([#872](https://github.com/Omega-JS-Stack/omega/issues/872)), and since
|
|
592
|
+
* [#915](https://github.com/Omega-JS-Stack/omega/issues/915) there is ONE of
|
|
593
|
+
* them: every brand with a git repo SNAPSHOTS its folder to `omega-deploy`, and
|
|
594
|
+
* CI only ever builds that branch, whoever started the deploy. The push lane
|
|
595
|
+
* (commit the developer's branch, push it, dispatch it) is gone, with the
|
|
596
|
+
* `git add -A` commit that swept work nobody meant to ship.
|
|
597
|
+
*
|
|
598
|
+
* Two facts still ride ON the lane, because the steps read them:
|
|
599
|
+
* - NESTED: the brand root is not the toplevel of the git repo it sits in (a
|
|
600
|
+
* brand inside this monorepo), so its git toplevel is somebody else's repo:
|
|
601
|
+
* the behind check has nothing to compare, and the secrets publisher skips
|
|
602
|
+
* its origin gate.
|
|
603
|
+
* - LINKED: the brand tree carries a `file:` @omega.js spec anywhere. A runner
|
|
604
|
+
* can install none of those, so the packed tarballs have to travel with the
|
|
605
|
+
* snapshot.
|
|
606
|
+
*
|
|
607
|
+
* A brand outside git altogether (`repo` false) can snapshot nothing: its lane
|
|
608
|
+
* is the wait and the dispatch alone, on whatever the deploy branch holds.
|
|
609
|
+
*
|
|
610
|
+
* @param {object} [options] - Options.
|
|
228
611
|
* @param {string} [options.dir] - Any directory inside the brand (default cwd).
|
|
229
|
-
* @
|
|
612
|
+
* @param {function} [options.execFn] - Injectable exec (tests).
|
|
613
|
+
* @returns {{ mode: string, ref: string, nested: boolean, linked: boolean, repo: boolean, brandRoot: string }}
|
|
614
|
+
* The lane, plus the brand root every step of it works from.
|
|
230
615
|
*/
|
|
231
|
-
function
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
616
|
+
function resolveDeployLane(options = {}) {
|
|
617
|
+
const brandRoot = findBrandRoot(options.dir || process.cwd());
|
|
618
|
+
const execFn = options.execFn || ((cmd, opts) => execSync(cmd, opts).toString());
|
|
619
|
+
const git = (command) => {
|
|
620
|
+
try {
|
|
621
|
+
return String(execFn(command, { cwd: brandRoot, stdio: ['ignore', 'pipe', 'ignore'] })).trim();
|
|
622
|
+
} catch (e) {
|
|
623
|
+
// A brand outside git at all: there is nothing to snapshot and nothing to
|
|
624
|
+
// push, so the lane is the plain one and the deploy speaks for itself.
|
|
625
|
+
return '';
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
const toplevel = git('git rev-parse --show-toplevel');
|
|
630
|
+
const repo = Boolean(toplevel);
|
|
631
|
+
const nested = repo && path.resolve(toplevel) !== path.resolve(brandRoot);
|
|
632
|
+
const linked = findLocalSpecs({ dir: brandRoot }).length > 0;
|
|
633
|
+
|
|
634
|
+
if (linked && !repo) {
|
|
635
|
+
// A linked brand has to SNAPSHOT (the packed tarballs travel no other way),
|
|
636
|
+
// and a snapshot is built out of a git index, so no repo is no lane. Said
|
|
637
|
+
// by name HERE, because the push would otherwise die a step later on a raw
|
|
638
|
+
// `fatal: not a git repository` out of `git check-ignore` (#872).
|
|
639
|
+
throw new Error(`${brandRoot} is not inside a git repo, and a linked brand needs a git repo to snapshot from: \`git init\` the brand, or restore registry versions with \`omega i live\`.`);
|
|
640
|
+
}
|
|
641
|
+
if (!repo) {
|
|
642
|
+
// Nothing to snapshot FROM, and nothing linked that would need to: the
|
|
643
|
+
// deploy is the wait and the dispatch, on whatever the branch already holds.
|
|
644
|
+
return { mode: 'dispatch', ref: SNAPSHOT_REF, nested, linked, repo, brandRoot };
|
|
239
645
|
}
|
|
646
|
+
|
|
647
|
+
return { mode: 'snapshot', ref: SNAPSHOT_REF, nested, linked, repo, brandRoot };
|
|
240
648
|
}
|
|
241
649
|
|
|
242
650
|
/**
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
651
|
+
* Refuse a deploy from a checkout that is BEHIND the repo's default branch
|
|
652
|
+
* ([#915](https://github.com/Omega-JS-Stack/omega/issues/915)).
|
|
653
|
+
*
|
|
654
|
+
* The delivery force-pushes this folder over `omega-deploy`. A checkout missing
|
|
655
|
+
* commits the default branch already carries would therefore publish a site
|
|
656
|
+
* without them, and the admin publish is exactly that case: it commits a post
|
|
657
|
+
* to the default branch and to the deploy branch, and a stale laptop would
|
|
658
|
+
* force it back off both. So the deploy stops here, with the fix in the line.
|
|
659
|
+
*
|
|
660
|
+
* Plain git via argument arrays, like every other git call in this lane. No
|
|
661
|
+
* `origin` remote, or a remote that has no such branch yet, is nothing to be
|
|
662
|
+
* behind: both proceed.
|
|
663
|
+
*
|
|
664
|
+
* @param {object} options - Options.
|
|
665
|
+
* @param {string} options.cwd - The brand root (its own repo's toplevel).
|
|
666
|
+
* @param {string} options.branch - The repo's default branch.
|
|
667
|
+
* @param {object} [options.logger] - Logger with `log` (silent when omitted).
|
|
668
|
+
* @param {function} [options.execFn] - Injectable exec (tests).
|
|
669
|
+
* @throws {Error} When `origin/<branch>` is not an ancestor of HEAD.
|
|
251
670
|
*/
|
|
252
|
-
function
|
|
253
|
-
const cwd = options
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
execFileSync('git', ['add', '-A'], { cwd, stdio: 'inherit' });
|
|
671
|
+
function assertNotBehind(options) {
|
|
672
|
+
const { cwd, branch, logger } = options;
|
|
673
|
+
const execFn = options.execFn || ((args, opts) => execFileSync('git', args, opts));
|
|
674
|
+
const git = (args) => execFn(args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
257
675
|
|
|
258
|
-
// `git diff --cached --quiet` exits 1 exactly when something is staged
|
|
259
|
-
let hasStaged = false;
|
|
260
676
|
try {
|
|
261
|
-
|
|
677
|
+
git(['fetch', 'origin', branch]);
|
|
262
678
|
} catch (e) {
|
|
263
|
-
|
|
264
|
-
|
|
679
|
+
// ONLY the two answers that really mean there is nothing to compare
|
|
680
|
+
// against. A fetch that failed on the network, the token or the remote's
|
|
681
|
+
// permissions says nothing about this checkout, and proceeding on it would
|
|
682
|
+
// force-push a stale folder over everything the branch carries.
|
|
683
|
+
const said = `${e.stderr || ''}${e.message || ''}`;
|
|
684
|
+
|
|
685
|
+
if (!/couldn't find remote ref|does not appear to be a git repository|No such remote/i.test(said)) {
|
|
686
|
+
throw new Error(`Could not fetch origin/${branch} in ${cwd}, so there is no telling whether this checkout is behind it, and the deploy force-pushes the checkout over ${SNAPSHOT_REF}. git said: ${said.trim() || 'nothing at all'}`);
|
|
687
|
+
}
|
|
265
688
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
} else if (options.logger) {
|
|
269
|
-
options.logger.log('Working tree clean — nothing to commit');
|
|
689
|
+
if (logger) logger.log(`No origin/${branch} to compare against yet: nothing to be behind.`);
|
|
690
|
+
return;
|
|
270
691
|
}
|
|
271
692
|
|
|
272
|
-
|
|
693
|
+
try {
|
|
694
|
+
// Exits 0 exactly when the remote branch is already in this history.
|
|
695
|
+
git(['merge-base', '--is-ancestor', `origin/${branch}`, 'HEAD']);
|
|
696
|
+
} catch (e) {
|
|
697
|
+
throw new Error(`This checkout is behind origin/${branch}, and the deploy force-pushes it over ${SNAPSHOT_REF}: anything ${branch} carries that you do not (a post the admin published, a sibling's commit) would be deployed away. Run \`git pull\` in ${cwd}, then deploy again.`);
|
|
698
|
+
}
|
|
273
699
|
}
|
|
274
700
|
|
|
275
701
|
module.exports = {
|
|
276
702
|
parseRemoteUrl,
|
|
277
703
|
dispatchRepo,
|
|
704
|
+
dispatchTarget,
|
|
278
705
|
resolveRepo,
|
|
279
706
|
resolveToken,
|
|
280
707
|
buildDispatch,
|
|
281
708
|
dispatchWorkflow,
|
|
282
709
|
deployViaDispatch,
|
|
710
|
+
deliverLane,
|
|
711
|
+
laneLabel,
|
|
283
712
|
findLocalSpecs,
|
|
284
|
-
|
|
285
|
-
|
|
713
|
+
resolveDeployLane,
|
|
714
|
+
assertNotBehind,
|
|
715
|
+
SNAPSHOT_REF,
|
|
286
716
|
};
|