camstack 0.6.0 → 0.7.0

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 +179 -9
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -6,9 +6,9 @@ import {
6
6
  // src/cli.ts
7
7
  import { createRequire } from "module";
8
8
  import { fileURLToPath } from "url";
9
- import { dirname, resolve as resolve2 } from "path";
9
+ import { dirname, resolve as resolve3 } from "path";
10
10
  import * as os4 from "os";
11
- import { parseArgs as parseArgs5 } from "util";
11
+ import { parseArgs as parseArgs6 } from "util";
12
12
 
13
13
  // src/commands/serve.ts
14
14
  import { parseArgs } from "util";
@@ -202,7 +202,7 @@ function buildAddon(addonDir, scriptName) {
202
202
  });
203
203
  } catch (err) {
204
204
  const msg = err instanceof Error ? err.message : String(err);
205
- throw new Error(`Build script "${scriptName}" failed in ${addonDir}: ${msg}`);
205
+ throw new Error(`Build script "${scriptName}" failed in ${addonDir}: ${msg}`, { cause: err });
206
206
  }
207
207
  }
208
208
  function packAddon(addonDir) {
@@ -750,10 +750,10 @@ function isRunningViaNpx() {
750
750
  return entry.includes("/_npx/") || entry.includes("\\_npx\\");
751
751
  }
752
752
  function runNpmInstall(spec) {
753
- return new Promise((resolve3, reject) => {
753
+ return new Promise((resolve4, reject) => {
754
754
  const proc = spawn("npm", ["install", "-g", spec], { stdio: "inherit" });
755
755
  proc.on("exit", (code) => {
756
- if (code === 0) resolve3();
756
+ if (code === 0) resolve4();
757
757
  else reject(new Error(`npm install -g ${spec} exited with code ${code}`));
758
758
  });
759
759
  proc.on("error", reject);
@@ -794,12 +794,12 @@ async function runUpdate(args) {
794
794
  }
795
795
  const { createRequire: createRequire2 } = await import("module");
796
796
  const { fileURLToPath: fileURLToPath2 } = await import("url");
797
- const { dirname: dirname2, resolve: resolve3 } = await import("path");
797
+ const { dirname: dirname2, resolve: resolve4 } = await import("path");
798
798
  const require3 = createRequire2(import.meta.url);
799
799
  const here = dirname2(fileURLToPath2(import.meta.url));
800
800
  let current = "0.0.0";
801
801
  try {
802
- const pkg = require3(resolve3(here, "..", "package.json"));
802
+ const pkg = require3(resolve4(here, "..", "package.json"));
803
803
  current = pkg.version;
804
804
  } catch {
805
805
  }
@@ -829,12 +829,151 @@ async function runUpdate(args) {
829
829
  clack2.outro(`\u2713 Installed camstack@${target}. Verify with: camstack --version`);
830
830
  }
831
831
 
832
+ // src/commands/dev.ts
833
+ import * as fs5 from "fs";
834
+ import * as path5 from "path";
835
+ import { parseArgs as parseArgs5 } from "util";
836
+ import * as clack3 from "@clack/prompts";
837
+ var DEFAULT_DEBOUNCE_MS = 500;
838
+ var DEFAULT_WATCH_SUBDIR = "src";
839
+ function resolveAddonDir(arg) {
840
+ if (arg.endsWith(".tgz") || arg.endsWith(".tar.gz")) {
841
+ throw new Error("`dev` requires a source directory, not a tarball (nothing to watch).");
842
+ }
843
+ const absolute = path5.resolve(arg);
844
+ if (fs5.existsSync(absolute) && fs5.statSync(absolute).isDirectory()) {
845
+ return absolute;
846
+ }
847
+ for (const candidate of [
848
+ path5.resolve("packages", `addon-${arg}`),
849
+ path5.resolve("packages", arg)
850
+ ]) {
851
+ if (fs5.existsSync(candidate) && fs5.statSync(candidate).isDirectory()) {
852
+ return candidate;
853
+ }
854
+ }
855
+ throw new Error(`Path not resolved: ${arg} (tried as-is + packages/addon-${arg} + packages/${arg})`);
856
+ }
857
+ function watchRecursive(target, onEvent) {
858
+ try {
859
+ const w = fs5.watch(target, { recursive: true }, onEvent);
860
+ return { close: () => w.close(), recursive: true };
861
+ } catch {
862
+ const w = fs5.watch(target, onEvent);
863
+ return { close: () => w.close(), recursive: false };
864
+ }
865
+ }
866
+ async function pushOnce(addonPath, opts, label) {
867
+ const start = Date.now();
868
+ console.log(`
869
+ [camstack dev] \u2192 ${label}\u2026`);
870
+ try {
871
+ await deployAddon(addonPath, opts);
872
+ const took = ((Date.now() - start) / 1e3).toFixed(1);
873
+ console.log(`[camstack dev] \u2713 Synced in ${took}s`);
874
+ } catch (err) {
875
+ const msg = err instanceof Error ? err.message : String(err);
876
+ console.error(`[camstack dev] \u2717 Push failed: ${msg}`);
877
+ console.error(`[camstack dev] Waiting for next change\u2026`);
878
+ }
879
+ }
880
+ async function runDev(args) {
881
+ const { values, positionals } = parseArgs5({
882
+ args: [...args],
883
+ options: {
884
+ server: { type: "string", short: "s" },
885
+ token: { type: "string", short: "t" },
886
+ node: { type: "string", short: "n" },
887
+ cluster: { type: "boolean", short: "c" },
888
+ "watch-path": { type: "string", short: "w" },
889
+ debounce: { type: "string" },
890
+ "no-build": { type: "boolean" },
891
+ "build-script": { type: "string" },
892
+ "no-initial-push": { type: "boolean" }
893
+ },
894
+ strict: true,
895
+ allowPositionals: true
896
+ });
897
+ const addonArg = positionals[0] ?? ".";
898
+ const addonDir = resolveAddonDir(addonArg);
899
+ const explicitServer = typeof values.server === "string" ? values.server : void 0;
900
+ const explicitToken = typeof values.token === "string" ? values.token : void 0;
901
+ const session = loadSession(explicitServer);
902
+ const serverUrl = explicitServer ?? process.env.CAMSTACK_SERVER ?? session?.server;
903
+ const token = explicitToken ?? process.env.CAMSTACK_TOKEN ?? session?.token;
904
+ if (!serverUrl || !token) {
905
+ throw new Error("Missing server/token. Run `camstack login` first, or pass --server + --token.");
906
+ }
907
+ const nodeId = typeof values.node === "string" ? values.node : void 0;
908
+ const cluster = values.cluster === true;
909
+ const deployOpts = {
910
+ serverUrl,
911
+ token,
912
+ ...nodeId ? { nodeId } : {},
913
+ ...cluster ? { cluster: true } : {},
914
+ buildMode: values["no-build"] === true ? "skip" : "auto",
915
+ ...typeof values["build-script"] === "string" ? { buildScript: values["build-script"] } : {}
916
+ };
917
+ const watchOverride = typeof values["watch-path"] === "string" ? values["watch-path"] : void 0;
918
+ const watchTarget = watchOverride ? path5.resolve(addonDir, watchOverride) : fs5.existsSync(path5.join(addonDir, DEFAULT_WATCH_SUBDIR)) ? path5.join(addonDir, DEFAULT_WATCH_SUBDIR) : addonDir;
919
+ if (!fs5.existsSync(watchTarget)) {
920
+ throw new Error(`Watch path does not exist: ${watchTarget}`);
921
+ }
922
+ const debounceMs = typeof values.debounce === "string" ? parseInt(values.debounce, 10) : DEFAULT_DEBOUNCE_MS;
923
+ clack3.intro(`camstack dev \u2014 ${path5.basename(addonDir)}`);
924
+ clack3.log.info(`Watch: ${watchTarget}`);
925
+ clack3.log.info(`Target: ${serverUrl}${cluster ? " (cluster)" : nodeId ? ` (node: ${nodeId})` : " (hub)"}`);
926
+ clack3.log.info(`Debounce: ${debounceMs}ms`);
927
+ if (values["no-initial-push"] !== true) {
928
+ await pushOnce(addonDir, deployOpts, "Initial sync");
929
+ } else {
930
+ console.log("[camstack dev] Skipping initial push (--no-initial-push). Waiting for changes\u2026");
931
+ }
932
+ let timer = null;
933
+ let pushing = false;
934
+ let pending = false;
935
+ const runDebouncedPush = () => {
936
+ if (timer) clearTimeout(timer);
937
+ timer = setTimeout(async () => {
938
+ if (pushing) {
939
+ pending = true;
940
+ return;
941
+ }
942
+ pushing = true;
943
+ try {
944
+ await pushOnce(addonDir, deployOpts, "File change detected \u2014 rebuilding");
945
+ } finally {
946
+ pushing = false;
947
+ if (pending) {
948
+ pending = false;
949
+ runDebouncedPush();
950
+ }
951
+ }
952
+ }, debounceMs);
953
+ };
954
+ const { close, recursive } = watchRecursive(watchTarget, () => runDebouncedPush());
955
+ if (!recursive) {
956
+ clack3.log.warn("Recursive watch not supported on this platform \u2014 only top-level changes will trigger a push. Upgrade Node 20+ for recursive support.");
957
+ }
958
+ const shutdown = () => {
959
+ console.log("\n[camstack dev] Stopping watcher\u2026");
960
+ if (timer) clearTimeout(timer);
961
+ close();
962
+ process.exit(0);
963
+ };
964
+ process.on("SIGINT", shutdown);
965
+ process.on("SIGTERM", shutdown);
966
+ console.log("\n[camstack dev] Watching for changes. Ctrl-C to stop.");
967
+ await new Promise(() => {
968
+ });
969
+ }
970
+
832
971
  // src/cli.ts
833
972
  var __dirname = dirname(fileURLToPath(import.meta.url));
834
973
  var require2 = createRequire(import.meta.url);
835
974
  var pkgVersion = "0.1.0";
836
975
  try {
837
- const pkg = require2(resolve2(__dirname, "..", "package.json"));
976
+ const pkg = require2(resolve3(__dirname, "..", "package.json"));
838
977
  pkgVersion = pkg.version;
839
978
  } catch {
840
979
  }
@@ -987,6 +1126,37 @@ function buildCommands() {
987
1126
  " -c, --cluster Push to hub + every online agent (requires admin token)"
988
1127
  ].join("\n")
989
1128
  },
1129
+ {
1130
+ name: "dev",
1131
+ aliases: ["watch"],
1132
+ summary: "Watch addon source + auto build & push on every change (live-reload loop)",
1133
+ run: runDev,
1134
+ help: () => [
1135
+ "Usage: camstack dev [options] [path]",
1136
+ " camstack watch [options] [path] (alias)",
1137
+ "",
1138
+ "Argument:",
1139
+ ' path Addon directory (or workspace name). Default: "."',
1140
+ "",
1141
+ "Watch options:",
1142
+ " -w, --watch-path <dir> Subpath to watch (default: <addon>/src if it exists, else addon root)",
1143
+ " --debounce <ms> Coalesce bursts of changes (default: 500)",
1144
+ " --no-initial-push Skip the first push, only react to changes",
1145
+ "",
1146
+ "Build options:",
1147
+ " --no-build Skip `npm run build` (just pack + upload)",
1148
+ " --build-script <n> Override script name (default: build)",
1149
+ "",
1150
+ "Target options:",
1151
+ " -s, --server <url> Server URL (default: cached session)",
1152
+ " -t, --token <token> Auth token override",
1153
+ " -n, --node <id> Push to a specific node",
1154
+ " -c, --cluster Push to hub + every online agent",
1155
+ "",
1156
+ "Stop with Ctrl-C. Each cycle is sequential \u2014 concurrent changes during",
1157
+ "a push are coalesced into the next cycle."
1158
+ ].join("\n")
1159
+ },
990
1160
  {
991
1161
  name: "update",
992
1162
  aliases: ["upgrade"],
@@ -1020,7 +1190,7 @@ function buildCommands() {
1020
1190
  }
1021
1191
  function parseSubcommandArgs(args, options, allowPositionals) {
1022
1192
  if (args.some((a) => HELP_FLAGS.has(a))) return null;
1023
- const parsed = parseArgs5({
1193
+ const parsed = parseArgs6({
1024
1194
  args: [...args],
1025
1195
  options,
1026
1196
  allowPositionals,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "camstack",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "CLI tool for managing and running CamStack server",
5
5
  "keywords": [
6
6
  "camstack",