deepspace 0.19.0 → 0.19.2

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
@@ -8,8 +8,8 @@ import {
8
8
  renderUsage
9
9
  } from "citty";
10
10
  import { sync as spawnSync8 } from "cross-spawn";
11
- import { readFileSync as readFileSync28, existsSync as existsSync25 } from "fs";
12
- import { dirname as dirname11, join as join25 } from "path";
11
+ import { readFileSync as readFileSync28, existsSync as existsSync26 } from "fs";
12
+ import { dirname as dirname12, join as join24 } from "path";
13
13
  import { fileURLToPath as fileURLToPath3 } from "url";
14
14
 
15
15
  // src/cli/lib/spinner.ts
@@ -205,6 +205,13 @@ var InputError = class extends Error {
205
205
  this.code = code;
206
206
  }
207
207
  };
208
+ var CliExit = class extends Error {
209
+ constructor(exitCode) {
210
+ super(`exit ${exitCode}`);
211
+ this.exitCode = exitCode;
212
+ this.name = "CliExit";
213
+ }
214
+ };
208
215
  function errorCode(err) {
209
216
  if (err instanceof ApiError || err instanceof InputError) return err.code;
210
217
  if (err instanceof Error && err.name === "GitError") return err.code ?? "git_error";
@@ -230,12 +237,17 @@ function formatCliError(err) {
230
237
  ${hint}` : message;
231
238
  }
232
239
  function renderCliError(err) {
240
+ if (err instanceof CliExit) {
241
+ process.exitCode = err.exitCode;
242
+ return;
243
+ }
233
244
  stopActiveSpinner();
234
245
  const message = formatCliError(err);
235
246
  const slug = errorCode(err);
236
247
  if (process.argv.includes("--json")) {
237
248
  console.log(JSON.stringify({ ok: false, ...slug ? { code: slug } : {}, error: message }));
238
- process.exit(1);
249
+ process.exitCode = 1;
250
+ return;
239
251
  }
240
252
  console.error(slug ? withSlug(message, slug) : message);
241
253
  if (process.env.DEBUG) {
@@ -244,7 +256,7 @@ function renderCliError(err) {
244
256
  API ${apiPath}${status3 ? ` (${status3})` : ""}`);
245
257
  if (err instanceof Error && err.stack) console.error("\n" + err.stack);
246
258
  }
247
- process.exit(1);
259
+ process.exitCode = 1;
248
260
  }
249
261
  function wrapCommandErrors(cmd) {
250
262
  const run2 = cmd.run;
@@ -301,10 +313,9 @@ function withoutReservedKeys(payload) {
301
313
  )
302
314
  );
303
315
  }
304
- function exitWhenFlushed(code) {
305
- if (process.stdout.writableLength === 0) process.exit(code);
306
- process.stdout.write("", () => process.exit(code));
307
- return void 0;
316
+ function finishCommand(code) {
317
+ stopActiveSpinner();
318
+ process.exitCode = code;
308
319
  }
309
320
  function nameVariants(name) {
310
321
  return [
@@ -398,9 +409,9 @@ function defineDeepspaceCommand(def) {
398
409
  else p3.log.error(line);
399
410
  if (refusal?.action) printAction(refusal.action);
400
411
  }
401
- exitWhenFlushed(refusal?.actionRequired ? 2 : 1);
412
+ finishCommand(refusal?.actionRequired ? 2 : 1);
402
413
  }
403
- if (succeeded) exitWhenFlushed(0);
414
+ if (succeeded) finishCommand(0);
404
415
  }
405
416
  });
406
417
  }
@@ -515,7 +526,7 @@ function effectivePlatformUrls(env = process.env) {
515
526
  var DASHBOARD_URL = DEEPSPACE_ENV === "production" ? "https://dashboard.deep.space" : DEEPSPACE_ENV === "staging" ? "https://dashboard.deepspacesites.com" : "http://127.0.0.1:9";
516
527
 
517
528
  // src/cli/auth.ts
518
- import { readFileSync as readFileSync2, writeFileSync, existsSync, mkdirSync } from "fs";
529
+ import { readFileSync as readFileSync2, existsSync, mkdirSync } from "fs";
519
530
  import { join } from "path";
520
531
  import { homedir } from "os";
521
532
 
@@ -543,6 +554,16 @@ async function exchangeSession(authUrl, sessionToken) {
543
554
  return data.token ?? null;
544
555
  }
545
556
 
557
+ // src/cli/lib/secure-file.ts
558
+ import { chmodSync, writeFileSync } from "fs";
559
+ function writeSecretFileSync(path, content) {
560
+ writeFileSync(path, content, { mode: 384 });
561
+ try {
562
+ chmodSync(path, 384);
563
+ } catch {
564
+ }
565
+ }
566
+
546
567
  // src/cli/auth.ts
547
568
  var AUTH_URL = process.env.DEEPSPACE_AUTH_URL ?? PLATFORM_URLS.auth;
548
569
  var PROD_AUTH_URL = "https://auth.deep.space";
@@ -585,7 +606,7 @@ async function ensureToken() {
585
606
  );
586
607
  }
587
608
  mkdirSync(DIR, { recursive: true, mode: 448 });
588
- writeFileSync(TOKEN_PATH, token, { mode: 384 });
609
+ writeSecretFileSync(TOKEN_PATH, token);
589
610
  return token;
590
611
  }
