deepspace 0.10.3 → 0.11.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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # deepspace
2
2
 
3
+ ## 0.11.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Replace the base64 deploy asset transport with a content-addressed one. The CLI now hashes the build output locally, asks the platform which files it is missing, and streams only those into the app's release store; unchanged files are never re-uploaded, and no deploy ever serializes its assets into a JSON payload. This removes the "Worker exceeded memory limit" failure that surfaced as a misleading Cloudflare-incident 503 on asset-heavy deploys, and a deploy-service resource limit is now reported distinctly from a genuine Cloudflare API incident.
8
+
9
+ **This release requires an updated deploy service and is required to deploy.** The old grouped-upload endpoints are gone: an older CLI is answered with a clear "update to deploy" instruction, and this CLI refuses to start against a deploy service that does not advertise the new transport. Releases recorded before the cutover can no longer be rolled back to directly — redeploy that commit instead.
10
+
3
11
  ## 0.10.3
4
12
 
5
13
  ### Patch Changes
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // src/cli/cli.ts
4
4
  import { defineCommand as defineCommand13, runMain, runCommand } from "citty";
5
5
  import { sync as spawnSync8 } from "cross-spawn";
6
- import { readFileSync as readFileSync27, existsSync as existsSync25 } from "fs";
6
+ import { readFileSync as readFileSync28, existsSync as existsSync25 } from "fs";
7
7
  import { dirname as dirname10, join as join24 } from "path";
8
8
  import { fileURLToPath as fileURLToPath3 } from "url";
9
9
 
@@ -3715,6 +3715,7 @@ import { resolve as resolve12 } from "path";
3715
3715
  // src/cli/commands/deploy/build.ts
3716
3716
  import * as p6 from "@clack/prompts";
3717
3717
  import { execSync as execSync2 } from "child_process";
3718
+ import { createHash as createHash2 } from "crypto";
3718
3719
  import { existsSync as existsSync15, readFileSync as readFileSync17, readdirSync as readdirSync4 } from "fs";
3719
3720
  import { dirname as dirname5, join as join16, resolve as resolve10 } from "path";
3720
3721
 
@@ -4174,9 +4175,12 @@ function collectAssets(dir) {
4174
4175
  if (entry.isDirectory()) {
4175
4176
  walk(fullPath, relativePath);
4176
4177
  } else {
4178
+ const bytes = readFileSync17(fullPath);
4177
4179
  assets.push({
4178
4180
  path: `/${relativePath}`,
4179
- contentBase64: readFileSync17(fullPath).toString("base64")
4181
+ hash: createHash2("sha256").update(bytes).digest("hex"),
4182
+ size: bytes.byteLength,
4183
+ sourcePath: fullPath
4180
4184
  });
4181
4185
  }
4182
4186
  }
