@scalebun-release/cli 2.5.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/bin/scalebun.js +2 -0
- package/lib/api/client.d.ts +52 -0
- package/lib/api/client.d.ts.map +1 -0
- package/lib/api/client.js +335 -0
- package/lib/api/client.js.map +1 -0
- package/lib/commands/ci-init.d.ts +3 -0
- package/lib/commands/ci-init.d.ts.map +1 -0
- package/lib/commands/ci-init.js +348 -0
- package/lib/commands/ci-init.js.map +1 -0
- package/lib/commands/doctor.d.ts +31 -0
- package/lib/commands/doctor.d.ts.map +1 -0
- package/lib/commands/doctor.js +559 -0
- package/lib/commands/doctor.js.map +1 -0
- package/lib/commands/launch.d.ts +3 -0
- package/lib/commands/launch.d.ts.map +1 -0
- package/lib/commands/launch.js +23 -0
- package/lib/commands/launch.js.map +1 -0
- package/lib/commands/link.d.ts +16 -0
- package/lib/commands/link.d.ts.map +1 -0
- package/lib/commands/link.js +164 -0
- package/lib/commands/link.js.map +1 -0
- package/lib/commands/login.d.ts +12 -0
- package/lib/commands/login.d.ts.map +1 -0
- package/lib/commands/login.js +124 -0
- package/lib/commands/login.js.map +1 -0
- package/lib/commands/migrate-codepush.d.ts +13 -0
- package/lib/commands/migrate-codepush.d.ts.map +1 -0
- package/lib/commands/migrate-codepush.js +98 -0
- package/lib/commands/migrate-codepush.js.map +1 -0
- package/lib/commands/ota-bundle.d.ts +22 -0
- package/lib/commands/ota-bundle.d.ts.map +1 -0
- package/lib/commands/ota-bundle.js +253 -0
- package/lib/commands/ota-bundle.js.map +1 -0
- package/lib/commands/ota-channels.d.ts +3 -0
- package/lib/commands/ota-channels.d.ts.map +1 -0
- package/lib/commands/ota-channels.js +231 -0
- package/lib/commands/ota-channels.js.map +1 -0
- package/lib/commands/ota-delete-bundle.d.ts +16 -0
- package/lib/commands/ota-delete-bundle.d.ts.map +1 -0
- package/lib/commands/ota-delete-bundle.js +96 -0
- package/lib/commands/ota-delete-bundle.js.map +1 -0
- package/lib/commands/ota-inspect.d.ts +3 -0
- package/lib/commands/ota-inspect.d.ts.map +1 -0
- package/lib/commands/ota-inspect.js +258 -0
- package/lib/commands/ota-inspect.js.map +1 -0
- package/lib/commands/ota-keys.d.ts +91 -0
- package/lib/commands/ota-keys.d.ts.map +1 -0
- package/lib/commands/ota-keys.js +765 -0
- package/lib/commands/ota-keys.js.map +1 -0
- package/lib/commands/ota-promote.d.ts +3 -0
- package/lib/commands/ota-promote.d.ts.map +1 -0
- package/lib/commands/ota-promote.js +194 -0
- package/lib/commands/ota-promote.js.map +1 -0
- package/lib/commands/ota-publish.d.ts +26 -0
- package/lib/commands/ota-publish.d.ts.map +1 -0
- package/lib/commands/ota-publish.js +892 -0
- package/lib/commands/ota-publish.js.map +1 -0
- package/lib/commands/ota-rollout.d.ts +3 -0
- package/lib/commands/ota-rollout.d.ts.map +1 -0
- package/lib/commands/ota-rollout.js +265 -0
- package/lib/commands/ota-rollout.js.map +1 -0
- package/lib/commands/ota-sign.d.ts +19 -0
- package/lib/commands/ota-sign.d.ts.map +1 -0
- package/lib/commands/ota-sign.js +245 -0
- package/lib/commands/ota-sign.js.map +1 -0
- package/lib/commands/push-apns.d.ts +3 -0
- package/lib/commands/push-apns.d.ts.map +1 -0
- package/lib/commands/push-apns.js +166 -0
- package/lib/commands/push-apns.js.map +1 -0
- package/lib/commands/quickstart.d.ts +4 -0
- package/lib/commands/quickstart.d.ts.map +1 -0
- package/lib/commands/quickstart.js +672 -0
- package/lib/commands/quickstart.js.map +1 -0
- package/lib/commands/shell.d.ts +28 -0
- package/lib/commands/shell.d.ts.map +1 -0
- package/lib/commands/shell.js +512 -0
- package/lib/commands/shell.js.map +1 -0
- package/lib/commands/tls-pins.d.ts +27 -0
- package/lib/commands/tls-pins.d.ts.map +1 -0
- package/lib/commands/tls-pins.js +209 -0
- package/lib/commands/tls-pins.js.map +1 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +233 -0
- package/lib/index.js.map +1 -0
- package/lib/ui/banner.d.ts +23 -0
- package/lib/ui/banner.d.ts.map +1 -0
- package/lib/ui/banner.js +162 -0
- package/lib/ui/banner.js.map +1 -0
- package/lib/ui/graphics.d.ts +18 -0
- package/lib/ui/graphics.d.ts.map +1 -0
- package/lib/ui/graphics.js +134 -0
- package/lib/ui/graphics.js.map +1 -0
- package/lib/ui/mark.d.ts +4 -0
- package/lib/ui/mark.d.ts.map +1 -0
- package/lib/ui/mark.js +21 -0
- package/lib/ui/mark.js.map +1 -0
- package/lib/ui/menu.d.ts +109 -0
- package/lib/ui/menu.d.ts.map +1 -0
- package/lib/ui/menu.js +314 -0
- package/lib/ui/menu.js.map +1 -0
- package/lib/ui/sixel.d.ts +6 -0
- package/lib/ui/sixel.d.ts.map +1 -0
- package/lib/ui/sixel.js +9 -0
- package/lib/ui/sixel.js.map +1 -0
- package/lib/utils/apps.d.ts +29 -0
- package/lib/utils/apps.d.ts.map +1 -0
- package/lib/utils/apps.js +19 -0
- package/lib/utils/apps.js.map +1 -0
- package/lib/utils/browser.d.ts +9 -0
- package/lib/utils/browser.d.ts.map +1 -0
- package/lib/utils/browser.js +39 -0
- package/lib/utils/browser.js.map +1 -0
- package/lib/utils/bundle.d.ts +153 -0
- package/lib/utils/bundle.d.ts.map +1 -0
- package/lib/utils/bundle.js +459 -0
- package/lib/utils/bundle.js.map +1 -0
- package/lib/utils/bundleStore.d.ts +128 -0
- package/lib/utils/bundleStore.d.ts.map +1 -0
- package/lib/utils/bundleStore.js +250 -0
- package/lib/utils/bundleStore.js.map +1 -0
- package/lib/utils/context.d.ts +88 -0
- package/lib/utils/context.d.ts.map +1 -0
- package/lib/utils/context.js +247 -0
- package/lib/utils/context.js.map +1 -0
- package/lib/utils/credentials.d.ts +59 -0
- package/lib/utils/credentials.d.ts.map +1 -0
- package/lib/utils/credentials.js +147 -0
- package/lib/utils/credentials.js.map +1 -0
- package/lib/utils/guide.d.ts +63 -0
- package/lib/utils/guide.d.ts.map +1 -0
- package/lib/utils/guide.js +191 -0
- package/lib/utils/guide.js.map +1 -0
- package/lib/utils/hash.d.ts +3 -0
- package/lib/utils/hash.d.ts.map +1 -0
- package/lib/utils/hash.js +44 -0
- package/lib/utils/hash.js.map +1 -0
- package/lib/utils/native-id.d.ts +17 -0
- package/lib/utils/native-id.d.ts.map +1 -0
- package/lib/utils/native-id.js +105 -0
- package/lib/utils/native-id.js.map +1 -0
- package/lib/utils/output.d.ts +96 -0
- package/lib/utils/output.d.ts.map +1 -0
- package/lib/utils/output.js +135 -0
- package/lib/utils/output.js.map +1 -0
- package/lib/utils/prompt.d.ts +106 -0
- package/lib/utils/prompt.d.ts.map +1 -0
- package/lib/utils/prompt.js +457 -0
- package/lib/utils/prompt.js.map +1 -0
- package/lib/utils/provenance.d.ts +30 -0
- package/lib/utils/provenance.d.ts.map +1 -0
- package/lib/utils/provenance.js +157 -0
- package/lib/utils/provenance.js.map +1 -0
- package/lib/utils/resolve.d.ts +81 -0
- package/lib/utils/resolve.d.ts.map +1 -0
- package/lib/utils/resolve.js +139 -0
- package/lib/utils/resolve.js.map +1 -0
- package/lib/utils/sdkBin.d.ts +18 -0
- package/lib/utils/sdkBin.d.ts.map +1 -0
- package/lib/utils/sdkBin.js +76 -0
- package/lib/utils/sdkBin.js.map +1 -0
- package/lib/utils/signer.d.ts +94 -0
- package/lib/utils/signer.d.ts.map +1 -0
- package/lib/utils/signer.js +230 -0
- package/lib/utils/signer.js.map +1 -0
- package/lib/utils/signing.d.ts +102 -0
- package/lib/utils/signing.d.ts.map +1 -0
- package/lib/utils/signing.js +229 -0
- package/lib/utils/signing.js.map +1 -0
- package/lib/utils/transportSecurity.d.ts +36 -0
- package/lib/utils/transportSecurity.d.ts.map +1 -0
- package/lib/utils/transportSecurity.js +74 -0
- package/lib/utils/transportSecurity.js.map +1 -0
- package/lib/utils/trustAnchor.d.ts +109 -0
- package/lib/utils/trustAnchor.d.ts.map +1 -0
- package/lib/utils/trustAnchor.js +169 -0
- package/lib/utils/trustAnchor.js.map +1 -0
- package/lib/utils/ui.d.ts +12 -0
- package/lib/utils/ui.d.ts.map +1 -0
- package/lib/utils/ui.js +60 -0
- package/lib/utils/ui.js.map +1 -0
- package/lib/utils/validate.d.ts +62 -0
- package/lib/utils/validate.d.ts.map +1 -0
- package/lib/utils/validate.js +189 -0
- package/lib/utils/validate.js.map +1 -0
- package/lib/vendor/ota-protocol/base64.d.ts +11 -0
- package/lib/vendor/ota-protocol/base64.d.ts.map +1 -0
- package/lib/vendor/ota-protocol/base64.js +39 -0
- package/lib/vendor/ota-protocol/base64.js.map +1 -0
- package/lib/vendor/ota-protocol/codec.d.ts +26 -0
- package/lib/vendor/ota-protocol/codec.d.ts.map +1 -0
- package/lib/vendor/ota-protocol/codec.js +349 -0
- package/lib/vendor/ota-protocol/codec.js.map +1 -0
- package/lib/vendor/ota-protocol/hex.d.ts +8 -0
- package/lib/vendor/ota-protocol/hex.d.ts.map +1 -0
- package/lib/vendor/ota-protocol/hex.js +31 -0
- package/lib/vendor/ota-protocol/hex.js.map +1 -0
- package/lib/vendor/ota-protocol/index.d.ts +17 -0
- package/lib/vendor/ota-protocol/index.d.ts.map +1 -0
- package/lib/vendor/ota-protocol/index.js +41 -0
- package/lib/vendor/ota-protocol/index.js.map +1 -0
- package/lib/vendor/ota-protocol/limits.d.ts +39 -0
- package/lib/vendor/ota-protocol/limits.d.ts.map +1 -0
- package/lib/vendor/ota-protocol/limits.js +42 -0
- package/lib/vendor/ota-protocol/limits.js.map +1 -0
- package/lib/vendor/ota-protocol/types.d.ts +73 -0
- package/lib/vendor/ota-protocol/types.d.ts.map +1 -0
- package/lib/vendor/ota-protocol/types.js +24 -0
- package/lib/vendor/ota-protocol/types.js.map +1 -0
- package/package.json +37 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* HTTPS enforcement for everything the CLI authenticates to.
|
|
4
|
+
*
|
|
5
|
+
* WHAT WAS WRONG. The API client picked its transport straight off the URL
|
|
6
|
+
* scheme — `parsed.protocol === 'https:' ? https : http` — with nothing
|
|
7
|
+
* requiring HTTPS in the first place, and every request attaches
|
|
8
|
+
* `Authorization: Bearer <token>`. `scalebun login` accepts an arbitrary
|
|
9
|
+
* `--api-url` and POSTs an email and password to it. So a CLI pointed at an
|
|
10
|
+
* `http://` URL — by a typo, a copied CI snippet, or a tampered config —
|
|
11
|
+
* would put a personal access token, or the user's actual password, on the
|
|
12
|
+
* wire in cleartext, with no warning and no failure.
|
|
13
|
+
*
|
|
14
|
+
* THE RULE. Credentials only travel over TLS. The single exception is
|
|
15
|
+
* loopback, because local development against `http://localhost:3000` is a
|
|
16
|
+
* real workflow and traffic that never leaves the machine cannot be
|
|
17
|
+
* intercepted on the network. Anything else is refused before a socket is
|
|
18
|
+
* opened, rather than warned about — a warning printed above a successful
|
|
19
|
+
* request is not a control.
|
|
20
|
+
*
|
|
21
|
+
* Loopback is matched on resolved literals, not on name. `localhost` and the
|
|
22
|
+
* reserved 127/8 and ::1 literals are loopback by definition; an arbitrary
|
|
23
|
+
* hostname that merely happens to resolve to 127.0.0.1 today is not, because
|
|
24
|
+
* whoever controls the DNS answer controls where the token goes tomorrow.
|
|
25
|
+
*/
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.InsecureApiUrlError = void 0;
|
|
28
|
+
exports.assertSecureApiUrl = assertSecureApiUrl;
|
|
29
|
+
/** Hostnames that are loopback by specification rather than by resolution. */
|
|
30
|
+
const LOOPBACK_HOSTNAMES = new Set(['localhost', '127.0.0.1', '::1', '[::1]']);
|
|
31
|
+
function isLoopback(hostname) {
|
|
32
|
+
const h = hostname.toLowerCase();
|
|
33
|
+
if (LOOPBACK_HOSTNAMES.has(h))
|
|
34
|
+
return true;
|
|
35
|
+
// The whole of 127.0.0.0/8 is loopback, not just 127.0.0.1.
|
|
36
|
+
return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(h);
|
|
37
|
+
}
|
|
38
|
+
class InsecureApiUrlError extends Error {
|
|
39
|
+
constructor(url, hostname) {
|
|
40
|
+
super(`Refusing to send credentials over an unencrypted connection to ${url}\n` +
|
|
41
|
+
` The CLI attaches your access token to every request, and "http://" would\n` +
|
|
42
|
+
` put it on the wire in cleartext where anyone on the network can read it.\n` +
|
|
43
|
+
`\n` +
|
|
44
|
+
` Use https://${hostname} instead.\n` +
|
|
45
|
+
` (Plain http:// is allowed only for localhost / 127.0.0.1 during local development.)`);
|
|
46
|
+
this.name = 'InsecureApiUrlError';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.InsecureApiUrlError = InsecureApiUrlError;
|
|
50
|
+
/**
|
|
51
|
+
* Validate an API base URL before it is used or persisted.
|
|
52
|
+
*
|
|
53
|
+
* @returns the normalised URL (trailing slashes stripped)
|
|
54
|
+
* @throws InsecureApiUrlError for a non-loopback `http://` URL
|
|
55
|
+
* @throws Error for a URL that is malformed or uses a non-HTTP scheme
|
|
56
|
+
*/
|
|
57
|
+
function assertSecureApiUrl(rawUrl) {
|
|
58
|
+
let parsed;
|
|
59
|
+
try {
|
|
60
|
+
parsed = new URL(rawUrl);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
throw new Error(`Not a valid API URL: ${rawUrl}`);
|
|
64
|
+
}
|
|
65
|
+
if (parsed.protocol === 'https:')
|
|
66
|
+
return rawUrl.replace(/\/+$/, '');
|
|
67
|
+
if (parsed.protocol === 'http:') {
|
|
68
|
+
if (isLoopback(parsed.hostname))
|
|
69
|
+
return rawUrl.replace(/\/+$/, '');
|
|
70
|
+
throw new InsecureApiUrlError(rawUrl, parsed.hostname);
|
|
71
|
+
}
|
|
72
|
+
throw new Error(`Unsupported API URL scheme "${parsed.protocol}" in ${rawUrl} — expected https://`);
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=transportSecurity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transportSecurity.js","sourceRoot":"","sources":["../../src/utils/transportSecurity.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;;;AAiCH,gDAkBC;AAjDD,8EAA8E;AAC9E,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;AAE/E,SAAS,UAAU,CAAC,QAAgB;IAClC,MAAM,CAAC,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;IACjC,IAAI,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3C,4DAA4D;IAC5D,OAAO,kCAAkC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpD,CAAC;AAED,MAAa,mBAAoB,SAAQ,KAAK;IAC5C,YAAY,GAAW,EAAE,QAAgB;QACvC,KAAK,CACH,kEAAkE,GAAG,IAAI;YACvE,8EAA8E;YAC9E,8EAA8E;YAC9E,IAAI;YACJ,iBAAiB,QAAQ,aAAa;YACtC,uFAAuF,CAC1F,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACpC,CAAC;CACF;AAZD,kDAYC;AAED;;;;;;GAMG;AACH,SAAgB,kBAAkB,CAAC,MAAc;IAC/C,IAAI,MAAW,CAAC;IAChB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,wBAAwB,MAAM,EAAE,CAAC,CAAC;IACpD,CAAC;IAED,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAEpE,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAChC,IAAI,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC;YAAE,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACnE,MAAM,IAAI,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IACzD,CAAC;IAED,MAAM,IAAI,KAAK,CACb,+BAA+B,MAAM,CAAC,QAAQ,QAAQ,MAAM,sBAAsB,CACnF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The native trust anchor: the public half of your signing key, compiled into
|
|
3
|
+
* the app so the device can refuse anything it did not authorise.
|
|
4
|
+
*
|
|
5
|
+
* Each platform reads a different file from a different place, and both fail
|
|
6
|
+
* the same way when it is missing — an empty registry, which means every
|
|
7
|
+
* release is refused with UNKNOWN_KEY_ID.
|
|
8
|
+
*
|
|
9
|
+
* Android android/app/src/main/assets/scalebun_ota_keys.json
|
|
10
|
+
* In `assets/`, NOT `res/raw/`. The registry reads it with
|
|
11
|
+
* `context.assets.open()`; a file in res/raw is invisible to that
|
|
12
|
+
* call, so enforcement is silently off — the app still runs, still
|
|
13
|
+
* updates, and simply verifies nothing.
|
|
14
|
+
*
|
|
15
|
+
* iOS ios/<App>/ScaleBunOtaKeys.json
|
|
16
|
+
* Read with `Bundle.main.url(forResource:withExtension:)`, so it
|
|
17
|
+
* must be a member of the app target's Copy Bundle Resources phase.
|
|
18
|
+
* Writing the file is not enough: Xcode ships only what the target
|
|
19
|
+
* references, and a file sitting beside the sources that nobody
|
|
20
|
+
* added is not in the .app at all. isInXcodeResources() below exists
|
|
21
|
+
* to catch exactly that.
|
|
22
|
+
*
|
|
23
|
+
* The filenames differ by platform, and the iOS one is `.json`. Earlier CLI
|
|
24
|
+
* output told people to add `ScaleBunOtaKeys.plist`, which no code has ever
|
|
25
|
+
* read — following it produced UNKNOWN_KEY_ID with nothing to point at.
|
|
26
|
+
*/
|
|
27
|
+
export interface TrustAnchor {
|
|
28
|
+
/**
|
|
29
|
+
* The PROJECT id, not the app id. The verifier compares this against the
|
|
30
|
+
* signed release's projectId and refuses on PROJECT_MISMATCH. Both ids are
|
|
31
|
+
* `cm…` cuids and are trivially confused, which is exactly why callers should
|
|
32
|
+
* be handed this value rather than asked for it.
|
|
33
|
+
*/
|
|
34
|
+
projectId: string;
|
|
35
|
+
keys: Array<{
|
|
36
|
+
keyId: string;
|
|
37
|
+
publicKey: string;
|
|
38
|
+
}>;
|
|
39
|
+
}
|
|
40
|
+
export declare const ANDROID_ANCHOR_RELATIVE: string;
|
|
41
|
+
/** `ScaleBunOtaKeyRegistry.defaultResource` + ".json", on the iOS side. */
|
|
42
|
+
export declare const IOS_ANCHOR_FILENAME = "ScaleBunOtaKeys.json";
|
|
43
|
+
export interface WriteAnchorResult {
|
|
44
|
+
path: string;
|
|
45
|
+
/** Key ids trusted after the write, in file order. */
|
|
46
|
+
keyIds: string[];
|
|
47
|
+
/** True when an existing anchor was read and its other keys preserved. */
|
|
48
|
+
merged: boolean;
|
|
49
|
+
}
|
|
50
|
+
export interface WriteIosAnchorResult extends WriteAnchorResult {
|
|
51
|
+
/**
|
|
52
|
+
* False when project.pbxproj does not mention the file yet.
|
|
53
|
+
*
|
|
54
|
+
* This is the iOS-specific trap, and the reason the flag exists: the anchor
|
|
55
|
+
* can be written perfectly and still not ship, because Xcode copies only what
|
|
56
|
+
* the target references. The device then finds no resource, builds an empty
|
|
57
|
+
* registry, and refuses every release with UNKNOWN_KEY_ID — while a
|
|
58
|
+
* correct-looking file sits right there in the source tree.
|
|
59
|
+
*/
|
|
60
|
+
inXcodeResources: boolean;
|
|
61
|
+
}
|
|
62
|
+
export declare function writeAndroidTrustAnchor(opts: {
|
|
63
|
+
cwd: string;
|
|
64
|
+
projectId: string;
|
|
65
|
+
keyId: string;
|
|
66
|
+
publicKeyPem: string;
|
|
67
|
+
}): WriteAnchorResult;
|
|
68
|
+
/** `ios/<App>.xcodeproj`, or null when there is no iOS project here. */
|
|
69
|
+
export declare function findXcodeProject(cwd: string): string | null;
|
|
70
|
+
/**
|
|
71
|
+
* The folder the app's own sources live in — `ios/<App>/`.
|
|
72
|
+
*
|
|
73
|
+
* Xcode groups mirror the filesystem here by React Native convention, so that
|
|
74
|
+
* is where a new resource belongs if it is going to be easy to add to the
|
|
75
|
+
* target. Falls back to whichever folder holds the AppDelegate, for projects
|
|
76
|
+
* renamed away from their directory.
|
|
77
|
+
*/
|
|
78
|
+
export declare function iosAppSourceDir(cwd: string): string | null;
|
|
79
|
+
/**
|
|
80
|
+
* Is the anchor already referenced by the Xcode project?
|
|
81
|
+
*
|
|
82
|
+
* A substring check over project.pbxproj rather than a real parse: the question
|
|
83
|
+
* is only "has anyone added this filename", and erring toward "no" costs one
|
|
84
|
+
* extra printing of the instructions.
|
|
85
|
+
*/
|
|
86
|
+
export declare function isInXcodeResources(cwd: string): boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Add a key to the iOS trust anchor, creating the file if needed.
|
|
89
|
+
*
|
|
90
|
+
* Writes into `ios/<App>/` and reports whether the project references it yet.
|
|
91
|
+
* Adding it to the target is left to the caller: that means editing
|
|
92
|
+
* project.pbxproj, and a corrupted pbxproj costs far more than the single drag
|
|
93
|
+
* it would save.
|
|
94
|
+
*/
|
|
95
|
+
export declare function writeIosTrustAnchor(opts: {
|
|
96
|
+
cwd: string;
|
|
97
|
+
projectId: string;
|
|
98
|
+
keyId: string;
|
|
99
|
+
publicKeyPem: string;
|
|
100
|
+
}): WriteIosAnchorResult;
|
|
101
|
+
/**
|
|
102
|
+
* Does this look like a native Android project we can write into? Used to skip
|
|
103
|
+
* the anchor with a clear message instead of scattering directories into
|
|
104
|
+
* whatever folder the user happened to run from.
|
|
105
|
+
*/
|
|
106
|
+
export declare function hasAndroidProject(cwd: string): boolean;
|
|
107
|
+
/** The iOS counterpart: an `ios/` folder with an actual Xcode project in it. */
|
|
108
|
+
export declare function hasIosProject(cwd: string): boolean;
|
|
109
|
+
//# sourceMappingURL=trustAnchor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trustAnchor.d.ts","sourceRoot":"","sources":["../../src/utils/trustAnchor.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,WAAW;IAC1B;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACnD;AAED,eAAO,MAAM,uBAAuB,QAOnC,CAAC;AAEF,2EAA2E;AAC3E,eAAO,MAAM,mBAAmB,yBAAyB,CAAC;AAE1D,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,0EAA0E;IAC1E,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,oBAAqB,SAAQ,iBAAiB;IAC7D;;;;;;;;OAQG;IACH,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AA2CD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,iBAAiB,CAEpB;AAED,wEAAwE;AACxE,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAK3D;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAgB1D;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAUvD;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,oBAAoB,CASvB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED,gFAAgF;AAChF,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAElD"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.IOS_ANCHOR_FILENAME = exports.ANDROID_ANCHOR_RELATIVE = void 0;
|
|
37
|
+
exports.writeAndroidTrustAnchor = writeAndroidTrustAnchor;
|
|
38
|
+
exports.findXcodeProject = findXcodeProject;
|
|
39
|
+
exports.iosAppSourceDir = iosAppSourceDir;
|
|
40
|
+
exports.isInXcodeResources = isInXcodeResources;
|
|
41
|
+
exports.writeIosTrustAnchor = writeIosTrustAnchor;
|
|
42
|
+
exports.hasAndroidProject = hasAndroidProject;
|
|
43
|
+
exports.hasIosProject = hasIosProject;
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
exports.ANDROID_ANCHOR_RELATIVE = path.join('android', 'app', 'src', 'main', 'assets', 'scalebun_ota_keys.json');
|
|
47
|
+
/** `ScaleBunOtaKeyRegistry.defaultResource` + ".json", on the iOS side. */
|
|
48
|
+
exports.IOS_ANCHOR_FILENAME = 'ScaleBunOtaKeys.json';
|
|
49
|
+
/**
|
|
50
|
+
* Add a key to an anchor file, creating it if needed.
|
|
51
|
+
*
|
|
52
|
+
* Merges rather than overwrites: during a rotation two keys are valid at once,
|
|
53
|
+
* and clobbering the old one strands every device still running the previous
|
|
54
|
+
* build — they would refuse the next release and have no way back. A key with
|
|
55
|
+
* the same keyId is replaced in place so re-running is idempotent.
|
|
56
|
+
*/
|
|
57
|
+
function writeAnchorAt(anchorPath, opts) {
|
|
58
|
+
fs.mkdirSync(path.dirname(anchorPath), { recursive: true });
|
|
59
|
+
let keys = [];
|
|
60
|
+
let merged = false;
|
|
61
|
+
if (fs.existsSync(anchorPath)) {
|
|
62
|
+
try {
|
|
63
|
+
const existing = JSON.parse(fs.readFileSync(anchorPath, 'utf8'));
|
|
64
|
+
if (Array.isArray(existing?.keys)) {
|
|
65
|
+
keys = existing.keys.filter((k) => k && typeof k.keyId === 'string' && typeof k.publicKey === 'string');
|
|
66
|
+
merged = keys.length > 0;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Unreadable or hand-mangled: start clean rather than fail setup. The
|
|
71
|
+
// file is regenerable — the private key is the irreplaceable half.
|
|
72
|
+
keys = [];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
keys = keys.filter((k) => k.keyId !== opts.keyId);
|
|
76
|
+
keys.push({ keyId: opts.keyId, publicKey: opts.publicKeyPem.trim() });
|
|
77
|
+
const anchor = { projectId: opts.projectId, keys };
|
|
78
|
+
fs.writeFileSync(anchorPath, JSON.stringify(anchor, null, 2) + '\n');
|
|
79
|
+
return { path: anchorPath, keyIds: keys.map((k) => k.keyId), merged };
|
|
80
|
+
}
|
|
81
|
+
function writeAndroidTrustAnchor(opts) {
|
|
82
|
+
return writeAnchorAt(path.join(opts.cwd, exports.ANDROID_ANCHOR_RELATIVE), opts);
|
|
83
|
+
}
|
|
84
|
+
/** `ios/<App>.xcodeproj`, or null when there is no iOS project here. */
|
|
85
|
+
function findXcodeProject(cwd) {
|
|
86
|
+
const iosDir = path.join(cwd, 'ios');
|
|
87
|
+
if (!fs.existsSync(iosDir))
|
|
88
|
+
return null;
|
|
89
|
+
const proj = fs.readdirSync(iosDir).find((name) => name.endsWith('.xcodeproj'));
|
|
90
|
+
return proj ? path.join(iosDir, proj) : null;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The folder the app's own sources live in — `ios/<App>/`.
|
|
94
|
+
*
|
|
95
|
+
* Xcode groups mirror the filesystem here by React Native convention, so that
|
|
96
|
+
* is where a new resource belongs if it is going to be easy to add to the
|
|
97
|
+
* target. Falls back to whichever folder holds the AppDelegate, for projects
|
|
98
|
+
* renamed away from their directory.
|
|
99
|
+
*/
|
|
100
|
+
function iosAppSourceDir(cwd) {
|
|
101
|
+
const proj = findXcodeProject(cwd);
|
|
102
|
+
if (!proj)
|
|
103
|
+
return null;
|
|
104
|
+
const iosDir = path.join(cwd, 'ios');
|
|
105
|
+
const byName = path.join(iosDir, path.basename(proj, '.xcodeproj'));
|
|
106
|
+
if (fs.existsSync(byName) && fs.statSync(byName).isDirectory())
|
|
107
|
+
return byName;
|
|
108
|
+
for (const entry of fs.readdirSync(iosDir)) {
|
|
109
|
+
const dir = path.join(iosDir, entry);
|
|
110
|
+
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory())
|
|
111
|
+
continue;
|
|
112
|
+
const hasDelegate = fs
|
|
113
|
+
.readdirSync(dir)
|
|
114
|
+
.some((f) => /^AppDelegate\.(swift|mm?|h)$/.test(f));
|
|
115
|
+
if (hasDelegate)
|
|
116
|
+
return dir;
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Is the anchor already referenced by the Xcode project?
|
|
122
|
+
*
|
|
123
|
+
* A substring check over project.pbxproj rather than a real parse: the question
|
|
124
|
+
* is only "has anyone added this filename", and erring toward "no" costs one
|
|
125
|
+
* extra printing of the instructions.
|
|
126
|
+
*/
|
|
127
|
+
function isInXcodeResources(cwd) {
|
|
128
|
+
const proj = findXcodeProject(cwd);
|
|
129
|
+
if (!proj)
|
|
130
|
+
return false;
|
|
131
|
+
const pbx = path.join(proj, 'project.pbxproj');
|
|
132
|
+
if (!fs.existsSync(pbx))
|
|
133
|
+
return false;
|
|
134
|
+
try {
|
|
135
|
+
return fs.readFileSync(pbx, 'utf8').includes(exports.IOS_ANCHOR_FILENAME);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Add a key to the iOS trust anchor, creating the file if needed.
|
|
143
|
+
*
|
|
144
|
+
* Writes into `ios/<App>/` and reports whether the project references it yet.
|
|
145
|
+
* Adding it to the target is left to the caller: that means editing
|
|
146
|
+
* project.pbxproj, and a corrupted pbxproj costs far more than the single drag
|
|
147
|
+
* it would save.
|
|
148
|
+
*/
|
|
149
|
+
function writeIosTrustAnchor(opts) {
|
|
150
|
+
const dir = iosAppSourceDir(opts.cwd);
|
|
151
|
+
if (!dir) {
|
|
152
|
+
throw new Error('No ios/<App> source directory found — is this a React Native project with a native iOS app?');
|
|
153
|
+
}
|
|
154
|
+
const res = writeAnchorAt(path.join(dir, exports.IOS_ANCHOR_FILENAME), opts);
|
|
155
|
+
return { ...res, inXcodeResources: isInXcodeResources(opts.cwd) };
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Does this look like a native Android project we can write into? Used to skip
|
|
159
|
+
* the anchor with a clear message instead of scattering directories into
|
|
160
|
+
* whatever folder the user happened to run from.
|
|
161
|
+
*/
|
|
162
|
+
function hasAndroidProject(cwd) {
|
|
163
|
+
return fs.existsSync(path.join(cwd, 'android', 'app'));
|
|
164
|
+
}
|
|
165
|
+
/** The iOS counterpart: an `ios/` folder with an actual Xcode project in it. */
|
|
166
|
+
function hasIosProject(cwd) {
|
|
167
|
+
return iosAppSourceDir(cwd) !== null;
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=trustAnchor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trustAnchor.js","sourceRoot":"","sources":["../../src/utils/trustAnchor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The status box as a string.
|
|
3
|
+
*
|
|
4
|
+
* The interactive session redraws the whole screen on every keypress, and it
|
|
5
|
+
* does that with ONE write — a sequence of console.log calls would tear
|
|
6
|
+
* visibly as the terminal painted each line. Returning text lets the session
|
|
7
|
+
* compose a frame; `renderStatus` below keeps the print-and-exit path
|
|
8
|
+
* unchanged, so the two can never drift.
|
|
9
|
+
*/
|
|
10
|
+
export declare function statusText(): string;
|
|
11
|
+
export declare function renderStatus(): void;
|
|
12
|
+
//# sourceMappingURL=ui.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../src/utils/ui.ts"],"names":[],"mappings":"AAYA;;;;;;;;GAQG;AACH,wBAAgB,UAAU,IAAI,MAAM,CA8BnC;AAED,wBAAgB,YAAY,IAAI,IAAI,CAEnC"}
|
package/lib/utils/ui.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.statusText = statusText;
|
|
7
|
+
exports.renderStatus = renderStatus;
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const credentials_1 = require("./credentials");
|
|
10
|
+
// NOTE: the splash art (mark + wordmark) now lives in ../ui/banner.ts. This file
|
|
11
|
+
// keeps only the status box and command table — the body of the no-arg command.
|
|
12
|
+
// Column arithmetic must measure the RENDERED width, not the string length —
|
|
13
|
+
// coloured content carries ANSI escapes that take zero columns. Strip them first,
|
|
14
|
+
// or the right border lands on a different column on every row.
|
|
15
|
+
const STRIP_ANSI = /\x1b\[[0-9;]*m/g;
|
|
16
|
+
const visibleLen = (s) => s.replace(STRIP_ANSI, '').length;
|
|
17
|
+
/**
|
|
18
|
+
* The status box as a string.
|
|
19
|
+
*
|
|
20
|
+
* The interactive session redraws the whole screen on every keypress, and it
|
|
21
|
+
* does that with ONE write — a sequence of console.log calls would tear
|
|
22
|
+
* visibly as the terminal painted each line. Returning text lets the session
|
|
23
|
+
* compose a frame; `renderStatus` below keeps the print-and-exit path
|
|
24
|
+
* unchanged, so the two can never drift.
|
|
25
|
+
*/
|
|
26
|
+
function statusText() {
|
|
27
|
+
const out = [];
|
|
28
|
+
const creds = (0, credentials_1.readCredentials)();
|
|
29
|
+
const verbose = process.argv.slice(2).includes('--verbose');
|
|
30
|
+
const inner = 58;
|
|
31
|
+
const border = (l, r) => out.push(chalk_1.default.gray(l + '─'.repeat(inner + 2) + r));
|
|
32
|
+
const row = (content) => {
|
|
33
|
+
const pad = ' '.repeat(Math.max(0, inner - visibleLen(content)));
|
|
34
|
+
out.push(`${chalk_1.default.gray('│')} ${content}${pad} ${chalk_1.default.gray('│')}`);
|
|
35
|
+
};
|
|
36
|
+
border('┌', '┐');
|
|
37
|
+
if (creds) {
|
|
38
|
+
row(chalk_1.default.bold.green('● AUTHENTICATED'));
|
|
39
|
+
// The account you're signed in as (captured at login; absent for pre-1.10.1
|
|
40
|
+
// logins and the CI env-var path).
|
|
41
|
+
if (creds.email)
|
|
42
|
+
row(chalk_1.default.dim(creds.email));
|
|
43
|
+
// Show the API target only when it is NOT the default production backend
|
|
44
|
+
// (someone pointed at staging / self-hosted / localhost needs to see where
|
|
45
|
+
// their deploy is going), or under --verbose. On production it is noise.
|
|
46
|
+
if (verbose || creds.apiUrl !== credentials_1.DEFAULT_API_URL) {
|
|
47
|
+
row(`${chalk_1.default.dim('API')} ${chalk_1.default.cyan(creds.apiUrl)}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
row(`${chalk_1.default.bold.yellow('○ NOT LOGGED IN')} ${chalk_1.default.dim('· run')} ${chalk_1.default.cyan('scalebun login')}`);
|
|
52
|
+
}
|
|
53
|
+
border('└', '┘');
|
|
54
|
+
out.push('');
|
|
55
|
+
return out.join('\n') + '\n';
|
|
56
|
+
}
|
|
57
|
+
function renderStatus() {
|
|
58
|
+
process.stdout.write(statusText());
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=ui.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../../src/utils/ui.ts"],"names":[],"mappings":";;;;;AAqBA,gCA8BC;AAED,oCAEC;AAvDD,kDAA0B;AAC1B,+CAAiE;AAEjE,iFAAiF;AACjF,gFAAgF;AAEhF,6EAA6E;AAC7E,kFAAkF;AAClF,gEAAgE;AAChE,MAAM,UAAU,GAAG,iBAAiB,CAAC;AACrC,MAAM,UAAU,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC;AAE3E;;;;;;;;GAQG;AACH,SAAgB,UAAU;IACxB,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,MAAM,KAAK,GAAG,IAAA,6BAAe,GAAE,CAAC;IAChC,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC5D,MAAM,KAAK,GAAG,EAAE,CAAC;IAEjB,MAAM,MAAM,GAAG,CAAC,CAAS,EAAE,CAAS,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,eAAK,CAAC,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAC7F,MAAM,GAAG,GAAG,CAAC,OAAe,EAAE,EAAE;QAC9B,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACjE,GAAG,CAAC,IAAI,CAAC,GAAG,eAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,OAAO,GAAG,GAAG,IAAI,eAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACrE,CAAC,CAAC;IAEF,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACjB,IAAI,KAAK,EAAE,CAAC;QACV,GAAG,CAAC,eAAK,CAAC,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC;QACzC,4EAA4E;QAC5E,mCAAmC;QACnC,IAAI,KAAK,CAAC,KAAK;YAAE,GAAG,CAAC,eAAK,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QAC7C,yEAAyE;QACzE,2EAA2E;QAC3E,yEAAyE;QACzE,IAAI,OAAO,IAAI,KAAK,CAAC,MAAM,KAAK,6BAAe,EAAE,CAAC;YAChD,GAAG,CAAC,GAAG,eAAK,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,eAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACzD,CAAC;IACH,CAAC;SAAM,CAAC;QACN,GAAG,CAAC,GAAG,eAAK,CAAC,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC,IAAI,eAAK,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,eAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;IACvG,CAAC;IACD,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACjB,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACb,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAC/B,CAAC;AAED,SAAgB,YAAY;IAC1B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC;AACrC,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** The allowed value closest to `raw`, when one is close enough to mean it. */
|
|
2
|
+
export declare function nearest(raw: string, allowed: readonly string[]): string | null;
|
|
3
|
+
export interface EnumOpts {
|
|
4
|
+
/** Accept any letter case, normalising to the allowed spelling. */
|
|
5
|
+
caseInsensitive?: boolean;
|
|
6
|
+
/** Extra guidance printed under the error. */
|
|
7
|
+
details?: string[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* One of `allowed`, or exit.
|
|
11
|
+
*
|
|
12
|
+
* Replaces `if (opts.x === 'known')` chains, where an unrecognised value is
|
|
13
|
+
* indistinguishable from the flag not being passed at all.
|
|
14
|
+
*/
|
|
15
|
+
export declare function checkedEnum<T extends string>(flag: string, raw: string, allowed: readonly T[], opts?: EnumOpts): T;
|
|
16
|
+
export interface IntOpts {
|
|
17
|
+
min?: number;
|
|
18
|
+
max?: number;
|
|
19
|
+
/** Used when the flag was omitted entirely. Never used to paper over junk. */
|
|
20
|
+
fallback?: number;
|
|
21
|
+
details?: string[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A whole number in range, or exit.
|
|
25
|
+
*
|
|
26
|
+
* `Number('12abc')` is NaN and `Number('')` is 0 — both of which a `|| default`
|
|
27
|
+
* turns into a plausible-looking value the user never asked for. Everything
|
|
28
|
+
* that is not cleanly an integer is refused here instead.
|
|
29
|
+
*/
|
|
30
|
+
export declare function checkedInt(flag: string, raw: unknown, opts?: IntOpts): number;
|
|
31
|
+
/**
|
|
32
|
+
* A rollout percentage, 0-100.
|
|
33
|
+
*
|
|
34
|
+
* A trailing `%` is accepted because it is the obvious thing to type for a
|
|
35
|
+
* value the help text calls a percentage, and refusing it teaches nothing.
|
|
36
|
+
*/
|
|
37
|
+
export declare function checkedPercent(flag: string, raw: unknown, fallback?: number): number;
|
|
38
|
+
/**
|
|
39
|
+
* An identifier, or exit.
|
|
40
|
+
*
|
|
41
|
+
* Catches the two ways ids arrive broken: an empty value from an unquoted
|
|
42
|
+
* shell variable that did not expand (`--app-id $APP` with APP unset becomes
|
|
43
|
+
* `--app-id` swallowing the NEXT flag), and a value with whitespace or a
|
|
44
|
+
* leading dash from a mis-quoted paste. Both otherwise reach the API and come
|
|
45
|
+
* back as a confusing 404.
|
|
46
|
+
*/
|
|
47
|
+
export declare function checkedId(flag: string, raw: unknown): string;
|
|
48
|
+
/**
|
|
49
|
+
* A semver range the server will accept, or exit.
|
|
50
|
+
*
|
|
51
|
+
* Deliberately loose. Ranges legitimately look like `1.0.0`, `^1.0.0`,
|
|
52
|
+
* `>=1.0.0`, `1.x`, `>=1.0.0 <2.0.0` and `*`, and a strict grammar here would
|
|
53
|
+
* reject valid input the API would have taken. The check is only for the
|
|
54
|
+
* shapes that are certainly wrong — empty, or containing no version at all —
|
|
55
|
+
* because those come back as a 400 describing a field the user never typed.
|
|
56
|
+
*/
|
|
57
|
+
export declare function checkedVersionRange(flag: string, raw: unknown): string;
|
|
58
|
+
/** Non-empty free text, or exit. Trims, so a whitespace-only value is empty. */
|
|
59
|
+
export declare function checkedText(flag: string, raw: unknown, opts?: {
|
|
60
|
+
max?: number;
|
|
61
|
+
}): string;
|
|
62
|
+
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/utils/validate.ts"],"names":[],"mappings":"AAoDA,+EAA+E;AAC/E,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CAc9E;AAED,MAAM,WAAW,QAAQ;IACvB,mEAAmE;IACnE,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,8CAA8C;IAC9C,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,EAC1C,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,SAAS,CAAC,EAAE,EACrB,IAAI,GAAE,QAAa,GAClB,CAAC,CAcH;AAED,MAAM,WAAW,OAAO;IACtB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,GAAE,OAAY,GAAG,MAAM,CAgCjF;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAQpF;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,MAAM,CAgB5D;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,MAAM,CAiBtE;AAED,gFAAgF;AAChF,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,MAAM,CAO3F"}
|