@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 +11 -7
- package/dist/index.js +136 -32
- package/package.json +1 -1
- package/skills/supacloud-cli/references/command-map.md +1 -1
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
|
|
216
|
-
version from `edge_functions list`; use `
|
|
217
|
-
does not yet exist. A stale value returns
|
|
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
|
-
|
|
233
|
-
|
|
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
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6667
|
-
return
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
8646
|
+
return activeFunctionVersionToken(input);
|
|
8543
8647
|
}
|
|
8544
8648
|
var expectedActiveVersionSchema = Type.Optional(decodedSchema(Type.Union([
|
|
8545
8649
|
Type.Literal("absent"),
|
|
8546
|
-
Type.Integer({ minimum:
|
|
8547
|
-
Type.String({ pattern:
|
|
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:
|
|
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 <
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
@@ -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
|
|
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.
|