camstack 0.7.0 → 0.7.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.
- package/dist/cli.js +102 -15
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -376,11 +376,23 @@ function resolveSessionFile(serverUrl) {
|
|
|
376
376
|
if (files.length === 0) return null;
|
|
377
377
|
return files.map((f) => ({ f, mtime: fs3.statSync(f).mtimeMs })).sort((a, b) => b.mtime - a.mtime)[0].f;
|
|
378
378
|
}
|
|
379
|
+
function isSessionFile(value) {
|
|
380
|
+
if (value === null || typeof value !== "object") return false;
|
|
381
|
+
const v = value;
|
|
382
|
+
if (typeof v.server !== "string") return false;
|
|
383
|
+
if (typeof v.username !== "string") return false;
|
|
384
|
+
if (typeof v.token !== "string") return false;
|
|
385
|
+
if (typeof v.tokenId !== "string") return false;
|
|
386
|
+
if (typeof v.createdAt !== "number") return false;
|
|
387
|
+
if (!Array.isArray(v.scopes)) return false;
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
379
390
|
function loadSession(serverUrl) {
|
|
380
391
|
const file = resolveSessionFile(serverUrl);
|
|
381
392
|
if (!file) return null;
|
|
382
393
|
try {
|
|
383
|
-
|
|
394
|
+
const parsed = JSON.parse(fs3.readFileSync(file, "utf8"));
|
|
395
|
+
return isSessionFile(parsed) ? parsed : null;
|
|
384
396
|
} catch {
|
|
385
397
|
return null;
|
|
386
398
|
}
|
|
@@ -526,20 +538,55 @@ async function resolveServerInteractive(presetNamespace) {
|
|
|
526
538
|
probeSpinner.stop(`Reachable at https://${probed.address}:${probed.port}`);
|
|
527
539
|
return `https://${probed.address}:${probed.port}`;
|
|
528
540
|
}
|
|
541
|
+
var BACK_SENTINEL = "__back__";
|
|
542
|
+
async function askWithBack(label, defaultValue) {
|
|
543
|
+
const v = await askText(`${label} (or type :back)`, defaultValue);
|
|
544
|
+
return v.trim() === ":back" || v.trim() === ":b" ? BACK_SENTINEL : v;
|
|
545
|
+
}
|
|
546
|
+
async function askPasswordWithBack() {
|
|
547
|
+
const v = await askPassword("Password (or type :back to change username)");
|
|
548
|
+
return v.trim() === ":back" || v.trim() === ":b" ? BACK_SENTINEL : v;
|
|
549
|
+
}
|
|
529
550
|
async function loginCommand(opts) {
|
|
530
551
|
clack.intro("camstack login");
|
|
531
|
-
let
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
552
|
+
let step = opts.server ? "username" : "server";
|
|
553
|
+
let server = opts.server ?? "";
|
|
554
|
+
let username = opts.username ?? "";
|
|
555
|
+
let password2 = opts.password ?? "";
|
|
556
|
+
if (opts.server) clack.log.info(`Server: ${opts.server}`);
|
|
557
|
+
while (step !== "password" || !password2) {
|
|
558
|
+
if (step === "server") {
|
|
559
|
+
server = await resolveServerInteractive(opts.namespace);
|
|
560
|
+
step = "username";
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
if (step === "username") {
|
|
564
|
+
const result2 = opts.username && !username ? opts.username : await askWithBack("Username", username || "admin");
|
|
565
|
+
if (result2 === BACK_SENTINEL) {
|
|
566
|
+
if (opts.server) {
|
|
567
|
+
clack.log.warn("Server was passed via --server; nothing to go back to. Re-enter username.");
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
step = "server";
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
username = result2;
|
|
574
|
+
step = "password";
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
const result = opts.password && !password2 ? opts.password : await askPasswordWithBack();
|
|
578
|
+
if (result === BACK_SENTINEL) {
|
|
579
|
+
step = "username";
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
password2 = result;
|
|
583
|
+
break;
|
|
536
584
|
}
|
|
537
|
-
const username = opts.username ?? await askText("Username", "admin");
|
|
538
585
|
const MAX_ATTEMPTS = 3;
|
|
539
|
-
let password2 = opts.password ?? await askPassword("Password");
|
|
540
586
|
let attempt = 1;
|
|
541
587
|
let jwt;
|
|
542
588
|
let displayName;
|
|
589
|
+
let userId;
|
|
543
590
|
while (true) {
|
|
544
591
|
const authSpinner = clack.spinner();
|
|
545
592
|
authSpinner.start(`Authenticating as ${username}${attempt > 1 ? ` (attempt ${attempt}/${MAX_ATTEMPTS})` : ""}`);
|
|
@@ -552,6 +599,7 @@ async function loginCommand(opts) {
|
|
|
552
599
|
);
|
|
553
600
|
jwt = login.token;
|
|
554
601
|
displayName = login.user.username;
|
|
602
|
+
userId = login.user.id;
|
|
555
603
|
authSpinner.stop(`Authenticated as ${displayName}`);
|
|
556
604
|
break;
|
|
557
605
|
} catch (err) {
|
|
@@ -578,13 +626,18 @@ async function loginCommand(opts) {
|
|
|
578
626
|
}
|
|
579
627
|
}
|
|
580
628
|
const mintSpinner = clack.spinner();
|
|
581
|
-
mintSpinner.start("Creating upload
|
|
629
|
+
mintSpinner.start("Creating scoped token (upload + log streaming)");
|
|
582
630
|
const tokenName = opts.tokenName ?? `camstack-cli@${os2.hostname()}`;
|
|
583
|
-
const scopes = [
|
|
631
|
+
const scopes = [
|
|
632
|
+
{ type: "route-prefix", target: "/api/addons/upload" },
|
|
633
|
+
{ type: "route-prefix", target: "/trpc/addons.onAddonLogs" },
|
|
634
|
+
{ type: "route-prefix", target: "/trpc/addons.getLogs" },
|
|
635
|
+
{ type: "route-prefix", target: "/trpc/addons.list" }
|
|
636
|
+
];
|
|
584
637
|
const created = await callTrpcMutation(
|
|
585
638
|
`${server}/trpc/userManagement.createScopedToken?batch=1`,
|
|
586
639
|
`Bearer ${jwt}`,
|
|
587
|
-
{ name: tokenName, scopes },
|
|
640
|
+
{ userId, name: tokenName, scopes },
|
|
588
641
|
isCreateScopedTokenPayload
|
|
589
642
|
);
|
|
590
643
|
const scopedToken = created.token;
|
|
@@ -626,18 +679,52 @@ async function logoutCommand(opts) {
|
|
|
626
679
|
clearSession(session.server);
|
|
627
680
|
clack.outro(`\u2713 Logged out of ${session.server}`);
|
|
628
681
|
}
|
|
629
|
-
function whoamiCommand(opts) {
|
|
682
|
+
async function whoamiCommand(opts) {
|
|
630
683
|
const session = loadSession(opts.server);
|
|
631
684
|
if (!session) {
|
|
632
685
|
console.log(`[camstack] Not logged in${opts.server ? ` to ${opts.server}` : ""}.`);
|
|
633
686
|
process.exit(1);
|
|
634
687
|
}
|
|
635
|
-
console.log(`[camstack] Active session:`);
|
|
688
|
+
console.log(`[camstack] Active session (local cache):`);
|
|
636
689
|
console.log(` server: ${session.server}`);
|
|
637
690
|
console.log(` username: ${session.username}`);
|
|
638
691
|
console.log(` token: ${session.token.slice(0, 12)}\u2026 (id: ${session.tokenId})`);
|
|
639
692
|
console.log(` scopes: ${session.scopes.map((s) => `${s.type}:${s.target}`).join(", ")}`);
|
|
640
693
|
console.log(` createdAt: ${new Date(session.createdAt).toISOString()}`);
|
|
694
|
+
console.log("");
|
|
695
|
+
console.log(`[camstack] Pinging ${session.server}/api/auth/whoami\u2026`);
|
|
696
|
+
try {
|
|
697
|
+
const controller = new AbortController();
|
|
698
|
+
const timer = setTimeout(() => controller.abort(), 3e3);
|
|
699
|
+
const res = await fetch(`${session.server}/api/auth/whoami`, {
|
|
700
|
+
headers: { Authorization: `Bearer ${session.token}` },
|
|
701
|
+
signal: controller.signal
|
|
702
|
+
});
|
|
703
|
+
clearTimeout(timer);
|
|
704
|
+
if (res.status === 401) {
|
|
705
|
+
console.error(`[camstack] \u2717 Token rejected by server (revoked / expired). Run \`camstack login\` to refresh.`);
|
|
706
|
+
process.exit(2);
|
|
707
|
+
}
|
|
708
|
+
if (!res.ok) {
|
|
709
|
+
const body = await res.text().catch(() => "");
|
|
710
|
+
console.error(`[camstack] \u2717 Server returned ${res.status}: ${body.slice(0, 160)}`);
|
|
711
|
+
process.exit(2);
|
|
712
|
+
}
|
|
713
|
+
const json = await res.json();
|
|
714
|
+
console.log(`[camstack] \u2713 Token valid on server.`);
|
|
715
|
+
console.log(` kind: ${json.kind ?? "unknown"}`);
|
|
716
|
+
if (json.userId) console.log(` userId: ${json.userId}`);
|
|
717
|
+
if (json.username) console.log(` username: ${json.username}`);
|
|
718
|
+
if (Array.isArray(json.scopes)) {
|
|
719
|
+
const scopeList = json.scopes.map((s) => `${s.type}:${s.target}`).join(", ");
|
|
720
|
+
console.log(` scopes: ${scopeList}`);
|
|
721
|
+
}
|
|
722
|
+
} catch (err) {
|
|
723
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
724
|
+
console.error(`[camstack] \u26A0 Could not reach server: ${msg}`);
|
|
725
|
+
console.error(`[camstack] The local cache may be stale. Try \`camstack login\` once the server is reachable.`);
|
|
726
|
+
process.exit(2);
|
|
727
|
+
}
|
|
641
728
|
}
|
|
642
729
|
|
|
643
730
|
// src/commands/update.ts
|
|
@@ -1234,7 +1321,7 @@ async function runLogout(args) {
|
|
|
1234
1321
|
const server = stringOpt(parsed.values, "server") ?? process.env.CAMSTACK_SERVER ?? "https://localhost:4443";
|
|
1235
1322
|
await logoutCommand({ server });
|
|
1236
1323
|
}
|
|
1237
|
-
function runWhoami(args) {
|
|
1324
|
+
async function runWhoami(args) {
|
|
1238
1325
|
const parsed = parseSubcommandArgs(args, {
|
|
1239
1326
|
server: { type: "string", short: "s" }
|
|
1240
1327
|
}, false);
|
|
@@ -1243,7 +1330,7 @@ function runWhoami(args) {
|
|
|
1243
1330
|
return;
|
|
1244
1331
|
}
|
|
1245
1332
|
const server = stringOpt(parsed.values, "server");
|
|
1246
|
-
whoamiCommand(server ? { server } : {});
|
|
1333
|
+
await whoamiCommand(server ? { server } : {});
|
|
1247
1334
|
}
|
|
1248
1335
|
async function runDeploy(args) {
|
|
1249
1336
|
const parsed = parseSubcommandArgs(args, {
|