@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
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* git-auth: how a GitHub token reaches `git` for ONE push, and how it is kept
|
|
3
|
+
* out of everything that prints ([#883](https://github.com/Omega-JS-Stack/omega/issues/883)).
|
|
4
|
+
*
|
|
5
|
+
* Two lanes push to a repo the checkout is not authenticated for: the snapshot
|
|
6
|
+
* push of `deploy-snapshot.js` and the web deploy's gh-pages push. Both need
|
|
7
|
+
* the same three facts, and both used to spell them themselves, which is how a
|
|
8
|
+
* credential ends up in an argv in one of them and not the other:
|
|
9
|
+
*
|
|
10
|
+
* - the credential NEVER enters argv or a url. A push url carrying a token is
|
|
11
|
+
* readable by every other process on the machine, is printed by git's own
|
|
12
|
+
* error output, and rides the message `execFileSync` throws (which carries
|
|
13
|
+
* the whole argv) straight into a CI log, outside Actions' masking. It
|
|
14
|
+
* travels in the config ENV of that one child process instead, which lives
|
|
15
|
+
* exactly as long as the push and lands on no disk;
|
|
16
|
+
* - the header is the BASIC form (`x-access-token:<token>`, base64), the one
|
|
17
|
+
* the checkout action uses: the git endpoints are not the REST API and
|
|
18
|
+
* ignore a bearer header, so the push falls back to a username prompt
|
|
19
|
+
* (proven on the first playground push, [#872](https://github.com/Omega-JS-Stack/omega/issues/872));
|
|
20
|
+
* - a failure is rethrown SCRUBBED, in both the raw form and the base64 the
|
|
21
|
+
* header carries, so no lane can print what it was careful not to pass.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
// The one config entry either lane sets: git applies `http.<url>.extraheader`
|
|
25
|
+
// to every request under that url prefix.
|
|
26
|
+
const EXTRAHEADER_KEY = 'http.https://github.com/.extraheader';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The token in the form the header carries it.
|
|
30
|
+
*
|
|
31
|
+
* @param {string} token - GitHub token.
|
|
32
|
+
* @returns {string} base64 of `x-access-token:<token>`.
|
|
33
|
+
*/
|
|
34
|
+
function gitAuthValue(token) {
|
|
35
|
+
return Buffer.from(`x-access-token:${token}`).toString('base64');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The environment entries that hand `git` the token through its own config, for
|
|
40
|
+
* the lifetime of one child process.
|
|
41
|
+
*
|
|
42
|
+
* @param {string} [token] - GitHub token; without one the result is empty, so a
|
|
43
|
+
* spread of it is a no-op and git asks for credentials itself.
|
|
44
|
+
* @returns {object} `GIT_CONFIG_COUNT`/`GIT_CONFIG_KEY_0`/`GIT_CONFIG_VALUE_0`, or `{}`.
|
|
45
|
+
*/
|
|
46
|
+
function gitAuthEnv(token) {
|
|
47
|
+
if (!token) return {};
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
GIT_CONFIG_COUNT: '1',
|
|
51
|
+
GIT_CONFIG_KEY_0: EXTRAHEADER_KEY,
|
|
52
|
+
GIT_CONFIG_VALUE_0: `AUTHORIZATION: basic ${gitAuthValue(token)}`,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Every appearance of the token replaced by a placeholder, raw and base64, so a
|
|
58
|
+
* failure can be printed.
|
|
59
|
+
*
|
|
60
|
+
* @param {string} message - Text to scrub.
|
|
61
|
+
* @param {string} [token] - The token to remove (absent scrubs nothing).
|
|
62
|
+
* @returns {string} The text with the token redacted.
|
|
63
|
+
*/
|
|
64
|
+
function scrubToken(message, token) {
|
|
65
|
+
const text = String(message === undefined || message === null ? '' : message);
|
|
66
|
+
if (!token) return text;
|
|
67
|
+
|
|
68
|
+
return text
|
|
69
|
+
.split(token).join('***')
|
|
70
|
+
.split(gitAuthValue(token)).join('***');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = { gitAuthEnv, gitAuthValue, scrubToken, EXTRAHEADER_KEY };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The git REMOTE boundary: the url a checkout points at, parsed, retargeted and
|
|
3
|
+
* written ([#890](https://github.com/Omega-JS-Stack/omega/issues/890)).
|
|
4
|
+
*
|
|
5
|
+
* `parseRemoteUrl` lived in `deploy.js`, whose load pulls in the local-link,
|
|
6
|
+
* pack and snapshot modules: 40ms nothing on the CLI BOOT path can spend for
|
|
7
|
+
* one regex. The boot prelude that heals a redirected `origin` needs the parse
|
|
8
|
+
* plus the two git calls around it, so the three live here, in one place, and
|
|
9
|
+
* `deploy.js` re-exports the parse it already published.
|
|
10
|
+
*
|
|
11
|
+
* Transport: `git` through execFile with an ARGV ARRAY, never a shell string,
|
|
12
|
+
* so a remote url can never inject a command. Every call names its directory
|
|
13
|
+
* with `-C`: git resolves a repo by walking UP, and a brand nested inside
|
|
14
|
+
* another repo would otherwise read (and rewrite) the enclosing repo's
|
|
15
|
+
* remote.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const { execFileSync } = require('node:child_process');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Parse a git remote URL into { owner, repo }.
|
|
22
|
+
* Handles ssh (git@github.com:o/r.git), https (https://github.com/o/r.git),
|
|
23
|
+
* and .git-less forms.
|
|
24
|
+
* @param {string} url - remote URL
|
|
25
|
+
* @returns {{ owner: string, repo: string }|null} Null for anything that is not a GitHub remote.
|
|
26
|
+
*/
|
|
27
|
+
function parseRemoteUrl(url) {
|
|
28
|
+
const match = (url || '').trim().match(/github\.com[/:]([^/]+)\/([^/\s]+?)(?:\.git)?$/);
|
|
29
|
+
if (!match) return null;
|
|
30
|
+
return { owner: match[1], repo: match[2] };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The same remote url, addressing another repo: the slug is substituted in
|
|
35
|
+
* place, so the url keeps its OWN form (an ssh remote stays ssh, an https
|
|
36
|
+
* remote stays https, a `.git` suffix stays or stays absent). A rewrite that
|
|
37
|
+
* normalized the form would change how the checkout authenticates.
|
|
38
|
+
*
|
|
39
|
+
* @param {string} url - The current remote url.
|
|
40
|
+
* @param {string} slug - The `owner/name` to point it at.
|
|
41
|
+
* @returns {string} The retargeted url.
|
|
42
|
+
* @throws {Error} When the url is not a GitHub remote (the caller parsed it already).
|
|
43
|
+
*/
|
|
44
|
+
function retargetRemoteUrl(url, slug) {
|
|
45
|
+
const current = parseRemoteUrl(url);
|
|
46
|
+
if (!current) {
|
|
47
|
+
throw new Error(`Cannot retarget a remote url that names no GitHub repo: ${String(url).trim() || '(none)'}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const trimmed = url.trim();
|
|
51
|
+
const currentSlug = `${current.owner}/${current.repo}`;
|
|
52
|
+
const at = trimmed.lastIndexOf(currentSlug);
|
|
53
|
+
|
|
54
|
+
return trimmed.slice(0, at) + slug + trimmed.slice(at + currentSlug.length);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A remote's url, as the checkout has it.
|
|
59
|
+
* @param {object} options
|
|
60
|
+
* @param {string} options.dir - The repository directory.
|
|
61
|
+
* @param {string} [options.remote] - Remote name (default: origin).
|
|
62
|
+
* @param {function} [options.execFn] - Injectable `(file, args, opts) => stdout` (tests).
|
|
63
|
+
* @returns {string} The trimmed url.
|
|
64
|
+
* @throws {Error} When git has no such remote, or the directory is no repository.
|
|
65
|
+
*/
|
|
66
|
+
function remoteUrl(options) {
|
|
67
|
+
const execFn = options.execFn || execFileSync;
|
|
68
|
+
const args = ['-C', options.dir, 'remote', 'get-url', options.remote || 'origin'];
|
|
69
|
+
|
|
70
|
+
return String(execFn('git', args, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] })).trim();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Point a remote at another url.
|
|
75
|
+
* @param {object} options
|
|
76
|
+
* @param {string} options.dir - The repository directory.
|
|
77
|
+
* @param {string} options.url - The url to set.
|
|
78
|
+
* @param {string} [options.remote] - Remote name (default: origin).
|
|
79
|
+
* @param {function} [options.execFn] - Injectable exec (tests).
|
|
80
|
+
* @returns {void}
|
|
81
|
+
* @throws {Error} When git refuses the write.
|
|
82
|
+
*/
|
|
83
|
+
function setRemoteUrl(options) {
|
|
84
|
+
const execFn = options.execFn || execFileSync;
|
|
85
|
+
const args = ['-C', options.dir, 'remote', 'set-url', options.remote || 'origin', options.url];
|
|
86
|
+
|
|
87
|
+
execFn('git', args, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = {
|
|
91
|
+
parseRemoteUrl,
|
|
92
|
+
retargetRemoteUrl,
|
|
93
|
+
remoteUrl,
|
|
94
|
+
setRemoteUrl,
|
|
95
|
+
};
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The GitHub repo boundary, ONE home
|
|
3
|
+
* ([#883](https://github.com/Omega-JS-Stack/omega/issues/883)).
|
|
4
|
+
*
|
|
5
|
+
* Three lanes used to talk to GitHub their own way: the manager's repo service
|
|
6
|
+
* (a gh-CLI wrapper), @omega.js/desktop's release precheck (an Octokit client
|
|
7
|
+
* with its own `ensureRepo`), and the extension's publish workflow (a `gh
|
|
8
|
+
* release` shell step inside a YAML template). Same four questions, three
|
|
9
|
+
* answers, three places to fix a bug. This module is the answer: every repo a
|
|
10
|
+
* brand owns is created, reconciled and served from here.
|
|
11
|
+
*
|
|
12
|
+
* Transport: the `gh` CLI through execFile with an ARGV ARRAY, never a shell
|
|
13
|
+
* string, so a brand-config value (a description, a homepage, a domain) can
|
|
14
|
+
* never inject a command. `gh` is already required by the manager and ships on
|
|
15
|
+
* every GitHub runner. Auth is the ambient `gh auth` session or a
|
|
16
|
+
* GH_TOKEN/GITHUB_TOKEN in the env, which gh honors on its own: nothing here
|
|
17
|
+
* reads, logs or passes a token.
|
|
18
|
+
*
|
|
19
|
+
* Every ensure is IDEMPOTENT and returns the same shape,
|
|
20
|
+
* `{ created, changed, planned }`, where `planned` is the human-readable list
|
|
21
|
+
* of what the call does (or WOULD do under `dryRun`), so a dry run prints the
|
|
22
|
+
* plan and a real run reports the same lines.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const { execFileSync } = require('node:child_process');
|
|
26
|
+
|
|
27
|
+
// GitHub Pages serves the built site from one orphan branch, the same one
|
|
28
|
+
// every OMEGA web deploy force-pushes.
|
|
29
|
+
const DEFAULT_PAGES_BRANCH = 'gh-pages';
|
|
30
|
+
|
|
31
|
+
// The plan name GitHub reports for a free org, and the answer for an owner
|
|
32
|
+
// with no plan to read (a user account): the two cases that cannot host a
|
|
33
|
+
// private Pages site.
|
|
34
|
+
const FREE_PLAN = 'free';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Run `gh` with an argv array and return its trimmed stdout.
|
|
38
|
+
*
|
|
39
|
+
* @param {string[]} args - The argv after `gh` (e.g. ['api', 'repos/o/r']).
|
|
40
|
+
* @param {object} [options]
|
|
41
|
+
* @param {function} [options.execFn] - Injectable `(file, args, opts) => stdout` (tests).
|
|
42
|
+
* @param {number} [options.timeout] - Milliseconds before the call is given up on (default: none).
|
|
43
|
+
* @returns {string} Trimmed stdout ('' when gh printed nothing).
|
|
44
|
+
* @throws {Error} When gh exits non-zero or times out, carrying its stderr.
|
|
45
|
+
*/
|
|
46
|
+
function gh(args, options = {}) {
|
|
47
|
+
const execFn = options.execFn || execFileSync;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
return String(execFn('gh', args, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], timeout: options.timeout })).trim();
|
|
51
|
+
} catch (error) {
|
|
52
|
+
const stderr = (error.stderr && error.stderr.toString()) || error.message;
|
|
53
|
+
throw new Error(`gh ${args.join(' ')} failed: ${stderr.trim()}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Run a `gh` call whose output is JSON.
|
|
59
|
+
* @param {string[]} args - The argv after `gh`.
|
|
60
|
+
* @param {object} [options] - See gh().
|
|
61
|
+
* @returns {object|null} The parsed body, or null when gh printed nothing.
|
|
62
|
+
*/
|
|
63
|
+
function ghJson(args, options = {}) {
|
|
64
|
+
const out = gh(args, options);
|
|
65
|
+
return out ? JSON.parse(out) : null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* A missing resource, told from a real failure: gh prints the status line, so a
|
|
70
|
+
* 404 is the answer "it does not exist" and anything else is a problem to raise.
|
|
71
|
+
* @param {Error} error - The error gh() threw.
|
|
72
|
+
* @returns {boolean}
|
|
73
|
+
*/
|
|
74
|
+
function isNotFound(error) {
|
|
75
|
+
return /404|Not Found/i.test(error.message);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* A repository's API record.
|
|
80
|
+
* @param {string} owner - Org or user.
|
|
81
|
+
* @param {string} name - Repo name.
|
|
82
|
+
* @param {object} [options] - See gh().
|
|
83
|
+
* @returns {object|null} The repo json, or null when it does not exist.
|
|
84
|
+
*/
|
|
85
|
+
function getRepo(owner, name, options = {}) {
|
|
86
|
+
try {
|
|
87
|
+
return ghJson(['api', `repos/${owner}/${name}`], options);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
if (isNotFound(error)) return null;
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Create a repo when it is missing, else reconcile the two facts a brand
|
|
96
|
+
* declares about it: its visibility and its homepage. The description is a
|
|
97
|
+
* create-time value (nothing derives it after the fact).
|
|
98
|
+
*
|
|
99
|
+
* @param {object} repo - The repo to ensure.
|
|
100
|
+
* @param {string} repo.owner - Org or user.
|
|
101
|
+
* @param {string} repo.name - Repo name.
|
|
102
|
+
* @param {boolean} [repo.private] - Visibility (default private).
|
|
103
|
+
* @param {string} [repo.description] - Set when the repo is created.
|
|
104
|
+
* @param {string} [repo.homepage] - The repo's url field, reconciled on drift.
|
|
105
|
+
* @param {string} [repo.reason] - Why this visibility, stated on the create line
|
|
106
|
+
* the plan prints. The CALLER owns the why (a website repo is public because
|
|
107
|
+
* the org is on the free plan, which only the manage walk knows), so it hands
|
|
108
|
+
* the words in rather than reformatting a line it matched by string.
|
|
109
|
+
* @param {boolean} [repo.autoInit] - Create with a first commit. A repo that
|
|
110
|
+
* must carry a commit before anything can publish to it (the releases repo:
|
|
111
|
+
* a release needs a tag, and a tag needs a commit) asks for it; a repo whose
|
|
112
|
+
* history is pushed from a machine never does, since an initial commit it did
|
|
113
|
+
* not make is a push it has to fight.
|
|
114
|
+
* @param {object} [options]
|
|
115
|
+
* @param {boolean} [options.dryRun] - Build the plan and touch nothing.
|
|
116
|
+
* @param {function} [options.execFn] - Injectable exec (tests).
|
|
117
|
+
* @returns {{ created: boolean, changed: boolean, planned: string[] }}
|
|
118
|
+
*/
|
|
119
|
+
function ensureRepo(repo, options = {}) {
|
|
120
|
+
const { owner, name } = repo;
|
|
121
|
+
const slug = `${owner}/${name}`;
|
|
122
|
+
const isPrivate = repo.private !== false;
|
|
123
|
+
const existing = getRepo(owner, name, options);
|
|
124
|
+
|
|
125
|
+
if (!existing) {
|
|
126
|
+
const planned = [`create ${slug} (${isPrivate ? 'private' : 'public'}${repo.reason ? `: ${repo.reason}` : ''})`];
|
|
127
|
+
if (options.dryRun) return { created: false, changed: false, planned };
|
|
128
|
+
|
|
129
|
+
const args = ['repo', 'create', slug, isPrivate ? '--private' : '--public'];
|
|
130
|
+
if (repo.description) args.push('--description', repo.description);
|
|
131
|
+
if (repo.homepage) args.push('--homepage', repo.homepage);
|
|
132
|
+
if (repo.autoInit) args.push('--add-readme');
|
|
133
|
+
gh(args, options);
|
|
134
|
+
|
|
135
|
+
return { created: true, changed: false, planned };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const fields = [];
|
|
139
|
+
const planned = [];
|
|
140
|
+
|
|
141
|
+
if (existing.private !== isPrivate) {
|
|
142
|
+
fields.push('-F', `private=${isPrivate}`);
|
|
143
|
+
planned.push(`${slug}: ${isPrivate ? 'public -> private' : 'private -> public'}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (repo.homepage && existing.homepage !== repo.homepage) {
|
|
147
|
+
fields.push('-f', `homepage=${repo.homepage}`);
|
|
148
|
+
planned.push(`${slug}: homepage -> ${repo.homepage}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (!fields.length) return { created: false, changed: false, planned };
|
|
152
|
+
if (options.dryRun) return { created: false, changed: false, planned };
|
|
153
|
+
|
|
154
|
+
gh(['api', `repos/${slug}`, '-X', 'PATCH', ...fields], options);
|
|
155
|
+
|
|
156
|
+
return { created: false, changed: true, planned };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* A repo's GitHub Pages configuration.
|
|
161
|
+
* @param {string} owner - Org or user.
|
|
162
|
+
* @param {string} name - Repo name.
|
|
163
|
+
* @param {object} [options] - See gh().
|
|
164
|
+
* @returns {object|null} The pages json, or null when Pages is off.
|
|
165
|
+
*/
|
|
166
|
+
function getPages(owner, name, options = {}) {
|
|
167
|
+
try {
|
|
168
|
+
return ghJson(['api', `repos/${owner}/${name}/pages`], options);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
if (isNotFound(error)) return null;
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Whether a branch exists on a repo.
|
|
177
|
+
* @param {string} owner - Org or user.
|
|
178
|
+
* @param {string} name - Repo name.
|
|
179
|
+
* @param {string} branch - Branch name.
|
|
180
|
+
* @param {object} [options] - See gh().
|
|
181
|
+
* @returns {boolean}
|
|
182
|
+
*/
|
|
183
|
+
function branchExists(owner, name, branch, options = {}) {
|
|
184
|
+
try {
|
|
185
|
+
ghJson(['api', `repos/${owner}/${name}/branches/${branch}`], options);
|
|
186
|
+
return true;
|
|
187
|
+
} catch (error) {
|
|
188
|
+
if (isNotFound(error)) return false;
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Point GitHub Pages at the deploy branch and, when the target has one, at its
|
|
195
|
+
* custom domain. Idempotent: a repo already serving that branch at that domain
|
|
196
|
+
* is left alone.
|
|
197
|
+
*
|
|
198
|
+
* Pages cannot be configured before the branch exists, and the branch is what
|
|
199
|
+
* the FIRST deploy pushes, so a fresh website repo reports `pending` instead of
|
|
200
|
+
* failing: the next walk after the first deploy finishes the job.
|
|
201
|
+
*
|
|
202
|
+
* @param {object} pages - What to serve.
|
|
203
|
+
* @param {string} pages.owner - Org or user.
|
|
204
|
+
* @param {string} pages.name - Repo name.
|
|
205
|
+
* @param {string} [pages.branch] - Source branch (default gh-pages).
|
|
206
|
+
* @param {string} [pages.cname] - Custom domain (the host the target is served at).
|
|
207
|
+
* @param {object} [options]
|
|
208
|
+
* @param {boolean} [options.dryRun] - Build the plan and touch nothing.
|
|
209
|
+
* @param {function} [options.execFn] - Injectable exec (tests).
|
|
210
|
+
* @param {object} [options.logger] - `{ log }` (default: console).
|
|
211
|
+
* @returns {{ created: boolean, changed: boolean, planned: string[], pending?: boolean }}
|
|
212
|
+
*/
|
|
213
|
+
function ensurePages(pages, options = {}) {
|
|
214
|
+
const { owner, name, cname } = pages;
|
|
215
|
+
const branch = pages.branch || DEFAULT_PAGES_BRANCH;
|
|
216
|
+
const slug = `${owner}/${name}`;
|
|
217
|
+
const logger = options.logger || console;
|
|
218
|
+
|
|
219
|
+
if (!branchExists(owner, name, branch, options)) {
|
|
220
|
+
logger.log(`${slug} has no ${branch} branch yet: Pages configures after the first deploy`);
|
|
221
|
+
return { created: false, changed: false, planned: [], pending: true };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const existing = getPages(owner, name, options);
|
|
225
|
+
const planned = [];
|
|
226
|
+
|
|
227
|
+
if (!existing) {
|
|
228
|
+
planned.push(`pages ${slug}: ${branch}${cname ? ` -> ${cname}` : ''}`);
|
|
229
|
+
if (options.dryRun) return { created: false, changed: false, planned };
|
|
230
|
+
|
|
231
|
+
gh(['api', `repos/${slug}/pages`, '-X', 'POST', '-f', `source[branch]=${branch}`, '-f', 'source[path]=/'], options);
|
|
232
|
+
if (cname) gh(['api', `repos/${slug}/pages`, '-X', 'PUT', '-f', `cname=${cname}`], options);
|
|
233
|
+
|
|
234
|
+
return { created: true, changed: false, planned };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const sourceBranch = existing.source ? existing.source.branch : undefined;
|
|
238
|
+
let changed = false;
|
|
239
|
+
|
|
240
|
+
if (sourceBranch !== branch) {
|
|
241
|
+
planned.push(`pages ${slug}: ${sourceBranch || 'none'} -> ${branch}`);
|
|
242
|
+
if (!options.dryRun) {
|
|
243
|
+
gh(['api', `repos/${slug}/pages`, '-X', 'PUT', '-f', `source[branch]=${branch}`, '-f', 'source[path]=/'], options);
|
|
244
|
+
changed = true;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (cname && existing.cname !== cname) {
|
|
249
|
+
planned.push(`pages ${slug}: domain -> ${cname}`);
|
|
250
|
+
if (!options.dryRun) {
|
|
251
|
+
gh(['api', `repos/${slug}/pages`, '-X', 'PUT', '-f', `cname=${cname}`], options);
|
|
252
|
+
changed = true;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return { created: false, changed, planned };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* An owner's GitHub plan, which is what says whether a private repo may serve
|
|
261
|
+
* Pages at all: only a paid org can. A USER owner and an org whose plan the
|
|
262
|
+
* token cannot read both answer `free`, the half that cannot, because guessing
|
|
263
|
+
* the other way publishes a site the brand meant to keep private.
|
|
264
|
+
*
|
|
265
|
+
* @param {string} owner - Org or user.
|
|
266
|
+
* @param {object} [options] - See gh().
|
|
267
|
+
* @returns {string} 'free' or the plan name.
|
|
268
|
+
*/
|
|
269
|
+
function ownerPlan(owner, options = {}) {
|
|
270
|
+
let org;
|
|
271
|
+
try {
|
|
272
|
+
org = ghJson(['api', `orgs/${owner}`], options);
|
|
273
|
+
} catch (error) {
|
|
274
|
+
if (isNotFound(error)) return FREE_PLAN;
|
|
275
|
+
throw error;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return (org && org.plan && org.plan.name) || FREE_PLAN;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
module.exports = {
|
|
282
|
+
gh,
|
|
283
|
+
getRepo,
|
|
284
|
+
ensureRepo,
|
|
285
|
+
getPages,
|
|
286
|
+
ensurePages,
|
|
287
|
+
ownerPlan,
|
|
288
|
+
DEFAULT_PAGES_BRANCH,
|
|
289
|
+
FREE_PLAN,
|
|
290
|
+
};
|
|
@@ -1548,6 +1548,34 @@ function healFailedMessage(result) {
|
|
|
1548
1548
|
].join('\n');
|
|
1549
1549
|
}
|
|
1550
1550
|
|
|
1551
|
+
/**
|
|
1552
|
+
* Leave the #350 prepare gate wired on a package this process just prepared
|
|
1553
|
+
* ([#870](https://github.com/Omega-JS-Stack/omega/issues/870)). prepare-package
|
|
1554
|
+
* rewrites `scripts.prepare` to its own UNGUARDED string on the way through, and
|
|
1555
|
+
* a heal that left it there hands a consumer install the door back. The prepare
|
|
1556
|
+
* script restores it in a `finally`, so this is the net for the case a finally
|
|
1557
|
+
* cannot reach: a prepare killed outright, or one that never ran the gate at all.
|
|
1558
|
+
* The guard is loaded from the devkit BESIDE the prepared package, the same hop
|
|
1559
|
+
* the gated script's own relative require makes, and its path is built at runtime
|
|
1560
|
+
* rather than written as a literal specifier, because devkit's vendored copies
|
|
1561
|
+
* carry `src/` only and a relative require of `tools/` would send the vendor walk
|
|
1562
|
+
* outside the module root.
|
|
1563
|
+
* @param {object} options
|
|
1564
|
+
* @param {string} options.monorepoRoot - The monorepo containing the package.
|
|
1565
|
+
* @param {string} options.packageDir - The package that was prepared.
|
|
1566
|
+
* @returns {boolean} Whether the manifest holds the gate now (true when the
|
|
1567
|
+
* checkout carries no guard to enforce).
|
|
1568
|
+
*/
|
|
1569
|
+
function keepPrepareGuarded(options) {
|
|
1570
|
+
const { monorepoRoot, packageDir } = options;
|
|
1571
|
+
const guardPath = path.join(monorepoRoot, 'packages', 'devkit', 'tools', 'prepare-guard.js');
|
|
1572
|
+
if (!fs.existsSync(guardPath)) {
|
|
1573
|
+
return true;
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
return require(guardPath).ensureGuarded({ packageDir }).guarded;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1551
1579
|
/**
|
|
1552
1580
|
* Check that a locally-linked framework's dist is at least as new as its src —
|
|
1553
1581
|
* reporting or rebuilding it when a src edit landed without a prepare, so
|
|
@@ -1661,6 +1689,10 @@ function ensureFreshLocalDist(options) {
|
|
|
1661
1689
|
stdio: 'inherit',
|
|
1662
1690
|
shell: process.platform === 'win32',
|
|
1663
1691
|
});
|
|
1692
|
+
|
|
1693
|
+
// Whatever that prepare did, it does not get to leave the gate off (#870)
|
|
1694
|
+
const guarded = inMonorepo ? keepPrepareGuarded({ monorepoRoot, packageDir: realDir }) : true;
|
|
1695
|
+
|
|
1664
1696
|
if (result.status !== 0) {
|
|
1665
1697
|
const failure = `npm run prepare exited ${result.status === null ? String(result.error && result.error.message || 'spawn error') : result.status}`;
|
|
1666
1698
|
if (inMonorepo) {
|
|
@@ -1671,6 +1703,21 @@ function ensureFreshLocalDist(options) {
|
|
|
1671
1703
|
console.warn(`omega: rebuild failed (${failure} in ${realDir}) — continuing on the stale dist`);
|
|
1672
1704
|
return { status: 'rebuild-failed', packageName, dir: realDir };
|
|
1673
1705
|
}
|
|
1706
|
+
|
|
1707
|
+
// A prepare that ended with the gate off hands a consumer install the #350
|
|
1708
|
+
// door back, so a heal that could not put it back is a FAILED heal like any
|
|
1709
|
+
// other: the boot stops on it rather than running on an ungated package.
|
|
1710
|
+
if (!guarded) {
|
|
1711
|
+
return {
|
|
1712
|
+
status: 'heal-failed',
|
|
1713
|
+
packageName,
|
|
1714
|
+
dir: realDir,
|
|
1715
|
+
reason,
|
|
1716
|
+
failure: 'npm run prepare left scripts.prepare off the #350 gate',
|
|
1717
|
+
monorepoRoot,
|
|
1718
|
+
};
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1674
1721
|
return { status: 'rebuilt', by: 'self', packageName, dir: realDir };
|
|
1675
1722
|
});
|
|
1676
1723
|
}
|
|
@@ -23,8 +23,16 @@
|
|
|
23
23
|
// - The Default section is replaced with the new framework's defaults.
|
|
24
24
|
// - Keys that already had values (in either Default or Custom) keep those values
|
|
25
25
|
// in the same section they were in.
|
|
26
|
-
// -
|
|
27
|
-
//
|
|
26
|
+
// - The Default section is COMPLETELY managed ([#926](https://github.com/Omega-JS-Stack/omega/issues/926)):
|
|
27
|
+
// a line the new framework block no longer carries LEAVES the consumer, so a
|
|
28
|
+
// rule the framework retires or rewrites actually takes effect everywhere. A
|
|
29
|
+
// retired framework line and a line a user typed inside the framework block
|
|
30
|
+
// cannot be told apart, and the block header already says it is overwritten on
|
|
31
|
+
// every setup, so `.gitignore` and `AGENTS.md` drop it. Lines a user owns
|
|
32
|
+
// belong under the Custom marker, which stays verbatim.
|
|
33
|
+
// - .env is the one exception: a retired key holding a NON-EMPTY value migrates
|
|
34
|
+
// to the Custom section (that value is data, not a rule), while an empty
|
|
35
|
+
// retired key (`KEY=` / `KEY=""`) drops like any other retired line.
|
|
28
36
|
// - .env values are normalized to **double-quoted** form on every merge:
|
|
29
37
|
// KEY=raw-value → KEY="raw-value"
|
|
30
38
|
// KEY="already-quoted" → KEY="already-quoted" (left alone)
|
|
@@ -110,24 +118,23 @@ function mergeLineBasedFiles(existingContent, newContent, fileName) {
|
|
|
110
118
|
}
|
|
111
119
|
}
|
|
112
120
|
|
|
113
|
-
//
|
|
114
|
-
//
|
|
121
|
+
// Anything in the existing Default section the new framework block no longer
|
|
122
|
+
// carries. The block is COMPLETELY managed (#926), so it DROPS: a retired
|
|
123
|
+
// framework line and a line the user typed into the framework block cannot be
|
|
124
|
+
// told apart, and the block header already says it is overwritten on every
|
|
125
|
+
// setup. The one exception is an .env key holding a real value, which is the
|
|
126
|
+
// user's DATA rather than a rule, so it migrates down to Custom; an empty one
|
|
127
|
+
// drops with the rest. .gitignore and AGENTS.md migrate nothing.
|
|
115
128
|
const migratedToCustom = [];
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
-
|
|
129
|
+
if (isEnvFile) {
|
|
130
|
+
for (const line of existingDefault) {
|
|
131
|
+
const trimmed = line.trim();
|
|
132
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
119
133
|
|
|
120
|
-
if (isEnvFile) {
|
|
121
134
|
const key = trimmed.split('=')[0].trim();
|
|
122
|
-
if (key && !newDefaultKeys.has(key) && !existingCustomKeys.has(key)) {
|
|
135
|
+
if (key && !newDefaultKeys.has(key) && !existingCustomKeys.has(key) && !envValueIsEmpty(trimmed)) {
|
|
123
136
|
migratedToCustom.push(normalizeEnvLine(line));
|
|
124
137
|
}
|
|
125
|
-
} else {
|
|
126
|
-
// .gitignore: line not in new defaults → migrate.
|
|
127
|
-
const inNew = newLines.some((nl) => nl.trim() === trimmed);
|
|
128
|
-
if (!inNew) {
|
|
129
|
-
migratedToCustom.push(line);
|
|
130
|
-
}
|
|
131
138
|
}
|
|
132
139
|
}
|
|
133
140
|
|
|
@@ -186,7 +193,7 @@ function normalizeEnvLine(line) {
|
|
|
186
193
|
if (eqIdx < 0) return line; // no `=` — not a KEY=VALUE line
|
|
187
194
|
|
|
188
195
|
const key = trimmed.slice(0, eqIdx).trim();
|
|
189
|
-
|
|
196
|
+
const value = trimmed.slice(eqIdx + 1);
|
|
190
197
|
|
|
191
198
|
// Strip trailing whitespace + inline comment-after-value (rare; we only strip a # that follows a space).
|
|
192
199
|
// We do NOT strip # inside quoted values. Detect that by checking if value starts with a quote.
|
|
@@ -253,9 +253,10 @@ async function run({ hostName, hostRun, argv = process.argv.slice(2) }) {
|
|
|
253
253
|
const target = findTarget(process.cwd());
|
|
254
254
|
|
|
255
255
|
// No context — the bootstrap case (a verb run in a fresh directory has
|
|
256
|
-
// no framework dep yet, by definition). Run the
|
|
257
|
-
//
|
|
258
|
-
//
|
|
256
|
+
// no framework dep yet, by definition). Run the INSTALLED @omega.js/manager
|
|
257
|
+
// when one resolves, else the HOST framework's CLI exactly like the
|
|
258
|
+
// pre-dispatcher bins did, and say which one so a hoist-winner at a brand
|
|
259
|
+
// root is never a silent mystery.
|
|
259
260
|
if (!target) {
|
|
260
261
|
// …but ONLY for a verb that cannot write. The fallback hands a mutating verb
|
|
261
262
|
// to whichever framework won npm's bin link, and that verb scaffolds its own
|
|
@@ -279,6 +280,20 @@ async function run({ hostName, hostRun, argv = process.argv.slice(2) }) {
|
|
|
279
280
|
process.exit(1);
|
|
280
281
|
}
|
|
281
282
|
|
|
283
|
+
// The manager wins a contextless run whenever the install carries one: the
|
|
284
|
+
// verbs that survive the refusal above are the ones that run where no
|
|
285
|
+
// target exists yet (onboard, create, new), and those are the manager's own.
|
|
286
|
+
// Without this, a fresh brand-template clone that installed ONLY
|
|
287
|
+
// @omega.js/manager still ran the backend, because the manager DEPENDS on
|
|
288
|
+
// the backend and npm's arbitrary hoist winner linked the backend's bin:
|
|
289
|
+
// `npx omega onboard` answered `Unknown command`
|
|
290
|
+
// ([#908](https://github.com/Omega-JS-Stack/omega/issues/908)).
|
|
291
|
+
const manager = tryResolveCli(MANAGER, process.cwd());
|
|
292
|
+
if (manager.cliPath) {
|
|
293
|
+
console.error(`omega: no target context found from ${process.cwd()}: running ${MANAGER}`);
|
|
294
|
+
return require(manager.cliPath).run();
|
|
295
|
+
}
|
|
296
|
+
|
|
282
297
|
console.error(`omega: no target context found from ${process.cwd()} — running ${hostName}`);
|
|
283
298
|
return hostRun();
|
|
284
299
|
}
|