deepspace 0.22.1 → 0.23.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
@@ -993,18 +993,6 @@ ${line}
993
993
  }
994
994
  writeFileSync2(wranglerPath, src);
995
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
- }
1008
996
 
1009
997
  // src/cli/commands/dev.ts
1010
998
  import { resolve as resolve8, basename as basename2, join as join11 } from "path";
@@ -1589,6 +1577,7 @@ function resolveAppNameForEnv(config, envName) {
1589
1577
  if (!envBlock) {
1590
1578
  return {
1591
1579
  ok: false,
1580
+ code: "missing_env_block",
1592
1581
  reason: `wrangler.toml: no [env.${envName}] block found. Add one with a distinct \`name\` to deploy this environment.`
1593
1582
  };
1594
1583
  }
@@ -2400,6 +2389,12 @@ import { join as join10 } from "path";
2400
2389
  import { pathToFileURL } from "url";
2401
2390
 
2402
2391
  // src/server/schemas/registry.ts
2392
+ var SYSTEM_ASSIGNED_COLUMNS = /* @__PURE__ */ new Set(["email", "name", "imageUrl", "role"]);
2393
+ var SYSTEM_MAINTAINED_COLUMNS = /* @__PURE__ */ new Set(["createdAt", "lastSeenAt"]);
2394
+ var SYSTEM_MANAGED_COLUMNS = /* @__PURE__ */ new Set([
2395
+ ...SYSTEM_ASSIGNED_COLUMNS,
2396
+ ...SYSTEM_MAINTAINED_COLUMNS
2397
+ ]);
2403
2398
  var USERS_COLUMNS = [
2404
2399
  { name: "email", storage: "text", interpretation: "plain" },
2405
2400
  { name: "name", storage: "text", interpretation: "plain" },
@@ -3064,7 +3059,7 @@ function ensurePlaywright(appDir) {
3064
3059
  import { readFileSync as readFileSync14 } from "fs";
3065
3060
 
3066
3061
  // src/testing/accounts.ts
3067
- import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync13, writeFileSync as writeFileSync5 } from "fs";
3062
+ import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync13 } from "fs";
3068
3063
  import { homedir as homedir2 } from "os";
3069
3064
  import { join as join12 } from "path";
3070
3065
  var TEST_ACCOUNTS_PATH = join12(homedir2(), ".deepspace", "test-accounts.json");
@@ -3103,7 +3098,9 @@ function reconcileTestAccountScopes(store, remote, scope) {
3103
3098
  };
3104
3099
  }
