@supacloud/cli 0.23.0 → 0.25.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 +32 -2
- package/dist/index.js +65 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -111,18 +111,48 @@ loopback development origins, with the default `:80` likewise omitted. Use
|
|
|
111
111
|
`release` is an official CLI entry point for the existing Management API
|
|
112
112
|
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
|
-
Management authority.
|
|
115
|
-
|
|
114
|
+
Management authority.
|
|
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
|
+
```
|
|
116
131
|
|
|
117
132
|
```bash
|
|
118
133
|
supacloud-cli release logical_backup_list --ref abc123
|
|
119
134
|
supacloud-cli release logical_backup_create --ref abc123
|
|
135
|
+
supacloud-cli release logical_backup_restore --ref abc123 \
|
|
136
|
+
--backup_id logical-full_abc123_<backup-id-suffix> \
|
|
137
|
+
--expected_sha256 <64-lowercase-hex> \
|
|
138
|
+
--restore_confirmation RESTORE_PROJECT:abc123:logical-full_abc123_<backup-id-suffix>:<64-lowercase-hex>
|
|
120
139
|
supacloud-cli release postgrest_status --ref abc123
|
|
121
140
|
supacloud-cli release postgrest_restart --ref abc123
|
|
122
141
|
```
|
|
123
142
|
|
|
124
143
|
Backup creation reports success only after the CLI verifies exactly one new
|
|
125
144
|
logical-backup receipt against the inventory before and after the mutation.
|
|
145
|
+
Logical restore is project-scoped: pause the selected project first, obtain the
|
|
146
|
+
backup ID and SHA-256 from `logical_backup_list`, and supply both the normal
|
|
147
|
+
production `--confirm-production <ref>` value (for production profiles) and
|
|
148
|
+
the exact `--restore_confirmation
|
|
149
|
+
RESTORE_PROJECT:<ref>:<backup_id>:<sha256>`. Before POST, the CLI re-reads
|
|
150
|
+
that same project's inventory and binds the request to the complete verified
|
|
151
|
+
backup identity; it then verifies both the server receipt and a fresh inventory
|
|
152
|
+
read. It never retries a restore. A transport, server, or unreadable-response
|
|
153
|
+
failure is reported as `OUTCOME_UNKNOWN`; read the inventory and investigate
|
|
154
|
+
before any new restore decision.
|
|
155
|
+
|
|
126
156
|
PostgREST restart reports success only after it receives a matching restart
|
|
127
157
|
receipt and reads back `desired=running`, `actual=running`, and
|
|
128
158
|
`health=healthy`. Both mutating controls follow the normal production
|
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"],
|
|
@@ -6486,7 +6486,7 @@ var ACTION_POLICY = {
|
|
|
6486
6486
|
mutations: { read: ["status"] },
|
|
6487
6487
|
release: {
|
|
6488
6488
|
read: ["logical_backup_list", "postgrest_status"],
|
|
6489
|
-
write: ["logical_backup_create", "postgrest_restart"]
|
|
6489
|
+
write: ["logical_backup_create", "logical_backup_restore", "postgrest_restart"]
|
|
6490
6490
|
},
|
|
6491
6491
|
secrets: { read: ["list"], write: ["upsert", "delete"] },
|
|
6492
6492
|
frontend: {
|
|
@@ -10098,6 +10098,7 @@ var formatTaskStats = (data) => {
|
|
|
10098
10098
|
`);
|
|
10099
10099
|
};
|
|
10100
10100
|
var ok = (res) => res.ok ? JSON.stringify(res.data, null, 2) : `❌ Failed (${res.status}): ${JSON.stringify(res.data)}`;
|
|
10101
|
+
var simple = (res, msg) => res.ok ? `✅ ${msg}` : `❌ Failed (${res.status})`;
|
|
10101
10102
|
function buildProjectLogsPath(ref, logType) {
|
|
10102
10103
|
const params = new URLSearchParams({ limit: "200" });
|
|
10103
10104
|
if (logType && logType !== "all") {
|
|
@@ -10114,9 +10115,11 @@ function resolveRef(refFromArgs, defaultRef) {
|
|
|
10114
10115
|
function registerUserProjectCliTools(server, http, options = {}) {
|
|
10115
10116
|
const { projectRef } = options;
|
|
10116
10117
|
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`, {
|
|
10118
|
+
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
10119
|
action: withDescription(stringEnum([
|
|
10119
10120
|
"get",
|
|
10121
|
+
"pause",
|
|
10122
|
+
"restore",
|
|
10120
10123
|
"health",
|
|
10121
10124
|
"logs",
|
|
10122
10125
|
"api_keys",
|
|
@@ -10144,6 +10147,12 @@ Actions: get, health, logs, api_keys, settings, tasks, task_detail, task_cancel,
|
|
|
10144
10147
|
return projectReadResponse(projectGetRead(await http.get(`/v1/projects/${resolvedRef}`, {
|
|
10145
10148
|
maxResponseBytes: PROJECT_READ_RESPONSE_MAX_BYTES
|
|
10146
10149
|
}), resolvedRef));
|
|
10150
|
+
case "pause":
|
|
10151
|
+
text = simple(await http.post(`/v1/projects/${resolvedRef}/pause`), `Project ${resolvedRef} paused`);
|
|
10152
|
+
break;
|
|
10153
|
+
case "restore":
|
|
10154
|
+
text = simple(await http.post(`/v1/projects/${resolvedRef}/restore`), `Project ${resolvedRef} restored`);
|
|
10155
|
+
break;
|
|
10147
10156
|
case "health":
|
|
10148
10157
|
text = ok(await http.get(`/v1/projects/${resolvedRef}/health`));
|
|
10149
10158
|
break;
|
|
@@ -10540,7 +10549,7 @@ var redirectStatus = Type.Optional(Type.Union([
|
|
|
10540
10549
|
Type.Literal(308)
|
|
10541
10550
|
]));
|
|
10542
10551
|
var ok2 = (res) => res.ok ? JSON.stringify(res.data, null, 2) : `❌ Failed (${res.status}): ${JSON.stringify(res.data)}`;
|
|
10543
|
-
var
|
|
10552
|
+
var simple2 = (res, msg) => res.ok ? `✅ ${msg}` : `❌ Failed (${res.status}): ${JSON.stringify(res.data)}`;
|
|
10544
10553
|
function registerGatewayTools(server, http, options = {}) {
|
|
10545
10554
|
const { projectRef } = options;
|
|
10546
10555
|
server.tool("gateway", `Gateway / Caddy 配置(通过 JSON Admin API 注入)。要求 admin 权限。
|
|
@@ -10802,11 +10811,11 @@ Actions: routes, upsert_route, update_route, delete_route, config, get_certifica
|
|
|
10802
10811
|
break;
|
|
10803
10812
|
case "set_custom_hostname": {
|
|
10804
10813
|
need("custom_hostname", custom_hostname);
|
|
10805
|
-
text =
|
|
10814
|
+
text = simple2(await http.post(`/v1/projects/${projectRefValue}/custom-hostname`, { custom_hostname }), `Custom hostname ${custom_hostname} requested`);
|
|
10806
10815
|
break;
|
|
10807
10816
|
}
|
|
10808
10817
|
case "delete_custom_hostname":
|
|
10809
|
-
text =
|
|
10818
|
+
text = simple2(await http.delete(`/v1/projects/${projectRefValue}/custom-hostname`), "Custom hostname removed");
|
|
10810
10819
|
break;
|
|
10811
10820
|
case "verify_custom_hostname":
|
|
10812
10821
|
text = ok2(await http.post(`/v1/projects/${projectRefValue}/custom-hostname/verify`));
|
|
@@ -12277,6 +12286,19 @@ function newlyCreatedBackup(before, after) {
|
|
|
12277
12286
|
const additions = after.filter((backup) => !known.has(backup.backup_id));
|
|
12278
12287
|
return additions.length === 1 ? additions[0] : null;
|
|
12279
12288
|
}
|
|
12289
|
+
function restoreRequest(projectRef, backupId, expectedSha256, restoreConfirmation) {
|
|
12290
|
+
if (typeof backupId !== "string" || !backupBelongsToProject(backupId, projectRef)) {
|
|
12291
|
+
throw new Error("'backup_id' must identify a logical-full backup for 'ref'");
|
|
12292
|
+
}
|
|
12293
|
+
if (typeof expectedSha256 !== "string" || !SHA256.test(expectedSha256)) {
|
|
12294
|
+
throw new Error("'expected_sha256' must be a lowercase SHA-256 digest");
|
|
12295
|
+
}
|
|
12296
|
+
const confirmation = `RESTORE_PROJECT:${projectRef}:${backupId}:${expectedSha256}`;
|
|
12297
|
+
if (restoreConfirmation !== confirmation) {
|
|
12298
|
+
throw new Error("'restore_confirmation' must exactly confirm the selected logical backup restore");
|
|
12299
|
+
}
|
|
12300
|
+
return { backup_id: backupId, expected_sha256: expectedSha256, confirmation };
|
|
12301
|
+
}
|
|
12280
12302
|
function endpoint(projectRef) {
|
|
12281
12303
|
if (!validProjectRef(projectRef))
|
|
12282
12304
|
throw new Error("'ref' is invalid for release controls");
|
|
@@ -12334,15 +12356,19 @@ function isRestartReceipt(value) {
|
|
|
12334
12356
|
return isRecord(value) && value.service === "postgrest" && value.action === "restart" && value.success === true;
|
|
12335
12357
|
}
|
|
12336
12358
|
function registerReleaseTools(server, http, options = {}) {
|
|
12337
|
-
server.tool("release", "Verified release controls using a Management API credential. Actions: logical_backup_list, logical_backup_create, postgrest_status, postgrest_restart", {
|
|
12359
|
+
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", {
|
|
12338
12360
|
action: withDescription(stringEnum([
|
|
12339
12361
|
"logical_backup_list",
|
|
12340
12362
|
"logical_backup_create",
|
|
12363
|
+
"logical_backup_restore",
|
|
12341
12364
|
"postgrest_status",
|
|
12342
12365
|
"postgrest_restart"
|
|
12343
12366
|
]), "Release control action"),
|
|
12344
|
-
ref: optional(Type.String(), options.projectRef ? "Optional override when not auto-linked" : "Project ref")
|
|
12345
|
-
|
|
12367
|
+
ref: optional(Type.String(), options.projectRef ? "Optional override when not auto-linked" : "Project ref"),
|
|
12368
|
+
backup_id: optional(Type.String(), "[logical_backup_restore] Exact verified logical-full backup ID from the selected project inventory"),
|
|
12369
|
+
expected_sha256: optional(Type.String(), "[logical_backup_restore] Exact lowercase SHA-256 from the selected project inventory"),
|
|
12370
|
+
restore_confirmation: optional(Type.String(), "[logical_backup_restore] Exact RESTORE_PROJECT:<ref>:<backup_id>:<sha256> confirmation")
|
|
12371
|
+
}, async ({ action, ref, backup_id, expected_sha256, restore_confirmation }) => {
|
|
12346
12372
|
const projectRef = typeof ref === "string" && ref || options.projectRef;
|
|
12347
12373
|
if (!projectRef)
|
|
12348
12374
|
throw new Error("'ref' is required for release controls");
|
|
@@ -12379,6 +12405,32 @@ function registerReleaseTools(server, http, options = {}) {
|
|
|
12379
12405
|
backup: publicBackup(addedBackup)
|
|
12380
12406
|
});
|
|
12381
12407
|
}
|
|
12408
|
+
if (action === "logical_backup_restore") {
|
|
12409
|
+
const request = restoreRequest(projectRef, backup_id, expected_sha256, restore_confirmation);
|
|
12410
|
+
const before = await readInventory(http, projectRef);
|
|
12411
|
+
const beforeFailure = readInventoryFailure("release.logical_backup.restore", before);
|
|
12412
|
+
if (beforeFailure)
|
|
12413
|
+
return beforeFailure;
|
|
12414
|
+
const selectedBackup = before.inventory.find((backup) => backup.backup_id === request.backup_id && backup.sha256 === request.expected_sha256);
|
|
12415
|
+
if (!selectedBackup) {
|
|
12416
|
+
return releaseControlFailure("release.logical_backup.restore", "MUTATION_NOT_SUCCEEDED", null);
|
|
12417
|
+
}
|
|
12418
|
+
const mutation2 = await http.postReleaseMutation(`${endpoint(projectRef)}/database/backups/logical/restore`, request, { timeoutMs: BACKUP_TIMEOUT_MS });
|
|
12419
|
+
if (!mutation2.ok || mutation2.status !== 200) {
|
|
12420
|
+
return mutationFailure("release.logical_backup.restore", mutation2);
|
|
12421
|
+
}
|
|
12422
|
+
const responseBackup = isRecord(mutation2.data) ? verifiedBackup(mutation2.data.restored_backup, projectRef) : null;
|
|
12423
|
+
const after = await readInventory(http, projectRef);
|
|
12424
|
+
const afterFailure = readInventoryFailure("release.logical_backup.restore", after);
|
|
12425
|
+
const restoredInventoryBackup = after.inventory?.find((backup) => backup.backup_id === request.backup_id);
|
|
12426
|
+
if (!responseBackup || !equalBackup(responseBackup, selectedBackup) || afterFailure || !restoredInventoryBackup || !equalBackup(restoredInventoryBackup, selectedBackup)) {
|
|
12427
|
+
return releaseControlFailure("release.logical_backup.restore", "OUTCOME_UNKNOWN", mutation2.status);
|
|
12428
|
+
}
|
|
12429
|
+
return releaseControlSuccess("release.logical_backup.restore", {
|
|
12430
|
+
project_ref: projectRef,
|
|
12431
|
+
backup: publicBackup(selectedBackup)
|
|
12432
|
+
});
|
|
12433
|
+
}
|
|
12382
12434
|
if (action === "postgrest_status") {
|
|
12383
12435
|
const read2 = await readPostgrestStatus(http, projectRef);
|
|
12384
12436
|
const failure = readPostgrestFailure("release.postgrest.status", read2);
|
|
@@ -12407,7 +12459,7 @@ function registerReleaseTools(server, http, options = {}) {
|
|
|
12407
12459
|
// package.json
|
|
12408
12460
|
var package_default = {
|
|
12409
12461
|
name: "@supacloud/cli",
|
|
12410
|
-
version: "0.
|
|
12462
|
+
version: "0.25.0",
|
|
12411
12463
|
description: "Project-scoped CLI for SupaCloud users",
|
|
12412
12464
|
type: "module",
|
|
12413
12465
|
main: "./dist/index.js",
|
|
@@ -12451,6 +12503,8 @@ var commandName = "supacloud-cli";
|
|
|
12451
12503
|
var preferredCommand = commandName;
|
|
12452
12504
|
var projectActionSchema = stringEnum([
|
|
12453
12505
|
"get",
|
|
12506
|
+
"pause",
|
|
12507
|
+
"restore",
|
|
12454
12508
|
"health",
|
|
12455
12509
|
"logs",
|
|
12456
12510
|
"api_keys",
|
|
@@ -12652,6 +12706,7 @@ EXAMPLES
|
|
|
12652
12706
|
${preferredCommand} project logs --log_type database
|
|
12653
12707
|
${preferredCommand} project task_stats
|
|
12654
12708
|
${preferredCommand} release logical_backup_create --ref abc123
|
|
12709
|
+
${preferredCommand} release logical_backup_restore --ref abc123 --backup_id <backup_id> --expected_sha256 <sha256> --restore_confirmation RESTORE_PROJECT:abc123:<backup_id>:<sha256>
|
|
12655
12710
|
${preferredCommand} release postgrest_status --ref abc123
|
|
12656
12711
|
${preferredCommand} release postgrest_restart --ref abc123
|
|
12657
12712
|
${preferredCommand} queue stats --queue emails
|