@supacloud/cli 0.22.0 → 0.23.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 +22 -0
  2. package/dist/index.js +276 -21
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -106,6 +106,28 @@ loopback development origins, with the default `:80` likewise omitted. Use
106
106
  `SUPACLOUD_PROJECT_REF` when it cannot be inferred from a managed
107
107
  `<ref>.api.*` application hostname.
108
108
 
109
+ ### Verified release controls
110
+
111
+ `release` is an official CLI entry point for the existing Management API
112
+ logical-backup and PostgREST lifecycle capabilities. It requires the Management
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.
116
+
117
+ ```bash
118
+ supacloud-cli release logical_backup_list --ref abc123
119
+ supacloud-cli release logical_backup_create --ref abc123
120
+ supacloud-cli release postgrest_status --ref abc123
121
+ supacloud-cli release postgrest_restart --ref abc123
122
+ ```
123
+
124
+ Backup creation reports success only after the CLI verifies exactly one new
125
+ logical-backup receipt against the inventory before and after the mutation.
126
+ PostgREST restart reports success only after it receives a matching restart
127
+ receipt and reads back `desired=running`, `actual=running`, and
128
+ `health=healthy`. Both mutating controls follow the normal production
129
+ confirmation and read-only protections.
130
+
109
131
  The legacy `.env` fallback is unclassified and therefore does not enable the
110
132
  production confirmation gate. Production automation must select a `prod` or
111
133
  `production` profile, or set `SUPACLOUD_ENV=production` together with a complete
package/dist/index.js CHANGED
@@ -6484,6 +6484,10 @@ var ACTION_POLICY = {
6484
6484
  },
6485
6485
  scheduled_functions: { read: ["list", "get"], write: ["create", "update", "delete"] },
6486
6486
  mutations: { read: ["status"] },
6487
+ release: {
6488
+ read: ["logical_backup_list", "postgrest_status"],
6489
+ write: ["logical_backup_create", "postgrest_restart"]
6490
+ },
6487
6491
  secrets: { read: ["list"], write: ["upsert", "delete"] },
6488
6492
  frontend: {
6489
6493
  read: ["list", "get", "build_logs", "list_frameworks", "list_records"],
@@ -6568,6 +6572,7 @@ function validateExecutionPolicyCoverage(tools) {
6568
6572
 
6569
6573
  // src/shared/transports/http.ts
6570
6574
  var DEFAULT_TIMEOUT = 30000;
6575
+ var MAX_POST_TIMEOUT_MS = 36 * 60000;
6571
6576
  var RELEASE_MUTATION_RESPONSE_TIMEOUT = 5000;
6572
6577
  var RELEASE_MUTATION_RESPONSE_MAX_BYTES = 64 * 1024;
6573
6578
  var MAX_RETRIES = 2;
@@ -6581,6 +6586,29 @@ function validatedGetResponseLimit(options) {
6581
6586
  }
6582
6587
  return maxBytes;
6583
6588
  }
6589
+ function validatedJsonResponseLimit(maxBytes) {
6590
+ if (maxBytes === undefined)
6591
+ return;
6592
+ if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) {
6593
+ throw new RangeError("HTTP JSON response limit must be a positive safe integer");
6594
+ }
6595
+ return maxBytes;
6596
+ }
6597
+ function validatedPostTimeout(options) {
6598
+ const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT;
6599
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > MAX_POST_TIMEOUT_MS) {
6600
+ throw new RangeError(`HTTP request timeout must be between 1 and ${MAX_POST_TIMEOUT_MS} ms`);
6601
+ }
6602
+ return timeoutMs;
6603
+ }
6604
+ function validatedResponseTimeout(timeoutMs) {
6605
+ if (timeoutMs === undefined)
6606
+ return;
6607
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > MAX_POST_TIMEOUT_MS) {
6608
+ throw new RangeError(`HTTP response timeout must be between 1 and ${MAX_POST_TIMEOUT_MS} ms`);
6609
+ }
6610
+ return timeoutMs;
6611
+ }
6584
6612
  function isRetryableMethod(method) {
6585
6613
  const normalizedMethod = (method ?? "GET").toUpperCase();
6586
6614
  return normalizedMethod === "GET" || normalizedMethod === "HEAD";
@@ -6609,9 +6637,9 @@ function responseReadFailure(status) {
6609
6637
  responseReadError: true
6610
6638
  };
6611
6639
  }
