@supacloud/cli 0.17.0 → 0.18.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/README.md CHANGED
@@ -212,10 +212,10 @@ already exist. Add the positive version observed from `list` as
212
212
  pointer; this remains correct across an active-version A→B→A transition.
213
213
 
214
214
  `deploy`, `deploy_bundle`, and `activate` require
215
- `--expected-active-version <N|absent>`. Read the current positive integer
216
- version from `edge_functions list`; use `absent` only when creating a slug that
217
- does not yet exist. A stale value returns HTTP 409 without building, preheating,
218
- or activating another version. List output remains a JSON array with string
215
+ `--expected-active-version <N|absent>`. Read the current non-negative integer
216
+ version from `edge_functions list`; use `0` for a listed legacy Function and
217
+ `absent` only when creating a slug that does not yet exist. A stale value returns
218
+ HTTP 409 without building, preheating, or activating another version. List output remains a JSON array with string
219
219
  `slug` and numeric `version` fields, while source output is exactly
220
220
  `{ "code": "..." }`. Release automation must use `source --version <N>` for a
221
221
  version-bound backup.
@@ -228,9 +228,13 @@ server response body.
228
228
  Mutation receipts use schema `supacloud.cli.release-control.v1`. An
229
229
  `OUTCOME_UNKNOWN` error means the server may have committed the mutation before
230
230
  the response was lost or failed validation; read back current state before any
231
- retry.
232
- Version `0` is reserved for service-internal legacy recovery and cannot be used
233
- as a public CLI/API activation target or expected active version.
231
+ retry. For Function deploy, bundle deploy, and activation, the CLI applies a
232
+ separate 5-second, 64 KiB response-body boundary after receiving HTTP headers.
233
+ A stalled, oversized, truncated, unreadable, or malformed body is always
234
+ `OUTCOME_UNKNOWN` and its content is never included in CLI output.
235
+ Version `0` is reserved as the active-version CAS token for legacy Functions. It
236
+ can be passed only as `--expected-active-version`; immutable source reads and
237
+ activation targets still require a positive version.
234
238
 