591
612
  function isTokenValid(jwt) {
@@ -603,7 +624,7 @@ registerAuthRefresh(async () => {
603
624
  const token = await exchangeSession(AUTH_URL, sessionToken);
604
625
  if (!token) return null;
605
626
  mkdirSync(DIR, { recursive: true, mode: 448 });
606
- writeFileSync(TOKEN_PATH, token, { mode: 384 });
627
+ writeSecretFileSync(TOKEN_PATH, token);
607
628
  return token;
608
629
  } catch {
609
630
  return null;
@@ -611,19 +632,18 @@ registerAuthRefresh(async () => {
611
632
  });
612
633
 
613
634
  // src/cli/commands/login.ts
614
- import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3 } from "fs";
615
- import { join as join2 } from "path";
616
- import { homedir as homedir2 } from "os";
635
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync3 } from "fs";
636
+ import { dirname as dirname2 } from "path";
617
637
  import { randomBytes, createHash } from "crypto";
618
638
  import * as p4 from "@clack/prompts";
619
639
 
620
640
  // src/cli/lib/open-browser.ts
621
- import { exec } from "child_process";
641
+ import { spawn } from "child_process";
622
642
  function openBrowser(url) {
623
643
  const quoted = JSON.stringify(url);
624
644
  const cmd = process.platform === "darwin" ? `open ${quoted}` : process.platform === "win32" ? `start "" ${quoted}` : `xdg-open ${quoted}`;
625
- exec(cmd, () => {
626
- });
645
+ spawn(cmd, { shell: true, detached: true, stdio: "ignore", windowsHide: true }).on("error", () => {
646
+ }).unref();
627
647
  }
628
648
 
629
649
  // src/cli/commands/login.ts
@@ -716,9 +736,13 @@ var login_default = defineDeepspaceCommand({
716
736
  if (!json) s.start("Waiting for authentication...");
717
737
  const result = await pollForCompletion(sessionId, codeVerifier);
718
738
  if (!result) {
719
- throw new Refusal("Login timed out. Run `deepspace auth login` to try again.", "login_timeout", {
720
- action: cliAction("deepspace", "auth", "login")
721
- });
739
+ throw new Refusal(
740
+ "Login timed out. Run `deepspace auth login` to try again.",
741
+ "login_timeout",
742
+ {
743
+ action: cliAction("deepspace", "auth", "login")
744
+ }
745
+ );
722
746
  }
723
747
  storeCredentials(result.sessionToken, result.jwt);
724
748
  await provisionProfile(result.jwt);
@@ -798,11 +822,10 @@ async function pollForCompletion(sessionId, codeVerifier) {
798
822
  return null;
799
823
  }
800
824
  function storeCredentials(sessionToken, jwt) {
801
- const dir = join2(homedir2(), ".deepspace");
802
- mkdirSync2(dir, { recursive: true, mode: 448 });
803
825
  const { sessionPath, tokenPath } = credentialPaths(AUTH_URL2);
804
- writeFileSync2(sessionPath, sessionToken, { mode: 384 });
805
- writeFileSync2(tokenPath, jwt, { mode: 384 });
826
+ mkdirSync2(dirname2(sessionPath), { recursive: true, mode: 448 });
827
+ writeSecretFileSync(sessionPath, sessionToken);
828
+ writeSecretFileSync(tokenPath, jwt);
806
829
  }
807
830
  async function provisionProfile(jwt) {
808
831
  try {
@@ -883,8 +906,8 @@ var logout_default = defineDeepspaceCommand({
883
906
  });
884
907
 
885
908
  // src/cli/lib/app-identity.ts
886
- import { existsSync as existsSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
887
- import { join as join3, resolve as resolve2 } from "path";
909
+ import { existsSync as existsSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
910
+ import { join as join2, resolve as resolve2 } from "path";
888
911
  import { parse as parseToml } from "smol-toml";
889
912
 
890
913
  // src/server/utils/registry-client.ts
@@ -919,7 +942,7 @@ function mintAppId(now = Date.now()) {
919
942
  return `app_${mintUlid(now)}`;
920
943
  }
921
944
  function readWranglerIdentityConfig(cwd) {
922
- const wranglerPath = join3(resolve2(cwd), "wrangler.toml");
945
+ const wranglerPath = join2(resolve2(cwd), "wrangler.toml");
923
946
  if (!existsSync3(wranglerPath)) return null;
924
947
  try {
925
948
  return parseToml(readFileSync5(wranglerPath, "utf-8"));
@@ -938,7 +961,7 @@ function readAppId(cwd = process.cwd(), wranglerEnv) {
938
961
  return typeof id === "string" && APP_ID_RE.test(id) ? id : null;
939
962
  }
940
963
  function writeAppId(cwd, appId, opts = {}) {
941
- const wranglerPath = join3(resolve2(cwd), "wrangler.toml");
964
+ const wranglerPath = join2(resolve2(cwd), "wrangler.toml");
942
965
  if (!existsSync3(wranglerPath)) {
943
966
  throw new Error(`No wrangler.toml in ${resolve2(cwd)}`);
944
967
  }
@@ -971,19 +994,19 @@ ${header}
971
994
  ${line}
972
995
  `;
973
996
  }
974
- writeFileSync3(wranglerPath, src);
997
+ writeFileSync2(wranglerPath, src);
975
998
  }
976
999
 
977
1000
  // src/cli/commands/dev.ts
978
- import { resolve as resolve8, basename as basename2, join as join12 } from "path";
979
- import spawn from "cross-spawn";
1001
+ import { resolve as resolve8, basename as basename2, join as join11 } from "path";
1002
+ import spawn2 from "cross-spawn";
980
1003
 
981
1004
  // src/cli/lib/app-context.ts
982
1005
  import { existsSync as existsSync4, readFileSync as readFileSync6, readdirSync } from "fs";
983
- import { dirname as dirname2, join as join4, resolve as resolve3 } from "path";
1006
+ import { dirname as dirname3, join as join3, resolve as resolve3 } from "path";
984
1007
  import { parse as parseToml2 } from "smol-toml";
985
1008
  function detectAppName(cwd = process.cwd()) {
986
- const wranglerPath = join4(resolve3(cwd), "wrangler.toml");
1009
+ const wranglerPath = join3(resolve3(cwd), "wrangler.toml");
987
1010
  if (!existsSync4(wranglerPath)) return null;
988
1011
  try {
989
1012
  const config = parseToml2(readFileSync6(wranglerPath, "utf-8"));
@@ -998,15 +1021,15 @@ function detectAppName(cwd = process.cwd()) {
998
1021
  function findAppDir(start = process.cwd()) {
999
1022
  let dir = resolve3(start);
1000
1023
  for (; ; ) {
1001
- if (existsSync4(join4(dir, "wrangler.toml"))) return dir;
1002
- const parent = dirname2(dir);
1024
+ if (existsSync4(join3(dir, "wrangler.toml"))) return dir;
1025
+ const parent = dirname3(dir);
1003
1026
  if (parent === dir) return null;
1004
1027
  dir = parent;
1005
1028
  }
1006
1029
  }
1007
1030
  function findChildApps(dir) {
1008
1031
  try {
1009
- return readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && existsSync4(join4(dir, entry.name, "wrangler.toml"))).map((entry) => entry.name).sort();
1032
+ return readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && existsSync4(join3(dir, entry.name, "wrangler.toml"))).map((entry) => entry.name).sort();
1010
1033
  } catch {
1011
1034
  return [];
1012
1035
  }
@@ -1017,16 +1040,16 @@ import {
1017
1040
  existsSync as existsSync5,
1018
1041
  readFileSync as readFileSync8,
1019
1042
  realpathSync as realpathSync2,
1020
- writeFileSync as writeFileSync4,
1043
+ writeFileSync as writeFileSync3,
1021
1044
  mkdirSync as mkdirSync4,
1022
1045
  renameSync,
1023
1046
  unlinkSync
1024
1047
  } from "fs";
1025
- import { join as join6, resolve as resolve5, dirname as dirname4, basename, isAbsolute as isAbsolute2, sep } from "path";
1048
+ import { join as join5, resolve as resolve5, dirname as dirname5, basename, isAbsolute as isAbsolute2, sep } from "path";
1026
1049
 
1027
1050
  // src/cli/lib/git/repository.ts
1028
1051
  import { appendFileSync, mkdirSync as mkdirSync3, readFileSync as readFileSync7 } from "fs";
1029
- import { dirname as dirname3, join as join5, resolve as resolve4 } from "path";
1052
+ import { dirname as dirname4, join as join4, resolve as resolve4 } from "path";
1030
1053
 
1031
1054
  // src/cli/lib/git/process.ts
1032
1055
  import { sync as spawnSync } from "cross-spawn";
@@ -1230,7 +1253,7 @@ function listWorktrees(cwd) {
1230
1253
  }
1231
1254
  function ensureLocalExclude(cwd, pattern) {
1232
1255
  const commonDir = resolve4(cwd, gitLine(cwd, ["rev-parse", "--git-common-dir"]));
1233
- const excludePath = join5(commonDir, "info", "exclude");
1256
+ const excludePath = join4(commonDir, "info", "exclude");
1234
1257
  let current = "";
1235
1258
  try {
1236
1259
  current = readFileSync7(excludePath, "utf-8");
@@ -1238,7 +1261,7 @@ function ensureLocalExclude(cwd, pattern) {
1238
1261
  }
1239
1262
  if (current.split("\n").some((line) => line.trim() === pattern)) return;
1240
1263
  try {
1241
- mkdirSync3(dirname3(excludePath), { recursive: true });
1264
+ mkdirSync3(dirname4(excludePath), { recursive: true });
1242
1265
  appendFileSync(
1243
1266
  excludePath,
1244
1267
  `${current.endsWith("\n") || current === "" ? "" : "\n"}${pattern}
@@ -1250,9 +1273,9 @@ function ensureLocalExclude(cwd, pattern) {
1250
1273
 
1251
1274
  // src/cli/lib/launch-config.ts
1252
1275
  function writeLaunchFile(launchPath, config) {
1253
- mkdirSync4(dirname4(launchPath), { recursive: true });
1276
+ mkdirSync4(dirname5(launchPath), { recursive: true });
1254
1277
  const tmpPath = `${launchPath}.${process.pid}.tmp`;
1255
- writeFileSync4(tmpPath, JSON.stringify(config, null, 2) + "\n");
1278
+ writeFileSync3(tmpPath, JSON.stringify(config, null, 2) + "\n");
1256
1279
  try {
1257
1280
  renameSync(tmpPath, launchPath);
1258
1281
  } catch (err) {
@@ -1273,7 +1296,7 @@ function readLaunchFile(launchPath) {
1273
1296
  return parsed;
1274
1297
  }
1275
1298
  function writeLaunchConfigIfMissing(appDir, appName, port, opts = {}) {
1276
- const launchPath = join6(appDir, ".claude", "launch.json");
1299
+ const launchPath = join5(appDir, ".claude", "launch.json");
1277
1300
  try {
1278
1301
  const config = readLaunchFile(launchPath);
1279
1302
  if (!config) return;
@@ -1314,7 +1337,7 @@ function writeLaunchConfigIfMissing(appDir, appName, port, opts = {}) {
1314
1337
  }
1315
1338
  function resolveAppLaunchPort(appDir) {
1316
1339
  try {
1317
- const config = readLaunchFile(join6(appDir, ".claude", "launch.json"));
1340
+ const config = readLaunchFile(join5(appDir, ".claude", "launch.json"));
1318
1341
  if (!config) return null;
1319
1342
  const appName = detectAppName(appDir) ?? basename(appDir);
1320
1343
  const entry = config.configurations.find((c) => c?.name === appName) ?? (config.configurations.length === 1 ? config.configurations[0] : void 0);
@@ -1331,10 +1354,10 @@ function detectClaudeWorktree(appDir) {
1331
1354
  listWorktrees(appDir).map((worktree) => canonicalPath(worktree.path))
1332
1355
  );
1333
1356
  if (!registered.has(worktreeRoot)) return null;
1334
- const worktreesDir = dirname4(worktreeRoot);
1335
- const claudeDir = dirname4(worktreesDir);
1357
+ const worktreesDir = dirname5(worktreeRoot);
1358
+ const claudeDir = dirname5(worktreesDir);
1336
1359
  if (basename(worktreesDir) !== "worktrees" || basename(claudeDir) !== ".claude") return null;
1337
- const ownerRoot = canonicalPath(dirname4(claudeDir));
1360
+ const ownerRoot = canonicalPath(dirname5(claudeDir));
1338
1361
  if (!registered.has(ownerRoot)) return null;
1339
1362
  return { mainRepoRoot: ownerRoot, worktreeName: basename(worktreeRoot) };
1340
1363
  } catch {
@@ -1368,12 +1391,12 @@ function deriveWorktreePort(worktreeKey) {
1368
1391
  return WORKTREE_PORT_MIN + hash % WORKTREE_PORT_RANGE;
1369
1392
  }
1370
1393
  function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName, appDir, opts) {
1371
- const launchPath = join6(mainRepoRoot, ".claude", "launch.json");
1394
+ const launchPath = join5(mainRepoRoot, ".claude", "launch.json");
1372
1395
  const entryName = WORKTREE_ENTRY_PREFIX + worktreeName;
1373
1396
  try {
1374
1397
  const config = readLaunchFile(launchPath);
1375
1398
  if (!config) return null;
1376
- const worktreesRoot = join6(mainRepoRoot, ".claude", "worktrees") + sep;
1399
+ const worktreesRoot = join5(mainRepoRoot, ".claude", "worktrees") + sep;
1377
1400
  config.configurations = config.configurations.filter((c) => {
1378
1401
  if (c?.name === entryName) return false;
1379
1402
  const isStaleWorktreeEntry = typeof c?.name === "string" && c.name.startsWith(WORKTREE_ENTRY_PREFIX) && typeof c.cwd === "string" && isAbsolute2(c.cwd) && c.cwd.startsWith(worktreesRoot) && !existsSync5(c.cwd);
@@ -1411,7 +1434,7 @@ function resolveWorktreePort(appDir) {
1411
1434
  const claude = detectClaudeWorktree(appDir);
1412
1435
  if (claude) {
1413
1436
  try {
1414
- const config = readLaunchFile(join6(claude.mainRepoRoot, ".claude", "launch.json"));
1437
+ const config = readLaunchFile(join5(claude.mainRepoRoot, ".claude", "launch.json"));
1415
1438
  const entry = config?.configurations.find(
1416
1439
  (c) => c?.name === WORKTREE_ENTRY_PREFIX + claude.worktreeName
1417
1440
  );
@@ -1472,19 +1495,9 @@ async function fetchAppIdentityToken(deployUrl, callerJwt, appId) {
1472
1495
  return body.token;
1473
1496
  }
1474
1497
 
1475
- // src/cli/lib/secure-file.ts
1476
- import { chmodSync, writeFileSync as writeFileSync5 } from "fs";
1477
- function writeSecretFileSync(path, content) {
1478
- writeFileSync5(path, content, { mode: 384 });
1479
- try {
1480
- chmodSync(path, 384);
1481
- } catch {
1482
- }
1483
- }
1484
-
1485
1498
  // src/cli/lib/wrangler-env.ts
1486
- import { existsSync as existsSync6, readFileSync as readFileSync9, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
1487
- import { join as join7 } from "path";
1499
+ import { existsSync as existsSync6, readFileSync as readFileSync9, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
1500
+ import { join as join6 } from "path";
1488
1501
  import { parse as parseToml3, stringify as stringifyToml } from "smol-toml";
1489
1502
 
1490
1503
  // src/server/rooms/app-name.ts
@@ -1585,7 +1598,7 @@ function resolveAppNameForEnv(config, envName) {
1585
1598
  return resolveAppName(envBlock.name);
1586
1599
  }
1587
1600
  function devVarsPathFor(appDir) {
1588
- return join7(appDir, ".dev.vars");
1601
+ return join6(appDir, ".dev.vars");
1589
1602
  }
1590
1603
  function prepareWranglerEnvConfig(appDir, envName) {
1591
1604
  if (!envName) return { cleanup() {
@@ -1594,11 +1607,11 @@ function prepareWranglerEnvConfig(appDir, envName) {
1594
1607
  const resolved = resolveAppNameForEnv(config, envName);
1595
1608
  if (!resolved.ok) throw new Error(resolved.reason);
1596
1609
  const flattened = flattenWranglerEnvConfig(config, envName);
1597
- const configPath = join7(
1610
+ const configPath = join6(
1598
1611
  appDir,
1599
1612
  `.wrangler.deepspace.${safeGeneratedConfigSegment(envName)}.${process.pid}.${++generatedConfigSeq}.toml`
1600
1613
  );
1601
- writeFileSync6(
1614
+ writeFileSync4(
1602
1615
  configPath,
1603
1616
  [
1604
1617
  "# Generated by DeepSpace for this CLI run.",
@@ -1660,7 +1673,7 @@ var WranglerConfigError = class extends Error {
1660
1673
  }
1661
1674
  };
1662
1675
  function readWranglerConfig(appDir) {
1663
- const path = join7(appDir, "wrangler.toml");
1676
+ const path = join6(appDir, "wrangler.toml");
1664
1677
  const raw = readFileSync9(path, "utf-8");
1665
1678
  try {
1666
1679
  return parseToml3(raw);
@@ -1669,7 +1682,7 @@ function readWranglerConfig(appDir) {
1669
1682
  }
1670
1683
  }
1671
1684
  function hasWranglerConfig(appDir) {
1672
- return existsSync6(join7(appDir, "wrangler.toml"));
1685
+ return existsSync6(join6(appDir, "wrangler.toml"));
1673
1686
  }
1674
1687
 
1675
1688
  // src/cli/lib/dev-vars.ts
@@ -1712,21 +1725,21 @@ async function writeDevVars(appDir, ownerId, callerJwt, wranglerEnv, opts = {})
1712
1725
 
1713
1726
  // src/cli/lib/install-status.ts
1714
1727
  import { existsSync as existsSync8, readFileSync as readFileSync11, realpathSync as realpathSync3 } from "fs";
1715
- import { dirname as dirname5, join as join9, resolve as resolve6 } from "path";
1728
+ import { dirname as dirname6, join as join8, resolve as resolve6 } from "path";
1716
1729
 
1717
1730
  // src/cli/lib/package-manager.ts
1718
1731
  import { existsSync as existsSync7, readFileSync as readFileSync10 } from "fs";
1719
- import { join as join8 } from "path";
1732
+ import { join as join7 } from "path";
1720
1733
  function detectPackageManager(appDir) {
1721
1734
  try {
1722
- const pkg2 = JSON.parse(readFileSync10(join8(appDir, "package.json"), "utf-8"));
1735
+ const pkg2 = JSON.parse(readFileSync10(join7(appDir, "package.json"), "utf-8"));
1723
1736
  const name = typeof pkg2.packageManager === "string" ? pkg2.packageManager.split("@")[0] : "";
1724
1737
  if (name === "npm" || name === "pnpm" || name === "yarn" || name === "bun") return name;
1725
1738
  } catch {
1726
1739
  }
1727
- if (existsSync7(join8(appDir, "pnpm-lock.yaml"))) return "pnpm";
1728
- if (existsSync7(join8(appDir, "yarn.lock"))) return "yarn";
1729
- if (existsSync7(join8(appDir, "bun.lock")) || existsSync7(join8(appDir, "bun.lockb"))) return "bun";
1740
+ if (existsSync7(join7(appDir, "pnpm-lock.yaml"))) return "pnpm";
1741
+ if (existsSync7(join7(appDir, "yarn.lock"))) return "yarn";
1742
+ if (existsSync7(join7(appDir, "bun.lock")) || existsSync7(join7(appDir, "bun.lockb"))) return "bun";
1730
1743
  return "npm";
1731
1744
  }
1732
1745
  function installCommand(appDir) {
@@ -1736,10 +1749,10 @@ function installCommand(appDir) {
1736
1749
  // src/cli/lib/install-status.ts
1737
1750
  function installState(appDir) {
1738
1751
  if (resolvesDeepspace(appDir)) return "ready";
1739
- const sentinelDir = join9(appDir, ".deepspace");
1740
- if (existsSync8(join9(sentinelDir, "install.err"))) return "failed";
1741
- if (existsSync8(join9(sentinelDir, "install.started"))) {
1742
- if (!existsSync8(join9(sentinelDir, "install.done")) && installWorkerAlive(sentinelDir)) {
1752
+ const sentinelDir = join8(appDir, ".deepspace");
1753
+ if (existsSync8(join8(sentinelDir, "install.err"))) return "failed";
1754
+ if (existsSync8(join8(sentinelDir, "install.started"))) {
1755
+ if (!existsSync8(join8(sentinelDir, "install.done")) && installWorkerAlive(sentinelDir)) {
1743
1756
  return "installing";
1744
1757
  }
1745
1758
  return "failed";
@@ -1751,11 +1764,11 @@ function tailHint(logPath, platform = process.platform) {
1751
1764
  }
1752
1765
  function ensureInstallReady(appDir) {
1753
1766
  if (resolvesDeepspace(appDir)) return;
1754
- const sentinelDir = join9(appDir, ".deepspace");
1755
- const errPath = join9(sentinelDir, "install.err");
1756
- const startedPath = join9(sentinelDir, "install.started");
1757
- const donePath = join9(sentinelDir, "install.done");
1758
- const logPath = join9(sentinelDir, "install.log");
1767
+ const sentinelDir = join8(appDir, ".deepspace");
1768
+ const errPath = join8(sentinelDir, "install.err");
1769
+ const startedPath = join8(sentinelDir, "install.started");
1770
+ const donePath = join8(sentinelDir, "install.done");
1771
+ const logPath = join8(sentinelDir, "install.log");
1759
1772
  if (existsSync8(errPath)) {
1760
1773
  const detail = readFileSync11(errPath, "utf-8").trimEnd();
1761
1774
  const log29 = existsSync8(logPath) ? ` Full log: ${logPath}.` : "";
@@ -1796,9 +1809,9 @@ function resolvesDeepspace(fromDir) {
1796
1809
  } catch {
1797
1810
  }
1798
1811
  for (; ; ) {
1799
- if (existsSync8(join9(dir, "node_modules", "deepspace", "package.json"))) return true;
1812
+ if (existsSync8(join8(dir, "node_modules", "deepspace", "package.json"))) return true;
1800
1813
  if (dir === checkoutRoot) return false;
1801
- const parent = dirname5(dir);
1814
+ const parent = dirname6(dir);
1802
1815
  if (parent === dir) return false;
1803
1816
  dir = parent;
1804
1817
  }
@@ -1811,7 +1824,7 @@ function canonicalPath2(path) {
1811
1824
  }
1812
1825
  }
1813
1826
  function installWorkerAlive(sentinelDir) {
1814
- const pidPath = join9(sentinelDir, "install.pid");
1827
+ const pidPath = join8(sentinelDir, "install.pid");
1815
1828
  if (!existsSync8(pidPath)) return false;
1816
1829
  const pid = Number(readFileSync11(pidPath, "utf-8").trim());
1817
1830
  if (!Number.isSafeInteger(pid) || pid <= 0) return false;
@@ -1864,7 +1877,7 @@ function preflightWindowsWorkerd(appDir) {
1864
1877
 
1865
1878
  // src/cli/lib/macos-junk.ts
1866
1879
  import { readdirSync as readdirSync2, rmSync as rmSync3 } from "fs";
1867
- import { join as join10 } from "path";
1880
+ import { join as join9 } from "path";
1868
1881
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".wrangler", ".vite"]);
1869
1882
  function removeMacosJunk(dir) {
1870
1883
  let removed = 0;
@@ -1875,7 +1888,7 @@ function removeMacosJunk(dir) {
1875
1888
  return removed;
1876
1889
  }
1877
1890
  for (const entry of entries) {
1878
- const path = join10(dir, entry.name);
1891
+ const path = join9(dir, entry.name);
1879
1892
  if (entry.isDirectory()) {
1880
1893
  if (!SKIP_DIRS.has(entry.name)) removed += removeMacosJunk(path);
1881
1894
  continue;
@@ -2373,7 +2386,7 @@ async function refreshSecretsCache(deployUrl, token, appId, wranglerEnv, selecte
2373
2386
 
2374
2387
  // src/cli/lib/schema-lint.ts
2375
2388
  import { existsSync as existsSync10 } from "fs";
2376
- import { join as join11 } from "path";
2389
+ import { join as join10 } from "path";
2377
2390
  import { pathToFileURL } from "url";
2378
2391
 
2379
2392
  // src/server/schemas/registry.ts
@@ -2432,12 +2445,12 @@ function lintSchema(schema) {
2432
2445
 
2433
2446
  // src/cli/lib/schema-lint.ts
2434
2447
  async function lintProjectSchemas(appDir) {
2435
- const schemasPath = join11(appDir, "src", "schemas.ts");
2448
+ const schemasPath = join10(appDir, "src", "schemas.ts");
2436
2449
  if (!existsSync10(schemasPath)) return null;
2437
2450
  let schemas;
2438
2451
  try {
2439
2452
  const esbuild = await import("esbuild");
2440
- const outFile = join11(appDir, ".wrangler", "deploy", "schemas.lint.bundle.mjs");
2453
+ const outFile = join10(appDir, ".wrangler", "deploy", "schemas.lint.bundle.mjs");
2441
2454
  await esbuild.build({
2442
2455
  entryPoints: [schemasPath],
2443
2456
  bundle: true,
@@ -2498,9 +2511,9 @@ function isPortListening(port, host, timeoutMs = 500) {
2498
2511
  socket.connect(port, target2);
2499
2512
  });
2500
2513
  }
2501
- async function waitForPortListening(port, host, timeoutMs) {
2514
+ async function waitForPortListening(port, host, timeoutMs, signal) {
2502
2515
  const deadline = Date.now() + timeoutMs;
2503
- while (Date.now() < deadline) {
2516
+ while (Date.now() < deadline && !signal?.aborted) {
2504
2517
  if (await isPortListening(port, host)) return true;
2505
2518
  await new Promise((resolve21) => setTimeout(resolve21, 250));
2506
2519
  }
@@ -2598,7 +2611,7 @@ var dev_default = defineDeepspaceCommand({
2598
2611
  );
2599
2612
  } else if (!args.json) {
2600
2613
  console.warn(
2601
- `Warning: could not update ${join12(worktree.mainRepoRoot, ".claude", "launch.json")} (malformed or unwritable) \u2014 the Claude preview tool may serve the main repo's code instead of this worktree.`
2614
+ `Warning: could not update ${join11(worktree.mainRepoRoot, ".claude", "launch.json")} (malformed or unwritable) \u2014 the Claude preview tool may serve the main repo's code instead of this worktree.`
2602
2615
  );
2603
2616
  }
2604
2617
  }
@@ -2664,13 +2677,15 @@ Free it with \`${killCmd}\`, or start on another port: \`deepspace dev start --p
2664
2677
  }
2665
2678
  const url = `http://${host === DEFAULT_DEV_HOST ? "localhost" : host}:${port}`;
2666
2679
  say("Starting dev server...\n");
2667
- const vite = spawn("npx", ["vite", "--port", String(port), "--strictPort", "--host", host], {
2680
+ const vite = spawn2("npx", ["vite", "--port", String(port), "--strictPort", "--host", host], {
2668
2681
  cwd: appDir,
2669
2682
  stdio: "inherit",
2670
2683
  env: wranglerViteEnv(process.env, wranglerConfig, { DEEPSPACE_PORT: String(port) })
2671
2684
  });
2685
+ const readiness = new AbortController();
2672
2686
  if (args.json) {
2673
- void waitForPortListening(port, host, READINESS_TIMEOUT_MS).then((ready) => {
2687
+ void waitForPortListening(port, host, READINESS_TIMEOUT_MS, readiness.signal).then((ready) => {
2688
+ if (readiness.signal.aborted) return;
2674
2689
  console.log(
2675
2690
  JSON.stringify(
2676
2691
  ready ? { ok: true, ready: true, url, port, host, appDir } : {
@@ -2702,6 +2717,7 @@ Free it with \`${killCmd}\`, or start on another port: \`deepspace dev start --p
2702
2717
  });
2703
2718
  process.off("SIGINT", stop);
2704
2719
  process.off("SIGTERM", stop);
2720
+ readiness.abort();
2705
2721
  if (!devExitSucceeded(code, stopping)) {
2706
2722
  throw new Refusal(`Dev server exited with code ${code}.`, "dev_server_failed", {
2707
2723
  extra: { port, appDir, exitCode: code }
@@ -2817,12 +2833,12 @@ var kill_default = defineDeepspaceCommand({
2817
2833
  }
2818
2834
  });
2819
2835
  function noTargetsMessage(opts) {
2820
- const { enumerated, swept, all, port } = opts;
2836
+ const { enumerated, swept, all, port, platform = process.platform } = opts;
2821
2837
  const couldObserve = enumerated || all && swept;
2822
2838
  if (!couldObserve) {
2823
2839
  return {
2824
2840
  ok: false,
2825
- message: `Couldn't determine what's running on :${port}: no lsof/pgrep and /proc is unavailable, so nothing was inspected or killed. Install lsof (or procps for pgrep), or find and kill the process manually.`
2841
+ message: platform === "win32" ? `Couldn't determine what's running on :${port}: the PowerShell port query could not be run, so nothing was inspected or killed. Either powershell.exe (or pwsh) is not on PATH, or Get-NetTCPConnection is unavailable on this Windows edition. Find the process manually with \`netstat -ano | findstr :${port}\`.` : `Couldn't determine what's running on :${port}: no lsof/pgrep and /proc is unavailable, so nothing was inspected or killed. Install lsof (or procps for pgrep), or find and kill the process manually.`
2826
2842
  };
2827
2843
  }
2828
2844
  return {
@@ -2917,7 +2933,7 @@ function sweepByName(names) {
2917
2933
  const all = /* @__PURE__ */ new Set();
2918
2934
  if (IS_WIN) {
2919
2935
  const orClauses = names.map((n) => `($_.Name -like '*${n}*') -or ($_.CommandLine -like '*${n}*')`).join(" -or ");
2920
- const script = `Get-CimInstance Win32_Process | Where-Object { ${orClauses} } | Select-Object -ExpandProperty ProcessId`;
2936
+ const script = `Get-CimInstance Win32_Process | Where-Object { $_.ProcessId -ne $PID -and (${orClauses}) } | Select-Object -ExpandProperty ProcessId`;
2921
2937
  const r = runPowershellChecked(script);
2922
2938
  for (const pid of r.pids) if (pid !== process.pid) all.add(pid);
2923
2939
  return { pids: [...all], swept: r.ran };
@@ -2954,14 +2970,22 @@ function run(cmd) {
2954
2970
  function runPowershell(script) {
2955
2971
  return runPowershellChecked(script).pids;
2956
2972
  }
2973
+ var PS_SENTINEL = "__deepspace_ps_ok__";
2974
+ function withSentinel(script) {
2975
+ return `$ErrorActionPreference='Stop'; try { ${script}; '${PS_SENTINEL}' } catch { }`;
2976
+ }
2977
+ function readPowershellPids(stdout) {
2978
+ if (!stdout.includes(PS_SENTINEL)) return { pids: [], ran: false };
2979
+ return { pids: parsePidLines(stdout.replace(PS_SENTINEL, "")), ran: true };
2980
+ }
2957
2981
  function runPowershellChecked(script) {
2958
2982
  const exe = pwshOrPowershell();
2959
2983
  try {
2960
- const r = spawnSync2(exe, ["-NoProfile", "-NonInteractive", "-Command", script], {
2984
+ const r = spawnSync2(exe, ["-NoProfile", "-NonInteractive", "-Command", withSentinel(script)], {
2961
2985
  encoding: "utf-8"
2962
2986
  });
2963
2987
  if (r.error) return { pids: [], ran: false };
2964
- return { pids: r.status === 0 ? parsePidLines(r.stdout) : [], ran: r.status === 0 };
2988
+ return readPowershellPids(r.stdout ?? "");
2965
2989
  } catch {
2966
2990
  return { pids: [], ran: false };
2967
2991
  }
@@ -3026,10 +3050,10 @@ function ensurePlaywright(appDir) {
3026
3050
  import { readFileSync as readFileSync14 } from "fs";
3027
3051
 
3028
3052
  // src/testing/accounts.ts
3029
- import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync13, writeFileSync as writeFileSync7 } from "fs";
3030
- import { homedir as homedir3 } from "os";
3031
- import { join as join13 } from "path";
3032
- var TEST_ACCOUNTS_PATH = join13(homedir3(), ".deepspace", "test-accounts.json");
3053
+ import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync13, writeFileSync as writeFileSync5 } from "fs";
3054
+ import { homedir as homedir2 } from "os";
3055
+ import { join as join12 } from "path";
3056
+ var TEST_ACCOUNTS_PATH = join12(homedir2(), ".deepspace", "test-accounts.json");
3033
3057
  var PRODUCTION_AUTH_ORIGIN = "https://auth.deep.space";
3034
3058
  var STAGING_AUTH_ORIGIN = "https://auth.deepspacesites.com";
3035
3059
  function currentTestAccountScope(env = process.env) {
@@ -3115,8 +3139,8 @@ function validTestAccounts(value) {
3115
3139
  );
3116
3140
  }
3117
3141
  function saveTestAccountStore(store) {
3118
- mkdirSync5(join13(homedir3(), ".deepspace"), { recursive: true });
3119
- writeFileSync7(TEST_ACCOUNTS_PATH, JSON.stringify(store, null, 2), { mode: 384 });
3142
+ mkdirSync5(join12(homedir2(), ".deepspace"), { recursive: true });
3143
+ writeFileSync5(TEST_ACCOUNTS_PATH, JSON.stringify(store, null, 2), { mode: 384 });
3120
3144
  }
3121
3145
 
3122
3146
  // src/cli/lib/test-account-service.ts
@@ -3616,7 +3640,7 @@ var clear = defineDeepspaceCommand({
3616
3640
  return { data: { deleted: 0, requested: 0, failures: [] } };
3617
3641
  }
3618
3642
  if (!args.yes) {
3619
- if (args.json) {
3643
+ if (!process.stdin.isTTY || args.json) {
3620
3644
  throw new Refusal(
3621
3645
  "Deleting test accounts needs confirmation. Pass --yes to confirm non-interactively.",
3622
3646
  "confirmation_required"
@@ -3845,15 +3869,15 @@ function describeFilesFailure(status3, body) {
3845
3869
  import * as p6 from "@clack/prompts";
3846
3870
  import { execSync as execSync2 } from "child_process";
3847
3871
  import { createHash as createHash2 } from "crypto";
3848
- import { chmodSync as chmodSync2, existsSync as existsSync14, lstatSync as lstatSync2, readFileSync as readFileSync17, readdirSync as readdirSync4 } from "fs";
3849
- import { dirname as dirname6, join as join16, resolve as resolve11 } from "path";
3872
+ import { existsSync as existsSync14, lstatSync as lstatSync2, readFileSync as readFileSync17, readdirSync as readdirSync4, unlinkSync as unlinkSync2 } from "fs";
3873
+ import { dirname as dirname7, join as join15, resolve as resolve11 } from "path";
3850
3874
 
3851
3875
  // src/documentation/deploy.ts
3852
3876
  import { existsSync as existsSync12, readFileSync as readFileSync15 } from "fs";
3853
- import { join as join14 } from "path";
3877
+ import { join as join13 } from "path";
3854
3878
  function readDocumentationDeployManifest(appDir, clientDir) {
3855
- if (!existsSync12(join14(appDir, "documentation.json"))) return void 0;
3856
- const manifestPath = join14(clientDir, "_documentation", "manifest.json");
3879
+ if (!existsSync12(join13(appDir, "documentation.json"))) return void 0;
3880
+ const manifestPath = join13(clientDir, "_documentation", "manifest.json");
3857
3881
  if (!existsSync12(manifestPath)) {
3858
3882
  throw new Error(
3859
3883
  "documentation.json exists but the build produced no documentation manifest. Ensure vite.config.ts includes deepSpaceDocumentation() from deepspace/documentation."
@@ -3874,7 +3898,7 @@ import {
3874
3898
  readFileSync as readFileSync16,
3875
3899
  writeSync
3876
3900
  } from "fs";
3877
- import { join as join15 } from "path";
3901
+ import { join as join14 } from "path";
3878
3902
 
3879
3903
  // src/shared/protocol/app-migrations.ts
3880
3904
  var APP_MIGRATION_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/;
@@ -3926,7 +3950,7 @@ var APP_MIGRATION_DEFINITIONS = SOURCE_MIGRATIONS.map(
3926
3950
  ({ id, description }) => ({ id, description })
3927
3951
  );
3928
3952
  function readAppliedAppMigrations(appDir) {
3929
- const path = join15(appDir, APP_MIGRATIONS_MANIFEST);
3953
+ const path = join14(appDir, APP_MIGRATIONS_MANIFEST);
3930
3954
  if (!existsSync13(path)) return [];
3931
3955
  const source = readRegularFile(path);
3932
3956
  let parsed;
@@ -3953,7 +3977,7 @@ function planAppMigrations(appDir) {
3953
3977
  const edits = [];
3954
3978
  for (const repoPath of tracked) {
3955
3979
  if (!isSourcePath(repoPath, appPrefix)) continue;
3956
- const absolutePath = join15(repo, repoPath);
3980
+ const absolutePath = join14(repo, repoPath);
3957
3981
  let stat;
3958
3982
  try {
3959
3983
  stat = lstatSync(absolutePath);
@@ -3961,7 +3985,8 @@ function planAppMigrations(appDir) {
3961
3985
  continue;
3962
3986
  }
3963
3987
  if (!stat.isFile() || stat.isSymbolicLink()) continue;
3964
- const before = readRegularFile(absolutePath);
3988
+ const checkout = readRegularFile(absolutePath);
3989
+ const before = normalizeEol(checkout);
3965
3990
  let after = before;
3966
3991
  for (const migration of pendingMigrations) {
3967
3992
  const result = migration.transform(after, repoPath);
@@ -3979,7 +4004,9 @@ function planAppMigrations(appDir) {
3979
4004
  }
3980
4005
  blockers.push(...migration.findBlockers(after, repoPath));
3981
4006
  }
3982
- if (after !== before) edits.push({ path: repoPath, before, after });
4007
+ if (after !== before) {
4008
+ edits.push({ path: repoPath, before: checkout, after: restoreEol(after, checkout) });
4009
+ }
3983
4010
  }
3984
4011
  const pending = pendingMigrations.map((migration) => {
3985
4012
  const planned = perMigration.get(migration.id);
@@ -3987,7 +4014,7 @@ function planAppMigrations(appDir) {
3987
4014
  });
3988
4015
  if (pending.length > 0 && blockers.length === 0) {
3989
4016
  const manifestPath = `${appPrefix}${APP_MIGRATIONS_MANIFEST}`;
3990
- const absoluteManifestPath = join15(repo, manifestPath);
4017
+ const absoluteManifestPath = join14(repo, manifestPath);
3991
4018
  const before = existsSync13(absoluteManifestPath) ? readRegularFile(absoluteManifestPath) : null;
3992
4019
  const after = `${JSON.stringify([...appliedIds, ...pending.map(({ id }) => id)], null, 2)}
3993
4020
  `;
@@ -4003,13 +4030,19 @@ function planAppMigrations(appDir) {
4003
4030
  function applyAppMigrationPlan(appDir, plan) {
4004
4031
  const repo = repoToplevel(appDir);
4005
4032
  for (const edit of plan.edits) {
4006
- const absolutePath = join15(repo, edit.path);
4033
+ const absolutePath = join14(repo, edit.path);
4007
4034
  const current = existsSync13(absolutePath) ? readRegularFile(absolutePath) : null;
4008
4035
  if (current !== edit.before) {
4009
4036
  throw new Error(`Migration input changed after planning: ${edit.path}`);
4010
4037
  }
4011
4038
  }
4012
- for (const edit of plan.edits) writeRegularFile(join15(repo, edit.path), edit.before, edit.after);
4039
+ for (const edit of plan.edits) writeRegularFile(join14(repo, edit.path), edit.before, edit.after);
4040
+ }
4041
+ function normalizeEol(source) {
4042
+ return source.replaceAll("\r\n", "\n");
4043
+ }
4044
+ function restoreEol(source, checkout) {
4045
+ return checkout.includes("\r\n") ? source.replaceAll("\n", "\r\n") : source;
4013
4046
  }
4014
4047
  function readRegularFile(path) {
4015
4048
  const before = lstatSync(path);
@@ -4161,6 +4194,7 @@ ${SECURE_JOB_ROOM_CONSTRUCTOR}`
4161
4194
  ]
4162
4195
  ];
4163
4196
  var HTTP_ROLE_IMPORT = `import { resolveAppRole } from 'deepspace/worker'`;
4197
+ var HTTP_ROLE_IMPORT_PATTERN = /\bimport\s*\{[^}]*\bresolveAppRole\b[^}]*\}\s*from\s*(['"])deepspace\/worker\1/;
4164
4198
  var HTTP_ROLE_IMPORT_SEAM = `import type { AppContext, Env } from '../../worker.js'`;
4165
4199
  var DEBUG_ROUTE_SEAM = ` app.all('/api/debug/*', async (c) => {
4166
4200
  if (c.env.ALLOW_DEBUG_ROUTES !== 'true') {
@@ -4199,8 +4233,11 @@ function secureDebugRouteOccurrences(source) {
4199
4233
  0
4200
4234
  );
4201
4235
  }
4236
+ function httpRoleImportOccurrences(source) {
4237
+ return source.match(new RegExp(HTTP_ROLE_IMPORT_PATTERN.source, "g"))?.length ?? 0;
4238
+ }
4202
4239
  function hasSecureDebugRoute(source) {
4203
- return debugRouteOccurrences(source) === 1 && secureDebugRouteOccurrences(source) === 1 && occurrences(source, HTTP_ROLE_IMPORT) === 1;
4240
+ return debugRouteOccurrences(source) === 1 && secureDebugRouteOccurrences(source) === 1 && httpRoleImportOccurrences(source) === 1;
4204
4241
  }
4205
4242
  function migrateSecureRoomBoundaries(source, file) {
4206
4243
  if (file.endsWith("src/server/realtime-routes.ts")) {
@@ -4243,7 +4280,7 @@ function migrateDebugRouteAuthorization(source) {
4243
4280
  const matchingRoutes = DEBUG_ROUTE_REPLACEMENTS.filter(
4244
4281
  ([before2]) => occurrences(source, before2) === 1
4245
4282
  );
4246
- if (debugRouteOccurrences(source) !== 1 || matchingRoutes.length !== 1 || occurrences(source, HTTP_ROLE_IMPORT) !== 0 || occurrences(source, HTTP_ROLE_IMPORT_SEAM) !== 1) {
4283
+ if (debugRouteOccurrences(source) !== 1 || matchingRoutes.length !== 1 || httpRoleImportOccurrences(source) !== 0 || occurrences(source, HTTP_ROLE_IMPORT_SEAM) !== 1) {
4247
4284
  return { source, replacements: 0 };
4248
4285
  }
4249
4286
  const [before, after] = matchingRoutes[0];
@@ -4357,7 +4394,9 @@ function findWorkerOwnedNotFoundBlockers(source, file) {
4357
4394
  const blockers = [];
4358
4395
  const lines = source.split("\n");
4359
4396
  for (const [index, line] of lines.entries()) {
4360
- if (!/['"`]\/index\.html/.test(line)) continue;
4397
+ if (!/(?:\burl\.pathname\s*=\s*|\bnew\s+URL\s*\(\s*)['"`]\/index\.html/.test(line)) {
4398
+ continue;
4399
+ }
4361
4400
  blockers.push({
4362
4401
  migrationId: WORKER_OWNED_NOT_FOUND_MIGRATION_ID,
4363
4402
  file,
@@ -4370,14 +4409,14 @@ function findWorkerOwnedNotFoundBlockers(source, file) {
4370
4409
 
4371
4410
  // src/cli/commands/deploy/build.ts
4372
4411
  var RESERVED_RUN_WORKER_FIRST2 = new Set(RESERVED_RUN_WORKER_FIRST);
4373
- function secureBuildDevVars(workerDir) {
4374
- const path = join16(workerDir, ".dev.vars");
4412
+ function removeBuildDevVars(workerDir) {
4413
+ const path = join15(workerDir, ".dev.vars");
4375
4414
  if (!existsSync14(path)) return false;
4376
4415
  const stat = lstatSync2(path);
4377
4416
  if (!stat.isFile() || stat.isSymbolicLink()) {
4378
4417
  throw new Error(`Refusing unsafe build secret path: ${path}`);
4379
4418
  }
4380
- chmodSync2(path, 384);
4419
+ unlinkSync2(path);
4381
4420
  return true;
4382
4421
  }
4383
4422
  function declaredCompatibilityDate(config) {
@@ -4415,7 +4454,7 @@ async function buildDeployBundle(options) {
4415
4454
  preparedWranglerConfig?.cleanup();
4416
4455
  }
4417
4456
  spinner2.stop("Built");
4418
- const deployConfigPath = join16(appDir, ".wrangler", "deploy", "config.json");
4457
+ const deployConfigPath = join15(appDir, ".wrangler", "deploy", "config.json");
4419
4458
  if (!existsSync14(deployConfigPath)) {
4420
4459
  output.die(
4421
4460
  "Build output config not found at .wrangler/deploy/config.json",
@@ -4423,7 +4462,7 @@ async function buildDeployBundle(options) {
4423
4462
  );
4424
4463
  }
4425
4464
  const deployConfig = JSON.parse(readFileSync17(deployConfigPath, "utf-8"));
4426
- const outputWranglerPath = resolve11(dirname6(deployConfigPath), deployConfig.configPath);
4465
+ const outputWranglerPath = resolve11(dirname7(deployConfigPath), deployConfig.configPath);
4427
4466
  if (!existsSync14(outputWranglerPath)) {
4428
4467
  output.die(`Output wrangler.json not found at ${outputWranglerPath}`, "build_output_missing");
4429
4468
  }
@@ -4435,13 +4474,13 @@ async function buildDeployBundle(options) {
4435
4474
  "build_output_mismatch"
4436
4475
  );
4437
4476
  }
4438
- const workerDir = dirname6(outputWranglerPath);
4477
+ const workerDir = dirname7(outputWranglerPath);
4439
4478
  try {
4440
- secureBuildDevVars(workerDir);
4479
+ removeBuildDevVars(workerDir);
4441
4480
  } catch (error) {
4442
4481
  output.die(errorMessage(error), "build_output_unsafe");
4443
4482
  }
4444
- const workerBundlePath = join16(workerDir, outputConfig.main);
4483
+ const workerBundlePath = join15(workerDir, outputConfig.main);
4445
4484
  const clientDir = outputConfig.assets?.directory ? resolve11(workerDir, outputConfig.assets.directory) : null;
4446
4485
  if (!existsSync14(workerBundlePath)) {
4447
4486
  output.die(`Worker bundle not found at ${workerBundlePath}`, "build_output_missing");
@@ -4533,7 +4572,7 @@ function collectAssets(dir) {
4533
4572
  const walk = (currentDir, prefix) => {
4534
4573
  for (const entry of readdirSync4(currentDir, { withFileTypes: true })) {
4535
4574
  if (isDeployAssetControlFile(entry.name)) continue;
4536
- const fullPath = join16(currentDir, entry.name);
4575
+ const fullPath = join15(currentDir, entry.name);
4537
4576
  const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
4538
4577
  if (entry.isDirectory()) {
4539
4578
  walk(fullPath, relativePath);
@@ -4557,7 +4596,7 @@ function readDeployAssetConfig(clientDir) {
4557
4596
  ["_headers", "_headers"],
4558
4597
  ["_redirects", "_redirects"]
4559
4598
  ]) {
4560
- const filePath = join16(clientDir, file);
4599
+ const filePath = join15(clientDir, file);
4561
4600
  if (!existsSync14(filePath)) continue;
4562
4601
  const content = readFileSync17(filePath, "utf8");
4563
4602
  if (content.trim()) config[key] = content;
@@ -4574,17 +4613,17 @@ function errorMessage(error) {
4574
4613
  // src/cli/commands/deploy/commerce.ts
4575
4614
  import * as p7 from "@clack/prompts";
4576
4615
  import { existsSync as existsSync15 } from "fs";
4577
- import { join as join17 } from "path";
4616
+ import { join as join16 } from "path";
4578
4617
  import { pathToFileURL as pathToFileURL2 } from "url";
4579
4618
  var API_URL2 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
4580
4619
  function errorMessage2(error) {
4581
4620
  return error instanceof Error ? error.message : String(error);
4582
4621
  }
4583
4622
  async function loadDeclaration(appDir, sourceFile, outputFile, exportName) {
4584
- const sourcePath = join17(appDir, "src", sourceFile);
4623
+ const sourcePath = join16(appDir, "src", sourceFile);
4585
4624
  if (!existsSync15(sourcePath)) return void 0;
4586
4625
  const esbuild = await import("esbuild");
4587
- const outputPath = join17(appDir, ".wrangler", "deploy", outputFile);
4626
+ const outputPath = join16(appDir, ".wrangler", "deploy", outputFile);
4588
4627
  await esbuild.build({
4589
4628
  entryPoints: [sourcePath],
4590
4629
  bundle: true,
@@ -4677,7 +4716,7 @@ Stripe will keep charging them until you cancel. Cancel at the end of their curr
4677
4716
  }
4678
4717
  }
4679
4718
  async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
4680
- if (!existsSync15(join17(appDir, "src", "subscriptions.ts"))) return;
4719
+ if (!existsSync15(join16(appDir, "src", "subscriptions.ts"))) return;
4681
4720
  let plans;
4682
4721
  try {
4683
4722
  plans = await loadDeclaration(
@@ -4732,7 +4771,7 @@ async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
4732
4771
  }
4733
4772
  }
4734
4773
  async function syncOneTimeProducts(appDir, appName, token) {
4735
- if (!existsSync15(join17(appDir, "src", "products.ts"))) return;
4774
+ if (!existsSync15(join16(appDir, "src", "products.ts"))) return;
4736
4775
  let products;
4737
4776
  try {
4738
4777
  products = await loadDeclaration(
@@ -4827,7 +4866,7 @@ function createDeployOutput(json) {
4827
4866
  else console.error(withSlug(message, code));
4828
4867
  if (action) printAction(action);
4829
4868
  }
4830
- process.exit(opts.actionRequired ? 2 : 1);
4869
+ throw new CliExit(opts.actionRequired ? 2 : 1);
4831
4870
  }
4832
4871
  };
4833
4872
  }
@@ -5556,6 +5595,7 @@ async function syncDeployRepository(options) {
5556
5595
  sourceRevision = claimed.revision;
5557
5596
  }
5558
5597
  } catch (error) {
5598
+ if (error instanceof CliExit) throw error;
5559
5599
  const failure = deployRepositoryFailure(error, appDir);
5560
5600
  output.die(failure.error, failure.code);
5561
5601
  }
@@ -5672,7 +5712,7 @@ async function deployBuiltBundle(options) {
5672
5712
  ...action ? { action } : {}
5673
5713
  });
5674
5714
  }
5675
- process.exit(actionRequired ? 2 : 1);
5715
+ throw new CliExit(actionRequired ? 2 : 1);
5676
5716
  };
5677
5717
  const makeForm = () => {
5678
5718
  const form = new FormData();
@@ -6583,6 +6623,7 @@ var apps_default = defineDeepspaceCommand({
6583
6623
 
6584
6624
  // src/cli/commands/logs.ts
6585
6625
  import { defineCommand as defineCommand4 } from "citty";
6626
+ import { setTimeout as delay } from "timers/promises";
6586
6627
 
6587
6628
  // src/shared/log-events.ts
6588
6629
  var LOG_LEVELS = ["debug", "log", "info", "warn", "error"];
@@ -6693,7 +6734,7 @@ function formatEvent(e, color, now = Date.now()) {
6693
6734
  const client = e.source === "client" ? paint("35", "CLIENT", color) + " " : "";
6694
6735
  return `${time} ${client}${tag} ${body}`;
6695
6736
  }
6696
- var sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
6737
+ var sleep3 = (ms, signal) => delay(ms, void 0, { signal });
6697
6738
  var logs_default = defineCommand4({
6698
6739
  meta: {
6699
6740
  name: "logs",
@@ -6769,12 +6810,17 @@ var logs_default = defineCommand4({
6769
6810
  const windowLabel = args.since?.trim() || "15m";
6770
6811
  const MAX_LIMIT = 500;
6771
6812
  const followLimit = followInitialLimit(limit, MAX_LIMIT);
6772
- const fetchPage = async (since, pageLimit, token2) => {
6813
+ const fetchPage = async (since, pageLimit, token2, signal) => {
6773
6814
  const params = new URLSearchParams({ since: String(since) });
6774
6815
  if (pageLimit !== void 0) params.set("limit", String(pageLimit));
6775
6816
  if (args.level) params.set("level", args.level);
6776
6817
  if (args.search) params.set("search", args.search);
6777
- return apiFetch(DEPLOY_URL6, token2, `/api/apps/${appId}/logs?${params}`);
6818
+ return apiFetch(
6819
+ DEPLOY_URL6,
6820
+ token2,
6821
+ `/api/apps/${appId}/logs?${params}`,
6822
+ signal ? { signal } : void 0
6823
+ );
6778
6824
  };
6779
6825
  const print = (events) => {
6780
6826
  for (const e of events) {
@@ -6810,7 +6856,8 @@ var logs_default = defineCommand4({
6810
6856
  }
6811
6857
  return;
6812
6858
  }
6813
- process.on("SIGINT", () => process.exit(0));
6859
+ const tail = new AbortController();
6860
+ process.once("SIGINT", () => tail.abort());
6814
6861
  if (!args.json) {
6815
6862
  console.error(`Tailing logs for ${appId} \u2014 Ctrl+C to stop.`);
6816
6863
  }
@@ -6835,12 +6882,23 @@ var logs_default = defineCommand4({
6835
6882
  }
6836
6883
  }
6837
6884
  for (; ; ) {
6838
- await sleep3(backoff || interval);
6885
+ try {
6886
+ await sleep3(backoff || interval, tail.signal);
6887
+ } catch {
6888
+ return;
6889
+ }
6839
6890
  const freshToken = await ensureToken();
6891
+ if (tail.signal.aborted) return;
6840
6892
  let page;
6841
6893
  try {
6842
- page = await fetchPage(nextPollSince(cursor, initialSince), followLimit, freshToken);
6894
+ page = await fetchPage(
6895
+ nextPollSince(cursor, initialSince),
6896
+ followLimit,
6897
+ freshToken,
6898
+ tail.signal
6899
+ );
6843
6900
  } catch (err) {
6901
+ if (tail.signal.aborted) return;
6844
6902
  if (err instanceof ApiError && [401, 403, 404].includes(err.status)) throw err;
6845
6903
  backoff = Math.min(backoff ? backoff * 2 : MIN_POLL_MS, 3e4);
6846
6904
  const msg = err instanceof Error ? err.message : String(err);
@@ -6946,7 +7004,7 @@ var usage_default = defineDeepspaceCommand({
6946
7004
 
6947
7005
  // src/cli/commands/add.ts
6948
7006
  import { existsSync as existsSync18, readFileSync as readFileSync20, realpathSync as realpathSync4 } from "fs";
6949
- import { dirname as dirname8, join as join19, resolve as resolve15 } from "path";
7007
+ import { dirname as dirname9, join as join18, resolve as resolve15 } from "path";
6950
7008
  import { pathToFileURL as pathToFileURL3 } from "url";
6951
7009
 
6952
7010
  // src/cli/commands/feature-installer.ts
@@ -6962,17 +7020,17 @@ import {
6962
7020
  readSync,
6963
7021
  readdirSync as readdirSync5,
6964
7022
  rmSync as rmSync4,
6965
- writeFileSync as writeFileSync8
7023
+ writeFileSync as writeFileSync6
6966
7024
  } from "fs";
6967
7025
  import { spawnSync as spawnSync6 } from "child_process";
6968
7026
  import { tmpdir as tmpdir2 } from "os";
6969
- import { dirname as dirname7, isAbsolute as isAbsolute4, join as join18, resolve as resolve14 } from "path";
7027
+ import { dirname as dirname8, isAbsolute as isAbsolute4, join as join17, resolve as resolve14 } from "path";
6970
7028
  import { fileURLToPath } from "url";
6971
7029
  var NAV_MARKER = "// \u2500\u2500 Features add nav items below this line \u2500\u2500";
6972
7030
  function featurePackageRoot(moduleUrl = import.meta.url) {
6973
- let dir = dirname7(fileURLToPath(moduleUrl));
7031
+ let dir = dirname8(fileURLToPath(moduleUrl));
6974
7032
  for (; ; ) {
6975
- const packagePath = join18(dir, "package.json");
7033
+ const packagePath = join17(dir, "package.json");
6976
7034
  if (existsSync17(packagePath)) {
6977
7035
  try {
6978
7036
  const pkg2 = JSON.parse(readFileSync19(packagePath, "utf-8"));
@@ -6980,16 +7038,16 @@ function featurePackageRoot(moduleUrl = import.meta.url) {
6980
7038
  } catch {
6981
7039
  }
6982
7040
  }
6983
- const parent = dirname7(dir);
7041
+ const parent = dirname8(dir);
6984
7042
  if (parent === dir) throw new Error("Could not locate the deepspace package root");
6985
7043
  dir = parent;
6986
7044
  }
6987
7045
  }
6988
7046
  function hasFeatureCatalog(packageRoot = featurePackageRoot()) {
6989
- return existsSync17(join18(packageRoot, "features"));
7047
+ return existsSync17(join17(packageRoot, "features"));
6990
7048
  }
6991
7049
  function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
6992
- const configPath = join18(packageRoot, "features", id, "feature.json");
7050
+ const configPath = join17(packageRoot, "features", id, "feature.json");
6993
7051
  if (!existsSync17(configPath)) return null;
6994
7052
  try {
6995
7053
  const config = JSON.parse(readFileSync19(configPath, "utf-8"));
@@ -7002,7 +7060,7 @@ function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
7002
7060
  }
7003
7061
  }
7004
7062
  function listFeatureConfigs(packageRoot = featurePackageRoot()) {
7005
- const featuresDir = join18(packageRoot, "features");
7063
+ const featuresDir = join17(packageRoot, "features");
7006
7064
  if (!existsSync17(featuresDir)) return [];
7007
7065
  return readdirSync5(featuresDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => readFeatureConfig(entry.name, packageRoot)).filter((config) => config !== null && !config.internal).sort((a, b) => a.id.localeCompare(b.id));
7008
7066
  }
@@ -7051,17 +7109,17 @@ function resolveFeatureDestPath(fileDest, route) {
7051
7109
  return route?.protected === false ? `${appPages}${rest}` : `${appPages}(protected)/${rest}`;
7052
7110
  }
7053
7111
  function copyFile(src, dest) {
7054
- mkdirSync6(dirname7(dest), { recursive: true });
7112
+ mkdirSync6(dirname8(dest), { recursive: true });
7055
7113
  copyFileSync(src, dest);
7056
7114
  }
7057
7115
  function copyFileWithImportShift(src, dest, levels) {
7058
- mkdirSync6(dirname7(dest), { recursive: true });
7116
+ mkdirSync6(dirname8(dest), { recursive: true });
7059
7117
  const prefix = "../".repeat(levels);
7060
7118
  const shifted = readFileSync19(src, "utf-8").replace(
7061
7119
  /(from\s+|import\s+|import\()(['"])(\.\.\/)/g,
7062
7120
  `$1$2${prefix}$3`
7063
7121
  );
7064
- writeFileSync8(dest, shifted);
7122
+ writeFileSync6(dest, shifted);
7065
7123
  }
7066
7124
  function installFiles(packageRoot, config, targetDir, emit) {
7067
7125
  let copied = 0;
@@ -7070,9 +7128,9 @@ function installFiles(packageRoot, config, targetDir, emit) {
7070
7128
  const preexisting = [];
7071
7129
  const missing = [];
7072
7130
  for (const file of config.files) {
7073
- const srcPath = join18(packageRoot, "features", config.id, file.src);
7131
+ const srcPath = join17(packageRoot, "features", config.id, file.src);
7074
7132
  const resolvedDest = resolveFeatureDestPath(file.dest, config.route);
7075
- const destPath = join18(targetDir, resolvedDest);
7133
+ const destPath = join17(targetDir, resolvedDest);
7076
7134
  const existed = existsSync17(destPath);
7077
7135
  if (existed) preexisting.push(resolvedDest);
7078
7136
  if (!existsSync17(srcPath)) {
@@ -7112,7 +7170,7 @@ function unresolvedSchema(schema, reason, emit) {
7112
7170
  }
7113
7171
  function integrateSchema(config, targetDir, emit) {
7114
7172
  if (!config.schema) return [];
7115
- const schemasPath = join18(targetDir, "src", "schemas.ts");
7173
+ const schemasPath = join17(targetDir, "src", "schemas.ts");
7116
7174
  if (!existsSync17(schemasPath)) {
7117
7175
  return [unresolvedSchema(config.schema, "src/schemas.ts was not found.", emit)];
7118
7176
  }
@@ -7147,7 +7205,7 @@ export const schemas`
7147
7205
  return [unresolvedSchema(config.schema, reason, emit)];
7148
7206
  }
7149
7207
  content = withEntry;
7150
- writeFileSync8(schemasPath, content);
7208
+ writeFileSync6(schemasPath, content);
7151
7209
  emit(` Schema integrated: ${exportName} -> schemas.ts`);
7152
7210
  return [];
7153
7211
  }
@@ -7169,7 +7227,7 @@ function unresolvedActions(actions, reason, emit) {
7169
7227
  }
7170
7228
  function integrateActions(config, targetDir, emit) {
7171
7229
  if (!config.actions?.length) return [];
7172
- const actionsPath = join18(targetDir, "src", "actions", "index.ts");
7230
+ const actionsPath = join17(targetDir, "src", "actions", "index.ts");
7173
7231
  if (!existsSync17(actionsPath)) {
7174
7232
  return [unresolvedActions(config.actions, "src/actions/index.ts was not found.", emit)];
7175
7233
  }
@@ -7213,13 +7271,13 @@ ${registryEntry}`);
7213
7271
  integrated++;
7214
7272
  emit(` Action integrated: ${action.registerAs} (${action.exportName})`);
7215
7273
  }
7216
- if (integrated > 0) writeFileSync8(actionsPath, content);
7274
+ if (integrated > 0) writeFileSync6(actionsPath, content);
7217
7275
  return unresolved;
7218
7276
  }
7219
7277
  function integrateCode(config, targetDir, emit) {
7220
7278
  const unresolved = [];
7221
7279
  for (const insertion of config.code ?? []) {
7222
- const targetPath = join18(targetDir, insertion.file);
7280
+ const targetPath = join17(targetDir, insertion.file);
7223
7281
  if (!existsSync17(targetPath)) {
7224
7282
  unresolved.push({
7225
7283
  area: "code",
@@ -7249,34 +7307,36 @@ function integrateCode(config, targetDir, emit) {
7249
7307
  if (insertion.import && !content.includes(insertion.import)) {
7250
7308
  const importIndex = content.search(/^import\s/m);
7251
7309
  content = `${content.slice(0, importIndex < 0 ? 0 : importIndex)}${insertion.import}
7252
- ${content.slice(importIndex < 0 ? 0 : importIndex)}`;
7310
+ ${content.slice(
7311
+ importIndex < 0 ? 0 : importIndex
7312
+ )}`;
7253
7313
  }
7254
7314
  const currentMarkerIndex = content.indexOf(insertion.marker);
7255
7315
  const insertAt = insertion.placement === "before" ? currentMarkerIndex : currentMarkerIndex + insertion.marker.length;
7256
7316
  const prefix = insertion.placement === "before" ? "" : "\n";
7257
7317
  const suffix = insertion.placement === "before" ? "\n" : "";
7258
7318
  content = `${content.slice(0, insertAt)}${prefix}${insertion.insert}${suffix}${content.slice(insertAt)}`;
7259
- writeFileSync8(targetPath, content);
7319
+ writeFileSync6(targetPath, content);
7260
7320
  emit(` Integrated: ${insertion.file}`);
7261
7321
  }
7262
7322
  return unresolved;
7263
7323
  }
7264
7324
  function integrateIgnore(config, targetDir) {
7265
7325
  if (!config.ignore?.length) return;
7266
- const targetPath = join18(targetDir, ".gitignore");
7326
+ const targetPath = join17(targetDir, ".gitignore");
7267
7327
  const current = existsSync17(targetPath) ? readFileSync19(targetPath, "utf-8") : "";
7268
7328
  const present = new Set(current.split(/\r?\n/).map((line) => line.trim()));
7269
7329
  const missing = config.ignore.filter((pattern) => !present.has(pattern));
7270
7330
  if (missing.length === 0) return;
7271
7331
  const separator = current === "" || current.endsWith("\n") ? "" : "\n";
7272
- writeFileSync8(targetPath, `${current}${separator}${missing.join("\n")}
7332
+ writeFileSync6(targetPath, `${current}${separator}${missing.join("\n")}
7273
7333
  `);
7274
7334
  }
7275
7335
  function integrateWrangler(config, targetDir, emit) {
7276
7336
  const runWorkerFirst = config.wrangler?.runWorkerFirst === true;
7277
7337
  const rateLimits = config.wrangler?.rateLimits ?? [];
7278
7338
  if (!runWorkerFirst && rateLimits.length === 0) return [];
7279
- const targetPath = join18(targetDir, "wrangler.toml");
7339
+ const targetPath = join17(targetDir, "wrangler.toml");
7280
7340
  const steps = [
7281
7341
  ...runWorkerFirst ? ["Set assets.run_worker_first = true in wrangler.toml."] : [],
7282
7342
  ...rateLimits.map((binding) => `Add the ${binding.name} rate-limit binding to wrangler.toml.`)
@@ -7306,7 +7366,7 @@ ${rateLimitBindingBlock(scope, binding)}
7306
7366
  }
7307
7367
  }
7308
7368
  if (changed) {
7309
- writeFileSync8(targetPath, source);
7369
+ writeFileSync6(targetPath, source);
7310
7370
  emit(" Integrated: wrangler.toml");
7311
7371
  } else emit(" Integration already present: wrangler.toml");
7312
7372
  return [];
@@ -7360,7 +7420,7 @@ function rateLimitBindingBlock(scope, binding) {
7360
7420
  }
7361
7421
  function integrateCss(packageRoot, config, targetDir, emit) {
7362
7422
  if (!config.css?.length) return [];
7363
- const stylesPath = join18(targetDir, "src", "styles.css");
7423
+ const stylesPath = join17(targetDir, "src", "styles.css");
7364
7424
  if (!existsSync17(stylesPath)) {
7365
7425
  emit(" Warning: Cannot integrate CSS \u2014 src/styles.css not found");
7366
7426
  return [
@@ -7377,7 +7437,7 @@ function integrateCss(packageRoot, config, targetDir, emit) {
7377
7437
  let integrated = 0;
7378
7438
  const unresolved = [];
7379
7439
  for (const cssFile of config.css) {
7380
- const srcPath = join18(packageRoot, "features", config.id, cssFile);
7440
+ const srcPath = join17(packageRoot, "features", config.id, cssFile);
7381
7441
  if (!existsSync17(srcPath)) {
7382
7442
  emit(` Warning: CSS source not found: ${cssFile}`, "stderr");
7383
7443
  unresolved.push({
@@ -7399,22 +7459,29 @@ ${css}`;
7399
7459
  integrated++;
7400
7460
  emit(` CSS integrated: ${cssFile} -> styles.css`);
7401
7461
  }
7402
- if (integrated > 0) writeFileSync8(stylesPath, styles);
7462
+ if (integrated > 0) writeFileSync6(stylesPath, styles);
7403
7463
  return unresolved;
7404
7464
  }
7405
7465
  function unresolvedRoute(route, config, reason, emit) {
7466
+ const roles = routeRolesSuffix(route);
7406
7467
  emit("");
7407
7468
  emit(" Add manually to src/nav.ts:");
7408
- emit(` { path: '${route.path}', label: '${config.name}' },`);
7469
+ emit(` { path: '${route.path}', label: '${config.name}'${roles} },`);
7409
7470
  return {
7410
7471
  area: "nav",
7411
7472
  reason,
7412
- steps: [`Add { path: '${route.path}', label: '${config.name}' } to the exported nav array.`]
7473
+ steps: [
7474
+ `Add { path: '${route.path}', label: '${config.name}'${roles} } to the exported nav array.`
7475
+ ]
7413
7476
  };
7414
7477
  }
7478
+ function routeRolesSuffix(route) {
7479
+ const roles = route.roles?.length ? route.roles : route.protected === false ? [] : ["member"];
7480
+ return roles.length ? `, roles: [${roles.map((role) => `'${role}' as Role`).join(", ")}]` : "";
7481
+ }
7415
7482
  function integrateRoute(config, targetDir, emit) {
7416
7483
  if (!config.route) return [];
7417
- const navPath = join18(targetDir, "src", "nav.ts");
7484
+ const navPath = join17(targetDir, "src", "nav.ts");
7418
7485
  if (!existsSync17(navPath)) {
7419
7486
  return [unresolvedRoute(config.route, config, "src/nav.ts was not found.", emit)];
7420
7487
  }
@@ -7435,11 +7502,11 @@ function integrateRoute(config, targetDir, emit) {
7435
7502
  )
7436
7503
  ];
7437
7504
  }
7438
- const roles = config.route.protected === false ? "" : ", roles: ['member' as Role]";
7505
+ const roles = routeRolesSuffix(config.route);
7439
7506
  const entry = ` { path: '${routePath}', label: '${config.name}'${roles} },`;
7440
7507
  content = content.replace(NAV_MARKER, `${NAV_MARKER}
7441
7508
  ${entry}`);
7442
- writeFileSync8(navPath, content);
7509
+ writeFileSync6(navPath, content);
7443
7510
  emit(` Nav wired: ${routePath} (${config.name})`);
7444
7511
  emit(" Route: automatic (file-based routing via src/pages/)");
7445
7512
  return [];
@@ -7465,10 +7532,10 @@ function runPackageInstall(packageManager, targetDir, outputVisible) {
7465
7532
  const child = spawnSync6(packageManager, ["install"], { ...options, stdio: "inherit" });
7466
7533
  return { error: child.error, status: child.status };
7467
7534
  }
7468
- const captureDir = mkdtempSync(join18(tmpdir2(), "deepspace-add-install-"));
7535
+ const captureDir = mkdtempSync(join17(tmpdir2(), "deepspace-add-install-"));
7469
7536
  let outputFd;
7470
7537
  try {
7471
- outputFd = openSync2(join18(captureDir, "output.log"), "w+");
7538
+ outputFd = openSync2(join17(captureDir, "output.log"), "w+");
7472
7539
  const child = spawnSync6(packageManager, ["install"], {
7473
7540
  ...options,
7474
7541
  stdio: ["ignore", outputFd, outputFd]
@@ -7492,7 +7559,7 @@ function integrateDependencies(config, targetDir, runInstall, outputVisible, emi
7492
7559
  if (declarations.every(({ values }) => !values || Object.keys(values).length === 0)) {
7493
7560
  return { added: [], installRan: false, packageManager };
7494
7561
  }
7495
- const packagePath = join18(targetDir, "package.json");
7562
+ const packagePath = join17(targetDir, "package.json");
7496
7563
  if (!existsSync17(packagePath)) {
7497
7564
  emit(" Could not patch package.json (file not found)");
7498
7565
  return { added: [], installRan: false, packageManager };
@@ -7523,7 +7590,7 @@ function integrateDependencies(config, targetDir, runInstall, outputVisible, emi
7523
7590
  if (added.length === 0) {
7524
7591
  emit(" Dependencies already present");
7525
7592
  } else {
7526
- writeFileSync8(packagePath, `${JSON.stringify(pkg2, null, 2)}
7593
+ writeFileSync6(packagePath, `${JSON.stringify(pkg2, null, 2)}
7527
7594
  `, "utf-8");
7528
7595
  emit(` Added to package.json: ${added.map(({ name }) => name).join(", ")}`);
7529
7596
  if (skipped.length) emit(` Already present (kept your version): ${skipped.join(", ")}`);
@@ -7559,7 +7626,7 @@ ${child.diagnosticTail}` : "";
7559
7626
  }
7560
7627
  function dependencySnapshot(targetDir) {
7561
7628
  try {
7562
- const pkg2 = JSON.parse(readFileSync19(join18(targetDir, "package.json"), "utf-8"));
7629
+ const pkg2 = JSON.parse(readFileSync19(join17(targetDir, "package.json"), "utf-8"));
7563
7630
  return {
7564
7631
  dependencies: pkg2.dependencies ?? {},
7565
7632
  devDependencies: pkg2.devDependencies ?? {}
@@ -7571,7 +7638,7 @@ function dependencySnapshot(targetDir) {
7571
7638
  function installedNavFeatures(packageRoot, targetDir) {
7572
7639
  return listFeatureConfigs(packageRoot).filter((feature) => feature.category === "nav" && feature.files.length > 0).filter((feature) => {
7573
7640
  const signature = resolveFeatureDestPath(feature.files[0].dest, feature.route);
7574
- return existsSync17(join18(targetDir, signature));
7641
+ return existsSync17(join17(targetDir, signature));
7575
7642
  }).map((feature) => feature.id);
7576
7643
  }
7577
7644
  function warnNavConflicts(packageRoot, config, targetDir, emit) {
@@ -7631,7 +7698,7 @@ function installFeature(options) {
7631
7698
  );
7632
7699
  if (config.instructions?.length) {
7633
7700
  emit("");
7634
- emit("--- Manual wiring needed ---");
7701
+ emit("--- Next steps ---");
7635
7702
  emit("");
7636
7703
  config.instructions.forEach((instruction, index) => {
7637
7704
  emit(`${index + 1}. ${instruction}`);
@@ -7804,8 +7871,8 @@ function renderFeatureInfo(config) {
7804
7871
  return lines.join("\n");
7805
7872
  }
7806
7873
  function isDeepSpaceApp(dir) {
7807
- if (existsSync18(join19(dir, "wrangler.toml"))) return true;
7808
- const packagePath = join19(dir, "package.json");
7874
+ if (existsSync18(join18(dir, "wrangler.toml"))) return true;
7875
+ const packagePath = join18(dir, "package.json");
7809
7876
  if (!existsSync18(packagePath)) return false;
7810
7877
  try {
7811
7878
  const pkg2 = JSON.parse(readFileSync20(packagePath, "utf-8"));
@@ -7817,9 +7884,9 @@ function isDeepSpaceApp(dir) {
7817
7884
  function installedPackageRoot(fromDir) {
7818
7885
  let dir = fromDir;
7819
7886
  for (; ; ) {
7820
- const candidate = join19(dir, "node_modules", "deepspace");
7821
- if (existsSync18(join19(candidate, "package.json"))) return candidate;
7822
- const parent = dirname8(dir);
7887
+ const candidate = join18(dir, "node_modules", "deepspace");
7888
+ if (existsSync18(join18(candidate, "package.json"))) return candidate;
7889
+ const parent = dirname9(dir);
7823
7890
  if (parent === dir) return null;
7824
7891
  dir = parent;
7825
7892
  }
@@ -7835,8 +7902,8 @@ async function targetInstaller(appDir) {
7835
7902
  if (realpathSync4(installedRoot) === realpathSync4(featurePackageRoot())) {
7836
7903
  return { installFeature };
7837
7904
  }
7838
- const entry = join19(installedRoot, "dist", "feature-installer.js");
7839
- const catalog = join19(installedRoot, "features");
7905
+ const entry = join18(installedRoot, "dist", "feature-installer.js");
7906
+ const catalog = join18(installedRoot, "features");
7840
7907
  if (!existsSync18(entry) || !existsSync18(catalog)) {
7841
7908
  throw new Refusal(
7842
7909
  `The app's installed deepspace package does not contain the typed feature installer.
@@ -7974,6 +8041,7 @@ Create one with: deepspace app create <name>`,
7974
8041
  }
7975
8042
  if (!outcome.ok) {
7976
8043
  throw new Refusal(outcome.error, outcome.code, {
8044
+ actionRequired: outcome.code === "manual_integration_required",
7977
8045
  ..."data" in outcome ? { extra: outcome.data } : {}
7978
8046
  });
7979
8047
  }
@@ -8011,13 +8079,12 @@ function fmtDate(iso) {
8011
8079
  }
8012
8080
  async function confirm4(message) {
8013
8081
  process.stdout.write(`${message} [y/N] `);
8014
- return new Promise((resolve21) => {
8082
+ const answer = await new Promise((resolve21) => {
8015
8083
  process.stdin.setEncoding("utf-8");
8016
- process.stdin.once("data", (data) => {
8017
- const answer = data.toString().trim().toLowerCase();
8018
- resolve21(answer === "y" || answer === "yes");
8019
- });
8084
+ process.stdin.once("data", (data) => resolve21(data.toString().trim().toLowerCase()));
8020
8085
  });
8086
+ process.stdin.pause();
8087
+ return answer === "y" || answer === "yes";
8021
8088
  }
8022
8089
  var cannotPrompt = (json) => json || !process.stdin.isTTY;
8023
8090
  var search = defineDeepspaceCommand({
@@ -9368,7 +9435,7 @@ var integrations_default = defineCommand9({
9368
9435
  // src/cli/commands/feedback.ts
9369
9436
  import * as p16 from "@clack/prompts";
9370
9437
  import { existsSync as existsSync20, readFileSync as readFileSync22 } from "fs";
9371
- import { dirname as dirname9, join as join20 } from "path";
9438
+ import { dirname as dirname10, join as join19 } from "path";
9372
9439
  import { fileURLToPath as fileURLToPath2 } from "url";
9373
9440
  import { release } from "os";
9374
9441
  var API_URL8 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
@@ -9389,10 +9456,10 @@ function normalizeType(raw) {
9389
9456
  }
9390
9457
  function readCliVersion() {
9391
9458
  try {
9392
- const here = dirname9(fileURLToPath2(import.meta.url));
9459
+ const here = dirname10(fileURLToPath2(import.meta.url));
9393
9460
  const candidates = [
9394
- join20(here, "..", "package.json"),
9395
- join20(here, "..", "..", "..", "package.json")
9461
+ join19(here, "..", "package.json"),
9462
+ join19(here, "..", "..", "..", "package.json")
9396
9463
  ];
9397
9464
  for (const path of candidates) {
9398
9465
  if (!existsSync20(path)) continue;
@@ -9793,7 +9860,7 @@ var get2 = defineCommand10({
9793
9860
  const { secrets } = await listSecrets(DEPLOY_URL11, t.token, t.appId, t.configName);
9794
9861
  const item = secrets.find((s) => s.key === key);
9795
9862
  if (!item)
9796
- fail(new InputError(`Secret "${key}" not found in ${t.configName}`, "secret_not_found"));
9863
+ throw new InputError(`Secret "${key}" not found in ${t.configName}`, "secret_not_found");
9797
9864
  ok(
9798
9865
  args.json === true,
9799
9866
  {
@@ -9865,7 +9932,7 @@ var upload = defineCommand10({
9865
9932
  const content = args.file === "-" ? readFileSync23(0, "utf-8") : readFileSync23(args.file, "utf-8");
9866
9933
  const secrets = parseSecretsUpload(content);
9867
9934
  if (Object.keys(secrets).length === 0)
9868
- fail(new InputError("No secrets found in the input.", "empty_input"));
9935
+ throw new InputError("No secrets found in the input.", "empty_input");
9869
9936
  await uploadSecrets(DEPLOY_URL11, t.token, t.appId, t.configName, secrets, args.replace);
9870
9937
  const uploaded = Object.keys(secrets);
9871
9938
  ok(
@@ -9896,11 +9963,9 @@ var download = defineCommand10({
9896
9963
  const t = await resolveTarget(args);
9897
9964
  const format = args.format;
9898
9965
  if (!["dotenv", "json", "shell"].includes(format)) {
9899
- fail(
9900
- new InputError(
9901
- `Unknown format "${args.format}" \u2014 use dotenv, json, or shell.`,
9902
- "invalid_format"
9903
- )
9966
+ throw new InputError(
9967
+ `Unknown format "${args.format}" \u2014 use dotenv, json, or shell.`,
9968
+ "invalid_format"
9904
9969
  );
9905
9970
  }
9906
9971
  const { secrets } = await fetchSecretsValues(DEPLOY_URL11, t.token, t.appId, t.configName);
@@ -9918,11 +9983,9 @@ var pull = defineCommand10({
9918
9983
  const wranglerEnv = args.env?.trim() || void 0;
9919
9984
  const appDir = findAppDir();
9920
9985
  if (!appDir)
9921
- fail(
9922
- new InputError(
9923
- "Run from a DeepSpace app directory (one containing wrangler.toml).",
9924
- "not_in_app_repo"
9925
- )
9986
+ throw new InputError(
9987
+ "Run from a DeepSpace app directory (one containing wrangler.toml).",
9988
+ "not_in_app_repo"
9926
9989
  );
9927
9990
  const t = await resolveTarget(args);
9928
9991
  const ownerId = decodeJwtPayload(t.token).sub;
@@ -9986,11 +10049,9 @@ var configsCreate = defineCommand10({
9986
10049
  const { configs: configs2 } = await listConfigs(DEPLOY_URL11, t.token, t.appId);
9987
10050
  if (configs2.some((c) => c.name === name)) {
9988
10051
  if (copyFrom) {
9989
- fail(
9990
- new InputError(
9991
- `Config "${name}" already exists \u2014 refusing to copy "${copyFrom}" over it. Delete it first, or pick a new name.`,
9992
- "config_exists"
9993
- )
10052
+ throw new InputError(
10053
+ `Config "${name}" already exists \u2014 refusing to copy "${copyFrom}" over it. Delete it first, or pick a new name.`,
10054
+ "config_exists"
9994
10055
  );
9995
10056
  }
9996
10057
  ok(
@@ -10890,7 +10951,7 @@ var pull_default = defineDeepspaceCommand({
10890
10951
  // src/cli/commands/clone.ts
10891
10952
  import * as p20 from "@clack/prompts";
10892
10953
  import { existsSync as existsSync22, readdirSync as readdirSync6, rmSync as rmSync5 } from "fs";
10893
- import { resolve as resolve17, basename as basename5, join as join21 } from "path";
10954
+ import { resolve as resolve17, basename as basename5, join as join20 } from "path";
10894
10955
  function cleanFailedCloneDir(dir) {
10895
10956
  let entries;
10896
10957
  try {
@@ -10900,7 +10961,7 @@ function cleanFailedCloneDir(dir) {
10900
10961
  }
10901
10962
  if (entries.length === 1 && entries[0] === ".git") {
10902
10963
  try {
10903
- rmSync5(join21(dir, ".git"), { recursive: true, force: true });
10964
+ rmSync5(join20(dir, ".git"), { recursive: true, force: true });
10904
10965
  return [];
10905
10966
  } catch {
10906
10967
  return entries;
@@ -11385,8 +11446,16 @@ function printOverlaps(overlaps, json) {
11385
11446
 
11386
11447
  // src/cli/commands/workspace/local.ts
11387
11448
  import * as p24 from "@clack/prompts";
11388
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync25, realpathSync as realpathSync6, renameSync as renameSync2, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
11389
- import { dirname as dirname10, isAbsolute as isAbsolute5, join as join22, relative as relative2, sep as sep2 } from "path";
11449
+ import {
11450
+ existsSync as existsSync23,
11451
+ mkdirSync as mkdirSync7,
11452
+ readFileSync as readFileSync25,
11453
+ realpathSync as realpathSync6,
11454
+ renameSync as renameSync2,
11455
+ statSync as statSync3,
11456
+ writeFileSync as writeFileSync7
11457
+ } from "fs";
11458
+ import { dirname as dirname11, isAbsolute as isAbsolute5, join as join21, relative as relative2, sep as sep2 } from "path";
11390
11459
  var WORKSPACE_MARKER = "deepspace-workspace.json";
11391
11460
  function excludeWorktreeDir(appDir, dir) {
11392
11461
  try {
@@ -11407,7 +11476,7 @@ function worktreeMarkerPath(worktreeRoot) {
11407
11476
  const result = runGit(worktreeRoot, ["rev-parse", "--absolute-git-dir"], { allowFail: true });
11408
11477
  if (result.status !== 0) return null;
11409
11478
  const gitDir = result.stdout.toString("utf-8").trim();
11410
- return isAbsolute5(gitDir) ? join22(gitDir, WORKSPACE_MARKER) : null;
11479
+ return isAbsolute5(gitDir) ? join21(gitDir, WORKSPACE_MARKER) : null;
11411
11480
  } catch {
11412
11481
  return null;
11413
11482
  }
@@ -11418,8 +11487,8 @@ function markManagedWorkspace(worktreeRoot, workspaceId) {
11418
11487
  const marker = { version: 1, workspaceId, mode: "managed-linked" };
11419
11488
  const tempPath = `${markerPath}.${process.pid}.tmp`;
11420
11489
  try {
11421
- mkdirSync7(dirname10(markerPath), { recursive: true });
11422
- writeFileSync9(tempPath, JSON.stringify(marker) + "\n");
11490
+ mkdirSync7(dirname11(markerPath), { recursive: true });
11491
+ writeFileSync7(tempPath, JSON.stringify(marker) + "\n");
11423
11492
  renameSync2(tempPath, markerPath);
11424
11493
  return true;
11425
11494
  } catch {
@@ -11452,11 +11521,11 @@ function isManagedWorkspaceWorktree(worktreeRoot, workspaceId) {
11452
11521
  function defaultWorkspaceRoot(appDir, workspaceId) {
11453
11522
  const primary = listWorktrees(appDir)[0];
11454
11523
  if (!primary) throw new Error("Git did not report a primary worktree");
11455
- return join22(primary.path, ".deepspace", "ws", workspaceId.slice(3).toLowerCase());
11524
+ return join21(primary.path, ".deepspace", "ws", workspaceId.slice(3).toLowerCase());
11456
11525
  }
11457
11526
  function appDirInWorktree(appDir, worktreeRoot) {
11458
11527
  const appRelative = relative2(realpathSync6(repoToplevel(appDir)), realpathSync6(appDir));
11459
- return appRelative ? join22(worktreeRoot, appRelative) : worktreeRoot;
11528
+ return appRelative ? join21(worktreeRoot, appRelative) : worktreeRoot;
11460
11529
  }
11461
11530
  function primaryAppDir(appDir) {
11462
11531
  const primary = listWorktrees(appDir)[0];
@@ -11491,7 +11560,7 @@ function rematerializeWorkspaceWorktree(appDir, worktreeRoot, branch, workspaceI
11491
11560
  function inOwnLinkedWorktree(appDir, id) {
11492
11561
  if (workspaceIdFromBranch(currentBranch(appDir)) !== id) return false;
11493
11562
  try {
11494
- return statSync3(join22(repoToplevel(appDir), ".git")).isFile();
11563
+ return statSync3(join21(repoToplevel(appDir), ".git")).isFile();
11495
11564
  } catch {
11496
11565
  return false;
11497
11566
  }
@@ -11532,6 +11601,7 @@ function cleanupJson(outcome) {
11532
11601
  worktreeRetained: outcome.worktreeRetained,
11533
11602
  branchDeleted: outcome.branchDeleted,
11534
11603
  ...outcome.mainDir ? { mainDir: outcome.mainDir } : {},
11604
+ ...outcome.leftoverDir ? { leftoverDir: outcome.leftoverDir } : {},
11535
11605
  ...outcome.error ? { error: outcome.error } : {}
11536
11606
  };
11537
11607
  }
@@ -11606,7 +11676,23 @@ function cleanupWorkspaceLocal(appDir, id, trunkBranch, options = {}) {
11606
11676
  }
11607
11677
  const remove2 = runGit(mainDir, ["worktree", "remove", linked.path], { allowFail: true });
11608
11678
  if (remove2.status !== 0) {
11609
- out.error = `git worktree remove ${linked.path} failed: ${remove2.stderr.toString("utf-8").trim() || "unknown git error"}`;
11679
+ const reason = remove2.stderr.toString("utf-8").trim() || "unknown git error";
11680
+ const registered = listWorktrees(mainDir);
11681
+ const stillRegistered = registered.length === 0 || registered.some((worktree, index) => index > 0 && worktree.path === linked.path);
11682
+ if (stillRegistered) {
11683
+ out.error = `git worktree remove ${linked.path} failed: ${reason}`;
11684
+ return out;
11685
+ }
11686
+ runGit(mainDir, ["worktree", "prune"], { allowFail: true });
11687
+ out.worktreeDir = null;
11688
+ const leftover = existsSync23(linked.path);
11689
+ if (leftover) out.leftoverDir = linked.path;
11690
+ else out.worktreeRemoved = linked.path;
11691
+ deleteWsBranch(mainDir, branch, expectedBranchOid, out);
11692
+ if (leftover) {
11693
+ const branchProblem = out.error;
11694
+ out.error = `git removed the worktree but could not delete its files (${reason})` + (branchProblem ? `. Also: ${branchProblem}` : "");
11695
+ }
11610
11696
  return out;
11611
11697
  }
11612
11698
  out.worktreeRemoved = linked.path;
@@ -11628,12 +11714,20 @@ function cleanupWorkspaceLocal(appDir, id, trunkBranch, options = {}) {
11628
11714
  function cleanupRefusalMessage(outcome) {
11629
11715
  if (outcome.worktreeDir) {
11630
11716
  const dir = shQuote(outcome.worktreeDir);
11631
- return `Cleanup incomplete: ${outcome.error}. The worktree at ${dir} still holds uncommitted or untracked work \u2014 that's why the plain removal refused. Inspect it (\`git -C ${dir} status\`) and commit anything worth keeping to the workspace branch. Once it's clean, remove it from the main checkout: \`git worktree remove ${dir}\`.`;
11717
+ if (outcome.error.includes("contains modified or untracked files")) {
11718
+ return `Cleanup incomplete: ${outcome.error}. The worktree at ${dir} still holds uncommitted or untracked work \u2014 that's why the plain removal refused. Inspect it (\`git -C ${dir} status\`) and commit anything worth keeping to the workspace branch. Once it's clean, remove it from the main checkout: \`git worktree remove ${dir}\`.`;
11719
+ }
11720
+ return `Cleanup incomplete: ${outcome.error}. The worktree at ${dir} is still registered; git refused the plain removal for the reason above. Inspect it (\`git -C ${dir} status\`), resolve that, then remove it from the main checkout: \`git worktree remove ${dir}\`.`;
11721
+ }
11722
+ if (outcome.leftoverDir) {
11723
+ return `Cleanup incomplete: ${outcome.error}. Close anything using that folder \u2014 a dev server, an editor, or a shell inside it \u2014 then delete: ${shQuote(outcome.leftoverDir)}`;
11632
11724
  }
11633
11725
  return `Cleanup incomplete: ${outcome.error}. Finish it from the main checkout once ${shQuote(outcome.branch)} is safe to delete.`;
11634
11726
  }
11635
11727
  function cleanupAction(outcome) {
11636
- return outcome.worktreeDir ? { cwd: outcome.worktreeDir, argv: ["git", "status"] } : { cwd: process.cwd(), argv: ["git", "branch", "--list", outcome.branch] };
11728
+ if (outcome.worktreeDir) return { cwd: outcome.worktreeDir, argv: ["git", "status"] };
11729
+ if (outcome.leftoverDir) return { cwd: process.cwd(), argv: ["git", "worktree", "list"] };
11730
+ return { cwd: process.cwd(), argv: ["git", "branch", "--list", outcome.branch] };
11637
11731
  }
11638
11732
  function reportCleanupHuman(outcome) {
11639
11733
  if (outcome.error) {
@@ -11641,6 +11735,7 @@ function reportCleanupHuman(outcome) {
11641
11735
  cleanupRefusalMessage({
11642
11736
  error: outcome.error,
11643
11737
  worktreeDir: outcome.worktreeDir,
11738
+ leftoverDir: outcome.leftoverDir,
11644
11739
  branch: outcome.branch
11645
11740
  })
11646
11741
  );
@@ -11888,7 +11983,7 @@ var newWorkspaceCommand = defineDeepspaceCommand({
11888
11983
 
11889
11984
  // src/cli/commands/workspace/attach.ts
11890
11985
  import * as p26 from "@clack/prompts";
11891
- import { existsSync as existsSync23, readdirSync as readdirSync7, realpathSync as realpathSync7, rmSync as rmSync6 } from "fs";
11986
+ import { existsSync as existsSync24, readdirSync as readdirSync7, realpathSync as realpathSync7, rmSync as rmSync6 } from "fs";
11892
11987
  import { isAbsolute as isAbsolute7, resolve as resolve19 } from "path";
11893
11988
  function samePath(a, b) {
11894
11989
  const real = (p35) => {
@@ -11908,7 +12003,7 @@ function finishedWorkspaceMessage(id, view) {
11908
12003
  return `Workspace ${id} is ${workspace.status} \u2014 its ref is retained briefly (fetch ${workspace.ref} to inspect), but attach only works on active workspaces.`;
11909
12004
  }
11910
12005
  function cleanFailedFreshAttachDir(dir) {
11911
- if (!existsSync23(dir)) return [];
12006
+ if (!existsSync24(dir)) return [];
11912
12007
  let entries;
11913
12008
  try {
11914
12009
  entries = readdirSync7(dir);
@@ -11990,7 +12085,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
11990
12085
  const localTip = resolveCommit(appDir, `refs/heads/${branch}`);
11991
12086
  if (localTip) {
11992
12087
  let existing = resolveWorkspaceWorktree(listWorktrees(appDir), id);
11993
- if (existing && !existsSync23(existing)) {
12088
+ if (existing && !existsSync24(existing)) {
11994
12089
  runGit(appDir, ["worktree", "prune"], { allowFail: true });
11995
12090
  existing = null;
11996
12091
  }
@@ -12108,7 +12203,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
12108
12203
  process.cwd(),
12109
12204
  (typeof args.dir === "string" ? args.dir.trim() : "") || `ws-${id.slice(3).toLowerCase()}`
12110
12205
  );
12111
- if (existsSync23(dir)) {
12206
+ if (existsSync24(dir)) {
12112
12207
  spinner2?.stop("Target exists.");
12113
12208
  throw new Refusal(`${dir} already exists \u2014 pass a different directory.`, "dir_exists");
12114
12209
  }
@@ -12446,12 +12541,12 @@ import * as p30 from "@clack/prompts";
12446
12541
  // src/cli/lib/validation.ts
12447
12542
  import { sync as spawnSync7 } from "cross-spawn";
12448
12543
  import { readFileSync as readFileSync26 } from "fs";
12449
- import { join as join23 } from "path";
12544
+ import { join as join22 } from "path";
12450
12545
  var MAX_SUMMARY_CHARS = 4e3;
12451
12546
  var MAX_OUTPUT_BUFFER = 64 * 1024 * 1024;
12452
12547
  function configuredCommand(appDir) {
12453
12548
  try {
12454
- const pkg2 = JSON.parse(readFileSync26(join23(appDir, "package.json"), "utf-8"));
12549
+ const pkg2 = JSON.parse(readFileSync26(join22(appDir, "package.json"), "utf-8"));
12455
12550
  if (pkg2.scripts?.validate) return `${detectPackageManager(appDir)} run --silent validate`;
12456
12551
  } catch {
12457
12552
  }
@@ -12792,12 +12887,14 @@ var landWorkspaceCommand = defineDeepspaceCommand({
12792
12887
  if (cleanup?.error) {
12793
12888
  const action2 = cleanupAction({
12794
12889
  worktreeDir: cleanup.worktreeDir,
12890
+ leftoverDir: cleanup.leftoverDir,
12795
12891
  branch: cleanup.branch
12796
12892
  });
12797
12893
  throw new Refusal(
12798
12894
  cleanupRefusalMessage({
12799
12895
  error: cleanup.error,
12800
12896
  worktreeDir: cleanup.worktreeDir,
12897
+ leftoverDir: cleanup.leftoverDir,
12801
12898
  branch: cleanup.branch
12802
12899
  }),
12803
12900
  "cleanup_incomplete",
@@ -12963,12 +13060,14 @@ var dropWorkspaceCommand = defineDeepspaceCommand({
12963
13060
  if (cleanup?.error) {
12964
13061
  const action = cleanupAction({
12965
13062
  worktreeDir: cleanup.worktreeDir,
13063
+ leftoverDir: cleanup.leftoverDir,
12966
13064
  branch: cleanup.branch
12967
13065
  });
12968
13066
  throw new Refusal(
12969
13067
  cleanupRefusalMessage({
12970
13068
  error: cleanup.error,
12971
13069
  worktreeDir: cleanup.worktreeDir,
13070
+ leftoverDir: cleanup.leftoverDir,
12972
13071
  branch: cleanup.branch
12973
13072
  }),
12974
13073
  "cleanup_incomplete",
@@ -13523,10 +13622,167 @@ function sameRefs(left, right) {
13523
13622
 
13524
13623
  // src/cli/commands/update.ts
13525
13624
  import * as p34 from "@clack/prompts";
13526
- import { existsSync as existsSync24, readFileSync as readFileSync27, writeFileSync as writeFileSync10 } from "fs";
13527
- import { join as join24, resolve as resolve20 } from "path";
13625
+ import { existsSync as existsSync25, readFileSync as readFileSync27, writeFileSync as writeFileSync8 } from "fs";
13626
+ import { join as join23, resolve as resolve20 } from "path";
13627
+
13628
+ // package.json
13629
+ var package_default = {
13630
+ name: "deepspace",
13631
+ version: "0.19.2",
13632
+ engines: {
13633
+ node: ">=22.15.0 <23 || >=24 <25 || >=26 <27"
13634
+ },
13635
+ description: "DeepSpace SDK \u2014 build real-time collaborative apps on Cloudflare Workers",
13636
+ type: "module",
13637
+ sideEffects: false,
13638
+ main: "./dist/index.js",
13639
+ types: "./dist/index.d.ts",
13640
+ bin: {
13641
+ deepspace: "dist/cli.js"
13642
+ },
13643
+ exports: {
13644
+ ".": {
13645
+ types: "./dist/index.d.ts",
13646
+ import: "./dist/index.js"
13647
+ },
13648
+ "./schema": {
13649
+ types: "./dist/schema.d.ts",
13650
+ import: "./dist/schema.js"
13651
+ },
13652
+ "./worker": {
13653
+ types: "./dist/worker.d.ts",
13654
+ import: "./dist/worker.js"
13655
+ },
13656
+ "./server": {
13657
+ types: "./dist/server.d.ts",
13658
+ import: "./dist/server.js"
13659
+ },
13660
+ "./testing": {
13661
+ types: "./dist/testing.d.ts",
13662
+ import: "./dist/testing.js"
13663
+ },
13664
+ "./documentation": {
13665
+ types: "./dist/documentation.d.ts",
13666
+ import: "./dist/documentation.js"
13667
+ },
13668
+ "./documentation/react": {
13669
+ types: "./dist/documentation-react.d.ts",
13670
+ import: "./dist/documentation-react.js"
13671
+ }
13672
+ },
13673
+ files: [
13674
+ "dist",
13675
+ "features",
13676
+ "CHANGELOG.md"
13677
+ ],
13678
+ scripts: {
13679
+ build: "node ../../scripts/clean-artifacts.mjs dist --tsbuildinfo && tsup",
13680
+ "type-check": "tsc --noEmit",
13681
+ test: "vitest run",
13682
+ "test:documentation-browser": "playwright test --config src/documentation/runtime/__browser__/playwright.config.ts",
13683
+ clean: "node ../../scripts/clean-artifacts.mjs dist --tsbuildinfo",
13684
+ prepublishOnly: "pnpm build"
13685
+ },
13686
+ dependencies: {
13687
+ "@ai-sdk/anthropic": "2.0.91",
13688
+ "@ai-sdk/openai": "2.0.116",
13689
+ "@ai-sdk/openai-compatible": "1.0.46",
13690
+ "@clack/prompts": "^1.2.0",
13691
+ "@mdx-js/mdx": "^3.1.1",
13692
+ acorn: "^8.16.0",
13693
+ ai: "5.0.222",
13694
+ "better-auth": "1.6.23",
13695
+ citty: "^0.2.0",
13696
+ "cross-spawn": "^7.0.6",
13697
+ esbuild: "^0.27.0",
13698
+ "highlight.js": "^11.11.1",
13699
+ hono: "^4.6.0",
13700
+ jose: "^6.1.0",
13701
+ marked: "^18.0.7",
13702
+ "mdast-util-gfm": "^3.1.0",
13703
+ "mdast-util-to-markdown": "^2.1.2",
13704
+ "rehype-highlight": "^7.0.2",
13705
+ "remark-frontmatter": "^5.0.0",
13706
+ "remark-gfm": "^4.0.1",
13707
+ "sanitize-html": "^2.17.6",
13708
+ "smol-toml": "^1.6.1",
13709
+ yaml: "^2.9.0",
13710
+ yjs: "^13.6.0",
13711
+ zod: "^4.3.6"
13712
+ },
13713
+ peerDependencies: {
13714
+ "@playwright/test": ">=1.40.0",
13715
+ clsx: ">=2.0.0",
13716
+ "lucide-react": ">=0.400.0",
13717
+ react: "^18.0.0 || ^19.0.0",
13718
+ "react-dom": "^18.0.0 || ^19.0.0",
13719
+ "tailwind-merge": ">=2.0.0"
13720
+ },
13721
+ peerDependenciesMeta: {
13722
+ "@playwright/test": {
13723
+ optional: true
13724
+ },
13725
+ react: {
13726
+ optional: true
13727
+ },
13728
+ "react-dom": {
13729
+ optional: true
13730
+ },
13731
+ clsx: {
13732
+ optional: true
13733
+ },
13734
+ "tailwind-merge": {
13735
+ optional: true
13736
+ },
13737
+ "lucide-react": {
13738
+ optional: true
13739
+ }
13740
+ },
13741
+ devDependencies: {
13742
+ "@cloudflare/workers-types": "^4.20241205.0",
13743
+ "@playwright/test": "^1.59.1",
13744
+ "@types/better-sqlite3": "^7.6.12",
13745
+ "@types/cross-spawn": "^6.0.6",
13746
+ "@types/react": "^19.0.0",
13747
+ "@types/react-dom": "^19.0.0",
13748
+ "@types/sanitize-html": "^2.16.1",
13749
+ "better-sqlite3": "^12.0.0",
13750
+ clsx: "^2.1.1",
13751
+ "framer-motion": "^12.38.0",
13752
+ jsdom: "^29.1.1",
13753
+ "lucide-react": "^1.7.0",
13754
+ "react-router-dom": "^7.1.0",
13755
+ "tailwind-merge": "^3.5.0",
13756
+ tsup: "^8.3.0",
13757
+ typescript: "^5.7.0",
13758
+ vitest: "^4.1.0"
13759
+ },
13760
+ keywords: [
13761
+ "deepspace",
13762
+ "cloudflare-workers",
13763
+ "durable-objects",
13764
+ "real-time",
13765
+ "collaborative",
13766
+ "sdk"
13767
+ ],
13768
+ license: "Apache-2.0",
13769
+ homepage: "https://deep.space",
13770
+ repository: {
13771
+ type: "git",
13772
+ url: "git+https://github.com/deepdotspace/deepspace.git",
13773
+ directory: "packages/deepspace"
13774
+ },
13775
+ bugs: {
13776
+ url: "https://github.com/deepdotspace/deepspace/issues"
13777
+ }
13778
+ };
13779
+
13780
+ // src/cli/commands/update.ts
13528
13781
  var REGISTRY = "https://registry.npmjs.org/deepspace/latest";
13529
13782
  var DOCS_URL = "https://documentation.deep.space/cli-reference/commands";
13783
+ var BUILD_PREVIEW_SECRETS_GUIDE = "https://github.com/deepdotspace/deepspace-sdk/blob/main/docs/migrations/build-preview-secrets.md";
13784
+ var USERS_SCHEMA_VISIBILITY_GUIDE = "https://github.com/deepdotspace/deepspace-sdk/blob/main/docs/migrations/users-schema-member-visibility.md";
13785
+ var COMPATIBLE_AI_VERSION = package_default.dependencies.ai;
13530
13786
  var OLDEST_SUPPORTED_MINOR = 12;
13531
13787
  function parseVersion(raw) {
13532
13788
  const match = /^(\d+)\.(\d+)\.(\d+)/.exec(raw.trim().replace(/^[\^~]/, ""));
@@ -13538,27 +13794,58 @@ function gapIsTooWide(from, to) {
13538
13794
  return from.major === 0 && from.minor < OLDEST_SUPPORTED_MINOR;
13539
13795
  }
13540
13796
  function readAppSdkVersion(appDir) {
13541
- const installed = join24(appDir, "node_modules", "deepspace", "package.json");
13542
- if (existsSync24(installed)) {
13797
+ const installed = join23(appDir, "node_modules", "deepspace", "package.json");
13798
+ if (existsSync25(installed)) {
13543
13799
  const parsed2 = JSON.parse(readFileSync27(installed, "utf8"));
13544
13800
  if (typeof parsed2.version === "string") return parsed2.version;
13545
13801
  }
13546
- const manifest = join24(appDir, "package.json");
13547
- if (!existsSync24(manifest)) return null;
13802
+ const manifest = join23(appDir, "package.json");
13803
+ if (!existsSync25(manifest)) return null;
13548
13804
  const parsed = JSON.parse(readFileSync27(manifest, "utf8"));
13549
13805
  return parsed.dependencies?.deepspace ?? null;
13550
13806
  }
13551
13807
  function pinSdkVersion(appDir, version) {
13552
- const path = join24(appDir, "package.json");
13808
+ const path = join23(appDir, "package.json");
13553
13809
  const source = readFileSync27(path, "utf8");
13554
- const updated = source.replace(
13555
- /("deepspace"\s*:\s*")[^"]+(")/,
13556
- (_match, head, tail) => `${head}^${version}${tail}`
13557
- );
13558
- if (updated === source) return false;
13559
- writeFileSync10(path, updated);
13810
+ const manifest = JSON.parse(source);
13811
+ const dependencies = manifest.dependencies;
13812
+ if (!dependencies || typeof dependencies !== "object" || Array.isArray(dependencies)) return false;
13813
+ let changed = false;
13814
+ if (Object.prototype.hasOwnProperty.call(dependencies, "deepspace") && typeof dependencies.deepspace === "string" && dependencies.deepspace !== `^${version}`) {
13815
+ dependencies.deepspace = `^${version}`;
13816
+ changed = true;
13817
+ }
13818
+ if (Object.prototype.hasOwnProperty.call(dependencies, "ai") && typeof dependencies.ai === "string" && dependencies.ai !== COMPATIBLE_AI_VERSION) {
13819
+ dependencies.ai = COMPATIBLE_AI_VERSION;
13820
+ changed = true;
13821
+ }
13822
+ if (!changed) return false;
13823
+ const eol = source.includes("\r\n") ? "\r\n" : "\n";
13824
+ const indent = source.match(/\r?\n([\t ]+)"/)?.[1] ?? " ";
13825
+ const finalEol = source.endsWith("\n") ? eol : "";
13826
+ writeFileSync8(path, JSON.stringify(manifest, null, indent).replaceAll("\n", eol) + finalEol);
13560
13827
  return true;
13561
13828
  }
13829
+ function buildPreviewSecretsUpgradeInstruction(appDir) {
13830
+ const configName = [
13831
+ "vite.config.ts",
13832
+ "vite.config.mts",
13833
+ "vite.config.js",
13834
+ "vite.config.mjs"
13835
+ ].find((candidate) => existsSync25(join23(appDir, candidate)));
13836
+ if (!configName) return null;
13837
+ const source = readFileSync27(join23(appDir, configName), "utf8");
13838
+ if (source.includes("deepspace-remove-build-preview-secrets")) return null;
13839
+ return `${configName}: add the build-preview secret cleanup from ${BUILD_PREVIEW_SECRETS_GUIDE}; app update left this app-owned file unchanged.`;
13840
+ }
13841
+ function usersSchemaVisibilityUpgradeInstruction(appDir) {
13842
+ const schemaName = "src/schemas/users-schema.ts";
13843
+ const path = join23(appDir, schemaName);
13844
+ if (!existsSync25(path)) return null;
13845
+ const source = readFileSync27(path, "utf8");
13846
+ if (!/\bmember\s*:\s*\{[^}]*\bread\s*:\s*true\b/s.test(source)) return null;
13847
+ return `${schemaName}: review the member user-row visibility change at ${USERS_SCHEMA_VISIBILITY_GUIDE}; app update left this app-owned schema unchanged.`;
13848
+ }
13562
13849
  async function latestPublishedVersion() {
13563
13850
  const response = await fetch(REGISTRY, { signal: AbortSignal.timeout(15e3) }).catch(
13564
13851
  (error) => {
@@ -13648,27 +13935,36 @@ var update_default = defineDeepspaceCommand({
13648
13935
  }
13649
13936
  const dryRun = args["dry-run"] === true;
13650
13937
  const plan = planAppMigrations(appDir);
13938
+ const previewSecretsInstruction = buildPreviewSecretsUpgradeInstruction(appDir);
13939
+ const usersSchemaInstruction = usersSchemaVisibilityUpgradeInstruction(appDir);
13940
+ const manualInstructions = [previewSecretsInstruction, usersSchemaInstruction].filter(
13941
+ (instruction) => instruction !== null
13942
+ );
13651
13943
  const pinned = dryRun ? false : pinSdkVersion(appDir, targetRaw);
13652
13944
  if (!dryRun && plan.edits.length) applyAppMigrationPlan(appDir, plan);
13653
13945
  if (!args.json) {
13654
13946
  p34.log.info(`deepspace ${currentRaw} \u2192 ${targetRaw}`);
13655
13947
  if (pinned) p34.log.success("package.json now asks for the current SDK");
13948
+ for (const instruction of manualInstructions) p34.log.warn(instruction);
13656
13949
  if (!plan.pending.length && !plan.blockers.length) {
13657
- p34.log.success("No source changes required.");
13950
+ p34.log.success("No automatic source changes required.");
13658
13951
  }
13659
13952
  reportPlan(plan, !dryRun);
13660
13953
  }
13661
- if (plan.blockers.length) {
13954
+ if (plan.blockers.length || manualInstructions.length) {
13955
+ const remaining = plan.blockers.length + manualInstructions.length;
13662
13956
  throw new Refusal(
13663
- `${plan.blockers.length} change(s) need a human or agent edit; each is listed above with its file, line, and the change required.`,
13957
+ `${remaining} change(s) need a human or agent edit; follow the returned manual instructions and blockers.`,
13664
13958
  "manual_changes_required",
13665
13959
  {
13960
+ ...pinned ? { action: cliAction("npm", "install") } : {},
13666
13961
  actionRequired: true,
13667
13962
  extra: {
13668
13963
  currentVersion: currentRaw,
13669
13964
  targetVersion: targetRaw,
13670
13965
  applied: plan.pending.map((migration) => migration.id),
13671
- blockers: plan.blockers
13966
+ blockers: plan.blockers,
13967
+ manualInstructions
13672
13968
  }
13673
13969
  }
13674
13970
  );
@@ -13679,6 +13975,7 @@ var update_default = defineDeepspaceCommand({
13679
13975
  targetVersion: targetRaw,
13680
13976
  pinned,
13681
13977
  dryRun,
13978
+ manualInstructions,
13682
13979
  applied: plan.pending.map((migration) => ({
13683
13980
  id: migration.id,
13684
13981
  description: migration.description,
@@ -13695,9 +13992,9 @@ var update_default = defineDeepspaceCommand({
13695
13992
  });
13696
13993
 
13697
13994
  // src/cli/cli.ts
13698
- var __dirname = dirname11(fileURLToPath3(import.meta.url));
13699
- var pkg = JSON.parse(readFileSync28(join25(__dirname, "..", "package.json"), "utf-8"));
13700
- if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync25("/etc/ssl/certs/ca-certificates.crt")) {
13995
+ var __dirname = dirname12(fileURLToPath3(import.meta.url));
13996
+ var pkg = JSON.parse(readFileSync28(join24(__dirname, "..", "package.json"), "utf-8"));
13997
+ if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync26("/etc/ssl/certs/ca-certificates.crt")) {
13701
13998
  process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
13702
13999
  }
13703
14000
  var create2 = defineCommand14({