deepspace 0.13.0 → 0.14.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,14 @@
1
1
  # deepspace
2
2
 
3
+ ## 0.14.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Enforce the per-tier storage quota on app files. An app's file allocation had per-file, per-part and per-request ceilings but no admission against the owner's plan, so a free-tier app could hold 202 MiB against a 128 MiB limit with every upload reporting success and no surface reporting usage. One per-tier table now serves both storage allocations (the repo store and app files), and the shared files handler admits every write: the single-request upload before the put, a chunked upload against its declared total at init, and again against the object R2 actually assembled at complete — an over-limit assembly is deleted rather than left in the allocation. An upsert is charged only for the bytes it adds. The owner's tier caps the allocation whoever writes, so an app writing at runtime and its owner's CLI meet the same limit; a billing lookup that fails refuses writes rather than admitting unmetered storage, and leaves reads working. Refusals answer `storage_quota_exceeded` with the used and limit byte counts and name the recovery, and `deepspace app files list` now prints storage used against the limit.
8
+ - `deepspace deploy` and `deepspace rollback` now wait for the release they just published, not merely for the app to answer. Every upload carries a synthetic `/.well-known/deepspace/release.json` asset naming its own version, injected by the platform at upload time — so this holds for every app already deployed, including rollbacks of bundles that predate the change, with no app code and no redeploy. Cloudflare serves assets atomically with the version, so the CLI polls that path until the edge returns the release it shipped; without a stamp (an older platform, or a resumed activation) it falls back to the previous reachability probe. "Deployed!" previously landed 30–60 seconds before the edge agreed, which made any deploy-then-assert script flaky by construction.
9
+
10
+ Invites can now be accepted without the emailed link. `deepspace app collaborators invites` lists the invites waiting for your signed-in email and `deepspace app collaborators accept <app-id>` accepts one — the same authority the `/join` page enforces (the signed-in email must match the invited address), reachable from a terminal. Previously an invitee who had never signed in could reach a state with no way forward: signing in did not grant access, the CLI said it would, and only the emailed link could complete it. That CLI message now describes what actually happens.
11
+
3
12
  ## 0.13.0
4
13
 
5
14
  ### Minor Changes
package/dist/cli.js CHANGED
@@ -3798,6 +3798,36 @@ import { resolve as resolve12 } from "path";
3798
3798
 
3799
3799
  // src/cli/lib/edge-propagation.ts
3800
3800
  var NOT_SERVING = ["Assets have not yet deployed", "No app configured"];
3801
+ var RELEASE_STAMP_PATH = "/.well-known/deepspace/release.json";
3802
+ async function waitForLiveRelease(url, releaseStamp, timeoutMs) {
3803
+ return releaseStamp ? waitForReleaseStamp(url, releaseStamp, timeoutMs) : waitForEdgePropagation(url, timeoutMs);
3804
+ }
3805
+ async function waitForReleaseStamp(url, releaseStamp, timeoutMs) {
3806
+ const deadline = Date.now() + timeoutMs;
3807
+ let attempt = 0;
3808
+ let streak = 0;
3809
+ while (Date.now() < deadline) {
3810
+ attempt++;
3811
+ try {
3812
+ const probe = new URL(RELEASE_STAMP_PATH, url);
3813
+ probe.searchParams.set("probe", `${Date.now()}-${attempt}`);
3814
+ const response = await fetch(probe, { redirect: "manual" });
3815
+ const body = await response.json().catch(() => null);
3816
+ if (response.ok && body?.release === releaseStamp) {
3817
+ streak++;
3818
+ if (streak >= 3) return true;
3819
+ await new Promise((resolve19) => setTimeout(resolve19, 1500));
3820
+ continue;
3821
+ }
3822
+ streak = 0;
3823
+ } catch {
3824
+ streak = 0;
3825
+ }
3826
+ const waitMs = Math.min(8e3, 1e3 * 2 ** (attempt - 1));
3827
+ await new Promise((resolve19) => setTimeout(resolve19, waitMs));
3828
+ }
3829
+ return false;
3830
+ }
3801
3831
  async function waitForEdgePropagation(url, timeoutMs) {
3802
3832
  const deadline = Date.now() + timeoutMs;
3803
3833
  let attempt = 0;
@@ -3840,6 +3870,13 @@ function planUploadParts(totalBytes, partSize) {
3840
3870
  }
3841
3871
  return parts;
3842
3872
  }
3873
+ var APP_STORAGE_LIMIT_BYTES = {
3874
+ test: 0,
3875
+ free: 128 * 1024 * 1024,
3876
+ starter: 512 * 1024 * 1024,
3877
+ premium: 2 * 1024 * 1024 * 1024,
3878
+ admin: 10 * 1024 * 1024 * 1024
3879
+ };
3843
3880
  var MAX_DEPLOY_ASSET_FILE_BYTES = 25 * 1024 * 1024;
