deepspace 0.27.0 → 0.27.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/{chunk-SF6GNPYL.js → chunk-CRIG6LPA.js} +2 -2
  3. package/dist/{chunk-SF6GNPYL.js.map → chunk-CRIG6LPA.js.map} +1 -1
  4. package/dist/cli.js +280 -222
  5. package/dist/cli.js.map +1 -1
  6. package/dist/documentation-client-core.js +1 -1
  7. package/dist/documentation-default-renderer.cjs +1 -1
  8. package/dist/documentation-react.js +1 -1
  9. package/dist/documentation-runtime.js +1 -1
  10. package/dist/documentation-server-core.js +1 -1
  11. package/dist/documentation.d.ts +0 -1
  12. package/dist/documentation.js +41 -21
  13. package/dist/documentation.js.map +1 -1
  14. package/dist/feature-installer.js +27 -61
  15. package/dist/feature-installer.js.map +1 -1
  16. package/dist/index.d.ts +6 -55
  17. package/dist/index.js +4 -20
  18. package/dist/index.js.map +1 -1
  19. package/dist/schema.js.map +1 -1
  20. package/dist/server.d.ts +5 -38
  21. package/dist/server.js +3 -57
  22. package/dist/server.js.map +1 -1
  23. package/dist/worker.d.ts +7 -80
  24. package/dist/worker.js +4 -62
  25. package/dist/worker.js.map +1 -1
  26. package/features/admin-page/feature.json +0 -1
  27. package/features/ai-chat/feature.json +0 -1
  28. package/features/canvas/feature.json +0 -1
  29. package/features/cron/feature.json +0 -1
  30. package/features/documents/feature.json +0 -1
  31. package/features/file-attachments/feature.json +0 -1
  32. package/features/file-manager/feature.json +0 -1
  33. package/features/integration-test/feature.json +0 -1
  34. package/features/items/feature.json +0 -1
  35. package/features/kanban/feature.json +0 -1
  36. package/features/landing/feature.json +0 -1
  37. package/features/leaderboard/feature.json +0 -1
  38. package/features/messaging/feature.json +0 -1
  39. package/features/search-bar/feature.json +0 -1
  40. package/features/tasks/feature.json +0 -1
  41. package/features/testing/feature.json +0 -1
  42. package/features/topbar/feature.json +1 -4
  43. package/features/tree/feature.json +1 -4
  44. package/package.json +1 -1
  45. package/features/presence-test/feature.json +0 -22
  46. package/features/presence-test/src/PresenceTestPage.tsx +0 -120
package/dist/cli.js CHANGED
@@ -1946,6 +1946,15 @@ async function warnIfPhantomApp(deployUrl, token, appId, explicit) {
1946
1946
  }
1947
1947
 
1948
1948
  // src/cli/lib/app-tokens.ts
