deepspace 0.29.1 → 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
 
@@ -854,6 +854,10 @@ async function ensureToken(options = {}) {
854
854
  throw notAuthenticated("The stored session is no longer valid (expired or unreadable)");
855
855
  return token;
856
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
+ }
857
861
  function notAuthenticated(state) {
858
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})`;
859
863
  const elsewhere = Object.entries(PLANE_AUTH_URLS).filter(([, url]) => url !== AUTH_URL && existsSync(credentialPaths(url).sessionPath)).map(([plane]) => plane);
@@ -865,7 +869,7 @@ function notAuthenticated(state) {
865
869
  return new Refusal(
866
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).`,
867
871
  "not_authenticated",
868
- { action: cliAction("deepspace", "auth", "login") }
872
+ { action: loginAction() }
869
873
  );
870
874
  }
871
875
  function isTokenValid(jwt, minimumValidityMs) {
@@ -1006,7 +1010,7 @@ var login_default = defineDeepspaceCommand({
1006
1010
  "Login timed out. Run `deepspace auth login` to try again.",
1007
1011
  "login_timeout",
1008
1012
  {
1009
- action: cliAction("deepspace", "auth", "login")
1013
+ action: loginAction()
1010
1014
  }
1011
1015
  );
1012
1016
  }
@@ -1078,14 +1082,14 @@ async function createCliLoginSession(codeChallenge) {
1078
1082
  throw new Refusal(
1079
1083
  `Could not reach the auth service to create a login session: ${error instanceof Error ? error.message : String(error)}`,
1080
1084
  "login_session_unreachable",
1081
- { action: cliAction("deepspace", "auth", "login") }
1085
+ { action: loginAction() }
1082
1086
  );
1083
1087
  }
1084
1088
  if (!sessionRes.ok) {
1085
1089
  throw new Refusal(
1086
1090
  `Could not create login session (${sessionRes.status})`,
1087
1091
  "login_session_failed",
1088
- { action: cliAction("deepspace", "auth", "login") }
1092
+ { action: loginAction() }
1089
1093
  );
1090
1094
  }
1091
1095
  const sessionData = await sessionRes.json().catch(() => ({}));
@@ -1093,7 +1097,7 @@ async function createCliLoginSession(codeChallenge) {
1093
1097
  throw new Refusal(
1094
1098
  "Could not create login session \u2014 the auth server returned no session id or login url.",
1095
1099
  "login_session_failed",
1096
- { action: cliAction("deepspace", "auth", "login") }
1100
+ { action: loginAction() }
1097
1101
  );
1098
1102
  }
1099
1103
  return { sessionId: sessionData.sessionId, loginUrl: sessionData.loginUrl };
@@ -2771,7 +2775,7 @@ async function fetchAppIdentityToken(deployUrl, callerJwt, appId) {
2771
2775
  // wrangler env, and a bare `app init` under --env would target the
2772
2776
  // TOP-LEVEL [vars] slot. dev/test/deploy attach the env-aware
2773
2777
  // remedy as a structured action; surfaces without an action channel
2774
- // (secrets pull among them) fall back to the
2778
+ // fall back to the
2775
2779
  // app_not_found hint (check the id / `deepspace app list`).
2776
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.`,
2777
2781
  404,
@@ -2805,7 +2809,7 @@ async function fetchAppIdentityToken(deployUrl, callerJwt, appId) {
2805
2809
  // src/cli/lib/dev-vars.ts
2806
2810
  var HEADER = [
2807
2811
  "# Generated by deepspace \u2014 do not edit; this file is rewritten on every",
2808
- "# 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:",
2809
2813
  "# npx deepspace secrets set KEY=value"
2810
2814
  ].join("\n");
2811
2815
  function requireAppIdFor(appDir, wranglerEnv) {
@@ -3053,6 +3057,15 @@ function canonicalPath2(path) {
3053
3057
  return resolve7(path);
3054
3058
  }
3055
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
+ }
3056
3069
 
3057
3070
  // src/cli/lib/preflight.ts
3058
3071
  import { execFileSync } from "child_process";
@@ -3124,6 +3137,8 @@ function removeMacosJunk(dir) {
3124
3137
 
3125
3138
  // src/server/rooms/binding-manifest.ts
3126
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}}$`);
3127
3142
  var AUTO_PROVISION_SENTINEL = "auto";
3128
3143
  var VECTORIZE_METRICS = /* @__PURE__ */ new Set(["cosine", "euclidean", "dot-product"]);
3129
3144
  var ALLOWED_BINDING_TYPES = /* @__PURE__ */ new Set([
@@ -3184,6 +3199,12 @@ function validateBindingManifest(manifest) {
3184
3199
  errors.push({ reason: `Binding of type "${type}" missing 'name' (string)` });
3185
3200
  continue;
3186
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
+ }
3187
3208
  if (RESERVED_BINDING_NAMES.has(name)) {
3188
3209
  errors.push({ reason: `Binding name "${name}" is reserved by the SDK` });
3189
3210
  continue;
@@ -3425,7 +3446,6 @@ var RESERVED_SECRET_NAMES = /* @__PURE__ */ new Set([
3425
3446
  "API_WORKER_URL",
3426
3447
  "PLATFORM_WORKER_URL"
3427
3448
  ]);
3428
- var SECRET_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
3429
3449
  var CONFIG_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
3430
3450
  var MAX_SECRET_VALUE_BYTES = 32 * 1024;
3431
3451
  function validateConfigName(configName) {
@@ -3438,6 +3458,12 @@ function validateConfigName(configName) {
3438
3458
  return configName;
3439
3459
  }
3440
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
+ }
3441
3467
  if (!SECRET_NAME_RE.test(name)) {
3442
3468
  throw new InputError(
3443
3469
  `Invalid secret name "${name}". Use letters, numbers, and underscores; start with a letter or underscore.`,
@@ -3993,7 +4019,7 @@ var dev_default = defineDeepspaceCommand({
3993
4019
  token = await ensureToken();
3994
4020
  } catch (err) {
3995
4021
  throw new Refusal(err instanceof Error ? err.message : String(err), "not_authenticated", {
3996
- action: cliAction("deepspace", "auth", "login")
4022
+ action: loginAction()
3997
4023
  });
3998
4024
  }
3999
4025
  let payload;
@@ -4003,7 +4029,7 @@ var dev_default = defineDeepspaceCommand({
4003
4029
  throw new Refusal(
4004
4030
  "Malformed session token. Run `npx deepspace auth login`.",
4005
4031
  "not_authenticated",
4006
- { action: cliAction("deepspace", "auth", "login") }
4032
+ { action: loginAction() }
4007
4033
  );
4008
4034
  }
4009
4035
  say(`Logged in as ${payload.name ?? payload.email}`);
@@ -4380,7 +4406,7 @@ function isAlive(pid) {
4380
4406
  }
4381
4407
 
4382
4408
  // src/cli/commands/test.ts
4383
- 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";
4384
4410
  import { join as join14, relative as relative2, resolve as resolve10, sep as sep2 } from "path";
4385
4411
  import { sync as spawnSync5 } from "cross-spawn";
4386
4412
 
@@ -4638,12 +4664,39 @@ function playwrightTestArgs(testFiles, flags = {}) {
4638
4664
  "tests/playwright.config.ts",
4639
4665
  "--output",
4640
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",
4641
4671
  ...flags.grep ? ["--grep", flags.grep] : [],
4642
4672
  ...flags.project ? ["--project", flags.project] : [],
4643
4673
  ...flags.headed ? ["--headed"] : [],
4644
4674
  ...testFiles
4645
4675
  ];
4646
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
+ }
4647
4700
  var test_default = defineDeepspaceCommand({
4648
4701
  meta: {
4649
4702
  name: "test",
@@ -4717,7 +4770,7 @@ var test_default = defineDeepspaceCommand({
4717
4770
  throw new Refusal(
4718
4771
  err instanceof Error ? err.message : "Not logged in. Run `deepspace auth login` first.",
4719
4772
  "not_authenticated",
4720
- { action: cliAction("deepspace", "auth", "login") }
4773
+ { action: loginAction() }
4721
4774
  );
4722
4775
  }
4723
4776
  let generatedSecretsCache;
@@ -4753,15 +4806,21 @@ var test_default = defineDeepspaceCommand({
4753
4806
  }
4754
4807
  let exitCode = 0;
4755
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
+ };
4756
4815
  switch (suite) {
4757
4816
  case "smoke":
4758
- exitCode = runPlaywright(appDir, ["tests/smoke.spec.ts"], port, wranglerEnv, forwarded);
4817
+ exitCode = playwright(["tests/smoke.spec.ts"]);
4759
4818
  break;
4760
4819
  case "api":
4761
- exitCode = runPlaywright(appDir, ["tests/api.spec.ts"], port, wranglerEnv, forwarded);
4820
+ exitCode = playwright(["tests/api.spec.ts"]);
4762
4821
  break;
4763
4822
  case "e2e":
4764
- exitCode = runPlaywright(appDir, [], port, wranglerEnv, forwarded);
4823
+ exitCode = playwright([]);
4765
4824
  break;
4766
4825
  case "unit":
4767
4826
  if (forwarded.grep || forwarded.project || forwarded.headed) {
@@ -4775,12 +4834,12 @@ var test_default = defineDeepspaceCommand({
4775
4834
  exitCode = runVitest(appDir, wranglerEnv);
4776
4835
  if (exitCode === 0) {
4777
4836
  await ensurePortFree(port, "0.0.0.0");
4778
- exitCode = runPlaywright(appDir, [], port, wranglerEnv, forwarded);
4837
+ exitCode = playwright([]);
4779
4838
  }
4780
4839
  break;
4781
4840
  case "default":
4782
4841
  skippedSpecs = specsSkippedByDefaultSuite(appDir);
4783
- exitCode = runPlaywright(appDir, [...DEFAULT_SUITE_SPECS], port, wranglerEnv, forwarded);
4842
+ exitCode = playwright([...DEFAULT_SUITE_SPECS]);
4784
4843
  if (skippedSpecs.length > 0) {
4785
4844
  say(
4786
4845
  `skipped ${skippedSpecs.length} spec file(s) not in smoke+api (${skippedSpecs.join(", ")}) \u2014 run 'deepspace test run all'`
@@ -4796,7 +4855,7 @@ var test_default = defineDeepspaceCommand({
4796
4855
  "spec_not_found"
4797
4856
  );
4798
4857
  }
4799
- exitCode = runPlaywright(appDir, [suite], port, wranglerEnv, forwarded);
4858
+ exitCode = playwright([suite]);
4800
4859
  } else {
4801
4860
  throw new Refusal(
4802
4861
  `Unknown test suite: ${suite}
@@ -4805,12 +4864,20 @@ Available: smoke, api, e2e, unit, all \u2014 or a path to one spec file (tests/<
4805
4864
  );
4806
4865
  }
4807
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
+ }
4808
4873
  if (exitCode !== 0) {
4809
4874
  throw new Refusal(`Test suite '${suite}' failed (exit ${exitCode}).`, "tests_failed", {
4810
- extra: { suite, port, exitCode, skippedSpecs }
4875
+ extra: { suite, port, exitCode, skippedSpecs, skippedTests }
4811
4876
  });
4812
4877
  }
4813
- return { data: { suite, port, appDir, wranglerEnv: wranglerEnv ?? null, skippedSpecs } };
4878
+ return {
4879
+ data: { suite, port, appDir, wranglerEnv: wranglerEnv ?? null, skippedSpecs, skippedTests }
4880
+ };
4814
4881
  }
4815
4882
  });
4816
4883
  function runSuite(appDir, argv, wranglerEnv, extraEnv = {}) {
@@ -4835,10 +4902,19 @@ function runSuite(appDir, argv, wranglerEnv, extraEnv = {}) {
4835
4902
  }
4836
4903
  }
4837
4904
  function runPlaywright(appDir, testFiles, port, wranglerEnv, flags = {}) {
4838
- 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, {
4839
4908
  DEEPSPACE_PORT: String(port),
4840
- PLAYWRIGHT_HTML_OUTPUT_DIR: ".deepspace/playwright-report"
4909
+ PLAYWRIGHT_HTML_OUTPUT_DIR: ".deepspace/playwright-report",
4910
+ PLAYWRIGHT_JSON_OUTPUT_FILE: resultsPath
4841
4911
  });
4912
+ let raw = "";
4913
+ try {
4914
+ raw = readFileSync13(resultsPath, "utf-8");
4915
+ } catch {
4916
+ }
4917
+ return { exitCode, skippedTests: skippedTestsFromPlaywrightJson(raw) };
4842
4918
  }
4843
4919
  function runVitest(appDir, wranglerEnv) {
4844
4920
  return runSuite(appDir, ["vitest", "run", "--passWithNoTests"], wranglerEnv);
@@ -5452,14 +5528,30 @@ var CONSECUTIVE_MATCHES = 10;
5452
5528
  var MATCH_INTERVAL_MS = 250;
5453
5529
  async function waitForLiveRelease(url, releaseStamp, timeoutMs) {
5454
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) {
5455
5549
  const deadline = Date.now() + timeoutMs;
5456
5550
  let attempt = 0;
5457
5551
  let streak = 0;
5458
5552
  while (Date.now() < deadline) {
5459
5553
  attempt++;
5460
- const probe = new URL(RELEASE_STAMP_PATH, url);
5461
- probe.searchParams.set("probe", `${Date.now()}-${attempt}`);
5462
- if (await servesRelease(probe, releaseStamp)) {
5554
+ if (await probe(attempt)) {
5463
5555
  streak++;
5464
5556
  if (streak >= CONSECUTIVE_MATCHES) return "confirmed";
5465
5557
  await sleep2(MATCH_INTERVAL_MS);
@@ -5470,11 +5562,16 @@ async function waitForLiveRelease(url, releaseStamp, timeoutMs) {
5470
5562
  }
5471
5563
  return "unconfirmed";
5472
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
+ }
5473
5570
  async function servesRelease(probe, releaseStamp) {
5474
- const body = await getOnFreshConnection(probe);
5475
- if (body === null) return false;
5571
+ const result = await getOnFreshConnection(probe);
5572
+ if (result === null || result.statusCode !== 200) return false;
5476
5573
  try {
5477
- return JSON.parse(body).release === releaseStamp;
5574
+ return JSON.parse(result.body).release === releaseStamp;
5478
5575
  } catch {
5479
5576
  return false;
5480
5577
  }
@@ -5486,17 +5583,13 @@ function getOnFreshConnection(url, timeoutMs = 5e3) {
5486
5583
  url,
5487
5584
  { method: "GET", agent: false, timeout: timeoutMs, headers: { "cache-control": "no-cache" } },
5488
5585
  (response) => {
5489
- if (response.statusCode !== 200) {
5490
- response.resume();
5491
- resolve23(null);
5492
- return;
5493
- }
5586
+ const statusCode = response.statusCode ?? 0;
5494
5587
  let body = "";
5495
5588
  response.setEncoding("utf8");
5496
5589
  response.on("data", (chunk) => {
5497
5590
  if (body.length < 4096) body += chunk;
5498
5591
  });
5499
- response.on("end", () => resolve23(body));
5592
+ response.on("end", () => resolve23({ statusCode, body }));
5500
5593
  response.on("error", () => resolve23(null));
5501
5594
  }
5502
5595
  );
@@ -5580,7 +5673,7 @@ function describeFilesFailure(status3, body) {
5580
5673
  import * as p6 from "@clack/prompts";
5581
5674
  import { execSync as execSync2 } from "child_process";
5582
5675
  import { createHash as createHash2 } from "crypto";
5583
- 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";
5584
5677
  import { dirname as dirname7, isAbsolute as isAbsolute4, join as join18, relative as relative3, resolve as resolve14, sep as sep3 } from "path";
5585
5678
 
5586
5679
  // src/build/app-id.ts
@@ -5611,7 +5704,7 @@ function removeBuildDevVars(workerDir) {
5611
5704
  }
5612
5705
 
5613
5706
  // src/documentation/deploy.ts
5614
- import { existsSync as existsSync18, readFileSync as readFileSync13 } from "fs";
5707
+ import { existsSync as existsSync18, readFileSync as readFileSync14 } from "fs";
5615
5708
  import { join as join16 } from "path";
5616
5709
  function readDocumentationDeployManifest(appDir, clientDir) {
5617
5710
  if (!existsSync18(join16(appDir, "documentation.json"))) return void 0;
@@ -5621,11 +5714,11 @@ function readDocumentationDeployManifest(appDir, clientDir) {
5621
5714
  "documentation.json exists but the build produced no documentation manifest. Ensure vite.config.ts includes deepSpaceDocumentation() from deepspace/documentation."
5622
5715
  );
5623
5716
  }
5624
- return JSON.parse(readFileSync13(manifestPath, "utf8"));
5717
+ return JSON.parse(readFileSync14(manifestPath, "utf8"));
5625
5718
  }
5626
5719
 
5627
5720
  // src/cli/commands/update/app-migrations.ts
5628
- import { existsSync as existsSync19, readFileSync as readFileSync14 } from "fs";
5721
+ import { existsSync as existsSync19, readFileSync as readFileSync15 } from "fs";
5629
5722
  import { join as join17 } from "path";
5630
5723
 
5631
5724
  // src/shared/protocol/app-migrations.ts
@@ -5704,7 +5797,7 @@ function readAppliedAppMigrations(appDir) {
5704
5797
  if (!existsSync19(path)) return [];
5705
5798
  let parsed;
5706
5799
  try {
5707
- parsed = JSON.parse(readFileSync14(path, "utf8"));
5800
+ parsed = JSON.parse(readFileSync15(path, "utf8"));
5708
5801
  } catch {
5709
5802
  throw new Refusal(
5710
5803
  `${APP_MIGRATIONS_MANIFEST} must contain valid JSON`,
@@ -5770,12 +5863,12 @@ async function buildDeployBundle(options) {
5770
5863
  "build_output_missing"
5771
5864
  );
5772
5865
  }
5773
- const deployConfig = JSON.parse(readFileSync15(deployConfigPath, "utf-8"));
5866
+ const deployConfig = JSON.parse(readFileSync16(deployConfigPath, "utf-8"));
5774
5867
  const outputWranglerPath = resolve14(dirname7(deployConfigPath), deployConfig.configPath);
5775
5868
  if (!existsSync20(outputWranglerPath)) {
5776
5869
  output.die(`Output wrangler.json not found at ${outputWranglerPath}`, "build_output_missing");
5777
5870
  }
5778
- const outputConfig = JSON.parse(readFileSync15(outputWranglerPath, "utf-8"));
5871
+ const outputConfig = JSON.parse(readFileSync16(outputWranglerPath, "utf-8"));
5779
5872
  if (outputConfig.name && outputConfig.name !== appName) {
5780
5873
  const envHint = envName ? ` (--env ${envName})` : "";
5781
5874
  output.die(
@@ -5876,7 +5969,7 @@ function collectWorkerBundle(workerDir, mainPath, clientDir) {
5876
5969
  if (entry.isDirectory()) {
5877
5970
  walk(fullPath);
5878
5971
  } else if (entry.isFile() && WORKER_ES_MODULE.test(entry.name)) {
5879
- modules.push({ name: moduleName(root, fullPath), content: readFileSync15(fullPath, "utf-8") });
5972
+ modules.push({ name: moduleName(root, fullPath), content: readFileSync16(fullPath, "utf-8") });
5880
5973
  }
5881
5974
  }
5882
5975
  };
@@ -5930,7 +6023,7 @@ function clientAppIdRefusal(assets, appId, envName) {
5930
6023
  for (const asset of assets) {
5931
6024
  if (!CLIENT_CODE_ASSET.test(asset.path)) continue;
5932
6025
  if (DOCUMENTATION_OUTPUT.test(asset.path)) continue;
5933
- const source = readFileSync15(asset.sourcePath, "utf-8");
6026
+ const source = readFileSync16(asset.sourcePath, "utf-8");
5934
6027
  if (unsubstituted === void 0 && source.includes(APP_ID_DEFINE)) unsubstituted = asset.path;
5935
6028
  for (const found of source.matchAll(APP_ID_LITERAL)) {
5936
6029
  if (found[0] !== appId && !foreign.has(found[0])) foreign.set(found[0], asset.path);
@@ -5964,7 +6057,7 @@ function collectAssets(dir) {
5964
6057
  if (entry.isDirectory()) {
5965
6058
  walk(fullPath, relativePath);
5966
6059
  } else {
5967
- const bytes = readFileSync15(fullPath);
6060
+ const bytes = readFileSync16(fullPath);
5968
6061
  assets.push({
5969
6062
  path: `/${relativePath}`,
5970
6063
  hash: createHash2("sha256").update(bytes).digest("hex"),
@@ -5985,7 +6078,7 @@ function readDeployAssetConfig(clientDir) {
5985
6078
  ]) {
5986
6079
  const filePath = join18(clientDir, file);
5987
6080
  if (!existsSync20(filePath)) continue;
5988
- const content = readFileSync15(filePath, "utf8");
6081
+ const content = readFileSync16(filePath, "utf8");
5989
6082
  if (content.trim()) config[key] = content;
5990
6083
  }
5991
6084
  return config;
@@ -6310,7 +6403,7 @@ function createDeployOutput(json) {
6310
6403
 
6311
6404
  // src/cli/commands/deploy/request.ts
6312
6405
  import * as p11 from "@clack/prompts";
6313
- import { readFileSync as readFileSync16 } from "fs";
6406
+ import { readFileSync as readFileSync17 } from "fs";
6314
6407
 
6315
6408
  // src/cli/commands/deploy/repository.ts
6316
6409
  import * as p9 from "@clack/prompts";
@@ -7481,7 +7574,7 @@ async function uploadDeployAssetsWithAuth(options, auth2) {
7481
7574
  // against the plan before it touches storage, and a stream body
7482
7575
  // would be sent chunked with no length at all; a retry also needs a
7483
7576
  // body it can send again.
7484
- body: readFileSync16(asset.sourcePath)
7577
+ body: readFileSync17(asset.sourcePath)
7485
7578
  }),
7486
7579
  { attempts: ASSET_UPLOAD_ATTEMPTS, timeoutMs: ASSET_UPLOAD_ATTEMPT_TIMEOUT_MS }
7487
7580
  )
@@ -7526,7 +7619,10 @@ function isDeployServiceResourceLimit(status3, error) {
7526
7619
  }
7527
7620
  function formatDeployWorkerError(status3, error, code) {
7528
7621
  const detail = error ?? `Deployment error (${status3})`;
7529
- 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") {
7530
7626
  return detail;
7531
7627
  }
7532
7628
  if (isDeployServiceResourceLimit(status3, error)) {
@@ -7553,8 +7649,9 @@ function errorMessage5(error) {
7553
7649
 
7554
7650
  // src/cli/commands/deploy/lock.ts
7555
7651
  import { randomUUID as randomUUID2 } from "crypto";
7556
- 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";
7557
7653
  import { dirname as dirname8, join as join20 } from "path";
7654
+ var DEPLOY_LOCK_MAX_AGE_MS = 10 * 6e4;
7558
7655
  function deployLockPath(appDir) {
7559
7656
  return join20(appDir, ".deepspace", "deploy.lock");
7560
7657
  }
@@ -7566,29 +7663,58 @@ function acquireDeployLock(appDir) {
7566
7663
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
7567
7664
  token: randomUUID2()
7568
7665
  };
7569
- try {
7570
- writeFileSync6(path, JSON.stringify(record), { flag: "wx" });
7571
- } catch (err) {
7572
- if (err.code !== "EEXIST") throw err;
7573
- const holder = readLock(path);
7574
- throw new Refusal(
7575
- (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.`,
7576
- "deploy_in_progress",
7577
- {
7578
- extra: {
7579
- lockPath: path,
7580
- ...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
+ }
7581
7687
  }
7582
- }
7583
- );
7688
+ );
7689
+ }
7584
7690
  }
7585
- return () => {
7586
- 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);
7587
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);
7588
7714
  }
7589
7715
  function readLock(path) {
7590
7716
  try {
7591
- const parsed = JSON.parse(readFileSync17(path, "utf-8"));
7717
+ const parsed = JSON.parse(readFileSync18(path, "utf-8"));
7592
7718
  return typeof parsed.pid === "number" && typeof parsed.startedAt === "string" && typeof parsed.token === "string" ? { pid: parsed.pid, startedAt: parsed.startedAt, token: parsed.token } : null;
7593
7719
  } catch {
7594
7720
  return null;
@@ -7823,11 +7949,20 @@ async function runLockedDeploy(args, output, appDir, envName) {
7823
7949
  );
7824
7950
  }
7825
7951
  let serving = "unverifiable";
7952
+ let dataPlane = "unconfirmed";
7826
7953
  const edgeWaitStarted = Date.now();
7827
7954
  if (body.url) {
7828
7955
  spinner2.message("Waiting for the edge to serve this release...");
7829
7956
  serving = await waitForLiveRelease(body.url, body.releaseStamp, 9e4);
7830
- 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
+ }
7831
7966
  if (serving !== "confirmed") {
7832
7967
  spinner2.stop(
7833
7968
  serving === "unconfirmed" ? "Deployed \u2014 the edge is still rolling this release out" : "Deployed \u2014 serving could not be verified from here"
@@ -7846,6 +7981,7 @@ async function runLockedDeploy(args, output, appDir, envName) {
7846
7981
  releaseId: body.releaseId ?? null,
7847
7982
  bundleRetained: body.bundleRetained ?? null,
7848
7983
  serving,
7984
+ dataPlane,
7849
7985
  recoverable: repository.recoverable,
7850
7986
  ...worktree,
7851
7987
  ...renameFields,
@@ -7857,7 +7993,7 @@ async function runLockedDeploy(args, output, appDir, envName) {
7857
7993
  }
7858
7994
  }
7859
7995
  spinner2.stop(
7860
- `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)`
7861
7997
  );
7862
7998
  p12.log.success(`Live at: ${body.url}`);
7863
7999
  await syncPostDeployCommerce(appDir, appId, output.nonInteractive);
@@ -7870,6 +8006,7 @@ async function runLockedDeploy(args, output, appDir, envName) {
7870
8006
  // Always present, so a caller branches on one field instead of
7871
8007
  // inferring success from an absent one.
7872
8008
  serving,
8009
+ dataPlane,
7873
8010
  releaseId: body.releaseId ?? null,
7874
8011
  bundleRetained: body.bundleRetained ?? null,
7875
8012
  recoverable: repository.recoverable,
@@ -7999,7 +8136,7 @@ var undeploy_default = defineDeepspaceCommand({
7999
8136
  token = await ensureToken();
8000
8137
  } catch (err) {
8001
8138
  throw new Refusal(err instanceof Error ? err.message : String(err), "not_authenticated", {
8002
- action: cliAction("deepspace", "auth", "login")
8139
+ action: loginAction()
8003
8140
  });
8004
8141
  }
8005
8142
  let appId;
@@ -8070,13 +8207,20 @@ var undeploy_default = defineDeepspaceCommand({
8070
8207
  }
8071
8208
  const hosts = body.releasedHosts ?? [];
8072
8209
  const alreadyUndeployed = hosts.length === 0;
8073
- 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
+ );
8074
8218
  if (!args.json) {
8075
8219
  p13.outro(
8076
- 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.`
8077
8221
  );
8078
8222
  }
8079
- return { data: { appId, releasedHosts: hosts, alreadyUndeployed } };
8223
+ return { data: { appId, releasedHosts: hosts, alreadyUndeployed, released } };
8080
8224
  }
8081
8225
  });
8082
8226
 
@@ -8091,7 +8235,7 @@ var whoami_default = defineDeepspaceCommand({
8091
8235
  } catch (err) {
8092
8236
  const msg = err instanceof Error ? err.message : "Not logged in. Run `deepspace auth login` first.";
8093
8237
  throw isNetworkError(err) ? new Refusal(msg, "network_error") : new Refusal(msg, "not_authenticated", {
8094
- action: cliAction("deepspace", "auth", "login")
8238
+ action: loginAction()
8095
8239
  });
8096
8240
  }
8097
8241
  let payload;
@@ -8099,7 +8243,7 @@ var whoami_default = defineDeepspaceCommand({
8099
8243
  payload = decodeJwtPayload(jwt);
8100
8244
  } catch {
8101
8245
  throw new Refusal("Malformed session token. Run `deepspace auth login` again.", "not_authenticated", {
8102
- action: cliAction("deepspace", "auth", "login")
8246
+ action: loginAction()
8103
8247
  });
8104
8248
  }
8105
8249
  const isTestAccount = !!payload.email?.endsWith("@deepspace.test");
@@ -8610,7 +8754,7 @@ var usage_default = defineDeepspaceCommand({
8610
8754
  });
8611
8755
 
8612
8756
  // src/cli/commands/add.ts
8613
- 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";
8614
8758
  import { dirname as dirname10, join as join23, resolve as resolve17 } from "path";
8615
8759
  import { pathToFileURL as pathToFileURL3 } from "url";
8616
8760
 
@@ -8623,10 +8767,10 @@ import {
8623
8767
  mkdirSync as mkdirSync9,
8624
8768
  mkdtempSync,
8625
8769
  openSync as openSync2,
8626
- readFileSync as readFileSync18,
8770
+ readFileSync as readFileSync19,
8627
8771
  readSync,
8628
8772
  readdirSync as readdirSync8,
8629
- rmSync as rmSync7,
8773
+ rmSync as rmSync8,
8630
8774
  writeFileSync as writeFileSync7
8631
8775
  } from "fs";
8632
8776
  import { spawnSync as spawnSync7 } from "child_process";
@@ -8640,7 +8784,7 @@ function featurePackageRoot(moduleUrl = import.meta.url) {
8640
8784
  const packagePath = join22(dir, "package.json");
8641
8785
  if (existsSync22(packagePath)) {
8642
8786
  try {
8643
- const pkg2 = JSON.parse(readFileSync18(packagePath, "utf-8"));
8787
+ const pkg2 = JSON.parse(readFileSync19(packagePath, "utf-8"));
8644
8788
  if (pkg2.name === "deepspace") return dir;
8645
8789
  } catch {
8646
8790
  }
@@ -8657,7 +8801,7 @@ function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
8657
8801
  const configPath = join22(packageRoot, "features", id, "feature.json");
8658
8802
  if (!existsSync22(configPath)) return null;
8659
8803
  try {
8660
- const config = JSON.parse(readFileSync18(configPath, "utf-8"));
8804
+ const config = JSON.parse(readFileSync19(configPath, "utf-8"));
8661
8805
  if (typeof config.id !== "string" || typeof config.name !== "string" || !Array.isArray(config.files)) {
8662
8806
  return null;
8663
8807
  }
@@ -8709,7 +8853,7 @@ function copyFile(src, dest) {
8709
8853
  function copyFileWithImportShift(src, dest, levels) {
8710
8854
  mkdirSync9(dirname9(dest), { recursive: true });
8711
8855
  const prefix = "../".repeat(levels);
8712
- const shifted = readFileSync18(src, "utf-8").replace(
8856
+ const shifted = readFileSync19(src, "utf-8").replace(
8713
8857
  /(from\s+|import\s+|import\()(['"])(\.\.\/)/g,
8714
8858
  `$1$2${prefix}$3`
8715
8859
  );
@@ -8768,7 +8912,7 @@ function integrateSchema(config, targetDir, emit) {
8768
8912
  if (!existsSync22(schemasPath)) {
8769
8913
  return [unresolvedSchema(config.schema, "src/schemas.ts was not found.", emit)];
8770
8914
  }
8771
- let content = readFileSync18(schemasPath, "utf-8");
8915
+ let content = readFileSync19(schemasPath, "utf-8");
8772
8916
  const { exportName, importPath, spreadOperator } = config.schema;
8773
8917
  const importPattern = new RegExp(`^import\\s+\\{[^}]*\\b${exportName}\\b`, "m");
8774
8918
  if (importPattern.test(content)) {
@@ -8825,7 +8969,7 @@ function integrateActions(config, targetDir, emit) {
8825
8969
  if (!existsSync22(actionsPath)) {
8826
8970
  return [unresolvedActions(config.actions, "src/actions/index.ts was not found.", emit)];
8827
8971
  }
8828
- let content = readFileSync18(actionsPath, "utf-8");
8972
+ let content = readFileSync19(actionsPath, "utf-8");
8829
8973
  const registryPattern = /export const actions:\s*Record<string,\s*ActionHandler(?:<[^>]*>)?>\s*=\s*\{/;
8830
8974
  if (!registryPattern.test(content)) {
8831
8975
  const reason = "src/actions/index.ts has an unexpected structure.";
@@ -8880,7 +9024,7 @@ function integrateCode(config, targetDir, emit) {
8880
9024
  });
8881
9025
  continue;
8882
9026
  }
8883
- const original = readFileSync18(targetPath, "utf-8");
9027
+ const original = readFileSync19(targetPath, "utf-8");
8884
9028
  if (original.includes(insertion.insert.trim())) {
8885
9029
  emit(` Integration already present: ${insertion.file}`);
8886
9030
  continue;
@@ -8918,7 +9062,7 @@ ${content.slice(
8918
9062
  function integrateIgnore(config, targetDir) {
8919
9063
  if (!config.ignore?.length) return;
8920
9064
  const targetPath = join22(targetDir, ".gitignore");
8921
- const current = existsSync22(targetPath) ? readFileSync18(targetPath, "utf-8") : "";
9065
+ const current = existsSync22(targetPath) ? readFileSync19(targetPath, "utf-8") : "";
8922
9066
  const present = new Set(current.split(/\r?\n/).map((line) => line.trim()));
8923
9067
  const missing = config.ignore.filter((pattern) => !present.has(pattern));
8924
9068
  if (missing.length === 0) return;
@@ -8938,7 +9082,7 @@ function integrateWrangler(config, targetDir, emit) {
8938
9082
  if (!existsSync22(targetPath)) {
8939
9083
  return [{ area: "wrangler", reason: "wrangler.toml was not found.", steps }];
8940
9084
  }
8941
- let source = readFileSync18(targetPath, "utf-8");
9085
+ let source = readFileSync19(targetPath, "utf-8");
8942
9086
  if (runWorkerFirst && !/^\[assets\]\s*$/m.test(source)) {
8943
9087
  return [{ area: "wrangler", reason: "wrangler.toml has no [assets] section.", steps }];
8944
9088
  }
@@ -9035,7 +9179,7 @@ function integrateRoute(config, targetDir, emit) {
9035
9179
  if (!existsSync22(navPath)) {
9036
9180
  return [unresolvedRoute(config.route, config, "src/nav.ts was not found.", emit)];
9037
9181
  }
9038
- let content = readFileSync18(navPath, "utf-8");
9182
+ let content = readFileSync19(navPath, "utf-8");
9039
9183
  const routePath = config.route.path;
9040
9184
  if (content.includes(`'${routePath}'`)) {
9041
9185
  emit(` Nav already present: ${routePath}`);
@@ -9097,7 +9241,7 @@ function runPackageInstall(packageManager, targetDir, outputVisible) {
9097
9241
  };
9098
9242
  } finally {
9099
9243
  if (outputFd !== void 0) closeSync2(outputFd);
9100
- rmSync7(captureDir, { recursive: true, force: true });
9244
+ rmSync8(captureDir, { recursive: true, force: true });
9101
9245
  }
9102
9246
  }
9103
9247
  function integrateDependencies(config, targetDir, runInstall, outputVisible, emit) {
@@ -9116,7 +9260,7 @@ function integrateDependencies(config, targetDir, runInstall, outputVisible, emi
9116
9260
  }
9117
9261
  let pkg2;
9118
9262
  try {
9119
- pkg2 = JSON.parse(readFileSync18(packagePath, "utf-8"));
9263
+ pkg2 = JSON.parse(readFileSync19(packagePath, "utf-8"));
9120
9264
  } catch (error) {
9121
9265
  emit(
9122
9266
  ` Could not patch package.json (parse error: ${error instanceof Error ? error.message : String(error)})`
@@ -9176,7 +9320,7 @@ ${child.diagnosticTail}` : "";
9176
9320
  }
9177
9321
  function dependencySnapshot(targetDir) {
9178
9322
  try {
9179
- const pkg2 = JSON.parse(readFileSync18(join22(targetDir, "package.json"), "utf-8"));
9323
+ const pkg2 = JSON.parse(readFileSync19(join22(targetDir, "package.json"), "utf-8"));
9180
9324
  return {
9181
9325
  dependencies: pkg2.dependencies ?? {},
9182
9326
  devDependencies: pkg2.devDependencies ?? {}
@@ -9424,7 +9568,7 @@ function isDeepSpaceApp(dir) {
9424
9568
  const packagePath = join23(dir, "package.json");
9425
9569
  if (!existsSync23(packagePath)) return false;
9426
9570
  try {
9427
- const pkg2 = JSON.parse(readFileSync19(packagePath, "utf-8"));
9571
+ const pkg2 = JSON.parse(readFileSync20(packagePath, "utf-8"));
9428
9572
  return Boolean(pkg2.dependencies?.deepspace || pkg2.devDependencies?.deepspace);
9429
9573
  } catch {
9430
9574
  return false;
@@ -9896,10 +10040,10 @@ var domain_default = defineCommand5({
9896
10040
  // src/cli/commands/app-files.ts
9897
10041
  import { defineCommand as defineCommand6 } from "citty";
9898
10042
  import { basename as basename4, resolve as resolve18 } from "path";
9899
- import { existsSync as existsSync24, statSync as statSync4 } from "fs";
10043
+ import { existsSync as existsSync24, statSync as statSync5 } from "fs";
9900
10044
 
9901
10045
  // src/cli/lib/app-files-api.ts
9902
- import { createWriteStream, openAsBlob, statSync as statSync3 } from "fs";
10046
+ import { createWriteStream, openAsBlob, statSync as statSync4 } from "fs";
9903
10047
  import { basename as basename3, extname } from "path";
9904
10048
  import { Readable } from "stream";
9905
10049
  import { pipeline } from "stream/promises";
@@ -10009,7 +10153,7 @@ Content-Type: ${contentType}\r
10009
10153
  }
10010
10154
  async function uploadAppFile(baseUrl, token, appId, localPath, key, onProgress) {
10011
10155
  const path = filesPath(appId, `/upload?key=${encodeKeyPath(key)}`);
10012
- const size = statSync3(localPath).size;
10156
+ const size = statSync4(localPath).size;
10013
10157
  if (size > MAX_APP_FILE_BYTES) {
10014
10158
  throw new ApiError(oversizeMessage(size), 0, "too_large", path);
10015
10159
  }
@@ -10175,7 +10319,7 @@ var put = defineDeepspaceCommand({
10175
10319
  },
10176
10320
  async run({ args }) {
10177
10321
  const localPath = resolve18(String(args.file));
10178
- if (!existsSync24(localPath) || !statSync4(localPath).isFile()) {
10322
+ if (!existsSync24(localPath) || !statSync5(localPath).isFile()) {
10179
10323
  throw new Refusal(`No such file: ${args.file}`, "file_not_found");
10180
10324
  }
10181
10325
  const key = requireKey(args.key ?? basename4(localPath));
@@ -10685,7 +10829,7 @@ var transfer_default = defineCommand8({
10685
10829
  import { defineCommand as defineCommand9 } from "citty";
10686
10830
 
10687
10831
  // src/cli/commands/_invoke-impl.ts
10688
- import { readFileSync as readFileSync20 } from "fs";
10832
+ import { readFileSync as readFileSync21 } from "fs";
10689
10833
  import * as p15 from "@clack/prompts";
10690
10834
  var API_URL7 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
10691
10835
  var DEFAULT_TIMEOUT_MS = 12e4;
@@ -10732,7 +10876,7 @@ async function resolveBody(opts) {
10732
10876
  "no_stdin"
10733
10877
  );
10734
10878
  }
10735
- 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");
10736
10880
  if (opts.bodyFile === "-" && raw.trim() === "") {
10737
10881
  throw new Refusal(
10738
10882
  "Nothing arrived on stdin \u2014 check the command feeding this one, or pass --body / a file path.",
@@ -10919,7 +11063,7 @@ async function runInvoke(args) {
10919
11063
  } catch (err) {
10920
11064
  const msg = err instanceof Error ? err.message : String(err);
10921
11065
  throw new Refusal(msg, "not_authenticated", {
10922
- action: cliAction("deepspace", "auth", "login")
11066
+ action: loginAction()
10923
11067
  });
10924
11068
  }
10925
11069
  const controller = new AbortController();
@@ -10975,7 +11119,7 @@ async function runInvoke(args) {
10975
11119
  delete rest.message;
10976
11120
  delete rest.code;
10977
11121
  throw new Refusal(lines.join("\n"), code, {
10978
- ...res.status === 401 ? { action: cliAction("deepspace", "auth", "login") } : {},
11122
+ ...res.status === 401 ? { action: loginAction() } : {},
10979
11123
  extra: rest
10980
11124
  });
10981
11125
  }
@@ -11381,7 +11525,7 @@ async function callAgent(target2, path, init, timeout) {
11381
11525
  throw new Refusal(
11382
11526
  "Authentication was rejected. Run `deepspace auth login` and try again.",
11383
11527
  "not_authenticated",
11384
- { action: cliAction("deepspace", "auth", "login") }
11528
+ { action: loginAction() }
11385
11529
  );
11386
11530
  }
11387
11531
  }
@@ -11472,7 +11616,7 @@ var agent_default = defineCommand10({
11472
11616
 
11473
11617
  // src/cli/commands/feedback.ts
11474
11618
  import * as p16 from "@clack/prompts";
11475
- import { existsSync as existsSync25, readFileSync as readFileSync21 } from "fs";
11619
+ import { existsSync as existsSync25, readFileSync as readFileSync22 } from "fs";
11476
11620
  import { dirname as dirname11, join as join24 } from "path";
11477
11621
  import { fileURLToPath as fileURLToPath2 } from "url";
11478
11622
  import { release } from "os";
@@ -11501,7 +11645,7 @@ function readCliVersion() {
11501
11645
  ];
11502
11646
  for (const path of candidates) {
11503
11647
  if (!existsSync25(path)) continue;
11504
- const pkg2 = JSON.parse(readFileSync21(path, "utf-8"));
11648
+ const pkg2 = JSON.parse(readFileSync22(path, "utf-8"));
11505
11649
  if (pkg2.name === "deepspace" && pkg2.version) return pkg2.version;
11506
11650
  }
11507
11651
  } catch {
@@ -11676,7 +11820,7 @@ Context: deepspace ${payload.cliVersion} \xB7 ${payload.nodeVersion} \xB7 ${payl
11676
11820
  throw new Refusal(
11677
11821
  err.message ?? "Not signed in. Run `deepspace auth login`.",
11678
11822
  "not_authenticated",
11679
- { action: cliAction("deepspace", "auth", "login") }
11823
+ { action: loginAction() }
11680
11824
  );
11681
11825
  }
11682
11826
  const result = await api3(token, "/api/feedback", {
@@ -11697,7 +11841,7 @@ Context: deepspace ${payload.cliVersion} \xB7 ${payload.nodeVersion} \xB7 ${payl
11697
11841
 
11698
11842
  // src/cli/commands/secrets.ts
11699
11843
  import { defineCommand as defineCommand11 } from "citty";
11700
- import { readFileSync as readFileSync22 } from "fs";
11844
+ import { readFileSync as readFileSync23 } from "fs";
11701
11845
 
11702
11846
  // src/cli/lib/citty-args.ts
11703
11847
  function dedupePositionals(bound, rest) {
@@ -12012,7 +12156,7 @@ async function readUploadSource(file) {
12012
12156
  return readStreamText(process.stdin, MAX_STDIN_BYTES);
12013
12157
  }
12014
12158
  try {
12015
- return readFileSync22(file, "utf-8");
12159
+ return readFileSync23(file, "utf-8");
12016
12160
  } catch (err) {
12017
12161
  const errno = err.code;
12018
12162
  if (typeof errno !== "string" || errno.length === 0) throw err;
@@ -12062,47 +12206,6 @@ var download = defineCommand11({
12062
12206
  process.stdout.write(formatSecretsDownload(secrets, format));
12063
12207
  }
12064
12208
  });
12065
- var pull = defineCommand11({
12066
- meta: { name: "pull", description: "Refresh the .dev.vars cache from the app store" },
12067
- args: { ...COMMON_ARGS },
12068
- async run({ args }) {
12069
- const wranglerEnv = args.env?.trim() || void 0;
12070
- const appDir = findAppDir();
12071
- if (!appDir)
12072
- throw new InputError(
12073
- "Run from a DeepSpace app directory (one containing wrangler.toml).",
12074
- "not_in_app_repo"
12075
- );
12076
- const t = await resolveTarget(args, { register: true });
12077
- const ownerId = decodeJwtPayload(t.token).sub;
12078
- const refreshed = await refreshSecretsCache(
12079
- DEPLOY_URL12,
12080
- t.token,
12081
- t.appId,
12082
- wranglerEnv,
12083
- t.configName
12084
- );
12085
- try {
12086
- await writeDevVars(appDir, ownerId, t.token, wranglerEnv, {
12087
- appId: t.appId,
12088
- generatedSecretsCache: refreshed.rendered
12089
- });
12090
- } catch (err) {
12091
- if (args.app && err instanceof Refusal && err.code === "app_not_found") {
12092
- throw new Refusal(err.message, err.code);
12093
- }
12094
- throw err;
12095
- }
12096
- const count = Object.keys(refreshed.pulled?.values ?? {}).length;
12097
- ok(
12098
- args.json === true,
12099
- { appId: t.appId, config: t.configName, pulled: count },
12100
- () => console.log(
12101
- refreshed.pulled ? `Pulled ${count} secrets (${t.configName}) into .dev.vars.` : `Config ${t.configName} does not exist yet; regenerated .dev.vars without app secrets.`
12102
- )
12103
- );
12104
- }
12105
- });
12106
12209
  var configsList = defineCommand11({
12107
12210
  meta: { name: "list", description: "List the app\u2019s configs" },
12108
12211
  args: { ...COMMON_ARGS },
@@ -12212,7 +12315,6 @@ var secrets_default = defineCommand11({
12212
12315
  delete: del2,
12213
12316
  upload,
12214
12317
  download,
12215
- pull,
12216
12318
  configs
12217
12319
  }
12218
12320
  });
@@ -12369,7 +12471,7 @@ var init_default = defineDeepspaceCommand({
12369
12471
  // src/cli/commands/status.ts
12370
12472
  import { defineCommand as defineCommand12 } from "citty";
12371
12473
  import * as p17 from "@clack/prompts";
12372
- import { existsSync as existsSync26, readFileSync as readFileSync23 } from "fs";
12474
+ import { existsSync as existsSync26, readFileSync as readFileSync24 } from "fs";
12373
12475
  function timeAgo(iso) {
12374
12476
  const ms = Date.now() - Date.parse(iso);
12375
12477
  if (!Number.isFinite(ms) || ms < 0) return iso;
@@ -12473,7 +12575,7 @@ var status_default = defineCommand12({
12473
12575
  let undecodableToken = false;
12474
12576
  try {
12475
12577
  const payload = decodeJwtPayload(
12476
- readFileSync23(TOKEN_PATH, "utf-8").trim()
12578
+ readFileSync24(TOKEN_PATH, "utf-8").trim()
12477
12579
  );
12478
12580
  if (payload.email) who = payload.email;
12479
12581
  userId = payload.sub ?? null;
@@ -13321,7 +13423,7 @@ var pull_default = defineDeepspaceCommand({
13321
13423
 
13322
13424
  // src/cli/commands/clone.ts
13323
13425
  import * as p20 from "@clack/prompts";
13324
- 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";
13325
13427
  import { resolve as resolve19, basename as basename5, join as join25 } from "path";
13326
13428
  function cleanFailedCloneDir(dir) {
13327
13429
  let entries;
@@ -13332,7 +13434,7 @@ function cleanFailedCloneDir(dir) {
13332
13434
  }
13333
13435
  if (entries.length === 1 && entries[0] === ".git") {
13334
13436
  try {
13335
- rmSync8(join25(dir, ".git"), { recursive: true, force: true });
13437
+ rmSync9(join25(dir, ".git"), { recursive: true, force: true });
13336
13438
  return [];
13337
13439
  } catch {
13338
13440
  return entries;
@@ -13434,7 +13536,7 @@ var clone_default = defineDeepspaceCommand({
13434
13536
  },
13435
13537
  // One runnable follow-up instead of the old five-line "Next steps" block:
13436
13538
  // nothing else in the clone works until the deps are installed. Secrets
13437
- // (`deepspace secrets pull`) and `dev` follow from there.
13539
+ // and `dev` (which refreshes `.dev.vars` itself) follow from there.
13438
13540
  action: { cwd: dir, argv: [detectPackageManager(dir), "install"] }
13439
13541
  };
13440
13542
  }
@@ -13574,6 +13676,12 @@ var rollback_default = defineDeepspaceCommand({
13574
13676
  type: "boolean",
13575
13677
  description: "Proceed even though the target release declares fewer Durable Object classes than the current one \u2014 THEIR STORED DATA IS DELETED.",
13576
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
13577
13685
  }
13578
13686
  },
13579
13687
  async run({ args }) {
@@ -13605,7 +13713,15 @@ var rollback_default = defineDeepspaceCommand({
13605
13713
  if ("error" in picked) throw new Refusal(picked.error, picked.code);
13606
13714
  releaseId = picked.releaseId;
13607
13715
  }
13608
- 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`);
13609
13725
  let result;
13610
13726
  try {
13611
13727
  result = await apiFetch(DEPLOY_URL15, token, `/api/deploy/${appId}/rollback`, {
@@ -13822,10 +13938,10 @@ import * as p24 from "@clack/prompts";
13822
13938
  import {
13823
13939
  existsSync as existsSync28,
13824
13940
  mkdirSync as mkdirSync10,
13825
- readFileSync as readFileSync24,
13941
+ readFileSync as readFileSync25,
13826
13942
  realpathSync as realpathSync6,
13827
13943
  renameSync as renameSync2,
13828
- statSync as statSync5,
13944
+ statSync as statSync6,
13829
13945
  writeFileSync as writeFileSync8
13830
13946
  } from "fs";
13831
13947
  import { dirname as dirname12, isAbsolute as isAbsolute6, join as join26, relative as relative4, sep as sep4 } from "path";
@@ -13885,7 +14001,7 @@ function isManagedWorkspaceWorktree(worktreeRoot, workspaceId) {
13885
14001
  const markerPath = worktreeMarkerPath(worktreeRoot);
13886
14002
  if (!markerPath) return false;
13887
14003
  try {
13888
- const marker = JSON.parse(readFileSync24(markerPath, "utf-8"));
14004
+ const marker = JSON.parse(readFileSync25(markerPath, "utf-8"));
13889
14005
  return marker.version === 1 && marker.workspaceId === workspaceId && marker.mode === "managed-linked";
13890
14006
  } catch {
13891
14007
  return false;
@@ -13928,7 +14044,7 @@ function rematerializeWorkspaceWorktree(appDir, worktreeRoot, branch, workspaceI
13928
14044
  function inOwnLinkedWorktree(appDir, id) {
13929
14045
  if (workspaceIdFromBranch(currentBranch(appDir)) !== id) return false;
13930
14046
  try {
13931
- return statSync5(join26(repoToplevel(appDir), ".git")).isFile();
14047
+ return statSync6(join26(repoToplevel(appDir), ".git")).isFile();
13932
14048
  } catch {
13933
14049
  return false;
13934
14050
  }
@@ -14374,7 +14490,7 @@ var newWorkspaceCommand = defineDeepspaceCommand({
14374
14490
 
14375
14491
  // src/cli/commands/workspace/attach.ts
14376
14492
  import * as p26 from "@clack/prompts";
14377
- 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";
14378
14494
  import { isAbsolute as isAbsolute8, resolve as resolve21 } from "path";
14379
14495
  function samePath(a, b) {
14380
14496
  const real = (p34) => {
@@ -14405,7 +14521,7 @@ function cleanFailedFreshAttachDir(dir) {
14405
14521
  return entries;
14406
14522
  }
14407
14523
  try {
14408
- rmSync9(dir, { recursive: true, force: true });
14524
+ rmSync10(dir, { recursive: true, force: true });
14409
14525
  return [];
14410
14526
  } catch {
14411
14527
  return entries.length > 0 ? entries : ["<cleanup-failed>"];
@@ -14941,13 +15057,13 @@ import * as p30 from "@clack/prompts";
14941
15057
 
14942
15058
  // src/cli/lib/validation.ts
14943
15059
  import { sync as spawnSync8 } from "cross-spawn";
14944
- import { readFileSync as readFileSync25 } from "fs";
15060
+ import { readFileSync as readFileSync26 } from "fs";
14945
15061
  import { join as join27 } from "path";
14946
15062
  var MAX_SUMMARY_CHARS = 4e3;
14947
15063
  var MAX_OUTPUT_BUFFER = 64 * 1024 * 1024;
14948
15064
  function configuredCommand(appDir) {
14949
15065
  try {
14950
- const pkg2 = JSON.parse(readFileSync25(join27(appDir, "package.json"), "utf-8"));
15066
+ const pkg2 = JSON.parse(readFileSync26(join27(appDir, "package.json"), "utf-8"));
14951
15067
  if (pkg2.scripts?.validate) return `${detectPackageManager(appDir)} run --silent validate`;
14952
15068
  } catch {
14953
15069
  }
@@ -15899,13 +16015,13 @@ function reportSource(appId, state, json) {
15899
16015
 
15900
16016
  // src/cli/commands/update.ts
15901
16017
  import * as p33 from "@clack/prompts";
15902
- import { existsSync as existsSync30, readFileSync as readFileSync26 } from "fs";
16018
+ import { existsSync as existsSync30, readFileSync as readFileSync27 } from "fs";
15903
16019
  import { join as join28, resolve as resolve22 } from "path";
15904
16020
 
15905
16021
  // package.json
15906
16022
  var package_default = {
15907
16023
  name: "deepspace",
15908
- version: "0.29.1",
16024
+ version: "0.30.0",
15909
16025
  engines: {
15910
16026
  node: ">=22.15.0 <23 || >=24 <25 || >=26 <27"
15911
16027
  },
@@ -16087,7 +16203,7 @@ function readManifest(appDir) {
16087
16203
  throw new Refusal("This app has no package.json \u2014 nothing to update.", "not_in_app_repo");
16088
16204
  }
16089
16205
  try {
16090
- return JSON.parse(readFileSync26(path, "utf8"));
16206
+ return JSON.parse(readFileSync27(path, "utf8"));
16091
16207
  } catch {
16092
16208
  throw new Refusal("package.json must contain valid JSON.", "invalid_package_manifest");
16093
16209
  }
@@ -16123,7 +16239,7 @@ function usersSchemaVisibilityUpgradeInstruction(appDir) {
16123
16239
  const schemaName = "src/schemas/users-schema.ts";
16124
16240
  const path = join28(appDir, schemaName);
16125
16241
  if (!existsSync30(path)) return null;
16126
- const source = readFileSync26(path, "utf8");
16242
+ const source = readFileSync27(path, "utf8");
16127
16243
  if (!/\bmember\s*:\s*\{[^}]*\bread\s*:\s*true\b/s.test(source)) return null;
16128
16244
  return `${schemaName}: review member user-row visibility at ${USERS_SCHEMA_VISIBILITY_GUIDE}; leave the app-owned schema unchanged until you choose its policy.`;
16129
16245
  }
@@ -16223,7 +16339,7 @@ process.stdout.on("error", (error) => {
16223
16339
  throw error;
16224
16340
  });
16225
16341
  var __dirname = dirname13(fileURLToPath3(import.meta.url));
16226
- var pkg = JSON.parse(readFileSync27(join29(__dirname, "..", "package.json"), "utf-8"));
16342
+ var pkg = JSON.parse(readFileSync28(join29(__dirname, "..", "package.json"), "utf-8"));
16227
16343
  if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync31("/etc/ssl/certs/ca-certificates.crt")) {
16228
16344
  process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
16229
16345
  }