deepspace 0.19.5 → 0.21.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/{chunk-A7UFIOWO.js → chunk-KDCDRHBA.js} +7 -2
  3. package/dist/chunk-KDCDRHBA.js.map +1 -0
  4. package/dist/cli.js +219 -94
  5. package/dist/cli.js.map +1 -1
  6. package/dist/documentation-client-core.d.ts +3 -2
  7. package/dist/documentation-client-core.js +1 -1
  8. package/dist/documentation-default-renderer.cjs +48 -48
  9. package/dist/documentation-react.d.ts +2 -1
  10. package/dist/documentation-react.js +1 -1
  11. package/dist/documentation-runtime.js +7 -7
  12. package/dist/documentation-server-core.d.ts +3 -2
  13. package/dist/documentation-server-core.js +1 -1
  14. package/dist/documentation.d.ts +40 -12
  15. package/dist/documentation.js +40 -7
  16. package/dist/documentation.js.map +1 -1
  17. package/dist/index.d.ts +225 -21
  18. package/dist/index.js +262 -112
  19. package/dist/index.js.map +1 -1
  20. package/dist/{public-CFAj1hL2.d.ts → public-DenQ1TsZ.d.ts} +36 -11
  21. package/dist/server.d.ts +46 -29
  22. package/dist/server.js +1041 -254
  23. package/dist/server.js.map +1 -1
  24. package/dist/testing-mcp.d.ts +69 -0
  25. package/dist/testing-mcp.js +99 -0
  26. package/dist/testing-mcp.js.map +1 -0
  27. package/dist/testing.d.ts +17 -20
  28. package/dist/testing.js +47 -25
  29. package/dist/testing.js.map +1 -1
  30. package/dist/worker.d.ts +73 -32
  31. package/dist/worker.js +1094 -244
  32. package/dist/worker.js.map +1 -1
  33. package/features/ai-chat/feature.json +2 -2
  34. package/features/ai-chat/src/AiChatPage.tsx +56 -21
  35. package/features/ai-chat/src/ChatPanel.tsx +33 -10
  36. package/features/ai-chat/src/__tests__/ChatPanel.lifecycle.test.tsx +156 -0
  37. package/features/cron/src/CronLogPage.tsx +107 -69
  38. package/features/file-attachments/feature.json +2 -1
  39. package/features/file-manager/feature.json +2 -1
  40. package/features/file-manager/src/FileManagerPage.tsx +227 -194
  41. package/features/integration-test/src/IntegrationTestPage.tsx +54 -34
  42. package/package.json +5 -1
  43. package/dist/chunk-A7UFIOWO.js.map +0 -1
package/dist/cli.js CHANGED
@@ -530,7 +530,7 @@ import { readFileSync as readFileSync2, existsSync, mkdirSync } from "fs";
530
530
  import { join } from "path";
531
531
  import { homedir } from "os";
532
532
 