3105
3100
  function reconcileTestAccounts(local, remote) {
3106
- const localById = new Map(local.filter((account) => account.id).map((account) => [account.id, account]));
3101
+ const localById = new Map(
3102
+ local.filter((account) => account.id).map((account) => [account.id, account])
3103
+ );
3107
3104
  return remote.flatMap((account) => {
3108
3105
  const stored = localById.get(account.id);
3109
3106
  return stored ? [{ ...stored, ...account }] : [];
@@ -3138,7 +3135,9 @@ function readTestAccountStore() {
3138
3135
  return { version: 2, scopes: {} };
3139
3136
  }
3140
3137
  const candidate = raw;
3141
- const scopes = candidate.scopes && typeof candidate.scopes === "object" && !Array.isArray(candidate.scopes) ? Object.fromEntries(Object.entries(candidate.scopes).map(([key, value]) => [key, validTestAccounts(value)])) : {};
3138
+ const scopes = candidate.scopes && typeof candidate.scopes === "object" && !Array.isArray(candidate.scopes) ? Object.fromEntries(
3139
+ Object.entries(candidate.scopes).map(([key, value]) => [key, validTestAccounts(value)])
3140
+ ) : {};
3142
3141
  return { version: 2, scopes };
3143
3142
  } catch {
3144
3143
  return { version: 2, scopes: {} };
@@ -3154,7 +3153,7 @@ function validTestAccounts(value) {
3154
3153
  }
3155
3154
  function saveTestAccountStore(store) {
3156
3155
  mkdirSync5(join12(homedir2(), ".deepspace"), { recursive: true });
3157
- writeFileSync5(TEST_ACCOUNTS_PATH, JSON.stringify(store, null, 2), { mode: 384 });
3156
+ writeSecretFileSync(TEST_ACCOUNTS_PATH, JSON.stringify(store, null, 2));
3158
3157
  }
3159
3158
 
3160
3159
  // src/cli/lib/test-account-service.ts
@@ -3190,6 +3189,29 @@ async function createRemoteTestAccount(input) {
3190
3189
  }
3191
3190
  return { ...data, label: data.label ?? null };
3192
3191
  }
3192
+ async function recoverRemoteTestAccount(id) {
3193
+ const response = await fetch(`${AUTH_URL4}/api/auth/test-accounts/${id}/credential`, {
3194
+ method: "POST",
3195
+ headers: { Cookie: sessionCookie(), Origin: AUTH_URL4 }
3196
+ });
3197
+ const data = await response.json().catch(() => ({}));
3198
+ if (response.status === 404) {
3199
+ throw new Error(
3200
+ `No test account ${id} on this account. Run \`deepspace test accounts list\` for the current pool.`
3201
+ );
3202
+ }
3203
+ if (!response.ok || typeof data.password !== "string" || typeof data.email !== "string" || typeof data.userId !== "string") {
3204
+ throw new Error(data.error ?? "Failed to re-issue the test account credential");
3205
+ }
3206
+ return {
3207
+ id,
3208
+ email: data.email,
3209
+ userId: data.userId,
3210
+ label: data.label ?? null,
3211
+ createdAt: data.createdAt ?? Date.now(),
3212
+ password: data.password
3213
+ };
3214
+ }
3193
3215
  async function deleteRemoteTestAccount(id) {
3194
3216
  const response = await fetch(`${AUTH_URL4}/api/auth/test-accounts/${id}`, {
3195
3217
  method: "DELETE",
@@ -3342,10 +3364,10 @@ var test_default = defineDeepspaceCommand({
3342
3364
  if (forwarded.grep || forwarded.project || forwarded.headed) {
3343
3365
  console.error("note: --grep/--project/--headed apply to Playwright suites and are ignored by `unit`");
3344
3366
  }
3345
- exitCode = runVitest(appDir);
3367
+ exitCode = runVitest(appDir, wranglerEnv);
3346
3368
  break;
3347
3369
  case "all":
3348
- exitCode = runVitest(appDir);
3370
+ exitCode = runVitest(appDir, wranglerEnv);
3349
3371
  if (exitCode === 0) exitCode = runPlaywright(appDir, [], port, wranglerEnv, forwarded);
3350
3372
  break;
3351
3373
  case "default":
@@ -3376,7 +3398,7 @@ Available: smoke, api, e2e, unit, all`,
3376
3398
  return { data: { suite, port, appDir, wranglerEnv: wranglerEnv ?? null } };
3377
3399
  }
3378
3400
  });
3379
- function runPlaywright(appDir, testFiles, port, wranglerEnv, flags) {
3401
+ function runSuite(appDir, argv, wranglerEnv, extraEnv = {}) {
3380
3402
  let wranglerConfig;
3381
3403
  try {
3382
3404
  wranglerConfig = prepareWranglerEnvConfig(appDir, wranglerEnv);
@@ -3385,25 +3407,24 @@ function runPlaywright(appDir, testFiles, port, wranglerEnv, flags) {
3385
3407
  return 1;
3386
3408
  }
3387
3409
  try {
3388
- const result = spawnSync4("npx", playwrightTestArgs(testFiles, flags), {
3410
+ const result = spawnSync4("npx", argv, {
3389
3411
  cwd: appDir,
3390
3412
  stdio: "inherit",
3391
- env: wranglerViteEnv(process.env, wranglerConfig, {
3392
- DEEPSPACE_PORT: String(port),
3393
- PLAYWRIGHT_HTML_OUTPUT_DIR: ".deepspace/playwright-report"
3394
- })
3413
+ env: wranglerViteEnv(process.env, wranglerConfig, extraEnv)
3395
3414
  });
3396
3415
  return result.status ?? 1;
3397
3416
  } finally {
3398
3417
  wranglerConfig.cleanup();
3399
3418
  }
3400
3419
  }
3401
- function runVitest(appDir) {
3402
- const result = spawnSync4("npx", ["vitest", "run", "--passWithNoTests"], {
3403
- cwd: appDir,
3404
- stdio: "inherit"
3420
+ function runPlaywright(appDir, testFiles, port, wranglerEnv, flags = {}) {
3421
+ return runSuite(appDir, playwrightTestArgs(testFiles, flags), wranglerEnv, {
3422
+ DEEPSPACE_PORT: String(port),
3423
+ PLAYWRIGHT_HTML_OUTPUT_DIR: ".deepspace/playwright-report"
3405
3424
  });
3406
- return result.status ?? 1;
3425
+ }
3426
+ function runVitest(appDir, wranglerEnv) {
3427
+ return runSuite(appDir, ["vitest", "run", "--passWithNoTests"], wranglerEnv);
3407
3428
  }
3408
3429
 
3409
3430
  // src/cli/commands/screenshot.ts
@@ -3741,6 +3762,107 @@ Failed:
3741
3762
  return { data: { deleted: ok2, requested: targets.length, failures: [] } };
3742
3763
  }
3743
3764
  });
3765
+ var recover = defineDeepspaceCommand({
3766
+ meta: {
3767
+ name: "recover",
3768
+ description: "Re-issue the local credential for a test account you own"
3769
+ },
3770
+ args: {
3771
+ email: {
3772
+ type: "string",
3773
+ description: "Email of the test account to recover",
3774
+ required: false
3775
+ },
3776
+ id: {
3777
+ type: "string",
3778
+ description: "ID of the test account to recover",
3779
+ required: false
3780
+ },
3781
+ all: {
3782
+ type: "boolean",
3783
+ description: "Recover every pool account missing a local credential",
3784
+ required: false
3785
+ }
3786
+ },
3787
+ async run({ args }) {
3788
+ const selectors = [args.email, args.id, args.all].filter(Boolean).length;
3789
+ if (selectors === 0) {
3790
+ throw new Refusal("Provide --email <email>, --id <id>, or --all.", "missing_argument");
3791
+ }
3792
+ if (selectors > 1) {
3793
+ throw new Refusal("Provide exactly one of --email, --id, or --all.", "conflicting_arguments");
3794
+ }
3795
+ await ensureToken();
3796
+ let remote;
3797
+ try {
3798
+ remote = await fetchRemoteTestAccounts();
3799
+ } catch (err) {
3800
+ throw new Refusal(`Failed: ${err.message}`, "test_accounts_list_failed");
3801
+ }
3802
+ const localAccounts = loadAllTestAccounts();
3803
+ const known = new Set(localAccounts.map((account) => account.id));
3804
+ const localNames = new Map(
3805
+ localAccounts.flatMap(
3806
+ (account) => account.name ? [[account.email, account.name]] : []
3807
+ )
3808
+ );
3809
+ let targets;
3810
+ if (args.all) {
3811
+ targets = remote.filter((account) => !known.has(account.id));
3812
+ if (targets.length === 0) {
3813
+ if (!args.json) console.log("Every pool account already has a local credential.");
3814
+ return { data: { recovered: [], alreadyLocal: remote.length } };
3815
+ }
3816
+ } else {
3817
+ const match = args.id ? remote.find((account) => account.id === args.id) : remote.find((account) => account.email === args.email);
3818
+ if (!match) {
3819
+ throw new Refusal(
3820
+ `No test account ${args.id ? `with id ${args.id}` : `with email ${args.email}`} on this account.`,
3821
+ "test_account_not_found",
3822
+ { action: cliAction("deepspace", "test", "accounts", "list") }
3823
+ );
3824
+ }
3825
+ targets = [match];
3826
+ }
3827
+ const recovered = [];
3828
+ const failures = [];
3829
+ for (const target2 of targets) {
3830
+ try {
3831
+ const account = await recoverRemoteTestAccount(target2.id);
3832
+ const name = localNames.get(account.email);
3833
+ upsertTestAccount({
3834
+ id: account.id,
3835
+ email: account.email,
3836
+ password: account.password,
3837
+ userId: account.userId,
3838
+ label: account.label,
3839
+ ...name ? { name } : {},
3840
+ createdAt: account.createdAt
3841
+ });
3842
+ recovered.push({ id: account.id, email: account.email });
3843
+ } catch (err) {
3844
+ failures.push({ email: target2.email, error: err.message });
3845
+ }
3846
+ }
3847
+ if (failures.length > 0) {
3848
+ throw new Refusal(
3849
+ `Recovered ${recovered.length}/${targets.length} test account(s).
3850
+ Failed:
3851
+ ` + failures.map((failure) => ` ${failure.email}: ${failure.error}`).join("\n"),
3852
+ "test_account_recover_failed",
3853
+ { extra: { recovered, requested: targets.length, failures } }
3854
+ );
3855
+ }
3856
+ if (!args.json) {
3857
+ for (const account of recovered) console.log(`Recovered ${account.email}`);
3858
+ console.log(
3859
+ `
3860
+ Stored in ${TEST_ACCOUNTS_PATH}. The previous password (if any machine still holds one) no longer works \u2014 recover there too.`
3861
+ );
3862
+ }
3863
+ return { data: { recovered, requested: targets.length, failures: [] } };
3864
+ }
3865
+ });
3744
3866
  var test_accounts_default = defineCommand2({
3745
3867
  meta: {
3746
3868
  name: "test-accounts",
@@ -3750,7 +3872,8 @@ var test_accounts_default = defineCommand2({
3750
3872
  create,
3751
3873
  list,
3752
3874
  delete: del,
3753
- clear
3875
+ clear,
3876
+ recover
3754
3877
  }
3755
3878
  });
3756
3879
 
@@ -4470,7 +4593,7 @@ function declaredCompatibilityDate(config) {
4470
4593
  return typeof declared === "string" && /^\d{4}-\d{2}-\d{2}$/.test(declared) ? declared : null;
4471
4594
  }
4472
4595
  async function buildDeployBundle(options) {
4473
- const { appDir, appName, envName, output, spinner: spinner2 } = options;
4596
+ const { appDir, appName, appId, envName, output, spinner: spinner2 } = options;
4474
4597
  const junk = removeMacosJunk(appDir);
4475
4598
  if (junk > 0) p6.log.info(`Removed ${junk} macOS metadata file(s) (._*, .DS_Store)`);
4476
4599
  const lintFindings = await lintProjectSchemas(appDir);
@@ -4545,6 +4668,8 @@ async function buildDeployBundle(options) {
4545
4668
  const assets = collectAssets(clientDir);
4546
4669
  const assetConfig = readDeployAssetConfig(clientDir);
4547
4670
  spinner2.stop(`Collected ${assets.length} assets`);
4671
+ const appIdRefusal = clientAppIdRefusal(assets, appId, envName);
4672
+ if (appIdRefusal) output.die(appIdRefusal.message, appIdRefusal.code);
4548
4673
  const workerJs = readFileSync17(workerBundlePath, "utf-8");
4549
4674
  const doBindings = outputConfig.durable_objects?.bindings;
4550
4675
  const sqliteClasses = new Set(
@@ -4613,6 +4738,44 @@ function oversizedAssetRefusal(assets) {
4613
4738
  if (!tooBig) return null;
4614
4739
  return `${tooBig.path} is ${formatBytes(tooBig.size)}; the per-file limit for deploy assets is ${formatBytes(MAX_DEPLOY_ASSET_FILE_BYTES)}. ${LARGE_MEDIA_HOME}`;
4615
4740
  }
4741
+ var CLIENT_CODE_ASSET = /\.(?:js|mjs|html)$/i;
4742
+ var APP_ID_LITERAL = /app_[0-9A-HJKMNP-TV-Z]{26}/g;
4743
+ var APP_ID_DEFINE = "__DEEPSPACE_APP_ID__";
4744
+ var APP_ID_ADOPTION_STEPS = `Point the client at the build-injected id \u2014 the SDK's \`deepspace/build\` supplies it:
4745
+ \u2022 vite.config.ts: import { deepspaceBuild } from 'deepspace/build' and add
4746
+ deepspaceBuild({ appDir: fileURLToPath(new URL('.', import.meta.url)) }) to plugins
4747
+ \u2022 vitest.config.ts: import { appIdDefine } and set define: appIdDefine({ appDir })
4748
+ \u2022 src/constants.ts: declare const __DEEPSPACE_APP_ID__: string; export it as APP_ID
4749
+ (drop the frozen literal)
4750
+ Apply all three together: src/constants.ts on its own builds clean and then throws in the browser. A newly scaffolded app already has this.`;
4751
+ function clientAppIdRefusal(assets, appId, envName) {
4752
+ const foreign = /* @__PURE__ */ new Map();
4753
+ let unsubstituted;
4754
+ for (const asset of assets) {
4755
+ if (!CLIENT_CODE_ASSET.test(asset.path)) continue;
4756
+ const source = readFileSync17(asset.sourcePath, "utf-8");
4757
+ if (unsubstituted === void 0 && source.includes(APP_ID_DEFINE)) unsubstituted = asset.path;
4758
+ for (const found of source.matchAll(APP_ID_LITERAL)) {
4759
+ if (found[0] !== appId && !foreign.has(found[0])) foreign.set(found[0], asset.path);
4760
+ }
4761
+ }
4762
+ if (unsubstituted !== void 0) {
4763
+ return {
4764
+ code: "app_id_define_unsubstituted",
4765
+ message: `Built client assets still contain \`${APP_ID_DEFINE}\` (first in ${unsubstituted}) \u2014 nothing replaced it at build time. The browser evaluates that identifier and throws ReferenceError before the app renders, so this bundle is a blank page for every visitor. src/constants.ts was moved onto the build-time app id without the build config that supplies it.
4766
+ ${APP_ID_ADOPTION_STEPS}`
4767
+ };
4768
+ }
4769
+ if (foreign.size === 0) return null;
4770
+ const target2 = envName ? `--env ${envName} (${appId})` : appId;
4771
+ return {
4772
+ code: "app_id_env_mismatch",
4773
+ message: `Built client assets carry a different app id than the one being deployed. Deploying ${target2}, but the bundle contains:
4774
+ ` + [...foreign].map(([id, path]) => ` \u2022 ${id} in ${path}`).join("\n") + `
4775
+ The browser keys its RecordRooms to the id in the bundle, so this would serve ${envName ? `${envName}'s` : "this app's"} worker against another app's data. The usual cause is an app scaffolded before the id was build-injected: src/constants.ts still holds \`export const APP_ID = 'app_\u2026'\` frozen at scaffold time.
4776
+ ${APP_ID_ADOPTION_STEPS}`
4777
+ };
4778
+ }
4616
4779
  var LARGE_MEDIA_HOME = "Large media belongs in your app files (`deepspace app files put`), not in the deploy bundle \u2014 move it out of `public/` and reference it at runtime.";
4617
4780
  function collectAssets(dir) {
4618
4781
  const assets = [];
@@ -6252,7 +6415,7 @@ var deploy_default = defineCommand3({
6252
6415
  output.showIntro();
6253
6416
  const wranglerConfig = readWranglerConfig(appDir);
6254
6417
  const nameResult = resolveAppNameForEnv(wranglerConfig, envName);
6255
- if (!nameResult.ok) return output.die(nameResult.reason, "invalid_app_name");
6418
+ if (!nameResult.ok) return output.die(nameResult.reason, nameResult.code ?? "invalid_app_name");
6256
6419
  const declaredName = envName ? wranglerConfig.env?.[envName]?.name : wranglerConfig.name;
6257
6420
  if (declaredName !== nameResult.name) {
6258
6421
  const location = envName ? `[env.${envName}].name` : "`name`";
@@ -6307,6 +6470,7 @@ var deploy_default = defineCommand3({
6307
6470
  const bundle = await buildDeployBundle({
6308
6471
  appDir,
6309
6472
  appName,
6473
+ appId,
6310
6474
  envName,
6311
6475
  output,
6312
6476
  spinner: spinner2
@@ -7142,7 +7306,7 @@ import {
7142
7306
  readSync,
7143
7307
  readdirSync as readdirSync5,
7144
7308
  rmSync as rmSync4,
7145
- writeFileSync as writeFileSync6
7309
+ writeFileSync as writeFileSync5
7146
7310
  } from "fs";
7147
7311
  import { spawnSync as spawnSync6 } from "child_process";
7148
7312
  import { tmpdir as tmpdir2 } from "os";
@@ -7241,7 +7405,7 @@ function copyFileWithImportShift(src, dest, levels) {
7241
7405
  /(from\s+|import\s+|import\()(['"])(\.\.\/)/g,
7242
7406
  `$1$2${prefix}$3`
7243
7407
  );
7244
- writeFileSync6(dest, shifted);
7408
+ writeFileSync5(dest, shifted);
7245
7409
  }
7246
7410
  function installFiles(packageRoot, config, targetDir, emit) {
7247
7411
  let copied = 0;
@@ -7327,7 +7491,7 @@ export const schemas`
7327
7491
  return [unresolvedSchema(config.schema, reason, emit)];
7328
7492
  }
7329
7493
  content = withEntry;
7330
- writeFileSync6(schemasPath, content);
7494
+ writeFileSync5(schemasPath, content);
7331
7495
  emit(` Schema integrated: ${exportName} -> schemas.ts`);
7332
7496
  return [];
7333
7497
  }
@@ -7393,7 +7557,7 @@ ${registryEntry}`);
7393
7557
  integrated++;
7394
7558
  emit(` Action integrated: ${action.registerAs} (${action.exportName})`);
7395
7559
  }
7396
- if (integrated > 0) writeFileSync6(actionsPath, content);
7560
+ if (integrated > 0) writeFileSync5(actionsPath, content);
7397
7561
  return unresolved;
7398
7562
  }
7399
7563
  function integrateCode(config, targetDir, emit) {
@@ -7438,7 +7602,7 @@ ${content.slice(
7438
7602
  const prefix = insertion.placement === "before" ? "" : "\n";
7439
7603
  const suffix = insertion.placement === "before" ? "\n" : "";
7440
7604
  content = `${content.slice(0, insertAt)}${prefix}${insertion.insert}${suffix}${content.slice(insertAt)}`;
7441
- writeFileSync6(targetPath, content);
7605
+ writeFileSync5(targetPath, content);
7442
7606
  emit(` Integrated: ${insertion.file}`);
7443
7607
  }
7444
7608
  return unresolved;
@@ -7451,7 +7615,7 @@ function integrateIgnore(config, targetDir) {
7451
7615
  const missing = config.ignore.filter((pattern) => !present.has(pattern));
7452
7616
  if (missing.length === 0) return;
7453
7617
  const separator = current === "" || current.endsWith("\n") ? "" : "\n";
7454
- writeFileSync6(targetPath, `${current}${separator}${missing.join("\n")}
7618
+ writeFileSync5(targetPath, `${current}${separator}${missing.join("\n")}
7455
7619
  `);
7456
7620
  }
7457
7621
  function integrateWrangler(config, targetDir, emit) {
@@ -7488,7 +7652,7 @@ ${rateLimitBindingBlock(scope, binding)}
7488
7652
  }
7489
7653
  }
7490
7654
  if (changed) {
7491
- writeFileSync6(targetPath, source);
7655
+ writeFileSync5(targetPath, source);
7492
7656
  emit(" Integrated: wrangler.toml");
7493
7657
  } else emit(" Integration already present: wrangler.toml");
7494
7658
  return [];
@@ -7581,7 +7745,7 @@ ${css}`;
7581
7745
  integrated++;
7582
7746
  emit(` CSS integrated: ${cssFile} -> styles.css`);
7583
7747
  }
7584
- if (integrated > 0) writeFileSync6(stylesPath, styles);
7748
+ if (integrated > 0) writeFileSync5(stylesPath, styles);
7585
7749
  return unresolved;
7586
7750
  }
7587
7751
  function unresolvedRoute(route, config, reason, emit) {
@@ -7628,7 +7792,7 @@ function integrateRoute(config, targetDir, emit) {
7628
7792
  const entry = ` { path: '${routePath}', label: '${config.name}'${roles} },`;
7629
7793
  content = content.replace(NAV_MARKER, `${NAV_MARKER}
7630
7794
  ${entry}`);
7631
- writeFileSync6(navPath, content);
7795
+ writeFileSync5(navPath, content);
7632
7796
  emit(` Nav wired: ${routePath} (${config.name})`);
7633
7797
  emit(" Route: automatic (file-based routing via src/pages/)");
7634
7798
  return [];
@@ -7712,7 +7876,7 @@ function integrateDependencies(config, targetDir, runInstall, outputVisible, emi
7712
7876
  if (added.length === 0) {
7713
7877
  emit(" Dependencies already present");
7714
7878
  } else {
7715
- writeFileSync6(packagePath, `${JSON.stringify(pkg2, null, 2)}
7879
+ writeFileSync5(packagePath, `${JSON.stringify(pkg2, null, 2)}
7716
7880
  `, "utf-8");
7717
7881
  emit(` Added to package.json: ${added.map(({ name }) => name).join(", ")}`);
7718
7882
  if (skipped.length) emit(` Already present (kept your version): ${skipped.join(", ")}`);
@@ -10337,7 +10501,6 @@ var init_default = defineDeepspaceCommand({
10337
10501
  method: "POST"
10338
10502
  });
10339
10503
  writeAppId(appDir, appId, { wranglerEnv: envName, force: Boolean(args["new-id"]) });
10340
- if (!envName) stampAppIdInSources(appDir, appId, existing);
10341
10504
  const committedScaffold = commitScaffoldIfUnborn(appDir, token);
10342
10505
  if (!args.json) {
10343
10506
  if (existing) {
@@ -11662,7 +11825,7 @@ import {
11662
11825
  realpathSync as realpathSync6,
11663
11826
  renameSync as renameSync2,
11664
11827
  statSync as statSync3,
11665
- writeFileSync as writeFileSync7
11828
+ writeFileSync as writeFileSync6
11666
11829
  } from "fs";
11667
11830
  import { dirname as dirname11, isAbsolute as isAbsolute5, join as join22, relative as relative2, sep as sep2 } from "path";
11668
11831
  var WORKSPACE_MARKER = "deepspace-workspace.json";
@@ -11697,7 +11860,7 @@ function markManagedWorkspace(worktreeRoot, workspaceId) {
11697
11860
  const tempPath = `${markerPath}.${process.pid}.tmp`;
11698
11861
  try {
11699
11862
  mkdirSync7(dirname11(markerPath), { recursive: true });
11700
- writeFileSync7(tempPath, JSON.stringify(marker) + "\n");
11863
+ writeFileSync6(tempPath, JSON.stringify(marker) + "\n");
11701
11864
  renameSync2(tempPath, markerPath);
11702
11865
  return true;
11703
11866
  } catch {
@@ -13831,13 +13994,13 @@ function sameRefs(left, right) {
13831
13994
 
13832
13995
  // src/cli/commands/update.ts
13833
13996
  import * as p34 from "@clack/prompts";
13834
- import { existsSync as existsSync26, readFileSync as readFileSync27, writeFileSync as writeFileSync8 } from "fs";
13997
+ import { existsSync as existsSync26, readFileSync as readFileSync27, writeFileSync as writeFileSync7 } from "fs";
13835
13998
  import { join as join24, resolve as resolve20 } from "path";
13836
13999
 
13837
14000
  // package.json
13838
14001
  var package_default = {
13839
14002
  name: "deepspace",
13840
- version: "0.22.1",
14003
+ version: "0.23.0",
13841
14004
  engines: {
13842
14005
  node: ">=22.15.0 <23 || >=24 <25 || >=26 <27"
13843
14006
  },
@@ -13874,6 +14037,10 @@ var package_default = {
13874
14037
  types: "./dist/testing-mcp.d.ts",
13875
14038
  import: "./dist/testing-mcp.js"
13876
14039
  },
14040
+ "./build": {
14041
+ types: "./dist/build.d.ts",
14042
+ import: "./dist/build.js"
14043
+ },
13877
14044
  "./documentation": {
13878
14045
  types: "./dist/documentation.d.ts",
13879
14046
  import: "./dist/documentation.js"
@@ -14037,7 +14204,7 @@ function pinSdkVersion(appDir, version) {
14037
14204
  const eol = source.includes("\r\n") ? "\r\n" : "\n";
14038
14205
  const indent = source.match(/\r?\n([\t ]+)"/)?.[1] ?? " ";
14039
14206
  const finalEol = source.endsWith("\n") ? eol : "";
14040
- writeFileSync8(path, JSON.stringify(manifest, null, indent).replaceAll("\n", eol) + finalEol);
14207
+ writeFileSync7(path, JSON.stringify(manifest, null, indent).replaceAll("\n", eol) + finalEol);
14041
14208
  return true;
14042
14209
  }
14043
14210
  function buildPreviewSecretsUpgradeInstruction(appDir) {
@@ -14049,8 +14216,10 @@ function buildPreviewSecretsUpgradeInstruction(appDir) {
14049
14216
  ].find((candidate) => existsSync26(join24(appDir, candidate)));
14050
14217
  if (!configName) return null;
14051
14218
  const source = readFileSync27(join24(appDir, configName), "utf8");
14052
- if (source.includes("deepspace-remove-build-preview-secrets")) return null;
14053
- return `${configName}: add the build-preview secret cleanup from ${BUILD_PREVIEW_SECRETS_GUIDE}; app update left this app-owned file unchanged.`;
14219
+ if (source.includes("deepspace-remove-build-preview-secrets") || source.includes("deepspaceBuild(") || source.includes("'deepspace/build'") || source.includes('"deepspace/build"')) {
14220
+ return null;
14221
+ }
14222
+ return `${configName}: adopt the deepspaceBuild() plugin from deepspace/build (which owns this cleanup), or add the inline cleanup from ${BUILD_PREVIEW_SECRETS_GUIDE}; app update left this app-owned file unchanged.`;
14054
14223
  }
14055
14224
  function usersSchemaVisibilityUpgradeInstruction(appDir) {
14056
14225
  const schemaName = "src/schemas/users-schema.ts";