1949
+ async function refusalDetail(response) {
1950
+ const text2 = await response.text().catch(() => "");
1951
+ try {
1952
+ const body = JSON.parse(text2);
1953
+ return { detail: body.error ?? text2, code: body.code };
1954
+ } catch {
1955
+ return { detail: text2 };
1956
+ }
1957
+ }
1949
1958
  async function fetchPublicKey(authUrl) {
1950
1959
  const response = await fetch(`${authUrl}/api/auth/jwks`);
1951
1960
  if (!response.ok) {
@@ -1965,8 +1974,13 @@ async function mintAppOwnerJwt(authUrl, callerJwt, appId) {
1965
1974
  body: JSON.stringify({ appId })
1966
1975
  });
1967
1976
  if (!response.ok) {
1968
- const detail = await response.text().catch(() => "");
1969
- throw new Error(`Failed to mint APP_OWNER_JWT (${response.status}): ${detail}`);
1977
+ const { detail, code } = await refusalDetail(response);
1978
+ throw new ApiError(
1979
+ `Failed to mint APP_OWNER_JWT (${response.status}): ${detail}`,
1980
+ response.status,
1981
+ code,
1982
+ "/api/auth/mint-app-token"
1983
+ );
1970
1984
  }
1971
1985
  const body = await response.json();
1972
1986
  if (!body.token) {
@@ -1982,10 +1996,37 @@ async function fetchAppIdentityToken(deployUrl, callerJwt, appId) {
1982
1996
  headers: { Authorization: `Bearer ${callerJwt}` }
1983
1997
  }
1984
1998
  );
1985
- if (response.status === 404) return null;
1999
+ if (response.status === 404) {
2000
+ const { detail, code } = await refusalDetail(response);
2001
+ if (code === "app_not_found") {
2002
+ throw new ApiError(
2003
+ // No executable command in this prose: this lib cannot know the
2004
+ // wrangler env, and a bare `app init` under --env would target the
2005
+ // TOP-LEVEL [vars] slot. dev/test/deploy attach the env-aware
2006
+ // remedy as a structured action; surfaces without an action channel
2007
+ // (secrets pull among them) fall back to the
2008
+ // app_not_found hint (check the id / `deepspace app list`).
2009
+ `App ${appId} is not registered on this platform \u2014 DEEPSPACE_APP_ID may belong to a different environment (a staging id does not exist on prod, and vice versa), or the app was never initialized here.`,
2010
+ 404,
2011
+ code,
2012
+ `/api/apps/${appId}/identity-token`
2013
+ );
2014
+ }
2015
+ throw new ApiError(
2016
+ `Failed to fetch APP_IDENTITY_TOKEN (404): ${detail || "no such route"} \u2014 is DEEPSPACE_DEPLOY_URL pointing at the right deploy service?`,
2017
+ 404,
2018
+ code,
2019
+ `/api/apps/${appId}/identity-token`
2020
+ );
2021
+ }
1986
2022
  if (!response.ok) {
1987
- const detail = await response.text().catch(() => "");
1988
- throw new Error(`Failed to fetch APP_IDENTITY_TOKEN (${response.status}): ${detail}`);
2023
+ const { detail, code } = await refusalDetail(response);
2024
+ throw new ApiError(
2025
+ `Failed to fetch APP_IDENTITY_TOKEN (${response.status}): ${detail}`,
2026
+ response.status,
2027
+ code,
2028
+ `/api/apps/${appId}/identity-token`
2029
+ );
1989
2030
  }
1990
2031
  const body = await response.json();
1991
2032
  if (!body.token) {
@@ -2006,11 +2047,29 @@ function requireAppIdFor(appDir, wranglerEnv) {
2006
2047
  throw appTargetMissingError(appDir, wranglerEnv);
2007
2048
  }
2008
2049
  async function writeDevVars(appDir, ownerId, callerJwt, wranglerEnv, opts = {}) {
2009
- const urls = PLATFORM_URLS;
2050
+ const urls = effectivePlatformUrls();
2010
2051
  const publicKey = await fetchPublicKey(urls.auth);
2011
2052
  const appId = opts.appId ?? requireAppIdFor(appDir, wranglerEnv);
2012
- const appOwnerJwt = await mintAppOwnerJwt(urls.auth, callerJwt, appId);
2013
- const appIdentityToken = await fetchAppIdentityToken(urls.deploy, callerJwt, appId);
2053
+ let appOwnerJwt;
2054
+ let appIdentityToken;
2055
+ try {
2056
+ appOwnerJwt = await mintAppOwnerJwt(urls.auth, callerJwt, appId);
2057
+ appIdentityToken = await fetchAppIdentityToken(urls.deploy, callerJwt, appId);
2058
+ } catch (err) {
2059
+ if (err instanceof ApiError && err.code === "app_not_found") {
2060
+ throw new Refusal(err.message, "app_not_found", {
2061
+ actionRequired: true,
2062
+ action: {
2063
+ cwd: appDir,
2064
+ // Env-scoped like every upstream step: a bare `app init` reads and
2065
+ // writes the TOP-LEVEL [vars] slot — under --env it would either
2066
+ // no-op on the prod id or mint a new app into the wrong scope.
2067
+ argv: ["deepspace", "app", "init", ...wranglerEnv ? ["--env", wranglerEnv] : []]
2068
+ }
2069
+ });
2070
+ }
2071
+ throw err;
2072
+ }
2014
2073
  const sdkVars = [
2015
2074
  `AUTH_JWT_PUBLIC_KEY="${publicKey}"`,
2016
2075
  `AUTH_JWT_ISSUER=${urls.auth}/api/auth`,
@@ -2019,7 +2078,7 @@ async function writeDevVars(appDir, ownerId, callerJwt, wranglerEnv, opts = {})
2019
2078
  `PLATFORM_WORKER_URL=${urls.platform}`,
2020
2079
  `OWNER_USER_ID=${ownerId}`,
2021
2080
  `APP_OWNER_JWT=${appOwnerJwt}`,
2022
- ...appIdentityToken ? [`APP_IDENTITY_TOKEN=${appIdentityToken}`] : [],
2081
+ `APP_IDENTITY_TOKEN=${appIdentityToken}`,
2023
2082
  `ALLOW_DEBUG_ROUTES=true`
2024
2083
  ].join("\n");
2025
2084
  const devVarsPath = devVarsPathFor(appDir);
@@ -3238,22 +3297,24 @@ var dev_default = defineDeepspaceCommand({
3238
3297
  );
3239
3298
  const readiness = new AbortController();
3240
3299
  if (args.json) {
3241
- void waitForPortListening(port, host, READINESS_TIMEOUT_MS, readiness.signal).then((ready) => {
3242
- if (readiness.signal.aborted) return;
3243
- console.log(
3244
- JSON.stringify(
3245
- ready ? { ok: true, ready: true, url, port, host, appDir } : {
3246
- ok: false,
3247
- code: "dev_server_not_ready",
3248
- error: `The dev server did not answer on ${url} within ${READINESS_TIMEOUT_MS / 1e3}s.`,
3249
- url,
3250
- port,
3251
- host,
3252
- appDir
3253
- }
3254
- )
3255
- );
3256
- });
3300
+ void waitForPortListening(port, host, READINESS_TIMEOUT_MS, readiness.signal).then(
3301
+ (ready) => {
3302
+ if (readiness.signal.aborted) return;
3303
+ console.log(
3304
+ JSON.stringify(
3305
+ ready ? { ok: true, ready: true, url, port, host, appDir } : {
3306
+ ok: false,
3307
+ code: "dev_server_not_ready",
3308
+ error: `The dev server did not answer on ${url} within ${READINESS_TIMEOUT_MS / 1e3}s.`,
3309
+ url,
3310
+ port,
3311
+ host,
3312
+ appDir
3313
+ }
3314
+ )
3315
+ );
3316
+ }
3317
+ );
3257
3318
  }
3258
3319
  let stopping = false;
3259
3320
  const stop = () => {
@@ -5446,7 +5507,7 @@ function createDeployOutput(json) {
5446
5507
  }
5447
5508
 
5448
5509
  // src/cli/commands/deploy/request.ts
5449
- import * as p7 from "@clack/prompts";
5510
+ import * as p8 from "@clack/prompts";
5450
5511
  import { readFileSync as readFileSync15 } from "fs";
5451
5512
 
5452
5513
  // src/cli/commands/deploy/repository.ts
@@ -6157,6 +6218,80 @@ function detachedHeadRefusal() {
6157
6218
  };
6158
6219
  }
6159
6220
 
6221
+ // src/cli/commands/deploy/secrets.ts
6222
+ import * as p7 from "@clack/prompts";
6223
+ function secretsConfigCreateAction(appDir, configName, envName) {
6224
+ return {
6225
+ cwd: appDir,
6226
+ argv: [
6227
+ "deepspace",
6228
+ "secrets",
6229
+ "configs",
6230
+ "create",
6231
+ configName,
6232
+ ...envName ? ["--env", envName] : []
6233
+ ]
6234
+ };
6235
+ }
6236
+ async function loadDeploySecrets(options) {
6237
+ const { deployUrl, appDir, appId, envName, ownerId, token, output } = options;
6238
+ const configName = defaultConfigNameForEnv(envName);
6239
+ let exists = false;
6240
+ let refreshed;
6241
+ try {
6242
+ refreshed = await refreshSecretsCache(deployUrl, token, appId, envName, configName);
6243
+ exists = refreshed.pulled !== null;
6244
+ if (refreshed.summary) p7.log.info(refreshed.summary);
6245
+ const names = Object.keys(refreshed.pulled?.values ?? {}).sort();
6246
+ if (names.length) p7.log.info(`App secrets: ${names.join(", ")}`);
6247
+ else if (exists) {
6248
+ p7.log.warn(
6249
+ "App secrets: none \u2014 this deploy ships no user secrets and removes any live on the app"
6250
+ );
6251
+ }
6252
+ } catch (error) {
6253
+ if (error?.status === 403) {
6254
+ output.die(
6255
+ "You're not authorized to deploy this app \u2014 you must be its owner or a current collaborator. If you were a collaborator, your access may have been revoked.",
6256
+ "not_authorized"
6257
+ );
6258
+ }
6259
+ output.die(
6260
+ `Failed to refresh app secrets before deploy: ${errorMessage4(error)}`,
6261
+ "secrets_refresh_failed"
6262
+ );
6263
+ }
6264
+ try {
6265
+ await writeDevVars(appDir, ownerId, token, envName, {
6266
+ appId,
6267
+ generatedSecretsCache: refreshed.rendered
6268
+ });
6269
+ } catch (error) {
6270
+ if (error instanceof Refusal && error.code === "app_not_found") {
6271
+ output.die(`Could not write .dev.vars before deploy: ${error.message}`, error.code, {
6272
+ actionRequired: true,
6273
+ action: error.action
6274
+ });
6275
+ }
6276
+ const code = error instanceof ApiError && error.code ? error.code : "dev_vars_failed";
6277
+ output.die(`Could not write .dev.vars before deploy: ${errorMessage4(error)}`, code);
6278
+ }
6279
+ if (!exists) {
6280
+ output.die(
6281
+ `Secrets config "${configName}" does not exist. Create it explicitly before deploying; this distinguishes an intentional empty config from an uninitialized app and protects live Worker secrets from a deleted config.`,
6282
+ "secrets_config_missing",
6283
+ {
6284
+ actionRequired: true,
6285
+ action: secretsConfigCreateAction(appDir, configName, envName)
6286
+ }
6287
+ );
6288
+ }
6289
+ return { configName };
6290
+ }
6291
+ function errorMessage4(error) {
6292
+ return error instanceof Error ? error.message : String(error);
6293
+ }
6294
+
6160
6295
  // src/cli/commands/deploy/request.ts
6161
6296
  var postWithRetry = fetchWithTransientRetry;
6162
6297
  var STALE_DISPLAY_NAME_LOCATIONS = [
@@ -6173,6 +6308,7 @@ function renamePromptMessage(rename2) {
6173
6308
  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.";
6174
6309
  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.";
6175
6310
  var REQUIRED_ASSET_TRANSPORT = "content-addressed-v1";
6311
+ var REQUIRED_SECRETS_SOURCE = "store-read-v1";
6176
6312
  var REQUIRED_WORKER_MODULE_TRANSPORT = "multipart-v1";
6177
6313
  var UPLOAD_CONCURRENCY = 16;
6178
6314
  var ASSET_UPLOAD_ATTEMPT_TIMEOUT_MS = 15e4;
@@ -6214,7 +6350,8 @@ async function deployBuiltBundle(options) {
6214
6350
  claimReleased,
6215
6351
  ignoreStale,
6216
6352
  bundle,
6217
- secrets,
6353
+ secretsConfig,
6354
+ envName,
6218
6355
  repository,
6219
6356
  output,
6220
6357
  spinner
@@ -6222,11 +6359,11 @@ async function deployBuiltBundle(options) {
6222
6359
  const auth2 = createDeployAuthSession(token, refreshToken);
6223
6360
  spinner.start(`Deploying ${appName}...`);
6224
6361
  if (bundle.extraRoutes === true) {
6225
- p7.log.info("Worker-first routing: all requests");
6362
+ p8.log.info("Worker-first routing: all requests");
6226
6363
  } else if (bundle.extraRoutes.length) {
6227
- p7.log.info(`Custom worker-first routes: ${bundle.extraRoutes.join(", ")}`);
6364
+ p8.log.info(`Custom worker-first routes: ${bundle.extraRoutes.join(", ")}`);
6228
6365
  }
6229
- await requireDeployCapabilities(deployUrl, bundle.worker.modules.length > 1, spinner, output);
6366
+ await requireWorkerModulesCapability(deployUrl, bundle.worker.modules.length > 1, spinner, output);
6230
6367
  await uploadDeployAssetsWithAuth(
6231
6368
  { deployUrl, appId, assets: bundle.assets, output, spinner },
6232
6369
  auth2
@@ -6236,7 +6373,7 @@ async function deployBuiltBundle(options) {
6236
6373
  const bail = async (message, stopLabel = "Deploy failed", code, actionRequired = false, rawAction) => {
6237
6374
  const action = rawAction ? executableAction(rawAction) : void 0;
6238
6375
  if (stopLabel !== null) spinner.stop(stopLabel);
6239
- p7.cancel(code ? withSlug(message, code) : message);
6376
+ p8.cancel(code ? withSlug(message, code) : message);
6240
6377
  if (action) printAction(action);
6241
6378
  if (output.json) {
6242
6379
  output.emitJson({
@@ -6278,7 +6415,7 @@ async function deployBuiltBundle(options) {
6278
6415
  if (bundle.customBindings.length) {
6279
6416
  form.append("bindingManifest", JSON.stringify(bundle.customBindings));
6280
6417
  }
6281
- if (secrets.authoritative) form.append("userSecrets", JSON.stringify(secrets.values));
6418
+ form.append("secretsConfig", secretsConfig);
6282
6419
  if (bundle.extraRoutes === true || bundle.extraRoutes.length) {
6283
6420
  form.append("extraRunWorkerFirst", JSON.stringify(bundle.extraRoutes));
6284
6421
  }
@@ -6335,7 +6472,7 @@ async function deployBuiltBundle(options) {
6335
6472
  response = await postCommit();
6336
6473
  } catch (error) {
6337
6474
  await bail(
6338
- error instanceof ApiError ? errorMessage4(error) : `Deploy request failed: ${errorMessage4(error)}`,
6475
+ error instanceof ApiError ? errorMessage5(error) : `Deploy request failed: ${errorMessage5(error)}`,
6339
6476
  "Deploy failed",
6340
6477
  error instanceof ApiError ? error.code : "deploy_request_failed"
6341
6478
  );
@@ -6349,8 +6486,8 @@ async function deployBuiltBundle(options) {
6349
6486
  if (output.nonInteractive) {
6350
6487
  await bail(renameRefusalMessage(rename2), null, "rename_required");
6351
6488
  }
6352
- const confirmed = await p7.confirm({ message: renamePromptMessage(rename2) });
6353
- if (p7.isCancel(confirmed) || !confirmed) await bail("Deploy cancelled.", null);
6489
+ const confirmed = await p8.confirm({ message: renamePromptMessage(rename2) });
6490
+ if (p8.isCancel(confirmed) || !confirmed) await bail("Deploy cancelled.", null);
6354
6491
  confirmRename = true;
6355
6492
  renamedFrom = rename2.fromHost;
6356
6493
  spinner.start(`Deploying ${appName}...`);
@@ -6366,6 +6503,15 @@ async function deployBuiltBundle(options) {
6366
6503
  { cwd: appDir, argv: ["deepspace", "pull"] }
6367
6504
  );
6368
6505
  }
6506
+ if (response.status === 409 && body.code === "secrets_config_missing") {
6507
+ await bail(
6508
+ `Secrets config "${secretsConfig}" does not exist on the platform (it was deleted after the pre-deploy check). Create it explicitly before deploying` + (envName ? ` (run the action below \u2014 plain \`configs create\` targets the top-level app)` : "") + ".",
6509
+ "Deploy failed",
6510
+ "secrets_config_missing",
6511
+ true,
6512
+ secretsConfigCreateAction(appDir, secretsConfig, envName)
6513
+ );
6514
+ }
6369
6515
  if (response.status === 409 && body.code === "release_in_progress") {
6370
6516
  await bail(
6371
6517
  `${body.error ?? "Another deploy of this app is already in progress"}. It is committing now \u2014 this run built and uploaded fine but did not release. Wait a moment and run the same deploy again; if it keeps refusing for more than a couple of minutes, check \`deepspace releases\`.`,
@@ -6382,14 +6528,14 @@ async function deployBuiltBundle(options) {
6382
6528
  body.code ?? "deploy_failed"
6383
6529
  );
6384
6530
  }
6385
- p7.log.info(`Platform commit: ${Math.round((Date.now() - commitStarted) / 1e3)}s`);
6531
+ p8.log.info(`Platform commit: ${Math.round((Date.now() - commitStarted) / 1e3)}s`);
6386
6532
  if (body.staleBaseGuard === "skipped") {
6387
- p7.log.warn(
6533
+ p8.log.warn(
6388
6534
  "The stale-base guard was skipped (repo store unavailable) \u2014 this deploy was NOT checked against the live release. Verify you're not clobbering newer work."
6389
6535
  );
6390
6536
  }
6391
6537
  if (body.bundleRetained === false) {
6392
- p7.log.warn(
6538
+ p8.log.warn(
6393
6539
  "This deploy is live, but its rollback bundle was not retained because the app storage quota is full."
6394
6540
  );
6395
6541
  }
@@ -6398,33 +6544,62 @@ async function deployBuiltBundle(options) {
6398
6544
  function assetManifest(assets) {
6399
6545
  return assets.map((asset) => ({ path: asset.path, hash: asset.hash, size: asset.size }));
6400
6546
  }
6401
- async function requireDeployCapabilities(deployUrl, needsWorkerModules, spinner, output) {
6402
- let capabilities;
6547
+ async function probeDeployCapabilities(deployUrl, spinner, output) {
6548
+ let capabilities = {};
6403
6549
  try {
6404
6550
  const response = await postWithRetry(`${deployUrl}/api/health`, () => ({}), {
6405
- timeoutMs: 15e3
6551
+ attempts: 2,
6552
+ timeoutMs: 1e4
6406
6553
  });
6407
6554
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
6408
6555
  const body = await response.json();
6409
- capabilities = body.capabilities;
6556
+ capabilities = body.capabilities ?? {};
6410
6557
  } catch (error) {
6411
- spinner.stop("Deploy failed");
6558
+ spinner?.stop("Deploy failed");
6412
6559
  output.die(
6413
- `Could not reach the DeepSpace deploy service at ${deployUrl} to check its capabilities: ${errorMessage4(error)}`,
6560
+ `Could not reach the DeepSpace deploy service at ${deployUrl} to check its capabilities: ${errorMessage5(error)}`,
6414
6561
  "deploy_service_unreachable"
6415
6562
  );
6416
6563
  }
6417
- if (capabilities?.assetTransport !== REQUIRED_ASSET_TRANSPORT) {
6418
- spinner.stop("Deploy failed");
6419
- output.die(
6420
- `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.`,
6421
- "asset_transport_unsupported"
6422
- );
6423
- }
6424
- if (needsWorkerModules && capabilities?.workerModules !== REQUIRED_WORKER_MODULE_TRANSPORT) {
6425
- spinner.stop("Deploy failed");
6564
+ return capabilities;
6565
+ }
6566
+ function requireCapability(deployUrl, spinner, output, name, advertised, required, code, pinHint = "") {
6567
+ if (advertised === required) return;
6568
+ spinner?.stop("Deploy failed");
6569
+ output.die(
6570
+ `The DeepSpace deploy service at ${deployUrl} does not support this CLI's ${name} (needs "${required}", found ${advertised === void 0 ? "none" : `"${String(advertised)}"`}). Wait for the platform to finish updating${pinHint}.`,
6571
+ code
6572
+ );
6573
+ }
6574
+ async function requireDeployCapabilities(deployUrl, spinner, output) {
6575
+ const capabilities = await probeDeployCapabilities(deployUrl, spinner, output);
6576
+ requireCapability(
6577
+ deployUrl,
6578
+ spinner,
6579
+ output,
6580
+ "asset transport",
6581
+ capabilities.assetTransport,
6582
+ REQUIRED_ASSET_TRANSPORT,
6583
+ "asset_transport_unsupported"
6584
+ );
6585
+ requireCapability(
6586
+ deployUrl,
6587
+ spinner,
6588
+ output,
6589
+ "secrets source",
6590
+ capabilities.secretsSource,
6591
+ REQUIRED_SECRETS_SOURCE,
6592
+ "secrets_source_unsupported",
6593
+ ", or pin the last pre-cutover CLI (`npm i -g deepspace@0.23`)"
6594
+ );
6595
+ }
6596
+ async function requireWorkerModulesCapability(deployUrl, needsWorkerModules, spinner, output) {
6597
+ if (!needsWorkerModules) return;
6598
+ const capabilities = await probeDeployCapabilities(deployUrl, spinner, output);
6599
+ if (capabilities.workerModules !== REQUIRED_WORKER_MODULE_TRANSPORT) {
6600
+ spinner?.stop("Deploy failed");
6426
6601
  output.die(
6427
- `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.`,
6602
+ `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.`,
6428
6603
  "worker_module_transport_unsupported"
6429
6604
  );
6430
6605
  }
@@ -6448,9 +6623,9 @@ async function uploadDeployAssetsWithAuth(options, auth2) {
6448
6623
  } catch (error) {
6449
6624
  spinner.stop("Deploy failed");
6450
6625
  if (error instanceof ApiError) {
6451
- output.die(errorMessage4(error), error.code ?? "upload_failed");
6626
+ output.die(errorMessage5(error), error.code ?? "upload_failed");
6452
6627
  }
6453
- output.die(`Asset plan failed (network): ${errorMessage4(error)}`, "upload_failed");
6628
+ output.die(`Asset plan failed (network): ${errorMessage5(error)}`, "upload_failed");
6454
6629
  }
6455
6630
  const planBody = await planResponse.json().catch(() => ({}));
6456
6631
  if (!planResponse.ok) {
@@ -6473,11 +6648,11 @@ async function uploadDeployAssetsWithAuth(options, auth2) {
6473
6648
  missing.push(asset);
6474
6649
  }
6475
6650
  if (missing.length === 0) {
6476
- p7.log.info(`Assets: ${byHash.size} file(s), all already uploaded`);
6651
+ p8.log.info(`Assets: ${byHash.size} file(s), all already uploaded`);
6477
6652
  return;
6478
6653
  }
6479
6654
  const missingBytes = missing.reduce((total2, asset) => total2 + asset.size, 0);
6480
- p7.log.info(
6655
+ p8.log.info(
6481
6656
  `Assets: ${byHash.size} file(s), uploading ${missing.length} (${Math.ceil(missingBytes / 1024)} KiB); the rest are already stored`
6482
6657
  );
6483
6658
  const total = missing.length;
@@ -6518,16 +6693,16 @@ async function uploadDeployAssetsWithAuth(options, auth2) {
6518
6693
  const uploadStarted = Date.now();
6519
6694
  try {
6520
6695
  await Promise.all(Array.from({ length: Math.min(UPLOAD_CONCURRENCY, missing.length) }, worker));
6521
- p7.log.info(`Uploaded ${total} file(s) in ${Math.round((Date.now() - uploadStarted) / 1e3)}s`);
6696
+ p8.log.info(`Uploaded ${total} file(s) in ${Math.round((Date.now() - uploadStarted) / 1e3)}s`);
6522
6697
  } catch (error) {
6523
6698
  spinner.stop("Deploy failed");
6524
6699
  if (error instanceof AssetUploadError) {
6525
6700
  output.die(formatDeployWorkerError(error.status, error.message, error.code), error.code);
6526
6701
  }
6527
6702
  if (error instanceof ApiError) {
6528
- output.die(errorMessage4(error), error.code ?? "upload_failed");
6703
+ output.die(errorMessage5(error), error.code ?? "upload_failed");
6529
6704
  }
6530
- output.die(`Asset upload failed (network): ${errorMessage4(error)}`, "upload_failed");
6705
+ output.die(`Asset upload failed (network): ${errorMessage5(error)}`, "upload_failed");
6531
6706
  }
6532
6707
  }
6533
6708
  var AssetUploadError = class extends Error {
@@ -6543,7 +6718,9 @@ function isDeployServiceResourceLimit(status3, error) {
6543
6718
  }
6544
6719
  function formatDeployWorkerError(status3, error, code) {
6545
6720
  const detail = error ?? `Deployment error (${status3})`;
6546
- if (code === "cli_outdated") return detail;
6721
+ if (code === "cli_outdated" || code === "secrets_read_failed" || code === "release_reconciliation_pending") {
6722
+ return detail;
6723
+ }
6547
6724
  if (isDeployServiceResourceLimit(status3, error)) {
6548
6725
  return `${DEPLOY_SERVICE_LIMIT_HINT}
6549
6726
 
@@ -6562,82 +6739,6 @@ Underlying error: ${detail}`;
6562
6739
 
6563
6740
  Underlying error: ${detail}`;
6564
6741
  }
6565
- function errorMessage4(error) {
6566
- return error instanceof Error ? error.message : String(error);
6567
- }
6568
-
6569
- // src/cli/commands/deploy/secrets.ts
6570
- import * as p8 from "@clack/prompts";
6571
- async function loadDeploySecrets(options) {
6572
- const { deployUrl, appDir, appId, envName, ownerId, token, output } = options;
6573
- let linked = null;
6574
- try {
6575
- const refreshed = await refreshSecretsCache(deployUrl, token, appId, envName);
6576
- linked = refreshed.pulled;
6577
- await writeDevVars(appDir, ownerId, token, envName, {
6578
- appId,
6579
- generatedSecretsCache: refreshed.rendered
6580
- });
6581
- if (refreshed.summary) p8.log.info(refreshed.summary);
6582
- } catch (error) {
6583
- if (error?.status === 403) {
6584
- output.die(
6585
- "You're not authorized to deploy this app \u2014 you must be its owner or a current collaborator. If you were a collaborator, your access may have been revoked.",
6586
- "not_authorized"
6587
- );
6588
- }
6589
- output.die(
6590
- `Failed to refresh app secrets before deploy: ${errorMessage5(error)}`,
6591
- "secrets_refresh_failed"
6592
- );
6593
- }
6594
- if (!linked) {
6595
- const configName = defaultConfigNameForEnv(envName);
6596
- output.die(
6597
- `Secrets config "${configName}" does not exist. Create it explicitly before deploying; this distinguishes an intentional empty config from an uninitialized app and protects live Worker secrets during the platform rollout.`,
6598
- "secrets_config_missing",
6599
- {
6600
- action: {
6601
- cwd: appDir,
6602
- argv: [
6603
- "deepspace",
6604
- "secrets",
6605
- "configs",
6606
- "create",
6607
- configName,
6608
- ...envName ? ["--env", envName] : []
6609
- ]
6610
- }
6611
- }
6612
- );
6613
- }
6614
- return { linked };
6615
- }
6616
- function prepareDeploySecrets(options) {
6617
- const { cache, customBindings, doManifest, output } = options;
6618
- const values = cache.linked ? { ...cache.linked.values } : {};
6619
- const names = Object.keys(values);
6620
- const declaredBindingNames = /* @__PURE__ */ new Set([
6621
- ...customBindings.map((binding) => binding.name),
6622
- ...(doManifest ?? []).map((binding) => binding.binding)
6623
- ]);
6624
- for (const name of names) {
6625
- if (RESERVED_BINDING_NAMES.has(name)) {
6626
- output.die(
6627
- `App secret "${name}" is a reserved binding name \u2014 rename or remove that secret before deploying.`,
6628
- "reserved_binding"
6629
- );
6630
- }
6631
- if (declaredBindingNames.has(name)) {
6632
- output.die(
6633
- `App secret "${name}" collides with a binding declared in wrangler.toml \u2014 rename one or the other.`,
6634
- "binding_collision"
6635
- );
6636
- }
6637
- }
6638
- if (names.length) p8.log.info(`App secrets: ${names.join(", ")}`);
6639
- return { values, names, authoritative: cache.linked !== null };
6640
- }
6641
6742
  function errorMessage5(error) {
6642
6743
  return error instanceof Error ? error.message : String(error);
6643
6744
  }
@@ -6817,11 +6918,12 @@ async function runLockedDeploy(args, output, appDir, envName) {
6817
6918
  });
6818
6919
  if (!sourceState.registered) {
6819
6920
  output.die(
6820
- `${appId} is not registered. If this repo's id came from an older SDK's scaffold, run \`deepspace app init --new-id\` to register it as a fresh app; a brand-new app dir registers with \`deepspace app init\`.`,
6921
+ `${appId} is not registered. If this repo's id came from an older SDK's scaffold, run \`deepspace app init --new-id${envName ? ` --env ${envName}` : ""}\` to register it as a fresh app; a brand-new app dir registers with \`deepspace app init${envName ? ` --env ${envName}` : ""}\`.`,
6821
6922
  "app_not_registered"
6822
6923
  );
6823
6924
  }
6824
- const secretsCache = await loadDeploySecrets({
6925
+ await requireDeployCapabilities(DEPLOY_URL4, null, output);
6926
+ const { configName: secretsConfig } = await loadDeploySecrets({
6825
6927
  deployUrl: DEPLOY_URL4,
6826
6928
  appDir,
6827
6929
  appId,
@@ -6833,7 +6935,9 @@ async function runLockedDeploy(args, output, appDir, envName) {
6833
6935
  const ownerJwtRefusal = ownerJwtMissingRefusal(sourceState);
6834
6936
  if (ownerJwtRefusal) output.die(ownerJwtRefusal.error, ownerJwtRefusal.code);
6835
6937
  if (sourceState.onBehalf) {
6836
- process.stderr.write("Deploying on behalf of the owner \u2014 this app belongs to another account.\n");
6938
+ process.stderr.write(
6939
+ "Deploying on behalf of the owner \u2014 this app belongs to another account.\n"
6940
+ );
6837
6941
  }
6838
6942
  let confirmRename = args.rename === true;
6839
6943
  const rename2 = pendingRename(sourceState.registeredHost, appName);
@@ -6878,12 +6982,6 @@ async function runLockedDeploy(args, output, appDir, envName) {
6878
6982
  output,
6879
6983
  sourceState
6880
6984
  });
6881
- const secrets = prepareDeploySecrets({
6882
- cache: secretsCache,
6883
- customBindings: bundle.customBindings,
6884
- doManifest: bundle.doManifest,
6885
- output
6886
- });
6887
6985
  const body = await deployBuiltBundle({
6888
6986
  deployUrl: DEPLOY_URL4,
6889
6987
  appDir,
@@ -6895,7 +6993,8 @@ async function runLockedDeploy(args, output, appDir, envName) {
6895
6993
  claimReleased: args["claim-released"] === true,
6896
6994
  ignoreStale: Boolean(args["ignore-stale"]),
6897
6995
  bundle,
6898
- secrets,
6996
+ secretsConfig,
6997
+ envName,
6899
6998
  repository,
6900
6999
  output,
6901
7000
  spinner
@@ -7773,24 +7872,11 @@ function featureCatalog(packageRoot = featurePackageRoot()) {
7773
7872
  description: config.description ?? null
7774
7873
  }));
7775
7874
  }
7776
- function editDistance2(a, b) {
7777
- const row = Array.from({ length: b.length + 1 }, (_, index) => index);
7778
- for (let i = 1; i <= a.length; i++) {
7779
- let previous = row[0];
7780
- row[0] = i;
7781
- for (let j = 1; j <= b.length; j++) {
7782
- const current = row[j];
7783
- row[j] = Math.min(row[j] + 1, row[j - 1] + 1, previous + (a[i - 1] === b[j - 1] ? 0 : 1));
7784
- previous = current;
7785
- }
7786
- }
7787
- return row[b.length];
7788
- }
7789
7875
  function suggestFeature(featureId, packageRoot = featurePackageRoot()) {
7790
7876
  let closest = null;
7791
7877
  let closestDistance = Number.POSITIVE_INFINITY;
7792
7878
  for (const feature of listFeatureConfigs(packageRoot)) {
7793
- const distance = editDistance2(featureId, feature.id);
7879
+ const distance = editDistance(featureId, feature.id);
7794
7880
  if (distance < closestDistance) {
7795
7881
  closest = feature.id;
7796
7882
  closestDistance = distance;
@@ -7839,7 +7925,7 @@ function installFiles(packageRoot, config, targetDir, emit) {
7839
7925
  if (!existed) missing.push(resolvedDest);
7840
7926
  continue;
7841
7927
  }
7842
- if (existed && !file.overwrite) {
7928
+ if (existed) {
7843
7929
  emit(` Exists (skipped): ${resolvedDest}`);
7844
7930
  skipped++;
7845
7931
  continue;
@@ -7848,8 +7934,8 @@ function installFiles(packageRoot, config, targetDir, emit) {
7848
7934
  const shiftLevels = resolvedDest.split("/").length - file.dest.split("/").length;
7849
7935
  if (wasRedirected) copyFileWithImportShift(srcPath, destPath, shiftLevels);
7850
7936
  else copyFile(srcPath, destPath);
7851
- emit(` ${existed ? "Overwrote" : "Copied"}: ${resolvedDest}`);
7852
- if (!existed) created.push(resolvedDest);
7937
+ emit(` Copied: ${resolvedDest}`);
7938
+ created.push(resolvedDest);
7853
7939
  copied++;
7854
7940
  }
7855
7941
  return { copied, skipped, files: { created, preexisting, missing } };
@@ -8119,50 +8205,6 @@ function rateLimitBindingBlock(scope, binding) {
8119
8205
  `simple = { limit = ${binding.limit}, period = ${binding.period} }`
8120
8206
  ].join("\n");
8121
8207
  }
8122
- function integrateCss(packageRoot, config, targetDir, emit) {
8123
- if (!config.css?.length) return [];
8124
- const stylesPath = join22(targetDir, "src", "styles.css");
8125
- if (!existsSync21(stylesPath)) {
8126
- emit(" Warning: Cannot integrate CSS \u2014 src/styles.css not found");
8127
- return [
8128
- {
8129
- area: "css",
8130
- reason: "src/styles.css was not found.",
8131
- steps: config.css.map(
8132
- (cssFile) => `Append node_modules/deepspace/features/${config.id}/${cssFile} to src/styles.css.`
8133
- )
8134
- }
8135
- ];
8136
- }
8137
- let styles = readFileSync17(stylesPath, "utf-8");
8138
- let integrated = 0;
8139
- const unresolved = [];
8140
- for (const cssFile of config.css) {
8141
- const srcPath = join22(packageRoot, "features", config.id, cssFile);
8142
- if (!existsSync21(srcPath)) {
8143
- emit(` Warning: CSS source not found: ${cssFile}`, "stderr");
8144
- unresolved.push({
8145
- area: "css",
8146
- reason: `Feature CSS source '${cssFile}' was not found in the installed package.`,
8147
- steps: ["Reinstall the current deepspace package, then retry this feature installation."]
8148
- });
8149
- continue;
8150
- }
8151
- const css = readFileSync17(srcPath, "utf-8");
8152
- const fingerprint = css.match(/\.([\w-]+)\s*\{|@keyframes\s+([\w-]+)/);
8153
- const marker = fingerprint?.[1] ?? fingerprint?.[2];
8154
- if (marker && styles.includes(marker)) {
8155
- emit(` CSS already present: ${cssFile} (found .${marker})`);
8156
- continue;
8157
- }
8158
- styles += `
8159
- ${css}`;
8160
- integrated++;
8161
- emit(` CSS integrated: ${cssFile} -> styles.css`);
8162
- }
8163
- if (integrated > 0) writeFileSync7(stylesPath, styles);
8164
- return unresolved;
8165
- }
8166
8208
  function unresolvedRoute(route, config, reason, emit) {
8167
8209
  const roles = routeRolesSuffix(route);
8168
8210
  emit("");
@@ -8387,7 +8429,6 @@ function installFeature(options) {
8387
8429
  ...integrateActions(config, targetDir, emit),
8388
8430
  ...integrateWrangler(config, targetDir, emit),
8389
8431
  ...integrateCode(config, targetDir, emit),
8390
- ...integrateCss(packageRoot, config, targetDir, emit),
8391
8432
  ...integrateRoute(config, targetDir, emit)
8392
8433
  ];
8393
8434
  const dependencyResult = integrateDependencies(
@@ -10852,10 +10893,17 @@ var pull = defineCommand9({
10852
10893
  wranglerEnv,
10853
10894
  t.configName
10854
10895
  );
10855
- await writeDevVars(appDir, ownerId, t.token, wranglerEnv, {
10856
- appId: t.appId,
10857
- generatedSecretsCache: refreshed.rendered
10858
- });
10896
+ try {
10897
+ await writeDevVars(appDir, ownerId, t.token, wranglerEnv, {
10898
+ appId: t.appId,
10899
+ generatedSecretsCache: refreshed.rendered
10900
+ });
10901
+ } catch (err) {
10902
+ if (args.app && err instanceof Refusal && err.code === "app_not_found") {
10903
+ throw new Refusal(err.message, err.code);
10904
+ }
10905
+ throw err;
10906
+ }
10859
10907
  const count = Object.keys(refreshed.pulled?.values ?? {}).length;
10860
10908
  ok(
10861
10909
  args.json === true,
@@ -11031,7 +11079,7 @@ var init_default = defineDeepspaceCommand({
11031
11079
  } catch (error) {
11032
11080
  if (error instanceof ApiError && error.code === "forbidden") {
11033
11081
  throw new Refusal(
11034
- `wrangler.toml carries ${existing}, which is registered to another user. Run \`deepspace app init --new-id\` to fork this repo as your own app (new data and secrets; the original is untouched).`,
11082
+ `wrangler.toml carries ${existing}, which is registered to another user. Run \`deepspace app init --new-id${envName ? ` --env ${envName}` : ""}\` to fork this repo as your own app (new data and secrets; the original is untouched).`,
11035
11083
  "not_app_owner"
11036
11084
  );
11037
11085
  }
@@ -11039,7 +11087,7 @@ var init_default = defineDeepspaceCommand({
11039
11087
  }
11040
11088
  if (!state.registered) {
11041
11089
  throw new Refusal(
11042
- `wrangler.toml carries ${existing}, but that id was never registered \u2014 it was minted locally by an older SDK. Ids are server-minted at registration now, and an existing unregistered id cannot be claimed. Run \`deepspace app init --new-id\` to register this repo as a fresh app (new data and secrets; nothing to migrate \u2014 the old id never had server-side state).`,
11090
+ `wrangler.toml carries ${existing}, but that id was never registered \u2014 it was minted locally by an older SDK. Ids are server-minted at registration now, and an existing unregistered id cannot be claimed. Run \`deepspace app init --new-id${envName ? ` --env ${envName}` : ""}\` to register this repo as a fresh app (new data and secrets; nothing to migrate \u2014 the old id never had server-side state).`,
11043
11091
  "app_not_registered",
11044
11092
  {
11045
11093
  action: cliAction(
@@ -11117,7 +11165,14 @@ var init_default = defineDeepspaceCommand({
11117
11165
  ...!committedScaffold && wranglerConfigUncommitted(appDir) ? {
11118
11166
  action: {
11119
11167
  cwd: appDir,
11120
- argv: ["git", "commit", "-m", existing ? `Fork as ${appId}` : `Register ${appId}`, "--", "wrangler.toml"]
11168
+ argv: [
11169
+ "git",
11170
+ "commit",
11171
+ "-m",
11172
+ existing ? `Fork as ${appId}` : `Register ${appId}`,
11173
+ "--",
11174
+ "wrangler.toml"
11175
+ ]
11121
11176
  }
11122
11177
  } : {}
11123
11178
  };
@@ -12254,9 +12309,11 @@ var releases_default = defineDeepspaceCommand({
12254
12309
  // src/cli/commands/rollback.ts
12255
12310
  import * as p20 from "@clack/prompts";
12256
12311
  var REL_ID_RE = /^rel_[0-9A-HJKMNP-TV-Z]{26}$/;
12257
- function unavailableDoGuardRefusal(message) {
12312
+ function unavailableDoGuardRefusal(message, detail) {
12313
+ const cause = typeof detail === "string" && detail !== "" ? `
12314
+ Cause: ${detail}` : "";
12258
12315
  return new Refusal(
12259
- `${message}
12316
+ `${message}${cause}
12260
12317
  Retry in a moment, or re-run with --allow-do-deletion to accept the risk.`,
12261
12318
  "do_guard_unavailable"
12262
12319
  );
@@ -12352,7 +12409,7 @@ Re-run with --allow-do-deletion to accept the data loss.`,
12352
12409
  );
12353
12410
  }
12354
12411
  if (err instanceof ApiError && err.code === "do_guard_unavailable") {
12355
- throw unavailableDoGuardRefusal(err.message);
12412
+ throw unavailableDoGuardRefusal(err.message, err.details?.detail);
12356
12413
  }
12357
12414
  throw err;
12358
12415
  }
@@ -12373,7 +12430,8 @@ Re-run with --allow-do-deletion to accept the data loss.`,
12373
12430
  const doClassGuard = result.doClassGuard === "unverified" ? "unverified" : void 0;
12374
12431
  if (!args.json) {
12375
12432
  if (result.url) {
12376
- if (serving === "unconfirmed") p20.log.warn(`URL: ${result.url} (accepted; serving not yet verified)`);
12433
+ if (serving === "unconfirmed")
12434
+ p20.log.warn(`URL: ${result.url} (accepted; serving not yet verified)`);
12377
12435
  else p20.log.success(`Live at: ${result.url}`);
12378
12436
  }
12379
12437
  if (result.bundleRetained === false) {
@@ -14627,7 +14685,7 @@ import { join as join28, resolve as resolve21 } from "path";
14627
14685
  // package.json
14628
14686
  var package_default = {
14629
14687
  name: "deepspace",
14630
- version: "0.27.0",
14688
+ version: "0.27.1",
14631
14689
  engines: {
14632
14690
  node: ">=22.15.0 <23 || >=24 <25 || >=26 <27"
14633
14691
  },