deepspace 0.29.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  renderUsage
9
9
  } from "citty";
10
10
  import { sync as spawnSync9 } from "cross-spawn";
11
- import { readFileSync as readFileSync27, existsSync as existsSync31 } from "fs";
11
+ import { readFileSync as readFileSync28, existsSync as existsSync31 } from "fs";
12
12
  import { dirname as dirname13, join as join29 } from "path";
13
13
  import { fileURLToPath as fileURLToPath3 } from "url";
14
14
 
@@ -301,6 +301,49 @@ function apiFetchReadWithRetry(baseUrl, token, path) {
301
301
  function isTransientFailure(error) {
302
302
  return error instanceof ApiError && (error.status === 0 || error.status === 408 || error.status === 429 || error.status >= 500);
303
303
  }
304
+ async function fetchIntegrationCatalog(baseUrl, opts = {}) {
305
+ const path = opts.summary ? "/api/integrations?summary=1" : "/api/integrations";
306
+ let res;
307
+ try {
308
+ res = await fetch(`${baseUrl}${path}`, { signal: AbortSignal.timeout(15e3) });
309
+ } catch (err) {
310
+ throw new ApiError(
311
+ `Could not fetch the integration catalog: ${err instanceof Error ? err.message : String(err)}`,
312
+ 0,
313
+ "catalog_unavailable",
314
+ path
315
+ );
316
+ }
317
+ if (!res.ok) {
318
+ throw new ApiError(
319
+ `Failed to fetch integration catalog (${res.status})`,
320
+ res.status,
321
+ "catalog_unavailable",
322
+ path
323
+ );
324
+ }
325
+ let value;
326
+ try {
327
+ value = await res.json();
328
+ } catch {
329
+ throw new ApiError(
330
+ "The integration catalog returned invalid JSON.",
331
+ res.status,
332
+ "invalid_catalog",
333
+ path
334
+ );
335
+ }
336
+ const integrations = value?.integrations;
337
+ if (!integrations || typeof integrations !== "object" || Array.isArray(integrations)) {
338
+ throw new ApiError(
339
+ "The integration catalog returned an invalid shape.",
340
+ res.status,
341
+ "invalid_catalog",
342
+ path
343
+ );
344
+ }
345
+ return value;
346
+ }
304
347
 
305
348
  // src/cli/lib/cli-errors.ts
306
349
  var InputError = class extends Error {
@@ -664,9 +707,13 @@ function appDomainForEnv(env) {
664
707
  return env === "production" ? "app.space" : env === "staging" ? "spacestest.com" : null;
665
708
  }
666
709
  var PLATFORM_URLS = DEEPSPACE_ENV === "production" ? PROD_URLS : DEEPSPACE_ENV === "staging" ? STAGING_URLS : INVALID_URLS;
710
+ var PLANE_URLS = {
711
+ production: PROD_URLS,
712
+ staging: STAGING_URLS
713
+ };
667
714
  var PLANE_AUTH_URLS = {
668
- production: PROD_URLS.auth,
669
- staging: STAGING_URLS.auth
715
+ production: PLANE_URLS.production.auth,
716
+ staging: PLANE_URLS.staging.auth
670
717
  };
671
718
  function effectivePlatformUrls(env = process.env) {
672
719
  return {
@@ -807,6 +854,10 @@ async function ensureToken(options = {}) {
807
854
  throw notAuthenticated("The stored session is no longer valid (expired or unreadable)");
808
855
  return token;
809
856
  }
857
+ function loginAction() {
858
+ const canRunBare = Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY) || Boolean(process.env.DEEPSPACE_EMAIL) && Boolean(process.env.DEEPSPACE_PASSWORD);
859
+ return canRunBare ? cliAction("deepspace", "auth", "login") : void 0;
860
+ }
810
861
  function notAuthenticated(state) {
811
862
  const selected = process.env.DEEPSPACE_AUTH_URL !== void 0 ? `the auth service at ${AUTH_URL} (selected by DEEPSPACE_AUTH_URL)` : DEEPSPACE_ENV === "production" && process.env.DEEPSPACE_ENV === void 0 ? "production" : `${DEEPSPACE_ENV} (selected by DEEPSPACE_ENV=${process.env.DEEPSPACE_ENV})`;
812
863
  const elsewhere = Object.entries(PLANE_AUTH_URLS).filter(([, url]) => url !== AUTH_URL && existsSync(credentialPaths(url).sessionPath)).map(([plane]) => plane);
@@ -818,7 +869,7 @@ function notAuthenticated(state) {
818
869
  return new Refusal(
819
870
  `${state} on ${selected}.${other} Run \`deepspace auth login\` (headless: \`deepspace auth login --email you@example.com --password-stdin\`, or set DEEPSPACE_EMAIL and DEEPSPACE_PASSWORD).`,
820
871
  "not_authenticated",
821
- { action: cliAction("deepspace", "auth", "login") }
872
+ { action: loginAction() }
822
873
  );
823
874
  }
824
875
  function isTokenValid(jwt, minimumValidityMs) {
@@ -959,7 +1010,7 @@ var login_default = defineDeepspaceCommand({
959
1010
  "Login timed out. Run `deepspace auth login` to try again.",
960
1011
  "login_timeout",
961
1012
  {
962
- action: cliAction("deepspace", "auth", "login")
1013
+ action: loginAction()
963
1014
  }
964
1015
  );
965
1016
  }
@@ -1031,14 +1082,14 @@ async function createCliLoginSession(codeChallenge) {
1031
1082
  throw new Refusal(
1032
1083
  `Could not reach the auth service to create a login session: ${error instanceof Error ? error.message : String(error)}`,
1033
1084
  "login_session_unreachable",
1034
- { action: cliAction("deepspace", "auth", "login") }
1085
+ { action: loginAction() }
1035
1086
  );
1036
1087
  }
1037
1088
  if (!sessionRes.ok) {
1038
1089
  throw new Refusal(
1039
1090
  `Could not create login session (${sessionRes.status})`,
1040
1091
  "login_session_failed",
1041
- { action: cliAction("deepspace", "auth", "login") }
1092
+ { action: loginAction() }
1042
1093
  );
1043
1094
  }
1044
1095
  const sessionData = await sessionRes.json().catch(() => ({}));
@@ -1046,7 +1097,7 @@ async function createCliLoginSession(codeChallenge) {
1046
1097
  throw new Refusal(
1047
1098
  "Could not create login session \u2014 the auth server returned no session id or login url.",
1048
1099
  "login_session_failed",
1049
- { action: cliAction("deepspace", "auth", "login") }
1100
+ { action: loginAction() }
1050
1101
  );
1051
1102
  }
1052
1103
  return { sessionId: sessionData.sessionId, loginUrl: sessionData.loginUrl };
@@ -2724,7 +2775,7 @@ async function fetchAppIdentityToken(deployUrl, callerJwt, appId) {
2724
2775
  // wrangler env, and a bare `app init` under --env would target the
2725
2776
  // TOP-LEVEL [vars] slot. dev/test/deploy attach the env-aware
2726
2777
  // remedy as a structured action; surfaces without an action channel
2727
- // (secrets pull among them) fall back to the
2778
+ // fall back to the
2728
2779
  // app_not_found hint (check the id / `deepspace app list`).
2729
2780
  `App ${appId} is not registered on this platform \u2014 DEEPSPACE_APP_ID may belong to a different environment (a staging id does not exist on prod, and vice versa), or the app was never initialized here.`,
2730
2781
  404,
@@ -2758,7 +2809,7 @@ async function fetchAppIdentityToken(deployUrl, callerJwt, appId) {
2758
2809
  // src/cli/lib/dev-vars.ts
2759
2810
  var HEADER = [
2760
2811
  "# Generated by deepspace \u2014 do not edit; this file is rewritten on every",
2761
- "# dev, test, deploy, and secrets pull run. App values live in the secret store:",
2812
+ "# dev, test, and deploy run. App values live in the secret store:",
2762
2813
  "# npx deepspace secrets set KEY=value"
2763
2814
  ].join("\n");
2764
2815
  function requireAppIdFor(appDir, wranglerEnv) {
@@ -3006,6 +3057,15 @@ function canonicalPath2(path) {
3006
3057
  return resolve7(path);
3007
3058
  }
3008
3059
  }
3060
+ function processAlive(pid) {
3061
+ if (!Number.isSafeInteger(pid) || pid <= 0) return false;
3062
+ try {
3063
+ process.kill(pid, 0);
3064
+ return true;
3065
+ } catch (err) {
3066
+ return err.code === "EPERM";
3067
+ }
3068
+ }
3009
3069
 
3010
3070
  // src/cli/lib/preflight.ts
3011
3071
  import { execFileSync } from "child_process";
@@ -3077,6 +3137,8 @@ function removeMacosJunk(dir) {
3077
3137
 
3078
3138
  // src/server/rooms/binding-manifest.ts
3079
3139
  var MAX_ENV_VAR_BYTES = 5 * 1024;
3140
+ var MAX_BINDING_NAME_LENGTH = 256;
3141
+ var SECRET_NAME_RE = new RegExp(`^[A-Za-z_][A-Za-z0-9_]{0,${MAX_BINDING_NAME_LENGTH - 1}}$`);
3080
3142
  var AUTO_PROVISION_SENTINEL = "auto";
3081
3143
  var VECTORIZE_METRICS = /* @__PURE__ */ new Set(["cosine", "euclidean", "dot-product"]);
3082
3144
  var ALLOWED_BINDING_TYPES = /* @__PURE__ */ new Set([
@@ -3137,6 +3199,12 @@ function validateBindingManifest(manifest) {
3137
3199
  errors.push({ reason: `Binding of type "${type}" missing 'name' (string)` });
3138
3200
  continue;
3139
3201
  }
3202
+ if (name.length > MAX_BINDING_NAME_LENGTH) {
3203
+ errors.push({
3204
+ reason: `Binding name "${name.slice(0, 64)}\u2026" is ${name.length} characters; the limit is ${MAX_BINDING_NAME_LENGTH}`
3205
+ });
3206
+ continue;
3207
+ }
3140
3208
  if (RESERVED_BINDING_NAMES.has(name)) {
3141
3209
  errors.push({ reason: `Binding name "${name}" is reserved by the SDK` });
3142
3210
  continue;
@@ -3378,7 +3446,6 @@ var RESERVED_SECRET_NAMES = /* @__PURE__ */ new Set([
3378
3446
  "API_WORKER_URL",
3379
3447
  "PLATFORM_WORKER_URL"
3380
3448
  ]);
3381
- var SECRET_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
3382
3449
  var CONFIG_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
3383
3450
  var MAX_SECRET_VALUE_BYTES = 32 * 1024;
3384
3451
  function validateConfigName(configName) {
@@ -3391,6 +3458,12 @@ function validateConfigName(configName) {
3391
3458
  return configName;
3392
3459
  }
3393
3460
  function validateSecretName(name) {
3461
+ if (name.length > MAX_BINDING_NAME_LENGTH) {
3462
+ throw new InputError(
3463
+ `Secret name "${name.slice(0, 64)}\u2026" is ${name.length} characters; the limit is ${MAX_BINDING_NAME_LENGTH}.`,
3464
+ "invalid_secret_name"
3465
+ );
3466
+ }
3394
3467
  if (!SECRET_NAME_RE.test(name)) {
3395
3468
  throw new InputError(
3396
3469
  `Invalid secret name "${name}". Use letters, numbers, and underscores; start with a letter or underscore.`,
@@ -3946,7 +4019,7 @@ var dev_default = defineDeepspaceCommand({
3946
4019
  token = await ensureToken();
3947
4020
  } catch (err) {
3948
4021
  throw new Refusal(err instanceof Error ? err.message : String(err), "not_authenticated", {
3949
- action: cliAction("deepspace", "auth", "login")
4022
+ action: loginAction()
3950
4023
  });
3951
4024
  }
3952
4025
  let payload;
@@ -3956,7 +4029,7 @@ var dev_default = defineDeepspaceCommand({
3956
4029
  throw new Refusal(
3957
4030
  "Malformed session token. Run `npx deepspace auth login`.",
3958
4031
  "not_authenticated",
3959
- { action: cliAction("deepspace", "auth", "login") }
4032
+ { action: loginAction() }
3960
4033
  );
3961
4034
  }
3962
4035
  say(`Logged in as ${payload.name ?? payload.email}`);
@@ -4333,7 +4406,7 @@ function isAlive(pid) {
4333
4406
  }
4334
4407
 
4335
4408
  // src/cli/commands/test.ts
4336
- import { existsSync as existsSync16, readdirSync as readdirSync5 } from "fs";
4409
+ import { existsSync as existsSync16, readFileSync as readFileSync13, readdirSync as readdirSync5, rmSync as rmSync6 } from "fs";
4337
4410
  import { join as join14, relative as relative2, resolve as resolve10, sep as sep2 } from "path";
4338
4411
  import { sync as spawnSync5 } from "cross-spawn";
4339
4412
 
@@ -4591,12 +4664,39 @@ function playwrightTestArgs(testFiles, flags = {}) {
4591
4664
  "tests/playwright.config.ts",
4592
4665
  "--output",
4593
4666
  PLAYWRIGHT_OUTPUT_DIR,
4667
+ // The default list reporter stays for the live stream; the json reporter
4668
+ // is what lets the CLI surface runtime skips and their authored reasons,
4669
+ // which the list reporter swallows (AX C3, docs/audits/2026-09-01).
4670
+ "--reporter=list,json",
4594
4671
  ...flags.grep ? ["--grep", flags.grep] : [],
4595
4672
  ...flags.project ? ["--project", flags.project] : [],
4596
4673
  ...flags.headed ? ["--headed"] : [],
4597
4674
  ...testFiles
4598
4675
  ];
4599
4676
  }
4677
+ function skippedTestsFromPlaywrightJson(raw) {
4678
+ const skipped = [];
4679
+ const walk = (suite) => {
4680
+ for (const spec of suite.specs ?? []) {
4681
+ for (const test of spec.tests ?? []) {
4682
+ if (test.status !== "skipped") continue;
4683
+ const note3 = test.annotations?.find((entry) => entry.type === "skip")?.description;
4684
+ skipped.push({
4685
+ spec: typeof spec.file === "string" ? spec.file : "",
4686
+ title: typeof spec.title === "string" ? spec.title : "",
4687
+ reason: typeof note3 === "string" && note3 ? note3 : null
4688
+ });
4689
+ }
4690
+ }
4691
+ for (const child of suite.suites ?? []) walk(child);
4692
+ };
4693
+ try {
4694
+ walk(JSON.parse(raw));
4695
+ } catch {
4696
+ return [];
4697
+ }
4698
+ return skipped;
4699
+ }
4600
4700
  var test_default = defineDeepspaceCommand({
4601
4701
  meta: {
4602
4702
  name: "test",
@@ -4670,7 +4770,7 @@ var test_default = defineDeepspaceCommand({
4670
4770
  throw new Refusal(
4671
4771
  err instanceof Error ? err.message : "Not logged in. Run `deepspace auth login` first.",
4672
4772
  "not_authenticated",
4673
- { action: cliAction("deepspace", "auth", "login") }
4773
+ { action: loginAction() }
4674
4774
  );
4675
4775
  }
4676
4776
  let generatedSecretsCache;
@@ -4706,15 +4806,21 @@ var test_default = defineDeepspaceCommand({
4706
4806
  }
4707
4807
  let exitCode = 0;
4708
4808
  let skippedSpecs = [];
4809
+ let skippedTests = [];
4810
+ const playwright = (testFiles) => {
4811
+ const result = runPlaywright(appDir, testFiles, port, wranglerEnv, forwarded);
4812
+ skippedTests = result.skippedTests;
4813
+ return result.exitCode;
4814
+ };
4709
4815
  switch (suite) {
4710
4816
  case "smoke":
4711
- exitCode = runPlaywright(appDir, ["tests/smoke.spec.ts"], port, wranglerEnv, forwarded);
4817
+ exitCode = playwright(["tests/smoke.spec.ts"]);
4712
4818
  break;
4713
4819
  case "api":
4714
- exitCode = runPlaywright(appDir, ["tests/api.spec.ts"], port, wranglerEnv, forwarded);
4820
+ exitCode = playwright(["tests/api.spec.ts"]);
4715
4821
  break;
4716
4822
  case "e2e":
4717
- exitCode = runPlaywright(appDir, [], port, wranglerEnv, forwarded);
4823
+ exitCode = playwright([]);
4718
4824
  break;
4719
4825
  case "unit":
4720
4826
  if (forwarded.grep || forwarded.project || forwarded.headed) {
@@ -4728,12 +4834,12 @@ var test_default = defineDeepspaceCommand({
4728
4834
  exitCode = runVitest(appDir, wranglerEnv);
4729
4835
  if (exitCode === 0) {
4730
4836
  await ensurePortFree(port, "0.0.0.0");
4731
- exitCode = runPlaywright(appDir, [], port, wranglerEnv, forwarded);
4837
+ exitCode = playwright([]);
4732
4838
  }
4733
4839
  break;
4734
4840
  case "default":
4735
4841
  skippedSpecs = specsSkippedByDefaultSuite(appDir);
4736
- exitCode = runPlaywright(appDir, [...DEFAULT_SUITE_SPECS], port, wranglerEnv, forwarded);
4842
+ exitCode = playwright([...DEFAULT_SUITE_SPECS]);
4737
4843
  if (skippedSpecs.length > 0) {
4738
4844
  say(
4739
4845
  `skipped ${skippedSpecs.length} spec file(s) not in smoke+api (${skippedSpecs.join(", ")}) \u2014 run 'deepspace test run all'`
@@ -4749,7 +4855,7 @@ var test_default = defineDeepspaceCommand({
4749
4855
  "spec_not_found"
4750
4856
  );
4751
4857
  }
4752
- exitCode = runPlaywright(appDir, [suite], port, wranglerEnv, forwarded);
4858
+ exitCode = playwright([suite]);
4753
4859
  } else {
4754
4860
  throw new Refusal(
4755
4861
  `Unknown test suite: ${suite}
@@ -4758,12 +4864,20 @@ Available: smoke, api, e2e, unit, all \u2014 or a path to one spec file (tests/<
4758
4864
  );
4759
4865
  }
4760
4866
  }
4867
+ if (skippedTests.length > 0) {
4868
+ const reasons = [...new Set(skippedTests.map((test) => test.reason).filter(Boolean))];
4869
+ say(
4870
+ `${skippedTests.length} test(s) were skipped at runtime` + (reasons.length > 0 ? ` \u2014 ${reasons.join(" | ")}` : "")
4871
+ );
4872
+ }
4761
4873
  if (exitCode !== 0) {
4762
4874
  throw new Refusal(`Test suite '${suite}' failed (exit ${exitCode}).`, "tests_failed", {
4763
- extra: { suite, port, exitCode, skippedSpecs }
4875
+ extra: { suite, port, exitCode, skippedSpecs, skippedTests }
4764
4876
  });
4765
4877
  }
4766
- return { data: { suite, port, appDir, wranglerEnv: wranglerEnv ?? null, skippedSpecs } };
4878
+ return {
4879
+ data: { suite, port, appDir, wranglerEnv: wranglerEnv ?? null, skippedSpecs, skippedTests }
4880
+ };
4767
4881
  }
4768
4882
  });
4769
4883
  function runSuite(appDir, argv, wranglerEnv, extraEnv = {}) {
@@ -4788,10 +4902,19 @@ function runSuite(appDir, argv, wranglerEnv, extraEnv = {}) {
4788
4902
  }
4789
4903
  }
4790
4904
  function runPlaywright(appDir, testFiles, port, wranglerEnv, flags = {}) {
4791
- return runSuite(appDir, playwrightTestArgs(testFiles, flags), wranglerEnv, {
4905
+ const resultsPath = join14(appDir, ".deepspace", "playwright-results.json");
4906
+ rmSync6(resultsPath, { force: true });
4907
+ const exitCode = runSuite(appDir, playwrightTestArgs(testFiles, flags), wranglerEnv, {
4792
4908
  DEEPSPACE_PORT: String(port),
4793
- PLAYWRIGHT_HTML_OUTPUT_DIR: ".deepspace/playwright-report"
4909
+ PLAYWRIGHT_HTML_OUTPUT_DIR: ".deepspace/playwright-report",
4910
+ PLAYWRIGHT_JSON_OUTPUT_FILE: resultsPath
4794
4911
  });
4912
+ let raw = "";
4913
+ try {
4914
+ raw = readFileSync13(resultsPath, "utf-8");
4915
+ } catch {
4916
+ }
4917
+ return { exitCode, skippedTests: skippedTestsFromPlaywrightJson(raw) };
4795
4918
  }
4796
4919
  function runVitest(appDir, wranglerEnv) {
4797
4920
  return runSuite(appDir, ["vitest", "run", "--passWithNoTests"], wranglerEnv);
@@ -5405,14 +5528,30 @@ var CONSECUTIVE_MATCHES = 10;
5405
5528
  var MATCH_INTERVAL_MS = 250;
5406
5529
  async function waitForLiveRelease(url, releaseStamp, timeoutMs) {
5407
5530
  if (!releaseStamp) return "unverifiable";
5531
+ return agreeingProbes(
5532
+ timeoutMs,
5533
+ async (attempt) => servesRelease(bustedUrl(RELEASE_STAMP_PATH, url, attempt), releaseStamp)
5534
+ );
5535
+ }
5536
+ async function waitForHostReleased(url, timeoutMs) {
5537
+ return agreeingProbes(timeoutMs, async (attempt) => {
5538
+ const result = await getOnFreshConnection(bustedUrl("/", url, attempt));
5539
+ return result !== null && result.statusCode === 404;
5540
+ });
5541
+ }
5542
+ async function waitForDataPlane(url, appId, timeoutMs) {
5543
+ return agreeingProbes(timeoutMs, async (attempt) => {
5544
+ const result = await getOnFreshConnection(bustedUrl(`/ws/app:${appId}`, url, attempt));
5545
+ return result !== null && result.statusCode < 500;
5546
+ });
5547
+ }
5548
+ async function agreeingProbes(timeoutMs, probe) {
5408
5549
  const deadline = Date.now() + timeoutMs;
5409
5550
  let attempt = 0;
5410
5551
  let streak = 0;
5411
5552
  while (Date.now() < deadline) {
5412
5553
  attempt++;
5413
- const probe = new URL(RELEASE_STAMP_PATH, url);
5414
- probe.searchParams.set("probe", `${Date.now()}-${attempt}`);
5415
- if (await servesRelease(probe, releaseStamp)) {
5554
+ if (await probe(attempt)) {
5416
5555
  streak++;
5417
5556
  if (streak >= CONSECUTIVE_MATCHES) return "confirmed";
5418
5557
  await sleep2(MATCH_INTERVAL_MS);
@@ -5423,11 +5562,16 @@ async function waitForLiveRelease(url, releaseStamp, timeoutMs) {
5423
5562
  }
5424
5563
  return "unconfirmed";
5425
5564
  }
5565
+ function bustedUrl(path, base, attempt) {
5566
+ const probe = new URL(path, base);
5567
+ probe.searchParams.set("probe", `${Date.now()}-${attempt}`);
5568
+ return probe;
5569
+ }
5426
5570
  async function servesRelease(probe, releaseStamp) {
5427
- const body = await getOnFreshConnection(probe);
5428
- if (body === null) return false;
5571
+ const result = await getOnFreshConnection(probe);
5572
+ if (result === null || result.statusCode !== 200) return false;
5429
5573
  try {
5430
- return JSON.parse(body).release === releaseStamp;
5574
+ return JSON.parse(result.body).release === releaseStamp;
5431
5575
  } catch {
5432
5576
  return false;
5433
5577
  }
@@ -5439,17 +5583,13 @@ function getOnFreshConnection(url, timeoutMs = 5e3) {
5439
5583
  url,
5440
5584
  { method: "GET", agent: false, timeout: timeoutMs, headers: { "cache-control": "no-cache" } },
5441
5585
  (response) => {
5442
- if (response.statusCode !== 200) {
5443
- response.resume();
5444
- resolve23(null);
5445
- return;
5446
- }
5586
+ const statusCode = response.statusCode ?? 0;
5447
5587
  let body = "";
5448
5588
  response.setEncoding("utf8");
5449
5589
  response.on("data", (chunk) => {
5450
5590
  if (body.length < 4096) body += chunk;
5451
5591
  });
5452
- response.on("end", () => resolve23(body));
5592
+ response.on("end", () => resolve23({ statusCode, body }));
5453
5593
  response.on("error", () => resolve23(null));
5454
5594
  }
5455
5595
  );
@@ -5533,7 +5673,7 @@ function describeFilesFailure(status3, body) {
5533
5673
  import * as p6 from "@clack/prompts";
5534
5674
  import { execSync as execSync2 } from "child_process";
5535
5675
  import { createHash as createHash2 } from "crypto";
5536
- import { existsSync as existsSync20, readFileSync as readFileSync15, readdirSync as readdirSync7 } from "fs";
5676
+ import { existsSync as existsSync20, readFileSync as readFileSync16, readdirSync as readdirSync7 } from "fs";
5537
5677
  import { dirname as dirname7, isAbsolute as isAbsolute4, join as join18, relative as relative3, resolve as resolve14, sep as sep3 } from "path";
5538
5678
 
5539
5679
  // src/build/app-id.ts
@@ -5564,7 +5704,7 @@ function removeBuildDevVars(workerDir) {
5564
5704
  }
5565
5705
 
5566
5706
  // src/documentation/deploy.ts
5567
- import { existsSync as existsSync18, readFileSync as readFileSync13 } from "fs";
5707
+ import { existsSync as existsSync18, readFileSync as readFileSync14 } from "fs";
5568
5708
  import { join as join16 } from "path";
5569
5709
  function readDocumentationDeployManifest(appDir, clientDir) {
5570
5710
  if (!existsSync18(join16(appDir, "documentation.json"))) return void 0;
@@ -5574,11 +5714,11 @@ function readDocumentationDeployManifest(appDir, clientDir) {
5574
5714
  "documentation.json exists but the build produced no documentation manifest. Ensure vite.config.ts includes deepSpaceDocumentation() from deepspace/documentation."
5575
5715
  );
5576
5716
  }
5577
- return JSON.parse(readFileSync13(manifestPath, "utf8"));
5717
+ return JSON.parse(readFileSync14(manifestPath, "utf8"));
5578
5718
  }
5579
5719
 
5580
5720
  // src/cli/commands/update/app-migrations.ts
5581
- import { existsSync as existsSync19, readFileSync as readFileSync14 } from "fs";
5721
+ import { existsSync as existsSync19, readFileSync as readFileSync15 } from "fs";
5582
5722
  import { join as join17 } from "path";
5583
5723
 
5584
5724
  // src/shared/protocol/app-migrations.ts
@@ -5657,7 +5797,7 @@ function readAppliedAppMigrations(appDir) {
5657
5797
  if (!existsSync19(path)) return [];
5658
5798
  let parsed;
5659
5799
  try {
5660
- parsed = JSON.parse(readFileSync14(path, "utf8"));
5800
+ parsed = JSON.parse(readFileSync15(path, "utf8"));
5661
5801
  } catch {
5662
5802
  throw new Refusal(
5663
5803
  `${APP_MIGRATIONS_MANIFEST} must contain valid JSON`,
@@ -5723,12 +5863,12 @@ async function buildDeployBundle(options) {
5723
5863
  "build_output_missing"
5724
5864
  );
5725
5865
  }
5726
- const deployConfig = JSON.parse(readFileSync15(deployConfigPath, "utf-8"));
5866
+ const deployConfig = JSON.parse(readFileSync16(deployConfigPath, "utf-8"));
5727
5867
  const outputWranglerPath = resolve14(dirname7(deployConfigPath), deployConfig.configPath);
5728
5868
  if (!existsSync20(outputWranglerPath)) {
5729
5869
  output.die(`Output wrangler.json not found at ${outputWranglerPath}`, "build_output_missing");
5730
5870
  }
5731
- const outputConfig = JSON.parse(readFileSync15(outputWranglerPath, "utf-8"));
5871
+ const outputConfig = JSON.parse(readFileSync16(outputWranglerPath, "utf-8"));
5732
5872
  if (outputConfig.name && outputConfig.name !== appName) {
5733
5873
  const envHint = envName ? ` (--env ${envName})` : "";
5734
5874
  output.die(
@@ -5829,7 +5969,7 @@ function collectWorkerBundle(workerDir, mainPath, clientDir) {
5829
5969
  if (entry.isDirectory()) {
5830
5970
  walk(fullPath);
5831
5971
  } else if (entry.isFile() && WORKER_ES_MODULE.test(entry.name)) {
5832
- modules.push({ name: moduleName(root, fullPath), content: readFileSync15(fullPath, "utf-8") });
5972
+ modules.push({ name: moduleName(root, fullPath), content: readFileSync16(fullPath, "utf-8") });
5833
5973
  }
5834
5974
  }
5835
5975
  };
@@ -5883,7 +6023,7 @@ function clientAppIdRefusal(assets, appId, envName) {
5883
6023
  for (const asset of assets) {
5884
6024
  if (!CLIENT_CODE_ASSET.test(asset.path)) continue;
5885
6025
  if (DOCUMENTATION_OUTPUT.test(asset.path)) continue;
5886
- const source = readFileSync15(asset.sourcePath, "utf-8");
6026
+ const source = readFileSync16(asset.sourcePath, "utf-8");
5887
6027
  if (unsubstituted === void 0 && source.includes(APP_ID_DEFINE)) unsubstituted = asset.path;
5888
6028
  for (const found of source.matchAll(APP_ID_LITERAL)) {
5889
6029
  if (found[0] !== appId && !foreign.has(found[0])) foreign.set(found[0], asset.path);
@@ -5917,7 +6057,7 @@ function collectAssets(dir) {
5917
6057
  if (entry.isDirectory()) {
5918
6058
  walk(fullPath, relativePath);
5919
6059
  } else {
5920
- const bytes = readFileSync15(fullPath);
6060
+ const bytes = readFileSync16(fullPath);
5921
6061
  assets.push({
5922
6062
  path: `/${relativePath}`,
5923
6063
  hash: createHash2("sha256").update(bytes).digest("hex"),
@@ -5938,7 +6078,7 @@ function readDeployAssetConfig(clientDir) {
5938
6078
  ]) {
5939
6079
  const filePath = join18(clientDir, file);
5940
6080
  if (!existsSync20(filePath)) continue;
5941
- const content = readFileSync15(filePath, "utf8");
6081
+ const content = readFileSync16(filePath, "utf8");
5942
6082
  if (content.trim()) config[key] = content;
5943
6083
  }
5944
6084
  return config;
@@ -6263,7 +6403,7 @@ function createDeployOutput(json) {
6263
6403
 
6264
6404
  // src/cli/commands/deploy/request.ts
6265
6405
  import * as p11 from "@clack/prompts";
6266
- import { readFileSync as readFileSync16 } from "fs";
6406
+ import { readFileSync as readFileSync17 } from "fs";
6267
6407
 
6268
6408
  // src/cli/commands/deploy/repository.ts
6269
6409
  import * as p9 from "@clack/prompts";
@@ -7434,7 +7574,7 @@ async function uploadDeployAssetsWithAuth(options, auth2) {
7434
7574
  // against the plan before it touches storage, and a stream body
7435
7575
  // would be sent chunked with no length at all; a retry also needs a
7436
7576
  // body it can send again.
7437
- body: readFileSync16(asset.sourcePath)
7577
+ body: readFileSync17(asset.sourcePath)
7438
7578
  }),
7439
7579
  { attempts: ASSET_UPLOAD_ATTEMPTS, timeoutMs: ASSET_UPLOAD_ATTEMPT_TIMEOUT_MS }
7440
7580
  )
@@ -7479,7 +7619,10 @@ function isDeployServiceResourceLimit(status3, error) {
7479
7619
  }
7480
7620
  function formatDeployWorkerError(status3, error, code) {
7481
7621
  const detail = error ?? `Deployment error (${status3})`;
7482
- if (code === "cli_outdated" || code === "secrets_read_failed" || code === "release_reconciliation_pending") {
7622
+ if (code === "cli_outdated" || code === "secrets_read_failed" || code === "release_reconciliation_pending" || // Cloudflare deterministically rejected this upload; the sentence already
7623
+ // names the input to fix, and incident boilerplate would prescribe an
7624
+ // infinite wait-and-retry loop (AX S1, docs/audits/2026-09-01).
7625
+ code === "worker_upload_rejected") {
7483
7626
  return detail;
7484
7627
  }
7485
7628
  if (isDeployServiceResourceLimit(status3, error)) {
@@ -7506,8 +7649,9 @@ function errorMessage5(error) {
7506
7649
 
7507
7650
  // src/cli/commands/deploy/lock.ts
7508
7651
  import { randomUUID as randomUUID2 } from "crypto";
7509
- import { mkdirSync as mkdirSync8, readFileSync as readFileSync17, rmSync as rmSync6, writeFileSync as writeFileSync6 } from "fs";
7652
+ import { mkdirSync as mkdirSync8, readFileSync as readFileSync18, rmSync as rmSync7, statSync as statSync3, writeFileSync as writeFileSync6 } from "fs";
7510
7653
  import { dirname as dirname8, join as join20 } from "path";
7654
+ var DEPLOY_LOCK_MAX_AGE_MS = 10 * 6e4;
7511
7655
  function deployLockPath(appDir) {
7512
7656
  return join20(appDir, ".deepspace", "deploy.lock");
7513
7657
  }
@@ -7519,29 +7663,58 @@ function acquireDeployLock(appDir) {
7519
7663
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
7520
7664
  token: randomUUID2()
7521
7665
  };
7522
- try {
7523
- writeFileSync6(path, JSON.stringify(record), { flag: "wx" });
7524
- } catch (err) {
7525
- if (err.code !== "EEXIST") throw err;
7526
- const holder = readLock(path);
7527
- throw new Refusal(
7528
- (holder ? `Another deploy of this directory holds the deploy lock (pid ${holder.pid}, started ${holder.startedAt})` : `A deploy lock is present at ${path} but unreadable, so this run cannot tell whether a deploy is live`) + ` \u2014 two deploys of one checkout race on its build output. Judge liveness by the START TIME, not the pid: no deploy runs longer than a few minutes, so a lock older than ~10 minutes is a dead deploy (in containers the pid can read as alive \u2014 a reaped child leaves a zombie \`ps\` still reports). Then remove ${path} and retry.`,
7529
- "deploy_in_progress",
7530
- {
7531
- extra: {
7532
- lockPath: path,
7533
- ...holder ? { holder: { pid: holder.pid, startedAt: holder.startedAt } } : {}
7666
+ const take = () => {
7667
+ try {
7668
+ writeFileSync6(path, JSON.stringify(record), { flag: "wx" });
7669
+ return true;
7670
+ } catch (err) {
7671
+ if (err.code !== "EEXIST") throw err;
7672
+ return false;
7673
+ }
7674
+ };
7675
+ if (!take()) {
7676
+ if (lockIsStale(path)) rmSync7(path, { force: true });
7677
+ if (!take()) {
7678
+ const holder = readLock(path);
7679
+ throw new Refusal(
7680
+ (holder ? `Another deploy of this directory holds the deploy lock (pid ${holder.pid}, started ${holder.startedAt})` : `A fresh deploy lock is present at ${path} but unreadable, so this run cannot tell whether a deploy is live`) + ` \u2014 two deploys of one checkout race on its build output. Dead and stale locks are reclaimed automatically, so this holder still looks live; wait for it to finish, or remove ${path} and retry only if you are certain no deploy is running.`,
7681
+ "deploy_in_progress",
7682
+ {
7683
+ extra: {
7684
+ lockPath: path,
7685
+ ...holder ? { holder: { pid: holder.pid, startedAt: holder.startedAt } } : {}
7686
+ }
7534
7687
  }
7535
- }
7536
- );
7688
+ );
7689
+ }
7537
7690
  }
7538
- return () => {
7539
- if (readLock(path)?.token === record.token) rmSync6(path, { force: true });
7691
+ const onSignal = (signal) => {
7692
+ release2();
7693
+ process.stderr.write(`Deploy interrupted by ${signal}; released the deploy lock.
7694
+ `);
7695
+ process.exit(signal === "SIGTERM" ? 143 : 130);
7540
7696
  };
7697
+ process.once("SIGINT", onSignal);
7698
+ process.once("SIGTERM", onSignal);
7699
+ function release2() {
7700
+ process.removeListener("SIGINT", onSignal);
7701
+ process.removeListener("SIGTERM", onSignal);
7702
+ if (readLock(path)?.token === record.token) rmSync7(path, { force: true });
7703
+ }
7704
+ return release2;
7705
+ }
7706
+ function lockIsStale(path) {
7707
+ try {
7708
+ if (Date.now() - statSync3(path).mtimeMs > DEPLOY_LOCK_MAX_AGE_MS) return true;
7709
+ } catch {
7710
+ return false;
7711
+ }
7712
+ const holder = readLock(path);
7713
+ return holder !== null && !processAlive(holder.pid);
7541
7714
  }
7542
7715
  function readLock(path) {
7543
7716
  try {
7544
- const parsed = JSON.parse(readFileSync17(path, "utf-8"));
7717
+ const parsed = JSON.parse(readFileSync18(path, "utf-8"));
7545
7718
  return typeof parsed.pid === "number" && typeof parsed.startedAt === "string" && typeof parsed.token === "string" ? { pid: parsed.pid, startedAt: parsed.startedAt, token: parsed.token } : null;
7546
7719
  } catch {
7547
7720
  return null;
@@ -7776,11 +7949,20 @@ async function runLockedDeploy(args, output, appDir, envName) {
7776
7949
  );
7777
7950
  }
7778
7951
  let serving = "unverifiable";
7952
+ let dataPlane = "unconfirmed";
7779
7953
  const edgeWaitStarted = Date.now();
7780
7954
  if (body.url) {
7781
7955
  spinner2.message("Waiting for the edge to serve this release...");
7782
7956
  serving = await waitForLiveRelease(body.url, body.releaseStamp, 9e4);
7783
- if (serving === "confirmed") await wakeWorker(body.url);
7957
+ if (serving === "confirmed") {
7958
+ dataPlane = await waitForDataPlane(body.url, appId, 3e4);
7959
+ await wakeWorker(body.url);
7960
+ if (dataPlane === "unconfirmed") {
7961
+ p12.log.warn(
7962
+ "The edge serves the new release, but its worker still answered 5xx on the data plane \u2014 Durable Object bindings can lag a redeploy briefly. First requests may fail; retry before asserting against the app."
7963
+ );
7964
+ }
7965
+ }
7784
7966
  if (serving !== "confirmed") {
7785
7967
  spinner2.stop(
7786
7968
  serving === "unconfirmed" ? "Deployed \u2014 the edge is still rolling this release out" : "Deployed \u2014 serving could not be verified from here"
@@ -7799,6 +7981,7 @@ async function runLockedDeploy(args, output, appDir, envName) {
7799
7981
  releaseId: body.releaseId ?? null,
7800
7982
  bundleRetained: body.bundleRetained ?? null,
7801
7983
  serving,
7984
+ dataPlane,
7802
7985
  recoverable: repository.recoverable,
7803
7986
  ...worktree,
7804
7987
  ...renameFields,
@@ -7810,7 +7993,7 @@ async function runLockedDeploy(args, output, appDir, envName) {
7810
7993
  }
7811
7994
  }
7812
7995
  spinner2.stop(
7813
- `Deployed! (edge confirmed in ${Math.round((Date.now() - edgeWaitStarted) / 1e3)}s)`
7996
+ `Deployed! (edge${dataPlane === "confirmed" ? " + data plane" : ""} confirmed in ${Math.round((Date.now() - edgeWaitStarted) / 1e3)}s)`
7814
7997
  );
7815
7998
  p12.log.success(`Live at: ${body.url}`);
7816
7999
  await syncPostDeployCommerce(appDir, appId, output.nonInteractive);
@@ -7823,6 +8006,7 @@ async function runLockedDeploy(args, output, appDir, envName) {
7823
8006
  // Always present, so a caller branches on one field instead of
7824
8007
  // inferring success from an absent one.
7825
8008
  serving,
8009
+ dataPlane,
7826
8010
  releaseId: body.releaseId ?? null,
7827
8011
  bundleRetained: body.bundleRetained ?? null,
7828
8012
  recoverable: repository.recoverable,
@@ -7952,7 +8136,7 @@ var undeploy_default = defineDeepspaceCommand({
7952
8136
  token = await ensureToken();
7953
8137
  } catch (err) {
7954
8138
  throw new Refusal(err instanceof Error ? err.message : String(err), "not_authenticated", {
7955
- action: cliAction("deepspace", "auth", "login")
8139
+ action: loginAction()
7956
8140
  });
7957
8141
  }
7958
8142
  let appId;
@@ -8023,13 +8207,20 @@ var undeploy_default = defineDeepspaceCommand({
8023
8207
  }
8024
8208
  const hosts = body.releasedHosts ?? [];
8025
8209
  const alreadyUndeployed = hosts.length === 0;
8026
- s?.stop(alreadyUndeployed ? "Nothing to remove" : "Removed");
8210
+ let released = null;
8211
+ if (!alreadyUndeployed) {
8212
+ s?.message("Waiting for the edge to release the host...");
8213
+ released = await waitForHostReleased(`https://${hosts[0]}`, 9e4);
8214
+ }
8215
+ s?.stop(
8216
+ alreadyUndeployed ? "Nothing to remove" : released === "confirmed" ? "Removed (edge confirmed the takedown)" : "Removed \u2014 the edge is still releasing the host"
8217
+ );
8027
8218
  if (!args.json) {
8028
8219
  p13.outro(
8029
- alreadyUndeployed ? `${target2} was already offline \u2014 no URL was serving, so nothing changed. The app keeps its id; redeploy to bring it back.` : `${hosts.join(", ")} taken down. The app keeps its id \u2014 redeploy to bring it back.`
8220
+ alreadyUndeployed ? `${target2} was already offline \u2014 no URL was serving, so nothing changed. The app keeps its id; redeploy to bring it back.` : `${hosts.join(", ")} taken down${released === "confirmed" ? "" : " (cached edges may answer 200 for a short while yet)"}. The app keeps its id \u2014 redeploy to bring it back.`
8030
8221
  );
8031
8222
  }
8032
- return { data: { appId, releasedHosts: hosts, alreadyUndeployed } };
8223
+ return { data: { appId, releasedHosts: hosts, alreadyUndeployed, released } };
8033
8224
  }
8034
8225
  });
8035
8226
 
@@ -8044,7 +8235,7 @@ var whoami_default = defineDeepspaceCommand({
8044
8235
  } catch (err) {
8045
8236
  const msg = err instanceof Error ? err.message : "Not logged in. Run `deepspace auth login` first.";
8046
8237
  throw isNetworkError(err) ? new Refusal(msg, "network_error") : new Refusal(msg, "not_authenticated", {
8047
- action: cliAction("deepspace", "auth", "login")
8238
+ action: loginAction()
8048
8239
  });
8049
8240
  }
8050
8241
  let payload;
@@ -8052,7 +8243,7 @@ var whoami_default = defineDeepspaceCommand({
8052
8243
  payload = decodeJwtPayload(jwt);
8053
8244
  } catch {
8054
8245
  throw new Refusal("Malformed session token. Run `deepspace auth login` again.", "not_authenticated", {
8055
- action: cliAction("deepspace", "auth", "login")
8246
+ action: loginAction()
8056
8247
  });
8057
8248
  }
8058
8249
  const isTestAccount = !!payload.email?.endsWith("@deepspace.test");
@@ -8563,7 +8754,7 @@ var usage_default = defineDeepspaceCommand({
8563
8754
  });
8564
8755
 
8565
8756
  // src/cli/commands/add.ts
8566
- import { existsSync as existsSync23, readFileSync as readFileSync19, realpathSync as realpathSync4 } from "fs";
8757
+ import { existsSync as existsSync23, readFileSync as readFileSync20, realpathSync as realpathSync4 } from "fs";
8567
8758
  import { dirname as dirname10, join as join23, resolve as resolve17 } from "path";
8568
8759
  import { pathToFileURL as pathToFileURL3 } from "url";
8569
8760
 
@@ -8576,10 +8767,10 @@ import {
8576
8767
  mkdirSync as mkdirSync9,
8577
8768
  mkdtempSync,
8578
8769
  openSync as openSync2,
8579
- readFileSync as readFileSync18,
8770
+ readFileSync as readFileSync19,
8580
8771
  readSync,
8581
8772
  readdirSync as readdirSync8,
8582
- rmSync as rmSync7,
8773
+ rmSync as rmSync8,
8583
8774
  writeFileSync as writeFileSync7
8584
8775
  } from "fs";
8585
8776
  import { spawnSync as spawnSync7 } from "child_process";
@@ -8593,7 +8784,7 @@ function featurePackageRoot(moduleUrl = import.meta.url) {
8593
8784
  const packagePath = join22(dir, "package.json");
8594
8785
  if (existsSync22(packagePath)) {
8595
8786
  try {
8596
- const pkg2 = JSON.parse(readFileSync18(packagePath, "utf-8"));
8787
+ const pkg2 = JSON.parse(readFileSync19(packagePath, "utf-8"));
8597
8788
  if (pkg2.name === "deepspace") return dir;
8598
8789
  } catch {
8599
8790
  }
@@ -8610,7 +8801,7 @@ function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
8610
8801
  const configPath = join22(packageRoot, "features", id, "feature.json");
8611
8802
  if (!existsSync22(configPath)) return null;
8612
8803
  try {
8613
- const config = JSON.parse(readFileSync18(configPath, "utf-8"));
8804
+ const config = JSON.parse(readFileSync19(configPath, "utf-8"));
8614
8805
  if (typeof config.id !== "string" || typeof config.name !== "string" || !Array.isArray(config.files)) {
8615
8806
  return null;
8616
8807
  }
@@ -8662,7 +8853,7 @@ function copyFile(src, dest) {
8662
8853
  function copyFileWithImportShift(src, dest, levels) {
8663
8854
  mkdirSync9(dirname9(dest), { recursive: true });
8664
8855
  const prefix = "../".repeat(levels);
8665
- const shifted = readFileSync18(src, "utf-8").replace(
8856
+ const shifted = readFileSync19(src, "utf-8").replace(
8666
8857
  /(from\s+|import\s+|import\()(['"])(\.\.\/)/g,
8667
8858
  `$1$2${prefix}$3`
8668
8859
  );
@@ -8721,7 +8912,7 @@ function integrateSchema(config, targetDir, emit) {
8721
8912
  if (!existsSync22(schemasPath)) {
8722
8913
  return [unresolvedSchema(config.schema, "src/schemas.ts was not found.", emit)];
8723
8914
  }
8724
- let content = readFileSync18(schemasPath, "utf-8");
8915
+ let content = readFileSync19(schemasPath, "utf-8");
8725
8916
  const { exportName, importPath, spreadOperator } = config.schema;
8726
8917
  const importPattern = new RegExp(`^import\\s+\\{[^}]*\\b${exportName}\\b`, "m");
8727
8918
  if (importPattern.test(content)) {
@@ -8778,7 +8969,7 @@ function integrateActions(config, targetDir, emit) {
8778
8969
  if (!existsSync22(actionsPath)) {
8779
8970
  return [unresolvedActions(config.actions, "src/actions/index.ts was not found.", emit)];
8780
8971
  }
8781
- let content = readFileSync18(actionsPath, "utf-8");
8972
+ let content = readFileSync19(actionsPath, "utf-8");
8782
8973
  const registryPattern = /export const actions:\s*Record<string,\s*ActionHandler(?:<[^>]*>)?>\s*=\s*\{/;
8783
8974
  if (!registryPattern.test(content)) {
8784
8975
  const reason = "src/actions/index.ts has an unexpected structure.";
@@ -8833,7 +9024,7 @@ function integrateCode(config, targetDir, emit) {
8833
9024
  });
8834
9025
  continue;
8835
9026
  }
8836
- const original = readFileSync18(targetPath, "utf-8");
9027
+ const original = readFileSync19(targetPath, "utf-8");
8837
9028
  if (original.includes(insertion.insert.trim())) {
8838
9029
  emit(` Integration already present: ${insertion.file}`);
8839
9030
  continue;
@@ -8871,7 +9062,7 @@ ${content.slice(
8871
9062
  function integrateIgnore(config, targetDir) {
8872
9063
  if (!config.ignore?.length) return;
8873
9064
  const targetPath = join22(targetDir, ".gitignore");
8874
- const current = existsSync22(targetPath) ? readFileSync18(targetPath, "utf-8") : "";
9065
+ const current = existsSync22(targetPath) ? readFileSync19(targetPath, "utf-8") : "";
8875
9066
  const present = new Set(current.split(/\r?\n/).map((line) => line.trim()));
8876
9067
  const missing = config.ignore.filter((pattern) => !present.has(pattern));
8877
9068
  if (missing.length === 0) return;
@@ -8891,7 +9082,7 @@ function integrateWrangler(config, targetDir, emit) {
8891
9082
  if (!existsSync22(targetPath)) {
8892
9083
  return [{ area: "wrangler", reason: "wrangler.toml was not found.", steps }];
8893
9084
  }
8894
- let source = readFileSync18(targetPath, "utf-8");
9085
+ let source = readFileSync19(targetPath, "utf-8");
8895
9086
  if (runWorkerFirst && !/^\[assets\]\s*$/m.test(source)) {
8896
9087
  return [{ area: "wrangler", reason: "wrangler.toml has no [assets] section.", steps }];
8897
9088
  }
@@ -8988,7 +9179,7 @@ function integrateRoute(config, targetDir, emit) {
8988
9179
  if (!existsSync22(navPath)) {
8989
9180
  return [unresolvedRoute(config.route, config, "src/nav.ts was not found.", emit)];
8990
9181
  }
8991
- let content = readFileSync18(navPath, "utf-8");
9182
+ let content = readFileSync19(navPath, "utf-8");
8992
9183
  const routePath = config.route.path;
8993
9184
  if (content.includes(`'${routePath}'`)) {
8994
9185
  emit(` Nav already present: ${routePath}`);
@@ -9050,7 +9241,7 @@ function runPackageInstall(packageManager, targetDir, outputVisible) {
9050
9241
  };
9051
9242
  } finally {
9052
9243
  if (outputFd !== void 0) closeSync2(outputFd);
9053
- rmSync7(captureDir, { recursive: true, force: true });
9244
+ rmSync8(captureDir, { recursive: true, force: true });
9054
9245
  }
9055
9246
  }
9056
9247
  function integrateDependencies(config, targetDir, runInstall, outputVisible, emit) {
@@ -9069,7 +9260,7 @@ function integrateDependencies(config, targetDir, runInstall, outputVisible, emi
9069
9260
  }
9070
9261
  let pkg2;
9071
9262
  try {
9072
- pkg2 = JSON.parse(readFileSync18(packagePath, "utf-8"));
9263
+ pkg2 = JSON.parse(readFileSync19(packagePath, "utf-8"));
9073
9264
  } catch (error) {
9074
9265
  emit(
9075
9266
  ` Could not patch package.json (parse error: ${error instanceof Error ? error.message : String(error)})`
@@ -9129,7 +9320,7 @@ ${child.diagnosticTail}` : "";
9129
9320
  }
9130
9321
  function dependencySnapshot(targetDir) {
9131
9322
  try {
9132
- const pkg2 = JSON.parse(readFileSync18(join22(targetDir, "package.json"), "utf-8"));
9323
+ const pkg2 = JSON.parse(readFileSync19(join22(targetDir, "package.json"), "utf-8"));
9133
9324
  return {
9134
9325
  dependencies: pkg2.dependencies ?? {},
9135
9326
  devDependencies: pkg2.devDependencies ?? {}
@@ -9377,7 +9568,7 @@ function isDeepSpaceApp(dir) {
9377
9568
  const packagePath = join23(dir, "package.json");
9378
9569
  if (!existsSync23(packagePath)) return false;
9379
9570
  try {
9380
- const pkg2 = JSON.parse(readFileSync19(packagePath, "utf-8"));
9571
+ const pkg2 = JSON.parse(readFileSync20(packagePath, "utf-8"));
9381
9572
  return Boolean(pkg2.dependencies?.deepspace || pkg2.devDependencies?.deepspace);
9382
9573
  } catch {
9383
9574
  return false;
@@ -9849,10 +10040,10 @@ var domain_default = defineCommand5({
9849
10040
  // src/cli/commands/app-files.ts
9850
10041
  import { defineCommand as defineCommand6 } from "citty";
9851
10042
  import { basename as basename4, resolve as resolve18 } from "path";
9852
- import { existsSync as existsSync24, statSync as statSync4 } from "fs";
10043
+ import { existsSync as existsSync24, statSync as statSync5 } from "fs";
9853
10044
 
9854
10045
  // src/cli/lib/app-files-api.ts
9855
- import { createWriteStream, openAsBlob, statSync as statSync3 } from "fs";
10046
+ import { createWriteStream, openAsBlob, statSync as statSync4 } from "fs";
9856
10047
  import { basename as basename3, extname } from "path";
9857
10048
  import { Readable } from "stream";
9858
10049
  import { pipeline } from "stream/promises";
@@ -9962,7 +10153,7 @@ Content-Type: ${contentType}\r
9962
10153
  }
9963
10154
  async function uploadAppFile(baseUrl, token, appId, localPath, key, onProgress) {
9964
10155
  const path = filesPath(appId, `/upload?key=${encodeKeyPath(key)}`);
9965
- const size = statSync3(localPath).size;
10156
+ const size = statSync4(localPath).size;
9966
10157
  if (size > MAX_APP_FILE_BYTES) {
9967
10158
  throw new ApiError(oversizeMessage(size), 0, "too_large", path);
9968
10159
  }
@@ -10128,7 +10319,7 @@ var put = defineDeepspaceCommand({
10128
10319
  },
10129
10320
  async run({ args }) {
10130
10321
  const localPath = resolve18(String(args.file));
10131
- if (!existsSync24(localPath) || !statSync4(localPath).isFile()) {
10322
+ if (!existsSync24(localPath) || !statSync5(localPath).isFile()) {
10132
10323
  throw new Refusal(`No such file: ${args.file}`, "file_not_found");
10133
10324
  }
10134
10325
  const key = requireKey(args.key ?? basename4(localPath));
@@ -10638,7 +10829,7 @@ var transfer_default = defineCommand8({
10638
10829
  import { defineCommand as defineCommand9 } from "citty";
10639
10830
 
10640
10831
  // src/cli/commands/_invoke-impl.ts
10641
- import { readFileSync as readFileSync20 } from "fs";
10832
+ import { readFileSync as readFileSync21 } from "fs";
10642
10833
  import * as p15 from "@clack/prompts";
10643
10834
  var API_URL7 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
10644
10835
  var DEFAULT_TIMEOUT_MS = 12e4;
@@ -10685,7 +10876,7 @@ async function resolveBody(opts) {
10685
10876
  "no_stdin"
10686
10877
  );
10687
10878
  }
10688
- raw = opts.bodyFile === "-" ? await readStreamText(process.stdin) : readFileSync20(opts.bodyFile, "utf-8");
10879
+ raw = opts.bodyFile === "-" ? await readStreamText(process.stdin) : readFileSync21(opts.bodyFile, "utf-8");
10689
10880
  if (opts.bodyFile === "-" && raw.trim() === "") {
10690
10881
  throw new Refusal(
10691
10882
  "Nothing arrived on stdin \u2014 check the command feeding this one, or pass --body / a file path.",
@@ -10704,12 +10895,12 @@ async function resolveBody(opts) {
10704
10895
  return raw;
10705
10896
  }
10706
10897
  async function fetchCatalog(opts = {}) {
10707
- const url = opts.summary ? `${API_URL7}/api/integrations?summary=1` : `${API_URL7}/api/integrations`;
10708
- const res = await fetch(url);
10709
- if (!res.ok) {
10710
- throw new Refusal(`Failed to fetch integration catalog (${res.status})`, "catalog_unavailable");
10898
+ try {
10899
+ return await fetchIntegrationCatalog(API_URL7, { summary: opts.summary });
10900
+ } catch (err) {
10901
+ if (err instanceof ApiError) throw new Refusal(err.message, err.code ?? "catalog_unavailable");
10902
+ throw err;
10711
10903
  }
10712
- return await res.json();
10713
10904
  }
10714
10905
  function findEndpoint(catalog, integration, endpoint) {
10715
10906
  const endpoints = catalog.integrations[integration];
@@ -10793,9 +10984,13 @@ function requireEndpoint(catalog, integration, endpoint) {
10793
10984
  const available = catalog.integrations[integration];
10794
10985
  if (!available) {
10795
10986
  const names = Object.keys(catalog.integrations).sort().join(", ");
10796
- throw new Refusal(`Unknown integration '${integration}'. Available: ${names}`, "unknown_integration", {
10797
- action: cliAction("deepspace", "integrations", "list")
10798
- });
10987
+ throw new Refusal(
10988
+ `Unknown integration '${integration}'. Available: ${names}`,
10989
+ "unknown_integration",
10990
+ {
10991
+ action: cliAction("deepspace", "integrations", "list")
10992
+ }
10993
+ );
10799
10994
  }
10800
10995
  const endpoints = available.map((e) => e.endpoint).join(", ");
10801
10996
  throw new Refusal(
@@ -10811,9 +11006,7 @@ async function runInfo(args) {
10811
11006
  if (!args.json) {
10812
11007
  console.log(`${integration}/${endpoint}`);
10813
11008
  if (info2.description) console.log(` ${info2.description}`);
10814
- console.log(
10815
- ` billing: ${priceLabel(info2.billing)}`
10816
- );
11009
+ console.log(` billing: ${priceLabel(info2.billing)}`);
10817
11010
  if (info2.requiresOAuth) {
10818
11011
  console.log(
10819
11012
  " Requires OAuth: without a connected account, a call succeeds with data { requiresOAuth: true, authUrl } \u2014 open the authUrl, then retry."
@@ -10870,7 +11063,7 @@ async function runInvoke(args) {
10870
11063
  } catch (err) {
10871
11064
  const msg = err instanceof Error ? err.message : String(err);
10872
11065
  throw new Refusal(msg, "not_authenticated", {
10873
- action: cliAction("deepspace", "auth", "login")
11066
+ action: loginAction()
10874
11067
  });
10875
11068
  }
10876
11069
  const controller = new AbortController();
@@ -10926,7 +11119,7 @@ async function runInvoke(args) {
10926
11119
  delete rest.message;
10927
11120
  delete rest.code;
10928
11121
  throw new Refusal(lines.join("\n"), code, {
10929
- ...res.status === 401 ? { action: cliAction("deepspace", "auth", "login") } : {},
11122
+ ...res.status === 401 ? { action: loginAction() } : {},
10930
11123
  extra: rest
10931
11124
  });
10932
11125
  }
@@ -11332,7 +11525,7 @@ async function callAgent(target2, path, init, timeout) {
11332
11525
  throw new Refusal(
11333
11526
  "Authentication was rejected. Run `deepspace auth login` and try again.",
11334
11527
  "not_authenticated",
11335
- { action: cliAction("deepspace", "auth", "login") }
11528
+ { action: loginAction() }
11336
11529
  );
11337
11530
  }
11338
11531
  }
@@ -11423,7 +11616,7 @@ var agent_default = defineCommand10({
11423
11616
 
11424
11617
  // src/cli/commands/feedback.ts
11425
11618
  import * as p16 from "@clack/prompts";
11426
- import { existsSync as existsSync25, readFileSync as readFileSync21 } from "fs";
11619
+ import { existsSync as existsSync25, readFileSync as readFileSync22 } from "fs";
11427
11620
  import { dirname as dirname11, join as join24 } from "path";
11428
11621
  import { fileURLToPath as fileURLToPath2 } from "url";
11429
11622
  import { release } from "os";
@@ -11452,7 +11645,7 @@ function readCliVersion() {
11452
11645
  ];
11453
11646
  for (const path of candidates) {
11454
11647
  if (!existsSync25(path)) continue;
11455
- const pkg2 = JSON.parse(readFileSync21(path, "utf-8"));
11648
+ const pkg2 = JSON.parse(readFileSync22(path, "utf-8"));
11456
11649
  if (pkg2.name === "deepspace" && pkg2.version) return pkg2.version;
11457
11650
  }
11458
11651
  } catch {
@@ -11627,7 +11820,7 @@ Context: deepspace ${payload.cliVersion} \xB7 ${payload.nodeVersion} \xB7 ${payl
11627
11820
  throw new Refusal(
11628
11821
  err.message ?? "Not signed in. Run `deepspace auth login`.",
11629
11822
  "not_authenticated",
11630
- { action: cliAction("deepspace", "auth", "login") }
11823
+ { action: loginAction() }
11631
11824
  );
11632
11825
  }
11633
11826
  const result = await api3(token, "/api/feedback", {
@@ -11648,7 +11841,7 @@ Context: deepspace ${payload.cliVersion} \xB7 ${payload.nodeVersion} \xB7 ${payl
11648
11841
 
11649
11842
  // src/cli/commands/secrets.ts
11650
11843
  import { defineCommand as defineCommand11 } from "citty";
11651
- import { readFileSync as readFileSync22 } from "fs";
11844
+ import { readFileSync as readFileSync23 } from "fs";
11652
11845
 
11653
11846
  // src/cli/lib/citty-args.ts
11654
11847
  function dedupePositionals(bound, rest) {
@@ -11963,7 +12156,7 @@ async function readUploadSource(file) {
11963
12156
  return readStreamText(process.stdin, MAX_STDIN_BYTES);
11964
12157
  }
11965
12158
  try {
11966
- return readFileSync22(file, "utf-8");
12159
+ return readFileSync23(file, "utf-8");
11967
12160
  } catch (err) {
11968
12161
  const errno = err.code;
11969
12162
  if (typeof errno !== "string" || errno.length === 0) throw err;
@@ -12013,47 +12206,6 @@ var download = defineCommand11({
12013
12206
  process.stdout.write(formatSecretsDownload(secrets, format));
12014
12207
  }
12015
12208
  });
12016
- var pull = defineCommand11({
12017
- meta: { name: "pull", description: "Refresh the .dev.vars cache from the app store" },
12018
- args: { ...COMMON_ARGS },
12019
- async run({ args }) {
12020
- const wranglerEnv = args.env?.trim() || void 0;
12021
- const appDir = findAppDir();
12022
- if (!appDir)
12023
- throw new InputError(
12024
- "Run from a DeepSpace app directory (one containing wrangler.toml).",
12025
- "not_in_app_repo"
12026
- );
12027
- const t = await resolveTarget(args, { register: true });
12028
- const ownerId = decodeJwtPayload(t.token).sub;
12029
- const refreshed = await refreshSecretsCache(
12030
- DEPLOY_URL12,
12031
- t.token,
12032
- t.appId,
12033
- wranglerEnv,
12034
- t.configName
12035
- );
12036
- try {
12037
- await writeDevVars(appDir, ownerId, t.token, wranglerEnv, {
12038
- appId: t.appId,
12039
- generatedSecretsCache: refreshed.rendered
12040
- });
12041
- } catch (err) {
12042
- if (args.app && err instanceof Refusal && err.code === "app_not_found") {
12043
- throw new Refusal(err.message, err.code);
12044
- }
12045
- throw err;
12046
- }
12047
- const count = Object.keys(refreshed.pulled?.values ?? {}).length;
12048
- ok(
12049
- args.json === true,
12050
- { appId: t.appId, config: t.configName, pulled: count },
12051
- () => console.log(
12052
- refreshed.pulled ? `Pulled ${count} secrets (${t.configName}) into .dev.vars.` : `Config ${t.configName} does not exist yet; regenerated .dev.vars without app secrets.`
12053
- )
12054
- );
12055
- }
12056
- });
12057
12209
  var configsList = defineCommand11({
12058
12210
  meta: { name: "list", description: "List the app\u2019s configs" },
12059
12211
  args: { ...COMMON_ARGS },
@@ -12163,7 +12315,6 @@ var secrets_default = defineCommand11({
12163
12315
  delete: del2,
12164
12316
  upload,
12165
12317
  download,
12166
- pull,
12167
12318
  configs
12168
12319
  }
12169
12320
  });
@@ -12320,7 +12471,7 @@ var init_default = defineDeepspaceCommand({
12320
12471
  // src/cli/commands/status.ts
12321
12472
  import { defineCommand as defineCommand12 } from "citty";
12322
12473
  import * as p17 from "@clack/prompts";
12323
- import { existsSync as existsSync26, readFileSync as readFileSync23 } from "fs";
12474
+ import { existsSync as existsSync26, readFileSync as readFileSync24 } from "fs";
12324
12475
  function timeAgo(iso) {
12325
12476
  const ms = Date.now() - Date.parse(iso);
12326
12477
  if (!Number.isFinite(ms) || ms < 0) return iso;
@@ -12424,7 +12575,7 @@ var status_default = defineCommand12({
12424
12575
  let undecodableToken = false;
12425
12576
  try {
12426
12577
  const payload = decodeJwtPayload(
12427
- readFileSync23(TOKEN_PATH, "utf-8").trim()
12578
+ readFileSync24(TOKEN_PATH, "utf-8").trim()
12428
12579
  );
12429
12580
  if (payload.email) who = payload.email;
12430
12581
  userId = payload.sub ?? null;
@@ -13272,7 +13423,7 @@ var pull_default = defineDeepspaceCommand({
13272
13423
 
13273
13424
  // src/cli/commands/clone.ts
13274
13425
  import * as p20 from "@clack/prompts";
13275
- import { existsSync as existsSync27, readdirSync as readdirSync9, rmSync as rmSync8 } from "fs";
13426
+ import { existsSync as existsSync27, readdirSync as readdirSync9, rmSync as rmSync9 } from "fs";
13276
13427
  import { resolve as resolve19, basename as basename5, join as join25 } from "path";
13277
13428
  function cleanFailedCloneDir(dir) {
13278
13429
  let entries;
@@ -13283,7 +13434,7 @@ function cleanFailedCloneDir(dir) {
13283
13434
  }
13284
13435
  if (entries.length === 1 && entries[0] === ".git") {
13285
13436
  try {
13286
- rmSync8(join25(dir, ".git"), { recursive: true, force: true });
13437
+ rmSync9(join25(dir, ".git"), { recursive: true, force: true });
13287
13438
  return [];
13288
13439
  } catch {
13289
13440
  return entries;
@@ -13385,7 +13536,7 @@ var clone_default = defineDeepspaceCommand({
13385
13536
  },
13386
13537
  // One runnable follow-up instead of the old five-line "Next steps" block:
13387
13538
  // nothing else in the clone works until the deps are installed. Secrets
13388
- // (`deepspace secrets pull`) and `dev` follow from there.
13539
+ // and `dev` (which refreshes `.dev.vars` itself) follow from there.
13389
13540
  action: { cwd: dir, argv: [detectPackageManager(dir), "install"] }
13390
13541
  };
13391
13542
  }
@@ -13525,6 +13676,12 @@ var rollback_default = defineDeepspaceCommand({
13525
13676
  type: "boolean",
13526
13677
  description: "Proceed even though the target release declares fewer Durable Object classes than the current one \u2014 THEIR STORED DATA IS DELETED.",
13527
13678
  default: false
13679
+ },
13680
+ yes: {
13681
+ type: "boolean",
13682
+ alias: "y",
13683
+ description: "Skip the confirmation (required for --json / non-interactive)",
13684
+ default: false
13528
13685
  }
13529
13686
  },
13530
13687
  async run({ args }) {
@@ -13556,7 +13713,15 @@ var rollback_default = defineDeepspaceCommand({
13556
13713
  if ("error" in picked) throw new Refusal(picked.error, picked.code);
13557
13714
  releaseId = picked.releaseId;
13558
13715
  }
13559
- spinner2?.message(`Rolling back to ${releaseId}\u2026`);
13716
+ spinner2?.stop(`Rollback target: ${releaseId}`);
13717
+ await requireConsent({
13718
+ yes: Boolean(args.yes),
13719
+ json: Boolean(args.json),
13720
+ message: `This re-deploys release ${releaseId} as the live release of ${appId} (live secrets are kept, not the release's).`,
13721
+ prompt: `Roll ${appId} back to ${releaseId}?`,
13722
+ extra: { appId, releaseId }
13723
+ });
13724
+ spinner2?.start(`Rolling back to ${releaseId}\u2026`);
13560
13725
  let result;
13561
13726
  try {
13562
13727
  result = await apiFetch(DEPLOY_URL15, token, `/api/deploy/${appId}/rollback`, {
@@ -13773,10 +13938,10 @@ import * as p24 from "@clack/prompts";
13773
13938
  import {
13774
13939
  existsSync as existsSync28,
13775
13940
  mkdirSync as mkdirSync10,
13776
- readFileSync as readFileSync24,
13941
+ readFileSync as readFileSync25,
13777
13942
  realpathSync as realpathSync6,
13778
13943
  renameSync as renameSync2,
13779
- statSync as statSync5,
13944
+ statSync as statSync6,
13780
13945
  writeFileSync as writeFileSync8
13781
13946
  } from "fs";
13782
13947
  import { dirname as dirname12, isAbsolute as isAbsolute6, join as join26, relative as relative4, sep as sep4 } from "path";
@@ -13836,7 +14001,7 @@ function isManagedWorkspaceWorktree(worktreeRoot, workspaceId) {
13836
14001
  const markerPath = worktreeMarkerPath(worktreeRoot);
13837
14002
  if (!markerPath) return false;
13838
14003
  try {
13839
- const marker = JSON.parse(readFileSync24(markerPath, "utf-8"));
14004
+ const marker = JSON.parse(readFileSync25(markerPath, "utf-8"));
13840
14005
  return marker.version === 1 && marker.workspaceId === workspaceId && marker.mode === "managed-linked";
13841
14006
  } catch {
13842
14007
  return false;
@@ -13879,7 +14044,7 @@ function rematerializeWorkspaceWorktree(appDir, worktreeRoot, branch, workspaceI
13879
14044
  function inOwnLinkedWorktree(appDir, id) {
13880
14045
  if (workspaceIdFromBranch(currentBranch(appDir)) !== id) return false;
13881
14046
  try {
13882
- return statSync5(join26(repoToplevel(appDir), ".git")).isFile();
14047
+ return statSync6(join26(repoToplevel(appDir), ".git")).isFile();
13883
14048
  } catch {
13884
14049
  return false;
13885
14050
  }
@@ -14325,7 +14490,7 @@ var newWorkspaceCommand = defineDeepspaceCommand({
14325
14490
 
14326
14491
  // src/cli/commands/workspace/attach.ts
14327
14492
  import * as p26 from "@clack/prompts";
14328
- import { existsSync as existsSync29, readdirSync as readdirSync10, realpathSync as realpathSync7, rmSync as rmSync9 } from "fs";
14493
+ import { existsSync as existsSync29, readdirSync as readdirSync10, realpathSync as realpathSync7, rmSync as rmSync10 } from "fs";
14329
14494
  import { isAbsolute as isAbsolute8, resolve as resolve21 } from "path";
14330
14495
  function samePath(a, b) {
14331
14496
  const real = (p34) => {
@@ -14356,7 +14521,7 @@ function cleanFailedFreshAttachDir(dir) {
14356
14521
  return entries;
14357
14522
  }
14358
14523
  try {
14359
- rmSync9(dir, { recursive: true, force: true });
14524
+ rmSync10(dir, { recursive: true, force: true });
14360
14525
  return [];
14361
14526
  } catch {
14362
14527
  return entries.length > 0 ? entries : ["<cleanup-failed>"];
@@ -14892,13 +15057,13 @@ import * as p30 from "@clack/prompts";
14892
15057
 
14893
15058
  // src/cli/lib/validation.ts
14894
15059
  import { sync as spawnSync8 } from "cross-spawn";
14895
- import { readFileSync as readFileSync25 } from "fs";
15060
+ import { readFileSync as readFileSync26 } from "fs";
14896
15061
  import { join as join27 } from "path";
14897
15062
  var MAX_SUMMARY_CHARS = 4e3;
14898
15063
  var MAX_OUTPUT_BUFFER = 64 * 1024 * 1024;
14899
15064
  function configuredCommand(appDir) {
14900
15065
  try {
14901
- const pkg2 = JSON.parse(readFileSync25(join27(appDir, "package.json"), "utf-8"));
15066
+ const pkg2 = JSON.parse(readFileSync26(join27(appDir, "package.json"), "utf-8"));
14902
15067
  if (pkg2.scripts?.validate) return `${detectPackageManager(appDir)} run --silent validate`;
14903
15068
  } catch {
14904
15069
  }
@@ -15850,13 +16015,13 @@ function reportSource(appId, state, json) {
15850
16015
 
15851
16016
  // src/cli/commands/update.ts
15852
16017
  import * as p33 from "@clack/prompts";
15853
- import { existsSync as existsSync30, readFileSync as readFileSync26 } from "fs";
16018
+ import { existsSync as existsSync30, readFileSync as readFileSync27 } from "fs";
15854
16019
  import { join as join28, resolve as resolve22 } from "path";
15855
16020
 
15856
16021
  // package.json
15857
16022
  var package_default = {
15858
16023
  name: "deepspace",
15859
- version: "0.29.0",
16024
+ version: "0.30.0",
15860
16025
  engines: {
15861
16026
  node: ">=22.15.0 <23 || >=24 <25 || >=26 <27"
15862
16027
  },
@@ -16038,7 +16203,7 @@ function readManifest(appDir) {
16038
16203
  throw new Refusal("This app has no package.json \u2014 nothing to update.", "not_in_app_repo");
16039
16204
  }
16040
16205
  try {
16041
- return JSON.parse(readFileSync26(path, "utf8"));
16206
+ return JSON.parse(readFileSync27(path, "utf8"));
16042
16207
  } catch {
16043
16208
  throw new Refusal("package.json must contain valid JSON.", "invalid_package_manifest");
16044
16209
  }
@@ -16074,7 +16239,7 @@ function usersSchemaVisibilityUpgradeInstruction(appDir) {
16074
16239
  const schemaName = "src/schemas/users-schema.ts";
16075
16240
  const path = join28(appDir, schemaName);
16076
16241
  if (!existsSync30(path)) return null;
16077
- const source = readFileSync26(path, "utf8");
16242
+ const source = readFileSync27(path, "utf8");
16078
16243
  if (!/\bmember\s*:\s*\{[^}]*\bread\s*:\s*true\b/s.test(source)) return null;
16079
16244
  return `${schemaName}: review member user-row visibility at ${USERS_SCHEMA_VISIBILITY_GUIDE}; leave the app-owned schema unchanged until you choose its policy.`;
16080
16245
  }
@@ -16174,7 +16339,7 @@ process.stdout.on("error", (error) => {
16174
16339
  throw error;
16175
16340
  });
16176
16341
  var __dirname = dirname13(fileURLToPath3(import.meta.url));
16177
- var pkg = JSON.parse(readFileSync27(join29(__dirname, "..", "package.json"), "utf-8"));
16342
+ var pkg = JSON.parse(readFileSync28(join29(__dirname, "..", "package.json"), "utf-8"));
16178
16343
  if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync31("/etc/ssl/certs/ca-certificates.crt")) {
16179
16344
  process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
16180
16345
  }