@supacloud/cli 0.23.0 → 0.24.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.
Files changed (3) hide show
  1. package/README.md +16 -2
  2. package/dist/index.js +49 -5
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -111,18 +111,32 @@ 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. Restore remains an admin-only operation and is not
115
- exposed by this command group.
114
+ Management authority.
116
115
 
117
116
  ```bash
118
117
  supacloud-cli release logical_backup_list --ref abc123
119
118
  supacloud-cli release logical_backup_create --ref abc123
119
+ supacloud-cli release logical_backup_restore --ref abc123 \
120
+ --backup_id logical-full_abc123_<backup-id-suffix> \
121
+ --expected_sha256 <64-lowercase-hex> \
122
+ --restore_confirmation RESTORE_PROJECT:abc123:logical-full_abc123_<backup-id-suffix>:<64-lowercase-hex>
120
123
  supacloud-cli release postgrest_status --ref abc123
121
124
  supacloud-cli release postgrest_restart --ref abc123
122
125
  ```
123
126
 
124
127
  Backup creation reports success only after the CLI verifies exactly one new
125
128
  logical-backup receipt against the inventory before and after the mutation.
129
+ Logical restore is project-scoped: pause the selected project first, obtain the
130
+ backup ID and SHA-256 from `logical_backup_list`, and supply both the normal
131
+ production `--confirm-production <ref>` value (for production profiles) and
132
+ the exact `--restore_confirmation
133
+ RESTORE_PROJECT:<ref>:<backup_id>:<sha256>`. Before POST, the CLI re-reads
134
+ that same project's inventory and binds the request to the complete verified
135
+ backup identity; it then verifies both the server receipt and a fresh inventory
136
+ read. It never retries a restore. A transport, server, or unreadable-response
137
+ failure is reported as `OUTCOME_UNKNOWN`; read the inventory and investigate
138
+ before any new restore decision.
139
+
126
140
  PostgREST restart reports success only after it receives a matching restart
127
141
  receipt and reads back `desired=running`, `actual=running`, and
128
142
  `health=healthy`. Both mutating controls follow the normal production
package/dist/index.js CHANGED
@@ -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: {
@@ -12277,6 +12277,19 @@ function newlyCreatedBackup(before, after) {
12277
12277
  const additions = after.filter((backup) => !known.has(backup.backup_id));
12278
12278
  return additions.length === 1 ? additions[0] : null;
12279
12279
  }
12280
+ function restoreRequest(projectRef, backupId, expectedSha256, restoreConfirmation) {
12281
+ if (typeof backupId !== "string" || !backupBelongsToProject(backupId, projectRef)) {
12282
+ throw new Error("'backup_id' must identify a logical-full backup for 'ref'");
12283
+ }
12284
+ if (typeof expectedSha256 !== "string" || !SHA256.test(expectedSha256)) {
12285
+ throw new Error("'expected_sha256' must be a lowercase SHA-256 digest");
12286
+ }
12287
+ const confirmation = `RESTORE_PROJECT:${projectRef}:${backupId}:${expectedSha256}`;
12288
+ if (restoreConfirmation !== confirmation) {
12289
+ throw new Error("'restore_confirmation' must exactly confirm the selected logical backup restore");
12290
+ }
12291
+ return { backup_id: backupId, expected_sha256: expectedSha256, confirmation };
12292
+ }
12280
12293
  function endpoint(projectRef) {
12281
12294
  if (!validProjectRef(projectRef))
12282
12295
  throw new Error("'ref' is invalid for release controls");
@@ -12334,15 +12347,19 @@ function isRestartReceipt(value) {
12334
12347
  return isRecord(value) && value.service === "postgrest" && value.action === "restart" && value.success === true;
12335
12348
  }
12336
12349
  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", {
12350
+ 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
12351
  action: withDescription(stringEnum([
12339
12352
  "logical_backup_list",
12340
12353
  "logical_backup_create",
12354
+ "logical_backup_restore",
12341
12355
  "postgrest_status",
12342
12356
  "postgrest_restart"
12343
12357
  ]), "Release control action"),
12344
- ref: optional(Type.String(), options.projectRef ? "Optional override when not auto-linked" : "Project ref")
12345
- }, async ({ action, ref }) => {
12358
+ ref: optional(Type.String(), options.projectRef ? "Optional override when not auto-linked" : "Project ref"),
12359
+ backup_id: optional(Type.String(), "[logical_backup_restore] Exact verified logical-full backup ID from the selected project inventory"),
12360
+ expected_sha256: optional(Type.String(), "[logical_backup_restore] Exact lowercase SHA-256 from the selected project inventory"),
12361
+ restore_confirmation: optional(Type.String(), "[logical_backup_restore] Exact RESTORE_PROJECT:<ref>:<backup_id>:<sha256> confirmation")
12362
+ }, async ({ action, ref, backup_id, expected_sha256, restore_confirmation }) => {
12346
12363
  const projectRef = typeof ref === "string" && ref || options.projectRef;
12347
12364
  if (!projectRef)
12348
12365
  throw new Error("'ref' is required for release controls");
@@ -12379,6 +12396,32 @@ function registerReleaseTools(server, http, options = {}) {
12379
12396
  backup: publicBackup(addedBackup)
12380
12397
  });
12381
12398
  }
