@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 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 deploy --ref abc123 --slug hello --path ./supabase/functions/hello --expected-active-version absent
165
- supacloud-cli edge_functions deploy --ref abc123 --slug hello --prebundled-path ./dist/hello.js --expected-sha256 <sha256> --expected-active-version 4
166
- supacloud-cli edge_functions deploy_bundle --ref abc123 --slug hello --files '{"index.ts":"export default { fetch: () => new Response(\"ok\") }"}' --expected-active-version 7
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. List output remains a JSON array with string
225
- `slug` and numeric `version` fields, while source output is exactly
226
- `{ "code": "..." }`. Release automation must use `source --version <N>` for a
227
- version-bound backup.
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, and activation, the CLI applies a
238
- separate 5-second, 64 KiB response-body boundary after receiving HTTP headers.
239
- A stalled, oversized, truncated, unreadable, or malformed body is always
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 postWithResponseReader(path, serializedBody, responseReader) {
6785
+ async mutationWithResponseReader(method, path, serializedBody, responseReader) {
6777
6786
  try {
6778
6787
  const response = await fetchWithRetry(`${this.baseUrl}${path}`, {
6779
- method: "POST",
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 = options.maxResponseBytes === undefined ? await res.json().catch(() => null) : await boundedResponseJson(res, options.maxResponseBytes);
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.postWithResponseReader(path, serializedRequestBody(body), responseJsonOrNull);
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.postWithResponseReader(path, serializedRequestBody(body), releaseMutationResponseJson);
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 = safeFunctionList(response.data);
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 = objectRecord(payload);
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 objectRecord(candidate) {
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 mutationIdentityMatches(receipt, expectation) {
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 = objectRecord(payload);
8864
- const config = objectRecord(receipt?.config);
8865
- if (!receipt || !config || !mutationIdentityMatches(receipt, expectation))
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
- if (activeVersion === null || typeof config.verify_jwt !== "boolean" || !confirmedFunctionConfig(config, expectation.config ?? {}))
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
- return { projectRef, functionSlug, version, expectedActiveVersion };
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, { expected_active_version: expectedActiveVersion }));
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
- text = await updateFunctionConfig();
9052
- break;
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
- text = (await http.delete(edgeFunctionResourcePath(ref, slug))).ok ? `✅ Function ${slug} deleted` : `❌ Failed`;
9064
- break;
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
- text = ok(await http.get(`/v1/projects/${resolvedRef}`));
9555
- break;
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 SAFE_SLUG_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
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 objectRecord2(candidate) {
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 = objectRecord2(payload);
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 = objectRecord2(mapping);
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" && SAFE_SLUG_PATTERN.test(schedule.slug);
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 = objectRecord2(candidate);
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 = objectRecord2(response.data);
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 = objectRecord2(response.data);
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 = objectRecord2(response.data);
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 = objectRecord2(response.data);
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 (!SAFE_SLUG_PATTERN.test(slug))
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 = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}$/;
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 objectRecord3(candidate) {
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 = objectRecord3(candidate);
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 = objectRecord3(candidate);
11841
+ const record = objectRecord4(candidate);
11416
11842
  return record && Object.keys(record).length === 0 ? record : null;
11417
11843
  }
11418
- function canonicalTimestamp(candidate) {
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 || canonicalTimestamp(candidate);
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 && (typeof mutation.resource_key !== "string" || !RESOURCE_KEY_PATTERN.test(mutation.resource_key)))
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) && canonicalTimestamp(mutation.created_at) && canonicalTimestamp(mutation.updated_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.19.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 deploy --ref abc123 --slug hello --path ./supabase/functions/hello --expected-active-version absent
11834
- ${preferredCommand} edge_functions deploy --ref abc123 --slug hello --prebundled-path ./dist/hello.js --expected-sha256 <sha256> --expected-active-version 4
11835
- ${preferredCommand} edge_functions activate --ref abc123 --slug hello --version 3 --expected-active-version 4
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/cli",
3
- "version": "0.19.0",
3
+ "version": "0.21.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 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.
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.