deepspace 0.24.0 → 0.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  renderUsage
9
9
  } from "citty";
10
10
  import { sync as spawnSync8 } from "cross-spawn";
11
- import { readFileSync as readFileSync28, existsSync as existsSync29 } from "fs";
11
+ import { readFileSync as readFileSync27, existsSync as existsSync29 } from "fs";
12
12
  import { dirname as dirname13, join as join29 } from "path";
13
13
  import { fileURLToPath as fileURLToPath3 } from "url";
14
14
 
@@ -133,6 +133,41 @@ function printAction(action) {
133
133
  p2.log.message(`Next: ${prefix}${command}`);
134
134
  }
135
135
 
136
+ // src/cli/lib/fetch-retry.ts
137
+ async function retryTransient(run2, {
138
+ delaysMs,
139
+ shouldRetryError = () => true,
140
+ shouldRetryResult = () => false
141
+ }) {
142
+ for (let attempt = 0; ; attempt++) {
143
+ try {
144
+ const result = await run2();
145
+ if (!shouldRetryResult(result) || attempt >= delaysMs.length) return result;
146
+ } catch (error) {
147
+ if (!shouldRetryError(error) || attempt >= delaysMs.length) throw error;
148
+ }
149
+ await new Promise((resolve23) => setTimeout(resolve23, delaysMs[attempt]));
150
+ }
151
+ }
152
+ async function fetchWithTransientRetry(url, makeInit, {
153
+ attempts = 4,
154
+ retryServerErrors = true,
155
+ timeoutMs = 6e4
156
+ } = {}) {
157
+ return retryTransient(
158
+ async () => {
159
+ return await fetch(url, { ...makeInit(), signal: AbortSignal.timeout(timeoutMs) });
160
+ },
161
+ {
162
+ delaysMs: Array.from(
163
+ { length: Math.max(0, attempts - 1) },
164
+ (_, attempt) => Math.min(8e3, 500 * 2 ** attempt)
165
+ ),
166
+ shouldRetryResult: (response) => retryServerErrors && (response.status >= 500 || response.status === 408 || response.status === 429)
167
+ }
168
+ );
169
+ }
170
+
136
171
  // src/cli/lib/api.ts
137
172
  var ApiError = class extends Error {
138
173
  constructor(message, status3, code, apiPath, details) {
@@ -159,11 +194,6 @@ async function apiFetch(baseUrl, token, path, init, isRetry = false) {
159
194
  ...init?.headers ?? {}
160
195
  }
161
196
  });