12399
+ if (action === "logical_backup_restore") {
12400
+ const request = restoreRequest(projectRef, backup_id, expected_sha256, restore_confirmation);
12401
+ const before = await readInventory(http, projectRef);
12402
+ const beforeFailure = readInventoryFailure("release.logical_backup.restore", before);
12403
+ if (beforeFailure)
12404
+ return beforeFailure;
12405
+ const selectedBackup = before.inventory.find((backup) => backup.backup_id === request.backup_id && backup.sha256 === request.expected_sha256);
12406
+ if (!selectedBackup) {
12407
+ return releaseControlFailure("release.logical_backup.restore", "MUTATION_NOT_SUCCEEDED", null);
12408
+ }
12409
+ const mutation2 = await http.postReleaseMutation(`${endpoint(projectRef)}/database/backups/logical/restore`, request, { timeoutMs: BACKUP_TIMEOUT_MS });
12410
+ if (!mutation2.ok || mutation2.status !== 200) {
12411
+ return mutationFailure("release.logical_backup.restore", mutation2);
12412
+ }
12413
+ const responseBackup = isRecord(mutation2.data) ? verifiedBackup(mutation2.data.restored_backup, projectRef) : null;
12414
+ const after = await readInventory(http, projectRef);
12415
+ const afterFailure = readInventoryFailure("release.logical_backup.restore", after);
12416
+ const restoredInventoryBackup = after.inventory?.find((backup) => backup.backup_id === request.backup_id);
12417
+ if (!responseBackup || !equalBackup(responseBackup, selectedBackup) || afterFailure || !restoredInventoryBackup || !equalBackup(restoredInventoryBackup, selectedBackup)) {
12418
+ return releaseControlFailure("release.logical_backup.restore", "OUTCOME_UNKNOWN", mutation2.status);
12419
+ }
12420
+ return releaseControlSuccess("release.logical_backup.restore", {
12421
+ project_ref: projectRef,
12422
+ backup: publicBackup(selectedBackup)
12423
+ });
12424
+ }
12382
12425
  if (action === "postgrest_status") {
12383
12426
  const read2 = await readPostgrestStatus(http, projectRef);
12384
12427
  const failure = readPostgrestFailure("release.postgrest.status", read2);
@@ -12407,7 +12450,7 @@ function registerReleaseTools(server, http, options = {}) {
12407
12450
  // package.json
12408
12451
  var package_default = {
12409
12452
  name: "@supacloud/cli",
12410
- version: "0.23.0",
12453
+ version: "0.24.0",
12411
12454
  description: "Project-scoped CLI for SupaCloud users",
12412
12455
  type: "module",
12413
12456
  main: "./dist/index.js",
@@ -12652,6 +12695,7 @@ EXAMPLES
12652
12695
  ${preferredCommand} project logs --log_type database
12653
12696
  ${preferredCommand} project task_stats
12654
12697
  ${preferredCommand} release logical_backup_create --ref abc123
12698
+ ${preferredCommand} release logical_backup_restore --ref abc123 --backup_id <backup_id> --expected_sha256 <sha256> --restore_confirmation RESTORE_PROJECT:abc123:<backup_id>:<sha256>
12655
12699
  ${preferredCommand} release postgrest_status --ref abc123
12656
12700
  ${preferredCommand} release postgrest_restart --ref abc123
12657
12701
  ${preferredCommand} queue stats --queue emails
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/cli",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "description": "Project-scoped CLI for SupaCloud users",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",