@supacloud/cli 0.90.2 → 0.92.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/dist/index.js CHANGED
@@ -10553,10 +10553,29 @@ var ApplicationDeployResultSchema = _Object_({
10553
10553
  replayed: Boolean2()
10554
10554
  }, closed3))
10555
10555
  }, closed3);
10556
- function parseApplicationDeployResult(value) {
10556
+ function parseApplicationDeployResult(value, expected) {
10557
10557
  if (!Check2(ApplicationDeployResultSchema, value)) {
10558
10558
  throw new Error("Invalid application deploy result");
10559
10559
  }
10560
+ const backend = value.backend_receipt;
10561
+ const bindingKeys = [
10562
+ "project_ref",
10563
+ "application_id",
10564
+ "environment_id",
10565
+ "release_id",
10566
+ "mutation_id",
10567
+ "plan_sha256"
10568
+ ];
10569
+ if (bindingKeys.some((key) => backend[key] !== value[key]) || !backend.postgrest_healthy || backend.applied_versions.length > 0 && (!backend.schema_reloaded || !backend.backup_id)) {
10570
+ throw new Error("Invalid application deploy receipt binding");
10571
+ }
10572
+ if (expected) {
10573
+ const { request } = expected;
10574
+ const configurationId = request.configuration_id ?? request.configuration_write?.configuration_id;
10575
+ if (value.project_ref !== expected.project_ref || value.application_id !== expected.application_id || value.environment_id !== expected.environment_id || value.release_id !== request.release_id || value.mutation_id !== request.mutation_id || configurationId !== undefined && value.configuration_id !== configurationId || request.configuration_write !== undefined && value.configuration_id !== request.configuration_write.configuration_id || request.expected_plan_sha256 !== undefined && value.plan_sha256 !== request.expected_plan_sha256 || Boolean(value.web) !== Boolean(request.web) || request.web !== undefined && (value.web?.deployment_id !== request.web.deployment_id || value.web.release_id !== request.web.release_id)) {
10576
+ throw new Error("Application deploy receipt does not match request");
10577
+ }
10578
+ }
10560
10579
  return structuredClone(value);
10561
10580
  }
10562
10581
  // node_modules/@supacloud/delivery/src/application-history.ts
@@ -25509,6 +25528,8 @@ function releasePhaseFailure(phase, result, completed) {
25509
25528
  };
25510
25529
  if (phaseResult?.activation_id !== undefined)
25511
25530
  payload.activation_id = phaseResult.activation_id;
25531
+ if (typeof phaseResult?.deployment_id === "string")
25532
+ payload.deployment_id = phaseResult.deployment_id;
25512
25533
  if (phaseResult?.expected_activation_id !== undefined) {
25513
25534
  payload.expected_activation_id = phaseResult.expected_activation_id;
25514
25535
  }
@@ -25612,7 +25633,7 @@ async function runAppRelease(request, options, delegate) {
25612
25633
  ...request.web === undefined ? {} : { web: request.web }
25613
25634
  };
25614
25635
  validateToolArguments(APPLICATION_TOOL_SCHEMA, deployArgs);
25615
- const deployed = await delegate(deployArgs);
25636
+ const deployed = options.projectDeployment ? await options.projectDeployment(deployArgs) : await delegate(deployArgs);
25616
25637
  const deployReceipt = resultObject(deployed);
25617
25638
  const resolvedConfigurationId = typeof deployReceipt?.configuration_id === "string" ? deployReceipt.configuration_id : undefined;
25618
25639
  if (resolvedConfigurationId !== undefined) {
@@ -25632,6 +25653,7 @@ async function runAppRelease(request, options, delegate) {
25632
25653
  application_id: id,
25633
25654
  environment_id: environmentId,
25634
25655
  release_id: releaseId,
25656
+ ...typeof deployReceipt.deployment_id === "string" ? { deployment_id: deployReceipt.deployment_id } : {},
25635
25657
  ...configurationId === undefined ? {} : { configuration_id: configurationId },
25636
25658
  manifest_path: manifestPath,
25637
25659
  build: buildReceipt ? completed.build : { skipped: true, manifest_path: manifestPath },
@@ -25646,6 +25668,7 @@ async function runAppRelease(request, options, delegate) {
25646
25668
  `Release: ${releaseId}`,
25647
25669
  `Configuration: ${configurationId}`,
25648
25670
  `Activation: ${result.activation_id ?? "not performed"}`,
25671
+ ...result.deployment_id === undefined ? [] : [`Deployment: ${result.deployment_id}`],
25649
25672
  `Build: ${request.skip_build ? "skipped" : "completed"}`
25650
25673
  ].join(`
25651
25674
  `));
@@ -28163,12 +28186,35 @@ async function readDeployConfig(configRoot) {
28163
28186
  return {};
28164
28187
  const config = record4(candidate);
28165
28188
  const frontend = record4(config?.frontend);
28189
+ const application = record4(config?.application);
28166
28190
  const targetValues = record4(config?.targets);
28167
- if (!config || config.frontend !== undefined && !frontend || config.targets !== undefined && !targetValues) {
28168
- throw new Error("supacloud.json must contain object-valued 'frontend' or 'targets' configuration");
28191
+ if (!config || config.frontend !== undefined && !frontend || config.application !== undefined && !application || config.targets !== undefined && !targetValues) {
28192
+ throw new Error("supacloud.json must contain object-valued 'application', 'frontend' or 'targets' configuration");
28193
+ }
28194
+ let parsedApplication;
28195
+ if (application) {
28196
+ const id = optionalText(application.id, "application.id");
28197
+ const environmentId = optionalText(application.environmentId, "application.environmentId");
28198
+ if (!id || !Check2(ApplicationIdSchema, id) || environmentId !== undefined && !Check2(ApplicationIdSchema, environmentId)) {
28199
+ throw new Error("supacloud.json requires a valid application.id and optional application.environmentId");
28200
+ }
28201
+ const configurationPath = optionalText(application.configurationPath, "application.configurationPath");
28202
+ const configurationId = optionalText(application.configurationId, "application.configurationId");
28203
+ if (configurationPath && configurationId) {
28204
+ throw new Error("supacloud.json application must set only one of application.configurationPath or application.configurationId");
28205
+ }
28206
+ const manifestPath = optionalText(application.manifestPath, "application.manifestPath");
28207
+ parsedApplication = {
28208
+ id,
28209
+ ...environmentId ? { environmentId } : {},
28210
+ ...configurationPath ? { configurationPath } : {},
28211
+ ...configurationId ? { configurationId } : {},
28212
+ ...manifestPath ? { manifestPath } : {}
28213
+ };
28169
28214
  }
28170
28215
  return {
28171
28216
  defaultTarget: optionalText(config.defaultTarget, "defaultTarget"),
28217
+ ...parsedApplication === undefined ? {} : { application: parsedApplication },
28172
28218
  targets: targetValues ? Object.fromEntries(Object.entries(targetValues).map(([name, value]) => [name, deployTargetConfig(value, name)])) : undefined,
28173
28219
  frontend: frontend ? {
28174
28220
  id: optionalText(frontend.id, "frontend.id"),
@@ -28186,6 +28232,25 @@ async function packageName(projectDirectory) {
28186
28232
  async function pathExists(path) {
28187
28233
  return access(path).then(() => true, () => false);
28188
28234
  }
28235
+ async function hasProjectReleaseManifest(root) {
28236
+ if (await pathExists(join17(root, "deploy-manifest.json")))
28237
+ return true;
28238
+ const directory = await opendir(join17(root, "supacloud")).catch((error) => {
28239
+ if (error instanceof Error && "code" in error && error.code === "ENOENT")
28240
+ return null;
28241
+ throw error;
28242
+ });
28243
+ if (!directory)
28244
+ return false;
28245
+ let entries = 0;
28246
+ for await (const entry of directory) {
28247
+ if (++entries > 128)
28248
+ throw new Error("Too many SupaCloud project roots to resolve a deployment");
28249
+ if ((entry.isDirectory() || entry.isSymbolicLink()) && await pathExists(join17(root, "supacloud", entry.name, "deploy-manifest.json")))
28250
+ return true;
28251
+ }
28252
+ return false;
28253
+ }
28189
28254
  async function defaultBuildCommand(projectDirectory, configRoot) {
28190
28255
  const packageJson = record4(await readJson(join17(projectDirectory, "package.json")));
28191
28256
  const scripts = record4(packageJson?.scripts);
@@ -28457,16 +28522,10 @@ async function stageApplicationWebRelease(http, projectRef, configRoot, skipBuil
28457
28522
  const remoteOutput = selected.outputDirectory && selected.outputDirectory !== "." ? selected.outputDirectory : "";
28458
28523
  const outputDirectory = resolveInsideProject(projectDirectory, configuredOutput || remoteOutput || defaultOutputDirectory(selected.framework));
28459
28524
  const build = createBuildEnvironment(target.config.buildEnv, environmentVariables);
28460
- const report = phaseReporter(json);
28461
- report("web", `${target.name} (${selected.id})`);
28462
28525
  assertBuildEnvironment(build, skipBuild);
28463
28526
  if (!skipBuild) {
28464
- report("build", buildCommand);
28465
28527
  await runBuild(buildCommand, projectDirectory, build.env, json);
28466
- } else {
28467
- report("build", "skipped");
28468
28528
  }
28469
- report("package", relative10(projectDirectory, outputDirectory) || ".");
28470
28529
  const archive = await createFrontendArchive(outputDirectory);
28471
28530
  try {
28472
28531
  const authority = payload(await readFrontendReleaseAuthority(http, projectRef, selected.id));
@@ -28477,10 +28536,8 @@ async function stageApplicationWebRelease(http, projectRef, configRoot, skipBuil
28477
28536
  throw new Error("Active Web release tree does not match the local archive");
28478
28537
  }
28479
28538
  if (archive.treeSha256 === current?.tree_sha256) {
28480
- report("done", "Web already current");
28481
28539
  return {};
28482
28540
  }
28483
- report("upload", `${archive.fileCount} files`);
28484
28541
  const uploaded = payload(await uploadFrontendRelease(http, projectRef, selected.id, archive.archivePath));
28485
28542
  const release = record4(uploaded.release);
28486
28543
  const releaseId = requiredString(release?.release_id, "release.release_id");
@@ -28488,7 +28545,6 @@ async function stageApplicationWebRelease(http, projectRef, configRoot, skipBuil
28488
28545
  throw new Error("Uploaded Web release identity does not match the local archive");
28489
28546
  if (release?.tree_sha256 !== archive.treeSha256)
28490
28547
  throw new Error("Uploaded Web release tree does not match the local build");
28491
- report("done", releaseId.slice(0, 12));
28492
28548
  return {
28493
28549
  request: {
28494
28550
  deployment_id: selected.id,
@@ -28502,14 +28558,6 @@ async function stageApplicationWebRelease(http, projectRef, configRoot, skipBuil
28502
28558
  await archive.cleanup();
28503
28559
  }
28504
28560
  }
28505
- function phaseReporter(json) {
28506
- const started = performance.now();
28507
- return (phase, detail) => {
28508
- if (!json)
28509
- console.error(` ${phase.padEnd(10)} ${detail}`);
28510
- return Math.round(performance.now() - started);
28511
- };
28512
- }
28513
28561
  function deployResponse(result, json) {
28514
28562
  if (json)
28515
28563
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -28518,14 +28566,13 @@ function deployResponse(result, json) {
28518
28566
  result.url ? `URL: ${String(result.url)}` : null,
28519
28567
  result.release_id ? `Release: ${String(result.release_id)}` : null,
28520
28568
  result.active_version ? `Active version: ${String(result.active_version)}` : null,
28521
- result.file_count ? `Files: ${String(result.file_count)}` : null,
28522
- `Duration: ${(Number(result.duration_ms) / 1000).toFixed(1)}s`
28569
+ result.file_count ? `Files: ${String(result.file_count)}` : null
28523
28570
  ].filter((line) => Boolean(line));
28524
28571
  return { content: [{ type: "text", text: lines.join(`
28525
28572
  `) }] };
28526
28573
  }
28527
28574
  function registerDeployTools(server, http, options = {}) {
28528
- registerTool(server, "deploy", "Build and publish the linked frontend with one command", deployToolSchema, async (args) => {
28575
+ registerTool(server, "deploy", "Build and publish the linked project with one command", deployToolSchema, async (args) => {
28529
28576
  if (args.dry_run === true && (args.plan === true || args.diff === true)) {
28530
28577
  throw new Error("Use dry_run for resolution only, or plan/diff for a built release comparison, not both");
28531
28578
  }
@@ -28533,8 +28580,19 @@ function registerDeployTools(server, http, options = {}) {
28533
28580
  const projectRef = String(args.ref || options.projectRef || "").trim();
28534
28581
  if (!projectRef)
28535
28582
  throw new Error("A linked project ref is required. Set SUPACLOUD_PROJECT_REF or pass --ref");
28536
- const configRoot = await findDeployConfigRoot(invocationDirectory);
28537
- if (options.application?.id) {
28583
+ const configRoot = await findDeployConfigRoot(invocationDirectory, options.environment ?? undefined);
28584
+ const config = await readDeployConfig(configRoot);
28585
+ const application = options.application?.id ? options.application : config.application ? {
28586
+ ...config.application,
28587
+ environmentId: options.environment || config.application.environmentId
28588
+ } : undefined;
28589
+ if (!args.target && await hasProjectReleaseManifest(configRoot)) {
28590
+ 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.");
28591
+ }
28592
+ if (application?.id) {
28593
+ if (Object.values(config.targets ?? {}).some((target) => target.type === "edge_function")) {
28594
+ throw new Error("PROJECT_DEPLOYMENT_UNSUPPORTED: Application deployment cannot yet include declared Edge Function targets. No partial deployment was performed.");
28595
+ }
28538
28596
  if (!options.applicationRelease) {
28539
28597
  throw new Error("Application release support is unavailable in this CLI context");
28540
28598
  }
@@ -28544,12 +28602,12 @@ function registerDeployTools(server, http, options = {}) {
28544
28602
  if (args.dry_run === true || args.plan === true || args.diff === true) {
28545
28603
  throw new Error("Application profile deploy does not support dry_run, plan, or diff; use app deploy-plan for a read-only plan");
28546
28604
  }
28547
- const environmentId = options.application.environmentId.trim();
28548
- if (!environmentId) {
28605
+ const environmentId = application.environmentId?.trim();
28606
+ if (!environmentId || !Check2(ApplicationIdSchema, environmentId) || !Check2(ApplicationIdSchema, application.id)) {
28549
28607
  throw new Error("Application profile requires SUPACLOUD_APPLICATION_ENVIRONMENT_ID or SUPACLOUD_ENV");
28550
28608
  }
28551
- const configurationPath = options.application.configurationPath?.trim();
28552
- const configurationId = options.application.configurationId?.trim();
28609
+ const configurationPath = application.configurationPath?.trim();
28610
+ const configurationId = application.configurationId?.trim();
28553
28611
  if (configurationPath && configurationId) {
28554
28612
  throw new Error("Application profile must set only one of SUPACLOUD_APPLICATION_CONFIGURATION_PATH or SUPACLOUD_APPLICATION_CONFIGURATION_ID");
28555
28613
  }
@@ -28557,20 +28615,22 @@ function registerDeployTools(server, http, options = {}) {
28557
28615
  const result = await options.applicationRelease({
28558
28616
  action: "release",
28559
28617
  ref: projectRef,
28560
- id: options.application.id,
28618
+ id: application.id,
28561
28619
  environment_id: environmentId,
28562
28620
  root: configRoot,
28563
28621
  ...configurationPath ? { configuration_path: configurationPath } : {},
28564
28622
  ...configurationId ? { configuration_id: configurationId } : {},
28565
- ...options.application.manifestPath ? { manifest_path: options.application.manifestPath } : {},
28623
+ ...application.manifestPath ? { manifest_path: application.manifestPath } : {},
28566
28624
  ...args.skip_build === true ? { skip_build: true } : {},
28567
28625
  ...stagedWeb?.request === undefined ? {} : { web: stagedWeb.request },
28568
28626
  ...args.json === true ? { format: "json", json: true } : {}
28569
28627
  });
28570
28628
  return result;
28571
28629
  }
28572
- const config = await readDeployConfig(configRoot);
28573
28630
  const target = selectTarget(configRoot, invocationDirectory, config, String(args.target || "").trim() || undefined);
28631
+ 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")))) {
28632
+ 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.");
28633
+ }
28574
28634
  const projectDirectory = target.root;
28575
28635
  const targetState = await lstat5(projectDirectory).catch(() => null);
28576
28636
  if (!targetState?.isDirectory() || targetState.isSymbolicLink()) {
@@ -28578,8 +28638,6 @@ function registerDeployTools(server, http, options = {}) {
28578
28638
  }
28579
28639
  const requestedId = String(args.id || target.config.id || "").trim() || undefined;
28580
28640
  const build = createBuildEnvironment(target.config.buildEnv, options.environmentVariables);
28581
- const report = phaseReporter(args.json === true);
28582
- report("inspect", `${target.name} (${projectRef})`);
28583
28641
  if (target.config.type === "edge_function") {
28584
28642
  if (args.diff === true)
28585
28643
  throw new Error("Function release diffs require an immutable function release manifest");
@@ -28624,12 +28682,8 @@ function registerDeployTools(server, http, options = {}) {
28624
28682
  }
28625
28683
  assertBuildEnvironment(build, args.skip_build === true);
28626
28684
  if (args.skip_build !== true) {
28627
- report("build", buildCommand);
28628
28685
  await runBuild(buildCommand, projectDirectory, build.env, args.json === true);
28629
- } else {
28630
- report("build", "skipped");
28631
28686
  }
28632
- report("deploy", `edge function ${slug}`);
28633
28687
  const deployed = await options.edgeFunctionDeploy({
28634
28688
  action: "deploy_bundle",
28635
28689
  ref: projectRef,
@@ -28646,7 +28700,6 @@ function registerDeployTools(server, http, options = {}) {
28646
28700
  return deployed;
28647
28701
  const deployedText = deployed.content.find((chunk) => chunk.type === "text")?.text;
28648
28702
  const deployedPayload = deployedText ? record4(JSON.parse(deployedText)) : null;
28649
- const durationMs = report("done", slug);
28650
28703
  return deployResponse({
28651
28704
  ok: true,
28652
28705
  unchanged: false,
@@ -28655,8 +28708,7 @@ function registerDeployTools(server, http, options = {}) {
28655
28708
  project_ref: projectRef,
28656
28709
  slug,
28657
28710
  active_version: deployedPayload?.active_version,
28658
- activation_id: deployedPayload?.activation_id,
28659
- duration_ms: durationMs
28711
+ activation_id: deployedPayload?.activation_id
28660
28712
  }, args.json === true);
28661
28713
  }
28662
28714
  const listResponse = await http.get(`/v1/projects/${encodeURIComponent(projectRef)}/frontend/deployments`);
@@ -28682,12 +28734,8 @@ function registerDeployTools(server, http, options = {}) {
28682
28734
  }
28683
28735
  assertBuildEnvironment(build, args.skip_build === true);
28684
28736
  if (args.skip_build !== true) {
28685
- report("build", buildCommand);
28686
28737
  await runBuild(buildCommand, projectDirectory, build.env, args.json === true);
28687
- } else {
28688
- report("build", "skipped");
28689
28738
  }
28690
- report("package", relative10(projectDirectory, outputDirectory) || ".");
28691
28739
  const archive = await createFrontendArchive(outputDirectory);
28692
28740
  try {
28693
28741
  const inventory = payload(await readFrontendReleaseAuthority(http, projectRef, selected.id));
@@ -28699,7 +28747,6 @@ function registerDeployTools(server, http, options = {}) {
28699
28747
  }
28700
28748
  const unchanged = archive.treeSha256 === current?.tree_sha256;
28701
28749
  if (args.plan === true || args.diff === true) {
28702
- const durationMs = report("done", unchanged ? "unchanged plan" : "release diff ready");
28703
28750
  return { content: [{ type: "text", text: JSON.stringify({
28704
28751
  schema: "supacloud.frontend-deploy-plan.v1",
28705
28752
  ok: true,
@@ -28722,12 +28769,10 @@ function registerDeployTools(server, http, options = {}) {
28722
28769
  actions: unchanged ? [] : ["upload_release", "activate_release"],
28723
28770
  file_count: archive.fileCount,
28724
28771
  source_bytes: archive.sourceBytes,
28725
- archive_bytes: archive.archiveBytes,
28726
- duration_ms: durationMs
28772
+ archive_bytes: archive.archiveBytes
28727
28773
  }, null, 2) }] };
28728
28774
  }
28729
28775
  if (unchanged) {
28730
- const durationMs = report("done", "already current");
28731
28776
  return deployResponse({
28732
28777
  ok: true,
28733
28778
  unchanged: true,
@@ -28737,11 +28782,9 @@ function registerDeployTools(server, http, options = {}) {
28737
28782
  tree_sha256: archive.treeSha256,
28738
28783
  url: selected.deploymentUrl,
28739
28784
  file_count: archive.fileCount,
28740
- source_bytes: archive.sourceBytes,
28741
- duration_ms: durationMs
28785
+ source_bytes: archive.sourceBytes
28742
28786
  }, args.json === true);
28743
28787
  }
28744
- report("upload", `${archive.fileCount} files`);
28745
28788
  const uploaded = payload(await uploadFrontendRelease(http, projectRef, selected.id, archive.archivePath));
28746
28789
  const release = record4(uploaded.release);
28747
28790
  const releaseId = requiredString(release?.release_id, "release.release_id");
@@ -28749,7 +28792,6 @@ function registerDeployTools(server, http, options = {}) {
28749
28792
  throw new Error("Uploaded release identity does not match the local archive");
28750
28793
  if (release?.tree_sha256 !== archive.treeSha256)
28751
28794
  throw new Error("Uploaded release tree does not match the local build");
28752
- report("activate", releaseId.slice(0, 12));
28753
28795
  const mutationId = crypto.randomUUID();
28754
28796
  const activationResponse = await activateFrontendRelease(http, {
28755
28797
  projectRef,
@@ -28778,7 +28820,6 @@ function registerDeployTools(server, http, options = {}) {
28778
28820
  const activated = payload(activationResponse);
28779
28821
  const finalResponse = await http.get(`/v1/projects/${encodeURIComponent(projectRef)}/frontend/deployments/${encodeURIComponent(selected.id)}`);
28780
28822
  const finalDeployment = finalResponse.ok ? frontendDeployment(finalResponse.data) : selected;
28781
- const durationMs = report("done", finalDeployment.deploymentUrl || selected.id);
28782
28823
  return deployResponse({
28783
28824
  ok: true,
28784
28825
  unchanged: false,
@@ -28790,8 +28831,7 @@ function registerDeployTools(server, http, options = {}) {
28790
28831
  previous_activation_id: activeActivationId,
28791
28832
  url: finalDeployment.deploymentUrl,
28792
28833
  file_count: archive.fileCount,
28793
- source_bytes: archive.sourceBytes,
28794
- duration_ms: durationMs
28834
+ source_bytes: archive.sourceBytes
28795
28835
  }, args.json === true);
28796
28836
  } finally {
28797
28837
  await archive.cleanup();
@@ -29378,10 +29418,109 @@ function registerDeveloperExperienceTools(server, options = {}) {
29378
29418
  registerTool(server, "init", "Create SupaCloud deployment configuration and non-secret environment examples without overwriting project files.", initToolSchema, (request) => initializeProject(request, options));
29379
29419
  registerTool(server, "env", "List or locally validate named SupaCloud environment profiles without contacting the platform or printing secrets.", environmentToolSchema, (request) => inspectEnvironment(request, options));
29380
29420
  }
29421
+
29422
+ // src/shared/tools/project-deployment.ts
29423
+ var operation = "applications.project_deploy";
29424
+ var scopeSchema = _Object_({
29425
+ project_ref: String2({ pattern: "^[a-z0-9-]{1,20}$" }),
29426
+ application_id: ApplicationIdSchema,
29427
+ environment_id: ApplicationIdSchema
29428
+ });
29429
+ var statusSchema = _Object_({
29430
+ schema: Literal("supacloud.project-deployment-status.v1"),
29431
+ deployment_id: ApplicationDeployRequestSchema.properties.mutation_id,
29432
+ status: Union([
29433
+ Literal("pending"),
29434
+ Literal("running"),
29435
+ Literal("succeeded"),
29436
+ Literal("failed_retryable"),
29437
+ Literal("failed_terminal"),
29438
+ Literal("outcome_unknown")
29439
+ ]),
29440
+ phase: Union([
29441
+ Literal("queued"),
29442
+ Literal("prepared"),
29443
+ Literal("migrating"),
29444
+ Literal("activating"),
29445
+ Literal("binding-web"),
29446
+ Literal("verifying")
29447
+ ]),
29448
+ operation: Literal("application.release.deploy"),
29449
+ ...scopeSchema.properties,
29450
+ receipt: Optional(Unknown()),
29451
+ failure_code: Optional(String2({ pattern: "^[A-Z][A-Z0-9_]{0,63}$" }))
29452
+ }, { additionalProperties: false });
29453
+ function responseReceipt(data, expected) {
29454
+ if (!data || typeof data !== "object" || Array.isArray(data) || !("ok" in data) || data.ok !== true) {
29455
+ throw new Error("Invalid project deployment response");
29456
+ }
29457
+ const { ok: _ok, ...receipt } = data;
29458
+ return parseApplicationDeployResult(receipt, expected);
29459
+ }
29460
+ async function runProjectDeployment(http, args) {
29461
+ const scope = { project_ref: args.ref, application_id: args.id, environment_id: args.environment_id };
29462
+ const request = {
29463
+ mutation_id: args.mutation_id,
29464
+ release_id: args.release_id,
29465
+ ...args.configuration_id === undefined ? {} : { configuration_id: args.configuration_id },
29466
+ ...args.configuration_write === undefined ? {} : { configuration_write: args.configuration_write },
29467
+ ...args.expected_plan_sha256 === undefined ? {} : { expected_plan_sha256: args.expected_plan_sha256 },
29468
+ ...args.approved_migration_digest === undefined ? {} : { approved_migration_digest: args.approved_migration_digest },
29469
+ ...args.web === undefined ? {} : { web: args.web }
29470
+ };
29471
+ if (!Check2(scopeSchema, scope) || !Check2(ApplicationDeployRequestSchema, request) || request.configuration_id !== undefined && request.configuration_write !== undefined && request.configuration_id !== request.configuration_write.configuration_id) {
29472
+ return releaseControlFailure(operation, "INVALID_RESPONSE", null);
29473
+ }
29474
+ const expected = { ...scope, request };
29475
+ const identity = {
29476
+ ...scope,
29477
+ deployment_id: request.mutation_id,
29478
+ mutation_id: request.mutation_id,
29479
+ release_id: request.release_id
29480
+ };
29481
+ const endpoint = `/v1/projects/${encodeURIComponent(scope.project_ref)}/environments/${encodeURIComponent(scope.environment_id)}/deployments`;
29482
+ const { mutation_id, ...candidate } = request;
29483
+ const result = await http.post(endpoint, {
29484
+ ...candidate,
29485
+ deployment_id: mutation_id,
29486
+ application_id: scope.application_id
29487
+ }, { timeoutMs: 120000, maxJsonBytes: 131072, responseTimeoutMs: 30000 });
29488
+ if (result.ok && result.status === 200) {
29489
+ try {
29490
+ return releaseControlSuccess(operation, {
29491
+ ...responseReceipt(result.data, expected),
29492
+ deployment_id: mutation_id
29493
+ });
29494
+ } catch {}
29495
+ } else if (!result.ok && !result.transportError && !result.responseReadError && result.status < 500 && result.status !== 408) {
29496
+ return releaseControlMutationFailure(operation, result, identity);
29497
+ }
29498
+ const observed = await http.get(`${endpoint}/${encodeURIComponent(mutation_id)}`, {
29499
+ retry: false,
29500
+ totalTimeoutMs: 30000,
29501
+ maxJsonBytes: 131072
29502
+ });
29503
+ try {
29504
+ if (!observed.ok || observed.status !== 200 || !Check2(statusSchema, observed.data))
29505
+ throw new Error;
29506
+ const state = observed.data;
29507
+ 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")
29508
+ throw new Error;
29509
+ const receipt = parseApplicationDeployResult(state.receipt, expected);
29510
+ return releaseControlSuccess(operation, {
29511
+ ...receipt,
29512
+ deployment_id: mutation_id,
29513
+ replayed: true,
29514
+ execution_performed: false
29515
+ });
29516
+ } catch {
29517
+ return releaseControlFailure(operation, "OUTCOME_UNKNOWN", result.transportError ? null : result.status, identity);
29518
+ }
29519
+ }
29381
29520
  // package.json
29382
29521
  var package_default = {
29383
29522
  name: "@supacloud/cli",
29384
- version: "0.90.2",
29523
+ version: "0.92.0",
29385
29524
  description: "Project-scoped CLI for SupaCloud users",
29386
29525
  type: "module",
29387
29526
  main: "./dist/index.js",
@@ -29434,13 +29573,13 @@ var package_default = {
29434
29573
  },
29435
29574
  dependencies: {
29436
29575
  "@supacloud/compiler": "^0.44.1",
29437
- "@supacloud/delivery": "0.15.0",
29576
+ "@supacloud/delivery": "0.15.1",
29438
29577
  "@supacloud/db": "0.16.1",
29439
29578
  typebox: "1.3.36"
29440
29579
  },
29441
29580
  overrides: {
29442
29581
  "@supacloud/contracts": "0.8.1",
29443
- "@supacloud/delivery": "0.15.0",
29582
+ "@supacloud/delivery": "0.15.1",
29444
29583
  "@graphql-tools/utils": "^12.0.3"
29445
29584
  },
29446
29585
  devDependencies: {
@@ -29996,7 +30135,8 @@ function createCliTools(context, confirmProduction) {
29996
30135
  Object.assign(tools, captureTools((server) => registerAppTools(server, {
29997
30136
  getApplications: () => tools.applications?.callback,
29998
30137
  onDevProgress: writeDevProgress,
29999
- projectRef: context.projectRef || undefined
30138
+ projectRef: context.projectRef || undefined,
30139
+ projectDeployment: (args) => runProjectDeployment(http, args)
30000
30140
  })));
30001
30141
  assign(captureTools((server) => registerDeployTools(server, http, {
30002
30142
  environment: context.environment,
@@ -30,6 +30,7 @@ declare const REMOTE_APP_ACTIONS: {
30
30
  };
31
31
  export interface AppToolOptions {
32
32
  getApplications?: () => ((args: Record<string, unknown>) => Promise<ToolResult>) | undefined;
33
+ projectDeployment?: (args: Record<string, unknown>) => Promise<ToolResult>;
33
34
  projectRef?: string;
34
35
  /** Abort signal for long-running local actions (`app dev` watch), used for cancellation and tests. */
35
36
  signal?: AbortSignal;
@@ -0,0 +1,3 @@
1
+ import type { ToolResult } from "../tool-server";
2
+ import type { HttpTransport } from "../transports/http";
3
+ export declare function runProjectDeployment(http: Pick<HttpTransport, "post" | "get">, args: Record<string, unknown>): Promise<ToolResult>;
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/cli",
3
- "version": "0.90.2",
3
+ "version": "0.92.0",
4
4
  "description": "Project-scoped CLI for SupaCloud users",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -53,13 +53,13 @@
53
53
  },
54
54
  "dependencies": {
55
55
  "@supacloud/compiler": "^0.44.1",
56
- "@supacloud/delivery": "0.15.0",
56
+ "@supacloud/delivery": "0.15.1",
57
57
  "@supacloud/db": "0.16.1",
58
58
  "typebox": "1.3.36"
59
59
  },
60
60
  "overrides": {
61
61
  "@supacloud/contracts": "0.8.1",
62
- "@supacloud/delivery": "0.15.0",
62
+ "@supacloud/delivery": "0.15.1",
63
63
  "@graphql-tools/utils": "^12.0.3"
64
64
  },
65
65
  "devDependencies": {