162
- if (res.status === 401 && !isRetry && refreshAuthToken) {
163
- const fresh = await refreshAuthToken();
164
- if (fresh && fresh !== token) return apiFetch(baseUrl, fresh, path, init, true);
165
- }
166
- text2 = await res.text();
167
197
  } catch (err) {
168
198
  throw new ApiError(
169
199
  `Could not reach the deploy service at ${baseUrl}: ${err instanceof Error ? err.message : String(err)}. Check your connection (and DEEPSPACE_DEPLOY_URL), then retry.`,
@@ -172,6 +202,20 @@ async function apiFetch(baseUrl, token, path, init, isRetry = false) {
172
202
  path
173
203
  );
174
204
  }
205
+ if (res.status === 401 && !isRetry && refreshAuthToken) {
206
+ const fresh = await refreshAuthToken();
207
+ if (fresh && fresh !== token) return apiFetch(baseUrl, fresh, path, init, true);
208
+ }
209
+ try {
210
+ text2 = await res.text();
211
+ } catch (err) {
212
+ throw new ApiError(
213
+ `Could not read the deploy service response at ${baseUrl}: ${err instanceof Error ? err.message : String(err)}. Check your connection (and DEEPSPACE_DEPLOY_URL), then retry.`,
214
+ 0,
215
+ "network_error",
216
+ path
217
+ );
218
+ }
175
219
  if (!res.ok) {
176
220
  let msg = text2;
177
221
  let code;
@@ -217,6 +261,27 @@ async function apiFetch(baseUrl, token, path, init, isRetry = false) {
217
261
  }
218
262
  return parsed;
219
263
  }
264
+ async function apiFetchWithTransientRetry(baseUrl, token, path, init) {
265
+ return retryTransient(
266
+ async () => await apiFetch(baseUrl, token, path, {
267
+ ...init,
268
+ signal: AbortSignal.timeout(15e3)
269
+ }),
270
+ {
271
+ delaysMs: [250, 750, 1500],
272
+ // A 401 refresh owns its own bounded auth-service retry loop. Only retry
273
+ // failures produced by this deploy-service request, never multiply an
274
+ // exhausted /api/auth/token exchange by replaying the rejected bearer.
275
+ shouldRetryError: (error) => isTransientFailure(error) && error.apiPath === path
276
+ }
277
+ );
278
+ }
279
+ function apiFetchReadWithRetry(baseUrl, token, path) {
280
+ return apiFetchWithTransientRetry(baseUrl, token, path);
281
+ }
282
+ function isTransientFailure(error) {
283
+ return error instanceof ApiError && (error.status === 0 || error.status === 408 || error.status === 429 || error.status >= 500);
284
+ }
220
285
 
221
286
  // src/cli/lib/cli-errors.ts
222
287
  var InputError = class extends Error {
@@ -567,16 +632,57 @@ function decodeJwtPayload(token) {
567
632
  // src/cli/session.ts
568
633
  var SESSION_COOKIE = "__Secure-better-auth.session_token";
569
634
  async function exchangeSession(authUrl, sessionToken) {
570
- const res = await fetch(`${authUrl}/api/auth/token`, {
571
- method: "POST",
572
- headers: {
573
- Cookie: `${SESSION_COOKIE}=${encodeURIComponent(sessionToken)}`,
574
- Origin: authUrl
575
- }
576
- });
577
- if (!res.ok) return null;
578
- const data = await res.json();
579
- return data.token ?? null;
635
+ const path = "/api/auth/token";
636
+ let res;
637
+ try {
638
+ res = await fetchWithTransientRetry(
639
+ `${authUrl}${path}`,
640
+ () => ({
641
+ method: "POST",
642
+ headers: {
643
+ Cookie: `${SESSION_COOKIE}=${encodeURIComponent(sessionToken)}`,
644
+ Origin: authUrl
645
+ }
646
+ }),
647
+ { timeoutMs: 15e3 }
648
+ );
649
+ } catch (error) {
650
+ throw new ApiError(
651
+ `Could not reach the auth service at ${authUrl}: ${error instanceof Error ? error.message : String(error)}. Check your connection, then retry.`,
652
+ 0,
653
+ "network_error",
654
+ path
655
+ );
656
+ }
657
+ if (res.status === 401) return null;
658
+ if (!res.ok) {
659
+ throw new ApiError(
660
+ `The auth service could not refresh the CLI token (HTTP ${res.status}). Retry without logging in again.`,
661
+ res.status,
662
+ res.status === 429 ? "rate_limited" : "auth_service_unavailable",
663
+ path
664
+ );
665
+ }
666
+ let data;
667
+ try {
668
+ data = await res.json();
669
+ } catch {
670
+ throw new ApiError(
671
+ "The auth service returned a malformed token response.",
672
+ res.status,
673
+ "invalid_response",
674
+ path
675
+ );
676
+ }
677
+ if (!data.token) {
678
+ throw new ApiError(
679
+ "The auth service returned no token for a valid session.",
680
+ res.status,
681
+ "invalid_response",
682
+ path
683
+ );
684
+ }
685
+ return data.token;
580
686
  }
581
687
 
582
688
  // src/cli/lib/secure-file.ts
@@ -609,19 +715,18 @@ function credentialPaths(authUrl = AUTH_URL) {
609
715
  return { sessionPath: join(DIR, `session${suffix}`), tokenPath: join(DIR, `token${suffix}`) };
610
716
  }
611
717
  var { sessionPath: SESSION_PATH, tokenPath: TOKEN_PATH } = credentialPaths();
612
- async function ensureToken() {
718
+ async function ensureToken(options = {}) {
719
+ const minimumValidityMs = options.minimumValidityMs ?? 3e4;
613
720
  if (existsSync(TOKEN_PATH)) {
614
721
  const existing = readFileSync2(TOKEN_PATH, "utf-8").trim();
615
- if (isTokenValid(existing)) {
722
+ if (isTokenValid(existing, minimumValidityMs)) {
616
723
  return existing;
617
724
  }
618
725
  }
619
726
  if (!existsSync(SESSION_PATH)) throw notAuthenticated("Not logged in");
620
- const sessionToken = readFileSync2(SESSION_PATH, "utf-8").trim();
621
- const token = await exchangeSession(AUTH_URL, sessionToken);
622
- if (!token) throw notAuthenticated("The stored session is no longer valid (expired or unreadable)");
623
- mkdirSync(DIR, { recursive: true, mode: 448 });
624
- writeSecretFileSync(TOKEN_PATH, token);
727
+ const token = await exchangeStoredSession();
728
+ if (!token)
729
+ throw notAuthenticated("The stored session is no longer valid (expired or unreadable)");
625
730
  return token;
626
731
  }
627
732
  function notAuthenticated(state) {
@@ -638,30 +743,30 @@ function notAuthenticated(state) {
638
743
  { action: cliAction("deepspace", "auth", "login") }
639
744
  );
640
745
  }
641
- function isTokenValid(jwt) {
746
+ function isTokenValid(jwt, minimumValidityMs) {
642
747
  try {
643
748
  const payload = decodeJwtPayload(jwt);
644
- return typeof payload.exp === "number" && payload.exp * 1e3 > Date.now() + 3e4;
749
+ return typeof payload.exp === "number" && payload.exp * 1e3 > Date.now() + minimumValidityMs;
645
750
  } catch {
646
751
  return false;
647
752
  }
648
753
  }
649
- registerAuthRefresh(async () => {
650
- try {
651
- if (!existsSync(SESSION_PATH)) return null;
652
- const sessionToken = readFileSync2(SESSION_PATH, "utf-8").trim();
653
- const token = await exchangeSession(AUTH_URL, sessionToken);
654
- if (!token) return null;
655
- mkdirSync(DIR, { recursive: true, mode: 448 });
656
- writeSecretFileSync(TOKEN_PATH, token);
657
- return token;
658
- } catch {
659
- return null;
660
- }
661
- });
754
+ async function refreshTokenFromSession() {
755
+ return await exchangeStoredSession();
756
+ }
757
+ async function exchangeStoredSession() {
758
+ if (!existsSync(SESSION_PATH)) return null;
759
+ const sessionToken = readFileSync2(SESSION_PATH, "utf-8").trim();
760
+ const token = await exchangeSession(AUTH_URL, sessionToken);
761
+ if (!token) return null;
762
+ mkdirSync(DIR, { recursive: true, mode: 448 });
763
+ writeSecretFileSync(TOKEN_PATH, token);
764
+ return token;
765
+ }
766
+ registerAuthRefresh(refreshTokenFromSession);
662
767
 
663
768
  // src/cli/commands/login.ts
664
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync3 } from "fs";
769
+ import { mkdirSync as mkdirSync2 } from "fs";
665
770
  import { dirname as dirname2 } from "path";
666
771
  import { randomBytes, createHash } from "crypto";
667
772
  import * as p4 from "@clack/prompts";
@@ -675,6 +780,25 @@ function openBrowser(url) {
675
780
  }).unref();
676
781
  }
677
782
 
783
+ // src/cli/lib/stdio.ts
784
+ var MAX_STDIN_BYTES = 1024 * 1024;
785
+ async function readStreamText(stream, maxBytes) {
786
+ const chunks = [];
787
+ let total = 0;
788
+ for await (const chunk of stream) {
789
+ const bytes = typeof chunk === "string" ? Buffer.from(chunk, "utf-8") : Buffer.from(chunk);
790
+ total += bytes.byteLength;
791
+ if (maxBytes !== void 0 && total > maxBytes) {
792
+ throw new InputError(
793
+ `Input on stdin exceeds ${Math.floor(maxBytes / 1024)} KiB \u2014 refusing to buffer more.`,
794
+ "input_too_large"
795
+ );
796
+ }
797
+ chunks.push(bytes);
798
+ }
799
+ return Buffer.concat(chunks).toString("utf-8");
800
+ }
801
+
678
802
  // src/cli/commands/login.ts
679
803
  var AUTH_URL2 = process.env.DEEPSPACE_AUTH_URL ?? PLATFORM_URLS.auth;
680
804
  var API_URL = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
@@ -708,7 +832,7 @@ var login_default = defineDeepspaceCommand({
708
832
  passwordArg: args.password,
709
833
  envEmail: interactive ? void 0 : process.env.DEEPSPACE_EMAIL,
710
834
  envPassword: interactive ? void 0 : process.env.DEEPSPACE_PASSWORD,
711
- passwordStdin: args["password-stdin"] ? readPasswordFromStdin() : void 0
835
+ passwordStdin: args["password-stdin"] ? await readPasswordFromStdin() : void 0
712
836
  });
713
837
  const passwordIntent = Boolean(args["password-stdin"]) || args.password !== void 0 || password !== void 0;
714
838
  const decision = loginModeDecision({ email, password, passwordIntent });
@@ -735,27 +859,7 @@ var login_default = defineDeepspaceCommand({
735
859
  if (!json) s.start("Creating login session...");
736
860
  const codeVerifier = base64url(randomBytes(32));
737
861
  const codeChallenge = base64url(createHash("sha256").update(codeVerifier).digest());
738
- const sessionRes = await fetch(`${AUTH_URL2}/api/auth/cli/session`, {
739
- method: "POST",
740
- headers: { "Content-Type": "application/json" },
741
- body: JSON.stringify({ code_challenge: codeChallenge })
742
- });
743
- if (!sessionRes.ok) {
744
- throw new Refusal(
745
- `Could not create login session (${sessionRes.status})`,
746
- "login_session_failed",
747
- { action: cliAction("deepspace", "auth", "login") }
748
- );
749
- }
750
- const sessionData = await sessionRes.json();
751
- if (!sessionData.sessionId || !sessionData.loginUrl) {
752
- throw new Refusal(
753
- "Could not create login session \u2014 the auth server returned no session id or login url.",
754
- "login_session_failed",
755
- { action: cliAction("deepspace", "auth", "login") }
756
- );
757
- }
758
- const { sessionId, loginUrl } = sessionData;
862
+ const { sessionId, loginUrl } = await createCliLoginSession(codeChallenge);
759
863
  if (!json) {
760
864
  s.stop("Opening browser...");
761
865
  p4.log.info(`If the browser doesn't open, visit:
@@ -816,14 +920,50 @@ function loginModeDecision(opts) {
816
920
  message: "Non-interactive login needs a non-empty password: --password-stdin, $DEEPSPACE_PASSWORD, or --password."
817
921
  };
818
922
  }
819
- function readPasswordFromStdin() {
923
+ async function readPasswordFromStdin() {
820
924
  if (process.stdin.isTTY) {
821
925
  throw new Refusal(
822
926
  '--password-stdin expects the password piped on stdin, e.g. `printf %s "$PW" | deepspace auth login --email you@x.com --password-stdin`.',
823
927
  "password_stdin_needs_pipe"
824
928
  );
825
929
  }
826
- return readFileSync3(0, "utf-8").replace(/\r?\n$/, "");
930
+ return (await readStreamText(process.stdin, MAX_STDIN_BYTES)).replace(/\r?\n$/, "");
931
+ }
932
+ async function createCliLoginSession(codeChallenge) {
933
+ let sessionRes;
934
+ try {
935
+ sessionRes = await fetchWithTransientRetry(
936
+ `${AUTH_URL2}/api/auth/cli/session`,
937
+ () => ({
938
+ method: "POST",
939
+ headers: { "Content-Type": "application/json" },
940
+ body: JSON.stringify({ code_challenge: codeChallenge })
941
+ }),
942
+ { timeoutMs: 15e3 }
943
+ );
944
+ } catch (error) {
945
+ throw new Refusal(
946
+ `Could not reach the auth service to create a login session: ${error instanceof Error ? error.message : String(error)}`,
947
+ "login_session_unreachable",
948
+ { action: cliAction("deepspace", "auth", "login") }
949
+ );
950
+ }
951
+ if (!sessionRes.ok) {
952
+ throw new Refusal(
953
+ `Could not create login session (${sessionRes.status})`,
954
+ "login_session_failed",
955
+ { action: cliAction("deepspace", "auth", "login") }
956
+ );
957
+ }
958
+ const sessionData = await sessionRes.json().catch(() => ({}));
959
+ if (!sessionData.sessionId || !sessionData.loginUrl) {
960
+ throw new Refusal(
961
+ "Could not create login session \u2014 the auth server returned no session id or login url.",
962
+ "login_session_failed",
963
+ { action: cliAction("deepspace", "auth", "login") }
964
+ );
965
+ }
966
+ return { sessionId: sessionData.sessionId, loginUrl: sessionData.loginUrl };
827
967
  }
828
968
  async function pollForCompletion(sessionId, codeVerifier) {
829
969
  const maxAttempts = 120;
@@ -879,7 +1019,7 @@ async function doEmailLogin(email, password) {
879
1019
  const body = await res.json().catch(() => ({}));
880
1020
  throw new Refusal(
881
1021
  body.message ?? `Authentication failed (${res.status})`,
882
- res.status === 401 ? "invalid_credentials" : "login_failed"
1022
+ res.status === 401 ? "invalid_credentials" : res.status === 429 ? "rate_limited" : "login_failed"
883
1023
  );
884
1024
  }
885
1025
  const setCookie = res.headers.get("set-cookie") ?? "";
@@ -890,7 +1030,10 @@ async function doEmailLogin(email, password) {
890
1030
  const sessionToken = decodeURIComponent(cookieMatch[1]);
891
1031
  const jwt = await exchangeSession(AUTH_URL2, sessionToken);
892
1032
  if (!jwt) {
893
- throw new Refusal("Signed in, but the auth service issued no token for the session.", "login_failed");
1033
+ throw new Refusal(
1034
+ "Signed in, but the auth service issued no token for the session.",
1035
+ "login_failed"
1036
+ );
894
1037
  }
895
1038
  storeCredentials(sessionToken, jwt);
896
1039
  await provisionProfile(jwt);
@@ -903,7 +1046,7 @@ async function doEmailLogin(email, password) {
903
1046
  }
904
1047
 
905
1048
  // src/cli/commands/logout.ts
906
- import { existsSync as existsSync2, readFileSync as readFileSync4, rmSync } from "fs";
1049
+ import { existsSync as existsSync2, readFileSync as readFileSync3, rmSync } from "fs";
907
1050
  var AUTH_URL3 = process.env.DEEPSPACE_AUTH_URL ?? PLATFORM_URLS.auth;
908
1051
  var { sessionPath: SESSION_PATH2, tokenPath: TOKEN_PATH2 } = credentialPaths(AUTH_URL3);
909
1052
  var logout_default = defineDeepspaceCommand({
@@ -912,7 +1055,7 @@ var logout_default = defineDeepspaceCommand({
912
1055
  description: "Sign out and remove cached credentials"
913
1056
  },
914
1057
  async run({ args }) {
915
- const sessionToken = existsSync2(SESSION_PATH2) ? readFileSync4(SESSION_PATH2, "utf-8").trim() : null;
1058
+ const sessionToken = existsSync2(SESSION_PATH2) ? readFileSync3(SESSION_PATH2, "utf-8").trim() : null;
916
1059
  if (sessionToken) {
917
1060
  try {
918
1061
  await fetch(`${AUTH_URL3}/api/auth/sign-out`, {
@@ -938,11 +1081,11 @@ var logout_default = defineDeepspaceCommand({
938
1081
  });
939
1082
 
940
1083
  // src/cli/lib/app-identity.ts
941
- import { existsSync as existsSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
1084
+ import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
942
1085
  import { join as join3, resolve as resolve2 } from "path";
943
1086
 
944
1087
  // src/cli/lib/wrangler-env.ts
945
- import { existsSync as existsSync3, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
1088
+ import { existsSync as existsSync3, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
946
1089
  import { join as join2 } from "path";
947
1090
  import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
948
1091
 
@@ -1152,7 +1295,7 @@ function causeDetail(err) {
1152
1295
  function readWranglerConfigFile(path) {
1153
1296
  let raw;
1154
1297
  try {
1155
- raw = readFileSync5(path, "utf-8");
1298
+ raw = readFileSync4(path, "utf-8");
1156
1299
  } catch (err) {
1157
1300
  const missing = err?.code === "ENOENT";
1158
1301
  throw new WranglerConfigError(
@@ -1237,7 +1380,7 @@ function writeAppId(cwd, appId, opts = {}) {
1237
1380
  );
1238
1381
  }
1239
1382
  }
1240
- let src = readFileSync6(wranglerPath, "utf-8");
1383
+ let src = readFileSync5(wranglerPath, "utf-8");
1241
1384
  const header = opts.wranglerEnv ? `[env.${opts.wranglerEnv}.vars]` : "[vars]";
1242
1385
  const line = `DEEPSPACE_APP_ID = "${appId}"`;
1243
1386
  const headerRe = new RegExp(`^\\${header.replace(/\./g, "\\.")}[ \\t]*$`, "m");
@@ -1295,7 +1438,7 @@ function findChildApps(dir) {
1295
1438
  // src/cli/lib/launch-config.ts
1296
1439
  import {
1297
1440
  existsSync as existsSync7,
1298
- readFileSync as readFileSync8,
1441
+ readFileSync as readFileSync7,
1299
1442
  realpathSync as realpathSync2,
1300
1443
  writeFileSync as writeFileSync4,
1301
1444
  mkdirSync as mkdirSync4,
@@ -1305,7 +1448,7 @@ import {
1305
1448
  import { join as join6, resolve as resolve5, dirname as dirname5, basename, isAbsolute as isAbsolute2, sep } from "path";
1306
1449
 
1307
1450
  // src/cli/lib/git/repository.ts
1308
- import { appendFileSync, existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync7 } from "fs";
1451
+ import { appendFileSync, existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync6 } from "fs";
1309
1452
  import { dirname as dirname4, join as join5, resolve as resolve4 } from "path";
1310
1453
 
1311
1454
  // src/cli/lib/git/process.ts
@@ -1529,7 +1672,7 @@ function ensureLocalExclude(cwd, pattern) {
1529
1672
  const excludePath = join5(commonDir, "info", "exclude");
1530
1673
  let current = "";
1531
1674
  try {
1532
- current = readFileSync7(excludePath, "utf-8");
1675
+ current = readFileSync6(excludePath, "utf-8");
1533
1676
  } catch {
1534
1677
  }
1535
1678
  if (current.split("\n").some((line) => line.trim() === pattern)) return;
@@ -1562,7 +1705,7 @@ function writeLaunchFile(launchPath, config) {
1562
1705
  function readLaunchFile(launchPath) {
1563
1706
  const empty = { version: "0.0.1", configurations: [] };
1564
1707
  if (!existsSync7(launchPath)) return empty;
1565
- const raw = readFileSync8(launchPath, "utf-8").trim();
1708
+ const raw = readFileSync7(launchPath, "utf-8").trim();
1566
1709
  if (!raw) return empty;
1567
1710
  const parsed = JSON.parse(raw);
1568
1711
  if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.configurations)) return null;
@@ -1732,7 +1875,7 @@ function githubSourceRefusal(appId, repository) {
1732
1875
  }
1733
1876
  async function getAppSource(deployUrl, token, appId) {
1734
1877
  try {
1735
- const state = await apiFetch(
1878
+ const state = await apiFetchReadWithRetry(
1736
1879
  deployUrl,
1737
1880
  token,
1738
1881
  `/api/apps/${encodeURIComponent(appId)}/source`
@@ -1890,7 +2033,7 @@ function liveAppUrl(app2) {
1890
2033
  return app2.status === "active" && app2.deployedAt !== null ? app2.url : null;
1891
2034
  }
1892
2035
  async function listAppsPage(deployUrl, token) {
1893
- const body = await apiFetch(deployUrl, token, "/api/apps");
2036
+ const body = await apiFetchReadWithRetry(deployUrl, token, "/api/apps");
1894
2037
  if (!Array.isArray(body.apps)) {
1895
2038
  throw new InputError(
1896
2039
  `The deploy service at ${deployUrl} returned an unexpected response shape for the app list \u2014 check DEEPSPACE_DEPLOY_URL (is this the service the app lives on?).`,
@@ -2098,15 +2241,15 @@ async function writeDevVars(appDir, ownerId, callerJwt, wranglerEnv, opts = {})
2098
2241
  }
2099
2242
 
2100
2243
  // src/cli/lib/install-status.ts
2101
- import { existsSync as existsSync9, readFileSync as readFileSync10, realpathSync as realpathSync3 } from "fs";
2244
+ import { existsSync as existsSync9, readFileSync as readFileSync9, realpathSync as realpathSync3 } from "fs";
2102
2245
  import { dirname as dirname6, join as join8, resolve as resolve6 } from "path";
2103
2246
 
2104
2247
  // src/cli/lib/package-manager.ts
2105
- import { existsSync as existsSync8, readFileSync as readFileSync9 } from "fs";
2248
+ import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
2106
2249
  import { join as join7 } from "path";
2107
2250
  function detectPackageManager(appDir) {
2108
2251
  try {
2109
- const pkg2 = JSON.parse(readFileSync9(join7(appDir, "package.json"), "utf-8"));
2252
+ const pkg2 = JSON.parse(readFileSync8(join7(appDir, "package.json"), "utf-8"));
2110
2253
  const name = typeof pkg2.packageManager === "string" ? pkg2.packageManager.split("@")[0] : "";
2111
2254
  if (name === "npm" || name === "pnpm" || name === "yarn" || name === "bun") return name;
2112
2255
  } catch {
@@ -2144,7 +2287,7 @@ function ensureInstallReady(appDir) {
2144
2287
  const donePath = join8(sentinelDir, "install.done");
2145
2288
  const logPath = join8(sentinelDir, "install.log");
2146
2289
  if (existsSync9(errPath)) {
2147
- const detail = readFileSync10(errPath, "utf-8").trimEnd();
2290
+ const detail = readFileSync9(errPath, "utf-8").trimEnd();
2148
2291
  const log29 = existsSync9(logPath) ? ` Full log: ${logPath}.` : "";
2149
2292
  throw new Refusal(
2150
2293
  `Dependency install failed: ${detail}.${log29} Run \`npm install\` (or \`bun install\`) manually, then retry.`,
@@ -2200,7 +2343,7 @@ function canonicalPath2(path) {
2200
2343
  function installWorkerAlive(sentinelDir) {
2201
2344
  const pidPath = join8(sentinelDir, "install.pid");
2202
2345
  if (!existsSync9(pidPath)) return false;
2203
- return processAlive(Number(readFileSync10(pidPath, "utf-8").trim()));
2346
+ return processAlive(Number(readFileSync9(pidPath, "utf-8").trim()));
2204
2347
  }
2205
2348
  function processAlive(pid) {
2206
2349
  if (!Number.isSafeInteger(pid) || pid <= 0) return false;
@@ -2281,9 +2424,12 @@ function removeMacosJunk(dir) {
2281
2424
  }
2282
2425
 
2283
2426
  // src/server/rooms/binding-manifest.ts
2427
+ var MAX_ENV_VAR_BYTES = 5 * 1024;
2284
2428
  var AUTO_PROVISION_SENTINEL = "auto";
2285
2429
  var VECTORIZE_METRICS = /* @__PURE__ */ new Set(["cosine", "euclidean", "dot-product"]);
2286
2430
  var ALLOWED_BINDING_TYPES = /* @__PURE__ */ new Set([
2431
+ "plain_text",
2432
+ "json",
2287
2433
  "vectorize",
2288
2434
  "ai",
2289
2435
  "r2_bucket",
@@ -2361,6 +2507,17 @@ function validateBindingManifest(manifest) {
2361
2507
  }
2362
2508
  function requiredFieldError(b) {
2363
2509
  switch (b.type) {
2510
+ case "plain_text":
2511
+ if (typeof b.text !== "string") {
2512
+ return `plain_text binding "${b.name}" missing text (string)`;
2513
+ }
2514
+ return envVarSizeError(b.name, b.text);
2515
+ case "json": {
2516
+ if (!isBindingJsonValue(b.json)) {
2517
+ return `json binding "${b.name}" must contain a JSON-serializable value`;
2518
+ }
2519
+ return envVarSizeError(b.name, JSON.stringify(b.json));
2520
+ }
2364
2521
  case "ai_search":
2365
2522
  if (b.instance_name !== AUTO_PROVISION_SENTINEL) {
2366
2523
  return `ai_search binding "${b.name}" must set instance_name = "auto"; managed instances cannot be selected directly`;
@@ -2435,6 +2592,18 @@ function bindingManifestFromOutputConfig(outputConfig) {
2435
2592
  );
2436
2593
  }
2437
2594
  const out = [];
2595
+ const vars = isObject(outputConfig.vars) ? outputConfig.vars : null;
2596
+ for (const [name, value] of Object.entries(vars ?? {})) {
2597
+ if (name === "DEEPSPACE_APP_ID" || name === "APP_NAME") continue;
2598
+ if (typeof value === "string") {
2599
+ out.push({ type: "plain_text", name, text: value });
2600
+ continue;
2601
+ }
2602
+ if (!isBindingJsonValue(value)) {
2603
+ throw new Error(`Wrangler var "${name}" is not a JSON-serializable value`);
2604
+ }
2605
+ out.push({ type: "json", name, json: value });
2606
+ }
2438
2607
  const aiBinding = pluckString(outputConfig.ai, "binding");
2439
2608
  if (aiBinding) out.push({ type: "ai", name: aiBinding });
2440
2609
  for (const v of asObjectArray(outputConfig.vectorize)) {
@@ -2517,6 +2686,25 @@ function bindingManifestFromOutputConfig(outputConfig) {
2517
2686
  function isObject(v) {
2518
2687
  return v !== null && typeof v === "object" && !Array.isArray(v);
2519
2688
  }
2689
+ function isBindingJsonValue(value, ancestors = /* @__PURE__ */ new Set()) {
2690
+ if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) {
2691
+ return true;
2692
+ }
2693
+ if (typeof value !== "object") return false;
2694
+ if (ancestors.has(value)) return false;
2695
+ if (!Array.isArray(value)) {
2696
+ const prototype = Object.getPrototypeOf(value);
2697
+ if (prototype !== Object.prototype && prototype !== null) return false;
2698
+ }
2699
+ ancestors.add(value);
2700
+ const valid = Array.isArray(value) ? value.every((entry) => isBindingJsonValue(entry, ancestors)) : Object.values(value).every((entry) => isBindingJsonValue(entry, ancestors));
2701
+ ancestors.delete(value);
2702
+ return valid;
2703
+ }
2704
+ function envVarSizeError(name, serializedValue) {
2705
+ const size = new TextEncoder().encode(serializedValue).byteLength;
2706
+ return size <= MAX_ENV_VAR_BYTES ? null : `Environment variable binding "${name}" is ${size} bytes; maximum is ${MAX_ENV_VAR_BYTES}`;
2707
+ }
2520
2708
  function pluckString(v, key) {
2521
2709
  if (!isObject(v)) return null;
2522
2710
  const val = v[key];
@@ -2573,21 +2761,31 @@ function defaultConfigNameForEnv(wranglerEnv) {
2573
2761
  function secretsApi(deployUrl, token, path, init) {
2574
2762
  return apiFetch(deployUrl, token, `/api/secrets${path}`, init);
2575
2763
  }
2764
+ function readSecretsApi(deployUrl, token, path) {
2765
+ return apiFetchReadWithRetry(deployUrl, token, `/api/secrets${path}`);
2766
+ }
2767
+ function retryingSecretsApi(deployUrl, token, path, init) {
2768
+ return apiFetchWithTransientRetry(deployUrl, token, `/api/secrets${path}`, init);
2769
+ }
2576
2770
  function listSecrets(deployUrl, token, appId, configName) {
2577
- return secretsApi(deployUrl, token, `/${appId}/configs/${encodeURIComponent(configName)}`);
2771
+ return readSecretsApi(deployUrl, token, `/${appId}/configs/${encodeURIComponent(configName)}`);
2578
2772
  }
2579
2773
  function fetchSecretsValues(deployUrl, token, appId, configName) {
2580
- return secretsApi(deployUrl, token, `/${appId}/configs/${encodeURIComponent(configName)}/values`);
2774
+ return readSecretsApi(
2775
+ deployUrl,
2776
+ token,
2777
+ `/${appId}/configs/${encodeURIComponent(configName)}/values`
2778
+ );
2581
2779
  }
2582
2780
  function getSecretPlain(deployUrl, token, appId, configName, key) {
2583
- return secretsApi(
2781
+ return readSecretsApi(
2584
2782
  deployUrl,
2585
2783
  token,
2586
2784
  `/${appId}/configs/${encodeURIComponent(configName)}/secrets/${encodeURIComponent(key)}?plain=true`
2587
2785
  );
2588
2786
  }
2589
2787
  function setSecret(deployUrl, token, appId, configName, key, value) {
2590
- return secretsApi(
2788
+ return retryingSecretsApi(
2591
2789
  deployUrl,
2592
2790
  token,
2593
2791
  `/${appId}/configs/${encodeURIComponent(configName)}/secrets/${encodeURIComponent(key)}`,
@@ -2595,7 +2793,7 @@ function setSecret(deployUrl, token, appId, configName, key, value) {
2595
2793
  );
2596
2794
  }
2597
2795
  function deleteSecret(deployUrl, token, appId, configName, key) {
2598
- return secretsApi(
2796
+ return retryingSecretsApi(
2599
2797
  deployUrl,
2600
2798
  token,
2601
2799
  `/${appId}/configs/${encodeURIComponent(configName)}/secrets/${encodeURIComponent(key)}`,
@@ -2603,7 +2801,7 @@ function deleteSecret(deployUrl, token, appId, configName, key) {
2603
2801
  );
2604
2802
  }
2605
2803
  function uploadSecrets(deployUrl, token, appId, configName, secrets, replace) {
2606
- return secretsApi(
2804
+ return retryingSecretsApi(
2607
2805
  deployUrl,
2608
2806
  token,
2609
2807
  `/${appId}/configs/${encodeURIComponent(configName)}/secrets`,
@@ -2611,7 +2809,7 @@ function uploadSecrets(deployUrl, token, appId, configName, secrets, replace) {
2611
2809
  );
2612
2810
  }
2613
2811
  function listConfigs(deployUrl, token, appId) {
2614
- return secretsApi(deployUrl, token, `/${appId}/configs`);
2812
+ return readSecretsApi(deployUrl, token, `/${appId}/configs`);
2615
2813
  }
2616
2814
  function createConfig(deployUrl, token, appId, configName, copyFrom) {
2617
2815
  return secretsApi(deployUrl, token, `/${appId}/configs`, {
@@ -3146,7 +3344,7 @@ Free it with \`${killCmd}\`, or start on another port: \`deepspace dev start --p
3146
3344
 
3147
3345
  // src/cli/commands/kill.ts
3148
3346
  import { spawnSync as spawnSync2 } from "child_process";
3149
- import { readFileSync as readFileSync11, readdirSync as readdirSync3, readlinkSync } from "fs";
3347
+ import { readFileSync as readFileSync10, readdirSync as readdirSync3, readlinkSync } from "fs";
3150
3348
  import { setTimeout as wait } from "timers/promises";
3151
3349
  var SIGTERM_GRACE_MS = 1500;
3152
3350
  var IS_WIN = process.platform === "win32";
@@ -3297,7 +3495,7 @@ function listenerPidsViaProc(port) {
3297
3495
  for (const f of ["/proc/net/tcp", "/proc/net/tcp6"]) {
3298
3496
  let data;
3299
3497
  try {
3300
- data = readFileSync11(f, "utf-8");
3498
+ data = readFileSync10(f, "utf-8");
3301
3499
  } catch {
3302
3500
  continue;
3303
3501
  }
@@ -3477,10 +3675,10 @@ function ensurePlaywright(appDir) {
3477
3675
  }
3478
3676
 
3479
3677
  // src/cli/lib/test-account-service.ts
3480
- import { readFileSync as readFileSync13 } from "fs";
3678
+ import { readFileSync as readFileSync12 } from "fs";
3481
3679
 
3482
3680
  // src/testing/accounts.ts
3483
- import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync12 } from "fs";
3681
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync11 } from "fs";
3484
3682
  import { homedir as homedir2 } from "os";
3485
3683
  import { join as join12 } from "path";
3486
3684
  var TEST_ACCOUNTS_PATH = join12(homedir2(), ".deepspace", "test-accounts.json");
@@ -3550,7 +3748,7 @@ function removeTestAccounts(ids, emails = [], scope = currentTestAccountScope())
3550
3748
  function readTestAccountStore() {
3551
3749
  if (!existsSync12(TEST_ACCOUNTS_PATH)) return { version: 2, scopes: {} };
3552
3750
  try {
3553
- const raw = JSON.parse(readFileSync12(TEST_ACCOUNTS_PATH, "utf-8"));
3751
+ const raw = JSON.parse(readFileSync11(TEST_ACCOUNTS_PATH, "utf-8"));
3554
3752
  if (Array.isArray(raw)) return { version: 2, scopes: {} };
3555
3753
  if (!raw || typeof raw !== "object") {
3556
3754
  return { version: 2, scopes: {} };
@@ -3581,7 +3779,7 @@ function saveTestAccountStore(store) {
3581
3779
  var SESSION_COOKIE2 = "__Secure-better-auth.session_token";
3582
3780
  var AUTH_URL4 = process.env.DEEPSPACE_AUTH_URL ?? PLATFORM_URLS.auth;
3583
3781
  function sessionCookie() {
3584
- const token = readFileSync13(SESSION_PATH, "utf-8").trim();
3782
+ const token = readFileSync12(SESSION_PATH, "utf-8").trim();
3585
3783
  return `${SESSION_COOKIE2}=${encodeURIComponent(token)}`;
3586
3784
  }
3587
3785
  async function fetchRemoteTestAccounts() {
@@ -4516,8 +4714,8 @@ function describeFilesFailure(status3, body) {
4516
4714
  import * as p6 from "@clack/prompts";
4517
4715
  import { execSync as execSync2 } from "child_process";
4518
4716
  import { createHash as createHash2 } from "crypto";
4519
- import { existsSync as existsSync16, readFileSync as readFileSync16, readdirSync as readdirSync6 } from "fs";
4520
- import { dirname as dirname7, join as join17, resolve as resolve13 } from "path";
4717
+ import { existsSync as existsSync16, readFileSync as readFileSync15, readdirSync as readdirSync6 } from "fs";
4718
+ import { dirname as dirname7, isAbsolute as isAbsolute3, join as join17, relative as relative2, resolve as resolve13, sep as sep3 } from "path";
4521
4719
 
4522
4720
  // src/build/app-id.ts
4523
4721
  import { resolve as resolve11 } from "path";
@@ -4547,7 +4745,7 @@ function removeBuildDevVars(workerDir) {
4547
4745
  }
4548
4746
 
4549
4747
  // src/documentation/deploy.ts
4550
- import { existsSync as existsSync14, readFileSync as readFileSync14 } from "fs";
4748
+ import { existsSync as existsSync14, readFileSync as readFileSync13 } from "fs";
4551
4749
  import { join as join15 } from "path";
4552
4750
  function readDocumentationDeployManifest(appDir, clientDir) {
4553
4751
  if (!existsSync14(join15(appDir, "documentation.json"))) return void 0;
@@ -4557,11 +4755,11 @@ function readDocumentationDeployManifest(appDir, clientDir) {
4557
4755
  "documentation.json exists but the build produced no documentation manifest. Ensure vite.config.ts includes deepSpaceDocumentation() from deepspace/documentation."
4558
4756
  );
4559
4757
  }
4560
- return JSON.parse(readFileSync14(manifestPath, "utf8"));
4758
+ return JSON.parse(readFileSync13(manifestPath, "utf8"));
4561
4759
  }
4562
4760
 
4563
4761
  // src/cli/commands/update/app-migrations.ts
4564
- import { existsSync as existsSync15, readFileSync as readFileSync15 } from "fs";
4762
+ import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
4565
4763
  import { join as join16 } from "path";
4566
4764
 
4567
4765
  // src/shared/protocol/app-migrations.ts
@@ -4640,7 +4838,7 @@ function readAppliedAppMigrations(appDir) {
4640
4838
  if (!existsSync15(path)) return [];
4641
4839
  let parsed;
4642
4840
  try {
4643
- parsed = JSON.parse(readFileSync15(path, "utf8"));
4841
+ parsed = JSON.parse(readFileSync14(path, "utf8"));
4644
4842
  } catch {
4645
4843
  throw new Refusal(
4646
4844
  `${APP_MIGRATIONS_MANIFEST} must contain valid JSON`,
@@ -4706,12 +4904,12 @@ async function buildDeployBundle(options) {
4706
4904
  "build_output_missing"
4707
4905
  );
4708
4906
  }
4709
- const deployConfig = JSON.parse(readFileSync16(deployConfigPath, "utf-8"));
4907
+ const deployConfig = JSON.parse(readFileSync15(deployConfigPath, "utf-8"));
4710
4908
  const outputWranglerPath = resolve13(dirname7(deployConfigPath), deployConfig.configPath);
4711
4909
  if (!existsSync16(outputWranglerPath)) {
4712
4910
  output.die(`Output wrangler.json not found at ${outputWranglerPath}`, "build_output_missing");
4713
4911
  }
4714
- const outputConfig = JSON.parse(readFileSync16(outputWranglerPath, "utf-8"));
4912
+ const outputConfig = JSON.parse(readFileSync15(outputWranglerPath, "utf-8"));
4715
4913
  if (outputConfig.name && outputConfig.name !== appName) {
4716
4914
  const envHint = envName ? ` (--env ${envName})` : "";
4717
4915
  output.die(
@@ -4745,7 +4943,13 @@ async function buildDeployBundle(options) {
4745
4943
  spinner2.stop(`Collected ${assets.length} assets`);
4746
4944
  const appIdRefusal = clientAppIdRefusal(assets, appId, envName);
4747
4945
  if (appIdRefusal) output.die(appIdRefusal.message, appIdRefusal.code);
4748
- const workerJs = readFileSync16(workerBundlePath, "utf-8");
4946
+ const worker = (() => {
4947
+ try {
4948
+ return collectWorkerBundle(workerDir, workerBundlePath, clientDir);
4949
+ } catch (error) {
4950
+ return output.die(errorMessage(error), "build_output_invalid");
4951
+ }
4952
+ })();
4749
4953
  const doBindings = outputConfig.durable_objects?.bindings;
4750
4954
  const sqliteClasses = new Set(
4751
4955
  outputConfig.migrations?.flatMap((migration) => migration.new_sqlite_classes ?? []) ?? []
@@ -4780,7 +4984,7 @@ async function buildDeployBundle(options) {
4780
4984
  return {
4781
4985
  assets,
4782
4986
  assetConfig,
4783
- workerJs,
4987
+ worker,
4784
4988
  appMigrations,
4785
4989
  doManifest,
4786
4990
  customBindings,
@@ -4790,6 +4994,43 @@ async function buildDeployBundle(options) {
4790
4994
  notFoundHandling: typeof outputConfig.assets?.not_found_handling === "string" ? outputConfig.assets.not_found_handling : null
4791
4995
  };
4792
4996
  }
4997
+ var WORKER_ES_MODULE = /\.(?:js|mjs)$/i;
4998
+ function collectWorkerBundle(workerDir, mainPath, clientDir) {
4999
+ const root = resolve13(workerDir);
5000
+ const mainAbsolute = resolve13(mainPath);
5001
+ const excludedClient = clientDir ? resolve13(clientDir) : null;
5002
+ const main2 = moduleName(root, mainAbsolute);
5003
+ const modules = [];
5004
+ const walk = (directory) => {
5005
+ for (const entry of readdirSync6(directory, { withFileTypes: true }).sort(
5006
+ (a, b) => a.name.localeCompare(b.name)
5007
+ )) {
5008
+ const fullPath = join17(directory, entry.name);
5009
+ if (excludedClient && isWithin(excludedClient, fullPath)) continue;
5010
+ if (entry.isDirectory()) {
5011
+ walk(fullPath);
5012
+ } else if (entry.isFile() && WORKER_ES_MODULE.test(entry.name)) {
5013
+ modules.push({ name: moduleName(root, fullPath), content: readFileSync15(fullPath, "utf-8") });
5014
+ }
5015
+ }
5016
+ };
5017
+ walk(root);
5018
+ const mainIndex = modules.findIndex((module) => module.name === main2);
5019
+ if (mainIndex < 0) throw new Error(`Worker main module not found in generated output: ${main2}`);
5020
+ const [mainModule] = modules.splice(mainIndex, 1);
5021
+ return { main: main2, modules: [mainModule, ...modules] };
5022
+ }
5023
+ function moduleName(root, path) {
5024
+ const name = relative2(root, path);
5025
+ if (!name || isAbsolute3(name) || name === ".." || name.startsWith(`..${sep3}`)) {
5026
+ throw new Error(`Worker module is outside the generated output directory: ${path}`);
5027
+ }
5028
+ return name.split(sep3).join("/");
5029
+ }
5030
+ function isWithin(root, path) {
5031
+ const name = relative2(root, path);
5032
+ return name === "" || !isAbsolute3(name) && name !== ".." && !name.startsWith(`..${sep3}`);
5033
+ }
4793
5034
  function extractRunWorkerFirst(config) {
4794
5035
  const raw = Array.isArray(config.assets?.run_worker_first) ? config.assets.run_worker_first : [];
4795
5036
  const routes = [];
@@ -4821,7 +5062,7 @@ function clientAppIdRefusal(assets, appId, envName) {
4821
5062
  let unsubstituted;
4822
5063
  for (const asset of assets) {
4823
5064
  if (!CLIENT_CODE_ASSET.test(asset.path)) continue;
4824
- const source = readFileSync16(asset.sourcePath, "utf-8");
5065
+ const source = readFileSync15(asset.sourcePath, "utf-8");
4825
5066
  if (unsubstituted === void 0 && source.includes(APP_ID_DEFINE)) unsubstituted = asset.path;
4826
5067
  for (const found of source.matchAll(APP_ID_LITERAL)) {
4827
5068
  if (found[0] !== appId && !foreign.has(found[0])) foreign.set(found[0], asset.path);
@@ -4855,7 +5096,7 @@ function collectAssets(dir) {
4855
5096
  if (entry.isDirectory()) {
4856
5097
  walk(fullPath, relativePath);
4857
5098
  } else {
4858
- const bytes = readFileSync16(fullPath);
5099
+ const bytes = readFileSync15(fullPath);
4859
5100
  assets.push({
4860
5101
  path: `/${relativePath}`,
4861
5102
  hash: createHash2("sha256").update(bytes).digest("hex"),
@@ -4876,7 +5117,7 @@ function readDeployAssetConfig(clientDir) {
4876
5117
  ]) {
4877
5118
  const filePath = join17(clientDir, file);
4878
5119
  if (!existsSync16(filePath)) continue;
4879
- const content = readFileSync16(filePath, "utf8");
5120
+ const content = readFileSync15(filePath, "utf8");
4880
5121
  if (content.trim()) config[key] = content;
4881
5122
  }
4882
5123
  return config;
@@ -5201,7 +5442,7 @@ function createDeployOutput(json) {
5201
5442
 
5202
5443
  // src/cli/commands/deploy/request.ts
5203
5444
  import * as p10 from "@clack/prompts";
5204
- import { readFileSync as readFileSync17 } from "fs";
5445
+ import { readFileSync as readFileSync16 } from "fs";
5205
5446
 
5206
5447
  // src/cli/commands/deploy/repository.ts
5207
5448
  import * as p9 from "@clack/prompts";
@@ -5293,7 +5534,7 @@ function committedSecretRefusal(cwd, commitOid, options) {
5293
5534
  // src/cli/lib/vc-remote.ts
5294
5535
  import { existsSync as existsSync18 } from "fs";
5295
5536
  import { tmpdir } from "os";
5296
- import { isAbsolute as isAbsolute3, relative as relative2, resolve as resolve14 } from "path";
5537
+ import { isAbsolute as isAbsolute4, relative as relative3, resolve as resolve14 } from "path";
5297
5538
  function spaceRemoteName(environment = DEEPSPACE_ENV) {
5298
5539
  if (environment === "staging") return "space-staging";
5299
5540
  if (environment === "production") return "space";
@@ -5339,8 +5580,8 @@ function entryIsTransient(cwd) {
5339
5580
  const entry = process.argv[1];
5340
5581
  if (!entry) return true;
5341
5582
  if (/[\\/]_npx[\\/]/.test(entry) || entry.startsWith(tmpdir())) return true;
5342
- const rel = relative2(resolve14(cwd), resolve14(entry));
5343
- return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
5583
+ const rel = relative3(resolve14(cwd), resolve14(entry));
5584
+ return rel === "" || !rel.startsWith("..") && !isAbsolute4(rel);
5344
5585
  }
5345
5586
  function helperEntryUsable(command) {
5346
5587
  const match = /^!(?:DEEPSPACE_ENV=(production|staging) )?'([^']*)' '([^']*)' git-credential$/.exec(command);
@@ -5891,6 +6132,7 @@ function detachedHeadRefusal() {
5891
6132
  }
5892
6133
 
5893
6134
  // src/cli/commands/deploy/request.ts
6135
+ var postWithRetry = fetchWithTransientRetry;
5894
6136
  var STALE_DISPLAY_NAME_LOCATIONS = [
5895
6137
  "src/constants.ts:APP_NAME",
5896
6138
  "wrangler.toml:[vars].APP_NAME"
@@ -5905,7 +6147,36 @@ function renamePromptMessage(rename2) {
5905
6147
  var CLOUDFLARE_DEPLOY_ERROR_HINT = "Deployment reached the DeepSpace deploy worker, but Cloudflare's deployment control-plane API failed. This is often caused by a Cloudflare Dashboard/API incident or Workers for Platforms entitlement outage. Already deployed apps should keep serving; wait for Cloudflare to recover and retry.";
5906
6148
  var DEPLOY_SERVICE_LIMIT_HINT = "The DeepSpace deploy service hit a resource limit processing this deploy (the deploy worker was terminated before it could answer). This is a DeepSpace-side failure, not a Cloudflare incident \u2014 your app was not changed. Retry; if it keeps happening, report the app id and asset count.";
5907
6149
  var REQUIRED_ASSET_TRANSPORT = "content-addressed-v1";
6150
+ var REQUIRED_WORKER_MODULE_TRANSPORT = "multipart-v1";
5908
6151
  var UPLOAD_CONCURRENCY = 16;
6152
+ var ASSET_UPLOAD_ATTEMPT_TIMEOUT_MS = 15e4;
6153
+ var ASSET_UPLOAD_ATTEMPTS = 2;
6154
+ function createDeployAuthSession(initialToken, refreshToken) {
6155
+ let currentToken = initialToken;
6156
+ let refreshInFlight = null;
6157
+ const recover2 = async (rejectedToken) => {
6158
+ if (currentToken !== rejectedToken) return currentToken;
6159
+ if (!refreshToken) return null;
6160
+ if (!refreshInFlight) {
6161
+ refreshInFlight = refreshToken().then((freshToken) => {
6162
+ if (freshToken && freshToken !== currentToken) currentToken = freshToken;
6163
+ return freshToken;
6164
+ }).finally(() => {
6165
+ refreshInFlight = null;
6166
+ });
6167
+ }
6168
+ return await refreshInFlight;
6169
+ };
6170
+ return {
6171
+ async request(run2) {
6172
+ const rejectedToken = currentToken;
6173
+ const response = await run2(rejectedToken);
6174
+ if (response.status !== 401) return response;
6175
+ const freshToken = await recover2(rejectedToken);
6176
+ return freshToken && freshToken !== rejectedToken ? await run2(freshToken) : response;
6177
+ }
6178
+ };
6179
+ }
5909
6180
  async function deployBuiltBundle(options) {
5910
6181
  const {
5911
6182
  deployUrl,
@@ -5913,6 +6184,7 @@ async function deployBuiltBundle(options) {
5913
6184
  appId,
5914
6185
  appName,
5915
6186
  token,
6187
+ refreshToken,
5916
6188
  claimReleased,
5917
6189
  ignoreStale,
5918
6190
  bundle,
@@ -5921,14 +6193,18 @@ async function deployBuiltBundle(options) {
5921
6193
  output,
5922
6194
  spinner: spinner2
5923
6195
  } = options;
6196
+ const auth2 = createDeployAuthSession(token, refreshToken);
5924
6197
  spinner2.start(`Deploying ${appName}...`);
5925
6198
  if (bundle.extraRoutes === true) {
5926
6199
  p10.log.info("Worker-first routing: all requests");
5927
6200
  } else if (bundle.extraRoutes.length) {
5928
6201
  p10.log.info(`Custom worker-first routes: ${bundle.extraRoutes.join(", ")}`);
5929
6202
  }
5930
- await requireAssetTransport(deployUrl, spinner2, output);
5931
- await uploadDeployAssets({ deployUrl, appId, token, assets: bundle.assets, output, spinner: spinner2 });
6203
+ await requireDeployCapabilities(deployUrl, bundle.worker.modules.length > 1, spinner2, output);
6204
+ await uploadDeployAssetsWithAuth(
6205
+ { deployUrl, appId, assets: bundle.assets, output, spinner: spinner2 },
6206
+ auth2
6207
+ );
5932
6208
  spinner2.message(`Deploying ${appName}...`);
5933
6209
  let confirmRename = options.rename;
5934
6210
  const bail = async (message, stopLabel = "Deploy failed", code, actionRequired = false, rawAction) => {
@@ -5949,11 +6225,27 @@ async function deployBuiltBundle(options) {
5949
6225
  };
5950
6226
  const makeForm = () => {
5951
6227
  const form = new FormData();
6228
+ const main2 = bundle.worker.modules.find((module) => module.name === bundle.worker.main);
6229
+ if (!main2) return output.die("Built Worker bundle has no main module.", "build_output_invalid");
5952
6230
  form.append(
5953
6231
  "worker",
5954
- new Blob([bundle.workerJs], { type: "application/javascript" }),
5955
- "worker.js"
5956
- );
6232
+ new Blob([main2.content], { type: "application/javascript+module" }),
6233
+ main2.name
6234
+ );
6235
+ form.append("workerMain", bundle.worker.main);
6236
+ const additionalModules = bundle.worker.modules.filter(
6237
+ (module) => module.name !== bundle.worker.main
6238
+ );
6239
+ if (additionalModules.length) {
6240
+ form.append("workerModules", JSON.stringify(additionalModules.map((module) => module.name)));
6241
+ for (const module of additionalModules) {
6242
+ form.append(
6243
+ "workerModule",
6244
+ new Blob([module.content], { type: "application/javascript+module" }),
6245
+ module.name
6246
+ );
6247
+ }
6248
+ }
5957
6249
  form.append("assetManifest", JSON.stringify(assetManifest(bundle.assets)));
5958
6250
  form.append("appMigrations", JSON.stringify(bundle.appMigrations));
5959
6251
  if (bundle.doManifest) form.append("doManifest", JSON.stringify(bundle.doManifest));
@@ -5997,14 +6289,16 @@ async function deployBuiltBundle(options) {
5997
6289
  let commitStarted = Date.now();
5998
6290
  const postCommit = async () => {
5999
6291
  commitStarted = Date.now();
6000
- return postWithRetry(
6001
- `${deployUrl}/api/deploy/${appId}`,
6002
- () => ({
6003
- method: "POST",
6004
- headers: { Authorization: `Bearer ${token}` },
6005
- body: makeForm()
6006
- }),
6007
- { retryServerErrors: false, attempts: 2, timeoutMs: 24e4 }
6292
+ return auth2.request(
6293
+ (bearer) => postWithRetry(
6294
+ `${deployUrl}/api/deploy/${appId}`,
6295
+ () => ({
6296
+ method: "POST",
6297
+ headers: { Authorization: `Bearer ${bearer}` },
6298
+ body: makeForm()
6299
+ }),
6300
+ { retryServerErrors: false, attempts: 2, timeoutMs: 24e4 }
6301
+ )
6008
6302
  );
6009
6303
  };
6010
6304
  let response;
@@ -6012,9 +6306,9 @@ async function deployBuiltBundle(options) {
6012
6306
  response = await postCommit();
6013
6307
  } catch (error) {
6014
6308
  await bail(
6015
- `Deploy request failed: ${errorMessage4(error)}`,
6309
+ error instanceof ApiError ? errorMessage4(error) : `Deploy request failed: ${errorMessage4(error)}`,
6016
6310
  "Deploy failed",
6017
- "deploy_request_failed"
6311
+ error instanceof ApiError ? error.code : "deploy_request_failed"
6018
6312
  );
6019
6313
  throw error;
6020
6314
  }
@@ -6075,13 +6369,15 @@ async function deployBuiltBundle(options) {
6075
6369
  function assetManifest(assets) {
6076
6370
  return assets.map((asset) => ({ path: asset.path, hash: asset.hash, size: asset.size }));
6077
6371
  }
6078
- async function requireAssetTransport(deployUrl, spinner2, output) {
6079
- let advertised;
6372
+ async function requireDeployCapabilities(deployUrl, needsWorkerModules, spinner2, output) {
6373
+ let capabilities;
6080
6374
  try {
6081
- const response = await fetch(`${deployUrl}/api/health`);
6375
+ const response = await postWithRetry(`${deployUrl}/api/health`, () => ({}), {
6376
+ timeoutMs: 15e3
6377
+ });
6082
6378
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
6083
6379
  const body = await response.json();
6084
- advertised = body.capabilities?.assetTransport;
6380
+ capabilities = body.capabilities;
6085
6381
  } catch (error) {
6086
6382
  spinner2.stop("Deploy failed");
6087
6383
  output.die(
@@ -6089,30 +6385,42 @@ async function requireAssetTransport(deployUrl, spinner2, output) {
6089
6385
  "deploy_service_unreachable"
6090
6386
  );
6091
6387
  }
6092
- if (advertised !== REQUIRED_ASSET_TRANSPORT) {
6388
+ if (capabilities?.assetTransport !== REQUIRED_ASSET_TRANSPORT) {
6093
6389
  spinner2.stop("Deploy failed");
6094
6390
  output.die(
6095
- `The DeepSpace deploy service at ${deployUrl} does not support this CLI's asset transport (needs "${REQUIRED_ASSET_TRANSPORT}", found ${advertised === void 0 ? "none" : `"${String(advertised)}"`}). Wait for the platform to finish updating, or pin an older deepspace CLI.`,
6391
+ `The DeepSpace deploy service at ${deployUrl} does not support this CLI's asset transport (needs "${REQUIRED_ASSET_TRANSPORT}", found ${capabilities?.assetTransport === void 0 ? "none" : `"${String(capabilities.assetTransport)}"`}). Wait for the platform to finish updating, or pin an older deepspace CLI.`,
6096
6392
  "asset_transport_unsupported"
6097
6393
  );
6098
6394
  }
6395
+ if (needsWorkerModules && capabilities?.workerModules !== REQUIRED_WORKER_MODULE_TRANSPORT) {
6396
+ spinner2.stop("Deploy failed");
6397
+ output.die(
6398
+ `This build contains multiple Worker modules, but the DeepSpace deploy service at ${deployUrl} does not support their transport (needs "${REQUIRED_WORKER_MODULE_TRANSPORT}", found ${capabilities?.workerModules === void 0 ? "none" : `"${String(capabilities.workerModules)}"`}). Wait for the platform to finish updating before retrying this deploy.`,
6399
+ "worker_module_transport_unsupported"
6400
+ );
6401
+ }
6099
6402
  }
6100
- async function uploadDeployAssets(options) {
6101
- const { deployUrl, appId, token, assets, spinner: spinner2 } = options;
6403
+ async function uploadDeployAssetsWithAuth(options, auth2) {
6404
+ const { deployUrl, appId, assets, spinner: spinner2 } = options;
6102
6405
  const output = options.output;
6103
6406
  const byHash = new Map(assets.map((asset) => [asset.hash, asset]));
6104
6407
  spinner2.message("Checking which assets are already uploaded...");
6105
6408
  let planResponse;
6106
6409
  try {
6107
- planResponse = await postWithRetry(`${deployUrl}/api/deploy/${appId}/asset-plan`, () => ({
6108
- method: "POST",
6109
- headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
6110
- body: JSON.stringify({
6111
- assets: [...byHash.values()].map((asset) => ({ hash: asset.hash, size: asset.size }))
6112
- })
6113
- }));
6410
+ planResponse = await auth2.request(
6411
+ (bearer) => postWithRetry(`${deployUrl}/api/deploy/${appId}/asset-plan`, () => ({
6412
+ method: "POST",
6413
+ headers: { Authorization: `Bearer ${bearer}`, "Content-Type": "application/json" },
6414
+ body: JSON.stringify({
6415
+ assets: [...byHash.values()].map((asset) => ({ hash: asset.hash, size: asset.size }))
6416
+ })
6417
+ }))
6418
+ );
6114
6419
  } catch (error) {
6115
6420
  spinner2.stop("Deploy failed");
6421
+ if (error instanceof ApiError) {
6422
+ output.die(errorMessage4(error), error.code ?? "upload_failed");
6423
+ }
6116
6424
  output.die(`Asset plan failed (network): ${errorMessage4(error)}`, "upload_failed");
6117
6425
  }
6118
6426
  const planBody = await planResponse.json().catch(() => ({}));
@@ -6148,20 +6456,23 @@ async function uploadDeployAssets(options) {
6148
6456
  const next = () => missing.pop();
6149
6457
  const worker = async () => {
6150
6458
  for (let asset = next(); asset; asset = next()) {
6151
- const response = await postWithRetry(
6152
- `${deployUrl}/api/deploy/${appId}/assets/${asset.hash}`,
6153
- () => ({
6154
- method: "PUT",
6155
- headers: {
6156
- Authorization: `Bearer ${token}`,
6157
- "Content-Type": "application/octet-stream"
6158
- },
6159
- // Read whole, per attempt. The platform matches Content-Length
6160
- // against the plan before it touches storage, and a stream body
6161
- // would be sent chunked with no length at all; a retry also needs a
6162
- // body it can send again.
6163
- body: readFileSync17(asset.sourcePath)
6164
- })
6459
+ const response = await auth2.request(
6460
+ (bearer) => postWithRetry(
6461
+ `${deployUrl}/api/deploy/${appId}/assets/${asset.hash}`,
6462
+ () => ({
6463
+ method: "PUT",
6464
+ headers: {
6465
+ Authorization: `Bearer ${bearer}`,
6466
+ "Content-Type": "application/octet-stream"
6467
+ },
6468
+ // Read whole, per attempt. The platform matches Content-Length
6469
+ // against the plan before it touches storage, and a stream body
6470
+ // would be sent chunked with no length at all; a retry also needs a
6471
+ // body it can send again.
6472
+ body: readFileSync16(asset.sourcePath)
6473
+ }),
6474
+ { attempts: ASSET_UPLOAD_ATTEMPTS, timeoutMs: ASSET_UPLOAD_ATTEMPT_TIMEOUT_MS }
6475
+ )
6165
6476
  );
6166
6477
  if (!response.ok) {
6167
6478
  const body = await response.json().catch(() => ({}));
@@ -6178,14 +6489,15 @@ async function uploadDeployAssets(options) {
6178
6489
  const uploadStarted = Date.now();
6179
6490
  try {
6180
6491
  await Promise.all(Array.from({ length: Math.min(UPLOAD_CONCURRENCY, missing.length) }, worker));
6181
- p10.log.info(
6182
- `Uploaded ${total} file(s) in ${Math.round((Date.now() - uploadStarted) / 1e3)}s`
6183
- );
6492
+ p10.log.info(`Uploaded ${total} file(s) in ${Math.round((Date.now() - uploadStarted) / 1e3)}s`);
6184
6493
  } catch (error) {
6185
6494
  spinner2.stop("Deploy failed");
6186
6495
  if (error instanceof AssetUploadError) {
6187
6496
  output.die(formatDeployWorkerError(error.status, error.message, error.code), error.code);
6188
6497
  }
6498
+ if (error instanceof ApiError) {
6499
+ output.die(errorMessage4(error), error.code ?? "upload_failed");
6500
+ }
6189
6501
  output.die(`Asset upload failed (network): ${errorMessage4(error)}`, "upload_failed");
6190
6502
  }
6191
6503
  }
@@ -6224,29 +6536,6 @@ Underlying error: ${detail}`;
6224
6536
  function errorMessage4(error) {
6225
6537
  return error instanceof Error ? error.message : String(error);
6226
6538
  }
6227
- async function postWithRetry(url, makeInit, {
6228
- attempts = 4,
6229
- retryServerErrors = true,
6230
- timeoutMs = 6e4
6231
- } = {}) {
6232
- let lastResponse;
6233
- let lastError;
6234
- for (let attempt = 1; attempt <= attempts; attempt++) {
6235
- try {
6236
- const response = await fetch(url, { ...makeInit(), signal: AbortSignal.timeout(timeoutMs) });
6237
- const transient = retryServerErrors && (response.status >= 500 || response.status === 408 || response.status === 429);
6238
- if (!transient) return response;
6239
- lastResponse = response;
6240
- } catch (error) {
6241
- lastError = error;
6242
- }
6243
- if (attempt < attempts) {
6244
- await new Promise((resolve23) => setTimeout(resolve23, Math.min(8e3, 500 * 2 ** (attempt - 1))));
6245
- }
6246
- }
6247
- if (lastResponse) return lastResponse;
6248
- throw lastError instanceof Error ? lastError : new Error(String(lastError));
6249
- }
6250
6539
 
6251
6540
  // src/cli/commands/deploy/secrets.ts
6252
6541
  import * as p11 from "@clack/prompts";
@@ -6326,7 +6615,7 @@ function errorMessage5(error) {
6326
6615
 
6327
6616
  // src/cli/commands/deploy/lock.ts
6328
6617
  import { randomUUID as randomUUID2 } from "crypto";
6329
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync18, rmSync as rmSync4, writeFileSync as writeFileSync5 } from "fs";
6618
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync17, rmSync as rmSync4, writeFileSync as writeFileSync5 } from "fs";
6330
6619
  import { dirname as dirname8, join as join19 } from "path";
6331
6620
  function deployLockPath(appDir) {
6332
6621
  return join19(appDir, ".deepspace", "deploy.lock");
@@ -6361,7 +6650,7 @@ function acquireDeployLock(appDir) {
6361
6650
  }
6362
6651
  function readLock(path) {
6363
6652
  try {
6364
- const parsed = JSON.parse(readFileSync18(path, "utf-8"));
6653
+ const parsed = JSON.parse(readFileSync17(path, "utf-8"));
6365
6654
  return typeof parsed.pid === "number" && typeof parsed.startedAt === "string" && typeof parsed.token === "string" ? { pid: parsed.pid, startedAt: parsed.startedAt, token: parsed.token } : null;
6366
6655
  } catch {
6367
6656
  return null;
@@ -6370,6 +6659,8 @@ function readLock(path) {
6370
6659
 
6371
6660
  // src/cli/commands/deploy.ts
6372
6661
  var DEPLOY_URL3 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
6662
+ var DEPLOY_TOKEN_MIN_VALIDITY_MS = 10 * 60 * 1e3;
6663
+ var POST_DEPLOY_TOKEN_MIN_VALIDITY_MS = 2 * 60 * 1e3;
6373
6664
  var deploy_default = defineCommand3({
6374
6665
  meta: {
6375
6666
  name: "deploy",
@@ -6544,11 +6835,12 @@ async function runLockedDeploy(args, output, appDir, envName) {
6544
6835
  spinner2.stop("Deploy failed");
6545
6836
  output.die(oversized, "assets_too_large");
6546
6837
  }
6838
+ const { token: deployToken } = await authenticate(appDir, output, DEPLOY_TOKEN_MIN_VALIDITY_MS);
6547
6839
  const repository = await syncDeployRepository({
6548
6840
  deployUrl: DEPLOY_URL3,
6549
6841
  appDir,
6550
6842
  appId,
6551
- token,
6843
+ token: deployToken,
6552
6844
  push: args.push !== false,
6553
6845
  ignoreStale: Boolean(args["ignore-stale"]),
6554
6846
  output,
@@ -6565,7 +6857,8 @@ async function runLockedDeploy(args, output, appDir, envName) {
6565
6857
  appDir,
6566
6858
  appId,
6567
6859
  appName,
6568
- token,
6860
+ token: deployToken,
6861
+ refreshToken: refreshTokenFromSession,
6569
6862
  rename: confirmRename,
6570
6863
  claimReleased: args["claim-released"] === true,
6571
6864
  ignoreStale: Boolean(args["ignore-stale"]),
@@ -6597,7 +6890,7 @@ async function runLockedDeploy(args, output, appDir, envName) {
6597
6890
  serving === "unconfirmed" ? "Some requests still get the previous release. Cloudflare rolls a version out per edge machine; it usually settles within a couple of minutes. Wait before asserting against it." : "This release carries no serving stamp (older platform, or a resumed deploy), so the CLI cannot tell which release the edge answers with."
6598
6891
  );
6599
6892
  p12.log.info(`URL: ${body.url}`);
6600
- await syncCommerce(appDir, appId, token, output.nonInteractive);
6893
+ await syncPostDeployCommerce(appDir, appId, output.nonInteractive);
6601
6894
  if (output.json) {
6602
6895
  return output.emitJson({
6603
6896
  ok: true,
@@ -6621,7 +6914,7 @@ async function runLockedDeploy(args, output, appDir, envName) {
6621
6914
  `Deployed! (edge confirmed in ${Math.round((Date.now() - edgeWaitStarted) / 1e3)}s)`
6622
6915
  );
6623
6916
  p12.log.success(`Live at: ${body.url}`);
6624
- await syncCommerce(appDir, appId, token, output.nonInteractive);
6917
+ await syncPostDeployCommerce(appDir, appId, output.nonInteractive);
6625
6918
  if (output.json) {
6626
6919
  return output.emitJson({
6627
6920
  ok: true,
@@ -6645,6 +6938,14 @@ async function syncCommerce(appDir, appId, token, nonInteractive) {
6645
6938
  await syncSubscriptionPlans(appDir, appId, token, nonInteractive);
6646
6939
  await syncOneTimeProducts(appDir, appId, token);
6647
6940
  }
6941
+ async function syncPostDeployCommerce(appDir, appId, nonInteractive) {
6942
+ try {
6943
+ const token = await ensureToken({ minimumValidityMs: POST_DEPLOY_TOKEN_MIN_VALIDITY_MS });
6944
+ await syncCommerce(appDir, appId, token, nonInteractive);
6945
+ } catch (error) {
6946
+ p12.log.warn(`Deployed successfully, but commerce sync was skipped: ${errorMessage6(error)}`);
6947
+ }
6948
+ }
6648
6949
  function ownerJwtMissingRefusal(state) {
6649
6950
  if (state.onBehalf?.ownerJwtLive !== false) return null;
6650
6951
  return {
@@ -6691,11 +6992,12 @@ function forbiddenDeployMessage(appId, token) {
6691
6992
  const grant = email ? `run \`deepspace app collaborators add ${email}\`` : "add your email as a collaborator";
6692
6993
  return `${appId} belongs to another account \u2014 you are signed in as ${who}, who is neither its owner nor a collaborator. Ask the owner to ${grant} (collaborators can deploy), or log in as the owner; to publish this code as your OWN app instead, run \`deepspace app init --new-id\`.`;
6693
6994
  }
6694
- async function authenticate(appDir, output) {
6995
+ async function authenticate(appDir, output, minimumValidityMs) {
6695
6996
  try {
6696
- const token = await ensureToken();
6997
+ const token = await ensureToken({ minimumValidityMs });
6697
6998
  return { token, ownerId: decodeJwtPayload(token).sub };
6698
6999
  } catch (error) {
7000
+ if (errorCode(error) !== "not_authenticated") throw error;
6699
7001
  output.die(errorMessage6(error), "not_authenticated", {
6700
7002
  action: { cwd: appDir, argv: ["deepspace", "auth", "login"] }
6701
7003
  });
@@ -7105,7 +7407,12 @@ var logs_default = defineCommand4({
7105
7407
  throw new Refusal(
7106
7408
  `${appId} has never been deployed, so it has no logs. Deploy it first (\`deepspace deploy\`), then read its logs.`,
7107
7409
  "app_not_deployed",
7108
- appDir ? { action: { cwd: appDir, argv: ["deepspace", "deploy", ...envArg ? ["--env", envArg] : []] } } : {}
7410
+ appDir ? {
7411
+ action: {
7412
+ cwd: appDir,
7413
+ argv: ["deepspace", "deploy", ...envArg ? ["--env", envArg] : []]
7414
+ }
7415
+ } : {}
7109
7416
  );
7110
7417
  }
7111
7418
  const MAX_LIMIT = 500;
@@ -7115,12 +7422,8 @@ var logs_default = defineCommand4({
7115
7422
  if (pageLimit !== void 0) params.set("limit", String(pageLimit));
7116
7423
  if (args.level) params.set("level", args.level);
7117
7424
  if (args.search) params.set("search", args.search);
7118
- return apiFetch(
7119
- DEPLOY_URL6,
7120
- token2,
7121
- `/api/apps/${appId}/logs?${params}`,
7122
- signal ? { signal } : void 0
7123
- );
7425
+ const path = `/api/apps/${appId}/logs?${params}`;
7426
+ return signal ? apiFetch(DEPLOY_URL6, token2, path, { signal }) : apiFetchReadWithRetry(DEPLOY_URL6, token2, path);
7124
7427
  };
7125
7428
  const print = (events) => {
7126
7429
  for (const e of events) {
@@ -7303,7 +7606,7 @@ var usage_default = defineDeepspaceCommand({
7303
7606
  });
7304
7607
 
7305
7608
  // src/cli/commands/add.ts
7306
- import { existsSync as existsSync20, readFileSync as readFileSync20, realpathSync as realpathSync4 } from "fs";
7609
+ import { existsSync as existsSync20, readFileSync as readFileSync19, realpathSync as realpathSync4 } from "fs";
7307
7610
  import { dirname as dirname10, join as join22, resolve as resolve17 } from "path";
7308
7611
  import { pathToFileURL as pathToFileURL3 } from "url";
7309
7612
 
@@ -7316,7 +7619,7 @@ import {
7316
7619
  mkdirSync as mkdirSync7,
7317
7620
  mkdtempSync,
7318
7621
  openSync,
7319
- readFileSync as readFileSync19,
7622
+ readFileSync as readFileSync18,
7320
7623
  readSync,
7321
7624
  readdirSync as readdirSync7,
7322
7625
  rmSync as rmSync5,
@@ -7324,7 +7627,7 @@ import {
7324
7627
  } from "fs";
7325
7628
  import { spawnSync as spawnSync6 } from "child_process";
7326
7629
  import { tmpdir as tmpdir2 } from "os";
7327
- import { dirname as dirname9, isAbsolute as isAbsolute4, join as join21, resolve as resolve16 } from "path";
7630
+ import { dirname as dirname9, isAbsolute as isAbsolute5, join as join21, resolve as resolve16 } from "path";
7328
7631
  import { fileURLToPath } from "url";
7329
7632
  var NAV_MARKER = "// \u2500\u2500 Features add nav items below this line \u2500\u2500";
7330
7633
  function featurePackageRoot(moduleUrl = import.meta.url) {
@@ -7333,7 +7636,7 @@ function featurePackageRoot(moduleUrl = import.meta.url) {
7333
7636
  const packagePath = join21(dir, "package.json");
7334
7637
  if (existsSync19(packagePath)) {
7335
7638
  try {
7336
- const pkg2 = JSON.parse(readFileSync19(packagePath, "utf-8"));
7639
+ const pkg2 = JSON.parse(readFileSync18(packagePath, "utf-8"));
7337
7640
  if (pkg2.name === "deepspace") return dir;
7338
7641
  } catch {
7339
7642
  }
@@ -7350,7 +7653,7 @@ function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
7350
7653
  const configPath = join21(packageRoot, "features", id, "feature.json");
7351
7654
  if (!existsSync19(configPath)) return null;
7352
7655
  try {
7353
- const config = JSON.parse(readFileSync19(configPath, "utf-8"));
7656
+ const config = JSON.parse(readFileSync18(configPath, "utf-8"));
7354
7657
  if (typeof config.id !== "string" || typeof config.name !== "string" || !Array.isArray(config.files)) {
7355
7658
  return null;
7356
7659
  }
@@ -7399,7 +7702,7 @@ function suggestFeature(featureId, packageRoot = featurePackageRoot()) {
7399
7702
  return closest !== null && closestDistance <= threshold ? closest : null;
7400
7703
  }
7401
7704
  function resolveTargetDir(raw) {
7402
- return isAbsolute4(raw) ? raw : resolve16(raw);
7705
+ return isAbsolute5(raw) ? raw : resolve16(raw);
7403
7706
  }
7404
7707
  function resolveFeatureDestPath(fileDest, route) {
7405
7708
  const pages = "src/pages/";
@@ -7415,7 +7718,7 @@ function copyFile(src, dest) {
7415
7718
  function copyFileWithImportShift(src, dest, levels) {
7416
7719
  mkdirSync7(dirname9(dest), { recursive: true });
7417
7720
  const prefix = "../".repeat(levels);
7418
- const shifted = readFileSync19(src, "utf-8").replace(
7721
+ const shifted = readFileSync18(src, "utf-8").replace(
7419
7722
  /(from\s+|import\s+|import\()(['"])(\.\.\/)/g,
7420
7723
  `$1$2${prefix}$3`
7421
7724
  );
@@ -7474,7 +7777,7 @@ function integrateSchema(config, targetDir, emit) {
7474
7777
  if (!existsSync19(schemasPath)) {
7475
7778
  return [unresolvedSchema(config.schema, "src/schemas.ts was not found.", emit)];
7476
7779
  }
7477
- let content = readFileSync19(schemasPath, "utf-8");
7780
+ let content = readFileSync18(schemasPath, "utf-8");
7478
7781
  const { exportName, importPath, spreadOperator } = config.schema;
7479
7782
  const importPattern = new RegExp(`^import\\s+\\{[^}]*\\b${exportName}\\b`, "m");
7480
7783
  if (importPattern.test(content)) {
@@ -7531,7 +7834,7 @@ function integrateActions(config, targetDir, emit) {
7531
7834
  if (!existsSync19(actionsPath)) {
7532
7835
  return [unresolvedActions(config.actions, "src/actions/index.ts was not found.", emit)];
7533
7836
  }
7534
- let content = readFileSync19(actionsPath, "utf-8");
7837
+ let content = readFileSync18(actionsPath, "utf-8");
7535
7838
  const registryPattern = /export const actions:\s*Record<string,\s*ActionHandler(?:<[^>]*>)?>\s*=\s*\{/;
7536
7839
  if (!registryPattern.test(content)) {
7537
7840
  const reason = "src/actions/index.ts has an unexpected structure.";
@@ -7586,7 +7889,7 @@ function integrateCode(config, targetDir, emit) {
7586
7889
  });
7587
7890
  continue;
7588
7891
  }
7589
- const original = readFileSync19(targetPath, "utf-8");
7892
+ const original = readFileSync18(targetPath, "utf-8");
7590
7893
  if (original.includes(insertion.insert.trim())) {
7591
7894
  emit(` Integration already present: ${insertion.file}`);
7592
7895
  continue;
@@ -7624,7 +7927,7 @@ ${content.slice(
7624
7927
  function integrateIgnore(config, targetDir) {
7625
7928
  if (!config.ignore?.length) return;
7626
7929
  const targetPath = join21(targetDir, ".gitignore");
7627
- const current = existsSync19(targetPath) ? readFileSync19(targetPath, "utf-8") : "";
7930
+ const current = existsSync19(targetPath) ? readFileSync18(targetPath, "utf-8") : "";
7628
7931
  const present = new Set(current.split(/\r?\n/).map((line) => line.trim()));
7629
7932
  const missing = config.ignore.filter((pattern) => !present.has(pattern));
7630
7933
  if (missing.length === 0) return;
@@ -7644,7 +7947,7 @@ function integrateWrangler(config, targetDir, emit) {
7644
7947
  if (!existsSync19(targetPath)) {
7645
7948
  return [{ area: "wrangler", reason: "wrangler.toml was not found.", steps }];
7646
7949
  }
7647
- let source = readFileSync19(targetPath, "utf-8");
7950
+ let source = readFileSync18(targetPath, "utf-8");
7648
7951
  if (runWorkerFirst && !/^\[assets\]\s*$/m.test(source)) {
7649
7952
  return [{ area: "wrangler", reason: "wrangler.toml has no [assets] section.", steps }];
7650
7953
  }
@@ -7733,7 +8036,7 @@ function integrateCss(packageRoot, config, targetDir, emit) {
7733
8036
  }
7734
8037
  ];
7735
8038
  }
7736
- let styles = readFileSync19(stylesPath, "utf-8");
8039
+ let styles = readFileSync18(stylesPath, "utf-8");
7737
8040
  let integrated = 0;
7738
8041
  const unresolved = [];
7739
8042
  for (const cssFile of config.css) {
@@ -7747,7 +8050,7 @@ function integrateCss(packageRoot, config, targetDir, emit) {
7747
8050
  });
7748
8051
  continue;
7749
8052
  }
7750
- const css = readFileSync19(srcPath, "utf-8");
8053
+ const css = readFileSync18(srcPath, "utf-8");
7751
8054
  const fingerprint = css.match(/\.([\w-]+)\s*\{|@keyframes\s+([\w-]+)/);
7752
8055
  const marker = fingerprint?.[1] ?? fingerprint?.[2];
7753
8056
  if (marker && styles.includes(marker)) {
@@ -7785,7 +8088,7 @@ function integrateRoute(config, targetDir, emit) {
7785
8088
  if (!existsSync19(navPath)) {
7786
8089
  return [unresolvedRoute(config.route, config, "src/nav.ts was not found.", emit)];
7787
8090
  }
7788
- let content = readFileSync19(navPath, "utf-8");
8091
+ let content = readFileSync18(navPath, "utf-8");
7789
8092
  const routePath = config.route.path;
7790
8093
  if (content.includes(`'${routePath}'`)) {
7791
8094
  emit(` Nav already present: ${routePath}`);
@@ -7866,7 +8169,7 @@ function integrateDependencies(config, targetDir, runInstall, outputVisible, emi
7866
8169
  }
7867
8170
  let pkg2;
7868
8171
  try {
7869
- pkg2 = JSON.parse(readFileSync19(packagePath, "utf-8"));
8172
+ pkg2 = JSON.parse(readFileSync18(packagePath, "utf-8"));
7870
8173
  } catch (error) {
7871
8174
  emit(
7872
8175
  ` Could not patch package.json (parse error: ${error instanceof Error ? error.message : String(error)})`
@@ -7926,7 +8229,7 @@ ${child.diagnosticTail}` : "";
7926
8229
  }
7927
8230
  function dependencySnapshot(targetDir) {
7928
8231
  try {
7929
- const pkg2 = JSON.parse(readFileSync19(join21(targetDir, "package.json"), "utf-8"));
8232
+ const pkg2 = JSON.parse(readFileSync18(join21(targetDir, "package.json"), "utf-8"));
7930
8233
  return {
7931
8234
  dependencies: pkg2.dependencies ?? {},
7932
8235
  devDependencies: pkg2.devDependencies ?? {}
@@ -8175,7 +8478,7 @@ function isDeepSpaceApp(dir) {
8175
8478
  const packagePath = join22(dir, "package.json");
8176
8479
  if (!existsSync20(packagePath)) return false;
8177
8480
  try {
8178
- const pkg2 = JSON.parse(readFileSync20(packagePath, "utf-8"));
8481
+ const pkg2 = JSON.parse(readFileSync19(packagePath, "utf-8"));
8179
8482
  return Boolean(pkg2.dependencies?.deepspace || pkg2.devDependencies?.deepspace);
8180
8483
  } catch {
8181
8484
  return false;
@@ -9455,7 +9758,7 @@ var transfer_default = defineCommand8({
9455
9758
  import { defineCommand as defineCommand9 } from "citty";
9456
9759
 
9457
9760
  // src/cli/commands/_invoke-impl.ts
9458
- import { readFileSync as readFileSync21 } from "fs";
9761
+ import { readFileSync as readFileSync20 } from "fs";
9459
9762
  import * as p15 from "@clack/prompts";
9460
9763
  var API_URL7 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
9461
9764
  var DEFAULT_TIMEOUT_MS = 12e4;
@@ -9488,7 +9791,7 @@ function parseTimeout(raw) {
9488
9791
  }
9489
9792
  return timeout;
9490
9793
  }
9491
- function resolveBody(opts) {
9794
+ async function resolveBody(opts) {
9492
9795
  if (opts.body != null && opts.bodyFile != null) {
9493
9796
  throw new Refusal("Pass either --body or --body-file, not both.", "conflicting_body_args");
9494
9797
  }
@@ -9496,7 +9799,19 @@ function resolveBody(opts) {
9496
9799
  if (opts.body != null) {
9497
9800
  raw = opts.body;
9498
9801
  } else if (opts.bodyFile != null) {
9499
- raw = opts.bodyFile === "-" ? readFileSync21(0, "utf-8") : readFileSync21(opts.bodyFile, "utf-8");
9802
+ if (opts.bodyFile === "-" && process.stdin.isTTY) {
9803
+ throw new Refusal(
9804
+ 'Reading the body from stdin ("-"), but stdin is a terminal \u2014 pipe JSON in, or pass --body / a file path.',
9805
+ "no_stdin"
9806
+ );
9807
+ }
9808
+ raw = opts.bodyFile === "-" ? await readStreamText(process.stdin) : readFileSync20(opts.bodyFile, "utf-8");
9809
+ if (opts.bodyFile === "-" && raw.trim() === "") {
9810
+ throw new Refusal(
9811
+ "Nothing arrived on stdin \u2014 check the command feeding this one, or pass --body / a file path.",
9812
+ "empty_input"
9813
+ );
9814
+ }
9500
9815
  } else {
9501
9816
  return "{}";
9502
9817
  }
@@ -9642,7 +9957,7 @@ async function runInfo(args) {
9642
9957
  }
9643
9958
  async function runInvoke(args) {
9644
9959
  const { integration, endpoint } = parseTarget(args.target);
9645
- const body = resolveBody({ body: args.body, bodyFile: args.bodyFile });
9960
+ const body = await resolveBody({ body: args.body, bodyFile: args.bodyFile });
9646
9961
  const timeoutMs = args.timeout ?? DEFAULT_TIMEOUT_MS;
9647
9962
  if (!args.yes) {
9648
9963
  const info2 = requireEndpoint(await fetchCatalog({ summary: true }), integration, endpoint);
@@ -9822,7 +10137,7 @@ var integrations_default = defineCommand9({
9822
10137
 
9823
10138
  // src/cli/commands/feedback.ts
9824
10139
  import * as p16 from "@clack/prompts";
9825
- import { existsSync as existsSync22, readFileSync as readFileSync22 } from "fs";
10140
+ import { existsSync as existsSync22, readFileSync as readFileSync21 } from "fs";
9826
10141
  import { dirname as dirname11, join as join23 } from "path";
9827
10142
  import { fileURLToPath as fileURLToPath2 } from "url";
9828
10143
  import { release } from "os";
@@ -9851,7 +10166,7 @@ function readCliVersion() {
9851
10166
  ];
9852
10167
  for (const path of candidates) {
9853
10168
  if (!existsSync22(path)) continue;
9854
- const pkg2 = JSON.parse(readFileSync22(path, "utf-8"));
10169
+ const pkg2 = JSON.parse(readFileSync21(path, "utf-8"));
9855
10170
  if (pkg2.name === "deepspace" && pkg2.version) return pkg2.version;
9856
10171
  }
9857
10172
  } catch {
@@ -10047,7 +10362,7 @@ Context: deepspace ${payload.cliVersion} \xB7 ${payload.nodeVersion} \xB7 ${payl
10047
10362
 
10048
10363
  // src/cli/commands/secrets.ts
10049
10364
  import { defineCommand as defineCommand10 } from "citty";
10050
- import { readFileSync as readFileSync23 } from "fs";
10365
+ import { readFileSync as readFileSync22 } from "fs";
10051
10366
 
10052
10367
  // src/cli/lib/citty-args.ts
10053
10368
  function dedupePositionals(bound, rest) {
@@ -10261,20 +10576,25 @@ var del2 = defineCommand10({
10261
10576
  for (const key of keys) {
10262
10577
  const name = validateSecretName(key);
10263
10578
  try {
10264
- await deleteSecret(DEPLOY_URL11, t.token, t.appId, t.configName, name);
10265
- deleted++;
10579
+ const result = await deleteSecret(DEPLOY_URL11, t.token, t.appId, t.configName, name);
10580
+ if (result.deleted === false) absent++;
10581
+ else deleted++;
10266
10582
  } catch (err) {
10267
10583
  if (err?.status === 404) absent++;
10268
10584
  else throw err;
10269
10585
  }
10270
10586
  }
10271
10587
  const note3 = absent > 0 ? ` (${absent} already absent)` : "";
10272
- ok(args.json === true, { appId: t.appId, config: t.configName, deleted, absent, ...APPLIES_AT_DEPLOY }, () => {
10273
- console.log(
10274
- `Deleted ${deleted} secret${deleted === 1 ? "" : "s"} from ${t.configName}.${note3}`
10275
- );
10276
- if (deleted > 0) console.log(APPLY_HINT);
10277
- });
10588
+ ok(
10589
+ args.json === true,
10590
+ { appId: t.appId, config: t.configName, deleted, absent, ...APPLIES_AT_DEPLOY },
10591
+ () => {
10592
+ console.log(
10593
+ `Deleted ${deleted} secret${deleted === 1 ? "" : "s"} from ${t.configName}.${note3}`
10594
+ );
10595
+ if (deleted > 0) console.log(APPLY_HINT);
10596
+ }
10597
+ );
10278
10598
  } catch (err) {
10279
10599
  fail(err);
10280
10600
  }
@@ -10298,14 +10618,20 @@ var upload = defineCommand10({
10298
10618
  async run({ args }) {
10299
10619
  try {
10300
10620
  const t = await resolveTarget(args);
10301
- const secrets = parseSecretsUpload(readUploadSource(args.file));
10621
+ const secrets = parseSecretsUpload(await readUploadSource(args.file));
10302
10622
  if (Object.keys(secrets).length === 0)
10303
10623
  throw new InputError("No secrets found in the input.", "empty_input");
10304
10624
  await uploadSecrets(DEPLOY_URL11, t.token, t.appId, t.configName, secrets, args.replace);
10305
10625
  const uploaded = Object.keys(secrets);
10306
10626
  ok(
10307
10627
  args.json === true,
10308
- { appId: t.appId, config: t.configName, uploaded, replaced: args.replace === true, ...APPLIES_AT_DEPLOY },
10628
+ {
10629
+ appId: t.appId,
10630
+ config: t.configName,
10631
+ uploaded,
10632
+ replaced: args.replace === true,
10633
+ ...APPLIES_AT_DEPLOY
10634
+ },
10309
10635
  () => {
10310
10636
  console.log(`Uploaded ${uploaded.length} secrets to ${t.configName}.`);
10311
10637
  if (!warnIfDebugRoutesEnabled(secrets)) console.log(APPLY_HINT);
@@ -10316,10 +10642,18 @@ var upload = defineCommand10({
10316
10642
  }
10317
10643
  }
10318
10644
  });
10319
- function readUploadSource(file) {
10320
- if (file === "-") return readFileSync23(0, "utf-8");
10645
+ async function readUploadSource(file) {
10646
+ if (file === "-") {
10647
+ if (process.stdin.isTTY) {
10648
+ throw new InputError(
10649
+ 'Reading secrets from stdin ("-"), but stdin is a terminal \u2014 pipe dotenv or JSON input, or pass a file path.',
10650
+ "no_stdin"
10651
+ );
10652
+ }
10653
+ return readStreamText(process.stdin, MAX_STDIN_BYTES);
10654
+ }
10321
10655
  try {
10322
- return readFileSync23(file, "utf-8");
10656
+ return readFileSync22(file, "utf-8");
10323
10657
  } catch (err) {
10324
10658
  if (err.code !== "ENOENT") throw err;
10325
10659
  throw new InputError(
@@ -10656,7 +10990,7 @@ var init_default = defineDeepspaceCommand({
10656
10990
  // src/cli/commands/status.ts
10657
10991
  import { defineCommand as defineCommand11 } from "citty";
10658
10992
  import * as p17 from "@clack/prompts";
10659
- import { existsSync as existsSync24, readFileSync as readFileSync24 } from "fs";
10993
+ import { existsSync as existsSync24, readFileSync as readFileSync23 } from "fs";
10660
10994
  function timeAgo(iso) {
10661
10995
  const ms = Date.now() - Date.parse(iso);
10662
10996
  if (!Number.isFinite(ms) || ms < 0) return iso;
@@ -10757,7 +11091,7 @@ var status_default = defineCommand11({
10757
11091
  let who = "logged in";
10758
11092
  try {
10759
11093
  const payload = decodeJwtPayload(
10760
- readFileSync24(TOKEN_PATH, "utf-8").trim()
11094
+ readFileSync23(TOKEN_PATH, "utf-8").trim()
10761
11095
  );
10762
11096
  if (payload.email) who = payload.email;
10763
11097
  userId = payload.sub ?? null;
@@ -11827,7 +12161,7 @@ import { defineCommand as defineCommand12 } from "citty";
11827
12161
 
11828
12162
  // src/cli/commands/workspace/new.ts
11829
12163
  import * as p25 from "@clack/prompts";
11830
- import { isAbsolute as isAbsolute6, resolve as resolve20 } from "path";
12164
+ import { isAbsolute as isAbsolute7, resolve as resolve20 } from "path";
11831
12165
 
11832
12166
  // src/cli/commands/workspace/analysis.ts
11833
12167
  import * as p23 from "@clack/prompts";
@@ -11963,23 +12297,23 @@ import * as p24 from "@clack/prompts";
11963
12297
  import {
11964
12298
  existsSync as existsSync26,
11965
12299
  mkdirSync as mkdirSync8,
11966
- readFileSync as readFileSync25,
12300
+ readFileSync as readFileSync24,
11967
12301
  realpathSync as realpathSync6,
11968
12302
  renameSync as renameSync2,
11969
12303
  statSync as statSync3,
11970
12304
  writeFileSync as writeFileSync7
11971
12305
  } from "fs";
11972
- import { dirname as dirname12, isAbsolute as isAbsolute5, join as join26, relative as relative3, sep as sep3 } from "path";
12306
+ import { dirname as dirname12, isAbsolute as isAbsolute6, join as join26, relative as relative4, sep as sep4 } from "path";
11973
12307
  var WORKSPACE_MARKER = "deepspace-workspace.json";
11974
12308
  function excludeWorktreeDir(appDir, dir) {
11975
12309
  try {
11976
12310
  ensureLocalExclude(appDir, ".deepspace/");
11977
12311
  const realDir = realpathSync6(dir);
11978
- const rel = relative3(realpathSync6(repoToplevel(appDir)), realDir);
11979
- if (!rel || rel === ".." || rel.startsWith(`..${sep3}`) || isAbsolute5(rel)) return;
11980
- const pattern = rel.split(sep3).join("/");
12312
+ const rel = relative4(realpathSync6(repoToplevel(appDir)), realDir);
12313
+ if (!rel || rel === ".." || rel.startsWith(`..${sep4}`) || isAbsolute6(rel)) return;
12314
+ const pattern = rel.split(sep4).join("/");
11981
12315
  if (pattern === ".deepspace" || pattern.startsWith(".deepspace/")) return;
11982
- const relApp = relative3(realpathSync6(appDir), realDir).split(sep3).join("/");
12316
+ const relApp = relative4(realpathSync6(appDir), realDir).split(sep4).join("/");
11983
12317
  if (relApp === ".deepspace" || relApp.startsWith(".deepspace/")) return;
11984
12318
  ensureLocalExclude(appDir, `/${pattern}/`);
11985
12319
  } catch {
@@ -11990,7 +12324,7 @@ function worktreeMarkerPath(worktreeRoot) {
11990
12324
  const result = runGit(worktreeRoot, ["rev-parse", "--absolute-git-dir"], { allowFail: true });
11991
12325
  if (result.status !== 0) return null;
11992
12326
  const gitDir = result.stdout.toString("utf-8").trim();
11993
- return isAbsolute5(gitDir) ? join26(gitDir, WORKSPACE_MARKER) : null;
12327
+ return isAbsolute6(gitDir) ? join26(gitDir, WORKSPACE_MARKER) : null;
11994
12328
  } catch {
11995
12329
  return null;
11996
12330
  }
@@ -12026,7 +12360,7 @@ function isManagedWorkspaceWorktree(worktreeRoot, workspaceId) {
12026
12360
  const markerPath = worktreeMarkerPath(worktreeRoot);
12027
12361
  if (!markerPath) return false;
12028
12362
  try {
12029
- const marker = JSON.parse(readFileSync25(markerPath, "utf-8"));
12363
+ const marker = JSON.parse(readFileSync24(markerPath, "utf-8"));
12030
12364
  return marker.version === 1 && marker.workspaceId === workspaceId && marker.mode === "managed-linked";
12031
12365
  } catch {
12032
12366
  return false;
@@ -12038,7 +12372,7 @@ function defaultWorkspaceRoot(appDir, workspaceId) {
12038
12372
  return join26(primary.path, ".deepspace", "ws", workspaceId.slice(3).toLowerCase());
12039
12373
  }
12040
12374
  function appDirInWorktree(appDir, worktreeRoot) {
12041
- const appRelative = relative3(realpathSync6(repoToplevel(appDir)), realpathSync6(appDir));
12375
+ const appRelative = relative4(realpathSync6(repoToplevel(appDir)), realpathSync6(appDir));
12042
12376
  return appRelative ? join26(worktreeRoot, appRelative) : worktreeRoot;
12043
12377
  }
12044
12378
  function primaryAppDir(appDir) {
@@ -12459,7 +12793,7 @@ var newWorkspaceCommand = defineDeepspaceCommand({
12459
12793
  idempotencyKey: mintIdempotencyKey()
12460
12794
  });
12461
12795
  const dirInput = typeof args.dir === "string" ? args.dir.trim() : "";
12462
- const worktreeRoot = dirInput ? isAbsolute6(dirInput) ? dirInput : resolve20(appDir, dirInput) : defaultWorkspaceRoot(appDir, id);
12796
+ const worktreeRoot = dirInput ? isAbsolute7(dirInput) ? dirInput : resolve20(appDir, dirInput) : defaultWorkspaceRoot(appDir, id);
12463
12797
  const dir = appDirInWorktree(appDir, worktreeRoot);
12464
12798
  try {
12465
12799
  materializeWorkspaceWorktree(appDir, worktreeRoot, branch, baseOid, id);
@@ -12498,7 +12832,7 @@ var newWorkspaceCommand = defineDeepspaceCommand({
12498
12832
  // src/cli/commands/workspace/attach.ts
12499
12833
  import * as p26 from "@clack/prompts";
12500
12834
  import { existsSync as existsSync27, readdirSync as readdirSync9, realpathSync as realpathSync7, rmSync as rmSync7 } from "fs";
12501
- import { isAbsolute as isAbsolute7, resolve as resolve21 } from "path";
12835
+ import { isAbsolute as isAbsolute8, resolve as resolve21 } from "path";
12502
12836
  function samePath(a, b) {
12503
12837
  const real = (p35) => {
12504
12838
  try {
@@ -12594,7 +12928,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
12594
12928
  );
12595
12929
  }
12596
12930
  const requestedDir = typeof args.dir === "string" ? args.dir.trim() : "";
12597
- const worktreeRoot = requestedDir ? isAbsolute7(requestedDir) ? requestedDir : resolve21(appDir, requestedDir) : defaultWorkspaceRoot(appDir, id);
12931
+ const worktreeRoot = requestedDir ? isAbsolute8(requestedDir) ? requestedDir : resolve21(appDir, requestedDir) : defaultWorkspaceRoot(appDir, id);
12598
12932
  const dir2 = appDirInWorktree(appDir, worktreeRoot);
12599
12933
  const localTip = resolveCommit(appDir, `refs/heads/${branch}`);
12600
12934
  if (localTip) {
@@ -13054,13 +13388,13 @@ import * as p30 from "@clack/prompts";
13054
13388
 
13055
13389
  // src/cli/lib/validation.ts
13056
13390
  import { sync as spawnSync7 } from "cross-spawn";
13057
- import { readFileSync as readFileSync26 } from "fs";
13391
+ import { readFileSync as readFileSync25 } from "fs";
13058
13392
  import { join as join27 } from "path";
13059
13393
  var MAX_SUMMARY_CHARS = 4e3;
13060
13394
  var MAX_OUTPUT_BUFFER = 64 * 1024 * 1024;
13061
13395
  function configuredCommand(appDir) {
13062
13396
  try {
13063
- const pkg2 = JSON.parse(readFileSync26(join27(appDir, "package.json"), "utf-8"));
13397
+ const pkg2 = JSON.parse(readFileSync25(join27(appDir, "package.json"), "utf-8"));
13064
13398
  if (pkg2.scripts?.validate) return `${detectPackageManager(appDir)} run --silent validate`;
13065
13399
  } catch {
13066
13400
  }
@@ -14149,13 +14483,13 @@ function sameRefs(left, right) {
14149
14483
 
14150
14484
  // src/cli/commands/update.ts
14151
14485
  import * as p34 from "@clack/prompts";
14152
- import { existsSync as existsSync28, readFileSync as readFileSync27 } from "fs";
14486
+ import { existsSync as existsSync28, readFileSync as readFileSync26 } from "fs";
14153
14487
  import { join as join28, resolve as resolve22 } from "path";
14154
14488
 
14155
14489
  // package.json
14156
14490
  var package_default = {
14157
14491
  name: "deepspace",
14158
- version: "0.24.0",
14492
+ version: "0.24.1",
14159
14493
  engines: {
14160
14494
  node: ">=22.15.0 <23 || >=24 <25 || >=26 <27"
14161
14495
  },
@@ -14337,7 +14671,7 @@ function readManifest(appDir) {
14337
14671
  throw new Refusal("This app has no package.json \u2014 nothing to update.", "not_in_app_repo");
14338
14672
  }
14339
14673
  try {
14340
- return JSON.parse(readFileSync27(path, "utf8"));
14674
+ return JSON.parse(readFileSync26(path, "utf8"));
14341
14675
  } catch {
14342
14676
  throw new Refusal("package.json must contain valid JSON.", "invalid_package_manifest");
14343
14677
  }
@@ -14373,7 +14707,7 @@ function usersSchemaVisibilityUpgradeInstruction(appDir) {
14373
14707
  const schemaName = "src/schemas/users-schema.ts";
14374
14708
  const path = join28(appDir, schemaName);
14375
14709
  if (!existsSync28(path)) return null;
14376
- const source = readFileSync27(path, "utf8");
14710
+ const source = readFileSync26(path, "utf8");
14377
14711
  if (!/\bmember\s*:\s*\{[^}]*\bread\s*:\s*true\b/s.test(source)) return null;
14378
14712
  return `${schemaName}: review member user-row visibility at ${USERS_SCHEMA_VISIBILITY_GUIDE}; leave the app-owned schema unchanged until you choose its policy.`;
14379
14713
  }
@@ -14469,7 +14803,7 @@ var update_default = defineDeepspaceCommand({
14469
14803
 
14470
14804
  // src/cli/cli.ts
14471
14805
  var __dirname = dirname13(fileURLToPath3(import.meta.url));
14472
- var pkg = JSON.parse(readFileSync28(join29(__dirname, "..", "package.json"), "utf-8"));
14806
+ var pkg = JSON.parse(readFileSync27(join29(__dirname, "..", "package.json"), "utf-8"));
14473
14807
  if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync29("/etc/ssl/certs/ca-certificates.crt")) {
14474
14808
  process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
14475
14809
  }