@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
|
@@ -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,172 @@
|
|
|
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. The brand's own origin,
|
|
10
|
+
* read once for the prelude, the manage walk and the deploy alike, and the
|
|
11
|
+
* refusal the latter two raise when it disagrees with the config, live here too
|
|
12
|
+
* ([#934](https://github.com/Omega-JS-Stack/omega/issues/934)).
|
|
13
|
+
*
|
|
14
|
+
* Transport: `git` through execFile with an ARGV ARRAY, never a shell string,
|
|
15
|
+
* so a remote url can never inject a command. Every call names its directory
|
|
16
|
+
* with `-C`: git resolves a repo by walking UP, and a brand nested inside
|
|
17
|
+
* another repo would otherwise read (and rewrite) the enclosing repo's
|
|
18
|
+
* remote.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const path = require('node:path');
|
|
22
|
+
const { execFileSync } = require('node:child_process');
|
|
23
|
+
const jetpack = require('fs-jetpack');
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Parse a git remote URL into { owner, repo }.
|
|
27
|
+
* Handles ssh (git@github.com:o/r.git), https (https://github.com/o/r.git),
|
|
28
|
+
* and .git-less forms.
|
|
29
|
+
* @param {string} url - remote URL
|
|
30
|
+
* @returns {{ owner: string, repo: string }|null} Null for anything that is not a GitHub remote.
|
|
31
|
+
*/
|
|
32
|
+
function parseRemoteUrl(url) {
|
|
33
|
+
const match = (url || '').trim().match(/github\.com[/:]([^/]+)\/([^/\s]+?)(?:\.git)?$/);
|
|
34
|
+
if (!match) return null;
|
|
35
|
+
return { owner: match[1], repo: match[2] };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The same remote url, addressing another repo: the slug is substituted in
|
|
40
|
+
* place, so the url keeps its OWN form (an ssh remote stays ssh, an https
|
|
41
|
+
* remote stays https, a `.git` suffix stays or stays absent). A rewrite that
|
|
42
|
+
* normalized the form would change how the checkout authenticates.
|
|
43
|
+
*
|
|
44
|
+
* @param {string} url - The current remote url.
|
|
45
|
+
* @param {string} slug - The `owner/name` to point it at.
|
|
46
|
+
* @returns {string} The retargeted url.
|
|
47
|
+
* @throws {Error} When the url is not a GitHub remote (the caller parsed it already).
|
|
48
|
+
*/
|
|
49
|
+
function retargetRemoteUrl(url, slug) {
|
|
50
|
+
const current = parseRemoteUrl(url);
|
|
51
|
+
if (!current) {
|
|
52
|
+
throw new Error(`Cannot retarget a remote url that names no GitHub repo: ${String(url).trim() || '(none)'}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const trimmed = url.trim();
|
|
56
|
+
const currentSlug = `${current.owner}/${current.repo}`;
|
|
57
|
+
const at = trimmed.lastIndexOf(currentSlug);
|
|
58
|
+
|
|
59
|
+
return trimmed.slice(0, at) + slug + trimmed.slice(at + currentSlug.length);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A remote's url, as the checkout has it.
|
|
64
|
+
* @param {object} options
|
|
65
|
+
* @param {string} options.dir - The repository directory.
|
|
66
|
+
* @param {string} [options.remote] - Remote name (default: origin).
|
|
67
|
+
* @param {function} [options.execFn] - Injectable `(file, args, opts) => stdout` (tests).
|
|
68
|
+
* @returns {string} The trimmed url.
|
|
69
|
+
* @throws {Error} When git has no such remote, or the directory is no repository.
|
|
70
|
+
*/
|
|
71
|
+
function remoteUrl(options) {
|
|
72
|
+
const execFn = options.execFn || execFileSync;
|
|
73
|
+
const args = ['-C', options.dir, 'remote', 'get-url', options.remote || 'origin'];
|
|
74
|
+
|
|
75
|
+
return String(execFn('git', args, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] })).trim();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Point a remote at another url.
|
|
80
|
+
* @param {object} options
|
|
81
|
+
* @param {string} options.dir - The repository directory.
|
|
82
|
+
* @param {string} options.url - The url to set.
|
|
83
|
+
* @param {string} [options.remote] - Remote name (default: origin).
|
|
84
|
+
* @param {function} [options.execFn] - Injectable exec (tests).
|
|
85
|
+
* @returns {void}
|
|
86
|
+
* @throws {Error} When git refuses the write.
|
|
87
|
+
*/
|
|
88
|
+
function setRemoteUrl(options) {
|
|
89
|
+
const execFn = options.execFn || execFileSync;
|
|
90
|
+
const args = ['-C', options.dir, 'remote', 'set-url', options.remote || 'origin', options.url];
|
|
91
|
+
|
|
92
|
+
execFn('git', args, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* A brand's OWN `origin`, read and parsed: the one read the boot prelude, the
|
|
97
|
+
* manage walk and the deploy share
|
|
98
|
+
* ([#934](https://github.com/Omega-JS-Stack/omega/issues/934)).
|
|
99
|
+
*
|
|
100
|
+
* The `.git` must sit AT the directory, answered by one stat before git is
|
|
101
|
+
* asked anything: a brand nested in another repo (a fixture brand inside this
|
|
102
|
+
* monorepo, a target checked out under someone else's tree) has no origin of
|
|
103
|
+
* its own, and reading the ENCLOSING repo's would compare, heal or refuse on a
|
|
104
|
+
* repo the brand is not. No origin, and an origin that is not a GitHub remote,
|
|
105
|
+
* are expected conditions (a checkout nobody has pushed yet, a brand hosted
|
|
106
|
+
* elsewhere), so each answers its reason rather than throwing.
|
|
107
|
+
*
|
|
108
|
+
* @param {object} options
|
|
109
|
+
* @param {string} options.dir - The brand root.
|
|
110
|
+
* @param {function} [options.execFn] - Injectable `(file, args, opts) => stdout` (tests).
|
|
111
|
+
* @returns {{ url: string, owner: string, repo: string, slug: string }|{ reason: 'no-git'|'no-origin'|'foreign-remote' }}
|
|
112
|
+
*/
|
|
113
|
+
function readOrigin(options) {
|
|
114
|
+
if (!jetpack.exists(path.join(options.dir, '.git'))) return { reason: 'no-git' };
|
|
115
|
+
|
|
116
|
+
let url;
|
|
117
|
+
try {
|
|
118
|
+
url = remoteUrl({ dir: options.dir, execFn: options.execFn });
|
|
119
|
+
} catch (e) {
|
|
120
|
+
return { reason: 'no-origin' };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const parsed = parseRemoteUrl(url);
|
|
124
|
+
if (!parsed) return { reason: 'foreign-remote' };
|
|
125
|
+
|
|
126
|
+
return { url, owner: parsed.owner, repo: parsed.repo, slug: `${parsed.owner}/${parsed.repo}` };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Refuse when the brand's `origin` names another repo than the source repo its
|
|
131
|
+
* config derives ([#934](https://github.com/Omega-JS-Stack/omega/issues/934)).
|
|
132
|
+
* Called by the verbs that ACT on the derived repo (the manage walk's repo
|
|
133
|
+
* service, the deploy lane), which would otherwise ensure or push to a repo the
|
|
134
|
+
* checkout does not point at. The boot prelude only states the same line: it
|
|
135
|
+
* runs before every verb, and a fatal boot would lock out the verbs that fix it.
|
|
136
|
+
*
|
|
137
|
+
* Nothing to compare is no refusal: a brand with no origin of its own (see
|
|
138
|
+
* `readOrigin`), and a config that derives no source repo.
|
|
139
|
+
*
|
|
140
|
+
* @param {object} options
|
|
141
|
+
* @param {string} options.dir - The brand root.
|
|
142
|
+
* @param {object} [options.config] - The composed config. Omitted, the brand's
|
|
143
|
+
* PRODUCTION config is loaded from `dir` (the one every deploy address reads,
|
|
144
|
+
* [#856](https://github.com/Omega-JS-Stack/omega/issues/856)), and only once
|
|
145
|
+
* an origin exists to compare it with.
|
|
146
|
+
* @param {function} [options.execFn] - Injectable git exec (tests).
|
|
147
|
+
* @returns {object} The `readOrigin` answer it compared (or had nothing to
|
|
148
|
+
* compare with), so a caller that treats a missing origin its own way reads
|
|
149
|
+
* the reason without a second read.
|
|
150
|
+
* @throws {Error} The drift line, from `@omega.js/config`'s `repoDrift`.
|
|
151
|
+
*/
|
|
152
|
+
function assertOriginMatches(options) {
|
|
153
|
+
const origin = readOrigin(options);
|
|
154
|
+
if (!origin.slug) return origin;
|
|
155
|
+
|
|
156
|
+
const { loadConfig, repoDrift } = require('../config/index.js');
|
|
157
|
+
const config = options.config || loadConfig(options.dir, null, { environment: 'production' }).config;
|
|
158
|
+
const drift = repoDrift(origin.slug, config);
|
|
159
|
+
|
|
160
|
+
if (drift) throw new Error(drift);
|
|
161
|
+
|
|
162
|
+
return origin;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
module.exports = {
|
|
166
|
+
parseRemoteUrl,
|
|
167
|
+
retargetRemoteUrl,
|
|
168
|
+
remoteUrl,
|
|
169
|
+
setRemoteUrl,
|
|
170
|
+
readOrigin,
|
|
171
|
+
assertOriginMatches,
|
|
172
|
+
};
|
|
@@ -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
|
+
};
|