deepspace 0.25.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  runCommand,
8
8
  renderUsage
9
9
  } from "citty";
10
- import { sync as spawnSync8 } from "cross-spawn";
10
+ import { sync as spawnSync9 } from "cross-spawn";
11
11
  import { readFileSync as readFileSync27, existsSync as existsSync30 } from "fs";
12
12
  import { dirname as dirname13, join as join29 } from "path";
13
13
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -366,7 +366,9 @@ function errorCode(err) {
366
366
  return void 0;
367
367
  }
368
368
  var API_ERROR_HINTS = {
369
- not_app_owner: "Only the app owner can do this.",
369
+ // Starts where the server sentence ("Only the app owner can do this.")
370
+ // stops, so the two read as one message instead of a stutter.
371
+ not_app_owner: "Collaborators can deploy and manage secrets, but ownership verbs (collaborators, transfer, undeploy) stay with the owner. Ask them to run it, have them transfer the app (`deepspace app transfer offer`), or fork your checkout as your own app (`deepspace app init --new-id`).",
370
372
  app_not_found: "App not found on the service this command asked. Check the app id \u2014 the DEEPSPACE_APP_ID value in wrangler.toml, usually `app_\u2026` (a legacy app's id is its name); list your apps with `deepspace app list`. If you are overriding DEEPSPACE_DEPLOY_URL (staging), note that account/collaborator commands go to the PLATFORM API \u2014 an app registered only on a staging deploy service is unknown there. (`domain` commands take the deployed app *name* instead.)",
371
373
  not_app_owner_or_collaborator: "You must be the app owner or a collaborator to do this.",
372
374
  test_account_cannot_be_collaborator: "Test accounts cannot be added as collaborators. Use a real DeepSpace account.",
@@ -591,13 +593,16 @@ function findUnknownCommand(argv, rootCommands) {
591
593
  const exactAll = commandPaths(table).filter((path) => path.at(-1) === token);
592
594
  const exactHit = exactAll.length === 1 ? exactAll[0] : null;
593
595
  const split = splitTokenPath(token, table);
594
- const relativeSuggestion = exactHit ?? split ?? closestCommandPath(token, table);
595
- if (!relativeSuggestion) return null;
596
- const guessed = relativeSuggestion.at(-1) ?? "";
596
+ const rootExactAll = exactHit || split ? [] : commandPaths(rootCommands).filter((path) => path.at(-1) === token);
597
+ const rootExact = rootExactAll.length === 1 ? rootExactAll[0] : null;
598
+ const relativeSuggestion = exactHit ?? split ?? (rootExact ? null : closestCommandPath(token, table));
599
+ const suggestionPath = relativeSuggestion ? [...accepted, ...relativeSuggestion] : rootExact;
600
+ if (!suggestionPath) return null;
601
+ const guessed = suggestionPath.at(-1) ?? "";
597
602
  return {
598
603
  attemptedPath: ["deepspace", ...accepted, token],
599
604
  helpPath: ["deepspace", ...accepted],
600
- suggestion: ["deepspace", ...accepted, ...relativeSuggestion],
605
+ suggestion: ["deepspace", ...suggestionPath],
601
606
  executable: split !== null && !DESTRUCTIVE_VERBS.has(guessed),
602
607
  // POSITIONALS ONLY. The remainder exists so the corrected verb keeps
603
608
  // the arguments it needs; carrying flags too meant `auth logn --email
@@ -1120,6 +1125,10 @@ var logout_default = defineDeepspaceCommand({
1120
1125
  }
1121
1126
  });
1122
1127
 
1128
+ // src/cli/lib/app-registration.ts
1129
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, rmSync as rmSync3, statSync } from "fs";
1130
+ import { join as join4 } from "path";
1131
+
1123
1132
  // src/cli/lib/app-identity.ts
1124
1133
  import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
1125
1134
  import { join as join3, resolve as resolve2 } from "path";
@@ -1459,55 +1468,9 @@ ${line}
1459
1468
  writeFileSync3(wranglerPath, src);
1460
1469
  }
1461
1470
 
1462
- // src/cli/commands/dev.ts
1463
- import { resolve as resolve8, basename as basename2, join as join11 } from "path";
1464
- import spawn2 from "cross-spawn";
1465
-
1466
- // src/cli/lib/app-context.ts
1467
- import { existsSync as existsSync5, readdirSync } from "fs";
1468
- import { dirname as dirname3, join as join4, resolve as resolve3 } from "path";
1469
- function detectAppName(cwd = process.cwd()) {
1470
- const appDir = resolve3(cwd);
1471
- if (!existsSync5(join4(appDir, "wrangler.toml"))) return null;
1472
- const { name } = readWranglerConfig(appDir);
1473
- return typeof name === "string" && name.length > 0 ? name : null;
1474
- }
1475
- function findAppDir(start = process.cwd()) {
1476
- let dir = resolve3(start);
1477
- for (; ; ) {
1478
- if (existsSync5(join4(dir, "wrangler.toml"))) return dir;
1479
- const parent = dirname3(dir);
1480
- if (parent === dir) return null;
1481
- dir = parent;
1482
- }
1483
- }
1484
- function findChildApps(dir) {
1485
- try {
1486
- return readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && existsSync5(join4(dir, entry.name, "wrangler.toml"))).map((entry) => entry.name).sort();
1487
- } catch {
1488
- return [];
1489
- }
1490
- }
1491
-
1492
- // src/cli/lib/launch-config.ts
1493
- import {
1494
- existsSync as existsSync8,
1495
- readFileSync as readFileSync7,
1496
- realpathSync as realpathSync2,
1497
- writeFileSync as writeFileSync4,
1498
- mkdirSync as mkdirSync4,
1499
- renameSync,
1500
- unlinkSync
1501
- } from "fs";
1502
- import { join as join6, resolve as resolve5, dirname as dirname5, basename, isAbsolute as isAbsolute2, sep } from "path";
1503
-
1504
- // src/cli/lib/git/repository.ts
1505
- import { appendFileSync, existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync6 } from "fs";
1506
- import { dirname as dirname4, join as join5, resolve as resolve4 } from "path";
1507
-
1508
1471
  // src/cli/lib/git/process.ts
1509
1472
  import { sync as spawnSync } from "cross-spawn";
1510
- import { existsSync as existsSync6 } from "fs";
1473
+ import { existsSync as existsSync5 } from "fs";
1511
1474
  var MAX_GIT_BUFFER = 512 * 1024 * 1024;
1512
1475
  var GIT_TIMEOUT_MS = 3e5;
