camstack 0.7.1 → 0.7.3

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.
Files changed (2) hide show
  1. package/dist/cli.js +94 -12
  2. 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
- return JSON.parse(fs3.readFileSync(file, "utf8"));
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,17 +538,51 @@ 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 server = opts.server;
532
- if (!server) {
533
- server = await resolveServerInteractive(opts.namespace);
534
- } else {
535
- clack.log.info(`Server: ${server}`);
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;
@@ -633,18 +679,54 @@ async function logoutCommand(opts) {
633
679
  clearSession(session.server);
634
680
  clack.outro(`\u2713 Logged out of ${session.server}`);
635
681
  }
636
- function whoamiCommand(opts) {
682
+ async function whoamiCommand(opts) {
637
683
  const session = loadSession(opts.server);
638
684
  if (!session) {
639
685
  console.log(`[camstack] Not logged in${opts.server ? ` to ${opts.server}` : ""}.`);
640
686
  process.exit(1);
641
687
  }
642
- console.log(`[camstack] Active session:`);
688
+ console.log(`[camstack] Active session (local cache):`);
643
689
  console.log(` server: ${session.server}`);
644
690
  console.log(` username: ${session.username}`);
645
691
  console.log(` token: ${session.token.slice(0, 12)}\u2026 (id: ${session.tokenId})`);
646
692
  console.log(` scopes: ${session.scopes.map((s) => `${s.type}:${s.target}`).join(", ")}`);
647
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
+ clearSession(session.server);
706
+ console.error(`[camstack] \u2717 Token rejected by server (revoked / expired).`);
707
+ console.error(`[camstack] Local cache cleared. Run \`camstack login\` to issue a new one.`);
708
+ process.exit(2);
709
+ }
710
+ if (!res.ok) {
711
+ const body = await res.text().catch(() => "");
712
+ console.error(`[camstack] \u2717 Server returned ${res.status}: ${body.slice(0, 160)}`);
713
+ process.exit(2);
714
+ }
715
+ const json = await res.json();
716
+ console.log(`[camstack] \u2713 Token valid on server.`);
717
+ console.log(` kind: ${json.kind ?? "unknown"}`);
718
+ if (json.userId) console.log(` userId: ${json.userId}`);
719
+ if (json.username) console.log(` username: ${json.username}`);
720
+ if (Array.isArray(json.scopes)) {
721
+ const scopeList = json.scopes.map((s) => `${s.type}:${s.target}`).join(", ");
722
+ console.log(` scopes: ${scopeList}`);
723
+ }
724
+ } catch (err) {
725
+ const msg = err instanceof Error ? err.message : String(err);
726
+ console.error(`[camstack] \u26A0 Could not reach server: ${msg}`);
727
+ console.error(`[camstack] The local cache may be stale. Try \`camstack login\` once the server is reachable.`);
728
+ process.exit(2);
729
+ }
648
730
  }
649
731
 
650
732
  // src/commands/update.ts
@@ -1241,7 +1323,7 @@ async function runLogout(args) {
1241
1323
  const server = stringOpt(parsed.values, "server") ?? process.env.CAMSTACK_SERVER ?? "https://localhost:4443";
1242
1324
  await logoutCommand({ server });
1243
1325
  }
1244
- function runWhoami(args) {
1326
+ async function runWhoami(args) {
1245
1327
  const parsed = parseSubcommandArgs(args, {
1246
1328
  server: { type: "string", short: "s" }
1247
1329
  }, false);
@@ -1250,7 +1332,7 @@ function runWhoami(args) {
1250
1332
  return;
1251
1333
  }
1252
1334
  const server = stringOpt(parsed.values, "server");
1253
- whoamiCommand(server ? { server } : {});
1335
+ await whoamiCommand(server ? { server } : {});
1254
1336
  }
1255
1337
  async function runDeploy(args) {
1256
1338
  const parsed = parseSubcommandArgs(args, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "camstack",
3
- "version": "0.7.1",
3
+ "version": "0.7.3",
4
4
  "description": "CLI tool for managing and running CamStack server",
5
5
  "keywords": [
6
6
  "camstack",