@supacloud/cli 0.93.0 → 0.94.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 CHANGED
@@ -13,7 +13,7 @@ are not supported.
13
13
 
14
14
  ## Project Setup and Environment Profiles
15
15
 
16
- For an existing frontend, the shortest path is:
16
+ For an existing SupaCloud application with a frontend, the shortest path is:
17
17
 
18
18
  ```sh
19
19
  supacloud init
@@ -26,6 +26,11 @@ supacloud --env test deploy
26
26
  overwriting an existing project configuration. `env list` and `env check` are
27
27
  local-only, redact credentials, and provide stable JSON with `--json` for CI or
28
28
  AI tooling. The real `.env.supacloud.*` files are added to `.gitignore`.
29
+ Fill in `SUPACLOUD_APPLICATION_ID` and the Management credentials in the
30
+ selected profile, and configure the application's current delivery build.
31
+ Standalone frontend deployment is no longer supported. Upgrade the CLI with
32
+ `npm install -g @supacloud/cli@latest` and upgrade the SupaCloud server together.
33
+ `deploy` checks the server protocol before building or uploading.
29
34
 
30
35
  ## Application Starter
31
36
 
@@ -356,6 +361,7 @@ target configuration:
356
361
 
357
362
  ```json
358
363
  {
364
+ "application": { "id": "orders", "environmentId": "test" },
359
365
  "targets": {
360
366
  "web": {
361
367
  "type": "frontend",
@@ -383,36 +389,24 @@ loopback development origins, with the default `:80` likewise omitted. Use
383
389
 
384
390
  ### Immutable frontend releases
385
391
 
386
- The `frontend` command keeps the existing deployment, Git, and tar.zst
387
- actions and also exposes the immutable prebuilt release workflow:
392
+ The `frontend` command retains release inspection and explicit operator rollback.
393
+ Publish through `supacloud deploy`, which delegates activation to the platform:
388
394
 
389
395
  ```bash
390
396
  supacloud frontend list_releases --ref abc123 --id web
391
397
  supacloud frontend get_active_release --ref abc123 --id web
392
398
  supacloud frontend get_release --ref abc123 --id web --release_id <sha256>
393
- supacloud frontend upload_release --ref abc123 --id web --archive_path ./dist.tar.zst
394
- supacloud frontend activate_release --ref abc123 --id web \
395
- --release_id <sha256> \
396
- --expected_active_release_id absent \
397
- --expected_activation_id absent \
398
- --mutation_id <retry-stable-uuid-v4>
399
399
  supacloud frontend rollback --ref abc123 --id web --release_id <retained-sha256>
400
400
  supacloud frontend rollback --ref abc123 --id web
401
401
  ```
402
402
 
403
- `upload_release` hashes and streams an existing regular tar.zst file without
404
- buffering the full archive. The Management API binds the upload to that SHA-256,
405
- and the CLI reads the immutable release back before reporting success.
406
- `activate_release` uses both the observed active release and activation IDs as
407
- optimistic concurrency tokens, then verifies the authoritative active release.
408
- Use the values returned by `get_active_release`; `absent` is valid only when no
409
- release has been activated. Production uploads, activations and rollback require the
410
- normal exact `--confirm-production <ref>` value, and
411
- `SUPACLOUD_READ_ONLY=true` blocks all mutations.
412
-
413
- `get_active_release` automatically handles older servers returning HTTP 404
414
- with a one-record history page and exact active artifact readback. It never
415
- falls back for authentication, integrity or server failures.
403
+ `deploy_git`, `deploy_upload`, `redeploy`, `upload_release` and `activate_release`
404
+ have been removed from `frontend`. Old invocations fail with an upgrade message,
405
+ without reading an archive or sending a request. Production rollback retains
406
+ the exact `--confirm-production <ref>` gate; read-only mode blocks mutations.
407
+
408
+ `get_active_release` requires the current server endpoint. An HTTP 404 returns
409
+ an upgrade/deployment-existence hint and never falls back to historical inventory.
416
410
  `rollback` without `--release_id` asks SupaCloud for a journal-verified previous
417
411
  immutable release, reads the current CAS identity and generates the mutation ID
418
412
  itself. An explicit `--release_id` remains available for a deliberate rollback
@@ -635,26 +629,23 @@ reporting success. Management endpoint projection is checked before and after.
635
629
 
636
630
  ### Internal release scope controls
637
631
 
638
- Scope files and manual release-controller operations are internal compatibility
639
- surfaces. The project-scoped FA deployment command is a proposed future
640
- contract and is not implemented yet. For current deployments, use the
641
- target-level one-command flow documented in
632
+ Do not generate, rebind or hash scope files as part of a deployment.
633
+ Use an application binding and the unified flow documented in
642
634
  [`docs/cli-guide.md`](../../docs/cli-guide.md), for example:
643
635
 
644
636
  ```bash
645
637
  supacloud deploy
646
- supacloud deploy --target web
647
- supacloud deploy --target api
648
638
  ```
649
639
 
650
640
  FA deployment policy and the boundary between platform-owned operations and
651
641
  FA-owned business features are documented in
652
642
  [`docs/fa-deployment-capability-boundary.md`](../../docs/fa-deployment-capability-boundary.md).
653
643
 
654
- Scope creation, rebinding, inspection and hash calculation are intentionally
655
- undocumented here. They remain available only for internal compatibility,
656
- audit and historical receipt migration. The future project-scoped flow will
657
- not require a scope file once its implementation lands.
644
+ `deploy --target`, `--id`, `--build_command`, `--output_dir`, `--plan`, `--diff`
645
+ and `--dry_run` are removed. Put build settings in `supacloud.json`; use
646
+ `app deploy-plan` for read-only review. Historical project-specific manifests
647
+ and standalone Function targets must move to application delivery configuration.
648
+ The CLI rejects them rather than falling back to a partial Web deployment.
658
649
 
659
650
  The legacy `.env` fallback is unclassified and therefore does not enable the
660
651
  production confirmation gate. Production automation must select a `prod` or
package/dist/index.js CHANGED
@@ -8766,6 +8766,28 @@ function outcomeUnknownGuidance(text) {
8766
8766
  保持原 --env/--env-file 选择,先查询:${command}` : "");
8767
8767
  }
8768
8768
 
8769
+ // src/shared/deployment-upgrade.ts
8770
+ var DEPLOYMENT_UPGRADE = "DEPLOYMENT_UPGRADE_REQUIRED: Legacy deployment is no longer supported. Upgrade with `npm install -g @supacloud/cli@latest`, bind application.id and application.environmentId in supacloud.json (or SUPACLOUD_APPLICATION_ID and SUPACLOUD_ENV), then run `supacloud deploy`. SupaCloud owns deployment planning and activation; do not retry with legacy commands.";
8771
+ var removedFrontendActions = new Set([
8772
+ "deploy_git",
8773
+ "deploy_upload",
8774
+ "redeploy",
8775
+ "upload_release",
8776
+ "activate_release"
8777
+ ]);
8778
+ var removedDeployFlags = ["target", "id", "build_command", "output_dir", "plan", "diff", "dry_run"];
8779
+ function assertCurrentDeploymentInput(tool, input) {
8780
+ if (!input || typeof input !== "object" || Array.isArray(input))
8781
+ return;
8782
+ const args = input;
8783
+ if (tool === "frontend" && typeof args["action"] === "string" && removedFrontendActions.has(args["action"])) {
8784
+ throw new Error(DEPLOYMENT_UPGRADE);
8785
+ }
8786
+ if (tool === "deploy" && removedDeployFlags.some((flag) => Object.hasOwn(args, flag))) {
8787
+ throw new Error(`${DEPLOYMENT_UPGRADE} Put build settings in the frontend configuration. For read-only review use \`supacloud app deploy-plan\`.`);
8788
+ }
8789
+ }
8790
+
8769
8791
  // src/shared/cli.ts
8770
8792
  function cliToolResultIsError(toolResult) {
8771
8793
  if (toolResult.isError === true)
@@ -8889,6 +8911,7 @@ async function runCli(cliTools, args, options = {}) {
8889
8911
  const parsedArgs = parseCliFlags(args, startIdx, tool.schema);
8890
8912
  if (startIdx === 2)
8891
8913
  parsedArgs.action = args[1];
8914
+ assertCurrentDeploymentInput(args[0], parsedArgs);
8892
8915
  validateToolArguments(tool.schema, parsedArgs);
8893
8916
  const result = await tool.callback(parsedArgs);
8894
8917
  if (result && result.content && Array.isArray(result.content)) {
@@ -11188,6 +11211,7 @@ var resultSchema = _Object_({
11188
11211
  });
11189
11212
  function registerTool(server, name, description, schema, callback) {
11190
11213
  server.tool(name, description, schema, async (input) => {
11214
+ assertCurrentDeploymentInput(name, input);
11191
11215
  const args = parseToolArguments(schema, input);
11192
11216
  const result = await callback(args);
11193
11217
  if (!Check2(resultSchema, result))
@@ -16319,39 +16343,6 @@ async function getFrontendRelease(http, projectRef, deploymentId, releaseId) {
16319
16343
  }
16320
16344
  return toolResponse({ project_ref: projectRef, deployment_id: deploymentId, release });
16321
16345
  }
16322
- async function uploadFrontendRelease(http, projectRef, deploymentId, archivePath) {
16323
- const endpoint = releaseEndpoint(projectRef, deploymentId);
16324
- const archive = await verifiedArchive(archivePath);
16325
- let response;
16326
- try {
16327
- response = await http.postBinary(endpoint, {
16328
- stream: archiveStream(archive),
16329
- byteLength: archive.sizeBytes
16330
- }, {
16331
- contentType: FRONTEND_ARCHIVE_CONTENT_TYPE,
16332
- contentLength: archive.sizeBytes,
16333
- contentSha256: archive.sha256,
16334
- maxJsonBytes: RESPONSE_MAX_BYTES,
16335
- timeoutMs: UPLOAD_REQUEST_TIMEOUT_MS,
16336
- responseTimeoutMs: MUTATION_RESPONSE_TIMEOUT_MS
16337
- });
16338
- } finally {
16339
- await archive.handle.close();
16340
- }
16341
- const release = response.ok ? releaseEnvelope(response.data, { projectRef, deploymentId, releaseId: archive.sha256 }) : null;
16342
- if (!release) {
16343
- if (response.status >= 400 && response.status < 500 && response.status !== 408 && !response.transportError) {
16344
- return releaseFailure("frontend.upload_release", "HTTP_ERROR", response.status);
16345
- }
16346
- return uploadReadback(http, projectRef, deploymentId, archive.sha256, response.status);
16347
- }
16348
- const readback = await http.get(`${endpoint}/${release.release_id}`, { maxJsonBytes: RESPONSE_MAX_BYTES });
16349
- const verified = readback.ok ? releaseEnvelope(readback.data, { projectRef, deploymentId, releaseId: release.release_id }) : null;
16350
- if (!verified || verified.tree_sha256 !== release.tree_sha256) {
16351
- return releaseFailure("frontend.upload_release", "OUTCOME_UNKNOWN", readback.status);
16352
- }
16353
- return toolResponse({ project_ref: projectRef, deployment_id: deploymentId, release: verified });
16354
- }
16355
16346
  async function uploadProjectWebRelease(http, projectRef, environmentId, deploymentId, archivePath) {
16356
16347
  const endpoint = `/v1/projects/${encodeURIComponent(projectRef)}/environments/${encodeURIComponent(environmentId)}/deployments/web-release?deployment_id=${encodeURIComponent(deploymentId)}`;
16357
16348
  const archive = await verifiedArchive(archivePath);
@@ -16406,41 +16397,6 @@ async function uploadProjectWebRelease(http, projectRef, environmentId, deployme
16406
16397
  await archive.handle.close();
16407
16398
  }
16408
16399
  }
16409
- async function uploadFrontendSource(http, projectRef, deploymentId, archivePath) {
16410
- const endpoint = `${deploymentEndpoint(projectRef, deploymentId)}/deploy/upload`;
16411
- const archive = await verifiedArchive(archivePath);
16412
- try {
16413
- const response = await http.postBinary(endpoint, {
16414
- stream: archiveStream(archive),
16415
- byteLength: archive.sizeBytes
16416
- }, {
16417
- contentType: FRONTEND_ARCHIVE_CONTENT_TYPE,
16418
- contentLength: archive.sizeBytes,
16419
- contentSha256: archive.sha256,
16420
- maxJsonBytes: RESPONSE_MAX_BYTES,
16421
- timeoutMs: UPLOAD_REQUEST_TIMEOUT_MS
16422
- });
16423
- if (!response.ok) {
16424
- return releaseFailure("frontend.deploy_upload", response.status >= 400 && response.status < 500 ? "HTTP_ERROR" : "OUTCOME_UNKNOWN", response.status);
16425
- }
16426
- if (!response.data || typeof response.data !== "object" || !("success" in response.data) || response.data.success !== true) {
16427
- return releaseFailure("frontend.deploy_upload", "INVALID_RESPONSE", response.status);
16428
- }
16429
- return toolResponse(response.data);
16430
- } finally {
16431
- await archive.handle.close();
16432
- }
16433
- }
16434
- async function uploadReadback(http, projectRef, deploymentId, releaseId, uploadStatus) {
16435
- const response = await http.get(releasePath(projectRef, deploymentId, releaseId), {
16436
- maxJsonBytes: RESPONSE_MAX_BYTES
16437
- });
16438
- const release = response.ok ? releaseEnvelope(response.data, { projectRef, deploymentId, releaseId }) : null;
16439
- if (!release) {
16440
- return releaseFailure("frontend.upload_release", "OUTCOME_UNKNOWN", uploadStatus);
16441
- }
16442
- return toolResponse({ project_ref: projectRef, deployment_id: deploymentId, release });
16443
- }
16444
16400
  function stableJson(candidate) {
16445
16401
  if (candidate === null || typeof candidate !== "object")
16446
16402
  return JSON.stringify(candidate);
@@ -16518,9 +16474,6 @@ async function activeReleaseReadback(http, identity) {
16518
16474
  if (inventory.active_release_id !== identity.releaseId || inventory.active_activation_id !== identity.mutationId) {
16519
16475
  return { release: null, status: 200 };
16520
16476
  }
16521
- if (snapshot.exactReadback) {
16522
- return { release: inventory.releases[0] ?? null, status: 200 };
16523
- }
16524
16477
  const releaseRead = await http.get(releasePath(identity.projectRef, identity.deploymentId, identity.releaseId), { maxJsonBytes: RESPONSE_MAX_BYTES });
16525
16478
  const release = releaseRead.ok ? releaseEnvelope(releaseRead.data, {
16526
16479
  projectRef: identity.projectRef,
@@ -16538,43 +16491,31 @@ async function readActiveFrontendRelease(http, projectRef, deploymentId) {
16538
16491
  if (inventory.next_cursor !== null || inventory.releases.length !== (inventory.active_release_id === null ? 0 : 1) || inventory.releases[0] && inventory.releases[0].release_id !== inventory.active_release_id) {
16539
16492
  return { failure: releaseReadFailure("frontend.get_active_release", response), status: response.status };
16540
16493
  }
16541
- return { inventory, exactReadback: false };
16542
- }
16543
- if (response.ok || response.status !== 404) {
16544
- return { failure: releaseReadFailure("frontend.get_active_release", response), status: response.status };
16494
+ return { inventory };
16545
16495
  }
16546
- const historyResponse = await http.get(`${releaseEndpoint(projectRef, deploymentId)}?limit=1`, {
16547
- maxJsonBytes: RESPONSE_MAX_BYTES
16548
- });
16549
- const history = historyResponse.ok && historyResponse.status === 200 ? releaseInventory(historyResponse.data, { projectRef, deploymentId }) : null;
16550
- if (!history || history.releases.length > 1) {
16496
+ if (response.status === 404) {
16551
16497
  return {
16552
- failure: releaseReadFailure("frontend.get_active_release", historyResponse),
16553
- status: historyResponse.status
16554
- };
16555
- }
16556
- if (history.active_release_id === null) {
16557
- return { inventory: { ...history, releases: [], next_cursor: null }, exactReadback: false };
16558
- }
16559
- const exactResponse = await http.get(releasePath(projectRef, deploymentId, history.active_release_id), {
16560
- maxJsonBytes: RESPONSE_MAX_BYTES
16561
- });
16562
- const release = exactResponse.ok && exactResponse.status === 200 ? releaseEnvelope(exactResponse.data, { projectRef, deploymentId, releaseId: history.active_release_id }) : null;
16563
- if (!release) {
16564
- return {
16565
- failure: releaseReadFailure("frontend.get_active_release", exactResponse),
16566
- status: exactResponse.status
16498
+ failure: {
16499
+ isError: true,
16500
+ content: [{ type: "text", text: JSON.stringify({
16501
+ ok: false,
16502
+ operation: "frontend.get_active_release",
16503
+ error: {
16504
+ code: "DEPLOYMENT_UPGRADE_REQUIRED",
16505
+ http_status: 404,
16506
+ message: `${DEPLOYMENT_UPGRADE} Check that the deployment exists and upgrade the server if this endpoint is unavailable.`
16507
+ }
16508
+ }) }]
16509
+ },
16510
+ status: response.status
16567
16511
  };
16568
16512
  }
16569
- return { inventory: { ...history, releases: [release], next_cursor: null }, exactReadback: true };
16513
+ return { failure: releaseReadFailure("frontend.get_active_release", response), status: response.status };
16570
16514
  }
16571
16515
  async function getActiveFrontendRelease(http, projectRef, deploymentId) {
16572
16516
  const snapshot = await readActiveFrontendRelease(http, projectRef, deploymentId);
16573
16517
  return "failure" in snapshot ? snapshot.failure : toolResponse(snapshot.inventory);
16574
16518
  }
16575
- async function readFrontendReleaseAuthority(http, projectRef, deploymentId) {
16576
- return getActiveFrontendRelease(http, projectRef, deploymentId);
16577
- }
16578
16519
  async function rollbackFrontendRelease(http, projectRef, deploymentId, releaseId) {
16579
16520
  if (releaseId !== undefined && !RELEASE_ID_PATTERN.test(releaseId)) {
16580
16521
  throw new Error("'release_id' must be a SHA-256 digest");
@@ -16670,7 +16611,6 @@ async function readPreviousFrontendRelease(http, projectRef, deploymentId) {
16670
16611
  releases: [],
16671
16612
  next_cursor: null
16672
16613
  },
16673
- exactReadback: false,
16674
16614
  ...previous ? { previousRelease: previous } : {}
16675
16615
  };
16676
16616
  }
@@ -16740,16 +16680,13 @@ async function activationReadback(http, input, mutationId, activationStatus) {
16740
16680
  // src/shared/tools/frontend-tools.ts
16741
16681
  function registerFrontendTools(server, http) {
16742
16682
  registerTool(server, "frontend", `Frontend hosting and immutable prebuilt releases. Supports: static, react, vue, svelte, sveltekit, sveltekit-static, nextjs, nuxt, astro.
16743
- Actions: list, get, create, update, delete, deploy_git, deploy_upload, redeploy, build_logs, add_domain, remove_domain, set_env, list_frameworks, list_records, list_releases, get_active_release, get_release, upload_release, activate_release, rollback`, {
16683
+ Actions: list, get, create, update, delete, build_logs, add_domain, remove_domain, set_env, list_frameworks, list_records, list_releases, get_active_release, get_release, rollback. Publish with supacloud deploy.`, {
16744
16684
  action: withDescription(stringEnum([
16745
16685
  "list",
16746
16686
  "get",
16747
16687
  "create",
16748
16688
  "update",
16749
16689
  "delete",
16750
- "deploy_git",
16751
- "deploy_upload",
16752
- "redeploy",
16753
16690
  "build_logs",
16754
16691
  "add_domain",
16755
16692
  "remove_domain",
@@ -16759,8 +16696,6 @@ Actions: list, get, create, update, delete, deploy_git, deploy_upload, redeploy,
16759
16696
  "list_releases",
16760
16697
  "get_active_release",
16761
16698
  "get_release",
16762
- "upload_release",
16763
- "activate_release",
16764
16699
  "rollback"
16765
16700
  ]), "Action"),
16766
16701
  ref: optional(String2(), "Project ref"),
@@ -16774,13 +16709,7 @@ Actions: list, get, create, update, delete, deploy_git, deploy_upload, redeploy,
16774
16709
  node_version: optional(String2(), "[create/update] Node.js version"),
16775
16710
  health_check_path: optional(String2(), "[create/update] SSR readiness path (default: /)"),
16776
16711
  env_vars: optional(Record(String2(), String2()), "[create/update/set_env] Environment variables"),
16777
- git_url: optional(String2(), "[deploy_git] Git repository URL"),
16778
- branch: optional(String2(), "[deploy_git] Branch (default: main)"),
16779
- archive_path: optional(String2(), "[deploy_upload/upload_release] Local tar.zst file path"),
16780
- release_id: optional(String2(), "[get_release/activate_release] SHA-256 release ID; [rollback] optional, defaults to journal-verified previous release"),
16781
- expected_active_release_id: optional(String2(), "[activate_release] Current release SHA-256 or absent"),
16782
- expected_activation_id: optional(String2(), "[activate_release] Current activation UUIDv4 or absent"),
16783
- mutation_id: optional(String2(), "[activate_release] Required retry-stable UUIDv4"),
16712
+ release_id: optional(String2(), "[get_release] SHA-256 release ID; [rollback] optional, defaults to journal-verified previous release"),
16784
16713
  cursor: optional(String2(), "[list_releases] Last release SHA-256 cursor"),
16785
16714
  limit: optional(Number2(), "[list_releases] Page size, 1-100 (default 50)")
16786
16715
  }, async (args) => {
@@ -16797,13 +16726,7 @@ Actions: list, get, create, update, delete, deploy_git, deploy_upload, redeploy,
16797
16726
  node_version,
16798
16727
  health_check_path,
16799
16728
  env_vars,
16800
- git_url,
16801
- branch,
16802
- archive_path,
16803
16729
  release_id,
16804
- expected_active_release_id,
16805
- expected_activation_id,
16806
- mutation_id,
16807
16730
  cursor,
16808
16731
  limit
16809
16732
  } = args;
@@ -16858,22 +16781,6 @@ Actions: list, get, create, update, delete, deploy_git, deploy_upload, redeploy,
16858
16781
  need("id", id);
16859
16782
  text = (await http.delete(`/v1/projects/${ref}/frontend/deployments/${id}`)).ok ? `✅ Deleted` : `❌ Failed`;
16860
16783
  break;
16861
- case "deploy_git":
16862
- need("ref", ref);
16863
- need("id", id);
16864
- need("git_url", git_url);
16865
- text = ok(await http.post(`/v1/projects/${ref}/frontend/deployments/${id}/deploy/git`, { git_url, branch }));
16866
- break;
16867
- case "deploy_upload":
16868
- need("ref", ref);
16869
- need("id", id);
16870
- need("archive_path", archive_path);
16871
- return uploadFrontendSource(http, ref, id, archive_path);
16872
- case "redeploy":
16873
- need("ref", ref);
16874
- need("id", id);
16875
- text = ok(await http.post(`/v1/projects/${ref}/frontend/deployments/${id}/redeploy`));
16876
- break;
16877
16784
  case "build_logs":
16878
16785
  need("ref", ref);
16879
16786
  need("id", id);
@@ -16919,26 +16826,6 @@ Actions: list, get, create, update, delete, deploy_git, deploy_upload, redeploy,
16919
16826
  need("ref", ref);
16920
16827
  need("id", id);
16921
16828
  return getActiveFrontendRelease(http, ref, id);
16922
- case "upload_release":
16923
- need("ref", ref);
16924
- need("id", id);
16925
- need("archive_path", archive_path);
16926
- return uploadFrontendRelease(http, ref, id, archive_path);
16927
- case "activate_release":
16928
- need("ref", ref);
16929
- need("id", id);
16930
- need("release_id", release_id);
16931
- need("expected_active_release_id", expected_active_release_id);
16932
- need("expected_activation_id", expected_activation_id);
16933
- need("mutation_id", mutation_id);
16934
- return activateFrontendRelease(http, {
16935
- projectRef: ref,
16936
- deploymentId: id,
16937
- releaseId: release_id,
16938
- expectedActiveReleaseId: expected_active_release_id,
16939
- expectedActivationId: expected_activation_id,
16940
- mutationId: mutation_id
16941
- });
16942
16829
  case "rollback":
16943
16830
  need("ref", ref);
16944
16831
  need("id", id);
@@ -21230,7 +21117,7 @@ var starter_metadata_default = {
21230
21117
  version: "0.8.1"
21231
21118
  },
21232
21119
  db: {
21233
- version: "0.16.1",
21120
+ version: "0.16.2",
21234
21121
  peerDependencies: {
21235
21122
  "drizzle-orm": "1.0.0-rc.5-169397b"
21236
21123
  },
@@ -28108,6 +27995,117 @@ import { spawn as spawn2 } from "node:child_process";
28108
27995
  import { createHash as createHash9 } from "node:crypto";
28109
27996
  import { tmpdir as tmpdir3 } from "node:os";
28110
27997
  import { basename as basename8, dirname as dirname11, join as join17, relative as relative10, resolve as resolve22, sep as sep5 } from "node:path";
27998
+
27999
+ // src/shared/tools/project-deployment.ts
28000
+ async function requireProjectDeploymentProtocol(http, projectRef, environmentId) {
28001
+ const response = await http.get(`/v1/projects/${encodeURIComponent(projectRef)}/environments/${encodeURIComponent(environmentId)}/deployments/protocol`, { retry: false, totalTimeoutMs: 5000, maxJsonBytes: 4096 });
28002
+ if (response.transportError || response.responseReadError || response.status >= 500 || response.status === 401 || response.status === 403 || response.status === 429) {
28003
+ throw new Error(`DEPLOYMENT_PROTOCOL_UNAVAILABLE: Cannot verify the selected server (HTTP ${response.status}). Check connectivity, credentials and server health; no upload or deployment was attempted.`);
28004
+ }
28005
+ const data = response.data;
28006
+ if (response.status !== 200 || !response.ok || !data || typeof data !== "object" || !("protocol" in data) || data.protocol !== "supacloud.project-deployment.v1" || !("project_ref" in data) || data.project_ref !== projectRef || !("environment_id" in data) || data.environment_id !== environmentId) {
28007
+ throw new Error("DEPLOYMENT_PROTOCOL_MISMATCH: Upgrade the SupaCloud server and CLI together (`npm install -g @supacloud/cli@latest`), then retry `supacloud deploy`. No upload or deployment was attempted; legacy endpoints will not be used.");
28008
+ }
28009
+ }
28010
+ var operation = "applications.project_deploy";
28011
+ var scopeSchema = _Object_({
28012
+ project_ref: String2({ pattern: "^[a-z0-9-]{1,20}$" }),
28013
+ application_id: ApplicationIdSchema,
28014
+ environment_id: ApplicationIdSchema
28015
+ });
28016
+ var statusSchema = _Object_({
28017
+ schema: Literal("supacloud.project-deployment-status.v1"),
28018
+ deployment_id: ApplicationDeployRequestSchema.properties.mutation_id,
28019
+ status: Union([
28020
+ Literal("pending"),
28021
+ Literal("running"),
28022
+ Literal("succeeded"),
28023
+ Literal("failed_retryable"),
28024
+ Literal("failed_terminal"),
28025
+ Literal("outcome_unknown")
28026
+ ]),
28027
+ phase: Union([
28028
+ Literal("queued"),
28029
+ Literal("prepared"),
28030
+ Literal("migrating"),
28031
+ Literal("activating"),
28032
+ Literal("binding-web"),
28033
+ Literal("verifying")
28034
+ ]),
28035
+ operation: Literal("application.release.deploy"),
28036
+ ...scopeSchema.properties,
28037
+ receipt: Optional(Unknown()),
28038
+ failure_code: Optional(String2({ pattern: "^[A-Z][A-Z0-9_]{0,63}$" }))
28039
+ }, { additionalProperties: false });
28040
+ function responseReceipt(data, expected) {
28041
+ if (!data || typeof data !== "object" || Array.isArray(data) || !("ok" in data) || data.ok !== true) {
28042
+ throw new Error("Invalid project deployment response");
28043
+ }
28044
+ const { ok: _ok, ...receipt } = data;
28045
+ return parseApplicationDeployResult(receipt, expected);
28046
+ }
28047
+ async function runProjectDeployment(http, args) {
28048
+ const scope = { project_ref: args.ref, application_id: args.id, environment_id: args.environment_id };
28049
+ const request = {
28050
+ mutation_id: args.mutation_id,
28051
+ release_id: args.release_id,
28052
+ ...args.configuration_id === undefined ? {} : { configuration_id: args.configuration_id },
28053
+ ...args.configuration_write === undefined ? {} : { configuration_write: args.configuration_write },
28054
+ ...args.expected_plan_sha256 === undefined ? {} : { expected_plan_sha256: args.expected_plan_sha256 },
28055
+ ...args.approved_migration_digest === undefined ? {} : { approved_migration_digest: args.approved_migration_digest },
28056
+ ...args.web_release === undefined ? {} : { web_release: args.web_release }
28057
+ };
28058
+ if (!Check2(scopeSchema, scope) || !Check2(ApplicationDeployRequestSchema, request) || request.configuration_id !== undefined && request.configuration_write !== undefined && request.configuration_id !== request.configuration_write.configuration_id) {
28059
+ return releaseControlFailure(operation, "INVALID_RESPONSE", null);
28060
+ }
28061
+ const expected = { ...scope, request };
28062
+ const identity = {
28063
+ ...scope,
28064
+ deployment_id: request.mutation_id,
28065
+ mutation_id: request.mutation_id,
28066
+ release_id: request.release_id
28067
+ };
28068
+ const endpoint = `/v1/projects/${encodeURIComponent(scope.project_ref)}/environments/${encodeURIComponent(scope.environment_id)}/deployments`;
28069
+ const { mutation_id, ...candidate } = request;
28070
+ const result = await http.post(endpoint, {
28071
+ ...candidate,
28072
+ deployment_id: mutation_id,
28073
+ application_id: scope.application_id
28074
+ }, { timeoutMs: 120000, maxJsonBytes: 131072, responseTimeoutMs: 30000 });
28075
+ if (result.ok && result.status === 200) {
28076
+ try {
28077
+ return releaseControlSuccess(operation, {
28078
+ ...responseReceipt(result.data, expected),
28079
+ deployment_id: mutation_id
28080
+ });
28081
+ } catch {}
28082
+ } else if (!result.ok && !result.transportError && !result.responseReadError && result.status < 500 && result.status !== 408) {
28083
+ return releaseControlMutationFailure(operation, result, identity);
28084
+ }
28085
+ const observed = await http.get(`${endpoint}/${encodeURIComponent(mutation_id)}`, {
28086
+ retry: false,
28087
+ totalTimeoutMs: 30000,
28088
+ maxJsonBytes: 131072
28089
+ });
28090
+ try {
28091
+ if (!observed.ok || observed.status !== 200 || !Check2(statusSchema, observed.data))
28092
+ throw new Error;
28093
+ const state = observed.data;
28094
+ if (state.deployment_id !== mutation_id || state.project_ref !== scope.project_ref || state.application_id !== scope.application_id || state.environment_id !== scope.environment_id || state.status !== "succeeded" || state.phase !== "verifying")
28095
+ throw new Error;
28096
+ const receipt = parseApplicationDeployResult(state.receipt, expected);
28097
+ return releaseControlSuccess(operation, {
28098
+ ...receipt,
28099
+ deployment_id: mutation_id,
28100
+ replayed: true,
28101
+ execution_performed: false
28102
+ });
28103
+ } catch {
28104
+ return releaseControlFailure(operation, "OUTCOME_UNKNOWN", result.transportError ? null : result.status, identity);
28105
+ }
28106
+ }
28107
+
28108
+ // src/shared/tools/deploy-tools.ts
28111
28109
  var MAX_SOURCE_BYTES = 256 * 1024 * 1024;
28112
28110
  var PUBLIC_BUILD_ENV_PREFIXES = ["PUBLIC_", "VITE_", "NEXT_PUBLIC_", "NUXT_PUBLIC_", "ASTRO_PUBLIC_"];
28113
28111
  var SENSITIVE_BUILD_ENV = /(?:^|_)(?:PASSWORD|PASS|SECRET|TOKEN|KEY|CREDENTIAL|AUTHORIZATION|COOKIE|SESSION)(?:_|$)|(?:DATABASE_URL|DATABASE_URI|CONNECTION_STRING|DSN|PRIVATE_KEY)/i;
@@ -28119,15 +28117,8 @@ function isProtectedBuildEnv(name) {
28119
28117
  }
28120
28118
  var deployToolSchema = {
28121
28119
  ref: optional(String2(), "Project ref (defaults to the linked project)"),
28122
- target: optional(String2(), "Named deploy target from supacloud.json"),
28123
- id: optional(String2(), "Frontend deployment ID (auto-selected when unambiguous)"),
28124
28120
  cwd: optional(String2(), "Project directory (default: current directory)"),
28125
- build_command: optional(String2(), "Build command override"),
28126
- output_dir: optional(String2(), "Build output directory override"),
28127
28121
  skip_build: optional(Boolean2(), "Use an existing output directory without building"),
28128
- dry_run: optional(Boolean2(), "Resolve and validate the deployment without building or publishing"),
28129
- plan: optional(Boolean2(), "Build and compare the release with current authority without publishing"),
28130
- diff: optional(Boolean2(), "Build and report the immutable release-level diff without publishing"),
28131
28122
  json: optional(Boolean2(), "Print only the final JSON result")
28132
28123
  };
28133
28124
  function record4(candidate) {
@@ -28386,40 +28377,6 @@ function targetRoot(configRoot, target, name) {
28386
28377
  }
28387
28378
  return root;
28388
28379
  }
28389
- function selectTarget(configRoot, invocationDirectory, config, requestedName) {
28390
- const targets = config.targets ? Object.entries(config.targets) : [["frontend", {
28391
- type: "frontend",
28392
- root: config.frontend?.root,
28393
- id: config.frontend?.id,
28394
- buildCommand: config.frontend?.buildCommand,
28395
- outputDirectory: config.frontend?.outputDirectory,
28396
- buildEnv: config.frontend?.buildEnv
28397
- }]];
28398
- if (targets.length === 0)
28399
- throw new Error("supacloud.json defines no deploy targets");
28400
- if (requestedName) {
28401
- const selected = targets.find(([name]) => name === requestedName);
28402
- if (!selected)
28403
- throw new Error(`Deploy target '${requestedName}' was not found in supacloud.json`);
28404
- return { name: selected[0], config: selected[1], root: targetRoot(configRoot, selected[1], selected[0]) };
28405
- }
28406
- const containing = targets.map(([name, target]) => ({ name, target, root: targetRoot(configRoot, target, name) })).filter((candidate) => invocationDirectory === candidate.root || invocationDirectory.startsWith(`${candidate.root}${sep5}`)).sort((left, right) => right.root.length - left.root.length);
28407
- if (containing.length === 1 || containing.length > 1 && containing[0].root !== containing[1].root) {
28408
- const selected = containing[0];
28409
- return { name: selected.name, config: selected.target, root: selected.root };
28410
- }
28411
- if (config.defaultTarget) {
28412
- const selected = targets.find(([name]) => name === config.defaultTarget);
28413
- if (!selected)
28414
- throw new Error(`supacloud.json defaultTarget '${config.defaultTarget}' was not found`);
28415
- return { name: selected[0], config: selected[1], root: targetRoot(configRoot, selected[1], selected[0]) };
28416
- }
28417
- if (targets.length === 1) {
28418
- const selected = targets[0];
28419
- return { name: selected[0], config: selected[1], root: targetRoot(configRoot, selected[1], selected[0]) };
28420
- }
28421
- throw new Error(`Multiple deploy targets found (${targets.map(([name]) => name).sort().join(", ")}). Pass --target`);
28422
- }
28423
28380
  function defaultOutputDirectory(framework) {
28424
28381
  switch (framework) {
28425
28382
  case "sveltekit-static":
@@ -28599,24 +28556,8 @@ async function stageApplicationWebRelease(http, projectRef, environmentId, confi
28599
28556
  await archive.cleanup();
28600
28557
  }
28601
28558
  }
28602
- function deployResponse(result, json) {
28603
- if (json)
28604
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
28605
- const lines = [
28606
- result.unchanged === true ? "Already up to date." : `Deployed ${String(result.target || result.deployment_id || result.slug)}.`,
28607
- result.url ? `URL: ${String(result.url)}` : null,
28608
- result.release_id ? `Release: ${String(result.release_id)}` : null,
28609
- result.active_version ? `Active version: ${String(result.active_version)}` : null,
28610
- result.file_count ? `Files: ${String(result.file_count)}` : null
28611
- ].filter((line) => Boolean(line));
28612
- return { content: [{ type: "text", text: lines.join(`
28613
- `) }] };
28614
- }
28615
28559
  function registerDeployTools(server, http, options = {}) {
28616
28560
  registerTool(server, "deploy", "Build and publish the linked project with one command", deployToolSchema, async (args) => {
28617
- if (args.dry_run === true && (args.plan === true || args.diff === true)) {
28618
- throw new Error("Use dry_run for resolution only, or plan/diff for a built release comparison, not both");
28619
- }
28620
28561
  const invocationDirectory = resolve22(String(args.cwd || options.cwd || process.cwd()));
28621
28562
  const projectRef = String(args.ref || options.projectRef || "").trim();
28622
28563
  if (!projectRef)
@@ -28627,8 +28568,8 @@ function registerDeployTools(server, http, options = {}) {
28627
28568
  ...config.application,
28628
28569
  environmentId: options.environment || config.application.environmentId
28629
28570
  } : undefined;
28630
- if (!args.target && await hasProjectReleaseManifest(configRoot)) {
28631
- throw new Error("PROJECT_DEPLOYMENT_UNSUPPORTED: This project declares a release manifest that the current deployment orchestrator cannot execute. No Web-only fallback or remote mutation was performed.");
28571
+ if (await hasProjectReleaseManifest(configRoot)) {
28572
+ throw new Error(`${DEPLOYMENT_UPGRADE} Replace deploy-manifest.json with the current application delivery configuration; no remote mutation was performed.`);
28632
28573
  }
28633
28574
  if (application?.id) {
28634
28575
  if (Object.values(config.targets ?? {}).some((target) => target.type === "edge_function")) {
@@ -28637,12 +28578,6 @@ function registerDeployTools(server, http, options = {}) {
28637
28578
  if (!options.applicationRelease) {
28638
28579
  throw new Error("Application release support is unavailable in this CLI context");
28639
28580
  }
28640
- if (args.target || args.build_command || args.output_dir) {
28641
- throw new Error("Application profile deploy does not accept frontend target, build command, or output directory flags");
28642
- }
28643
- if (args.dry_run === true || args.plan === true || args.diff === true) {
28644
- throw new Error("Application profile deploy does not support dry_run, plan, or diff; use app deploy-plan for a read-only plan");
28645
- }
28646
28581
  const environmentId = application.environmentId?.trim();
28647
28582
  if (!environmentId || !Check2(ApplicationIdSchema, environmentId) || !Check2(ApplicationIdSchema, application.id)) {
28648
28583
  throw new Error("Application profile requires SUPACLOUD_APPLICATION_ENVIRONMENT_ID or SUPACLOUD_ENV");
@@ -28652,6 +28587,7 @@ function registerDeployTools(server, http, options = {}) {
28652
28587
  if (configurationPath && configurationId) {
28653
28588
  throw new Error("Application profile must set only one of SUPACLOUD_APPLICATION_CONFIGURATION_PATH or SUPACLOUD_APPLICATION_CONFIGURATION_ID");
28654
28589
  }
28590
+ await requireProjectDeploymentProtocol(http, projectRef, environmentId);
28655
28591
  const stagedWeb = await stageApplicationWebRelease(http, projectRef, environmentId, configRoot, args.skip_build === true, args.json === true, options.environmentVariables);
28656
28592
  const result = await options.applicationRelease({
28657
28593
  action: "release",
@@ -28668,215 +28604,7 @@ function registerDeployTools(server, http, options = {}) {
28668
28604
  });
28669
28605
  return result;
28670
28606
  }
28671
- const target = selectTarget(configRoot, invocationDirectory, config, String(args.target || "").trim() || undefined);
28672
- if (!args.target && target.config.type === "frontend" && (Object.values(config.targets ?? {}).some((candidate) => candidate.type === "edge_function") || await pathExists(join17(configRoot, "supacloud.config.ts")) || await pathExists(join17(configRoot, "supabase", "migrations")) || await pathExists(join17(configRoot, "supabase", "functions")))) {
28673
- throw new Error("PROJECT_DEPLOYMENT_UNSUPPORTED: Backend declarations were detected but are not covered by this deployment. No Web-only fallback or remote mutation was performed.");
28674
- }
28675
- const projectDirectory = target.root;
28676
- const targetState = await lstat5(projectDirectory).catch(() => null);
28677
- if (!targetState?.isDirectory() || targetState.isSymbolicLink()) {
28678
- throw new Error(`Deploy target '${target.name}' root is not a regular directory: ${projectDirectory}`);
28679
- }
28680
- const requestedId = String(args.id || target.config.id || "").trim() || undefined;
28681
- const build = createBuildEnvironment(target.config.buildEnv, options.environmentVariables);
28682
- if (target.config.type === "edge_function") {
28683
- if (args.diff === true)
28684
- throw new Error("Function release diffs require an immutable function release manifest");
28685
- if (!options.edgeFunctionDeploy)
28686
- throw new Error("Edge Function deploy support is unavailable in this CLI context");
28687
- const slug = target.config.slug || requestedId || target.name;
28688
- const functionListResponse = await options.edgeFunctionDeploy({
28689
- action: "list",
28690
- ref: projectRef
28691
- });
28692
- if (functionListResponse.isError) {
28693
- throw new Error(`Unable to read the current identity for Edge Function '${slug}'`);
28694
- }
28695
- const functionListText = functionListResponse.content.find((chunk) => chunk.type === "text")?.text;
28696
- if (!functionListText)
28697
- throw new Error(`Unable to read the current identity for Edge Function '${slug}'`);
28698
- const functions = projectedFunctionList(JSON.parse(functionListText));
28699
- if (!functions)
28700
- throw new Error(`Invalid current identity for Edge Function '${slug}'`);
28701
- const current = functions?.find((candidate) => candidate.slug === slug);
28702
- const activeVersion = current && typeof current.version === "number" ? String(current.version) : "absent";
28703
- const activationId = current && typeof current.activation_id === "string" ? current.activation_id : "legacy";
28704
- if (!activeVersion || !activationId)
28705
- throw new Error(`Edge Function '${slug}' has an invalid active identity`);
28706
- const buildCommand = String(args.build_command || target.config.buildCommand || "").trim() || (args.skip_build === true ? "" : await defaultBuildCommand(projectDirectory, configRoot));
28707
- const bundleDirectory = resolveInsideProject(projectDirectory, String(args.output_dir || target.config.bundleDirectory || target.config.outputDirectory || "dist"), true);
28708
- if (args.dry_run === true || args.plan === true) {
28709
- return { content: [{ type: "text", text: JSON.stringify({
28710
- ok: true,
28711
- dry_run: true,
28712
- type: target.config.type,
28713
- target: target.name,
28714
- project_ref: projectRef,
28715
- slug,
28716
- build_command: args.skip_build === true ? null : buildCommand,
28717
- build_env: build.names,
28718
- missing_build_env: build.missing,
28719
- bundle_directory: bundleDirectory,
28720
- expected_active_version: activeVersion,
28721
- expected_activation_id: activationId
28722
- }, null, 2) }] };
28723
- }
28724
- assertBuildEnvironment(build, args.skip_build === true);
28725
- if (args.skip_build !== true) {
28726
- await runBuild(buildCommand, projectDirectory, build.env, args.json === true);
28727
- }
28728
- const deployed = await options.edgeFunctionDeploy({
28729
- action: "deploy_bundle",
28730
- ref: projectRef,
28731
- slug,
28732
- "bundle-dir": bundleDirectory,
28733
- entrypoint: target.config.entrypoint || "index.ts",
28734
- minify: target.config.minify,
28735
- verify_jwt: target.config.verifyJwt,
28736
- framework: target.config.framework,
28737
- "expected-active-version": activeVersion,
28738
- "expected-activation-id": activationId
28739
- });
28740
- if (deployed.isError)
28741
- return deployed;
28742
- const deployedText = deployed.content.find((chunk) => chunk.type === "text")?.text;
28743
- const deployedPayload = deployedText ? record4(JSON.parse(deployedText)) : null;
28744
- return deployResponse({
28745
- ok: true,
28746
- unchanged: false,
28747
- type: target.config.type,
28748
- target: target.name,
28749
- project_ref: projectRef,
28750
- slug,
28751
- active_version: deployedPayload?.active_version,
28752
- activation_id: deployedPayload?.activation_id
28753
- }, args.json === true);
28754
- }
28755
- const listResponse = await http.get(`/v1/projects/${encodeURIComponent(projectRef)}/frontend/deployments`);
28756
- if (!listResponse.ok)
28757
- throw new Error(`Failed to list frontend deployments (HTTP ${listResponse.status})`);
28758
- const selected = selectDeployment(deploymentList(listResponse.data), requestedId, await packageName(projectDirectory));
28759
- const configuredBuildCommand = String(args.build_command || target.config.buildCommand || selected.buildCommand || "").trim();
28760
- const buildCommand = configuredBuildCommand || (args.skip_build === true ? "" : await defaultBuildCommand(projectDirectory, configRoot));
28761
- const configuredOutput = String(args.output_dir || target.config.outputDirectory || "").trim();
28762
- const remoteOutput = selected.outputDirectory && selected.outputDirectory !== "." ? selected.outputDirectory : "";
28763
- const outputDirectory = resolveInsideProject(projectDirectory, configuredOutput || remoteOutput || defaultOutputDirectory(selected.framework));
28764
- if (args.dry_run === true) {
28765
- return { content: [{ type: "text", text: JSON.stringify({
28766
- ok: true,
28767
- dry_run: true,
28768
- project_ref: projectRef,
28769
- deployment_id: selected.id,
28770
- build_command: args.skip_build === true ? null : buildCommand,
28771
- build_env: build.names,
28772
- missing_build_env: build.missing,
28773
- output_directory: outputDirectory
28774
- }, null, 2) }] };
28775
- }
28776
- assertBuildEnvironment(build, args.skip_build === true);
28777
- if (args.skip_build !== true) {
28778
- await runBuild(buildCommand, projectDirectory, build.env, args.json === true);
28779
- }
28780
- const archive = await createFrontendArchive(outputDirectory);
28781
- try {
28782
- const inventory = payload(await readFrontendReleaseAuthority(http, projectRef, selected.id));
28783
- const activeReleaseId = typeof inventory.active_release_id === "string" ? inventory.active_release_id : null;
28784
- const activeActivationId = typeof inventory.active_activation_id === "string" ? inventory.active_activation_id : null;
28785
- const current = Array.isArray(inventory.releases) ? record4(inventory.releases[0]) : null;
28786
- if (archive.sha256 === activeReleaseId && archive.treeSha256 !== current?.tree_sha256) {
28787
- throw new Error("Active release tree does not match the local archive");
28788
- }
28789
- const unchanged = archive.treeSha256 === current?.tree_sha256;
28790
- if (args.plan === true || args.diff === true) {
28791
- return { content: [{ type: "text", text: JSON.stringify({
28792
- schema: "supacloud.frontend-deploy-plan.v1",
28793
- ok: true,
28794
- planned: true,
28795
- published: false,
28796
- unchanged,
28797
- environment: options.environment ?? null,
28798
- type: "frontend",
28799
- target: target.name,
28800
- project_ref: projectRef,
28801
- deployment_id: selected.id,
28802
- comparison: "immutable_release",
28803
- archive_format: FRONTEND_ARCHIVE_FORMAT,
28804
- release_id: archive.sha256,
28805
- tree_sha256: archive.treeSha256,
28806
- active_release_id: activeReleaseId,
28807
- active_tree_sha256: current?.tree_sha256 ?? null,
28808
- expected_active_release_id: activeReleaseId ?? "absent",
28809
- expected_activation_id: activeActivationId ?? "absent",
28810
- actions: unchanged ? [] : ["upload_release", "activate_release"],
28811
- file_count: archive.fileCount,
28812
- source_bytes: archive.sourceBytes,
28813
- archive_bytes: archive.archiveBytes
28814
- }, null, 2) }] };
28815
- }
28816
- if (unchanged) {
28817
- return deployResponse({
28818
- ok: true,
28819
- unchanged: true,
28820
- project_ref: projectRef,
28821
- deployment_id: selected.id,
28822
- release_id: activeReleaseId,
28823
- tree_sha256: archive.treeSha256,
28824
- url: selected.deploymentUrl,
28825
- file_count: archive.fileCount,
28826
- source_bytes: archive.sourceBytes
28827
- }, args.json === true);
28828
- }
28829
- const uploaded = payload(await uploadFrontendRelease(http, projectRef, selected.id, archive.archivePath));
28830
- const release = record4(uploaded.release);
28831
- const releaseId = requiredString(release?.release_id, "release.release_id");
28832
- if (releaseId !== archive.sha256)
28833
- throw new Error("Uploaded release identity does not match the local archive");
28834
- if (release?.tree_sha256 !== archive.treeSha256)
28835
- throw new Error("Uploaded release tree does not match the local build");
28836
- const mutationId = crypto.randomUUID();
28837
- const activationResponse = await activateFrontendRelease(http, {
28838
- projectRef,
28839
- deploymentId: selected.id,
28840
- releaseId,
28841
- expectedActiveReleaseId: activeReleaseId || "absent",
28842
- expectedActivationId: activeActivationId || "absent",
28843
- mutationId
28844
- });
28845
- if (activationResponse.isError) {
28846
- const failure = record4(JSON.parse(activationResponse.content[0]?.text ?? "{}"));
28847
- return {
28848
- isError: true,
28849
- content: [{ type: "text", text: JSON.stringify({
28850
- ...failure,
28851
- operation: "deploy",
28852
- project_ref: projectRef,
28853
- deployment_id: selected.id,
28854
- mutation_id: mutationId,
28855
- target_release_id: releaseId,
28856
- expected_active_release_id: activeReleaseId ?? "absent",
28857
- expected_activation_id: activeActivationId ?? "absent"
28858
- }) }]
28859
- };
28860
- }
28861
- const activated = payload(activationResponse);
28862
- const finalResponse = await http.get(`/v1/projects/${encodeURIComponent(projectRef)}/frontend/deployments/${encodeURIComponent(selected.id)}`);
28863
- const finalDeployment = finalResponse.ok ? frontendDeployment(finalResponse.data) : selected;
28864
- return deployResponse({
28865
- ok: true,
28866
- unchanged: false,
28867
- project_ref: projectRef,
28868
- deployment_id: selected.id,
28869
- release_id: activated.active_release_id,
28870
- activation_id: activated.activation_id,
28871
- previous_release_id: activeReleaseId,
28872
- previous_activation_id: activeActivationId,
28873
- url: finalDeployment.deploymentUrl,
28874
- file_count: archive.fileCount,
28875
- source_bytes: archive.sourceBytes
28876
- }, args.json === true);
28877
- } finally {
28878
- await archive.cleanup();
28879
- }
28607
+ throw new Error(DEPLOYMENT_UPGRADE);
28880
28608
  });
28881
28609
  }
28882
28610
 
@@ -29168,6 +28896,7 @@ var TEST_PROFILE = [
29168
28896
  "SUPACLOUD_API_URL=https://management.test.example.com",
29169
28897
  "SUPACLOUD_API_TOKEN=<test-management-api-token>",
29170
28898
  "SUPACLOUD_PROJECT_REF=<test-project-ref>",
28899
+ "SUPACLOUD_APPLICATION_ID=<application-id>",
29171
28900
  ""
29172
28901
  ].join(`
29173
28902
  `);
@@ -29176,6 +28905,7 @@ var PRODUCTION_PROFILE = [
29176
28905
  "SUPACLOUD_API_URL=https://management.example.com",
29177
28906
  "SUPACLOUD_API_TOKEN=<production-management-api-token>",
29178
28907
  "SUPACLOUD_PROJECT_REF=<production-project-ref>",
28908
+ "SUPACLOUD_APPLICATION_ID=<application-id>",
29179
28909
  ""
29180
28910
  ].join(`
29181
28911
  `);
@@ -29299,6 +29029,7 @@ async function initializeProject(request, options) {
29299
29029
  },
29300
29030
  next: [
29301
29031
  "cp .env.supacloud.test.example .env.supacloud.test",
29032
+ "Set SUPACLOUD_APPLICATION_ID to the application's ID and configure its current SupaCloud delivery build.",
29302
29033
  "supacloud env check",
29303
29034
  "supacloud --env test deploy"
29304
29035
  ]
@@ -29346,17 +29077,16 @@ function statusMissing(context) {
29346
29077
  }
29347
29078
  return ["SUPACLOUD_API_URL", "SUPACLOUD_API_TOKEN", "SUPACLOUD_PROJECT_REF"];
29348
29079
  }
29349
- function deployMissing(context) {
29080
+ function deployMissing(context, application) {
29350
29081
  const missing = statusMissing(context);
29351
29082
  if (context.credentialScope !== "management") {
29352
29083
  return [...missing, "Management API context"];
29353
29084
  }
29354
- if (context.applicationId) {
29355
- if (!context.applicationEnvironmentId)
29356
- missing.push("SUPACLOUD_APPLICATION_ENVIRONMENT_ID");
29357
- if (!context.applicationConfigurationPath && !context.applicationConfigurationId) {
29358
- missing.push("SUPACLOUD_APPLICATION_CONFIGURATION_PATH or SUPACLOUD_APPLICATION_CONFIGURATION_ID");
29359
- }
29085
+ if (!Check2(ApplicationIdSchema, context.applicationId || application?.id)) {
29086
+ missing.push("valid SUPACLOUD_APPLICATION_ID or supacloud.json application.id");
29087
+ }
29088
+ if (!Check2(ApplicationIdSchema, context.applicationEnvironmentId || application?.environmentId)) {
29089
+ missing.push("valid SUPACLOUD_APPLICATION_ENVIRONMENT_ID or SUPACLOUD_ENV");
29360
29090
  }
29361
29091
  return missing;
29362
29092
  }
@@ -29366,7 +29096,13 @@ async function inspectProfile(profile) {
29366
29096
  environmentName: profile.name
29367
29097
  });
29368
29098
  const status = statusMissing(context);
29369
- const deploy = deployMissing(context);
29099
+ const configText = await readFile8(join19(dirname12(profile.path), "supacloud.json"), "utf8").catch((error) => {
29100
+ if (error instanceof Error && "code" in error && error.code === "ENOENT")
29101
+ return "{}";
29102
+ throw error;
29103
+ });
29104
+ const config = record5(JSON.parse(configText));
29105
+ const deploy = deployMissing(context, record5(config?.application));
29370
29106
  return {
29371
29107
  name: profile.name,
29372
29108
  path: profile.path,
@@ -29459,109 +29195,10 @@ function registerDeveloperExperienceTools(server, options = {}) {
29459
29195
  registerTool(server, "init", "Create SupaCloud deployment configuration and non-secret environment examples without overwriting project files.", initToolSchema, (request) => initializeProject(request, options));
29460
29196
  registerTool(server, "env", "List or locally validate named SupaCloud environment profiles without contacting the platform or printing secrets.", environmentToolSchema, (request) => inspectEnvironment(request, options));
29461
29197
  }
29462
-
29463
- // src/shared/tools/project-deployment.ts
29464
- var operation = "applications.project_deploy";
29465
- var scopeSchema = _Object_({
29466
- project_ref: String2({ pattern: "^[a-z0-9-]{1,20}$" }),
29467
- application_id: ApplicationIdSchema,
29468
- environment_id: ApplicationIdSchema
29469
- });
29470
- var statusSchema = _Object_({
29471
- schema: Literal("supacloud.project-deployment-status.v1"),
29472
- deployment_id: ApplicationDeployRequestSchema.properties.mutation_id,
29473
- status: Union([
29474
- Literal("pending"),
29475
- Literal("running"),
29476
- Literal("succeeded"),
29477
- Literal("failed_retryable"),
29478
- Literal("failed_terminal"),
29479
- Literal("outcome_unknown")
29480
- ]),
29481
- phase: Union([
29482
- Literal("queued"),
29483
- Literal("prepared"),
29484
- Literal("migrating"),
29485
- Literal("activating"),
29486
- Literal("binding-web"),
29487
- Literal("verifying")
29488
- ]),
29489
- operation: Literal("application.release.deploy"),
29490
- ...scopeSchema.properties,
29491
- receipt: Optional(Unknown()),
29492
- failure_code: Optional(String2({ pattern: "^[A-Z][A-Z0-9_]{0,63}$" }))
29493
- }, { additionalProperties: false });
29494
- function responseReceipt(data, expected) {
29495
- if (!data || typeof data !== "object" || Array.isArray(data) || !("ok" in data) || data.ok !== true) {
29496
- throw new Error("Invalid project deployment response");
29497
- }
29498
- const { ok: _ok, ...receipt } = data;
29499
- return parseApplicationDeployResult(receipt, expected);
29500
- }
29501
- async function runProjectDeployment(http, args) {
29502
- const scope = { project_ref: args.ref, application_id: args.id, environment_id: args.environment_id };
29503
- const request = {
29504
- mutation_id: args.mutation_id,
29505
- release_id: args.release_id,
29506
- ...args.configuration_id === undefined ? {} : { configuration_id: args.configuration_id },
29507
- ...args.configuration_write === undefined ? {} : { configuration_write: args.configuration_write },
29508
- ...args.expected_plan_sha256 === undefined ? {} : { expected_plan_sha256: args.expected_plan_sha256 },
29509
- ...args.approved_migration_digest === undefined ? {} : { approved_migration_digest: args.approved_migration_digest },
29510
- ...args.web_release === undefined ? {} : { web_release: args.web_release }
29511
- };
29512
- if (!Check2(scopeSchema, scope) || !Check2(ApplicationDeployRequestSchema, request) || request.configuration_id !== undefined && request.configuration_write !== undefined && request.configuration_id !== request.configuration_write.configuration_id) {
29513
- return releaseControlFailure(operation, "INVALID_RESPONSE", null);
29514
- }
29515
- const expected = { ...scope, request };
29516
- const identity = {
29517
- ...scope,
29518
- deployment_id: request.mutation_id,
29519
- mutation_id: request.mutation_id,
29520
- release_id: request.release_id
29521
- };
29522
- const endpoint = `/v1/projects/${encodeURIComponent(scope.project_ref)}/environments/${encodeURIComponent(scope.environment_id)}/deployments`;
29523
- const { mutation_id, ...candidate } = request;
29524
- const result = await http.post(endpoint, {
29525
- ...candidate,
29526
- deployment_id: mutation_id,
29527
- application_id: scope.application_id
29528
- }, { timeoutMs: 120000, maxJsonBytes: 131072, responseTimeoutMs: 30000 });
29529
- if (result.ok && result.status === 200) {
29530
- try {
29531
- return releaseControlSuccess(operation, {
29532
- ...responseReceipt(result.data, expected),
29533
- deployment_id: mutation_id
29534
- });
29535
- } catch {}
29536
- } else if (!result.ok && !result.transportError && !result.responseReadError && result.status < 500 && result.status !== 408) {
29537
- return releaseControlMutationFailure(operation, result, identity);
29538
- }
29539
- const observed = await http.get(`${endpoint}/${encodeURIComponent(mutation_id)}`, {
29540
- retry: false,
29541
- totalTimeoutMs: 30000,
29542
- maxJsonBytes: 131072
29543
- });
29544
- try {
29545
- if (!observed.ok || observed.status !== 200 || !Check2(statusSchema, observed.data))
29546
- throw new Error;
29547
- const state = observed.data;
29548
- if (state.deployment_id !== mutation_id || state.project_ref !== scope.project_ref || state.application_id !== scope.application_id || state.environment_id !== scope.environment_id || state.status !== "succeeded" || state.phase !== "verifying")
29549
- throw new Error;
29550
- const receipt = parseApplicationDeployResult(state.receipt, expected);
29551
- return releaseControlSuccess(operation, {
29552
- ...receipt,
29553
- deployment_id: mutation_id,
29554
- replayed: true,
29555
- execution_performed: false
29556
- });
29557
- } catch {
29558
- return releaseControlFailure(operation, "OUTCOME_UNKNOWN", result.transportError ? null : result.status, identity);
29559
- }
29560
- }
29561
29198
  // package.json
29562
29199
  var package_default = {
29563
29200
  name: "@supacloud/cli",
29564
- version: "0.93.0",
29201
+ version: "0.94.0",
29565
29202
  description: "Project-scoped CLI for SupaCloud users",
29566
29203
  type: "module",
29567
29204
  main: "./dist/index.js",
@@ -29615,7 +29252,7 @@ var package_default = {
29615
29252
  dependencies: {
29616
29253
  "@supacloud/compiler": "^0.44.1",
29617
29254
  "@supacloud/delivery": "0.16.0",
29618
- "@supacloud/db": "0.16.1",
29255
+ "@supacloud/db": "0.16.2",
29619
29256
  typebox: "1.3.36"
29620
29257
  },
29621
29258
  overrides: {
@@ -29837,8 +29474,6 @@ EXAMPLES
29837
29474
  ${preferredCommand} deploy
29838
29475
  ${preferredCommand} --env test deploy
29839
29476
  ${preferredCommand} --env production deploy --confirm-production <project-ref>
29840
- ${preferredCommand} deploy --target web
29841
- ${preferredCommand} deploy --target api
29842
29477
  ${preferredCommand} dev sync --env test --target functions --function api
29843
29478
  ${preferredCommand} dev status --env test
29844
29479
  ${preferredCommand} dev watch --env test --target project
@@ -30194,8 +29829,7 @@ function createCliTools(context, confirmProduction) {
30194
29829
  applicationRelease: (args) => tools.app ? tools.app.callback(args) : Promise.resolve({
30195
29830
  isError: true,
30196
29831
  content: [{ type: "text", text: "Application release support is unavailable in this CLI context" }]
30197
- }),
30198
- edgeFunctionDeploy: advancedTools.edge_functions?.callback
29832
+ })
30199
29833
  })));
30200
29834
  assign(captureTools((server) => registerGatewayTools(server, http, {
30201
29835
  projectRef: context.projectRef || undefined
@@ -0,0 +1,2 @@
1
+ export declare const DEPLOYMENT_UPGRADE = "DEPLOYMENT_UPGRADE_REQUIRED: Legacy deployment is no longer supported. Upgrade with `npm install -g @supacloud/cli@latest`, bind application.id and application.environmentId in supacloud.json (or SUPACLOUD_APPLICATION_ID and SUPACLOUD_ENV), then run `supacloud deploy`. SupaCloud owns deployment planning and activation; do not retry with legacy commands.";
2
+ export declare function assertCurrentDeploymentInput(tool: string, input: unknown): void;
@@ -9,7 +9,7 @@ export declare const APPLICATION_TOOL_SCHEMA: {
9
9
  ttl_seconds: Type.TOptional<Type.TInteger>;
10
10
  wait: Type.TOptional<Type.TBoolean>;
11
11
  timeout_seconds: Type.TOptional<Type.TInteger>;
12
- action: Type.TEnum<("list_releases" | "get_release" | "upload_release" | "activate_release" | "logs" | "get_runtime" | "get_deployment_evidence" | "get_configuration" | "get_release_transfer_plan" | "get_promotion_plan" | "get_promotion_status" | "get_rollback_snapshot" | "get_history" | "get_deploy_plan" | "get_preview_plan" | "list_previews" | "get_preview" | "put_configuration" | "deploy_application" | "transfer_release" | "promote_application" | "reconcile_promotion" | "create_preview" | "reconcile_preview" | "cleanup_preview" | "deploy_release" | "rollback_release" | "reconcile_activation" | "retire_activation")[]>;
12
+ action: Type.TEnum<("upload_release" | "activate_release" | "list_releases" | "get_release" | "logs" | "get_runtime" | "get_deployment_evidence" | "get_configuration" | "get_release_transfer_plan" | "get_promotion_plan" | "get_promotion_status" | "get_rollback_snapshot" | "get_history" | "get_deploy_plan" | "get_preview_plan" | "list_previews" | "get_preview" | "put_configuration" | "deploy_application" | "transfer_release" | "promote_application" | "reconcile_promotion" | "create_preview" | "reconcile_preview" | "cleanup_preview" | "deploy_release" | "rollback_release" | "reconcile_activation" | "retire_activation")[]>;
13
13
  ref: Type.TString;
14
14
  id: Type.TString;
15
15
  environment_id: Type.TOptional<Type.TString>;
@@ -14,7 +14,6 @@ interface DeployToolOptions {
14
14
  manifestPath?: string;
15
15
  };
16
16
  applicationRelease?: (args: Record<string, unknown>) => Promise<ToolResponse>;
17
- edgeFunctionDeploy?: (args: Record<string, unknown>) => Promise<ToolResponse>;
18
17
  }
19
18
  interface ToolResponse {
20
19
  content: Array<{
@@ -25,15 +24,8 @@ interface ToolResponse {
25
24
  }
26
25
  export declare const deployToolSchema: {
27
26
  ref: Type.TOptional<Type.TString>;
28
- target: Type.TOptional<Type.TString>;
29
- id: Type.TOptional<Type.TString>;
30
27
  cwd: Type.TOptional<Type.TString>;
31
- build_command: Type.TOptional<Type.TString>;
32
- output_dir: Type.TOptional<Type.TString>;
33
28
  skip_build: Type.TOptional<Type.TBoolean>;
34
- dry_run: Type.TOptional<Type.TBoolean>;
35
- plan: Type.TOptional<Type.TBoolean>;
36
- diff: Type.TOptional<Type.TBoolean>;
37
29
  json: Type.TOptional<Type.TBoolean>;
38
30
  };
39
31
  export declare function findDeployConfigRoot(startDirectory: string, environmentName?: string): Promise<string>;
@@ -15,7 +15,7 @@ export declare const environmentToolSchema: ToolSchema;
15
15
  declare function outputDirectoryFor(manifest: PackageManifest): string;
16
16
  declare function profileNameFromFile(name: string): string | null;
17
17
  declare function statusMissing(context: ResolvedContext): string[];
18
- declare function deployMissing(context: ResolvedContext): string[];
18
+ declare function deployMissing(context: ResolvedContext, application?: Record<string, unknown> | null): string[];
19
19
  export declare function registerDeveloperExperienceTools(server: ToolServer, options?: DeveloperExperienceOptions): void;
20
20
  export declare const __internal: {
21
21
  outputDirectoryFor: typeof outputDirectoryFor;
@@ -1,3 +1,4 @@
1
1
  import type { ToolResult } from "../tool-server";
2
2
  import type { HttpTransport } from "../transports/http";
3
+ export declare function requireProjectDeploymentProtocol(http: Pick<HttpTransport, "get">, projectRef: string, environmentId: string): Promise<void>;
3
4
  export declare function runProjectDeployment(http: Pick<HttpTransport, "post" | "get">, args: Record<string, unknown>): Promise<ToolResult>;
@@ -20,7 +20,7 @@ export interface RemoteDevToolOptions {
20
20
  }
21
21
  export declare const remoteDevToolSchema: {
22
22
  action: Type.TEnum<("status" | "migrate" | "watch" | "sync")[]>;
23
- target: Type.TOptional<Type.TEnum<("project" | "frontend" | "db" | "functions")[]>>;
23
+ target: Type.TOptional<Type.TEnum<("frontend" | "project" | "db" | "functions")[]>>;
24
24
  project_dir: Type.TOptional<Type.TString>;
25
25
  remote_root: Type.TOptional<Type.TString>;
26
26
  remote_host: Type.TOptional<Type.TString>;
@@ -129,12 +129,37 @@ function schemaProperties(schema) {
129
129
  // src/shared/tool-server.ts
130
130
  import { Type as Type2 } from "typebox";
131
131
  import { Value as Value2 } from "typebox/value";
132
+
133
+ // src/shared/deployment-upgrade.ts
134
+ var DEPLOYMENT_UPGRADE = "DEPLOYMENT_UPGRADE_REQUIRED: Legacy deployment is no longer supported. Upgrade with `npm install -g @supacloud/cli@latest`, bind application.id and application.environmentId in supacloud.json (or SUPACLOUD_APPLICATION_ID and SUPACLOUD_ENV), then run `supacloud deploy`. SupaCloud owns deployment planning and activation; do not retry with legacy commands.";
135
+ var removedFrontendActions = new Set([
136
+ "deploy_git",
137
+ "deploy_upload",
138
+ "redeploy",
139
+ "upload_release",
140
+ "activate_release"
141
+ ]);
142
+ var removedDeployFlags = ["target", "id", "build_command", "output_dir", "plan", "diff", "dry_run"];
143
+ function assertCurrentDeploymentInput(tool, input) {
144
+ if (!input || typeof input !== "object" || Array.isArray(input))
145
+ return;
146
+ const args = input;
147
+ if (tool === "frontend" && typeof args["action"] === "string" && removedFrontendActions.has(args["action"])) {
148
+ throw new Error(DEPLOYMENT_UPGRADE);
149
+ }
150
+ if (tool === "deploy" && removedDeployFlags.some((flag) => Object.hasOwn(args, flag))) {
151
+ throw new Error(`${DEPLOYMENT_UPGRADE} Put build settings in the frontend configuration. For read-only review use \`supacloud app deploy-plan\`.`);
152
+ }
153
+ }
154
+
155
+ // src/shared/tool-server.ts
132
156
  var resultSchema = Type2.Object({
133
157
  content: Type2.Array(Type2.Object({ type: Type2.Literal("text"), text: Type2.String() })),
134
158
  isError: Type2.Optional(Type2.Boolean())
135
159
  });
136
160
  function registerTool(server, name, description, schema, callback) {
137
161
  server.tool(name, description, schema, async (input) => {
162
+ assertCurrentDeploymentInput(name, input);
138
163
  const args = parseToolArguments(schema, input);
139
164
  const result = await callback(args);
140
165
  if (!Value2.Check(resultSchema, result))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/cli",
3
- "version": "0.93.0",
3
+ "version": "0.94.0",
4
4
  "description": "Project-scoped CLI for SupaCloud users",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -54,7 +54,7 @@
54
54
  "dependencies": {
55
55
  "@supacloud/compiler": "^0.44.1",
56
56
  "@supacloud/delivery": "0.16.0",
57
- "@supacloud/db": "0.16.1",
57
+ "@supacloud/db": "0.16.2",
58
58
  "typebox": "1.3.36"
59
59
  },
60
60
  "overrides": {
@@ -29,6 +29,15 @@ Use `supacloud` as the project-level control surface and keep durable changes re
29
29
 
30
30
  ## Workflow
31
31
 
32
+ For normal application deployment, run `supacloud --env <name> deploy`.
33
+ Use the current application delivery configuration and a bound application ID.
34
+ Do not assemble a client-side sequence of scope generation, backup, migration
35
+ apply, PostgREST refresh, Function deployment and Web activation.
36
+ The server owns that sequence. Old `deploy --target` and `frontend` publishing
37
+ actions are removed: upgrade CLI and server together, migrate the configuration,
38
+ and retry the unified command. Do not substitute a legacy endpoint when it fails.
39
+ The inspection and database-maintenance workflow below is not a deployment checklist.
40
+
32
41
  1. Classify the request using [references/command-map.md](references/command-map.md).
33
42
  2. For database work, load [references/database-workflow.md](references/database-workflow.md) and choose either new-change or historical-drift reconciliation.
34
43
  3. For AI-assisted database changes, first request bounded context:
@@ -6,6 +6,7 @@ Load this reference when selecting a command surface or when a user asks an AI t
6
6
 
7
7
  | Intent | Use | Guardrail |
8
8
  | --- | --- | --- |
9
+ | Deploy the bound application | `supacloud --env <name> deploy` | Use current CLI/server and application delivery configuration; never fall back to standalone target or direct frontend activation commands |
9
10
  | Inspect current project binding | `supacloud status` | Read-only; run first |
10
11
  | Inspect selected project API/Auth/Studio origins | `supacloud project endpoints` | Uses the Management API's authoritative projection; do not reconstruct domains locally |
11
12
  | Enumerate projects or endpoint projections | `supacloud-admin project list` / `project list_endpoints` | Platform-wide read; never promote a project credential to Admin authority |
@@ -23,9 +24,6 @@ Load this reference when selecting a command surface or when a user asks an AI t
23
24
  | Inspect or back up a remote database | `supabase db_pull`, `migration_list`, `db_dump`, `gen_types` | Requires explicit PostgreSQL DSN; redact it |
24
25
  | Replay the release-canary fixture stage receipt | `release release_canary_fixture_stage_replay` | Dual-bind Management project context and that project's service-role application origin; accepts only the exact subject/request UUID pair and a strict idempotent receipt |
25
26
  | Disable a staged release-canary fixture | `release release_canary_fixture_disable_replay` | Uses the fixed disable RPC once, accepts idempotent false/true receipts, then queries the existing pending RPC with issuer/subject and requires authoritative JSON `false`; fixture binding comes from the strict disable receipt |
26
- | Rebind release scope manifest to upstream base commit | `release scope_rebind` | Local maintenance action; requires the exact `--environment_id`, updates only scopes bound to that environment, supports `--dry_run`/`--task`, and recalculates `scope_sha256` |
27
- | Inspect release scope manifest and git alignment | `release scope_inspect` | Local maintenance action; requires the exact `--environment_id`, rejects legacy or cross-environment scopes, returns canonical hash, and checks alignment with HEAD / origin/main |
28
- | Scaffold a new canonical release scope manifest | `release scope_create` | Local maintenance action; requires `--environment_id` and outputs a current-main-bound scope with sorted functions, migrations, and environment identity |
29
27
  | Preview/apply migrations remotely | `supabase push` | Always dry-run first; production needs explicit approval |
30
28
  | Mark proven-equivalent historical migrations as applied | `database baseline_migrations` | Dry-run, schema-equivalence proof, backup, explicit approval |
31
29
  | Inspect auth users or generate a controlled login link | `auth list_users`, `auth get_user`, `auth generate_link` | User reads are bounded; generation supports only `magiclink`, `recovery`, and `invite`, requires production confirmation, and returns only a validated action URL |
@@ -47,11 +45,11 @@ until a project-scoped context is resolved.
47
45
  - `auth`: provider/configuration plus bounded user lookup and production-confirmed `magiclink`, `recovery`, or `invite` generation. Search/email/redirect inputs and returned action URLs are bounded and validated before use.
48
46
  - `storage`: buckets and object-management workflows.
49
47
  - `edge_functions`: list, atomically read one active or deleted identity with `get_config`, read immutable source, deploy, activate, configure, and delete Edge Functions. For every mutation, pass the `activation_id` read from the same `list` or `get_config` snapshot as `--expected-activation-id`; use `legacy` only for a never-created or listed legacy Function, not for a deleted slug with a tombstone UUID. Deploy and activate actions also require the non-negative observed version as `--expected-active-version`; use `absent` for a never-created slug or a `get_config` tombstone. Version `0` is a legacy version token and cannot be used as a source or activation target.
50
- - `frontend`: list, build/deploy, domain, and deployment workflows.
48
+ - `frontend`: hosting management, release inspection and operator rollback. Publishing uses `supacloud deploy`; `deploy_git`, `deploy_upload`, `redeploy`, `upload_release` and `activate_release` are removed.
51
49
  - `secrets`: project secret management; never print values after write.
52
50
  - `queue`, `task_events`, `diagnostics`: asynchronous workload operations and bounded diagnostics.
53
51
  - `gateway`: project route/config/rebuild operations.
54
- - `release`: local release scope maintenance (`scope_rebind`, `scope_inspect`, `scope_create`), verified backup/PostgREST lifecycle controls, plus production-confirmed, non-retried, strict release-canary fixture stage and disable replay actions. Scope maintenance is not a prerequisite for the normal `supacloud --env <name> deploy` flow.
52
+ - `release`: explicit backup/PostgREST maintenance and release-canary replay diagnostics. Do not run these or generate release scopes as prerequisites for `supacloud --env <name> deploy`; the platform owns deployment orchestration.
55
53
  - `ai`: inspect or install this packaged Skill.
56
54
 
57
55
  ## Safe inspection pattern