235
239
  ```json
236
240
  {
package/dist/index.js CHANGED
@@ -6576,6 +6576,8 @@ function validateExecutionPolicyCoverage(tools) {
6576
6576
 
6577
6577
  // src/shared/transports/http.ts
6578
6578
  var DEFAULT_TIMEOUT = 30000;
6579
+ var RELEASE_MUTATION_RESPONSE_TIMEOUT = 5000;
6580
+ var RELEASE_MUTATION_RESPONSE_MAX_BYTES = 64 * 1024;
6579
6581
  var MAX_RETRIES = 2;
6580
6582
  var RETRY_BASE_DELAY = 500;
6581
6583
  function isRetryableMethod(method) {
@@ -6598,6 +6600,14 @@ function transportFailure(error) {
6598
6600
  transportError: true
6599
6601
  };
6600
6602
  }
6603
+ function responseReadFailure(status) {
6604
+ return {
6605
+ ok: false,
6606
+ status,
6607
+ data: { error: "Response body unavailable", code: "RESPONSE_READ_ERROR" },
6608
+ responseReadError: true
6609
+ };
6610
+ }
6601
6611
  async function fetchWithTimeout(url, options) {
6602
6612
  const controller = new AbortController;
6603
6613
  const timeout = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT);
@@ -6647,41 +6657,114 @@ function joinedResponseBytes(chunks, totalBytes) {
6647
6657
  }
6648
6658
  return responseBytes;
6649
6659
  }
6650
- async function responseBytesWithinLimit(response, maxBytes) {
6651
- if (declaredResponseTooLarge(response, maxBytes)) {
6652
- await response.body?.cancel();
6653
- return null;
6654
- }
6655
- if (!response.body)
6656
- return new Uint8Array;
6657
- const reader = response.body.getReader();
6660
+ function cancelResponseReader(reader) {
6661
+ reader.cancel().catch(() => {
6662
+ return;
6663
+ });
6664
+ }
6665
+ function serializedRequestBody(body) {
6666
+ return body ? JSON.stringify(body) : undefined;
6667
+ }
6668
+ async function responseBytesFromReader(reader, maxBytes, declaredBytes) {
6658
6669
  const chunks = [];
6659
6670
  let totalBytes = 0;
6660
6671
  while (true) {
6661
6672
  const { done, value } = await reader.read();
6662
- if (done)
6663
- return joinedResponseBytes(chunks, totalBytes);
6673
+ if (done) {
6674
+ if (declaredBytes !== null && totalBytes !== declaredBytes)
6675
+ return { ok: false };
6676
+ return { ok: true, bytes: joinedResponseBytes(chunks, totalBytes) };
6677
+ }
6664
6678
  totalBytes += value.byteLength;
6665
6679
  if (totalBytes > maxBytes) {
6666
- await reader.cancel();
6667
- return null;
6680
+ cancelResponseReader(reader);
6681
+ return { ok: false };
6668
6682
  }
6669
6683
  chunks.push(value);
6670
6684
  }
6671
6685
  }
6686
+ async function responseBytesWithinLimit(response, maxBytes) {
6687
+ if (declaredResponseTooLarge(response, maxBytes)) {
6688
+ await response.body?.cancel();
6689
+ return null;
6690
+ }
6691
+ if (!response.body)
6692
+ return new Uint8Array;
6693
+ const reader = response.body.getReader();
6694
+ const bodyRead = await responseBytesFromReader(reader, maxBytes, null);
6695
+ return bodyRead.ok ? bodyRead.bytes : null;
6696
+ }
6672
6697
  function parsedUtf8Json(responseBytes) {
6673
6698
  try {
6674
6699
  const responseText = new TextDecoder("utf-8", { fatal: true }).decode(responseBytes);
6675
- return JSON.parse(responseText);
6700
+ return { ok: true, parsedJson: JSON.parse(responseText) };
6676
6701
  } catch (error) {
6677
6702
  if (error instanceof SyntaxError || error instanceof TypeError)
6678
- return null;
6703
+ return { ok: false };
6679
6704
  throw error;
6680
6705
  }
6681
6706
  }
6682
6707
  async function boundedResponseJson(response, maxBytes) {
6683
6708
  const responseBytes = await responseBytesWithinLimit(response, maxBytes);
6684
- return responseBytes === null ? null : parsedUtf8Json(responseBytes);
6709
+ if (responseBytes === null)
6710
+ return null;
6711
+ const parsed = parsedUtf8Json(responseBytes);
6712
+ return parsed.ok ? parsed.parsedJson : null;
6713
+ }
6714
+ function declaredIdentityResponseBytes(response) {
6715
+ const contentEncoding = response.headers.get("content-encoding");
6716
+ if (contentEncoding !== null && contentEncoding.toLowerCase() !== "identity")
6717
+ return null;
6718
+ const contentLength = response.headers.get("content-length");
6719
+ if (contentLength === null)
6720
+ return null;
6721
+ if (!/^\d+$/.test(contentLength))
6722
+ return "invalid";
6723
+ const declaredBytes = Number(contentLength);
6724
+ return Number.isSafeInteger(declaredBytes) ? declaredBytes : "invalid";
6725
+ }
6726
+ async function releaseMutationResponseBytes(response) {
6727
+ const declaredBytes = declaredIdentityResponseBytes(response);
6728
+ if (declaredBytes === "invalid" || declaredBytes !== null && declaredBytes > RELEASE_MUTATION_RESPONSE_MAX_BYTES) {
6729
+ response.body?.cancel().catch(() => {
6730
+ return;
6731
+ });
6732
+ return { ok: false };
6733
+ }
6734
+ if (!response.body) {
6735
+ return declaredBytes === null || declaredBytes === 0 ? { ok: true, bytes: new Uint8Array } : { ok: false };
6736
+ }
6737
+ return responseBytesBeforeDeadline(response.body.getReader(), declaredBytes);
6738
+ }
6739
+ async function responseBytesBeforeDeadline(reader, declaredBytes) {
6740
+ let deadlineTimer;
6741
+ const deadline = new Promise((resolve2) => {
6742
+ deadlineTimer = setTimeout(() => {
6743
+ cancelResponseReader(reader);
6744
+ resolve2({ ok: false });
6745
+ }, RELEASE_MUTATION_RESPONSE_TIMEOUT);
6746
+ });
6747
+ try {
6748
+ return await Promise.race([
6749
+ responseBytesFromReader(reader, RELEASE_MUTATION_RESPONSE_MAX_BYTES, declaredBytes),
6750
+ deadline
6751
+ ]);
6752
+ } catch {
6753
+ cancelResponseReader(reader);
6754
+ return { ok: false };
6755
+ } finally {
6756
+ clearTimeout(deadlineTimer);
6757
+ }
6758
+ }
6759
+ async function releaseMutationResponseJson(response) {
6760
+ const responseBytes = await releaseMutationResponseBytes(response);
6761
+ if (!responseBytes.ok)
6762
+ return { ok: false };
6763
+ const parsed = parsedUtf8Json(responseBytes.bytes);
6764
+ return parsed.ok ? { ok: true, parsedJson: parsed.parsedJson } : { ok: false };
6765
+ }
6766
+ async function responseJsonOrNull(response) {
6767
+ return { ok: true, parsedJson: await response.json().catch(() => null) };
6685
6768
  }
6686
6769
 
6687
6770
  class HttpTransport {
@@ -6697,6 +6780,19 @@ class HttpTransport {
6697
6780
  "Content-Type": "application/json"
6698
6781
  };
6699
6782
  }
6783
+ async postWithResponseReader(path, serializedBody, responseReader) {
6784
+ try {
6785
+ const response = await fetchWithRetry(`${this.baseUrl}${path}`, {
6786
+ method: "POST",
6787
+ headers: this.headers(),
6788
+ body: serializedBody
6789
+ });
6790
+ const responseBody = await responseReader(response);
6791
+ return responseBody.ok ? { ok: response.ok, status: response.status, data: responseBody.parsedJson } : responseReadFailure(response.status);
6792
+ } catch (error) {
6793
+ return transportFailure(error);
6794
+ }
6795
+ }
6700
6796
  async get(path, options = {}) {
6701
6797
  try {
6702
6798
  const res = await fetchWithRetry(`${this.baseUrl}${path}`, {
@@ -6711,17 +6807,14 @@ class HttpTransport {
6711
6807
  }
6712
6808
  async post(path, body) {
6713
6809
  try {
6714
- const res = await fetchWithRetry(`${this.baseUrl}${path}`, {
6715
- method: "POST",
6716
- headers: this.headers(),
6717
- body: body ? JSON.stringify(body) : undefined
6718
- });
6719
- const data = await res.json().catch(() => null);
6720
- return { ok: res.ok, status: res.status, data };
6810
+ return await this.postWithResponseReader(path, serializedRequestBody(body), responseJsonOrNull);
6721
6811
  } catch (error) {
6722
6812
  return transportFailure(error);
6723
6813
  }
6724
6814
  }
6815
+ async postReleaseMutation(path, body) {
6816
+ return this.postWithResponseReader(path, serializedRequestBody(body), releaseMutationResponseJson);
6817
+ }
6725
6818
  async postMultipart(path, formData) {
6726
6819
  try {
6727
6820
  const headers = { Authorization: `Bearer ${this.token}` };
@@ -7886,7 +7979,7 @@ function releaseControlFailure(operation, code, httpStatus) {
7886
7979
  });
7887
7980
  }
7888
7981
  function releaseControlMutationFailure(operation, response) {
7889
- const outcomeUnknown = response.transportError || response.status === 408 || response.status >= 500;
7982
+ const outcomeUnknown = response.transportError || response.responseReadError || response.status === 408 || response.status >= 500;
7890
7983
  return outcomeUnknown ? releaseControlFailure(operation, "OUTCOME_UNKNOWN", response.transportError ? null : response.status) : releaseControlFailure(operation, "HTTP_ERROR", response.status);
7891
7984
  }
7892
7985
  function releaseControlResponse(payload) {
@@ -8523,6 +8616,17 @@ function positiveFunctionVersion(input, label) {
8523
8616
  }
8524
8617
  return version;
8525
8618
  }
8619
+ function activeFunctionVersionToken(input) {
8620
+ if (typeof input !== "string" && typeof input !== "number") {
8621
+ throw new Error("Expected active version must be a canonical non-negative safe integer");
8622
+ }
8623
+ const version = String(input);
8624
+ if (!CANONICAL_FUNCTION_VERSION_PATTERN.test(version) || !Number.isSafeInteger(Number(version))) {
8625
+ throw new Error("Expected active version must be a canonical non-negative safe integer");
8626
+ }
8627
+ return version;
8628
+ }
8629
+ var CANONICAL_FUNCTION_VERSION_PATTERN = /^(?:0|[1-9][0-9]*)$/;
8526
8630
  var POSITIVE_FUNCTION_VERSION_PATTERN = /^[1-9][0-9]*$/;
8527
8631
  var SAFE_FUNCTION_SLUG_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
8528
8632
  var FUNCTION_ACTIVATION_ARGUMENTS = new Set([
@@ -8539,15 +8643,15 @@ var functionVersionSchema = Type.Optional(decodedSchema(Type.Union([
8539
8643
  function parseExpectedActiveVersion(input) {
8540
8644
  if (input === "absent")
8541
8645
  return input;
8542
- return positiveFunctionVersion(input, "Expected active version");
8646
+ return activeFunctionVersionToken(input);
8543
8647
  }
8544
8648
  var expectedActiveVersionSchema = Type.Optional(decodedSchema(Type.Union([
8545
8649
  Type.Literal("absent"),
8546
- Type.Integer({ minimum: 1, maximum: Number.MAX_SAFE_INTEGER }),
8547
- Type.String({ pattern: POSITIVE_FUNCTION_VERSION_PATTERN.source, maxLength: 16 })
8650
+ Type.Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER }),
8651
+ Type.String({ pattern: CANONICAL_FUNCTION_VERSION_PATTERN.source, maxLength: 16 })
8548
8652
  ]), Type.Union([
8549
8653
  Type.Literal("absent"),
8550
- Type.String({ pattern: POSITIVE_FUNCTION_VERSION_PATTERN.source, maxLength: 16 })
8654
+ Type.String({ pattern: CANONICAL_FUNCTION_VERSION_PATTERN.source, maxLength: 16 })
8551
8655
  ]), parseExpectedActiveVersion));
8552
8656
  var secretListSchema = Type.Array(Type.Object({ name: Type.String(), value: Type.String() }));
8553
8657
  var ENVIRONMENT_SECRET_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]{0,255}$/;
@@ -8679,7 +8783,7 @@ function safeFunctionList(payload) {
8679
8783
  const edgeFunction = objectRecord(candidate);
8680
8784
  const slug = edgeFunction?.slug;
8681
8785
  const version = edgeFunction?.version;
8682
- if (typeof slug !== "string" || !SAFE_FUNCTION_SLUG_PATTERN.test(slug) || typeof version !== "number" || !Number.isSafeInteger(version) || version < 1 || functionSlugs.has(slug))
8786
+ if (typeof slug !== "string" || !SAFE_FUNCTION_SLUG_PATTERN.test(slug) || typeof version !== "number" || !Number.isSafeInteger(version) || version < 0 || functionSlugs.has(slug))
8683
8787
  return null;
8684
8788
  functionSlugs.add(slug);
8685
8789
  }
@@ -8828,7 +8932,7 @@ async function activateFunctionVersion(http, args, readOnly = false) {
8828
8932
  slug: functionSlug,
8829
8933
  expectedActiveVersion,
8830
8934
  targetVersion: version
8831
- }, await http.post(endpoint, { expected_active_version: expectedActiveVersion }));
8935
+ }, await http.postReleaseMutation(endpoint, { expected_active_version: expectedActiveVersion }));
8832
8936
  }
8833
8937
  function registerAdvancedTools(server, http, environment = process.env, options = {}) {
8834
8938
  server.tool("edge_functions", `Edge Function management (Deno/Bun serverless). Source deploys are bundled; verified prebuilt artifacts stay byte-exact.
