@m-kopa/launchpad-cli 0.26.1 → 0.27.1
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/CHANGELOG.md +77 -0
- package/dist/auth/flow.d.ts +7 -3
- package/dist/auth/flow.d.ts.map +1 -1
- package/dist/auth/gateway-flow.d.ts +76 -0
- package/dist/auth/gateway-flow.d.ts.map +1 -0
- package/dist/auth/session.d.ts +35 -2
- package/dist/auth/session.d.ts.map +1 -1
- package/dist/cli.js +401 -103
- package/dist/commands/login.d.ts +10 -0
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/logout.d.ts +7 -0
- package/dist/commands/logout.d.ts.map +1 -1
- package/dist/config.d.ts +11 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +2 -2
- package/skills/launchpad-content-pr/SKILL.md +146 -124
- package/skills/launchpad-deploy/SKILL.md +153 -67
- package/skills/launchpad-deploy-status/SKILL.md +136 -36
- package/skills/launchpad-destroy/SKILL.md +163 -65
- package/skills/launchpad-onboard/SKILL.md +43 -14
- package/skills/launchpad-status/SKILL.md +119 -25
package/dist/cli.js
CHANGED
|
@@ -19,21 +19,33 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
19
19
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
20
20
|
|
|
21
21
|
// src/version.ts
|
|
22
|
-
var CLI_VERSION = "0.
|
|
22
|
+
var CLI_VERSION = "0.27.1";
|
|
23
23
|
|
|
24
24
|
// src/config.ts
|
|
25
25
|
import * as os from "node:os";
|
|
26
26
|
import * as path from "node:path";
|
|
27
27
|
var DEFAULT_BOT_URL = "https://launchpad-portal-bot.mkopa-launchpad.workers.dev";
|
|
28
|
+
var DEFAULT_AUTH_GATEWAY_URL = "https://auth.launchpad.m-kopa.us";
|
|
28
29
|
function loadConfig(env = process.env) {
|
|
29
30
|
const rawBotUrl = env.LAUNCHPAD_BOT_URL ?? DEFAULT_BOT_URL;
|
|
30
31
|
const botUrl = rawBotUrl.replace(/\/+$/, "");
|
|
32
|
+
const rawGatewayUrl = env.LAUNCHPAD_AUTH_GATEWAY_URL ?? DEFAULT_AUTH_GATEWAY_URL;
|
|
33
|
+
const authGatewayUrl = rawGatewayUrl.replace(/\/+$/, "");
|
|
34
|
+
const authLegacy = env.LAUNCHPAD_AUTH_LEGACY === "1";
|
|
31
35
|
const sessionPath = env.LAUNCHPAD_SESSION_PATH ?? path.join(os.homedir(), ".launchpad", "session.json");
|
|
32
36
|
const cacheDir = env.LAUNCHPAD_CACHE_DIR ?? path.join(os.homedir(), ".launchpad", "cache");
|
|
33
37
|
const stateDir = env.LAUNCHPAD_STATE_DIR ?? path.join(os.homedir(), ".launchpad", "state");
|
|
34
38
|
const rawPlatformRepo = env.LAUNCHPAD_PLATFORM_REPO;
|
|
35
39
|
const platformRepoPath = rawPlatformRepo !== undefined && rawPlatformRepo.length > 0 ? path.resolve(rawPlatformRepo) : null;
|
|
36
|
-
return {
|
|
40
|
+
return {
|
|
41
|
+
botUrl,
|
|
42
|
+
authGatewayUrl,
|
|
43
|
+
authLegacy,
|
|
44
|
+
sessionPath,
|
|
45
|
+
cacheDir,
|
|
46
|
+
stateDir,
|
|
47
|
+
platformRepoPath
|
|
48
|
+
};
|
|
37
49
|
}
|
|
38
50
|
|
|
39
51
|
// src/http/errors.ts
|
|
@@ -64,7 +76,7 @@ class TransportError extends Error {
|
|
|
64
76
|
}
|
|
65
77
|
|
|
66
78
|
// src/auth/flow.ts
|
|
67
|
-
import { randomBytes as
|
|
79
|
+
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
68
80
|
|
|
69
81
|
// src/auth/callback-server.ts
|
|
70
82
|
import { createServer } from "node:http";
|
|
@@ -371,6 +383,10 @@ import * as fs from "node:fs/promises";
|
|
|
371
383
|
import * as path2 from "node:path";
|
|
372
384
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
373
385
|
var SESSION_VERSION = 1;
|
|
386
|
+
var GATEWAY_SESSION_VERSION = 2;
|
|
387
|
+
function isGatewaySession(s) {
|
|
388
|
+
return s.version === GATEWAY_SESSION_VERSION;
|
|
389
|
+
}
|
|
374
390
|
|
|
375
391
|
class SessionParseError extends Error {
|
|
376
392
|
code = "session_parse_error";
|
|
@@ -394,8 +410,11 @@ async function readSession(sessionPath) {
|
|
|
394
410
|
throw new SessionParseError(`session file at ${sessionPath} is not an object`);
|
|
395
411
|
}
|
|
396
412
|
const obj = parsed;
|
|
413
|
+
if (obj.version === GATEWAY_SESSION_VERSION) {
|
|
414
|
+
return parseGatewaySession(obj, sessionPath);
|
|
415
|
+
}
|
|
397
416
|
if (obj.version !== SESSION_VERSION) {
|
|
398
|
-
throw new SessionParseError(`unsupported session version ${String(obj.version)} at ${sessionPath}; expected ${SESSION_VERSION}`);
|
|
417
|
+
throw new SessionParseError(`unsupported session version ${String(obj.version)} at ${sessionPath}; expected ${SESSION_VERSION} or ${GATEWAY_SESSION_VERSION}`);
|
|
399
418
|
}
|
|
400
419
|
for (const k of [
|
|
401
420
|
"accessToken",
|
|
@@ -416,6 +435,20 @@ async function readSession(sessionPath) {
|
|
|
416
435
|
}
|
|
417
436
|
return obj;
|
|
418
437
|
}
|
|
438
|
+
function parseGatewaySession(obj, sessionPath) {
|
|
439
|
+
if (obj.kind !== "gateway") {
|
|
440
|
+
throw new SessionParseError(`session file at ${sessionPath}: version 2 with unknown kind ${String(obj.kind)}`);
|
|
441
|
+
}
|
|
442
|
+
for (const k of ["accessToken", "refreshToken", "gatewayUrl", "issuedAt"]) {
|
|
443
|
+
if (typeof obj[k] !== "string") {
|
|
444
|
+
throw new SessionParseError(`session file at ${sessionPath}: missing or non-string ${k}`);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (typeof obj.accessTokenExpiresAt !== "number") {
|
|
448
|
+
throw new SessionParseError(`session file at ${sessionPath}: missing or non-number accessTokenExpiresAt`);
|
|
449
|
+
}
|
|
450
|
+
return obj;
|
|
451
|
+
}
|
|
419
452
|
async function writeSession(sessionPath, session) {
|
|
420
453
|
const dir = path2.dirname(sessionPath);
|
|
421
454
|
await fs.mkdir(dir, { recursive: true, mode: 448 });
|
|
@@ -453,6 +486,9 @@ function describe4(e) {
|
|
|
453
486
|
return e instanceof Error ? e.message : String(e);
|
|
454
487
|
}
|
|
455
488
|
|
|
489
|
+
// src/auth/gateway-flow.ts
|
|
490
|
+
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
491
|
+
|
|
456
492
|
// src/auth/browser.ts
|
|
457
493
|
import { spawn } from "node:child_process";
|
|
458
494
|
|
|
@@ -487,16 +523,185 @@ function chooseOpener(platform, url) {
|
|
|
487
523
|
}
|
|
488
524
|
}
|
|
489
525
|
|
|
526
|
+
// src/auth/gateway-flow.ts
|
|
527
|
+
var CLI_SESSION_AUTH_PATH = "/__cli_session_auth";
|
|
528
|
+
var CLI_SESSION_TOKEN_PATH = "/__cli_session_token";
|
|
529
|
+
var CLI_LOGOUT_PATH = "/__cli_logout";
|
|
530
|
+
|
|
531
|
+
class GatewayUnavailableError extends Error {
|
|
532
|
+
code = "gateway_unavailable";
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
class GatewayTokenError extends Error {
|
|
536
|
+
code = "gateway_token_error";
|
|
537
|
+
httpStatus;
|
|
538
|
+
constructor(message, httpStatus) {
|
|
539
|
+
super(message);
|
|
540
|
+
this.name = "GatewayTokenError";
|
|
541
|
+
if (httpStatus !== undefined)
|
|
542
|
+
this.httpStatus = httpStatus;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
class GatewayRateLimitError extends Error {
|
|
547
|
+
code = "gateway_rate_limited";
|
|
548
|
+
retryAfterSec;
|
|
549
|
+
constructor(retryAfterSec) {
|
|
550
|
+
super(`gateway rate-limited the request — retry in ${retryAfterSec}s (your session is intact)`);
|
|
551
|
+
this.name = "GatewayRateLimitError";
|
|
552
|
+
this.retryAfterSec = retryAfterSec;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
async function gatewayLogin(opts) {
|
|
556
|
+
const fetcher = opts.fetcher ?? fetch;
|
|
557
|
+
const opener = opts.browserOpener ?? ((url) => openBrowser(url));
|
|
558
|
+
await probeGateway(opts.gatewayUrl, fetcher);
|
|
559
|
+
const state = randomBytes3(16).toString("hex");
|
|
560
|
+
const server = await bindCallbackServer(state);
|
|
561
|
+
try {
|
|
562
|
+
const redirectUri = `http://127.0.0.1:${server.port}/callback`;
|
|
563
|
+
const pkce = generatePkcePair();
|
|
564
|
+
const u = new URL(`${opts.gatewayUrl}${CLI_SESSION_AUTH_PATH}`);
|
|
565
|
+
u.searchParams.set("cb", redirectUri);
|
|
566
|
+
u.searchParams.set("code_challenge", pkce.challenge);
|
|
567
|
+
u.searchParams.set("state", state);
|
|
568
|
+
const authUrl = u.toString();
|
|
569
|
+
opts.onAuthUrl?.(authUrl);
|
|
570
|
+
try {
|
|
571
|
+
await opener(authUrl);
|
|
572
|
+
} catch (e) {
|
|
573
|
+
opts.onAuthUrl?.(`(could not auto-open browser: ${describe5(e)} — copy the URL above into a browser instead)`);
|
|
574
|
+
}
|
|
575
|
+
const callback = await server.result;
|
|
576
|
+
const pair = await postSessionTokenForm(opts.gatewayUrl, new URLSearchParams({
|
|
577
|
+
grant_type: "authorization_code",
|
|
578
|
+
code: callback.code,
|
|
579
|
+
code_verifier: pkce.verifier
|
|
580
|
+
}), fetcher);
|
|
581
|
+
const session = {
|
|
582
|
+
version: GATEWAY_SESSION_VERSION,
|
|
583
|
+
kind: "gateway",
|
|
584
|
+
accessToken: pair.accessToken,
|
|
585
|
+
refreshToken: pair.refreshToken,
|
|
586
|
+
accessTokenExpiresAt: Date.now() + pair.expiresInSec * 1000,
|
|
587
|
+
gatewayUrl: opts.gatewayUrl,
|
|
588
|
+
issuedAt: new Date().toISOString()
|
|
589
|
+
};
|
|
590
|
+
await writeSession(opts.sessionPath, session);
|
|
591
|
+
return session;
|
|
592
|
+
} finally {
|
|
593
|
+
await server.close();
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
async function refreshGatewayTokens(params, fetcher = fetch) {
|
|
597
|
+
return postSessionTokenForm(params.gatewayUrl, new URLSearchParams({
|
|
598
|
+
grant_type: "refresh_token",
|
|
599
|
+
refresh_token: params.refreshToken
|
|
600
|
+
}), fetcher);
|
|
601
|
+
}
|
|
602
|
+
async function revokeGatewaySession(params, fetcher = fetch) {
|
|
603
|
+
const url = `${params.gatewayUrl}${CLI_LOGOUT_PATH}`;
|
|
604
|
+
let res;
|
|
605
|
+
try {
|
|
606
|
+
res = await fetcher(url, {
|
|
607
|
+
method: "POST",
|
|
608
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
609
|
+
body: new URLSearchParams({ refresh_token: params.refreshToken }).toString()
|
|
610
|
+
});
|
|
611
|
+
} catch (e) {
|
|
612
|
+
throw new GatewayTokenError(`logout endpoint: network error: ${describe5(e)}`);
|
|
613
|
+
}
|
|
614
|
+
if (res.status === 429) {
|
|
615
|
+
throw new GatewayRateLimitError(readRetryAfter(res));
|
|
616
|
+
}
|
|
617
|
+
if (res.status !== 204 && !res.ok) {
|
|
618
|
+
const detail = await res.text().catch(() => "");
|
|
619
|
+
throw new GatewayTokenError(`logout endpoint returned HTTP ${res.status}: ${detail.slice(0, 200)}`, res.status);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
async function probeGateway(gatewayUrl, fetcher) {
|
|
623
|
+
const url = `${gatewayUrl}${CLI_SESSION_AUTH_PATH}`;
|
|
624
|
+
let res;
|
|
625
|
+
try {
|
|
626
|
+
res = await fetcher(url, { method: "GET" });
|
|
627
|
+
} catch (e) {
|
|
628
|
+
throw new GatewayUnavailableError(`auth gateway unreachable at ${gatewayUrl}: ${describe5(e)}`);
|
|
629
|
+
}
|
|
630
|
+
if (res.status === 429) {
|
|
631
|
+
throw new GatewayRateLimitError(readRetryAfter(res));
|
|
632
|
+
}
|
|
633
|
+
if (res.status !== 400) {
|
|
634
|
+
throw new GatewayUnavailableError(`auth gateway at ${gatewayUrl} is not serving the cli-session grant (HTTP ${res.status} from ${CLI_SESSION_AUTH_PATH})`);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
async function postSessionTokenForm(gatewayUrl, form, fetcher) {
|
|
638
|
+
const url = `${gatewayUrl}${CLI_SESSION_TOKEN_PATH}`;
|
|
639
|
+
let res;
|
|
640
|
+
try {
|
|
641
|
+
res = await fetcher(url, {
|
|
642
|
+
method: "POST",
|
|
643
|
+
headers: {
|
|
644
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
645
|
+
accept: "application/json"
|
|
646
|
+
},
|
|
647
|
+
body: form.toString()
|
|
648
|
+
});
|
|
649
|
+
} catch (e) {
|
|
650
|
+
throw new GatewayTokenError(`session token endpoint: network error: ${describe5(e)}`);
|
|
651
|
+
}
|
|
652
|
+
if (res.status === 429) {
|
|
653
|
+
throw new GatewayRateLimitError(readRetryAfter(res));
|
|
654
|
+
}
|
|
655
|
+
if (!res.ok) {
|
|
656
|
+
const detail = await res.text().catch(() => "");
|
|
657
|
+
throw new GatewayTokenError(`session token endpoint returned HTTP ${res.status}: ${detail.slice(0, 200)}`, res.status);
|
|
658
|
+
}
|
|
659
|
+
let parsed;
|
|
660
|
+
try {
|
|
661
|
+
parsed = await res.json();
|
|
662
|
+
} catch (e) {
|
|
663
|
+
throw new GatewayTokenError(`session token endpoint: non-JSON response: ${describe5(e)}`);
|
|
664
|
+
}
|
|
665
|
+
if (parsed === null || typeof parsed !== "object") {
|
|
666
|
+
throw new GatewayTokenError(`session token endpoint: response is not an object`);
|
|
667
|
+
}
|
|
668
|
+
const obj = parsed;
|
|
669
|
+
if (typeof obj.access_token !== "string") {
|
|
670
|
+
throw new GatewayTokenError(`session token endpoint: response missing access_token`);
|
|
671
|
+
}
|
|
672
|
+
if (typeof obj.refresh_token !== "string") {
|
|
673
|
+
throw new GatewayTokenError(`session token endpoint: response missing refresh_token`);
|
|
674
|
+
}
|
|
675
|
+
const expiresIn = obj.expires_in;
|
|
676
|
+
if (typeof expiresIn !== "number" || !Number.isFinite(expiresIn) || expiresIn <= 0) {
|
|
677
|
+
throw new GatewayTokenError(`session token endpoint: response missing positive finite numeric expires_in (got ${String(expiresIn)})`);
|
|
678
|
+
}
|
|
679
|
+
return {
|
|
680
|
+
accessToken: obj.access_token,
|
|
681
|
+
refreshToken: obj.refresh_token,
|
|
682
|
+
expiresInSec: expiresIn
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
function readRetryAfter(res) {
|
|
686
|
+
const raw = res.headers.get("retry-after");
|
|
687
|
+
const n = raw === null ? NaN : Number.parseInt(raw, 10);
|
|
688
|
+
return Number.isFinite(n) && n > 0 ? n : 60;
|
|
689
|
+
}
|
|
690
|
+
function describe5(e) {
|
|
691
|
+
return e instanceof Error ? e.message : String(e);
|
|
692
|
+
}
|
|
693
|
+
|
|
490
694
|
// src/auth/flow.ts
|
|
491
695
|
class LoginRequiredError extends Error {
|
|
492
696
|
code = "login_required";
|
|
493
697
|
}
|
|
494
698
|
var REFRESH_SKEW_MS = 30000;
|
|
699
|
+
var MAX_ABSORBED_RETRY_AFTER_SEC = 10;
|
|
495
700
|
async function login(opts) {
|
|
496
701
|
const fetcher = opts.fetcher ?? fetch;
|
|
497
702
|
const opener = opts.browserOpener ?? ((url) => openBrowser(url));
|
|
498
703
|
const endpoints = await discoverOauthEndpoints(opts.botUrl, fetcher);
|
|
499
|
-
const state =
|
|
704
|
+
const state = randomBytes4(16).toString("hex");
|
|
500
705
|
const server = await bindCallbackServer(state);
|
|
501
706
|
try {
|
|
502
707
|
const redirectUri = `http://127.0.0.1:${server.port}/callback`;
|
|
@@ -514,7 +719,7 @@ async function login(opts) {
|
|
|
514
719
|
try {
|
|
515
720
|
await opener(authUrl);
|
|
516
721
|
} catch (e) {
|
|
517
|
-
opts.onAuthUrl?.(`(could not auto-open browser: ${
|
|
722
|
+
opts.onAuthUrl?.(`(could not auto-open browser: ${describe6(e)} — copy the URL above into a browser instead)`);
|
|
518
723
|
}
|
|
519
724
|
const callback = await server.result;
|
|
520
725
|
const tokens = await exchangeCodeForTokens({
|
|
@@ -541,7 +746,7 @@ async function login(opts) {
|
|
|
541
746
|
await server.close();
|
|
542
747
|
}
|
|
543
748
|
}
|
|
544
|
-
async function getValidAccessToken(sessionPath, fetcher = fetch, now = Date.now) {
|
|
749
|
+
async function getValidAccessToken(sessionPath, fetcher = fetch, now = Date.now, sleep = (ms) => new Promise((r) => setTimeout(r, ms))) {
|
|
545
750
|
const session = await readSession(sessionPath);
|
|
546
751
|
if (session === null) {
|
|
547
752
|
throw new LoginRequiredError("no session — run `launchpad login`");
|
|
@@ -549,6 +754,9 @@ async function getValidAccessToken(sessionPath, fetcher = fetch, now = Date.now)
|
|
|
549
754
|
if (session.accessTokenExpiresAt - REFRESH_SKEW_MS > now()) {
|
|
550
755
|
return { accessToken: session.accessToken, session };
|
|
551
756
|
}
|
|
757
|
+
if (isGatewaySession(session)) {
|
|
758
|
+
return refreshGatewaySession(session, sessionPath, fetcher, now, sleep);
|
|
759
|
+
}
|
|
552
760
|
if (session.resource === undefined) {
|
|
553
761
|
throw new LoginRequiredError("session was written by a pre-0.7.1 CLI and is missing the resource indicator — run `launchpad login`");
|
|
554
762
|
}
|
|
@@ -576,7 +784,39 @@ async function getValidAccessToken(sessionPath, fetcher = fetch, now = Date.now)
|
|
|
576
784
|
await writeSession(sessionPath, refreshed);
|
|
577
785
|
return { accessToken: refreshed.accessToken, session: refreshed };
|
|
578
786
|
}
|
|
579
|
-
function
|
|
787
|
+
async function refreshGatewaySession(session, sessionPath, fetcher, now, sleep) {
|
|
788
|
+
let pair;
|
|
789
|
+
try {
|
|
790
|
+
try {
|
|
791
|
+
pair = await refreshGatewayTokens({ gatewayUrl: session.gatewayUrl, refreshToken: session.refreshToken }, fetcher);
|
|
792
|
+
} catch (e) {
|
|
793
|
+
if (e instanceof GatewayRateLimitError && e.retryAfterSec <= MAX_ABSORBED_RETRY_AFTER_SEC) {
|
|
794
|
+
await sleep(e.retryAfterSec * 1000);
|
|
795
|
+
pair = await refreshGatewayTokens({ gatewayUrl: session.gatewayUrl, refreshToken: session.refreshToken }, fetcher);
|
|
796
|
+
} else {
|
|
797
|
+
throw e;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
} catch (e) {
|
|
801
|
+
if (e instanceof GatewayTokenError && (e.httpStatus === 400 || e.httpStatus === 401)) {
|
|
802
|
+
await clearSession(sessionPath).catch(() => {
|
|
803
|
+
return;
|
|
804
|
+
});
|
|
805
|
+
throw new LoginRequiredError(`session revoked or expired — run \`launchpad login\` (gateway said: ${e.message})`);
|
|
806
|
+
}
|
|
807
|
+
throw e;
|
|
808
|
+
}
|
|
809
|
+
const refreshed = {
|
|
810
|
+
...session,
|
|
811
|
+
accessToken: pair.accessToken,
|
|
812
|
+
refreshToken: pair.refreshToken,
|
|
813
|
+
accessTokenExpiresAt: now() + pair.expiresInSec * 1000,
|
|
814
|
+
issuedAt: new Date(now()).toISOString()
|
|
815
|
+
};
|
|
816
|
+
await writeSession(sessionPath, refreshed);
|
|
817
|
+
return { accessToken: refreshed.accessToken, session: refreshed };
|
|
818
|
+
}
|
|
819
|
+
function describe6(e) {
|
|
580
820
|
return e instanceof Error ? e.message : String(e);
|
|
581
821
|
}
|
|
582
822
|
function buildAuthorizationUrl(params) {
|
|
@@ -598,7 +838,7 @@ async function apiJson(cfg, opts, fetcher = fetch) {
|
|
|
598
838
|
try {
|
|
599
839
|
parsed = await res.json();
|
|
600
840
|
} catch (e) {
|
|
601
|
-
throw new TransportError(`bot returned non-JSON body for ${opts.path}: ${
|
|
841
|
+
throw new TransportError(`bot returned non-JSON body for ${opts.path}: ${describe7(e)}`);
|
|
602
842
|
}
|
|
603
843
|
return parsed;
|
|
604
844
|
}
|
|
@@ -640,7 +880,7 @@ async function apiRaw(cfg, opts, fetcher = fetch) {
|
|
|
640
880
|
try {
|
|
641
881
|
res = await fetcher(url, init);
|
|
642
882
|
} catch (e) {
|
|
643
|
-
throw new TransportError(`network error calling ${url}: ${
|
|
883
|
+
throw new TransportError(`network error calling ${url}: ${describe7(e)}`);
|
|
644
884
|
}
|
|
645
885
|
if (res.status === 401) {
|
|
646
886
|
const detail = await peek(res);
|
|
@@ -671,7 +911,7 @@ async function peek(res) {
|
|
|
671
911
|
return "";
|
|
672
912
|
}
|
|
673
913
|
}
|
|
674
|
-
function
|
|
914
|
+
function describe7(e) {
|
|
675
915
|
return e instanceof Error ? e.message : String(e);
|
|
676
916
|
}
|
|
677
917
|
|
|
@@ -701,7 +941,7 @@ async function runApps(_args, io) {
|
|
|
701
941
|
io.err(e.message);
|
|
702
942
|
return 1;
|
|
703
943
|
}
|
|
704
|
-
io.err(`launchpad apps failed: ${
|
|
944
|
+
io.err(`launchpad apps failed: ${describe8(e)}`);
|
|
705
945
|
return 1;
|
|
706
946
|
}
|
|
707
947
|
}
|
|
@@ -749,7 +989,7 @@ function formatRelative(iso) {
|
|
|
749
989
|
return `${hr}h ago`;
|
|
750
990
|
return `${Math.floor(hr / 24)}d ago`;
|
|
751
991
|
}
|
|
752
|
-
function
|
|
992
|
+
function describe8(e) {
|
|
753
993
|
return e instanceof Error ? e.message : String(e);
|
|
754
994
|
}
|
|
755
995
|
|
|
@@ -1046,7 +1286,7 @@ async function runClone(args, io) {
|
|
|
1046
1286
|
io.err(`launchpad clone: ${e.message}`);
|
|
1047
1287
|
return 1;
|
|
1048
1288
|
}
|
|
1049
|
-
io.err(`launchpad clone failed: ${
|
|
1289
|
+
io.err(`launchpad clone failed: ${describe9(e)}`);
|
|
1050
1290
|
return 1;
|
|
1051
1291
|
}
|
|
1052
1292
|
}
|
|
@@ -1063,7 +1303,7 @@ function formatBytes(n) {
|
|
|
1063
1303
|
return `${(n / 1024).toFixed(1)}KB`;
|
|
1064
1304
|
return `${(n / (1024 * 1024)).toFixed(1)}MB`;
|
|
1065
1305
|
}
|
|
1066
|
-
function
|
|
1306
|
+
function describe9(e) {
|
|
1067
1307
|
return e instanceof Error ? e.message : String(e);
|
|
1068
1308
|
}
|
|
1069
1309
|
|
|
@@ -1129,7 +1369,7 @@ async function runCreate(args, io) {
|
|
|
1129
1369
|
io.err(`launchpad create: ${e.message}`);
|
|
1130
1370
|
return 1;
|
|
1131
1371
|
}
|
|
1132
|
-
io.err(`launchpad create failed: ${
|
|
1372
|
+
io.err(`launchpad create failed: ${describe10(e)}`);
|
|
1133
1373
|
return 1;
|
|
1134
1374
|
}
|
|
1135
1375
|
}
|
|
@@ -1214,7 +1454,7 @@ function printUsage(io) {
|
|
|
1214
1454
|
].join(`
|
|
1215
1455
|
`));
|
|
1216
1456
|
}
|
|
1217
|
-
function
|
|
1457
|
+
function describe10(e) {
|
|
1218
1458
|
return e instanceof Error ? e.message : String(e);
|
|
1219
1459
|
}
|
|
1220
1460
|
|
|
@@ -3882,7 +4122,7 @@ async function pollUntilApplied(args) {
|
|
|
3882
4122
|
path: `/apps/${args.slug}/manifest/state`
|
|
3883
4123
|
}, args.fetcher);
|
|
3884
4124
|
} catch (e) {
|
|
3885
|
-
args.io.err(`! state fetch failed (will retry): ${
|
|
4125
|
+
args.io.err(`! state fetch failed (will retry): ${describe11(e)}`);
|
|
3886
4126
|
await sleep(args.pollIntervalSec * 1000);
|
|
3887
4127
|
continue;
|
|
3888
4128
|
}
|
|
@@ -3942,7 +4182,7 @@ function deletePinIfPresent(cfg, slug, io) {
|
|
|
3942
4182
|
rmSync(pinPath, { force: true });
|
|
3943
4183
|
io.out(` (cleaned up obsolete pin file ${pinPath})`);
|
|
3944
4184
|
} catch (e) {
|
|
3945
|
-
io.err(`! warning: failed to delete obsolete pin file ${pinPath}: ${
|
|
4185
|
+
io.err(`! warning: failed to delete obsolete pin file ${pinPath}: ${describe11(e)}`);
|
|
3946
4186
|
}
|
|
3947
4187
|
}
|
|
3948
4188
|
async function loadSlugForResume(opts, io) {
|
|
@@ -3966,7 +4206,7 @@ async function defaultPrompt(question) {
|
|
|
3966
4206
|
rl.close();
|
|
3967
4207
|
}
|
|
3968
4208
|
}
|
|
3969
|
-
function
|
|
4209
|
+
function describe11(e) {
|
|
3970
4210
|
return e instanceof Error ? e.message : String(e);
|
|
3971
4211
|
}
|
|
3972
4212
|
function mapHttpError(e, slug, io) {
|
|
@@ -3990,7 +4230,7 @@ function mapHttpError(e, slug, io) {
|
|
|
3990
4230
|
io.err(`launchpad deploy --apply: ${e.message}`);
|
|
3991
4231
|
return 1;
|
|
3992
4232
|
}
|
|
3993
|
-
io.err(`launchpad deploy --apply failed: ${
|
|
4233
|
+
io.err(`launchpad deploy --apply failed: ${describe11(e)}`);
|
|
3994
4234
|
return 2;
|
|
3995
4235
|
}
|
|
3996
4236
|
function renderManifestError(loaded, io) {
|
|
@@ -4032,7 +4272,7 @@ async function runDeployDryRun(opts, io, deps = {}) {
|
|
|
4032
4272
|
try {
|
|
4033
4273
|
manifestSha = (deps.gitHeadSha ?? defaultGitHeadSha)(process.cwd());
|
|
4034
4274
|
} catch (e) {
|
|
4035
|
-
const msg = `failed to resolve git HEAD in ${process.cwd()}: ${
|
|
4275
|
+
const msg = `failed to resolve git HEAD in ${process.cwd()}: ${describe12(e)}`;
|
|
4036
4276
|
if (opts.json) {
|
|
4037
4277
|
io.out(JSON.stringify({ ok: false, kind: "git-error", message: msg }));
|
|
4038
4278
|
} else {
|
|
@@ -4081,7 +4321,7 @@ function defaultGitHeadSha(cwd) {
|
|
|
4081
4321
|
});
|
|
4082
4322
|
return out.trim();
|
|
4083
4323
|
}
|
|
4084
|
-
function
|
|
4324
|
+
function describe12(e) {
|
|
4085
4325
|
return e instanceof Error ? e.message : String(e);
|
|
4086
4326
|
}
|
|
4087
4327
|
function mapHttpError2(e, slug, json, io) {
|
|
@@ -4570,7 +4810,7 @@ async function runDeploy(args, io) {
|
|
|
4570
4810
|
io.err(`launchpad deploy: ${e.message}`);
|
|
4571
4811
|
return 1;
|
|
4572
4812
|
}
|
|
4573
|
-
io.err(`launchpad deploy failed: ${
|
|
4813
|
+
io.err(`launchpad deploy failed: ${describe13(e)}`);
|
|
4574
4814
|
return 1;
|
|
4575
4815
|
}
|
|
4576
4816
|
}
|
|
@@ -4642,7 +4882,7 @@ function formatBytes2(n) {
|
|
|
4642
4882
|
return `${(n / 1024).toFixed(1)}KB`;
|
|
4643
4883
|
return `${(n / (1024 * 1024)).toFixed(1)}MB`;
|
|
4644
4884
|
}
|
|
4645
|
-
function
|
|
4885
|
+
function describe13(e) {
|
|
4646
4886
|
return e instanceof Error ? e.message : String(e);
|
|
4647
4887
|
}
|
|
4648
4888
|
function surfaceDeployExtras(body, io, slug) {
|
|
@@ -4678,7 +4918,7 @@ async function runModelADeploy(args) {
|
|
|
4678
4918
|
}
|
|
4679
4919
|
slug = metaSlug;
|
|
4680
4920
|
} catch (e) {
|
|
4681
|
-
io.err(`launchpad deploy: failed to read ${manifestPath}: ${
|
|
4921
|
+
io.err(`launchpad deploy: failed to read ${manifestPath}: ${describe13(e)}`);
|
|
4682
4922
|
return 1;
|
|
4683
4923
|
}
|
|
4684
4924
|
if (!SLUG_RE4.test(slug)) {
|
|
@@ -4690,7 +4930,7 @@ async function runModelADeploy(args) {
|
|
|
4690
4930
|
try {
|
|
4691
4931
|
cfg = loadConfig();
|
|
4692
4932
|
} catch (e) {
|
|
4693
|
-
io.err(`launchpad deploy: ${
|
|
4933
|
+
io.err(`launchpad deploy: ${describe13(e)}`);
|
|
4694
4934
|
return 1;
|
|
4695
4935
|
}
|
|
4696
4936
|
let result;
|
|
@@ -4702,7 +4942,7 @@ async function runModelADeploy(args) {
|
|
|
4702
4942
|
io.err(" run `launchpad login` to refresh your session.");
|
|
4703
4943
|
return 1;
|
|
4704
4944
|
}
|
|
4705
|
-
io.err(`launchpad deploy: unexpected error: ${
|
|
4945
|
+
io.err(`launchpad deploy: unexpected error: ${describe13(e)}`);
|
|
4706
4946
|
return 1;
|
|
4707
4947
|
}
|
|
4708
4948
|
switch (result.kind) {
|
|
@@ -4777,9 +5017,12 @@ async function runModelADeploy(args) {
|
|
|
4777
5017
|
io.out("");
|
|
4778
5018
|
io.out(message);
|
|
4779
5019
|
io.out("");
|
|
5020
|
+
io.out(`Your bundle ships with this provisioning run — when lifecycle reaches "live",`);
|
|
5021
|
+
io.out("this deploy's content is what's serving. No second deploy needed.");
|
|
5022
|
+
io.out("");
|
|
4780
5023
|
io.out("Next steps:");
|
|
4781
5024
|
io.out(` launchpad status ${slug} # watch lifecycle (provisioning → live)`);
|
|
4782
|
-
io.out(` launchpad deploy #
|
|
5025
|
+
io.out(` launchpad deploy # only if the app comes up live WITHOUT your content (rare)`);
|
|
4783
5026
|
return 0;
|
|
4784
5027
|
}
|
|
4785
5028
|
if (typeof success.commit_sha !== "string" || typeof success.repo !== "string") {
|
|
@@ -4896,7 +5139,7 @@ async function runEnvvars(args, io) {
|
|
|
4896
5139
|
io.err(`launchpad envvars: ${e.message}`);
|
|
4897
5140
|
return 1;
|
|
4898
5141
|
}
|
|
4899
|
-
io.err(`launchpad envvars failed: ${
|
|
5142
|
+
io.err(`launchpad envvars failed: ${describe14(e)}`);
|
|
4900
5143
|
return 1;
|
|
4901
5144
|
}
|
|
4902
5145
|
}
|
|
@@ -4993,7 +5236,7 @@ function renderList(envVars, io) {
|
|
|
4993
5236
|
io.out(fmt(row));
|
|
4994
5237
|
}
|
|
4995
5238
|
}
|
|
4996
|
-
function
|
|
5239
|
+
function describe14(e) {
|
|
4997
5240
|
return e instanceof Error ? e.message : String(e);
|
|
4998
5241
|
}
|
|
4999
5242
|
|
|
@@ -5325,7 +5568,7 @@ async function fetchGroups(cfg, opts = {}) {
|
|
|
5325
5568
|
if (e instanceof UnauthenticatedError || e instanceof ForbiddenError) {
|
|
5326
5569
|
throw e;
|
|
5327
5570
|
}
|
|
5328
|
-
return { kind: "error", message:
|
|
5571
|
+
return { kind: "error", message: describe15(e) };
|
|
5329
5572
|
}
|
|
5330
5573
|
if (typeof response !== "object" || response === null || !Array.isArray(response.groups) || !response.groups.every(isEntraGroup)) {
|
|
5331
5574
|
return {
|
|
@@ -5375,7 +5618,7 @@ function writeCache(path9, envelope) {
|
|
|
5375
5618
|
writeFileSync2(path9, JSON.stringify(envelope), "utf8");
|
|
5376
5619
|
} catch {}
|
|
5377
5620
|
}
|
|
5378
|
-
function
|
|
5621
|
+
function describe15(e) {
|
|
5379
5622
|
return e instanceof Error ? e.message : String(e);
|
|
5380
5623
|
}
|
|
5381
5624
|
var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -5413,13 +5656,13 @@ function decodeJwtPayload(token) {
|
|
|
5413
5656
|
try {
|
|
5414
5657
|
json = Buffer.from(b64UrlToB64(payload), "base64").toString("utf8");
|
|
5415
5658
|
} catch (e) {
|
|
5416
|
-
throw new JwtParseError(`could not base64-decode JWT payload: ${
|
|
5659
|
+
throw new JwtParseError(`could not base64-decode JWT payload: ${describe16(e)}`);
|
|
5417
5660
|
}
|
|
5418
5661
|
let parsed;
|
|
5419
5662
|
try {
|
|
5420
5663
|
parsed = JSON.parse(json);
|
|
5421
5664
|
} catch (e) {
|
|
5422
|
-
throw new JwtParseError(`JWT payload is not JSON: ${
|
|
5665
|
+
throw new JwtParseError(`JWT payload is not JSON: ${describe16(e)}`);
|
|
5423
5666
|
}
|
|
5424
5667
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
5425
5668
|
throw new JwtParseError(`JWT payload is not an object`);
|
|
@@ -5430,7 +5673,7 @@ function b64UrlToB64(s) {
|
|
|
5430
5673
|
const padded = s.padEnd(s.length + (4 - s.length % 4) % 4, "=");
|
|
5431
5674
|
return padded.replace(/-/g, "+").replace(/_/g, "/");
|
|
5432
5675
|
}
|
|
5433
|
-
function
|
|
5676
|
+
function describe16(e) {
|
|
5434
5677
|
return e instanceof Error ? e.message : String(e);
|
|
5435
5678
|
}
|
|
5436
5679
|
|
|
@@ -5457,7 +5700,7 @@ async function runGroupsWhoami(args, io) {
|
|
|
5457
5700
|
try {
|
|
5458
5701
|
payload = decodeJwtPayload(session.accessToken);
|
|
5459
5702
|
} catch (e) {
|
|
5460
|
-
const message = e instanceof JwtParseError ? e.message :
|
|
5703
|
+
const message = e instanceof JwtParseError ? e.message : describe17(e);
|
|
5461
5704
|
if (json) {
|
|
5462
5705
|
io.out(JSON.stringify({ ok: false, kind: "jwt-parse-error", message }));
|
|
5463
5706
|
} else {
|
|
@@ -5538,7 +5781,7 @@ function parseFlags2(args) {
|
|
|
5538
5781
|
}
|
|
5539
5782
|
return { kind: "ok", json };
|
|
5540
5783
|
}
|
|
5541
|
-
function
|
|
5784
|
+
function describe17(e) {
|
|
5542
5785
|
return e instanceof Error ? e.message : String(e);
|
|
5543
5786
|
}
|
|
5544
5787
|
|
|
@@ -5641,7 +5884,7 @@ async function loadGroups(io, refresh, json) {
|
|
|
5641
5884
|
}
|
|
5642
5885
|
return { kind: "error", code: 3 };
|
|
5643
5886
|
}
|
|
5644
|
-
renderFetchError(io,
|
|
5887
|
+
renderFetchError(io, describe18(e), json);
|
|
5645
5888
|
return { kind: "error", code: 2 };
|
|
5646
5889
|
}
|
|
5647
5890
|
}
|
|
@@ -5836,7 +6079,7 @@ function renderTable2(io, groups) {
|
|
|
5836
6079
|
for (const r of rows)
|
|
5837
6080
|
io.out(fmt(r));
|
|
5838
6081
|
}
|
|
5839
|
-
function
|
|
6082
|
+
function describe18(e) {
|
|
5840
6083
|
return e instanceof Error ? e.message : String(e);
|
|
5841
6084
|
}
|
|
5842
6085
|
|
|
@@ -6472,56 +6715,111 @@ async function defaultPrompt2(question, fallback) {
|
|
|
6472
6715
|
}
|
|
6473
6716
|
|
|
6474
6717
|
// src/commands/login.ts
|
|
6475
|
-
var
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
6480
|
-
|
|
6718
|
+
var REAL_DEPS = { gatewayLogin, legacyLogin: login };
|
|
6719
|
+
var loginCommand = makeLoginCommand();
|
|
6720
|
+
function makeLoginCommand(deps = REAL_DEPS) {
|
|
6721
|
+
return {
|
|
6722
|
+
name: "login",
|
|
6723
|
+
summary: "authenticate (browser-based PKCE) and store a session",
|
|
6724
|
+
run: (args, io) => runLogin(args, io, deps)
|
|
6725
|
+
};
|
|
6726
|
+
}
|
|
6727
|
+
async function runLogin(_args, io, deps) {
|
|
6481
6728
|
try {
|
|
6482
6729
|
const cfg = loadConfig();
|
|
6483
|
-
|
|
6730
|
+
if (cfg.authLegacy) {
|
|
6731
|
+
io.out("LAUNCHPAD_AUTH_LEGACY=1 — using the legacy Cloudflare Access flow (deprecated; this path will be removed when the dual-auth window closes).");
|
|
6732
|
+
return await runLegacyLogin(io, cfg.botUrl, cfg.sessionPath, deps);
|
|
6733
|
+
}
|
|
6734
|
+
io.out("Opening browser to sign in via the Launchpad auth gateway…");
|
|
6484
6735
|
io.out("(if it doesn't open automatically, copy the URL below)");
|
|
6485
6736
|
io.out("");
|
|
6486
|
-
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6737
|
+
try {
|
|
6738
|
+
const session = await deps.gatewayLogin({
|
|
6739
|
+
gatewayUrl: cfg.authGatewayUrl,
|
|
6740
|
+
sessionPath: cfg.sessionPath,
|
|
6741
|
+
onAuthUrl: (url) => {
|
|
6742
|
+
io.out(url);
|
|
6743
|
+
io.out("");
|
|
6744
|
+
}
|
|
6745
|
+
});
|
|
6746
|
+
const expiresIn = Math.max(0, Math.round((session.accessTokenExpiresAt - Date.now()) / 1000));
|
|
6747
|
+
io.out(`Logged in. Session stored at ${cfg.sessionPath}`);
|
|
6748
|
+
io.out(`Access token expires in ~${expiresIn}s; refreshes silently.`);
|
|
6749
|
+
return 0;
|
|
6750
|
+
} catch (e) {
|
|
6751
|
+
io.err(`Gateway login failed: ${describe19(e)}`);
|
|
6752
|
+
io.err("Falling back to the legacy Cloudflare Access flow (DEPRECATED — this fallback will be removed when the dual-auth window closes).");
|
|
6753
|
+
io.err("");
|
|
6754
|
+
return await runLegacyLogin(io, cfg.botUrl, cfg.sessionPath, deps);
|
|
6755
|
+
}
|
|
6498
6756
|
} catch (e) {
|
|
6499
|
-
io.err(`launchpad login failed: ${
|
|
6757
|
+
io.err(`launchpad login failed: ${describe19(e)}`);
|
|
6500
6758
|
return 1;
|
|
6501
6759
|
}
|
|
6502
6760
|
}
|
|
6503
|
-
function
|
|
6761
|
+
async function runLegacyLogin(io, botUrl, sessionPath, deps) {
|
|
6762
|
+
io.out("Opening browser to authenticate with Cloudflare Access…");
|
|
6763
|
+
io.out("(if it doesn't open automatically, copy the URL below)");
|
|
6764
|
+
io.out("");
|
|
6765
|
+
const session = await deps.legacyLogin({
|
|
6766
|
+
botUrl,
|
|
6767
|
+
sessionPath,
|
|
6768
|
+
onAuthUrl: (url) => {
|
|
6769
|
+
io.out(url);
|
|
6770
|
+
io.out("");
|
|
6771
|
+
}
|
|
6772
|
+
});
|
|
6773
|
+
const expiresIn = Math.max(0, Math.round((session.accessTokenExpiresAt - Date.now()) / 1000));
|
|
6774
|
+
io.out(`Logged in. Session stored at ${sessionPath}`);
|
|
6775
|
+
io.out(`Access token expires in ~${expiresIn}s; refreshes silently.`);
|
|
6776
|
+
return 0;
|
|
6777
|
+
}
|
|
6778
|
+
function describe19(e) {
|
|
6504
6779
|
return e instanceof Error ? e.message : String(e);
|
|
6505
6780
|
}
|
|
6506
6781
|
|
|
6507
6782
|
// src/commands/logout.ts
|
|
6508
|
-
var
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6783
|
+
var REAL_DEPS2 = { revoke: revokeGatewaySession };
|
|
6784
|
+
var logoutCommand = makeLogoutCommand();
|
|
6785
|
+
function makeLogoutCommand(deps = REAL_DEPS2) {
|
|
6786
|
+
return {
|
|
6787
|
+
name: "logout",
|
|
6788
|
+
summary: "revoke the session server-side and clear it locally",
|
|
6789
|
+
run: (args, io) => runLogout(args, io, deps)
|
|
6790
|
+
};
|
|
6791
|
+
}
|
|
6792
|
+
async function runLogout(_args, io, deps) {
|
|
6514
6793
|
try {
|
|
6515
6794
|
const cfg = loadConfig();
|
|
6795
|
+
let session = null;
|
|
6796
|
+
try {
|
|
6797
|
+
session = await readSession(cfg.sessionPath);
|
|
6798
|
+
} catch (e) {
|
|
6799
|
+
if (!(e instanceof SessionParseError))
|
|
6800
|
+
throw e;
|
|
6801
|
+
io.err(`warning: session file is corrupt (${e.message}) — clearing it without a server-side revoke.`);
|
|
6802
|
+
}
|
|
6803
|
+
if (session !== null && isGatewaySession(session)) {
|
|
6804
|
+
try {
|
|
6805
|
+
await deps.revoke({
|
|
6806
|
+
gatewayUrl: session.gatewayUrl,
|
|
6807
|
+
refreshToken: session.refreshToken
|
|
6808
|
+
});
|
|
6809
|
+
io.out("Server-side session revoked.");
|
|
6810
|
+
} catch (e) {
|
|
6811
|
+
io.err(`warning: could not revoke the session server-side (${describe20(e)}) — clearing the local session anyway. Any in-flight access token expires within 15 minutes.`);
|
|
6812
|
+
}
|
|
6813
|
+
}
|
|
6516
6814
|
const had = await clearSession(cfg.sessionPath);
|
|
6517
6815
|
io.out(had ? `Logged out. Session cleared at ${cfg.sessionPath}` : "Already logged out.");
|
|
6518
6816
|
return 0;
|
|
6519
6817
|
} catch (e) {
|
|
6520
|
-
io.err(`launchpad logout failed: ${
|
|
6818
|
+
io.err(`launchpad logout failed: ${describe20(e)}`);
|
|
6521
6819
|
return 1;
|
|
6522
6820
|
}
|
|
6523
6821
|
}
|
|
6524
|
-
function
|
|
6822
|
+
function describe20(e) {
|
|
6525
6823
|
return e instanceof Error ? e.message : String(e);
|
|
6526
6824
|
}
|
|
6527
6825
|
|
|
@@ -6591,7 +6889,7 @@ async function runLogs(args, io) {
|
|
|
6591
6889
|
io.err(`launchpad logs: ${e.message}`);
|
|
6592
6890
|
return 1;
|
|
6593
6891
|
}
|
|
6594
|
-
io.err(`launchpad logs failed: ${
|
|
6892
|
+
io.err(`launchpad logs failed: ${describe21(e)}`);
|
|
6595
6893
|
return 1;
|
|
6596
6894
|
}
|
|
6597
6895
|
}
|
|
@@ -6671,7 +6969,7 @@ function formatRelative2(iso) {
|
|
|
6671
6969
|
return `${hr}h ago`;
|
|
6672
6970
|
return `${Math.floor(hr / 24)}d ago`;
|
|
6673
6971
|
}
|
|
6674
|
-
function
|
|
6972
|
+
function describe21(e) {
|
|
6675
6973
|
return e instanceof Error ? e.message : String(e);
|
|
6676
6974
|
}
|
|
6677
6975
|
|
|
@@ -6720,7 +7018,7 @@ async function runMerge(args, io) {
|
|
|
6720
7018
|
try {
|
|
6721
7019
|
body = await res.json();
|
|
6722
7020
|
} catch (e) {
|
|
6723
|
-
io.err(`launchpad merge: bot returned 2xx with malformed body: ${
|
|
7021
|
+
io.err(`launchpad merge: bot returned 2xx with malformed body: ${describe22(e)}`);
|
|
6724
7022
|
return 1;
|
|
6725
7023
|
}
|
|
6726
7024
|
io.out("");
|
|
@@ -6760,7 +7058,7 @@ async function runMerge(args, io) {
|
|
|
6760
7058
|
io.err(`launchpad merge: ${e.message}`);
|
|
6761
7059
|
return 1;
|
|
6762
7060
|
}
|
|
6763
|
-
io.err(`launchpad merge failed: ${
|
|
7061
|
+
io.err(`launchpad merge failed: ${describe22(e)}`);
|
|
6764
7062
|
return 1;
|
|
6765
7063
|
}
|
|
6766
7064
|
}
|
|
@@ -6844,7 +7142,7 @@ function renderBotError(status, env, io, prNumber = null) {
|
|
|
6844
7142
|
io.err(`launchpad merge: bot returned ${status} ${code}${detail !== undefined ? `: ${detail}` : ""}`);
|
|
6845
7143
|
}
|
|
6846
7144
|
}
|
|
6847
|
-
function
|
|
7145
|
+
function describe22(e) {
|
|
6848
7146
|
return e instanceof Error ? e.message : String(e);
|
|
6849
7147
|
}
|
|
6850
7148
|
|
|
@@ -7096,7 +7394,7 @@ async function runDestroy(args, io, prompt, isTty) {
|
|
|
7096
7394
|
io.err(`launchpad destroy: ${e.message}`);
|
|
7097
7395
|
return 1;
|
|
7098
7396
|
}
|
|
7099
|
-
io.err(`launchpad destroy failed: ${
|
|
7397
|
+
io.err(`launchpad destroy failed: ${describe23(e)}`);
|
|
7100
7398
|
return 1;
|
|
7101
7399
|
}
|
|
7102
7400
|
}
|
|
@@ -7297,7 +7595,7 @@ function printUsage3(io) {
|
|
|
7297
7595
|
].join(`
|
|
7298
7596
|
`));
|
|
7299
7597
|
}
|
|
7300
|
-
function
|
|
7598
|
+
function describe23(e) {
|
|
7301
7599
|
return e instanceof Error ? e.message : String(e);
|
|
7302
7600
|
}
|
|
7303
7601
|
|
|
@@ -7441,7 +7739,7 @@ async function runPull(args, io) {
|
|
|
7441
7739
|
io.err(`launchpad pull: ${e.message}`);
|
|
7442
7740
|
return 1;
|
|
7443
7741
|
}
|
|
7444
|
-
io.err(`launchpad pull failed: ${
|
|
7742
|
+
io.err(`launchpad pull failed: ${describe24(e)}`);
|
|
7445
7743
|
return 1;
|
|
7446
7744
|
}
|
|
7447
7745
|
}
|
|
@@ -7522,7 +7820,7 @@ function printUsage4(io) {
|
|
|
7522
7820
|
].join(`
|
|
7523
7821
|
`));
|
|
7524
7822
|
}
|
|
7525
|
-
function
|
|
7823
|
+
function describe24(e) {
|
|
7526
7824
|
return e instanceof Error ? e.message : String(e);
|
|
7527
7825
|
}
|
|
7528
7826
|
|
|
@@ -7731,7 +8029,7 @@ function mapBotError(e, slug, io) {
|
|
|
7731
8029
|
io.err(`launchpad status: ${e.message}`);
|
|
7732
8030
|
return 2;
|
|
7733
8031
|
}
|
|
7734
|
-
io.err(`launchpad status failed: ${
|
|
8032
|
+
io.err(`launchpad status failed: ${describe25(e)}`);
|
|
7735
8033
|
return 2;
|
|
7736
8034
|
}
|
|
7737
8035
|
async function runStatus(args, io) {
|
|
@@ -7757,7 +8055,7 @@ async function runStatus(args, io) {
|
|
|
7757
8055
|
localYaml = readFileSync12(parsed.file, "utf8");
|
|
7758
8056
|
} catch (e) {
|
|
7759
8057
|
if (!isEnoent(e)) {
|
|
7760
|
-
io.err(`launchpad status: cannot read local manifest at ${parsed.file}: ${
|
|
8058
|
+
io.err(`launchpad status: cannot read local manifest at ${parsed.file}: ${describe25(e)}`);
|
|
7761
8059
|
return 2;
|
|
7762
8060
|
}
|
|
7763
8061
|
}
|
|
@@ -7768,7 +8066,7 @@ async function runStatus(args, io) {
|
|
|
7768
8066
|
const { parse: parseYaml6 } = await import("yaml");
|
|
7769
8067
|
localObj = parseYaml6(localYaml);
|
|
7770
8068
|
} catch (e) {
|
|
7771
|
-
io.err(`launchpad status: ${parsed.file} is not valid YAML: ${
|
|
8069
|
+
io.err(`launchpad status: ${parsed.file} is not valid YAML: ${describe25(e)}`);
|
|
7772
8070
|
return 2;
|
|
7773
8071
|
}
|
|
7774
8072
|
const localParse = parseManifest(localObj);
|
|
@@ -7797,7 +8095,7 @@ async function runStatus(args, io) {
|
|
|
7797
8095
|
if (e instanceof UnauthenticatedError || e instanceof ForbiddenError) {
|
|
7798
8096
|
return mapBotError(e, parsed.slug, io);
|
|
7799
8097
|
}
|
|
7800
|
-
io.err(`launchpad status: live deployment state unavailable (${
|
|
8098
|
+
io.err(`launchpad status: live deployment state unavailable (${describe25(e)}) — ` + `the report below is from the platform manifest view only.`);
|
|
7801
8099
|
}
|
|
7802
8100
|
let standingExceptions = null;
|
|
7803
8101
|
try {
|
|
@@ -7806,7 +8104,7 @@ async function runStatus(args, io) {
|
|
|
7806
8104
|
if (e instanceof UnauthenticatedError || e instanceof ForbiddenError) {
|
|
7807
8105
|
return mapBotError(e, parsed.slug, io);
|
|
7808
8106
|
}
|
|
7809
|
-
io.err(`launchpad status: standing-exception inventory unavailable (${
|
|
8107
|
+
io.err(`launchpad status: standing-exception inventory unavailable (${describe25(e)}).`);
|
|
7810
8108
|
}
|
|
7811
8109
|
if (state.manifestYaml === null || state.manifestYaml === undefined) {
|
|
7812
8110
|
const live = deployment?.liveDeployment ?? null;
|
|
@@ -7857,7 +8155,7 @@ async function runStatus(args, io) {
|
|
|
7857
8155
|
const { parse: parseYaml6 } = await import("yaml");
|
|
7858
8156
|
deployedObj = parseYaml6(state.manifestYaml);
|
|
7859
8157
|
} catch (e) {
|
|
7860
|
-
io.err(`launchpad status: deployed manifest at ${state.lastAppliedManifestSha} is not valid YAML: ${
|
|
8158
|
+
io.err(`launchpad status: deployed manifest at ${state.lastAppliedManifestSha} is not valid YAML: ${describe25(e)}`);
|
|
7861
8159
|
return 2;
|
|
7862
8160
|
}
|
|
7863
8161
|
const deployedParse = parseManifest(deployedObj);
|
|
@@ -8196,7 +8494,7 @@ function printUsage6(io) {
|
|
|
8196
8494
|
].join(`
|
|
8197
8495
|
`));
|
|
8198
8496
|
}
|
|
8199
|
-
function
|
|
8497
|
+
function describe25(e) {
|
|
8200
8498
|
return e instanceof Error ? e.message : String(e);
|
|
8201
8499
|
}
|
|
8202
8500
|
function isEnoent(e) {
|
|
@@ -8274,7 +8572,7 @@ async function runReview(args, io) {
|
|
|
8274
8572
|
io.err(`launchpad review: ${e.message}`);
|
|
8275
8573
|
return 1;
|
|
8276
8574
|
}
|
|
8277
|
-
io.err(`launchpad review failed: ${
|
|
8575
|
+
io.err(`launchpad review failed: ${describe26(e)}`);
|
|
8278
8576
|
return 1;
|
|
8279
8577
|
}
|
|
8280
8578
|
}
|
|
@@ -8352,7 +8650,7 @@ function severityRank(s) {
|
|
|
8352
8650
|
return 3;
|
|
8353
8651
|
}
|
|
8354
8652
|
}
|
|
8355
|
-
function
|
|
8653
|
+
function describe26(e) {
|
|
8356
8654
|
return e instanceof Error ? e.message : String(e);
|
|
8357
8655
|
}
|
|
8358
8656
|
|
|
@@ -8412,7 +8710,7 @@ async function runRollback(opts, io, deps = {}) {
|
|
|
8412
8710
|
cwd: process.cwd()
|
|
8413
8711
|
});
|
|
8414
8712
|
} catch (e) {
|
|
8415
|
-
io.err(`launchpad rollback: git rev-parse failed to start: ${
|
|
8713
|
+
io.err(`launchpad rollback: git rev-parse failed to start: ${describe27(e)}`);
|
|
8416
8714
|
return 2;
|
|
8417
8715
|
}
|
|
8418
8716
|
if (revParse.exitCode !== 0) {
|
|
@@ -8427,7 +8725,7 @@ async function runRollback(opts, io, deps = {}) {
|
|
|
8427
8725
|
try {
|
|
8428
8726
|
show = await runner.run("git", ["show", `${verifiedSha}:${manifestRelpath}`], { cwd: process.cwd() });
|
|
8429
8727
|
} catch (e) {
|
|
8430
|
-
io.err(`launchpad rollback: git show failed to start: ${
|
|
8728
|
+
io.err(`launchpad rollback: git show failed to start: ${describe27(e)}`);
|
|
8431
8729
|
return 2;
|
|
8432
8730
|
}
|
|
8433
8731
|
if (show.exitCode !== 0) {
|
|
@@ -8461,7 +8759,7 @@ async function runRollback(opts, io, deps = {}) {
|
|
|
8461
8759
|
try {
|
|
8462
8760
|
writeFileSync5(manifestPath, historicalYaml, "utf8");
|
|
8463
8761
|
} catch (e) {
|
|
8464
|
-
io.err(`launchpad rollback: failed to write ${manifestPath}: ${
|
|
8762
|
+
io.err(`launchpad rollback: failed to write ${manifestPath}: ${describe27(e)}`);
|
|
8465
8763
|
return 2;
|
|
8466
8764
|
}
|
|
8467
8765
|
io.out(`Restored ${manifestPath} from ${verifiedSha.slice(0, 12)}.`);
|
|
@@ -8543,7 +8841,7 @@ async function defaultPrompt4(question) {
|
|
|
8543
8841
|
rl.close();
|
|
8544
8842
|
}
|
|
8545
8843
|
}
|
|
8546
|
-
function
|
|
8844
|
+
function describe27(e) {
|
|
8547
8845
|
return e instanceof Error ? e.message : String(e);
|
|
8548
8846
|
}
|
|
8549
8847
|
function renderManifestError3(result, io) {
|
|
@@ -8706,7 +9004,7 @@ async function runSecretsPush(opts, io, deps = {}) {
|
|
|
8706
9004
|
try {
|
|
8707
9005
|
envText = readFileSync14(envPath, "utf8");
|
|
8708
9006
|
} catch (e) {
|
|
8709
|
-
io.err(`launchpad secrets push: failed to read ${envPath}: ${
|
|
9007
|
+
io.err(`launchpad secrets push: failed to read ${envPath}: ${describe28(e)}`);
|
|
8710
9008
|
return 2;
|
|
8711
9009
|
}
|
|
8712
9010
|
const parsed = parseEnv(envText);
|
|
@@ -8837,10 +9135,10 @@ function mapPushError(e, slug, name, io) {
|
|
|
8837
9135
|
io.err(`✗ secrets push ${name}: ${e.message}`);
|
|
8838
9136
|
return 1;
|
|
8839
9137
|
}
|
|
8840
|
-
io.err(`✗ secrets push ${name} failed: ${
|
|
9138
|
+
io.err(`✗ secrets push ${name} failed: ${describe28(e)}`);
|
|
8841
9139
|
return 2;
|
|
8842
9140
|
}
|
|
8843
|
-
function
|
|
9141
|
+
function describe28(e) {
|
|
8844
9142
|
return e instanceof Error ? e.message : String(e);
|
|
8845
9143
|
}
|
|
8846
9144
|
function renderManifestError4(result, io) {
|
|
@@ -9584,7 +9882,7 @@ async function runSkills(args, io) {
|
|
|
9584
9882
|
return 64;
|
|
9585
9883
|
}
|
|
9586
9884
|
} catch (e) {
|
|
9587
|
-
io.err(`launchpad skills ${action}: ${
|
|
9885
|
+
io.err(`launchpad skills ${action}: ${describe29(e)}`);
|
|
9588
9886
|
return 1;
|
|
9589
9887
|
}
|
|
9590
9888
|
}
|
|
@@ -9714,7 +10012,7 @@ async function isDir(path12) {
|
|
|
9714
10012
|
return false;
|
|
9715
10013
|
}
|
|
9716
10014
|
}
|
|
9717
|
-
function
|
|
10015
|
+
function describe29(e) {
|
|
9718
10016
|
return e instanceof Error ? e.message : String(e);
|
|
9719
10017
|
}
|
|
9720
10018
|
|
|
@@ -9728,7 +10026,7 @@ import { readFileSync as readFileSync16, mkdtempSync, writeFileSync as writeFile
|
|
|
9728
10026
|
|
|
9729
10027
|
// src/commands/channel-auth.ts
|
|
9730
10028
|
import { createServer as createServer2 } from "node:http";
|
|
9731
|
-
import { createHash as createHash2, randomBytes as
|
|
10029
|
+
import { createHash as createHash2, randomBytes as randomBytes5 } from "node:crypto";
|
|
9732
10030
|
var CHANNEL_BASE = "https://get.launchpad.m-kopa.us";
|
|
9733
10031
|
var CLI_AUTH_URL = `${CHANNEL_BASE}/__cli_auth`;
|
|
9734
10032
|
var CLI_TOKEN_URL = `${CHANNEL_BASE}/__cli_token`;
|
|
@@ -9738,7 +10036,7 @@ function base64url(b) {
|
|
|
9738
10036
|
return b.toString("base64url");
|
|
9739
10037
|
}
|
|
9740
10038
|
function pkcePair() {
|
|
9741
|
-
const verifier = base64url(
|
|
10039
|
+
const verifier = base64url(randomBytes5(32));
|
|
9742
10040
|
const challenge = base64url(createHash2("sha256").update(verifier).digest());
|
|
9743
10041
|
return { verifier, challenge };
|
|
9744
10042
|
}
|
|
@@ -9784,7 +10082,7 @@ async function startLoopback(state, timeoutMs) {
|
|
|
9784
10082
|
async function runChannelLoopbackUpdate(deps) {
|
|
9785
10083
|
const timeoutMs = deps.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
9786
10084
|
const { verifier, challenge } = pkcePair();
|
|
9787
|
-
const state = base64url(
|
|
10085
|
+
const state = base64url(randomBytes5(16));
|
|
9788
10086
|
const loopback = await startLoopback(state, timeoutMs);
|
|
9789
10087
|
if (!loopback)
|
|
9790
10088
|
return { ok: false, reason: "no-browser", detail: "could not bind a loopback port" };
|
|
@@ -10235,7 +10533,7 @@ async function checkGroup(allowedGroup, cfg) {
|
|
|
10235
10533
|
if (e instanceof ForbiddenError) {
|
|
10236
10534
|
return { kind: "forbidden", message: e.message };
|
|
10237
10535
|
}
|
|
10238
|
-
return { kind: "network", message:
|
|
10536
|
+
return { kind: "network", message: describe30(e) };
|
|
10239
10537
|
}
|
|
10240
10538
|
}
|
|
10241
10539
|
async function checkGroups(groups) {
|
|
@@ -10448,7 +10746,7 @@ function parseArgs12(args) {
|
|
|
10448
10746
|
}
|
|
10449
10747
|
return { kind: "ok", file, json, strictGroups };
|
|
10450
10748
|
}
|
|
10451
|
-
function
|
|
10749
|
+
function describe30(e) {
|
|
10452
10750
|
return e instanceof Error ? e.message : String(e);
|
|
10453
10751
|
}
|
|
10454
10752
|
|
|
@@ -10497,11 +10795,11 @@ async function runWhoami(_args, io) {
|
|
|
10497
10795
|
io.err(e.message);
|
|
10498
10796
|
return 1;
|
|
10499
10797
|
}
|
|
10500
|
-
io.err(`launchpad whoami failed: ${
|
|
10798
|
+
io.err(`launchpad whoami failed: ${describe31(e)}`);
|
|
10501
10799
|
return 1;
|
|
10502
10800
|
}
|
|
10503
10801
|
}
|
|
10504
|
-
function
|
|
10802
|
+
function describe31(e) {
|
|
10505
10803
|
return e instanceof Error ? e.message : String(e);
|
|
10506
10804
|
}
|
|
10507
10805
|
|