@sprigr/cli 0.1.2

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builds.js","sourceRoot":"","sources":["../../src/commands/builds.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import { BuildSummary } from '../api.js';
2
+ export interface DeployArgs {
3
+ siteId: string;
4
+ dir: string;
5
+ framework?: 'static' | 'next' | 'astro' | 'remix';
6
+ endpoint: string;
7
+ apiKey: string;
8
+ /** Polling interval in milliseconds — default 2000. */
9
+ pollIntervalMs?: number;
10
+ /** Polling ceiling in milliseconds — default 30 minutes. */
11
+ pollCeilingMs?: number;
12
+ /** Optional sink for human-readable progress lines. Defaults to console.log. */
13
+ log?: (line: string) => void;
14
+ /** Override fetch (tests). */
15
+ fetchImpl?: typeof fetch;
16
+ /** Override sleep (tests). */
17
+ sleep?: (ms: number) => Promise<void>;
18
+ }
19
+ /**
20
+ * `sprigr deploy` — bundle a directory, POST to start-build, poll until
21
+ * the build reaches a terminal status.
22
+ *
23
+ * Exit codes for the bin wrapper to forward to `process.exit`:
24
+ * 0 succeeded
25
+ * 1 failed | cancelled | API error | bundling error
26
+ * 2 invalid arguments / config (caller surfaces before invoking deploy)
27
+ * 124 polling ceiling reached without terminal status
28
+ */
29
+ export declare function runDeploy(args: DeployArgs): Promise<{
30
+ exitCode: number;
31
+ build?: BuildSummary;
32
+ }>;
@@ -0,0 +1,96 @@
1
+ import { ApiClient, ApiError } from '../api.js';
2
+ import { bundleDirectory } from '../bundler.js';
3
+ const TERMINAL = new Set([
4
+ 'succeeded',
5
+ 'failed',
6
+ 'cancelled',
7
+ 'timeout',
8
+ ]);
9
+ /**
10
+ * `sprigr deploy` — bundle a directory, POST to start-build, poll until
11
+ * the build reaches a terminal status.
12
+ *
13
+ * Exit codes for the bin wrapper to forward to `process.exit`:
14
+ * 0 succeeded
15
+ * 1 failed | cancelled | API error | bundling error
16
+ * 2 invalid arguments / config (caller surfaces before invoking deploy)
17
+ * 124 polling ceiling reached without terminal status
18
+ */
19
+ export async function runDeploy(args) {
20
+ const log = args.log ?? ((line) => console.log(line));
21
+ const sleep = args.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
22
+ log(`bundling ${args.dir} ...`);
23
+ const bundle = await bundleDirectory(args.dir, args.framework ?? 'static');
24
+ if ('errors' in bundle) {
25
+ for (const err of bundle.errors)
26
+ log(` error: ${err}`);
27
+ return { exitCode: 1 };
28
+ }
29
+ const binaryNote = bundle.binaryCount > 0 ? ` (${bundle.binaryCount} binary)` : '';
30
+ log(` ${bundle.fileCount} files${binaryNote}, ${formatBytes(bundle.totalBytes)}`);
31
+ const client = new ApiClient({ endpoint: args.endpoint, apiKey: args.apiKey, fetchImpl: args.fetchImpl });
32
+ log(`starting build for ${args.siteId} ...`);
33
+ let buildId;
34
+ try {
35
+ const start = await client.startBuild({
36
+ siteId: args.siteId,
37
+ files: bundle.files,
38
+ framework: args.framework ?? 'static',
39
+ source: 'cli',
40
+ });
41
+ buildId = start.buildId;
42
+ log(` build ${buildId} (status=${start.status})`);
43
+ }
44
+ catch (err) {
45
+ if (err instanceof ApiError) {
46
+ log(` failed to start: ${err.status} ${err.message}`);
47
+ if (err.status === 402) {
48
+ log(` plan limit reached — visit the portal to upgrade or check usage.`);
49
+ }
50
+ }
51
+ else {
52
+ log(` failed to start: ${err instanceof Error ? err.message : String(err)}`);
53
+ }
54
+ return { exitCode: 1 };
55
+ }
56
+ const intervalMs = args.pollIntervalMs ?? 2_000;
57
+ const ceilingMs = args.pollCeilingMs ?? 30 * 60_000;
58
+ const deadline = Date.now() + ceilingMs;
59
+ let lastStatus = null;
60
+ while (Date.now() < deadline) {
61
+ let build;
62
+ try {
63
+ const detail = await client.getBuild(args.siteId, buildId);
64
+ build = detail.build;
65
+ }
66
+ catch (err) {
67
+ // Transient — log and retry. ApiError 5xx should not abort the poll.
68
+ log(` poll failed (${err instanceof Error ? err.message : String(err)}), retrying...`);
69
+ await sleep(intervalMs);
70
+ continue;
71
+ }
72
+ if (build.status !== lastStatus) {
73
+ log(` status: ${build.status}`);
74
+ lastStatus = build.status;
75
+ }
76
+ if (TERMINAL.has(build.status)) {
77
+ if (build.status === 'succeeded') {
78
+ log(` deployment ${build.deploymentId} (container ${build.containerSeconds ?? 0}s, ${build.durationMs ?? 0}ms)`);
79
+ return { exitCode: 0, build };
80
+ }
81
+ log(` build did not succeed: ${build.status}${build.errorSummary ? ` — ${build.errorSummary}` : ''}`);
82
+ return { exitCode: 1, build };
83
+ }
84
+ await sleep(intervalMs);
85
+ }
86
+ log(` timed out after ${(ceilingMs / 60_000).toFixed(0)} minutes — build is still running. Inspect with: sprigr builds get ${args.siteId} ${buildId}`);
87
+ return { exitCode: 124 };
88
+ }
89
+ function formatBytes(n) {
90
+ if (n < 1024)
91
+ return `${n} B`;
92
+ if (n < 1024 * 1024)
93
+ return `${(n / 1024).toFixed(1)} KiB`;
94
+ return `${(n / 1024 / 1024).toFixed(2)} MiB`;
95
+ }
96
+ //# sourceMappingURL=deploy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deploy.js","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * `sprigr login` — RFC 8628 device authorization grant + PKCE (S256).
3
+ *
4
+ * Flow:
5
+ * 1. Generate a random code_verifier (43..128 chars from the RFC 7636
6
+ * character set). Hash it with SHA-256 and base64url-encode →
7
+ * that's the code_challenge we send to the server.
8
+ * 2. POST /api/v1/cli/device/start with the challenge + a clientName.
9
+ * Server responds with deviceCode + userCode + verification URLs +
10
+ * poll interval.
11
+ * 3. Print the user-friendly URL. Try to open it in a browser.
12
+ * 4. Poll /api/v1/cli/device/poll with the deviceCode + verifier.
13
+ * Server hashes the verifier on receipt; PKCE means a stolen
14
+ * deviceCode without the verifier is useless to an attacker.
15
+ * While the device-code row is `pending`, we keep polling.
16
+ * Once `approved`, the server mints a fresh sk_mcp_* key and
17
+ * returns it.
18
+ * 5. Write the key to ~/.config/sprigr/credentials.json (mode 0600).
19
+ *
20
+ * Exit codes: 0 success, 1 user-cancelled / failure, 124 timeout.
21
+ */
22
+ export interface LoginArgs {
23
+ endpoint: string;
24
+ /**
25
+ * Named profile to write credentials under. When set, the credentials
26
+ * land at `~/.config/sprigr/credentials/<profile>.json` instead of the
27
+ * legacy single-file `credentials.json`, so multiple tenants can be
28
+ * logged in side-by-side. When omitted, behaves identically to the
29
+ * pre-profile CLI.
30
+ */
31
+ profile?: string;
32
+ /** Don't try to open the browser; just print the URL. Useful in CI / SSH. */
33
+ noBrowser?: boolean;
34
+ /** Override fetch (tests). */
35
+ fetchImpl?: typeof fetch;
36
+ /** Override sleep (tests). */
37
+ sleep?: (ms: number) => Promise<void>;
38
+ /** Override stdout writer (tests). */
39
+ log?: (line: string) => void;
40
+ /** Override the user-facing client identifier. Default 'sprigr-cli/<version>'. */
41
+ clientName?: string;
42
+ /** Polling ceiling in milliseconds — default 11 minutes (just past the 10-min code TTL). */
43
+ pollCeilingMs?: number;
44
+ }
45
+ export declare function runLogin(args: LoginArgs): Promise<{
46
+ exitCode: number;
47
+ credentialPath?: string;
48
+ }>;
49
+ export declare function runLogout(args?: {
50
+ log?: (line: string) => void;
51
+ profile?: string;
52
+ }): Promise<{
53
+ exitCode: number;
54
+ }>;
@@ -0,0 +1,192 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { credentialsFilePath, deleteCredentials, writeCredentials } from '../auth.js';
3
+ const RFC7636_VERIFIER_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
4
+ function randomVerifier() {
5
+ // 64 chars from the unreserved-character set. Hits the 43..128 range
6
+ // RFC 7636 §4.1 requires.
7
+ const bytes = new Uint8Array(64);
8
+ // Node 20+ exposes globalThis.crypto with getRandomValues.
9
+ globalThis.crypto.getRandomValues(bytes);
10
+ let out = '';
11
+ for (let i = 0; i < bytes.length; i++) {
12
+ out += RFC7636_VERIFIER_CHARS[bytes[i] % RFC7636_VERIFIER_CHARS.length];
13
+ }
14
+ return out;
15
+ }
16
+ function base64UrlEncode(buf) {
17
+ const bytes = new Uint8Array(buf);
18
+ let s = '';
19
+ for (let i = 0; i < bytes.length; i++)
20
+ s += String.fromCharCode(bytes[i]);
21
+ return Buffer.from(s, 'binary').toString('base64')
22
+ .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
23
+ }
24
+ async function pkceChallenge(verifier) {
25
+ const data = new TextEncoder().encode(verifier);
26
+ const digest = await globalThis.crypto.subtle.digest('SHA-256', data);
27
+ return base64UrlEncode(digest);
28
+ }
29
+ function tryOpenBrowser(url) {
30
+ // macOS = `open`, Linux = `xdg-open`, Windows = `start ""`. Failures
31
+ // are silent — the URL is printed regardless so the user can always
32
+ // copy-paste it.
33
+ const platform = process.platform;
34
+ let cmd;
35
+ let args;
36
+ if (platform === 'darwin') {
37
+ cmd = 'open';
38
+ args = [url];
39
+ }
40
+ else if (platform === 'win32') {
41
+ cmd = 'cmd';
42
+ args = ['/c', 'start', '""', url];
43
+ }
44
+ else {
45
+ cmd = 'xdg-open';
46
+ args = [url];
47
+ }
48
+ try {
49
+ const child = spawn(cmd, args, { stdio: 'ignore', detached: true });
50
+ child.on('error', () => { });
51
+ child.unref();
52
+ }
53
+ catch {
54
+ // best-effort; URL is still printed
55
+ }
56
+ }
57
+ export async function runLogin(args) {
58
+ const log = args.log ?? ((line) => process.stdout.write(line + '\n'));
59
+ const sleep = args.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
60
+ const fetchImpl = args.fetchImpl ?? fetch;
61
+ const clientName = args.clientName ?? 'sprigr-cli/0.1.0';
62
+ // 1. PKCE
63
+ const verifier = randomVerifier();
64
+ const challenge = await pkceChallenge(verifier);
65
+ // 2. Start
66
+ let start;
67
+ try {
68
+ const res = await fetchImpl(`${args.endpoint}/api/v1/cli/device/start`, {
69
+ method: 'POST',
70
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
71
+ body: JSON.stringify({
72
+ codeChallenge: challenge,
73
+ codeChallengeMethod: 'S256',
74
+ clientName,
75
+ }),
76
+ });
77
+ if (!res.ok) {
78
+ const body = await res.text();
79
+ log(`failed to start login: HTTP ${res.status} ${body}`);
80
+ return { exitCode: 1 };
81
+ }
82
+ start = await res.json();
83
+ }
84
+ catch (err) {
85
+ log(`failed to start login: ${err instanceof Error ? err.message : String(err)}`);
86
+ return { exitCode: 1 };
87
+ }
88
+ log('');
89
+ log(`Open this URL to authorize: ${start.verificationUriComplete}`);
90
+ log(`(Code: ${start.userCode})`);
91
+ log('');
92
+ log('Waiting for browser approval…');
93
+ if (!args.noBrowser) {
94
+ tryOpenBrowser(start.verificationUriComplete);
95
+ }
96
+ // 3. Poll
97
+ const intervalMs = Math.max(1, start.interval) * 1000;
98
+ const ceilingMs = args.pollCeilingMs ?? 11 * 60 * 1000;
99
+ const deadline = Date.now() + ceilingMs;
100
+ while (Date.now() < deadline) {
101
+ await sleep(intervalMs);
102
+ let res;
103
+ try {
104
+ res = await fetchImpl(`${args.endpoint}/api/v1/cli/device/poll`, {
105
+ method: 'POST',
106
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
107
+ body: JSON.stringify({ deviceCode: start.deviceCode, codeVerifier: verifier }),
108
+ });
109
+ }
110
+ catch (err) {
111
+ // Network hiccup — log + retry. Don't fail the login on a transient
112
+ // error halfway through the user's approval.
113
+ log(` poll failed (${err instanceof Error ? err.message : String(err)}), retrying…`);
114
+ continue;
115
+ }
116
+ if (res.status === 410) {
117
+ log('Login session expired. Re-run `sprigr login`.');
118
+ return { exitCode: 1 };
119
+ }
120
+ if (res.status === 403) {
121
+ log('Login was denied.');
122
+ return { exitCode: 1 };
123
+ }
124
+ if (!res.ok) {
125
+ // Specifically a verifier_mismatch is unrecoverable — that means
126
+ // someone other than us produced this deviceCode (shouldn't happen
127
+ // unless the random verifier collided, which is astronomically
128
+ // unlikely). Surface and exit.
129
+ const body = await res.text();
130
+ log(`unexpected poll response: HTTP ${res.status} ${body}`);
131
+ return { exitCode: 1 };
132
+ }
133
+ const data = await res.json();
134
+ if ('status' in data) {
135
+ // Still pending — loop. The server tells us the poll interval each
136
+ // time so we honor the latest server-side guidance.
137
+ if (data.status === 'pending' && data.interval) {
138
+ // No-op; the constant intervalMs above is enough for MVP.
139
+ }
140
+ continue;
141
+ }
142
+ // Success — write credentials.
143
+ const success = data;
144
+ const path = await writeCredentials({
145
+ apiKey: success.apiKey,
146
+ keyPrefix: success.keyPrefix,
147
+ keyId: success.keyId,
148
+ companyId: success.companyId,
149
+ userId: success.userId,
150
+ endpoint: args.endpoint,
151
+ loggedInAt: new Date().toISOString(),
152
+ }, args.profile);
153
+ log('');
154
+ log(`Logged in as ${success.userId} (company ${success.companyId})`);
155
+ log(`Key prefix: ${success.keyPrefix}`);
156
+ if (args.profile) {
157
+ log(`Profile: ${args.profile}`);
158
+ log(`Credentials saved to ${path} (mode 0600)`);
159
+ log(`Use this profile on subsequent commands:`);
160
+ log(` sprigr --profile ${args.profile} <command>`);
161
+ log(` SPRIGR_PROFILE=${args.profile} sprigr <command>`);
162
+ }
163
+ else {
164
+ log(`Credentials saved to ${path} (mode 0600)`);
165
+ }
166
+ return { exitCode: 0, credentialPath: path };
167
+ }
168
+ log('Timed out waiting for approval. Re-run `sprigr login` to try again.');
169
+ return { exitCode: 124 };
170
+ }
171
+ export async function runLogout(args = {}) {
172
+ const log = args.log ?? ((line) => process.stdout.write(line + '\n'));
173
+ try {
174
+ const removed = await deleteCredentials(args.profile);
175
+ const target = credentialsFilePath(args.profile);
176
+ if (removed) {
177
+ log(`Logged out (removed ${target}).`);
178
+ }
179
+ else if (args.profile) {
180
+ log(`No credentials for profile "${args.profile}" (${target}).`);
181
+ }
182
+ else {
183
+ log('You were not logged in (no credentials file).');
184
+ }
185
+ return { exitCode: 0 };
186
+ }
187
+ catch (err) {
188
+ log(`Failed to remove credentials: ${err instanceof Error ? err.message : String(err)}`);
189
+ return { exitCode: 1 };
190
+ }
191
+ }
192
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ export interface WhoamiArgs {
2
+ profile?: string;
3
+ /** When true, list every profile we have credentials for. */
4
+ all?: boolean;
5
+ /** Test injection. */
6
+ log?: (line: string) => void;
7
+ }
8
+ export declare function runWhoami(args: WhoamiArgs): Promise<{
9
+ exitCode: number;
10
+ }>;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * `sprigr whoami` — print the active profile + endpoint + companyId.
3
+ *
4
+ * Useful for confirming which tenant the next command is going to target,
5
+ * especially in shells that mix `--profile` flags, `SPRIGR_PROFILE` env,
6
+ * and the legacy single-file credentials. Reads only — never writes.
7
+ */
8
+ import { credentialsFilePath, listProfilesSync, readCredentialsSync, } from '../auth.js';
9
+ import { resolveEndpoint } from '../config.js';
10
+ export async function runWhoami(args) {
11
+ const log = args.log ?? ((line) => process.stdout.write(line + '\n'));
12
+ if (args.all) {
13
+ const profiles = listProfilesSync();
14
+ if (profiles.length === 0) {
15
+ log('No credentials found. Run `sprigr login` (or `sprigr login --profile <name>`).');
16
+ return { exitCode: 0 };
17
+ }
18
+ log('Configured profiles:');
19
+ for (const p of profiles) {
20
+ const creds = p.isLegacy
21
+ ? readCredentialsSync() // legacy single-file
22
+ : readCredentialsSync(p.name);
23
+ const label = p.isLegacy ? '(no profile / default file)' : p.name;
24
+ if (!creds) {
25
+ log(` ${label} — file present but unreadable`);
26
+ continue;
27
+ }
28
+ const endpoint = creds.endpoint ?? '(unset)';
29
+ const company = creds.companyId ?? '(unset)';
30
+ log(` ${label.padEnd(28)} ${endpoint} ${company}`);
31
+ }
32
+ return { exitCode: 0 };
33
+ }
34
+ const creds = readCredentialsSync(args.profile);
35
+ const endpoint = resolveEndpoint({ profile: args.profile });
36
+ if (!creds) {
37
+ log(args.profile
38
+ ? `No credentials for profile "${args.profile}". Run:`
39
+ : 'Not logged in. Run:');
40
+ log(args.profile
41
+ ? ` sprigr login --profile ${args.profile} --endpoint ${endpoint}`
42
+ : ` sprigr login --endpoint ${endpoint}`);
43
+ return { exitCode: 1 };
44
+ }
45
+ log(`Profile: ${args.profile ?? '(default)'}`);
46
+ log(`Endpoint: ${endpoint}`);
47
+ log(`Company: ${creds.companyId ?? '(unset)'}`);
48
+ log(`User: ${creds.userId ?? '(unset)'}`);
49
+ log(`Key prefix: ${creds.keyPrefix ?? '(unset)'}`);
50
+ log(`Logged in at: ${creds.loggedInAt}`);
51
+ log(`File: ${credentialsFilePath(args.profile)}`);
52
+ return { exitCode: 0 };
53
+ }
54
+ //# sourceMappingURL=whoami.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whoami.js","sourceRoot":"","sources":["../../src/commands/whoami.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ export interface ResolvedConfig {
2
+ endpoint: string;
3
+ apiKey: string;
4
+ }
5
+ /**
6
+ * Resolve the active profile name.
7
+ *
8
+ * Resolution order:
9
+ * 1. `--profile <name>` flag
10
+ * 2. `SPRIGR_PROFILE` env var
11
+ * 3. undefined (use legacy single-file credentials)
12
+ *
13
+ * Returns undefined when no profile is active so callers fall back to the
14
+ * single-file `~/.config/sprigr/credentials.json` and existing setups keep
15
+ * working unchanged.
16
+ */
17
+ export declare function resolveProfile(opts: {
18
+ flag?: string;
19
+ }): string | undefined;
20
+ export declare function resolveEndpoint(opts: {
21
+ flag?: string;
22
+ profile?: string;
23
+ }): string;
package/dist/config.js ADDED
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Resolve the API endpoint base URL.
3
+ *
4
+ * Resolution order:
5
+ * 1. `--endpoint <url>` flag (highest priority — for local-dev override)
6
+ * 2. `SPRIGR_ENDPOINT` env var
7
+ * 3. Credentials file `endpoint` field (sticky from last `sprigr login`)
8
+ * — profile-scoped when `profile` is set, legacy single-file otherwise
9
+ * 4. Hard default: production gateway
10
+ *
11
+ * Local dev typically uses `http://localhost:8788` (the gateway worker's pinned
12
+ * port) or a Cloudflare Tunnel hostname.
13
+ */
14
+ import { assertValidProfileName, readEndpointFromFileSync } from './auth.js';
15
+ const PROD_ENDPOINT = 'https://api.team.sprigr.com';
16
+ const STAGING_ENDPOINT = 'https://staging-api-team.sprigr.com';
17
+ /**
18
+ * Resolve the active profile name.
19
+ *
20
+ * Resolution order:
21
+ * 1. `--profile <name>` flag
22
+ * 2. `SPRIGR_PROFILE` env var
23
+ * 3. undefined (use legacy single-file credentials)
24
+ *
25
+ * Returns undefined when no profile is active so callers fall back to the
26
+ * single-file `~/.config/sprigr/credentials.json` and existing setups keep
27
+ * working unchanged.
28
+ */
29
+ export function resolveProfile(opts) {
30
+ const fromFlag = opts.flag?.trim();
31
+ if (fromFlag) {
32
+ assertValidProfileName(fromFlag);
33
+ return fromFlag;
34
+ }
35
+ const fromEnv = process.env.SPRIGR_PROFILE?.trim();
36
+ if (fromEnv) {
37
+ assertValidProfileName(fromEnv);
38
+ return fromEnv;
39
+ }
40
+ return undefined;
41
+ }
42
+ export function resolveEndpoint(opts) {
43
+ const fromFlag = opts.flag?.trim();
44
+ if (fromFlag)
45
+ return expand(fromFlag);
46
+ const fromEnv = process.env.SPRIGR_ENDPOINT?.trim();
47
+ if (fromEnv)
48
+ return expand(fromEnv);
49
+ const fromFile = readEndpointFromFileSync(opts.profile);
50
+ if (fromFile)
51
+ return normalize(fromFile);
52
+ return PROD_ENDPOINT;
53
+ }
54
+ function expand(value) {
55
+ const lower = value.toLowerCase();
56
+ if (lower === 'staging')
57
+ return STAGING_ENDPOINT;
58
+ if (lower === 'prod' || lower === 'production')
59
+ return PROD_ENDPOINT;
60
+ return normalize(value);
61
+ }
62
+ function normalize(url) {
63
+ return url.replace(/\/+$/, '');
64
+ }
65
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,sBAAsB,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AAE7E,MAAM,aAAa,GAAG,6BAA6B,CAAC;AACpD,MAAM,gBAAgB,GAAG,qCAAqC,CAAC;AAO/D;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,cAAc,CAAC,IAAuB;IACpD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;IACnC,IAAI,QAAQ,EAAE,CAAC;QACb,sBAAsB,CAAC,QAAQ,CAAC,CAAC;QACjC,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,IAAI,EAAE,CAAC;IACnD,IAAI,OAAO,EAAE,CAAC;QACZ,sBAAsB,CAAC,OAAO,CAAC,CAAC;QAChC,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAAyC;IACvE,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;IACnC,IAAI,QAAQ;QAAE,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,EAAE,CAAC;IACpD,IAAI,OAAO;QAAE,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC;IACpC,MAAM,QAAQ,GAAG,wBAAwB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACxD,IAAI,QAAQ;QAAE,OAAO,SAAS,CAAC,QAAQ,CAAC,CAAC;IACzC,OAAO,aAAa,CAAC;AACvB,CAAC;AAED,SAAS,MAAM,CAAC,KAAa;IAC3B,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;IAClC,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,gBAAgB,CAAC;IACjD,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,YAAY;QAAE,OAAO,aAAa,CAAC;IACrE,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC;AAC1B,CAAC;AAED,SAAS,SAAS,CAAC,GAAW;IAC5B,OAAO,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACjC,CAAC"}