@@ -8920,7 +9024,7 @@ ${deployCheck.err}`;
8920
9024
  break;
8921
9025
  }
8922
9026
  }
8923
- const deploymentResponse = await http.post(edgeFunctionResourcePath(ref, slug), {
9027
+ const deploymentResponse = await http.postReleaseMutation(edgeFunctionResourcePath(ref, slug), {
8924
9028
  code: deployCode.code,
8925
9029
  ...deployCode.prebundled ? { prebundled: true, expected_sha256: deployCode.expectedSha256 } : { minify },
8926
9030
  expected_active_version: expectedActiveVersion,
@@ -8936,7 +9040,7 @@ ${deployCheck.err}`;
8936
9040
  case "deploy_bundle":
8937
9041
  need("slug", slug);
8938
9042
  need("files", files);
8939
- const bundleResponse = await http.post(`${edgeFunctionResourcePath(ref, slug)}/bundle`, {
9043
+ const bundleResponse = await http.postReleaseMutation(`${edgeFunctionResourcePath(ref, slug)}/bundle`, {
8940
9044
  files,
8941
9045
  entrypoint,
8942
9046
  minify,
@@ -11267,7 +11371,7 @@ var SCHEDULE_TOOL_SCHEMA = {
11267
11371
  // package.json
11268
11372
  var package_default = {
11269
11373
  name: "@supacloud/cli",
11270
- version: "0.17.0",
11374
+ version: "0.18.0",
11271
11375
  description: "Project-scoped CLI for SupaCloud users",
11272
11376
  type: "module",
11273
11377
  main: "./dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/cli",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "Project-scoped CLI for SupaCloud users",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -31,7 +31,7 @@ until a project-scoped context is resolved.
31
31
  - `supabase`: allowlisted official CLI adapter for migration authoring, local reset/diff, explicit-DSN inspection/backup/type generation, and SupaCloud-controlled migration push.
32
32
  - `auth`: provider and authentication configuration.
33
33
  - `storage`: buckets and object-management workflows.
34
- - `edge_functions`: list, read immutable source, deploy, activate, and configure Edge Functions. Pass the positive version read from `list` to `source --version` and as `--expected-active-version`; use `absent` only for a new slug. Version `0` is internal-only.
34
+ - `edge_functions`: list, read immutable source, deploy, activate, and configure Edge Functions. Pass the non-negative version read from `list` as `--expected-active-version`; use `absent` only for a new slug. Version `0` is a legacy CAS token and cannot be used as a source or activation target.
35
35
  - `frontend`: list, build/deploy, domain, and deployment workflows.
36
36
  - `secrets`: project secret management; never print values after write.
37
37
  - `queue`, `task_events`, `diagnostics`: asynchronous workload operations and bounded diagnostics.