1513
1476
  var GitError = class extends Error {
@@ -1545,7 +1508,7 @@ function runGit(cwd, args, opts = {}) {
1545
1508
  if (result.error) {
1546
1509
  const code = result.error.code;
1547
1510
  if (code === "ENOENT" || code === "ENOTDIR") {
1548
- if (code === "ENOTDIR" || !existsSync6(cwd)) {
1511
+ if (code === "ENOTDIR" || !existsSync5(cwd)) {
1549
1512
  throw new GitError(
1550
1513
  `The directory this command runs in is not there: ${cwd}. If it was a workspace worktree, prune it (\`git worktree prune\`) and re-attach.`,
1551
1514
  "worktree_missing"
@@ -1594,7 +1557,323 @@ function gitMeetsFloor(version) {
1594
1557
  return major > 2 || major === 2 && minor >= 29;
1595
1558
  }
1596
1559
 
1560
+ // src/cli/lib/vc-remote.ts
1561
+ import { existsSync as existsSync6 } from "fs";
1562
+ import { tmpdir } from "os";
1563
+ import { isAbsolute as isAbsolute2, relative, resolve as resolve3 } from "path";
1564
+ function spaceRemoteName(environment = DEEPSPACE_ENV) {
1565
+ if (environment === "staging") return "space-staging";
1566
+ if (environment === "production") return "space";
1567
+ return "space-invalid";
1568
+ }
1569
+ var SPACE_REMOTE = spaceRemoteName();
1570
+ function spaceTrackingRef(branch, remote = SPACE_REMOTE) {
1571
+ return `refs/remotes/${remote}/${branch}`;
1572
+ }
1573
+ function spacePrivateRef(path, environment = DEEPSPACE_ENV) {
1574
+ const root = environment === "production" ? "refs/deepspace" : `refs/deepspace/${environment}`;
1575
+ return `${root}/${path}`;
1576
+ }
1577
+ function deployBaseUrl() {
1578
+ return process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
1579
+ }
1580
+ function repoUrl(appId, base = deployBaseUrl()) {
1581
+ return `${base.replace(/\/+$/, "")}/api/repo/${encodeURIComponent(appId)}`;
1582
+ }
1583
+ function gitAuthEnv(token, base = deployBaseUrl()) {
1584
+ const existing = Number(process.env.GIT_CONFIG_COUNT ?? "0");
1585
+ const index = Number.isInteger(existing) && existing > 0 ? existing : 0;
1586
+ return {
1587
+ GIT_CONFIG_COUNT: String(index + 1),
1588
+ [`GIT_CONFIG_KEY_${index}`]: `http.${base.replace(/\/+$/, "")}.extraHeader`,
1589
+ [`GIT_CONFIG_VALUE_${index}`]: `Authorization: Bearer ${token}`
1590
+ };
1591
+ }
1592
+ function runGitRemote(cwd, token, args, opts = {}) {
1593
+ return runGit(cwd, args, { ...opts, env: { ...gitAuthEnv(token), ...opts.env ?? {} } });
1594
+ }
1595
+ function entryIsTransient(cwd) {
1596
+ const entry = process.argv[1];
1597
+ if (!entry) return true;
1598
+ if (/[\\/]_npx[\\/]/.test(entry) || entry.startsWith(tmpdir())) return true;
1599
+ const rel = relative(resolve3(cwd), resolve3(entry));
1600
+ return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel);
1601
+ }
1602
+ function helperEntryUsable(command) {
1603
+ const match = /^!(?:DEEPSPACE_ENV=(production|staging) )?'([^']*)' '([^']*)' git-credential$/.exec(command);
1604
+ const expectedEnvironment = DEEPSPACE_ENV === "production" ? void 0 : DEEPSPACE_ENV;
1605
+ if (!match || match[1] !== expectedEnvironment) return false;
1606
+ return existsSync6(match[2]) && existsSync6(match[3]);
1607
+ }
1608
+ function credentialHelperCommand(environment = DEEPSPACE_ENV) {
1609
+ const entry = process.argv[1];
1610
+ if (!entry) throw new Error("CLI entry path is unavailable");
1611
+ const environmentPrefix = environment === "staging" ? "DEEPSPACE_ENV=staging " : "";
1612
+ return `!${environmentPrefix}${shQuote(process.execPath)} ${shQuote(entry)} git-credential`;
1613
+ }
1614
+ function installCredentialHelper(cwd, url) {
1615
+ try {
1616
+ const origin = new URL(url);
1617
+ const key = `credential.${origin.protocol}//${origin.host}.helper`;
1618
+ const helper = credentialHelperCommand();
1619
+ const wanted = ["", helper];
1620
+ const transient = entryIsTransient(cwd);
1621
+ const scope = transient ? "--worktree" : "--global";
1622
+ if (transient) {
1623
+ const enabled = runGit(cwd, ["config", "--local", "extensions.worktreeConfig", "true"], {
1624
+ allowFail: true
1625
+ });
1626
+ if (enabled.status !== 0) return;
1627
+ runGit(cwd, ["config", "--local", "--unset-all", key], { allowFail: true });
1628
+ }
1629
+ const current = runGit(cwd, ["config", scope, "--get-all", key], { allowFail: true });
1630
+ if (current.status === 0) {
1631
+ const existing = current.stdout.toString("utf-8").split("\n").filter((_, index, lines) => index < lines.length - 1);
1632
+ if (existing.length === wanted.length && existing.every((value, i) => value === wanted[i])) {
1633
+ return;
1634
+ }
1635
+ if (existing.length === wanted.length && existing[0] === "" && helperEntryUsable(existing[1])) {
1636
+ return;
1637
+ }
1638
+ runGit(cwd, ["config", scope, "--unset-all", key], { allowFail: true });
1639
+ }
1640
+ runGit(cwd, ["config", scope, "--add", key, ""]);
1641
+ runGit(cwd, ["config", scope, "--add", key, helper]);
1642
+ if (scope === "--global" && !process.argv.includes("--json")) {
1643
+ process.stderr.write(
1644
+ `note: added a git credential helper for ${origin.host} to your global git config so plain \`git clone\`/\`git push\` to DeepSpace authenticate. Undo with: git config --global --unset-all ${key}
1645
+ `
1646
+ );
1647
+ }
1648
+ } catch {
1649
+ }
1650
+ }
1651
+ function ensureSpaceRemote(cwd, appId, remote = SPACE_REMOTE, token) {
1652
+ const url = repoUrl(appId);
1653
+ const current = runGit(cwd, ["remote", "get-url", remote], { allowFail: true });
1654
+ const existing = current.status === 0 ? current.stdout.toString("utf-8").trim() : null;
1655
+ if (appIdFromRepoUrl(existing ?? "") !== appId) {
1656
+ const owned = declaredAppIds(cwd);
1657
+ if (owned.size > 0 && !owned.has(appId)) {
1658
+ throw new InputError(
1659
+ `This checkout declares ${[...owned].join(", ")}, but this command targets ${appId}. Pointing its \`${remote}\` remote at another app would send every later plain \`git push ${remote}\` to the wrong repo. Work on ${appId} from its own checkout (\`deepspace clone ${appId}\` into a new directory), or fork THIS checkout into a brand-new app of your own with \`deepspace app init --new-id\`.`,
1660
+ "app_checkout_mismatch"
1661
+ );
1662
+ }
1663
+ }
1664
+ if (token) ensureGitIdentity(cwd, token);
1665
+ if (existing === null) {
1666
+ runGit(cwd, ["remote", "add", remote, url]);
1667
+ } else if (existing !== url) {
1668
+ const priorApp = appIdFromRepoUrl(existing);
1669
+ if (priorApp && priorApp !== appId && !process.argv.includes("--json")) {
1670
+ process.stderr.write(
1671
+ `warning: remote '${remote}' was pointed at app ${priorApp}; re-aiming it at ${appId}.
1672
+ `
1673
+ );
1674
+ }
1675
+ runGit(cwd, ["remote", "set-url", remote, url]);
1676
+ }
1677
+ installCredentialHelper(cwd, url);
1678
+ return url;
1679
+ }
1680
+ function removeSpaceRemote(cwd, remote = SPACE_REMOTE) {
1681
+ const current = runGit(cwd, ["remote", "get-url", remote], { allowFail: true });
1682
+ if (current.status !== 0) return false;
1683
+ runGit(cwd, ["remote", "remove", remote]);
1684
+ return true;
1685
+ }
1686
+ function ensureGitIdentity(cwd, token) {
1687
+ try {
1688
+ const configured = (key) => runGit(cwd, ["config", "--get", key], { allowFail: true }).stdout.toString("utf-8").trim() !== "";
1689
+ const missingName = !configured("user.name");
1690
+ const missingEmail = !configured("user.email");
1691
+ if (!missingName && !missingEmail) return;
1692
+ if (runGit(cwd, ["config", "--get", "--type=bool", "user.useConfigOnly"], { allowFail: true }).stdout.toString("utf-8").trim() === "true") {
1693
+ return;
1694
+ }
1695
+ const payload = decodeJwtPayload(token);
1696
+ if (!payload.email) return;
1697
+ if (missingName) {
1698
+ runGit(cwd, ["config", "--local", "user.name", payload.name || payload.email], {
1699
+ allowFail: true
1700
+ });
1701
+ }
1702
+ if (missingEmail) {
1703
+ runGit(cwd, ["config", "--local", "user.email", payload.email], { allowFail: true });
1704
+ }
1705
+ const wrote = [...missingEmail ? ["user.email"] : [], ...missingName ? ["user.name"] : []];
1706
+ const dir = /^[A-Za-z0-9_./:@-]+$/.test(cwd) ? cwd : shQuote(cwd);
1707
+ const undo = wrote.map((key) => `git -C ${dir} config --local --unset ${key}`).join(" && ");
1708
+ process.stderr.write(
1709
+ `Git identity for this checkout set from your DeepSpace login (${payload.email}).
1710
+ Undo with: ${undo}
1711
+ `
1712
+ );
1713
+ } catch {
1714
+ }
1715
+ }
1716
+ function appIdFromRepoUrl(url) {
1717
+ const match = /\/api\/repo\/([^/]+)\/?$/.exec(url);
1718
+ return match ? decodeURIComponent(match[1]) : null;
1719
+ }
1720
+
1721
+ // src/cli/lib/app-registration.ts
1722
+ var DEPLOY_URL = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
1723
+ async function mintAppIdentity(appDir, token, opts = {}) {
1724
+ const { appId } = await apiFetch(DEPLOY_URL, token, "/api/apps/mint", {
1725
+ method: "POST"
1726
+ });
1727
+ try {
1728
+ writeAppId(appDir, appId, { wranglerEnv: opts.wranglerEnv, force: Boolean(opts.force) });
1729
+ } catch (error) {
1730
+ throw new Error(
1731
+ `Registered ${appId}, but writing it to wrangler.toml failed: ${error instanceof Error ? error.message : String(error)} \u2014 add DEEPSPACE_APP_ID = "${appId}" ${opts.wranglerEnv ? `to [env.${opts.wranglerEnv}.vars] ` : ""}by hand.`
1732
+ );
1733
+ }
1734
+ const committedScaffold = opts.commitScaffold === true && commitScaffoldIfUnborn(appDir, token);
1735
+ return { appId, committedScaffold };
1736
+ }
1737
+ function canHealAppRegistration(appDir, wranglerEnv) {
1738
+ if (!hasWranglerConfig(appDir)) return false;
1739
+ try {
1740
+ const cfg = readWranglerConfig(appDir);
1741
+ if (wranglerEnv && cfg.env?.[wranglerEnv] === void 0) return false;
1742
+ if (readAppIdVar(cfg, wranglerEnv) === void 0) return false;
1743
+ } catch {
1744
+ return false;
1745
+ }
1746
+ try {
1747
+ const committed = runGit(appDir, ["show", "HEAD:./wrangler.toml"], { allowFail: true });
1748
+ if (committed.status === 0 && committed.stdout.toString("utf-8").includes("__APP_ID__")) {
1749
+ return false;
1750
+ }
1751
+ } catch {
1752
+ }
1753
+ return true;
1754
+ }
1755
+ async function ensureAppRegistered(appDir, token, wranglerEnv, opts = {}) {
1756
+ const existing = readAppId(appDir, wranglerEnv);
1757
+ if (existing) return { appId: existing, minted: false, committedScaffold: false };
1758
+ if (!canHealAppRegistration(appDir, wranglerEnv)) return null;
1759
+ const release2 = acquireMintLock(appDir);
1760
+ if (!release2) {
1761
+ const winner = await waitForMintedId(appDir, wranglerEnv);
1762
+ if (winner) return { appId: winner, minted: false, committedScaffold: false };
1763
+ }
1764
+ try {
1765
+ const raced = readAppId(appDir, wranglerEnv);
1766
+ if (raced) return { appId: raced, minted: false, committedScaffold: false };
1767
+ const { appId, committedScaffold } = await mintAppIdentity(appDir, token, {
1768
+ wranglerEnv,
1769
+ commitScaffold: opts.commitScaffold
1770
+ });
1771
+ process.stderr.write(
1772
+ `Registered ${appId}${wranglerEnv ? ` (env: ${wranglerEnv})` : ""} on ${DEEPSPACE_ENV} to your account \u2014 apps register on first use` + (committedScaffold ? "; initial scaffold commit created" : "") + ".\n"
1773
+ );
1774
+ return { appId, minted: true, committedScaffold };
1775
+ } finally {
1776
+ release2?.();
1777
+ }
1778
+ }
1779
+ function acquireMintLock(appDir) {
1780
+ const lockDir = join4(appDir, ".deepspace", "register.lock");
1781
+ mkdirSync3(join4(appDir, ".deepspace"), { recursive: true });
1782
+ try {
1783
+ mkdirSync3(lockDir);
1784
+ } catch {
1785
+ try {
1786
+ const age = Date.now() - statSync(lockDir).mtimeMs;
1787
+ if (age < 2 * 60 * 1e3) return null;
1788
+ rmSync3(lockDir, { recursive: true, force: true });
1789
+ mkdirSync3(lockDir);
1790
+ } catch {
1791
+ return null;
1792
+ }
1793
+ }
1794
+ return () => rmSync3(lockDir, { recursive: true, force: true });
1795
+ }
1796
+ async function waitForMintedId(appDir, wranglerEnv) {
1797
+ const deadline = Date.now() + 3e4;
1798
+ while (Date.now() < deadline) {
1799
+ const id = readAppId(appDir, wranglerEnv);
1800
+ if (id) return id;
1801
+ if (!existsSync7(join4(appDir, ".deepspace", "register.lock"))) {
1802
+ return readAppId(appDir, wranglerEnv);
1803
+ }
1804
+ await new Promise((resolve23) => setTimeout(resolve23, 250));
1805
+ }
1806
+ return null;
1807
+ }
1808
+ function wranglerConfigUncommitted(appDir) {
1809
+ if (!existsSync7(join4(appDir, ".git"))) return false;
1810
+ const dryRun = runGit(appDir, ["commit", "--dry-run", "-m", "x", "--", "wrangler.toml"], {
1811
+ allowFail: true
1812
+ });
1813
+ return dryRun.status === 0;
1814
+ }
1815
+ function commitScaffoldIfUnborn(appDir, token) {
1816
+ try {
1817
+ if (!existsSync7(join4(appDir, ".git"))) return false;
1818
+ if (runGit(appDir, ["rev-parse", "--verify", "HEAD"], { allowFail: true }).status === 0) {
1819
+ return false;
1820
+ }
1821
+ ensureGitIdentity(appDir, token);
1822
+ if (runGit(appDir, ["add", "-A"], { allowFail: true }).status !== 0) return false;
1823
+ const commit = runGit(appDir, ["commit", "-m", "Initial DeepSpace scaffold", "--no-verify"], {
1824
+ allowFail: true
1825
+ });
1826
+ return commit.status === 0;
1827
+ } catch {
1828
+ return false;
1829
+ }
1830
+ }
1831
+
1832
+ // src/cli/commands/dev.ts
1833
+ import { resolve as resolve9, basename as basename2, join as join12 } from "path";
1834
+ import spawn2 from "cross-spawn";
1835
+
1836
+ // src/cli/lib/app-context.ts
1837
+ import { existsSync as existsSync8, readdirSync } from "fs";
1838
+ import { dirname as dirname3, join as join5, resolve as resolve4 } from "path";
1839
+ function detectAppName(cwd = process.cwd()) {
1840
+ const appDir = resolve4(cwd);
1841
+ if (!existsSync8(join5(appDir, "wrangler.toml"))) return null;
1842
+ const { name } = readWranglerConfig(appDir);
1843
+ return typeof name === "string" && name.length > 0 ? name : null;
1844
+ }
1845
+ function findAppDir(start = process.cwd()) {
1846
+ let dir = resolve4(start);
1847
+ for (; ; ) {
1848
+ if (existsSync8(join5(dir, "wrangler.toml"))) return dir;
1849
+ const parent = dirname3(dir);
1850
+ if (parent === dir) return null;
1851
+ dir = parent;
1852
+ }
1853
+ }
1854
+ function findChildApps(dir) {
1855
+ try {
1856
+ return readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && existsSync8(join5(dir, entry.name, "wrangler.toml"))).map((entry) => entry.name).sort();
1857
+ } catch {
1858
+ return [];
1859
+ }
1860
+ }
1861
+
1862
+ // src/cli/lib/launch-config.ts
1863
+ import {
1864
+ existsSync as existsSync10,
1865
+ readFileSync as readFileSync7,
1866
+ realpathSync as realpathSync2,
1867
+ writeFileSync as writeFileSync4,
1868
+ mkdirSync as mkdirSync5,
1869
+ renameSync,
1870
+ unlinkSync
1871
+ } from "fs";
1872
+ import { join as join7, resolve as resolve6, dirname as dirname5, basename, isAbsolute as isAbsolute3, sep } from "path";
1873
+
1597
1874
  // src/cli/lib/git/repository.ts
1875
+ import { appendFileSync, existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync6 } from "fs";
1876
+ import { dirname as dirname4, join as join6, resolve as resolve5 } from "path";
1598
1877
  function isPlausibleBranchName(name) {
1599
1878
  if (!name || name === "HEAD" || name === "@") return false;
1600
1879
  if (name.startsWith("/") || name.startsWith("-") || name.startsWith(".")) return false;
@@ -1644,7 +1923,7 @@ function assertNoOperationInProgress(cwd) {
1644
1923
  ["REVERT_HEAD", "revert", "git revert --continue", "git revert --abort"],
1645
1924
  ["rebase-merge", "rebase", "git rebase --continue", "git rebase --abort"],
1646
1925
  ["rebase-apply", "rebase", "git rebase --continue", "git rebase --abort"]
1647
- ].find(([marker]) => existsSync7(join5(resolve4(cwd, gitDir), marker)));
1926
+ ].find(([marker]) => existsSync9(join6(resolve5(cwd, gitDir), marker)));
1648
1927
  if (!inProgress) return;
1649
1928
  const [, kind, finish, abort] = inProgress;
1650
1929
  throw new InputError(
@@ -1658,13 +1937,13 @@ function repoToplevel(cwd) {
1658
1937
  function interruptedGitOperation(cwd) {
1659
1938
  const gitDir = runGit(cwd, ["rev-parse", "--git-path", "HEAD"], { allowFail: true });
1660
1939
  if (gitDir.status !== 0) return null;
1661
- const dir = dirname4(resolve4(cwd, gitDir.stdout.toString("utf-8").trim()));
1662
- if (existsSync7(join5(dir, "rebase-apply", "applying"))) return "am";
1663
- if (existsSync7(join5(dir, "rebase-merge")) || existsSync7(join5(dir, "rebase-apply"))) return "rebase";
1664
- if (existsSync7(join5(dir, "MERGE_HEAD"))) return "merge";
1665
- if (existsSync7(join5(dir, "CHERRY_PICK_HEAD"))) return "cherry-pick";
1666
- if (existsSync7(join5(dir, "REVERT_HEAD"))) return "revert";
1667
- if (existsSync7(join5(dir, "BISECT_LOG"))) return "bisect";
1940
+ const dir = dirname4(resolve5(cwd, gitDir.stdout.toString("utf-8").trim()));
1941
+ if (existsSync9(join6(dir, "rebase-apply", "applying"))) return "am";
1942
+ if (existsSync9(join6(dir, "rebase-merge")) || existsSync9(join6(dir, "rebase-apply"))) return "rebase";
1943
+ if (existsSync9(join6(dir, "MERGE_HEAD"))) return "merge";
1944
+ if (existsSync9(join6(dir, "CHERRY_PICK_HEAD"))) return "cherry-pick";
1945
+ if (existsSync9(join6(dir, "REVERT_HEAD"))) return "revert";
1946
+ if (existsSync9(join6(dir, "BISECT_LOG"))) return "bisect";
1668
1947
  return null;
1669
1948
  }
1670
1949
  function currentBranch(cwd) {
@@ -1741,11 +2020,11 @@ function listWorktrees(cwd) {
1741
2020
  current.branch = ref.startsWith("refs/heads/") ? ref.slice("refs/heads/".length) : ref;
1742
2021
  }
1743
2022
  }
1744
- return worktrees.filter((worktree) => existsSync7(worktree.path));
2023
+ return worktrees.filter((worktree) => existsSync9(worktree.path));
1745
2024
  }
1746
2025
  function ensureLocalExclude(cwd, pattern) {
1747
- const commonDir = resolve4(cwd, gitLine(cwd, ["rev-parse", "--git-common-dir"]));
1748
- const excludePath = join5(commonDir, "info", "exclude");
2026
+ const commonDir = resolve5(cwd, gitLine(cwd, ["rev-parse", "--git-common-dir"]));
2027
+ const excludePath = join6(commonDir, "info", "exclude");
1749
2028
  let current = "";
1750
2029
  try {
1751
2030
  current = readFileSync6(excludePath, "utf-8");
@@ -1753,7 +2032,7 @@ function ensureLocalExclude(cwd, pattern) {
1753
2032
  }
1754
2033
  if (current.split("\n").some((line) => line.trim() === pattern)) return;
1755
2034
  try {
1756
- mkdirSync3(dirname4(excludePath), { recursive: true });
2035
+ mkdirSync4(dirname4(excludePath), { recursive: true });
1757
2036
  appendFileSync(
1758
2037
  excludePath,
1759
2038
  `${current.endsWith("\n") || current === "" ? "" : "\n"}${pattern}
@@ -1765,7 +2044,7 @@ function ensureLocalExclude(cwd, pattern) {
1765
2044
 
1766
2045
  // src/cli/lib/launch-config.ts
1767
2046
  function writeLaunchFile(launchPath, config) {
1768
- mkdirSync4(dirname5(launchPath), { recursive: true });
2047
+ mkdirSync5(dirname5(launchPath), { recursive: true });
1769
2048
  const tmpPath = `${launchPath}.${process.pid}.tmp`;
1770
2049
  writeFileSync4(tmpPath, JSON.stringify(config, null, 2) + "\n");
1771
2050
  try {
@@ -1780,7 +2059,7 @@ function writeLaunchFile(launchPath, config) {
1780
2059
  }
1781
2060
  function readLaunchFile(launchPath) {
1782
2061
  const empty = { version: "0.0.1", configurations: [] };
1783
- if (!existsSync8(launchPath)) return empty;
2062
+ if (!existsSync10(launchPath)) return empty;
1784
2063
  const raw = readFileSync7(launchPath, "utf-8").trim();
1785
2064
  if (!raw) return empty;
1786
2065
  const parsed = JSON.parse(raw);
@@ -1788,7 +2067,7 @@ function readLaunchFile(launchPath) {
1788
2067
  return parsed;
1789
2068
  }
1790
2069
  function writeLaunchConfigIfMissing(appDir, appName, port, opts = {}) {
1791
- const launchPath = join6(appDir, ".claude", "launch.json");
2070
+ const launchPath = join7(appDir, ".claude", "launch.json");
1792
2071
  try {
1793
2072
  const config = readLaunchFile(launchPath);
1794
2073
  if (!config) return;
@@ -1829,7 +2108,7 @@ function writeLaunchConfigIfMissing(appDir, appName, port, opts = {}) {
1829
2108
  }
1830
2109
  function resolveAppLaunchPort(appDir) {
1831
2110
  try {
1832
- const config = readLaunchFile(join6(appDir, ".claude", "launch.json"));
2111
+ const config = readLaunchFile(join7(appDir, ".claude", "launch.json"));
1833
2112
  if (!config) return null;
1834
2113
  const appName = detectAppName(appDir) ?? basename(appDir);
1835
2114
  const entry = config.configurations.find((c) => c?.name === appName) ?? (config.configurations.length === 1 ? config.configurations[0] : void 0);
@@ -1860,7 +2139,7 @@ function canonicalPath(path) {
1860
2139
  try {
1861
2140
  return realpathSync2(path);
1862
2141
  } catch {
1863
- return resolve5(path);
2142
+ return resolve6(path);
1864
2143
  }
1865
2144
  }
1866
2145
  function detectLinkedWorktree(appDir) {
@@ -1883,21 +2162,21 @@ function deriveWorktreePort(worktreeKey) {
1883
2162
  return WORKTREE_PORT_MIN + hash % WORKTREE_PORT_RANGE;
1884
2163
  }
1885
2164
  function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName, appDir, opts) {
1886
- const launchPath = join6(mainRepoRoot, ".claude", "launch.json");
2165
+ const launchPath = join7(mainRepoRoot, ".claude", "launch.json");
1887
2166
  const entryName = WORKTREE_ENTRY_PREFIX + worktreeName;
1888
2167
  try {
1889
2168
  const config = readLaunchFile(launchPath);
1890
2169
  if (!config) return null;
1891
- const worktreesRoot = join6(mainRepoRoot, ".claude", "worktrees") + sep;
2170
+ const worktreesRoot = join7(mainRepoRoot, ".claude", "worktrees") + sep;
1892
2171
  config.configurations = config.configurations.filter((c) => {
1893
2172
  if (c?.name === entryName) return false;
1894
- const isStaleWorktreeEntry = typeof c?.name === "string" && c.name.startsWith(WORKTREE_ENTRY_PREFIX) && typeof c.cwd === "string" && isAbsolute2(c.cwd) && c.cwd.startsWith(worktreesRoot) && !existsSync8(c.cwd);
2173
+ const isStaleWorktreeEntry = typeof c?.name === "string" && c.name.startsWith(WORKTREE_ENTRY_PREFIX) && typeof c.cwd === "string" && isAbsolute3(c.cwd) && c.cwd.startsWith(worktreesRoot) && !existsSync10(c.cwd);
1895
2174
  return !isStaleWorktreeEntry;
1896
2175
  });
1897
2176
  let port = opts.port;
1898
2177
  if (opts.probePort) {
1899
2178
  const taken = new Set(
1900
- config.configurations.map((c) => Number(c?.port)).filter((p36) => Number.isInteger(p36) && p36 > 0)
2179
+ config.configurations.map((c) => Number(c?.port)).filter((p35) => Number.isInteger(p35) && p35 > 0)
1901
2180
  );
1902
2181
  for (let i = 0; i < WORKTREE_PORT_RANGE && taken.has(port); i++) {
1903
2182
  port = WORKTREE_PORT_MIN + (port - WORKTREE_PORT_MIN + 1) % WORKTREE_PORT_RANGE;
@@ -1912,7 +2191,7 @@ function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName, appDir, opts) {
1912
2191
  // the Claude Code desktop launch.json reference alongside `port`) and
1913
2192
  // verified end-to-end: with it set, the preview server builds from the
1914
2193
  // worktree instead of the session's main repo.
1915
- cwd: resolve5(appDir)
2194
+ cwd: resolve6(appDir)
1916
2195
  });
1917
2196
  writeLaunchFile(launchPath, config);
1918
2197
  return { entryName, port };
@@ -1926,7 +2205,7 @@ function resolveWorktreePort(appDir) {
1926
2205
  const claude = detectClaudeWorktree(appDir);
1927
2206
  if (claude) {
1928
2207
  try {
1929
- const config = readLaunchFile(join6(claude.mainRepoRoot, ".claude", "launch.json"));
2208
+ const config = readLaunchFile(join7(claude.mainRepoRoot, ".claude", "launch.json"));
1930
2209
  const entry = config?.configurations.find(
1931
2210
  (c) => c?.name === WORKTREE_ENTRY_PREFIX + claude.worktreeName
1932
2211
  );
@@ -1987,14 +2266,6 @@ async function getAppSource(deployUrl, token, appId) {
1987
2266
  throw error;
1988
2267
  }
1989
2268
  }
1990
- function setAppSource(deployUrl, token, appId, input) {
1991
- return apiFetch(
1992
- deployUrl,
1993
- token,
1994
- `/api/apps/${encodeURIComponent(appId)}/source`,
1995
- { method: "POST", body: JSON.stringify(input) }
1996
- );
1997
- }
1998
2269
 
1999
2270
  // src/cli/lib/repo-api.ts
2000
2271
  function isWorkspaceRef(ref) {
@@ -2002,10 +2273,15 @@ function isWorkspaceRef(ref) {
2002
2273
  return ref.startsWith(prefix) && isWorkspaceId(ref.slice(prefix.length));
2003
2274
  }
2004
2275
  function releaseSourceLabel(release2) {
2005
- if (release2.commitOid) return `commit ${release2.commitOid.slice(0, 10)}`;
2006
- if (release2.source?.provider === "github") return `GitHub \xB7 ${release2.source.repository}`;
2007
- if (release2.source?.provider === "deepspace") return "DeepSpace source, no commit recorded";
2008
- return "no source recorded";
2276
+ const dirty = release2.sourceDirty === true ? " (dirty worktree)" : "";
2277
+ if (release2.commitOid) return `commit ${release2.commitOid.slice(0, 10)}${dirty}`;
2278
+ if (release2.source?.provider === "github") {
2279
+ return `GitHub \xB7 ${release2.source.repository}${dirty ? ", dirty worktree" : ""}`;
2280
+ }
2281
+ if (release2.source?.provider === "deepspace") {
2282
+ return `DeepSpace source, no commit recorded${dirty}`;
2283
+ }
2284
+ return `no source recorded${dirty}`;
2009
2285
  }
2010
2286
  function mintIdempotencyKey() {
2011
2287
  return randomUUID();
@@ -2186,6 +2462,11 @@ async function resolveAppTarget(deployUrl, token, explicit, options = {}) {
2186
2462
  if (app2 !== void 0) return resolveAppSelector(deployUrl, token, app2);
2187
2463
  const { wranglerEnv } = parseWranglerEnvArg(options.wranglerEnv);
2188
2464
  const appDir = findAppDir();
2465
+ if (options.register === true) {
2466
+ const ensured = appDir ? await ensureAppRegistered(appDir, token, wranglerEnv) : null;
2467
+ if (!ensured) throw appTargetMissingError(appDir, wranglerEnv);
2468
+ return ensured.appId;
2469
+ }
2189
2470
  const id = appDir ? readAppId(appDir, wranglerEnv) : null;
2190
2471
  if (!id) throw appTargetMissingError(appDir, wranglerEnv);
2191
2472
  return id;
@@ -2255,7 +2536,9 @@ function assertAppTargetResolvable(appArg, options = {}) {
2255
2536
  }
2256
2537
  if (app2 !== void 0) return;
2257
2538
  const appDir = findAppDir();
2258
- if (!appDir || !readAppId(appDir, wranglerEnv)) throw appTargetMissingError(appDir, wranglerEnv);
2539
+ if (appDir && readAppId(appDir, wranglerEnv)) return;
2540
+ if (options.register === true && appDir && canHealAppRegistration(appDir, wranglerEnv)) return;
2541
+ throw appTargetMissingError(appDir, wranglerEnv);
2259
2542
  }
2260
2543
  async function warnIfPhantomApp(deployUrl, token, appId, explicit) {
2261
2544
  if (!explicit || !STRICT_APP_ID_RE.test(explicit.trim())) return;
@@ -2357,22 +2640,34 @@ async function writeDevVars(appDir, ownerId, callerJwt, wranglerEnv, opts = {})
2357
2640
  }
2358
2641
 
2359
2642
  // src/cli/lib/install-status.ts
2360
- import { existsSync as existsSync10, readFileSync as readFileSync9, realpathSync as realpathSync3 } from "fs";
2361
- import { dirname as dirname6, join as join8, resolve as resolve6 } from "path";
2643
+ import {
2644
+ appendFileSync as appendFileSync2,
2645
+ closeSync,
2646
+ existsSync as existsSync12,
2647
+ mkdirSync as mkdirSync6,
2648
+ openSync,
2649
+ readFileSync as readFileSync9,
2650
+ realpathSync as realpathSync3,
2651
+ rmSync as rmSync4,
2652
+ statSync as statSync2,
2653
+ writeFileSync as writeFileSync5
2654
+ } from "fs";
2655
+ import { dirname as dirname6, join as join9, resolve as resolve7 } from "path";
2656
+ import { sync as spawnSync2 } from "cross-spawn";
2362
2657
 
2363
2658
  // src/cli/lib/package-manager.ts
2364
- import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
2365
- import { join as join7 } from "path";
2659
+ import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
2660
+ import { join as join8 } from "path";
2366
2661
  function detectPackageManager(appDir) {
2367
2662
  try {
2368
- const pkg2 = JSON.parse(readFileSync8(join7(appDir, "package.json"), "utf-8"));
2663
+ const pkg2 = JSON.parse(readFileSync8(join8(appDir, "package.json"), "utf-8"));
2369
2664
  const name = typeof pkg2.packageManager === "string" ? pkg2.packageManager.split("@")[0] : "";
2370
2665
  if (name === "npm" || name === "pnpm" || name === "yarn" || name === "bun") return name;
2371
2666
  } catch {
2372
2667
  }
2373
- if (existsSync9(join7(appDir, "pnpm-lock.yaml"))) return "pnpm";
2374
- if (existsSync9(join7(appDir, "yarn.lock"))) return "yarn";
2375
- if (existsSync9(join7(appDir, "bun.lock")) || existsSync9(join7(appDir, "bun.lockb"))) return "bun";
2668
+ if (existsSync11(join8(appDir, "pnpm-lock.yaml"))) return "pnpm";
2669
+ if (existsSync11(join8(appDir, "yarn.lock"))) return "yarn";
2670
+ if (existsSync11(join8(appDir, "bun.lock")) || existsSync11(join8(appDir, "bun.lockb"))) return "bun";
2376
2671
  return "npm";
2377
2672
  }
2378
2673
  function installCommand(appDir) {
@@ -2382,10 +2677,10 @@ function installCommand(appDir) {
2382
2677
  // src/cli/lib/install-status.ts
2383
2678
  function installState(appDir) {
2384
2679
  if (resolvesDeepspace(appDir)) return "ready";
2385
- const sentinelDir = join8(appDir, ".deepspace");
2386
- if (existsSync10(join8(sentinelDir, "install.err"))) return "failed";
2387
- if (existsSync10(join8(sentinelDir, "install.started"))) {
2388
- if (!existsSync10(join8(sentinelDir, "install.done")) && installWorkerAlive(sentinelDir)) {
2680
+ const sentinelDir = join9(appDir, ".deepspace");
2681
+ if (existsSync12(join9(sentinelDir, "install.err"))) return "failed";
2682
+ if (existsSync12(join9(sentinelDir, "install.started"))) {
2683
+ if (!existsSync12(join9(sentinelDir, "install.done")) && installWorkerAlive(sentinelDir)) {
2389
2684
  return "installing";
2390
2685
  }
2391
2686
  return "failed";
@@ -2397,43 +2692,109 @@ function tailHint(logPath, platform = process.platform) {
2397
2692
  }
2398
2693
  function ensureInstallReady(appDir) {
2399
2694
  if (resolvesDeepspace(appDir)) return;
2400
- const sentinelDir = join8(appDir, ".deepspace");
2401
- const errPath = join8(sentinelDir, "install.err");
2402
- const startedPath = join8(sentinelDir, "install.started");
2403
- const donePath = join8(sentinelDir, "install.done");
2404
- const logPath = join8(sentinelDir, "install.log");
2405
- if (existsSync10(errPath)) {
2406
- const detail = readFileSync9(errPath, "utf-8").trimEnd();
2407
- const log29 = existsSync10(logPath) ? ` Full log: ${logPath}.` : "";
2695
+ const sentinelDir = join9(appDir, ".deepspace");
2696
+ const startedPath = join9(sentinelDir, "install.started");
2697
+ const donePath = join9(sentinelDir, "install.done");
2698
+ const logPath = join9(sentinelDir, "install.log");
2699
+ if (process.env.DEEPSPACE_NO_INSTALL) {
2408
2700
  throw new Refusal(
2409
- `Dependency install failed: ${detail}.${log29} Run \`npm install\` (or \`bun install\`) manually, then retry.`,
2410
- "install_failed",
2701
+ "Dependencies not installed, and DEEPSPACE_NO_INSTALL is set \u2014 run the install yourself, then retry.",
2702
+ "deps_missing",
2411
2703
  { action: { cwd: appDir, argv: [detectPackageManager(appDir), "install"] } }
2412
2704
  );
2413
2705
  }
2414
- if (existsSync10(startedPath) && !existsSync10(donePath)) {
2415
- if (!installWorkerAlive(sentinelDir)) {
2416
- const log29 = existsSync10(logPath) ? ` See what happened: ${logPath}.` : "";
2417
- throw new Refusal(
2418
- `The dependency install is no longer running and never finished.${log29} Run \`npm install\` (or \`bun install\`) manually, then retry.`,
2419
- "install_failed",
2420
- { action: { cwd: appDir, argv: [detectPackageManager(appDir), "install"] } }
2421
- );
2422
- }
2423
- const tail = existsSync10(logPath) ? ` Tail progress: ${tailHint(logPath)}.` : "";
2424
- throw new Refusal(
2425
- `Dependencies are still installing.${tail} Retry once it finishes.`,
2426
- "install_in_progress"
2427
- );
2706
+ if (existsSync12(startedPath) && !existsSync12(donePath) && installWorkerAlive(sentinelDir) && sentinelAgeMs(startedPath) < 30 * 60 * 1e3) {
2707
+ const tail = existsSync12(logPath) ? ` Tail progress: ${tailHint(logPath)}.` : "";
2708
+ throw new Refusal(
2709
+ `Dependencies are still installing.${tail} Retry once it finishes (or remove ${sentinelDir}/install.started if no install is actually running).`,
2710
+ "install_in_progress"
2711
+ );
2712
+ }
2713
+ healMissingInstall(appDir);
2714
+ }
2715
+ function healMissingInstall(appDir) {
2716
+ const manager = detectPackageManager(appDir);
2717
+ const sentinelDir = join9(appDir, ".deepspace");
2718
+ const logPath = join9(sentinelDir, "install.log");
2719
+ const errPath = join9(sentinelDir, "install.err");
2720
+ const previousFailure = existsSync12(errPath) ? readFileSync9(errPath, "utf-8").trimEnd() : null;
2721
+ process.stderr.write(
2722
+ `Installing dependencies (${manager} install) \u2014 first use installs them. ${previousFailure ? `Retrying after: ${previousFailure}. ` : ""}Watch: ${tailHint(logPath)}
2723
+ `
2724
+ );
2725
+ if (process.platform === "win32") {
2726
+ for (const ext of [".cmd", ".bat", ".exe", ".com", ".ps1"]) {
2727
+ if (existsSync12(join9(appDir, `${manager}${ext}`))) {
2728
+ throw new Refusal(
2729
+ `Refusing to install: ${appDir} contains ${manager}${ext}, which Windows would execute instead of the real ${manager}. Remove it, or run the install yourself from a shell you trust.`,
2730
+ "install_failed"
2731
+ );
2732
+ }
2733
+ }
2734
+ }
2735
+ mkdirSync6(sentinelDir, { recursive: true });
2736
+ ensureSentinelsIgnored(appDir);
2737
+ rmSync4(errPath, { force: true });
2738
+ rmSync4(join9(sentinelDir, "install.done"), { force: true });
2739
+ writeFileSync5(join9(sentinelDir, "install.started"), (/* @__PURE__ */ new Date()).toISOString());
2740
+ writeFileSync5(join9(sentinelDir, "install.pid"), String(process.pid));
2741
+ const logFd = openSync(logPath, "w");
2742
+ let result;
2743
+ try {
2744
+ result = spawnSync2(manager, ["install"], {
2745
+ cwd: appDir,
2746
+ stdio: ["ignore", logFd, logFd],
2747
+ // Bounded (repo rule: no task over 5 minutes): a hung registry must
2748
+ // surface as a fast failure, never a stall. The log names the cure.
2749
+ timeout: 5 * 60 * 1e3
2750
+ });
2751
+ } finally {
2752
+ closeSync(logFd);
2753
+ }
2754
+ if (result.status === 0 && !result.error && resolvesDeepspace(appDir)) {
2755
+ writeFileSync5(join9(sentinelDir, "install.done"), (/* @__PURE__ */ new Date()).toISOString());
2756
+ return;
2757
+ }
2758
+ const detail = result.error ? result.error.code === "ETIMEDOUT" ? `${manager} install timed out after 5 minutes` : result.error.message : result.status === 0 ? `${manager} install completed but the \`deepspace\` package still does not resolve \u2014 is this directory the app (its package.json must depend on deepspace)?` : result.status === null ? `${manager} install was killed (${result.signal ?? "signal"})` : `${manager} install exited ${result.status}`;
2759
+ writeFileSync5(errPath, detail);
2760
+ let logTail = "";
2761
+ try {
2762
+ const log28 = readFileSync9(logPath, "utf-8");
2763
+ const tail = log28.slice(-1500).trimEnd();
2764
+ if (tail) logTail = `
2765
+ --- install log (tail) ---
2766
+ ${tail}`;
2767
+ } catch {
2428
2768
  }
2429
2769
  throw new Refusal(
2430
- "Dependencies not installed. Run `npm install` (or `bun install`) first.",
2431
- "deps_missing",
2432
- {
2433
- action: { cwd: appDir, argv: [detectPackageManager(appDir), "install"] }
2434
- }
2770
+ `Dependency install failed: ${detail}. Full log: ${logPath}. It will be retried on the next command, or run \`${manager} install\` yourself.${logTail}`,
2771
+ "install_failed",
2772
+ { action: { cwd: appDir, argv: [manager, "install"] } }
2435
2773
  );
2436
2774
  }
2775
+ function sentinelAgeMs(path) {
2776
+ try {
2777
+ return Date.now() - statSync2(path).mtimeMs;
2778
+ } catch {
2779
+ return Number.POSITIVE_INFINITY;
2780
+ }
2781
+ }
2782
+ function ensureSentinelsIgnored(appDir) {
2783
+ try {
2784
+ const check = spawnSync2("git", ["-C", appDir, "check-ignore", "-q", ".deepspace"], {
2785
+ stdio: "ignore"
2786
+ });
2787
+ if (check.status === 0 || check.error) return;
2788
+ const excludePath = spawnSync2("git", ["-C", appDir, "rev-parse", "--git-path", "info/exclude"], {
2789
+ encoding: "utf-8"
2790
+ });
2791
+ if (excludePath.status !== 0) return;
2792
+ const target2 = resolve7(appDir, excludePath.stdout.trim());
2793
+ mkdirSync6(dirname6(target2), { recursive: true });
2794
+ appendFileSync2(target2, "\n# deepspace CLI state (added by first-use install)\n.deepspace/\n");
2795
+ } catch {
2796
+ }
2797
+ }
2437
2798
  function resolvesDeepspace(fromDir) {
2438
2799
  let dir = canonicalPath2(fromDir);
2439
2800
  let checkoutRoot = null;
@@ -2442,7 +2803,7 @@ function resolvesDeepspace(fromDir) {
2442
2803
  } catch {
2443
2804
  }
2444
2805
  for (; ; ) {
2445
- if (existsSync10(join8(dir, "node_modules", "deepspace", "package.json"))) return true;
2806
+ if (existsSync12(join9(dir, "node_modules", "deepspace", "package.json"))) return true;
2446
2807
  if (dir === checkoutRoot) return false;
2447
2808
  const parent = dirname6(dir);
2448
2809
  if (parent === dir) return false;
@@ -2453,13 +2814,15 @@ function canonicalPath2(path) {
2453
2814
  try {
2454
2815
  return realpathSync3(path);
2455
2816
  } catch {
2456
- return resolve6(path);
2817
+ return resolve7(path);
2457
2818
  }
2458
2819
  }
2459
2820
  function installWorkerAlive(sentinelDir) {
2460
- const pidPath = join8(sentinelDir, "install.pid");
2461
- if (!existsSync10(pidPath)) return false;
2462
- return processAlive(Number(readFileSync9(pidPath, "utf-8").trim()));
2821
+ const pidPath = join9(sentinelDir, "install.pid");
2822
+ if (!existsSync12(pidPath)) return false;
2823
+ const pid = Number(readFileSync9(pidPath, "utf-8").trim());
2824
+ if (pid === process.pid) return false;
2825
+ return processAlive(pid);
2463
2826
  }
2464
2827
  function processAlive(pid) {
2465
2828
  if (!Number.isSafeInteger(pid) || pid <= 0) return false;
@@ -2473,9 +2836,9 @@ function processAlive(pid) {
2473
2836
 
2474
2837
  // src/cli/lib/preflight.ts
2475
2838
  import { execFileSync } from "child_process";
2476
- import { existsSync as existsSync11 } from "fs";
2839
+ import { existsSync as existsSync13 } from "fs";
2477
2840
  import * as nodeModule from "module";
2478
- import { resolve as resolve7 } from "path";
2841
+ import { resolve as resolve8 } from "path";
2479
2842
  function preflightNodeVersion(command, hasRegisterHooks = typeof nodeModule.registerHooks === "function", version = process.versions.node) {
2480
2843
  const [major, minor] = version.split(".").map(Number);
2481
2844
  const supportedLine = major === 22 && minor >= 15 || major === 24 || major === 26;
@@ -2488,7 +2851,7 @@ function preflightNodeVersion(command, hasRegisterHooks = typeof nodeModule.regi
2488
2851
  var DLL_NOT_FOUND = -1073741515;
2489
2852
  function preflightWindowsWorkerd(appDir) {
2490
2853
  if (process.platform !== "win32") return;
2491
- const workerdExe = resolve7(
2854
+ const workerdExe = resolve8(
2492
2855
  appDir,
2493
2856
  "node_modules",
2494
2857
  "@cloudflare",
@@ -2496,7 +2859,7 @@ function preflightWindowsWorkerd(appDir) {
2496
2859
  "bin",
2497
2860
  "workerd.exe"
2498
2861
  );
2499
- if (!existsSync11(workerdExe)) return;
2862
+ if (!existsSync13(workerdExe)) return;
2500
2863
  try {
2501
2864
  execFileSync(workerdExe, ["--help"], { stdio: "pipe", timeout: 1e4 });
2502
2865
  return;
@@ -2511,8 +2874,8 @@ function preflightWindowsWorkerd(appDir) {
2511
2874
  }
2512
2875
 
2513
2876
  // src/cli/lib/macos-junk.ts
2514
- import { readdirSync as readdirSync2, rmSync as rmSync3 } from "fs";
2515
- import { join as join9 } from "path";
2877
+ import { readdirSync as readdirSync2, rmSync as rmSync5 } from "fs";
2878
+ import { join as join10 } from "path";
2516
2879
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".wrangler", ".vite"]);
2517
2880
  function removeMacosJunk(dir) {
2518
2881
  let removed = 0;
@@ -2523,14 +2886,14 @@ function removeMacosJunk(dir) {
2523
2886
  return removed;
2524
2887
  }
2525
2888
  for (const entry of entries) {
2526
- const path = join9(dir, entry.name);
2889
+ const path = join10(dir, entry.name);
2527
2890
  if (entry.isDirectory()) {
2528
2891
  if (!SKIP_DIRS.has(entry.name)) removed += removeMacosJunk(path);
2529
2892
  continue;
2530
2893
  }
2531
2894
  if (entry.name === ".DS_Store" || entry.name.startsWith("._")) {
2532
2895
  try {
2533
- rmSync3(path, { force: true });
2896
+ rmSync5(path, { force: true });
2534
2897
  removed++;
2535
2898
  } catch {
2536
2899
  }
@@ -3105,8 +3468,8 @@ async function refreshSecretsCache(deployUrl, token, appId, wranglerEnv, selecte
3105
3468
  }
3106
3469
 
3107
3470
  // src/cli/lib/schema-lint.ts
3108
- import { existsSync as existsSync12 } from "fs";
3109
- import { join as join10 } from "path";
3471
+ import { existsSync as existsSync14 } from "fs";
3472
+ import { join as join11 } from "path";
3110
3473
  import { pathToFileURL } from "url";
3111
3474
 
3112
3475
  // src/server/schemas/registry.ts
@@ -3139,7 +3502,7 @@ function lintSchema(schema) {
3139
3502
  const offendingRoles = Object.entries(schema.permissions).filter(([, perms]) => perms.read === true).map(([role]) => role);
3140
3503
  if (offendingRoles.length > 0) {
3141
3504
  const usesAware = Object.values(schema.permissions).some(
3142
- (p36) => typeof p36.read === "string" && VISIBILITY_AWARE.includes(p36.read)
3505
+ (p35) => typeof p35.read === "string" && VISIBILITY_AWARE.includes(p35.read)
3143
3506
  );
3144
3507
  if (!usesAware) {
3145
3508
  warnings.push(
@@ -3175,7 +3538,7 @@ function lintSchemaSet(schemas) {
3175
3538
  const warnings = [];
3176
3539
  if (!schemas.some((schema) => schema.name === "team_members")) {
3177
3540
  for (const schema of schemas) {
3178
- const teamRoles = Object.entries(schema.permissions).filter(([, p36]) => [p36.read, p36.update, p36.delete].includes("team")).map(([role]) => role);
3541
+ const teamRoles = Object.entries(schema.permissions).filter(([, p35]) => [p35.read, p35.update, p35.delete].includes("team")).map(([role]) => role);
3179
3542
  if (teamRoles.length > 0) {
3180
3543
  warnings.push(
3181
3544
  `[${schema.name}] role(s) ${teamRoles.join(", ")} use the 'team' permission level, but no 'team_members' collection is registered. 'team' resolves membership from team_members (teamId, userId, status), so without it those roles are denied everything \u2014 silently. Register a team_members schema or use another level.`
@@ -3188,12 +3551,12 @@ function lintSchemaSet(schemas) {
3188
3551
 
3189
3552
  // src/cli/lib/schema-lint.ts
3190
3553
  async function lintProjectSchemas(appDir) {
3191
- const schemasPath = join10(appDir, "src", "schemas.ts");
3192
- if (!existsSync12(schemasPath)) return null;
3554
+ const schemasPath = join11(appDir, "src", "schemas.ts");
3555
+ if (!existsSync14(schemasPath)) return null;
3193
3556
  let schemas;
3194
3557
  try {
3195
3558
  const esbuild = await import("esbuild");
3196
- const outFile = join10(appDir, ".wrangler", "deploy", "schemas.lint.bundle.mjs");
3559
+ const outFile = join11(appDir, ".wrangler", "deploy", "schemas.lint.bundle.mjs");
3197
3560
  await esbuild.build({
3198
3561
  entryPoints: [schemasPath],
3199
3562
  bundle: true,
@@ -3267,6 +3630,28 @@ async function waitForPortListening(port, host, timeoutMs, signal) {
3267
3630
  }
3268
3631
  return false;
3269
3632
  }
3633
+ async function assertReusableDevServer(port, waitMs = 15e3) {
3634
+ if (!await isPortListening(port, "127.0.0.1")) return;
3635
+ if (await isHttpResponding(port)) return;
3636
+ const deadline = Date.now() + waitMs;
3637
+ while (Date.now() < deadline) {
3638
+ if (!await isPortListening(port, "127.0.0.1")) return;
3639
+ await new Promise((resolve23) => setTimeout(resolve23, 500));
3640
+ }
3641
+ throw new Refusal(
3642
+ `Port ${port} is held by a server that no longer answers HTTP \u2014 likely a previous run's dev server still shutting down. Waited ${Math.round(waitMs / 1e3)}s for it to exit. Stop it (\`deepspace dev kill\`) or pass --port <other>, then re-run.`,
3643
+ "port_in_use",
3644
+ { extra: { port } }
3645
+ );
3646
+ }
3647
+ async function isHttpResponding(port, timeoutMs = 2e3) {
3648
+ try {
3649
+ await fetch(`http://127.0.0.1:${port}/`, { signal: AbortSignal.timeout(timeoutMs) });
3650
+ return true;
3651
+ } catch {
3652
+ return false;
3653
+ }
3654
+ }
3270
3655
  function resolvePort(arg) {
3271
3656
  const raw = arg ?? process.env.DEEPSPACE_PORT;
3272
3657
  if (!raw) return DEFAULT_PORT;
@@ -3280,7 +3665,7 @@ function resolvePort(arg) {
3280
3665
  // src/cli/commands/dev.ts
3281
3666
  var DEFAULT_DEV_HOST = "0.0.0.0";
3282
3667
  var READINESS_TIMEOUT_MS = 12e4;
3283
- var DEPLOY_URL = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
3668
+ var DEPLOY_URL2 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
3284
3669
  function noAppDirRefusal(start) {
3285
3670
  const children = findChildApps(start);
3286
3671
  const lines = [`No wrangler.toml found at or above ${start}.`];
@@ -3330,7 +3715,7 @@ var dev_default = defineDeepspaceCommand({
3330
3715
  preflightNodeVersion("dev start");
3331
3716
  const wranglerEnv = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
3332
3717
  const host = typeof args.host === "string" && args.host.trim() ? args.host.trim() : DEFAULT_DEV_HOST;
3333
- const start = resolve8(args.dir ?? ".");
3718
+ const start = resolve9(args.dir ?? ".");
3334
3719
  const appDir = findAppDir(start);
3335
3720
  if (!appDir) {
3336
3721
  throw noAppDirRefusal(start);
@@ -3360,18 +3745,13 @@ var dev_default = defineDeepspaceCommand({
3360
3745
  );
3361
3746
  } else if (!args.json) {
3362
3747
  console.warn(
3363
- `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.`
3748
+ `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.`
3364
3749
  );
3365
3750
  }
3366
3751
  }
3367
3752
  writeLaunchConfigIfMissing(appDir, detectAppName(appDir) ?? basename2(appDir), port, {
3368
3753
  updatePort: explicitPort
3369
3754
  });
3370
- ensureInstallReady(appDir);
3371
- const lintFindings = await lintProjectSchemas(appDir);
3372
- if (lintFindings && lintFindings.length > 0 && !args.json) {
3373
- for (const line of formatSchemaLintFindings(lintFindings)) console.warn(line);
3374
- }
3375
3755
  let token;
3376
3756
  try {
3377
3757
  token = await ensureToken();
@@ -3394,11 +3774,24 @@ var dev_default = defineDeepspaceCommand({
3394
3774
  if (wranglerEnv) say(`Wrangler env: ${wranglerEnv}`);
3395
3775
  say(`Port: ${port}`);
3396
3776
  if (host !== DEFAULT_DEV_HOST) say(`Host: ${host}`);
3777
+ ensureInstallReady(appDir);
3778
+ const lintFindings = await lintProjectSchemas(appDir);
3779
+ if (lintFindings && lintFindings.length > 0 && !args.json) {
3780
+ for (const line of formatSchemaLintFindings(lintFindings)) console.warn(line);
3781
+ }
3397
3782
  let generatedSecretsCache;
3398
- const appIdForSecrets = readAppId(appDir, wranglerEnv);
3783
+ let appIdForSecrets = null;
3784
+ try {
3785
+ appIdForSecrets = (await ensureAppRegistered(appDir, token, wranglerEnv))?.appId ?? null;
3786
+ } catch (registrationError) {
3787
+ process.stderr.write(
3788
+ `warning: could not register the app (${registrationError instanceof Error ? registrationError.message.split("\n")[0] : String(registrationError)}) \u2014 continuing without app secrets.
3789
+ `
3790
+ );
3791
+ }
3399
3792
  if (appIdForSecrets) {
3400
3793
  try {
3401
- const refreshed = await refreshSecretsCache(DEPLOY_URL, token, appIdForSecrets, wranglerEnv);
3794
+ const refreshed = await refreshSecretsCache(DEPLOY_URL2, token, appIdForSecrets, wranglerEnv);
3402
3795
  generatedSecretsCache = refreshed.rendered;
3403
3796
  if (refreshed.summary) say(refreshed.summary);
3404
3797
  } catch (err) {
@@ -3481,7 +3874,7 @@ Free it with \`${killCmd}\`, or start on another port: \`deepspace dev start --p
3481
3874
  });
3482
3875
 
3483
3876
  // src/cli/commands/kill.ts
3484
- import { spawnSync as spawnSync2 } from "child_process";
3877
+ import { spawnSync as spawnSync3 } from "child_process";
3485
3878
  import { readFileSync as readFileSync10, readdirSync as readdirSync3, readlinkSync } from "fs";
3486
3879
  import { setTimeout as wait } from "timers/promises";
3487
3880
  var SIGTERM_GRACE_MS = 1500;
@@ -3616,7 +4009,7 @@ function listenerPids(port) {
3616
4009
  }
3617
4010
  function runLsof(port) {
3618
4011
  try {
3619
- const r = spawnSync2("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"], {
4012
+ const r = spawnSync3("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"], {
3620
4013
  encoding: "utf-8"
3621
4014
  });
3622
4015
  if (r.error) return { pids: [], ran: false };
@@ -3701,7 +4094,7 @@ function sweepByName(names) {
3701
4094
  }
3702
4095
  function runPgrep(pgrepArgs) {
3703
4096
  try {
3704
- const r = spawnSync2("pgrep", pgrepArgs, { encoding: "utf-8" });
4097
+ const r = spawnSync3("pgrep", pgrepArgs, { encoding: "utf-8" });
3705
4098
  if (r.error) return { pids: [], ran: false };
3706
4099
  return {
3707
4100
  pids: r.status === 0 ? parsePidLines(r.stdout) : [],
@@ -3713,7 +4106,7 @@ function runPgrep(pgrepArgs) {
3713
4106
  }
3714
4107
  function run(cmd) {
3715
4108
  try {
3716
- const r = spawnSync2(cmd[0], cmd.slice(1), { encoding: "utf-8" });
4109
+ const r = spawnSync3(cmd[0], cmd.slice(1), { encoding: "utf-8" });
3717
4110
  if (r.status !== 0) return [];
3718
4111
  return parsePidLines(r.stdout);
3719
4112
  } catch {
@@ -3734,7 +4127,7 @@ function readPowershellPids(stdout) {
3734
4127
  function runPowershellChecked(script) {
3735
4128
  const exe = pwshOrPowershell();
3736
4129
  try {
3737
- const r = spawnSync2(exe, ["-NoProfile", "-NonInteractive", "-Command", withSentinel(script)], {
4130
+ const r = spawnSync3(exe, ["-NoProfile", "-NonInteractive", "-Command", withSentinel(script)], {
3738
4131
  encoding: "utf-8"
3739
4132
  });
3740
4133
  if (r.error) return { pids: [], ran: false };
@@ -3744,7 +4137,7 @@ function runPowershellChecked(script) {
3744
4137
  }
3745
4138
  }
3746
4139
  function pwshOrPowershell() {
3747
- const probe = spawnSync2("pwsh", ["-NoProfile", "-Command", "$PSVersionTable.PSVersion.Major"], {
4140
+ const probe = spawnSync3("pwsh", ["-NoProfile", "-Command", "$PSVersionTable.PSVersion.Major"], {
3748
4141
  encoding: "utf-8"
3749
4142
  });
3750
4143
  if (probe.status === 0) return "pwsh";
@@ -3772,12 +4165,12 @@ function isAlive(pid) {
3772
4165
 
3773
4166
  // src/cli/commands/test.ts
3774
4167
  import { readdirSync as readdirSync4 } from "fs";
3775
- import { join as join13, relative, resolve as resolve9, sep as sep2 } from "path";
3776
- import { sync as spawnSync4 } from "cross-spawn";
4168
+ import { join as join14, relative as relative2, resolve as resolve10, sep as sep2 } from "path";
4169
+ import { sync as spawnSync5 } from "cross-spawn";
3777
4170
 
3778
4171
  // src/cli/lib/playwright.ts
3779
4172
  import { execSync } from "child_process";
3780
- import { sync as spawnSync3 } from "cross-spawn";
4173
+ import { sync as spawnSync4 } from "cross-spawn";
3781
4174
  var childStdoutToStderr = false;
3782
4175
  function routeChildStdoutToStderr(enabled) {
3783
4176
  childStdoutToStderr = enabled;
@@ -3800,7 +4193,7 @@ function ensurePlaywright(appDir) {
3800
4193
  "Installing Chromium and its system libraries \u2014 this runs `apt-get` as root and can take several minutes on a cold machine."
3801
4194
  );
3802
4195
  }
3803
- const result = spawnSync3("npx", ["playwright", ...args], {
4196
+ const result = spawnSync4("npx", ["playwright", ...args], {
3804
4197
  cwd: appDir,
3805
4198
  stdio: childStdio()
3806
4199
  });
@@ -3816,10 +4209,10 @@ function ensurePlaywright(appDir) {
3816
4209
  import { readFileSync as readFileSync12 } from "fs";
3817
4210
 
3818
4211
  // src/testing/accounts.ts
3819
- import { existsSync as existsSync13, mkdirSync as mkdirSync5, readFileSync as readFileSync11 } from "fs";
4212
+ import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync11 } from "fs";
3820
4213
  import { homedir as homedir2 } from "os";
3821
- import { join as join12 } from "path";
3822
- var TEST_ACCOUNTS_PATH = join12(homedir2(), ".deepspace", "test-accounts.json");
4214
+ import { join as join13 } from "path";
4215
+ var TEST_ACCOUNTS_PATH = join13(homedir2(), ".deepspace", "test-accounts.json");
3823
4216
  var PRODUCTION_AUTH_ORIGIN = "https://auth.deep.space";
3824
4217
  var STAGING_AUTH_ORIGIN = "https://auth.deepspacesites.com";
3825
4218
  function currentTestAccountScope(env = process.env) {
@@ -3884,7 +4277,7 @@ function removeTestAccounts(ids, emails = [], scope = currentTestAccountScope())
3884
4277
  });
3885
4278
  }
3886
4279
  function readTestAccountStore() {
3887
- if (!existsSync13(TEST_ACCOUNTS_PATH)) return { version: 2, scopes: {} };
4280
+ if (!existsSync15(TEST_ACCOUNTS_PATH)) return { version: 2, scopes: {} };
3888
4281
  try {
3889
4282
  const raw = JSON.parse(readFileSync11(TEST_ACCOUNTS_PATH, "utf-8"));
3890
4283
  if (Array.isArray(raw)) return { version: 2, scopes: {} };
@@ -3909,7 +4302,7 @@ function validTestAccounts(value) {
3909
4302
  );
3910
4303
  }
3911
4304
  function saveTestAccountStore(store) {
3912
- mkdirSync5(join12(homedir2(), ".deepspace"), { recursive: true });
4305
+ mkdirSync7(join13(homedir2(), ".deepspace"), { recursive: true });
3913
4306
  writeSecretFileSync(TEST_ACCOUNTS_PATH, JSON.stringify(store, null, 2));
3914
4307
  }
3915
4308
 
@@ -3987,7 +4380,7 @@ async function syncTestAccountStore() {
3987
4380
  }
3988
4381
 
3989
4382
  // src/cli/commands/test.ts
3990
- var DEPLOY_URL2 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
4383
+ var DEPLOY_URL3 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
3991
4384
  var PLAYWRIGHT_OUTPUT_DIR = ".deepspace/test-results";
3992
4385
  var DEFAULT_SUITE_SPECS = ["tests/smoke.spec.ts", "tests/api.spec.ts"];
3993
4386
  function specsSkippedByDefaultSuite(appDir) {
@@ -4001,14 +4394,14 @@ function specsSkippedByDefaultSuite(appDir) {
4001
4394
  }
4002
4395
  for (const entry of entries) {
4003
4396
  if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
4004
- const full = join13(dir, entry.name);
4397
+ const full = join14(dir, entry.name);
4005
4398
  if (entry.isDirectory()) walk(full);
4006
4399
  else if (entry.name.endsWith(".spec.ts")) {
4007
- found.push(relative(appDir, full).split(sep2).join("/"));
4400
+ found.push(relative2(appDir, full).split(sep2).join("/"));
4008
4401
  }
4009
4402
  }
4010
4403
  };
4011
- walk(join13(appDir, "tests"));
4404
+ walk(join14(appDir, "tests"));
4012
4405
  return found.filter((spec) => !DEFAULT_SUITE_SPECS.includes(spec)).sort();
4013
4406
  }
4014
4407
  function playwrightTestArgs(testFiles, flags = {}) {
@@ -4077,7 +4470,7 @@ var test_default = defineDeepspaceCommand({
4077
4470
  project: typeof args.project === "string" && args.project ? args.project : void 0,
4078
4471
  headed: Boolean(args.headed)
4079
4472
  };
4080
- const start = resolve9(".");
4473
+ const start = resolve10(".");
4081
4474
  const appDir = findAppDir(start);
4082
4475
  if (!appDir) {
4083
4476
  throw noAppDirRefusal(start);
@@ -4085,7 +4478,6 @@ var test_default = defineDeepspaceCommand({
4085
4478
  const configuredPort = Boolean(args.port) || Boolean(process.env.DEEPSPACE_PORT);
4086
4479
  const worktreePort = configuredPort ? null : resolveWorktreePort(appDir);
4087
4480
  const port = worktreePort ?? resolvePort(args.port);
4088
- ensureInstallReady(appDir);
4089
4481
  let token;
4090
4482
  let ownerId;
4091
4483
  try {
@@ -4099,11 +4491,20 @@ var test_default = defineDeepspaceCommand({
4099
4491
  { action: cliAction("deepspace", "auth", "login") }
4100
4492
  );
4101
4493
  }
4494
+ ensureInstallReady(appDir);
4102
4495
  let generatedSecretsCache;
4103
- const appIdForSecrets = readAppId(appDir, wranglerEnv);
4496
+ let appIdForSecrets = null;
4497
+ try {
4498
+ appIdForSecrets = (await ensureAppRegistered(appDir, token, wranglerEnv))?.appId ?? null;
4499
+ } catch (registrationError) {
4500
+ process.stderr.write(
4501
+ `warning: could not register the app (${registrationError instanceof Error ? registrationError.message.split("\n")[0] : String(registrationError)}) \u2014 continuing without app secrets.
4502
+ `
4503
+ );
4504
+ }
4104
4505
  if (appIdForSecrets) {
4105
4506
  try {
4106
- const refreshed = await refreshSecretsCache(DEPLOY_URL2, token, appIdForSecrets, wranglerEnv);
4507
+ const refreshed = await refreshSecretsCache(DEPLOY_URL3, token, appIdForSecrets, wranglerEnv);
4107
4508
  generatedSecretsCache = refreshed.rendered;
4108
4509
  if (refreshed.summary) say(refreshed.summary);
4109
4510
  } catch (err) {
@@ -4129,6 +4530,7 @@ var test_default = defineDeepspaceCommand({
4129
4530
  if (suite !== "unit") {
4130
4531
  preflightWindowsWorkerd(appDir);
4131
4532
  ensurePlaywright(appDir);
4533
+ await assertReusableDevServer(port);
4132
4534
  }
4133
4535
  let exitCode = 0;
4134
4536
  let skippedSpecs = [];
@@ -4189,7 +4591,7 @@ function runSuite(appDir, argv, wranglerEnv, extraEnv = {}) {
4189
4591
  return 1;
4190
4592
  }
4191
4593
  try {
4192
- const result = spawnSync4("npx", argv, {
4594
+ const result = spawnSync5("npx", argv, {
4193
4595
  cwd: appDir,
4194
4596
  // Under --json the child's stdout is routed to our stderr: the live
4195
4597
  // suite output stays visible and stdout stays a single JSON line.
@@ -4212,8 +4614,8 @@ function runVitest(appDir, wranglerEnv) {
4212
4614
  }
4213
4615
 
4214
4616
  // src/cli/commands/screenshot.ts
4215
- import { sync as spawnSync5 } from "cross-spawn";
4216
- import { resolve as resolve10 } from "path";
4617
+ import { sync as spawnSync6 } from "cross-spawn";
4618
+ import { resolve as resolve11 } from "path";
4217
4619
  function normalizeViewportSize(value) {
4218
4620
  if (value === void 0) return void 0;
4219
4621
  const match = /^(\d+)\s*[x,]\s*(\d+)$/i.exec(String(value).trim());
@@ -4261,7 +4663,7 @@ var screenshot_default = defineDeepspaceCommand({
4261
4663
  },
4262
4664
  async run({ args }) {
4263
4665
  const viewport = normalizeViewportSize(args.viewport);
4264
- const appDir = resolve10(".");
4666
+ const appDir = resolve11(".");
4265
4667
  ensurePlaywright(appDir);
4266
4668
  const url = String(args.url);
4267
4669
  const output = String(args.output);
@@ -4271,7 +4673,7 @@ var screenshot_default = defineDeepspaceCommand({
4271
4673
  if (args["wait-for-timeout"] !== void 0) {
4272
4674
  playwrightArgs.push("--wait-for-timeout", String(args["wait-for-timeout"]));
4273
4675
  }
4274
- const result = spawnSync5("npx", playwrightArgs, {
4676
+ const result = spawnSync6("npx", playwrightArgs, {
4275
4677
  cwd: appDir,
4276
4678
  stdio: "inherit"
4277
4679
  });
@@ -4303,8 +4705,13 @@ var create = defineDeepspaceCommand({
4303
4705
  },
4304
4706
  password: {
4305
4707
  type: "string",
4306
- description: "Password (min 8 characters)",
4307
- required: true
4708
+ description: "Password (min 8 characters). Prefer --password-stdin \u2014 argv is visible in `ps` and shell history",
4709
+ required: false
4710
+ },
4711
+ "password-stdin": {
4712
+ type: "boolean",
4713
+ description: "Read the password from stdin (one trailing newline stripped)",
4714
+ default: false
4308
4715
  },
4309
4716
  name: {
4310
4717
  type: "string",
@@ -4320,7 +4727,26 @@ var create = defineDeepspaceCommand({
4320
4727
  async run({ args }) {
4321
4728
  await ensureToken();
4322
4729
  const email = args.email;
4323
- const password = args.password;
4730
+ if (args["password-stdin"] && args.password !== void 0) {
4731
+ throw new Refusal("Pass either --password or --password-stdin, not both.", "invalid_flags");
4732
+ }
4733
+ let password;
4734
+ if (args["password-stdin"]) {
4735
+ if (process.stdin.isTTY) {
4736
+ throw new Refusal(
4737
+ '--password-stdin expects the password piped on stdin, e.g. `printf %s "$PW" | deepspace test accounts create --email bot@deepspace.test --password-stdin`.',
4738
+ "password_stdin_needs_pipe"
4739
+ );
4740
+ }
4741
+ password = (await readStreamText(process.stdin, MAX_STDIN_BYTES)).replace(/\r?\n$/, "");
4742
+ } else if (typeof args.password === "string") {
4743
+ password = args.password;
4744
+ } else {
4745
+ throw new Refusal(
4746
+ "A password is required: pipe it with --password-stdin (preferred), or pass --password.",
4747
+ "missing_password"
4748
+ );
4749
+ }
4324
4750
  const name = args.name ?? email.split("@")[0];
4325
4751
  const label = args.label;
4326
4752
  let account;
@@ -4688,7 +5114,7 @@ var test_accounts_default = defineCommand2({
4688
5114
  // src/cli/commands/deploy.ts
4689
5115
  import * as p12 from "@clack/prompts";
4690
5116
  import { defineCommand as defineCommand3 } from "citty";
4691
- import { join as join20, resolve as resolve15 } from "path";
5117
+ import { join as join21, resolve as resolve15 } from "path";
4692
5118
 
4693
5119
  // src/cli/lib/edge-propagation.ts
4694
5120
  import { request as httpRequest } from "http";
@@ -4852,11 +5278,11 @@ function describeFilesFailure(status3, body) {
4852
5278
  import * as p6 from "@clack/prompts";
4853
5279
  import { execSync as execSync2 } from "child_process";
4854
5280
  import { createHash as createHash2 } from "crypto";
4855
- import { existsSync as existsSync17, readFileSync as readFileSync15, readdirSync as readdirSync6 } from "fs";
4856
- import { dirname as dirname7, isAbsolute as isAbsolute3, join as join17, relative as relative2, resolve as resolve13, sep as sep3 } from "path";
5281
+ import { existsSync as existsSync19, readFileSync as readFileSync15, readdirSync as readdirSync6 } from "fs";
5282
+ import { dirname as dirname7, isAbsolute as isAbsolute4, join as join18, relative as relative3, resolve as resolve14, sep as sep3 } from "path";
4857
5283
 
4858
5284
  // src/build/app-id.ts
4859
- import { resolve as resolve11 } from "path";
5285
+ import { resolve as resolve12 } from "path";
4860
5286
  var APP_ID_ADOPTION_EDITS = {
4861
5287
  "vite.config.ts": `import { deepspaceBuild } from 'deepspace/build' and add
4862
5288
  deepspaceBuild({ appDir: fileURLToPath(new URL('.', import.meta.url)) }) to plugins`,
@@ -4870,11 +5296,11 @@ var APP_ID_ADOPTION_STEPS = `Point the client at the build-injected id \u2014 th
4870
5296
  `).join("") + APP_ID_ADOPTION_TOGETHER;
4871
5297
 
4872
5298
  // src/build/plugin.ts
4873
- import { existsSync as existsSync14, lstatSync, readdirSync as readdirSync5, unlinkSync as unlinkSync2 } from "fs";
4874
- import { join as join14, resolve as resolve12 } from "path";
5299
+ import { existsSync as existsSync16, lstatSync, readdirSync as readdirSync5, unlinkSync as unlinkSync2 } from "fs";
5300
+ import { join as join15, resolve as resolve13 } from "path";
4875
5301
  function removeBuildDevVars(workerDir) {
4876
- const path = join14(workerDir, ".dev.vars");
4877
- if (!existsSync14(path)) return false;
5302
+ const path = join15(workerDir, ".dev.vars");
5303
+ if (!existsSync16(path)) return false;
4878
5304
  if (!lstatSync(path).isFile()) {
4879
5305
  throw new Error(`Refusing unsafe build secret path: ${path}`);
4880
5306
  }
@@ -4883,12 +5309,12 @@ function removeBuildDevVars(workerDir) {
4883
5309
  }
4884
5310
 
4885
5311
  // src/documentation/deploy.ts
4886
- import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
4887
- import { join as join15 } from "path";
5312
+ import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
5313
+ import { join as join16 } from "path";
4888
5314
  function readDocumentationDeployManifest(appDir, clientDir) {
4889
- if (!existsSync15(join15(appDir, "documentation.json"))) return void 0;
4890
- const manifestPath = join15(clientDir, "_documentation", "manifest.json");
4891
- if (!existsSync15(manifestPath)) {
5315
+ if (!existsSync17(join16(appDir, "documentation.json"))) return void 0;
5316
+ const manifestPath = join16(clientDir, "_documentation", "manifest.json");
5317
+ if (!existsSync17(manifestPath)) {
4892
5318
  throw new Error(
4893
5319
  "documentation.json exists but the build produced no documentation manifest. Ensure vite.config.ts includes deepSpaceDocumentation() from deepspace/documentation."
4894
5320
  );
@@ -4897,8 +5323,8 @@ function readDocumentationDeployManifest(appDir, clientDir) {
4897
5323
  }
4898
5324
 
4899
5325
  // src/cli/commands/update/app-migrations.ts
4900
- import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
4901
- import { join as join16 } from "path";
5326
+ import { existsSync as existsSync18, readFileSync as readFileSync14 } from "fs";
5327
+ import { join as join17 } from "path";
4902
5328
 
4903
5329
  // src/shared/protocol/app-migrations.ts
4904
5330
  var APP_MIGRATION_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/;
@@ -4972,8 +5398,8 @@ var APP_MIGRATION_DEFINITIONS = APP_MIGRATION_GUIDANCE.map(({ id, description })
4972
5398
  description
4973
5399
  }));
4974
5400
  function readAppliedAppMigrations(appDir) {
4975
- const path = join16(appDir, APP_MIGRATIONS_MANIFEST);
4976
- if (!existsSync16(path)) return [];
5401
+ const path = join17(appDir, APP_MIGRATIONS_MANIFEST);
5402
+ if (!existsSync18(path)) return [];
4977
5403
  let parsed;
4978
5404
  try {
4979
5405
  parsed = JSON.parse(readFileSync14(path, "utf8"));
@@ -5035,16 +5461,16 @@ async function buildDeployBundle(options) {
5035
5461
  preparedWranglerConfig?.cleanup();
5036
5462
  }
5037
5463
  spinner2.stop(`Built (${Math.round((Date.now() - buildStarted) / 1e3)}s)`);
5038
- const deployConfigPath = join17(appDir, ".wrangler", "deploy", "config.json");
5039
- if (!existsSync17(deployConfigPath)) {
5464
+ const deployConfigPath = join18(appDir, ".wrangler", "deploy", "config.json");
5465
+ if (!existsSync19(deployConfigPath)) {
5040
5466
  output.die(
5041
5467
  "Build output config not found at .wrangler/deploy/config.json",
5042
5468
  "build_output_missing"
5043
5469
  );
5044
5470
  }
5045
5471
  const deployConfig = JSON.parse(readFileSync15(deployConfigPath, "utf-8"));
5046
- const outputWranglerPath = resolve13(dirname7(deployConfigPath), deployConfig.configPath);
5047
- if (!existsSync17(outputWranglerPath)) {
5472
+ const outputWranglerPath = resolve14(dirname7(deployConfigPath), deployConfig.configPath);
5473
+ if (!existsSync19(outputWranglerPath)) {
5048
5474
  output.die(`Output wrangler.json not found at ${outputWranglerPath}`, "build_output_missing");
5049
5475
  }
5050
5476
  const outputConfig = JSON.parse(readFileSync15(outputWranglerPath, "utf-8"));
@@ -5061,12 +5487,12 @@ async function buildDeployBundle(options) {
5061
5487
  } catch (error) {
5062
5488
  output.die(errorMessage(error), "build_output_unsafe");
5063
5489
  }
5064
- const workerBundlePath = join17(workerDir, outputConfig.main);
5065
- const clientDir = outputConfig.assets?.directory ? resolve13(workerDir, outputConfig.assets.directory) : null;
5066
- if (!existsSync17(workerBundlePath)) {
5490
+ const workerBundlePath = join18(workerDir, outputConfig.main);
5491
+ const clientDir = outputConfig.assets?.directory ? resolve14(workerDir, outputConfig.assets.directory) : null;
5492
+ if (!existsSync19(workerBundlePath)) {
5067
5493
  output.die(`Worker bundle not found at ${workerBundlePath}`, "build_output_missing");
5068
5494
  }
5069
- if (!clientDir || !existsSync17(clientDir)) {
5495
+ if (!clientDir || !existsSync19(clientDir)) {
5070
5496
  return output.die(`Client assets not found at ${clientDir}`, "build_output_missing");
5071
5497
  }
5072
5498
  try {
@@ -5134,16 +5560,16 @@ async function buildDeployBundle(options) {
5134
5560
  }
5135
5561
  var WORKER_ES_MODULE = /\.(?:js|mjs)$/i;
5136
5562
  function collectWorkerBundle(workerDir, mainPath, clientDir) {
5137
- const root = resolve13(workerDir);
5138
- const mainAbsolute = resolve13(mainPath);
5139
- const excludedClient = clientDir ? resolve13(clientDir) : null;
5563
+ const root = resolve14(workerDir);
5564
+ const mainAbsolute = resolve14(mainPath);
5565
+ const excludedClient = clientDir ? resolve14(clientDir) : null;
5140
5566
  const main2 = moduleName(root, mainAbsolute);
5141
5567
  const modules = [];
5142
5568
  const walk = (directory) => {
5143
5569
  for (const entry of readdirSync6(directory, { withFileTypes: true }).sort(
5144
5570
  (a, b) => a.name.localeCompare(b.name)
5145
5571
  )) {
5146
- const fullPath = join17(directory, entry.name);
5572
+ const fullPath = join18(directory, entry.name);
5147
5573
  if (excludedClient && isWithin(excludedClient, fullPath)) continue;
5148
5574
  if (entry.isDirectory()) {
5149
5575
  walk(fullPath);
@@ -5159,15 +5585,15 @@ function collectWorkerBundle(workerDir, mainPath, clientDir) {
5159
5585
  return { main: main2, modules: [mainModule, ...modules] };
5160
5586
  }
5161
5587
  function moduleName(root, path) {
5162
- const name = relative2(root, path);
5163
- if (!name || isAbsolute3(name) || name === ".." || name.startsWith(`..${sep3}`)) {
5588
+ const name = relative3(root, path);
5589
+ if (!name || isAbsolute4(name) || name === ".." || name.startsWith(`..${sep3}`)) {
5164
5590
  throw new Error(`Worker module is outside the generated output directory: ${path}`);
5165
5591
  }
5166
5592
  return name.split(sep3).join("/");
5167
5593
  }
5168
5594
  function isWithin(root, path) {
5169
- const name = relative2(root, path);
5170
- return name === "" || !isAbsolute3(name) && name !== ".." && !name.startsWith(`..${sep3}`);
5595
+ const name = relative3(root, path);
5596
+ return name === "" || !isAbsolute4(name) && name !== ".." && !name.startsWith(`..${sep3}`);
5171
5597
  }
5172
5598
  function extractRunWorkerFirst(config) {
5173
5599
  const raw = Array.isArray(config.assets?.run_worker_first) ? config.assets.run_worker_first : [];
@@ -5229,7 +5655,7 @@ function collectAssets(dir) {
5229
5655
  const walk = (currentDir, prefix) => {
5230
5656
  for (const entry of readdirSync6(currentDir, { withFileTypes: true })) {
5231
5657
  if (isDeployAssetControlFile(entry.name)) continue;
5232
- const fullPath = join17(currentDir, entry.name);
5658
+ const fullPath = join18(currentDir, entry.name);
5233
5659
  const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
5234
5660
  if (entry.isDirectory()) {
5235
5661
  walk(fullPath, relativePath);
@@ -5253,8 +5679,8 @@ function readDeployAssetConfig(clientDir) {
5253
5679
  ["_headers", "_headers"],
5254
5680
  ["_redirects", "_redirects"]
5255
5681
  ]) {
5256
- const filePath = join17(clientDir, file);
5257
- if (!existsSync17(filePath)) continue;
5682
+ const filePath = join18(clientDir, file);
5683
+ if (!existsSync19(filePath)) continue;
5258
5684
  const content = readFileSync15(filePath, "utf8");
5259
5685
  if (content.trim()) config[key] = content;
5260
5686
  }
@@ -5269,8 +5695,8 @@ function errorMessage(error) {
5269
5695
 
5270
5696
  // src/cli/commands/deploy/commerce.ts
5271
5697
  import * as p7 from "@clack/prompts";
5272
- import { existsSync as existsSync18 } from "fs";
5273
- import { join as join18 } from "path";
5698
+ import { existsSync as existsSync20 } from "fs";
5699
+ import { join as join19 } from "path";
5274
5700
  import { pathToFileURL as pathToFileURL2 } from "url";
5275
5701
 
5276
5702
  // src/shared/api-error.ts
@@ -5322,10 +5748,10 @@ function errorMessage2(error) {
5322
5748
  return error instanceof Error ? error.message : String(error);
5323
5749
  }
5324
5750
  async function loadDeclaration(appDir, sourceFile, outputFile, exportName) {
5325
- const sourcePath = join18(appDir, "src", sourceFile);
5326
- if (!existsSync18(sourcePath)) return void 0;
5751
+ const sourcePath = join19(appDir, "src", sourceFile);
5752
+ if (!existsSync20(sourcePath)) return void 0;
5327
5753
  const esbuild = await import("esbuild");
5328
- const outputPath = join18(appDir, ".wrangler", "deploy", outputFile);
5754
+ const outputPath = join19(appDir, ".wrangler", "deploy", outputFile);
5329
5755
  await esbuild.build({
5330
5756
  entryPoints: [sourcePath],
5331
5757
  bundle: true,
@@ -5418,7 +5844,7 @@ Stripe will keep charging them until you cancel. Cancel at the end of their curr
5418
5844
  }
5419
5845
  }
5420
5846
  async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
5421
- if (!existsSync18(join18(appDir, "src", "subscriptions.ts"))) return;
5847
+ if (!existsSync20(join19(appDir, "src", "subscriptions.ts"))) return;
5422
5848
  let plans;
5423
5849
  try {
5424
5850
  plans = await loadDeclaration(
@@ -5473,7 +5899,7 @@ async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
5473
5899
  }
5474
5900
  }
5475
5901
  async function syncOneTimeProducts(appDir, appName, token) {
5476
- if (!existsSync18(join18(appDir, "src", "products.ts"))) return;
5902
+ if (!existsSync20(join19(appDir, "src", "products.ts"))) return;
5477
5903
  let products;
5478
5904
  try {
5479
5905
  products = await loadDeclaration(
@@ -5573,189 +5999,18 @@ function createDeployOutput(json) {
5573
5999
  else console.error(withSlug(message, code));
5574
6000
  if (action) printAction(action);
5575
6001
  }
5576
- throw new CliExit(opts.actionRequired ? 2 : 1);
5577
- }
5578
- };
5579
- }
5580
-
5581
- // src/cli/commands/deploy/request.ts
5582
- import * as p10 from "@clack/prompts";
5583
- import { readFileSync as readFileSync16 } from "fs";
5584
-
5585
- // src/cli/commands/deploy/repository.ts
5586
- import * as p9 from "@clack/prompts";
5587
-
5588
- // src/cli/lib/vc-remote.ts
5589
- import { existsSync as existsSync19 } from "fs";
5590
- import { tmpdir } from "os";
5591
- import { isAbsolute as isAbsolute4, relative as relative3, resolve as resolve14 } from "path";
5592
- function spaceRemoteName(environment = DEEPSPACE_ENV) {
5593
- if (environment === "staging") return "space-staging";
5594
- if (environment === "production") return "space";
5595
- return "space-invalid";
5596
- }
5597
- var SPACE_REMOTE = spaceRemoteName();
5598
- function spaceTrackingRef(branch, remote = SPACE_REMOTE) {
5599
- return `refs/remotes/${remote}/${branch}`;
5600
- }
5601
- function spacePrivateRef(path, environment = DEEPSPACE_ENV) {
5602
- const root = environment === "production" ? "refs/deepspace" : `refs/deepspace/${environment}`;
5603
- return `${root}/${path}`;
5604
- }
5605
- function deployBaseUrl() {
5606
- return process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
5607
- }
5608
- function repoUrl(appId, base = deployBaseUrl()) {
5609
- return `${base.replace(/\/+$/, "")}/api/repo/${encodeURIComponent(appId)}`;
5610
- }
5611
- function gitAuthEnv(token, base = deployBaseUrl()) {
5612
- const existing = Number(process.env.GIT_CONFIG_COUNT ?? "0");
5613
- const index = Number.isInteger(existing) && existing > 0 ? existing : 0;
5614
- return {
5615
- GIT_CONFIG_COUNT: String(index + 1),
5616
- [`GIT_CONFIG_KEY_${index}`]: `http.${base.replace(/\/+$/, "")}.extraHeader`,
5617
- [`GIT_CONFIG_VALUE_${index}`]: `Authorization: Bearer ${token}`
5618
- };
5619
- }
5620
- function gitSourceImportEnv(token, revision, base = deployBaseUrl()) {
5621
- const env = gitAuthEnv(token, base);
5622
- const index = Number(env.GIT_CONFIG_COUNT);
5623
- return {
5624
- ...env,
5625
- GIT_CONFIG_COUNT: String(index + 1),
5626
- [`GIT_CONFIG_KEY_${index}`]: `http.${base.replace(/\/+$/, "")}.extraHeader`,
5627
- [`GIT_CONFIG_VALUE_${index}`]: `X-DeepSpace-Source-Revision: ${revision}`
5628
- };
5629
- }
5630
- function runGitRemote(cwd, token, args, opts = {}) {
5631
- return runGit(cwd, args, { ...opts, env: { ...gitAuthEnv(token), ...opts.env ?? {} } });
5632
- }
5633
- function entryIsTransient(cwd) {
5634
- const entry = process.argv[1];
5635
- if (!entry) return true;
5636
- if (/[\\/]_npx[\\/]/.test(entry) || entry.startsWith(tmpdir())) return true;
5637
- const rel = relative3(resolve14(cwd), resolve14(entry));
5638
- return rel === "" || !rel.startsWith("..") && !isAbsolute4(rel);
5639
- }
5640
- function helperEntryUsable(command) {
5641
- const match = /^!(?:DEEPSPACE_ENV=(production|staging) )?'([^']*)' '([^']*)' git-credential$/.exec(command);
5642
- const expectedEnvironment = DEEPSPACE_ENV === "production" ? void 0 : DEEPSPACE_ENV;
5643
- if (!match || match[1] !== expectedEnvironment) return false;
5644
- return existsSync19(match[2]) && existsSync19(match[3]);
5645
- }
5646
- function credentialHelperCommand(environment = DEEPSPACE_ENV) {
5647
- const entry = process.argv[1];
5648
- if (!entry) throw new Error("CLI entry path is unavailable");
5649
- const environmentPrefix = environment === "staging" ? "DEEPSPACE_ENV=staging " : "";
5650
- return `!${environmentPrefix}${shQuote(process.execPath)} ${shQuote(entry)} git-credential`;
5651
- }
5652
- function installCredentialHelper(cwd, url) {
5653
- try {
5654
- const origin = new URL(url);
5655
- const key = `credential.${origin.protocol}//${origin.host}.helper`;
5656
- const helper = credentialHelperCommand();
5657
- const wanted = ["", helper];
5658
- const transient = entryIsTransient(cwd);
5659
- const scope = transient ? "--worktree" : "--global";
5660
- if (transient) {
5661
- const enabled = runGit(cwd, ["config", "--local", "extensions.worktreeConfig", "true"], {
5662
- allowFail: true
5663
- });
5664
- if (enabled.status !== 0) return;
5665
- runGit(cwd, ["config", "--local", "--unset-all", key], { allowFail: true });
5666
- }
5667
- const current = runGit(cwd, ["config", scope, "--get-all", key], { allowFail: true });
5668
- if (current.status === 0) {
5669
- const existing = current.stdout.toString("utf-8").split("\n").filter((_, index, lines) => index < lines.length - 1);
5670
- if (existing.length === wanted.length && existing.every((value, i) => value === wanted[i])) {
5671
- return;
5672
- }
5673
- if (existing.length === wanted.length && existing[0] === "" && helperEntryUsable(existing[1])) {
5674
- return;
5675
- }
5676
- runGit(cwd, ["config", scope, "--unset-all", key], { allowFail: true });
5677
- }
5678
- runGit(cwd, ["config", scope, "--add", key, ""]);
5679
- runGit(cwd, ["config", scope, "--add", key, helper]);
5680
- if (scope === "--global" && !process.argv.includes("--json")) {
5681
- process.stderr.write(
5682
- `note: added a git credential helper for ${origin.host} to your global git config so plain \`git clone\`/\`git push\` to DeepSpace authenticate. Undo with: git config --global --unset-all ${key}
5683
- `
5684
- );
5685
- }
5686
- } catch {
5687
- }
5688
- }
5689
- function ensureSpaceRemote(cwd, appId, remote = SPACE_REMOTE, token) {
5690
- const url = repoUrl(appId);
5691
- const current = runGit(cwd, ["remote", "get-url", remote], { allowFail: true });
5692
- const existing = current.status === 0 ? current.stdout.toString("utf-8").trim() : null;
5693
- if (appIdFromRepoUrl(existing ?? "") !== appId) {
5694
- const owned = declaredAppIds(cwd);
5695
- if (owned.size > 0 && !owned.has(appId)) {
5696
- throw new InputError(
5697
- `This checkout declares ${[...owned].join(", ")}, but this command targets ${appId}. Pointing its \`${remote}\` remote at another app would send every later plain \`git push ${remote}\` to the wrong repo. Work on ${appId} from its own checkout (\`deepspace clone ${appId}\` into a new directory), or fork THIS checkout into a brand-new app of your own with \`deepspace app init --new-id\`.`,
5698
- "app_checkout_mismatch"
5699
- );
5700
- }
5701
- }
5702
- if (token) ensureGitIdentity(cwd, token);
5703
- if (existing === null) {
5704
- runGit(cwd, ["remote", "add", remote, url]);
5705
- } else if (existing !== url) {
5706
- const priorApp = appIdFromRepoUrl(existing);
5707
- if (priorApp && priorApp !== appId && !process.argv.includes("--json")) {
5708
- process.stderr.write(
5709
- `warning: remote '${remote}' was pointed at app ${priorApp}; re-aiming it at ${appId}.
5710
- `
5711
- );
5712
- }
5713
- runGit(cwd, ["remote", "set-url", remote, url]);
5714
- }
5715
- installCredentialHelper(cwd, url);
5716
- return url;
5717
- }
5718
- function removeSpaceRemote(cwd, remote = SPACE_REMOTE) {
5719
- const current = runGit(cwd, ["remote", "get-url", remote], { allowFail: true });
5720
- if (current.status !== 0) return false;
5721
- runGit(cwd, ["remote", "remove", remote]);
5722
- return true;
5723
- }
5724
- function ensureGitIdentity(cwd, token) {
5725
- try {
5726
- const configured = (key) => runGit(cwd, ["config", "--get", key], { allowFail: true }).stdout.toString("utf-8").trim() !== "";
5727
- const missingName = !configured("user.name");
5728
- const missingEmail = !configured("user.email");
5729
- if (!missingName && !missingEmail) return;
5730
- if (runGit(cwd, ["config", "--get", "--type=bool", "user.useConfigOnly"], { allowFail: true }).stdout.toString("utf-8").trim() === "true") {
5731
- return;
5732
- }
5733
- const payload = decodeJwtPayload(token);
5734
- if (!payload.email) return;
5735
- if (missingName) {
5736
- runGit(cwd, ["config", "--local", "user.name", payload.name || payload.email], {
5737
- allowFail: true
5738
- });
5739
- }
5740
- if (missingEmail) {
5741
- runGit(cwd, ["config", "--local", "user.email", payload.email], { allowFail: true });
5742
- }
5743
- const wrote = [...missingEmail ? ["user.email"] : [], ...missingName ? ["user.name"] : []];
5744
- const dir = /^[A-Za-z0-9_./:@-]+$/.test(cwd) ? cwd : shQuote(cwd);
5745
- const undo = wrote.map((key) => `git -C ${dir} config --local --unset ${key}`).join(" && ");
5746
- process.stderr.write(
5747
- `Git identity for this checkout set from your DeepSpace login (${payload.email}).
5748
- Undo with: ${undo}
5749
- `
5750
- );
5751
- } catch {
5752
- }
5753
- }
5754
- function appIdFromRepoUrl(url) {
5755
- const match = /\/api\/repo\/([^/]+)\/?$/.exec(url);
5756
- return match ? decodeURIComponent(match[1]) : null;
6002
+ throw new CliExit(opts.actionRequired ? 2 : 1);
6003
+ }
6004
+ };
5757
6005
  }
5758
6006
 
6007
+ // src/cli/commands/deploy/request.ts
6008
+ import * as p10 from "@clack/prompts";
6009
+ import { readFileSync as readFileSync16 } from "fs";
6010
+
6011
+ // src/cli/commands/deploy/repository.ts
6012
+ import * as p9 from "@clack/prompts";
6013
+
5759
6014
  // src/cli/lib/git/safety.ts
5760
6015
  var SECRET_FILE_RE = /(^|\/)(\.dev\.vars(\.[^/]*)?|\.env(\.[^/]*)?|\.npmrc|\.envrc|\.mcp\.json)$/i;
5761
6016
  var SECRET_TEMPLATE_RE = /\.(example|sample|template)$/i;
@@ -6196,41 +6451,6 @@ function selectGitHubRemote(cwd, options = {}) {
6196
6451
  const matching = repository ? remotes.filter((remote) => remote.repository === repository) : remotes;
6197
6452
  return matching.find((remote) => remote.name === "origin") ?? (matching.length === 1 ? matching[0] : null);
6198
6453
  }
6199
- function remotePublicRefs(cwd, remote) {
6200
- let output;
6201
- try {
6202
- output = runGit(cwd, ["ls-remote", "--refs", remote]).stdout.toString("utf-8");
6203
- } catch (error) {
6204
- if (!isGitCredentialFailure(error)) throw error;
6205
- throw new GitError(
6206
- "Git could not authenticate the remote. In a container, forward an SSH agent or use an ephemeral Git credential helper, then retry. Do not put tokens in remote URLs.",
6207
- "github_credentials_required"
6208
- );
6209
- }
6210
- return output.split("\n").map((line) => line.trim()).filter(Boolean).map((line) => {
6211
- const [oid, name] = line.split(/\s+/, 2);
6212
- return { oid, name };
6213
- }).filter(
6214
- (ref) => /^[0-9a-f]{40}$/.test(ref.oid) && (ref.name.startsWith("refs/heads/") || ref.name.startsWith("refs/tags/"))
6215
- );
6216
- }
6217
- function isGitCredentialFailure(error) {
6218
- if (!(error instanceof Error)) return false;
6219
- return /authentication failed|could not read username|permission denied \(publickey\)|repository not found|terminal prompts disabled/i.test(
6220
- error.message
6221
- );
6222
- }
6223
- function localTransferRefs(cwd, prefix, target2) {
6224
- const output = runGit(cwd, [
6225
- "for-each-ref",
6226
- "--format=%(refname) %(objectname)",
6227
- prefix
6228
- ]).stdout.toString("utf-8");
6229
- return output.split("\n").map((line) => line.trim()).filter(Boolean).map((line) => {
6230
- const [name, oid] = line.split(/\s+/, 2);
6231
- return { name: `${target2}/${name.slice(`${prefix}/`.length)}`, oid };
6232
- }).filter((ref) => /^[0-9a-f]{40}$/.test(ref.oid));
6233
- }
6234
6454
 
6235
6455
  // src/cli/commands/deploy/repository.ts
6236
6456
  function describeWorktree(appDir) {
@@ -6240,17 +6460,27 @@ function describeWorktree(appDir) {
6240
6460
  return { branch: null, dirty: null };
6241
6461
  }
6242
6462
  }
6463
+ function externalGitSource(appDir, source) {
6464
+ if (source?.provider === "github") return true;
6465
+ if (source !== null) return false;
6466
+ try {
6467
+ return listGitHubRemotes(appDir).length > 0;
6468
+ } catch {
6469
+ return false;
6470
+ }
6471
+ }
6472
+ function observedGitHubRepository(appDir) {
6473
+ try {
6474
+ return selectGitHubRemote(appDir)?.repository ?? listGitHubRemotes(appDir)[0]?.repository ?? null;
6475
+ } catch {
6476
+ return null;
6477
+ }
6478
+ }
6243
6479
  function preflightDeployRepository(options) {
6244
6480
  const { appDir, push, source } = options;
6245
- if (!push || source?.provider === "github") return null;
6481
+ if (!push || externalGitSource(appDir, source)) return null;
6246
6482
  try {
6247
6483
  assertSyncableRepo(appDir);
6248
- if (!source && listGitHubRemotes(appDir).length > 0) {
6249
- return {
6250
- code: "source_unclaimed",
6251
- error: "This app has a GitHub remote but no claimed source. Choose once with `deepspace app source github` (manual GitHub ownership) or `deepspace app source deepspace` (packaged DeepSpace source), then deploy again."
6252
- };
6253
- }
6254
6484
  assertNoOperationInProgress(appDir);
6255
6485
  const branch = currentBranch(appDir);
6256
6486
  if (!isWorkTreeClean(appDir)) return dirtyWorktreeRefusal(branch, appDir);
@@ -6267,9 +6497,18 @@ async function syncDeployRepository(options) {
6267
6497
  const deployKey = mintIdempotencyKey();
6268
6498
  let source = sourceState.source;
6269
6499
  let sourceRevision = sourceState.revision;
6270
- const baseReleaseId = null;
6271
6500
  const worktree = describeWorktree(appDir);
6272
- if (source?.provider === "github") {
6501
+ const external = externalGitSource(appDir, source);
6502
+ if (external) {
6503
+ if (sourceState.source?.provider === "github") {
6504
+ try {
6505
+ if (removeSpaceRemote(appDir)) {
6506
+ p9.log.info(`Removed the stale \`space\` git remote \u2014 GitHub owns this app's source.`);
6507
+ }
6508
+ } catch {
6509
+ }
6510
+ }
6511
+ const observedRepository = source === null ? observedGitHubRepository(appDir) : null;
6273
6512
  const where = `GitHub source: shipping the working tree of ${worktree.branch ?? "(detached HEAD)"}`;
6274
6513
  if (worktree.dirty) {
6275
6514
  p9.log.warn(
@@ -6278,7 +6517,7 @@ async function syncDeployRepository(options) {
6278
6517
  } else {
6279
6518
  p9.log.info(`${where} (clean). GitHub-source releases record no commit lineage.`);
6280
6519
  }
6281
- return { commitOid, recoverable, deployKey, source, sourceRevision, baseReleaseId, ...worktree };
6520
+ return { commitOid, recoverable, deployKey, source, sourceRevision, observedRepository, ...worktree };
6282
6521
  }
6283
6522
  if (!push) {
6284
6523
  try {
@@ -6286,7 +6525,12 @@ async function syncDeployRepository(options) {
6286
6525
  commitOid = resolveCommit(appDir, "HEAD");
6287
6526
  } catch {
6288
6527
  }
6289
- return { commitOid, recoverable, deployKey, source, sourceRevision, baseReleaseId, ...worktree };
6528
+ if (worktree.dirty) {
6529
+ p9.log.warn(
6530
+ `--no-push: shipping the working tree WITH uncommitted changes \u2014 the recorded commit ${commitOid ? commitOid.slice(0, 10) : ""} is not exactly what went live.`
6531
+ );
6532
+ }
6533
+ return { commitOid, recoverable, deployKey, source, sourceRevision, observedRepository: null, ...worktree };
6290
6534
  }
6291
6535
  try {
6292
6536
  const preflight = preflightDeployRepository({ appDir, push, source });
@@ -6302,7 +6546,7 @@ async function syncDeployRepository(options) {
6302
6546
  }
6303
6547
  const tip = resolveCommit(appDir, `refs/heads/${branch}`);
6304
6548
  if (!tip) {
6305
- return { commitOid, recoverable, deployKey, source, sourceRevision, baseReleaseId, ...worktree };
6549
+ return { commitOid, recoverable, deployKey, source, sourceRevision, observedRepository: null, ...worktree };
6306
6550
  }
6307
6551
  const secretFiles = workspaceBranchId ? [] : trackedSecretFiles(appDir, tip);
6308
6552
  if (workspaceBranchId) {
@@ -6331,6 +6575,11 @@ async function syncDeployRepository(options) {
6331
6575
  `Skipping cloud-repo push \u2014 the branch tracks secret file(s): ${secretFiles.join(", ")}. ` + secretRecoverySentence(secretFiles, "`deepspace push`") + ` Without the push this deploy carries no source lineage: if the live release has one, the server refuses it as stale (--ignore-stale replaces it anyway), and this release's source isn't recoverable until the push works.`
6332
6576
  );
6333
6577
  } else {
6578
+ if (sourceState.source === null) {
6579
+ process.stderr.write(
6580
+ "This app's source is unclaimed \u2014 this deploy's push claims DeepSpace source for it, permanently.\n"
6581
+ );
6582
+ }
6334
6583
  const pushResult = await pushWithTransientRetry(
6335
6584
  () => pushToSpace(appDir, token, `refs/heads/${branch}:refs/heads/${branch}`, {
6336
6585
  remote: sourceRemote
@@ -6391,7 +6640,7 @@ async function syncDeployRepository(options) {
6391
6640
  const failure = deployRepositoryFailure(error, appDir);
6392
6641
  output.die(failure.error, failure.code);
6393
6642
  }
6394
- return { commitOid, recoverable, deployKey, source, sourceRevision, baseReleaseId, ...worktree };
6643
+ return { commitOid, recoverable, deployKey, source, sourceRevision, observedRepository: null, ...worktree };
6395
6644
  }
6396
6645
  function classifyRemoteState(appDir, token, branch, localTip, remote) {
6397
6646
  let remoteTip = null;
@@ -6617,7 +6866,10 @@ async function deployBuiltBundle(options) {
6617
6866
  form.append("sourceRepository", repository.source.repository);
6618
6867
  }
6619
6868
  }
6620
- if (repository.baseReleaseId) form.append("baseReleaseId", repository.baseReleaseId);
6869
+ if (repository.dirty !== null) form.append("sourceDirty", repository.dirty ? "true" : "false");
6870
+ if (repository.observedRepository) {
6871
+ form.append("observedRepository", repository.observedRepository);
6872
+ }
6621
6873
  if (ignoreStale) form.append("ignoreStale", "true");
6622
6874
  form.append("deployKey", repository.deployKey);
6623
6875
  return form;
@@ -6951,21 +7203,21 @@ function errorMessage5(error) {
6951
7203
 
6952
7204
  // src/cli/commands/deploy/lock.ts
6953
7205
  import { randomUUID as randomUUID2 } from "crypto";
6954
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync17, rmSync as rmSync4, writeFileSync as writeFileSync5 } from "fs";
6955
- import { dirname as dirname8, join as join19 } from "path";
7206
+ import { mkdirSync as mkdirSync8, readFileSync as readFileSync17, rmSync as rmSync6, writeFileSync as writeFileSync6 } from "fs";
7207
+ import { dirname as dirname8, join as join20 } from "path";
6956
7208
  function deployLockPath(appDir) {
6957
- return join19(appDir, ".deepspace", "deploy.lock");
7209
+ return join20(appDir, ".deepspace", "deploy.lock");
6958
7210
  }
6959
7211
  function acquireDeployLock(appDir) {
6960
7212
  const path = deployLockPath(appDir);
6961
- mkdirSync6(dirname8(path), { recursive: true });
7213
+ mkdirSync8(dirname8(path), { recursive: true });
6962
7214
  const record = {
6963
7215
  pid: process.pid,
6964
7216
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
6965
7217
  token: randomUUID2()
6966
7218
  };
6967
7219
  try {
6968
- writeFileSync5(path, JSON.stringify(record), { flag: "wx" });
7220
+ writeFileSync6(path, JSON.stringify(record), { flag: "wx" });
6969
7221
  } catch (err) {
6970
7222
  if (err.code !== "EEXIST") throw err;
6971
7223
  const holder = readLock(path);
@@ -6981,7 +7233,7 @@ function acquireDeployLock(appDir) {
6981
7233
  );
6982
7234
  }
6983
7235
  return () => {
6984
- if (readLock(path)?.token === record.token) rmSync4(path, { force: true });
7236
+ if (readLock(path)?.token === record.token) rmSync6(path, { force: true });
6985
7237
  };
6986
7238
  }
6987
7239
  function readLock(path) {
@@ -6994,7 +7246,7 @@ function readLock(path) {
6994
7246
  }
6995
7247
 
6996
7248
  // src/cli/commands/deploy.ts
6997
- var DEPLOY_URL3 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
7249
+ var DEPLOY_URL4 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
6998
7250
  var DEPLOY_TOKEN_MIN_VALIDITY_MS = 10 * 60 * 1e3;
6999
7251
  var POST_DEPLOY_TOKEN_MIN_VALIDITY_MS = 2 * 60 * 1e3;
7000
7252
  var deploy_default = defineCommand3({
@@ -7067,15 +7319,7 @@ async function runDeploy(args, output) {
7067
7319
  if (selectorRefusal) output.die(selectorRefusal.error, selectorRefusal.code);
7068
7320
  const envName = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
7069
7321
  if (!hasWranglerConfig(appDir)) {
7070
- output.die(noWranglerConfigMessage(join20(appDir, "wrangler.toml")), "not_in_app_repo");
7071
- }
7072
- try {
7073
- ensureInstallReady(appDir);
7074
- } catch (error) {
7075
- const refusal = error;
7076
- output.die(refusal.message ?? String(error), refusal.code ?? "deps_missing", {
7077
- action: refusal.action
7078
- });
7322
+ output.die(noWranglerConfigMessage(join21(appDir, "wrangler.toml")), "not_in_app_repo");
7079
7323
  }
7080
7324
  const releaseLock = acquireDeployLock(appDir);
7081
7325
  try {
@@ -7099,24 +7343,30 @@ async function runLockedDeploy(args, output, appDir, envName) {
7099
7343
  );
7100
7344
  }
7101
7345
  const appName = nameResult.name;
7102
- const appId = readAppId(appDir, envName);
7346
+ let appId = readAppId(appDir, envName);
7103
7347
  p12.log.info(envName ? `App: ${appName} (env: ${envName})` : `App: ${appName}`);
7104
7348
  const { token, ownerId } = await authenticate(appDir, output);
7349
+ try {
7350
+ ensureInstallReady(appDir);
7351
+ } catch (error) {
7352
+ const refusal = error;
7353
+ return output.die(refusal.message ?? String(error), refusal.code ?? "install_failed", {
7354
+ action: refusal.action
7355
+ });
7356
+ }
7105
7357
  if (!appId) {
7106
- return output.die(
7107
- `No app id in wrangler.toml${envName ? ` for [env.${envName}]` : ""}. Run \`deepspace app init${envName ? ` --env ${envName}` : ""}\` to register this app, then re-deploy.`,
7108
- "app_not_initialized",
7109
- {
7110
- action: {
7111
- cwd: appDir,
7112
- argv: ["deepspace", "app", "init", ...envName ? ["--env", envName] : []]
7113
- },
7114
- actionRequired: true
7115
- }
7116
- );
7358
+ const ensured = await ensureAppRegistered(appDir, token, envName, { commitScaffold: true });
7359
+ if (!ensured) {
7360
+ return output.die(
7361
+ envName ? `No app id for [env.${envName}] \u2014 declare the [env.${envName}] block with its own DEEPSPACE_APP_ID entry (the \`__APP_ID__\` placeholder is enough), or run \`deepspace app init --env ${envName}\`.` : "wrangler.toml declares no DEEPSPACE_APP_ID \u2014 this does not look like a DeepSpace app. Run `deepspace app init` to register it as one.",
7362
+ envName ? "no_app_id_for_env" : "app_not_initialized"
7363
+ );
7364
+ }
7365
+ appId = ensured.appId;
7117
7366
  }
7367
+ commitScaffoldIfUnborn(appDir, token);
7118
7368
  p12.log.info(`Id: ${appId}`);
7119
- const sourceState = await getAppSource(DEPLOY_URL3, token, appId).catch((error) => {
7369
+ const sourceState = await getAppSource(DEPLOY_URL4, token, appId).catch((error) => {
7120
7370
  if (errorCode(error) === "forbidden") {
7121
7371
  output.die(forbiddenDeployMessage(appId, token), "forbidden");
7122
7372
  }
@@ -7129,7 +7379,7 @@ async function runLockedDeploy(args, output, appDir, envName) {
7129
7379
  );
7130
7380
  }
7131
7381
  const secretsCache = await loadDeploySecrets({
7132
- deployUrl: DEPLOY_URL3,
7382
+ deployUrl: DEPLOY_URL4,
7133
7383
  appDir,
7134
7384
  appId,
7135
7385
  envName,
@@ -7173,7 +7423,7 @@ async function runLockedDeploy(args, output, appDir, envName) {
7173
7423
  }
7174
7424
  const { token: deployToken } = await authenticate(appDir, output, DEPLOY_TOKEN_MIN_VALIDITY_MS);
7175
7425
  const repository = await syncDeployRepository({
7176
- deployUrl: DEPLOY_URL3,
7426
+ deployUrl: DEPLOY_URL4,
7177
7427
  appDir,
7178
7428
  appId,
7179
7429
  token: deployToken,
@@ -7189,7 +7439,7 @@ async function runLockedDeploy(args, output, appDir, envName) {
7189
7439
  output
7190
7440
  });
7191
7441
  const body = await deployBuiltBundle({
7192
- deployUrl: DEPLOY_URL3,
7442
+ deployUrl: DEPLOY_URL4,
7193
7443
  appDir,
7194
7444
  appId,
7195
7445
  appName,
@@ -7342,7 +7592,7 @@ async function authenticate(appDir, output, minimumValidityMs) {
7342
7592
 
7343
7593
  // src/cli/commands/undeploy.ts
7344
7594
  import * as p13 from "@clack/prompts";
7345
- var DEPLOY_URL4 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
7595
+ var DEPLOY_URL5 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
7346
7596
  var undeploy_default = defineDeepspaceCommand({
7347
7597
  meta: {
7348
7598
  name: "undeploy",
@@ -7361,7 +7611,8 @@ var undeploy_default = defineDeepspaceCommand({
7361
7611
  },
7362
7612
  yes: {
7363
7613
  type: "boolean",
7364
- description: "Skip the interactive confirmation. (Scripts and --json callers are never prompted; the command itself is their consent.)",
7614
+ alias: "y",
7615
+ description: "Skip the confirmation (required for --json / non-interactive)",
7365
7616
  default: false
7366
7617
  }
7367
7618
  },
@@ -7379,7 +7630,7 @@ var undeploy_default = defineDeepspaceCommand({
7379
7630
  let label;
7380
7631
  const positional = typeof args.name === "string" && args.name.trim() ? args.name.trim() : void 0;
7381
7632
  if (positional) {
7382
- appId = await resolveAppSelector(DEPLOY_URL4, token, positional);
7633
+ appId = await resolveAppSelector(DEPLOY_URL5, token, positional);
7383
7634
  label = positional;
7384
7635
  } else if (hasWranglerConfig(process.cwd())) {
7385
7636
  appId = readAppId(process.cwd(), envName) ?? void 0;
@@ -7393,6 +7644,13 @@ var undeploy_default = defineDeepspaceCommand({
7393
7644
  );
7394
7645
  }
7395
7646
  const target2 = label && label !== appId ? `${label} (${appId})` : appId;
7647
+ if (args.yes !== true && (args.json === true || !process.stdin.isTTY)) {
7648
+ throw new Refusal(
7649
+ `Undeploying ${target2} takes its URL offline immediately and destroys its live data \u2014 records, messages, canvas state, cron history \u2014 with the worker (secrets and the registration stay). Re-run with --yes to confirm.`,
7650
+ "confirmation_required",
7651
+ { extra: { appId } }
7652
+ );
7653
+ }
7396
7654
  if (!args.json && !args.yes && process.stdin.isTTY) {
7397
7655
  p13.intro(`Undeploying ${target2}`);
7398
7656
  const confirmed = await p13.confirm({
@@ -7407,7 +7665,7 @@ var undeploy_default = defineDeepspaceCommand({
7407
7665
  }
7408
7666
  const s = args.json ? null : createSpinner();
7409
7667
  s?.start("Removing...");
7410
- const res = await fetch(`${DEPLOY_URL4}/api/deploy/${appId}`, {
7668
+ const res = await fetch(`${DEPLOY_URL5}/api/deploy/${appId}`, {
7411
7669
  method: "DELETE",
7412
7670
  headers: { Authorization: `Bearer ${token}` }
7413
7671
  });
@@ -7492,7 +7750,7 @@ var whoami_default = defineDeepspaceCommand({
7492
7750
  });
7493
7751
 
7494
7752
  // src/cli/commands/apps.ts
7495
- var DEPLOY_URL5 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
7753
+ var DEPLOY_URL6 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
7496
7754
  var apps_default = defineDeepspaceCommand({
7497
7755
  meta: {
7498
7756
  name: "apps",
@@ -7500,7 +7758,7 @@ var apps_default = defineDeepspaceCommand({
7500
7758
  },
7501
7759
  async run({ args }) {
7502
7760
  const token = await ensureToken();
7503
- const { apps, pendingTransfers } = await listAppsPage(DEPLOY_URL5, token);
7761
+ const { apps, pendingTransfers } = await listAppsPage(DEPLOY_URL6, token);
7504
7762
  if (!args.json) {
7505
7763
  if (!apps.length && !pendingTransfers.length) {
7506
7764
  console.log("No apps yet. Create one with `npx create-deepspace <name>` and `deepspace deploy`.");
@@ -7558,7 +7816,7 @@ import { setTimeout as delay } from "timers/promises";
7558
7816
  var LOG_LEVELS = ["debug", "log", "info", "warn", "error"];
7559
7817
 
7560
7818
  // src/cli/commands/logs.ts
7561
- var DEPLOY_URL6 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
7819
+ var DEPLOY_URL7 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
7562
7820
  var RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
7563
7821
  var DEFAULT_WINDOW_MS = 15 * 60 * 1e3;
7564
7822
  var MIN_POLL_MS = 2500;
@@ -7719,7 +7977,7 @@ var logs_default = defineCommand4({
7719
7977
  const envArg = args.env;
7720
7978
  assertAppTargetResolvable(args.app, { wranglerEnv: envArg });
7721
7979
  const token = await ensureToken();
7722
- const appId = await resolveAppTarget(DEPLOY_URL6, token, args.app, { wranglerEnv: envArg });
7980
+ const appId = await resolveAppTarget(DEPLOY_URL7, token, args.app, { wranglerEnv: envArg });
7723
7981
  if (args.level && !LOG_LEVELS.includes(args.level)) {
7724
7982
  throw new InputError(
7725
7983
  `Invalid --level "${args.level}". Use: ${LOG_LEVELS.join(", ")}`,
@@ -7738,7 +7996,7 @@ var logs_default = defineCommand4({
7738
7996
  }
7739
7997
  const initialSince = args.since ? parseSince(args.since) : Date.now() - DEFAULT_WINDOW_MS;
7740
7998
  const windowLabel = args.since?.trim() || "15m";
7741
- const registryRow = (await listApps(DEPLOY_URL6, token)).find((app2) => app2.appId === appId);
7999
+ const registryRow = (await listApps(DEPLOY_URL7, token)).find((app2) => app2.appId === appId);
7742
8000
  if (registryRow && registryRow.deployedAt === null) {
7743
8001
  const appDir = args.app === void 0 ? findAppDir() : null;
7744
8002
  throw new Refusal(
@@ -7760,7 +8018,7 @@ var logs_default = defineCommand4({
7760
8018
  if (args.level) params.set("level", args.level);
7761
8019
  if (args.search) params.set("search", args.search);
7762
8020
  const path = `/api/apps/${appId}/logs?${params}`;
7763
- return signal ? apiFetch(DEPLOY_URL6, token2, path, { signal }) : apiFetchReadWithRetry(DEPLOY_URL6, token2, path);
8021
+ return signal ? apiFetch(DEPLOY_URL7, token2, path, { signal }) : apiFetchReadWithRetry(DEPLOY_URL7, token2, path);
7764
8022
  };
7765
8023
  const print = (events) => {
7766
8024
  for (const e of events) {
@@ -7960,35 +8218,35 @@ var usage_default = defineDeepspaceCommand({
7960
8218
  });
7961
8219
 
7962
8220
  // src/cli/commands/add.ts
7963
- import { existsSync as existsSync21, readFileSync as readFileSync19, realpathSync as realpathSync4 } from "fs";
7964
- import { dirname as dirname10, join as join22, resolve as resolve17 } from "path";
8221
+ import { existsSync as existsSync22, readFileSync as readFileSync19, realpathSync as realpathSync4 } from "fs";
8222
+ import { dirname as dirname10, join as join23, resolve as resolve17 } from "path";
7965
8223
  import { pathToFileURL as pathToFileURL3 } from "url";
7966
8224
 
7967
8225
  // src/cli/commands/feature-installer.ts
7968
8226
  import {
7969
- closeSync,
8227
+ closeSync as closeSync2,
7970
8228
  copyFileSync,
7971
- existsSync as existsSync20,
8229
+ existsSync as existsSync21,
7972
8230
  fstatSync,
7973
- mkdirSync as mkdirSync7,
8231
+ mkdirSync as mkdirSync9,
7974
8232
  mkdtempSync,
7975
- openSync,
8233
+ openSync as openSync2,
7976
8234
  readFileSync as readFileSync18,
7977
8235
  readSync,
7978
8236
  readdirSync as readdirSync7,
7979
- rmSync as rmSync5,
7980
- writeFileSync as writeFileSync6
8237
+ rmSync as rmSync7,
8238
+ writeFileSync as writeFileSync7
7981
8239
  } from "fs";
7982
- import { spawnSync as spawnSync6 } from "child_process";
8240
+ import { spawnSync as spawnSync7 } from "child_process";
7983
8241
  import { tmpdir as tmpdir2 } from "os";
7984
- import { dirname as dirname9, isAbsolute as isAbsolute5, join as join21, resolve as resolve16 } from "path";
8242
+ import { dirname as dirname9, isAbsolute as isAbsolute5, join as join22, resolve as resolve16 } from "path";
7985
8243
  import { fileURLToPath } from "url";
7986
8244
  var NAV_MARKER = "// \u2500\u2500 Features add nav items below this line \u2500\u2500";
7987
8245
  function featurePackageRoot(moduleUrl = import.meta.url) {
7988
8246
  let dir = dirname9(fileURLToPath(moduleUrl));
7989
8247
  for (; ; ) {
7990
- const packagePath = join21(dir, "package.json");
7991
- if (existsSync20(packagePath)) {
8248
+ const packagePath = join22(dir, "package.json");
8249
+ if (existsSync21(packagePath)) {
7992
8250
  try {
7993
8251
  const pkg2 = JSON.parse(readFileSync18(packagePath, "utf-8"));
7994
8252
  if (pkg2.name === "deepspace") return dir;
@@ -8001,11 +8259,11 @@ function featurePackageRoot(moduleUrl = import.meta.url) {
8001
8259
  }
8002
8260
  }
8003
8261
  function hasFeatureCatalog(packageRoot = featurePackageRoot()) {
8004
- return existsSync20(join21(packageRoot, "features"));
8262
+ return existsSync21(join22(packageRoot, "features"));
8005
8263
  }
8006
8264
  function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
8007
- const configPath = join21(packageRoot, "features", id, "feature.json");
8008
- if (!existsSync20(configPath)) return null;
8265
+ const configPath = join22(packageRoot, "features", id, "feature.json");
8266
+ if (!existsSync21(configPath)) return null;
8009
8267
  try {
8010
8268
  const config = JSON.parse(readFileSync18(configPath, "utf-8"));
8011
8269
  if (typeof config.id !== "string" || typeof config.name !== "string" || !Array.isArray(config.files)) {
@@ -8017,8 +8275,8 @@ function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
8017
8275
  }
8018
8276
  }
8019
8277
  function listFeatureConfigs(packageRoot = featurePackageRoot()) {
8020
- const featuresDir = join21(packageRoot, "features");
8021
- if (!existsSync20(featuresDir)) return [];
8278
+ const featuresDir = join22(packageRoot, "features");
8279
+ if (!existsSync21(featuresDir)) return [];
8022
8280
  return readdirSync7(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));
8023
8281
  }
8024
8282
  function featureCatalog(packageRoot = featurePackageRoot()) {
@@ -8066,17 +8324,17 @@ function resolveFeatureDestPath(fileDest, route) {
8066
8324
  return route?.protected === false ? `${appPages}${rest}` : `${appPages}(protected)/${rest}`;
8067
8325
  }
8068
8326
  function copyFile(src, dest) {
8069
- mkdirSync7(dirname9(dest), { recursive: true });
8327
+ mkdirSync9(dirname9(dest), { recursive: true });
8070
8328
  copyFileSync(src, dest);
8071
8329
  }
8072
8330
  function copyFileWithImportShift(src, dest, levels) {
8073
- mkdirSync7(dirname9(dest), { recursive: true });
8331
+ mkdirSync9(dirname9(dest), { recursive: true });
8074
8332
  const prefix = "../".repeat(levels);
8075
8333
  const shifted = readFileSync18(src, "utf-8").replace(
8076
8334
  /(from\s+|import\s+|import\()(['"])(\.\.\/)/g,
8077
8335
  `$1$2${prefix}$3`
8078
8336
  );
8079
- writeFileSync6(dest, shifted);
8337
+ writeFileSync7(dest, shifted);
8080
8338
  }
8081
8339
  function installFiles(packageRoot, config, targetDir, emit) {
8082
8340
  let copied = 0;
@@ -8085,12 +8343,12 @@ function installFiles(packageRoot, config, targetDir, emit) {
8085
8343
  const preexisting = [];
8086
8344
  const missing = [];
8087
8345
  for (const file of config.files) {
8088
- const srcPath = join21(packageRoot, "features", config.id, file.src);
8346
+ const srcPath = join22(packageRoot, "features", config.id, file.src);
8089
8347
  const resolvedDest = resolveFeatureDestPath(file.dest, config.route);
8090
- const destPath = join21(targetDir, resolvedDest);
8091
- const existed = existsSync20(destPath);
8348
+ const destPath = join22(targetDir, resolvedDest);
8349
+ const existed = existsSync21(destPath);
8092
8350
  if (existed) preexisting.push(resolvedDest);
8093
- if (!existsSync20(srcPath)) {
8351
+ if (!existsSync21(srcPath)) {
8094
8352
  emit(` Warning: source not found: ${file.src}`, "stderr");
8095
8353
  if (!existed) missing.push(resolvedDest);
8096
8354
  continue;
@@ -8127,8 +8385,8 @@ function unresolvedSchema(schema, reason, emit) {
8127
8385
  }
8128
8386
  function integrateSchema(config, targetDir, emit) {
8129
8387
  if (!config.schema) return [];
8130
- const schemasPath = join21(targetDir, "src", "schemas.ts");
8131
- if (!existsSync20(schemasPath)) {
8388
+ const schemasPath = join22(targetDir, "src", "schemas.ts");
8389
+ if (!existsSync21(schemasPath)) {
8132
8390
  return [unresolvedSchema(config.schema, "src/schemas.ts was not found.", emit)];
8133
8391
  }
8134
8392
  let content = readFileSync18(schemasPath, "utf-8");
@@ -8162,7 +8420,7 @@ export const schemas`
8162
8420
  return [unresolvedSchema(config.schema, reason, emit)];
8163
8421
  }
8164
8422
  content = withEntry;
8165
- writeFileSync6(schemasPath, content);
8423
+ writeFileSync7(schemasPath, content);
8166
8424
  emit(` Schema integrated: ${exportName} -> schemas.ts`);
8167
8425
  return [];
8168
8426
  }
@@ -8184,8 +8442,8 @@ function unresolvedActions(actions, reason, emit) {
8184
8442
  }
8185
8443
  function integrateActions(config, targetDir, emit) {
8186
8444
  if (!config.actions?.length) return [];
8187
- const actionsPath = join21(targetDir, "src", "actions", "index.ts");
8188
- if (!existsSync20(actionsPath)) {
8445
+ const actionsPath = join22(targetDir, "src", "actions", "index.ts");
8446
+ if (!existsSync21(actionsPath)) {
8189
8447
  return [unresolvedActions(config.actions, "src/actions/index.ts was not found.", emit)];
8190
8448
  }
8191
8449
  let content = readFileSync18(actionsPath, "utf-8");
@@ -8228,14 +8486,14 @@ ${registryEntry}`);
8228
8486
  integrated++;
8229
8487
  emit(` Action integrated: ${action.registerAs} (${action.exportName})`);
8230
8488
  }
8231
- if (integrated > 0) writeFileSync6(actionsPath, content);
8489
+ if (integrated > 0) writeFileSync7(actionsPath, content);
8232
8490
  return unresolved;
8233
8491
  }
8234
8492
  function integrateCode(config, targetDir, emit) {
8235
8493
  const unresolved = [];
8236
8494
  for (const insertion of config.code ?? []) {
8237
- const targetPath = join21(targetDir, insertion.file);
8238
- if (!existsSync20(targetPath)) {
8495
+ const targetPath = join22(targetDir, insertion.file);
8496
+ if (!existsSync21(targetPath)) {
8239
8497
  unresolved.push({
8240
8498
  area: "code",
8241
8499
  reason: `${insertion.file} was not found.`,
@@ -8273,32 +8531,32 @@ ${content.slice(
8273
8531
  const prefix = insertion.placement === "before" ? "" : "\n";
8274
8532
  const suffix = insertion.placement === "before" ? "\n" : "";
8275
8533
  content = `${content.slice(0, insertAt)}${prefix}${insertion.insert}${suffix}${content.slice(insertAt)}`;
8276
- writeFileSync6(targetPath, content);
8534
+ writeFileSync7(targetPath, content);
8277
8535
  emit(` Integrated: ${insertion.file}`);
8278
8536
  }
8279
8537
  return unresolved;
8280
8538
  }
8281
8539
  function integrateIgnore(config, targetDir) {
8282
8540
  if (!config.ignore?.length) return;
8283
- const targetPath = join21(targetDir, ".gitignore");
8284
- const current = existsSync20(targetPath) ? readFileSync18(targetPath, "utf-8") : "";
8541
+ const targetPath = join22(targetDir, ".gitignore");
8542
+ const current = existsSync21(targetPath) ? readFileSync18(targetPath, "utf-8") : "";
8285
8543
  const present = new Set(current.split(/\r?\n/).map((line) => line.trim()));
8286
8544
  const missing = config.ignore.filter((pattern) => !present.has(pattern));
8287
8545
  if (missing.length === 0) return;
8288
8546
  const separator = current === "" || current.endsWith("\n") ? "" : "\n";
8289
- writeFileSync6(targetPath, `${current}${separator}${missing.join("\n")}
8547
+ writeFileSync7(targetPath, `${current}${separator}${missing.join("\n")}
8290
8548
  `);
8291
8549
  }
8292
8550
  function integrateWrangler(config, targetDir, emit) {
8293
8551
  const runWorkerFirst = config.wrangler?.runWorkerFirst === true;
8294
8552
  const rateLimits = config.wrangler?.rateLimits ?? [];
8295
8553
  if (!runWorkerFirst && rateLimits.length === 0) return [];
8296
- const targetPath = join21(targetDir, "wrangler.toml");
8554
+ const targetPath = join22(targetDir, "wrangler.toml");
8297
8555
  const steps = [
8298
8556
  ...runWorkerFirst ? ["Set assets.run_worker_first = true in wrangler.toml."] : [],
8299
8557
  ...rateLimits.map((binding) => `Add the ${binding.name} rate-limit binding to wrangler.toml.`)
8300
8558
  ];
8301
- if (!existsSync20(targetPath)) {
8559
+ if (!existsSync21(targetPath)) {
8302
8560
  return [{ area: "wrangler", reason: "wrangler.toml was not found.", steps }];
8303
8561
  }
8304
8562
  let source = readFileSync18(targetPath, "utf-8");
@@ -8323,7 +8581,7 @@ ${rateLimitBindingBlock(scope, binding)}
8323
8581
  }
8324
8582
  }
8325
8583
  if (changed) {
8326
- writeFileSync6(targetPath, source);
8584
+ writeFileSync7(targetPath, source);
8327
8585
  emit(" Integrated: wrangler.toml");
8328
8586
  } else emit(" Integration already present: wrangler.toml");
8329
8587
  return [];
@@ -8377,8 +8635,8 @@ function rateLimitBindingBlock(scope, binding) {
8377
8635
  }
8378
8636
  function integrateCss(packageRoot, config, targetDir, emit) {
8379
8637
  if (!config.css?.length) return [];
8380
- const stylesPath = join21(targetDir, "src", "styles.css");
8381
- if (!existsSync20(stylesPath)) {
8638
+ const stylesPath = join22(targetDir, "src", "styles.css");
8639
+ if (!existsSync21(stylesPath)) {
8382
8640
  emit(" Warning: Cannot integrate CSS \u2014 src/styles.css not found");
8383
8641
  return [
8384
8642
  {
@@ -8394,8 +8652,8 @@ function integrateCss(packageRoot, config, targetDir, emit) {
8394
8652
  let integrated = 0;
8395
8653
  const unresolved = [];
8396
8654
  for (const cssFile of config.css) {
8397
- const srcPath = join21(packageRoot, "features", config.id, cssFile);
8398
- if (!existsSync20(srcPath)) {
8655
+ const srcPath = join22(packageRoot, "features", config.id, cssFile);
8656
+ if (!existsSync21(srcPath)) {
8399
8657
  emit(` Warning: CSS source not found: ${cssFile}`, "stderr");
8400
8658
  unresolved.push({
8401
8659
  area: "css",
@@ -8416,7 +8674,7 @@ ${css}`;
8416
8674
  integrated++;
8417
8675
  emit(` CSS integrated: ${cssFile} -> styles.css`);
8418
8676
  }
8419
- if (integrated > 0) writeFileSync6(stylesPath, styles);
8677
+ if (integrated > 0) writeFileSync7(stylesPath, styles);
8420
8678
  return unresolved;
8421
8679
  }
8422
8680
  function unresolvedRoute(route, config, reason, emit) {
@@ -8438,8 +8696,8 @@ function routeRolesSuffix(route) {
8438
8696
  }
8439
8697
  function integrateRoute(config, targetDir, emit) {
8440
8698
  if (!config.route) return [];
8441
- const navPath = join21(targetDir, "src", "nav.ts");
8442
- if (!existsSync20(navPath)) {
8699
+ const navPath = join22(targetDir, "src", "nav.ts");
8700
+ if (!existsSync21(navPath)) {
8443
8701
  return [unresolvedRoute(config.route, config, "src/nav.ts was not found.", emit)];
8444
8702
  }
8445
8703
  let content = readFileSync18(navPath, "utf-8");
@@ -8463,7 +8721,7 @@ function integrateRoute(config, targetDir, emit) {
8463
8721
  const entry = ` { path: '${routePath}', label: '${config.name}'${roles} },`;
8464
8722
  content = content.replace(NAV_MARKER, `${NAV_MARKER}
8465
8723
  ${entry}`);
8466
- writeFileSync6(navPath, content);
8724
+ writeFileSync7(navPath, content);
8467
8725
  emit(` Nav wired: ${routePath} (${config.name})`);
8468
8726
  emit(" Route: automatic (file-based routing via src/pages/)");
8469
8727
  return [];
@@ -8486,14 +8744,14 @@ function runPackageInstall(packageManager, targetDir, outputVisible) {
8486
8744
  shell: process.platform === "win32"
8487
8745
  };
8488
8746
  if (outputVisible) {
8489
- const child = spawnSync6(packageManager, ["install"], { ...options, stdio: "inherit" });
8747
+ const child = spawnSync7(packageManager, ["install"], { ...options, stdio: "inherit" });
8490
8748
  return { error: child.error, status: child.status };
8491
8749
  }
8492
- const captureDir = mkdtempSync(join21(tmpdir2(), "deepspace-add-install-"));
8750
+ const captureDir = mkdtempSync(join22(tmpdir2(), "deepspace-add-install-"));
8493
8751
  let outputFd;
8494
8752
  try {
8495
- outputFd = openSync(join21(captureDir, "output.log"), "w+");
8496
- const child = spawnSync6(packageManager, ["install"], {
8753
+ outputFd = openSync2(join22(captureDir, "output.log"), "w+");
8754
+ const child = spawnSync7(packageManager, ["install"], {
8497
8755
  ...options,
8498
8756
  stdio: ["ignore", outputFd, outputFd]
8499
8757
  });
@@ -8503,8 +8761,8 @@ function runPackageInstall(packageManager, targetDir, outputVisible) {
8503
8761
  diagnosticTail: readDiagnosticTail(outputFd)
8504
8762
  };
8505
8763
  } finally {
8506
- if (outputFd !== void 0) closeSync(outputFd);
8507
- rmSync5(captureDir, { recursive: true, force: true });
8764
+ if (outputFd !== void 0) closeSync2(outputFd);
8765
+ rmSync7(captureDir, { recursive: true, force: true });
8508
8766
  }
8509
8767
  }
8510
8768
  function integrateDependencies(config, targetDir, runInstall, outputVisible, emit) {
@@ -8516,8 +8774,8 @@ function integrateDependencies(config, targetDir, runInstall, outputVisible, emi
8516
8774
  if (declarations.every(({ values }) => !values || Object.keys(values).length === 0)) {
8517
8775
  return { added: [], installRan: false, packageManager };
8518
8776
  }
8519
- const packagePath = join21(targetDir, "package.json");
8520
- if (!existsSync20(packagePath)) {
8777
+ const packagePath = join22(targetDir, "package.json");
8778
+ if (!existsSync21(packagePath)) {
8521
8779
  emit(" Could not patch package.json (file not found)");
8522
8780
  return { added: [], installRan: false, packageManager };
8523
8781
  }
@@ -8547,7 +8805,7 @@ function integrateDependencies(config, targetDir, runInstall, outputVisible, emi
8547
8805
  if (added.length === 0) {
8548
8806
  emit(" Dependencies already present");
8549
8807
  } else {
8550
- writeFileSync6(packagePath, `${JSON.stringify(pkg2, null, 2)}
8808
+ writeFileSync7(packagePath, `${JSON.stringify(pkg2, null, 2)}
8551
8809
  `, "utf-8");
8552
8810
  emit(` Added to package.json: ${added.map(({ name }) => name).join(", ")}`);
8553
8811
  if (skipped.length) emit(` Already present (kept your version): ${skipped.join(", ")}`);
@@ -8583,7 +8841,7 @@ ${child.diagnosticTail}` : "";
8583
8841
  }
8584
8842
  function dependencySnapshot(targetDir) {
8585
8843
  try {
8586
- const pkg2 = JSON.parse(readFileSync18(join21(targetDir, "package.json"), "utf-8"));
8844
+ const pkg2 = JSON.parse(readFileSync18(join22(targetDir, "package.json"), "utf-8"));
8587
8845
  return {
8588
8846
  dependencies: pkg2.dependencies ?? {},
8589
8847
  devDependencies: pkg2.devDependencies ?? {}
@@ -8595,7 +8853,7 @@ function dependencySnapshot(targetDir) {
8595
8853
  function installedNavFeatures(packageRoot, targetDir) {
8596
8854
  return listFeatureConfigs(packageRoot).filter((feature) => feature.category === "nav" && feature.files.length > 0).filter((feature) => {
8597
8855
  const signature = resolveFeatureDestPath(feature.files[0].dest, feature.route);
8598
- return existsSync20(join21(targetDir, signature));
8856
+ return existsSync21(join22(targetDir, signature));
8599
8857
  }).map((feature) => feature.id);
8600
8858
  }
8601
8859
  function warnNavConflicts(packageRoot, config, targetDir, emit) {
@@ -8828,9 +9086,9 @@ function renderFeatureInfo(config) {
8828
9086
  return lines.join("\n");
8829
9087
  }
8830
9088
  function isDeepSpaceApp(dir) {
8831
- if (existsSync21(join22(dir, "wrangler.toml"))) return true;
8832
- const packagePath = join22(dir, "package.json");
8833
- if (!existsSync21(packagePath)) return false;
9089
+ if (existsSync22(join23(dir, "wrangler.toml"))) return true;
9090
+ const packagePath = join23(dir, "package.json");
9091
+ if (!existsSync22(packagePath)) return false;
8834
9092
  try {
8835
9093
  const pkg2 = JSON.parse(readFileSync19(packagePath, "utf-8"));
8836
9094
  return Boolean(pkg2.dependencies?.deepspace || pkg2.devDependencies?.deepspace);
@@ -8841,8 +9099,8 @@ function isDeepSpaceApp(dir) {
8841
9099
  function installedPackageRoot(fromDir) {
8842
9100
  let dir = fromDir;
8843
9101
  for (; ; ) {
8844
- const candidate = join22(dir, "node_modules", "deepspace");
8845
- if (existsSync21(join22(candidate, "package.json"))) return candidate;
9102
+ const candidate = join23(dir, "node_modules", "deepspace");
9103
+ if (existsSync22(join23(candidate, "package.json"))) return candidate;
8846
9104
  const parent = dirname10(dir);
8847
9105
  if (parent === dir) return null;
8848
9106
  dir = parent;
@@ -8859,9 +9117,9 @@ async function targetInstaller(appDir) {
8859
9117
  if (realpathSync4(installedRoot) === realpathSync4(featurePackageRoot())) {
8860
9118
  return { installFeature };
8861
9119
  }
8862
- const entry = join22(installedRoot, "dist", "feature-installer.js");
8863
- const catalog = join22(installedRoot, "features");
8864
- if (!existsSync21(entry) || !existsSync21(catalog)) {
9120
+ const entry = join23(installedRoot, "dist", "feature-installer.js");
9121
+ const catalog = join23(installedRoot, "features");
9122
+ if (!existsSync22(entry) || !existsSync22(catalog)) {
8865
9123
  throw new Refusal(
8866
9124
  `The app's installed deepspace package does not contain the typed feature installer.
8867
9125
  Expected: ${entry}
@@ -8954,7 +9212,7 @@ var add_default = defineDeepspaceCommand({
8954
9212
  return { data: { features: featureCatalog(root) } };
8955
9213
  }
8956
9214
  const appDir = resolve17(args.dir ?? ".");
8957
- if (!existsSync21(appDir)) {
9215
+ if (!existsSync22(appDir)) {
8958
9216
  throw new Refusal(
8959
9217
  `Target directory not found: ${appDir}
8960
9218
  Create a new app first: deepspace app create <name>`,
@@ -9012,7 +9270,7 @@ Create one with: deepspace app create <name>`,
9012
9270
  // src/cli/commands/domain.ts
9013
9271
  import { defineCommand as defineCommand5 } from "citty";
9014
9272
  var API_URL4 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
9015
- var DEPLOY_URL7 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
9273
+ var DEPLOY_URL8 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
9016
9274
  var api = (token, path, init) => apiFetch(API_URL4, token, path, init);
9017
9275
  function scrubInternal(value) {
9018
9276
  if (Array.isArray(value)) return value.map(scrubInternal);
@@ -9093,7 +9351,7 @@ var buy = defineDeepspaceCommand({
9093
9351
  async run({ args }) {
9094
9352
  const domain = String(args.domain);
9095
9353
  const token = await ensureToken();
9096
- const appId = await resolveAppTarget(DEPLOY_URL7, token, args.app);
9354
+ const appId = await resolveAppTarget(DEPLOY_URL8, token, args.app);
9097
9355
  const priceCheck = await api(token, "/api/domains/check", {
9098
9356
  method: "POST",
9099
9357
  body: JSON.stringify({ domains: [domain] })
@@ -9246,7 +9504,7 @@ var attach = defineDeepspaceCommand({
9246
9504
  const domain = String(args.domain);
9247
9505
  const token = await ensureToken();
9248
9506
  const targetAppId = await resolveAppTarget(
9249
- DEPLOY_URL7,
9507
+ DEPLOY_URL8,
9250
9508
  token,
9251
9509
  args.app
9252
9510
  );
@@ -9330,10 +9588,10 @@ var domain_default = defineCommand5({
9330
9588
  // src/cli/commands/app-files.ts
9331
9589
  import { defineCommand as defineCommand6 } from "citty";
9332
9590
  import { basename as basename4, resolve as resolve18 } from "path";
9333
- import { existsSync as existsSync22, statSync as statSync2 } from "fs";
9591
+ import { existsSync as existsSync23, statSync as statSync4 } from "fs";
9334
9592
 
9335
9593
  // src/cli/lib/app-files-api.ts
9336
- import { createWriteStream, openAsBlob, statSync } from "fs";
9594
+ import { createWriteStream, openAsBlob, statSync as statSync3 } from "fs";
9337
9595
  import { basename as basename3, extname } from "path";
9338
9596
  import { Readable } from "stream";
9339
9597
  import { pipeline } from "stream/promises";
@@ -9443,7 +9701,7 @@ Content-Type: ${contentType}\r
9443
9701
  }
9444
9702
  async function uploadAppFile(baseUrl, token, appId, localPath, key, onProgress) {
9445
9703
  const path = filesPath(appId, `/upload?key=${encodeKeyPath(key)}`);
9446
- const size = statSync(localPath).size;
9704
+ const size = statSync3(localPath).size;
9447
9705
  if (size > MAX_APP_FILE_BYTES) {
9448
9706
  throw new ApiError(oversizeMessage(size), 0, "too_large", path);
9449
9707
  }
@@ -9561,11 +9819,11 @@ async function downloadAppFile(baseUrl, token, appId, key, destination) {
9561
9819
  }
9562
9820
 
9563
9821
  // src/cli/commands/app-files.ts
9564
- var DEPLOY_URL8 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
9822
+ var DEPLOY_URL9 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
9565
9823
  var PLATFORM_URL = process.env.DEEPSPACE_PLATFORM_URL ?? PLATFORM_URLS.platform;
9566
9824
  async function target(app2) {
9567
9825
  const token = await ensureToken();
9568
- const appId = await resolveAppTarget(DEPLOY_URL8, token, app2);
9826
+ const appId = await resolveAppTarget(DEPLOY_URL9, token, app2);
9569
9827
  return { token, appId };
9570
9828
  }
9571
9829
  var MAX_LIST_LIMIT = 1e3;
@@ -9609,7 +9867,7 @@ var put = defineDeepspaceCommand({
9609
9867
  },
9610
9868
  async run({ args }) {
9611
9869
  const localPath = resolve18(String(args.file));
9612
- if (!existsSync22(localPath) || !statSync2(localPath).isFile()) {
9870
+ if (!existsSync23(localPath) || !statSync4(localPath).isFile()) {
9613
9871
  throw new Refusal(`No such file: ${args.file}`, "file_not_found");
9614
9872
  }
9615
9873
  const key = requireKey(args.key ?? basename4(localPath));
@@ -9752,7 +10010,7 @@ function isTestAccountEmail(email) {
9752
10010
 
9753
10011
  // src/cli/commands/collaborators.ts
9754
10012
  var API_URL5 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
9755
- var DEPLOY_URL9 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
10013
+ var DEPLOY_URL10 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
9756
10014
  var COLLABORATOR_GRANTS = ["deploy", "secrets:read", "secrets:write"];
9757
10015
  var COLLABORATOR_GRANT_SENTENCE = `grants ${COLLABORATOR_GRANTS.join(", ")} \u2014 they can deploy the app AND read and change every app secret in plaintext; they cannot undeploy or transfer it`;
9758
10016
  function api2(token, path, init) {
@@ -9765,7 +10023,7 @@ var list4 = defineDeepspaceCommand({
9765
10023
  },
9766
10024
  async run({ args }) {
9767
10025
  const token = await ensureToken();
9768
- const app2 = await resolveAppTarget(DEPLOY_URL9, token, args.app);
10026
+ const app2 = await resolveAppTarget(DEPLOY_URL10, token, args.app);
9769
10027
  const { collaborators, pending = [] } = await api2(token, `/api/app-collaborators/${encodeURIComponent(app2)}`);
9770
10028
  if (!collaborators.length && !pending.length) {
9771
10029
  if (!args.json) {
@@ -9782,8 +10040,8 @@ var list4 = defineDeepspaceCommand({
9782
10040
  }
9783
10041
  if (pending.length) {
9784
10042
  console.log(`PENDING INVITES ON ${app2}`);
9785
- for (const p36 of pending) {
9786
- console.log(` ${p36.email} (expires ${new Date(p36.expiresAt).toLocaleDateString()})`);
10043
+ for (const p35 of pending) {
10044
+ console.log(` ${p35.email} (expires ${new Date(p35.expiresAt).toLocaleDateString()})`);
9787
10045
  }
9788
10046
  }
9789
10047
  }
@@ -9806,7 +10064,7 @@ var add = defineDeepspaceCommand({
9806
10064
  async run({ args }) {
9807
10065
  const email = String(args.email);
9808
10066
  const token = await ensureToken();
9809
- const app2 = await resolveAppTarget(DEPLOY_URL9, token, args.app);
10067
+ const app2 = await resolveAppTarget(DEPLOY_URL10, token, args.app);
9810
10068
  if (isTestAccountEmail(email)) {
9811
10069
  throw new Refusal(
9812
10070
  "Test accounts (@deepspace.test) cannot be added as collaborators. Use a real DeepSpace account.",
@@ -9853,7 +10111,7 @@ var remove = defineDeepspaceCommand({
9853
10111
  async run({ args }) {
9854
10112
  const email = String(args.email);
9855
10113
  const token = await ensureToken();
9856
- const app2 = await resolveAppTarget(DEPLOY_URL9, token, args.app);
10114
+ const app2 = await resolveAppTarget(DEPLOY_URL10, token, args.app);
9857
10115
  const { collaborators } = await api2(
9858
10116
  token,
9859
10117
  `/api/app-collaborators/${encodeURIComponent(app2)}`
@@ -9883,13 +10141,13 @@ var cancel3 = defineDeepspaceCommand({
9883
10141
  async run({ args }) {
9884
10142
  const email = String(args.email);
9885
10143
  const token = await ensureToken();
9886
- const app2 = await resolveAppTarget(DEPLOY_URL9, token, args.app);
10144
+ const app2 = await resolveAppTarget(DEPLOY_URL10, token, args.app);
9887
10145
  const { pending = [] } = await api2(
9888
10146
  token,
9889
10147
  `/api/app-collaborators/${encodeURIComponent(app2)}`
9890
10148
  );
9891
10149
  const target2 = email.trim().toLowerCase();
9892
- const found = pending.find((p36) => p36.email.toLowerCase() === target2);
10150
+ const found = pending.find((p35) => p35.email.toLowerCase() === target2);
9893
10151
  if (!found) {
9894
10152
  throw new Refusal(`${email} has no pending invite on ${app2}`, "no_pending_invite", {
9895
10153
  action: cliAction("deepspace", "app", "collaborators", "list", "--app", app2)
@@ -9963,7 +10221,7 @@ var collaborators_default = defineCommand7({
9963
10221
  import { defineCommand as defineCommand8 } from "citty";
9964
10222
  import * as p14 from "@clack/prompts";
9965
10223
  var API_URL6 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
9966
- var DEPLOY_URL10 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
10224
+ var DEPLOY_URL11 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
9967
10225
  var offer = defineDeepspaceCommand({
9968
10226
  meta: { name: "offer", description: "Offer this app to another DeepSpace user" },
9969
10227
  args: {
@@ -9977,7 +10235,7 @@ var offer = defineDeepspaceCommand({
9977
10235
  async run({ args }) {
9978
10236
  const email = String(args.email);
9979
10237
  const token = await ensureToken();
9980
- const app2 = await resolveAppTarget(DEPLOY_URL10, token, args.app);
10238
+ const app2 = await resolveAppTarget(DEPLOY_URL11, token, args.app);
9981
10239
  const pending = await apiFetch(
9982
10240
  API_URL6,
9983
10241
  token,
@@ -10033,7 +10291,7 @@ var status2 = defineDeepspaceCommand({
10033
10291
  },
10034
10292
  async run({ args }) {
10035
10293
  const token = await ensureToken();
10036
- const app2 = await resolveAppTarget(DEPLOY_URL10, token, args.app);
10294
+ const app2 = await resolveAppTarget(DEPLOY_URL11, token, args.app);
10037
10295
  const { transfer } = await apiFetch(API_URL6, token, `/api/app-transfers/${encodeURIComponent(app2)}`);
10038
10296
  if (!transfer) {
10039
10297
  if (!args.json) console.log(`No pending transfer for ${app2}.`);
@@ -10062,7 +10320,7 @@ var accept2 = defineDeepspaceCommand({
10062
10320
  const msg = err instanceof Error ? err.message : String(err);
10063
10321
  throw new Refusal(msg, msg.startsWith("No app id") ? "not_in_app_repo" : "invalid_app");
10064
10322
  }
10065
- const { replayed, acceptedAt, inheritedCollaborators } = await apiFetch(DEPLOY_URL10, token, `/api/apps/${encodeURIComponent(app2)}/transfer/accept`, {
10323
+ const { replayed, acceptedAt, inheritedCollaborators } = await apiFetch(DEPLOY_URL11, token, `/api/apps/${encodeURIComponent(app2)}/transfer/accept`, {
10066
10324
  method: "POST"
10067
10325
  });
10068
10326
  if (!args.json) {
@@ -10097,7 +10355,7 @@ var cancel4 = defineDeepspaceCommand({
10097
10355
  },
10098
10356
  async run({ args }) {
10099
10357
  const token = await ensureToken();
10100
- const app2 = await resolveAppTarget(DEPLOY_URL10, token, args.app);
10358
+ const app2 = await resolveAppTarget(DEPLOY_URL11, token, args.app);
10101
10359
  await apiFetch(API_URL6, token, `/api/app-transfers/${encodeURIComponent(app2)}`, { method: "DELETE" });
10102
10360
  if (!args.json) console.log(`\u2713 Transfer offer for ${app2} cancelled.`);
10103
10361
  return { data: { app: app2, cancelled: true } };
@@ -10491,8 +10749,8 @@ var integrations_default = defineCommand9({
10491
10749
 
10492
10750
  // src/cli/commands/feedback.ts
10493
10751
  import * as p16 from "@clack/prompts";
10494
- import { existsSync as existsSync23, readFileSync as readFileSync21 } from "fs";
10495
- import { dirname as dirname11, join as join23 } from "path";
10752
+ import { existsSync as existsSync24, readFileSync as readFileSync21 } from "fs";
10753
+ import { dirname as dirname11, join as join24 } from "path";
10496
10754
  import { fileURLToPath as fileURLToPath2 } from "url";
10497
10755
  import { release } from "os";
10498
10756
  var API_URL8 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
@@ -10515,11 +10773,11 @@ function readCliVersion() {
10515
10773
  try {
10516
10774
  const here = dirname11(fileURLToPath2(import.meta.url));
10517
10775
  const candidates = [
10518
- join23(here, "..", "package.json"),
10519
- join23(here, "..", "..", "..", "package.json")
10776
+ join24(here, "..", "package.json"),
10777
+ join24(here, "..", "..", "..", "package.json")
10520
10778
  ];
10521
10779
  for (const path of candidates) {
10522
- if (!existsSync23(path)) continue;
10780
+ if (!existsSync24(path)) continue;
10523
10781
  const pkg2 = JSON.parse(readFileSync21(path, "utf-8"));
10524
10782
  if (pkg2.name === "deepspace" && pkg2.version) return pkg2.version;
10525
10783
  }
@@ -10750,7 +11008,7 @@ function parseCursorArg(raw) {
10750
11008
  }
10751
11009
 
10752
11010
  // src/cli/commands/secrets.ts
10753
- var DEPLOY_URL11 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
11011
+ var DEPLOY_URL12 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
10754
11012
  var COMMON_ARGS = {
10755
11013
  app: {
10756
11014
  type: "string",
@@ -10776,12 +11034,13 @@ var COMMON_ARGS = {
10776
11034
  default: false
10777
11035
  }
10778
11036
  };
10779
- async function resolveTarget(args) {
10780
- assertAppTargetResolvable(args.app, { wranglerEnv: args.env });
11037
+ async function resolveTarget(args, opts = {}) {
11038
+ const register = opts.register === true;
11039
+ assertAppTargetResolvable(args.app, { wranglerEnv: args.env, register });
10781
11040
  const { wranglerEnv } = parseWranglerEnvArg(args.env);
10782
11041
  const configName = validateConfigName(args.config?.trim() || defaultConfigNameForEnv(wranglerEnv));
10783
11042
  const token = await ensureToken();
10784
- const appId = await resolveAppTarget(DEPLOY_URL11, token, args.app, { wranglerEnv });
11043
+ const appId = await resolveAppTarget(DEPLOY_URL12, token, args.app, { wranglerEnv, register });
10785
11044
  return { appId, configName, token };
10786
11045
  }
10787
11046
  function ok(json, data, human) {
@@ -10810,7 +11069,7 @@ var list6 = defineCommand10({
10810
11069
  },
10811
11070
  async run({ args }) {
10812
11071
  const t = await resolveTarget(args);
10813
- const { secrets } = await listSecrets(DEPLOY_URL11, t.token, t.appId, t.configName);
11072
+ const { secrets } = await listSecrets(DEPLOY_URL12, t.token, t.appId, t.configName);
10814
11073
  if (args.json) {
10815
11074
  console.log(JSON.stringify({ ok: true, appId: t.appId, config: t.configName, secrets }));
10816
11075
  return;
@@ -10831,7 +11090,7 @@ var set = defineCommand10({
10831
11090
  secret: { type: "positional", description: "KEY=value (repeatable)", required: true }
10832
11091
  },
10833
11092
  async run({ args }) {
10834
- const t = await resolveTarget(args);
11093
+ const t = await resolveTarget(args, { register: true });
10835
11094
  const pairs = dedupePositionals(args.secret, args._);
10836
11095
  const secrets = {};
10837
11096
  const dupes = [];
@@ -10851,9 +11110,9 @@ var set = defineCommand10({
10851
11110
  }
10852
11111
  if (Object.keys(secrets).length === 1) {
10853
11112
  const [[k, v]] = Object.entries(secrets);
10854
- await setSecret(DEPLOY_URL11, t.token, t.appId, t.configName, k, v);
11113
+ await setSecret(DEPLOY_URL12, t.token, t.appId, t.configName, k, v);
10855
11114
  } else {
10856
- await uploadSecrets(DEPLOY_URL11, t.token, t.appId, t.configName, secrets, false);
11115
+ await uploadSecrets(DEPLOY_URL12, t.token, t.appId, t.configName, secrets, false);
10857
11116
  }
10858
11117
  const n = Object.keys(secrets).length;
10859
11118
  ok(
@@ -10886,11 +11145,11 @@ var get2 = defineCommand10({
10886
11145
  const t = await resolveTarget(args);
10887
11146
  const key = validateSecretName(args.key);
10888
11147
  if (args.plain) {
10889
- const { value } = await getSecretPlain(DEPLOY_URL11, t.token, t.appId, t.configName, key);
11148
+ const { value } = await getSecretPlain(DEPLOY_URL12, t.token, t.appId, t.configName, key);
10890
11149
  process.stdout.write(process.stdout.isTTY ? value + "\n" : value);
10891
11150
  return;
10892
11151
  }
10893
- const { secrets } = await listSecrets(DEPLOY_URL11, t.token, t.appId, t.configName);
11152
+ const { secrets } = await listSecrets(DEPLOY_URL12, t.token, t.appId, t.configName);
10894
11153
  const item = secrets.find((s) => s.key === key);
10895
11154
  if (!item)
10896
11155
  throw new InputError(`Secret "${key}" not found in ${t.configName}`, "secret_not_found");
@@ -10921,7 +11180,7 @@ var del2 = defineCommand10({
10921
11180
  for (const key of keys) {
10922
11181
  const name = validateSecretName(key);
10923
11182
  try {
10924
- const result = await deleteSecret(DEPLOY_URL11, t.token, t.appId, t.configName, name);
11183
+ const result = await deleteSecret(DEPLOY_URL12, t.token, t.appId, t.configName, name);
10925
11184
  if (result.deleted === false) absent++;
10926
11185
  else deleted++;
10927
11186
  } catch (err) {
@@ -10959,11 +11218,11 @@ var upload = defineCommand10({
10959
11218
  }
10960
11219
  },
10961
11220
  async run({ args }) {
10962
- const t = await resolveTarget(args);
11221
+ const t = await resolveTarget(args, { register: true });
10963
11222
  const secrets = parseSecretsUpload(await readUploadSource(String(args.file)));
10964
11223
  if (Object.keys(secrets).length === 0)
10965
11224
  throw new InputError("No secrets found in the input.", "empty_input");
10966
- await uploadSecrets(DEPLOY_URL11, t.token, t.appId, t.configName, secrets, args.replace);
11225
+ await uploadSecrets(DEPLOY_URL12, t.token, t.appId, t.configName, secrets, args.replace);
10967
11226
  const uploaded = Object.keys(secrets);
10968
11227
  ok(
10969
11228
  args.json === true,
@@ -11038,7 +11297,7 @@ var download = defineCommand10({
11038
11297
  "invalid_format"
11039
11298
  );
11040
11299
  }
11041
- const { secrets } = await fetchSecretsValues(DEPLOY_URL11, t.token, t.appId, t.configName);
11300
+ const { secrets } = await fetchSecretsValues(DEPLOY_URL12, t.token, t.appId, t.configName);
11042
11301
  process.stdout.write(formatSecretsDownload(secrets, format));
11043
11302
  }
11044
11303
  });
@@ -11053,10 +11312,10 @@ var pull = defineCommand10({
11053
11312
  "Run from a DeepSpace app directory (one containing wrangler.toml).",
11054
11313
  "not_in_app_repo"
11055
11314
  );
11056
- const t = await resolveTarget(args);
11315
+ const t = await resolveTarget(args, { register: true });
11057
11316
  const ownerId = decodeJwtPayload(t.token).sub;
11058
11317
  const refreshed = await refreshSecretsCache(
11059
- DEPLOY_URL11,
11318
+ DEPLOY_URL12,
11060
11319
  t.token,
11061
11320
  t.appId,
11062
11321
  wranglerEnv,
@@ -11081,7 +11340,7 @@ var configsList = defineCommand10({
11081
11340
  args: { ...COMMON_ARGS },
11082
11341
  async run({ args }) {
11083
11342
  const t = await resolveTarget(args);
11084
- const { configs: configs2 } = await listConfigs(DEPLOY_URL11, t.token, t.appId);
11343
+ const { configs: configs2 } = await listConfigs(DEPLOY_URL12, t.token, t.appId);
11085
11344
  ok(args.json === true, { appId: t.appId, configs: configs2 }, () => {
11086
11345
  if (configs2.length === 0) {
11087
11346
  console.log("No configs yet \u2014 the first `secrets set` creates one.");
@@ -11101,10 +11360,10 @@ var configsCreate = defineCommand10({
11101
11360
  "copy-from": { type: "string", description: "Copy secrets from this config", required: false }
11102
11361
  },
11103
11362
  async run({ args }) {
11104
- const t = await resolveTarget(args);
11363
+ const t = await resolveTarget(args, { register: true });
11105
11364
  const name = validateConfigName(args.name);
11106
11365
  const copyFrom = args["copy-from"] || void 0;
11107
- const { configs: configs2 } = await listConfigs(DEPLOY_URL11, t.token, t.appId);
11366
+ const { configs: configs2 } = await listConfigs(DEPLOY_URL12, t.token, t.appId);
11108
11367
  if (configs2.some((c) => c.name === name)) {
11109
11368
  if (copyFrom) {
11110
11369
  throw new InputError(
@@ -11119,7 +11378,7 @@ var configsCreate = defineCommand10({
11119
11378
  );
11120
11379
  return;
11121
11380
  }
11122
- await createConfig(DEPLOY_URL11, t.token, t.appId, name, copyFrom);
11381
+ await createConfig(DEPLOY_URL12, t.token, t.appId, name, copyFrom);
11123
11382
  ok(
11124
11383
  args.json === true,
11125
11384
  { appId: t.appId, config: name, created: true, ...copyFrom ? { copyFrom } : {} },
@@ -11151,7 +11410,7 @@ var configsDelete = defineCommand10({
11151
11410
  let secrets = [];
11152
11411
  let counted = true;
11153
11412
  try {
11154
- secrets = (await listSecrets(DEPLOY_URL11, t.token, t.appId, name)).secrets;
11413
+ secrets = (await listSecrets(DEPLOY_URL12, t.token, t.appId, name)).secrets;
11155
11414
  } catch (error) {
11156
11415
  if (args.yes !== true) throw error;
11157
11416
  counted = false;
@@ -11167,7 +11426,7 @@ var configsDelete = defineCommand10({
11167
11426
  throw new InputError("Cancelled.", "confirmation_required");
11168
11427
  }
11169
11428
  }
11170
- await deleteConfig(DEPLOY_URL11, t.token, t.appId, name);
11429
+ await deleteConfig(DEPLOY_URL12, t.token, t.appId, name);
11171
11430
  ok(
11172
11431
  args.json === true,
11173
11432
  { appId: t.appId, config: name, deleted: true, secretsDeleted: counted ? count : null },
@@ -11196,32 +11455,7 @@ var secrets_default = defineCommand10({
11196
11455
  });
11197
11456
 
11198
11457
  // src/cli/commands/init.ts
11199
- import { existsSync as existsSync24 } from "fs";
11200
- import { join as join24 } from "path";
11201
- var DEPLOY_URL12 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
11202
- function wranglerConfigUncommitted(appDir) {
11203
- if (!existsSync24(join24(appDir, ".git"))) return false;
11204
- const dryRun = runGit(appDir, ["commit", "--dry-run", "-m", "x", "--", "wrangler.toml"], {
11205
- allowFail: true
11206
- });
11207
- return dryRun.status === 0;
11208
- }
11209
- function commitScaffoldIfUnborn(appDir, token) {
11210
- try {
11211
- if (!existsSync24(join24(appDir, ".git"))) return false;
11212
- if (runGit(appDir, ["rev-parse", "--verify", "HEAD"], { allowFail: true }).status === 0) {
11213
- return false;
11214
- }
11215
- ensureGitIdentity(appDir, token);
11216
- if (runGit(appDir, ["add", "-A"], { allowFail: true }).status !== 0) return false;
11217
- const commit = runGit(appDir, ["commit", "-m", "Initial DeepSpace scaffold", "--no-verify"], {
11218
- allowFail: true
11219
- });
11220
- return commit.status === 0;
11221
- } catch {
11222
- return false;
11223
- }
11224
- }
11458
+ var DEPLOY_URL13 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
11225
11459
  var init_default = defineDeepspaceCommand({
11226
11460
  meta: {
11227
11461
  name: "init",
@@ -11262,7 +11496,7 @@ var init_default = defineDeepspaceCommand({
11262
11496
  const token2 = await ensureToken();
11263
11497
  let state;
11264
11498
  try {
11265
- state = await getAppSource(DEPLOY_URL12, token2, existing);
11499
+ state = await getAppSource(DEPLOY_URL13, token2, existing);
11266
11500
  } catch (error) {
11267
11501
  if (error instanceof ApiError && error.code === "forbidden") {
11268
11502
  throw new Refusal(
@@ -11287,15 +11521,17 @@ var init_default = defineDeepspaceCommand({
11287
11521
  }
11288
11522
  );
11289
11523
  }
11524
+ const healedScaffold = commitScaffoldIfUnborn(appDir, token2);
11290
11525
  if (!args.json) {
11291
11526
  console.log(
11292
- `Already initialized: ${existing}${envName ? ` (env: ${envName})` : ""} \u2014 registered on ${DEEPSPACE_ENV}`
11527
+ `Already initialized: ${existing}${envName ? ` (env: ${envName})` : ""} \u2014 registered on ${DEEPSPACE_ENV}` + (healedScaffold ? " \u2014 initial scaffold commit created." : "")
11293
11528
  );
11294
11529
  console.log(`App dir: ${appDir}`);
11295
11530
  }
11296
11531
  return {
11297
11532
  data: {
11298
11533
  status: "already_initialized",
11534
+ ...healedScaffold ? { committedScaffold: true } : {},
11299
11535
  appId: existing,
11300
11536
  appDir,
11301
11537
  env: DEEPSPACE_ENV,
@@ -11304,11 +11540,11 @@ var init_default = defineDeepspaceCommand({
11304
11540
  };
11305
11541
  }
11306
11542
  const token = await ensureToken();
11307
- const { appId } = await apiFetch(DEPLOY_URL12, token, "/api/apps/mint", {
11308
- method: "POST"
11543
+ const { appId, committedScaffold } = await mintAppIdentity(appDir, token, {
11544
+ wranglerEnv: envName,
11545
+ force: Boolean(args["new-id"]),
11546
+ commitScaffold: true
11309
11547
  });
11310
- writeAppId(appDir, appId, { wranglerEnv: envName, force: Boolean(args["new-id"]) });
11311
- const committedScaffold = commitScaffoldIfUnborn(appDir, token);
11312
11548
  if (!args.json) {
11313
11549
  const envSuffix = envName ? ` (env: ${envName})` : "";
11314
11550
  const commitNote = committedScaffold ? " \u2014 initial scaffold commit created." : " \u2014 commit wrangler.toml.";
@@ -11484,7 +11720,7 @@ var status_default = defineCommand11({
11484
11720
  const { appName, appId, appIdError } = resolveStatusApp(appDir, wranglerEnv);
11485
11721
  lines.push([
11486
11722
  "App",
11487
- `${appName}${wranglerEnv ? ` [env.${wranglerEnv}]` : ""}${appId ? ` (${appId})` : appIdError ? ` \u2014 ${appIdError}` : " \u2014 no DEEPSPACE_APP_ID; run `deepspace app init`"}`
11723
+ `${appName}${wranglerEnv ? ` [env.${wranglerEnv}]` : ""}${appId ? ` (${appId})` : appIdError ? ` \u2014 ${appIdError}` : " \u2014 no DEEPSPACE_APP_ID yet; it registers on first use (deploy, secrets, dev\u2026)"}`
11488
11724
  ]);
11489
11725
  json.inApp = true;
11490
11726
  json.appName = appName;
@@ -11495,7 +11731,7 @@ var status_default = defineCommand11({
11495
11731
  const depsInstallCommand = installCommand(appDir);
11496
11732
  lines.push([
11497
11733
  "Deps",
11498
- deps === "ready" ? "installed" : deps === "installing" ? "dependency installation is still running" : `${deps} \u2014 run ${depsInstallCommand}`
11734
+ deps === "ready" ? "installed" : deps === "installing" ? "dependency installation is still running" : deps === "missing" ? "missing \u2014 any command that needs them installs on first use" : `failed \u2014 retried automatically by the next dev/test/deploy (or run ${depsInstallCommand})`
11499
11735
  ]);
11500
11736
  json.deps = deps;
11501
11737
  let branch = null;
@@ -11573,7 +11809,10 @@ var status_default = defineCommand11({
11573
11809
  createdAt: rel.createdAt,
11574
11810
  byYou,
11575
11811
  source: rel.source,
11576
- sourceRevision: rel.sourceRevision
11812
+ sourceRevision: rel.sourceRevision,
11813
+ // The human line can say "dirty worktree"; the machine mirror
11814
+ // must be able to say the same.
11815
+ sourceDirty: rel.sourceDirty ?? null
11577
11816
  };
11578
11817
  if (!entry) {
11579
11818
  lines.push(["Live", `${release2} \xB7 serving state not visible from your account`]);
@@ -11604,8 +11843,13 @@ var status_default = defineCommand11({
11604
11843
  ]);
11605
11844
  json.source = { ...source, revision: sourceResult.value.revision };
11606
11845
  } else {
11607
- lines.push(["Source", "unclaimed \xB7 next normal deploy defaults to DeepSpace"]);
11846
+ const github = externalGitSource(appDir, null);
11847
+ lines.push([
11848
+ "Source",
11849
+ `unclaimed \xB7 ${github ? "next deploy infers GitHub from this checkout\u2019s remote" : "next deploy syncs to DeepSpace and claims it, permanently"}`
11850
+ ]);
11608
11851
  json.source = null;
11852
+ json.sourceInference = github ? "github" : "deepspace";
11609
11853
  }
11610
11854
  const [refsResult, wsResult, releaseResult, appsResult] = await Promise.all([
11611
11855
  githubSource ? Promise.resolve({ ok: true, value: null }) : observeRemote(api4.getRefs()),
@@ -11775,9 +12019,9 @@ var push_default = defineDeepspaceCommand({
11775
12019
  const spinner2 = args.json ? null : createSpinner();
11776
12020
  spinner2?.start(`Checking ${branch} before push\u2026`);
11777
12021
  const resolveTarget3 = async () => {
11778
- assertAppTargetResolvable(appArg);
12022
+ assertAppTargetResolvable(appArg, { register: true });
11779
12023
  const token2 = await ensureToken();
11780
- const appId2 = await resolveAppTarget(deployBaseUrl(), token2, appArg);
12024
+ const appId2 = await resolveAppTarget(deployBaseUrl(), token2, appArg, { register: true });
11781
12025
  await warnIfPhantomApp(deployBaseUrl(), token2, appId2, appArg?.trim() || void 0);
11782
12026
  return { token: token2, appId: appId2 };
11783
12027
  };
@@ -11850,6 +12094,12 @@ var push_default = defineDeepspaceCommand({
11850
12094
  if (sourceState.source?.provider === "github") {
11851
12095
  throw githubSourceRefusal(appId, sourceState.source.repository);
11852
12096
  }
12097
+ if (sourceState.source === null) {
12098
+ process.stderr.write(
12099
+ `This app's source is unclaimed \u2014 this push claims DeepSpace source for it, permanently (a checkout that deploys from GitHub instead should not run \`deepspace push\`).
12100
+ `
12101
+ );
12102
+ }
11853
12103
  const pullRecoveryCwd = selectedBranchCheckout(appDir, branch) ?? appDir;
11854
12104
  ensureSpaceRemote(appDir, appId, void 0, token);
11855
12105
  if (force) {
@@ -11939,7 +12189,21 @@ var push_default = defineDeepspaceCommand({
11939
12189
  extra: { status: result.status, appId, branch, oid: tipOid }
11940
12190
  });
11941
12191
  }
11942
- return { data: { status: result.status, appId, branch, oid: tipOid } };
12192
+ const uncommitted = appDir ? statusFiles(appDir) : [];
12193
+ if (!args.json && uncommitted.length > 0) {
12194
+ p19.log.warn(
12195
+ `${uncommitted.length} uncommitted file${uncommitted.length === 1 ? "" : "s"} NOT pushed \u2014 push publishes committed history only. Commit and push again to publish them.`
12196
+ );
12197
+ }
12198
+ return {
12199
+ data: {
12200
+ status: result.status,
12201
+ appId,
12202
+ branch,
12203
+ oid: tipOid,
12204
+ ...uncommitted.length > 0 ? { uncommitted } : {}
12205
+ }
12206
+ };
11943
12207
  } catch (err) {
11944
12208
  if (err instanceof Refusal) throw err;
11945
12209
  const transportFailure = classifyPushTransportFailure(err, appDir ?? void 0);
@@ -12236,7 +12500,7 @@ var pull_default = defineDeepspaceCommand({
12236
12500
 
12237
12501
  // src/cli/commands/clone.ts
12238
12502
  import * as p21 from "@clack/prompts";
12239
- import { existsSync as existsSync26, readdirSync as readdirSync8, rmSync as rmSync6 } from "fs";
12503
+ import { existsSync as existsSync26, readdirSync as readdirSync8, rmSync as rmSync8 } from "fs";
12240
12504
  import { resolve as resolve19, basename as basename5, join as join25 } from "path";
12241
12505
  function cleanFailedCloneDir(dir) {
12242
12506
  let entries;
@@ -12247,7 +12511,7 @@ function cleanFailedCloneDir(dir) {
12247
12511
  }
12248
12512
  if (entries.length === 1 && entries[0] === ".git") {
12249
12513
  try {
12250
- rmSync6(join25(dir, ".git"), { recursive: true, force: true });
12514
+ rmSync8(join25(dir, ".git"), { recursive: true, force: true });
12251
12515
  return [];
12252
12516
  } catch {
12253
12517
  return entries;
@@ -12378,7 +12642,7 @@ async function actorLabels(token, appId) {
12378
12642
  }
12379
12643
 
12380
12644
  // src/cli/commands/releases.ts
12381
- var DEPLOY_URL13 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
12645
+ var DEPLOY_URL14 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
12382
12646
  var releases_default = defineDeepspaceCommand({
12383
12647
  meta: {
12384
12648
  name: "releases",
@@ -12413,8 +12677,8 @@ var releases_default = defineDeepspaceCommand({
12413
12677
  const spinner2 = args.json ? null : createSpinner();
12414
12678
  spinner2?.start("Loading release history\u2026");
12415
12679
  const token = await ensureToken();
12416
- const appId = await resolveAppTarget(DEPLOY_URL13, token, appArg, { wranglerEnv: envArg });
12417
- const { releases } = await repoApi(DEPLOY_URL13, token, appId).listReleases(limit);
12680
+ const appId = await resolveAppTarget(DEPLOY_URL14, token, appArg, { wranglerEnv: envArg });
12681
+ const { releases } = await repoApi(DEPLOY_URL14, token, appId).listReleases(limit);
12418
12682
  spinner2?.stop(`Loaded ${releases.length} ${releases.length === 1 ? "release" : "releases"}.`);
12419
12683
  if (!args.json) {
12420
12684
  if (releases.length === 0) {
@@ -12422,7 +12686,7 @@ var releases_default = defineDeepspaceCommand({
12422
12686
  } else {
12423
12687
  const actors = await actorLabels(token, appId);
12424
12688
  for (const r of releases) {
12425
- const source = releaseSourceLabel(r);
12689
+ const source = displayText(releaseSourceLabel(r));
12426
12690
  const rollback = r.rollbackAvailable ? "rollback available" : "rollback unavailable";
12427
12691
  console.log(
12428
12692
  `#${r.seq} ${r.id} ${r.kind.padEnd(8)} ${r.createdAt} ${actors.get(r.actor) ?? r.actor} ${source} ${rollback}`
@@ -12447,7 +12711,7 @@ Retry in a moment, or re-run with --allow-do-deletion to accept the risk.`,
12447
12711
  "do_guard_unavailable"
12448
12712
  );
12449
12713
  }
12450
- var DEPLOY_URL14 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
12714
+ var DEPLOY_URL15 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
12451
12715
  function pickPreviousRelease(releases) {
12452
12716
  if (releases.length === 0) {
12453
12717
  return { error: "No releases recorded yet \u2014 nothing to roll back to.", code: "no_releases" };
@@ -12509,8 +12773,8 @@ var rollback_default = defineDeepspaceCommand({
12509
12773
  const spinner2 = args.json ? null : createSpinner();
12510
12774
  spinner2?.start("Preparing rollback\u2026");
12511
12775
  const token = await ensureToken();
12512
- const appId = await resolveAppTarget(DEPLOY_URL14, token, appArg, { wranglerEnv: envArg });
12513
- const api4 = repoApi(DEPLOY_URL14, token, appId);
12776
+ const appId = await resolveAppTarget(DEPLOY_URL15, token, appArg, { wranglerEnv: envArg });
12777
+ const api4 = repoApi(DEPLOY_URL15, token, appId);
12514
12778
  let releaseId = args.release ? String(args.release).trim() : "";
12515
12779
  if (!releaseId) {
12516
12780
  const { releases } = await api4.listReleases(2);
@@ -12521,7 +12785,7 @@ var rollback_default = defineDeepspaceCommand({
12521
12785
  spinner2?.message(`Rolling back to ${releaseId}\u2026`);
12522
12786
  let result;
12523
12787
  try {
12524
- result = await apiFetch(DEPLOY_URL14, token, `/api/deploy/${appId}/rollback`, {
12788
+ result = await apiFetch(DEPLOY_URL15, token, `/api/deploy/${appId}/rollback`, {
12525
12789
  method: "POST",
12526
12790
  body: JSON.stringify({
12527
12791
  releaseId,
@@ -12733,12 +12997,12 @@ function printOverlaps(overlaps, json) {
12733
12997
  import * as p25 from "@clack/prompts";
12734
12998
  import {
12735
12999
  existsSync as existsSync27,
12736
- mkdirSync as mkdirSync8,
13000
+ mkdirSync as mkdirSync10,
12737
13001
  readFileSync as readFileSync24,
12738
13002
  realpathSync as realpathSync6,
12739
13003
  renameSync as renameSync2,
12740
- statSync as statSync3,
12741
- writeFileSync as writeFileSync7
13004
+ statSync as statSync5,
13005
+ writeFileSync as writeFileSync8
12742
13006
  } from "fs";
12743
13007
  import { dirname as dirname12, isAbsolute as isAbsolute6, join as join26, relative as relative4, sep as sep4 } from "path";
12744
13008
  var WORKSPACE_MARKER = "deepspace-workspace.json";
@@ -12772,8 +13036,8 @@ function markManagedWorkspace(worktreeRoot, workspaceId) {
12772
13036
  const marker = { version: 1, workspaceId, mode: "managed-linked" };
12773
13037
  const tempPath = `${markerPath}.${process.pid}.tmp`;
12774
13038
  try {
12775
- mkdirSync8(dirname12(markerPath), { recursive: true });
12776
- writeFileSync7(tempPath, JSON.stringify(marker) + "\n");
13039
+ mkdirSync10(dirname12(markerPath), { recursive: true });
13040
+ writeFileSync8(tempPath, JSON.stringify(marker) + "\n");
12777
13041
  renameSync2(tempPath, markerPath);
12778
13042
  return true;
12779
13043
  } catch {
@@ -12840,7 +13104,7 @@ function rematerializeWorkspaceWorktree(appDir, worktreeRoot, branch, workspaceI
12840
13104
  function inOwnLinkedWorktree(appDir, id) {
12841
13105
  if (workspaceIdFromBranch(currentBranch(appDir)) !== id) return false;
12842
13106
  try {
12843
- return statSync3(join26(repoToplevel(appDir), ".git")).isFile();
13107
+ return statSync5(join26(repoToplevel(appDir), ".git")).isFile();
12844
13108
  } catch {
12845
13109
  return false;
12846
13110
  }
@@ -13286,14 +13550,14 @@ var newWorkspaceCommand = defineDeepspaceCommand({
13286
13550
 
13287
13551
  // src/cli/commands/workspace/attach.ts
13288
13552
  import * as p27 from "@clack/prompts";
13289
- import { existsSync as existsSync28, readdirSync as readdirSync9, realpathSync as realpathSync7, rmSync as rmSync7 } from "fs";
13553
+ import { existsSync as existsSync28, readdirSync as readdirSync9, realpathSync as realpathSync7, rmSync as rmSync9 } from "fs";
13290
13554
  import { isAbsolute as isAbsolute8, resolve as resolve21 } from "path";
13291
13555
  function samePath(a, b) {
13292
- const real = (p36) => {
13556
+ const real = (p35) => {
13293
13557
  try {
13294
- return realpathSync7(p36);
13558
+ return realpathSync7(p35);
13295
13559
  } catch {
13296
- return resolve21(p36);
13560
+ return resolve21(p35);
13297
13561
  }
13298
13562
  };
13299
13563
  return real(a) === real(b);
@@ -13317,7 +13581,7 @@ function cleanFailedFreshAttachDir(dir) {
13317
13581
  return entries;
13318
13582
  }
13319
13583
  try {
13320
- rmSync7(dir, { recursive: true, force: true });
13584
+ rmSync9(dir, { recursive: true, force: true });
13321
13585
  return [];
13322
13586
  } catch {
13323
13587
  return entries.length > 0 ? entries : ["<cleanup-failed>"];
@@ -13851,7 +14115,7 @@ var workspaceStatusCommand = defineDeepspaceCommand({
13851
14115
  import * as p31 from "@clack/prompts";
13852
14116
 
13853
14117
  // src/cli/lib/validation.ts
13854
- import { sync as spawnSync7 } from "cross-spawn";
14118
+ import { sync as spawnSync8 } from "cross-spawn";
13855
14119
  import { readFileSync as readFileSync25 } from "fs";
13856
14120
  import { join as join27 } from "path";
13857
14121
  var MAX_SUMMARY_CHARS = 4e3;
@@ -13869,7 +14133,7 @@ function resolveValidationCommand(appDir, explicit) {
13869
14133
  }
13870
14134
  function runValidationCommand(appDir, command) {
13871
14135
  const started = Date.now();
13872
- const res = spawnSync7(command, [], {
14136
+ const res = spawnSync8(command, [], {
13873
14137
  cwd: appDir,
13874
14138
  shell: true,
13875
14139
  stdio: ["ignore", "pipe", "pipe"],
@@ -14537,7 +14801,8 @@ function formatEvent2(e, lands, actors) {
14537
14801
  case "release.rollback":
14538
14802
  detail = `#${Number(s.seq ?? 0)} ${releaseSourceLabel({
14539
14803
  commitOid: typeof s.commitOid === "string" ? s.commitOid : null,
14540
- source: s.sourceProvider === "github" && typeof s.sourceRepository === "string" ? { provider: "github", repository: s.sourceRepository } : s.sourceProvider === "deepspace" ? { provider: "deepspace" } : null
14804
+ source: s.sourceProvider === "github" && typeof s.sourceRepository === "string" ? { provider: "github", repository: s.sourceRepository } : s.sourceProvider === "deepspace" ? { provider: "deepspace" } : null,
14805
+ sourceDirty: typeof s.sourceDirty === "boolean" ? s.sourceDirty : null
14541
14806
  })}`;
14542
14807
  break;
14543
14808
  // Unknown kinds still print their kind + actor — never crash the feed.
@@ -14731,17 +14996,15 @@ var git_credential_default = defineCommand13({
14731
14996
  });
14732
14997
 
14733
14998
  // src/cli/commands/source.ts
14734
- import * as p34 from "@clack/prompts";
14735
- var TRANSFER_PREFIX = spacePrivateRef("source-transfer");
14736
14999
  var source_default = defineDeepspaceCommand({
14737
15000
  meta: {
14738
15001
  name: "source",
14739
- description: "Show or change the app\u2019s one authoritative Git repository"
15002
+ description: "Show the app\u2019s source authority (inferred from use, never declared)"
14740
15003
  },
14741
15004
  args: {
14742
15005
  provider: {
14743
15006
  type: "positional",
14744
- description: "Source authority: deepspace is platform-hosted and commit-first; github deploys the local working tree without Git sync (omit to show)",
15007
+ description: "No longer accepted \u2014 source is inferred, not declared",
14745
15008
  required: false
14746
15009
  },
14747
15010
  app: {
@@ -14749,71 +15012,41 @@ var source_default = defineDeepspaceCommand({
14749
15012
  alias: "a",
14750
15013
  description: "App id or subdomain name (default: current app)",
14751
15014
  required: false
14752
- },
14753
- remote: {
14754
- type: "string",
14755
- description: "GitHub remote name when it cannot be inferred (default: origin)",
14756
- required: false
14757
15015
  }
14758
15016
  },
14759
15017
  async run({ args }) {
14760
- const requested = typeof args.provider === "string" ? args.provider.trim().toLowerCase() : "";
14761
- if (requested && requested !== "github" && requested !== "deepspace") {
14762
- throw new Refusal("Source must be `github` or `deepspace`.", "invalid_source");
14763
- }
14764
- const token = await ensureToken();
14765
- const deployUrl = deployBaseUrl();
14766
- const appId = await resolveAppTarget(
14767
- deployUrl,
14768
- token,
14769
- typeof args.app === "string" ? args.app : void 0
14770
- );
14771
- const state = await getAppSource(deployUrl, token, appId);
14772
- if (!requested) {
14773
- reportSource(appId, state, args.json);
14774
- return { data: sourceData(state) };
14775
- }
14776
- const appDir = findAppDir();
14777
- if (!appDir) {
15018
+ if (typeof args.provider === "string" && args.provider.trim()) {
14778
15019
  throw new Refusal(
14779
- "Changing source requires a local app checkout with wrangler.toml and Git history.",
14780
- "not_in_app_repo"
15020
+ "Source is no longer declared. A checkout with a GitHub remote deploys as GitHub automatically, and an app becomes DeepSpace-source by publishing to it (`deepspace push`) \u2014 permanently. This command only reports.",
15021
+ "source_inferred"
14781
15022
  );
14782
15023
  }
14783
- const remoteArg = typeof args.remote === "string" ? args.remote.trim() : void 0;
14784
- const result = requested === "github" ? await moveToGitHub({ appDir, appId, token, deployUrl, state, remoteArg }) : await moveToDeepSpace({ appDir, appId, token, deployUrl, state, remoteArg });
14785
- const reconciliation = reconcileSpaceRemote(appDir, appId, result.source);
14786
- const spaceRemote = reconciliation.spaceRemote;
14787
- if (!args.json) {
14788
- p34.log.success(
14789
- result.source.provider === "github" ? `Source: GitHub \xB7 ${result.source.repository} (revision ${result.revision})` : `Source: DeepSpace (revision ${result.revision})`
14790
- );
14791
- p34.log.info(
14792
- spaceRemote === "removed" ? `Removed the \`${SPACE_REMOTE}\` git remote \u2014 DeepSpace no longer owns this repo.` : spaceRemote === "absent" ? `No \`${SPACE_REMOTE}\` git remote to remove \u2014 source operations go through GitHub.` : spaceRemote === "present" ? `The \`${SPACE_REMOTE}\` git remote points at this app's cloud repo.` : `The local \`${SPACE_REMOTE}\` git remote still needs reconciliation.`
14793
- );
14794
- if (reconciliation.localReconciliation) {
14795
- p34.log.warn(reconciliation.localReconciliation.message);
15024
+ const appArg = typeof args.app === "string" ? args.app : void 0;
15025
+ if (appArg === void 0) {
15026
+ const appDir = findAppDir();
15027
+ if (appDir && !readAppId(appDir)) {
15028
+ if (!args.json) {
15029
+ console.log("App: not registered yet \u2014 it registers on first use (deploy, secrets, dev\u2026)");
15030
+ console.log("Source: unclaimed \u2014 a checkout with a GitHub remote deploys as GitHub; the first `deepspace push` claims DeepSpace source permanently.");
15031
+ }
15032
+ return { data: { appId: null, source: null, revision: 0, registered: false } };
14796
15033
  }
14797
15034
  }
15035
+ const token = await ensureToken();
15036
+ const deployUrl = deployBaseUrl();
15037
+ const appId = await resolveAppTarget(deployUrl, token, appArg);
15038
+ const state = await getAppSource(deployUrl, token, appId);
15039
+ reportSource(appId, state, args.json);
14798
15040
  return {
14799
15041
  data: {
14800
- appId,
14801
- source: result.source,
14802
- revision: result.revision,
14803
- spaceRemote,
14804
- ...reconciliation.localReconciliation ? { localReconciliation: reconciliation.localReconciliation } : {}
15042
+ appId: state.appId,
15043
+ source: state.source,
15044
+ revision: state.revision,
15045
+ registered: state.registered
14805
15046
  }
14806
15047
  };
14807
15048
  }
14808
15049
  });
14809
- function sourceData(state) {
14810
- return {
14811
- appId: state.appId,
14812
- source: state.source,
14813
- revision: state.revision,
14814
- registered: state.registered
14815
- };
14816
- }
14817
15050
  function reportSource(appId, state, json) {
14818
15051
  if (json) return;
14819
15052
  console.log(`App: ${appId}`);
@@ -14822,220 +15055,22 @@ function reportSource(appId, state, json) {
14822
15055
  } else if (state.source?.provider === "deepspace") {
14823
15056
  console.log("Source: DeepSpace");
14824
15057
  } else {
14825
- console.log("Source: unclaimed (the first normal deploy defaults to DeepSpace)");
14826
- }
14827
- console.log(`Revision: ${state.revision}`);
14828
- }
14829
- async function moveToGitHub(input) {
14830
- const { appDir, appId, token, deployUrl, state, remoteArg } = input;
14831
- const selected = requireGitHubRemote(appDir, remoteArg);
14832
- const target2 = { provider: "github", repository: selected.repository };
14833
- if (sameSource(state.source, target2)) return { source: target2, revision: state.revision };
14834
- if (state.source?.provider === "deepspace") {
14835
- assertSyncableRepo(appDir);
14836
- const api4 = repoApi(deployUrl, token, appId);
14837
- const { views } = await api4.listWorkspaces();
14838
- if (views.length > 0) {
14839
- throw new Refusal(
14840
- "Land or drop every active DeepSpace workspace before moving source to GitHub.",
14841
- "active_workspaces"
14842
- );
14843
- }
14844
- ensureSpaceRemote(appDir, appId);
14845
- runGitRemote(appDir, token, [
14846
- "fetch",
14847
- "--quiet",
14848
- "--prune",
14849
- SPACE_REMOTE,
14850
- `+refs/heads/*:${TRANSFER_PREFIX}/heads/*`,
14851
- `+refs/tags/*:${TRANSFER_PREFIX}/tags/*`
14852
- ]);
14853
- const { refs } = await api4.getRefs().then((value) => {
14854
- if (!value)
14855
- throw new Refusal("The DeepSpace source repository is unavailable.", "source_unavailable");
14856
- return value;
14857
- });
14858
- const wanted = refs.filter(isPublicRef).map(({ name, oid }) => ({ name, oid }));
14859
- const fetched = transferRefs(appDir);
14860
- if (!sameRefs(fetched, wanted)) {
14861
- throw new Refusal(
14862
- "DeepSpace branches or tags changed while they were being fetched. Retry the transfer from fresh refs.",
14863
- "source_changed"
14864
- );
14865
- }
14866
- const latest = await api4.latestRelease().then((value) => value.release);
14867
- requireReleaseReachable(appDir, latest, wanted);
14868
- const remote = new Map(
14869
- remotePublicRefs(appDir, selected.name).map((ref) => [ref.name, ref.oid])
14870
- );
14871
- const missing = wanted.filter((ref) => remote.get(ref.name) !== ref.oid);
14872
- if (missing.length > 0) {
14873
- const refspecs = missing.map((ref) => transferRefspec(ref.name));
14874
- throw new Refusal(
14875
- `GitHub ${selected.repository} is missing ${missing.length} authoritative branch/tag ref(s). Push them manually, then rerun this command.`,
14876
- "github_push_required",
14877
- {
14878
- actionRequired: true,
14879
- action: { cwd: appDir, argv: ["git", "push", "--atomic", selected.name, ...refspecs] },
14880
- extra: {
14881
- appId,
14882
- repository: selected.repository,
14883
- missing: missing.map((ref) => ref.name)
14884
- }
14885
- }
14886
- );
14887
- }
14888
- return setAppSource(deployUrl, token, appId, {
14889
- source: target2,
14890
- expectedRevision: state.revision,
14891
- refs: wanted,
14892
- ...releaseExpectation(latest)
14893
- });
14894
- }
14895
- remotePublicRefs(appDir, selected.name);
14896
- return setAppSource(deployUrl, token, appId, {
14897
- source: target2,
14898
- expectedRevision: state.revision
14899
- });
14900
- }
14901
- async function moveToDeepSpace(input) {
14902
- const { appDir, appId, token, deployUrl, state, remoteArg } = input;
14903
- const target2 = { provider: "deepspace" };
14904
- if (sameSource(state.source, target2)) return { source: target2, revision: state.revision };
14905
- assertSyncableRepo(appDir);
14906
- if (state.source?.provider !== "github") {
14907
- return setAppSource(deployUrl, token, appId, {
14908
- source: target2,
14909
- expectedRevision: state.revision
14910
- });
14911
- }
14912
- const selected = requireGitHubRemote(appDir, remoteArg, state.source.repository);
14913
- fetchGitHubTransferRefs(appDir, selected.name);
14914
- const refs = transferRefs(appDir);
14915
- if (refs.length === 0) {
14916
- throw new Refusal("GitHub has no branches or tags to import.", "source_empty");
14917
- }
14918
- const latest = await repoApi(deployUrl, token, appId).latestRelease().then((value) => value.release);
14919
- requireReleaseReachable(appDir, latest, refs);
14920
- const pushed = runGit(
14921
- appDir,
14922
- [
14923
- "push",
14924
- "--force",
14925
- "--atomic",
14926
- "--prune",
14927
- repoUrl(appId, deployUrl),
14928
- `${TRANSFER_PREFIX}/heads/*:refs/heads/*`,
14929
- `${TRANSFER_PREFIX}/tags/*:refs/tags/*`
14930
- ],
14931
- { allowFail: true, env: gitSourceImportEnv(token, state.revision, deployUrl) }
14932
- );
14933
- if (pushed.status !== 0) {
14934
- throw new Refusal(
14935
- `DeepSpace source import failed: ${pushed.stderr.toString("utf-8").trim() || "git push failed"}`,
14936
- "source_import_failed"
14937
- );
14938
- }
14939
- const latestGitHubRefs = remotePublicRefs(appDir, selected.name);
14940
- if (!sameRefs(latestGitHubRefs, refs)) {
14941
- throw new Refusal(
14942
- "GitHub branches or tags changed while they were being imported. Retry the transfer from fresh refs.",
14943
- "source_changed"
14944
- );
14945
- }
14946
- return setAppSource(deployUrl, token, appId, {
14947
- source: target2,
14948
- expectedRevision: state.revision,
14949
- refs,
14950
- ...releaseExpectation(latest)
14951
- });
14952
- }
14953
- function reconcileSpaceRemote(appDir, appId, source) {
14954
- try {
14955
- if (source.provider === "github") {
14956
- return { spaceRemote: removeSpaceRemote(appDir) ? "removed" : "absent" };
14957
- }
14958
- ensureSpaceRemote(appDir, appId);
14959
- return { spaceRemote: "present" };
14960
- } catch (error) {
14961
- const detail = error instanceof Error ? error.message : String(error);
14962
- return {
14963
- spaceRemote: "repair_required",
14964
- localReconciliation: {
14965
- required: true,
14966
- code: "git_remote_reconciliation_failed",
14967
- message: `Source authority is ${source.provider}, but the local \`${SPACE_REMOTE}\` remote could not be reconciled: ${detail}`
14968
- }
14969
- };
14970
- }
14971
- }
14972
- function requireGitHubRemote(appDir, remoteArg, repository) {
14973
- const selected = selectGitHubRemote(appDir, { name: remoteArg, repository });
14974
- if (selected) return selected;
14975
- throw new Refusal(
14976
- remoteArg ? `Remote "${remoteArg}" is not a GitHub repository${repository ? ` for ${repository}` : ""}.` : repository ? `No local GitHub remote points at ${repository}. Add one or pass --remote <name>.` : "No unambiguous GitHub remote was found. Add `origin` or pass --remote <name>.",
14977
- "github_remote_required"
14978
- );
14979
- }
14980
- function fetchGitHubTransferRefs(appDir, remote) {
14981
- runGit(appDir, [
14982
- "fetch",
14983
- "--quiet",
14984
- "--prune",
14985
- remote,
14986
- `+refs/heads/*:${TRANSFER_PREFIX}/heads/*`,
14987
- `+refs/tags/*:${TRANSFER_PREFIX}/tags/*`
14988
- ]);
14989
- }
14990
- function transferRefs(appDir) {
14991
- return localTransferRefs(appDir, `${TRANSFER_PREFIX}/heads`, "refs/heads").concat(
14992
- localTransferRefs(appDir, `${TRANSFER_PREFIX}/tags`, "refs/tags")
14993
- );
14994
- }
14995
- function transferRefspec(ref) {
14996
- if (ref.startsWith("refs/heads/")) {
14997
- return `${TRANSFER_PREFIX}/heads/${ref.slice("refs/heads/".length)}:${ref}`;
14998
- }
14999
- return `${TRANSFER_PREFIX}/tags/${ref.slice("refs/tags/".length)}:${ref}`;
15000
- }
15001
- function isPublicRef(ref) {
15002
- return ref.name.startsWith("refs/heads/") || ref.name.startsWith("refs/tags/");
15003
- }
15004
- function requireReleaseReachable(appDir, release2, refs) {
15005
- if (!release2?.commitOid) return;
15006
- const known = resolveCommit(appDir, release2.commitOid) !== null;
15007
- const reachable = known && refs.some((ref) => isAncestor(appDir, release2.commitOid, ref.oid));
15008
- if (!reachable) {
15009
- throw new Refusal(
15010
- `Live release #${release2.seq} (${release2.commitOid.slice(0, 10)}) is not reachable from a public branch or tag. Redeploy a public branch before changing source.`,
15011
- "release_not_public"
15058
+ console.log(
15059
+ "Source: unclaimed \u2014 a checkout with a GitHub remote deploys as GitHub; the first `deepspace push` claims DeepSpace source permanently."
15012
15060
  );
15013
15061
  }
15014
- }
15015
- function releaseExpectation(release2) {
15016
- return {
15017
- expectedReleaseId: release2?.id ?? null,
15018
- expectedReleaseCommitOid: release2?.commitOid ?? null
15019
- };
15020
- }
15021
- function sameSource(left, right) {
15022
- return left?.provider === right.provider && (left.provider !== "github" || right.provider === "github" && left.repository === right.repository);
15023
- }
15024
- function sameRefs(left, right) {
15025
- if (left.length !== right.length) return false;
15026
- const expected = new Map(right.map((ref) => [ref.name, ref.oid]));
15027
- return left.every((ref) => expected.get(ref.name) === ref.oid);
15062
+ console.log(`Revision: ${state.revision}`);
15028
15063
  }
15029
15064
 
15030
15065
  // src/cli/commands/update.ts
15031
- import * as p35 from "@clack/prompts";
15066
+ import * as p34 from "@clack/prompts";
15032
15067
  import { existsSync as existsSync29, readFileSync as readFileSync26 } from "fs";
15033
15068
  import { join as join28, resolve as resolve22 } from "path";
15034
15069
 
15035
15070
  // package.json
15036
15071
  var package_default = {
15037
15072
  name: "deepspace",
15038
- version: "0.25.0",
15073
+ version: "0.26.0",
15039
15074
  engines: {
15040
15075
  node: ">=22.15.0 <23 || >=24 <25 || >=26 <27"
15041
15076
  },
@@ -15290,7 +15325,7 @@ var update_default = defineDeepspaceCommand({
15290
15325
  } : null;
15291
15326
  if (versionGuidance) {
15292
15327
  const steps2 = [versionGuidance.step];
15293
- if (!args.json) p35.log.warn(versionGuidance.step);
15328
+ if (!args.json) p34.log.warn(versionGuidance.step);
15294
15329
  return {
15295
15330
  data: {
15296
15331
  ready: false,
@@ -15326,9 +15361,9 @@ var update_default = defineDeepspaceCommand({
15326
15361
  ];
15327
15362
  const status3 = unverifiedSdk ? "dependency_unverified" : steps.length === 0 ? "aligned" : "guidance_available";
15328
15363
  if (!args.json) {
15329
- p35.log.info(`App dependency: ${currentSpec}; guidance owner: deepspace CLI ${targetVersion}`);
15330
- for (const step of steps) p35.log.message(`\u2022 ${step}`);
15331
- if (steps.length === 0) p35.log.success("This app is already aligned with this CLI version.");
15364
+ p34.log.info(`App dependency: ${currentSpec}; guidance owner: deepspace CLI ${targetVersion}`);
15365
+ for (const step of steps) p34.log.message(`\u2022 ${step}`);
15366
+ if (steps.length === 0) p34.log.success("This app is already aligned with this CLI version.");
15332
15367
  }
15333
15368
  const data = {
15334
15369
  ready: status3 === "aligned",
@@ -15394,7 +15429,7 @@ var create2 = defineCommand14({
15394
15429
  }
15395
15430
  const scaffolder = `create-deepspace@${pkg.version}`;
15396
15431
  console.log(`Running: npx ${scaffolder} ${rawArgs2.join(" ")}`.trimEnd() + "\n");
15397
- const res = spawnSync8("npx", ["-y", scaffolder, ...rawArgs2], { stdio: "inherit" });
15432
+ const res = spawnSync9("npx", ["-y", scaffolder, ...rawArgs2], { stdio: "inherit" });
15398
15433
  if (res.error) {
15399
15434
  const err = res.error;
15400
15435
  console.error(