6612
- async function fetchWithTimeout(url, options) {
6640
+ async function fetchWithTimeout(url, options, timeoutMs = DEFAULT_TIMEOUT) {
6613
6641
  const controller = new AbortController;
6614
- const timeout = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT);
6642
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
6615
6643
  try {
6616
6644
  return await fetch(url, {
6617
6645
  ...options,
@@ -6622,11 +6650,11 @@ async function fetchWithTimeout(url, options) {
6622
6650
  clearTimeout(timeout);
6623
6651
  }
6624
6652
  }
6625
- async function fetchWithRetry(url, options) {
6653
+ async function fetchWithRetry(url, options, timeoutMs = DEFAULT_TIMEOUT) {
6626
6654
  const retries = isRetryableMethod(options.method) ? MAX_RETRIES : 0;
6627
6655
  for (let attempt = 0;attempt <= retries; attempt++) {
6628
6656
  try {
6629
- const res = await fetchWithTimeout(url, options);
6657
+ const res = await fetchWithTimeout(url, options, timeoutMs);
6630
6658
  if (res.status >= 500 && res.status < 600 && attempt < retries) {
6631
6659
  const delay = RETRY_BASE_DELAY * Math.pow(2, attempt);
6632
6660
  await new Promise((r) => setTimeout(r, delay));
@@ -6685,7 +6713,7 @@ async function responseBytesFromReader(reader, maxBytes, declaredBytes) {
6685
6713
  chunks.push(value);
6686
6714
  }
6687
6715
  }
6688
- async function responseBytesWithinLimit(response, maxBytes) {
6716
+ async function responseBytesWithinLimit(response, maxBytes, responseTimeoutMs) {
6689
6717
  if (declaredResponseTooLarge(response, maxBytes)) {
6690
6718
  await response.body?.cancel();
6691
6719
  return null;
@@ -6693,7 +6721,7 @@ async function responseBytesWithinLimit(response, maxBytes) {
6693
6721
  if (!response.body)
6694
6722
  return new Uint8Array;
6695
6723
  const reader = response.body.getReader();
6696
- const bodyRead = await responseBytesFromReader(reader, maxBytes, null);
6724
+ const bodyRead = responseTimeoutMs === undefined ? await responseBytesFromReader(reader, maxBytes, null) : await responseBytesBeforeDeadline(reader, maxBytes, null, responseTimeoutMs);
6697
6725
  return bodyRead.ok ? bodyRead.bytes : null;
6698
6726
  }
6699
6727
  function parsedUtf8Json(responseBytes) {
@@ -6706,8 +6734,8 @@ function parsedUtf8Json(responseBytes) {
6706
6734
  throw error;
6707
6735
  }
6708
6736
  }
6709
- async function boundedResponseJson(response, maxBytes) {
6710
- const responseBytes = await responseBytesWithinLimit(response, maxBytes);
6737
+ async function boundedResponseJson(response, maxBytes, responseTimeoutMs) {
6738
+ const responseBytes = await responseBytesWithinLimit(response, maxBytes, responseTimeoutMs);
6711
6739
  if (responseBytes === null)
6712
6740
  return null;
6713
6741
  const parsed = parsedUtf8Json(responseBytes);
@@ -6736,19 +6764,19 @@ async function releaseMutationResponseBytes(response) {
6736
6764
  if (!response.body) {
6737
6765
  return declaredBytes === null || declaredBytes === 0 ? { ok: true, bytes: new Uint8Array } : { ok: false };
6738
6766
  }
6739
- return responseBytesBeforeDeadline(response.body.getReader(), declaredBytes);
6767
+ return responseBytesBeforeDeadline(response.body.getReader(), RELEASE_MUTATION_RESPONSE_MAX_BYTES, declaredBytes, RELEASE_MUTATION_RESPONSE_TIMEOUT);
6740
6768
  }
6741
- async function responseBytesBeforeDeadline(reader, declaredBytes) {
6769
+ async function responseBytesBeforeDeadline(reader, maxBytes, declaredBytes, responseTimeoutMs) {
6742
6770
  let deadlineTimer;
6743
6771
  const deadline = new Promise((resolve2) => {
6744
6772
  deadlineTimer = setTimeout(() => {
6745
6773
  cancelResponseReader(reader);
6746
6774
  resolve2({ ok: false });
6747
- }, RELEASE_MUTATION_RESPONSE_TIMEOUT);
6775
+ }, responseTimeoutMs);
6748
6776
  });
6749
6777
  try {
6750
6778
  return await Promise.race([
6751
- responseBytesFromReader(reader, RELEASE_MUTATION_RESPONSE_MAX_BYTES, declaredBytes),
6779
+ responseBytesFromReader(reader, maxBytes, declaredBytes),
6752
6780
  deadline
6753
6781
  ]);
6754
6782
  } catch {
@@ -6782,13 +6810,13 @@ class HttpTransport {
6782
6810
  "Content-Type": "application/json"
6783
6811
  };
6784
6812
  }
6785
- async mutationWithResponseReader(method, path, serializedBody, responseReader) {
6813
+ async mutationWithResponseReader(method, path, serializedBody, responseReader, timeoutMs = DEFAULT_TIMEOUT) {
6786
6814
  try {
6787
6815
  const response = await fetchWithRetry(`${this.baseUrl}${path}`, {
6788
6816
  method,
6789
6817
  headers: this.headers(),
6790
6818
  body: serializedBody
6791
- });
6819
+ }, timeoutMs);
6792
6820
  const responseBody = await responseReader(response);
6793
6821
  return responseBody.ok ? { ok: response.ok, status: response.status, data: responseBody.parsedJson } : responseReadFailure(response.status);
6794
6822
  } catch (error) {
@@ -6797,26 +6825,47 @@ class HttpTransport {
6797
6825
  }
6798
6826
  async get(path, options = {}) {
6799
6827
  const maxResponseBytes = validatedGetResponseLimit(options);
6828
+ const maxJsonBytes = validatedJsonResponseLimit(options.maxJsonBytes);
6829
+ const responseTimeoutMs = validatedResponseTimeout(options.responseTimeoutMs);
6830
+ if (maxResponseBytes !== undefined && maxJsonBytes !== undefined) {
6831
+ throw new RangeError("HTTP response limit options are mutually exclusive");
6832
+ }
6800
6833
  try {
6801
6834
  const res = await fetchWithRetry(`${this.baseUrl}${path}`, {
6802
6835
  method: "GET",
6803
6836
  headers: this.headers()
6804
6837
  });
6838
+ if (maxJsonBytes !== undefined) {
6839
+ const data2 = await boundedResponseJson(res, maxJsonBytes, responseTimeoutMs);
6840
+ return data2 === null ? responseReadFailure(res.status) : { ok: res.ok, status: res.status, data: data2 };
6841
+ }
6805
6842
  const data = maxResponseBytes === undefined ? await res.json().catch(() => null) : await boundedResponseJson(res, maxResponseBytes);
6806
6843
  return { ok: res.ok, status: res.status, data };
6807
6844
  } catch (error) {
6808
6845
  return transportFailure(error);
6809
6846
  }
6810
6847
  }
6811
- async post(path, body) {
6848
+ async post(path, body, options) {
6849
+ const timeoutMs = validatedPostTimeout(options);
6850
+ const maxJsonBytes = validatedJsonResponseLimit(options?.maxJsonBytes);
6812
6851
  try {
6813
- return await this.mutationWithResponseReader("POST", path, serializedRequestBody(body), responseJsonOrNull);
6852
+ if (maxJsonBytes === undefined) {
6853
+ return await this.mutationWithResponseReader("POST", path, serializedRequestBody(body), responseJsonOrNull, timeoutMs);
6854
+ }
6855
+ const response = await fetchWithRetry(`${this.baseUrl}${path}`, {
6856
+ method: "POST",
6857
+ headers: this.headers(),
6858
+ body: serializedRequestBody(body)
6859
+ }, timeoutMs);
6860
+ const data = await boundedResponseJson(response, maxJsonBytes);
6861
+ return data === null ? responseReadFailure(response.status) : { ok: response.ok, status: response.status, data };
6814
6862
  } catch (error) {
6815
6863
  return transportFailure(error);
6816
6864
  }
6817
6865
  }
6818
- async postReleaseMutation(path, body) {
6819
- return this.mutationWithResponseReader("POST", path, serializedRequestBody(body), releaseMutationResponseJson);
6866
+ async postReleaseMutation(path, body, options) {
6867
+ const timeoutMs = validatedPostTimeout(options);
6868
+ return this.mutationWithResponseReader("POST", path, serializedRequestBody(body), releaseMutationResponseJson, timeoutMs);
6820
6869
  }
6821
6870
  async patchReleaseMutation(path, body) {
6822
6871
  return this.mutationWithResponseReader("PATCH", path, serializedRequestBody(body), releaseMutationResponseJson);
@@ -12155,10 +12204,210 @@ var MUTATION_TOOL_SCHEMA = {
12155
12204
  ref: withDescription(Type.String(), "[status] Project ref"),
12156
12205
  mutation_id: withDescription(Type.String(), "[status] Client mutation UUID")
12157
12206
  };
12207
+
12208
+ // src/shared/tools/release-tools.ts
12209
+ var SAFE_PROJECT_REF = /^[A-Za-z0-9_-]{1,64}$/;
12210
+ var BACKUP_ID = /^logical-full_[A-Za-z0-9_-]{1,64}_[a-f0-9]{32}$/;
12211
+ var SHA256 = /^[a-f0-9]{64}$/;
12212
+ var SAFE_DATABASE = /^[^\u0000-\u001f\u007f]{1,128}$/;
12213
+ var INVENTORY_MAX_BYTES = 1024 * 1024;
12214
+ var MUTATION_MAX_BYTES = 64 * 1024;
12215
+ var BACKUP_TIMEOUT_MS = 36 * 60000;
12216
+ var RELEASE_READ_RESPONSE_TIMEOUT_MS = 5000;
12217
+ function isRecord(value) {
12218
+ return value !== null && typeof value === "object" && !Array.isArray(value);
12219
+ }
12220
+ function canonicalTimestamp3(value) {
12221
+ if (typeof value !== "string")
12222
+ return false;
12223
+ const parsed = new Date(value);
12224
+ return Number.isFinite(parsed.valueOf()) && parsed.toISOString() === value;
12225
+ }
12226
+ function validProjectRef(ref) {
12227
+ return SAFE_PROJECT_REF.test(ref);
12228
+ }
12229
+ function backupBelongsToProject(backupId, projectRef) {
12230
+ return BACKUP_ID.test(backupId) && backupId.startsWith(`logical-full_${projectRef}_`);
12231
+ }
12232
+ function verifiedBackup(value, projectRef) {
12233
+ if (!isRecord(value) || typeof value.backup_id !== "string" || !backupBelongsToProject(value.backup_id, projectRef) || value.project_ref !== projectRef || 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
+ return null;
12235
+ return {
12236
+ backup_id: value.backup_id,
12237
+ project_ref: projectRef,
12238
+ database: value.database,
12239
+ kind: "logical-full",
12240
+ created_at: value.created_at,
12241
+ completed_at: value.completed_at,
12242
+ bytes: value.bytes,
12243
+ sha256: value.sha256
12244
+ };
12245
+ }
12246
+ function backupInventory(value, projectRef) {
12247
+ if (!isRecord(value) || !Array.isArray(value.backups))
12248
+ return null;
12249
+ const backups = value.backups.map((backup) => verifiedBackup(backup, projectRef));
12250
+ if (backups.some((backup) => backup === null))
12251
+ return null;
12252
+ const inventory = backups;
12253
+ return new Set(inventory.map((backup) => backup.backup_id)).size === inventory.length ? inventory : null;
12254
+ }
12255
+ function publicBackup(backup) {
12256
+ return {
12257
+ backup_id: backup.backup_id,
12258
+ project_ref: backup.project_ref,
12259
+ kind: backup.kind,
12260
+ created_at: backup.created_at,
12261
+ completed_at: backup.completed_at,
12262
+ bytes: backup.bytes,
12263
+ sha256: backup.sha256
12264
+ };
12265
+ }
12266
+ function equalBackup(left, right) {
12267
+ return left.backup_id === right.backup_id && left.project_ref === right.project_ref && left.database === right.database && left.kind === right.kind && left.created_at === right.created_at && left.completed_at === right.completed_at && left.bytes === right.bytes && left.sha256 === right.sha256;
12268
+ }
12269
+ function newlyCreatedBackup(before, after) {
12270
+ const afterById = new Map(after.map((backup) => [backup.backup_id, backup]));
12271
+ for (const previous of before) {
12272
+ const current = afterById.get(previous.backup_id);
12273
+ if (!current || !equalBackup(previous, current))
12274
+ return null;
12275
+ }
12276
+ const known = new Set(before.map((backup) => backup.backup_id));
12277
+ const additions = after.filter((backup) => !known.has(backup.backup_id));
12278
+ return additions.length === 1 ? additions[0] : null;
12279
+ }
12280
+ function endpoint(projectRef) {
12281
+ if (!validProjectRef(projectRef))
12282
+ throw new Error("'ref' is invalid for release controls");
12283
+ return `/v1/projects/${encodeURIComponent(projectRef)}`;
12284
+ }
12285
+ function httpFailure(operation, response) {
12286
+ if (response.responseReadError) {
12287
+ return releaseControlFailure(operation, "INVALID_RESPONSE", response.status);
12288
+ }
12289
+ return releaseControlFailure(operation, "HTTP_ERROR", response.transportError ? null : response.status);
12290
+ }
12291
+ function mutationFailure(operation, response) {
12292
+ if (response.responseReadError || response.transportError || response.status === 408 || response.status >= 500) {
12293
+ return releaseControlFailure(operation, "OUTCOME_UNKNOWN", response.transportError ? null : response.status);
12294
+ }
12295
+ return releaseControlFailure(operation, "HTTP_ERROR", response.status);
12296
+ }
12297
+ async function readInventory(http, projectRef) {
12298
+ const response = await http.get(`${endpoint(projectRef)}/database/backups/logical`, {
12299
+ maxJsonBytes: INVENTORY_MAX_BYTES,
12300
+ responseTimeoutMs: RELEASE_READ_RESPONSE_TIMEOUT_MS
12301
+ });
12302
+ return { response, inventory: response.ok && response.status === 200 ? backupInventory(response.data, projectRef) : null };
12303
+ }
12304
+ function readInventoryFailure(operation, read) {
12305
+ if (!read.response.ok)
12306
+ return httpFailure(operation, read.response);
12307
+ if (read.response.status !== 200 || !read.inventory) {
12308
+ return releaseControlFailure(operation, "INVALID_RESPONSE", read.response.status);
12309
+ }
12310
+ return null;
12311
+ }
12312
+ function postgrestStatus(value) {
12313
+ if (!isRecord(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)))
12314
+ return null;
12315
+ return {
12316
+ desired: value.desired,
12317
+ actual: value.actual,
12318
+ health: value.health
12319
+ };
12320
+ }
12321
+ async function readPostgrestStatus(http, projectRef) {
12322
+ const response = await http.get(`${endpoint(projectRef)}/services/postgrest/status`, {
12323
+ maxJsonBytes: MUTATION_MAX_BYTES,
12324
+ responseTimeoutMs: RELEASE_READ_RESPONSE_TIMEOUT_MS
12325
+ });
12326
+ return { response, status: response.ok && response.status === 200 ? postgrestStatus(response.data) : null };
12327
+ }
12328
+ function readPostgrestFailure(operation, read) {
12329
+ if (!read.response.ok)
12330
+ return httpFailure(operation, read.response);
12331
+ return read.response.status === 200 && read.status ? null : releaseControlFailure(operation, "INVALID_RESPONSE", read.response.status);
12332
+ }
12333
+ function isRestartReceipt(value) {
12334
+ return isRecord(value) && value.service === "postgrest" && value.action === "restart" && value.success === true;
12335
+ }
12336
+ 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", {
12338
+ action: withDescription(stringEnum([
12339
+ "logical_backup_list",
12340
+ "logical_backup_create",
12341
+ "postgrest_status",
12342
+ "postgrest_restart"
12343
+ ]), "Release control action"),
12344
+ ref: optional(Type.String(), options.projectRef ? "Optional override when not auto-linked" : "Project ref")
12345
+ }, async ({ action, ref }) => {
12346
+ const projectRef = typeof ref === "string" && ref || options.projectRef;
12347
+ if (!projectRef)
12348
+ throw new Error("'ref' is required for release controls");
12349
+ if (!validProjectRef(projectRef))
12350
+ throw new Error("'ref' is invalid for release controls");
12351
+ if (action === "logical_backup_list") {
12352
+ const read2 = await readInventory(http, projectRef);
12353
+ const failure = readInventoryFailure("release.logical_backup.list", read2);
12354
+ return failure ?? releaseControlSuccess("release.logical_backup.list", {
12355
+ project_ref: projectRef,
12356
+ backups: read2.inventory.map(publicBackup)
12357
+ });
12358
+ }
12359
+ if (action === "logical_backup_create") {
12360
+ const before = await readInventory(http, projectRef);
12361
+ const beforeFailure = readInventoryFailure("release.logical_backup.create", before);
12362
+ if (beforeFailure)
12363
+ return beforeFailure;
12364
+ const mutation2 = await http.postReleaseMutation(`${endpoint(projectRef)}/database/backups/logical`, {}, {
12365
+ timeoutMs: BACKUP_TIMEOUT_MS
12366
+ });
12367
+ const after = await readInventory(http, projectRef);
12368
+ if (!mutation2.ok || mutation2.status !== 200) {
12369
+ return mutationFailure("release.logical_backup.create", mutation2);
12370
+ }
12371
+ const responseBackup = isRecord(mutation2.data) ? verifiedBackup(mutation2.data.backup, projectRef) : null;
12372
+ const afterFailure = readInventoryFailure("release.logical_backup.create", after);
12373
+ const addedBackup = after.inventory && newlyCreatedBackup(before.inventory, after.inventory);
12374
+ if (!responseBackup || afterFailure || !addedBackup || !equalBackup(responseBackup, addedBackup)) {
12375
+ return releaseControlFailure("release.logical_backup.create", "OUTCOME_UNKNOWN", mutation2.status);
12376
+ }
12377
+ return releaseControlSuccess("release.logical_backup.create", {
12378
+ project_ref: projectRef,
12379
+ backup: publicBackup(addedBackup)
12380
+ });
12381
+ }
12382
+ if (action === "postgrest_status") {
12383
+ const read2 = await readPostgrestStatus(http, projectRef);
12384
+ const failure = readPostgrestFailure("release.postgrest.status", read2);
12385
+ return failure ?? releaseControlSuccess("release.postgrest.status", {
12386
+ project_ref: projectRef,
12387
+ postgrest: read2.status
12388
+ });
12389
+ }
12390
+ if (action !== "postgrest_restart")
12391
+ throw new Error("Unknown release control action");
12392
+ const mutation = await http.postReleaseMutation(`${endpoint(projectRef)}/services/postgrest/restart`);
12393
+ const read = await readPostgrestStatus(http, projectRef);
12394
+ if (!mutation.ok || mutation.status !== 200) {
12395
+ return mutationFailure("release.postgrest.restart", mutation);
12396
+ }
12397
+ const readFailure = readPostgrestFailure("release.postgrest.restart", read);
12398
+ if (!isRestartReceipt(mutation.data) || readFailure || read.status.desired !== "running" || read.status.actual !== "running" || read.status.health !== "healthy") {
12399
+ return releaseControlFailure("release.postgrest.restart", "OUTCOME_UNKNOWN", mutation.status);
12400
+ }
12401
+ return releaseControlSuccess("release.postgrest.restart", {
12402
+ project_ref: projectRef,
12403
+ postgrest: read.status
12404
+ });
12405
+ });
12406
+ }
12158
12407
  // package.json
12159
12408
  var package_default = {
12160
12409
  name: "@supacloud/cli",
12161
- version: "0.22.0",
12410
+ version: "0.23.0",
12162
12411
  description: "Project-scoped CLI for SupaCloud users",
12163
12412
  type: "module",
12164
12413
  main: "./dist/index.js",
@@ -12185,7 +12434,7 @@ var package_default = {
12185
12434
  license: "MIT",
12186
12435
  repository: {
12187
12436
  type: "git",
12188
- url: "https://github.com/zuohuadong/supacloud.git",
12437
+ url: "https://github.com/vibeunion/supacloud.git",
12189
12438
  directory: "packages/cli"
12190
12439
  },
12191
12440
  dependencies: {
@@ -12402,6 +12651,9 @@ EXAMPLES
12402
12651
  ${preferredCommand} project get
12403
12652
  ${preferredCommand} project logs --log_type database
12404
12653
  ${preferredCommand} project task_stats
12654
+ ${preferredCommand} release logical_backup_create --ref abc123
12655
+ ${preferredCommand} release postgrest_status --ref abc123
12656
+ ${preferredCommand} release postgrest_restart --ref abc123
12405
12657
  ${preferredCommand} queue stats --queue emails
12406
12658
  ${preferredCommand} queue dlq --queue emails --limit 20
12407
12659
  ${preferredCommand} frontend list --ref abc123
@@ -12494,7 +12746,7 @@ function createCliTools(context, confirmProduction) {
12494
12746
  ]
12495
12747
  })
12496
12748
  };
12497
- for (const name of ["database", "auth", "storage", "edge_functions", "secrets", "frontend", "queue", "task_events", "scheduled_functions", "mutations", "diagnostics", "gateway", "branch"]) {
12749
+ for (const name of ["database", "auth", "storage", "edge_functions", "secrets", "frontend", "queue", "task_events", "scheduled_functions", "mutations", "diagnostics", "gateway", "branch", "release"]) {
12498
12750
  tools[name] = {
12499
12751
  schema: { action: genericActionSchema },
12500
12752
  callback: async () => ({
@@ -12579,6 +12831,9 @@ function createCliTools(context, confirmProduction) {
12579
12831
  readOnly: context.readOnly
12580
12832
  })));
12581
12833
  assign(captureTools((server) => registerMutationTools(server, http)));
12834
+ assign(captureTools((server) => registerReleaseTools(server, http, {
12835
+ projectRef: context.projectRef || undefined
12836
+ })));
12582
12837
  assign(captureTools((server) => registerFrontendTools(server, http)));
12583
12838
  assign(captureTools((server) => registerGatewayTools(server, http, {
12584
12839
  projectRef: context.projectRef || undefined
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/cli",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "Project-scoped CLI for SupaCloud users",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -27,7 +27,7 @@
27
27
  "license": "MIT",
28
28
  "repository": {
29
29
  "type": "git",
30
- "url": "https://github.com/zuohuadong/supacloud.git",
30
+ "url": "https://github.com/vibeunion/supacloud.git",
31
31
  "directory": "packages/cli"
32
32
  },
33
33
  "dependencies": {