deepspace 0.10.1 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/chunk-T7V4GUIN.js +1638 -0
  3. package/dist/chunk-T7V4GUIN.js.map +1 -0
  4. package/dist/cli.js +731 -324
  5. package/dist/cli.js.map +1 -1
  6. package/dist/documentation-client-core.d.ts +18 -0
  7. package/dist/documentation-client-core.js +395 -0
  8. package/dist/documentation-client-core.js.map +1 -0
  9. package/dist/documentation-default-renderer.cjs +124 -0
  10. package/dist/documentation-react.d.ts +2 -0
  11. package/dist/documentation-react.js +35 -0
  12. package/dist/documentation-react.js.map +1 -0
  13. package/dist/documentation-runtime.js +71 -0
  14. package/dist/documentation-server-core.d.ts +11 -0
  15. package/dist/documentation-server-core.js +48 -0
  16. package/dist/documentation-server-core.js.map +1 -0
  17. package/dist/documentation.d.ts +366 -0
  18. package/dist/documentation.js +3953 -0
  19. package/dist/documentation.js.map +1 -0
  20. package/dist/feature-installer.js +145 -0
  21. package/dist/feature-installer.js.map +1 -1
  22. package/dist/index.d.ts +177 -1
  23. package/dist/index.js +133 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/public-BGRX_Cjd.d.ts +234 -0
  26. package/dist/schema.d.ts +186 -0
  27. package/dist/schema.js +610 -0
  28. package/dist/schema.js.map +1 -0
  29. package/dist/server.d.ts +83 -4
  30. package/dist/server.js +296 -1
  31. package/dist/server.js.map +1 -1
  32. package/dist/testing.d.ts +5 -8
  33. package/dist/testing.js +48 -14
  34. package/dist/testing.js.map +1 -1
  35. package/dist/worker.d.ts +249 -5
  36. package/dist/worker.js +1438 -19
  37. package/dist/worker.js.map +1 -1
  38. package/features/admin-page/src/admin-schema.ts +1 -1
  39. package/features/ai-chat/feature.json +2 -2
  40. package/features/ai-chat/src/ChatPanel.tsx +6 -11
  41. package/features/ai-chat/src/ai-chat-schema.ts +1 -2
  42. package/features/canvas/src/canvas-schema.ts +1 -1
  43. package/features/documentation/content/index.mdx +14 -0
  44. package/features/documentation/content/quickstart.mdx +12 -0
  45. package/features/documentation/documentation.json +18 -0
  46. package/features/documentation/feature.json +68 -0
  47. package/features/{docs → documents}/feature.json +27 -27
  48. package/features/{docs/src/DocsEditorPage.tsx → documents/src/DocumentsEditorPage.tsx} +74 -74
  49. package/features/{docs/src/DocsListPage.tsx → documents/src/DocumentsListPage.tsx} +102 -102
  50. package/features/{docs/src/DocsOutlinePanel.tsx → documents/src/DocumentsOutlinePanel.tsx} +9 -9
  51. package/features/{docs/src/DocsPresence.tsx → documents/src/DocumentsPresence.tsx} +18 -18
  52. package/features/{docs → documents}/src/InviteDialog.tsx +21 -21
  53. package/features/{docs → documents}/src/LibrarySidebar.tsx +25 -25
  54. package/features/documents/src/__tests__/documents-presence-access.test.ts +71 -0
  55. package/features/{docs/src/docs-library-types.ts → documents/src/documents-library-types.ts} +3 -3
  56. package/features/{docs/src/docs-schema.ts → documents/src/documents-schema.ts} +6 -6
  57. package/features/{docs/src/docs-ui.css → documents/src/documents-ui.css} +89 -89
  58. package/features/{docs → documents}/src/editor/DocEditorSurface.tsx +11 -11
  59. package/features/{docs/src/editor/DocsTiptapToolbar.tsx → documents/src/editor/DocumentsTiptapToolbar.tsx} +21 -21
  60. package/features/{docs → documents}/src/editor/useDocEditor.ts +4 -4
  61. package/features/{docs/src/use-docs-presence-access.ts → documents/src/use-documents-presence-access.ts} +51 -51
  62. package/features/file-attachments/src/attachments-schema.ts +1 -1
  63. package/features/items/src/items-schema.ts +1 -1
  64. package/features/leaderboard/src/leaderboard-schema.ts +1 -1
  65. package/features/messaging/src/ChatMultiPage.tsx +1 -1
  66. package/features/messaging/src/ChatPage.tsx +1 -1
  67. package/features/messaging/src/messaging-schema.ts +2 -2
  68. package/features/tasks/src/tasks-schema.ts +1 -1
  69. package/features/testing/src/test-schema.ts +2 -2
  70. package/package.json +26 -1
  71. package/features/docs/src/__tests__/docs-presence-access.test.ts +0 -71
package/dist/cli.js CHANGED
@@ -3,8 +3,8 @@
3
3
  // src/cli/cli.ts
4
4
  import { defineCommand as defineCommand14, runMain, runCommand } from "citty";
5
5
  import { sync as spawnSync8 } from "cross-spawn";
6
- import { readFileSync as readFileSync26, existsSync as existsSync25 } from "fs";
7
- import { dirname as dirname10, join as join24 } from "path";
6
+ import { readFileSync as readFileSync28, existsSync as existsSync26 } from "fs";
7
+ import { dirname as dirname10, join as join25 } from "path";
8
8
  import { fileURLToPath as fileURLToPath3 } from "url";
9
9
 
10
10
  // src/cli/lib/spinner.ts
@@ -1372,7 +1372,8 @@ var ALLOWED_BINDING_TYPES = /* @__PURE__ */ new Set([
1372
1372
  "queue",
1373
1373
  "browser_rendering",
1374
1374
  "analytics_engine",
1375
- "hyperdrive"
1375
+ "hyperdrive",
1376
+ "ratelimit"
1376
1377
  ]);
1377
1378
  var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
1378
1379
  "ASSETS",
@@ -1380,6 +1381,7 @@ var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
1380
1381
  "API_WORKER",
1381
1382
  "DEEPSPACE_APP_ID",
1382
1383
  "DEEPSPACE_RESOURCE_ID",
1384
+ "DEEPSPACE_RELEASE_OPERATION_ID",
1383
1385
  "APP_NAME",
1384
1386
  "OWNER_USER_ID",
1385
1387
  "AUTH_JWT_PUBLIC_KEY",
@@ -1470,6 +1472,17 @@ function requiredFieldError(b) {
1470
1472
  return `hyperdrive binding "${b.name}": auto-provisioning not yet supported (provide a real id)`;
1471
1473
  }
1472
1474
  return null;
1475
+ case "ratelimit":
1476
+ if (!/^\d+$/u.test(b.namespace_id) || Number(b.namespace_id) <= 0) {
1477
+ return `ratelimit binding "${b.name}" requires a positive integer namespace_id string`;
1478
+ }
1479
+ if (!Number.isInteger(b.simple?.limit) || b.simple.limit <= 0) {
1480
+ return `ratelimit binding "${b.name}" requires simple.limit (positive integer)`;
1481
+ }
1482
+ if (b.simple.period !== 10 && b.simple.period !== 60) {
1483
+ return `ratelimit binding "${b.name}" requires simple.period (10 or 60)`;
1484
+ }
1485
+ return null;
1473
1486
  case "ai":
1474
1487
  case "browser_rendering":
1475
1488
  case "analytics_engine":
@@ -1547,6 +1560,15 @@ function bindingManifestFromOutputConfig(outputConfig) {
1547
1560
  const id = pluckString(h, "id");
1548
1561
  if (name && id) out.push({ type: "hyperdrive", name, id });
1549
1562
  }
1563
+ for (const r of asObjectArray(outputConfig.ratelimits)) {
1564
+ const name = pluckString(r, "name");
1565
+ const namespace_id = pluckString(r, "namespace_id");
1566
+ const simple = isObject(r.simple) ? r.simple : null;
1567
+ const limit = pluckNumber(simple, "limit");
1568
+ const period = pluckNumber(simple, "period");
1569
+ if (!name || !namespace_id || limit === null || period !== 10 && period !== 60) continue;
1570
+ out.push({ type: "ratelimit", name, namespace_id, simple: { limit, period } });
1571
+ }
1550
1572
  return out;
1551
1573
  }