533
- // src/cli/jwt.ts
533
+ // src/shared/jwt.ts
534
534
  function decodeJwtPayload(token) {
535
535
  const payloadSegment = token.split(".")[1];
536
536
  if (!payloadSegment) {
@@ -2677,11 +2677,15 @@ Free it with \`${killCmd}\`, or start on another port: \`deepspace dev start --p
2677
2677
  }
2678
2678
  const url = `http://${host === DEFAULT_DEV_HOST ? "localhost" : host}:${port}`;
2679
2679
  say("Starting dev server...\n");
2680
- const vite = spawn2("npx", ["vite", "--port", String(port), "--strictPort", "--host", host], {
2681
- cwd: appDir,
2682
- stdio: "inherit",
2683
- env: wranglerViteEnv(process.env, wranglerConfig, { DEEPSPACE_PORT: String(port) })
2684
- });
2680
+ const vite = spawn2(
2681
+ "npx",
2682
+ ["vite", "--port", String(port), "--strictPort", "--host", host, "--clearScreen", "false"],
2683
+ {
2684
+ cwd: appDir,
2685
+ stdio: "inherit",
2686
+ env: wranglerViteEnv(process.env, wranglerConfig, { DEEPSPACE_PORT: String(port) })
2687
+ }
2688
+ );
2685
2689
  const readiness = new AbortController();
2686
2690
  if (args.json) {
2687
2691
  void waitForPortListening(port, host, READINESS_TIMEOUT_MS, readiness.signal).then((ready) => {
@@ -3195,7 +3199,7 @@ async function syncTestAccountStore() {
3195
3199
  // src/cli/commands/test.ts
3196
3200
  var DEPLOY_URL2 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
3197
3201
  var PLAYWRIGHT_OUTPUT_DIR = ".deepspace/test-results";
3198
- function playwrightTestArgs(testFiles) {
3202
+ function playwrightTestArgs(testFiles, flags = {}) {
3199
3203
  return [
3200
3204
  "playwright",
3201
3205
  "test",
@@ -3203,6 +3207,9 @@ function playwrightTestArgs(testFiles) {
3203
3207
  "tests/playwright.config.ts",
3204
3208
  "--output",
3205
3209
  PLAYWRIGHT_OUTPUT_DIR,
3210
+ ...flags.grep ? ["--grep", flags.grep] : [],
3211
+ ...flags.project ? ["--project", flags.project] : [],
3212
+ ...flags.headed ? ["--headed"] : [],
3206
3213
  ...testFiles
3207
3214
  ];
3208
3215
  }
@@ -3223,6 +3230,21 @@ var test_default = defineDeepspaceCommand({
3223
3230
  description: `Port for vite/webServer (default ${DEFAULT_PORT}, or $DEEPSPACE_PORT)`,
3224
3231
  required: false
3225
3232
  },
3233
+ grep: {
3234
+ type: "string",
3235
+ description: "Only run Playwright tests matching this pattern (forwarded to --grep)",
3236
+ required: false
3237
+ },
3238
+ project: {
3239
+ type: "string",
3240
+ description: "Playwright project to run (forwarded to --project)",
3241
+ required: false
3242
+ },
3243
+ headed: {
3244
+ type: "boolean",
3245
+ description: "Run Playwright browsers headed (forwarded to --headed)",
3246
+ default: false
3247
+ },
3226
3248
  env: {
3227
3249
  type: "string",
3228
3250
  alias: "e",
@@ -3237,6 +3259,11 @@ var test_default = defineDeepspaceCommand({
3237
3259
  preflightNodeVersion("test run");
3238
3260
  const suite = args.suite ?? "default";
3239
3261
  const wranglerEnv = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
3262
+ const forwarded = {
3263
+ grep: typeof args.grep === "string" && args.grep ? args.grep : void 0,
3264
+ project: typeof args.project === "string" && args.project ? args.project : void 0,
3265
+ headed: Boolean(args.headed)
3266
+ };
3240
3267
  const start = resolve9(".");
3241
3268
  const appDir = findAppDir(start);
3242
3269
  if (!appDir) {
@@ -3293,32 +3320,36 @@ var test_default = defineDeepspaceCommand({
3293
3320
  let exitCode = 0;
3294
3321
  switch (suite) {
3295
3322
  case "smoke":
3296
- exitCode = runPlaywright(appDir, ["tests/smoke.spec.ts"], port, wranglerEnv);
3323
+ exitCode = runPlaywright(appDir, ["tests/smoke.spec.ts"], port, wranglerEnv, forwarded);
3297
3324
  break;
3298
3325
  case "api":
3299
- exitCode = runPlaywright(appDir, ["tests/api.spec.ts"], port, wranglerEnv);
3326
+ exitCode = runPlaywright(appDir, ["tests/api.spec.ts"], port, wranglerEnv, forwarded);
3300
3327
  break;
3301
3328
  case "e2e":
3302
- exitCode = runPlaywright(appDir, [], port, wranglerEnv);
3329
+ exitCode = runPlaywright(appDir, [], port, wranglerEnv, forwarded);
3303
3330
  break;
3304
3331
  case "unit":
3332
+ if (forwarded.grep || forwarded.project || forwarded.headed) {
3333
+ console.error("note: --grep/--project/--headed apply to Playwright suites and are ignored by `unit`");
3334
+ }
3305
3335
  exitCode = runVitest(appDir);
3306
3336
  break;
3307
3337
  case "all":
3308
3338
  exitCode = runVitest(appDir);
3309
- if (exitCode === 0) exitCode = runPlaywright(appDir, [], port, wranglerEnv);
3339
+ if (exitCode === 0) exitCode = runPlaywright(appDir, [], port, wranglerEnv, forwarded);
3310
3340
  break;
3311
3341
  case "default":
3312
3342
  exitCode = runPlaywright(
3313
3343
  appDir,
3314
3344
  ["tests/smoke.spec.ts", "tests/api.spec.ts"],
3315
3345
  port,
3316
- wranglerEnv
3346
+ wranglerEnv,
3347
+ forwarded
3317
3348
  );
3318
3349
  break;
3319
3350
  default:
3320
3351
  if (suite.endsWith(".spec.ts")) {
3321
- exitCode = runPlaywright(appDir, [suite], port, wranglerEnv);
3352
+ exitCode = runPlaywright(appDir, [suite], port, wranglerEnv, forwarded);
3322
3353
  } else {
3323
3354
  throw new Refusal(
3324
3355
  `Unknown test suite: ${suite}
@@ -3335,7 +3366,7 @@ Available: smoke, api, e2e, unit, all`,
3335
3366
  return { data: { suite, port, appDir, wranglerEnv: wranglerEnv ?? null } };
3336
3367
  }
3337
3368
  });
3338
- function runPlaywright(appDir, testFiles, port, wranglerEnv) {
3369
+ function runPlaywright(appDir, testFiles, port, wranglerEnv, flags) {
3339
3370
  let wranglerConfig;
3340
3371
  try {
3341
3372
  wranglerConfig = prepareWranglerEnvConfig(appDir, wranglerEnv);
@@ -3344,7 +3375,7 @@ function runPlaywright(appDir, testFiles, port, wranglerEnv) {
3344
3375
  return 1;
3345
3376
  }
3346
3377
  try {
3347
- const result = spawnSync4("npx", playwrightTestArgs(testFiles), {
3378
+ const result = spawnSync4("npx", playwrightTestArgs(testFiles, flags), {
3348
3379
  cwd: appDir,
3349
3380
  stdio: "inherit",
3350
3381
  env: wranglerViteEnv(process.env, wranglerConfig, {
@@ -3453,7 +3484,7 @@ var create = defineDeepspaceCommand({
3453
3484
  await ensureToken();
3454
3485
  const email = args.email;
3455
3486
  const password = args.password;
3456
- const name = args.name;
3487
+ const name = args.name ?? email.split("@")[0];
3457
3488
  const label = args.label;
3458
3489
  let account;
3459
3490
  try {
@@ -3477,6 +3508,7 @@ var create = defineDeepspaceCommand({
3477
3508
  console.log(`Created test account:`);
3478
3509
  console.log(` ID: ${account.id}`);
3479
3510
  console.log(` Email: ${account.email}`);
3511
+ console.log(` Selector: ${name}`);
3480
3512
  console.log(` Password: ${password}`);
3481
3513
  console.log(` UserID: ${account.userId}`);
3482
3514
  if (account.label) console.log(` Label: ${account.label}`);
@@ -3487,6 +3519,7 @@ Saved to ${TEST_ACCOUNTS_PATH}`);
3487
3519
  data: {
3488
3520
  id: account.id,
3489
3521
  email: account.email,
3522
+ name,
3490
3523
  // The password is already echoed on the human path and stored 0600 in
3491
3524
  // ACCOUNTS_PATH — withholding it from --json would only force scripts
3492
3525
  // to parse the file themselves.
@@ -3504,6 +3537,18 @@ var list = defineDeepspaceCommand({
3504
3537
  name: "list",
3505
3538
  description: "List your test accounts"
3506
3539
  },
3540
+ args: {
3541
+ usable: {
3542
+ type: "boolean",
3543
+ description: "Only accounts with locally saved credentials (usable by the users() fixture)",
3544
+ default: false
3545
+ },
3546
+ reveal: {
3547
+ type: "boolean",
3548
+ description: "Print locally saved passwords instead of masking them",
3549
+ default: false
3550
+ }
3551
+ },
3507
3552
  async run({ args }) {
3508
3553
  await ensureToken();
3509
3554
  let remote;
@@ -3512,42 +3557,47 @@ var list = defineDeepspaceCommand({
3512
3557
  } catch (err) {
3513
3558
  throw new Refusal(`Failed: ${err.message}`, "test_accounts_list_failed");
3514
3559
  }
3515
- const local = loadAllTestAccounts();
3516
- const localByEmail = new Map(local.map((a) => [a.email, a]));
3517
- if (remote.length === 0) {
3560
+ const localByEmail = new Map(loadAllTestAccounts().map((a) => [a.email, a]));
3561
+ const accounts = remote.map((a) => {
3562
+ const stored = localByEmail.get(a.email);
3563
+ return {
3564
+ id: a.id,
3565
+ email: a.email,
3566
+ userId: a.userId,
3567
+ label: a.label,
3568
+ createdAt: a.createdAt,
3569
+ name: stored?.name ?? null,
3570
+ password: stored?.password ?? null,
3571
+ usableByFixture: Boolean(stored?.password)
3572
+ };
3573
+ }).filter((a) => !args.usable || a.usableByFixture);
3574
+ if (accounts.length === 0) {
3518
3575
  if (!args.json) {
3519
3576
  console.log(
3520
- "No test accounts. Create one with: deepspace test accounts create --email <email> --password <password>"
3577
+ remote.length > 0 ? `No test accounts with locally saved credentials (${remote.length} remote-only). Recreate them with: deepspace test accounts create --email <email> --password <password>` : "No test accounts. Create one with: deepspace test accounts create --email <email> --password <password>"
3521
3578
  );
3522
3579
  }
3523
3580
  return { data: { accounts: [], count: 0, limit: 10 } };
3524
3581
  }
3525
3582
  if (!args.json) {
3526
- console.log(`Test accounts (${remote.length}/10):
3583
+ console.log(`Test accounts (${remote.length}/10${args.usable ? ", usable only" : ""}):
3527
3584
  `);
3528
- for (const a of remote) {
3529
- const stored = localByEmail.get(a.email);
3585
+ for (const a of accounts) {
3530
3586
  const date = new Date(a.createdAt).toLocaleDateString();
3531
3587
  console.log(` ${a.email}${a.label ? ` (${a.label})` : ""}`);
3532
3588
  console.log(` ID: ${a.id} UserID: ${a.userId} Created: ${date}`);
3533
- if (stored?.password) {
3534
- console.log(` Password: ${stored.password}`);
3589
+ console.log(` Selector: ${a.name ?? "(none)"}`);
3590
+ if (a.usableByFixture) {
3591
+ console.log(` Password: ${args.reveal ? a.password : "(saved locally)"}`);
3535
3592
  } else {
3536
- console.log(` Password: (not saved locally)`);
3593
+ console.log(` Password: (not saved locally \u2014 not usable by the users() fixture)`);
3537
3594
  }
3538
3595
  }
3539
3596
  }
3540
3597
  return {
3541
3598
  data: {
3542
- accounts: remote.map((a) => ({
3543
- id: a.id,
3544
- email: a.email,
3545
- userId: a.userId,
3546
- label: a.label,
3547
- createdAt: a.createdAt,
3548
- password: localByEmail.get(a.email)?.password ?? null
3549
- })),
3550
- count: remote.length,
3599
+ accounts,
3600
+ count: accounts.length,
3551
3601
  limit: 10
3552
3602
  }
3553
3603
  };
@@ -3799,13 +3849,13 @@ var MAX_UPLOAD_REQUEST_BYTES = 25 * 1024 * 1024;
3799
3849
  var UPLOAD_PART_BYTES = 20 * 1024 * 1024;
3800
3850
  var MAX_UPLOAD_PARTS = Math.ceil(MAX_APP_FILE_BYTES / UPLOAD_PART_BYTES);
3801
3851
  var MAX_BASE64_UPLOAD_BYTES = Math.floor(MAX_UPLOAD_REQUEST_BYTES * 3 / 4);
3802
- function planUploadParts(totalBytes, partSize) {
3852
+ function planUploadParts(totalBytes) {
3803
3853
  const parts = [];
3804
- for (let start = 0; start < totalBytes; start += partSize) {
3854
+ for (let start = 0; start < totalBytes; start += UPLOAD_PART_BYTES) {
3805
3855
  parts.push({
3806
3856
  partNumber: parts.length + 1,
3807
3857
  start,
3808
- end: Math.min(start + partSize, totalBytes)
3858
+ end: Math.min(start + UPLOAD_PART_BYTES, totalBytes)
3809
3859
  });
3810
3860
  }
3811
3861
  return parts;
@@ -3820,20 +3870,6 @@ var ACCOUNT_STORAGE_LIMIT_BYTES = {
3820
3870
  };
3821
3871
  var REPO_STORE_LIMIT_BYTES = 256 * 1024 * 1024;
3822
3872
  var MAX_DEPLOY_ASSET_FILE_BYTES = 25 * 1024 * 1024;
3823
- var DANGEROUS_MIME_TYPES = /* @__PURE__ */ new Set([
3824
- "text/html",
3825
- "application/xhtml+xml",
3826
- "image/svg+xml",
3827
- "application/xml",
3828
- "text/xml",
3829
- "application/javascript",
3830
- "text/javascript",
3831
- "application/ecmascript",
3832
- "text/ecmascript"
3833
- ]);
3834
- function activeContentMessage(mimeType) {
3835
- return `${mimeType} is not accepted: the files API serves your app's own origin, so stored HTML/SVG/JavaScript would execute there with your users' sessions. Ship it as a build asset under \`public/\` instead, where it is served as part of your app rather than as user-uploaded content.`;
3836
- }
3837
3873
  function formatBytes(bytes) {
3838
3874
  if (bytes >= 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GiB`;
3839
3875
  if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
@@ -4431,6 +4467,7 @@ async function buildDeployBundle(options) {
4431
4467
  if (lintFindings && lintFindings.length > 0) {
4432
4468
  p6.log.warn(formatSchemaLintFindings(lintFindings).join("\n"));
4433
4469
  }
4470
+ const buildStarted = Date.now();
4434
4471
  spinner2.start("Building...");
4435
4472
  let preparedWranglerConfig;
4436
4473
  try {
@@ -4453,7 +4490,7 @@ async function buildDeployBundle(options) {
4453
4490
  } finally {
4454
4491
  preparedWranglerConfig?.cleanup();
4455
4492
  }
4456
- spinner2.stop("Built");
4493
+ spinner2.stop(`Built (${Math.round((Date.now() - buildStarted) / 1e3)}s)`);
4457
4494
  const deployConfigPath = join15(appDir, ".wrangler", "deploy", "config.json");
4458
4495
  if (!existsSync14(deployConfigPath)) {
4459
4496
  output.die(
@@ -4615,6 +4652,51 @@ import * as p7 from "@clack/prompts";
4615
4652
  import { existsSync as existsSync15 } from "fs";
4616
4653
  import { join as join16 } from "path";
4617
4654
  import { pathToFileURL as pathToFileURL2 } from "url";
4655
+
4656
+ // src/shared/api-error.ts
4657
+ var SLUG_RE = /^[a-z0-9]+(?:[_.-][a-z0-9]+)*$/;
4658
+ var HUMAN_MESSAGES = {
4659
+ owner_connect_not_ready: "This app isn't ready to accept payments yet \u2014 the app owner needs to finish payment setup.",
4660
+ invalid_json: "The request body was not valid JSON.",
4661
+ invalid_body: "The request body is invalid.",
4662
+ invalid_product_id: "Invalid product id.",
4663
+ duplicate_product_id: "Duplicate product id.",
4664
+ unknown_product: "Unknown product.",
4665
+ too_many_products: "Too many products declared.",
4666
+ invalid_amount: "Invalid amount.",
4667
+ invalid_name: "Invalid product name.",
4668
+ invalid_description: "Invalid product description.",
4669
+ unsupported_currency: "Unsupported currency.",
4670
+ unknown_app: "Unknown app.",
4671
+ not_app_owner: "Only the app owner (or a collaborator) can do this.",
4672
+ invalid_return_url: "Invalid return URL.",
4673
+ invalid_cancel_url: "Invalid cancel URL.",
4674
+ unknown_plan: "Unknown subscription plan.",
4675
+ unknown_price: "No price is configured for that billing interval.",
4676
+ no_subscription: "No subscription found for this account."
4677
+ };
4678
+ function normalizeApiError(status3, body) {
4679
+ const payload = body !== null && typeof body === "object" ? body : {};
4680
+ const rawError = typeof payload.error === "string" ? payload.error : void 0;
4681
+ const message = typeof payload.message === "string" ? payload.message : void 0;
4682
+ const humanError = rawError !== void 0 && !SLUG_RE.test(rawError) ? rawError : void 0;
4683
+ const code = rawError !== void 0 && SLUG_RE.test(rawError) ? rawError : typeof payload.code === "string" && SLUG_RE.test(payload.code) ? payload.code : void 0;
4684
+ const details = {};
4685
+ for (const [key, value] of Object.entries(payload)) {
4686
+ if (key === "success" || key === "error" || key === "message" || key === "issues" || key === "code")
4687
+ continue;
4688
+ details[key] = value;
4689
+ }
4690
+ return {
4691
+ error: message ?? humanError ?? (code !== void 0 ? HUMAN_MESSAGES[code] : void 0) ?? rawError ?? `Request failed (${status3})`,
4692
+ ...code !== void 0 ? { code } : {},
4693
+ status: status3,
4694
+ ...Object.keys(details).length > 0 ? { details } : {},
4695
+ ...Array.isArray(payload.issues) ? { issues: payload.issues } : {}
4696
+ };
4697
+ }
4698
+
4699
+ // src/cli/commands/deploy/commerce.ts
4618
4700
  var API_URL2 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
4619
4701
  function errorMessage2(error) {
4620
4702
  return error instanceof Error ? error.message : String(error);
@@ -4688,7 +4770,7 @@ Stripe will keep charging them until you cancel. Cancel at the end of their curr
4688
4770
  });
4689
4771
  const body = await response.json().catch(() => ({}));
4690
4772
  if (!response.ok || !body.success) {
4691
- lastError = body.error ?? `HTTP ${response.status}`;
4773
+ lastError = normalizeApiError(response.status, body).error;
4692
4774
  spinner2.stop(`Cancel failed for ${change.slug}: ${lastError}`);
4693
4775
  stopped = true;
4694
4776
  break;
@@ -4746,7 +4828,7 @@ async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
4746
4828
  if (!response.ok || !body.ok) {
4747
4829
  spinner2.stop("Plan sync skipped");
4748
4830
  p7.log.warn(
4749
- `Plan sync failed: ${body.error ?? `HTTP ${response.status}`}${body.hint ? ` \u2014 ${body.hint}` : ""}`
4831
+ `Plan sync failed: ${normalizeApiError(response.status, body).error}${body.hint ? ` \u2014 ${body.hint}` : ""}`
4750
4832
  );
4751
4833
  return;
4752
4834
  }
@@ -4804,7 +4886,7 @@ async function syncOneTimeProducts(appDir, appName, token) {
4804
4886
  }
4805
4887
  spinner2?.stop("Product sync skipped");
4806
4888
  p7.log.warn(
4807
- `Product sync failed: ${body.error ?? `HTTP ${response.status}`}${body.details ? ` \u2014 ${JSON.stringify(body.details)}` : ""}`
4889
+ `Product sync failed: ${normalizeApiError(response.status, body).error}${body.details ? ` \u2014 ${JSON.stringify(body.details)}` : ""}`
4808
4890
  );
4809
4891
  } catch (error) {
4810
4892
  spinner2?.stop("Product sync skipped");
@@ -5672,7 +5754,7 @@ function detachedHeadRefusal() {
5672
5754
  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.";
5673
5755
  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.";
5674
5756
  var REQUIRED_ASSET_TRANSPORT = "content-addressed-v1";
5675
- var UPLOAD_CONCURRENCY = 4;
5757
+ var UPLOAD_CONCURRENCY = 16;
5676
5758
  async function deployBuiltBundle(options) {
5677
5759
  const {
5678
5760
  deployUrl,
@@ -5761,15 +5843,19 @@ async function deployBuiltBundle(options) {
5761
5843
  form.append("deployKey", repository.deployKey);
5762
5844
  return form;
5763
5845
  };
5764
- const postCommit = async () => postWithRetry(
5765
- `${deployUrl}/api/deploy/${appId}`,
5766
- () => ({
5767
- method: "POST",
5768
- headers: { Authorization: `Bearer ${token}` },
5769
- body: makeForm()
5770
- }),
5771
- { retryServerErrors: false }
5772
- );
5846
+ let commitStarted = Date.now();
5847
+ const postCommit = async () => {
5848
+ commitStarted = Date.now();
5849
+ return postWithRetry(
5850
+ `${deployUrl}/api/deploy/${appId}`,
5851
+ () => ({
5852
+ method: "POST",
5853
+ headers: { Authorization: `Bearer ${token}` },
5854
+ body: makeForm()
5855
+ }),
5856
+ { retryServerErrors: false, attempts: 2, timeoutMs: 24e4 }
5857
+ );
5858
+ };
5773
5859
  let response;
5774
5860
  try {
5775
5861
  response = await postCommit();
@@ -5816,6 +5902,7 @@ async function deployBuiltBundle(options) {
5816
5902
  body.code
5817
5903
  );
5818
5904
  }
5905
+ p10.log.info(`Platform commit: ${Math.round((Date.now() - commitStarted) / 1e3)}s`);
5819
5906
  if (body.onBehalfOfOwner) {
5820
5907
  p10.log.warn("This release is attributed to the app's owner, not to your account.");
5821
5908
  }
@@ -5934,8 +6021,12 @@ async function uploadDeployAssets(options) {
5934
6021
  spinner2.message(`Uploading assets \u2014 ${uploaded}/${total}...`);
5935
6022
  }
5936
6023
  };
6024
+ const uploadStarted = Date.now();
5937
6025
  try {
5938
6026
  await Promise.all(Array.from({ length: Math.min(UPLOAD_CONCURRENCY, missing.length) }, worker));
6027
+ p10.log.info(
6028
+ `Uploaded ${total} file(s) in ${Math.round((Date.now() - uploadStarted) / 1e3)}s`
6029
+ );
5939
6030
  } catch (error) {
5940
6031
  spinner2.stop("Deploy failed");
5941
6032
  if (error instanceof AssetUploadError) {
@@ -5981,13 +6072,14 @@ function errorMessage4(error) {
5981
6072
  }
5982
6073
  async function postWithRetry(url, makeInit, {
5983
6074
  attempts = 4,
5984
- retryServerErrors = true
6075
+ retryServerErrors = true,
6076
+ timeoutMs = 6e4
5985
6077
  } = {}) {
5986
6078
  let lastResponse;
5987
6079
  let lastError;
5988
6080
  for (let attempt = 1; attempt <= attempts; attempt++) {
5989
6081
  try {
5990
- const response = await fetch(url, makeInit());
6082
+ const response = await fetch(url, { ...makeInit(), signal: AbortSignal.timeout(timeoutMs) });
5991
6083
  const transient = retryServerErrors && (response.status >= 500 || response.status === 408 || response.status === 429);
5992
6084
  if (!transient) return response;
5993
6085
  lastResponse = response;
@@ -6232,6 +6324,7 @@ var deploy_default = defineCommand3({
6232
6324
  spinner: spinner2
6233
6325
  });
6234
6326
  let serving = "unverifiable";
6327
+ const edgeWaitStarted = Date.now();
6235
6328
  if (body.url) {
6236
6329
  spinner2.message("Waiting for the edge to serve this release...");
6237
6330
  serving = await waitForLiveRelease(body.url, body.releaseStamp, 9e4);
@@ -6261,7 +6354,9 @@ var deploy_default = defineCommand3({
6261
6354
  return;
6262
6355
  }
6263
6356
  }
6264
- spinner2.stop("Deployed!");
6357
+ spinner2.stop(
6358
+ `Deployed! (edge confirmed in ${Math.round((Date.now() - edgeWaitStarted) / 1e3)}s)`
6359
+ );
6265
6360
  p12.log.success(`Live at: ${body.url}`);
6266
6361
  await syncCommerce(appDir, appId, token, output.nonInteractive);
6267
6362
  if (output.json) {
@@ -6522,7 +6617,10 @@ var undeploy_default = defineDeepspaceCommand({
6522
6617
  }
6523
6618
  );
6524
6619
  }
6525
- throw new Refusal(body.error ?? `Undeploy error (${res.status})`, "undeploy_failed");
6620
+ throw new Refusal(
6621
+ body.error ?? `Undeploy error (${res.status})`,
6622
+ body.code ?? "undeploy_failed"
6623
+ );
6526
6624
  }
6527
6625
  s?.stop("Removed");
6528
6626
  const hosts = body.releasedHosts ?? [];
@@ -8491,9 +8589,6 @@ async function uploadAppFile(baseUrl, token, appId, localPath, key, onProgress)
8491
8589
  throw new ApiError(oversizeMessage(size), 0, "too_large", path);
8492
8590
  }
8493
8591
  const declaredType = contentTypeFor(localPath);
8494
- if (DANGEROUS_MIME_TYPES.has(declaredType)) {
8495
- throw new ApiError(activeContentMessage(declaredType), 0, "active_content", path);
8496
- }
8497
8592
  if (size > UPLOAD_PART_BYTES) {
8498
8593
  return uploadInParts(baseUrl, token, appId, localPath, key, size, onProgress);
8499
8594
  }
@@ -8514,24 +8609,35 @@ function isRetryable(err) {
8514
8609
  async function uploadInParts(baseUrl, token, appId, localPath, key, size, onProgress) {
8515
8610
  const contentType = contentTypeFor(localPath);
8516
8611
  const initPath = filesPath(appId, `/multipart?key=${encodeKeyPath(key)}`);
8517
- const session = await readJson(
8518
- await request(baseUrl, token, initPath, {
8519
- method: "POST",
8520
- headers: { "Content-Type": "application/json" },
8521
- body: JSON.stringify({ name: basename3(localPath), mimeType: contentType, size })
8522
- }),
8523
- initPath
8524
- );
8525
- const query = `uploadKey=${encodeKeyPath(session.uploadKey)}&uploadId=${encodeURIComponent(session.uploadId)}`;
8526
- if (!Number.isSafeInteger(session.partSize) || session.partSize < 1) {
8612
+ const requestId = randomUUID2();
8613
+ const initBody = JSON.stringify({
8614
+ requestId,
8615
+ name: basename3(localPath),
8616
+ mimeType: contentType,
8617
+ size
8618
+ });
8619
+ const sendInit = () => request(baseUrl, token, initPath, {
8620
+ method: "POST",
8621
+ headers: { "Content-Type": "application/json" },
8622
+ body: initBody
8623
+ }).then((response) => readJson(response, initPath));
8624
+ let session;
8625
+ try {
8626
+ session = await sendInit();
8627
+ } catch (error) {
8628
+ if (!isRetryable(error)) throw error;
8629
+ session = await sendInit();
8630
+ }
8631
+ const query = `uploadKey=${encodeKeyPath(session.uploadKey)}&uploadId=${encodeURIComponent(session.uploadId)}&reservationId=${encodeURIComponent(session.reservationId)}`;
8632
+ if (!session.uploadId || !session.uploadKey || !session.reservationId || session.partSize !== UPLOAD_PART_BYTES) {
8527
8633
  throw new ApiError(
8528
- `The platform advertised an unusable part size (${session.partSize}).`,
8634
+ `The platform returned an unusable part size or multipart session (${session.partSize}).`,
8529
8635
  0,
8530
8636
  "invalid_response",
8531
8637
  initPath
8532
8638
  );
8533
8639
  }
8534
- const plan = planUploadParts(size, session.partSize);
8640
+ const plan = planUploadParts(size);
8535
8641
  const file = await openAsBlob(localPath, { type: contentType });
8536
8642
  const uploaded = [];
8537
8643
  try {
@@ -9211,7 +9317,9 @@ async function runList(args) {
9211
9317
  for (const ep of endpoints) {
9212
9318
  const pad = ep.endpoint.padEnd(widest);
9213
9319
  const cost = formatCurrency(ep.billing.baseCost, ep.billing.currency);
9214
- console.log(` ${pad} ${ep.billing.model.padEnd(12)} ${cost}`);
9320
+ const oauth = ep.requiresOAuth ? " [oauth]" : "";
9321
+ console.log(` ${pad} ${ep.billing.model.padEnd(12)} ${cost}${oauth}`);
9322
+ if (ep.description) console.log(` ${ep.description}`);
9215
9323
  }
9216
9324
  console.log();
9217
9325
  }
@@ -9240,9 +9348,15 @@ async function runInfo(args) {
9240
9348
  }
9241
9349
  if (!args.json) {
9242
9350
  console.log(`${integration}/${endpoint}`);
9351
+ if (info2.description) console.log(` ${info2.description}`);
9243
9352
  console.log(
9244
9353
  ` billing: ${formatCurrency(info2.billing.baseCost, info2.billing.currency)} ${billingUnit(info2.billing.model)}`
9245
9354
  );
9355
+ if (info2.requiresOAuth) {
9356
+ console.log(
9357
+ " Requires OAuth: without a connected account, a call succeeds with data { requiresOAuth: true, authUrl } \u2014 open the authUrl, then retry."
9358
+ );
9359
+ }
9246
9360
  console.log();
9247
9361
  console.log("Input schema:");
9248
9362
  console.log(
@@ -9251,6 +9365,11 @@ async function runInfo(args) {
9251
9365
  console.log();
9252
9366
  console.log("Example body:");
9253
9367
  console.log(info2.example ? JSON.stringify(info2.example, null, 2) : " (no example available)");
9368
+ if (info2.outputSchema) {
9369
+ console.log();
9370
+ console.log("Output schema:");
9371
+ console.log(JSON.stringify(info2.outputSchema, null, 2));
9372
+ }
9254
9373
  }
9255
9374
  return { data: info2 };
9256
9375
  }
@@ -9331,17 +9450,19 @@ async function runInvoke(args) {
9331
9450
  }
9332
9451
  return { data: payload };
9333
9452
  }
9334
- const errMsg = payload.error ?? `Request failed (${res.status})`;
9335
- const lines = [`${integration}/${endpoint} (${res.status}, ${elapsed}ms): ${errMsg}`];
9336
- if (Array.isArray(payload.issues)) {
9337
- for (const issue of payload.issues) {
9453
+ const norm = normalizeApiError(res.status, payload);
9454
+ const lines = [`${integration}/${endpoint} (${res.status}, ${elapsed}ms): ${norm.error}`];
9455
+ if (norm.issues) {
9456
+ for (const issue of norm.issues) {
9338
9457
  const path = issue.path?.length ? issue.path.join(".") : "(root)";
9339
9458
  lines.push(` - ${path}: ${issue.message}`);
9340
9459
  }
9341
9460
  }
9342
- const code = typeof payload.code === "string" ? payload.code : "integration_call_failed";
9461
+ const code = norm.code ?? "integration_call_failed";
9343
9462
  const rest = { ...payload };
9344
9463
  delete rest.error;
9464
+ delete rest.message;
9465
+ delete rest.code;
9345
9466
  throw new Refusal(lines.join("\n"), code, {
9346
9467
  ...res.status === 401 ? { action: cliAction("deepspace", "auth", "login") } : {},
9347
9468
  extra: rest
@@ -12587,7 +12708,7 @@ function conflictMarkerFiles(diffCheckOutput) {
12587
12708
  function pullAfterLandAction(primaryDir, intoBranch, landedOid) {
12588
12709
  if (currentBranch(primaryDir) !== intoBranch) return null;
12589
12710
  if (resolveCommit(primaryDir, "HEAD") === landedOid) return null;
12590
- return { cwd: primaryDir, argv: ["deepspace", "pull"] };
12711
+ return executableAction({ cwd: primaryDir, argv: ["deepspace", "pull"] });
12591
12712
  }
12592
12713
  function landResumeArgv(args) {
12593
12714
  const argv = ["deepspace", "workspace", "land"];
@@ -12868,6 +12989,7 @@ var landWorkspaceCommand = defineDeepspaceCommand({
12868
12989
  }
12869
12990
  spinner2?.stop(`Landed ${id} into ${intoBranch} at ${landedOid.slice(0, 10)}.`);
12870
12991
  const primaryDir = primaryAppDir(appDir);
12992
+ const action = pullAfterLandAction(primaryDir, intoBranch, landedOid);
12871
12993
  const cleanup = keepWorktree ? null : cleanupWorkspaceLocal(appDir, id, intoBranch, { expectedBranchOid: landedOid });
12872
12994
  const inOwnWorktree = !cleanup && inOwnLinkedWorktree(appDir, id);
12873
12995
  const retainedWorktree = cleanup?.worktreeRetained ?? (keepWorktree || inOwnWorktree ? appDir : null);
@@ -12901,7 +13023,6 @@ var landWorkspaceCommand = defineDeepspaceCommand({
12901
13023
  { actionRequired: true, action: action2, extra: data }
12902
13024
  );
12903
13025
  }
12904
- const action = pullAfterLandAction(primaryDir, intoBranch, landedOid);
12905
13026
  return { data, ...action ? { action } : {} };
12906
13027
  }
12907
13028
  });
@@ -13628,7 +13749,7 @@ import { join as join23, resolve as resolve20 } from "path";
13628
13749
  // package.json
13629
13750
  var package_default = {
13630
13751
  name: "deepspace",
13631
- version: "0.19.5",
13752
+ version: "0.21.0",
13632
13753
  engines: {
13633
13754
  node: ">=22.15.0 <23 || >=24 <25 || >=26 <27"
13634
13755
  },
@@ -13661,6 +13782,10 @@ var package_default = {
13661
13782
  types: "./dist/testing.d.ts",
13662
13783
  import: "./dist/testing.js"
13663
13784
  },
13785
+ "./testing/mcp": {
13786
+ types: "./dist/testing-mcp.d.ts",
13787
+ import: "./dist/testing-mcp.js"
13788
+ },
13664
13789
  "./documentation": {
13665
13790
  types: "./dist/documentation.d.ts",
13666
13791
  import: "./dist/documentation.js"