@supacloud/cli 0.25.0 → 0.26.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/dist/index.js +282 -69
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6473,6 +6473,10 @@ var ACTION_POLICY = {
|
|
|
6473
6473
|
read: ["list_providers", "get_provider", "supported_providers", "get_settings", "get_config"],
|
|
6474
6474
|
write: ["configure_provider", "update_provider", "disable_provider", "wechat_mini", "wechat_open", "update_settings", "update_config"]
|
|
6475
6475
|
},
|
|
6476
|
+
oauth_clients: {
|
|
6477
|
+
read: ["list", "get"],
|
|
6478
|
+
write: ["create", "delete"]
|
|
6479
|
+
},
|
|
6476
6480
|
storage: {
|
|
6477
6481
|
read: ["status", "list_buckets", "get_bucket", "list_files"],
|
|
6478
6482
|
write: ["create_bucket", "update_bucket", "delete_bucket", "upload_base64", "delete_file"]
|
|
@@ -8170,6 +8174,214 @@ ${JSON.stringify(r.data, null, 2)}` : `❌ Failed (${r.status}): ${JSON.stringif
|
|
|
8170
8174
|
});
|
|
8171
8175
|
}
|
|
8172
8176
|
|
|
8177
|
+
// src/shared/tools/oauth-client-tools.ts
|
|
8178
|
+
var RELEASE_CANARY_CLIENT_NAME = "supacloud-release-canary";
|
|
8179
|
+
var CLIENT_ID_PATTERN = /^[A-Za-z0-9._~-]{1,256}$/;
|
|
8180
|
+
var MAX_CLIENT_LIST_BYTES = 256 * 1024;
|
|
8181
|
+
var READ_TIMEOUT_MS = 5000;
|
|
8182
|
+
function isRecord(value) {
|
|
8183
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
8184
|
+
}
|
|
8185
|
+
function releaseCanaryCallbackUri(value) {
|
|
8186
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
8187
|
+
throw new Error("'redirect_uri' is required");
|
|
8188
|
+
}
|
|
8189
|
+
let uri;
|
|
8190
|
+
try {
|
|
8191
|
+
uri = new URL(value);
|
|
8192
|
+
} catch {
|
|
8193
|
+
throw new Error("'redirect_uri' must be an absolute HTTPS or loopback HTTP URL");
|
|
8194
|
+
}
|
|
8195
|
+
const loopback = uri.hostname === "127.0.0.1" || uri.hostname === "[::1]";
|
|
8196
|
+
const isHttps = uri.protocol === "https:";
|
|
8197
|
+
const isPortBoundLoopback = uri.protocol === "http:" && loopback && Boolean(uri.port);
|
|
8198
|
+
if (!isHttps && !isPortBoundLoopback || !uri.hostname || uri.username || uri.password || uri.search || uri.hash) {
|
|
8199
|
+
throw new Error("'redirect_uri' must be an exact HTTPS callback or port-bound loopback HTTP callback without credentials, query, or fragment");
|
|
8200
|
+
}
|
|
8201
|
+
return uri.toString();
|
|
8202
|
+
}
|
|
8203
|
+
function createdClientId(value) {
|
|
8204
|
+
if (!isRecord(value))
|
|
8205
|
+
return null;
|
|
8206
|
+
try {
|
|
8207
|
+
return clientId(value.client_id);
|
|
8208
|
+
} catch {
|
|
8209
|
+
return null;
|
|
8210
|
+
}
|
|
8211
|
+
}
|
|
8212
|
+
function clientId(value) {
|
|
8213
|
+
if (typeof value !== "string" || !CLIENT_ID_PATTERN.test(value)) {
|
|
8214
|
+
throw new Error("'client_id' is invalid");
|
|
8215
|
+
}
|
|
8216
|
+
return value;
|
|
8217
|
+
}
|
|
8218
|
+
function projectRef(value) {
|
|
8219
|
+
if (typeof value !== "string" || !value.trim())
|
|
8220
|
+
throw new Error("'ref' is required");
|
|
8221
|
+
return projectRefPathSegment(value.trim(), "OAuth client");
|
|
8222
|
+
}
|
|
8223
|
+
function oauthClientsPath(ref) {
|
|
8224
|
+
return `/v1/projects/${encodeURIComponent(ref)}/auth/oauth-clients`;
|
|
8225
|
+
}
|
|
8226
|
+
function expectedClient(value, redirectUri) {
|
|
8227
|
+
if (!isRecord(value) || typeof value.client_id !== "string" || !CLIENT_ID_PATTERN.test(value.client_id) || value.client_name !== RELEASE_CANARY_CLIENT_NAME || value.client_type !== "public" || value.token_endpoint_auth_method !== "none" || !Array.isArray(value.redirect_uris) || value.redirect_uris.length !== 1 || !Array.isArray(value.grant_types) || value.grant_types.length !== 1 || value.grant_types[0] !== "authorization_code" || !Array.isArray(value.response_types) || value.response_types.length !== 1 || value.response_types[0] !== "code")
|
|
8228
|
+
return null;
|
|
8229
|
+
let callback;
|
|
8230
|
+
try {
|
|
8231
|
+
callback = releaseCanaryCallbackUri(value.redirect_uris[0]);
|
|
8232
|
+
} catch {
|
|
8233
|
+
return null;
|
|
8234
|
+
}
|
|
8235
|
+
if (redirectUri !== undefined && callback !== redirectUri)
|
|
8236
|
+
return null;
|
|
8237
|
+
return {
|
|
8238
|
+
client_id: value.client_id,
|
|
8239
|
+
client_name: RELEASE_CANARY_CLIENT_NAME,
|
|
8240
|
+
client_type: "public",
|
|
8241
|
+
token_endpoint_auth_method: "none",
|
|
8242
|
+
redirect_uris: [callback],
|
|
8243
|
+
grant_types: ["authorization_code"],
|
|
8244
|
+
response_types: ["code"]
|
|
8245
|
+
};
|
|
8246
|
+
}
|
|
8247
|
+
function clientInventory(value) {
|
|
8248
|
+
if (!isRecord(value) || !Array.isArray(value.clients))
|
|
8249
|
+
return null;
|
|
8250
|
+
const clients = value.clients.filter((client) => isRecord(client) && client.client_name === RELEASE_CANARY_CLIENT_NAME).map((client) => expectedClient(client));
|
|
8251
|
+
if (clients.some((client) => client === null))
|
|
8252
|
+
return null;
|
|
8253
|
+
const inventory = clients;
|
|
8254
|
+
return new Set(inventory.map((client) => client.client_id)).size === inventory.length ? inventory : null;
|
|
8255
|
+
}
|
|
8256
|
+
function readFailure(operation, response) {
|
|
8257
|
+
if (!response.ok) {
|
|
8258
|
+
return releaseControlFailure(operation, response.responseReadError ? "INVALID_RESPONSE" : "HTTP_ERROR", response.transportError ? null : response.status);
|
|
8259
|
+
}
|
|
8260
|
+
return null;
|
|
8261
|
+
}
|
|
8262
|
+
async function listClients(http, ref) {
|
|
8263
|
+
const response = await http.get(oauthClientsPath(ref), {
|
|
8264
|
+
maxJsonBytes: MAX_CLIENT_LIST_BYTES,
|
|
8265
|
+
responseTimeoutMs: READ_TIMEOUT_MS
|
|
8266
|
+
});
|
|
8267
|
+
return { response, clients: response.ok && response.status === 200 ? clientInventory(response.data) : null };
|
|
8268
|
+
}
|
|
8269
|
+
async function getClient(http, ref, id) {
|
|
8270
|
+
const response = await http.get(`${oauthClientsPath(ref)}/${encodeURIComponent(id)}`, {
|
|
8271
|
+
maxJsonBytes: MAX_CLIENT_LIST_BYTES,
|
|
8272
|
+
responseTimeoutMs: READ_TIMEOUT_MS
|
|
8273
|
+
});
|
|
8274
|
+
return { response, client: response.ok && response.status === 200 ? expectedClient(response.data) : null };
|
|
8275
|
+
}
|
|
8276
|
+
function exactSingleClient(inventory, redirectUri) {
|
|
8277
|
+
return inventory.length === 1 && inventory[0]?.redirect_uris[0] === redirectUri ? inventory[0] : null;
|
|
8278
|
+
}
|
|
8279
|
+
async function listReleaseCanaryClients(http, ref) {
|
|
8280
|
+
const read = await listClients(http, ref);
|
|
8281
|
+
const failure = readFailure("oauth_clients.list", read.response);
|
|
8282
|
+
if (failure)
|
|
8283
|
+
return failure;
|
|
8284
|
+
if (!read.clients)
|
|
8285
|
+
return releaseControlFailure("oauth_clients.list", "INVALID_RESPONSE", read.response.status);
|
|
8286
|
+
return releaseControlSuccess("oauth_clients.list", { project_ref: ref, clients: read.clients });
|
|
8287
|
+
}
|
|
8288
|
+
async function getReleaseCanaryClient(http, ref, id) {
|
|
8289
|
+
const read = await getClient(http, ref, id);
|
|
8290
|
+
const failure = readFailure("oauth_clients.get", read.response);
|
|
8291
|
+
if (failure)
|
|
8292
|
+
return failure;
|
|
8293
|
+
if (!read.client || read.client.client_id !== id) {
|
|
8294
|
+
return releaseControlFailure("oauth_clients.get", "INVALID_RESPONSE", read.response.status);
|
|
8295
|
+
}
|
|
8296
|
+
return releaseControlSuccess("oauth_clients.get", { project_ref: ref, client: read.client });
|
|
8297
|
+
}
|
|
8298
|
+
async function createReleaseCanaryClient(http, ref, redirectUri) {
|
|
8299
|
+
const before = await listClients(http, ref);
|
|
8300
|
+
const beforeFailure = readFailure("oauth_clients.create", before.response);
|
|
8301
|
+
if (beforeFailure)
|
|
8302
|
+
return beforeFailure;
|
|
8303
|
+
if (!before.clients)
|
|
8304
|
+
return releaseControlFailure("oauth_clients.create", "INVALID_RESPONSE", before.response.status);
|
|
8305
|
+
const existing = exactSingleClient(before.clients, redirectUri);
|
|
8306
|
+
if (existing) {
|
|
8307
|
+
return releaseControlSuccess("oauth_clients.create", {
|
|
8308
|
+
project_ref: ref,
|
|
8309
|
+
client: existing,
|
|
8310
|
+
reused: true
|
|
8311
|
+
});
|
|
8312
|
+
}
|
|
8313
|
+
if (before.clients.length > 0) {
|
|
8314
|
+
return releaseControlFailure("oauth_clients.create", "MUTATION_NOT_SUCCEEDED", null, { project_ref: ref });
|
|
8315
|
+
}
|
|
8316
|
+
const mutation = await http.postReleaseMutation(oauthClientsPath(ref), {
|
|
8317
|
+
client_type: "public",
|
|
8318
|
+
token_endpoint_auth_method: "none",
|
|
8319
|
+
redirect_uris: [redirectUri],
|
|
8320
|
+
grant_types: ["authorization_code"],
|
|
8321
|
+
client_name: RELEASE_CANARY_CLIENT_NAME
|
|
8322
|
+
});
|
|
8323
|
+
if (!mutation.ok) {
|
|
8324
|
+
return releaseControlMutationFailure("oauth_clients.create", mutation, { project_ref: ref });
|
|
8325
|
+
}
|
|
8326
|
+
const createdId = createdClientId(mutation.data);
|
|
8327
|
+
if (!createdId)
|
|
8328
|
+
return releaseControlFailure("oauth_clients.create", "OUTCOME_UNKNOWN", mutation.status, { project_ref: ref });
|
|
8329
|
+
const read = await getClient(http, ref, createdId);
|
|
8330
|
+
const readFailureResult = readFailure("oauth_clients.create", read.response);
|
|
8331
|
+
if (readFailureResult || !read.client || read.client.client_id !== createdId || read.client.redirect_uris[0] !== redirectUri) {
|
|
8332
|
+
return releaseControlFailure("oauth_clients.create", "OUTCOME_UNKNOWN", mutation.status, { project_ref: ref });
|
|
8333
|
+
}
|
|
8334
|
+
return releaseControlSuccess("oauth_clients.create", {
|
|
8335
|
+
project_ref: ref,
|
|
8336
|
+
client: read.client,
|
|
8337
|
+
reused: false
|
|
8338
|
+
});
|
|
8339
|
+
}
|
|
8340
|
+
async function deleteReleaseCanaryClient(http, ref, id, redirectUri) {
|
|
8341
|
+
const before = await getClient(http, ref, id);
|
|
8342
|
+
const beforeFailure = readFailure("oauth_clients.delete", before.response);
|
|
8343
|
+
if (beforeFailure)
|
|
8344
|
+
return beforeFailure;
|
|
8345
|
+
if (!before.client || before.client.client_id !== id || before.client.redirect_uris[0] !== redirectUri) {
|
|
8346
|
+
return releaseControlFailure("oauth_clients.delete", "MUTATION_NOT_SUCCEEDED", null, { project_ref: ref });
|
|
8347
|
+
}
|
|
8348
|
+
const mutation = await http.deleteReleaseMutation(`${oauthClientsPath(ref)}/${encodeURIComponent(id)}`);
|
|
8349
|
+
if (!mutation.ok || ![200, 204].includes(mutation.status)) {
|
|
8350
|
+
return releaseControlMutationFailure("oauth_clients.delete", mutation, { project_ref: ref });
|
|
8351
|
+
}
|
|
8352
|
+
const after = await listClients(http, ref);
|
|
8353
|
+
const afterFailure = readFailure("oauth_clients.delete", after.response);
|
|
8354
|
+
if (afterFailure || !after.clients || after.clients.some((client) => client.client_id === id)) {
|
|
8355
|
+
return releaseControlFailure("oauth_clients.delete", "OUTCOME_UNKNOWN", mutation.status, { project_ref: ref });
|
|
8356
|
+
}
|
|
8357
|
+
return releaseControlSuccess("oauth_clients.delete", {
|
|
8358
|
+
project_ref: ref,
|
|
8359
|
+
client_id: id,
|
|
8360
|
+
deleted: true
|
|
8361
|
+
});
|
|
8362
|
+
}
|
|
8363
|
+
function registerOAuthClientTools(server, http) {
|
|
8364
|
+
server.tool("oauth_clients", "Dedicated release-canary public OAuth client lifecycle. It only manages the exact supacloud-release-canary public authorization-code client and never returns client secrets.", {
|
|
8365
|
+
action: withDescription(stringEnum(["list", "get", "create", "delete"]), "OAuth client action"),
|
|
8366
|
+
ref: withDescription(Type.String(), "Central SupAuth project ref"),
|
|
8367
|
+
client_id: optional(Type.String(), "[get/delete] Exact release-canary public OAuth client ID"),
|
|
8368
|
+
redirect_uri: optional(Type.String(), "[create/delete] Exact HTTPS or port-bound RFC 8252 loopback callback")
|
|
8369
|
+
}, async ({ action, ref, client_id, redirect_uri }) => {
|
|
8370
|
+
const targetRef = projectRef(ref);
|
|
8371
|
+
if (action === "list")
|
|
8372
|
+
return listReleaseCanaryClients(http, targetRef);
|
|
8373
|
+
if (action === "get")
|
|
8374
|
+
return getReleaseCanaryClient(http, targetRef, clientId(client_id));
|
|
8375
|
+
if (action === "create") {
|
|
8376
|
+
return createReleaseCanaryClient(http, targetRef, releaseCanaryCallbackUri(redirect_uri));
|
|
8377
|
+
}
|
|
8378
|
+
if (action === "delete") {
|
|
8379
|
+
return deleteReleaseCanaryClient(http, targetRef, clientId(client_id), releaseCanaryCallbackUri(redirect_uri));
|
|
8380
|
+
}
|
|
8381
|
+
throw new Error("Unknown OAuth client action");
|
|
8382
|
+
});
|
|
8383
|
+
}
|
|
8384
|
+
|
|
8173
8385
|
// src/shared/tools/storage-tools.ts
|
|
8174
8386
|
var MAX_BUCKET_ID_LENGTH = 100;
|
|
8175
8387
|
var MAX_MIME_TYPE_COUNT = 100;
|
|
@@ -9184,13 +9396,13 @@ async function readFunctionSource(http, request) {
|
|
|
9184
9396
|
function mutationIdentityMatches2(receipt, expectation) {
|
|
9185
9397
|
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;
|
|
9186
9398
|
}
|
|
9187
|
-
async function readFunctionIdentity(http,
|
|
9188
|
-
const resourcePath = edgeFunctionResourcePath(
|
|
9399
|
+
async function readFunctionIdentity(http, projectRef2, slug) {
|
|
9400
|
+
const resourcePath = edgeFunctionResourcePath(projectRef2, slug);
|
|
9189
9401
|
const response = await http.get(`${resourcePath}/config`);
|
|
9190
9402
|
if (!response.ok) {
|
|
9191
9403
|
return releaseControlFailure("edge_functions.get_config", "HTTP_ERROR", response.status);
|
|
9192
9404
|
}
|
|
9193
|
-
const identity = projectedFunctionIdentity(response.data,
|
|
9405
|
+
const identity = projectedFunctionIdentity(response.data, projectRef2, slug);
|
|
9194
9406
|
return identity ? { content: [{ type: "text", text: JSON.stringify(identity, null, 2) }] } : releaseControlFailure("edge_functions.get_config", "INVALID_RESPONSE", response.status);
|
|
9195
9407
|
}
|
|
9196
9408
|
async function updateFunctionConfiguration(http, request) {
|
|
@@ -9253,15 +9465,15 @@ function readOnlyActivationResult() {
|
|
|
9253
9465
|
};
|
|
9254
9466
|
}
|
|
9255
9467
|
function functionActivationTarget(args) {
|
|
9256
|
-
const
|
|
9468
|
+
const projectRef2 = typeof args.ref === "string" ? args.ref.trim() : "";
|
|
9257
9469
|
const functionSlug = typeof args.slug === "string" ? args.slug.trim() : "";
|
|
9258
9470
|
const version = positiveFunctionVersion(args.version, "Function activation version");
|
|
9259
|
-
projectRefPathSegment(
|
|
9471
|
+
projectRefPathSegment(projectRef2, "Edge Function activation");
|
|
9260
9472
|
if (!SAFE_FUNCTION_SLUG_PATTERN.test(functionSlug))
|
|
9261
9473
|
throw new Error("'slug' is invalid for 'activate'");
|
|
9262
9474
|
const expectedActiveVersion = requiredExpectedActiveVersion(args, "activate");
|
|
9263
9475
|
const expectedActivationId = requiredExpectedActivationId(args, "activate");
|
|
9264
|
-
return { projectRef, functionSlug, version, expectedActiveVersion, expectedActivationId };
|
|
9476
|
+
return { projectRef: projectRef2, functionSlug, version, expectedActiveVersion, expectedActivationId };
|
|
9265
9477
|
}
|
|
9266
9478
|
function requiredExpectedActiveVersion(args, action) {
|
|
9267
9479
|
const expected = args["expected-active-version"];
|
|
@@ -9289,11 +9501,11 @@ async function activateFunctionVersion(http, args, readOnly = false) {
|
|
|
9289
9501
|
const unsupported = Object.keys(args).filter((name) => !FUNCTION_ACTIVATION_ARGUMENTS.has(name));
|
|
9290
9502
|
if (unsupported.length > 0)
|
|
9291
9503
|
throw new Error(`'${unsupported[0]}' is not supported for 'activate'`);
|
|
9292
|
-
const { projectRef, functionSlug, version, expectedActiveVersion, expectedActivationId } = functionActivationTarget(args);
|
|
9293
|
-
const endpoint = edgeFunctionResourcePath(
|
|
9504
|
+
const { projectRef: projectRef2, functionSlug, version, expectedActiveVersion, expectedActivationId } = functionActivationTarget(args);
|
|
9505
|
+
const endpoint = edgeFunctionResourcePath(projectRef2, functionSlug) + `/versions/${encodeURIComponent(version)}/activate`;
|
|
9294
9506
|
return functionMutationResponse({
|
|
9295
9507
|
operation: "edge_functions.activate",
|
|
9296
|
-
projectRef,
|
|
9508
|
+
projectRef: projectRef2,
|
|
9297
9509
|
slug: functionSlug,
|
|
9298
9510
|
expectedActiveVersion,
|
|
9299
9511
|
expectedActivationId,
|
|
@@ -10113,7 +10325,7 @@ function resolveRef(refFromArgs, defaultRef) {
|
|
|
10113
10325
|
return ref;
|
|
10114
10326
|
}
|
|
10115
10327
|
function registerUserProjectCliTools(server, http, options = {}) {
|
|
10116
|
-
const { projectRef } = options;
|
|
10328
|
+
const { projectRef: projectRef2 } = options;
|
|
10117
10329
|
server.tool("project", `Project-scoped inspection and developer operations.
|
|
10118
10330
|
Actions: get, pause, restore, health, logs, api_keys, settings, tasks, task_detail, task_cancel, task_retry, task_stats, dlq, background_settings, update_background_settings`, {
|
|
10119
10331
|
action: withDescription(stringEnum([
|
|
@@ -10133,14 +10345,14 @@ Actions: get, pause, restore, health, logs, api_keys, settings, tasks, task_deta
|
|
|
10133
10345
|
"background_settings",
|
|
10134
10346
|
"update_background_settings"
|
|
10135
10347
|
]), "Action to perform"),
|
|
10136
|
-
ref: optional(Type.String(),
|
|
10348
|
+
ref: optional(Type.String(), projectRef2 ? "Optional override when not auto-linked" : "Project ref"),
|
|
10137
10349
|
log_type: optional(stringEnum(["all", "auth", "database", "api"]), "[logs] Filter by service"),
|
|
10138
10350
|
task_id: optional(Type.String(), "[task_detail/task_cancel/task_retry] Task ID"),
|
|
10139
10351
|
limit: optional(Type.Number(), "[tasks/dlq] Max items to return"),
|
|
10140
10352
|
concurrency: optional(Type.Number(), "[update_background_settings] Max concurrent background tasks"),
|
|
10141
10353
|
max_attempts: optional(Type.Number(), "[update_background_settings] Max attempts for background tasks")
|
|
10142
10354
|
}, async ({ action, ref, log_type, task_id, limit, concurrency, max_attempts }) => {
|
|
10143
|
-
const resolvedRef = resolveRef(ref,
|
|
10355
|
+
const resolvedRef = resolveRef(ref, projectRef2);
|
|
10144
10356
|
let text;
|
|
10145
10357
|
switch (action) {
|
|
10146
10358
|
case "get":
|
|
@@ -10311,7 +10523,7 @@ function resolveRef2(refFromArgs, defaultRef) {
|
|
|
10311
10523
|
return ref;
|
|
10312
10524
|
}
|
|
10313
10525
|
function registerQueueTools(server, http, options = {}) {
|
|
10314
|
-
const { projectRef } = options;
|
|
10526
|
+
const { projectRef: projectRef2 } = options;
|
|
10315
10527
|
server.tool("queue", `Message queue operations for task-based messaging.
|
|
10316
10528
|
Actions: list, stats, list_messages, dlq, get_message, send, receive, ack, release, fail, retry, delete_message, get_settings, update_settings`, {
|
|
10317
10529
|
action: withDescription(stringEnum([
|
|
@@ -10351,7 +10563,7 @@ Actions: list, stats, list_messages, dlq, get_message, send, receive, ack, relea
|
|
|
10351
10563
|
max_attempts_setting: optional(Type.Number(), "[update_settings] Max delivery attempts"),
|
|
10352
10564
|
rate_limit: optional(Type.Number(), "[update_settings] Rate limit per minute")
|
|
10353
10565
|
}, async (args) => {
|
|
10354
|
-
const resolvedRef = resolveRef2(args.ref,
|
|
10566
|
+
const resolvedRef = resolveRef2(args.ref, projectRef2);
|
|
10355
10567
|
const q = args.queue;
|
|
10356
10568
|
const need = (fields) => {
|
|
10357
10569
|
for (const f of fields) {
|
|
@@ -10551,7 +10763,7 @@ var redirectStatus = Type.Optional(Type.Union([
|
|
|
10551
10763
|
var ok2 = (res) => res.ok ? JSON.stringify(res.data, null, 2) : `❌ Failed (${res.status}): ${JSON.stringify(res.data)}`;
|
|
10552
10764
|
var simple2 = (res, msg) => res.ok ? `✅ ${msg}` : `❌ Failed (${res.status}): ${JSON.stringify(res.data)}`;
|
|
10553
10765
|
function registerGatewayTools(server, http, options = {}) {
|
|
10554
|
-
const { projectRef } = options;
|
|
10766
|
+
const { projectRef: projectRef2 } = options;
|
|
10555
10767
|
server.tool("gateway", `Gateway / Caddy 配置(通过 JSON Admin API 注入)。要求 admin 权限。
|
|
10556
10768
|
Actions: routes, upsert_route, update_route, delete_route, config, get_certificate, update_certificate, issue_certificate, deploy_certificate, rebuild, custom_hostname, set_custom_hostname, delete_custom_hostname, verify_custom_hostname`, {
|
|
10557
10769
|
action: withDescription(stringEnum([
|
|
@@ -10570,7 +10782,7 @@ Actions: routes, upsert_route, update_route, delete_route, config, get_certifica
|
|
|
10570
10782
|
"delete_custom_hostname",
|
|
10571
10783
|
"verify_custom_hostname"
|
|
10572
10784
|
]), "Action"),
|
|
10573
|
-
ref: optional(Type.String(),
|
|
10785
|
+
ref: optional(Type.String(), projectRef2 ? "可选:覆盖自动关联的项目 ref" : "项目 ref"),
|
|
10574
10786
|
route_id: optional(Type.String(), "[upsert_route/update_route/delete_route] 路由 ID(字母/数字/_/-,1-64)"),
|
|
10575
10787
|
hosts: withDescription(stringArray, "[upsert_route/update_route] 主机名列表,逗号分隔或 JSON 数组(1-20)"),
|
|
10576
10788
|
paths: withDescription(stringArray, "[upsert_route/update_route] 路径列表,逗号分隔或 JSON 数组(1-32)"),
|
|
@@ -10605,7 +10817,7 @@ Actions: routes, upsert_route, update_route, delete_route, config, get_certifica
|
|
|
10605
10817
|
custom_hostname: optional(Type.String(), "[set_custom_hostname] 自定义域名")
|
|
10606
10818
|
}, async (args) => {
|
|
10607
10819
|
const resolveRef3 = (override) => {
|
|
10608
|
-
const ref2 = override ||
|
|
10820
|
+
const ref2 = override || projectRef2;
|
|
10609
10821
|
if (!ref2)
|
|
10610
10822
|
throw new Error("'ref' is required for this action");
|
|
10611
10823
|
return ref2;
|
|
@@ -10828,8 +11040,8 @@ Actions: routes, upsert_route, update_route, delete_route, config, get_certifica
|
|
|
10828
11040
|
}
|
|
10829
11041
|
|
|
10830
11042
|
// src/shared/tools/branch-tools.ts
|
|
10831
|
-
function resolveProjectRef(ref,
|
|
10832
|
-
const resolved = typeof ref === "string" && ref.trim() ? ref.trim() :
|
|
11043
|
+
function resolveProjectRef(ref, projectRef2) {
|
|
11044
|
+
const resolved = typeof ref === "string" && ref.trim() ? ref.trim() : projectRef2 || "";
|
|
10833
11045
|
if (!resolved)
|
|
10834
11046
|
throw new Error("'ref' is required for this action");
|
|
10835
11047
|
return resolved;
|
|
@@ -11281,14 +11493,14 @@ async function executeMigrationPush(request, runtime) {
|
|
|
11281
11493
|
const pushMigrations = runtime.getPushMigrations?.();
|
|
11282
11494
|
if (!pushMigrations)
|
|
11283
11495
|
return missingMigrationContextResult();
|
|
11284
|
-
const
|
|
11285
|
-
if (!
|
|
11496
|
+
const projectRef2 = request.ref || runtime.projectRef;
|
|
11497
|
+
if (!projectRef2)
|
|
11286
11498
|
return missingProjectRefResult();
|
|
11287
11499
|
const workdir = resolveExistingWorkdir(request.workdir, runtime.fallbackWorkdir);
|
|
11288
11500
|
const migrationDirectory = resolve3(workdir, request.dir || "supabase/migrations");
|
|
11289
11501
|
const migrationResponse = await pushMigrations({
|
|
11290
11502
|
action: "push_migrations",
|
|
11291
|
-
ref:
|
|
11503
|
+
ref: projectRef2,
|
|
11292
11504
|
dir: migrationDirectory,
|
|
11293
11505
|
dry_run: request.dry_run
|
|
11294
11506
|
});
|
|
@@ -12223,7 +12435,7 @@ var INVENTORY_MAX_BYTES = 1024 * 1024;
|
|
|
12223
12435
|
var MUTATION_MAX_BYTES = 64 * 1024;
|
|
12224
12436
|
var BACKUP_TIMEOUT_MS = 36 * 60000;
|
|
12225
12437
|
var RELEASE_READ_RESPONSE_TIMEOUT_MS = 5000;
|
|
12226
|
-
function
|
|
12438
|
+
function isRecord2(value) {
|
|
12227
12439
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
12228
12440
|
}
|
|
12229
12441
|
function canonicalTimestamp3(value) {
|
|
@@ -12235,15 +12447,15 @@ function canonicalTimestamp3(value) {
|
|
|
12235
12447
|
function validProjectRef(ref) {
|
|
12236
12448
|
return SAFE_PROJECT_REF.test(ref);
|
|
12237
12449
|
}
|
|
12238
|
-
function backupBelongsToProject(backupId,
|
|
12239
|
-
return BACKUP_ID.test(backupId) && backupId.startsWith(`logical-full_${
|
|
12450
|
+
function backupBelongsToProject(backupId, projectRef2) {
|
|
12451
|
+
return BACKUP_ID.test(backupId) && backupId.startsWith(`logical-full_${projectRef2}_`);
|
|
12240
12452
|
}
|
|
12241
|
-
function verifiedBackup(value,
|
|
12242
|
-
if (!
|
|
12453
|
+
function verifiedBackup(value, projectRef2) {
|
|
12454
|
+
if (!isRecord2(value) || typeof value.backup_id !== "string" || !backupBelongsToProject(value.backup_id, projectRef2) || value.project_ref !== projectRef2 || typeof value.database !== "string" || !SAFE_DATABASE.test(value.database) || value.kind !== "logical-full" || !canonicalTimestamp3(value.created_at) || !canonicalTimestamp3(value.completed_at) || new Date(value.completed_at).valueOf() < new Date(value.created_at).valueOf() || typeof value.bytes !== "number" || !Number.isSafeInteger(value.bytes) || value.bytes <= 0 || typeof value.sha256 !== "string" || !SHA256.test(value.sha256))
|
|
12243
12455
|
return null;
|
|
12244
12456
|
return {
|
|
12245
12457
|
backup_id: value.backup_id,
|
|
12246
|
-
project_ref:
|
|
12458
|
+
project_ref: projectRef2,
|
|
12247
12459
|
database: value.database,
|
|
12248
12460
|
kind: "logical-full",
|
|
12249
12461
|
created_at: value.created_at,
|
|
@@ -12252,10 +12464,10 @@ function verifiedBackup(value, projectRef) {
|
|
|
12252
12464
|
sha256: value.sha256
|
|
12253
12465
|
};
|
|
12254
12466
|
}
|
|
12255
|
-
function backupInventory(value,
|
|
12256
|
-
if (!
|
|
12467
|
+
function backupInventory(value, projectRef2) {
|
|
12468
|
+
if (!isRecord2(value) || !Array.isArray(value.backups))
|
|
12257
12469
|
return null;
|
|
12258
|
-
const backups = value.backups.map((backup) => verifiedBackup(backup,
|
|
12470
|
+
const backups = value.backups.map((backup) => verifiedBackup(backup, projectRef2));
|
|
12259
12471
|
if (backups.some((backup) => backup === null))
|
|
12260
12472
|
return null;
|
|
12261
12473
|
const inventory = backups;
|
|
@@ -12286,23 +12498,23 @@ function newlyCreatedBackup(before, after) {
|
|
|
12286
12498
|
const additions = after.filter((backup) => !known.has(backup.backup_id));
|
|
12287
12499
|
return additions.length === 1 ? additions[0] : null;
|
|
12288
12500
|
}
|
|
12289
|
-
function restoreRequest(
|
|
12290
|
-
if (typeof backupId !== "string" || !backupBelongsToProject(backupId,
|
|
12501
|
+
function restoreRequest(projectRef2, backupId, expectedSha256, restoreConfirmation) {
|
|
12502
|
+
if (typeof backupId !== "string" || !backupBelongsToProject(backupId, projectRef2)) {
|
|
12291
12503
|
throw new Error("'backup_id' must identify a logical-full backup for 'ref'");
|
|
12292
12504
|
}
|
|
12293
12505
|
if (typeof expectedSha256 !== "string" || !SHA256.test(expectedSha256)) {
|
|
12294
12506
|
throw new Error("'expected_sha256' must be a lowercase SHA-256 digest");
|
|
12295
12507
|
}
|
|
12296
|
-
const confirmation = `RESTORE_PROJECT:${
|
|
12508
|
+
const confirmation = `RESTORE_PROJECT:${projectRef2}:${backupId}:${expectedSha256}`;
|
|
12297
12509
|
if (restoreConfirmation !== confirmation) {
|
|
12298
12510
|
throw new Error("'restore_confirmation' must exactly confirm the selected logical backup restore");
|
|
12299
12511
|
}
|
|
12300
12512
|
return { backup_id: backupId, expected_sha256: expectedSha256, confirmation };
|
|
12301
12513
|
}
|
|
12302
|
-
function endpoint(
|
|
12303
|
-
if (!validProjectRef(
|
|
12514
|
+
function endpoint(projectRef2) {
|
|
12515
|
+
if (!validProjectRef(projectRef2))
|
|
12304
12516
|
throw new Error("'ref' is invalid for release controls");
|
|
12305
|
-
return `/v1/projects/${encodeURIComponent(
|
|
12517
|
+
return `/v1/projects/${encodeURIComponent(projectRef2)}`;
|
|
12306
12518
|
}
|
|
12307
12519
|
function httpFailure(operation, response) {
|
|
12308
12520
|
if (response.responseReadError) {
|
|
@@ -12316,12 +12528,12 @@ function mutationFailure(operation, response) {
|
|
|
12316
12528
|
}
|
|
12317
12529
|
return releaseControlFailure(operation, "HTTP_ERROR", response.status);
|
|
12318
12530
|
}
|
|
12319
|
-
async function readInventory(http,
|
|
12320
|
-
const response = await http.get(`${endpoint(
|
|
12531
|
+
async function readInventory(http, projectRef2) {
|
|
12532
|
+
const response = await http.get(`${endpoint(projectRef2)}/database/backups/logical`, {
|
|
12321
12533
|
maxJsonBytes: INVENTORY_MAX_BYTES,
|
|
12322
12534
|
responseTimeoutMs: RELEASE_READ_RESPONSE_TIMEOUT_MS
|
|
12323
12535
|
});
|
|
12324
|
-
return { response, inventory: response.ok && response.status === 200 ? backupInventory(response.data,
|
|
12536
|
+
return { response, inventory: response.ok && response.status === 200 ? backupInventory(response.data, projectRef2) : null };
|
|
12325
12537
|
}
|
|
12326
12538
|
function readInventoryFailure(operation, read) {
|
|
12327
12539
|
if (!read.response.ok)
|
|
@@ -12332,7 +12544,7 @@ function readInventoryFailure(operation, read) {
|
|
|
12332
12544
|
return null;
|
|
12333
12545
|
}
|
|
12334
12546
|
function postgrestStatus(value) {
|
|
12335
|
-
if (!
|
|
12547
|
+
if (!isRecord2(value) || value.component !== "postgrest" || !["running", "stopped"].includes(String(value.desired)) || !["running", "stopped", "starting", "error"].includes(String(value.actual)) || !["healthy", "unhealthy", "unknown"].includes(String(value.health)))
|
|
12336
12548
|
return null;
|
|
12337
12549
|
return {
|
|
12338
12550
|
desired: value.desired,
|
|
@@ -12340,8 +12552,8 @@ function postgrestStatus(value) {
|
|
|
12340
12552
|
health: value.health
|
|
12341
12553
|
};
|
|
12342
12554
|
}
|
|
12343
|
-
async function readPostgrestStatus(http,
|
|
12344
|
-
const response = await http.get(`${endpoint(
|
|
12555
|
+
async function readPostgrestStatus(http, projectRef2) {
|
|
12556
|
+
const response = await http.get(`${endpoint(projectRef2)}/services/postgrest/status`, {
|
|
12345
12557
|
maxJsonBytes: MUTATION_MAX_BYTES,
|
|
12346
12558
|
responseTimeoutMs: RELEASE_READ_RESPONSE_TIMEOUT_MS
|
|
12347
12559
|
});
|
|
@@ -12353,7 +12565,7 @@ function readPostgrestFailure(operation, read) {
|
|
|
12353
12565
|
return read.response.status === 200 && read.status ? null : releaseControlFailure(operation, "INVALID_RESPONSE", read.response.status);
|
|
12354
12566
|
}
|
|
12355
12567
|
function isRestartReceipt(value) {
|
|
12356
|
-
return
|
|
12568
|
+
return isRecord2(value) && value.service === "postgrest" && value.action === "restart" && value.success === true;
|
|
12357
12569
|
}
|
|
12358
12570
|
function registerReleaseTools(server, http, options = {}) {
|
|
12359
12571
|
server.tool("release", "Verified release controls using a Management API credential. Actions: logical_backup_list, logical_backup_create, logical_backup_restore, postgrest_status, postgrest_restart", {
|
|
@@ -12369,45 +12581,45 @@ function registerReleaseTools(server, http, options = {}) {
|
|
|
12369
12581
|
expected_sha256: optional(Type.String(), "[logical_backup_restore] Exact lowercase SHA-256 from the selected project inventory"),
|
|
12370
12582
|
restore_confirmation: optional(Type.String(), "[logical_backup_restore] Exact RESTORE_PROJECT:<ref>:<backup_id>:<sha256> confirmation")
|
|
12371
12583
|
}, async ({ action, ref, backup_id, expected_sha256, restore_confirmation }) => {
|
|
12372
|
-
const
|
|
12373
|
-
if (!
|
|
12584
|
+
const projectRef2 = typeof ref === "string" && ref || options.projectRef;
|
|
12585
|
+
if (!projectRef2)
|
|
12374
12586
|
throw new Error("'ref' is required for release controls");
|
|
12375
|
-
if (!validProjectRef(
|
|
12587
|
+
if (!validProjectRef(projectRef2))
|
|
12376
12588
|
throw new Error("'ref' is invalid for release controls");
|
|
12377
12589
|
if (action === "logical_backup_list") {
|
|
12378
|
-
const read2 = await readInventory(http,
|
|
12590
|
+
const read2 = await readInventory(http, projectRef2);
|
|
12379
12591
|
const failure = readInventoryFailure("release.logical_backup.list", read2);
|
|
12380
12592
|
return failure ?? releaseControlSuccess("release.logical_backup.list", {
|
|
12381
|
-
project_ref:
|
|
12593
|
+
project_ref: projectRef2,
|
|
12382
12594
|
backups: read2.inventory.map(publicBackup)
|
|
12383
12595
|
});
|
|
12384
12596
|
}
|
|
12385
12597
|
if (action === "logical_backup_create") {
|
|
12386
|
-
const before = await readInventory(http,
|
|
12598
|
+
const before = await readInventory(http, projectRef2);
|
|
12387
12599
|
const beforeFailure = readInventoryFailure("release.logical_backup.create", before);
|
|
12388
12600
|
if (beforeFailure)
|
|
12389
12601
|
return beforeFailure;
|
|
12390
|
-
const mutation2 = await http.postReleaseMutation(`${endpoint(
|
|
12602
|
+
const mutation2 = await http.postReleaseMutation(`${endpoint(projectRef2)}/database/backups/logical`, {}, {
|
|
12391
12603
|
timeoutMs: BACKUP_TIMEOUT_MS
|
|
12392
12604
|
});
|
|
12393
|
-
const after = await readInventory(http,
|
|
12605
|
+
const after = await readInventory(http, projectRef2);
|
|
12394
12606
|
if (!mutation2.ok || mutation2.status !== 200) {
|
|
12395
12607
|
return mutationFailure("release.logical_backup.create", mutation2);
|
|
12396
12608
|
}
|
|
12397
|
-
const responseBackup =
|
|
12609
|
+
const responseBackup = isRecord2(mutation2.data) ? verifiedBackup(mutation2.data.backup, projectRef2) : null;
|
|
12398
12610
|
const afterFailure = readInventoryFailure("release.logical_backup.create", after);
|
|
12399
12611
|
const addedBackup = after.inventory && newlyCreatedBackup(before.inventory, after.inventory);
|
|
12400
12612
|
if (!responseBackup || afterFailure || !addedBackup || !equalBackup(responseBackup, addedBackup)) {
|
|
12401
12613
|
return releaseControlFailure("release.logical_backup.create", "OUTCOME_UNKNOWN", mutation2.status);
|
|
12402
12614
|
}
|
|
12403
12615
|
return releaseControlSuccess("release.logical_backup.create", {
|
|
12404
|
-
project_ref:
|
|
12616
|
+
project_ref: projectRef2,
|
|
12405
12617
|
backup: publicBackup(addedBackup)
|
|
12406
12618
|
});
|
|
12407
12619
|
}
|
|
12408
12620
|
if (action === "logical_backup_restore") {
|
|
12409
|
-
const request = restoreRequest(
|
|
12410
|
-
const before = await readInventory(http,
|
|
12621
|
+
const request = restoreRequest(projectRef2, backup_id, expected_sha256, restore_confirmation);
|
|
12622
|
+
const before = await readInventory(http, projectRef2);
|
|
12411
12623
|
const beforeFailure = readInventoryFailure("release.logical_backup.restore", before);
|
|
12412
12624
|
if (beforeFailure)
|
|
12413
12625
|
return beforeFailure;
|
|
@@ -12415,43 +12627,43 @@ function registerReleaseTools(server, http, options = {}) {
|
|
|
12415
12627
|
if (!selectedBackup) {
|
|
12416
12628
|
return releaseControlFailure("release.logical_backup.restore", "MUTATION_NOT_SUCCEEDED", null);
|
|
12417
12629
|
}
|
|
12418
|
-
const mutation2 = await http.postReleaseMutation(`${endpoint(
|
|
12630
|
+
const mutation2 = await http.postReleaseMutation(`${endpoint(projectRef2)}/database/backups/logical/restore`, request, { timeoutMs: BACKUP_TIMEOUT_MS });
|
|
12419
12631
|
if (!mutation2.ok || mutation2.status !== 200) {
|
|
12420
12632
|
return mutationFailure("release.logical_backup.restore", mutation2);
|
|
12421
12633
|
}
|
|
12422
|
-
const responseBackup =
|
|
12423
|
-
const after = await readInventory(http,
|
|
12634
|
+
const responseBackup = isRecord2(mutation2.data) ? verifiedBackup(mutation2.data.restored_backup, projectRef2) : null;
|
|
12635
|
+
const after = await readInventory(http, projectRef2);
|
|
12424
12636
|
const afterFailure = readInventoryFailure("release.logical_backup.restore", after);
|
|
12425
12637
|
const restoredInventoryBackup = after.inventory?.find((backup) => backup.backup_id === request.backup_id);
|
|
12426
12638
|
if (!responseBackup || !equalBackup(responseBackup, selectedBackup) || afterFailure || !restoredInventoryBackup || !equalBackup(restoredInventoryBackup, selectedBackup)) {
|
|
12427
12639
|
return releaseControlFailure("release.logical_backup.restore", "OUTCOME_UNKNOWN", mutation2.status);
|
|
12428
12640
|
}
|
|
12429
12641
|
return releaseControlSuccess("release.logical_backup.restore", {
|
|
12430
|
-
project_ref:
|
|
12642
|
+
project_ref: projectRef2,
|
|
12431
12643
|
backup: publicBackup(selectedBackup)
|
|
12432
12644
|
});
|
|
12433
12645
|
}
|
|
12434
12646
|
if (action === "postgrest_status") {
|
|
12435
|
-
const read2 = await readPostgrestStatus(http,
|
|
12647
|
+
const read2 = await readPostgrestStatus(http, projectRef2);
|
|
12436
12648
|
const failure = readPostgrestFailure("release.postgrest.status", read2);
|
|
12437
12649
|
return failure ?? releaseControlSuccess("release.postgrest.status", {
|
|
12438
|
-
project_ref:
|
|
12650
|
+
project_ref: projectRef2,
|
|
12439
12651
|
postgrest: read2.status
|
|
12440
12652
|
});
|
|
12441
12653
|
}
|
|
12442
12654
|
if (action !== "postgrest_restart")
|
|
12443
12655
|
throw new Error("Unknown release control action");
|
|
12444
|
-
const mutation = await http.postReleaseMutation(`${endpoint(
|
|
12445
|
-
const read = await readPostgrestStatus(http,
|
|
12656
|
+
const mutation = await http.postReleaseMutation(`${endpoint(projectRef2)}/services/postgrest/restart`);
|
|
12657
|
+
const read = await readPostgrestStatus(http, projectRef2);
|
|
12446
12658
|
if (!mutation.ok || mutation.status !== 200) {
|
|
12447
12659
|
return mutationFailure("release.postgrest.restart", mutation);
|
|
12448
12660
|
}
|
|
12449
|
-
const
|
|
12450
|
-
if (!isRestartReceipt(mutation.data) ||
|
|
12661
|
+
const readFailure2 = readPostgrestFailure("release.postgrest.restart", read);
|
|
12662
|
+
if (!isRestartReceipt(mutation.data) || readFailure2 || read.status.desired !== "running" || read.status.actual !== "running" || read.status.health !== "healthy") {
|
|
12451
12663
|
return releaseControlFailure("release.postgrest.restart", "OUTCOME_UNKNOWN", mutation.status);
|
|
12452
12664
|
}
|
|
12453
12665
|
return releaseControlSuccess("release.postgrest.restart", {
|
|
12454
|
-
project_ref:
|
|
12666
|
+
project_ref: projectRef2,
|
|
12455
12667
|
postgrest: read.status
|
|
12456
12668
|
});
|
|
12457
12669
|
});
|
|
@@ -12459,7 +12671,7 @@ function registerReleaseTools(server, http, options = {}) {
|
|
|
12459
12671
|
// package.json
|
|
12460
12672
|
var package_default = {
|
|
12461
12673
|
name: "@supacloud/cli",
|
|
12462
|
-
version: "0.
|
|
12674
|
+
version: "0.26.0",
|
|
12463
12675
|
description: "Project-scoped CLI for SupaCloud users",
|
|
12464
12676
|
type: "module",
|
|
12465
12677
|
main: "./dist/index.js",
|
|
@@ -12801,7 +13013,7 @@ function createCliTools(context, confirmProduction) {
|
|
|
12801
13013
|
]
|
|
12802
13014
|
})
|
|
12803
13015
|
};
|
|
12804
|
-
for (const name of ["database", "auth", "storage", "edge_functions", "secrets", "frontend", "queue", "task_events", "scheduled_functions", "mutations", "diagnostics", "gateway", "branch", "release"]) {
|
|
13016
|
+
for (const name of ["database", "auth", "oauth_clients", "storage", "edge_functions", "secrets", "frontend", "queue", "task_events", "scheduled_functions", "mutations", "diagnostics", "gateway", "branch", "release"]) {
|
|
12805
13017
|
tools[name] = {
|
|
12806
13018
|
schema: { action: genericActionSchema },
|
|
12807
13019
|
callback: async () => ({
|
|
@@ -12878,6 +13090,7 @@ function createCliTools(context, confirmProduction) {
|
|
|
12878
13090
|
pushMigrations = databaseTools.database?.callback;
|
|
12879
13091
|
assign(databaseTools);
|
|
12880
13092
|
assign(captureTools((server) => registerAuthTools(server, http)));
|
|
13093
|
+
assign(captureTools((server) => registerOAuthClientTools(server, http)));
|
|
12881
13094
|
assign(captureTools((server) => registerStorageTools(server, http)));
|
|
12882
13095
|
assign(captureTools((server) => registerAdvancedTools(server, http, process.env, {
|
|
12883
13096
|
readOnly: context.readOnly
|