deepspace 0.21.0 → 0.22.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -8,8 +8,8 @@ import {
8
8
  renderUsage
9
9
  } from "citty";
10
10
  import { sync as spawnSync8 } from "cross-spawn";
11
- import { readFileSync as readFileSync28, existsSync as existsSync26 } from "fs";
12
- import { dirname as dirname12, join as join24 } from "path";
11
+ import { readFileSync as readFileSync28, existsSync as existsSync27 } from "fs";
12
+ import { dirname as dirname12, join as join25 } from "path";
13
13
  import { fileURLToPath as fileURLToPath3 } from "url";
14
14
 
15
15
  // src/cli/lib/spinner.ts
@@ -913,8 +913,6 @@ import { parse as parseToml } from "smol-toml";
913
913
  // src/server/utils/registry-client.ts
914
914
  var APP_ID_RE = /^app_[0-9A-HJKMNP-TV-Z]{26}$/;
915
915
  var STRICT_APP_ID_RE = APP_ID_RE;
916
-
917
- // src/cli/lib/app-identity.ts
918
916
  var CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
919
917
  function mintUlid(now = Date.now()) {
920
918
  let ts = "";
@@ -938,9 +936,8 @@ function mintUlid(now = Date.now()) {
938
936
  }
939
937
  return `${ts}${rs}`.slice(0, 26);
940
938
  }
941
- function mintAppId(now = Date.now()) {
942
- return `app_${mintUlid(now)}`;
943
- }
939
+
940
+ // src/cli/lib/app-identity.ts
944
941
  function readWranglerIdentityConfig(cwd) {
945
942
  const wranglerPath = join2(resolve2(cwd), "wrangler.toml");
946
943
  if (!existsSync3(wranglerPath)) return null;
@@ -996,6 +993,18 @@ ${line}
996
993
  }
997
994
  writeFileSync2(wranglerPath, src);
998
995
  }
996
+ var APP_ID_PLACEHOLDER = "__APP_ID__";
997
+ function stampAppIdInSources(cwd, appId, previousAppId) {
998
+ const constantsPath = join2(resolve2(cwd), "src", "constants.ts");
999
+ if (!existsSync3(constantsPath)) return false;
1000
+ const needles = [APP_ID_PLACEHOLDER, ...previousAppId ? [previousAppId] : []];
1001
+ const content = readFileSync5(constantsPath, "utf-8");
1002
+ let next = content;
1003
+ for (const needle of needles) next = next.replaceAll(needle, appId);
1004
+ if (next === content) return false;
1005
+ writeFileSync2(constantsPath, next);
1006
+ return true;
1007
+ }
999
1008
 
1000
1009
  // src/cli/commands/dev.ts
1001
1010
  import { resolve as resolve8, basename as basename2, join as join11 } from "path";
