deepspace 0.4.2 → 0.4.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.
package/dist/cli.js CHANGED
@@ -3,14 +3,14 @@
3
3
  // src/cli/cli.ts
4
4
  import { defineCommand as defineCommand19, runMain } from "citty";
5
5
  import { execSync as execSync5 } from "child_process";
6
- import { readFileSync as readFileSync11, existsSync as existsSync14 } from "fs";
7
- import { dirname as dirname2, join as join16 } from "path";
6
+ import { readFileSync as readFileSync11, existsSync as existsSync12 } from "fs";
7
+ import { dirname as dirname3, join as join14 } from "path";
8
8
  import { fileURLToPath } from "url";
9
9
 
10
10
  // src/cli/commands/login.ts
11
11
  import { defineCommand } from "citty";
12
12
  import { writeFileSync as writeFileSync2, mkdirSync } from "fs";
13
- import { join as join2 } from "path";
13
+ import { join as join3 } from "path";
14
14
  import { homedir } from "os";
15
15
  import { exec } from "child_process";
16
16
  import { platform } from "os";
@@ -35,7 +35,11 @@ function createSpinner() {
35
35
  }
36
36
 
37
37
  // src/cli/env.ts
38
- import { existsSync, readFileSync, writeFileSync } from "fs";
38
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
39
+ import { join as join2 } from "path";
40
+
41
+ // src/cli/lib/wrangler-env.ts
42
+ import { existsSync, readFileSync } from "fs";
39
43
  import { join } from "path";
40
44
  import { parse as parseToml } from "smol-toml";
41
45
 
@@ -92,6 +96,57 @@ function sanitizeAppName(raw) {
92
96
  return raw.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
93
97
  }
94
98
 
99
+ // src/cli/lib/wrangler-env.ts
100
+ function resolveAppNameForEnv(config, envName) {
101
+ if (!envName) {
102
+ return resolveAppName(config.name);
103
+ }
104
+ const envBlock = config.env?.[envName];
105
+ if (!envBlock) {
106
+ return {
107
+ ok: false,
108
+ reason: `wrangler.toml: no [env.${envName}] block found. Add one with a distinct \`name\` to deploy this environment.`
109
+ };
110
+ }
111
+ if (typeof envBlock.name !== "string" || envBlock.name.trim() === "") {
112
+ return {
113
+ ok: false,
114
+ reason: `wrangler.toml: [env.${envName}].name is missing. Set a distinct app name (e.g. "${typeof config.name === "string" ? config.name : "myapp"}-${envName}") so this environment doesn't overwrite the default.`
115
+ };
116
+ }
117
+ if (envBlock.name === config.name) {
118
+ return {
119
+ ok: false,
120
+ reason: `wrangler.toml: [env.${envName}].name "${envBlock.name}" is the same as the top-level name. Pick a distinct name (e.g. "${config.name}-${envName}") so deploys don't collide.`
121
+ };
122
+ }
123
+ return resolveAppName(envBlock.name);
124
+ }
125
+ function devVarsPathFor(appDir, envName) {
126
+ return envName ? join(appDir, `.dev.vars.${envName}`) : join(appDir, ".dev.vars");
127
+ }
128
+ var WranglerConfigError = class extends Error {
129
+ constructor(path, cause) {
130
+ const detail = cause instanceof Error ? cause.message : String(cause);
131
+ super(`wrangler.toml: malformed TOML at ${path}: ${detail}`);
132
+ this.path = path;
133
+ this.cause = cause;
134
+ this.name = "WranglerConfigError";
135
+ }
136
+ };
137
+ function readWranglerConfig(appDir) {
138
+ const path = join(appDir, "wrangler.toml");
139
+ const raw = readFileSync(path, "utf-8");
140
+ try {
141
+ return parseToml(raw);
142
+ } catch (err) {
143
+ throw new WranglerConfigError(path, err);
144
+ }
145
+ }
146
+ function hasWranglerConfig(appDir) {
147
+ return existsSync(join(appDir, "wrangler.toml"));
148
+ }
149
+
95
150
  // src/cli/env.ts
96
151
  var ENV_URLS = {
97
152
  auth: "https://auth.deep.space",
@@ -115,19 +170,18 @@ async function fetchPublicKey(authUrl) {
115
170
  }
116
171
  return data.publicKey;
117
172
  }
