@supacloud/cli 0.17.0 → 0.19.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 +35 -25
- package/dist/index.js +453 -102
- package/package.json +1 -1
- package/skills/supacloud-cli/references/command-map.md +1 -1
package/README.md
CHANGED
|
@@ -92,15 +92,19 @@ supacloud-cli status
|
|
|
92
92
|
|
|
93
93
|
Project context is resolved from one atomic source: a named profile selected by
|
|
94
94
|
`--env`, an explicit `--env-file`, a complete process environment, or the
|
|
95
|
-
legacy `.env` fallback. Core URL,
|
|
96
|
-
by mixing sources. If `SUPACLOUD_ENV` is set without a complete process
|
|
97
|
-
it strictly selects `.env.supacloud.<value>`.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
-
|
|
103
|
-
-
|
|
95
|
+
legacy `.env` fallback. Core URL, credential, and project-ref values are not
|
|
96
|
+
filled by mixing sources. If `SUPACLOUD_ENV` is set without a complete process
|
|
97
|
+
context, it strictly selects `.env.supacloud.<value>`.
|
|
98
|
+
|
|
99
|
+
The two credential scopes are separate. Management-backed remote commands use
|
|
100
|
+
only `SUPACLOUD_API_URL` + `SUPACLOUD_API_TOKEN`. An application profile using
|
|
101
|
+
`SUPABASE_URL` + `SUPABASE_SERVICE_ROLE_KEY` can be auto-linked for `status`,
|
|
102
|
+
but its service-role key is never substituted for a Management token and cannot
|
|
103
|
+
enable Management-backed tools. Both URL types must be canonical HTTPS origins;
|
|
104
|
+
omit explicit default ports such as `:443`. HTTP is accepted only for literal
|
|
105
|
+
loopback development origins, with the default `:80` likewise omitted. Use
|
|
106
|
+
`SUPACLOUD_PROJECT_REF` when it cannot be inferred from a managed
|
|
107
|
+
`<ref>.api.*` application hostname.
|
|
104
108
|
|
|
105
109
|
The legacy `.env` fallback is unclassified and therefore does not enable the
|
|
106
110
|
production confirmation gate. Production automation must select a `prod` or
|
|
@@ -127,11 +131,13 @@ for one command. A production profile cannot target a different project with
|
|
|
127
131
|
`--ref`; the requested ref and `--confirm-production` must both exactly match
|
|
128
132
|
the profile's project ref.
|
|
129
133
|
|
|
130
|
-
`status` checks configuration,
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
134
|
+
`status` checks configuration, connectivity, and authentication against the
|
|
135
|
+
selected credential scope. Application profiles probe the project data API at
|
|
136
|
+
the exact configured origin; credential-bearing probes refuse redirects. The
|
|
137
|
+
command exits non-zero when any required check fails. Its output includes
|
|
138
|
+
`credentialScope`, `environment`, `source` (`kind` and `path`), `apiUrl`,
|
|
139
|
+
`projectRef`, `readOnly`, `production`, and `hasApiToken`. It never prints the
|
|
140
|
+
API token or service-role key.
|
|
135
141
|
|
|
136
142
|
Examples:
|
|
137
143
|
|
|
@@ -212,10 +218,10 @@ already exist. Add the positive version observed from `list` as
|
|
|
212
218
|
pointer; this remains correct across an active-version A→B→A transition.
|
|
213
219
|
|
|
214
220
|
`deploy`, `deploy_bundle`, and `activate` require
|
|
215
|
-
`--expected-active-version <N|absent>`. Read the current
|
|
216
|
-
version from `edge_functions list`; use `
|
|
217
|
-
does not yet exist. A stale value returns
|
|
218
|
-
or activating another version. List output remains a JSON array with string
|
|
221
|
+
`--expected-active-version <N|absent>`. Read the current non-negative integer
|
|
222
|
+
version from `edge_functions list`; use `0` for a listed legacy Function and
|
|
223
|
+
`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
|
|
219
225
|
`slug` and numeric `version` fields, while source output is exactly
|
|
220
226
|
`{ "code": "..." }`. Release automation must use `source --version <N>` for a
|
|
221
227
|
version-bound backup.
|
|
@@ -228,9 +234,13 @@ server response body.
|
|
|
228
234
|
Mutation receipts use schema `supacloud.cli.release-control.v1`. An
|
|
229
235
|
`OUTCOME_UNKNOWN` error means the server may have committed the mutation before
|
|
230
236
|
the response was lost or failed validation; read back current state before any
|
|
231
|
-
retry.
|
|
232
|
-
|
|
233
|
-
|
|
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.
|
|
241
|
+
Version `0` is reserved as the active-version CAS token for legacy Functions. It
|
|
242
|
+
can be passed only as `--expected-active-version`; immutable source reads and
|
|
243
|
+
activation targets still require a positive version.
|
|
234
244
|
|
|
235
245
|
```json
|
|
236
246
|
{
|
|
@@ -321,10 +331,10 @@ supacloud-cli supabase push --ref abc123 --dir supabase/migrations --dry_run
|
|
|
321
331
|
supacloud-cli supabase push --ref abc123 --dir supabase/migrations
|
|
322
332
|
```
|
|
323
333
|
|
|
324
|
-
`push` uses
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
334
|
+
`push` uses only `SUPACLOUD_API_TOKEN` for the SupaCloud Management API. That
|
|
335
|
+
token, upstream access tokens, database passwords, and secret/key environment
|
|
336
|
+
variables are removed from the official CLI child process, and command output
|
|
337
|
+
is redacted.
|
|
328
338
|
|
|
329
339
|
`push` requires a resolved project ref; pass `--ref` explicitly or set
|
|
330
340
|
`SUPACLOUD_PROJECT_REF`. Relative migration directories are resolved against
|
package/dist/index.js
CHANGED
|
@@ -6333,58 +6333,45 @@ function readEnvFile(path, required) {
|
|
|
6333
6333
|
throw new Error(`Failed to read SupaCloud environment file ${path}: ${message}`);
|
|
6334
6334
|
}
|
|
6335
6335
|
}
|
|
6336
|
-
function
|
|
6337
|
-
const
|
|
6338
|
-
if (!
|
|
6336
|
+
function canonicalApiOrigin(value) {
|
|
6337
|
+
const candidate = value.trim();
|
|
6338
|
+
if (!candidate)
|
|
6339
6339
|
return "";
|
|
6340
6340
|
try {
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6341
|
+
const url = new URL(candidate);
|
|
6342
|
+
const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
|
|
6343
|
+
const allowedProtocol = url.protocol === "https:" || url.protocol === "http:" && loopback;
|
|
6344
|
+
const exactOrigin = candidate === url.origin || candidate === `${url.origin}/`;
|
|
6345
|
+
return allowedProtocol && !url.username && !url.password && exactOrigin && url.pathname === "/" && !url.search && !url.hash ? url.origin : "";
|
|
6346
|
+
} catch (error) {
|
|
6347
|
+
if (error instanceof TypeError)
|
|
6348
|
+
return "";
|
|
6349
|
+
throw error;
|
|
6344
6350
|
}
|
|
6345
6351
|
}
|
|
6346
6352
|
function hostFromUrl(value) {
|
|
6347
|
-
|
|
6348
|
-
return new URL(value).hostname;
|
|
6349
|
-
} catch {
|
|
6350
|
-
return "";
|
|
6351
|
-
}
|
|
6353
|
+
return value ? new URL(value).hostname : "";
|
|
6352
6354
|
}
|
|
6353
6355
|
function inferProjectRefFromSupabaseUrl(value) {
|
|
6354
|
-
|
|
6355
|
-
if (!normalized)
|
|
6356
|
+
if (!value)
|
|
6356
6357
|
return "";
|
|
6357
|
-
return new URL(
|
|
6358
|
+
return new URL(value).hostname.match(/^([a-z0-9-]+)\.api\./i)?.[1] ?? "";
|
|
6358
6359
|
}
|
|
6359
|
-
function
|
|
6360
|
-
const
|
|
6361
|
-
if (
|
|
6362
|
-
return "";
|
|
6363
|
-
|
|
6364
|
-
const host = url.hostname;
|
|
6365
|
-
if (host.startsWith("api.")) {
|
|
6366
|
-
url.hostname = `studio.${host.slice("api.".length)}`;
|
|
6367
|
-
return url.toString().replace(/\/+$/, "");
|
|
6368
|
-
}
|
|
6369
|
-
const ref = projectRef.trim();
|
|
6370
|
-
if (ref && host.startsWith(`${ref}.api.`)) {
|
|
6371
|
-
url.hostname = `studio-${ref}.${host.slice(`${ref}.api.`.length)}`;
|
|
6372
|
-
return url.toString().replace(/\/+$/, "");
|
|
6373
|
-
}
|
|
6374
|
-
const managedHost = host.match(/^([a-z0-9-]+)\.api\.(.+)$/i);
|
|
6375
|
-
if (managedHost) {
|
|
6376
|
-
url.hostname = `studio-${managedHost[1]}.${managedHost[2]}`;
|
|
6377
|
-
return url.toString().replace(/\/+$/, "");
|
|
6378
|
-
}
|
|
6379
|
-
return normalized;
|
|
6360
|
+
function sourceCredentialScope(values, explicitApiUrl, supabaseUrl) {
|
|
6361
|
+
const hasManagementContext = Boolean(explicitApiUrl.trim() || values.SUPACLOUD_API_TOKEN?.trim() || values.SUPACLOUD_HOST?.trim());
|
|
6362
|
+
if (hasManagementContext)
|
|
6363
|
+
return "management";
|
|
6364
|
+
return supabaseUrl || values.SUPABASE_SERVICE_ROLE_KEY?.trim() ? "project_application" : "incomplete";
|
|
6380
6365
|
}
|
|
6381
6366
|
function sourceProjectCore(values) {
|
|
6382
|
-
const supabaseUrl =
|
|
6367
|
+
const supabaseUrl = canonicalApiOrigin(values.SUPABASE_URL || "");
|
|
6383
6368
|
const projectRef = (values.SUPACLOUD_PROJECT_REF || values.X_PROJECT_REF || "").trim() || inferProjectRefFromSupabaseUrl(supabaseUrl);
|
|
6384
6369
|
const explicitApiUrl = values.SUPACLOUD_API_URL || values.SUPACLOUD_MANAGEMENT_API_URL || values.MANAGEMENT_API_URL || "";
|
|
6385
|
-
const
|
|
6386
|
-
const
|
|
6387
|
-
|
|
6370
|
+
const credentialScope = sourceCredentialScope(values, explicitApiUrl, supabaseUrl);
|
|
6371
|
+
const managementUrl = explicitApiUrl || (values.SUPACLOUD_HOST ? `http://${values.SUPACLOUD_HOST}:9090` : "");
|
|
6372
|
+
const apiUrl = credentialScope === "management" ? canonicalApiOrigin(managementUrl) : "";
|
|
6373
|
+
const apiToken = credentialScope === "management" ? values.SUPACLOUD_API_TOKEN || "" : "";
|
|
6374
|
+
return { apiUrl, apiToken, projectRef, supabaseUrl, credentialScope };
|
|
6388
6375
|
}
|
|
6389
6376
|
function processValues(env) {
|
|
6390
6377
|
return Object.fromEntries(Object.entries(env).filter((entry) => entry[1] !== undefined));
|
|
@@ -6394,6 +6381,9 @@ function hasProcessContext(env) {
|
|
|
6394
6381
|
}
|
|
6395
6382
|
function completeProjectContext(values) {
|
|
6396
6383
|
const core = sourceProjectCore(values);
|
|
6384
|
+
if (core.credentialScope === "project_application") {
|
|
6385
|
+
return Boolean(core.supabaseUrl && values.SUPABASE_SERVICE_ROLE_KEY?.trim() && core.projectRef);
|
|
6386
|
+
}
|
|
6397
6387
|
return Boolean(core.apiUrl && core.apiToken && core.projectRef);
|
|
6398
6388
|
}
|
|
6399
6389
|
function namedEnvironmentSource(cwd, selector) {
|
|
@@ -6459,6 +6449,7 @@ function resolveSupaCloudContext(env = process.env, cwd = process.cwd(), selecti
|
|
|
6459
6449
|
production: source.environment === "prod" || source.environment === "production",
|
|
6460
6450
|
inferredSupabaseUrl: core.supabaseUrl,
|
|
6461
6451
|
inferredServiceRoleKey: source.values.SUPABASE_SERVICE_ROLE_KEY || "",
|
|
6452
|
+
credentialScope: core.credentialScope,
|
|
6462
6453
|
source: source.kind,
|
|
6463
6454
|
sourcePath: source.path
|
|
6464
6455
|
};
|
|
@@ -6492,6 +6483,7 @@ var ACTION_POLICY = {
|
|
|
6492
6483
|
write: ["deploy", "deploy_bundle", "config", "activate", "delete"]
|
|
6493
6484
|
},
|
|
6494
6485
|
scheduled_functions: { read: ["list", "get"], write: ["create", "update", "delete"] },
|
|
6486
|
+
mutations: { read: ["status"] },
|
|
6495
6487
|
secrets: { read: ["list"], write: ["upsert", "delete"] },
|
|
6496
6488
|
frontend: {
|
|
6497
6489
|
read: ["list", "get", "build_logs", "list_frameworks", "list_records"],
|
|
@@ -6576,6 +6568,8 @@ function validateExecutionPolicyCoverage(tools) {
|
|
|
6576
6568
|
|
|
6577
6569
|
// src/shared/transports/http.ts
|
|
6578
6570
|
var DEFAULT_TIMEOUT = 30000;
|
|
6571
|
+
var RELEASE_MUTATION_RESPONSE_TIMEOUT = 5000;
|
|
6572
|
+
var RELEASE_MUTATION_RESPONSE_MAX_BYTES = 64 * 1024;
|
|
6579
6573
|
var MAX_RETRIES = 2;
|
|
6580
6574
|
var RETRY_BASE_DELAY = 500;
|
|
6581
6575
|
function isRetryableMethod(method) {
|
|
@@ -6598,13 +6592,22 @@ function transportFailure(error) {
|
|
|
6598
6592
|
transportError: true
|
|
6599
6593
|
};
|
|
6600
6594
|
}
|
|
6595
|
+
function responseReadFailure(status) {
|
|
6596
|
+
return {
|
|
6597
|
+
ok: false,
|
|
6598
|
+
status,
|
|
6599
|
+
data: { error: "Response body unavailable", code: "RESPONSE_READ_ERROR" },
|
|
6600
|
+
responseReadError: true
|
|
6601
|
+
};
|
|
6602
|
+
}
|
|
6601
6603
|
async function fetchWithTimeout(url, options) {
|
|
6602
6604
|
const controller = new AbortController;
|
|
6603
6605
|
const timeout = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT);
|
|
6604
6606
|
try {
|
|
6605
6607
|
return await fetch(url, {
|
|
6606
6608
|
...options,
|
|
6607
|
-
signal: controller.signal
|
|
6609
|
+
signal: controller.signal,
|
|
6610
|
+
redirect: "error"
|
|
6608
6611
|
});
|
|
6609
6612
|
} finally {
|
|
6610
6613
|
clearTimeout(timeout);
|
|
@@ -6647,41 +6650,114 @@ function joinedResponseBytes(chunks, totalBytes) {
|
|
|
6647
6650
|
}
|
|
6648
6651
|
return responseBytes;
|
|
6649
6652
|
}
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
|
|
6653
|
+
function cancelResponseReader(reader) {
|
|
6654
|
+
reader.cancel().catch(() => {
|
|
6655
|
+
return;
|
|
6656
|
+
});
|
|
6657
|
+
}
|
|
6658
|
+
function serializedRequestBody(body) {
|
|
6659
|
+
return body ? JSON.stringify(body) : undefined;
|
|
6660
|
+
}
|
|
6661
|
+
async function responseBytesFromReader(reader, maxBytes, declaredBytes) {
|
|
6658
6662
|
const chunks = [];
|
|
6659
6663
|
let totalBytes = 0;
|
|
6660
6664
|
while (true) {
|
|
6661
6665
|
const { done, value } = await reader.read();
|
|
6662
|
-
if (done)
|
|
6663
|
-
|
|
6666
|
+
if (done) {
|
|
6667
|
+
if (declaredBytes !== null && totalBytes !== declaredBytes)
|
|
6668
|
+
return { ok: false };
|
|
6669
|
+
return { ok: true, bytes: joinedResponseBytes(chunks, totalBytes) };
|
|
6670
|
+
}
|
|
6664
6671
|
totalBytes += value.byteLength;
|
|
6665
6672
|
if (totalBytes > maxBytes) {
|
|
6666
|
-
|
|
6667
|
-
return
|
|
6673
|
+
cancelResponseReader(reader);
|
|
6674
|
+
return { ok: false };
|
|
6668
6675
|
}
|
|
6669
6676
|
chunks.push(value);
|
|
6670
6677
|
}
|
|
6671
6678
|
}
|
|
6679
|
+
async function responseBytesWithinLimit(response, maxBytes) {
|
|
6680
|
+
if (declaredResponseTooLarge(response, maxBytes)) {
|
|
6681
|
+
await response.body?.cancel();
|
|
6682
|
+
return null;
|
|
6683
|
+
}
|
|
6684
|
+
if (!response.body)
|
|
6685
|
+
return new Uint8Array;
|
|
6686
|
+
const reader = response.body.getReader();
|
|
6687
|
+
const bodyRead = await responseBytesFromReader(reader, maxBytes, null);
|
|
6688
|
+
return bodyRead.ok ? bodyRead.bytes : null;
|
|
6689
|
+
}
|
|
6672
6690
|
function parsedUtf8Json(responseBytes) {
|
|
6673
6691
|
try {
|
|
6674
6692
|
const responseText = new TextDecoder("utf-8", { fatal: true }).decode(responseBytes);
|
|
6675
|
-
return JSON.parse(responseText);
|
|
6693
|
+
return { ok: true, parsedJson: JSON.parse(responseText) };
|
|
6676
6694
|
} catch (error) {
|
|
6677
6695
|
if (error instanceof SyntaxError || error instanceof TypeError)
|
|
6678
|
-
return
|
|
6696
|
+
return { ok: false };
|
|
6679
6697
|
throw error;
|
|
6680
6698
|
}
|
|
6681
6699
|
}
|
|
6682
6700
|
async function boundedResponseJson(response, maxBytes) {
|
|
6683
6701
|
const responseBytes = await responseBytesWithinLimit(response, maxBytes);
|
|
6684
|
-
|
|
6702
|
+
if (responseBytes === null)
|
|
6703
|
+
return null;
|
|
6704
|
+
const parsed = parsedUtf8Json(responseBytes);
|
|
6705
|
+
return parsed.ok ? parsed.parsedJson : null;
|
|
6706
|
+
}
|
|
6707
|
+
function declaredIdentityResponseBytes(response) {
|
|
6708
|
+
const contentEncoding = response.headers.get("content-encoding");
|
|
6709
|
+
if (contentEncoding !== null && contentEncoding.toLowerCase() !== "identity")
|
|
6710
|
+
return null;
|
|
6711
|
+
const contentLength = response.headers.get("content-length");
|
|
6712
|
+
if (contentLength === null)
|
|
6713
|
+
return null;
|
|
6714
|
+
if (!/^\d+$/.test(contentLength))
|
|
6715
|
+
return "invalid";
|
|
6716
|
+
const declaredBytes = Number(contentLength);
|
|
6717
|
+
return Number.isSafeInteger(declaredBytes) ? declaredBytes : "invalid";
|
|
6718
|
+
}
|
|
6719
|
+
async function releaseMutationResponseBytes(response) {
|
|
6720
|
+
const declaredBytes = declaredIdentityResponseBytes(response);
|
|
6721
|
+
if (declaredBytes === "invalid" || declaredBytes !== null && declaredBytes > RELEASE_MUTATION_RESPONSE_MAX_BYTES) {
|
|
6722
|
+
response.body?.cancel().catch(() => {
|
|
6723
|
+
return;
|
|
6724
|
+
});
|
|
6725
|
+
return { ok: false };
|
|
6726
|
+
}
|
|
6727
|
+
if (!response.body) {
|
|
6728
|
+
return declaredBytes === null || declaredBytes === 0 ? { ok: true, bytes: new Uint8Array } : { ok: false };
|
|
6729
|
+
}
|
|
6730
|
+
return responseBytesBeforeDeadline(response.body.getReader(), declaredBytes);
|
|
6731
|
+
}
|
|
6732
|
+
async function responseBytesBeforeDeadline(reader, declaredBytes) {
|
|
6733
|
+
let deadlineTimer;
|
|
6734
|
+
const deadline = new Promise((resolve2) => {
|
|
6735
|
+
deadlineTimer = setTimeout(() => {
|
|
6736
|
+
cancelResponseReader(reader);
|
|
6737
|
+
resolve2({ ok: false });
|
|
6738
|
+
}, RELEASE_MUTATION_RESPONSE_TIMEOUT);
|
|
6739
|
+
});
|
|
6740
|
+
try {
|
|
6741
|
+
return await Promise.race([
|
|
6742
|
+
responseBytesFromReader(reader, RELEASE_MUTATION_RESPONSE_MAX_BYTES, declaredBytes),
|
|
6743
|
+
deadline
|
|
6744
|
+
]);
|
|
6745
|
+
} catch {
|
|
6746
|
+
cancelResponseReader(reader);
|
|
6747
|
+
return { ok: false };
|
|
6748
|
+
} finally {
|
|
6749
|
+
clearTimeout(deadlineTimer);
|
|
6750
|
+
}
|
|
6751
|
+
}
|
|
6752
|
+
async function releaseMutationResponseJson(response) {
|
|
6753
|
+
const responseBytes = await releaseMutationResponseBytes(response);
|
|
6754
|
+
if (!responseBytes.ok)
|
|
6755
|
+
return { ok: false };
|
|
6756
|
+
const parsed = parsedUtf8Json(responseBytes.bytes);
|
|
6757
|
+
return parsed.ok ? { ok: true, parsedJson: parsed.parsedJson } : { ok: false };
|
|
6758
|
+
}
|
|
6759
|
+
async function responseJsonOrNull(response) {
|
|
6760
|
+
return { ok: true, parsedJson: await response.json().catch(() => null) };
|
|
6685
6761
|
}
|
|
6686
6762
|
|
|
6687
6763
|
class HttpTransport {
|
|
@@ -6697,6 +6773,19 @@ class HttpTransport {
|
|
|
6697
6773
|
"Content-Type": "application/json"
|
|
6698
6774
|
};
|
|
6699
6775
|
}
|
|
6776
|
+
async postWithResponseReader(path, serializedBody, responseReader) {
|
|
6777
|
+
try {
|
|
6778
|
+
const response = await fetchWithRetry(`${this.baseUrl}${path}`, {
|
|
6779
|
+
method: "POST",
|
|
6780
|
+
headers: this.headers(),
|
|
6781
|
+
body: serializedBody
|
|
6782
|
+
});
|
|
6783
|
+
const responseBody = await responseReader(response);
|
|
6784
|
+
return responseBody.ok ? { ok: response.ok, status: response.status, data: responseBody.parsedJson } : responseReadFailure(response.status);
|
|
6785
|
+
} catch (error) {
|
|
6786
|
+
return transportFailure(error);
|
|
6787
|
+
}
|
|
6788
|
+
}
|
|
6700
6789
|
async get(path, options = {}) {
|
|
6701
6790
|
try {
|
|
6702
6791
|
const res = await fetchWithRetry(`${this.baseUrl}${path}`, {
|
|
@@ -6711,17 +6800,14 @@ class HttpTransport {
|
|
|
6711
6800
|
}
|
|
6712
6801
|
async post(path, body) {
|
|
6713
6802
|
try {
|
|
6714
|
-
|
|
6715
|
-
method: "POST",
|
|
6716
|
-
headers: this.headers(),
|
|
6717
|
-
body: body ? JSON.stringify(body) : undefined
|
|
6718
|
-
});
|
|
6719
|
-
const data = await res.json().catch(() => null);
|
|
6720
|
-
return { ok: res.ok, status: res.status, data };
|
|
6803
|
+
return await this.postWithResponseReader(path, serializedRequestBody(body), responseJsonOrNull);
|
|
6721
6804
|
} catch (error) {
|
|
6722
6805
|
return transportFailure(error);
|
|
6723
6806
|
}
|
|
6724
6807
|
}
|
|
6808
|
+
async postReleaseMutation(path, body) {
|
|
6809
|
+
return this.postWithResponseReader(path, serializedRequestBody(body), releaseMutationResponseJson);
|
|
6810
|
+
}
|
|
6725
6811
|
async postMultipart(path, formData) {
|
|
6726
6812
|
try {
|
|
6727
6813
|
const headers = { Authorization: `Bearer ${this.token}` };
|
|
@@ -7886,7 +7972,7 @@ function releaseControlFailure(operation, code, httpStatus) {
|
|
|
7886
7972
|
});
|
|
7887
7973
|
}
|
|
7888
7974
|
function releaseControlMutationFailure(operation, response) {
|
|
7889
|
-
const outcomeUnknown = response.transportError || response.status === 408 || response.status >= 500;
|
|
7975
|
+
const outcomeUnknown = response.transportError || response.responseReadError || response.status === 408 || response.status >= 500;
|
|
7890
7976
|
return outcomeUnknown ? releaseControlFailure(operation, "OUTCOME_UNKNOWN", response.transportError ? null : response.status) : releaseControlFailure(operation, "HTTP_ERROR", response.status);
|
|
7891
7977
|
}
|
|
7892
7978
|
function releaseControlResponse(payload) {
|
|
@@ -8523,6 +8609,17 @@ function positiveFunctionVersion(input, label) {
|
|
|
8523
8609
|
}
|
|
8524
8610
|
return version;
|
|
8525
8611
|
}
|
|
8612
|
+
function activeFunctionVersionToken(input) {
|
|
8613
|
+
if (typeof input !== "string" && typeof input !== "number") {
|
|
8614
|
+
throw new Error("Expected active version must be a canonical non-negative safe integer");
|
|
8615
|
+
}
|
|
8616
|
+
const version = String(input);
|
|
8617
|
+
if (!CANONICAL_FUNCTION_VERSION_PATTERN.test(version) || !Number.isSafeInteger(Number(version))) {
|
|
8618
|
+
throw new Error("Expected active version must be a canonical non-negative safe integer");
|
|
8619
|
+
}
|
|
8620
|
+
return version;
|
|
8621
|
+
}
|
|
8622
|
+
var CANONICAL_FUNCTION_VERSION_PATTERN = /^(?:0|[1-9][0-9]*)$/;
|
|
8526
8623
|
var POSITIVE_FUNCTION_VERSION_PATTERN = /^[1-9][0-9]*$/;
|
|
8527
8624
|
var SAFE_FUNCTION_SLUG_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
|
|
8528
8625
|
var FUNCTION_ACTIVATION_ARGUMENTS = new Set([
|
|
@@ -8539,15 +8636,15 @@ var functionVersionSchema = Type.Optional(decodedSchema(Type.Union([
|
|
|
8539
8636
|
function parseExpectedActiveVersion(input) {
|
|
8540
8637
|
if (input === "absent")
|
|
8541
8638
|
return input;
|
|
8542
|
-
return
|
|
8639
|
+
return activeFunctionVersionToken(input);
|
|
8543
8640
|
}
|
|
8544
8641
|
var expectedActiveVersionSchema = Type.Optional(decodedSchema(Type.Union([
|
|
8545
8642
|
Type.Literal("absent"),
|
|
8546
|
-
Type.Integer({ minimum:
|
|
8547
|
-
Type.String({ pattern:
|
|
8643
|
+
Type.Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER }),
|
|
8644
|
+
Type.String({ pattern: CANONICAL_FUNCTION_VERSION_PATTERN.source, maxLength: 16 })
|
|
8548
8645
|
]), Type.Union([
|
|
8549
8646
|
Type.Literal("absent"),
|
|
8550
|
-
Type.String({ pattern:
|
|
8647
|
+
Type.String({ pattern: CANONICAL_FUNCTION_VERSION_PATTERN.source, maxLength: 16 })
|
|
8551
8648
|
]), parseExpectedActiveVersion));
|
|
8552
8649
|
var secretListSchema = Type.Array(Type.Object({ name: Type.String(), value: Type.String() }));
|
|
8553
8650
|
var ENVIRONMENT_SECRET_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]{0,255}$/;
|
|
@@ -8679,7 +8776,7 @@ function safeFunctionList(payload) {
|
|
|
8679
8776
|
const edgeFunction = objectRecord(candidate);
|
|
8680
8777
|
const slug = edgeFunction?.slug;
|
|
8681
8778
|
const version = edgeFunction?.version;
|
|
8682
|
-
if (typeof slug !== "string" || !SAFE_FUNCTION_SLUG_PATTERN.test(slug) || typeof version !== "number" || !Number.isSafeInteger(version) || version <
|
|
8779
|
+
if (typeof slug !== "string" || !SAFE_FUNCTION_SLUG_PATTERN.test(slug) || typeof version !== "number" || !Number.isSafeInteger(version) || version < 0 || functionSlugs.has(slug))
|
|
8683
8780
|
return null;
|
|
8684
8781
|
functionSlugs.add(slug);
|
|
8685
8782
|
}
|
|
@@ -8828,7 +8925,7 @@ async function activateFunctionVersion(http, args, readOnly = false) {
|
|
|
8828
8925
|
slug: functionSlug,
|
|
8829
8926
|
expectedActiveVersion,
|
|
8830
8927
|
targetVersion: version
|
|
8831
|
-
}, await http.
|
|
8928
|
+
}, await http.postReleaseMutation(endpoint, { expected_active_version: expectedActiveVersion }));
|
|
8832
8929
|
}
|
|
8833
8930
|
function registerAdvancedTools(server, http, environment = process.env, options = {}) {
|
|
8834
8931
|
server.tool("edge_functions", `Edge Function management (Deno/Bun serverless). Source deploys are bundled; verified prebuilt artifacts stay byte-exact.
|
|
@@ -8920,7 +9017,7 @@ ${deployCheck.err}`;
|
|
|
8920
9017
|
break;
|
|
8921
9018
|
}
|
|
8922
9019
|
}
|
|
8923
|
-
const deploymentResponse = await http.
|
|
9020
|
+
const deploymentResponse = await http.postReleaseMutation(edgeFunctionResourcePath(ref, slug), {
|
|
8924
9021
|
code: deployCode.code,
|
|
8925
9022
|
...deployCode.prebundled ? { prebundled: true, expected_sha256: deployCode.expectedSha256 } : { minify },
|
|
8926
9023
|
expected_active_version: expectedActiveVersion,
|
|
@@ -8936,7 +9033,7 @@ ${deployCheck.err}`;
|
|
|
8936
9033
|
case "deploy_bundle":
|
|
8937
9034
|
need("slug", slug);
|
|
8938
9035
|
need("files", files);
|
|
8939
|
-
const bundleResponse = await http.
|
|
9036
|
+
const bundleResponse = await http.postReleaseMutation(`${edgeFunctionResourcePath(ref, slug)}/bundle`, {
|
|
8940
9037
|
files,
|
|
8941
9038
|
entrypoint,
|
|
8942
9039
|
minify,
|
|
@@ -10551,10 +10648,10 @@ function missingMigrationContextResult() {
|
|
|
10551
10648
|
content: [{
|
|
10552
10649
|
type: "text",
|
|
10553
10650
|
text: [
|
|
10554
|
-
"⚠️ Remote migration push requires SupaCloud
|
|
10555
|
-
"Provide
|
|
10651
|
+
"⚠️ Remote migration push requires SupaCloud Management API context.",
|
|
10652
|
+
"Provide SUPACLOUD_API_URL + SUPACLOUD_API_TOKEN.",
|
|
10556
10653
|
"Also pass --ref or set SUPACLOUD_PROJECT_REF when the project ref cannot be inferred from the URL.",
|
|
10557
|
-
"The
|
|
10654
|
+
"The Management token is sent only to the SupaCloud Management API and is never forwarded to the official CLI."
|
|
10558
10655
|
].join(`
|
|
10559
10656
|
`)
|
|
10560
10657
|
}]
|
|
@@ -10641,7 +10738,7 @@ function registerSupabaseCliTools(server, options = {}) {
|
|
|
10641
10738
|
readOnly: options.readOnly ?? false,
|
|
10642
10739
|
executeOfficialCli: options.executeOfficialCli || ((request) => executeOfficialSupabaseCli(request, environment))
|
|
10643
10740
|
};
|
|
10644
|
-
server.tool("supabase", "Controlled adapter for the official open-source Supabase CLI. Remote push stays on the SupaCloud
|
|
10741
|
+
server.tool("supabase", "Controlled adapter for the official open-source Supabase CLI. Remote push stays on the SupaCloud Management API and requires explicit Management credentials.", {
|
|
10645
10742
|
action: withDescription(stringEnum([
|
|
10646
10743
|
"version",
|
|
10647
10744
|
"migration_new",
|
|
@@ -11264,10 +11361,216 @@ var SCHEDULE_TOOL_SCHEMA = {
|
|
|
11264
11361
|
body_file: optional(Type.String(), "[create/update] Local JSON object file; content is never printed"),
|
|
11265
11362
|
header_env: withDescription(headerEnvironmentSchema, "[create/update] JSON map of HTTP header names to environment variable names")
|
|
11266
11363
|
};
|
|
11364
|
+
|
|
11365
|
+
// src/shared/mutation-protocol.ts
|
|
11366
|
+
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
|
+
var FINGERPRINT_PATTERN = /^[0-9a-f]{64}$/;
|
|
11368
|
+
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}$/;
|
|
11370
|
+
var FAILURE_CODE_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
|
|
11371
|
+
var LEASE_OWNER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:@/-]{0,254}$/;
|
|
11372
|
+
var TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
11373
|
+
var MAX_STATUS_RESPONSE_BYTES = 196608;
|
|
11374
|
+
var MUTATION_STATUSES = new Set([
|
|
11375
|
+
"pending",
|
|
11376
|
+
"running",
|
|
11377
|
+
"succeeded",
|
|
11378
|
+
"failed_retryable",
|
|
11379
|
+
"failed_terminal",
|
|
11380
|
+
"outcome_unknown"
|
|
11381
|
+
]);
|
|
11382
|
+
var MUTATION_RESPONSE_KEYS = ["project_ref", "mutation"];
|
|
11383
|
+
var MUTATION_KEYS = [
|
|
11384
|
+
"project_ref",
|
|
11385
|
+
"mutation_id",
|
|
11386
|
+
"operation",
|
|
11387
|
+
"resource_key",
|
|
11388
|
+
"request_fingerprint",
|
|
11389
|
+
"principal",
|
|
11390
|
+
"status",
|
|
11391
|
+
"checkpoint",
|
|
11392
|
+
"receipt",
|
|
11393
|
+
"response_status",
|
|
11394
|
+
"failure_code",
|
|
11395
|
+
"lease",
|
|
11396
|
+
"completed_at",
|
|
11397
|
+
"created_at",
|
|
11398
|
+
"updated_at"
|
|
11399
|
+
];
|
|
11400
|
+
var PRINCIPAL_KEYS = ["type", "id"];
|
|
11401
|
+
var LEASE_KEYS = ["owner", "expires_at", "fencing_epoch"];
|
|
11402
|
+
function isMutationId(candidate) {
|
|
11403
|
+
return typeof candidate === "string" && MUTATION_ID_PATTERN.test(candidate);
|
|
11404
|
+
}
|
|
11405
|
+
function objectRecord3(candidate) {
|
|
11406
|
+
return candidate && typeof candidate === "object" && !Array.isArray(candidate) ? candidate : null;
|
|
11407
|
+
}
|
|
11408
|
+
function exactRecord(candidate, keys) {
|
|
11409
|
+
const record = objectRecord3(candidate);
|
|
11410
|
+
if (!record || Object.keys(record).length !== keys.length)
|
|
11411
|
+
return null;
|
|
11412
|
+
return keys.every((key) => Object.hasOwn(record, key)) ? record : null;
|
|
11413
|
+
}
|
|
11414
|
+
function emptyProjection(candidate) {
|
|
11415
|
+
const record = objectRecord3(candidate);
|
|
11416
|
+
return record && Object.keys(record).length === 0 ? record : null;
|
|
11417
|
+
}
|
|
11418
|
+
function canonicalTimestamp(candidate) {
|
|
11419
|
+
if (typeof candidate !== "string" || !TIMESTAMP_PATTERN.test(candidate))
|
|
11420
|
+
return false;
|
|
11421
|
+
const milliseconds = Date.parse(candidate);
|
|
11422
|
+
return Number.isFinite(milliseconds) && new Date(milliseconds).toISOString() === candidate;
|
|
11423
|
+
}
|
|
11424
|
+
function nullableTimestamp(candidate) {
|
|
11425
|
+
return candidate === null || canonicalTimestamp(candidate);
|
|
11426
|
+
}
|
|
11427
|
+
function safePrincipal(candidate) {
|
|
11428
|
+
const principal = exactRecord(candidate, PRINCIPAL_KEYS);
|
|
11429
|
+
if (!principal || !["master", "admin", "project"].includes(String(principal.type)))
|
|
11430
|
+
return null;
|
|
11431
|
+
if (typeof principal.id !== "string" || !principal.id || principal.id.length > 320 || principal.id.trim() !== principal.id || /[\u0000-\u001f\u007f]/u.test(principal.id))
|
|
11432
|
+
return null;
|
|
11433
|
+
return { type: principal.type, id: principal.id };
|
|
11434
|
+
}
|
|
11435
|
+
function safeLease(candidate) {
|
|
11436
|
+
const lease = exactRecord(candidate, LEASE_KEYS);
|
|
11437
|
+
if (!lease || lease.owner !== null && (typeof lease.owner !== "string" || !LEASE_OWNER_PATTERN.test(lease.owner)))
|
|
11438
|
+
return null;
|
|
11439
|
+
if (!nullableTimestamp(lease.expires_at) || !Number.isSafeInteger(lease.fencing_epoch) || Number(lease.fencing_epoch) < 0)
|
|
11440
|
+
return null;
|
|
11441
|
+
return {
|
|
11442
|
+
owner: lease.owner,
|
|
11443
|
+
expires_at: lease.expires_at,
|
|
11444
|
+
fencing_epoch: Number(lease.fencing_epoch)
|
|
11445
|
+
};
|
|
11446
|
+
}
|
|
11447
|
+
function safeResponseStatus(candidate) {
|
|
11448
|
+
if (candidate === null)
|
|
11449
|
+
return null;
|
|
11450
|
+
return Number.isInteger(candidate) && Number(candidate) >= 100 && Number(candidate) <= 599 ? Number(candidate) : undefined;
|
|
11451
|
+
}
|
|
11452
|
+
function validLeaseState(status, lease) {
|
|
11453
|
+
const running = status === "running";
|
|
11454
|
+
return running ? lease.owner !== null && lease.expires_at !== null && lease.fencing_epoch > 0 : lease.owner === null && lease.expires_at === null;
|
|
11455
|
+
}
|
|
11456
|
+
function validMutationLifecycle(mutation, receipt, responseStatus) {
|
|
11457
|
+
const terminal = ["succeeded", "failed_terminal", "outcome_unknown"].includes(String(mutation.status));
|
|
11458
|
+
if (mutation.completed_at !== null !== terminal)
|
|
11459
|
+
return false;
|
|
11460
|
+
if (mutation.status === "succeeded") {
|
|
11461
|
+
return receipt !== null && responseStatus !== null && responseStatus >= 200 && responseStatus < 300 && mutation.failure_code === null;
|
|
11462
|
+
}
|
|
11463
|
+
if (mutation.status === "failed_terminal") {
|
|
11464
|
+
return receipt !== null && typeof mutation.failure_code === "string";
|
|
11465
|
+
}
|
|
11466
|
+
if (mutation.status === "failed_retryable" || mutation.status === "outcome_unknown") {
|
|
11467
|
+
return receipt !== null && typeof mutation.failure_code === "string";
|
|
11468
|
+
}
|
|
11469
|
+
if (mutation.status === "pending") {
|
|
11470
|
+
return receipt === null && responseStatus === null && mutation.failure_code === null;
|
|
11471
|
+
}
|
|
11472
|
+
return true;
|
|
11473
|
+
}
|
|
11474
|
+
function validMutationIdentity(mutation) {
|
|
11475
|
+
if (!isMutationId(mutation.mutation_id) || typeof mutation.project_ref !== "string")
|
|
11476
|
+
return false;
|
|
11477
|
+
if (typeof mutation.operation !== "string" || !OPERATION_PATTERN.test(mutation.operation))
|
|
11478
|
+
return false;
|
|
11479
|
+
if (mutation.resource_key !== null && (typeof mutation.resource_key !== "string" || !RESOURCE_KEY_PATTERN.test(mutation.resource_key)))
|
|
11480
|
+
return false;
|
|
11481
|
+
return typeof mutation.request_fingerprint === "string" && FINGERPRINT_PATTERN.test(mutation.request_fingerprint);
|
|
11482
|
+
}
|
|
11483
|
+
function validMutationTerminalFields(mutation) {
|
|
11484
|
+
if (typeof mutation.status !== "string" || !MUTATION_STATUSES.has(mutation.status))
|
|
11485
|
+
return false;
|
|
11486
|
+
if (mutation.failure_code !== null && (typeof mutation.failure_code !== "string" || !FAILURE_CODE_PATTERN.test(mutation.failure_code)))
|
|
11487
|
+
return false;
|
|
11488
|
+
return nullableTimestamp(mutation.completed_at) && canonicalTimestamp(mutation.created_at) && canonicalTimestamp(mutation.updated_at);
|
|
11489
|
+
}
|
|
11490
|
+
function safeMutationStatus(candidate) {
|
|
11491
|
+
const mutation = exactRecord(candidate, MUTATION_KEYS);
|
|
11492
|
+
if (!mutation || !validMutationIdentity(mutation) || !validMutationTerminalFields(mutation))
|
|
11493
|
+
return null;
|
|
11494
|
+
const principal = safePrincipal(mutation.principal);
|
|
11495
|
+
const checkpoint = emptyProjection(mutation.checkpoint);
|
|
11496
|
+
const receipt = mutation.receipt === null ? null : emptyProjection(mutation.receipt);
|
|
11497
|
+
const responseStatus = safeResponseStatus(mutation.response_status);
|
|
11498
|
+
const lease = safeLease(mutation.lease);
|
|
11499
|
+
if (!principal || !checkpoint || mutation.receipt !== null && !receipt || responseStatus === undefined || !lease || !validLeaseState(String(mutation.status), lease) || !validMutationLifecycle(mutation, receipt, responseStatus))
|
|
11500
|
+
return null;
|
|
11501
|
+
return {
|
|
11502
|
+
project_ref: mutation.project_ref,
|
|
11503
|
+
mutation_id: mutation.mutation_id,
|
|
11504
|
+
operation: mutation.operation,
|
|
11505
|
+
resource_key: mutation.resource_key,
|
|
11506
|
+
request_fingerprint: mutation.request_fingerprint,
|
|
11507
|
+
principal,
|
|
11508
|
+
status: mutation.status,
|
|
11509
|
+
checkpoint,
|
|
11510
|
+
receipt,
|
|
11511
|
+
response_status: responseStatus,
|
|
11512
|
+
failure_code: mutation.failure_code,
|
|
11513
|
+
lease,
|
|
11514
|
+
completed_at: mutation.completed_at,
|
|
11515
|
+
created_at: mutation.created_at,
|
|
11516
|
+
updated_at: mutation.updated_at
|
|
11517
|
+
};
|
|
11518
|
+
}
|
|
11519
|
+
function mutationStatusPath(ref, mutationId) {
|
|
11520
|
+
if (!isMutationId(mutationId))
|
|
11521
|
+
throw new Error("'mutation_id' must be a UUIDv4");
|
|
11522
|
+
return `/v1/projects/${projectRefPathSegment(ref, "Mutations")}/mutations/${encodeURIComponent(mutationId)}`;
|
|
11523
|
+
}
|
|
11524
|
+
function mutationStatusResponse(ref, mutationId, payload) {
|
|
11525
|
+
const response = exactRecord(payload, MUTATION_RESPONSE_KEYS);
|
|
11526
|
+
const mutation = safeMutationStatus(response?.mutation);
|
|
11527
|
+
return response?.project_ref === ref && mutation?.project_ref === ref && mutation.mutation_id === mutationId ? mutation : null;
|
|
11528
|
+
}
|
|
11529
|
+
async function fetchMutationStatus(http, ref, mutationId) {
|
|
11530
|
+
const response = await http.get(mutationStatusPath(ref, mutationId), {
|
|
11531
|
+
maxResponseBytes: MAX_STATUS_RESPONSE_BYTES
|
|
11532
|
+
});
|
|
11533
|
+
if (!response.ok) {
|
|
11534
|
+
return { kind: "unavailable", httpStatus: response.transportError ? null : response.status };
|
|
11535
|
+
}
|
|
11536
|
+
const mutation = mutationStatusResponse(ref, mutationId, response.data);
|
|
11537
|
+
return mutation ? { kind: "available", mutation } : { kind: "invalid" };
|
|
11538
|
+
}
|
|
11539
|
+
|
|
11540
|
+
// src/shared/tools/mutation-tools.ts
|
|
11541
|
+
function requiredText3(args, name) {
|
|
11542
|
+
const candidate = args[name];
|
|
11543
|
+
if (typeof candidate !== "string" || !candidate.trim())
|
|
11544
|
+
throw new Error(`'${name}' is required for 'status'`);
|
|
11545
|
+
return candidate.trim();
|
|
11546
|
+
}
|
|
11547
|
+
async function mutationStatus(http, args) {
|
|
11548
|
+
const ref = requiredText3(args, "ref");
|
|
11549
|
+
projectRefPathSegment(ref, "Mutations");
|
|
11550
|
+
const mutationId = requiredText3(args, "mutation_id");
|
|
11551
|
+
if (!isMutationId(mutationId))
|
|
11552
|
+
throw new Error("'mutation_id' must be a UUIDv4");
|
|
11553
|
+
const readback = await fetchMutationStatus(http, ref, mutationId);
|
|
11554
|
+
if (readback.kind === "unavailable") {
|
|
11555
|
+
return releaseControlFailure("mutations.status", "HTTP_ERROR", readback.httpStatus);
|
|
11556
|
+
}
|
|
11557
|
+
if (readback.kind === "invalid") {
|
|
11558
|
+
return releaseControlFailure("mutations.status", "INVALID_RESPONSE", null);
|
|
11559
|
+
}
|
|
11560
|
+
return releaseControlSuccess("mutations.status", { project_ref: ref, mutation: readback.mutation });
|
|
11561
|
+
}
|
|
11562
|
+
function registerMutationTools(server, http) {
|
|
11563
|
+
server.tool("mutations", "Durable mutation status readback", MUTATION_TOOL_SCHEMA, (args) => mutationStatus(http, args));
|
|
11564
|
+
}
|
|
11565
|
+
var MUTATION_TOOL_SCHEMA = {
|
|
11566
|
+
action: withDescription(stringEnum(["status"]), "Action"),
|
|
11567
|
+
ref: withDescription(Type.String(), "[status] Project ref"),
|
|
11568
|
+
mutation_id: withDescription(Type.String(), "[status] Client mutation UUID")
|
|
11569
|
+
};
|
|
11267
11570
|
// package.json
|
|
11268
11571
|
var package_default = {
|
|
11269
11572
|
name: "@supacloud/cli",
|
|
11270
|
-
version: "0.
|
|
11573
|
+
version: "0.19.0",
|
|
11271
11574
|
description: "Project-scoped CLI for SupaCloud users",
|
|
11272
11575
|
type: "module",
|
|
11273
11576
|
main: "./dist/index.js",
|
|
@@ -11332,13 +11635,14 @@ function failedEndpointProbe(error) {
|
|
|
11332
11635
|
const timedOut = error instanceof Error && error.name === "AbortError";
|
|
11333
11636
|
return { reachable: false, ok: false, httpStatus: null, error: timedOut ? "timeout" : "unreachable" };
|
|
11334
11637
|
}
|
|
11335
|
-
async function probeEndpoint(url,
|
|
11638
|
+
async function probeEndpoint(url, headers) {
|
|
11336
11639
|
const controller = new AbortController;
|
|
11337
11640
|
const timeout = setTimeout(() => controller.abort(), 3000);
|
|
11338
11641
|
try {
|
|
11339
11642
|
const response = await fetch(url, {
|
|
11340
11643
|
method: "GET",
|
|
11341
|
-
headers
|
|
11644
|
+
headers,
|
|
11645
|
+
redirect: "error",
|
|
11342
11646
|
signal: controller.signal
|
|
11343
11647
|
});
|
|
11344
11648
|
return successfulEndpointProbe(response);
|
|
@@ -11349,6 +11653,13 @@ async function probeEndpoint(url, token) {
|
|
|
11349
11653
|
}
|
|
11350
11654
|
}
|
|
11351
11655
|
function missingProjectContextFields(context) {
|
|
11656
|
+
if (context.credentialScope === "project_application") {
|
|
11657
|
+
return [
|
|
11658
|
+
!context.inferredSupabaseUrl ? "secureSupabaseUrl" : null,
|
|
11659
|
+
!context.inferredServiceRoleKey ? "serviceRoleKey" : null,
|
|
11660
|
+
!context.projectRef ? "projectRef" : null
|
|
11661
|
+
].filter((field) => Boolean(field));
|
|
11662
|
+
}
|
|
11352
11663
|
return [
|
|
11353
11664
|
!context.apiUrl ? "apiUrl" : null,
|
|
11354
11665
|
!context.apiToken ? "apiToken" : null,
|
|
@@ -11358,16 +11669,42 @@ function missingProjectContextFields(context) {
|
|
|
11358
11669
|
function authenticatedByProbe(authentication) {
|
|
11359
11670
|
if (!authentication)
|
|
11360
11671
|
return null;
|
|
11361
|
-
return authentication.reachable &&
|
|
11672
|
+
return authentication.reachable && authentication.ok;
|
|
11362
11673
|
}
|
|
11363
|
-
|
|
11364
|
-
|
|
11365
|
-
|
|
11366
|
-
|
|
11674
|
+
function connectivityProbeIsHealthy(scope, connectivity) {
|
|
11675
|
+
if (!connectivity)
|
|
11676
|
+
return null;
|
|
11677
|
+
if (scope !== "project_application")
|
|
11678
|
+
return connectivity.ok;
|
|
11679
|
+
return connectivity.reachable && (connectivity.ok || [401, 403].includes(connectivity.httpStatus ?? 0));
|
|
11680
|
+
}
|
|
11681
|
+
function projectApplicationHeaders(serviceRoleKey) {
|
|
11682
|
+
return { Authorization: `Bearer ${serviceRoleKey}`, apikey: serviceRoleKey };
|
|
11683
|
+
}
|
|
11684
|
+
function projectStatusApiUrl(context) {
|
|
11685
|
+
return context.credentialScope === "project_application" ? context.inferredSupabaseUrl : context.apiUrl;
|
|
11686
|
+
}
|
|
11687
|
+
function projectStatusProbePlan(context) {
|
|
11688
|
+
if (context.credentialScope === "project_application") {
|
|
11689
|
+
return {
|
|
11690
|
+
apiUrl: projectStatusApiUrl(context),
|
|
11691
|
+
connectivityPath: "/rest/v1/",
|
|
11692
|
+
authenticationPath: "/rest/v1/",
|
|
11693
|
+
authenticationHeaders: projectApplicationHeaders(context.inferredServiceRoleKey)
|
|
11694
|
+
};
|
|
11695
|
+
}
|
|
11696
|
+
return {
|
|
11697
|
+
apiUrl: projectStatusApiUrl(context),
|
|
11698
|
+
connectivityPath: "/health",
|
|
11699
|
+
authenticationPath: `/v1/projects/${encodeURIComponent(context.projectRef)}/health`,
|
|
11700
|
+
authenticationHeaders: { Authorization: `Bearer ${context.apiToken}` }
|
|
11701
|
+
};
|
|
11702
|
+
}
|
|
11703
|
+
function projectStatusChecks(missing, connectivity, authentication, connectivityOk) {
|
|
11367
11704
|
return {
|
|
11368
11705
|
configuration: { ok: missing.length === 0, missing },
|
|
11369
11706
|
connectivity: {
|
|
11370
|
-
ok:
|
|
11707
|
+
ok: connectivityOk,
|
|
11371
11708
|
reachable: connectivity?.reachable ?? null,
|
|
11372
11709
|
httpStatus: connectivity?.httpStatus ?? null,
|
|
11373
11710
|
error: connectivity?.error ?? null
|
|
@@ -11376,21 +11713,31 @@ async function collectProjectStatusChecks(context) {
|
|
|
11376
11713
|
project: { ok: authentication?.ok ?? null }
|
|
11377
11714
|
};
|
|
11378
11715
|
}
|
|
11716
|
+
async function collectProjectStatusChecks(context) {
|
|
11717
|
+
const missing = missingProjectContextFields(context);
|
|
11718
|
+
const probePlan = projectStatusProbePlan(context);
|
|
11719
|
+
const connectivity = probePlan.apiUrl ? await probeEndpoint(`${probePlan.apiUrl}${probePlan.connectivityPath}`) : null;
|
|
11720
|
+
const connectivityOk = connectivityProbeIsHealthy(context.credentialScope, connectivity);
|
|
11721
|
+
const authentication = missing.length === 0 && connectivityOk ? await probeEndpoint(`${probePlan.apiUrl}${probePlan.authenticationPath}`, probePlan.authenticationHeaders) : null;
|
|
11722
|
+
return projectStatusChecks(missing, connectivity, authentication, connectivityOk);
|
|
11723
|
+
}
|
|
11379
11724
|
function projectStatusIsHealthy(checks) {
|
|
11380
11725
|
return checks.configuration.ok && checks.connectivity.ok === true && checks.authentication.ok === true && checks.project.ok === true;
|
|
11381
11726
|
}
|
|
11382
11727
|
async function createProjectStatusResult(context) {
|
|
11383
11728
|
const checks = await collectProjectStatusChecks(context);
|
|
11729
|
+
const statusApiUrl = projectStatusApiUrl(context);
|
|
11384
11730
|
const statusPayload = {
|
|
11385
11731
|
mode: "project",
|
|
11732
|
+
credentialScope: context.credentialScope,
|
|
11386
11733
|
environment: context.environment || null,
|
|
11387
11734
|
source: { kind: context.source, path: context.sourcePath },
|
|
11388
11735
|
projectRef: context.projectRef || null,
|
|
11389
|
-
apiUrl:
|
|
11736
|
+
apiUrl: statusApiUrl || null,
|
|
11390
11737
|
readOnly: context.readOnly,
|
|
11391
11738
|
production: context.production,
|
|
11392
11739
|
autoLinked: Boolean(context.inferredSupabaseUrl && context.inferredServiceRoleKey),
|
|
11393
|
-
hasApiToken: Boolean(context.apiToken),
|
|
11740
|
+
hasApiToken: context.credentialScope === "project_application" ? Boolean(context.inferredServiceRoleKey) : Boolean(context.apiToken),
|
|
11394
11741
|
checks
|
|
11395
11742
|
};
|
|
11396
11743
|
return {
|
|
@@ -11448,15 +11795,15 @@ GLOBAL FLAGS
|
|
|
11448
11795
|
DEFAULT CONTEXT
|
|
11449
11796
|
|
|
11450
11797
|
Without a selector or project variables, runs use the current project's legacy .env.
|
|
11451
|
-
|
|
11452
|
-
|
|
11453
|
-
|
|
11454
|
-
|
|
11798
|
+
Application status accepts SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY.
|
|
11799
|
+
Management-backed project commands require SUPACLOUD_API_URL +
|
|
11800
|
+
SUPACLOUD_API_TOKEN. These credential scopes are never mixed.
|
|
11801
|
+
SUPACLOUD_PROJECT_REF is required when it cannot be inferred from <ref>.api.*.
|
|
11455
11802
|
|
|
11456
11803
|
SUPACLOUD_READ_ONLY=true blocks remote writes. Production writes require an
|
|
11457
11804
|
exact --confirm-production value, and cannot override the selected project ref.
|
|
11458
11805
|
|
|
11459
|
-
status checks configuration,
|
|
11806
|
+
status checks configuration, the selected API scope, connectivity, and authentication.
|
|
11460
11807
|
It exits non-zero when a required check fails.
|
|
11461
11808
|
|
|
11462
11809
|
${autoLink}
|
|
@@ -11487,6 +11834,7 @@ EXAMPLES
|
|
|
11487
11834
|
${preferredCommand} edge_functions deploy --ref abc123 --slug hello --prebundled-path ./dist/hello.js --expected-sha256 <sha256> --expected-active-version 4
|
|
11488
11835
|
${preferredCommand} edge_functions activate --ref abc123 --slug hello --version 3 --expected-active-version 4
|
|
11489
11836
|
${preferredCommand} scheduled_functions list --ref abc123
|
|
11837
|
+
${preferredCommand} mutations status --ref abc123 --mutation_id 00000000-0000-4000-8000-000000000001
|
|
11490
11838
|
${preferredCommand} edge_functions config --ref abc123 --slug hello --verify_jwt false --background_routes "/queue/*,/render/*"
|
|
11491
11839
|
${preferredCommand} secrets upsert --ref abc123 --from-env API_KEY,WEBHOOK_SECRET
|
|
11492
11840
|
${preferredCommand} gateway routes --ref abc123
|
|
@@ -11536,13 +11884,16 @@ function createCliTools(context, confirmProduction) {
|
|
|
11536
11884
|
{
|
|
11537
11885
|
type: "text",
|
|
11538
11886
|
text: [
|
|
11539
|
-
"⚠️ Project commands need a
|
|
11887
|
+
"⚠️ Project commands need a Management API context.",
|
|
11540
11888
|
"",
|
|
11541
11889
|
"Provide one of these sources:",
|
|
11542
11890
|
" - --env <name> for .env.supacloud.<name>",
|
|
11543
11891
|
" - --env-file <path> for a file declaring SUPACLOUD_ENV",
|
|
11544
|
-
" - .env with SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY",
|
|
11545
11892
|
" - SUPACLOUD_API_URL + SUPACLOUD_API_TOKEN",
|
|
11893
|
+
" - SUPACLOUD_PROJECT_REF when the profile cannot infer it",
|
|
11894
|
+
"",
|
|
11895
|
+
"SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY application profiles",
|
|
11896
|
+
"are accepted only by status and local commands.",
|
|
11546
11897
|
"",
|
|
11547
11898
|
"Then retry commands such as:",
|
|
11548
11899
|
` ${preferredCommand} project get`,
|
|
@@ -11553,7 +11904,7 @@ function createCliTools(context, confirmProduction) {
|
|
|
11553
11904
|
]
|
|
11554
11905
|
})
|
|
11555
11906
|
};
|
|
11556
|
-
for (const name of ["database", "auth", "storage", "edge_functions", "secrets", "frontend", "queue", "task_events", "scheduled_functions", "diagnostics", "gateway", "branch"]) {
|
|
11907
|
+
for (const name of ["database", "auth", "storage", "edge_functions", "secrets", "frontend", "queue", "task_events", "scheduled_functions", "mutations", "diagnostics", "gateway", "branch"]) {
|
|
11557
11908
|
tools[name] = {
|
|
11558
11909
|
schema: { action: genericActionSchema },
|
|
11559
11910
|
callback: async () => ({
|
|
@@ -11561,7 +11912,7 @@ function createCliTools(context, confirmProduction) {
|
|
|
11561
11912
|
content: [
|
|
11562
11913
|
{
|
|
11563
11914
|
type: "text",
|
|
11564
|
-
text: `⚠️ This command requires
|
|
11915
|
+
text: `⚠️ This command requires Management API context. Run \`${preferredCommand} status\` to inspect current detection.`
|
|
11565
11916
|
}
|
|
11566
11917
|
]
|
|
11567
11918
|
})
|
|
@@ -11580,7 +11931,7 @@ function createCliTools(context, confirmProduction) {
|
|
|
11580
11931
|
tools.branch = { schema: branchHelpTool.schema, callback: branchContextCallback };
|
|
11581
11932
|
}
|
|
11582
11933
|
};
|
|
11583
|
-
if (!context.apiUrl || !context.apiToken) {
|
|
11934
|
+
if (context.credentialScope !== "management" || !context.apiUrl || !context.apiToken) {
|
|
11584
11935
|
registerContextAwareHelp();
|
|
11585
11936
|
tools.setup_help = {
|
|
11586
11937
|
schema: {},
|
|
@@ -11592,19 +11943,18 @@ function createCliTools(context, confirmProduction) {
|
|
|
11592
11943
|
text: [
|
|
11593
11944
|
`⚠️ No project context found for ${preferredCommand}.`,
|
|
11594
11945
|
"",
|
|
11595
|
-
`${preferredCommand}
|
|
11946
|
+
`${preferredCommand} remote tools require Management API credentials.`,
|
|
11596
11947
|
"Provide one of these sources:",
|
|
11597
11948
|
"",
|
|
11598
11949
|
" 1. Named environment file",
|
|
11599
11950
|
" supacloud-cli --env test status",
|
|
11600
11951
|
"",
|
|
11601
|
-
" 2.
|
|
11602
|
-
" SUPABASE_URL=https://your-project.example.com",
|
|
11603
|
-
" SUPABASE_SERVICE_ROLE_KEY=...",
|
|
11604
|
-
"",
|
|
11605
|
-
" 3. Explicit environment variables",
|
|
11952
|
+
" 2. Explicit environment variables",
|
|
11606
11953
|
" SUPACLOUD_API_URL=https://your-project.example.com",
|
|
11607
11954
|
" SUPACLOUD_API_TOKEN=...",
|
|
11955
|
+
" SUPACLOUD_PROJECT_REF=your-project-ref",
|
|
11956
|
+
"",
|
|
11957
|
+
"Application SUPABASE_* profiles remain available to status and local commands.",
|
|
11608
11958
|
"",
|
|
11609
11959
|
"For server installation and tenant management, use:",
|
|
11610
11960
|
" supacloud-admin"
|
|
@@ -11638,6 +11988,7 @@ function createCliTools(context, confirmProduction) {
|
|
|
11638
11988
|
assign(captureTools((server) => registerScheduledFunctionTools(server, http, process.env, {
|
|
11639
11989
|
readOnly: context.readOnly
|
|
11640
11990
|
})));
|
|
11991
|
+
assign(captureTools((server) => registerMutationTools(server, http)));
|
|
11641
11992
|
assign(captureTools((server) => registerFrontendTools(server, http)));
|
|
11642
11993
|
assign(captureTools((server) => registerGatewayTools(server, http, {
|
|
11643
11994
|
projectRef: context.projectRef || undefined
|
package/package.json
CHANGED
|
@@ -31,7 +31,7 @@ until a project-scoped context is resolved.
|
|
|
31
31
|
- `supabase`: allowlisted official CLI adapter for migration authoring, local reset/diff, explicit-DSN inspection/backup/type generation, and SupaCloud-controlled migration push.
|
|
32
32
|
- `auth`: provider and authentication configuration.
|
|
33
33
|
- `storage`: buckets and object-management workflows.
|
|
34
|
-
- `edge_functions`: list, read immutable source, deploy, activate, and configure Edge Functions. Pass the
|
|
34
|
+
- `edge_functions`: list, read immutable source, deploy, activate, and configure Edge Functions. Pass the non-negative version read from `list` as `--expected-active-version`; use `absent` only for a new slug. Version `0` is a legacy CAS token and cannot be used as a source or activation target.
|
|
35
35
|
- `frontend`: list, build/deploy, domain, and deployment workflows.
|
|
36
36
|
- `secrets`: project secret management; never print values after write.
|
|
37
37
|
- `queue`, `task_events`, `diagnostics`: asynchronous workload operations and bounded diagnostics.
|