camstack 1.1.21 → 1.1.23

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 CHANGED
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runDiscover
4
- } from "./chunk-VL5TLTQF.js";
4
+ } from "./chunk-ODEV2JIB.js";
5
5
  import "./chunk-K3NQKI34.js";
6
6
 
7
7
  // src/cli.ts
8
8
  import { createRequire } from "module";
9
9
  import { fileURLToPath } from "url";
10
- import { dirname, resolve as resolve3 } from "path";
11
- import * as os4 from "os";
10
+ import { dirname, resolve as resolve5 } from "path";
11
+ import * as os5 from "os";
12
12
  import { parseArgs as parseArgs7 } from "util";
13
13
 
14
14
  // src/commands/serve.ts
@@ -151,7 +151,7 @@ function hubRequest(url, opts = {}) {
151
151
  const parsed = new URL(url);
152
152
  const isHttps = parsed.protocol === "https:";
153
153
  const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
154
- return new Promise((resolve4, reject) => {
154
+ return new Promise((resolve6, reject) => {
155
155
  const requestOptions = {
156
156
  protocol: parsed.protocol,
157
157
  hostname: parsed.hostname,
@@ -168,7 +168,7 @@ function hubRequest(url, opts = {}) {
168
168
  res.on("end", () => {
169
169
  const buffer = Buffer2.concat(chunks);
170
170
  const status = res.statusCode ?? 0;
171
- resolve4({
171
+ resolve6({
172
172
  ok: status >= 200 && status < 300,
173
173
  status,
174
174
  statusText: res.statusMessage ?? "",
@@ -538,25 +538,424 @@ async function deployAddon(addonPath, opts) {
538
538
  }
539
539
  }
540
540
 
541
- // src/commands/auth.ts
541
+ // src/commands/deploy-server.ts
542
+ import { execSync as execSync2 } from "child_process";
543
+ import { randomBytes as randomBytes2 } from "crypto";
544
+ import * as fs4 from "fs";
545
+ import * as os2 from "os";
546
+ import * as path4 from "path";
547
+
548
+ // src/commands/server-closure.ts
542
549
  import * as fs3 from "fs";
543
550
  import * as path3 from "path";
544
- import * as os2 from "os";
551
+ var CAMSTACK_SCOPE_PREFIX = "@camstack/";
552
+ function computeDevVersion(base, epochSeconds) {
553
+ return `${base}-dev.${epochSeconds}`;
554
+ }
555
+ function readPackageManifest(dir) {
556
+ const pkgJsonPath = path3.join(dir, "package.json");
557
+ try {
558
+ const parsed = JSON.parse(fs3.readFileSync(pkgJsonPath, "utf-8"));
559
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
560
+ const raw = parsed;
561
+ if (typeof raw["name"] !== "string") return null;
562
+ return {
563
+ name: raw["name"],
564
+ version: typeof raw["version"] === "string" ? raw["version"] : "0.0.0",
565
+ raw
566
+ };
567
+ } catch {
568
+ return null;
569
+ }
570
+ }
571
+ function loadWorkspacePackages(workspaceRoot) {
572
+ const root = readPackageManifest(workspaceRoot);
573
+ if (root === null) {
574
+ throw new Error(`No parseable package.json at workspace root: ${workspaceRoot}`);
575
+ }
576
+ const workspaces = root.raw["workspaces"];
577
+ const patterns = Array.isArray(workspaces) ? workspaces.filter((w) => typeof w === "string") : [];
578
+ const dirs = [];
579
+ for (const pattern of patterns) {
580
+ if (pattern.endsWith("/*")) {
581
+ const base = path3.join(workspaceRoot, pattern.slice(0, -2));
582
+ let entries;
583
+ try {
584
+ entries = fs3.readdirSync(base, { withFileTypes: true });
585
+ } catch {
586
+ continue;
587
+ }
588
+ for (const entry of entries) {
589
+ if (entry.isDirectory() || entry.isSymbolicLink()) dirs.push(path3.join(base, entry.name));
590
+ }
591
+ } else {
592
+ dirs.push(path3.join(workspaceRoot, pattern));
593
+ }
594
+ }
595
+ const byName = /* @__PURE__ */ new Map();
596
+ for (const dir of dirs) {
597
+ const manifest = readPackageManifest(dir);
598
+ if (manifest === null) continue;
599
+ byName.set(manifest.name, { name: manifest.name, dir, version: manifest.version });
600
+ }
601
+ return byName;
602
+ }
603
+ var INSTALL_DEP_FIELDS = [
604
+ "dependencies",
605
+ "optionalDependencies",
606
+ "peerDependencies"
607
+ ];
608
+ var BUILD_DEP_FIELDS = [...INSTALL_DEP_FIELDS, "devDependencies"];
609
+ function depNames(manifest, fields) {
610
+ const names = /* @__PURE__ */ new Set();
611
+ for (const field of fields) {
612
+ const deps = manifest[field];
613
+ if (deps === null || typeof deps !== "object" || Array.isArray(deps)) continue;
614
+ for (const name of Object.keys(deps)) names.add(name);
615
+ }
616
+ return [...names];
617
+ }
618
+ function walkClosure(rootName, workspace, fields) {
619
+ const ordered = [];
620
+ const visited = /* @__PURE__ */ new Set();
621
+ const visit = (name) => {
622
+ if (visited.has(name)) return;
623
+ visited.add(name);
624
+ const pkg = workspace.get(name);
625
+ if (pkg === void 0) return;
626
+ const manifest = readPackageManifest(pkg.dir);
627
+ if (manifest !== null) {
628
+ for (const dep of depNames(manifest.raw, fields)) {
629
+ if (dep.startsWith(CAMSTACK_SCOPE_PREFIX) && workspace.has(dep)) visit(dep);
630
+ }
631
+ }
632
+ ordered.push(pkg);
633
+ };
634
+ visit(rootName);
635
+ return ordered;
636
+ }
637
+ function discoverServerClosure(workspaceRoot) {
638
+ const serverDir = path3.join(workspaceRoot, "server", "backend");
639
+ const serverManifest = readPackageManifest(serverDir);
640
+ if (serverManifest === null) {
641
+ throw new Error(
642
+ `No parseable package.json at ${path3.join("server/backend", "package.json")} \u2014 run \`camstack deploy-server\` from the camstack-server workspace root.`
643
+ );
644
+ }
645
+ const workspace = loadWorkspacePackages(workspaceRoot);
646
+ const serverEntry = workspace.get(serverManifest.name);
647
+ if (serverEntry === void 0 || path3.resolve(serverEntry.dir) !== path3.resolve(serverDir)) {
648
+ throw new Error(
649
+ `${serverManifest.name} at server/backend is not listed in the root workspaces globs.`
650
+ );
651
+ }
652
+ const packSet = walkClosure(serverManifest.name, workspace, INSTALL_DEP_FIELDS);
653
+ const buildSet = walkClosure(serverManifest.name, workspace, BUILD_DEP_FIELDS);
654
+ return { serverVersion: serverManifest.version, packSet, buildSet };
655
+ }
656
+ function withDevVersionPackageJson(pkgDir, devVersion, intraSetNames, fn) {
657
+ const pkgJsonPath = path3.join(pkgDir, "package.json");
658
+ const originalBytes = fs3.readFileSync(pkgJsonPath);
659
+ const parsed = JSON.parse(originalBytes.toString("utf-8"));
660
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
661
+ throw new Error(`Malformed package.json: ${pkgJsonPath}`);
662
+ }
663
+ const manifest = {
664
+ ...parsed,
665
+ version: devVersion
666
+ };
667
+ for (const field of BUILD_DEP_FIELDS) {
668
+ const deps = manifest[field];
669
+ if (deps === null || typeof deps !== "object" || Array.isArray(deps)) continue;
670
+ const rewritten = {};
671
+ for (const [name, range] of Object.entries(deps)) {
672
+ rewritten[name] = intraSetNames.has(name) ? devVersion : range;
673
+ }
674
+ manifest[field] = rewritten;
675
+ }
676
+ fs3.writeFileSync(pkgJsonPath, `${JSON.stringify(manifest, null, 2)}
677
+ `, "utf-8");
678
+ try {
679
+ return fn();
680
+ } finally {
681
+ fs3.writeFileSync(pkgJsonPath, originalBytes);
682
+ }
683
+ }
684
+
685
+ // src/commands/deploy-server.ts
686
+ var BUILD_TIMEOUT_MS = 15 * 6e4;
687
+ var PACK_TIMEOUT_MS = 2 * 6e4;
688
+ var UPLOAD_TIMEOUT_MS = 10 * 6e4;
689
+ var APPLY_TIMEOUT_MS = 6e4;
690
+ var POLL_INTERVAL_MS = 3e3;
691
+ var POLL_BUDGET_MS = 10 * 6e4;
692
+ var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
693
+ function isRecord2(value) {
694
+ return value !== null && typeof value === "object" && !Array.isArray(value);
695
+ }
696
+ function hasBuildScript(pkgDir) {
697
+ const manifest = readJson(path4.join(pkgDir, "package.json"));
698
+ if (!isRecord2(manifest)) return false;
699
+ const scripts = manifest["scripts"];
700
+ return isRecord2(scripts) && typeof scripts["build"] === "string" && scripts["build"].length > 0;
701
+ }
702
+ function readJson(filePath) {
703
+ try {
704
+ return JSON.parse(fs4.readFileSync(filePath, "utf-8"));
705
+ } catch {
706
+ return null;
707
+ }
708
+ }
709
+ function buildPackage(pkg) {
710
+ if (!hasBuildScript(pkg.dir)) {
711
+ console.log(`[camstack] ${pkg.name}: no build script \u2014 skipping`);
712
+ return;
713
+ }
714
+ console.log(`[camstack] ${pkg.name}: npm run build`);
715
+ try {
716
+ execSync2("npm run build", { cwd: pkg.dir, stdio: "inherit", timeout: BUILD_TIMEOUT_MS });
717
+ } catch (err) {
718
+ const msg = err instanceof Error ? err.message : String(err);
719
+ throw new Error(`Build failed for ${pkg.name} (${pkg.dir}): ${msg}`, { cause: err });
720
+ }
721
+ }
722
+ function toPackFilename(raw) {
723
+ try {
724
+ const parsed = JSON.parse(raw);
725
+ if (!Array.isArray(parsed)) return null;
726
+ const first = parsed[0];
727
+ if (!isRecord2(first)) return null;
728
+ return typeof first["filename"] === "string" ? first["filename"] : null;
729
+ } catch {
730
+ const lines = raw.split("\n").filter((l) => l.trim().length > 0);
731
+ const last = lines[lines.length - 1]?.trim();
732
+ return last !== void 0 && last.length > 0 ? last : null;
733
+ }
734
+ }
735
+ function packPackage(pkg, devVersion, intraSetNames, outDir) {
736
+ const output = withDevVersionPackageJson(
737
+ pkg.dir,
738
+ devVersion,
739
+ intraSetNames,
740
+ () => execSync2(`npm pack --json --pack-destination ${JSON.stringify(outDir)}`, {
741
+ cwd: pkg.dir,
742
+ encoding: "utf8",
743
+ timeout: PACK_TIMEOUT_MS,
744
+ stdio: ["ignore", "pipe", "pipe"]
745
+ }).trim()
746
+ );
747
+ const filename = toPackFilename(output);
748
+ if (filename === null) {
749
+ throw new Error(`npm pack produced no tarball for ${pkg.name}`);
750
+ }
751
+ const tgzPath = path4.join(outDir, path4.basename(filename));
752
+ if (!fs4.existsSync(tgzPath)) {
753
+ throw new Error(`Expected tarball not found after pack: ${tgzPath}`);
754
+ }
755
+ return { name: pkg.name, tgzPath };
756
+ }
757
+ async function uploadClosure(args) {
758
+ const boundary = `----camstackdeployserver${randomBytes2(16).toString("hex")}`;
759
+ const CRLF = "\r\n";
760
+ const parts = [
761
+ Buffer.from(
762
+ `--${boundary}${CRLF}Content-Disposition: form-data; name="version"${CRLF}${CRLF}${args.version}${CRLF}`
763
+ )
764
+ ];
765
+ for (const tarball of args.tarballs) {
766
+ parts.push(
767
+ Buffer.from(
768
+ `--${boundary}${CRLF}Content-Disposition: form-data; name="file"; filename="${path4.basename(tarball.tgzPath)}"${CRLF}Content-Type: application/gzip${CRLF}${CRLF}`
769
+ ),
770
+ fs4.readFileSync(tarball.tgzPath),
771
+ Buffer.from(CRLF)
772
+ );
773
+ }
774
+ parts.push(Buffer.from(`--${boundary}--${CRLF}`));
775
+ const body = Buffer.concat(parts);
776
+ const response = await hubRequest(`${args.serverUrl}/api/server-upload`, {
777
+ method: "POST",
778
+ headers: {
779
+ Authorization: `Bearer ${args.token}`,
780
+ "Content-Type": `multipart/form-data; boundary=${boundary}`,
781
+ "Content-Length": String(body.length)
782
+ },
783
+ body,
784
+ timeoutMs: UPLOAD_TIMEOUT_MS
785
+ });
786
+ const payload = await response.json().catch(() => ({}));
787
+ if (!response.ok || !isRecord2(payload) || payload["success"] !== true) {
788
+ const detail = isRecord2(payload) && typeof payload["error"] === "string" ? payload["error"] : `${response.status} ${response.statusText}`;
789
+ throw new Error(`Server upload rejected: ${detail}`);
790
+ }
791
+ }
792
+ function toApplyResult(value) {
793
+ if (!isRecord2(value)) return null;
794
+ if (typeof value["accepted"] !== "boolean" || typeof value["message"] !== "string") return null;
795
+ return {
796
+ accepted: value["accepted"],
797
+ restarting: value["restarting"] === true,
798
+ message: value["message"]
799
+ };
800
+ }
801
+ function unwrapTrpcJson(body) {
802
+ const first = Array.isArray(body) ? body[0] : body;
803
+ if (!isRecord2(first)) return null;
804
+ const result = first["result"];
805
+ if (!isRecord2(result)) return null;
806
+ const data = result["data"];
807
+ if (!isRecord2(data)) return null;
808
+ return data["json"];
809
+ }
810
+ async function applyServerUpdate(args) {
811
+ const res = await hubRequest(
812
+ `${args.serverUrl}/trpc/serverManagement.applyServerUpdate?batch=1`,
813
+ {
814
+ method: "POST",
815
+ headers: {
816
+ Authorization: `Bearer ${args.token}`,
817
+ "Content-Type": "application/json"
818
+ },
819
+ body: JSON.stringify({ "0": { json: { version: args.version } } }),
820
+ timeoutMs: APPLY_TIMEOUT_MS
821
+ }
822
+ );
823
+ const body = await res.json().catch(() => null);
824
+ if (!res.ok) {
825
+ throw new Error(`applyServerUpdate returned ${res.status} ${res.statusText}`);
826
+ }
827
+ const result = toApplyResult(unwrapTrpcJson(body));
828
+ if (result === null) {
829
+ throw new Error("applyServerUpdate returned an unexpected payload shape");
830
+ }
831
+ return result;
832
+ }
833
+ function toStatusView(value) {
834
+ if (!isRecord2(value)) return null;
835
+ if (typeof value["updateState"] !== "string") return null;
836
+ const rolledBack = value["rolledBack"];
837
+ return {
838
+ runningVersion: typeof value["runningVersion"] === "string" ? value["runningVersion"] : null,
839
+ updateState: value["updateState"],
840
+ pendingVersion: typeof value["pendingVersion"] === "string" ? value["pendingVersion"] : null,
841
+ rolledBackFrom: isRecord2(rolledBack) && typeof rolledBack["fromVersion"] === "string" ? rolledBack["fromVersion"] : null,
842
+ rolledBackReason: isRecord2(rolledBack) && typeof rolledBack["reason"] === "string" ? rolledBack["reason"] : null
843
+ };
844
+ }
845
+ async function fetchServerStatus(serverUrl, token) {
846
+ const url = `${serverUrl}/trpc/serverManagement.getServerPackageStatus?input=${encodeURIComponent('{"json":null}')}`;
847
+ const res = await hubRequest(url, {
848
+ headers: { Authorization: `Bearer ${token}` },
849
+ timeoutMs: 1e4
850
+ });
851
+ if (!res.ok) return null;
852
+ const body = await res.json().catch(() => null);
853
+ return toStatusView(unwrapTrpcJson(body));
854
+ }
855
+ async function pollUntilPromoted(args) {
856
+ const deadline = Date.now() + POLL_BUDGET_MS;
857
+ let lastState = "";
858
+ while (Date.now() < deadline) {
859
+ let status = null;
860
+ try {
861
+ status = await fetchServerStatus(args.serverUrl, args.token);
862
+ } catch {
863
+ }
864
+ if (status !== null) {
865
+ const stateLine = `${status.updateState} (running ${status.runningVersion ?? "?"})`;
866
+ if (stateLine !== lastState) {
867
+ console.log(`[camstack] state: ${stateLine}`);
868
+ lastState = stateLine;
869
+ }
870
+ if (status.rolledBackFrom === args.version) {
871
+ throw new Error(
872
+ `Dev version ${args.version} was ROLLED BACK: ${status.rolledBackReason ?? "probation boot failed"}`
873
+ );
874
+ }
875
+ if (status.runningVersion === args.version && status.updateState === "idle") {
876
+ return;
877
+ }
878
+ }
879
+ await sleep2(POLL_INTERVAL_MS);
880
+ }
881
+ throw new Error(
882
+ `Timed out after ${POLL_BUDGET_MS / 6e4} minutes waiting for ${args.version} to promote \u2014 check \`serverManagement.getServerPackageStatus\` on the hub.`
883
+ );
884
+ }
885
+ function printClosure(closure) {
886
+ console.log(
887
+ `[camstack] Closure from server/backend: ${closure.packSet.length} packages to pack (+${closure.buildSet.length - closure.packSet.length} build-only)`
888
+ );
889
+ for (const pkg of closure.packSet) console.log(`[camstack] \u2022 ${pkg.name}`);
890
+ }
891
+ async function deployServer(opts) {
892
+ const workspaceRoot = path4.resolve(opts.workspaceRoot ?? ".");
893
+ const closure = discoverServerClosure(workspaceRoot);
894
+ printClosure(closure);
895
+ const devVersion = computeDevVersion(closure.serverVersion, Math.floor(Date.now() / 1e3));
896
+ console.log(`[camstack] Dev version: ${devVersion}`);
897
+ console.log(`[camstack] Building ${closure.buildSet.length} packages (dependency order)...`);
898
+ for (const pkg of closure.buildSet) buildPackage(pkg);
899
+ const intraSetNames = new Set(closure.packSet.map((p) => p.name));
900
+ const outDir = fs4.mkdtempSync(path4.join(os2.tmpdir(), "camstack-deploy-server-"));
901
+ try {
902
+ console.log(`[camstack] Packing ${closure.packSet.length} packages at ${devVersion}...`);
903
+ const tarballs = [];
904
+ for (const pkg of closure.packSet) {
905
+ const packed = packPackage(pkg, devVersion, intraSetNames, outDir);
906
+ console.log(`[camstack] \u2713 ${pkg.name} \u2192 ${path4.basename(packed.tgzPath)}`);
907
+ tarballs.push(packed);
908
+ }
909
+ console.log(`[camstack] Uploading ${tarballs.length} tarballs to ${opts.serverUrl}...`);
910
+ await uploadClosure({
911
+ serverUrl: opts.serverUrl,
912
+ token: opts.token,
913
+ version: devVersion,
914
+ tarballs
915
+ });
916
+ console.log("[camstack] \u2713 Upload accepted");
917
+ console.log(`[camstack] Applying server update to ${devVersion}...`);
918
+ const apply = await applyServerUpdate({
919
+ serverUrl: opts.serverUrl,
920
+ token: opts.token,
921
+ version: devVersion
922
+ });
923
+ if (!apply.accepted) {
924
+ console.error(`[camstack] \u2717 applyServerUpdate refused: ${apply.message}`);
925
+ process.exit(1);
926
+ }
927
+ console.log(`[camstack] \u2713 ${apply.message}`);
928
+ if (opts.verify === false) {
929
+ console.log("[camstack] --no-verify: not waiting for promotion (probation boot in progress)");
930
+ return;
931
+ }
932
+ console.log("[camstack] Waiting for the probation boot to promote...");
933
+ await pollUntilPromoted({ serverUrl: opts.serverUrl, token: opts.token, version: devVersion });
934
+ console.log(`[camstack] \u2713 ${devVersion} promoted \u2014 the hub is running the dev closure`);
935
+ } finally {
936
+ fs4.rmSync(outDir, { recursive: true, force: true });
937
+ }
938
+ }
939
+
940
+ // src/commands/auth.ts
941
+ import * as fs5 from "fs";
942
+ import * as path5 from "path";
943
+ import * as os3 from "os";
545
944
  import * as clack from "@clack/prompts";
546
- var SESSION_DIR = path3.join(os2.homedir(), ".camstack");
945
+ var SESSION_DIR = path5.join(os3.homedir(), ".camstack");
547
946
  function sessionFileForServer(serverUrl) {
548
947
  const slug = serverUrl.replace(/^https?:\/\//, "").replace(/[:/?#]+/g, "_").replace(/_+$/, "");
549
- return path3.join(SESSION_DIR, `${slug}.json`);
948
+ return path5.join(SESSION_DIR, `${slug}.json`);
550
949
  }
551
950
  function resolveSessionFile(serverUrl) {
552
951
  if (serverUrl) {
553
952
  const p = sessionFileForServer(serverUrl);
554
- return fs3.existsSync(p) ? p : null;
953
+ return fs5.existsSync(p) ? p : null;
555
954
  }
556
- if (!fs3.existsSync(SESSION_DIR)) return null;
557
- const files = fs3.readdirSync(SESSION_DIR).filter((f) => f.endsWith(".json")).map((f) => path3.join(SESSION_DIR, f));
955
+ if (!fs5.existsSync(SESSION_DIR)) return null;
956
+ const files = fs5.readdirSync(SESSION_DIR).filter((f) => f.endsWith(".json")).map((f) => path5.join(SESSION_DIR, f));
558
957
  if (files.length === 0) return null;
559
- return files.map((f) => ({ f, mtime: fs3.statSync(f).mtimeMs })).toSorted((a, b) => b.mtime - a.mtime)[0].f;
958
+ return files.map((f) => ({ f, mtime: fs5.statSync(f).mtimeMs })).toSorted((a, b) => b.mtime - a.mtime)[0].f;
560
959
  }
561
960
  function isSessionFile(value) {
562
961
  if (value === null || typeof value !== "object") return false;
@@ -573,24 +972,24 @@ function loadSession(serverUrl) {
573
972
  const file = resolveSessionFile(serverUrl);
574
973
  if (!file) return null;
575
974
  try {
576
- const parsed = JSON.parse(fs3.readFileSync(file, "utf8"));
975
+ const parsed = JSON.parse(fs5.readFileSync(file, "utf8"));
577
976
  return isSessionFile(parsed) ? parsed : null;
578
977
  } catch {
579
978
  return null;
580
979
  }
581
980
  }
582
981
  function saveSession(session) {
583
- if (!fs3.existsSync(SESSION_DIR)) {
584
- fs3.mkdirSync(SESSION_DIR, { recursive: true, mode: 448 });
982
+ if (!fs5.existsSync(SESSION_DIR)) {
983
+ fs5.mkdirSync(SESSION_DIR, { recursive: true, mode: 448 });
585
984
  }
586
985
  const file = sessionFileForServer(session.server);
587
- fs3.writeFileSync(file, JSON.stringify(session, null, 2), { mode: 384 });
986
+ fs5.writeFileSync(file, JSON.stringify(session, null, 2), { mode: 384 });
588
987
  return file;
589
988
  }
590
989
  function clearSession(serverUrl) {
591
990
  const file = sessionFileForServer(serverUrl);
592
- if (fs3.existsSync(file)) {
593
- fs3.unlinkSync(file);
991
+ if (fs5.existsSync(file)) {
992
+ fs5.unlinkSync(file);
594
993
  return true;
595
994
  }
596
995
  return false;
@@ -680,7 +1079,7 @@ function isUnknown(_value) {
680
1079
  return true;
681
1080
  }
682
1081
  async function resolveServerInteractive(presetNamespace) {
683
- const { discoverNodes, resolveHubFromDiscovered, filterHubNodes, DEFAULT_HUB_HTTPS_PORT } = await import("./discover-3WTX7YIA.js");
1082
+ const { discoverNodes, resolveHubFromDiscovered, filterHubNodes, DEFAULT_HUB_HTTPS_PORT } = await import("./discover-VNCOKFC2.js");
684
1083
  if (presetNamespace) {
685
1084
  const spinner4 = clack.spinner();
686
1085
  spinner4.start(`Discovering hub on LAN (namespace "${presetNamespace}")`);
@@ -817,7 +1216,7 @@ async function loginCommand(opts) {
817
1216
  }
818
1217
  const mintSpinner = clack.spinner();
819
1218
  mintSpinner.start("Creating scoped token (upload + log streaming)");
820
- const tokenName = opts.tokenName ?? `camstack-cli@${os2.hostname()}`;
1219
+ const tokenName = opts.tokenName ?? `camstack-cli@${os3.hostname()}`;
821
1220
  const scopes = [
822
1221
  {
823
1222
  type: "capability",
@@ -929,18 +1328,18 @@ import { spawn } from "child_process";
929
1328
  import * as clack2 from "@clack/prompts";
930
1329
 
931
1330
  // src/update-notifier.ts
932
- import * as fs4 from "fs";
933
- import * as path4 from "path";
934
- import * as os3 from "os";
935
- var CACHE_DIR = path4.join(os3.homedir(), ".camstack");
936
- var CACHE_FILE = path4.join(CACHE_DIR, "update-check.json");
1331
+ import * as fs6 from "fs";
1332
+ import * as path6 from "path";
1333
+ import * as os4 from "os";
1334
+ var CACHE_DIR = path6.join(os4.homedir(), ".camstack");
1335
+ var CACHE_FILE = path6.join(CACHE_DIR, "update-check.json");
937
1336
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
938
1337
  var REGISTRY_BASE = "https://registry.npmjs.org";
939
1338
  var FETCH_TIMEOUT_MS = 2500;
940
1339
  function readCache() {
941
1340
  try {
942
- if (!fs4.existsSync(CACHE_FILE)) return null;
943
- const parsed = JSON.parse(fs4.readFileSync(CACHE_FILE, "utf8"));
1341
+ if (!fs6.existsSync(CACHE_FILE)) return null;
1342
+ const parsed = JSON.parse(fs6.readFileSync(CACHE_FILE, "utf8"));
944
1343
  if (!parsed || typeof parsed !== "object") return null;
945
1344
  const c = parsed;
946
1345
  if (typeof c.lastCheckAt !== "number") return null;
@@ -955,8 +1354,8 @@ function readCache() {
955
1354
  }
956
1355
  function writeCache(c) {
957
1356
  try {
958
- if (!fs4.existsSync(CACHE_DIR)) fs4.mkdirSync(CACHE_DIR, { recursive: true, mode: 448 });
959
- fs4.writeFileSync(CACHE_FILE, JSON.stringify(c, null, 2));
1357
+ if (!fs6.existsSync(CACHE_DIR)) fs6.mkdirSync(CACHE_DIR, { recursive: true, mode: 448 });
1358
+ fs6.writeFileSync(CACHE_FILE, JSON.stringify(c, null, 2));
960
1359
  } catch {
961
1360
  }
962
1361
  }
@@ -1033,10 +1432,10 @@ function isRunningViaNpx() {
1033
1432
  return entry.includes("/_npx/") || entry.includes("\\_npx\\");
1034
1433
  }
1035
1434
  function runNpmInstall(spec) {
1036
- return new Promise((resolve4, reject) => {
1435
+ return new Promise((resolve6, reject) => {
1037
1436
  const proc = spawn("npm", ["install", "-g", spec], { stdio: "inherit" });
1038
1437
  proc.on("exit", (code) => {
1039
- if (code === 0) resolve4();
1438
+ if (code === 0) resolve6();
1040
1439
  else reject(new Error(`npm install -g ${spec} exited with code ${code}`));
1041
1440
  });
1042
1441
  proc.on("error", reject);
@@ -1077,12 +1476,12 @@ async function runUpdate(args) {
1077
1476
  }
1078
1477
  const { createRequire: createRequire2 } = await import("module");
1079
1478
  const { fileURLToPath: fileURLToPath2 } = await import("url");
1080
- const { dirname: dirname2, resolve: resolve4 } = await import("path");
1479
+ const { dirname: dirname2, resolve: resolve6 } = await import("path");
1081
1480
  const require3 = createRequire2(import.meta.url);
1082
1481
  const here = dirname2(fileURLToPath2(import.meta.url));
1083
1482
  let current = "0.0.0";
1084
1483
  try {
1085
- const pkg = require3(resolve4(here, "..", "package.json"));
1484
+ const pkg = require3(resolve6(here, "..", "package.json"));
1086
1485
  current = pkg.version;
1087
1486
  } catch {
1088
1487
  }
@@ -1115,8 +1514,8 @@ async function runUpdate(args) {
1115
1514
  }
1116
1515
 
1117
1516
  // src/commands/dev.ts
1118
- import * as fs5 from "fs";
1119
- import * as path5 from "path";
1517
+ import * as fs7 from "fs";
1518
+ import * as path7 from "path";
1120
1519
  import { parseArgs as parseArgs5 } from "util";
1121
1520
  import * as clack3 from "@clack/prompts";
1122
1521
  var DEFAULT_DEBOUNCE_MS = 500;
@@ -1125,15 +1524,15 @@ function resolveAddonDir(arg) {
1125
1524
  if (arg.endsWith(".tgz") || arg.endsWith(".tar.gz")) {
1126
1525
  throw new Error("`dev` requires a source directory, not a tarball (nothing to watch).");
1127
1526
  }
1128
- const absolute = path5.resolve(arg);
1129
- if (fs5.existsSync(absolute) && fs5.statSync(absolute).isDirectory()) {
1527
+ const absolute = path7.resolve(arg);
1528
+ if (fs7.existsSync(absolute) && fs7.statSync(absolute).isDirectory()) {
1130
1529
  return absolute;
1131
1530
  }
1132
1531
  for (const candidate of [
1133
- path5.resolve("packages", `addon-${arg}`),
1134
- path5.resolve("packages", arg)
1532
+ path7.resolve("packages", `addon-${arg}`),
1533
+ path7.resolve("packages", arg)
1135
1534
  ]) {
1136
- if (fs5.existsSync(candidate) && fs5.statSync(candidate).isDirectory()) {
1535
+ if (fs7.existsSync(candidate) && fs7.statSync(candidate).isDirectory()) {
1137
1536
  return candidate;
1138
1537
  }
1139
1538
  }
@@ -1143,10 +1542,10 @@ function resolveAddonDir(arg) {
1143
1542
  }
1144
1543
  function watchRecursive(target, onEvent) {
1145
1544
  try {
1146
- const w = fs5.watch(target, { recursive: true }, onEvent);
1545
+ const w = fs7.watch(target, { recursive: true }, onEvent);
1147
1546
  return { close: () => w.close(), recursive: true };
1148
1547
  } catch {
1149
- const w = fs5.watch(target, onEvent);
1548
+ const w = fs7.watch(target, onEvent);
1150
1549
  return { close: () => w.close(), recursive: false };
1151
1550
  }
1152
1551
  }
@@ -1202,12 +1601,12 @@ async function runDev(args) {
1202
1601
  ...typeof values["build-script"] === "string" ? { buildScript: values["build-script"] } : {}
1203
1602
  };
1204
1603
  const watchOverride = typeof values["watch-path"] === "string" ? values["watch-path"] : void 0;
1205
- const watchTarget = watchOverride ? path5.resolve(addonDir, watchOverride) : fs5.existsSync(path5.join(addonDir, DEFAULT_WATCH_SUBDIR)) ? path5.join(addonDir, DEFAULT_WATCH_SUBDIR) : addonDir;
1206
- if (!fs5.existsSync(watchTarget)) {
1604
+ const watchTarget = watchOverride ? path7.resolve(addonDir, watchOverride) : fs7.existsSync(path7.join(addonDir, DEFAULT_WATCH_SUBDIR)) ? path7.join(addonDir, DEFAULT_WATCH_SUBDIR) : addonDir;
1605
+ if (!fs7.existsSync(watchTarget)) {
1207
1606
  throw new Error(`Watch path does not exist: ${watchTarget}`);
1208
1607
  }
1209
1608
  const debounceMs = typeof values.debounce === "string" ? parseInt(values.debounce, 10) : DEFAULT_DEBOUNCE_MS;
1210
- clack3.intro(`camstack dev \u2014 ${path5.basename(addonDir)}`);
1609
+ clack3.intro(`camstack dev \u2014 ${path7.basename(addonDir)}`);
1211
1610
  clack3.log.info(`Watch: ${watchTarget}`);
1212
1611
  clack3.log.info(
1213
1612
  `Target: ${serverUrl}${cluster ? " (cluster)" : nodeId ? ` (node: ${nodeId})` : " (hub)"}`
@@ -1260,10 +1659,10 @@ async function runDev(args) {
1260
1659
  }
1261
1660
 
1262
1661
  // src/commands/watch.ts
1263
- import * as path6 from "path";
1662
+ import * as path8 from "path";
1264
1663
  import { parseArgs as parseArgs6 } from "util";
1265
1664
  import * as clack4 from "@clack/prompts";
1266
- var POLL_INTERVAL_MS = 2e3;
1665
+ var POLL_INTERVAL_MS2 = 2e3;
1267
1666
  var LOG_BATCH_LIMIT = 100;
1268
1667
  var LEVEL_COLOURS = {
1269
1668
  debug: "\x1B[90m",
@@ -1337,7 +1736,7 @@ async function runWatch(args) {
1337
1736
  };
1338
1737
  clack4.intro("camstack watch");
1339
1738
  const resolvedPath = await resolveAddonPathInteractive(addonArg);
1340
- const addonId = path6.basename(resolvedPath).replace(/^addon-/, "");
1739
+ const addonId = path8.basename(resolvedPath).replace(/^addon-/, "");
1341
1740
  if (values["no-initial-deploy"] !== true) {
1342
1741
  await deployAddon(resolvedPath, deployOpts);
1343
1742
  }
@@ -1386,7 +1785,7 @@ async function runWatch(args) {
1386
1785
  const msg = err instanceof Error ? err.message : String(err);
1387
1786
  console.error(`[camstack watch] poll error: ${msg}`);
1388
1787
  }
1389
- await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
1788
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS2));
1390
1789
  }
1391
1790
  }
1392
1791
 
@@ -1395,7 +1794,7 @@ var __dirname = dirname(fileURLToPath(import.meta.url));
1395
1794
  var require2 = createRequire(import.meta.url);
1396
1795
  var pkgVersion = "0.1.0";
1397
1796
  try {
1398
- const pkg = require2(resolve3(__dirname, "..", "package.json"));
1797
+ const pkg = require2(resolve5(__dirname, "..", "package.json"));
1399
1798
  pkgVersion = pkg.version;
1400
1799
  } catch {
1401
1800
  }
@@ -1481,7 +1880,7 @@ function buildCommands() {
1481
1880
  " -n, --namespace <ns> Filter discovery to this cluster namespace ($CAMSTACK_NAMESPACE)",
1482
1881
  " -u, --username <name> Username (skips prompt)",
1483
1882
  " -p, --password <pwd> Password (skips prompt \u2014 prefer letting the CLI prompt)",
1484
- ` --token-name <name> Name shown server-side (default: camstack-cli@${os4.hostname()})`
1883
+ ` --token-name <name> Name shown server-side (default: camstack-cli@${os5.hostname()})`
1485
1884
  ].join("\n")
1486
1885
  },
1487
1886
  {
@@ -1551,6 +1950,26 @@ function buildCommands() {
1551
1950
  " -c, --cluster Push to hub + every online agent (requires admin token)"
1552
1951
  ].join("\n")
1553
1952
  },
1953
+ {
1954
+ name: "deploy-server",
1955
+ summary: "Dev channel: build + pack the whole @camstack/server workspace closure, upload it, and apply it via the same stage/probation/promote cycle (no npm publish)",
1956
+ run: runDeployServer,
1957
+ help: () => [
1958
+ "Usage: camstack deploy-server [options]",
1959
+ "",
1960
+ "Run from the camstack-server workspace root. Discovers every workspace",
1961
+ " @camstack/* package in the @camstack/server dependency closure (graph-driven),",
1962
+ " builds them in dependency order, packs them at one uniform dev version",
1963
+ " (<serverBase>-dev.<epochSeconds>), uploads the tarballs to the hub",
1964
+ " (POST /api/server-upload), then applies via serverManagement.applyServerUpdate",
1965
+ " and waits for the probation boot to promote.",
1966
+ "",
1967
+ "Options:",
1968
+ " -s, --server <url> Server URL (default: cached session from `camstack login`)",
1969
+ " -t, --token <token> Auth token override ($CAMSTACK_TOKEN, then cached scoped token)",
1970
+ " --no-verify Do not wait for promotion after applyServerUpdate is accepted"
1971
+ ].join("\n")
1972
+ },
1554
1973
  {
1555
1974
  name: "watch",
1556
1975
  summary: "Deploy + tail server-side addon logs (polling /trpc/addons.getLogs)",
@@ -1626,10 +2045,10 @@ function buildCommands() {
1626
2045
  summary: "Print detailed version and platform info",
1627
2046
  run: () => {
1628
2047
  console.log(`camstack v${pkgVersion}`);
1629
- console.log(`Platform: ${os4.platform()}-${os4.arch()}`);
2048
+ console.log(`Platform: ${os5.platform()}-${os5.arch()}`);
1630
2049
  console.log(`Node.js: ${process.version}`);
1631
- console.log(`CPU: ${os4.cpus()[0]?.model ?? "unknown"} (${os4.cpus().length} cores)`);
1632
- console.log(`Memory: ${Math.round(os4.totalmem() / 1024 / 1024)} MB`);
2050
+ console.log(`CPU: ${os5.cpus()[0]?.model ?? "unknown"} (${os5.cpus().length} cores)`);
2051
+ console.log(`Memory: ${Math.round(os5.totalmem() / 1024 / 1024)} MB`);
1633
2052
  },
1634
2053
  help: () => "Usage: camstack info"
1635
2054
  }
@@ -1766,6 +2185,35 @@ async function runDeploy(args) {
1766
2185
  verify: !noVerify
1767
2186
  });
1768
2187
  }
2188
+ async function runDeployServer(args) {
2189
+ const parsed = parseSubcommandArgs(
2190
+ args,
2191
+ {
2192
+ server: { type: "string", short: "s" },
2193
+ token: { type: "string", short: "t" },
2194
+ "no-verify": { type: "boolean" }
2195
+ },
2196
+ false
2197
+ );
2198
+ if (!parsed) {
2199
+ console.log(commandHelp("deploy-server"));
2200
+ return;
2201
+ }
2202
+ const session = loadSession(stringOpt(parsed.values, "server"));
2203
+ const server = stringOpt(parsed.values, "server") ?? process.env.CAMSTACK_SERVER ?? session?.server ?? "https://localhost:4443";
2204
+ const token = stringOpt(parsed.values, "token") ?? process.env.CAMSTACK_TOKEN ?? session?.token;
2205
+ if (!token) {
2206
+ console.error(
2207
+ "[camstack] Error: No token. Run `camstack login` first, pass --token, or set CAMSTACK_TOKEN."
2208
+ );
2209
+ process.exit(1);
2210
+ }
2211
+ await deployServer({
2212
+ serverUrl: server,
2213
+ token,
2214
+ verify: !boolOpt(parsed.values, "no-verify")
2215
+ });
2216
+ }
1769
2217
  function commandHelp(name) {
1770
2218
  const cmd = buildCommands().find((c) => c.name === name);
1771
2219
  return cmd ? cmd.help() : "";