@@ -2184,6 +2193,7 @@ async function secretsApi(deployUrl, token, path, init = {}) {
2184
2193
  if (!res.ok) {
2185
2194
  const err = new Error(body.error ?? `Secrets request failed (${res.status})`);
2186
2195
  err.status = res.status;
2196
+ err.code = body.code;
2187
2197
  err.apiPath = path;
2188
2198
  throw err;
2189
2199
  }
@@ -5618,7 +5628,7 @@ async function syncDeployRepository(options) {
5618
5628
  p9.log.info(`Workspace ${workspaceBranchId} is synced at ${tip.slice(0, 10)}.`);
5619
5629
  } else if (secretFiles.length > 0) {
5620
5630
  p9.log.warn(
5621
- `Skipping cloud-repo push \u2014 the branch tracks secret file(s): ${secretFiles.join(", ")}. Untrack with \`git rm --cached ${secretFiles[0]}\` (add to .gitignore), re-commit, then \`deepspace push\`. Deploy continues; this release's source isn't recoverable until then.`
5631
+ `Skipping cloud-repo push \u2014 the branch tracks secret file(s): ${secretFiles.join(", ")}. Untrack with \`git rm --cached ${secretFiles[0]}\` (add to .gitignore), re-commit, then \`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.`
5622
5632
  );
5623
5633
  } else {
5624
5634
  const pushResult = await pushWithTransientRetry(
@@ -6252,13 +6262,21 @@ var deploy_default = defineCommand3({
6252
6262
  );
6253
6263
  }
6254
6264
  const appName = nameResult.name;
6255
- let appId = readAppId(appDir, envName);
6265
+ const appId = readAppId(appDir, envName);
6256
6266
  p12.log.info(envName ? `App: ${appName} (env: ${envName})` : `App: ${appName}`);
6257
6267
  const { token, ownerId } = await authenticate(appDir, output);
6258
6268
  if (!appId) {
6259
- appId = mintAppId();
6260
- writeAppId(appDir, appId, { wranglerEnv: envName });
6261
- p12.log.info(`Minted app id ${appId} \u2014 commit wrangler.toml.`);
6269
+ return output.die(
6270
+ `No app id in wrangler.toml${envName ? ` for [env.${envName}]` : ""}. Run \`deepspace app init${envName ? ` --env ${envName}` : ""}\` to register this app, then re-deploy.`,
6271
+ "app_not_initialized",
6272
+ {
6273
+ action: {
6274
+ cwd: appDir,
6275
+ argv: ["deepspace", "app", "init", ...envName ? ["--env", envName] : []]
6276
+ },
6277
+ actionRequired: true
6278
+ }
6279
+ );
6262
6280
  }
6263
6281
  p12.log.info(`Id: ${appId}`);
6264
6282
  const secretsCache = await loadDeploySecrets({
@@ -6271,6 +6289,12 @@ var deploy_default = defineCommand3({
6271
6289
  output
6272
6290
  });
6273
6291
  const sourceState = await getAppSource(DEPLOY_URL3, token, appId);
6292
+ if (!sourceState.registered) {
6293
+ output.die(
6294
+ `${appId} is not registered. If this repo's id came from an older SDK's scaffold, run \`deepspace app init --new-id\` to register it as a fresh app; a brand-new app dir registers with \`deepspace app init\`.`,
6295
+ "app_not_registered"
6296
+ );
6297
+ }
6274
6298
  const repositoryPreflight = preflightDeployRepository({
6275
6299
  appDir,
6276
6300
  push: args.push !== false,
@@ -6546,7 +6570,7 @@ async function warnIfPhantomApp(deployUrl, token, appId, explicit) {
6546
6570
  const apps = await listApps(deployUrl, token);
6547
6571
  if (!apps.some((app2) => app2.appId === appId)) {
6548
6572
  process.stderr.write(
6549
- `warning: ${appId} is not among your apps. If it exists and you have access (collaborator/admin), the server will use it; if it doesn't exist, this will register it as a NEW app under your account \u2014 check the id if that's not what you meant (a subdomain name also works with -a).
6573
+ `warning: ${appId} is not among your apps. If it exists and you have access (collaborator/admin), the server will use it; if it doesn't exist, the command will be refused (app_not_registered \u2014 ids are registered at \`deepspace app init\`). Check the id (a subdomain name also works with -a).
6550
6574
  `
6551
6575
  );
6552
6576
  }
@@ -10233,10 +10257,29 @@ var secrets_default = defineCommand10({
10233
10257
  });
10234
10258
 
10235
10259
  // src/cli/commands/init.ts
10260
+ import { existsSync as existsSync21 } from "fs";
10261
+ import { join as join20 } from "path";
10262
+ var DEPLOY_URL12 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
10263
+ function commitScaffoldIfUnborn(appDir, token) {
10264
+ try {
10265
+ if (!existsSync21(join20(appDir, ".git"))) return false;
10266
+ if (runGit(appDir, ["rev-parse", "--verify", "HEAD"], { allowFail: true }).status === 0) {
10267
+ return false;
10268
+ }
10269
+ ensureGitIdentity(appDir, token);
10270
+ if (runGit(appDir, ["add", "-A"], { allowFail: true }).status !== 0) return false;
10271
+ const commit = runGit(appDir, ["commit", "-m", "Initial DeepSpace scaffold", "--no-verify"], {
10272
+ allowFail: true
10273
+ });
10274
+ return commit.status === 0;
10275
+ } catch {
10276
+ return false;
10277
+ }
10278
+ }
10236
10279
  var init_default = defineDeepspaceCommand({
10237
10280
  meta: {
10238
10281
  name: "init",
10239
- description: "Mint this app\u2019s immutable DEEPSPACE_APP_ID into wrangler.toml"
10282
+ description: "Register this app: mint its immutable DEEPSPACE_APP_ID into wrangler.toml"
10240
10283
  },
10241
10284
  args: {
10242
10285
  "new-id": {
@@ -10262,6 +10305,25 @@ var init_default = defineDeepspaceCommand({
10262
10305
  const envName = args.env || void 0;
10263
10306
  const existing = readAppId(appDir, envName);
10264
10307
  if (existing && !args["new-id"]) {
10308
+ const token2 = await ensureToken();
10309
+ let state;
10310
+ try {
10311
+ state = await getAppSource(DEPLOY_URL12, token2, existing);
10312
+ } catch (error) {
10313
+ if (error instanceof ApiError && error.code === "forbidden") {
10314
+ throw new Refusal(
10315
+ `wrangler.toml carries ${existing}, which is registered to another user. Run \`deepspace app init --new-id\` to fork this repo as your own app (new data and secrets; the original is untouched).`,
10316
+ "not_app_owner"
10317
+ );
10318
+ }
10319
+ throw error;
10320
+ }
10321
+ if (!state.registered) {
10322
+ throw new Refusal(
10323
+ `wrangler.toml carries ${existing}, but that id was never registered \u2014 it was minted locally by an older SDK. Ids are server-minted at registration now, and an existing unregistered id cannot be claimed. Run \`deepspace app init --new-id\` to register this repo as a fresh app (new data and secrets; nothing to migrate \u2014 the old id never had server-side state).`,
10324
+ "app_not_registered"
10325
+ );
10326
+ }
10265
10327
  if (!args.json) {
10266
10328
  console.log(`Already initialized: ${existing}${envName ? ` (env: ${envName})` : ""}`);
10267
10329
  console.log(`App dir: ${appDir}`);
@@ -10270,23 +10332,31 @@ var init_default = defineDeepspaceCommand({
10270
10332
  data: { status: "already_initialized", appId: existing, appDir, env: envName ?? null }
10271
10333
  };
10272
10334
  }
10273
- const appId = mintAppId();
10335
+ const token = await ensureToken();
10336
+ const { appId } = await apiFetch(DEPLOY_URL12, token, "/api/apps/mint", {
10337
+ method: "POST"
10338
+ });
10274
10339
  writeAppId(appDir, appId, { wranglerEnv: envName, force: Boolean(args["new-id"]) });
10340
+ if (!envName) stampAppIdInSources(appDir, appId, existing);
10341
+ const committedScaffold = commitScaffoldIfUnborn(appDir, token);
10275
10342
  if (!args.json) {
10276
10343
  if (existing) {
10277
10344
  console.log(`Forked: ${existing} \u2192 ${appId}${envName ? ` (env: ${envName})` : ""}`);
10278
- console.log("The next deploy registers this as a NEW app; the original is untouched.");
10345
+ console.log("Registered as a NEW app under your account; the original is untouched.");
10279
10346
  } else {
10280
- console.log(`Minted ${appId}${envName ? ` (env: ${envName})` : ""} \u2014 commit wrangler.toml.`);
10281
- console.log("The first deploy registers it and claims the `name` subdomain.");
10347
+ console.log(
10348
+ `Registered ${appId}${envName ? ` (env: ${envName})` : ""} to your account` + (committedScaffold ? " \u2014 initial scaffold commit created." : " \u2014 commit wrangler.toml.")
10349
+ );
10350
+ console.log("The first deploy claims the `name` subdomain.");
10282
10351
  }
10283
10352
  console.log(`App dir: ${appDir}`);
10284
10353
  }
10285
10354
  return {
10286
10355
  data: {
10287
- status: existing ? "forked" : "minted",
10356
+ status: existing ? "forked" : "registered",
10288
10357
  appId,
10289
10358
  ...existing ? { previousAppId: existing } : {},
10359
+ committedScaffold,
10290
10360
  appDir,
10291
10361
  env: envName ?? null
10292
10362
  }
@@ -10297,7 +10367,7 @@ var init_default = defineDeepspaceCommand({
10297
10367
  // src/cli/commands/status.ts
10298
10368
  import { defineCommand as defineCommand11 } from "citty";
10299
10369
  import * as p17 from "@clack/prompts";
10300
- import { existsSync as existsSync21, readFileSync as readFileSync24 } from "fs";
10370
+ import { existsSync as existsSync22, readFileSync as readFileSync24 } from "fs";
10301
10371
  function timeAgo(iso) {
10302
10372
  const ms = Date.now() - Date.parse(iso);
10303
10373
  if (!Number.isFinite(ms) || ms < 0) return iso;
@@ -10382,7 +10452,7 @@ var status_default = defineCommand11({
10382
10452
  json.services = urls;
10383
10453
  if (urlOverrides.length > 0) json.urlOverrides = urlOverrides;
10384
10454
  }
10385
- facts.loggedIn = existsSync21(SESSION_PATH);
10455
+ facts.loggedIn = existsSync22(SESSION_PATH);
10386
10456
  let userId = null;
10387
10457
  if (!facts.loggedIn) {
10388
10458
  lines.push(["Session", "not logged in"]);
@@ -10710,6 +10780,24 @@ var push_default = defineDeepspaceCommand({
10710
10780
  );
10711
10781
  }
10712
10782
  const { token, appId } = await resolveTarget3();
10783
+ let sourceState;
10784
+ try {
10785
+ sourceState = await getAppSource(deployBaseUrl(), token, appId);
10786
+ } catch (error) {
10787
+ if (error instanceof ApiError && error.code === "forbidden") {
10788
+ throw new Refusal(
10789
+ `${appId} is registered to another user. Run \`deepspace app init --new-id\` to fork this repo as your own app (new data and secrets; the original is untouched).`,
10790
+ "not_app_owner"
10791
+ );
10792
+ }
10793
+ throw error;
10794
+ }
10795
+ if (!sourceState.registered) {
10796
+ throw new Refusal(
10797
+ `${appId} is not registered. If this repo's id came from an older SDK's scaffold, run \`deepspace app init --new-id\` to register it as a fresh app; a brand-new app dir registers with \`deepspace app init\`.`,
10798
+ "app_not_registered"
10799
+ );
10800
+ }
10713
10801
  const pullRecoveryCwd = selectedBranchCheckout(appDir, branch) ?? appDir;
10714
10802
  ensureSpaceRemote(appDir, appId);
10715
10803
  if (force) {
@@ -11071,18 +11159,18 @@ var pull_default = defineDeepspaceCommand({
11071
11159
 
11072
11160
  // src/cli/commands/clone.ts
11073
11161
  import * as p20 from "@clack/prompts";
11074
- import { existsSync as existsSync22, readdirSync as readdirSync6, rmSync as rmSync5 } from "fs";
11075
- import { resolve as resolve17, basename as basename5, join as join20 } from "path";
11162
+ import { existsSync as existsSync23, readdirSync as readdirSync6, rmSync as rmSync5 } from "fs";
11163
+ import { resolve as resolve17, basename as basename5, join as join21 } from "path";
11076
11164
  function cleanFailedCloneDir(dir) {
11077
11165
  let entries;
11078
11166
  try {
11079
11167
  entries = readdirSync6(dir);
11080
11168
  } catch {
11081
- return existsSync22(dir) ? ["<unreadable>"] : [];
11169
+ return existsSync23(dir) ? ["<unreadable>"] : [];
11082
11170
  }
11083
11171
  if (entries.length === 1 && entries[0] === ".git") {
11084
11172
  try {
11085
- rmSync5(join20(dir, ".git"), { recursive: true, force: true });
11173
+ rmSync5(join21(dir, ".git"), { recursive: true, force: true });
11086
11174
  return [];
11087
11175
  } catch {
11088
11176
  return entries;
@@ -11091,7 +11179,7 @@ function cleanFailedCloneDir(dir) {
11091
11179
  return entries;
11092
11180
  }
11093
11181
  function validateCloneTarget(dir) {
11094
- if (!existsSync22(dir)) return false;
11182
+ if (!existsSync23(dir)) return false;
11095
11183
  let entries;
11096
11184
  try {
11097
11185
  entries = readdirSync6(dir);
@@ -11214,7 +11302,7 @@ async function actorLabels(token, appId) {
11214
11302
  }
11215
11303
 
11216
11304
  // src/cli/commands/releases.ts
11217
- var DEPLOY_URL12 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
11305
+ var DEPLOY_URL13 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
11218
11306
  var releases_default = defineDeepspaceCommand({
11219
11307
  meta: {
11220
11308
  name: "releases",
@@ -11249,8 +11337,8 @@ var releases_default = defineDeepspaceCommand({
11249
11337
  const spinner2 = args.json ? null : createSpinner();
11250
11338
  spinner2?.start("Loading release history\u2026");
11251
11339
  const token = await ensureToken();
11252
- const appId = await resolveAppTarget(DEPLOY_URL12, token, appArg, { wranglerEnv: envArg });
11253
- const { releases } = await repoApi(DEPLOY_URL12, token, appId).listReleases(limit);
11340
+ const appId = await resolveAppTarget(DEPLOY_URL13, token, appArg, { wranglerEnv: envArg });
11341
+ const { releases } = await repoApi(DEPLOY_URL13, token, appId).listReleases(limit);
11254
11342
  spinner2?.stop(`Loaded ${releases.length} ${releases.length === 1 ? "release" : "releases"}.`);
11255
11343
  if (!args.json) {
11256
11344
  if (releases.length === 0) {
@@ -11283,7 +11371,7 @@ Retry in a moment, or re-run with --allow-do-deletion to accept the risk.`,
11283
11371
  "do_guard_unavailable"
11284
11372
  );
11285
11373
  }
11286
- var DEPLOY_URL13 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
11374
+ var DEPLOY_URL14 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
11287
11375
  function pickPreviousRelease(releases) {
11288
11376
  if (releases.length === 0) {
11289
11377
  return { error: "No releases recorded yet \u2014 nothing to roll back to.", code: "no_releases" };
@@ -11345,8 +11433,8 @@ var rollback_default = defineDeepspaceCommand({
11345
11433
  const spinner2 = args.json ? null : createSpinner();
11346
11434
  spinner2?.start("Preparing rollback\u2026");
11347
11435
  const token = await ensureToken();
11348
- const appId = await resolveAppTarget(DEPLOY_URL13, token, appArg, { wranglerEnv: envArg });
11349
- const api4 = repoApi(DEPLOY_URL13, token, appId);
11436
+ const appId = await resolveAppTarget(DEPLOY_URL14, token, appArg, { wranglerEnv: envArg });
11437
+ const api4 = repoApi(DEPLOY_URL14, token, appId);
11350
11438
  let releaseId = args.release ? String(args.release).trim() : "";
11351
11439
  if (!releaseId) {
11352
11440
  const { releases } = await api4.listReleases(2);
@@ -11357,7 +11445,7 @@ var rollback_default = defineDeepspaceCommand({
11357
11445
  spinner2?.message(`Rolling back to ${releaseId}\u2026`);
11358
11446
  let result;
11359
11447
  try {
11360
- result = await apiFetch(DEPLOY_URL13, token, `/api/deploy/${appId}/rollback`, {
11448
+ result = await apiFetch(DEPLOY_URL14, token, `/api/deploy/${appId}/rollback`, {
11361
11449
  method: "POST",
11362
11450
  body: JSON.stringify({
11363
11451
  releaseId,
@@ -11568,7 +11656,7 @@ function printOverlaps(overlaps, json) {
11568
11656
  // src/cli/commands/workspace/local.ts
11569
11657
  import * as p24 from "@clack/prompts";
11570
11658
  import {
11571
- existsSync as existsSync23,
11659
+ existsSync as existsSync24,
11572
11660
  mkdirSync as mkdirSync7,
11573
11661
  readFileSync as readFileSync25,
11574
11662
  realpathSync as realpathSync6,
@@ -11576,7 +11664,7 @@ import {
11576
11664
  statSync as statSync3,
11577
11665
  writeFileSync as writeFileSync7
11578
11666
  } from "fs";
11579
- import { dirname as dirname11, isAbsolute as isAbsolute5, join as join21, relative as relative2, sep as sep2 } from "path";
11667
+ import { dirname as dirname11, isAbsolute as isAbsolute5, join as join22, relative as relative2, sep as sep2 } from "path";
11580
11668
  var WORKSPACE_MARKER = "deepspace-workspace.json";
11581
11669
  function excludeWorktreeDir(appDir, dir) {
11582
11670
  try {
@@ -11597,7 +11685,7 @@ function worktreeMarkerPath(worktreeRoot) {
11597
11685
  const result = runGit(worktreeRoot, ["rev-parse", "--absolute-git-dir"], { allowFail: true });
11598
11686
  if (result.status !== 0) return null;
11599
11687
  const gitDir = result.stdout.toString("utf-8").trim();
11600
- return isAbsolute5(gitDir) ? join21(gitDir, WORKSPACE_MARKER) : null;
11688
+ return isAbsolute5(gitDir) ? join22(gitDir, WORKSPACE_MARKER) : null;
11601
11689
  } catch {
11602
11690
  return null;
11603
11691
  }
@@ -11642,11 +11730,11 @@ function isManagedWorkspaceWorktree(worktreeRoot, workspaceId) {
11642
11730
  function defaultWorkspaceRoot(appDir, workspaceId) {
11643
11731
  const primary = listWorktrees(appDir)[0];
11644
11732
  if (!primary) throw new Error("Git did not report a primary worktree");
11645
- return join21(primary.path, ".deepspace", "ws", workspaceId.slice(3).toLowerCase());
11733
+ return join22(primary.path, ".deepspace", "ws", workspaceId.slice(3).toLowerCase());
11646
11734
  }
11647
11735
  function appDirInWorktree(appDir, worktreeRoot) {
11648
11736
  const appRelative = relative2(realpathSync6(repoToplevel(appDir)), realpathSync6(appDir));
11649
- return appRelative ? join21(worktreeRoot, appRelative) : worktreeRoot;
11737
+ return appRelative ? join22(worktreeRoot, appRelative) : worktreeRoot;
11650
11738
  }
11651
11739
  function primaryAppDir(appDir) {
11652
11740
  const primary = listWorktrees(appDir)[0];
@@ -11681,7 +11769,7 @@ function rematerializeWorkspaceWorktree(appDir, worktreeRoot, branch, workspaceI
11681
11769
  function inOwnLinkedWorktree(appDir, id) {
11682
11770
  if (workspaceIdFromBranch(currentBranch(appDir)) !== id) return false;
11683
11771
  try {
11684
- return statSync3(join21(repoToplevel(appDir), ".git")).isFile();
11772
+ return statSync3(join22(repoToplevel(appDir), ".git")).isFile();
11685
11773
  } catch {
11686
11774
  return false;
11687
11775
  }
@@ -11806,7 +11894,7 @@ function cleanupWorkspaceLocal(appDir, id, trunkBranch, options = {}) {
11806
11894
  }
11807
11895
  runGit(mainDir, ["worktree", "prune"], { allowFail: true });
11808
11896
  out.worktreeDir = null;
11809
- const leftover = existsSync23(linked.path);
11897
+ const leftover = existsSync24(linked.path);
11810
11898
  if (leftover) out.leftoverDir = linked.path;
11811
11899
  else out.worktreeRemoved = linked.path;
11812
11900
  deleteWsBranch(mainDir, branch, expectedBranchOid, out);
@@ -12104,7 +12192,7 @@ var newWorkspaceCommand = defineDeepspaceCommand({
12104
12192
 
12105
12193
  // src/cli/commands/workspace/attach.ts
12106
12194
  import * as p26 from "@clack/prompts";
12107
- import { existsSync as existsSync24, readdirSync as readdirSync7, realpathSync as realpathSync7, rmSync as rmSync6 } from "fs";
12195
+ import { existsSync as existsSync25, readdirSync as readdirSync7, realpathSync as realpathSync7, rmSync as rmSync6 } from "fs";
12108
12196
  import { isAbsolute as isAbsolute7, resolve as resolve19 } from "path";
12109
12197
  function samePath(a, b) {
12110
12198
  const real = (p35) => {
@@ -12124,7 +12212,7 @@ function finishedWorkspaceMessage(id, view) {
12124
12212
  return `Workspace ${id} is ${workspace.status} \u2014 its ref is retained briefly (fetch ${workspace.ref} to inspect), but attach only works on active workspaces.`;
12125
12213
  }
12126
12214
  function cleanFailedFreshAttachDir(dir) {
12127
- if (!existsSync24(dir)) return [];
12215
+ if (!existsSync25(dir)) return [];
12128
12216
  let entries;
12129
12217
  try {
12130
12218
  entries = readdirSync7(dir);
@@ -12206,7 +12294,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
12206
12294
  const localTip = resolveCommit(appDir, `refs/heads/${branch}`);
12207
12295
  if (localTip) {
12208
12296
  let existing = resolveWorkspaceWorktree(listWorktrees(appDir), id);
12209
- if (existing && !existsSync24(existing)) {
12297
+ if (existing && !existsSync25(existing)) {
12210
12298
  runGit(appDir, ["worktree", "prune"], { allowFail: true });
12211
12299
  existing = null;
12212
12300
  }
@@ -12324,7 +12412,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
12324
12412
  process.cwd(),
12325
12413
  (typeof args.dir === "string" ? args.dir.trim() : "") || `ws-${id.slice(3).toLowerCase()}`
12326
12414
  );
12327
- if (existsSync24(dir)) {
12415
+ if (existsSync25(dir)) {
12328
12416
  spinner2?.stop("Target exists.");
12329
12417
  throw new Refusal(`${dir} already exists \u2014 pass a different directory.`, "dir_exists");
12330
12418
  }
@@ -12662,12 +12750,12 @@ import * as p30 from "@clack/prompts";
12662
12750
  // src/cli/lib/validation.ts
12663
12751
  import { sync as spawnSync7 } from "cross-spawn";
12664
12752
  import { readFileSync as readFileSync26 } from "fs";
12665
- import { join as join22 } from "path";
12753
+ import { join as join23 } from "path";
12666
12754
  var MAX_SUMMARY_CHARS = 4e3;
12667
12755
  var MAX_OUTPUT_BUFFER = 64 * 1024 * 1024;
12668
12756
  function configuredCommand(appDir) {
12669
12757
  try {
12670
- const pkg2 = JSON.parse(readFileSync26(join22(appDir, "package.json"), "utf-8"));
12758
+ const pkg2 = JSON.parse(readFileSync26(join23(appDir, "package.json"), "utf-8"));
12671
12759
  if (pkg2.scripts?.validate) return `${detectPackageManager(appDir)} run --silent validate`;
12672
12760
  } catch {
12673
12761
  }
@@ -13743,13 +13831,13 @@ function sameRefs(left, right) {
13743
13831
 
13744
13832
  // src/cli/commands/update.ts
13745
13833
  import * as p34 from "@clack/prompts";
13746
- import { existsSync as existsSync25, readFileSync as readFileSync27, writeFileSync as writeFileSync8 } from "fs";
13747
- import { join as join23, resolve as resolve20 } from "path";
13834
+ import { existsSync as existsSync26, readFileSync as readFileSync27, writeFileSync as writeFileSync8 } from "fs";
13835
+ import { join as join24, resolve as resolve20 } from "path";
13748
13836
 
13749
13837
  // package.json
13750
13838
  var package_default = {
13751
13839
  name: "deepspace",
13752
- version: "0.21.0",
13840
+ version: "0.22.1",
13753
13841
  engines: {
13754
13842
  node: ">=22.15.0 <23 || >=24 <25 || >=26 <27"
13755
13843
  },
@@ -13920,18 +14008,18 @@ function gapIsTooWide(from, to) {
13920
14008
  return from.major === 0 && from.minor < OLDEST_SUPPORTED_MINOR;
13921
14009
  }
13922
14010
  function readAppSdkVersion(appDir) {
13923
- const installed = join23(appDir, "node_modules", "deepspace", "package.json");
13924
- if (existsSync25(installed)) {
14011
+ const installed = join24(appDir, "node_modules", "deepspace", "package.json");
14012
+ if (existsSync26(installed)) {
13925
14013
  const parsed2 = JSON.parse(readFileSync27(installed, "utf8"));
13926
14014
  if (typeof parsed2.version === "string") return parsed2.version;
13927
14015
  }
13928
- const manifest = join23(appDir, "package.json");
13929
- if (!existsSync25(manifest)) return null;
14016
+ const manifest = join24(appDir, "package.json");
14017
+ if (!existsSync26(manifest)) return null;
13930
14018
  const parsed = JSON.parse(readFileSync27(manifest, "utf8"));
13931
14019
  return parsed.dependencies?.deepspace ?? null;
13932
14020
  }
13933
14021
  function pinSdkVersion(appDir, version) {
13934
- const path = join23(appDir, "package.json");
14022
+ const path = join24(appDir, "package.json");
13935
14023
  const source = readFileSync27(path, "utf8");
13936
14024
  const manifest = JSON.parse(source);
13937
14025
  const dependencies = manifest.dependencies;
@@ -13958,16 +14046,16 @@ function buildPreviewSecretsUpgradeInstruction(appDir) {
13958
14046
  "vite.config.mts",
13959
14047
  "vite.config.js",
13960
14048
  "vite.config.mjs"
13961
- ].find((candidate) => existsSync25(join23(appDir, candidate)));
14049
+ ].find((candidate) => existsSync26(join24(appDir, candidate)));
13962
14050
  if (!configName) return null;
13963
- const source = readFileSync27(join23(appDir, configName), "utf8");
14051
+ const source = readFileSync27(join24(appDir, configName), "utf8");
13964
14052
  if (source.includes("deepspace-remove-build-preview-secrets")) return null;
13965
14053
  return `${configName}: add the build-preview secret cleanup from ${BUILD_PREVIEW_SECRETS_GUIDE}; app update left this app-owned file unchanged.`;
13966
14054
  }
13967
14055
  function usersSchemaVisibilityUpgradeInstruction(appDir) {
13968
14056
  const schemaName = "src/schemas/users-schema.ts";
13969
- const path = join23(appDir, schemaName);
13970
- if (!existsSync25(path)) return null;
14057
+ const path = join24(appDir, schemaName);
14058
+ if (!existsSync26(path)) return null;
13971
14059
  const source = readFileSync27(path, "utf8");
13972
14060
  if (!/\bmember\s*:\s*\{[^}]*\bread\s*:\s*true\b/s.test(source)) return null;
13973
14061
  return `${schemaName}: review the member user-row visibility change at ${USERS_SCHEMA_VISIBILITY_GUIDE}; app update left this app-owned schema unchanged.`;
@@ -14119,8 +14207,8 @@ var update_default = defineDeepspaceCommand({
14119
14207
 
14120
14208
  // src/cli/cli.ts
14121
14209
  var __dirname = dirname12(fileURLToPath3(import.meta.url));
14122
- var pkg = JSON.parse(readFileSync28(join24(__dirname, "..", "package.json"), "utf-8"));
14123
- if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync26("/etc/ssl/certs/ca-certificates.crt")) {
14210
+ var pkg = JSON.parse(readFileSync28(join25(__dirname, "..", "package.json"), "utf-8"));
14211
+ if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync27("/etc/ssl/certs/ca-certificates.crt")) {
14124
14212
  process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
14125
14213
  }
14126
14214
  var create2 = defineCommand14({