1552
1574
  function isObject(v) {
@@ -1897,6 +1919,7 @@ var NON_INHERITABLE_ENV_KEYS = /* @__PURE__ */ new Set([
1897
1919
  "r2_buckets",
1898
1920
  "ai_search_namespaces",
1899
1921
  "ai_search",
1922
+ "ratelimits",
1900
1923
  "vectorize",
1901
1924
  "services",
1902
1925
  "queues",
@@ -1998,6 +2021,13 @@ function flattenWranglerEnvConfig(config, envName) {
1998
2021
  for (const [key, value] of Object.entries(envBlock)) {
1999
2022
  flattened[key] = value;
2000
2023
  }
2024
+ if (!("durable_objects" in envBlock) && config.durable_objects) {
2025
+ flattened.durable_objects = config.durable_objects;
2026
+ }
2027
+ const envVars = asTomlRecord(envBlock.vars) ?? {};
2028
+ if (!("APP_NAME" in envVars) && typeof envBlock.name === "string") {
2029
+ flattened.vars = { ...envVars, APP_NAME: envBlock.name };
2030
+ }
2001
2031
  return flattened;
2002
2032
  }
2003
2033
  function asTomlRecord(value) {
@@ -2532,6 +2562,9 @@ function noAppDirMessage(start, children) {
2532
2562
  }
2533
2563
  return lines.join("\n");
2534
2564
  }
2565
+ function devExitSucceeded(code, stopping) {
2566
+ return stopping || code === null || code === 0;
2567
+ }
2535
2568
  var dev_default = defineDeepspaceCommand({
2536
2569
  meta: {
2537
2570
  name: "dev",
@@ -2668,7 +2701,9 @@ Free it with \`${killCmd}\`, or start on another port: \`deepspace dev start --p
2668
2701
  stdio: "inherit",
2669
2702
  env: wranglerViteEnv(process.env, wranglerConfig, { DEEPSPACE_PORT: String(port) })
2670
2703
  });
2704
+ let stopping = false;
2671
2705
  const stop = () => {
2706
+ stopping = true;
2672
2707
  wranglerConfig.cleanup();
2673
2708
  vite.kill();
2674
2709
  };
@@ -2680,7 +2715,9 @@ Free it with \`${killCmd}\`, or start on another port: \`deepspace dev start --p
2680
2715
  res(c);
2681
2716
  });
2682
2717
  });
2683
- if (code !== null && code !== 0) {
2718
+ process.off("SIGINT", stop);
2719
+ process.off("SIGTERM", stop);
2720
+ if (!devExitSucceeded(code, stopping)) {
2684
2721
  throw new Refusal(`Dev server exited with code ${code}.`, "dev_server_failed", {
2685
2722
  extra: { port, appDir, exitCode: code }
2686
2723
  });
@@ -3000,6 +3037,169 @@ function ensurePlaywright(appDir) {
3000
3037
  }
3001
3038
  }
3002
3039
 
3040
+ // src/cli/lib/test-account-service.ts
3041
+ import { readFileSync as readFileSync14 } from "fs";
3042
+
3043
+ // src/testing/accounts.ts
3044
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync13, writeFileSync as writeFileSync7 } from "fs";
3045
+ import { homedir as homedir3 } from "os";
3046
+ import { join as join13 } from "path";
3047
+ var TEST_ACCOUNTS_PATH = join13(homedir3(), ".deepspace", "test-accounts.json");
3048
+ var PRODUCTION_AUTH_ORIGIN = "https://auth.deep.space";
3049
+ var STAGING_AUTH_ORIGIN = "https://auth.deepspacesites.com";
3050
+ function currentTestAccountScope(env = process.env) {
3051
+ const selected = env.DEEPSPACE_AUTH_URL ?? (env.DEEPSPACE_ENV === "staging" ? STAGING_AUTH_ORIGIN : PRODUCTION_AUTH_ORIGIN);
3052
+ return normalizeTestAccountScope(selected);
3053
+ }
3054
+ function normalizeTestAccountScope(value) {
3055
+ try {
3056
+ return new URL(value).origin;
3057
+ } catch {
3058
+ return value;
3059
+ }
3060
+ }
3061
+ function loadAllTestAccounts(scope = currentTestAccountScope()) {
3062
+ const store = readTestAccountStore();
3063
+ return mergeTestAccounts(store.scopes[scope] ?? [], store.unscoped);
3064
+ }
3065
+ function reconcileTestAccountStore(remote, scope = currentTestAccountScope()) {
3066
+ const result = reconcileTestAccountScopes(readTestAccountStore(), remote, scope);
3067
+ saveTestAccountStore(result.store);
3068
+ return { accounts: result.accounts, removed: result.removed };
3069
+ }
3070
+ function reconcileTestAccountScopes(store, remote, scope) {
3071
+ const scoped = store.scopes[scope] ?? [];
3072
+ const current = reconcileTestAccounts(scoped, remote);
3073
+ const migrated = reconcileTestAccounts(store.unscoped, remote);
3074
+ const accounts = mergeTestAccounts(current, migrated);
3075
+ return {
3076
+ accounts,
3077
+ removed: Math.max(0, scoped.length - current.length),
3078
+ store: {
3079
+ version: 2,
3080
+ scopes: { ...store.scopes, [scope]: accounts },
3081
+ unscoped: store.unscoped.filter((account) => !remote.some((item) => accountMatchesRemote(account, item)))
3082
+ }
3083
+ };
3084
+ }
3085
+ function reconcileTestAccounts(local, remote) {
3086
+ const localById = new Map(local.filter((account) => account.id).map((account) => [account.id, account]));
3087
+ const legacyByEmail = new Map(local.filter((account) => !account.id).map((account) => [account.email, account]));
3088
+ return remote.flatMap((account) => {
3089
+ const stored = localById.get(account.id) ?? legacyByEmail.get(account.email);
3090
+ return stored ? [{ ...stored, ...account }] : [];
3091
+ });
3092
+ }
3093
+ function upsertTestAccount(account, scope = currentTestAccountScope()) {
3094
+ const store = readTestAccountStore();
3095
+ const accounts = (store.scopes[scope] ?? []).filter(
3096
+ (stored) => stored.id !== account.id && stored.email !== account.email
3097
+ );
3098
+ saveTestAccountStore({
3099
+ version: 2,
3100
+ scopes: { ...store.scopes, [scope]: [...accounts, account] },
3101
+ unscoped: store.unscoped.filter(
3102
+ (stored) => stored.id !== account.id && stored.email !== account.email
3103
+ )
3104
+ });
3105
+ }
3106
+ function removeTestAccounts(ids, emails = [], scope = currentTestAccountScope()) {
3107
+ const idSet = new Set(ids);
3108
+ const emailSet = new Set(emails);
3109
+ const keep = (account) => !idSet.has(account.id ?? "") && !emailSet.has(account.email);
3110
+ const store = readTestAccountStore();
3111
+ saveTestAccountStore({
3112
+ version: 2,
3113
+ scopes: { ...store.scopes, [scope]: (store.scopes[scope] ?? []).filter(keep) },
3114
+ unscoped: store.unscoped.filter(keep)
3115
+ });
3116
+ }
3117
+ function readTestAccountStore() {
3118
+ if (!existsSync12(TEST_ACCOUNTS_PATH)) return { version: 2, scopes: {}, unscoped: [] };
3119
+ try {
3120
+ const raw = JSON.parse(readFileSync13(TEST_ACCOUNTS_PATH, "utf-8"));
3121
+ if (Array.isArray(raw)) return { version: 2, scopes: {}, unscoped: validTestAccounts(raw) };
3122
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
3123
+ return { version: 2, scopes: {}, unscoped: [] };
3124
+ }
3125
+ const candidate = raw;
3126
+ const scopes = candidate.scopes && typeof candidate.scopes === "object" && !Array.isArray(candidate.scopes) ? Object.fromEntries(Object.entries(candidate.scopes).map(([key, value]) => [key, validTestAccounts(value)])) : {};
3127
+ return { version: 2, scopes, unscoped: validTestAccounts(candidate.unscoped) };
3128
+ } catch {
3129
+ return { version: 2, scopes: {}, unscoped: [] };
3130
+ }
3131
+ }
3132
+ function validTestAccounts(value) {
3133
+ if (!Array.isArray(value)) return [];
3134
+ return value.filter((item) => Boolean(
3135
+ item && typeof item === "object" && typeof item.email === "string" && typeof item.password === "string"
3136
+ ));
3137
+ }
3138
+ function mergeTestAccounts(...groups) {
3139
+ const accounts = /* @__PURE__ */ new Map();
3140
+ for (const account of groups.flat()) {
3141
+ const key = account.id ? `id:${account.id}` : `email:${account.email}`;
3142
+ if (!accounts.has(key)) accounts.set(key, account);
3143
+ }
3144
+ return [...accounts.values()];
3145
+ }
3146
+ function accountMatchesRemote(local, remote) {
3147
+ return local.id ? local.id === remote.id : local.email === remote.email;
3148
+ }
3149
+ function saveTestAccountStore(store) {
3150
+ mkdirSync5(join13(homedir3(), ".deepspace"), { recursive: true });
3151
+ writeFileSync7(TEST_ACCOUNTS_PATH, JSON.stringify(store, null, 2), { mode: 384 });
3152
+ }
3153
+
3154
+ // src/cli/lib/test-account-service.ts
3155
+ var SESSION_COOKIE2 = "__Secure-better-auth.session_token";
3156
+ var AUTH_URL4 = process.env.DEEPSPACE_AUTH_URL ?? PLATFORM_URLS.auth;
3157
+ function sessionCookie() {
3158
+ const token = readFileSync14(SESSION_PATH, "utf-8").trim();
3159
+ return `${SESSION_COOKIE2}=${encodeURIComponent(token)}`;
3160
+ }
3161
+ async function fetchRemoteTestAccounts() {
3162
+ const response = await fetch(`${AUTH_URL4}/api/auth/test-accounts`, {
3163
+ headers: { Cookie: sessionCookie(), Origin: AUTH_URL4 }
3164
+ });
3165
+ const data = await response.json().catch(() => ({}));
3166
+ if (!response.ok || !data.accounts) {
3167
+ throw new Error(data.error ?? "Failed to list test accounts");
3168
+ }
3169
+ return data.accounts;
3170
+ }
3171
+ async function createRemoteTestAccount(input) {
3172
+ const response = await fetch(`${AUTH_URL4}/api/auth/test-accounts`, {
3173
+ method: "POST",
3174
+ headers: {
3175
+ "Content-Type": "application/json",
3176
+ Cookie: sessionCookie(),
3177
+ Origin: AUTH_URL4
3178
+ },
3179
+ body: JSON.stringify(input)
3180
+ });
3181
+ const data = await response.json().catch(() => ({}));
3182
+ if (!response.ok || typeof data.id !== "string" || typeof data.email !== "string" || typeof data.userId !== "string" || typeof data.createdAt !== "number") {
3183
+ throw new Error(data.error ?? "Unknown error");
3184
+ }
3185
+ return { ...data, label: data.label ?? null };
3186
+ }
3187
+ async function deleteRemoteTestAccount(id) {
3188
+ const response = await fetch(`${AUTH_URL4}/api/auth/test-accounts/${id}`, {
3189
+ method: "DELETE",
3190
+ headers: { Cookie: sessionCookie(), Origin: AUTH_URL4 }
3191
+ });
3192
+ if (!response.ok) {
3193
+ const data = await response.json().catch(() => ({}));
3194
+ throw new Error(data.error ?? `DELETE returned ${response.status}`);
3195
+ }
3196
+ }
3197
+ async function syncTestAccountStore() {
3198
+ const accounts = await fetchRemoteTestAccounts();
3199
+ const { removed } = reconcileTestAccountStore(accounts, normalizeTestAccountScope(AUTH_URL4));
3200
+ return { accounts, removed };
3201
+ }
3202
+
3003
3203
  // src/cli/commands/test.ts
3004
3204
  var DEPLOY_URL2 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
3005
3205
  var test_default = defineDeepspaceCommand({
@@ -3075,6 +3275,14 @@ var test_default = defineDeepspaceCommand({
3075
3275
  generatedSecretsCache,
3076
3276
  sharedDevVarsCache
3077
3277
  });
3278
+ if (suite !== "unit") {
3279
+ try {
3280
+ const { removed } = await syncTestAccountStore();
3281
+ if (removed > 0) say(`Removed ${removed} stale test account credential(s).`);
3282
+ } catch (error) {
3283
+ say(`Warning: could not reconcile test accounts: ${error instanceof Error ? error.message : String(error)}`);
3284
+ }
3285
+ }
3078
3286
  if (suite !== "unit") {
3079
3287
  preflightWindowsWorkerd(appDir);
3080
3288
  ensurePlaywright(appDir);
@@ -3226,49 +3434,6 @@ var screenshot_default = defineDeepspaceCommand({
3226
3434
  // src/cli/commands/test-accounts.ts
3227
3435
  import { defineCommand as defineCommand2 } from "citty";
3228
3436
  import * as p5 from "@clack/prompts";
3229
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync7, existsSync as existsSync12, mkdirSync as mkdirSync5 } from "fs";
3230
- import { join as join13 } from "path";
3231
- import { homedir as homedir3 } from "os";
3232
- var SESSION_COOKIE2 = "__Secure-better-auth.session_token";
3233
- var AUTH_URL4 = process.env.DEEPSPACE_AUTH_URL ?? PLATFORM_URLS.auth;
3234
- var DIR2 = join13(homedir3(), ".deepspace");
3235
- var ACCOUNTS_PATH = join13(DIR2, "test-accounts.json");
3236
- function loadAccounts() {
3237
- if (!existsSync12(ACCOUNTS_PATH)) return [];
3238
- try {
3239
- return JSON.parse(readFileSync13(ACCOUNTS_PATH, "utf-8"));
3240
- } catch {
3241
- return [];
3242
- }
3243
- }
3244
- function saveAccounts(accounts) {
3245
- mkdirSync5(DIR2, { recursive: true });
3246
- writeFileSync7(ACCOUNTS_PATH, JSON.stringify(accounts, null, 2), { mode: 384 });
3247
- }
3248
- function sessionCookie() {
3249
- const token = readFileSync13(SESSION_PATH, "utf-8").trim();
3250
- return `${SESSION_COOKIE2}=${encodeURIComponent(token)}`;
3251
- }
3252
- async function fetchRemoteAccounts() {
3253
- const res = await fetch(`${AUTH_URL4}/api/auth/test-accounts`, {
3254
- headers: { Cookie: sessionCookie(), Origin: AUTH_URL4 }
3255
- });
3256
- const data = await res.json().catch(() => ({}));
3257
- if (!res.ok || !data.accounts) {
3258
- throw new Error(data.error ?? "Failed to list test accounts");
3259
- }
3260
- return data.accounts;
3261
- }
3262
- async function deleteRemote(id) {
3263
- const res = await fetch(`${AUTH_URL4}/api/auth/test-accounts/${id}`, {
3264
- method: "DELETE",
3265
- headers: { Cookie: sessionCookie(), Origin: AUTH_URL4 }
3266
- });
3267
- if (!res.ok) {
3268
- const data = await res.json().catch(() => ({}));
3269
- throw new Error(data.error ?? `DELETE returned ${res.status}`);
3270
- }
3271
- }
3272
3437
  var create = defineDeepspaceCommand({
3273
3438
  meta: {
3274
3439
  name: "create",
@@ -3298,45 +3463,37 @@ var create = defineDeepspaceCommand({
3298
3463
  },
3299
3464
  async run({ args }) {
3300
3465
  await ensureToken();
3301
- const res = await fetch(`${AUTH_URL4}/api/auth/test-accounts`, {
3302
- method: "POST",
3303
- headers: {
3304
- "Content-Type": "application/json",
3305
- Cookie: sessionCookie(),
3306
- Origin: AUTH_URL4
3307
- },
3308
- body: JSON.stringify({
3309
- email: args.email,
3310
- password: args.password,
3311
- name: args.name,
3312
- label: args.label
3313
- })
3314
- });
3315
- const data = await res.json().catch(() => ({}));
3316
- if (!res.ok || !data.id) {
3317
- throw new Refusal(`Failed: ${data.error ?? "Unknown error"}`, "test_account_create_failed");
3466
+ const email = args.email;
3467
+ const password = args.password;
3468
+ const name = args.name;
3469
+ const label = args.label;
3470
+ let account;
3471
+ try {
3472
+ account = await createRemoteTestAccount({ email, password, name, label });
3473
+ } catch (error) {
3474
+ throw new Refusal(
3475
+ `Failed: ${error instanceof Error ? error.message : String(error)}`,
3476
+ "test_account_create_failed"
3477
+ );
3318
3478
  }
3319
- const account = data;
3320
- const accounts = loadAccounts();
3321
- accounts.push({
3479
+ upsertTestAccount({
3322
3480
  id: account.id,
3323
- email: args.email,
3324
- password: args.password,
3481
+ email,
3482
+ password,
3325
3483
  userId: account.userId,
3326
- name: args.name,
3484
+ name,
3327
3485
  label: account.label,
3328
3486
  createdAt: account.createdAt
3329
3487
  });
3330
- saveAccounts(accounts);
3331
3488
  if (!args.json) {
3332
3489
  console.log(`Created test account:`);
3333
3490
  console.log(` ID: ${account.id}`);
3334
3491
  console.log(` Email: ${account.email}`);
3335
- console.log(` Password: ${args.password}`);
3492
+ console.log(` Password: ${password}`);
3336
3493
  console.log(` UserID: ${account.userId}`);
3337
3494
  if (account.label) console.log(` Label: ${account.label}`);
3338
3495
  console.log(`
3339
- Saved to ${ACCOUNTS_PATH}`);
3496
+ Saved to ${TEST_ACCOUNTS_PATH}`);
3340
3497
  }
3341
3498
  return {
3342
3499
  data: {
@@ -3345,11 +3502,11 @@ Saved to ${ACCOUNTS_PATH}`);
3345
3502
  // The password is already echoed on the human path and stored 0600 in
3346
3503
  // ACCOUNTS_PATH — withholding it from --json would only force scripts
3347
3504
  // to parse the file themselves.
3348
- password: args.password,
3505
+ password,
3349
3506
  userId: account.userId,
3350
3507
  label: account.label ?? null,
3351
3508
  createdAt: account.createdAt,
3352
- savedTo: ACCOUNTS_PATH
3509
+ savedTo: TEST_ACCOUNTS_PATH
3353
3510
  }
3354
3511
  };
3355
3512
  }
@@ -3363,11 +3520,11 @@ var list = defineDeepspaceCommand({
3363
3520
  await ensureToken();
3364
3521
  let remote;
3365
3522
  try {
3366
- remote = await fetchRemoteAccounts();
3523
+ remote = (await syncTestAccountStore()).accounts;
3367
3524
  } catch (err) {
3368
3525
  throw new Refusal(`Failed: ${err.message}`, "test_accounts_list_failed");
3369
3526
  }
3370
- const local = loadAccounts();
3527
+ const local = loadAllTestAccounts();
3371
3528
  const localByEmail = new Map(local.map((a) => [a.email, a]));
3372
3529
  if (remote.length === 0) {
3373
3530
  if (!args.json) {
@@ -3438,7 +3595,7 @@ var del = defineDeepspaceCommand({
3438
3595
  if (targetEmail && !targetId) {
3439
3596
  let remote;
3440
3597
  try {
3441
- remote = await fetchRemoteAccounts();
3598
+ remote = await fetchRemoteTestAccounts();
3442
3599
  } catch (err) {
3443
3600
  throw new Refusal(`Failed: ${err.message}`, "test_accounts_list_failed");
3444
3601
  }
@@ -3451,14 +3608,11 @@ var del = defineDeepspaceCommand({
3451
3608
  targetId = match.id;
3452
3609
  }
3453
3610
  try {
3454
- await deleteRemote(targetId);
3611
+ await deleteRemoteTestAccount(targetId);
3455
3612
  } catch (err) {
3456
3613
  throw new Refusal(`Failed: ${err.message}`, "test_account_delete_failed");
3457
3614
  }
3458
- const accounts = loadAccounts().filter(
3459
- (a) => a.id !== targetId && (!targetEmail || a.email !== targetEmail)
3460
- );
3461
- saveAccounts(accounts);
3615
+ removeTestAccounts([targetId], targetEmail ? [targetEmail] : []);
3462
3616
  if (!args.json) {
3463
3617
  console.log(`Test account deleted${targetEmail ? `: ${targetEmail}` : `: ${targetId}`}`);
3464
3618
  }
@@ -3487,7 +3641,7 @@ var clear = defineDeepspaceCommand({
3487
3641
  await ensureToken();
3488
3642
  let remote;
3489
3643
  try {
3490
- remote = await fetchRemoteAccounts();
3644
+ remote = await fetchRemoteTestAccounts();
3491
3645
  } catch (err) {
3492
3646
  throw new Refusal(`Failed: ${err.message}`, "test_accounts_list_failed");
3493
3647
  }
@@ -3518,16 +3672,14 @@ var clear = defineDeepspaceCommand({
3518
3672
  const failures = [];
3519
3673
  for (const a of targets) {
3520
3674
  try {
3521
- await deleteRemote(a.id);
3675
+ await deleteRemoteTestAccount(a.id);
3522
3676
  ok2++;
3523
3677
  } catch (err) {
3524
3678
  failures.push({ email: a.email, error: err.message });
3525
3679
  }
3526
3680
  }
3527
- const deletedIds = new Set(
3528
- targets.filter((t) => !failures.find((f) => f.email === t.email)).map((t) => t.id)
3529
- );
3530
- saveAccounts(loadAccounts().filter((a) => !deletedIds.has(a.id)));
3681
+ const deleted = targets.filter((target) => !failures.some((failure) => failure.email === target.email));
3682
+ removeTestAccounts(deleted.map((target) => target.id), deleted.map((target) => target.email));
3531
3683
  if (failures.length > 0) {
3532
3684
  throw new Refusal(
3533
3685
  `Deleted ${ok2}/${targets.length} test account(s).
@@ -3562,22 +3714,36 @@ import { resolve as resolve12 } from "path";
3562
3714
  // src/cli/commands/deploy/build.ts
3563
3715
  import * as p6 from "@clack/prompts";
3564
3716
  import { execSync as execSync2 } from "child_process";
3565
- import { existsSync as existsSync14, readFileSync as readFileSync15, readdirSync as readdirSync4 } from "fs";
3566
- import { dirname as dirname5, join as join15, resolve as resolve10 } from "path";
3717
+ import { existsSync as existsSync15, readFileSync as readFileSync17, readdirSync as readdirSync4 } from "fs";
3718
+ import { dirname as dirname5, join as join16, resolve as resolve10 } from "path";
3719
+
3720
+ // src/documentation/deploy.ts
3721
+ import { existsSync as existsSync13, readFileSync as readFileSync15 } from "fs";
3722
+ import { join as join14 } from "path";
3723
+ function readDocumentationDeployManifest(appDir, clientDir) {
3724
+ if (!existsSync13(join14(appDir, "documentation.json"))) return void 0;
3725
+ const manifestPath = join14(clientDir, "_documentation", "manifest.json");
3726
+ if (!existsSync13(manifestPath)) {
3727
+ throw new Error(
3728
+ "documentation.json exists but the build produced no documentation manifest. Ensure vite.config.ts includes deepSpaceDocumentation() from deepspace/documentation."
3729
+ );
3730
+ }
3731
+ return JSON.parse(readFileSync15(manifestPath, "utf8"));
3732
+ }
3567
3733
 
3568
3734
  // src/cli/commands/migrate/app-migrations.ts
3569
3735
  import {
3570
3736
  closeSync,
3571
3737
  constants,
3572
- existsSync as existsSync13,
3738
+ existsSync as existsSync14,
3573
3739
  fstatSync,
3574
3740
  ftruncateSync,
3575
3741
  lstatSync,
3576
3742
  openSync,
3577
- readFileSync as readFileSync14,
3743
+ readFileSync as readFileSync16,
3578
3744
  writeSync
3579
3745
  } from "fs";
3580
- import { join as join14 } from "path";
3746
+ import { join as join15 } from "path";
3581
3747
 
3582
3748
  // src/shared/protocol/app-migrations.ts
3583
3749
  var APP_MIGRATION_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/;
@@ -3622,8 +3788,8 @@ var APP_MIGRATION_DEFINITIONS = SOURCE_MIGRATIONS.map(
3622
3788
  ({ id, description }) => ({ id, description })
3623
3789
  );
3624
3790
  function readAppliedAppMigrations(appDir) {
3625
- const path = join14(appDir, APP_MIGRATIONS_MANIFEST);
3626
- if (!existsSync13(path)) return [];
3791
+ const path = join15(appDir, APP_MIGRATIONS_MANIFEST);
3792
+ if (!existsSync14(path)) return [];
3627
3793
  const source = readRegularFile(path);
3628
3794
  let parsed;
3629
3795
  try {
@@ -3649,7 +3815,7 @@ function planAppMigrations(appDir) {
3649
3815
  const edits = [];
3650
3816
  for (const repoPath of tracked) {
3651
3817
  if (!isSourcePath(repoPath, appPrefix)) continue;
3652
- const absolutePath = join14(repo, repoPath);
3818
+ const absolutePath = join15(repo, repoPath);
3653
3819
  let stat;
3654
3820
  try {
3655
3821
  stat = lstatSync(absolutePath);
@@ -3683,8 +3849,8 @@ function planAppMigrations(appDir) {
3683
3849
  });
3684
3850
  if (pending.length > 0 && blockers.length === 0) {
3685
3851
  const manifestPath = `${appPrefix}${APP_MIGRATIONS_MANIFEST}`;
3686
- const absoluteManifestPath = join14(repo, manifestPath);
3687
- const before = existsSync13(absoluteManifestPath) ? readRegularFile(absoluteManifestPath) : null;
3852
+ const absoluteManifestPath = join15(repo, manifestPath);
3853
+ const before = existsSync14(absoluteManifestPath) ? readRegularFile(absoluteManifestPath) : null;
3688
3854
  const after = `${JSON.stringify([...appliedIds, ...pending.map(({ id }) => id)], null, 2)}
3689
3855
  `;
3690
3856
  if (before !== after) edits.push({ path: manifestPath, before, after });
@@ -3702,13 +3868,13 @@ function applyAppMigrationPlan(appDir, plan) {
3702
3868
  }
3703
3869
  const repo = repoToplevel(appDir);
3704
3870
  for (const edit of plan.edits) {
3705
- const absolutePath = join14(repo, edit.path);
3706
- const current = existsSync13(absolutePath) ? readRegularFile(absolutePath) : null;
3871
+ const absolutePath = join15(repo, edit.path);
3872
+ const current = existsSync14(absolutePath) ? readRegularFile(absolutePath) : null;
3707
3873
  if (current !== edit.before) {
3708
3874
  throw new Error(`Migration input changed after planning: ${edit.path}`);
3709
3875
  }
3710
3876
  }
3711
- for (const edit of plan.edits) writeRegularFile(join14(repo, edit.path), edit.before, edit.after);
3877
+ for (const edit of plan.edits) writeRegularFile(join15(repo, edit.path), edit.before, edit.after);
3712
3878
  }
3713
3879
  function readRegularFile(path) {
3714
3880
  const before = lstatSync(path);
@@ -3722,7 +3888,7 @@ function readRegularFile(path) {
3722
3888
  if (!opened.isFile() || opened.dev !== after.dev || opened.ino !== after.ino) {
3723
3889
  throw new Error(`Migration input changed while opening: ${path}`);
3724
3890
  }
3725
- return readFileSync14(fd, "utf-8");
3891
+ return readFileSync16(fd, "utf-8");
3726
3892
  } finally {
3727
3893
  closeSync(fd);
3728
3894
  }
@@ -3748,7 +3914,7 @@ function writeRegularFile(path, before, after) {
3748
3914
  if (!opened.isFile() || opened.dev !== currentPath.dev || opened.ino !== currentPath.ino) {
3749
3915
  throw new Error(`Migration input changed while opening: ${path}`);
3750
3916
  }
3751
- if (readFileSync14(fd, "utf-8") !== before) {
3917
+ if (readFileSync16(fd, "utf-8") !== before) {
3752
3918
  throw new Error(`Migration input changed after planning: ${path}`);
3753
3919
  }
3754
3920
  ftruncateSync(fd, 0);
@@ -3859,10 +4025,14 @@ var RESERVED_RUN_WORKER_FIRST = /* @__PURE__ */ new Set([
3859
4025
  "/ws/*",
3860
4026
  "/internal/*",
3861
4027
  "/v1/*",
3862
- "/_deepspace/*"
4028
+ "/_deepspace/*",
4029
+ "/_documentation",
4030
+ "/_documentation/*",
4031
+ "/_documentation-root",
4032
+ "/_documentation-root/*"
3863
4033
  ]);
3864
4034
  async function buildDeployBundle(options) {
3865
- const { appDir, appName, envName, sharedDevVarsCache, wranglerConfig, output, spinner: spinner2 } = options;
4035
+ const { appDir, appName, envName, sharedDevVarsCache, output, spinner: spinner2 } = options;
3866
4036
  const junk = removeMacosJunk(appDir);
3867
4037
  if (junk > 0) p6.log.info(`Removed ${junk} macOS metadata file(s) (._*, .DS_Store)`);
3868
4038
  const lintFindings = await lintProjectSchemas(appDir);
@@ -3875,10 +4045,15 @@ async function buildDeployBundle(options) {
3875
4045
  preparedWranglerConfig = prepareWranglerEnvConfig(appDir, envName, {
3876
4046
  sharedDevVarsCache
3877
4047
  });
4048
+ const appDomain = DEEPSPACE_ENV === "staging" ? "spacestest.com" : "app.space";
3878
4049
  execSync2("npx vite build", {
3879
4050
  cwd: appDir,
3880
4051
  stdio: "pipe",
3881
- env: wranglerViteEnv(process.env, preparedWranglerConfig)
4052
+ env: {
4053
+ ...wranglerViteEnv(process.env, preparedWranglerConfig),
4054
+ // The Vite plugin's one documentation compile stamps release URLs.
4055
+ DEEPSPACE_DOCUMENTATION_BASE_URL: `https://${appName}.${appDomain}/docs`
4056
+ }
3882
4057
  });
3883
4058
  } catch (error) {
3884
4059
  spinner2.stop("Build failed");
@@ -3889,19 +4064,19 @@ async function buildDeployBundle(options) {
3889
4064
  preparedWranglerConfig?.cleanup();
3890
4065
  }
3891
4066
  spinner2.stop("Built");
3892
- const deployConfigPath = join15(appDir, ".wrangler", "deploy", "config.json");
3893
- if (!existsSync14(deployConfigPath)) {
4067
+ const deployConfigPath = join16(appDir, ".wrangler", "deploy", "config.json");
4068
+ if (!existsSync15(deployConfigPath)) {
3894
4069
  output.die(
3895
4070
  "Build output config not found at .wrangler/deploy/config.json",
3896
4071
  "build_output_missing"
3897
4072
  );
3898
4073
  }
3899
- const deployConfig = JSON.parse(readFileSync15(deployConfigPath, "utf-8"));
4074
+ const deployConfig = JSON.parse(readFileSync17(deployConfigPath, "utf-8"));
3900
4075
  const outputWranglerPath = resolve10(dirname5(deployConfigPath), deployConfig.configPath);
3901
- if (!existsSync14(outputWranglerPath)) {
4076
+ if (!existsSync15(outputWranglerPath)) {
3902
4077
  output.die(`Output wrangler.json not found at ${outputWranglerPath}`, "build_output_missing");
3903
4078
  }
3904
- const outputConfig = JSON.parse(readFileSync15(outputWranglerPath, "utf-8"));
4079
+ const outputConfig = JSON.parse(readFileSync17(outputWranglerPath, "utf-8"));
3905
4080
  if (outputConfig.name && outputConfig.name !== appName) {
3906
4081
  const envHint = envName ? ` (--env ${envName})` : "";
3907
4082
  output.die(
@@ -3910,18 +4085,25 @@ async function buildDeployBundle(options) {
3910
4085
  );
3911
4086
  }
3912
4087
  const workerDir = dirname5(outputWranglerPath);
3913
- const workerBundlePath = join15(workerDir, outputConfig.main);
4088
+ const workerBundlePath = join16(workerDir, outputConfig.main);
3914
4089
  const clientDir = outputConfig.assets?.directory ? resolve10(workerDir, outputConfig.assets.directory) : null;
3915
- if (!existsSync14(workerBundlePath)) {
4090
+ if (!existsSync15(workerBundlePath)) {
3916
4091
  output.die(`Worker bundle not found at ${workerBundlePath}`, "build_output_missing");
3917
4092
  }
3918
- if (!clientDir || !existsSync14(clientDir)) {
4093
+ if (!clientDir || !existsSync15(clientDir)) {
3919
4094
  return output.die(`Client assets not found at ${clientDir}`, "build_output_missing");
3920
4095
  }
4096
+ try {
4097
+ const manifest = readDocumentationDeployManifest(appDir, clientDir);
4098
+ if (manifest) p6.log.info(`Documentation: ${manifest.pageCount} page(s)`);
4099
+ } catch (error) {
4100
+ output.die(errorMessage(error), "documentation_build_failed");
4101
+ }
3921
4102
  spinner2.start("Collecting assets...");
3922
4103
  const assets = collectAssets(clientDir);
4104
+ const assetConfig = readDeployAssetConfig(clientDir);
3923
4105
  spinner2.stop(`Collected ${assets.length} assets`);
3924
- const workerJs = readFileSync15(workerBundlePath, "utf-8");
4106
+ const workerJs = readFileSync17(workerBundlePath, "utf-8");
3925
4107
  const doBindings = outputConfig.durable_objects?.bindings;
3926
4108
  const sqliteClasses = new Set(
3927
4109
  outputConfig.migrations?.flatMap((migration) => migration.new_sqlite_classes ?? []) ?? []
@@ -3946,7 +4128,7 @@ async function buildDeployBundle(options) {
3946
4128
  `Custom bindings: ${customBindings.map((binding) => `${binding.name} (${binding.type})`).join(", ")}`
3947
4129
  );
3948
4130
  }
3949
- const extraRoutes = extractRunWorkerFirst(wranglerConfig);
4131
+ const extraRoutes = resolveDeployRunWorkerFirst(outputConfig);
3950
4132
  let appMigrations = [];
3951
4133
  try {
3952
4134
  appMigrations = readAppliedAppMigrations(appDir);
@@ -3955,6 +4137,7 @@ async function buildDeployBundle(options) {
3955
4137
  }
3956
4138
  return {
3957
4139
  assets,
4140
+ assetConfig,
3958
4141
  workerJs,
3959
4142
  appMigrations,
3960
4143
  doManifest,
@@ -3963,8 +4146,7 @@ async function buildDeployBundle(options) {
3963
4146
  };
3964
4147
  }
3965
4148
  function extractRunWorkerFirst(config) {
3966
- const raw = config.assets?.run_worker_first;
3967
- if (!Array.isArray(raw)) return [];
4149
+ const raw = Array.isArray(config.assets?.run_worker_first) ? config.assets.run_worker_first : [];
3968
4150
  const routes = [];
3969
4151
  const seen = /* @__PURE__ */ new Set();
3970
4152
  for (const entry of raw) {
@@ -3976,19 +4158,24 @@ function extractRunWorkerFirst(config) {
3976
4158
  }
3977
4159
  return routes;
3978
4160
  }
4161
+ function resolveDeployRunWorkerFirst(config) {
4162
+ if (config.assets?.run_worker_first === true) return true;
4163
+ const routes = extractRunWorkerFirst(config);
4164
+ return routes;
4165
+ }
3979
4166
  function collectAssets(dir) {
3980
4167
  const assets = [];
3981
4168
  const walk = (currentDir, prefix) => {
3982
4169
  for (const entry of readdirSync4(currentDir, { withFileTypes: true })) {
3983
- if (entry.name === ".assetsignore") continue;
3984
- const fullPath = join15(currentDir, entry.name);
4170
+ if (isDeployAssetControlFile(entry.name)) continue;
4171
+ const fullPath = join16(currentDir, entry.name);
3985
4172
  const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
3986
4173
  if (entry.isDirectory()) {
3987
4174
  walk(fullPath, relativePath);
3988
4175
  } else {
3989
4176
  assets.push({
3990
4177
  path: `/${relativePath}`,
3991
- contentBase64: readFileSync15(fullPath).toString("base64")
4178
+ contentBase64: readFileSync17(fullPath).toString("base64")
3992
4179
  });
3993
4180
  }
3994
4181
  }
@@ -3996,24 +4183,37 @@ function collectAssets(dir) {
3996
4183
  walk(dir, "");
3997
4184
  return assets;
3998
4185
  }
4186
+ function readDeployAssetConfig(clientDir) {
4187
+ const config = {};
4188
+ for (const [file, key] of [["_headers", "_headers"], ["_redirects", "_redirects"]]) {
4189
+ const filePath = join16(clientDir, file);
4190
+ if (!existsSync15(filePath)) continue;
4191
+ const content = readFileSync17(filePath, "utf8");
4192
+ if (content.trim()) config[key] = content;
4193
+ }
4194
+ return config;
4195
+ }
4196
+ function isDeployAssetControlFile(name) {
4197
+ return name === ".assetsignore" || name === "_headers" || name === "_redirects";
4198
+ }
3999
4199
  function errorMessage(error) {
4000
4200
  return error instanceof Error ? error.message : String(error);
4001
4201
  }
4002
4202
 
4003
4203
  // src/cli/commands/deploy/commerce.ts
4004
4204
  import * as p7 from "@clack/prompts";
4005
- import { existsSync as existsSync15 } from "fs";
4006
- import { join as join16 } from "path";
4205
+ import { existsSync as existsSync16 } from "fs";
4206
+ import { join as join17 } from "path";
4007
4207
  import { pathToFileURL as pathToFileURL2 } from "url";
4008
4208
  var API_URL2 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
4009
4209
  function errorMessage2(error) {
4010
4210
  return error instanceof Error ? error.message : String(error);
4011
4211
  }
4012
4212
  async function loadDeclaration(appDir, sourceFile, outputFile, exportName) {
4013
- const sourcePath = join16(appDir, "src", sourceFile);
4014
- if (!existsSync15(sourcePath)) return void 0;
4213
+ const sourcePath = join17(appDir, "src", sourceFile);
4214
+ if (!existsSync16(sourcePath)) return void 0;
4015
4215
  const esbuild = await import("esbuild");
4016
- const outputPath = join16(appDir, ".wrangler", "deploy", outputFile);
4216
+ const outputPath = join17(appDir, ".wrangler", "deploy", outputFile);
4017
4217
  await esbuild.build({
4018
4218
  entryPoints: [sourcePath],
4019
4219
  bundle: true,
@@ -4106,7 +4306,7 @@ Stripe will keep charging them until you cancel. Cancel at the end of their curr
4106
4306
  }
4107
4307
  }
4108
4308
  async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
4109
- if (!existsSync15(join16(appDir, "src", "subscriptions.ts"))) return;
4309
+ if (!existsSync16(join17(appDir, "src", "subscriptions.ts"))) return;
4110
4310
  let plans;
4111
4311
  try {
4112
4312
  plans = await loadDeclaration(
@@ -4161,7 +4361,7 @@ async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
4161
4361
  }
4162
4362
  }
4163
4363
  async function syncOneTimeProducts(appDir, appName, token) {
4164
- if (!existsSync15(join16(appDir, "src", "products.ts"))) return;
4364
+ if (!existsSync16(join17(appDir, "src", "products.ts"))) return;
4165
4365
  let products;
4166
4366
  try {
4167
4367
  products = await loadDeclaration(
@@ -4453,10 +4653,22 @@ function repoApi(deployUrl, token, appId) {
4453
4653
  }
4454
4654
 
4455
4655
  // src/cli/lib/vc-remote.ts
4456
- import { existsSync as existsSync16 } from "fs";
4656
+ import { existsSync as existsSync17 } from "fs";
4457
4657
  import { tmpdir } from "os";
4458
4658
  import { isAbsolute as isAbsolute3, relative, resolve as resolve11 } from "path";
4459
- var SPACE_REMOTE = "space";
4659
+ function spaceRemoteName(environment = DEEPSPACE_ENV) {
4660
+ if (environment === "staging") return "space-staging";
4661
+ if (environment === "production") return "space";
4662
+ return "space-invalid";
4663
+ }
4664
+ var SPACE_REMOTE = spaceRemoteName();
4665
+ function spaceTrackingRef(branch, remote = SPACE_REMOTE) {
4666
+ return `refs/remotes/${remote}/${branch}`;
4667
+ }
4668
+ function spacePrivateRef(path, environment = DEEPSPACE_ENV) {
4669
+ const root = environment === "production" ? "refs/deepspace" : `refs/deepspace/${environment}`;
4670
+ return `${root}/${path}`;
4671
+ }
4460
4672
  function deployBaseUrl() {
4461
4673
  return process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
4462
4674
  }
@@ -4493,13 +4705,16 @@ function entryIsTransient(cwd) {
4493
4705
  return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
4494
4706
  }
4495
4707
  function helperEntryUsable(command) {
4496
- const match = /^!'([^']*)' '([^']*)' git-credential$/.exec(command);
4497
- return match !== null && existsSync16(match[1]) && existsSync16(match[2]);
4708
+ const match = /^!(?:DEEPSPACE_ENV=(production|staging) )?'([^']*)' '([^']*)' git-credential$/.exec(command);
4709
+ const expectedEnvironment = DEEPSPACE_ENV === "production" ? void 0 : DEEPSPACE_ENV;
4710
+ if (!match || match[1] !== expectedEnvironment) return false;
4711
+ return existsSync17(match[2]) && existsSync17(match[3]);
4498
4712
  }
4499
- function credentialHelperCommand() {
4713
+ function credentialHelperCommand(environment = DEEPSPACE_ENV) {
4500
4714
  const entry = process.argv[1];
4501
4715
  if (!entry) throw new Error("CLI entry path is unavailable");
4502
- return `!${shQuote(process.execPath)} ${shQuote(entry)} git-credential`;
4716
+ const environmentPrefix = environment === "staging" ? "DEEPSPACE_ENV=staging " : "";
4717
+ return `!${environmentPrefix}${shQuote(process.execPath)} ${shQuote(entry)} git-credential`;
4503
4718
  }
4504
4719
  function installCredentialHelper(cwd, url) {
4505
4720
  try {
@@ -4538,22 +4753,22 @@ function installCredentialHelper(cwd, url) {
4538
4753
  } catch {
4539
4754
  }
4540
4755
  }
4541
- function ensureSpaceRemote(cwd, appId) {
4756
+ function ensureSpaceRemote(cwd, appId, remote = SPACE_REMOTE) {
4542
4757
  const url = repoUrl(appId);
4543
- const current = runGit(cwd, ["remote", "get-url", SPACE_REMOTE], { allowFail: true });
4758
+ const current = runGit(cwd, ["remote", "get-url", remote], { allowFail: true });
4544
4759
  if (current.status !== 0) {
4545
- runGit(cwd, ["remote", "add", SPACE_REMOTE, url]);
4760
+ runGit(cwd, ["remote", "add", remote, url]);
4546
4761
  } else {
4547
4762
  const existing = current.stdout.toString("utf-8").trim();
4548
4763
  if (existing !== url) {
4549
4764
  const priorApp = appIdFromRepoUrl(existing);
4550
4765
  if (priorApp && priorApp !== appId && !process.argv.includes("--json")) {
4551
4766
  process.stderr.write(
4552
- `warning: remote '${SPACE_REMOTE}' was pointed at app ${priorApp}; re-aiming it at ${appId}.
4767
+ `warning: remote '${remote}' was pointed at app ${priorApp}; re-aiming it at ${appId}.
4553
4768
  `
4554
4769
  );
4555
4770
  }
4556
- runGit(cwd, ["remote", "set-url", SPACE_REMOTE, url]);
4771
+ runGit(cwd, ["remote", "set-url", remote, url]);
4557
4772
  }
4558
4773
  }
4559
4774
  installCredentialHelper(cwd, url);
@@ -4628,7 +4843,7 @@ function pushToSpace(cwd, token, refspec, opts = {}) {
4628
4843
  const result = runGitRemote(
4629
4844
  cwd,
4630
4845
  token,
4631
- ["push", "--porcelain", ...opts.force ? ["--force"] : [], ...extra, SPACE_REMOTE, refspec],
4846
+ ["push", "--porcelain", ...opts.force ? ["--force"] : [], ...extra, opts.remote ?? SPACE_REMOTE, refspec],
4632
4847
  { allowFail: true }
4633
4848
  );
4634
4849
  const parsed = parsePushPorcelain(result.stdout.toString("utf-8"));
@@ -4811,7 +5026,8 @@ async function syncDeployRepository(options) {
4811
5026
  "source_unclaimed"
4812
5027
  );
4813
5028
  }
4814
- ensureSpaceRemote(appDir, appId);
5029
+ const sourceRemote = spaceRemoteName();
5030
+ ensureSpaceRemote(appDir, appId, sourceRemote);
4815
5031
  const branch = currentBranch(appDir);
4816
5032
  const workspaceBranchId = workspaceIdFromBranch(branch);
4817
5033
  if (!isWorkTreeClean(appDir)) {
@@ -4855,7 +5071,9 @@ async function syncDeployRepository(options) {
4855
5071
  );
4856
5072
  } else {
4857
5073
  const pushResult = await pushWithTransientRetry(
4858
- () => pushToSpace(appDir, token, `refs/heads/${branch}:refs/heads/${branch}`)
5074
+ () => pushToSpace(appDir, token, `refs/heads/${branch}:refs/heads/${branch}`, {
5075
+ remote: sourceRemote
5076
+ })
4859
5077
  );
4860
5078
  if (pushResult.status === "committed" || pushResult.status === "up_to_date") {
4861
5079
  recoverable = true;
@@ -4868,7 +5086,7 @@ async function syncDeployRepository(options) {
4868
5086
  "vc_push_rejected"
4869
5087
  );
4870
5088
  } else {
4871
- const remoteState = classifyRemoteState(appDir, token, branch, tip);
5089
+ const remoteState = classifyRemoteState(appDir, token, branch, tip, sourceRemote);
4872
5090
  if (remoteState.strictlyBehind && !ignoreStale) {
4873
5091
  let behind = "several";
4874
5092
  try {
@@ -4913,16 +5131,17 @@ async function syncDeployRepository(options) {
4913
5131
  }
4914
5132
  return { commitOid, recoverable, deployKey, source, sourceRevision, baseReleaseId };
4915
5133
  }
4916
- function classifyRemoteState(appDir, token, branch, localTip) {
5134
+ function classifyRemoteState(appDir, token, branch, localTip, remote) {
4917
5135
  let remoteTip = null;
5136
+ const remoteRef = `refs/remotes/${remote}/${branch}`;
4918
5137
  try {
4919
5138
  runGitRemote(appDir, token, [
4920
5139
  "fetch",
4921
5140
  "--quiet",
4922
- SPACE_REMOTE,
4923
- `+refs/heads/${branch}:refs/remotes/space/${branch}`
5141
+ remote,
5142
+ `+refs/heads/${branch}:${remoteRef}`
4924
5143
  ]);
4925
- remoteTip = resolveCommit(appDir, `refs/remotes/space/${branch}`);
5144
+ remoteTip = resolveCommit(appDir, remoteRef);
4926
5145
  return {
4927
5146
  remoteTip,
4928
5147
  strictlyBehind: remoteTip !== null && remoteTip !== localTip && isAncestor(appDir, localTip, remoteTip)
@@ -5004,7 +5223,9 @@ async function deployBuiltBundle(options) {
5004
5223
  spinner: spinner2
5005
5224
  } = options;
5006
5225
  spinner2.start(`Deploying ${appName}...`);
5007
- if (bundle.extraRoutes.length) {
5226
+ if (bundle.extraRoutes === true) {
5227
+ p10.log.info("Worker-first routing: all requests");
5228
+ } else if (bundle.extraRoutes.length) {
5008
5229
  p10.log.info(`Custom worker-first routes: ${bundle.extraRoutes.join(", ")}`);
5009
5230
  }
5010
5231
  const assetGroups = packAssetGroups(bundle.assets, GROUP_BYTES);
@@ -5074,9 +5295,12 @@ async function deployBuiltBundle(options) {
5074
5295
  form.append("bindingManifest", JSON.stringify(bundle.customBindings));
5075
5296
  }
5076
5297
  if (secrets.names.length) form.append("userSecrets", JSON.stringify(secrets.values));
5077
- if (bundle.extraRoutes.length) {
5298
+ if (bundle.extraRoutes === true || bundle.extraRoutes.length) {
5078
5299
  form.append("extraRunWorkerFirst", JSON.stringify(bundle.extraRoutes));
5079
5300
  }
5301
+ if (Object.keys(bundle.assetConfig).length) {
5302
+ form.append("assetConfig", JSON.stringify(bundle.assetConfig));
5303
+ }
5080
5304
  form.append("name", appName);
5081
5305
  if (confirmRename) form.append("confirmRename", "true");
5082
5306
  if (shouldSendLineage(repository.commitOid, repository.recoverable)) {
@@ -5115,11 +5339,6 @@ async function deployBuiltBundle(options) {
5115
5339
  throw error;
5116
5340
  }
5117
5341
  let body = await response.json().catch(() => ({}));
5118
- for (let attempt = 0; response.status === 503 && body.code === "release_finalization_pending" && attempt < 3; attempt++) {
5119
- await new Promise((resolve19) => setTimeout(resolve19, 250 * 2 ** attempt));
5120
- response = await postCommit();
5121
- body = await response.json().catch(() => ({}));
5122
- }
5123
5342
  if (response.status === 409 && body.code === "rename_required" && !confirmRename) {
5124
5343
  spinner2.stop("Rename confirmation needed");
5125
5344
  if (output.nonInteractive) {
@@ -5234,7 +5453,7 @@ async function postWithRetry(url, makeInit, {
5234
5453
 
5235
5454
  // src/cli/commands/deploy/secrets.ts
5236
5455
  import * as p11 from "@clack/prompts";
5237
- import { existsSync as existsSync17, readFileSync as readFileSync16 } from "fs";
5456
+ import { existsSync as existsSync18, readFileSync as readFileSync18 } from "fs";
5238
5457
  function classifyDevVarsSecrets(options) {
5239
5458
  const store = new Set(options.storeSecretNames);
5240
5459
  const strayKeys = options.handEditedDevVarKeys.filter((key) => !store.has(key));
@@ -5332,8 +5551,8 @@ function prepareDeploySecrets(options) {
5332
5551
  return { values, names };
5333
5552
  }
5334
5553
  function readCustomDevVars(path, output) {
5335
- if (!existsSync17(path)) return {};
5336
- const section = extractCustomDevVars(stripGeneratedSecretsCache(readFileSync16(path, "utf-8")));
5554
+ if (!existsSync18(path)) return {};
5555
+ const section = extractCustomDevVars(stripGeneratedSecretsCache(readFileSync18(path, "utf-8")));
5337
5556
  if (!section) return {};
5338
5557
  try {
5339
5558
  return parseDevVars(section);
@@ -5502,7 +5721,6 @@ var deploy_default = defineCommand3({
5502
5721
  appName,
5503
5722
  envName,
5504
5723
  sharedDevVarsCache: secretsCache.linked !== null,
5505
- wranglerConfig,
5506
5724
  output,
5507
5725
  spinner: spinner2
5508
5726
  });
@@ -5684,18 +5902,15 @@ async function resolveAppSelector(deployUrl, token, selector) {
5684
5902
  "app_not_found"
5685
5903
  );
5686
5904
  }
5687
- async function resolveAppTarget(deployUrl, token, explicit) {
5905
+ async function resolveAppTarget(deployUrl, token, explicit, options = {}) {
5906
+ assertAppTargetResolvable(explicit, options);
5688
5907
  const { app: app2, error } = parseAppArg(explicit);
5689
5908
  if (error) throw new InputError(error, "invalid_app");
5690
5909
  if (app2 !== void 0) return resolveAppSelector(deployUrl, token, app2);
5910
+ const { wranglerEnv } = parseWranglerEnvArg(options.wranglerEnv);
5691
5911
  const appDir = findAppDir();
5692
- const id = appDir ? readAppId(appDir) : null;
5693
- if (!id) {
5694
- throw new InputError(
5695
- "No app id. Run from an app directory whose wrangler.toml carries DEEPSPACE_APP_ID, or pass --app <id or name>.",
5696
- "not_in_app_repo"
5697
- );
5698
- }
5912
+ const id = appDir ? readAppId(appDir, wranglerEnv) : null;
5913
+ if (!id) throw appTargetMissingError(wranglerEnv);
5699
5914
  return id;
5700
5915
  }
5701
5916
  function parseAppArg(raw) {
@@ -5721,17 +5936,42 @@ function parseAppArg(raw) {
5721
5936
  }
5722
5937
  return { app: selector };
5723
5938
  }
5724
- function assertAppTargetResolvable(appArg) {
5939
+ function parseWranglerEnvArg(raw) {
5940
+ if (raw === void 0) return {};
5941
+ const wranglerEnv = raw.trim();
5942
+ if (!wranglerEnv) {
5943
+ return {
5944
+ error: "--env was given an empty environment name \u2014 pass a wrangler.toml environment name, or omit --env to use the top-level app."
5945
+ };
5946
+ }
5947
+ return { wranglerEnv };
5948
+ }
5949
+ function appTargetMissingError(wranglerEnv) {
5950
+ if (wranglerEnv) {
5951
+ return new InputError(
5952
+ `No app id for env "${wranglerEnv}" \u2014 wrangler.toml has no [env.${wranglerEnv}] block with its own DEEPSPACE_APP_ID.`,
5953
+ "no_app_id_for_env"
5954
+ );
5955
+ }
5956
+ return new InputError(
5957
+ "No app id. Run from an app directory whose wrangler.toml carries DEEPSPACE_APP_ID, or pass --app <id or name>.",
5958
+ "not_in_app_repo"
5959
+ );
5960
+ }
5961
+ function assertAppTargetResolvable(appArg, options = {}) {
5725
5962
  const { app: app2, error } = parseAppArg(appArg);
5726
5963
  if (error) throw new InputError(error, "invalid_app");
5727
- if (app2 !== void 0) return;
5728
- const appDir = findAppDir();
5729
- if (!appDir || !readAppId(appDir)) {
5964
+ const { wranglerEnv, error: envError } = parseWranglerEnvArg(options.wranglerEnv);
5965
+ if (envError) throw new InputError(envError, "invalid_env");
5966
+ if (app2 !== void 0 && wranglerEnv) {
5730
5967
  throw new InputError(
5731
- "No app id. Run from an app directory whose wrangler.toml carries DEEPSPACE_APP_ID, or pass --app <id or name>.",
5732
- "not_in_app_repo"
5968
+ "Pass either --app or --env, not both \u2014 --env reads the [env.<name>] block's own app id, while --app names an app directly.",
5969
+ "ambiguous_target"
5733
5970
  );
5734
5971
  }
5972
+ if (app2 !== void 0) return;
5973
+ const appDir = findAppDir();
5974
+ if (!appDir || !readAppId(appDir, wranglerEnv)) throw appTargetMissingError(wranglerEnv);
5735
5975
  }
5736
5976
  async function warnIfPhantomApp(deployUrl, token, appId, explicit) {
5737
5977
  if (!explicit || !STRICT_APP_ID_RE.test(explicit.trim())) return;
@@ -6058,28 +6298,10 @@ var logs_default = defineCommand4({
6058
6298
  }
6059
6299
  },
6060
6300
  async run({ args }) {
6301
+ const envArg = args.env;
6302
+ assertAppTargetResolvable(args.app, { wranglerEnv: envArg });
6061
6303
  const token = await ensureToken();
6062
- const wranglerEnv = args.env?.trim() || void 0;
6063
- if (args.app && wranglerEnv) {
6064
- throw new InputError(
6065
- "Pass either --app or --env, not both \u2014 --env reads the [env.<name>] block\u2019s own app id, --app names an app directly.",
6066
- "ambiguous_target"
6067
- );
6068
- }
6069
- let appId;
6070
- if (!args.app && wranglerEnv) {
6071
- const appDir = findAppDir();
6072
- const id = appDir ? readAppId(appDir, wranglerEnv) : null;
6073
- if (!id) {
6074
- throw new InputError(
6075
- `No app id for env "${wranglerEnv}" \u2014 wrangler.toml has no [env.${wranglerEnv}] block with its own DEEPSPACE_APP_ID.`,
6076
- "no_app_id_for_env"
6077
- );
6078
- }
6079
- appId = id;
6080
- } else {
6081
- appId = await resolveAppTarget(DEPLOY_URL6, token, args.app);
6082
- }
6304
+ const appId = await resolveAppTarget(DEPLOY_URL6, token, args.app, { wranglerEnv: envArg });
6083
6305
  if (args.level && !LOG_LEVELS.includes(args.level)) {
6084
6306
  throw new InputError(`Invalid --level "${args.level}". Use: ${LOG_LEVELS.join(", ")}`, "invalid_level");
6085
6307
  }
@@ -6248,20 +6470,20 @@ var usage_default = defineDeepspaceCommand({
6248
6470
  });
6249
6471
 
6250
6472
  // src/cli/commands/add.ts
6251
- import { existsSync as existsSync19, readFileSync as readFileSync18, realpathSync as realpathSync3 } from "fs";
6252
- import { dirname as dirname7, join as join18, resolve as resolve14 } from "path";
6473
+ import { existsSync as existsSync20, readFileSync as readFileSync20, realpathSync as realpathSync3 } from "fs";
6474
+ import { dirname as dirname7, join as join19, resolve as resolve14 } from "path";
6253
6475
  import { pathToFileURL as pathToFileURL3 } from "url";
6254
6476
 
6255
6477
  // src/cli/commands/feature-installer.ts
6256
6478
  import {
6257
6479
  closeSync as closeSync2,
6258
6480
  copyFileSync,
6259
- existsSync as existsSync18,
6481
+ existsSync as existsSync19,
6260
6482
  fstatSync as fstatSync2,
6261
6483
  mkdirSync as mkdirSync6,
6262
6484
  mkdtempSync,
6263
6485
  openSync as openSync2,
6264
- readFileSync as readFileSync17,
6486
+ readFileSync as readFileSync19,
6265
6487
  readSync,
6266
6488
  readdirSync as readdirSync5,
6267
6489
  rmSync as rmSync4,
@@ -6269,16 +6491,16 @@ import {
6269
6491
  } from "fs";
6270
6492
  import { spawnSync as spawnSync6 } from "child_process";
6271
6493
  import { tmpdir as tmpdir2 } from "os";
6272
- import { dirname as dirname6, isAbsolute as isAbsolute4, join as join17, resolve as resolve13 } from "path";
6494
+ import { dirname as dirname6, isAbsolute as isAbsolute4, join as join18, resolve as resolve13 } from "path";
6273
6495
  import { fileURLToPath } from "url";
6274
6496
  var NAV_MARKER = "// \u2500\u2500 Features add nav items below this line \u2500\u2500";
6275
6497
  function featurePackageRoot(moduleUrl = import.meta.url) {
6276
6498
  let dir = dirname6(fileURLToPath(moduleUrl));
6277
6499
  for (; ; ) {
6278
- const packagePath = join17(dir, "package.json");
6279
- if (existsSync18(packagePath)) {
6500
+ const packagePath = join18(dir, "package.json");
6501
+ if (existsSync19(packagePath)) {
6280
6502
  try {
6281
- const pkg2 = JSON.parse(readFileSync17(packagePath, "utf-8"));
6503
+ const pkg2 = JSON.parse(readFileSync19(packagePath, "utf-8"));
6282
6504
  if (pkg2.name === "deepspace") return dir;
6283
6505
  } catch {
6284
6506
  }
@@ -6289,13 +6511,13 @@ function featurePackageRoot(moduleUrl = import.meta.url) {
6289
6511
  }
6290
6512
  }
6291
6513
  function hasFeatureCatalog(packageRoot = featurePackageRoot()) {
6292
- return existsSync18(join17(packageRoot, "features"));
6514
+ return existsSync19(join18(packageRoot, "features"));
6293
6515
  }
6294
6516
  function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
6295
- const configPath = join17(packageRoot, "features", id, "feature.json");
6296
- if (!existsSync18(configPath)) return null;
6517
+ const configPath = join18(packageRoot, "features", id, "feature.json");
6518
+ if (!existsSync19(configPath)) return null;
6297
6519
  try {
6298
- const config = JSON.parse(readFileSync17(configPath, "utf-8"));
6520
+ const config = JSON.parse(readFileSync19(configPath, "utf-8"));
6299
6521
  if (typeof config.id !== "string" || typeof config.name !== "string" || !Array.isArray(config.files)) {
6300
6522
  return null;
6301
6523
  }
@@ -6305,8 +6527,8 @@ function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
6305
6527
  }
6306
6528
  }
6307
6529
  function listFeatureConfigs(packageRoot = featurePackageRoot()) {
6308
- const featuresDir = join17(packageRoot, "features");
6309
- if (!existsSync18(featuresDir)) return [];
6530
+ const featuresDir = join18(packageRoot, "features");
6531
+ if (!existsSync19(featuresDir)) return [];
6310
6532
  return readdirSync5(featuresDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => readFeatureConfig(entry.name, packageRoot)).filter((config) => config !== null && !config.internal).sort((a, b) => a.id.localeCompare(b.id));
6311
6533
  }
6312
6534
  function featureCatalog(packageRoot = featurePackageRoot()) {
@@ -6360,7 +6582,7 @@ function copyFile(src, dest) {
6360
6582
  function copyFileWithImportShift(src, dest, levels) {
6361
6583
  mkdirSync6(dirname6(dest), { recursive: true });
6362
6584
  const prefix = "../".repeat(levels);
6363
- const shifted = readFileSync17(src, "utf-8").replace(
6585
+ const shifted = readFileSync19(src, "utf-8").replace(
6364
6586
  /(from\s+|import\s+|import\()(['"])(\.\.\/)/g,
6365
6587
  `$1$2${prefix}$3`
6366
6588
  );
@@ -6373,12 +6595,12 @@ function installFiles(packageRoot, config, targetDir, emit) {
6373
6595
  const preexisting = [];
6374
6596
  const missing = [];
6375
6597
  for (const file of config.files) {
6376
- const srcPath = join17(packageRoot, "features", config.id, file.src);
6598
+ const srcPath = join18(packageRoot, "features", config.id, file.src);
6377
6599
  const resolvedDest = resolveFeatureDestPath(file.dest, config.route);
6378
- const destPath = join17(targetDir, resolvedDest);
6379
- const existed = existsSync18(destPath);
6600
+ const destPath = join18(targetDir, resolvedDest);
6601
+ const existed = existsSync19(destPath);
6380
6602
  if (existed) preexisting.push(resolvedDest);
6381
- if (!existsSync18(srcPath)) {
6603
+ if (!existsSync19(srcPath)) {
6382
6604
  emit(` Warning: source not found: ${file.src}`, "stderr");
6383
6605
  if (!existed) missing.push(resolvedDest);
6384
6606
  continue;
@@ -6415,11 +6637,11 @@ function unresolvedSchema(schema, reason, emit) {
6415
6637
  }
6416
6638
  function integrateSchema(config, targetDir, emit) {
6417
6639
  if (!config.schema) return [];
6418
- const schemasPath = join17(targetDir, "src", "schemas.ts");
6419
- if (!existsSync18(schemasPath)) {
6640
+ const schemasPath = join18(targetDir, "src", "schemas.ts");
6641
+ if (!existsSync19(schemasPath)) {
6420
6642
  return [unresolvedSchema(config.schema, "src/schemas.ts was not found.", emit)];
6421
6643
  }
6422
- let content = readFileSync17(schemasPath, "utf-8");
6644
+ let content = readFileSync19(schemasPath, "utf-8");
6423
6645
  const { exportName, importPath, spreadOperator } = config.schema;
6424
6646
  const importPattern = new RegExp(`^import\\s+\\{[^}]*\\b${exportName}\\b`, "m");
6425
6647
  if (importPattern.test(content)) {
@@ -6472,11 +6694,11 @@ function unresolvedActions(actions, reason, emit) {
6472
6694
  }
6473
6695
  function integrateActions(config, targetDir, emit) {
6474
6696
  if (!config.actions?.length) return [];
6475
- const actionsPath = join17(targetDir, "src", "actions", "index.ts");
6476
- if (!existsSync18(actionsPath)) {
6697
+ const actionsPath = join18(targetDir, "src", "actions", "index.ts");
6698
+ if (!existsSync19(actionsPath)) {
6477
6699
  return [unresolvedActions(config.actions, "src/actions/index.ts was not found.", emit)];
6478
6700
  }
6479
- let content = readFileSync17(actionsPath, "utf-8");
6701
+ let content = readFileSync19(actionsPath, "utf-8");
6480
6702
  const registryPattern = /export const actions:\s*Record<string,\s*ActionHandler(?:<[^>]*>)?>\s*=\s*\{/;
6481
6703
  if (!registryPattern.test(content)) {
6482
6704
  const reason = "src/actions/index.ts has an unexpected structure.";
@@ -6519,10 +6741,152 @@ ${registryEntry}`);
6519
6741
  if (integrated > 0) writeFileSync8(actionsPath, content);
6520
6742
  return unresolved;
6521
6743
  }
6744
+ function integrateCode(config, targetDir, emit) {
6745
+ const unresolved = [];
6746
+ for (const insertion of config.code ?? []) {
6747
+ const targetPath = join18(targetDir, insertion.file);
6748
+ if (!existsSync19(targetPath)) {
6749
+ unresolved.push({
6750
+ area: "code",
6751
+ reason: `${insertion.file} was not found.`,
6752
+ steps: [`Add ${insertion.insert.trim()} to ${insertion.file}.`]
6753
+ });
6754
+ continue;
6755
+ }
6756
+ const original = readFileSync19(targetPath, "utf-8");
6757
+ if (original.includes(insertion.insert.trim())) {
6758
+ emit(` Integration already present: ${insertion.file}`);
6759
+ continue;
6760
+ }
6761
+ const markerIndex = original.indexOf(insertion.marker);
6762
+ if (markerIndex < 0) {
6763
+ unresolved.push({
6764
+ area: "code",
6765
+ reason: `${insertion.file} does not contain the expected insertion point.`,
6766
+ steps: [
6767
+ ...insertion.import ? [`Add ${insertion.import} to ${insertion.file}.`] : [],
6768
+ `Add ${insertion.insert.trim()} to ${insertion.file}.`
6769
+ ]
6770
+ });
6771
+ continue;
6772
+ }
6773
+ let content = original;
6774
+ if (insertion.import && !content.includes(insertion.import)) {
6775
+ const importIndex = content.search(/^import\s/m);
6776
+ content = `${content.slice(0, importIndex < 0 ? 0 : importIndex)}${insertion.import}
6777
+ ${content.slice(importIndex < 0 ? 0 : importIndex)}`;
6778
+ }
6779
+ const currentMarkerIndex = content.indexOf(insertion.marker);
6780
+ const insertAt = insertion.placement === "before" ? currentMarkerIndex : currentMarkerIndex + insertion.marker.length;
6781
+ const prefix = insertion.placement === "before" ? "" : "\n";
6782
+ const suffix = insertion.placement === "before" ? "\n" : "";
6783
+ content = `${content.slice(0, insertAt)}${prefix}${insertion.insert}${suffix}${content.slice(insertAt)}`;
6784
+ writeFileSync8(targetPath, content);
6785
+ emit(` Integrated: ${insertion.file}`);
6786
+ }
6787
+ return unresolved;
6788
+ }
6789
+ function integrateIgnore(config, targetDir) {
6790
+ if (!config.ignore?.length) return;
6791
+ const targetPath = join18(targetDir, ".gitignore");
6792
+ const current = existsSync19(targetPath) ? readFileSync19(targetPath, "utf-8") : "";
6793
+ const present = new Set(current.split(/\r?\n/).map((line) => line.trim()));
6794
+ const missing = config.ignore.filter((pattern) => !present.has(pattern));
6795
+ if (missing.length === 0) return;
6796
+ const separator = current === "" || current.endsWith("\n") ? "" : "\n";
6797
+ writeFileSync8(targetPath, `${current}${separator}${missing.join("\n")}
6798
+ `);
6799
+ }
6800
+ function integrateWrangler(config, targetDir, emit) {
6801
+ const runWorkerFirst = config.wrangler?.runWorkerFirst === true;
6802
+ const rateLimits = config.wrangler?.rateLimits ?? [];
6803
+ if (!runWorkerFirst && rateLimits.length === 0) return [];
6804
+ const targetPath = join18(targetDir, "wrangler.toml");
6805
+ const steps = [
6806
+ ...runWorkerFirst ? ["Set assets.run_worker_first = true in wrangler.toml."] : [],
6807
+ ...rateLimits.map((binding) => `Add the ${binding.name} rate-limit binding to wrangler.toml.`)
6808
+ ];
6809
+ if (!existsSync19(targetPath)) {
6810
+ return [{ area: "wrangler", reason: "wrangler.toml was not found.", steps }];
6811
+ }
6812
+ let source = readFileSync19(targetPath, "utf-8");
6813
+ if (runWorkerFirst && !/^\[assets\]\s*$/m.test(source)) {
6814
+ return [{ area: "wrangler", reason: "wrangler.toml has no [assets] section.", steps }];
6815
+ }
6816
+ let changed = false;
6817
+ if (runWorkerFirst) {
6818
+ const updated = setRunWorkerFirst(source);
6819
+ changed ||= updated !== source;
6820
+ source = updated;
6821
+ }
6822
+ const scopes = ["", ...wranglerEnvironmentNames(source)];
6823
+ for (const scope of scopes) {
6824
+ for (const binding of rateLimits) {
6825
+ if (hasRateLimitBinding(source, scope, binding.name)) continue;
6826
+ source = `${source.trimEnd()}
6827
+
6828
+ ${rateLimitBindingBlock(scope, binding)}
6829
+ `;
6830
+ changed = true;
6831
+ }
6832
+ }
6833
+ if (changed) {
6834
+ writeFileSync8(targetPath, source);
6835
+ emit(" Integrated: wrangler.toml");
6836
+ } else emit(" Integration already present: wrangler.toml");
6837
+ return [];
6838
+ }
6839
+ function setRunWorkerFirst(source) {
6840
+ const headers = [...source.matchAll(/^\[(?:env\.[^\]]+\.)?assets\]\s*$/gm)];
6841
+ for (const header of headers.reverse()) {
6842
+ const start = header.index;
6843
+ const bodyStart = start + header[0].length;
6844
+ const nextHeader = source.slice(bodyStart).search(/^\[\[?[^\]]+\]\]?\s*$/m);
6845
+ const end = nextHeader < 0 ? source.length : bodyStart + nextHeader;
6846
+ const section = source.slice(start, end);
6847
+ if (/^\s*run_worker_first\s*=\s*true(?:\s*#.*)?$/m.test(section)) continue;
6848
+ const setting = /^(\s*)run_worker_first\s*=\s*(?:false|\[(?:[^\]"']|"(?:[^"\\]|\\[\s\S])*"|'[^']*')*\])(\s*(?:#.*)?)$/m;
6849
+ const updated = setting.test(section) ? section.replace(setting, "$1run_worker_first = true$2") : section.replace(/^\[(?:env\.[^\]]+\.)?assets\]\s*$/m, "$&\nrun_worker_first = true");
6850
+ source = source.slice(0, start) + updated + source.slice(end);
6851
+ }
6852
+ return source;
6853
+ }
6854
+ function wranglerEnvironmentNames(source) {
6855
+ return [
6856
+ ...new Set(
6857
+ [...source.matchAll(/^\[\[?env\.([^.\]]+)(?:\.|\]\]?\s*$)/gm)].map((match) => match[1])
6858
+ )
6859
+ ];
6860
+ }
6861
+ function hasRateLimitBinding(source, scope, name) {
6862
+ const header = scope ? `[[env.${scope}.ratelimits]]` : "[[ratelimits]]";
6863
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6864
+ const namePattern = new RegExp(
6865
+ `^\\s*name\\s*=\\s*(?:"${escaped}"|'${escaped}')\\s*(?:#.*)?$`,
6866
+ "m"
6867
+ );
6868
+ let offset = 0;
6869
+ while ((offset = source.indexOf(header, offset)) >= 0) {
6870
+ const end = source.slice(offset + header.length).search(/^\[/m);
6871
+ const blockEnd = end < 0 ? source.length : offset + header.length + end;
6872
+ if (namePattern.test(source.slice(offset, blockEnd))) return true;
6873
+ offset += header.length;
6874
+ }
6875
+ return false;
6876
+ }
6877
+ function rateLimitBindingBlock(scope, binding) {
6878
+ const header = scope ? `[[env.${scope}.ratelimits]]` : "[[ratelimits]]";
6879
+ return [
6880
+ header,
6881
+ `name = ${JSON.stringify(binding.name)}`,
6882
+ `namespace_id = ${JSON.stringify(binding.namespaceId)}`,
6883
+ `simple = { limit = ${binding.limit}, period = ${binding.period} }`
6884
+ ].join("\n");
6885
+ }
6522
6886
  function integrateCss(packageRoot, config, targetDir, emit) {
6523
6887
  if (!config.css?.length) return [];
6524
- const stylesPath = join17(targetDir, "src", "styles.css");
6525
- if (!existsSync18(stylesPath)) {
6888
+ const stylesPath = join18(targetDir, "src", "styles.css");
6889
+ if (!existsSync19(stylesPath)) {
6526
6890
  emit(" Warning: Cannot integrate CSS \u2014 src/styles.css not found");
6527
6891
  return [
6528
6892
  {
@@ -6534,12 +6898,12 @@ function integrateCss(packageRoot, config, targetDir, emit) {
6534
6898
  }
6535
6899
  ];
6536
6900
  }
6537
- let styles = readFileSync17(stylesPath, "utf-8");
6901
+ let styles = readFileSync19(stylesPath, "utf-8");
6538
6902
  let integrated = 0;
6539
6903
  const unresolved = [];
6540
6904
  for (const cssFile of config.css) {
6541
- const srcPath = join17(packageRoot, "features", config.id, cssFile);
6542
- if (!existsSync18(srcPath)) {
6905
+ const srcPath = join18(packageRoot, "features", config.id, cssFile);
6906
+ if (!existsSync19(srcPath)) {
6543
6907
  emit(` Warning: CSS source not found: ${cssFile}`, "stderr");
6544
6908
  unresolved.push({
6545
6909
  area: "css",
@@ -6548,7 +6912,7 @@ function integrateCss(packageRoot, config, targetDir, emit) {
6548
6912
  });
6549
6913
  continue;
6550
6914
  }
6551
- const css = readFileSync17(srcPath, "utf-8");
6915
+ const css = readFileSync19(srcPath, "utf-8");
6552
6916
  const fingerprint = css.match(/\.([\w-]+)\s*\{|@keyframes\s+([\w-]+)/);
6553
6917
  const marker = fingerprint?.[1] ?? fingerprint?.[2];
6554
6918
  if (marker && styles.includes(marker)) {
@@ -6575,11 +6939,11 @@ function unresolvedRoute(route, config, reason, emit) {
6575
6939
  }
6576
6940
  function integrateRoute(config, targetDir, emit) {
6577
6941
  if (!config.route) return [];
6578
- const navPath = join17(targetDir, "src", "nav.ts");
6579
- if (!existsSync18(navPath)) {
6942
+ const navPath = join18(targetDir, "src", "nav.ts");
6943
+ if (!existsSync19(navPath)) {
6580
6944
  return [unresolvedRoute(config.route, config, "src/nav.ts was not found.", emit)];
6581
6945
  }
6582
- let content = readFileSync17(navPath, "utf-8");
6946
+ let content = readFileSync19(navPath, "utf-8");
6583
6947
  const routePath = config.route.path;
6584
6948
  if (content.includes(`'${routePath}'`)) {
6585
6949
  emit(` Nav already present: ${routePath}`);
@@ -6626,10 +6990,10 @@ function runPackageInstall(packageManager, targetDir, outputVisible) {
6626
6990
  const child = spawnSync6(packageManager, ["install"], { ...options, stdio: "inherit" });
6627
6991
  return { error: child.error, status: child.status };
6628
6992
  }
6629
- const captureDir = mkdtempSync(join17(tmpdir2(), "deepspace-add-install-"));
6993
+ const captureDir = mkdtempSync(join18(tmpdir2(), "deepspace-add-install-"));
6630
6994
  let outputFd;
6631
6995
  try {
6632
- outputFd = openSync2(join17(captureDir, "output.log"), "w+");
6996
+ outputFd = openSync2(join18(captureDir, "output.log"), "w+");
6633
6997
  const child = spawnSync6(packageManager, ["install"], {
6634
6998
  ...options,
6635
6999
  stdio: ["ignore", outputFd, outputFd]
@@ -6653,14 +7017,14 @@ function integrateDependencies(config, targetDir, runInstall, outputVisible, emi
6653
7017
  if (declarations.every(({ values }) => !values || Object.keys(values).length === 0)) {
6654
7018
  return { added: [], installRan: false, packageManager };
6655
7019
  }
6656
- const packagePath = join17(targetDir, "package.json");
6657
- if (!existsSync18(packagePath)) {
7020
+ const packagePath = join18(targetDir, "package.json");
7021
+ if (!existsSync19(packagePath)) {
6658
7022
  emit(" Could not patch package.json (file not found)");
6659
7023
  return { added: [], installRan: false, packageManager };
6660
7024
  }
6661
7025
  let pkg2;
6662
7026
  try {
6663
- pkg2 = JSON.parse(readFileSync17(packagePath, "utf-8"));
7027
+ pkg2 = JSON.parse(readFileSync19(packagePath, "utf-8"));
6664
7028
  } catch (error) {
6665
7029
  emit(
6666
7030
  ` Could not patch package.json (parse error: ${error instanceof Error ? error.message : String(error)})`
@@ -6720,7 +7084,7 @@ ${child.diagnosticTail}` : "";
6720
7084
  }
6721
7085
  function dependencySnapshot(targetDir) {
6722
7086
  try {
6723
- const pkg2 = JSON.parse(readFileSync17(join17(targetDir, "package.json"), "utf-8"));
7087
+ const pkg2 = JSON.parse(readFileSync19(join18(targetDir, "package.json"), "utf-8"));
6724
7088
  return {
6725
7089
  dependencies: pkg2.dependencies ?? {},
6726
7090
  devDependencies: pkg2.devDependencies ?? {}
@@ -6732,7 +7096,7 @@ function dependencySnapshot(targetDir) {
6732
7096
  function installedNavFeatures(packageRoot, targetDir) {
6733
7097
  return listFeatureConfigs(packageRoot).filter((feature) => feature.category === "nav" && feature.files.length > 0).filter((feature) => {
6734
7098
  const signature = resolveFeatureDestPath(feature.files[0].dest, feature.route);
6735
- return existsSync18(join17(targetDir, signature));
7099
+ return existsSync19(join18(targetDir, signature));
6736
7100
  }).map((feature) => feature.id);
6737
7101
  }
6738
7102
  function warnNavConflicts(packageRoot, config, targetDir, emit) {
@@ -6770,6 +7134,7 @@ function installFeature(options) {
6770
7134
  emit(` ${config.description ?? ""}`);
6771
7135
  emit(` Target: ${targetDir}`);
6772
7136
  const installedFiles = installFiles(packageRoot, config, targetDir, emit);
7137
+ integrateIgnore(config, targetDir);
6773
7138
  emit("");
6774
7139
  emit(
6775
7140
  `Copied ${installedFiles.copied} file(s)${installedFiles.skipped > 0 ? `, skipped ${installedFiles.skipped} existing` : ""}`
@@ -6777,6 +7142,8 @@ function installFeature(options) {
6777
7142
  const unresolvedIntegrations = [
6778
7143
  ...integrateSchema(config, targetDir, emit),
6779
7144
  ...integrateActions(config, targetDir, emit),
7145
+ ...integrateWrangler(config, targetDir, emit),
7146
+ ...integrateCode(config, targetDir, emit),
6780
7147
  ...integrateCss(packageRoot, config, targetDir, emit),
6781
7148
  ...integrateRoute(config, targetDir, emit)
6782
7149
  ];
@@ -6869,9 +7236,10 @@ var CATEGORY_LABELS = {
6869
7236
  nav: "Navigation",
6870
7237
  layout: "Layouts",
6871
7238
  display: "Display",
6872
- landing: "Landing Page Sections"
7239
+ landing: "Landing Page Sections",
7240
+ publishing: "Publishing"
6873
7241
  };
6874
- var CATEGORY_ORDER = ["assistant", "data", "nav", "layout", "display", "landing"];
7242
+ var CATEGORY_ORDER = ["assistant", "data", "publishing", "nav", "layout", "display", "landing"];
6875
7243
  function groupByCategory(features) {
6876
7244
  const groups = /* @__PURE__ */ new Map();
6877
7245
  for (const feature of features) {
@@ -6961,11 +7329,11 @@ function renderFeatureInfo(config) {
6961
7329
  return lines.join("\n");
6962
7330
  }
6963
7331
  function isDeepSpaceApp(dir) {
6964
- if (existsSync19(join18(dir, "wrangler.toml"))) return true;
6965
- const packagePath = join18(dir, "package.json");
6966
- if (!existsSync19(packagePath)) return false;
7332
+ if (existsSync20(join19(dir, "wrangler.toml"))) return true;
7333
+ const packagePath = join19(dir, "package.json");
7334
+ if (!existsSync20(packagePath)) return false;
6967
7335
  try {
6968
- const pkg2 = JSON.parse(readFileSync18(packagePath, "utf-8"));
7336
+ const pkg2 = JSON.parse(readFileSync20(packagePath, "utf-8"));
6969
7337
  return Boolean(pkg2.dependencies?.deepspace || pkg2.devDependencies?.deepspace);
6970
7338
  } catch {
6971
7339
  return false;
@@ -6974,8 +7342,8 @@ function isDeepSpaceApp(dir) {
6974
7342
  function installedPackageRoot(fromDir) {
6975
7343
  let dir = fromDir;
6976
7344
  for (; ; ) {
6977
- const candidate = join18(dir, "node_modules", "deepspace");
6978
- if (existsSync19(join18(candidate, "package.json"))) return candidate;
7345
+ const candidate = join19(dir, "node_modules", "deepspace");
7346
+ if (existsSync20(join19(candidate, "package.json"))) return candidate;
6979
7347
  const parent = dirname7(dir);
6980
7348
  if (parent === dir) return null;
6981
7349
  dir = parent;
@@ -6992,9 +7360,9 @@ async function targetInstaller(appDir) {
6992
7360
  if (realpathSync3(installedRoot) === realpathSync3(featurePackageRoot())) {
6993
7361
  return { installFeature };
6994
7362
  }
6995
- const entry = join18(installedRoot, "dist", "feature-installer.js");
6996
- const catalog = join18(installedRoot, "features");
6997
- if (!existsSync19(entry) || !existsSync19(catalog)) {
7363
+ const entry = join19(installedRoot, "dist", "feature-installer.js");
7364
+ const catalog = join19(installedRoot, "features");
7365
+ if (!existsSync20(entry) || !existsSync20(catalog)) {
6998
7366
  throw new Refusal(
6999
7367
  `The app's installed deepspace package does not contain the typed feature installer.
7000
7368
  Expected: ${entry}
@@ -7087,7 +7455,7 @@ var add_default = defineDeepspaceCommand({
7087
7455
  return { data: { features: featureCatalog(root) } };
7088
7456
  }
7089
7457
  const appDir = resolve14(args.dir ?? ".");
7090
- if (!existsSync19(appDir)) {
7458
+ if (!existsSync20(appDir)) {
7091
7459
  throw new Refusal(
7092
7460
  `Target directory not found: ${appDir}
7093
7461
  Create a new app first: deepspace app create <name>`,
@@ -7748,7 +8116,7 @@ var transfer_default = defineCommand7({
7748
8116
  import { defineCommand as defineCommand8 } from "citty";
7749
8117
 
7750
8118
  // src/cli/commands/_invoke-impl.ts
7751
- import { readFileSync as readFileSync19 } from "fs";
8119
+ import { readFileSync as readFileSync21 } from "fs";
7752
8120
  import * as p15 from "@clack/prompts";
7753
8121
  var API_URL7 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
7754
8122
  var DEFAULT_TIMEOUT_MS = 12e4;
@@ -7789,7 +8157,7 @@ function resolveBody(opts) {
7789
8157
  if (opts.body != null) {
7790
8158
  raw = opts.body;
7791
8159
  } else if (opts.bodyFile != null) {
7792
- raw = opts.bodyFile === "-" ? readFileSync19(0, "utf-8") : readFileSync19(opts.bodyFile, "utf-8");
8160
+ raw = opts.bodyFile === "-" ? readFileSync21(0, "utf-8") : readFileSync21(opts.bodyFile, "utf-8");
7793
8161
  } else {
7794
8162
  return "{}";
7795
8163
  }
@@ -8068,8 +8436,8 @@ var integrations_default = defineCommand8({
8068
8436
 
8069
8437
  // src/cli/commands/library.ts
8070
8438
  import { defineCommand as defineCommand9 } from "citty";
8071
- import { existsSync as existsSync20, readFileSync as readFileSync20 } from "fs";
8072
- import { join as join19, resolve as resolve15 } from "path";
8439
+ import { existsSync as existsSync21, readFileSync as readFileSync22 } from "fs";
8440
+ import { join as join20, resolve as resolve15 } from "path";
8073
8441
  import { parse as parseToml4 } from "smol-toml";
8074
8442
  import * as p16 from "@clack/prompts";
8075
8443
  var DEFAULT_LIBRARY_APP = "deepdotspace-site";
@@ -8140,14 +8508,14 @@ var publish = defineDeepspaceCommand({
8140
8508
  let appName = str(args.app)?.trim();
8141
8509
  if (!appName) {
8142
8510
  const appDir = resolve15(str(args.dir) ?? ".");
8143
- const wranglerPath = join19(appDir, "wrangler.toml");
8144
- if (!existsSync20(wranglerPath)) {
8511
+ const wranglerPath = join20(appDir, "wrangler.toml");
8512
+ if (!existsSync21(wranglerPath)) {
8145
8513
  throw new Refusal(
8146
8514
  "No --app provided and no wrangler.toml found. Either pass --app <name> or run from your app's directory.",
8147
8515
  "not_in_app_repo"
8148
8516
  );
8149
8517
  }
8150
- const cfg = parseToml4(readFileSync20(wranglerPath, "utf-8"));
8518
+ const cfg = parseToml4(readFileSync22(wranglerPath, "utf-8"));
8151
8519
  const nameRes = resolveAppName(cfg.name);
8152
8520
  if (!nameRes.ok) throw new Refusal(`wrangler.toml: ${nameRes.reason}`, "invalid_config");
8153
8521
  appName = nameRes.name;
@@ -8285,8 +8653,8 @@ var library_default = defineCommand9({
8285
8653
 
8286
8654
  // src/cli/commands/feedback.ts
8287
8655
  import * as p17 from "@clack/prompts";
8288
- import { existsSync as existsSync21, readFileSync as readFileSync21 } from "fs";
8289
- import { dirname as dirname8, join as join20 } from "path";
8656
+ import { existsSync as existsSync22, readFileSync as readFileSync23 } from "fs";
8657
+ import { dirname as dirname8, join as join21 } from "path";
8290
8658
  import { fileURLToPath as fileURLToPath2 } from "url";
8291
8659
  import { release } from "os";
8292
8660
  var API_URL8 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
@@ -8309,12 +8677,12 @@ function readCliVersion() {
8309
8677
  try {
8310
8678
  const here = dirname8(fileURLToPath2(import.meta.url));
8311
8679
  const candidates = [
8312
- join20(here, "..", "package.json"),
8313
- join20(here, "..", "..", "..", "package.json")
8680
+ join21(here, "..", "package.json"),
8681
+ join21(here, "..", "..", "..", "package.json")
8314
8682
  ];
8315
8683
  for (const path of candidates) {
8316
- if (!existsSync21(path)) continue;
8317
- const pkg2 = JSON.parse(readFileSync21(path, "utf-8"));
8684
+ if (!existsSync22(path)) continue;
8685
+ const pkg2 = JSON.parse(readFileSync23(path, "utf-8"));
8318
8686
  if (pkg2.name === "deepspace" && pkg2.version) return pkg2.version;
8319
8687
  }
8320
8688
  } catch {
@@ -8510,7 +8878,7 @@ Context: deepspace ${payload.cliVersion} \xB7 ${payload.nodeVersion} \xB7 ${payl
8510
8878
 
8511
8879
  // src/cli/commands/secrets.ts
8512
8880
  import { defineCommand as defineCommand10 } from "citty";
8513
- import { readFileSync as readFileSync22 } from "fs";
8881
+ import { readFileSync as readFileSync24 } from "fs";
8514
8882
 
8515
8883
  // src/cli/lib/citty-args.ts
8516
8884
  function dedupePositionals(bound, rest) {
@@ -8780,7 +9148,7 @@ var upload = defineCommand10({
8780
9148
  async run({ args }) {
8781
9149
  try {
8782
9150
  const t = await resolveTarget(args);
8783
- const content = args.file === "-" ? readFileSync22(0, "utf-8") : readFileSync22(args.file, "utf-8");
9151
+ const content = args.file === "-" ? readFileSync24(0, "utf-8") : readFileSync24(args.file, "utf-8");
8784
9152
  const secrets = parseSecretsUpload(content);
8785
9153
  if (Object.keys(secrets).length === 0)
8786
9154
  fail(new InputError("No secrets found in the input.", "empty_input"));
@@ -9036,7 +9404,7 @@ var init_default = defineDeepspaceCommand({
9036
9404
  // src/cli/commands/status.ts
9037
9405
  import { defineCommand as defineCommand11 } from "citty";
9038
9406
  import * as p18 from "@clack/prompts";
9039
- import { existsSync as existsSync22, readFileSync as readFileSync23 } from "fs";
9407
+ import { existsSync as existsSync23, readFileSync as readFileSync25 } from "fs";
9040
9408
  function timeAgo(iso) {
9041
9409
  const ms = Date.now() - Date.parse(iso);
9042
9410
  if (!Number.isFinite(ms) || ms < 0) return iso;
@@ -9061,6 +9429,14 @@ function statusRemoteFailure(error) {
9061
9429
  json: { state: "unavailable", error: message, ...code ? { code } : {} }
9062
9430
  };
9063
9431
  }
9432
+ function resolveStatusApp(appDir, wranglerEnv) {
9433
+ if (!wranglerEnv) {
9434
+ return { appName: detectAppName(appDir) ?? "(unnamed)", appId: readAppId(appDir) };
9435
+ }
9436
+ const resolved = resolveAppNameForEnv(readWranglerConfig(appDir), wranglerEnv);
9437
+ if (!resolved.ok) throw new InputError(resolved.reason, "invalid_env");
9438
+ return { appName: resolved.name, appId: readAppId(appDir, wranglerEnv) };
9439
+ }
9064
9440
  var status_default = defineCommand11({
9065
9441
  meta: {
9066
9442
  name: "status",
@@ -9071,9 +9447,17 @@ var status_default = defineCommand11({
9071
9447
  type: "boolean",
9072
9448
  description: "Emit a single-line JSON result for scripts/agents",
9073
9449
  default: false
9450
+ },
9451
+ env: {
9452
+ type: "string",
9453
+ alias: "e",
9454
+ description: "wrangler.toml [env.<name>] slot \u2014 reads that environment\u2019s app identity",
9455
+ required: false
9074
9456
  }
9075
9457
  },
9076
9458
  async run({ args }) {
9459
+ const { wranglerEnv, error: envError } = parseWranglerEnvArg(args.env);
9460
+ if (envError) throw new InputError(envError, "invalid_env");
9077
9461
  const lines = [];
9078
9462
  const facts = {
9079
9463
  loggedIn: false,
@@ -9105,7 +9489,7 @@ var status_default = defineCommand11({
9105
9489
  json.services = urls;
9106
9490
  if (urlOverrides.length > 0) json.urlOverrides = urlOverrides;
9107
9491
  }
9108
- facts.loggedIn = existsSync22(SESSION_PATH);
9492
+ facts.loggedIn = existsSync23(SESSION_PATH);
9109
9493
  let userId = null;
9110
9494
  if (!facts.loggedIn) {
9111
9495
  lines.push(["Session", "not logged in"]);
@@ -9114,7 +9498,7 @@ var status_default = defineCommand11({
9114
9498
  let who = "logged in";
9115
9499
  try {
9116
9500
  const payload = decodeJwtPayload(
9117
- readFileSync23(TOKEN_PATH, "utf-8").trim()
9501
+ readFileSync25(TOKEN_PATH, "utf-8").trim()
9118
9502
  );
9119
9503
  if (payload.email) who = payload.email;
9120
9504
  userId = payload.sub ?? null;
@@ -9129,15 +9513,15 @@ var status_default = defineCommand11({
9129
9513
  lines.push(["App", "not inside a DeepSpace app directory"]);
9130
9514
  json.inApp = false;
9131
9515
  } else {
9132
- const appName = detectAppName(appDir) ?? "(unnamed)";
9133
- const appId = readAppId(appDir);
9516
+ const { appName, appId } = resolveStatusApp(appDir, wranglerEnv);
9134
9517
  lines.push([
9135
9518
  "App",
9136
- `${appName}${appId ? ` (${appId})` : " \u2014 no DEEPSPACE_APP_ID; run `deepspace app init`"}`
9519
+ `${appName}${wranglerEnv ? ` [env.${wranglerEnv}]` : ""}${appId ? ` (${appId})` : " \u2014 no DEEPSPACE_APP_ID; run `deepspace app init`"}`
9137
9520
  ]);
9138
9521
  json.inApp = true;
9139
9522
  json.appName = appName;
9140
9523
  json.appId = appId;
9524
+ if (wranglerEnv) json.wranglerEnv = wranglerEnv;
9141
9525
  const deps = installState(appDir);
9142
9526
  const depsInstallCommand = installCommand(appDir);
9143
9527
  lines.push([
@@ -9425,7 +9809,7 @@ var push_default = defineDeepspaceCommand({
9425
9809
  ensureSpaceRemote(appDir, appId);
9426
9810
  if (force) {
9427
9811
  spinner2?.message(`Checking the cloud ${branch} tip before force-push\u2026`);
9428
- const lastPushed = resolveCommit(appDir, `refs/deepspace/pushed/${branch}`);
9812
+ const lastPushed = resolveCommit(appDir, spacePrivateRef(`pushed/${branch}`));
9429
9813
  let remoteTip = null;
9430
9814
  try {
9431
9815
  runGitRemote(appDir, token, [
@@ -9470,7 +9854,7 @@ var push_default = defineDeepspaceCommand({
9470
9854
  );
9471
9855
  const ok2 = result.status === "up_to_date" || result.status === "committed";
9472
9856
  if (ok2) {
9473
- updateRef(appDir, `refs/deepspace/pushed/${branch}`, tipOid);
9857
+ updateRef(appDir, spacePrivateRef(`pushed/${branch}`), tipOid);
9474
9858
  }
9475
9859
  const recoverable = isRecoverablePushFailure(result.status);
9476
9860
  const rejectReason = result.reason ?? result.summary;
@@ -9512,7 +9896,7 @@ function workspaceBranchPullRefusal(branch, trunkBranch) {
9512
9896
  const id = workspaceIdFromBranch(branch);
9513
9897
  if (!id) return null;
9514
9898
  const trunk = trunkBranch || "main";
9515
- const merge = `git merge refs/remotes/space/${shQuote(trunk)}`;
9899
+ const merge = `git merge ${spaceTrackingRef(shQuote(trunk))}`;
9516
9900
  return {
9517
9901
  code: "workspace_branch",
9518
9902
  trunk,
@@ -9535,7 +9919,7 @@ function worktreeHoldingBranch(worktrees, branch, selfPath) {
9535
9919
  }
9536
9920
  function divergedMergeAdvice(branch, hasSelectedCheckout) {
9537
9921
  const b = shQuote(branch);
9538
- return hasSelectedCheckout ? `git merge refs/remotes/space/${b}` : `git checkout ${b} && git merge refs/remotes/space/${b}`;
9922
+ return hasSelectedCheckout ? `git merge ${spaceTrackingRef(b)}` : `git checkout ${b} && git merge ${spaceTrackingRef(b)}`;
9539
9923
  }
9540
9924
  var pull_default = defineDeepspaceCommand({
9541
9925
  meta: {
@@ -9608,11 +9992,12 @@ var pull_default = defineDeepspaceCommand({
9608
9992
  const wsRefusal = workspaceBranchPullRefusal(branch, trunkBranch);
9609
9993
  if (wsRefusal) {
9610
9994
  const trunkRef = `refs/heads/${wsRefusal.trunk}`;
9995
+ const trunkTrackingRef = spaceTrackingRef(wsRefusal.trunk);
9611
9996
  runGitRemote(appDir, token, [
9612
9997
  "fetch",
9613
9998
  "--quiet",
9614
9999
  SPACE_REMOTE,
9615
- `+${trunkRef}:refs/remotes/space/${wsRefusal.trunk}`
10000
+ `+${trunkRef}:${trunkTrackingRef}`
9616
10001
  ]);
9617
10002
  spinner2?.stop("Workspace branch.");
9618
10003
  const { path: branchWorktreePath2 } = selectedBranchCheckout2();
@@ -9625,7 +10010,7 @@ var pull_default = defineDeepspaceCommand({
9625
10010
  const step = branchWorktreePath2 ? selectedCheckoutClean2 ? `Merge the fetched trunk in the checkout that owns ${branch} (${branchWorktreePath2}).` : `The checkout that owns ${branch} (${branchWorktreePath2}) is dirty. Commit or stash there, then rerun \`${pullCommand2}\` from that checkout.` : localBranchExists ? canCheckoutHere2 ? `The branch is not checked out. Check it out here, then rerun \`${pullCommand2}\` to receive the merge step.` : `The branch is not checked out and this checkout is dirty. Commit or stash first (or use a clean worktree), check out ${shQuote(branch)}, then rerun \`${pullCommand2}\`.` : `This clone has not attached the workspace. Attach ${workspaceId}, then rerun \`${pullCommand2}\` from its checkout.`;
9626
10011
  const action = branchWorktreePath2 && selectedCheckoutClean2 ? {
9627
10012
  cwd: branchWorktreePath2,
9628
- argv: ["git", "merge", `refs/remotes/space/${wsRefusal.trunk}`]
10013
+ argv: ["git", "merge", trunkTrackingRef]
9629
10014
  } : canCheckoutHere2 ? { cwd: appDir, argv: ["git", "checkout", branch] } : !localBranchExists ? {
9630
10015
  cwd: appDir,
9631
10016
  argv: ["deepspace", "workspace", "attach", workspaceId, "--app", appId]
@@ -9649,17 +10034,18 @@ var pull_default = defineDeepspaceCommand({
9649
10034
  );
9650
10035
  }
9651
10036
  spinner2?.message(`Fetching ${branch} from the cloud repo\u2026`);
10037
+ const trackingRef = spaceTrackingRef(branch);
9652
10038
  runGitRemote(appDir, token, [
9653
10039
  "fetch",
9654
10040
  "--quiet",
9655
10041
  SPACE_REMOTE,
9656
- `+${refName}:refs/remotes/space/${branch}`
10042
+ `+${refName}:${trackingRef}`
9657
10043
  ]);
9658
- const remoteOid = resolveCommit(appDir, `refs/remotes/space/${branch}`);
10044
+ const remoteOid = resolveCommit(appDir, trackingRef);
9659
10045
  if (!remoteOid) {
9660
10046
  spinner2?.stop("Fetch failed.");
9661
10047
  throw new Refusal(
9662
- `Fetched, but refs/remotes/space/${branch} did not materialize \u2014 retry; if it persists, report it with \`deepspace feedback\`.`,
10048
+ `Fetched, but ${trackingRef} did not materialize \u2014 retry; if it persists, report it with \`deepspace feedback\`.`,
9663
10049
  "fetch_incomplete"
9664
10050
  );
9665
10051
  }
@@ -9696,8 +10082,8 @@ var pull_default = defineDeepspaceCommand({
9696
10082
  integration = "fetched_only_diverged";
9697
10083
  }
9698
10084
  const qBranch = shQuote(branch);
9699
- const ffRef = `git merge --ff-only refs/remotes/space/${qBranch}`;
9700
- const mergeRef = `git merge refs/remotes/space/${qBranch}`;
10085
+ const ffRef = `git merge --ff-only ${spaceTrackingRef(qBranch)}`;
10086
+ const mergeRef = `git merge ${spaceTrackingRef(qBranch)}`;
9701
10087
  const pullCommand = targetedVcCommand("pull", appId, branch);
9702
10088
  const pushCommand = targetedVcCommand("push", appId, branch);
9703
10089
  const selectedCheckoutClean = branchWorktreePath !== null && isWorkTreeClean(branchWorktreePath);
@@ -9719,35 +10105,35 @@ var pull_default = defineDeepspaceCommand({
9719
10105
  codeForState = "diverged";
9720
10106
  if (branchWorktreePath && selectedCheckoutClean) {
9721
10107
  const mergeAdvice = divergedMergeAdvice(branch, true);
9722
- line = `Fetched space/${branch}. ${branch} and the cloud repo diverged \u2014 run \`${mergeAdvice}\` in ${branchWorktreePath} (no network needed), then \`${pushCommand}\`.`;
10108
+ line = `Fetched ${SPACE_REMOTE}/${branch}. ${branch} and the cloud repo diverged \u2014 run \`${mergeAdvice}\` in ${branchWorktreePath} (no network needed), then \`${pushCommand}\`.`;
9723
10109
  recoveryAction = {
9724
10110
  cwd: branchWorktreePath,
9725
- argv: ["git", "merge", `refs/remotes/space/${branch}`]
10111
+ argv: ["git", "merge", trackingRef]
9726
10112
  };
9727
10113
  } else if (branchWorktreePath) {
9728
- line = `Fetched space/${branch}. ${branch} and the cloud repo diverged, but its checkout (${branchWorktreePath}) is dirty. Commit or stash there, then rerun \`${pullCommand}\` from that checkout.`;
10114
+ line = `Fetched ${SPACE_REMOTE}/${branch}. ${branch} and the cloud repo diverged, but its checkout (${branchWorktreePath}) is dirty. Commit or stash there, then rerun \`${pullCommand}\` from that checkout.`;
9729
10115
  } else if (canCheckoutHere) {
9730
- line = `Fetched space/${branch}. ${branch} and the cloud repo diverged, but ${branch} is not checked out. Check it out here, then rerun \`${pullCommand}\` to receive the merge step.`;
10116
+ line = `Fetched ${SPACE_REMOTE}/${branch}. ${branch} and the cloud repo diverged, but ${branch} is not checked out. Check it out here, then rerun \`${pullCommand}\` to receive the merge step.`;
9731
10117
  recoveryAction = { cwd: appDir, argv: ["git", "checkout", branch] };
9732
10118
  } else {
9733
- line = `Fetched space/${branch}. ${branch} and the cloud repo diverged, but ${branch} is not checked out and this checkout is dirty. Commit or stash first (or use a clean worktree), check out ${qBranch}, then rerun \`${pullCommand}\`.`;
10119
+ line = `Fetched ${SPACE_REMOTE}/${branch}. ${branch} and the cloud repo diverged, but ${branch} is not checked out and this checkout is dirty. Commit or stash first (or use a clean worktree), check out ${qBranch}, then rerun \`${pullCommand}\`.`;
9734
10120
  }
9735
10121
  break;
9736
10122
  case "fetched_only_dirty":
9737
10123
  codeForState = "dirty_worktree";
9738
- line = `Fetched space/${branch}, but the worktree is dirty. Either stash \u2192 \`${ffRef}\` \u2192 \`git stash pop\`, or commit first \u2192 \`${mergeRef}\` (or \`git rebase\`).`;
10124
+ line = `Fetched ${SPACE_REMOTE}/${branch}, but the worktree is dirty. Either stash \u2192 \`${ffRef}\` \u2192 \`git stash pop\`, or commit first \u2192 \`${mergeRef}\` (or \`git rebase\`).`;
9739
10125
  break;
9740
10126
  case "fetched_only_unborn":
9741
10127
  codeForState = "dirty_worktree";
9742
- line = `Fetched space/${branch} and created ${branch}, but the worktree has uncommitted files. Preserve them in a commit or stash (including untracked files), then restore the checkout; do not run reset --hard until \`git status\` confirms nothing worth keeping remains.`;
10128
+ line = `Fetched ${SPACE_REMOTE}/${branch} and created ${branch}, but the worktree has uncommitted files. Preserve them in a commit or stash (including untracked files), then restore the checkout; do not run reset --hard until \`git status\` confirms nothing worth keeping remains.`;
9743
10129
  break;
9744
10130
  case "fetched_only_worktree":
9745
10131
  codeForState = "branch_in_worktree";
9746
10132
  if (branchWorktreePath && selectedCheckoutClean) {
9747
- line = `Fetched space/${branch}, but ${branch} is checked out in another worktree (${branchWorktreePath}) \u2014 its ref was left untouched to keep that worktree in sync. Run \`${pullCommand}\` from there.`;
10133
+ line = `Fetched ${SPACE_REMOTE}/${branch}, but ${branch} is checked out in another worktree (${branchWorktreePath}) \u2014 its ref was left untouched to keep that worktree in sync. Run \`${pullCommand}\` from there.`;
9748
10134
  recoveryAction = targetedVcAction("pull", branchWorktreePath, appId, branch);
9749
10135
  } else {
9750
- line = `Fetched space/${branch}, but its checkout (${branchWorktreePath ?? "a linked worktree"}) is dirty, so the ref and worktree were left untouched. Commit or stash there, then run \`${pullCommand}\` from that checkout.`;
10136
+ line = `Fetched ${SPACE_REMOTE}/${branch}, but its checkout (${branchWorktreePath ?? "a linked worktree"}) is dirty, so the ref and worktree were left untouched. Commit or stash there, then run \`${pullCommand}\` from that checkout.`;
9751
10137
  }
9752
10138
  break;
9753
10139
  default: {
@@ -9756,8 +10142,8 @@ var pull_default = defineDeepspaceCommand({
9756
10142
  }
9757
10143
  }
9758
10144
  const actionRequired = recoveryAction !== void 0;
9759
- spinner2?.stop(codeForState ? `Fetched space/${branch}.` : line);
9760
- if (!args.json) p20.log.info(`App: ${appId} \xB7 space/${branch} at ${remoteOid.slice(0, 10)}`);
10145
+ spinner2?.stop(codeForState ? `Fetched ${SPACE_REMOTE}/${branch}.` : line);
10146
+ if (!args.json) p20.log.info(`App: ${appId} \xB7 ${SPACE_REMOTE}/${branch} at ${remoteOid.slice(0, 10)}`);
9761
10147
  if (codeForState) {
9762
10148
  throw new Refusal(line, codeForState, {
9763
10149
  actionRequired,
@@ -9777,18 +10163,18 @@ var pull_default = defineDeepspaceCommand({
9777
10163
 
9778
10164
  // src/cli/commands/clone.ts
9779
10165
  import * as p21 from "@clack/prompts";
9780
- import { existsSync as existsSync23, readdirSync as readdirSync6, rmSync as rmSync5 } from "fs";
9781
- import { resolve as resolve16, basename as basename3, join as join21 } from "path";
10166
+ import { existsSync as existsSync24, readdirSync as readdirSync6, rmSync as rmSync5 } from "fs";
10167
+ import { resolve as resolve16, basename as basename3, join as join22 } from "path";
9782
10168
  function cleanFailedCloneDir(dir) {
9783
10169
  let entries;
9784
10170
  try {
9785
10171
  entries = readdirSync6(dir);
9786
10172
  } catch {
9787
- return existsSync23(dir) ? ["<unreadable>"] : [];
10173
+ return existsSync24(dir) ? ["<unreadable>"] : [];
9788
10174
  }
9789
10175
  if (entries.length === 1 && entries[0] === ".git") {
9790
10176
  try {
9791
- rmSync5(join21(dir, ".git"), { recursive: true, force: true });
10177
+ rmSync5(join22(dir, ".git"), { recursive: true, force: true });
9792
10178
  return [];
9793
10179
  } catch {
9794
10180
  return entries;
@@ -9797,7 +10183,7 @@ function cleanFailedCloneDir(dir) {
9797
10183
  return entries;
9798
10184
  }
9799
10185
  function validateCloneTarget(dir) {
9800
- if (!existsSync23(dir)) return false;
10186
+ if (!existsSync24(dir)) return false;
9801
10187
  let entries;
9802
10188
  try {
9803
10189
  entries = readdirSync6(dir);
@@ -9911,6 +10297,12 @@ var releases_default = defineDeepspaceCommand({
9911
10297
  description: "App id or subdomain name (default: the surrounding app directory)",
9912
10298
  required: false
9913
10299
  },
10300
+ env: {
10301
+ type: "string",
10302
+ alias: "e",
10303
+ description: "wrangler.toml [env.<name>] slot \u2014 selects that environment's app id",
10304
+ required: false
10305
+ },
9914
10306
  limit: {
9915
10307
  type: "string",
9916
10308
  description: "Max entries (default 20)",
@@ -9919,13 +10311,15 @@ var releases_default = defineDeepspaceCommand({
9919
10311
  },
9920
10312
  async run({ args }) {
9921
10313
  const appArg = args.app;
10314
+ const envArg = args.env;
9922
10315
  const { limit, error: limitError } = parseLimitArg(args.limit);
9923
10316
  if (limitError) throw new Refusal(limitError, "invalid_limit");
9924
- assertAppTargetResolvable(appArg);
10317
+ assertAppTargetResolvable(appArg, { wranglerEnv: envArg });
10318
+ const { wranglerEnv } = parseWranglerEnvArg(envArg);
9925
10319
  const spinner2 = args.json ? null : createSpinner();
9926
10320
  spinner2?.start("Loading release history\u2026");
9927
10321
  const token = await ensureToken();
9928
- const appId = await resolveAppTarget(DEPLOY_URL11, token, appArg);
10322
+ const appId = await resolveAppTarget(DEPLOY_URL11, token, appArg, { wranglerEnv: envArg });
9929
10323
  const { releases } = await repoApi(DEPLOY_URL11, token, appId).listReleases(limit);
9930
10324
  spinner2?.stop(`Loaded ${releases.length} ${releases.length === 1 ? "release" : "releases"}.`);
9931
10325
  if (!args.json) {
@@ -9939,10 +10333,12 @@ var releases_default = defineDeepspaceCommand({
9939
10333
  `#${r.seq} ${r.id} ${r.kind.padEnd(8)} ${r.createdAt} ${r.actor} ${source} ${rollback}`
9940
10334
  );
9941
10335
  }
9942
- p22.log.info("Roll back an available release with: deepspace rollback <release-id>");
10336
+ p22.log.info(
10337
+ `Roll back an available release with: deepspace rollback <release-id>${wranglerEnv ? ` --env ${wranglerEnv}` : ""}`
10338
+ );
9943
10339
  }
9944
10340
  }
9945
- return { data: { appId, releases } };
10341
+ return { data: { appId, wranglerEnv: wranglerEnv ?? null, releases } };
9946
10342
  }
9947
10343
  });
9948
10344
 
@@ -9986,6 +10382,12 @@ var rollback_default = defineDeepspaceCommand({
9986
10382
  description: "App id or subdomain name (default: the surrounding app directory)",
9987
10383
  required: false
9988
10384
  },
10385
+ env: {
10386
+ type: "string",
10387
+ alias: "e",
10388
+ description: "wrangler.toml [env.<name>] slot \u2014 selects that environment's app id",
10389
+ required: false
10390
+ },
9989
10391
  "allow-do-deletion": {
9990
10392
  type: "boolean",
9991
10393
  description: "Proceed even though the target release declares fewer Durable Object classes than the current one \u2014 THEIR STORED DATA IS DELETED.",
@@ -9994,6 +10396,7 @@ var rollback_default = defineDeepspaceCommand({
9994
10396
  },
9995
10397
  async run({ args }) {
9996
10398
  const appArg = args.app;
10399
+ const envArg = args.env;
9997
10400
  if (args.release !== void 0) {
9998
10401
  const r = String(args.release).trim();
9999
10402
  if (!r) {
@@ -10006,11 +10409,12 @@ var rollback_default = defineDeepspaceCommand({
10006
10409
  throw new Refusal(`Invalid release id: ${r} (expected rel_<ULID>).`, "invalid_release");
10007
10410
  }
10008
10411
  }
10009
- assertAppTargetResolvable(appArg);
10412
+ assertAppTargetResolvable(appArg, { wranglerEnv: envArg });
10413
+ const { wranglerEnv } = parseWranglerEnvArg(envArg);
10010
10414
  const spinner2 = args.json ? null : createSpinner();
10011
10415
  spinner2?.start("Preparing rollback\u2026");
10012
10416
  const token = await ensureToken();
10013
- const appId = await resolveAppTarget(DEPLOY_URL12, token, appArg);
10417
+ const appId = await resolveAppTarget(DEPLOY_URL12, token, appArg, { wranglerEnv: envArg });
10014
10418
  const api4 = repoApi(DEPLOY_URL12, token, appId);
10015
10419
  let releaseId = args.release ? String(args.release).trim() : "";
10016
10420
  if (!releaseId) {
@@ -10075,6 +10479,7 @@ Re-run with --allow-do-deletion to accept the data loss.`,
10075
10479
  bundleRetained: result.bundleRetained ?? null,
10076
10480
  url: result.url ?? null,
10077
10481
  versionId: result.versionId ?? null,
10482
+ wranglerEnv: wranglerEnv ?? null,
10078
10483
  ...doClassGuard ? { doClassGuard } : {}
10079
10484
  }
10080
10485
  };
@@ -10091,7 +10496,7 @@ import { isAbsolute as isAbsolute6, resolve as resolve17 } from "path";
10091
10496
  // src/cli/commands/workspace/analysis.ts
10092
10497
  import * as p24 from "@clack/prompts";
10093
10498
  var MAX_OVERLAP_PATHS = 20;
10094
- var PEER_REF_PREFIX = "refs/deepspace/peers/";
10499
+ var PEER_REF_PREFIX = `${spacePrivateRef("peers")}/`;
10095
10500
  function peerWorkspaceRef(workspaceId) {
10096
10501
  return `${PEER_REF_PREFIX}${workspaceId}`;
10097
10502
  }
@@ -10102,7 +10507,7 @@ function formatCount(count) {
10102
10507
  function fetchTrunk(appDir, token, head) {
10103
10508
  if (!head?.startsWith("refs/heads/")) return null;
10104
10509
  const branch = head.slice("refs/heads/".length);
10105
- const remoteRef = `refs/remotes/space/${branch}`;
10510
+ const remoteRef = spaceTrackingRef(branch);
10106
10511
  runGitRemote(appDir, token, ["fetch", "--quiet", SPACE_REMOTE, `+${head}:${remoteRef}`]);
10107
10512
  const oid = resolveCommit(appDir, remoteRef);
10108
10513
  return oid ? { branch, ref: remoteRef, oid } : null;
@@ -10112,7 +10517,7 @@ function localTrunkOid(appDir, refs) {
10112
10517
  if (!head?.startsWith("refs/heads/")) return null;
10113
10518
  const advertised = refs?.refs.find((ref) => ref.name === head)?.oid ?? null;
10114
10519
  if (advertised && resolveCommit(appDir, advertised)) return advertised;
10115
- return resolveCommit(appDir, `refs/remotes/space/${head.slice("refs/heads/".length)}`);
10520
+ return resolveCommit(appDir, spaceTrackingRef(head.slice("refs/heads/".length)));
10116
10521
  }
10117
10522
  function fetchPeerWorkspaceRefs(appDir, token) {
10118
10523
  try {
@@ -10211,8 +10616,8 @@ function printOverlaps(overlaps, json) {
10211
10616
 
10212
10617
  // src/cli/commands/workspace/local.ts
10213
10618
  import * as p25 from "@clack/prompts";
10214
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync24, realpathSync as realpathSync5, renameSync as renameSync2, statSync, writeFileSync as writeFileSync9 } from "fs";
10215
- import { dirname as dirname9, isAbsolute as isAbsolute5, join as join22, relative as relative2, sep as sep2 } from "path";
10619
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync26, realpathSync as realpathSync5, renameSync as renameSync2, statSync, writeFileSync as writeFileSync9 } from "fs";
10620
+ import { dirname as dirname9, isAbsolute as isAbsolute5, join as join23, relative as relative2, sep as sep2 } from "path";
10216
10621
  var WORKSPACE_MARKER = "deepspace-workspace.json";
10217
10622
  function excludeWorktreeDir(appDir, dir) {
10218
10623
  try {
@@ -10233,7 +10638,7 @@ function worktreeMarkerPath(worktreeRoot) {
10233
10638
  const result = runGit(worktreeRoot, ["rev-parse", "--absolute-git-dir"], { allowFail: true });
10234
10639
  if (result.status !== 0) return null;
10235
10640
  const gitDir = result.stdout.toString("utf-8").trim();
10236
- return isAbsolute5(gitDir) ? join22(gitDir, WORKSPACE_MARKER) : null;
10641
+ return isAbsolute5(gitDir) ? join23(gitDir, WORKSPACE_MARKER) : null;
10237
10642
  } catch {
10238
10643
  return null;
10239
10644
  }
@@ -10269,7 +10674,7 @@ function isManagedWorkspaceWorktree(worktreeRoot, workspaceId) {
10269
10674
  const markerPath = worktreeMarkerPath(worktreeRoot);
10270
10675
  if (!markerPath) return false;
10271
10676
  try {
10272
- const marker = JSON.parse(readFileSync24(markerPath, "utf-8"));
10677
+ const marker = JSON.parse(readFileSync26(markerPath, "utf-8"));
10273
10678
  return marker.version === 1 && marker.workspaceId === workspaceId && marker.mode === "managed-linked";
10274
10679
  } catch {
10275
10680
  return false;
@@ -10278,11 +10683,11 @@ function isManagedWorkspaceWorktree(worktreeRoot, workspaceId) {
10278
10683
  function defaultWorkspaceRoot(appDir, workspaceId) {
10279
10684
  const primary = listWorktrees(appDir)[0];
10280
10685
  if (!primary) throw new Error("Git did not report a primary worktree");
10281
- return join22(primary.path, ".deepspace", "ws", workspaceId.slice(3).toLowerCase());
10686
+ return join23(primary.path, ".deepspace", "ws", workspaceId.slice(3).toLowerCase());
10282
10687
  }
10283
10688
  function appDirInWorktree(appDir, worktreeRoot) {
10284
10689
  const appRelative = relative2(realpathSync5(repoToplevel(appDir)), realpathSync5(appDir));
10285
- return appRelative ? join22(worktreeRoot, appRelative) : worktreeRoot;
10690
+ return appRelative ? join23(worktreeRoot, appRelative) : worktreeRoot;
10286
10691
  }
10287
10692
  function materializeWorkspaceWorktree(appDir, worktreeRoot, branch, startPoint, workspaceId, options = {}) {
10288
10693
  const createdOid = resolveCommit(appDir, startPoint);
@@ -10300,7 +10705,7 @@ function materializeWorkspaceWorktree(appDir, worktreeRoot, branch, startPoint,
10300
10705
  function inOwnLinkedWorktree(appDir, id) {
10301
10706
  if (workspaceIdFromBranch(currentBranch(appDir)) !== id) return false;
10302
10707
  try {
10303
- return statSync(join22(repoToplevel(appDir), ".git")).isFile();
10708
+ return statSync(join23(repoToplevel(appDir), ".git")).isFile();
10304
10709
  } catch {
10305
10710
  return false;
10306
10711
  }
@@ -10371,7 +10776,7 @@ function switchOffWsBranch(mainDir, trunkBranch) {
10371
10776
  if (resolveCommit(mainDir, `refs/heads/${trunkBranch}`)) {
10372
10777
  return runGit(mainDir, ["switch", "--quiet", trunkBranch], { allowFail: true }).status === 0;
10373
10778
  }
10374
- const remote = resolveCommit(mainDir, `refs/remotes/space/${trunkBranch}`);
10779
+ const remote = resolveCommit(mainDir, spaceTrackingRef(trunkBranch));
10375
10780
  if (remote) {
10376
10781
  return runGit(mainDir, ["switch", "--quiet", "-c", trunkBranch, remote], { allowFail: true }).status === 0;
10377
10782
  }
@@ -10684,7 +11089,7 @@ var newWorkspaceCommand = defineDeepspaceCommand({
10684
11089
 
10685
11090
  // src/cli/commands/workspace/attach.ts
10686
11091
  import * as p27 from "@clack/prompts";
10687
- import { existsSync as existsSync24, readdirSync as readdirSync7, rmSync as rmSync6 } from "fs";
11092
+ import { existsSync as existsSync25, readdirSync as readdirSync7, rmSync as rmSync6 } from "fs";
10688
11093
  import { isAbsolute as isAbsolute7, resolve as resolve18 } from "path";
10689
11094
  function finishedWorkspaceMessage(id, view) {
10690
11095
  const workspace = view.workspace;
@@ -10694,7 +11099,7 @@ function finishedWorkspaceMessage(id, view) {
10694
11099
  return `Workspace ${id} is ${workspace.status} \u2014 its ref is retained briefly (fetch ${workspace.ref} to inspect), but attach only works on active workspaces.`;
10695
11100
  }
10696
11101
  function cleanFailedFreshAttachDir(dir) {
10697
- if (!existsSync24(dir)) return [];
11102
+ if (!existsSync25(dir)) return [];
10698
11103
  let entries;
10699
11104
  try {
10700
11105
  entries = readdirSync7(dir);
@@ -10754,11 +11159,12 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
10754
11159
  });
10755
11160
  }
10756
11161
  ensureSpaceRemote(appDir, appId2);
11162
+ const localWorkspaceRef = spacePrivateRef(`workspaces/${id}`);
10757
11163
  runGitRemote(appDir, token2, [
10758
11164
  "fetch",
10759
11165
  "--quiet",
10760
11166
  SPACE_REMOTE,
10761
- `+${view2.workspace.ref}:${view2.workspace.ref}`
11167
+ `+${view2.workspace.ref}:${localWorkspaceRef}`
10762
11168
  ]);
10763
11169
  const tip2 = view2.tipOid ?? view2.workspace.baseOid;
10764
11170
  if (!resolveCommit(appDir, tip2)) {
@@ -10812,7 +11218,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
10812
11218
  process.cwd(),
10813
11219
  (typeof args.dir === "string" ? args.dir.trim() : "") || `ws-${id.slice(3).toLowerCase()}`
10814
11220
  );
10815
- if (existsSync24(dir)) {
11221
+ if (existsSync25(dir)) {
10816
11222
  spinner2?.stop("Target exists.");
10817
11223
  throw new Refusal(`${dir} already exists \u2014 pass a different directory.`, "dir_exists");
10818
11224
  }
@@ -10827,11 +11233,12 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
10827
11233
  try {
10828
11234
  runGit(process.cwd(), ["init", "--quiet", dir]);
10829
11235
  ensureSpaceRemote(dir, appId);
11236
+ const localWorkspaceRef = spacePrivateRef(`workspaces/${id}`);
10830
11237
  runGitRemote(dir, token, [
10831
11238
  "fetch",
10832
11239
  "--quiet",
10833
11240
  SPACE_REMOTE,
10834
- `+${view.workspace.ref}:${view.workspace.ref}`
11241
+ `+${view.workspace.ref}:${localWorkspaceRef}`
10835
11242
  ]);
10836
11243
  tip = view.tipOid ?? view.workspace.baseOid;
10837
11244
  if (!resolveCommit(dir, tip)) {
@@ -11129,13 +11536,13 @@ import * as p31 from "@clack/prompts";
11129
11536
 
11130
11537
  // src/cli/lib/validation.ts
11131
11538
  import { sync as spawnSync7 } from "cross-spawn";
11132
- import { readFileSync as readFileSync25 } from "fs";
11133
- import { join as join23 } from "path";
11539
+ import { readFileSync as readFileSync27 } from "fs";
11540
+ import { join as join24 } from "path";
11134
11541
  var MAX_SUMMARY_CHARS = 4e3;
11135
11542
  var MAX_OUTPUT_BUFFER = 64 * 1024 * 1024;
11136
11543
  function configuredCommand(appDir) {
11137
11544
  try {
11138
- const pkg2 = JSON.parse(readFileSync25(join23(appDir, "package.json"), "utf-8"));
11545
+ const pkg2 = JSON.parse(readFileSync27(join24(appDir, "package.json"), "utf-8"));
11139
11546
  if (pkg2.scripts?.validate) return `${detectPackageManager(appDir)} run --silent validate`;
11140
11547
  } catch {
11141
11548
  }
@@ -11266,7 +11673,7 @@ var landWorkspaceCommand = defineDeepspaceCommand({
11266
11673
  );
11267
11674
  }
11268
11675
  const intoRef = `refs/heads/${intoBranch}`;
11269
- const remoteRef = `refs/remotes/space/${intoBranch}`;
11676
+ const remoteRef = spaceTrackingRef(intoBranch);
11270
11677
  runGitRemote(appDir, token, ["fetch", "--quiet", SPACE_REMOTE, `+${intoRef}:${remoteRef}`], {
11271
11678
  allowFail: true
11272
11679
  });
@@ -11811,7 +12218,7 @@ var git_credential_default = defineCommand13({
11811
12218
 
11812
12219
  // src/cli/commands/source.ts
11813
12220
  import * as p34 from "@clack/prompts";
11814
- var TRANSFER_PREFIX = "refs/deepspace/source-transfer";
12221
+ var TRANSFER_PREFIX = spacePrivateRef("source-transfer");
11815
12222
  var source_default = defineDeepspaceCommand({
11816
12223
  meta: {
11817
12224
  name: "source",
@@ -12629,8 +13036,8 @@ function reportMigration(migration, json) {
12629
13036
 
12630
13037
  // src/cli/cli.ts
12631
13038
  var __dirname = dirname10(fileURLToPath3(import.meta.url));
12632
- var pkg = JSON.parse(readFileSync26(join24(__dirname, "..", "package.json"), "utf-8"));
12633
- if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync25("/etc/ssl/certs/ca-certificates.crt")) {
13039
+ var pkg = JSON.parse(readFileSync28(join25(__dirname, "..", "package.json"), "utf-8"));
13040
+ if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync26("/etc/ssl/certs/ca-certificates.crt")) {
12634
13041
  process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
12635
13042
  }
12636
13043
  var create2 = defineCommand14({