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.
- package/dist/cli.js +179 -9
- 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
|
|
9
|
+
import { dirname, resolve as resolve3 } from "path";
|
|
10
10
|
import * as os4 from "os";
|
|
11
|
-
import { parseArgs as
|
|
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((
|
|
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)
|
|
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:
|
|
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(
|
|
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(
|
|
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 =
|
|
1193
|
+
const parsed = parseArgs6({
|
|
1024
1194
|
args: [...args],
|
|
1025
1195
|
options,
|
|
1026
1196
|
allowPositionals,
|