@supacloud/cli 0.19.0 → 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.
- package/README.md +23 -12
- package/dist/index.js +536 -82
- package/package.json +1 -1
- package/skills/supacloud-cli/references/command-map.md +1 -1
package/README.md
CHANGED
|
@@ -161,11 +161,13 @@ supacloud-cli frontend list --ref abc123
|
|
|
161
161
|
supacloud-cli branch create --name feature-orders --data_mode schema_only
|
|
162
162
|
supacloud-cli branch promotion_plan --branch_ref preview123
|
|
163
163
|
supacloud-cli branch promote --branch_ref preview123 --plan_checksum <sha256>
|
|
164
|
-
supacloud-cli edge_functions
|
|
165
|
-
supacloud-cli edge_functions deploy --ref abc123 --slug hello --
|
|
166
|
-
supacloud-cli edge_functions
|
|
164
|
+
supacloud-cli edge_functions get_config --ref abc123 --slug hello
|
|
165
|
+
supacloud-cli edge_functions deploy --ref abc123 --slug hello --path ./supabase/functions/hello --expected-active-version absent --expected-activation-id legacy
|
|
166
|
+
supacloud-cli edge_functions deploy --ref abc123 --slug hello --prebundled-path ./dist/hello.js --expected-sha256 <sha256> --expected-active-version 4 --expected-activation-id <uuid>
|
|
167
|
+
supacloud-cli edge_functions deploy_bundle --ref abc123 --slug hello --files '{"index.ts":"export default { fetch: () => new Response(\"ok\") }"}' --expected-active-version 7 --expected-activation-id <uuid>
|
|
167
168
|
supacloud-cli edge_functions source --ref abc123 --slug hello --version 7 --output ./hello-v7.ts
|
|
168
|
-
supacloud-cli edge_functions activate --ref abc123 --slug hello --version 3 --expected-active-version 8
|
|
169
|
+
supacloud-cli edge_functions activate --ref abc123 --slug hello --version 3 --expected-active-version 8 --expected-activation-id <uuid>
|
|
170
|
+
supacloud-cli edge_functions delete --ref abc123 --slug hello --expected-activation-id <uuid>
|
|
169
171
|
supacloud-cli scheduled_functions list --ref abc123
|
|
170
172
|
supacloud-cli secrets upsert --ref abc123 --from-env API_KEY,WEBHOOK_SECRET
|
|
171
173
|
supacloud-cli storage list_buckets --ref abc123
|
|
@@ -221,10 +223,17 @@ pointer; this remains correct across an active-version A→B→A transition.
|
|
|
221
223
|
`--expected-active-version <N|absent>`. Read the current non-negative integer
|
|
222
224
|
version from `edge_functions list`; use `0` for a listed legacy Function and
|
|
223
225
|
`absent` only when creating a slug that does not yet exist. A stale value returns
|
|
224
|
-
HTTP 409 without building, preheating, or activating another version.
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
226
|
+
HTTP 409 without building, preheating, or activating another version. Every
|
|
227
|
+
Function mutation also requires `--expected-activation-id <uuid|legacy>` from
|
|
228
|
+
the same `list` or `get_config` snapshot. Use `get_config` for a single atomic
|
|
229
|
+
read of `active_version`, `activation_id`, and policy, including an `absent`
|
|
230
|
+
tombstone after deletion. Use `legacy` only for a never-created slug or a listed
|
|
231
|
+
legacy Function; recreating a deleted slug must use the tombstone UUID returned
|
|
232
|
+
by `delete` or `get_config`. This second token prevents an A→B→A version cycle
|
|
233
|
+
from satisfying a stale mutation. List output remains a JSON array with string
|
|
234
|
+
`slug`, numeric `version`, and canonical `activation_id` fields, while source
|
|
235
|
+
output is exactly `{ "code": "..." }`. Release automation must use
|
|
236
|
+
`source --version <N>` for a version-bound backup.
|
|
228
237
|
|
|
229
238
|
`edge_functions activate` restores an existing immutable Function version and
|
|
230
239
|
returns a machine-readable receipt containing the activated version and JWT
|
|
@@ -234,10 +243,10 @@ server response body.
|
|
|
234
243
|
Mutation receipts use schema `supacloud.cli.release-control.v1`. An
|
|
235
244
|
`OUTCOME_UNKNOWN` error means the server may have committed the mutation before
|
|
236
245
|
the response was lost or failed validation; read back current state before any
|
|
237
|
-
retry. For Function deploy, bundle deploy,
|
|
238
|
-
separate 5-second, 64 KiB response-body boundary after receiving
|
|
239
|
-
A stalled, oversized, truncated, unreadable, or malformed body is
|
|
240
|
-
`OUTCOME_UNKNOWN` and its content is never included in CLI output.
|
|
246
|
+
retry. For Function deploy, bundle deploy, activation, config, and delete, the
|
|
247
|
+
CLI applies a separate 5-second, 64 KiB response-body boundary after receiving
|
|
248
|
+
HTTP headers. A stalled, oversized, truncated, unreadable, or malformed body is
|
|
249
|
+
always `OUTCOME_UNKNOWN` and its content is never included in CLI output.
|
|
241
250
|
Version `0` is reserved as the active-version CAS token for legacy Functions. It
|
|
242
251
|
can be passed only as `--expected-active-version`; immutable source reads and
|
|
243
252
|
activation targets still require a positive version.
|
|
@@ -250,6 +259,8 @@ activation targets still require a positive version.
|
|
|
250
259
|
"project_ref": "abc123",
|
|
251
260
|
"slug": "hello",
|
|
252
261
|
"previous_active_version": "7",
|
|
262
|
+
"expected_activation_id": "9dc0e8da-207f-4f25-ae74-b1de4e66784d",
|
|
263
|
+
"activation_id": "6417591d-c038-46e8-91ca-4c8080514144",
|
|
253
264
|
"active_version": "8",
|
|
254
265
|
"version": "8",
|
|
255
266
|
"verify_jwt": true
|
package/dist/index.js
CHANGED
|
@@ -6478,7 +6478,7 @@ var ACTION_POLICY = {
|
|
|
6478
6478
|
write: ["create_bucket", "update_bucket", "delete_bucket", "upload_base64", "delete_file"]
|
|
6479
6479
|
},
|
|
6480
6480
|
edge_functions: {
|
|
6481
|
-
read: ["list", "source"],
|
|
6481
|
+
read: ["list", "get_config", "source"],
|
|
6482
6482
|
local: ["check"],
|
|
6483
6483
|
write: ["deploy", "deploy_bundle", "config", "activate", "delete"]
|
|
6484
6484
|
},
|
|
@@ -6572,6 +6572,15 @@ var RELEASE_MUTATION_RESPONSE_TIMEOUT = 5000;
|
|
|
6572
6572
|
var RELEASE_MUTATION_RESPONSE_MAX_BYTES = 64 * 1024;
|
|
6573
6573
|
var MAX_RETRIES = 2;
|
|
6574
6574
|
var RETRY_BASE_DELAY = 500;
|
|
6575
|
+
function validatedGetResponseLimit(options) {
|
|
6576
|
+
const maxBytes = options.maxResponseBytes;
|
|
6577
|
+
if (maxBytes === undefined)
|
|
6578
|
+
return;
|
|
6579
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) {
|
|
6580
|
+
throw new RangeError("HTTP response limit must be a positive safe integer");
|
|
6581
|
+
}
|
|
6582
|
+
return maxBytes;
|
|
6583
|
+
}
|
|
6575
6584
|
function isRetryableMethod(method) {
|
|
6576
6585
|
const normalizedMethod = (method ?? "GET").toUpperCase();
|
|
6577
6586
|
return normalizedMethod === "GET" || normalizedMethod === "HEAD";
|
|
@@ -6773,10 +6782,10 @@ class HttpTransport {
|
|
|
6773
6782
|
"Content-Type": "application/json"
|
|
6774
6783
|
};
|
|
6775
6784
|
}
|
|
6776
|
-
async
|
|
6785
|
+
async mutationWithResponseReader(method, path, serializedBody, responseReader) {
|
|
6777
6786
|
try {
|
|
6778
6787
|
const response = await fetchWithRetry(`${this.baseUrl}${path}`, {
|
|
6779
|
-
method
|
|
6788
|
+
method,
|
|
6780
6789
|
headers: this.headers(),
|
|
6781
6790
|
body: serializedBody
|
|
6782
6791
|
});
|
|
@@ -6787,12 +6796,13 @@ class HttpTransport {
|
|
|
6787
6796
|
}
|
|
6788
6797
|
}
|
|
6789
6798
|
async get(path, options = {}) {
|
|
6799
|
+
const maxResponseBytes = validatedGetResponseLimit(options);
|
|
6790
6800
|
try {
|
|
6791
6801
|
const res = await fetchWithRetry(`${this.baseUrl}${path}`, {
|
|
6792
6802
|
method: "GET",
|
|
6793
6803
|
headers: this.headers()
|
|
6794
6804
|
});
|
|
6795
|
-
const data =
|
|
6805
|
+
const data = maxResponseBytes === undefined ? await res.json().catch(() => null) : await boundedResponseJson(res, maxResponseBytes);
|
|
6796
6806
|
return { ok: res.ok, status: res.status, data };
|
|
6797
6807
|
} catch (error) {
|
|
6798
6808
|
return transportFailure(error);
|
|
@@ -6800,13 +6810,19 @@ class HttpTransport {
|
|
|
6800
6810
|
}
|
|
6801
6811
|
async post(path, body) {
|
|
6802
6812
|
try {
|
|
6803
|
-
return await this.
|
|
6813
|
+
return await this.mutationWithResponseReader("POST", path, serializedRequestBody(body), responseJsonOrNull);
|
|
6804
6814
|
} catch (error) {
|
|
6805
6815
|
return transportFailure(error);
|
|
6806
6816
|
}
|
|
6807
6817
|
}
|
|
6808
6818
|
async postReleaseMutation(path, body) {
|
|
6809
|
-
return this.
|
|
6819
|
+
return this.mutationWithResponseReader("POST", path, serializedRequestBody(body), releaseMutationResponseJson);
|
|
6820
|
+
}
|
|
6821
|
+
async patchReleaseMutation(path, body) {
|
|
6822
|
+
return this.mutationWithResponseReader("PATCH", path, serializedRequestBody(body), releaseMutationResponseJson);
|
|
6823
|
+
}
|
|
6824
|
+
async deleteReleaseMutation(path, body) {
|
|
6825
|
+
return this.mutationWithResponseReader("DELETE", path, serializedRequestBody(body), releaseMutationResponseJson);
|
|
6810
6826
|
}
|
|
6811
6827
|
async postMultipart(path, formData) {
|
|
6812
6828
|
try {
|
|
@@ -6848,11 +6864,12 @@ class HttpTransport {
|
|
|
6848
6864
|
return transportFailure(error);
|
|
6849
6865
|
}
|
|
6850
6866
|
}
|
|
6851
|
-
async delete(path) {
|
|
6867
|
+
async delete(path, body) {
|
|
6852
6868
|
try {
|
|
6853
6869
|
const res = await fetchWithRetry(`${this.baseUrl}${path}`, {
|
|
6854
6870
|
method: "DELETE",
|
|
6855
|
-
headers: this.headers()
|
|
6871
|
+
headers: this.headers(),
|
|
6872
|
+
body: serializedRequestBody(body)
|
|
6856
6873
|
});
|
|
6857
6874
|
const data = await res.json().catch(() => null);
|
|
6858
6875
|
return { ok: res.ok, status: res.status, data };
|
|
@@ -7957,14 +7974,15 @@ ${JSON.stringify(r.data, null, 2)}` : `❌ Failed (${r.status}): ${JSON.stringif
|
|
|
7957
7974
|
var RELEASE_CONTROL_RESPONSE_SCHEMA = "supacloud.cli.release-control.v1";
|
|
7958
7975
|
function releaseControlSuccess(operation, payload) {
|
|
7959
7976
|
return releaseControlResponse({
|
|
7977
|
+
...payload,
|
|
7960
7978
|
schema: RELEASE_CONTROL_RESPONSE_SCHEMA,
|
|
7961
7979
|
ok: true,
|
|
7962
|
-
operation
|
|
7963
|
-
...payload
|
|
7980
|
+
operation
|
|
7964
7981
|
});
|
|
7965
7982
|
}
|
|
7966
|
-
function releaseControlFailure(operation, code, httpStatus) {
|
|
7983
|
+
function releaseControlFailure(operation, code, httpStatus, safeState = {}) {
|
|
7967
7984
|
return releaseControlErrorResponse({
|
|
7985
|
+
...safeState,
|
|
7968
7986
|
schema: RELEASE_CONTROL_RESPONSE_SCHEMA,
|
|
7969
7987
|
ok: false,
|
|
7970
7988
|
operation,
|
|
@@ -8425,6 +8443,155 @@ import { tmpdir } from "node:os";
|
|
|
8425
8443
|
import { basename as basename2, join as join2, resolve as resolve2 } from "node:path";
|
|
8426
8444
|
import { promisify } from "node:util";
|
|
8427
8445
|
import { execFile } from "node:child_process";
|
|
8446
|
+
|
|
8447
|
+
// src/shared/tools/edge-function-response.ts
|
|
8448
|
+
var CANONICAL_VERSION_PATTERN = /^(?:0|[1-9][0-9]*)$/;
|
|
8449
|
+
var SAFE_SLUG_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
|
|
8450
|
+
var ACTIVATION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
8451
|
+
var LEGACY_ACTIVATION_ID = "legacy";
|
|
8452
|
+
var LIST_STRING_FIELDS = [
|
|
8453
|
+
"id",
|
|
8454
|
+
"name",
|
|
8455
|
+
"status",
|
|
8456
|
+
"entrypoint_path",
|
|
8457
|
+
"created_at",
|
|
8458
|
+
"updated_at"
|
|
8459
|
+
];
|
|
8460
|
+
var LIST_BOOLEAN_FIELDS = ["verify_jwt", "import_map"];
|
|
8461
|
+
function objectRecord(candidate) {
|
|
8462
|
+
return candidate !== null && typeof candidate === "object" && !Array.isArray(candidate) ? candidate : null;
|
|
8463
|
+
}
|
|
8464
|
+
function canonicalVersion(candidate) {
|
|
8465
|
+
return typeof candidate === "string" && CANONICAL_VERSION_PATTERN.test(candidate) && Number.isSafeInteger(Number(candidate));
|
|
8466
|
+
}
|
|
8467
|
+
function stringRoutes(candidate) {
|
|
8468
|
+
return Array.isArray(candidate) && candidate.every((route) => typeof route === "string");
|
|
8469
|
+
}
|
|
8470
|
+
function optionalTypedFields(source, fields, expectedType) {
|
|
8471
|
+
return fields.every((field) => source[field] === undefined || typeof source[field] === expectedType);
|
|
8472
|
+
}
|
|
8473
|
+
function validObservedFunctionActivationId(candidate) {
|
|
8474
|
+
return candidate === LEGACY_ACTIVATION_ID || typeof candidate === "string" && ACTIVATION_ID_PATTERN.test(candidate);
|
|
8475
|
+
}
|
|
8476
|
+
function validCommittedFunctionActivationId(candidate) {
|
|
8477
|
+
return typeof candidate === "string" && ACTIVATION_ID_PATTERN.test(candidate);
|
|
8478
|
+
}
|
|
8479
|
+
function projectedFunctionListEntry(candidate) {
|
|
8480
|
+
const functionRecord = objectRecord(candidate);
|
|
8481
|
+
if (!functionRecord || typeof functionRecord.slug !== "string" || !SAFE_SLUG_PATTERN.test(functionRecord.slug) || typeof functionRecord.version !== "number" || !Number.isSafeInteger(functionRecord.version) || functionRecord.version < 0 || !validObservedFunctionActivationId(functionRecord.activation_id) || !optionalTypedFields(functionRecord, LIST_STRING_FIELDS, "string") || !optionalTypedFields(functionRecord, LIST_BOOLEAN_FIELDS, "boolean") || functionRecord.background_routes !== undefined && !stringRoutes(functionRecord.background_routes))
|
|
8482
|
+
return null;
|
|
8483
|
+
const projected = {
|
|
8484
|
+
slug: functionRecord.slug,
|
|
8485
|
+
version: functionRecord.version,
|
|
8486
|
+
activation_id: functionRecord.activation_id
|
|
8487
|
+
};
|
|
8488
|
+
for (const field of [...LIST_STRING_FIELDS, ...LIST_BOOLEAN_FIELDS]) {
|
|
8489
|
+
if (functionRecord[field] !== undefined)
|
|
8490
|
+
projected[field] = functionRecord[field];
|
|
8491
|
+
}
|
|
8492
|
+
if (functionRecord.background_routes !== undefined) {
|
|
8493
|
+
projected.background_routes = functionRecord.background_routes;
|
|
8494
|
+
}
|
|
8495
|
+
return projected;
|
|
8496
|
+
}
|
|
8497
|
+
function projectedFunctionList(payload) {
|
|
8498
|
+
if (!Array.isArray(payload))
|
|
8499
|
+
return null;
|
|
8500
|
+
const slugs = new Set;
|
|
8501
|
+
const projected = [];
|
|
8502
|
+
for (const candidate of payload) {
|
|
8503
|
+
const functionRecord = projectedFunctionListEntry(candidate);
|
|
8504
|
+
if (!functionRecord)
|
|
8505
|
+
return null;
|
|
8506
|
+
const slug = functionRecord.slug;
|
|
8507
|
+
if (typeof slug !== "string" || slugs.has(slug))
|
|
8508
|
+
return null;
|
|
8509
|
+
slugs.add(slug);
|
|
8510
|
+
projected.push(functionRecord);
|
|
8511
|
+
}
|
|
8512
|
+
return projected;
|
|
8513
|
+
}
|
|
8514
|
+
function optionalConfigFields(response) {
|
|
8515
|
+
if (response.version !== undefined && !canonicalVersion(response.version))
|
|
8516
|
+
return null;
|
|
8517
|
+
if (response.import_map !== undefined && typeof response.import_map !== "string")
|
|
8518
|
+
return null;
|
|
8519
|
+
if (response.entrypoint !== undefined && typeof response.entrypoint !== "string")
|
|
8520
|
+
return null;
|
|
8521
|
+
return {
|
|
8522
|
+
...response.version === undefined ? {} : { version: response.version },
|
|
8523
|
+
...response.import_map === undefined ? {} : { import_map: response.import_map },
|
|
8524
|
+
...response.entrypoint === undefined ? {} : { entrypoint: response.entrypoint }
|
|
8525
|
+
};
|
|
8526
|
+
}
|
|
8527
|
+
function coherentFunctionVersion(activeVersion, version) {
|
|
8528
|
+
if (activeVersion === "absent")
|
|
8529
|
+
return version === undefined;
|
|
8530
|
+
if (activeVersion === "0")
|
|
8531
|
+
return version === undefined || version === "0";
|
|
8532
|
+
return version === activeVersion;
|
|
8533
|
+
}
|
|
8534
|
+
function projectedFunctionIdentity(payload, expectedProjectRef, expectedSlug) {
|
|
8535
|
+
const response = objectRecord(payload);
|
|
8536
|
+
if (!response || response.project_ref !== expectedProjectRef || response.slug !== expectedSlug || response.active_version !== "absent" && !canonicalVersion(response.active_version) || !validObservedFunctionActivationId(response.activation_id) || typeof response.verify_jwt !== "boolean" || !stringRoutes(response.background_routes))
|
|
8537
|
+
return null;
|
|
8538
|
+
const optionalFields = optionalConfigFields(response);
|
|
8539
|
+
if (!optionalFields || !coherentFunctionVersion(response.active_version, optionalFields.version))
|
|
8540
|
+
return null;
|
|
8541
|
+
return {
|
|
8542
|
+
project_ref: expectedProjectRef,
|
|
8543
|
+
slug: expectedSlug,
|
|
8544
|
+
active_version: response.active_version,
|
|
8545
|
+
verify_jwt: response.verify_jwt,
|
|
8546
|
+
background_routes: response.background_routes,
|
|
8547
|
+
...optionalFields,
|
|
8548
|
+
activation_id: response.activation_id
|
|
8549
|
+
};
|
|
8550
|
+
}
|
|
8551
|
+
function mutationIdentityMatches(response, expectation) {
|
|
8552
|
+
return response.success === true && response.project_ref === expectation.projectRef && response.slug === expectation.slug && response.expected_activation_id === expectation.expectedActivationId && validCommittedFunctionActivationId(response.activation_id) && response.activation_id !== expectation.expectedActivationId;
|
|
8553
|
+
}
|
|
8554
|
+
function configMatchesExpectation(response, expected) {
|
|
8555
|
+
if (typeof response.verify_jwt !== "boolean" || !stringRoutes(response.background_routes)) {
|
|
8556
|
+
return false;
|
|
8557
|
+
}
|
|
8558
|
+
if (expected.verify_jwt !== undefined && response.verify_jwt !== expected.verify_jwt)
|
|
8559
|
+
return false;
|
|
8560
|
+
return expected.background_routes === undefined || JSON.stringify(response.background_routes) === JSON.stringify(expected.background_routes);
|
|
8561
|
+
}
|
|
8562
|
+
function confirmedFunctionConfigMutation(payload, expectation) {
|
|
8563
|
+
const response = objectRecord(payload);
|
|
8564
|
+
if (!response || !mutationIdentityMatches(response, expectation) || !configMatchesExpectation(response, expectation.config))
|
|
8565
|
+
return null;
|
|
8566
|
+
const optionalFields = optionalConfigFields(response);
|
|
8567
|
+
if (!optionalFields)
|
|
8568
|
+
return null;
|
|
8569
|
+
return {
|
|
8570
|
+
project_ref: expectation.projectRef,
|
|
8571
|
+
slug: expectation.slug,
|
|
8572
|
+
expected_activation_id: expectation.expectedActivationId,
|
|
8573
|
+
activation_id: response.activation_id,
|
|
8574
|
+
verify_jwt: response.verify_jwt,
|
|
8575
|
+
background_routes: response.background_routes,
|
|
8576
|
+
...optionalFields
|
|
8577
|
+
};
|
|
8578
|
+
}
|
|
8579
|
+
function confirmedFunctionDeletion(payload, expectation) {
|
|
8580
|
+
const response = objectRecord(payload);
|
|
8581
|
+
const config = objectRecord(response?.config);
|
|
8582
|
+
if (!response || !config || !mutationIdentityMatches(response, expectation) || response.previous_active_version !== "absent" && !canonicalVersion(response.previous_active_version) || response.active_version !== "absent" || config.version !== undefined || config.activation_id !== response.activation_id || typeof config.verify_jwt !== "boolean")
|
|
8583
|
+
return null;
|
|
8584
|
+
return {
|
|
8585
|
+
project_ref: expectation.projectRef,
|
|
8586
|
+
slug: expectation.slug,
|
|
8587
|
+
expected_activation_id: expectation.expectedActivationId,
|
|
8588
|
+
activation_id: response.activation_id,
|
|
8589
|
+
previous_active_version: response.previous_active_version,
|
|
8590
|
+
active_version: "absent"
|
|
8591
|
+
};
|
|
8592
|
+
}
|
|
8593
|
+
|
|
8594
|
+
// src/shared/tools/advanced-tools.ts
|
|
8428
8595
|
var execFileAsync = promisify(execFile);
|
|
8429
8596
|
var SHA256_HEX_PATTERN = /^[a-f0-9]{64}$/;
|
|
8430
8597
|
function openFileIdentity(descriptor) {
|
|
@@ -8622,12 +8789,22 @@ function activeFunctionVersionToken(input) {
|
|
|
8622
8789
|
var CANONICAL_FUNCTION_VERSION_PATTERN = /^(?:0|[1-9][0-9]*)$/;
|
|
8623
8790
|
var POSITIVE_FUNCTION_VERSION_PATTERN = /^[1-9][0-9]*$/;
|
|
8624
8791
|
var SAFE_FUNCTION_SLUG_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
|
|
8792
|
+
var FUNCTION_ACTIVATION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
8793
|
+
var LEGACY_FUNCTION_ACTIVATION_ID = "legacy";
|
|
8625
8794
|
var FUNCTION_ACTIVATION_ARGUMENTS = new Set([
|
|
8626
8795
|
"action",
|
|
8627
8796
|
"ref",
|
|
8628
8797
|
"slug",
|
|
8629
8798
|
"version",
|
|
8630
|
-
"expected-active-version"
|
|
8799
|
+
"expected-active-version",
|
|
8800
|
+
"expected-activation-id"
|
|
8801
|
+
]);
|
|
8802
|
+
var FUNCTION_IDENTITY_MUTATIONS = new Set([
|
|
8803
|
+
"deploy",
|
|
8804
|
+
"deploy_bundle",
|
|
8805
|
+
"config",
|
|
8806
|
+
"activate",
|
|
8807
|
+
"delete"
|
|
8631
8808
|
]);
|
|
8632
8809
|
var functionVersionSchema = Type.Optional(decodedSchema(Type.Union([
|
|
8633
8810
|
Type.Integer({ minimum: 1, maximum: Number.MAX_SAFE_INTEGER }),
|
|
@@ -8646,6 +8823,10 @@ var expectedActiveVersionSchema = Type.Optional(decodedSchema(Type.Union([
|
|
|
8646
8823
|
Type.Literal("absent"),
|
|
8647
8824
|
Type.String({ pattern: CANONICAL_FUNCTION_VERSION_PATTERN.source, maxLength: 16 })
|
|
8648
8825
|
]), parseExpectedActiveVersion));
|
|
8826
|
+
var expectedActivationIdSchema = Type.Optional(Type.Union([
|
|
8827
|
+
Type.Literal(LEGACY_FUNCTION_ACTIVATION_ID),
|
|
8828
|
+
Type.String({ pattern: FUNCTION_ACTIVATION_ID_PATTERN.source, minLength: 36, maxLength: 36 })
|
|
8829
|
+
]));
|
|
8649
8830
|
var secretListSchema = Type.Array(Type.Object({ name: Type.String(), value: Type.String() }));
|
|
8650
8831
|
var ENVIRONMENT_SECRET_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]{0,255}$/;
|
|
8651
8832
|
var MAX_SECRET_COUNT = 1024;
|
|
@@ -8768,28 +8949,14 @@ var INVALID_FUNCTION_SOURCE_RESPONSE = "❌ Edge Function source response is inv
|
|
|
8768
8949
|
function invalidFunctionReadResponse(message) {
|
|
8769
8950
|
return { isError: true, content: [{ type: "text", text: message }] };
|
|
8770
8951
|
}
|
|
8771
|
-
function safeFunctionList(payload) {
|
|
8772
|
-
if (!Array.isArray(payload))
|
|
8773
|
-
return null;
|
|
8774
|
-
const functionSlugs = new Set;
|
|
8775
|
-
for (const candidate of payload) {
|
|
8776
|
-
const edgeFunction = objectRecord(candidate);
|
|
8777
|
-
const slug = edgeFunction?.slug;
|
|
8778
|
-
const version = edgeFunction?.version;
|
|
8779
|
-
if (typeof slug !== "string" || !SAFE_FUNCTION_SLUG_PATTERN.test(slug) || typeof version !== "number" || !Number.isSafeInteger(version) || version < 0 || functionSlugs.has(slug))
|
|
8780
|
-
return null;
|
|
8781
|
-
functionSlugs.add(slug);
|
|
8782
|
-
}
|
|
8783
|
-
return payload;
|
|
8784
|
-
}
|
|
8785
8952
|
function functionListResponse(response) {
|
|
8786
8953
|
if (!response.ok)
|
|
8787
8954
|
return invalidFunctionReadResponse(`❌ Failed (${response.status})`);
|
|
8788
|
-
const functions =
|
|
8955
|
+
const functions = projectedFunctionList(response.data);
|
|
8789
8956
|
return functions ? { content: [{ type: "text", text: JSON.stringify(functions, null, 2) }] } : invalidFunctionReadResponse(INVALID_FUNCTION_LIST_RESPONSE);
|
|
8790
8957
|
}
|
|
8791
8958
|
function confirmedFunctionConfig(payload, expected) {
|
|
8792
|
-
const response =
|
|
8959
|
+
const response = objectRecord2(payload);
|
|
8793
8960
|
if (!response)
|
|
8794
8961
|
return false;
|
|
8795
8962
|
if (expected.verify_jwt !== undefined && response.verify_jwt !== expected.verify_jwt)
|
|
@@ -8824,7 +8991,7 @@ function functionSourceOutput(slug, sourceCode, output) {
|
|
|
8824
8991
|
}]
|
|
8825
8992
|
};
|
|
8826
8993
|
}
|
|
8827
|
-
function
|
|
8994
|
+
function objectRecord2(candidate) {
|
|
8828
8995
|
return candidate && typeof candidate === "object" && !Array.isArray(candidate) ? candidate : null;
|
|
8829
8996
|
}
|
|
8830
8997
|
function edgeFunctionResourcePath(ref, slug) {
|
|
@@ -8846,8 +9013,31 @@ async function readFunctionSource(http, request) {
|
|
|
8846
9013
|
const sourceCode = functionSourceCode(response.data, sourceVersion === undefined ? "code" : "source_code");
|
|
8847
9014
|
return sourceCode === null ? invalidFunctionReadResponse(INVALID_FUNCTION_SOURCE_RESPONSE) : functionSourceOutput(request.slug, sourceCode, request.output);
|
|
8848
9015
|
}
|
|
8849
|
-
function
|
|
8850
|
-
return receipt.success === true && receipt.project_ref === expectation.projectRef && receipt.slug === expectation.slug && receipt.previous_active_version === expectation.expectedActiveVersion;
|
|
9016
|
+
function mutationIdentityMatches2(receipt, expectation) {
|
|
9017
|
+
return receipt.success === true && receipt.project_ref === expectation.projectRef && receipt.slug === expectation.slug && receipt.previous_active_version === expectation.expectedActiveVersion && receipt.expected_activation_id === expectation.expectedActivationId;
|
|
9018
|
+
}
|
|
9019
|
+
async function readFunctionIdentity(http, projectRef, slug) {
|
|
9020
|
+
const resourcePath = edgeFunctionResourcePath(projectRef, slug);
|
|
9021
|
+
const response = await http.get(`${resourcePath}/config`);
|
|
9022
|
+
if (!response.ok) {
|
|
9023
|
+
return releaseControlFailure("edge_functions.get_config", "HTTP_ERROR", response.status);
|
|
9024
|
+
}
|
|
9025
|
+
const identity = projectedFunctionIdentity(response.data, projectRef, slug);
|
|
9026
|
+
return identity ? { content: [{ type: "text", text: JSON.stringify(identity, null, 2) }] } : releaseControlFailure("edge_functions.get_config", "INVALID_RESPONSE", response.status);
|
|
9027
|
+
}
|
|
9028
|
+
async function updateFunctionConfiguration(http, request) {
|
|
9029
|
+
const response = await http.patchReleaseMutation(`${edgeFunctionResourcePath(request.projectRef, request.slug)}/config`, { ...request.config, expected_activation_id: request.expectedActivationId });
|
|
9030
|
+
if (!response.ok)
|
|
9031
|
+
return releaseControlMutationFailure("edge_functions.config", response);
|
|
9032
|
+
const confirmed = confirmedFunctionConfigMutation(response.data, request);
|
|
9033
|
+
return confirmed ? releaseControlSuccess("edge_functions.config", confirmed) : releaseControlFailure("edge_functions.config", "OUTCOME_UNKNOWN", response.status);
|
|
9034
|
+
}
|
|
9035
|
+
async function deleteFunction(http, request) {
|
|
9036
|
+
const response = await http.deleteReleaseMutation(edgeFunctionResourcePath(request.projectRef, request.slug), { expected_activation_id: request.expectedActivationId });
|
|
9037
|
+
if (!response.ok)
|
|
9038
|
+
return releaseControlMutationFailure("edge_functions.delete", response);
|
|
9039
|
+
const confirmed = confirmedFunctionDeletion(response.data, request);
|
|
9040
|
+
return confirmed ? releaseControlSuccess("edge_functions.delete", confirmed) : releaseControlFailure("edge_functions.delete", "OUTCOME_UNKNOWN", response.status);
|
|
8851
9041
|
}
|
|
8852
9042
|
function validReceiptVersion(activeVersion) {
|
|
8853
9043
|
return typeof activeVersion === "string" && POSITIVE_FUNCTION_VERSION_PATTERN.test(activeVersion) && Number.isSafeInteger(Number(activeVersion));
|
|
@@ -8860,14 +9050,15 @@ function confirmedMutationVersion(receipt, config, expectation) {
|
|
|
8860
9050
|
return activeVersion;
|
|
8861
9051
|
}
|
|
8862
9052
|
function confirmedFunctionMutation(expectation, payload) {
|
|
8863
|
-
const receipt =
|
|
8864
|
-
const config =
|
|
8865
|
-
if (!receipt || !config || !
|
|
9053
|
+
const receipt = objectRecord2(payload);
|
|
9054
|
+
const config = objectRecord2(receipt?.config);
|
|
9055
|
+
if (!receipt || !config || !mutationIdentityMatches2(receipt, expectation))
|
|
8866
9056
|
return null;
|
|
8867
9057
|
const activeVersion = confirmedMutationVersion(receipt, config, expectation);
|
|
8868
|
-
|
|
9058
|
+
const activationId = receipt.activation_id;
|
|
9059
|
+
if (activeVersion === null || !validCommittedFunctionActivationId(activationId) || config.activation_id !== activationId || typeof config.verify_jwt !== "boolean" || !confirmedFunctionConfig(config, expectation.config ?? {}))
|
|
8869
9060
|
return null;
|
|
8870
|
-
return { activeVersion, verifyJwt: config.verify_jwt };
|
|
9061
|
+
return { activeVersion, activationId, verifyJwt: config.verify_jwt };
|
|
8871
9062
|
}
|
|
8872
9063
|
function functionMutationResponse(expectation, response) {
|
|
8873
9064
|
if (!response.ok)
|
|
@@ -8880,6 +9071,8 @@ function functionMutationResponse(expectation, response) {
|
|
|
8880
9071
|
project_ref: expectation.projectRef,
|
|
8881
9072
|
slug: expectation.slug,
|
|
8882
9073
|
previous_active_version: expectation.expectedActiveVersion,
|
|
9074
|
+
expected_activation_id: expectation.expectedActivationId,
|
|
9075
|
+
activation_id: confirmed.activationId,
|
|
8883
9076
|
active_version: confirmed.activeVersion,
|
|
8884
9077
|
version: confirmed.activeVersion,
|
|
8885
9078
|
verify_jwt: confirmed.verifyJwt
|
|
@@ -8899,7 +9092,8 @@ function functionActivationTarget(args) {
|
|
|
8899
9092
|
if (!SAFE_FUNCTION_SLUG_PATTERN.test(functionSlug))
|
|
8900
9093
|
throw new Error("'slug' is invalid for 'activate'");
|
|
8901
9094
|
const expectedActiveVersion = requiredExpectedActiveVersion(args, "activate");
|
|
8902
|
-
|
|
9095
|
+
const expectedActivationId = requiredExpectedActivationId(args, "activate");
|
|
9096
|
+
return { projectRef, functionSlug, version, expectedActiveVersion, expectedActivationId };
|
|
8903
9097
|
}
|
|
8904
9098
|
function requiredExpectedActiveVersion(args, action) {
|
|
8905
9099
|
const expected = args["expected-active-version"];
|
|
@@ -8911,28 +9105,42 @@ function requiredExpectedActiveVersion(args, action) {
|
|
|
8911
9105
|
throw new Error("Expected active version is invalid");
|
|
8912
9106
|
return parsed;
|
|
8913
9107
|
}
|
|
9108
|
+
function requiredExpectedActivationId(args, action) {
|
|
9109
|
+
const expected = args["expected-activation-id"];
|
|
9110
|
+
if (expected === undefined) {
|
|
9111
|
+
throw new Error(`'--expected-activation-id' required for '${action}'`);
|
|
9112
|
+
}
|
|
9113
|
+
if (!validObservedFunctionActivationId(expected)) {
|
|
9114
|
+
throw new Error("Expected activation ID must be a canonical UUID or 'legacy'");
|
|
9115
|
+
}
|
|
9116
|
+
return expected;
|
|
9117
|
+
}
|
|
8914
9118
|
async function activateFunctionVersion(http, args, readOnly = false) {
|
|
8915
9119
|
if (readOnly)
|
|
8916
9120
|
return readOnlyActivationResult();
|
|
8917
9121
|
const unsupported = Object.keys(args).filter((name) => !FUNCTION_ACTIVATION_ARGUMENTS.has(name));
|
|
8918
9122
|
if (unsupported.length > 0)
|
|
8919
9123
|
throw new Error(`'${unsupported[0]}' is not supported for 'activate'`);
|
|
8920
|
-
const { projectRef, functionSlug, version, expectedActiveVersion } = functionActivationTarget(args);
|
|
9124
|
+
const { projectRef, functionSlug, version, expectedActiveVersion, expectedActivationId } = functionActivationTarget(args);
|
|
8921
9125
|
const endpoint = edgeFunctionResourcePath(projectRef, functionSlug) + `/versions/${encodeURIComponent(version)}/activate`;
|
|
8922
9126
|
return functionMutationResponse({
|
|
8923
9127
|
operation: "edge_functions.activate",
|
|
8924
9128
|
projectRef,
|
|
8925
9129
|
slug: functionSlug,
|
|
8926
9130
|
expectedActiveVersion,
|
|
9131
|
+
expectedActivationId,
|
|
8927
9132
|
targetVersion: version
|
|
8928
|
-
}, await http.postReleaseMutation(endpoint, {
|
|
9133
|
+
}, await http.postReleaseMutation(endpoint, {
|
|
9134
|
+
expected_active_version: expectedActiveVersion,
|
|
9135
|
+
expected_activation_id: expectedActivationId
|
|
9136
|
+
}));
|
|
8929
9137
|
}
|
|
8930
9138
|
function registerAdvancedTools(server, http, environment = process.env, options = {}) {
|
|
8931
9139
|
server.tool("edge_functions", `Edge Function management (Deno/Bun serverless). Source deploys are bundled; verified prebuilt artifacts stay byte-exact.
|
|
8932
|
-
Actions: list, deploy, deploy_bundle, config, source, activate, delete, check`, {
|
|
8933
|
-
action: withDescription(stringEnum(["list", "deploy", "deploy_bundle", "config", "source", "activate", "delete", "check"]), "Action"),
|
|
9140
|
+
Actions: list, get_config, deploy, deploy_bundle, config, source, activate, delete, check`, {
|
|
9141
|
+
action: withDescription(stringEnum(["list", "get_config", "deploy", "deploy_bundle", "config", "source", "activate", "delete", "check"]), "Action"),
|
|
8934
9142
|
ref: withDescription(Type.String(), "Project ref"),
|
|
8935
|
-
slug: optional(Type.String(), "[deploy/deploy_bundle/config/source/activate/delete/check] Function name"),
|
|
9143
|
+
slug: optional(Type.String(), "[get_config/deploy/deploy_bundle/config/source/activate/delete/check] Function name"),
|
|
8936
9144
|
version: withDescription(functionVersionSchema, "[source/activate] Existing immutable Function version; source requires a positive version"),
|
|
8937
9145
|
code: optional(Type.String(), "[deploy/check] Function source code (TypeScript)"),
|
|
8938
9146
|
path: optional(Type.String(), "[deploy/check] Local file path to read code from (alternative to code)"),
|
|
@@ -8944,13 +9152,15 @@ Actions: list, deploy, deploy_bundle, config, source, activate, delete, check`,
|
|
|
8944
9152
|
minify: optional(Type.Boolean(), "[deploy/deploy_bundle] Minify bundle"),
|
|
8945
9153
|
verify_jwt: optional(Type.Boolean(), "[deploy/deploy_bundle/config] Set JWT verification for this function"),
|
|
8946
9154
|
background_routes: withDescription(backgroundRoutesSchema, "[deploy/deploy_bundle/config] Background route paths; pass comma-separated or JSON array in CLI"),
|
|
8947
|
-
"expected-active-version": withDescription(expectedActiveVersionSchema, "[deploy/deploy_bundle/activate] Required current active version, or 'absent' when none exists")
|
|
9155
|
+
"expected-active-version": withDescription(expectedActiveVersionSchema, "[deploy/deploy_bundle/activate] Required current active version, or 'absent' when none exists"),
|
|
9156
|
+
"expected-activation-id": withDescription(expectedActivationIdSchema, "[deploy/deploy_bundle/config/activate/delete] Required activation ID from list, or 'legacy' for a new or legacy function")
|
|
8948
9157
|
}, async (args) => {
|
|
8949
9158
|
if (args.action === "activate")
|
|
8950
9159
|
return activateFunctionVersion(http, args, options.readOnly);
|
|
8951
9160
|
const { action, ref, slug, path: pathArg, output, files, entrypoint, minify, verify_jwt, background_routes } = args;
|
|
8952
9161
|
rejectPrebundledFlagsOutsideDeploy(action, args);
|
|
8953
9162
|
const expectedActiveVersion = action === "deploy" || action === "deploy_bundle" ? requiredExpectedActiveVersion(args, action) : undefined;
|
|
9163
|
+
const expectedActivationId = FUNCTION_IDENTITY_MUTATIONS.has(action) ? requiredExpectedActivationId(args, action) : undefined;
|
|
8954
9164
|
let code = args.code;
|
|
8955
9165
|
const need = (f, v) => {
|
|
8956
9166
|
if (!v)
|
|
@@ -8962,15 +9172,6 @@ Actions: list, deploy, deploy_bundle, config, source, activate, delete, check`,
|
|
|
8962
9172
|
...Array.isArray(background_routes) ? { background_routes } : {}
|
|
8963
9173
|
});
|
|
8964
9174
|
const hasFunctionConfig = () => Object.keys(functionConfig()).length > 0;
|
|
8965
|
-
const updateFunctionConfig = async () => {
|
|
8966
|
-
need("slug", slug);
|
|
8967
|
-
if (!hasFunctionConfig()) {
|
|
8968
|
-
throw new Error("'verify_jwt' or 'background_routes' required for 'config'");
|
|
8969
|
-
}
|
|
8970
|
-
const cr = await http.patch(`${edgeFunctionResourcePath(ref, slug)}/config`, functionConfig());
|
|
8971
|
-
return cr.ok ? `✅ Function ${slug} config updated
|
|
8972
|
-
${JSON.stringify(cr.data, null, 2)}` : `❌ Config update failed (${cr.status}): ${JSON.stringify(cr.data)}`;
|
|
8973
|
-
};
|
|
8974
9175
|
const checkSyntax = async (sourceCode) => {
|
|
8975
9176
|
const tmpDir = mkdtempSync(join2(tmpdir(), "supacloud-edge-check-"));
|
|
8976
9177
|
const tmpFile = join2(tmpDir, "index.ts");
|
|
@@ -8996,6 +9197,9 @@ ${e.stderr || e.message}` };
|
|
|
8996
9197
|
switch (action) {
|
|
8997
9198
|
case "list":
|
|
8998
9199
|
return functionListResponse(await http.get(edgeFunctionResourcePath(ref)));
|
|
9200
|
+
case "get_config":
|
|
9201
|
+
need("slug", slug);
|
|
9202
|
+
return readFunctionIdentity(http, ref, slug);
|
|
8999
9203
|
case "check":
|
|
9000
9204
|
need("code (or path)", code);
|
|
9001
9205
|
const checkRes = await checkSyntax(code);
|
|
@@ -9021,6 +9225,7 @@ ${deployCheck.err}`;
|
|
|
9021
9225
|
code: deployCode.code,
|
|
9022
9226
|
...deployCode.prebundled ? { prebundled: true, expected_sha256: deployCode.expectedSha256 } : { minify },
|
|
9023
9227
|
expected_active_version: expectedActiveVersion,
|
|
9228
|
+
expected_activation_id: expectedActivationId,
|
|
9024
9229
|
...functionConfig()
|
|
9025
9230
|
});
|
|
9026
9231
|
return functionMutationResponse({
|
|
@@ -9028,6 +9233,7 @@ ${deployCheck.err}`;
|
|
|
9028
9233
|
projectRef: ref,
|
|
9029
9234
|
slug,
|
|
9030
9235
|
expectedActiveVersion,
|
|
9236
|
+
expectedActivationId,
|
|
9031
9237
|
config: functionConfig()
|
|
9032
9238
|
}, deploymentResponse);
|
|
9033
9239
|
case "deploy_bundle":
|
|
@@ -9038,6 +9244,7 @@ ${deployCheck.err}`;
|
|
|
9038
9244
|
entrypoint,
|
|
9039
9245
|
minify,
|
|
9040
9246
|
expected_active_version: expectedActiveVersion,
|
|
9247
|
+
expected_activation_id: expectedActivationId,
|
|
9041
9248
|
...functionConfig()
|
|
9042
9249
|
});
|
|
9043
9250
|
return functionMutationResponse({
|
|
@@ -9045,11 +9252,20 @@ ${deployCheck.err}`;
|
|
|
9045
9252
|
projectRef: ref,
|
|
9046
9253
|
slug,
|
|
9047
9254
|
expectedActiveVersion,
|
|
9255
|
+
expectedActivationId,
|
|
9048
9256
|
config: functionConfig()
|
|
9049
9257
|
}, bundleResponse);
|
|
9050
9258
|
case "config":
|
|
9051
|
-
|
|
9052
|
-
|
|
9259
|
+
need("slug", slug);
|
|
9260
|
+
if (!hasFunctionConfig()) {
|
|
9261
|
+
throw new Error("'verify_jwt' or 'background_routes' required for 'config'");
|
|
9262
|
+
}
|
|
9263
|
+
return updateFunctionConfiguration(http, {
|
|
9264
|
+
projectRef: ref,
|
|
9265
|
+
slug,
|
|
9266
|
+
expectedActivationId,
|
|
9267
|
+
config: functionConfig()
|
|
9268
|
+
});
|
|
9053
9269
|
case "source":
|
|
9054
9270
|
need("slug", slug);
|
|
9055
9271
|
return readFunctionSource(http, {
|
|
@@ -9060,8 +9276,11 @@ ${deployCheck.err}`;
|
|
|
9060
9276
|
});
|
|
9061
9277
|
case "delete":
|
|
9062
9278
|
need("slug", slug);
|
|
9063
|
-
|
|
9064
|
-
|
|
9279
|
+
return deleteFunction(http, {
|
|
9280
|
+
projectRef: ref,
|
|
9281
|
+
slug,
|
|
9282
|
+
expectedActivationId
|
|
9283
|
+
});
|
|
9065
9284
|
default:
|
|
9066
9285
|
text = `❌ Unknown action`;
|
|
9067
9286
|
}
|
|
@@ -9395,7 +9614,210 @@ Actions: list, get, create, update, delete, deploy_git, deploy_upload, redeploy,
|
|
|
9395
9614
|
});
|
|
9396
9615
|
}
|
|
9397
9616
|
|
|
9617
|
+
// src/shared/tools/project-read-projection.ts
|
|
9618
|
+
var PROJECT_READ_RESPONSE_MAX_BYTES = 1048576;
|
|
9619
|
+
var PROJECT_REF_PATTERN3 = /^[a-z0-9-]{1,20}$/;
|
|
9620
|
+
var SAFE_IDENTIFIER_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
|
|
9621
|
+
var REGION_PATTERN = /^[A-Za-z0-9._-]{1,64}$/;
|
|
9622
|
+
var STATUS_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
|
|
9623
|
+
var DNS_LABEL_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?$/;
|
|
9624
|
+
var DATABASE_VERSION_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$/;
|
|
9625
|
+
var PROJECT_SUMMARY_KEYS = new Set([
|
|
9626
|
+
"id",
|
|
9627
|
+
"ref",
|
|
9628
|
+
"organization_id",
|
|
9629
|
+
"organization_slug",
|
|
9630
|
+
"name",
|
|
9631
|
+
"region",
|
|
9632
|
+
"created_at",
|
|
9633
|
+
"status"
|
|
9634
|
+
]);
|
|
9635
|
+
var PROJECT_DETAILS_KEYS = new Set([
|
|
9636
|
+
...PROJECT_SUMMARY_KEYS,
|
|
9637
|
+
"database",
|
|
9638
|
+
"api",
|
|
9639
|
+
"studio",
|
|
9640
|
+
"config",
|
|
9641
|
+
"anon_key",
|
|
9642
|
+
"services"
|
|
9643
|
+
]);
|
|
9644
|
+
var PROJECT_DATABASE_KEYS = new Set([
|
|
9645
|
+
"host",
|
|
9646
|
+
"version",
|
|
9647
|
+
"postgres_engine",
|
|
9648
|
+
"release_channel"
|
|
9649
|
+
]);
|
|
9650
|
+
var PROJECT_ENDPOINT_KEYS = new Set(["url"]);
|
|
9651
|
+
function plainRecord(candidate) {
|
|
9652
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate))
|
|
9653
|
+
return null;
|
|
9654
|
+
const prototype = Object.getPrototypeOf(candidate);
|
|
9655
|
+
return prototype === Object.prototype || prototype === null ? candidate : null;
|
|
9656
|
+
}
|
|
9657
|
+
function hasOnlyKeys(record, allowedKeys) {
|
|
9658
|
+
return Object.keys(record).every((key) => allowedKeys.has(key));
|
|
9659
|
+
}
|
|
9660
|
+
function hasWellFormedUnicode(text) {
|
|
9661
|
+
for (let index = 0;index < text.length; index++) {
|
|
9662
|
+
const codeUnit = text.charCodeAt(index);
|
|
9663
|
+
if (codeUnit >= 55296 && codeUnit <= 56319) {
|
|
9664
|
+
if (index + 1 >= text.length)
|
|
9665
|
+
return false;
|
|
9666
|
+
const lowSurrogate = text.charCodeAt(index + 1);
|
|
9667
|
+
if (lowSurrogate < 56320 || lowSurrogate > 57343)
|
|
9668
|
+
return false;
|
|
9669
|
+
index++;
|
|
9670
|
+
} else if (codeUnit >= 56320 && codeUnit <= 57343) {
|
|
9671
|
+
return false;
|
|
9672
|
+
}
|
|
9673
|
+
}
|
|
9674
|
+
return true;
|
|
9675
|
+
}
|
|
9676
|
+
function boundedText(candidate, maxLength) {
|
|
9677
|
+
return typeof candidate === "string" && candidate.length > 0 && candidate.length <= maxLength && !/[\u0000-\u001f\u007f]/u.test(candidate) && hasWellFormedUnicode(candidate) ? candidate : null;
|
|
9678
|
+
}
|
|
9679
|
+
function matchingText(candidate, maxLength, pattern) {
|
|
9680
|
+
const candidateText = boundedText(candidate, maxLength);
|
|
9681
|
+
return candidateText && pattern.test(candidateText) ? candidateText : null;
|
|
9682
|
+
}
|
|
9683
|
+
function canonicalTimestamp(candidate) {
|
|
9684
|
+
const timestamp = boundedText(candidate, 64);
|
|
9685
|
+
if (!timestamp)
|
|
9686
|
+
return null;
|
|
9687
|
+
const milliseconds = Date.parse(timestamp);
|
|
9688
|
+
return Number.isFinite(milliseconds) && new Date(milliseconds).toISOString() === timestamp ? timestamp : null;
|
|
9689
|
+
}
|
|
9690
|
+
function projectedSummary(project) {
|
|
9691
|
+
const summary = {
|
|
9692
|
+
id: matchingText(project.id, 128, SAFE_IDENTIFIER_PATTERN),
|
|
9693
|
+
ref: matchingText(project.ref, 20, PROJECT_REF_PATTERN3),
|
|
9694
|
+
organization_id: matchingText(project.organization_id, 128, SAFE_IDENTIFIER_PATTERN),
|
|
9695
|
+
organization_slug: matchingText(project.organization_slug, 128, SAFE_IDENTIFIER_PATTERN),
|
|
9696
|
+
name: boundedText(project.name, 100),
|
|
9697
|
+
region: matchingText(project.region, 64, REGION_PATTERN),
|
|
9698
|
+
created_at: canonicalTimestamp(project.created_at),
|
|
9699
|
+
status: matchingText(project.status, 64, STATUS_PATTERN)
|
|
9700
|
+
};
|
|
9701
|
+
return Object.values(summary).every((field) => field !== null) ? summary : null;
|
|
9702
|
+
}
|
|
9703
|
+
function databaseHost(candidate) {
|
|
9704
|
+
const host = boundedText(candidate, 255);
|
|
9705
|
+
if (!host)
|
|
9706
|
+
return null;
|
|
9707
|
+
if (host.startsWith("[") && host.endsWith("]")) {
|
|
9708
|
+
try {
|
|
9709
|
+
const parsedHost = new URL(`http://${host}`);
|
|
9710
|
+
return parsedHost.host === host ? host : null;
|
|
9711
|
+
} catch (error) {
|
|
9712
|
+
if (error instanceof TypeError)
|
|
9713
|
+
return null;
|
|
9714
|
+
throw error;
|
|
9715
|
+
}
|
|
9716
|
+
}
|
|
9717
|
+
const ipv4Parts = host.split(".");
|
|
9718
|
+
if (ipv4Parts.length === 4 && ipv4Parts.every((part) => /^\d{1,3}$/u.test(part))) {
|
|
9719
|
+
return ipv4Parts.every((part) => Number(part) <= 255) ? host : null;
|
|
9720
|
+
}
|
|
9721
|
+
return ipv4Parts.every((label) => DNS_LABEL_PATTERN.test(label)) ? host : null;
|
|
9722
|
+
}
|
|
9723
|
+
function projectDatabase(candidate) {
|
|
9724
|
+
const database = plainRecord(candidate);
|
|
9725
|
+
if (!database || !hasOnlyKeys(database, PROJECT_DATABASE_KEYS))
|
|
9726
|
+
return null;
|
|
9727
|
+
const host = databaseHost(database.host);
|
|
9728
|
+
const version = matchingText(database.version, 64, DATABASE_VERSION_PATTERN);
|
|
9729
|
+
const postgresEngine = matchingText(database.postgres_engine, 64, DATABASE_VERSION_PATTERN);
|
|
9730
|
+
const releaseChannel = matchingText(database.release_channel, 64, DATABASE_VERSION_PATTERN);
|
|
9731
|
+
return host && version && postgresEngine && releaseChannel ? { host, version, postgres_engine: postgresEngine, release_channel: releaseChannel } : null;
|
|
9732
|
+
}
|
|
9733
|
+
function rawUrlHasNoPath(candidate) {
|
|
9734
|
+
if (candidate.trim() !== candidate || candidate.includes("\\"))
|
|
9735
|
+
return false;
|
|
9736
|
+
const schemeEnd = candidate.indexOf("://");
|
|
9737
|
+
const pathStart = candidate.indexOf("/", schemeEnd + 3);
|
|
9738
|
+
return pathStart === -1;
|
|
9739
|
+
}
|
|
9740
|
+
function projectEndpoint(candidate) {
|
|
9741
|
+
const endpoint = plainRecord(candidate);
|
|
9742
|
+
if (!endpoint || !hasOnlyKeys(endpoint, PROJECT_ENDPOINT_KEYS))
|
|
9743
|
+
return null;
|
|
9744
|
+
const endpointUrl = boundedText(endpoint.url, 2048);
|
|
9745
|
+
if (!endpointUrl || !rawUrlHasNoPath(endpointUrl))
|
|
9746
|
+
return null;
|
|
9747
|
+
try {
|
|
9748
|
+
const url = new URL(endpointUrl);
|
|
9749
|
+
if (url.protocol !== "http:" && url.protocol !== "https:" || url.username || url.password || url.search || url.hash || url.pathname !== "/")
|
|
9750
|
+
return null;
|
|
9751
|
+
return { url: url.origin };
|
|
9752
|
+
} catch (error) {
|
|
9753
|
+
if (error instanceof TypeError)
|
|
9754
|
+
return null;
|
|
9755
|
+
throw error;
|
|
9756
|
+
}
|
|
9757
|
+
}
|
|
9758
|
+
function discardedDetailFieldsAreValid(project) {
|
|
9759
|
+
if (project.config !== undefined && plainRecord(project.config) === null)
|
|
9760
|
+
return false;
|
|
9761
|
+
if (project.anon_key !== undefined && boundedText(project.anon_key, 16384) === null)
|
|
9762
|
+
return false;
|
|
9763
|
+
return project.services === undefined || Array.isArray(project.services);
|
|
9764
|
+
}
|
|
9765
|
+
function projectDetails(candidate, expectedRef) {
|
|
9766
|
+
const project = plainRecord(candidate);
|
|
9767
|
+
if (!project || !hasOnlyKeys(project, PROJECT_DETAILS_KEYS))
|
|
9768
|
+
return null;
|
|
9769
|
+
const summary = projectedSummary(project);
|
|
9770
|
+
const database = projectDatabase(project.database);
|
|
9771
|
+
const api = project.api === undefined ? undefined : projectEndpoint(project.api);
|
|
9772
|
+
const studio = project.studio === undefined ? undefined : projectEndpoint(project.studio);
|
|
9773
|
+
if (!summary || summary.ref !== expectedRef || !database || !discardedDetailFieldsAreValid(project) || project.api !== undefined && !api || project.studio !== undefined && !studio)
|
|
9774
|
+
return null;
|
|
9775
|
+
return {
|
|
9776
|
+
...summary,
|
|
9777
|
+
database,
|
|
9778
|
+
...api ? { api } : {},
|
|
9779
|
+
...studio ? { studio } : {}
|
|
9780
|
+
};
|
|
9781
|
+
}
|
|
9782
|
+
function payloadWithinLimit(candidate) {
|
|
9783
|
+
try {
|
|
9784
|
+
const serializedPayload = JSON.stringify(candidate);
|
|
9785
|
+
return serializedPayload !== undefined && new TextEncoder().encode(serializedPayload).byteLength <= PROJECT_READ_RESPONSE_MAX_BYTES;
|
|
9786
|
+
} catch {
|
|
9787
|
+
return false;
|
|
9788
|
+
}
|
|
9789
|
+
}
|
|
9790
|
+
function validHttpStatus(status) {
|
|
9791
|
+
return Number.isSafeInteger(status) && status >= 100 && status <= 599;
|
|
9792
|
+
}
|
|
9793
|
+
function successfulResponse(response) {
|
|
9794
|
+
return response.ok === true && validHttpStatus(response.status) && response.status >= 200 && response.status <= 299;
|
|
9795
|
+
}
|
|
9796
|
+
function failedResult(message) {
|
|
9797
|
+
return { text: `❌ ${message}`, isError: true };
|
|
9798
|
+
}
|
|
9799
|
+
function failedHttpResult(label, status) {
|
|
9800
|
+
return failedResult(validHttpStatus(status) ? `${label} request failed (${status})` : `${label} request failed`);
|
|
9801
|
+
}
|
|
9802
|
+
function successfulResult(payload) {
|
|
9803
|
+
return { text: JSON.stringify(payload, null, 2), isError: false };
|
|
9804
|
+
}
|
|
9805
|
+
function projectGetRead(response, expectedRef) {
|
|
9806
|
+
if (!successfulResponse(response))
|
|
9807
|
+
return failedHttpResult("Project get", response.status);
|
|
9808
|
+
if (!payloadWithinLimit(response.data))
|
|
9809
|
+
return failedResult("Invalid project response");
|
|
9810
|
+
const project = projectDetails(response.data, expectedRef);
|
|
9811
|
+
return project ? successfulResult(project) : failedResult("Invalid project response");
|
|
9812
|
+
}
|
|
9813
|
+
|
|
9398
9814
|
// src/shared/tools/project-cli-tools.ts
|
|
9815
|
+
function projectReadResponse(readResult) {
|
|
9816
|
+
return {
|
|
9817
|
+
content: [{ type: "text", text: readResult.text }],
|
|
9818
|
+
...readResult.isError ? { isError: true } : {}
|
|
9819
|
+
};
|
|
9820
|
+
}
|
|
9399
9821
|
var formatTasks = (data) => {
|
|
9400
9822
|
if (!Array.isArray(data))
|
|
9401
9823
|
return JSON.stringify(data, null, 2);
|
|
@@ -9551,8 +9973,9 @@ Actions: get, health, logs, api_keys, settings, tasks, task_detail, task_cancel,
|
|
|
9551
9973
|
let text;
|
|
9552
9974
|
switch (action) {
|
|
9553
9975
|
case "get":
|
|
9554
|
-
|
|
9555
|
-
|
|
9976
|
+
return projectReadResponse(projectGetRead(await http.get(`/v1/projects/${resolvedRef}`, {
|
|
9977
|
+
maxResponseBytes: PROJECT_READ_RESPONSE_MAX_BYTES
|
|
9978
|
+
}), resolvedRef));
|
|
9556
9979
|
case "health":
|
|
9557
9980
|
text = ok(await http.get(`/v1/projects/${resolvedRef}/health`));
|
|
9558
9981
|
break;
|
|
@@ -10973,7 +11396,7 @@ var FORBIDDEN_HEADER_NAMES = new Set([
|
|
|
10973
11396
|
"x-project-ref"
|
|
10974
11397
|
]);
|
|
10975
11398
|
var SCHEDULE_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
10976
|
-
var
|
|
11399
|
+
var SAFE_SLUG_PATTERN2 = /^[A-Za-z0-9_-]{1,128}$/;
|
|
10977
11400
|
var CRON_PART_PATTERN = /^(\*|([0-9]+)(?:-([0-9]+))?)(?:\/([0-9]+))?$/;
|
|
10978
11401
|
var CRON_FIELD_BOUNDS = [[0, 59], [0, 23], [1, 31], [1, 12], [0, 7]];
|
|
10979
11402
|
var MAX_CRON_EXPRESSION_LENGTH = 256;
|
|
@@ -11002,7 +11425,7 @@ function parseHeaderEnvironment(input) {
|
|
|
11002
11425
|
}
|
|
11003
11426
|
}
|
|
11004
11427
|
var headerEnvironmentSchema = Type.Optional(decodedSchema(Type.Union([Type.String(), headerEnvironmentRecord]), headerEnvironmentRecord, parseHeaderEnvironment));
|
|
11005
|
-
function
|
|
11428
|
+
function objectRecord3(candidate) {
|
|
11006
11429
|
return candidate && typeof candidate === "object" && !Array.isArray(candidate) ? candidate : null;
|
|
11007
11430
|
}
|
|
11008
11431
|
function boundedCronInteger(input, minimum, maximum) {
|
|
@@ -11047,7 +11470,7 @@ function readScheduleBodyFile(bodyPathInput) {
|
|
|
11047
11470
|
throw error;
|
|
11048
11471
|
throw new Error("Scheduled Function body file must contain exact JSON");
|
|
11049
11472
|
}
|
|
11050
|
-
const body =
|
|
11473
|
+
const body = objectRecord3(payload);
|
|
11051
11474
|
if (!body)
|
|
11052
11475
|
throw new Error("Scheduled Function body file must contain a JSON object");
|
|
11053
11476
|
return body;
|
|
@@ -11088,7 +11511,7 @@ function headerValueIsStable(name, value) {
|
|
|
11088
11511
|
function scheduleHeaders(mapping, environment) {
|
|
11089
11512
|
if (mapping === undefined)
|
|
11090
11513
|
return;
|
|
11091
|
-
const headerEnvironment =
|
|
11514
|
+
const headerEnvironment = objectRecord3(mapping);
|
|
11092
11515
|
if (!headerEnvironment)
|
|
11093
11516
|
throw new Error("'header_env' must be a JSON object");
|
|
11094
11517
|
const entries = Object.entries(headerEnvironment).map(([headerName, environmentName]) => resolvedHeaderEntry(headerName, environmentName, environment));
|
|
@@ -11102,7 +11525,7 @@ function validSafeSchedule(schedule) {
|
|
|
11102
11525
|
return validScheduleIdentity(schedule) && validScheduleDefinition(schedule) && validScheduleMetadata(schedule);
|
|
11103
11526
|
}
|
|
11104
11527
|
function validScheduleIdentity(schedule) {
|
|
11105
|
-
return typeof schedule.id === "string" && SCHEDULE_ID_PATTERN.test(schedule.id) && typeof schedule.name === "string" && schedule.name.trim().length > 0 && schedule.name.length <= MAX_SCHEDULE_NAME_LENGTH && typeof schedule.slug === "string" &&
|
|
11528
|
+
return typeof schedule.id === "string" && SCHEDULE_ID_PATTERN.test(schedule.id) && typeof schedule.name === "string" && schedule.name.trim().length > 0 && schedule.name.length <= MAX_SCHEDULE_NAME_LENGTH && typeof schedule.slug === "string" && SAFE_SLUG_PATTERN2.test(schedule.slug);
|
|
11106
11529
|
}
|
|
11107
11530
|
function validScheduleDefinition(schedule) {
|
|
11108
11531
|
return typeof schedule.cron === "string" && validScheduledFunctionCron(schedule.cron) && (schedule.method === "GET" || schedule.method === "POST") && typeof schedule.enabled === "boolean";
|
|
@@ -11130,7 +11553,7 @@ function safeHeaderNames(candidate) {
|
|
|
11130
11553
|
return valid && new Set(names).size === names.length ? names.sort() : null;
|
|
11131
11554
|
}
|
|
11132
11555
|
function safeSchedule(candidate) {
|
|
11133
|
-
const schedule =
|
|
11556
|
+
const schedule = objectRecord3(candidate);
|
|
11134
11557
|
if (!schedule || !validSafeSchedule(schedule))
|
|
11135
11558
|
return null;
|
|
11136
11559
|
const safePayload = safeSchedulePayload(schedule);
|
|
@@ -11176,7 +11599,7 @@ function listResponse(ref, response) {
|
|
|
11176
11599
|
const operation = "scheduled_functions.list";
|
|
11177
11600
|
if (!response.ok)
|
|
11178
11601
|
return scheduleFailure(operation, response);
|
|
11179
|
-
const payload =
|
|
11602
|
+
const payload = objectRecord3(response.data);
|
|
11180
11603
|
const rawSchedules = payload?.schedules;
|
|
11181
11604
|
const schedules = Array.isArray(rawSchedules) ? rawSchedules.map(safeSchedule) : null;
|
|
11182
11605
|
if (payload?.project_ref !== ref || !schedules || schedules.some((schedule) => !schedule)) {
|
|
@@ -11191,7 +11614,7 @@ function getResponse(ref, scheduleId, response) {
|
|
|
11191
11614
|
const operation = "scheduled_functions.get";
|
|
11192
11615
|
if (!response.ok)
|
|
11193
11616
|
return scheduleFailure(operation, response);
|
|
11194
|
-
const payload =
|
|
11617
|
+
const payload = objectRecord3(response.data);
|
|
11195
11618
|
const schedule = safeSchedule(payload?.schedule);
|
|
11196
11619
|
if (payload?.project_ref !== ref || !schedule || schedule.id !== scheduleId) {
|
|
11197
11620
|
return releaseControlFailure(operation, "INVALID_RESPONSE", null);
|
|
@@ -11204,7 +11627,7 @@ function mutationResponse(expectation, response) {
|
|
|
11204
11627
|
const operation = `scheduled_functions.${action}`;
|
|
11205
11628
|
if (!response.ok)
|
|
11206
11629
|
return releaseControlMutationFailure(operation, response);
|
|
11207
|
-
const payload =
|
|
11630
|
+
const payload = objectRecord3(response.data);
|
|
11208
11631
|
const schedule = safeSchedule(payload?.schedule);
|
|
11209
11632
|
const confirmsRequest = schedule && Object.entries(expectedFields).every(([field, expected]) => isDeepStrictEqual(schedule[field], expected));
|
|
11210
11633
|
const confirmsRevision = action === "create" || payload?.previous_updated_at === expectation.expectedUpdatedAt && schedule !== null && schedule.updated_at > expectation.expectedUpdatedAt;
|
|
@@ -11222,7 +11645,7 @@ function deleteResponse(ref, scheduleId, expectedUpdatedAt, response) {
|
|
|
11222
11645
|
const operation = "scheduled_functions.delete";
|
|
11223
11646
|
if (!response.ok)
|
|
11224
11647
|
return releaseControlMutationFailure(operation, response);
|
|
11225
|
-
const payload =
|
|
11648
|
+
const payload = objectRecord3(response.data);
|
|
11226
11649
|
if (payload?.deleted !== true || payload.project_ref !== ref || payload.schedule_id !== scheduleId || payload.deleted_updated_at !== expectedUpdatedAt) {
|
|
11227
11650
|
return releaseControlFailure(operation, "OUTCOME_UNKNOWN", response.status);
|
|
11228
11651
|
}
|
|
@@ -11268,7 +11691,7 @@ function requiredName(args, action) {
|
|
|
11268
11691
|
}
|
|
11269
11692
|
function requiredSlug(args, action) {
|
|
11270
11693
|
const slug = requiredText2(args, "slug", action);
|
|
11271
|
-
if (!
|
|
11694
|
+
if (!SAFE_SLUG_PATTERN2.test(slug))
|
|
11272
11695
|
throw new Error(`'slug' is invalid for '${action}'`);
|
|
11273
11696
|
return slug;
|
|
11274
11697
|
}
|
|
@@ -11366,11 +11789,14 @@ var SCHEDULE_TOOL_SCHEMA = {
|
|
|
11366
11789
|
var MUTATION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
11367
11790
|
var FINGERPRINT_PATTERN = /^[0-9a-f]{64}$/;
|
|
11368
11791
|
var OPERATION_PATTERN = /^[a-z0-9][a-z0-9._:-]{0,127}$/;
|
|
11369
|
-
var RESOURCE_KEY_PATTERN = /^[
|
|
11792
|
+
var RESOURCE_KEY_PATTERN = /^v1\/(?:[a-z0-9][a-z0-9._-]{0,63})\/([A-Za-z0-9_-]{2,171})$/;
|
|
11793
|
+
var RESOURCE_ID_CONTROL_PATTERN = /[\u0000-\u001f\u007f-\u009f]/u;
|
|
11370
11794
|
var FAILURE_CODE_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
|
|
11371
11795
|
var LEASE_OWNER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:@/-]{0,254}$/;
|
|
11372
11796
|
var TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
11373
11797
|
var MAX_STATUS_RESPONSE_BYTES = 196608;
|
|
11798
|
+
var MAX_RESOURCE_ID_BYTES = 128;
|
|
11799
|
+
var FATAL_UTF8_DECODER = new TextDecoder("utf-8", { fatal: true });
|
|
11374
11800
|
var MUTATION_STATUSES = new Set([
|
|
11375
11801
|
"pending",
|
|
11376
11802
|
"running",
|
|
@@ -11402,27 +11828,27 @@ var LEASE_KEYS = ["owner", "expires_at", "fencing_epoch"];
|
|
|
11402
11828
|
function isMutationId(candidate) {
|
|
11403
11829
|
return typeof candidate === "string" && MUTATION_ID_PATTERN.test(candidate);
|
|
11404
11830
|
}
|
|
11405
|
-
function
|
|
11831
|
+
function objectRecord4(candidate) {
|
|
11406
11832
|
return candidate && typeof candidate === "object" && !Array.isArray(candidate) ? candidate : null;
|
|
11407
11833
|
}
|
|
11408
11834
|
function exactRecord(candidate, keys) {
|
|
11409
|
-
const record =
|
|
11835
|
+
const record = objectRecord4(candidate);
|
|
11410
11836
|
if (!record || Object.keys(record).length !== keys.length)
|
|
11411
11837
|
return null;
|
|
11412
11838
|
return keys.every((key) => Object.hasOwn(record, key)) ? record : null;
|
|
11413
11839
|
}
|
|
11414
11840
|
function emptyProjection(candidate) {
|
|
11415
|
-
const record =
|
|
11841
|
+
const record = objectRecord4(candidate);
|
|
11416
11842
|
return record && Object.keys(record).length === 0 ? record : null;
|
|
11417
11843
|
}
|
|
11418
|
-
function
|
|
11844
|
+
function canonicalTimestamp2(candidate) {
|
|
11419
11845
|
if (typeof candidate !== "string" || !TIMESTAMP_PATTERN.test(candidate))
|
|
11420
11846
|
return false;
|
|
11421
11847
|
const milliseconds = Date.parse(candidate);
|
|
11422
11848
|
return Number.isFinite(milliseconds) && new Date(milliseconds).toISOString() === candidate;
|
|
11423
11849
|
}
|
|
11424
11850
|
function nullableTimestamp(candidate) {
|
|
11425
|
-
return candidate === null ||
|
|
11851
|
+
return candidate === null || canonicalTimestamp2(candidate);
|
|
11426
11852
|
}
|
|
11427
11853
|
function safePrincipal(candidate) {
|
|
11428
11854
|
const principal = exactRecord(candidate, PRINCIPAL_KEYS);
|
|
@@ -11471,12 +11897,32 @@ function validMutationLifecycle(mutation, receipt, responseStatus) {
|
|
|
11471
11897
|
}
|
|
11472
11898
|
return true;
|
|
11473
11899
|
}
|
|
11900
|
+
function canonicalMutationResourceKey(candidate) {
|
|
11901
|
+
if (typeof candidate !== "string")
|
|
11902
|
+
return false;
|
|
11903
|
+
const match = RESOURCE_KEY_PATTERN.exec(candidate);
|
|
11904
|
+
if (!match)
|
|
11905
|
+
return false;
|
|
11906
|
+
const encodedResourceId = match[1];
|
|
11907
|
+
const resourceIdBytes = Buffer.from(encodedResourceId, "base64url");
|
|
11908
|
+
if (resourceIdBytes.byteLength < 1 || resourceIdBytes.byteLength > MAX_RESOURCE_ID_BYTES || resourceIdBytes.toString("base64url") !== encodedResourceId)
|
|
11909
|
+
return false;
|
|
11910
|
+
let resourceId;
|
|
11911
|
+
try {
|
|
11912
|
+
resourceId = FATAL_UTF8_DECODER.decode(resourceIdBytes);
|
|
11913
|
+
} catch (decodeError) {
|
|
11914
|
+
if (decodeError instanceof TypeError)
|
|
11915
|
+
return false;
|
|
11916
|
+
throw decodeError;
|
|
11917
|
+
}
|
|
11918
|
+
return resourceId.trim() === resourceId && !RESOURCE_ID_CONTROL_PATTERN.test(resourceId) && Buffer.from(resourceId, "utf8").equals(resourceIdBytes);
|
|
11919
|
+
}
|
|
11474
11920
|
function validMutationIdentity(mutation) {
|
|
11475
11921
|
if (!isMutationId(mutation.mutation_id) || typeof mutation.project_ref !== "string")
|
|
11476
11922
|
return false;
|
|
11477
11923
|
if (typeof mutation.operation !== "string" || !OPERATION_PATTERN.test(mutation.operation))
|
|
11478
11924
|
return false;
|
|
11479
|
-
if (mutation.resource_key !== null &&
|
|
11925
|
+
if (mutation.resource_key !== null && !canonicalMutationResourceKey(mutation.resource_key))
|
|
11480
11926
|
return false;
|
|
11481
11927
|
return typeof mutation.request_fingerprint === "string" && FINGERPRINT_PATTERN.test(mutation.request_fingerprint);
|
|
11482
11928
|
}
|
|
@@ -11485,7 +11931,7 @@ function validMutationTerminalFields(mutation) {
|
|
|
11485
11931
|
return false;
|
|
11486
11932
|
if (mutation.failure_code !== null && (typeof mutation.failure_code !== "string" || !FAILURE_CODE_PATTERN.test(mutation.failure_code)))
|
|
11487
11933
|
return false;
|
|
11488
|
-
return nullableTimestamp(mutation.completed_at) &&
|
|
11934
|
+
return nullableTimestamp(mutation.completed_at) && canonicalTimestamp2(mutation.created_at) && canonicalTimestamp2(mutation.updated_at);
|
|
11489
11935
|
}
|
|
11490
11936
|
function safeMutationStatus(candidate) {
|
|
11491
11937
|
const mutation = exactRecord(candidate, MUTATION_KEYS);
|
|
@@ -11557,6 +12003,12 @@ async function mutationStatus(http, args) {
|
|
|
11557
12003
|
if (readback.kind === "invalid") {
|
|
11558
12004
|
return releaseControlFailure("mutations.status", "INVALID_RESPONSE", null);
|
|
11559
12005
|
}
|
|
12006
|
+
if (readback.mutation.status !== "succeeded") {
|
|
12007
|
+
return releaseControlFailure("mutations.status", "MUTATION_NOT_SUCCEEDED", null, {
|
|
12008
|
+
project_ref: ref,
|
|
12009
|
+
mutation: readback.mutation
|
|
12010
|
+
});
|
|
12011
|
+
}
|
|
11560
12012
|
return releaseControlSuccess("mutations.status", { project_ref: ref, mutation: readback.mutation });
|
|
11561
12013
|
}
|
|
11562
12014
|
function registerMutationTools(server, http) {
|
|
@@ -11570,7 +12022,7 @@ var MUTATION_TOOL_SCHEMA = {
|
|
|
11570
12022
|
// package.json
|
|
11571
12023
|
var package_default = {
|
|
11572
12024
|
name: "@supacloud/cli",
|
|
11573
|
-
version: "0.
|
|
12025
|
+
version: "0.21.0",
|
|
11574
12026
|
description: "Project-scoped CLI for SupaCloud users",
|
|
11575
12027
|
type: "module",
|
|
11576
12028
|
main: "./dist/index.js",
|
|
@@ -11830,12 +12282,14 @@ EXAMPLES
|
|
|
11830
12282
|
${preferredCommand} branch promote --branch_ref preview123 --plan_checksum <sha256>
|
|
11831
12283
|
${preferredCommand} ai show_skill
|
|
11832
12284
|
${preferredCommand} ai install_skill --dry_run
|
|
11833
|
-
${preferredCommand} edge_functions
|
|
11834
|
-
${preferredCommand} edge_functions deploy --ref abc123 --slug hello --
|
|
11835
|
-
${preferredCommand} edge_functions
|
|
12285
|
+
${preferredCommand} edge_functions get_config --ref abc123 --slug hello
|
|
12286
|
+
${preferredCommand} edge_functions deploy --ref abc123 --slug hello --path ./supabase/functions/hello --expected-active-version absent --expected-activation-id legacy
|
|
12287
|
+
${preferredCommand} edge_functions deploy --ref abc123 --slug hello --prebundled-path ./dist/hello.js --expected-sha256 <sha256> --expected-active-version 4 --expected-activation-id <uuid>
|
|
12288
|
+
${preferredCommand} edge_functions activate --ref abc123 --slug hello --version 3 --expected-active-version 4 --expected-activation-id <uuid>
|
|
11836
12289
|
${preferredCommand} scheduled_functions list --ref abc123
|
|
11837
12290
|
${preferredCommand} mutations status --ref abc123 --mutation_id 00000000-0000-4000-8000-000000000001
|
|
11838
|
-
${preferredCommand} edge_functions config --ref abc123 --slug hello --verify_jwt false --background_routes "/queue/*,/render/*"
|
|
12291
|
+
${preferredCommand} edge_functions config --ref abc123 --slug hello --verify_jwt false --background_routes "/queue/*,/render/*" --expected-activation-id <uuid>
|
|
12292
|
+
${preferredCommand} edge_functions delete --ref abc123 --slug hello --expected-activation-id <uuid>
|
|
11839
12293
|
${preferredCommand} secrets upsert --ref abc123 --from-env API_KEY,WEBHOOK_SECRET
|
|
11840
12294
|
${preferredCommand} gateway routes --ref abc123
|
|
11841
12295
|
${preferredCommand} gateway upsert_route --ref abc123 --route_id webhook --hosts "api.example.com" --paths "/webhook/*" --upstream 10.0.0.5:8080
|
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
|
|
34
|
+
- `edge_functions`: list, atomically read one active or deleted identity with `get_config`, read immutable source, deploy, activate, configure, and delete Edge Functions. For every mutation, pass the `activation_id` read from the same `list` or `get_config` snapshot as `--expected-activation-id`; use `legacy` only for a never-created or listed legacy Function, not for a deleted slug with a tombstone UUID. Deploy and activate actions also require the non-negative observed version as `--expected-active-version`; use `absent` for a never-created slug or a `get_config` tombstone. Version `0` is a legacy version 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.
|