@supacloud/cli 0.24.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/README.md +16 -0
- package/dist/index.js +298 -74
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -113,6 +113,22 @@ logical-backup and PostgREST lifecycle capabilities. It requires the Management
|
|
|
113
113
|
API context above; it does not promote an application `service_role` key to
|
|
114
114
|
Management authority.
|
|
115
115
|
|
|
116
|
+
Project pause and restore are explicit lifecycle commands. A logical backup
|
|
117
|
+
restore requires an operator to pause the selected project first and then use
|
|
118
|
+
`project get` to confirm that its status is `paused`. `project restore` resumes
|
|
119
|
+
the paused project lifecycle when its database exists; if the database is
|
|
120
|
+
missing, the platform begins project re-provisioning instead. It does not
|
|
121
|
+
restore a database backup and is never invoked automatically by the CLI.
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
supacloud-cli project pause --ref abc123
|
|
125
|
+
supacloud-cli release logical_backup_restore --ref abc123 \
|
|
126
|
+
--backup_id logical-full_abc123_<backup-id-suffix> \
|
|
127
|
+
--expected_sha256 <64-lowercase-hex> \
|
|
128
|
+
--restore_confirmation RESTORE_PROJECT:abc123:logical-full_abc123_<backup-id-suffix>:<64-lowercase-hex>
|
|
129
|
+
supacloud-cli project restore --ref abc123
|
|
130
|
+
```
|
|
131
|
+
|
|
116
132
|
```bash
|
|
117
133
|
supacloud-cli release logical_backup_list --ref abc123
|
|
118
134
|
supacloud-cli release logical_backup_create --ref abc123
|
package/dist/index.js
CHANGED
|
@@ -6459,7 +6459,7 @@ function resolveSupaCloudContext(env = process.env, cwd = process.cwd(), selecti
|
|
|
6459
6459
|
var ACTION_POLICY = {
|
|
6460
6460
|
project: {
|
|
6461
6461
|
read: ["get", "health", "logs", "api_keys", "settings", "tasks", "task_detail", "task_stats", "dlq", "background_settings"],
|
|
6462
|
-
write: ["task_cancel", "task_retry", "update_background_settings"]
|
|
6462
|
+
write: ["pause", "restore", "task_cancel", "task_retry", "update_background_settings"]
|
|
6463
6463
|
},
|
|
6464
6464
|
database: {
|
|
6465
6465
|
read: ["list_tables", "describe_columns", "list_indexes", "list_constraints", "list_extensions", "rls_status", "rls_policies", "list_auth_users", "get_auth_user", "connections", "stats", "slow_queries", "list_migrations", "migration_inventory", "project_url", "generate_types"],
|
|
@@ -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,
|
|
@@ -10098,6 +10310,7 @@ var formatTaskStats = (data) => {
|
|
|
10098
10310
|
`);
|
|
10099
10311
|
};
|
|
10100
10312
|
var ok = (res) => res.ok ? JSON.stringify(res.data, null, 2) : `❌ Failed (${res.status}): ${JSON.stringify(res.data)}`;
|
|
10313
|
+
var simple = (res, msg) => res.ok ? `✅ ${msg}` : `❌ Failed (${res.status})`;
|
|
10101
10314
|
function buildProjectLogsPath(ref, logType) {
|
|
10102
10315
|
const params = new URLSearchParams({ limit: "200" });
|
|
10103
10316
|
if (logType && logType !== "all") {
|
|
@@ -10112,11 +10325,13 @@ function resolveRef(refFromArgs, defaultRef) {
|
|
|
10112
10325
|
return ref;
|
|
10113
10326
|
}
|
|
10114
10327
|
function registerUserProjectCliTools(server, http, options = {}) {
|
|
10115
|
-
const { projectRef } = options;
|
|
10328
|
+
const { projectRef: projectRef2 } = options;
|
|
10116
10329
|
server.tool("project", `Project-scoped inspection and developer operations.
|
|
10117
|
-
Actions: get, health, logs, api_keys, settings, tasks, task_detail, task_cancel, task_retry, task_stats, dlq, background_settings, update_background_settings`, {
|
|
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`, {
|
|
10118
10331
|
action: withDescription(stringEnum([
|
|
10119
10332
|
"get",
|
|
10333
|
+
"pause",
|
|
10334
|
+
"restore",
|
|
10120
10335
|
"health",
|
|
10121
10336
|
"logs",
|
|
10122
10337
|
"api_keys",
|
|
@@ -10130,20 +10345,26 @@ Actions: get, health, logs, api_keys, settings, tasks, task_detail, task_cancel,
|
|
|
10130
10345
|
"background_settings",
|
|
10131
10346
|
"update_background_settings"
|
|
10132
10347
|
]), "Action to perform"),
|
|
10133
|
-
ref: optional(Type.String(),
|
|
10348
|
+
ref: optional(Type.String(), projectRef2 ? "Optional override when not auto-linked" : "Project ref"),
|
|
10134
10349
|
log_type: optional(stringEnum(["all", "auth", "database", "api"]), "[logs] Filter by service"),
|
|
10135
10350
|
task_id: optional(Type.String(), "[task_detail/task_cancel/task_retry] Task ID"),
|
|
10136
10351
|
limit: optional(Type.Number(), "[tasks/dlq] Max items to return"),
|
|
10137
10352
|
concurrency: optional(Type.Number(), "[update_background_settings] Max concurrent background tasks"),
|
|
10138
10353
|
max_attempts: optional(Type.Number(), "[update_background_settings] Max attempts for background tasks")
|
|
10139
10354
|
}, async ({ action, ref, log_type, task_id, limit, concurrency, max_attempts }) => {
|
|
10140
|
-
const resolvedRef = resolveRef(ref,
|
|
10355
|
+
const resolvedRef = resolveRef(ref, projectRef2);
|
|
10141
10356
|
let text;
|
|
10142
10357
|
switch (action) {
|
|
10143
10358
|
case "get":
|
|
10144
10359
|
return projectReadResponse(projectGetRead(await http.get(`/v1/projects/${resolvedRef}`, {
|
|
10145
10360
|
maxResponseBytes: PROJECT_READ_RESPONSE_MAX_BYTES
|
|
10146
10361
|
}), resolvedRef));
|
|
10362
|
+
case "pause":
|
|
10363
|
+
text = simple(await http.post(`/v1/projects/${resolvedRef}/pause`), `Project ${resolvedRef} paused`);
|
|
10364
|
+
break;
|
|
10365
|
+
case "restore":
|
|
10366
|
+
text = simple(await http.post(`/v1/projects/${resolvedRef}/restore`), `Project ${resolvedRef} restored`);
|
|
10367
|
+
break;
|
|
10147
10368
|
case "health":
|
|
10148
10369
|
text = ok(await http.get(`/v1/projects/${resolvedRef}/health`));
|
|
10149
10370
|
break;
|
|
@@ -10302,7 +10523,7 @@ function resolveRef2(refFromArgs, defaultRef) {
|
|
|
10302
10523
|
return ref;
|
|
10303
10524
|
}
|
|
10304
10525
|
function registerQueueTools(server, http, options = {}) {
|
|
10305
|
-
const { projectRef } = options;
|
|
10526
|
+
const { projectRef: projectRef2 } = options;
|
|
10306
10527
|
server.tool("queue", `Message queue operations for task-based messaging.
|
|
10307
10528
|
Actions: list, stats, list_messages, dlq, get_message, send, receive, ack, release, fail, retry, delete_message, get_settings, update_settings`, {
|
|
10308
10529
|
action: withDescription(stringEnum([
|
|
@@ -10342,7 +10563,7 @@ Actions: list, stats, list_messages, dlq, get_message, send, receive, ack, relea
|
|
|
10342
10563
|
max_attempts_setting: optional(Type.Number(), "[update_settings] Max delivery attempts"),
|
|
10343
10564
|
rate_limit: optional(Type.Number(), "[update_settings] Rate limit per minute")
|
|
10344
10565
|
}, async (args) => {
|
|
10345
|
-
const resolvedRef = resolveRef2(args.ref,
|
|
10566
|
+
const resolvedRef = resolveRef2(args.ref, projectRef2);
|
|
10346
10567
|
const q = args.queue;
|
|
10347
10568
|
const need = (fields) => {
|
|
10348
10569
|
for (const f of fields) {
|
|
@@ -10540,9 +10761,9 @@ var redirectStatus = Type.Optional(Type.Union([
|
|
|
10540
10761
|
Type.Literal(308)
|
|
10541
10762
|
]));
|
|
10542
10763
|
var ok2 = (res) => res.ok ? JSON.stringify(res.data, null, 2) : `❌ Failed (${res.status}): ${JSON.stringify(res.data)}`;
|
|
10543
|
-
var
|
|
10764
|
+
var simple2 = (res, msg) => res.ok ? `✅ ${msg}` : `❌ Failed (${res.status}): ${JSON.stringify(res.data)}`;
|
|
10544
10765
|
function registerGatewayTools(server, http, options = {}) {
|
|
10545
|
-
const { projectRef } = options;
|
|
10766
|
+
const { projectRef: projectRef2 } = options;
|
|
10546
10767
|
server.tool("gateway", `Gateway / Caddy 配置(通过 JSON Admin API 注入)。要求 admin 权限。
|
|
10547
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`, {
|
|
10548
10769
|
action: withDescription(stringEnum([
|
|
@@ -10561,7 +10782,7 @@ Actions: routes, upsert_route, update_route, delete_route, config, get_certifica
|
|
|
10561
10782
|
"delete_custom_hostname",
|
|
10562
10783
|
"verify_custom_hostname"
|
|
10563
10784
|
]), "Action"),
|
|
10564
|
-
ref: optional(Type.String(),
|
|
10785
|
+
ref: optional(Type.String(), projectRef2 ? "可选:覆盖自动关联的项目 ref" : "项目 ref"),
|
|
10565
10786
|
route_id: optional(Type.String(), "[upsert_route/update_route/delete_route] 路由 ID(字母/数字/_/-,1-64)"),
|
|
10566
10787
|
hosts: withDescription(stringArray, "[upsert_route/update_route] 主机名列表,逗号分隔或 JSON 数组(1-20)"),
|
|
10567
10788
|
paths: withDescription(stringArray, "[upsert_route/update_route] 路径列表,逗号分隔或 JSON 数组(1-32)"),
|
|
@@ -10596,7 +10817,7 @@ Actions: routes, upsert_route, update_route, delete_route, config, get_certifica
|
|
|
10596
10817
|
custom_hostname: optional(Type.String(), "[set_custom_hostname] 自定义域名")
|
|
10597
10818
|
}, async (args) => {
|
|
10598
10819
|
const resolveRef3 = (override) => {
|
|
10599
|
-
const ref2 = override ||
|
|
10820
|
+
const ref2 = override || projectRef2;
|
|
10600
10821
|
if (!ref2)
|
|
10601
10822
|
throw new Error("'ref' is required for this action");
|
|
10602
10823
|
return ref2;
|
|
@@ -10802,11 +11023,11 @@ Actions: routes, upsert_route, update_route, delete_route, config, get_certifica
|
|
|
10802
11023
|
break;
|
|
10803
11024
|
case "set_custom_hostname": {
|
|
10804
11025
|
need("custom_hostname", custom_hostname);
|
|
10805
|
-
text =
|
|
11026
|
+
text = simple2(await http.post(`/v1/projects/${projectRefValue}/custom-hostname`, { custom_hostname }), `Custom hostname ${custom_hostname} requested`);
|
|
10806
11027
|
break;
|
|
10807
11028
|
}
|
|
10808
11029
|
case "delete_custom_hostname":
|
|
10809
|
-
text =
|
|
11030
|
+
text = simple2(await http.delete(`/v1/projects/${projectRefValue}/custom-hostname`), "Custom hostname removed");
|
|
10810
11031
|
break;
|
|
10811
11032
|
case "verify_custom_hostname":
|
|
10812
11033
|
text = ok2(await http.post(`/v1/projects/${projectRefValue}/custom-hostname/verify`));
|
|
@@ -10819,8 +11040,8 @@ Actions: routes, upsert_route, update_route, delete_route, config, get_certifica
|
|
|
10819
11040
|
}
|
|
10820
11041
|
|
|
10821
11042
|
// src/shared/tools/branch-tools.ts
|
|
10822
|
-
function resolveProjectRef(ref,
|
|
10823
|
-
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 || "";
|
|
10824
11045
|
if (!resolved)
|
|
10825
11046
|
throw new Error("'ref' is required for this action");
|
|
10826
11047
|
return resolved;
|
|
@@ -11272,14 +11493,14 @@ async function executeMigrationPush(request, runtime) {
|
|
|
11272
11493
|
const pushMigrations = runtime.getPushMigrations?.();
|
|
11273
11494
|
if (!pushMigrations)
|
|
11274
11495
|
return missingMigrationContextResult();
|
|
11275
|
-
const
|
|
11276
|
-
if (!
|
|
11496
|
+
const projectRef2 = request.ref || runtime.projectRef;
|
|
11497
|
+
if (!projectRef2)
|
|
11277
11498
|
return missingProjectRefResult();
|
|
11278
11499
|
const workdir = resolveExistingWorkdir(request.workdir, runtime.fallbackWorkdir);
|
|
11279
11500
|
const migrationDirectory = resolve3(workdir, request.dir || "supabase/migrations");
|
|
11280
11501
|
const migrationResponse = await pushMigrations({
|
|
11281
11502
|
action: "push_migrations",
|
|
11282
|
-
ref:
|
|
11503
|
+
ref: projectRef2,
|
|
11283
11504
|
dir: migrationDirectory,
|
|
11284
11505
|
dry_run: request.dry_run
|
|
11285
11506
|
});
|
|
@@ -12214,7 +12435,7 @@ var INVENTORY_MAX_BYTES = 1024 * 1024;
|
|
|
12214
12435
|
var MUTATION_MAX_BYTES = 64 * 1024;
|
|
12215
12436
|
var BACKUP_TIMEOUT_MS = 36 * 60000;
|
|
12216
12437
|
var RELEASE_READ_RESPONSE_TIMEOUT_MS = 5000;
|
|
12217
|
-
function
|
|
12438
|
+
function isRecord2(value) {
|
|
12218
12439
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
12219
12440
|
}
|
|
12220
12441
|
function canonicalTimestamp3(value) {
|
|
@@ -12226,15 +12447,15 @@ function canonicalTimestamp3(value) {
|
|
|
12226
12447
|
function validProjectRef(ref) {
|
|
12227
12448
|
return SAFE_PROJECT_REF.test(ref);
|
|
12228
12449
|
}
|
|
12229
|
-
function backupBelongsToProject(backupId,
|
|
12230
|
-
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}_`);
|
|
12231
12452
|
}
|
|
12232
|
-
function verifiedBackup(value,
|
|
12233
|
-
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))
|
|
12234
12455
|
return null;
|
|
12235
12456
|
return {
|
|
12236
12457
|
backup_id: value.backup_id,
|
|
12237
|
-
project_ref:
|
|
12458
|
+
project_ref: projectRef2,
|
|
12238
12459
|
database: value.database,
|
|
12239
12460
|
kind: "logical-full",
|
|
12240
12461
|
created_at: value.created_at,
|
|
@@ -12243,10 +12464,10 @@ function verifiedBackup(value, projectRef) {
|
|
|
12243
12464
|
sha256: value.sha256
|
|
12244
12465
|
};
|
|
12245
12466
|
}
|
|
12246
|
-
function backupInventory(value,
|
|
12247
|
-
if (!
|
|
12467
|
+
function backupInventory(value, projectRef2) {
|
|
12468
|
+
if (!isRecord2(value) || !Array.isArray(value.backups))
|
|
12248
12469
|
return null;
|
|
12249
|
-
const backups = value.backups.map((backup) => verifiedBackup(backup,
|
|
12470
|
+
const backups = value.backups.map((backup) => verifiedBackup(backup, projectRef2));
|
|
12250
12471
|
if (backups.some((backup) => backup === null))
|
|
12251
12472
|
return null;
|
|
12252
12473
|
const inventory = backups;
|
|
@@ -12277,23 +12498,23 @@ function newlyCreatedBackup(before, after) {
|
|
|
12277
12498
|
const additions = after.filter((backup) => !known.has(backup.backup_id));
|
|
12278
12499
|
return additions.length === 1 ? additions[0] : null;
|
|
12279
12500
|
}
|
|
12280
|
-
function restoreRequest(
|
|
12281
|
-
if (typeof backupId !== "string" || !backupBelongsToProject(backupId,
|
|
12501
|
+
function restoreRequest(projectRef2, backupId, expectedSha256, restoreConfirmation) {
|
|
12502
|
+
if (typeof backupId !== "string" || !backupBelongsToProject(backupId, projectRef2)) {
|
|
12282
12503
|
throw new Error("'backup_id' must identify a logical-full backup for 'ref'");
|
|
12283
12504
|
}
|
|
12284
12505
|
if (typeof expectedSha256 !== "string" || !SHA256.test(expectedSha256)) {
|
|
12285
12506
|
throw new Error("'expected_sha256' must be a lowercase SHA-256 digest");
|
|
12286
12507
|
}
|
|
12287
|
-
const confirmation = `RESTORE_PROJECT:${
|
|
12508
|
+
const confirmation = `RESTORE_PROJECT:${projectRef2}:${backupId}:${expectedSha256}`;
|
|
12288
12509
|
if (restoreConfirmation !== confirmation) {
|
|
12289
12510
|
throw new Error("'restore_confirmation' must exactly confirm the selected logical backup restore");
|
|
12290
12511
|
}
|
|
12291
12512
|
return { backup_id: backupId, expected_sha256: expectedSha256, confirmation };
|
|
12292
12513
|
}
|
|
12293
|
-
function endpoint(
|
|
12294
|
-
if (!validProjectRef(
|
|
12514
|
+
function endpoint(projectRef2) {
|
|
12515
|
+
if (!validProjectRef(projectRef2))
|
|
12295
12516
|
throw new Error("'ref' is invalid for release controls");
|
|
12296
|
-
return `/v1/projects/${encodeURIComponent(
|
|
12517
|
+
return `/v1/projects/${encodeURIComponent(projectRef2)}`;
|
|
12297
12518
|
}
|
|
12298
12519
|
function httpFailure(operation, response) {
|
|
12299
12520
|
if (response.responseReadError) {
|
|
@@ -12307,12 +12528,12 @@ function mutationFailure(operation, response) {
|
|
|
12307
12528
|
}
|
|
12308
12529
|
return releaseControlFailure(operation, "HTTP_ERROR", response.status);
|
|
12309
12530
|
}
|
|
12310
|
-
async function readInventory(http,
|
|
12311
|
-
const response = await http.get(`${endpoint(
|
|
12531
|
+
async function readInventory(http, projectRef2) {
|
|
12532
|
+
const response = await http.get(`${endpoint(projectRef2)}/database/backups/logical`, {
|
|
12312
12533
|
maxJsonBytes: INVENTORY_MAX_BYTES,
|
|
12313
12534
|
responseTimeoutMs: RELEASE_READ_RESPONSE_TIMEOUT_MS
|
|
12314
12535
|
});
|
|
12315
|
-
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 };
|
|
12316
12537
|
}
|
|
12317
12538
|
function readInventoryFailure(operation, read) {
|
|
12318
12539
|
if (!read.response.ok)
|
|
@@ -12323,7 +12544,7 @@ function readInventoryFailure(operation, read) {
|
|
|
12323
12544
|
return null;
|
|
12324
12545
|
}
|
|
12325
12546
|
function postgrestStatus(value) {
|
|
12326
|
-
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)))
|
|
12327
12548
|
return null;
|
|
12328
12549
|
return {
|
|
12329
12550
|
desired: value.desired,
|
|
@@ -12331,8 +12552,8 @@ function postgrestStatus(value) {
|
|
|
12331
12552
|
health: value.health
|
|
12332
12553
|
};
|
|
12333
12554
|
}
|
|
12334
|
-
async function readPostgrestStatus(http,
|
|
12335
|
-
const response = await http.get(`${endpoint(
|
|
12555
|
+
async function readPostgrestStatus(http, projectRef2) {
|
|
12556
|
+
const response = await http.get(`${endpoint(projectRef2)}/services/postgrest/status`, {
|
|
12336
12557
|
maxJsonBytes: MUTATION_MAX_BYTES,
|
|
12337
12558
|
responseTimeoutMs: RELEASE_READ_RESPONSE_TIMEOUT_MS
|
|
12338
12559
|
});
|
|
@@ -12344,7 +12565,7 @@ function readPostgrestFailure(operation, read) {
|
|
|
12344
12565
|
return read.response.status === 200 && read.status ? null : releaseControlFailure(operation, "INVALID_RESPONSE", read.response.status);
|
|
12345
12566
|
}
|
|
12346
12567
|
function isRestartReceipt(value) {
|
|
12347
|
-
return
|
|
12568
|
+
return isRecord2(value) && value.service === "postgrest" && value.action === "restart" && value.success === true;
|
|
12348
12569
|
}
|
|
12349
12570
|
function registerReleaseTools(server, http, options = {}) {
|
|
12350
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", {
|
|
@@ -12360,45 +12581,45 @@ function registerReleaseTools(server, http, options = {}) {
|
|
|
12360
12581
|
expected_sha256: optional(Type.String(), "[logical_backup_restore] Exact lowercase SHA-256 from the selected project inventory"),
|
|
12361
12582
|
restore_confirmation: optional(Type.String(), "[logical_backup_restore] Exact RESTORE_PROJECT:<ref>:<backup_id>:<sha256> confirmation")
|
|
12362
12583
|
}, async ({ action, ref, backup_id, expected_sha256, restore_confirmation }) => {
|
|
12363
|
-
const
|
|
12364
|
-
if (!
|
|
12584
|
+
const projectRef2 = typeof ref === "string" && ref || options.projectRef;
|
|
12585
|
+
if (!projectRef2)
|
|
12365
12586
|
throw new Error("'ref' is required for release controls");
|
|
12366
|
-
if (!validProjectRef(
|
|
12587
|
+
if (!validProjectRef(projectRef2))
|
|
12367
12588
|
throw new Error("'ref' is invalid for release controls");
|
|
12368
12589
|
if (action === "logical_backup_list") {
|
|
12369
|
-
const read2 = await readInventory(http,
|
|
12590
|
+
const read2 = await readInventory(http, projectRef2);
|
|
12370
12591
|
const failure = readInventoryFailure("release.logical_backup.list", read2);
|
|
12371
12592
|
return failure ?? releaseControlSuccess("release.logical_backup.list", {
|
|
12372
|
-
project_ref:
|
|
12593
|
+
project_ref: projectRef2,
|
|
12373
12594
|
backups: read2.inventory.map(publicBackup)
|
|
12374
12595
|
});
|
|
12375
12596
|
}
|
|
12376
12597
|
if (action === "logical_backup_create") {
|
|
12377
|
-
const before = await readInventory(http,
|
|
12598
|
+
const before = await readInventory(http, projectRef2);
|
|
12378
12599
|
const beforeFailure = readInventoryFailure("release.logical_backup.create", before);
|
|
12379
12600
|
if (beforeFailure)
|
|
12380
12601
|
return beforeFailure;
|
|
12381
|
-
const mutation2 = await http.postReleaseMutation(`${endpoint(
|
|
12602
|
+
const mutation2 = await http.postReleaseMutation(`${endpoint(projectRef2)}/database/backups/logical`, {}, {
|
|
12382
12603
|
timeoutMs: BACKUP_TIMEOUT_MS
|
|
12383
12604
|
});
|
|
12384
|
-
const after = await readInventory(http,
|
|
12605
|
+
const after = await readInventory(http, projectRef2);
|
|
12385
12606
|
if (!mutation2.ok || mutation2.status !== 200) {
|
|
12386
12607
|
return mutationFailure("release.logical_backup.create", mutation2);
|
|
12387
12608
|
}
|
|
12388
|
-
const responseBackup =
|
|
12609
|
+
const responseBackup = isRecord2(mutation2.data) ? verifiedBackup(mutation2.data.backup, projectRef2) : null;
|
|
12389
12610
|
const afterFailure = readInventoryFailure("release.logical_backup.create", after);
|
|
12390
12611
|
const addedBackup = after.inventory && newlyCreatedBackup(before.inventory, after.inventory);
|
|
12391
12612
|
if (!responseBackup || afterFailure || !addedBackup || !equalBackup(responseBackup, addedBackup)) {
|
|
12392
12613
|
return releaseControlFailure("release.logical_backup.create", "OUTCOME_UNKNOWN", mutation2.status);
|
|
12393
12614
|
}
|
|
12394
12615
|
return releaseControlSuccess("release.logical_backup.create", {
|
|
12395
|
-
project_ref:
|
|
12616
|
+
project_ref: projectRef2,
|
|
12396
12617
|
backup: publicBackup(addedBackup)
|
|
12397
12618
|
});
|
|
12398
12619
|
}
|
|
12399
12620
|
if (action === "logical_backup_restore") {
|
|
12400
|
-
const request = restoreRequest(
|
|
12401
|
-
const before = await readInventory(http,
|
|
12621
|
+
const request = restoreRequest(projectRef2, backup_id, expected_sha256, restore_confirmation);
|
|
12622
|
+
const before = await readInventory(http, projectRef2);
|
|
12402
12623
|
const beforeFailure = readInventoryFailure("release.logical_backup.restore", before);
|
|
12403
12624
|
if (beforeFailure)
|
|
12404
12625
|
return beforeFailure;
|
|
@@ -12406,43 +12627,43 @@ function registerReleaseTools(server, http, options = {}) {
|
|
|
12406
12627
|
if (!selectedBackup) {
|
|
12407
12628
|
return releaseControlFailure("release.logical_backup.restore", "MUTATION_NOT_SUCCEEDED", null);
|
|
12408
12629
|
}
|
|
12409
|
-
const mutation2 = await http.postReleaseMutation(`${endpoint(
|
|
12630
|
+
const mutation2 = await http.postReleaseMutation(`${endpoint(projectRef2)}/database/backups/logical/restore`, request, { timeoutMs: BACKUP_TIMEOUT_MS });
|
|
12410
12631
|
if (!mutation2.ok || mutation2.status !== 200) {
|
|
12411
12632
|
return mutationFailure("release.logical_backup.restore", mutation2);
|
|
12412
12633
|
}
|
|
12413
|
-
const responseBackup =
|
|
12414
|
-
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);
|
|
12415
12636
|
const afterFailure = readInventoryFailure("release.logical_backup.restore", after);
|
|
12416
12637
|
const restoredInventoryBackup = after.inventory?.find((backup) => backup.backup_id === request.backup_id);
|
|
12417
12638
|
if (!responseBackup || !equalBackup(responseBackup, selectedBackup) || afterFailure || !restoredInventoryBackup || !equalBackup(restoredInventoryBackup, selectedBackup)) {
|
|
12418
12639
|
return releaseControlFailure("release.logical_backup.restore", "OUTCOME_UNKNOWN", mutation2.status);
|
|
12419
12640
|
}
|
|
12420
12641
|
return releaseControlSuccess("release.logical_backup.restore", {
|
|
12421
|
-
project_ref:
|
|
12642
|
+
project_ref: projectRef2,
|
|
12422
12643
|
backup: publicBackup(selectedBackup)
|
|
12423
12644
|
});
|
|
12424
12645
|
}
|
|
12425
12646
|
if (action === "postgrest_status") {
|
|
12426
|
-
const read2 = await readPostgrestStatus(http,
|
|
12647
|
+
const read2 = await readPostgrestStatus(http, projectRef2);
|
|
12427
12648
|
const failure = readPostgrestFailure("release.postgrest.status", read2);
|
|
12428
12649
|
return failure ?? releaseControlSuccess("release.postgrest.status", {
|
|
12429
|
-
project_ref:
|
|
12650
|
+
project_ref: projectRef2,
|
|
12430
12651
|
postgrest: read2.status
|
|
12431
12652
|
});
|
|
12432
12653
|
}
|
|
12433
12654
|
if (action !== "postgrest_restart")
|
|
12434
12655
|
throw new Error("Unknown release control action");
|
|
12435
|
-
const mutation = await http.postReleaseMutation(`${endpoint(
|
|
12436
|
-
const read = await readPostgrestStatus(http,
|
|
12656
|
+
const mutation = await http.postReleaseMutation(`${endpoint(projectRef2)}/services/postgrest/restart`);
|
|
12657
|
+
const read = await readPostgrestStatus(http, projectRef2);
|
|
12437
12658
|
if (!mutation.ok || mutation.status !== 200) {
|
|
12438
12659
|
return mutationFailure("release.postgrest.restart", mutation);
|
|
12439
12660
|
}
|
|
12440
|
-
const
|
|
12441
|
-
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") {
|
|
12442
12663
|
return releaseControlFailure("release.postgrest.restart", "OUTCOME_UNKNOWN", mutation.status);
|
|
12443
12664
|
}
|
|
12444
12665
|
return releaseControlSuccess("release.postgrest.restart", {
|
|
12445
|
-
project_ref:
|
|
12666
|
+
project_ref: projectRef2,
|
|
12446
12667
|
postgrest: read.status
|
|
12447
12668
|
});
|
|
12448
12669
|
});
|
|
@@ -12450,7 +12671,7 @@ function registerReleaseTools(server, http, options = {}) {
|
|
|
12450
12671
|
// package.json
|
|
12451
12672
|
var package_default = {
|
|
12452
12673
|
name: "@supacloud/cli",
|
|
12453
|
-
version: "0.
|
|
12674
|
+
version: "0.26.0",
|
|
12454
12675
|
description: "Project-scoped CLI for SupaCloud users",
|
|
12455
12676
|
type: "module",
|
|
12456
12677
|
main: "./dist/index.js",
|
|
@@ -12494,6 +12715,8 @@ var commandName = "supacloud-cli";
|
|
|
12494
12715
|
var preferredCommand = commandName;
|
|
12495
12716
|
var projectActionSchema = stringEnum([
|
|
12496
12717
|
"get",
|
|
12718
|
+
"pause",
|
|
12719
|
+
"restore",
|
|
12497
12720
|
"health",
|
|
12498
12721
|
"logs",
|
|
12499
12722
|
"api_keys",
|
|
@@ -12790,7 +13013,7 @@ function createCliTools(context, confirmProduction) {
|
|
|
12790
13013
|
]
|
|
12791
13014
|
})
|
|
12792
13015
|
};
|
|
12793
|
-
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"]) {
|
|
12794
13017
|
tools[name] = {
|
|
12795
13018
|
schema: { action: genericActionSchema },
|
|
12796
13019
|
callback: async () => ({
|
|
@@ -12867,6 +13090,7 @@ function createCliTools(context, confirmProduction) {
|
|
|
12867
13090
|
pushMigrations = databaseTools.database?.callback;
|
|
12868
13091
|
assign(databaseTools);
|
|
12869
13092
|
assign(captureTools((server) => registerAuthTools(server, http)));
|
|
13093
|
+
assign(captureTools((server) => registerOAuthClientTools(server, http)));
|
|
12870
13094
|
assign(captureTools((server) => registerStorageTools(server, http)));
|
|
12871
13095
|
assign(captureTools((server) => registerAdvancedTools(server, http, process.env, {
|
|
12872
13096
|
readOnly: context.readOnly
|