118
- function readAppName(appDir) {
119
- const wranglerPath = join(appDir, "wrangler.toml");
120
- if (!existsSync(wranglerPath)) {
173
+ function readAppName(appDir, wranglerEnv) {
174
+ if (!hasWranglerConfig(appDir)) {
121
175
  throw new Error("No wrangler.toml found. Are you in a DeepSpace app directory?");
122
176
  }
123
- const parsed = parseToml(readFileSync(wranglerPath, "utf-8"));
124
- const resolved = resolveAppName(parsed.name);
125
- if (!resolved.ok) {
126
- throw new Error(`wrangler.toml: ${resolved.reason}`);
127
- }
128
- if (parsed.name !== resolved.name) {
177
+ const config = readWranglerConfig(appDir);
178
+ const resolved = resolveAppNameForEnv(config, wranglerEnv);
179
+ if (!resolved.ok) throw new Error(resolved.reason);
180
+ const rawName = wranglerEnv ? config.env?.[wranglerEnv]?.name : config.name;
181
+ if (rawName !== resolved.name) {
182
+ const where = wranglerEnv ? `[env.${wranglerEnv}].name` : "`name`";
129
183
  throw new Error(
130
- `wrangler.toml: name "${parsed.name}" is not in canonical form. Update \`name\` to "${resolved.name}" and re-run.`
184
+ `wrangler.toml: ${where} "${rawName}" is not in canonical form. Update it to "${resolved.name}" and re-run.`
131
185
  );
132
186
  }
133
187
  return resolved.name;
@@ -289,10 +343,10 @@ function parseDevVars(content) {
289
343
  }
290
344
  return result;
291
345
  }
292
- async function writeDevVars(appDir, env, ownerId, callerJwt) {
346
+ async function writeDevVars(appDir, env, ownerId, callerJwt, wranglerEnv) {
293
347
  const urls = ENVS[env];
294
348
  const publicKey = await fetchPublicKey(urls.auth);
295
- const appName = readAppName(appDir);
349
+ const appName = readAppName(appDir, wranglerEnv);
296
350
  const appOwnerJwt = await mintAppOwnerJwt(urls.auth, callerJwt, appName);
297
351
  const appIdentityToken = await fetchAppIdentityToken(urls.deploy, callerJwt, appName);
298
352
  const sdkVars = [
@@ -308,8 +362,8 @@ async function writeDevVars(appDir, env, ownerId, callerJwt) {
308
362
  // Production secrets never set this, so deployed apps 404 the route.
309
363
  `ALLOW_DEBUG_ROUTES=true`
310
364
  ].join("\n");
311
- const devVarsPath = join(appDir, ".dev.vars");
312
- const existing = existsSync(devVarsPath) ? readFileSync(devVarsPath, "utf-8") : "";
365
+ const devVarsPath = join2(appDir, wranglerEnv ? `.dev.vars.${wranglerEnv}` : ".dev.vars");
366
+ const existing = existsSync2(devVarsPath) ? readFileSync2(devVarsPath, "utf-8") : "";
313
367
  const custom = extractCustomDevVars(existing);
314
368
  const body = custom ? `${sdkVars}
315
369
 
@@ -433,10 +487,10 @@ async function pollForCompletion(sessionId, codeVerifier) {
433
487
  return null;
434
488
  }
435
489
  function storeCredentials(sessionToken, jwt) {
436
- const dir = join2(homedir(), ".deepspace");
490
+ const dir = join3(homedir(), ".deepspace");
437
491
  mkdirSync(dir, { recursive: true, mode: 448 });
438
- writeFileSync2(join2(dir, "session"), sessionToken, { mode: 384 });
439
- writeFileSync2(join2(dir, "token"), jwt, { mode: 384 });
492
+ writeFileSync2(join3(dir, "session"), sessionToken, { mode: 384 });
493
+ writeFileSync2(join3(dir, "token"), jwt, { mode: 384 });
440
494
  }
441
495
  function openBrowser(url) {
442
496
  const cmd = platform() === "darwin" ? `open "${url}"` : platform() === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
@@ -475,20 +529,20 @@ async function doEmailLogin(email, password) {
475
529
 
476
530
  // src/cli/commands/logout.ts
477
531
  import { defineCommand as defineCommand2 } from "citty";
478
- import { existsSync as existsSync2, readFileSync as readFileSync2, rmSync } from "fs";
479
- import { join as join3 } from "path";
532
+ import { existsSync as existsSync3, readFileSync as readFileSync3, rmSync } from "fs";
533
+ import { join as join4 } from "path";
480
534
  import { homedir as homedir2 } from "os";
481
535
  var AUTH_URL2 = process.env.DEEPSPACE_AUTH_URL ?? ENVS.prod.auth;
482
- var DIR = join3(homedir2(), ".deepspace");
483
- var SESSION_PATH = join3(DIR, "session");
484
- var TOKEN_PATH = join3(DIR, "token");
536
+ var DIR = join4(homedir2(), ".deepspace");
537
+ var SESSION_PATH = join4(DIR, "session");
538
+ var TOKEN_PATH = join4(DIR, "token");
485
539
  var logout_default = defineCommand2({
486
540
  meta: {
487
541
  name: "logout",
488
542
  description: "Sign out and remove cached credentials"
489
543
  },
490
544
  async run() {
491
- const sessionToken = existsSync2(SESSION_PATH) ? readFileSync2(SESSION_PATH, "utf-8").trim() : null;
545
+ const sessionToken = existsSync3(SESSION_PATH) ? readFileSync3(SESSION_PATH, "utf-8").trim() : null;
492
546
  if (sessionToken) {
493
547
  try {
494
548
  await fetch(`${AUTH_URL2}/api/auth/sign-out`, {
@@ -503,7 +557,7 @@ var logout_default = defineCommand2({
503
557
  }
504
558
  let removed = 0;
505
559
  for (const path of [SESSION_PATH, TOKEN_PATH]) {
506
- if (existsSync2(path)) {
560
+ if (existsSync3(path)) {
507
561
  rmSync(path);
508
562
  removed++;
509
563
  }
@@ -518,13 +572,12 @@ var logout_default = defineCommand2({
518
572
 
519
573
  // src/cli/commands/dev.ts
520
574
  import { defineCommand as defineCommand3 } from "citty";
521
- import { existsSync as existsSync6 } from "fs";
522
- import { join as join6, resolve as resolve2 } from "path";
575
+ import { resolve as resolve3, basename } from "path";
523
576
  import spawn from "cross-spawn";
524
577
 
525
578
  // src/cli/auth.ts
526
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
527
- import { join as join4 } from "path";
579
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
580
+ import { join as join5 } from "path";
528
581
  import { homedir as homedir3 } from "os";
529
582
 
530
583
  // src/cli/jwt.ts
@@ -538,20 +591,20 @@ function decodeJwtPayload(token2) {
538
591
 
539
592
  // src/cli/auth.ts
540
593
  var AUTH_URL3 = process.env.DEEPSPACE_AUTH_URL ?? ENVS.prod.auth;
541
- var DIR2 = join4(homedir3(), ".deepspace");
542
- var SESSION_PATH2 = join4(DIR2, "session");
543
- var TOKEN_PATH2 = join4(DIR2, "token");
594
+ var DIR2 = join5(homedir3(), ".deepspace");
595
+ var SESSION_PATH2 = join5(DIR2, "session");
596
+ var TOKEN_PATH2 = join5(DIR2, "token");
544
597
  async function ensureToken() {
545
- if (existsSync3(TOKEN_PATH2)) {
546
- const existing = readFileSync3(TOKEN_PATH2, "utf-8").trim();
598
+ if (existsSync4(TOKEN_PATH2)) {
599
+ const existing = readFileSync4(TOKEN_PATH2, "utf-8").trim();
547
600
  if (isTokenValid(existing)) {
548
601
  return existing;
549
602
  }
550
603
  }
551
- if (!existsSync3(SESSION_PATH2)) {
604
+ if (!existsSync4(SESSION_PATH2)) {
552
605
  throw new Error("Not logged in. Run `deepspace login` first.");
553
606
  }
554
- const sessionToken = readFileSync3(SESSION_PATH2, "utf-8").trim();
607
+ const sessionToken = readFileSync4(SESSION_PATH2, "utf-8").trim();
555
608
  const token2 = await exchangeSession(AUTH_URL3, sessionToken);
556
609
  if (!token2) {
557
610
  throw new Error("Session expired. Run `deepspace login` to re-authenticate.");
@@ -569,27 +622,86 @@ function isTokenValid(jwt) {
569
622
  }
570
623
  }
571
624
 
625
+ // src/cli/lib/app-context.ts
626
+ import { existsSync as existsSync5, readFileSync as readFileSync5, readdirSync, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3 } from "fs";
627
+ import { join as join6, resolve, dirname } from "path";
628
+ import { parse as parseToml2 } from "smol-toml";
629
+ function detectAppName(cwd = process.cwd()) {
630
+ const wranglerPath = join6(resolve(cwd), "wrangler.toml");
631
+ if (!existsSync5(wranglerPath)) return null;
632
+ try {
633
+ const cfg = parseToml2(readFileSync5(wranglerPath, "utf-8"));
634
+ return typeof cfg.name === "string" && cfg.name.length > 0 ? cfg.name : null;
635
+ } catch {
636
+ return null;
637
+ }
638
+ }
639
+ function requireAppName(explicit, cwd) {
640
+ if (explicit) return explicit;
641
+ const detected = detectAppName(cwd);
642
+ if (detected) return detected;
643
+ throw new Error(
644
+ "No app specified. Pass --app <name>, or run from an app directory with a wrangler.toml."
645
+ );
646
+ }
647
+ function findAppDir(start = process.cwd()) {
648
+ let dir = resolve(start);
649
+ for (; ; ) {
650
+ if (existsSync5(join6(dir, "wrangler.toml"))) return dir;
651
+ const parent = dirname(dir);
652
+ if (parent === dir) return null;
653
+ dir = parent;
654
+ }
655
+ }
656
+ function findChildApps(dir) {
657
+ try {
658
+ return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync5(join6(dir, e.name, "wrangler.toml"))).map((e) => e.name).sort();
659
+ } catch {
660
+ return [];
661
+ }
662
+ }
663
+ function writeLaunchConfigIfMissing(appDir, appName, port) {
664
+ const launchPath = join6(appDir, ".claude", "launch.json");
665
+ if (existsSync5(launchPath)) return;
666
+ const config = {
667
+ version: "0.0.1",
668
+ configurations: [
669
+ {
670
+ name: appName,
671
+ runtimeExecutable: "npx",
672
+ runtimeArgs: ["deepspace", "dev", "--port", String(port)],
673
+ port
674
+ }
675
+ ]
676
+ };
677
+ try {
678
+ mkdirSync3(join6(appDir, ".claude"), { recursive: true });
679
+ writeFileSync4(launchPath, JSON.stringify(config, null, 2) + "\n");
680
+ } catch {
681
+ }
682
+ }
683
+
572
684
  // src/cli/lib/install-status.ts
573
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
574
- import { join as join5 } from "path";
685
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
686
+ import { join as join7 } from "path";
575
687
  function ensureInstallReady(appDir) {
576
- if (existsSync4(join5(appDir, "node_modules", "deepspace", "package.json"))) return;
577
- const sentinelDir = join5(appDir, ".deepspace");
578
- const errPath = join5(sentinelDir, "install.err");
579
- const startedPath = join5(sentinelDir, "install.started");
580
- const donePath = join5(sentinelDir, "install.done");
581
- const logPath = join5(sentinelDir, "install.log");
582
- if (existsSync4(errPath)) {
688
+ if (existsSync6(join7(appDir, "node_modules", "deepspace", "package.json"))) return;
689
+ const sentinelDir = join7(appDir, ".deepspace");
690
+ const errPath = join7(sentinelDir, "install.err");
691
+ const startedPath = join7(sentinelDir, "install.started");
692
+ const donePath = join7(sentinelDir, "install.done");
693
+ const logPath = join7(sentinelDir, "install.log");
694
+ if (existsSync6(errPath)) {
583
695
  console.error("Background install failed:");
584
- console.error(readFileSync4(errPath, "utf-8").trimEnd());
585
- if (existsSync4(logPath)) console.error(`
696
+ console.error(readFileSync6(errPath, "utf-8").trimEnd());
697
+ if (existsSync6(logPath)) console.error(`
586
698
  Full log: ${logPath}`);
587
699
  console.error("\nRun `npm install` (or `bun install`) manually, then retry.");
588
700
  process.exit(1);
589
701
  }
590
- if (existsSync4(startedPath) && !existsSync4(donePath)) {
702
+ if (existsSync6(startedPath) && !existsSync6(donePath)) {
591
703
  console.error("Dependencies are still installing in the background.");
592
- if (existsSync4(logPath)) console.error(`Tail progress: tail -f ${logPath}`);
704
+ if (existsSync6(logPath)) console.error(`Tail progress: tail -f ${logPath}`);
593
705
  console.error("Retry once it finishes.");
594
706
  process.exit(1);
595
707
  }
@@ -599,12 +711,12 @@ Full log: ${logPath}`);
599
711
 
600
712
  // src/cli/lib/preflight.ts
601
713
  import { execFileSync } from "child_process";
602
- import { existsSync as existsSync5 } from "fs";
603
- import { resolve } from "path";
714
+ import { existsSync as existsSync7 } from "fs";
715
+ import { resolve as resolve2 } from "path";
604
716
  var DLL_NOT_FOUND = -1073741515;
605
717
  function preflightWindowsWorkerd(appDir) {
606
718
  if (process.platform !== "win32") return;
607
- const workerdExe = resolve(
719
+ const workerdExe = resolve2(
608
720
  appDir,
609
721
  "node_modules",
610
722
  "@cloudflare",
@@ -612,7 +724,7 @@ function preflightWindowsWorkerd(appDir) {
612
724
  "bin",
613
725
  "workerd.exe"
614
726
  );
615
- if (!existsSync5(workerdExe)) return;
727
+ if (!existsSync7(workerdExe)) return;
616
728
  try {
617
729
  execFileSync(workerdExe, ["--help"], { stdio: "pipe", timeout: 1e4 });
618
730
  return;
@@ -652,6 +764,11 @@ var dev_default = defineCommand3({
652
764
  description: "Use production workers (real auth, real API calls)",
653
765
  default: false
654
766
  },
767
+ env: {
768
+ type: "string",
769
+ description: "wrangler.toml [env.<name>] block to run (e.g. --env staging). Reads .dev.vars.<name> and applies the env's overrides at build time.",
770
+ required: false
771
+ },
655
772
  port: {
656
773
  type: "string",
657
774
  description: `Port to bind (default ${DEFAULT_PORT}, or $DEEPSPACE_PORT)`,
@@ -659,13 +776,23 @@ var dev_default = defineCommand3({
659
776
  }
660
777
  },
661
778
  async run({ args }) {
662
- const appDir = resolve2(args.dir ?? ".");
663
779
  const env = args.prod ? "prod" : "dev";
780
+ const wranglerEnv = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
664
781
  const port = resolvePort(args.port);
665
- if (!existsSync6(join6(appDir, "wrangler.toml"))) {
666
- console.error("No wrangler.toml found. Are you in a DeepSpace app directory?");
782
+ const start = resolve3(args.dir ?? ".");
783
+ const appDir = findAppDir(start);
784
+ if (!appDir) {
785
+ console.error(`No wrangler.toml found at or above ${start}.`);
786
+ const children = findChildApps(start);
787
+ if (children.length > 0) {
788
+ console.error("Did you mean to run inside one of these app directories?");
789
+ for (const c of children) console.error(` cd ${c}`);
790
+ } else {
791
+ console.error("Run from a DeepSpace app directory (one containing wrangler.toml).");
792
+ }
667
793
  process.exit(1);
668
794
  }
795
+ writeLaunchConfigIfMissing(appDir, detectAppName(appDir) ?? basename(appDir), port);
669
796
  ensureInstallReady(appDir);
670
797
  let token2;
671
798
  try {
@@ -682,9 +809,10 @@ var dev_default = defineCommand3({
682
809
  process.exit(1);
683
810
  }
684
811
  console.log(`Logged in as ${payload.name ?? payload.email}`);
685
- console.log(`Environment: ${env}`);
812
+ console.log(`Platform: ${env}`);
813
+ if (wranglerEnv) console.log(`Wrangler env: ${wranglerEnv}`);
686
814
  console.log(`Port: ${port}`);
687
- await writeDevVars(appDir, env, payload.sub, token2);
815
+ await writeDevVars(appDir, env, payload.sub, token2, wranglerEnv);
688
816
  preflightWindowsWorkerd(appDir);
689
817
  console.log("Starting dev server...\n");
690
818
  const vite = spawn(
@@ -693,7 +821,15 @@ var dev_default = defineCommand3({
693
821
  {
694
822
  cwd: appDir,
695
823
  stdio: "inherit",
696
- env: { ...process.env, DEEPSPACE_PORT: String(port) }
824
+ // CLOUDFLARE_ENV is what the Cloudflare Vite plugin keys off — it
825
+ // applies `[env.<name>]` overrides and loads `.dev.vars.<name>`
826
+ // instead of `.dev.vars`. Set only when --env was passed so the
827
+ // default invocation behaves exactly as before.
828
+ env: {
829
+ ...process.env,
830
+ DEEPSPACE_PORT: String(port),
831
+ ...wranglerEnv ? { CLOUDFLARE_ENV: wranglerEnv } : {}
832
+ }
697
833
  }
698
834
  );
699
835
  process.on("SIGINT", () => vite.kill());
@@ -875,8 +1011,7 @@ function isAlive(pid) {
875
1011
 
876
1012
  // src/cli/commands/test.ts
877
1013
  import { defineCommand as defineCommand5 } from "citty";
878
- import { existsSync as existsSync7 } from "fs";
879
- import { join as join7, resolve as resolve3 } from "path";
1014
+ import { resolve as resolve4 } from "path";
880
1015
  import { sync as spawnSync3 } from "cross-spawn";
881
1016
 
882
1017
  // src/cli/lib/playwright.ts
@@ -923,11 +1058,19 @@ var test_default = defineCommand5({
923
1058
  }
924
1059
  },
925
1060
  async run({ args }) {
926
- const appDir = resolve3(".");
927
1061
  const suite = args.suite ?? "default";
928
1062
  const port = resolvePort3(args.port);
929
- if (!existsSync7(join7(appDir, "wrangler.toml"))) {
930
- console.error("No wrangler.toml found. Are you in a DeepSpace app directory?");
1063
+ const start = resolve4(".");
1064
+ const appDir = findAppDir(start);
1065
+ if (!appDir) {
1066
+ console.error(`No wrangler.toml found at or above ${start}.`);
1067
+ const children = findChildApps(start);
1068
+ if (children.length > 0) {
1069
+ console.error("Did you mean to run inside one of these app directories?");
1070
+ for (const c of children) console.error(` cd ${c}`);
1071
+ } else {
1072
+ console.error("Run from a DeepSpace app directory (one containing wrangler.toml).");
1073
+ }
931
1074
  process.exit(1);
932
1075
  }
933
1076
  ensureInstallReady(appDir);
@@ -1014,7 +1157,7 @@ function resolvePort3(arg) {
1014
1157
  // src/cli/commands/screenshot.ts
1015
1158
  import { defineCommand as defineCommand6 } from "citty";
1016
1159
  import { sync as spawnSync4 } from "cross-spawn";
1017
- import { resolve as resolve4 } from "path";
1160
+ import { resolve as resolve5 } from "path";
1018
1161
  var screenshot_default = defineCommand6({
1019
1162
  meta: {
1020
1163
  name: "screenshot",
@@ -1043,7 +1186,7 @@ var screenshot_default = defineCommand6({
1043
1186
  }
1044
1187
  },
1045
1188
  run({ args }) {
1046
- const appDir = resolve4(".");
1189
+ const appDir = resolve5(".");
1047
1190
  ensurePlaywright(appDir);
1048
1191
  const playwrightArgs = ["playwright", "screenshot", args.url, args.output];
1049
1192
  if (args["full-page"]) playwrightArgs.push("--full-page");
@@ -1061,7 +1204,7 @@ var screenshot_default = defineCommand6({
1061
1204
  // src/cli/commands/test-accounts.ts
1062
1205
  import { defineCommand as defineCommand7 } from "citty";
1063
1206
  import * as p3 from "@clack/prompts";
1064
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
1207
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
1065
1208
  import { join as join8 } from "path";
1066
1209
  import { homedir as homedir4 } from "os";
1067
1210
  var SESSION_COOKIE2 = "__Secure-better-auth.session_token";
@@ -1071,17 +1214,17 @@ var ACCOUNTS_PATH = join8(DIR3, "test-accounts.json");
1071
1214
  function loadAccounts() {
1072
1215
  if (!existsSync8(ACCOUNTS_PATH)) return [];
1073
1216
  try {
1074
- return JSON.parse(readFileSync5(ACCOUNTS_PATH, "utf-8"));
1217
+ return JSON.parse(readFileSync7(ACCOUNTS_PATH, "utf-8"));
1075
1218
  } catch {
1076
1219
  return [];
1077
1220
  }
1078
1221
  }
1079
1222
  function saveAccounts(accounts) {
1080
- mkdirSync3(DIR3, { recursive: true });
1081
- writeFileSync4(ACCOUNTS_PATH, JSON.stringify(accounts, null, 2), { mode: 384 });
1223
+ mkdirSync4(DIR3, { recursive: true });
1224
+ writeFileSync5(ACCOUNTS_PATH, JSON.stringify(accounts, null, 2), { mode: 384 });
1082
1225
  }
1083
1226
  function sessionCookie() {
1084
- const token2 = readFileSync5(SESSION_PATH2, "utf-8").trim();
1227
+ const token2 = readFileSync7(SESSION_PATH2, "utf-8").trim();
1085
1228
  return `${SESSION_COOKIE2}=${encodeURIComponent(token2)}`;
1086
1229
  }
1087
1230
  async function fetchRemoteAccounts() {
@@ -1349,9 +1492,8 @@ var test_accounts_default = defineCommand7({
1349
1492
  import { defineCommand as defineCommand8 } from "citty";
1350
1493
  import { execSync as execSync2 } from "child_process";
1351
1494
  import { randomUUID } from "crypto";
1352
- import { readFileSync as readFileSync6, readdirSync, existsSync as existsSync9 } from "fs";
1353
- import { parse as parseToml2 } from "smol-toml";
1354
- import { join as join9, resolve as resolve5, dirname } from "path";
1495
+ import { readFileSync as readFileSync8, readdirSync as readdirSync2, existsSync as existsSync9 } from "fs";
1496
+ import { join as join9, resolve as resolve6, dirname as dirname2 } from "path";
1355
1497
  import { pathToFileURL } from "url";
1356
1498
  import * as p4 from "@clack/prompts";
1357
1499
 
@@ -1665,31 +1807,38 @@ var deploy_default = defineCommand8({
1665
1807
  type: "positional",
1666
1808
  description: "App directory (default: current directory)",
1667
1809
  required: false
1810
+ },
1811
+ env: {
1812
+ type: "string",
1813
+ description: "wrangler.toml [env.<name>] block to deploy (e.g. --env staging). Omit to deploy the top-level config.",
1814
+ required: false
1668
1815
  }
1669
1816
  },
1670
1817
  async run({ args }) {
1671
- const appDir = resolve5(args.dir ?? ".");
1672
- const wranglerPath = join9(appDir, "wrangler.toml");
1673
- if (!existsSync9(wranglerPath)) {
1818
+ const appDir = resolve6(args.dir ?? ".");
1819
+ const envName = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
1820
+ if (!hasWranglerConfig(appDir)) {
1674
1821
  console.error("No wrangler.toml found. Are you in a DeepSpace app directory?");
1675
1822
  process.exit(1);
1676
1823
  }
1677
1824
  ensureInstallReady(appDir);
1678
1825
  p4.intro("Deploying DeepSpace app");
1679
- const wranglerConfig = parseToml2(readFileSync6(wranglerPath, "utf-8"));
1680
- const nameRes = resolveAppName(wranglerConfig.name);
1826
+ const wranglerConfig = readWranglerConfig(appDir);
1827
+ const nameRes = resolveAppNameForEnv(wranglerConfig, envName);
1681
1828
  if (!nameRes.ok) {
1682
- p4.cancel(`wrangler.toml: ${nameRes.reason}`);
1829
+ p4.cancel(nameRes.reason);
1683
1830
  process.exit(1);
1684
1831
  }
1685
- if (wranglerConfig.name !== nameRes.name) {
1832
+ const declaredName = envName ? wranglerConfig.env?.[envName]?.name : wranglerConfig.name;
1833
+ if (declaredName !== nameRes.name) {
1834
+ const where = envName ? `[env.${envName}].name` : "`name`";
1686
1835
  p4.cancel(
1687
- `wrangler.toml: name "${wranglerConfig.name}" is not in canonical form. Update \`name\` to "${nameRes.name}" and re-run.`
1836
+ `wrangler.toml: ${where} "${declaredName}" is not in canonical form. Update it to "${nameRes.name}" and re-run.`
1688
1837
  );
1689
1838
  process.exit(1);
1690
1839
  }
1691
1840
  const appName = nameRes.name;
1692
- p4.log.info(`App: ${appName}`);
1841
+ p4.log.info(envName ? `App: ${appName} (env: ${envName})` : `App: ${appName}`);
1693
1842
  let token2;
1694
1843
  try {
1695
1844
  token2 = await ensureToken();
@@ -1700,7 +1849,11 @@ var deploy_default = defineCommand8({
1700
1849
  const s = createSpinner();
1701
1850
  s.start("Building...");
1702
1851
  try {
1703
- execSync2("npx vite build", { cwd: appDir, stdio: "pipe" });
1852
+ execSync2("npx vite build", {
1853
+ cwd: appDir,
1854
+ stdio: "pipe",
1855
+ env: envName ? { ...process.env, CLOUDFLARE_ENV: envName } : process.env
1856
+ });
1704
1857
  } catch (err) {
1705
1858
  s.stop("Build failed");
1706
1859
  const detail = [err.stdout?.toString(), err.stderr?.toString()].filter(Boolean).join("\n").trim();
@@ -1713,16 +1866,23 @@ var deploy_default = defineCommand8({
1713
1866
  p4.cancel("Build output config not found at .wrangler/deploy/config.json");
1714
1867
  process.exit(1);
1715
1868
  }
1716
- const deployConfig = JSON.parse(readFileSync6(deployConfigPath, "utf-8"));
1717
- const outputWranglerPath = resolve5(dirname(deployConfigPath), deployConfig.configPath);
1869
+ const deployConfig = JSON.parse(readFileSync8(deployConfigPath, "utf-8"));
1870
+ const outputWranglerPath = resolve6(dirname2(deployConfigPath), deployConfig.configPath);
1718
1871
  if (!existsSync9(outputWranglerPath)) {
1719
1872
  p4.cancel(`Output wrangler.json not found at ${outputWranglerPath}`);
1720
1873
  process.exit(1);
1721
1874
  }
1722
- const outputConfig = JSON.parse(readFileSync6(outputWranglerPath, "utf-8"));
1723
- const workerDir = dirname(outputWranglerPath);
1875
+ const outputConfig = JSON.parse(readFileSync8(outputWranglerPath, "utf-8"));
1876
+ if (outputConfig.name && outputConfig.name !== appName) {
1877
+ const envHint = envName ? ` (--env ${envName})` : "";
1878
+ p4.cancel(
1879
+ `Build output mismatch: expected name "${appName}"${envHint}, but the built wrangler.json declares "${outputConfig.name}". This usually means the Cloudflare Vite plugin didn't apply the requested env block. Check the plugin version and re-run.`
1880
+ );
1881
+ process.exit(1);
1882
+ }
1883
+ const workerDir = dirname2(outputWranglerPath);
1724
1884
  const workerBundlePath = join9(workerDir, outputConfig.main);
1725
- const clientDir = outputConfig.assets?.directory ? resolve5(workerDir, outputConfig.assets.directory) : null;
1885
+ const clientDir = outputConfig.assets?.directory ? resolve6(workerDir, outputConfig.assets.directory) : null;
1726
1886
  if (!existsSync9(workerBundlePath)) {
1727
1887
  p4.cancel(`Worker bundle not found at ${workerBundlePath}`);
1728
1888
  process.exit(1);
@@ -1734,7 +1894,7 @@ var deploy_default = defineCommand8({
1734
1894
  s.start("Collecting assets...");
1735
1895
  const assets = collectAssets(clientDir);
1736
1896
  s.stop(`Collected ${assets.length} assets`);
1737
- const workerJs = readFileSync6(workerBundlePath, "utf-8");
1897
+ const workerJs = readFileSync8(workerBundlePath, "utf-8");
1738
1898
  const doBindings = outputConfig.durable_objects?.bindings;
1739
1899
  const sqliteClasses = new Set(
1740
1900
  outputConfig.migrations?.flatMap((m) => m.new_sqlite_classes ?? []) ?? []
@@ -1761,10 +1921,10 @@ var deploy_default = defineCommand8({
1761
1921
  `Custom bindings: ${customBindings.map((b) => `${b.name} (${b.type})`).join(", ")}`
1762
1922
  );
1763
1923
  }
1764
- const devVarsPath = join9(appDir, ".dev.vars");
1924
+ const devVarsPath = devVarsPathFor(appDir, envName);
1765
1925
  let userSecrets = {};
1766
1926
  if (existsSync9(devVarsPath)) {
1767
- const customSection = extractCustomDevVars(readFileSync6(devVarsPath, "utf-8"));
1927
+ const customSection = extractCustomDevVars(readFileSync8(devVarsPath, "utf-8"));
1768
1928
  if (customSection) {
1769
1929
  try {
1770
1930
  userSecrets = parseDevVars(customSection);
@@ -2143,7 +2303,7 @@ async function waitForAssetsReady(url, timeoutMs) {
2143
2303
  function collectAssets(dir) {
2144
2304
  const assets = [];
2145
2305
  function walk(d, prefix) {
2146
- for (const entry of readdirSync(d, { withFileTypes: true })) {
2306
+ for (const entry of readdirSync2(d, { withFileTypes: true })) {
2147
2307
  if (entry.name === ".assetsignore") continue;
2148
2308
  const full = join9(d, entry.name);
2149
2309
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
@@ -2152,7 +2312,7 @@ function collectAssets(dir) {
2152
2312
  } else {
2153
2313
  assets.push({
2154
2314
  path: "/" + rel,
2155
- contentBase64: readFileSync6(full).toString("base64")
2315
+ contentBase64: readFileSync8(full).toString("base64")
2156
2316
  });
2157
2317
  }
2158
2318
  }
@@ -2163,9 +2323,6 @@ function collectAssets(dir) {
2163
2323
 
2164
2324
  // src/cli/commands/undeploy.ts
2165
2325
  import { defineCommand as defineCommand9 } from "citty";
2166
- import { readFileSync as readFileSync7, existsSync as existsSync10 } from "fs";
2167
- import { join as join10 } from "path";
2168
- import { parse as parseToml3 } from "smol-toml";
2169
2326
  import * as p5 from "@clack/prompts";
2170
2327
  var DEPLOY_URL2 = process.env.DEEPSPACE_DEPLOY_URL ?? ENVS.prod.deploy;
2171
2328
  var undeploy_default = defineCommand9({
@@ -2178,15 +2335,25 @@ var undeploy_default = defineCommand9({
2178
2335
  type: "positional",
2179
2336
  description: "App name to undeploy (reads from wrangler.toml if omitted)",
2180
2337
  required: false
2338
+ },
2339
+ env: {
2340
+ type: "string",
2341
+ description: "wrangler.toml [env.<name>] block whose deployed app to remove (e.g. --env staging). Ignored if a positional name is given.",
2342
+ required: false
2181
2343
  }
2182
2344
  },
2183
2345
  async run({ args }) {
2184
2346
  let appName = args.name;
2347
+ const envName = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
2185
2348
  if (!appName) {
2186
- const wranglerPath = join10(process.cwd(), "wrangler.toml");
2187
- if (existsSync10(wranglerPath)) {
2188
- const config = parseToml3(readFileSync7(wranglerPath, "utf-8"));
2189
- if (config.name) appName = config.name;
2349
+ if (hasWranglerConfig(process.cwd())) {
2350
+ const config = readWranglerConfig(process.cwd());
2351
+ const nameRes = resolveAppNameForEnv(config, envName);
2352
+ if (!nameRes.ok) {
2353
+ p5.cancel(nameRes.reason);
2354
+ process.exit(1);
2355
+ }
2356
+ appName = nameRes.name;
2190
2357
  }
2191
2358
  }
2192
2359
  if (!appName) {
@@ -2276,14 +2443,14 @@ var whoami_default = defineCommand10({
2276
2443
 
2277
2444
  // src/cli/commands/add.ts
2278
2445
  import { defineCommand as defineCommand11 } from "citty";
2279
- import { existsSync as existsSync11 } from "fs";
2280
- import { resolve as resolve6, join as join11 } from "path";
2446
+ import { existsSync as existsSync10 } from "fs";
2447
+ import { resolve as resolve7, join as join10 } from "path";
2281
2448
  import { execSync as execSync3 } from "child_process";
2282
2449
  function findScript(from) {
2283
- const inNodeModules = join11(from, "node_modules", "deepspace", "scripts", "add-feature.cjs");
2284
- if (existsSync11(inNodeModules)) return inNodeModules;
2285
- const inDotDeepspace = join11(from, ".deepspace", "scripts", "add-feature.cjs");
2286
- if (existsSync11(inDotDeepspace)) return inDotDeepspace;
2450
+ const inNodeModules = join10(from, "node_modules", "deepspace", "scripts", "add-feature.cjs");
2451
+ if (existsSync10(inNodeModules)) return inNodeModules;
2452
+ const inDotDeepspace = join10(from, ".deepspace", "scripts", "add-feature.cjs");
2453
+ if (existsSync10(inDotDeepspace)) return inDotDeepspace;
2287
2454
  return null;
2288
2455
  }
2289
2456
  var add_default = defineCommand11({
@@ -2315,14 +2482,14 @@ var add_default = defineCommand11({
2315
2482
  }
2316
2483
  },
2317
2484
  run({ args }) {
2318
- const appDir = resolve6(args.dir ?? ".");
2485
+ const appDir = resolve7(args.dir ?? ".");
2319
2486
  ensureInstallReady(appDir);
2320
2487
  const script = findScript(appDir);
2321
2488
  if (!script) {
2322
2489
  console.error("Could not find the deepspace feature installer.");
2323
2490
  console.error("Looked in:");
2324
- console.error(` ${join11(appDir, "node_modules", "deepspace", "scripts", "add-feature.cjs")}`);
2325
- console.error(` ${join11(appDir, ".deepspace", "scripts", "add-feature.cjs")}`);
2491
+ console.error(` ${join10(appDir, "node_modules", "deepspace", "scripts", "add-feature.cjs")}`);
2492
+ console.error(` ${join10(appDir, ".deepspace", "scripts", "add-feature.cjs")}`);
2326
2493
  console.error("Are you in a DeepSpace app directory? Try `npm install` first.");
2327
2494
  process.exit(1);
2328
2495
  }
@@ -2346,8 +2513,8 @@ var add_default = defineCommand11({
2346
2513
  // src/cli/commands/skills.ts
2347
2514
  import { defineCommand as defineCommand12 } from "citty";
2348
2515
  import spawn2 from "cross-spawn";
2349
- import { mkdirSync as mkdirSync4 } from "fs";
2350
- import { join as join12 } from "path";
2516
+ import { mkdirSync as mkdirSync5 } from "fs";
2517
+ import { join as join11 } from "path";
2351
2518
  import { homedir as homedir5 } from "os";
2352
2519
  var SKILL_REPO = "deepdotspace/deepspace-skill";
2353
2520
  function runSkillsAdd(extraArgs) {
@@ -2366,7 +2533,7 @@ function runSkillsAdd(extraArgs) {
2366
2533
  });
2367
2534
  }
2368
2535
  function precreateClaudeDir(baseDir) {
2369
- mkdirSync4(join12(baseDir, ".claude"), { recursive: true });
2536
+ mkdirSync5(join11(baseDir, ".claude"), { recursive: true });
2370
2537
  }
2371
2538
  var add = defineCommand12({
2372
2539
  meta: {
@@ -2423,31 +2590,6 @@ var skills_default = defineCommand12({
2423
2590
  // src/cli/commands/domain.ts
2424
2591
  import { defineCommand as defineCommand13 } from "citty";
2425
2592
  import { execSync as execSync4 } from "child_process";
2426
-
2427
- // src/cli/lib/app-context.ts
2428
- import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
2429
- import { join as join13, resolve as resolve7 } from "path";
2430
- import { parse as parseToml4 } from "smol-toml";
2431
- function detectAppName(cwd = process.cwd()) {
2432
- const wranglerPath = join13(resolve7(cwd), "wrangler.toml");
2433
- if (!existsSync12(wranglerPath)) return null;
2434
- try {
2435
- const cfg = parseToml4(readFileSync8(wranglerPath, "utf-8"));
2436
- return typeof cfg.name === "string" && cfg.name.length > 0 ? cfg.name : null;
2437
- } catch {
2438
- return null;
2439
- }
2440
- }
2441
- function requireAppName(explicit, cwd) {
2442
- if (explicit) return explicit;
2443
- const detected = detectAppName(cwd);
2444
- if (detected) return detected;
2445
- throw new Error(
2446
- "No app specified. Pass --app <name>, or run from an app directory with a wrangler.toml."
2447
- );
2448
- }
2449
-
2450
- // src/cli/commands/domain.ts
2451
2593
  var API_URL2 = process.env.DEEPSPACE_API_URL ?? ENVS.prod.api;
2452
2594
  async function api(token2, path, init) {
2453
2595
  const res = await fetch(`${API_URL2}${path}`, {
@@ -2815,8 +2957,9 @@ function flushAndExit(code) {
2815
2957
  process.stdout.write("", () => process.exit(code));
2816
2958
  });
2817
2959
  }
2818
- async function fetchCatalog() {
2819
- const res = await fetch(`${API_URL3}/api/integrations`);
2960
+ async function fetchCatalog(opts = {}) {
2961
+ const url = opts.summary ? `${API_URL3}/api/integrations?summary=1` : `${API_URL3}/api/integrations`;
2962
+ const res = await fetch(url);
2820
2963
  if (!res.ok) {
2821
2964
  throw new Error(`Failed to fetch integration catalog (${res.status})`);
2822
2965
  }
@@ -2832,7 +2975,7 @@ function formatCurrency(value, currency) {
2832
2975
  return `${value} ${currency}`;
2833
2976
  }
2834
2977
  async function runList(args) {
2835
- const catalog = await fetchCatalog();
2978
+ const catalog = await fetchCatalog({ summary: true });
2836
2979
  if (args.json) {
2837
2980
  process.stdout.write(JSON.stringify(catalog) + "\n");
2838
2981
  await flushAndExit(0);
@@ -3379,9 +3522,9 @@ var managed_repos_default = defineCommand16({
3379
3522
 
3380
3523
  // src/cli/commands/library.ts
3381
3524
  import { defineCommand as defineCommand17 } from "citty";
3382
- import { existsSync as existsSync13, readFileSync as readFileSync10 } from "fs";
3383
- import { join as join14, resolve as resolve8 } from "path";
3384
- import { parse as parseToml5 } from "smol-toml";
3525
+ import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
3526
+ import { join as join12, resolve as resolve8 } from "path";
3527
+ import { parse as parseToml3 } from "smol-toml";
3385
3528
  import * as p6 from "@clack/prompts";
3386
3529
  var DEFAULT_LIBRARY_APP = "deepdotspace-site";
3387
3530
  var publish = defineCommand17({
@@ -3458,11 +3601,11 @@ var publish = defineCommand17({
3458
3601
  let appName = typeof args.app === "string" && args.app.trim() ? args.app.trim() : void 0;
3459
3602
  if (!appName) {
3460
3603
  const appDir = resolve8(typeof args.dir === "string" && args.dir ? args.dir : ".");
3461
- const wranglerPath = join14(appDir, "wrangler.toml");
3462
- if (!existsSync13(wranglerPath)) {
3604
+ const wranglerPath = join12(appDir, "wrangler.toml");
3605
+ if (!existsSync11(wranglerPath)) {
3463
3606
  return die("No --app provided and no wrangler.toml found. Either pass --app <name> or run from your app's directory.");
3464
3607
  }
3465
- const cfg = parseToml5(readFileSync10(wranglerPath, "utf-8"));
3608
+ const cfg = parseToml3(readFileSync10(wranglerPath, "utf-8"));
3466
3609
  const nameRes = resolveAppName(cfg.name);
3467
3610
  if (!nameRes.ok) return die(`wrangler.toml: ${nameRes.reason}`);
3468
3611
  appName = nameRes.name;
@@ -3595,9 +3738,9 @@ var library_default = defineCommand17({
3595
3738
 
3596
3739
  // src/cli/commands/admin.ts
3597
3740
  import { defineCommand as defineCommand18 } from "citty";
3598
- import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
3741
+ import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
3599
3742
  import { homedir as homedir6 } from "os";
3600
- import { join as join15 } from "path";
3743
+ import { join as join13 } from "path";
3601
3744
  var AUTH_URL5 = process.env.DEEPSPACE_AUTH_URL ?? ENVS.prod.auth;
3602
3745
  var impersonate = defineCommand18({
3603
3746
  meta: {
@@ -3647,9 +3790,9 @@ var impersonate = defineCommand18({
3647
3790
  console.error(`Failed: ${data.error ?? `HTTP ${res.status}`}`);
3648
3791
  process.exit(1);
3649
3792
  }
3650
- const dir = join15(homedir6(), ".deepspace");
3651
- mkdirSync5(dir, { recursive: true });
3652
- writeFileSync5(TOKEN_PATH2, data.token, { mode: 384 });
3793
+ const dir = join13(homedir6(), ".deepspace");
3794
+ mkdirSync6(dir, { recursive: true });
3795
+ writeFileSync6(TOKEN_PATH2, data.token, { mode: 384 });
3653
3796
  console.log(`Impersonating ${data.user.name ?? data.user.email ?? data.user.id}`);
3654
3797
  console.log(`Email: ${data.user.email ?? "(none)"}`);
3655
3798
  console.log(`UserID: ${data.user.id}`);
@@ -3666,9 +3809,9 @@ var admin_default = defineCommand18({
3666
3809
  });
3667
3810
 
3668
3811
  // src/cli/cli.ts
3669
- var __dirname = dirname2(fileURLToPath(import.meta.url));
3670
- var pkg = JSON.parse(readFileSync11(join16(__dirname, "..", "package.json"), "utf-8"));
3671
- if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync14("/etc/ssl/certs/ca-certificates.crt")) {
3812
+ var __dirname = dirname3(fileURLToPath(import.meta.url));
3813
+ var pkg = JSON.parse(readFileSync11(join14(__dirname, "..", "package.json"), "utf-8"));
3814
+ if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync12("/etc/ssl/certs/ca-certificates.crt")) {
3672
3815
  process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
3673
3816
  }
3674
3817
  var create3 = defineCommand19({