@@ -4463,7 +4467,7 @@ function createDeployOutput(json) {
4463
4467
 
4464
4468
  // src/cli/commands/deploy/request.ts
4465
4469
  import * as p10 from "@clack/prompts";
4466
- import { randomUUID as randomUUID2 } from "crypto";
4470
+ import { readFileSync as readFileSync18 } from "fs";
4467
4471
 
4468
4472
  // src/cli/commands/deploy/repository.ts
4469
4473
  import * as p9 from "@clack/prompts";
@@ -5203,12 +5207,9 @@ function detachedHeadRefusal() {
5203
5207
 
5204
5208
  // src/cli/commands/deploy/request.ts
5205
5209
  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.";
5206
- var MAX_GROUP_BYTES = 40 * 1024 * 1024;
5207
- var GROUP_BYTES = (() => {
5208
- const configured = Number(process.env.DEEPSPACE_DEPLOY_GROUP_BYTES);
5209
- const bytes = Number.isInteger(configured) && configured > 0 ? configured : 3 * 1024 * 1024;
5210
- return Math.min(bytes, MAX_GROUP_BYTES);
5211
- })();
5210
+ 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.";
5211
+ var REQUIRED_ASSET_TRANSPORT = "content-addressed-v1";
5212
+ var UPLOAD_CONCURRENCY = 4;
5212
5213
  async function deployBuiltBundle(options) {
5213
5214
  const {
5214
5215
  deployUrl,
@@ -5229,44 +5230,11 @@ async function deployBuiltBundle(options) {
5229
5230
  } else if (bundle.extraRoutes.length) {
5230
5231
  p10.log.info(`Custom worker-first routes: ${bundle.extraRoutes.join(", ")}`);
5231
5232
  }
5232
- const assetGroups = packAssetGroups(bundle.assets, GROUP_BYTES);
5233
- if (assetGroups.length === 0) assetGroups.push([]);
5234
- const uploadId = randomUUID2();
5235
- const totalGroups = assetGroups.length;
5236
- try {
5237
- for (let index = 0; index < totalGroups; index++) {
5238
- const groupJson = JSON.stringify(assetGroups[index]);
5239
- spinner2.message(`Uploading assets \u2014 group ${index + 1}/${totalGroups}...`);
5240
- const groupResponse = await postWithRetry(
5241
- `${deployUrl}/api/deploy/${appId}/assets?uploadId=${uploadId}&groupIndex=${index}&totalGroups=${totalGroups}`,
5242
- () => ({
5243
- method: "POST",
5244
- headers: {
5245
- Authorization: `Bearer ${token}`,
5246
- "Content-Type": "application/json"
5247
- },
5248
- body: groupJson
5249
- })
5250
- );
5251
- if (!groupResponse.ok) {
5252
- const errorBody = await groupResponse.json().catch(() => ({}));
5253
- await abortStagedUpload(deployUrl, appId, uploadId, totalGroups, token);
5254
- spinner2.stop("Deploy failed");
5255
- output.die(
5256
- `Asset upload failed on group ${index + 1}/${totalGroups}: ${errorBody.error ?? `HTTP ${groupResponse.status}`}`,
5257
- "upload_failed"
5258
- );
5259
- }
5260
- }
5261
- } catch (error) {
5262
- await abortStagedUpload(deployUrl, appId, uploadId, totalGroups, token);
5263
- spinner2.stop("Deploy failed");
5264
- output.die(`Asset upload failed (network): ${errorMessage4(error)}`, "upload_failed");
5265
- }
5233
+ await requireAssetTransport(deployUrl, spinner2, output);
5234
+ await uploadDeployAssets({ deployUrl, appId, token, assets: bundle.assets, output, spinner: spinner2 });
5266
5235
  spinner2.message(`Deploying ${appName}...`);
5267
5236
  let confirmRename = options.rename;
5268
5237
  const bail = async (message, stopLabel = "Deploy failed", code, actionRequired = false, action) => {
5269
- await abortStagedUpload(deployUrl, appId, uploadId, totalGroups, token);
5270
5238
  if (stopLabel !== null) spinner2.stop(stopLabel);
5271
5239
  p10.cancel(code ? withSlug(message, code) : message);
5272
5240
  if (action) printAction(action);
@@ -5288,8 +5256,7 @@ async function deployBuiltBundle(options) {
5288
5256
  new Blob([bundle.workerJs], { type: "application/javascript" }),
5289
5257
  "worker.js"
5290
5258
  );
5291
- form.append("uploadId", uploadId);
5292
- form.append("totalGroups", String(totalGroups));
5259
+ form.append("assetManifest", JSON.stringify(assetManifest(bundle.assets)));
5293
5260
  form.append("appMigrations", JSON.stringify(bundle.appMigrations));
5294
5261
  if (bundle.doManifest) form.append("doManifest", JSON.stringify(bundle.doManifest));
5295
5262
  if (bundle.customBindings.length) {
@@ -5368,7 +5335,11 @@ async function deployBuiltBundle(options) {
5368
5335
  );
5369
5336
  }
5370
5337
  if (!response.ok || !body.success) {
5371
- await bail(formatDeployWorkerError(response.status, body.error), "Deploy failed", body.code);
5338
+ await bail(
5339
+ formatDeployWorkerError(response.status, body.error, body.code),
5340
+ "Deploy failed",
5341
+ body.code
5342
+ );
5372
5343
  }
5373
5344
  if (body.onBehalfOfOwner) {
5374
5345
  p10.log.warn(`Deployed on behalf of owner ${body.onBehalfOfOwner}`);
@@ -5385,17 +5356,139 @@ async function deployBuiltBundle(options) {
5385
5356
  }
5386
5357
  return body;
5387
5358
  }
5388
- async function abortStagedUpload(deployUrl, appId, uploadId, totalGroups, token) {
5359
+ function assetManifest(assets) {
5360
+ return assets.map((asset) => ({ path: asset.path, hash: asset.hash, size: asset.size }));
5361
+ }
5362
+ async function requireAssetTransport(deployUrl, spinner2, output) {
5363
+ let advertised;
5389
5364
  try {
5390
- await fetch(
5391
- `${deployUrl}/api/deploy/${appId}/assets?uploadId=${uploadId}&totalGroups=${totalGroups}`,
5392
- { method: "DELETE", headers: { Authorization: `Bearer ${token}` } }
5365
+ const response = await fetch(`${deployUrl}/api/health`);
5366
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
5367
+ const body = await response.json();
5368
+ advertised = body.capabilities?.assetTransport;
5369
+ } catch (error) {
5370
+ spinner2.stop("Deploy failed");
5371
+ output.die(
5372
+ `Could not reach the DeepSpace deploy service at ${deployUrl} to check its capabilities: ${errorMessage4(error)}`,
5373
+ "deploy_service_unreachable"
5374
+ );
5375
+ }
5376
+ if (advertised !== REQUIRED_ASSET_TRANSPORT) {
5377
+ spinner2.stop("Deploy failed");
5378
+ output.die(
5379
+ `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.`,
5380
+ "asset_transport_unsupported"
5393
5381
  );
5394
- } catch {
5395
5382
  }
5396
5383
  }
5397
- function formatDeployWorkerError(status3, error) {
5384
+ async function uploadDeployAssets(options) {
5385
+ const { deployUrl, appId, token, assets, spinner: spinner2 } = options;
5386
+ const output = options.output;
5387
+ const byHash = new Map(assets.map((asset) => [asset.hash, asset]));
5388
+ spinner2.message("Checking which assets are already uploaded...");
5389
+ let planResponse;
5390
+ try {
5391
+ planResponse = await postWithRetry(`${deployUrl}/api/deploy/${appId}/asset-plan`, () => ({
5392
+ method: "POST",
5393
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
5394
+ body: JSON.stringify({
5395
+ assets: [...byHash.values()].map((asset) => ({ hash: asset.hash, size: asset.size }))
5396
+ })
5397
+ }));
5398
+ } catch (error) {
5399
+ spinner2.stop("Deploy failed");
5400
+ output.die(`Asset plan failed (network): ${errorMessage4(error)}`, "upload_failed");
5401
+ }
5402
+ const planBody = await planResponse.json().catch(() => ({}));
5403
+ if (!planResponse.ok) {
5404
+ spinner2.stop("Deploy failed");
5405
+ output.die(
5406
+ formatDeployWorkerError(planResponse.status, planBody.error, planBody.code),
5407
+ planBody.code ?? "upload_failed"
5408
+ );
5409
+ }
5410
+ const missing = [];
5411
+ for (const hash of planBody.missing ?? []) {
5412
+ const asset = byHash.get(hash);
5413
+ if (!asset) {
5414
+ spinner2.stop("Deploy failed");
5415
+ output.die(
5416
+ `The deploy service asked for asset ${hash}, which is not part of this build. Re-run the deploy; if it persists, report the app id.`,
5417
+ "upload_failed"
5418
+ );
5419
+ }
5420
+ missing.push(asset);
5421
+ }
5422
+ if (missing.length === 0) {
5423
+ p10.log.info(`Assets: ${byHash.size} file(s), all already uploaded`);
5424
+ return;
5425
+ }
5426
+ const missingBytes = missing.reduce((total, asset) => total + asset.size, 0);
5427
+ p10.log.info(
5428
+ `Assets: ${byHash.size} file(s), uploading ${missing.length} (${Math.ceil(missingBytes / 1024)} KiB); the rest are already stored`
5429
+ );
5430
+ let uploaded = 0;
5431
+ const next = () => missing.pop();
5432
+ const worker = async () => {
5433
+ for (let asset = next(); asset; asset = next()) {
5434
+ const response = await postWithRetry(
5435
+ `${deployUrl}/api/deploy/${appId}/assets/${asset.hash}`,
5436
+ () => ({
5437
+ method: "PUT",
5438
+ headers: {
5439
+ Authorization: `Bearer ${token}`,
5440
+ "Content-Type": "application/octet-stream"
5441
+ },
5442
+ // Read whole, per attempt. The platform matches Content-Length
5443
+ // against the plan before it touches storage, and a stream body
5444
+ // would be sent chunked with no length at all; a retry also needs a
5445
+ // body it can send again.
5446
+ body: readFileSync18(asset.sourcePath)
5447
+ })
5448
+ );
5449
+ if (!response.ok) {
5450
+ const body = await response.json().catch(() => ({}));
5451
+ throw new AssetUploadError(
5452
+ body.error ?? `Upload of ${asset.path} failed: HTTP ${response.status}`,
5453
+ body.code ?? "upload_failed",
5454
+ response.status
5455
+ );
5456
+ }
5457
+ uploaded++;
5458
+ spinner2.message(`Uploading assets \u2014 ${uploaded}/${uploaded + missing.length}...`);
5459
+ }
5460
+ };
5461
+ try {
5462
+ await Promise.all(
5463
+ Array.from({ length: Math.min(UPLOAD_CONCURRENCY, missing.length) }, worker)
5464
+ );
5465
+ } catch (error) {
5466
+ spinner2.stop("Deploy failed");
5467
+ if (error instanceof AssetUploadError) {
5468
+ output.die(formatDeployWorkerError(error.status, error.message, error.code), error.code);
5469
+ }
5470
+ output.die(`Asset upload failed (network): ${errorMessage4(error)}`, "upload_failed");
5471
+ }
5472
+ }
5473
+ var AssetUploadError = class extends Error {
5474
+ constructor(message, code, status3) {
5475
+ super(message);
5476
+ this.code = code;
5477
+ this.status = status3;
5478
+ }
5479
+ };
5480
+ function isDeployServiceResourceLimit(status3, error) {
5481
+ if (!error) return status3 === 500 || status3 === 502;
5482
+ return /\b110[12]\b/.test(error) && /worker|error code/i.test(error);
5483
+ }
5484
+ function formatDeployWorkerError(status3, error, code) {
5398
5485
  const detail = error ?? `Deployment error (${status3})`;
5486
+ if (code === "cli_outdated") return detail;
5487
+ if (isDeployServiceResourceLimit(status3, error)) {
5488
+ return `${DEPLOY_SERVICE_LIMIT_HINT}
5489
+
5490
+ Underlying error: ${detail}`;
5491
+ }
5399
5492
  if (status3 < 500 && ![
5400
5493
  "Upload session failed",
5401
5494
  "Asset upload failed",
@@ -5412,23 +5505,6 @@ Underlying error: ${detail}`;
5412
5505
  function errorMessage4(error) {
5413
5506
  return error instanceof Error ? error.message : String(error);
5414
5507
  }
5415
- function packAssetGroups(assets, maxBytes) {
5416
- const groups = [];
5417
- let current = [];
5418
- let currentBytes = 2;
5419
- for (const asset of assets) {
5420
- const entryBytes = Buffer.byteLength(JSON.stringify(asset), "utf-8") + 1;
5421
- if (current.length > 0 && currentBytes + entryBytes > maxBytes) {
5422
- groups.push(current);
5423
- current = [];
5424
- currentBytes = 2;
5425
- }
5426
- current.push(asset);
5427
- currentBytes += entryBytes;
5428
- }
5429
- if (current.length > 0) groups.push(current);
5430
- return groups;
5431
- }
5432
5508
  async function postWithRetry(url, makeInit, {
5433
5509
  attempts = 4,
5434
5510
  retryServerErrors = true
@@ -5454,7 +5530,7 @@ async function postWithRetry(url, makeInit, {
5454
5530
 
5455
5531
  // src/cli/commands/deploy/secrets.ts
5456
5532
  import * as p11 from "@clack/prompts";
5457
- import { existsSync as existsSync18, readFileSync as readFileSync18 } from "fs";
5533
+ import { existsSync as existsSync18, readFileSync as readFileSync19 } from "fs";
5458
5534
  function classifyDevVarsSecrets(options) {
5459
5535
  const store = new Set(options.storeSecretNames);
5460
5536
  const strayKeys = options.handEditedDevVarKeys.filter((key) => !store.has(key));
@@ -5553,7 +5629,7 @@ function prepareDeploySecrets(options) {
5553
5629
  }
5554
5630
  function readCustomDevVars(path, output) {
5555
5631
  if (!existsSync18(path)) return {};
5556
- const section = extractCustomDevVars(stripGeneratedSecretsCache(readFileSync18(path, "utf-8")));
5632
+ const section = extractCustomDevVars(stripGeneratedSecretsCache(readFileSync19(path, "utf-8")));
5557
5633
  if (!section) return {};
5558
5634
  try {
5559
5635
  return parseDevVars(section);
@@ -6479,7 +6555,7 @@ var usage_default = defineDeepspaceCommand({
6479
6555
  });
6480
6556
 
6481
6557
  // src/cli/commands/add.ts
6482
- import { existsSync as existsSync20, readFileSync as readFileSync20, realpathSync as realpathSync3 } from "fs";
6558
+ import { existsSync as existsSync20, readFileSync as readFileSync21, realpathSync as realpathSync3 } from "fs";
6483
6559
  import { dirname as dirname7, join as join19, resolve as resolve14 } from "path";
6484
6560
  import { pathToFileURL as pathToFileURL3 } from "url";
6485
6561
 
@@ -6492,7 +6568,7 @@ import {
6492
6568
  mkdirSync as mkdirSync6,
6493
6569
  mkdtempSync,
6494
6570
  openSync as openSync2,
6495
- readFileSync as readFileSync19,
6571
+ readFileSync as readFileSync20,
6496
6572
  readSync,
6497
6573
  readdirSync as readdirSync5,
6498
6574
  rmSync as rmSync4,
@@ -6509,7 +6585,7 @@ function featurePackageRoot(moduleUrl = import.meta.url) {
6509
6585
  const packagePath = join18(dir, "package.json");
6510
6586
  if (existsSync19(packagePath)) {
6511
6587
  try {
6512
- const pkg2 = JSON.parse(readFileSync19(packagePath, "utf-8"));
6588
+ const pkg2 = JSON.parse(readFileSync20(packagePath, "utf-8"));
6513
6589
  if (pkg2.name === "deepspace") return dir;
6514
6590
  } catch {
6515
6591
  }
@@ -6526,7 +6602,7 @@ function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
6526
6602
  const configPath = join18(packageRoot, "features", id, "feature.json");
6527
6603
  if (!existsSync19(configPath)) return null;
6528
6604
  try {
6529
- const config = JSON.parse(readFileSync19(configPath, "utf-8"));
6605
+ const config = JSON.parse(readFileSync20(configPath, "utf-8"));
6530
6606
  if (typeof config.id !== "string" || typeof config.name !== "string" || !Array.isArray(config.files)) {
6531
6607
  return null;
6532
6608
  }
@@ -6591,7 +6667,7 @@ function copyFile(src, dest) {
6591
6667
  function copyFileWithImportShift(src, dest, levels) {
6592
6668
  mkdirSync6(dirname6(dest), { recursive: true });
6593
6669
  const prefix = "../".repeat(levels);
6594
- const shifted = readFileSync19(src, "utf-8").replace(
6670
+ const shifted = readFileSync20(src, "utf-8").replace(
6595
6671
  /(from\s+|import\s+|import\()(['"])(\.\.\/)/g,
6596
6672
  `$1$2${prefix}$3`
6597
6673
  );
@@ -6650,7 +6726,7 @@ function integrateSchema(config, targetDir, emit) {
6650
6726
  if (!existsSync19(schemasPath)) {
6651
6727
  return [unresolvedSchema(config.schema, "src/schemas.ts was not found.", emit)];
6652
6728
  }
6653
- let content = readFileSync19(schemasPath, "utf-8");
6729
+ let content = readFileSync20(schemasPath, "utf-8");
6654
6730
  const { exportName, importPath, spreadOperator } = config.schema;
6655
6731
  const importPattern = new RegExp(`^import\\s+\\{[^}]*\\b${exportName}\\b`, "m");
6656
6732
  if (importPattern.test(content)) {
@@ -6707,7 +6783,7 @@ function integrateActions(config, targetDir, emit) {
6707
6783
  if (!existsSync19(actionsPath)) {
6708
6784
  return [unresolvedActions(config.actions, "src/actions/index.ts was not found.", emit)];
6709
6785
  }
6710
- let content = readFileSync19(actionsPath, "utf-8");
6786
+ let content = readFileSync20(actionsPath, "utf-8");
6711
6787
  const registryPattern = /export const actions:\s*Record<string,\s*ActionHandler(?:<[^>]*>)?>\s*=\s*\{/;
6712
6788
  if (!registryPattern.test(content)) {
6713
6789
  const reason = "src/actions/index.ts has an unexpected structure.";
@@ -6762,7 +6838,7 @@ function integrateCode(config, targetDir, emit) {
6762
6838
  });
6763
6839
  continue;
6764
6840
  }
6765
- const original = readFileSync19(targetPath, "utf-8");
6841
+ const original = readFileSync20(targetPath, "utf-8");
6766
6842
  if (original.includes(insertion.insert.trim())) {
6767
6843
  emit(` Integration already present: ${insertion.file}`);
6768
6844
  continue;
@@ -6798,7 +6874,7 @@ ${content.slice(importIndex < 0 ? 0 : importIndex)}`;
6798
6874
  function integrateIgnore(config, targetDir) {
6799
6875
  if (!config.ignore?.length) return;
6800
6876
  const targetPath = join18(targetDir, ".gitignore");
6801
- const current = existsSync19(targetPath) ? readFileSync19(targetPath, "utf-8") : "";
6877
+ const current = existsSync19(targetPath) ? readFileSync20(targetPath, "utf-8") : "";
6802
6878
  const present = new Set(current.split(/\r?\n/).map((line) => line.trim()));
6803
6879
  const missing = config.ignore.filter((pattern) => !present.has(pattern));
6804
6880
  if (missing.length === 0) return;
@@ -6818,7 +6894,7 @@ function integrateWrangler(config, targetDir, emit) {
6818
6894
  if (!existsSync19(targetPath)) {
6819
6895
  return [{ area: "wrangler", reason: "wrangler.toml was not found.", steps }];
6820
6896
  }
6821
- let source = readFileSync19(targetPath, "utf-8");
6897
+ let source = readFileSync20(targetPath, "utf-8");
6822
6898
  if (runWorkerFirst && !/^\[assets\]\s*$/m.test(source)) {
6823
6899
  return [{ area: "wrangler", reason: "wrangler.toml has no [assets] section.", steps }];
6824
6900
  }
@@ -6907,7 +6983,7 @@ function integrateCss(packageRoot, config, targetDir, emit) {
6907
6983
  }
6908
6984
  ];
6909
6985
  }
6910
- let styles = readFileSync19(stylesPath, "utf-8");
6986
+ let styles = readFileSync20(stylesPath, "utf-8");
6911
6987
  let integrated = 0;
6912
6988
  const unresolved = [];
6913
6989
  for (const cssFile of config.css) {
@@ -6921,7 +6997,7 @@ function integrateCss(packageRoot, config, targetDir, emit) {
6921
6997
  });
6922
6998
  continue;
6923
6999
  }
6924
- const css = readFileSync19(srcPath, "utf-8");
7000
+ const css = readFileSync20(srcPath, "utf-8");
6925
7001
  const fingerprint = css.match(/\.([\w-]+)\s*\{|@keyframes\s+([\w-]+)/);
6926
7002
  const marker = fingerprint?.[1] ?? fingerprint?.[2];
6927
7003
  if (marker && styles.includes(marker)) {
@@ -6952,7 +7028,7 @@ function integrateRoute(config, targetDir, emit) {
6952
7028
  if (!existsSync19(navPath)) {
6953
7029
  return [unresolvedRoute(config.route, config, "src/nav.ts was not found.", emit)];
6954
7030
  }
6955
- let content = readFileSync19(navPath, "utf-8");
7031
+ let content = readFileSync20(navPath, "utf-8");
6956
7032
  const routePath = config.route.path;
6957
7033
  if (content.includes(`'${routePath}'`)) {
6958
7034
  emit(` Nav already present: ${routePath}`);
@@ -7033,7 +7109,7 @@ function integrateDependencies(config, targetDir, runInstall, outputVisible, emi
7033
7109
  }
7034
7110
  let pkg2;
7035
7111
  try {
7036
- pkg2 = JSON.parse(readFileSync19(packagePath, "utf-8"));
7112
+ pkg2 = JSON.parse(readFileSync20(packagePath, "utf-8"));
7037
7113
  } catch (error) {
7038
7114
  emit(
7039
7115
  ` Could not patch package.json (parse error: ${error instanceof Error ? error.message : String(error)})`
@@ -7093,7 +7169,7 @@ ${child.diagnosticTail}` : "";
7093
7169
  }
7094
7170
  function dependencySnapshot(targetDir) {
7095
7171
  try {
7096
- const pkg2 = JSON.parse(readFileSync19(join18(targetDir, "package.json"), "utf-8"));
7172
+ const pkg2 = JSON.parse(readFileSync20(join18(targetDir, "package.json"), "utf-8"));
7097
7173
  return {
7098
7174
  dependencies: pkg2.dependencies ?? {},
7099
7175
  devDependencies: pkg2.devDependencies ?? {}
@@ -7342,7 +7418,7 @@ function isDeepSpaceApp(dir) {
7342
7418
  const packagePath = join19(dir, "package.json");
7343
7419
  if (!existsSync20(packagePath)) return false;
7344
7420
  try {
7345
- const pkg2 = JSON.parse(readFileSync20(packagePath, "utf-8"));
7421
+ const pkg2 = JSON.parse(readFileSync21(packagePath, "utf-8"));
7346
7422
  return Boolean(pkg2.dependencies?.deepspace || pkg2.devDependencies?.deepspace);
7347
7423
  } catch {
7348
7424
  return false;
@@ -8125,7 +8201,7 @@ var transfer_default = defineCommand7({
8125
8201
  import { defineCommand as defineCommand8 } from "citty";
8126
8202
 
8127
8203
  // src/cli/commands/_invoke-impl.ts
8128
- import { readFileSync as readFileSync21 } from "fs";
8204
+ import { readFileSync as readFileSync22 } from "fs";
8129
8205
  import * as p15 from "@clack/prompts";
8130
8206
  var API_URL7 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
8131
8207
  var DEFAULT_TIMEOUT_MS = 12e4;
@@ -8166,7 +8242,7 @@ function resolveBody(opts) {
8166
8242
  if (opts.body != null) {
8167
8243
  raw = opts.body;
8168
8244
  } else if (opts.bodyFile != null) {
8169
- raw = opts.bodyFile === "-" ? readFileSync21(0, "utf-8") : readFileSync21(opts.bodyFile, "utf-8");
8245
+ raw = opts.bodyFile === "-" ? readFileSync22(0, "utf-8") : readFileSync22(opts.bodyFile, "utf-8");
8170
8246
  } else {
8171
8247
  return "{}";
8172
8248
  }
@@ -8445,7 +8521,7 @@ var integrations_default = defineCommand8({
8445
8521
 
8446
8522
  // src/cli/commands/feedback.ts
8447
8523
  import * as p16 from "@clack/prompts";
8448
- import { existsSync as existsSync21, readFileSync as readFileSync22 } from "fs";
8524
+ import { existsSync as existsSync21, readFileSync as readFileSync23 } from "fs";
8449
8525
  import { dirname as dirname8, join as join20 } from "path";
8450
8526
  import { fileURLToPath as fileURLToPath2 } from "url";
8451
8527
  import { release } from "os";
@@ -8474,7 +8550,7 @@ function readCliVersion() {
8474
8550
  ];
8475
8551
  for (const path of candidates) {
8476
8552
  if (!existsSync21(path)) continue;
8477
- const pkg2 = JSON.parse(readFileSync22(path, "utf-8"));
8553
+ const pkg2 = JSON.parse(readFileSync23(path, "utf-8"));
8478
8554
  if (pkg2.name === "deepspace" && pkg2.version) return pkg2.version;
8479
8555
  }
8480
8556
  } catch {
@@ -8670,7 +8746,7 @@ Context: deepspace ${payload.cliVersion} \xB7 ${payload.nodeVersion} \xB7 ${payl
8670
8746
 
8671
8747
  // src/cli/commands/secrets.ts
8672
8748
  import { defineCommand as defineCommand9 } from "citty";
8673
- import { readFileSync as readFileSync23 } from "fs";
8749
+ import { readFileSync as readFileSync24 } from "fs";
8674
8750
 
8675
8751
  // src/cli/lib/citty-args.ts
8676
8752
  function dedupePositionals(bound, rest) {
@@ -8940,7 +9016,7 @@ var upload = defineCommand9({
8940
9016
  async run({ args }) {
8941
9017
  try {
8942
9018
  const t = await resolveTarget(args);
8943
- const content = args.file === "-" ? readFileSync23(0, "utf-8") : readFileSync23(args.file, "utf-8");
9019
+ const content = args.file === "-" ? readFileSync24(0, "utf-8") : readFileSync24(args.file, "utf-8");
8944
9020
  const secrets = parseSecretsUpload(content);
8945
9021
  if (Object.keys(secrets).length === 0)
8946
9022
  fail(new InputError("No secrets found in the input.", "empty_input"));
@@ -9196,7 +9272,7 @@ var init_default = defineDeepspaceCommand({
9196
9272
  // src/cli/commands/status.ts
9197
9273
  import { defineCommand as defineCommand10 } from "citty";
9198
9274
  import * as p17 from "@clack/prompts";
9199
- import { existsSync as existsSync22, readFileSync as readFileSync24 } from "fs";
9275
+ import { existsSync as existsSync22, readFileSync as readFileSync25 } from "fs";
9200
9276
  function timeAgo(iso) {
9201
9277
  const ms = Date.now() - Date.parse(iso);
9202
9278
  if (!Number.isFinite(ms) || ms < 0) return iso;
@@ -9290,7 +9366,7 @@ var status_default = defineCommand10({
9290
9366
  let who = "logged in";
9291
9367
  try {
9292
9368
  const payload = decodeJwtPayload(
9293
- readFileSync24(TOKEN_PATH, "utf-8").trim()
9369
+ readFileSync25(TOKEN_PATH, "utf-8").trim()
9294
9370
  );
9295
9371
  if (payload.email) who = payload.email;
9296
9372
  userId = payload.sub ?? null;
@@ -10408,7 +10484,7 @@ function printOverlaps(overlaps, json) {
10408
10484
 
10409
10485
  // src/cli/commands/workspace/local.ts
10410
10486
  import * as p24 from "@clack/prompts";
10411
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync25, realpathSync as realpathSync5, renameSync as renameSync2, statSync, writeFileSync as writeFileSync9 } from "fs";
10487
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync26, realpathSync as realpathSync5, renameSync as renameSync2, statSync, writeFileSync as writeFileSync9 } from "fs";
10412
10488
  import { dirname as dirname9, isAbsolute as isAbsolute5, join as join22, relative as relative2, sep as sep2 } from "path";
10413
10489
  var WORKSPACE_MARKER = "deepspace-workspace.json";
10414
10490
  function excludeWorktreeDir(appDir, dir) {
@@ -10466,7 +10542,7 @@ function isManagedWorkspaceWorktree(worktreeRoot, workspaceId) {
10466
10542
  const markerPath = worktreeMarkerPath(worktreeRoot);
10467
10543
  if (!markerPath) return false;
10468
10544
  try {
10469
- const marker = JSON.parse(readFileSync25(markerPath, "utf-8"));
10545
+ const marker = JSON.parse(readFileSync26(markerPath, "utf-8"));
10470
10546
  return marker.version === 1 && marker.workspaceId === workspaceId && marker.mode === "managed-linked";
10471
10547
  } catch {
10472
10548
  return false;
@@ -11328,13 +11404,13 @@ import * as p30 from "@clack/prompts";
11328
11404
 
11329
11405
  // src/cli/lib/validation.ts
11330
11406
  import { sync as spawnSync7 } from "cross-spawn";
11331
- import { readFileSync as readFileSync26 } from "fs";
11407
+ import { readFileSync as readFileSync27 } from "fs";
11332
11408
  import { join as join23 } from "path";
11333
11409
  var MAX_SUMMARY_CHARS = 4e3;
11334
11410
  var MAX_OUTPUT_BUFFER = 64 * 1024 * 1024;
11335
11411
  function configuredCommand(appDir) {
11336
11412
  try {
11337
- const pkg2 = JSON.parse(readFileSync26(join23(appDir, "package.json"), "utf-8"));
11413
+ const pkg2 = JSON.parse(readFileSync27(join23(appDir, "package.json"), "utf-8"));
11338
11414
  if (pkg2.scripts?.validate) return `${detectPackageManager(appDir)} run --silent validate`;
11339
11415
  } catch {
11340
11416
  }
@@ -12828,7 +12904,7 @@ function reportMigration(migration, json) {
12828
12904
 
12829
12905
  // src/cli/cli.ts
12830
12906
  var __dirname = dirname10(fileURLToPath3(import.meta.url));
12831
- var pkg = JSON.parse(readFileSync27(join24(__dirname, "..", "package.json"), "utf-8"));
12907
+ var pkg = JSON.parse(readFileSync28(join24(__dirname, "..", "package.json"), "utf-8"));
12832
12908
  if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync25("/etc/ssl/certs/ca-certificates.crt")) {
12833
12909
  process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
12834
12910
  }