3844
3881
  var DANGEROUS_MIME_TYPES = /* @__PURE__ */ new Set([
3845
3882
  "text/html",
@@ -6040,7 +6077,7 @@ var deploy_default = defineCommand3({
6040
6077
  });
6041
6078
  if (body.url) {
6042
6079
  spinner2.message("Waiting for edge propagation...");
6043
- const ready = await waitForEdgePropagation(body.url, 9e4);
6080
+ const ready = await waitForLiveRelease(body.url, body.releaseStamp, 9e4);
6044
6081
  if (!ready) {
6045
6082
  spinner2.stop("Deployed (edge propagation still in progress after 90s)");
6046
6083
  p12.log.warn(
@@ -8458,8 +8495,14 @@ var list3 = defineDeepspaceCommand({
8458
8495
  const more = limit < MAX_LIST_LIMIT ? "raise --limit or narrow with --prefix" : "narrow with --prefix";
8459
8496
  if (result.truncated) console.log(`(truncated at ${limit} \u2014 ${more})`);
8460
8497
  }
8498
+ if (result.storage) {
8499
+ const { usedBytes, limitBytes } = result.storage;
8500
+ console.log(
8501
+ limitBytes === void 0 ? `Storage: ${formatBytes(usedBytes)} used (limit unavailable right now)` : `Storage: ${formatBytes(usedBytes)} of ${formatBytes(limitBytes)} used`
8502
+ );
8503
+ }
8461
8504
  }
8462
- return { data: { appId, files, truncated: result.truncated } };
8505
+ return { data: { appId, files, truncated: result.truncated, storage: result.storage ?? null } };
8463
8506
  }
8464
8507
  });
8465
8508
  var get = defineDeepspaceCommand({
@@ -8587,7 +8630,7 @@ var add = defineDeepspaceCommand({
8587
8630
  }
8588
8631
  if (!args.json) {
8589
8632
  console.log(
8590
- `\u2713 Invite sent to ${res.email} (expires ${expires}). They become a collaborator when they sign in.`
8633
+ `\u2713 Invite sent to ${res.email} (expires ${expires}). They accept it from the emailed link, or by signing in and running \`deepspace app collaborators accept ${app2}\`.`
8591
8634
  );
8592
8635
  }
8593
8636
  return { data: { ...res } };
@@ -8656,9 +8699,56 @@ var cancel3 = defineDeepspaceCommand({
8656
8699
  return { data: { cancelled: found } };
8657
8700
  }
8658
8701
  });
8702
+ var invites = defineDeepspaceCommand({
8703
+ meta: { name: "invites", description: "List invites waiting for you" },
8704
+ async run({ args }) {
8705
+ const token = await ensureToken();
8706
+ const { invites: invites2 } = await api2(
8707
+ token,
8708
+ "/api/app-collaborators/invites/mine"
8709
+ );
8710
+ if (!args.json) {
8711
+ if (!invites2.length) {
8712
+ console.log("No pending invites for your email.");
8713
+ } else {
8714
+ for (const invite of invites2) {
8715
+ console.log(
8716
+ `${invite.appId} invited by ${invite.invitedByName} (expires ${new Date(invite.expiresAt).toLocaleDateString()})`
8717
+ );
8718
+ }
8719
+ console.log("Accept one with `deepspace app collaborators accept <app-id>`.");
8720
+ }
8721
+ }
8722
+ return { data: { invites: invites2 } };
8723
+ }
8724
+ });
8725
+ var accept = defineDeepspaceCommand({
8726
+ meta: { name: "accept", description: "Accept an invite to collaborate on an app" },
8727
+ args: {
8728
+ app: { type: "positional", description: "App id (see `collaborators invites`)", required: true }
8729
+ },
8730
+ async run({ args }) {
8731
+ const appId = String(args.app);
8732
+ const token = await ensureToken();
8733
+ const res = await api2(
8734
+ token,
8735
+ `/api/app-collaborators/invites/${encodeURIComponent(appId)}/accept`,
8736
+ { method: "POST" }
8737
+ );
8738
+ if (!args.json) {
8739
+ console.log(
8740
+ res.alreadyMember ? `\u2022 You already have access to ${res.appId}.` : `\u2713 You can now deploy ${res.appId}.`
8741
+ );
8742
+ }
8743
+ return {
8744
+ data: { ...res },
8745
+ action: cliAction("deepspace", "pull", "--app", res.appId)
8746
+ };
8747
+ }
8748
+ });
8659
8749
  var collaborators_default = defineCommand7({
8660
8750
  meta: { name: "collaborators", description: "Manage who can deploy your app" },
8661
- subCommands: { list: list4, add, remove, cancel: cancel3 }
8751
+ subCommands: { list: list4, add, remove, cancel: cancel3, invites, accept }
8662
8752
  });
8663
8753
 
8664
8754
  // src/cli/commands/transfer.ts
@@ -8743,7 +8833,7 @@ var status2 = defineDeepspaceCommand({
8743
8833
  return { data: { app: app2, transfer } };
8744
8834
  }
8745
8835
  });
8746
- var accept = defineDeepspaceCommand({
8836
+ var accept2 = defineDeepspaceCommand({
8747
8837
  meta: { name: "accept", description: "Accept a transfer offered to you" },
8748
8838
  args: {
8749
8839
  app: { type: "string", alias: "a", description: "App id of the offered app", required: true }
@@ -8784,7 +8874,7 @@ var cancel4 = defineDeepspaceCommand({
8784
8874
  });
8785
8875
  var transfer_default = defineCommand8({
8786
8876
  meta: { name: "transfer", description: "Transfer app ownership to another user" },
8787
- subCommands: { offer, status: status2, accept, cancel: cancel4 }
8877
+ subCommands: { offer, status: status2, accept: accept2, cancel: cancel4 }
8788
8878
  });
8789
8879
 
8790
8880
  // src/cli/commands/integrations.ts
@@ -10945,7 +11035,7 @@ Re-run with --allow-do-deletion to accept the data loss.`,
10945
11035
  let edgePropagating = false;
10946
11036
  if (result.url) {
10947
11037
  spinner2?.message("Waiting for edge propagation...");
10948
- edgePropagating = !await waitForEdgePropagation(result.url, 9e4);
11038
+ edgePropagating = !await waitForLiveRelease(result.url, result.releaseStamp, 9e4);
10949
11039
  }
10950
11040
  spinner2?.stop(
10951
11041
  edgePropagating ? `Rolled back to ${result.rolledBackTo ?? releaseId} (edge propagation still in progress after 90s).` : `Rolled back to ${result.rolledBackTo ?? releaseId}.`