@supacloud/cli 0.90.2 → 0.91.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);
@@ -28525,7 +28590,7 @@ function deployResponse(result, json) {
28525
28590
  `) }] };
28526
28591
  }
28527
28592
  function registerDeployTools(server, http, options = {}) {
28528
- registerTool(server, "deploy", "Build and publish the linked frontend with one command", deployToolSchema, async (args) => {
28593
+ registerTool(server, "deploy", "Build and publish the linked project with one command", deployToolSchema, async (args) => {
28529
28594
  if (args.dry_run === true && (args.plan === true || args.diff === true)) {
28530
28595
  throw new Error("Use dry_run for resolution only, or plan/diff for a built release comparison, not both");
28531
28596
  }
@@ -28533,8 +28598,19 @@ function registerDeployTools(server, http, options = {}) {
28533
28598
  const projectRef = String(args.ref || options.projectRef || "").trim();
28534
28599
  if (!projectRef)
28535
28600
  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) {
28601
+ const configRoot = await findDeployConfigRoot(invocationDirectory, options.environment ?? undefined);
28602
+ const config = await readDeployConfig(configRoot);
28603
+ const application = options.application?.id ? options.application : config.application ? {
28604
+ ...config.application,
28605
+ environmentId: options.environment || config.application.environmentId
28606
+ } : undefined;
28607
+ if (!args.target && await hasProjectReleaseManifest(configRoot)) {
28608
+ 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.");
28609
+ }
28610
+ if (application?.id) {
28611
+ if (Object.values(config.targets ?? {}).some((target) => target.type === "edge_function")) {
28612
+ throw new Error("PROJECT_DEPLOYMENT_UNSUPPORTED: Application deployment cannot yet include declared Edge Function targets. No partial deployment was performed.");
28613
+ }
28538
28614
  if (!options.applicationRelease) {
28539
28615
  throw new Error("Application release support is unavailable in this CLI context");
28540
28616
  }
@@ -28544,12 +28620,12 @@ function registerDeployTools(server, http, options = {}) {
28544
28620
  if (args.dry_run === true || args.plan === true || args.diff === true) {
28545
28621
  throw new Error("Application profile deploy does not support dry_run, plan, or diff; use app deploy-plan for a read-only plan");
28546
28622
  }
28547
- const environmentId = options.application.environmentId.trim();
28548
- if (!environmentId) {
28623
+ const environmentId = application.environmentId?.trim();
28624
+ if (!environmentId || !Check2(ApplicationIdSchema, environmentId) || !Check2(ApplicationIdSchema, application.id)) {
28549
28625
  throw new Error("Application profile requires SUPACLOUD_APPLICATION_ENVIRONMENT_ID or SUPACLOUD_ENV");
28550
28626
  }
28551
- const configurationPath = options.application.configurationPath?.trim();
28552
- const configurationId = options.application.configurationId?.trim();
28627
+ const configurationPath = application.configurationPath?.trim();
28628
+ const configurationId = application.configurationId?.trim();
28553
28629
  if (configurationPath && configurationId) {
28554
28630
  throw new Error("Application profile must set only one of SUPACLOUD_APPLICATION_CONFIGURATION_PATH or SUPACLOUD_APPLICATION_CONFIGURATION_ID");
28555
28631
  }
@@ -28557,20 +28633,22 @@ function registerDeployTools(server, http, options = {}) {
28557
28633
  const result = await options.applicationRelease({
28558
28634
  action: "release",
28559
28635
  ref: projectRef,
28560
- id: options.application.id,
28636
+ id: application.id,
28561
28637
  environment_id: environmentId,
28562
28638
  root: configRoot,
28563
28639
  ...configurationPath ? { configuration_path: configurationPath } : {},
28564
28640
  ...configurationId ? { configuration_id: configurationId } : {},
28565
- ...options.application.manifestPath ? { manifest_path: options.application.manifestPath } : {},
28641
+ ...application.manifestPath ? { manifest_path: application.manifestPath } : {},
28566
28642
  ...args.skip_build === true ? { skip_build: true } : {},
28567
28643
  ...stagedWeb?.request === undefined ? {} : { web: stagedWeb.request },
28568
28644
  ...args.json === true ? { format: "json", json: true } : {}
28569
28645
  });
28570
28646
  return result;
28571
28647
  }
28572
- const config = await readDeployConfig(configRoot);
28573
28648
  const target = selectTarget(configRoot, invocationDirectory, config, String(args.target || "").trim() || undefined);
28649
+ 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")))) {
28650
+ 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.");
28651
+ }
28574
28652
  const projectDirectory = target.root;
28575
28653
  const targetState = await lstat5(projectDirectory).catch(() => null);
28576
28654
  if (!targetState?.isDirectory() || targetState.isSymbolicLink()) {
@@ -29378,10 +29456,109 @@ function registerDeveloperExperienceTools(server, options = {}) {
29378
29456
  registerTool(server, "init", "Create SupaCloud deployment configuration and non-secret environment examples without overwriting project files.", initToolSchema, (request) => initializeProject(request, options));
29379
29457
  registerTool(server, "env", "List or locally validate named SupaCloud environment profiles without contacting the platform or printing secrets.", environmentToolSchema, (request) => inspectEnvironment(request, options));
29380
29458
  }
29459
+
29460
+ // src/shared/tools/project-deployment.ts
29461
+ var operation = "applications.project_deploy";
29462
+ var scopeSchema = _Object_({
29463
+ project_ref: String2({ pattern: "^[a-z0-9-]{1,20}$" }),
29464
+ application_id: ApplicationIdSchema,
29465
+ environment_id: ApplicationIdSchema
29466
+ });
29467
+ var statusSchema = _Object_({
29468
+ schema: Literal("supacloud.project-deployment-status.v1"),
29469
+ deployment_id: ApplicationDeployRequestSchema.properties.mutation_id,
29470
+ status: Union([
29471
+ Literal("pending"),
29472
+ Literal("running"),
29473
+ Literal("succeeded"),
29474
+ Literal("failed_retryable"),
29475
+ Literal("failed_terminal"),
29476
+ Literal("outcome_unknown")
29477
+ ]),
29478
+ phase: Union([
29479
+ Literal("queued"),
29480
+ Literal("prepared"),
29481
+ Literal("migrating"),
29482
+ Literal("activating"),
29483
+ Literal("binding-web"),
29484
+ Literal("verifying")
29485
+ ]),
29486
+ operation: Literal("application.release.deploy"),
29487
+ ...scopeSchema.properties,
29488
+ receipt: Optional(Unknown()),
29489
+ failure_code: Optional(String2({ pattern: "^[A-Z][A-Z0-9_]{0,63}$" }))
29490
+ }, { additionalProperties: false });
29491
+ function responseReceipt(data, expected) {
29492
+ if (!data || typeof data !== "object" || Array.isArray(data) || !("ok" in data) || data.ok !== true) {
29493
+ throw new Error("Invalid project deployment response");
29494
+ }
29495
+ const { ok: _ok, ...receipt } = data;
29496
+ return parseApplicationDeployResult(receipt, expected);
29497
+ }
29498
+ async function runProjectDeployment(http, args) {
29499
+ const scope = { project_ref: args.ref, application_id: args.id, environment_id: args.environment_id };
29500
+ const request = {
29501
+ mutation_id: args.mutation_id,
29502
+ release_id: args.release_id,
29503
+ ...args.configuration_id === undefined ? {} : { configuration_id: args.configuration_id },
29504
+ ...args.configuration_write === undefined ? {} : { configuration_write: args.configuration_write },
29505
+ ...args.expected_plan_sha256 === undefined ? {} : { expected_plan_sha256: args.expected_plan_sha256 },
29506
+ ...args.approved_migration_digest === undefined ? {} : { approved_migration_digest: args.approved_migration_digest },
29507
+ ...args.web === undefined ? {} : { web: args.web }
29508
+ };
29509
+ if (!Check2(scopeSchema, scope) || !Check2(ApplicationDeployRequestSchema, request) || request.configuration_id !== undefined && request.configuration_write !== undefined && request.configuration_id !== request.configuration_write.configuration_id) {
29510
+ return releaseControlFailure(operation, "INVALID_RESPONSE", null);
29511
+ }
29512
+ const expected = { ...scope, request };
29513
+ const identity = {
29514
+ ...scope,
29515
+ deployment_id: request.mutation_id,
29516
+ mutation_id: request.mutation_id,
29517
+ release_id: request.release_id
29518
+ };
29519
+ const endpoint = `/v1/projects/${encodeURIComponent(scope.project_ref)}/environments/${encodeURIComponent(scope.environment_id)}/deployments`;
29520
+ const { mutation_id, ...candidate } = request;
29521
+ const result = await http.post(endpoint, {
29522
+ ...candidate,
29523
+ deployment_id: mutation_id,
29524
+ application_id: scope.application_id
29525
+ }, { timeoutMs: 120000, maxJsonBytes: 131072, responseTimeoutMs: 30000 });
29526
+ if (result.ok && result.status === 200) {
29527
+ try {
29528
+ return releaseControlSuccess(operation, {
29529
+ ...responseReceipt(result.data, expected),
29530
+ deployment_id: mutation_id
29531
+ });
29532
+ } catch {}
29533
+ } else if (!result.ok && !result.transportError && !result.responseReadError && result.status < 500 && result.status !== 408) {
29534
+ return releaseControlMutationFailure(operation, result, identity);
29535
+ }
29536
+ const observed = await http.get(`${endpoint}/${encodeURIComponent(mutation_id)}`, {
29537
+ retry: false,
29538
+ totalTimeoutMs: 30000,
29539
+ maxJsonBytes: 131072
29540
+ });
29541
+ try {
29542
+ if (!observed.ok || observed.status !== 200 || !Check2(statusSchema, observed.data))
29543
+ throw new Error;
29544
+ const state = observed.data;
29545
+ 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")
29546
+ throw new Error;
29547
+ const receipt = parseApplicationDeployResult(state.receipt, expected);
29548
+ return releaseControlSuccess(operation, {
29549
+ ...receipt,
29550
+ deployment_id: mutation_id,
29551
+ replayed: true,
29552
+ execution_performed: false
29553
+ });
29554
+ } catch {
29555
+ return releaseControlFailure(operation, "OUTCOME_UNKNOWN", result.transportError ? null : result.status, identity);
29556
+ }
29557
+ }
29381
29558
  // package.json
29382
29559
  var package_default = {
29383
29560
  name: "@supacloud/cli",
29384
- version: "0.90.2",
29561
+ version: "0.91.0",
29385
29562
  description: "Project-scoped CLI for SupaCloud users",
29386
29563
  type: "module",
29387
29564
  main: "./dist/index.js",
@@ -29434,13 +29611,13 @@ var package_default = {
29434
29611
  },
29435
29612
  dependencies: {
29436
29613
  "@supacloud/compiler": "^0.44.1",
29437
- "@supacloud/delivery": "0.15.0",
29614
+ "@supacloud/delivery": "0.15.1",
29438
29615
  "@supacloud/db": "0.16.1",
29439
29616
  typebox: "1.3.36"
29440
29617
  },
29441
29618
  overrides: {
29442
29619
  "@supacloud/contracts": "0.8.1",
29443
- "@supacloud/delivery": "0.15.0",
29620
+ "@supacloud/delivery": "0.15.1",
29444
29621
  "@graphql-tools/utils": "^12.0.3"
29445
29622
  },
29446
29623
  devDependencies: {
@@ -29996,7 +30173,8 @@ function createCliTools(context, confirmProduction) {
29996
30173
  Object.assign(tools, captureTools((server) => registerAppTools(server, {
29997
30174
  getApplications: () => tools.applications?.callback,
29998
30175
  onDevProgress: writeDevProgress,
29999
- projectRef: context.projectRef || undefined
30176
+ projectRef: context.projectRef || undefined,
30177
+ projectDeployment: (args) => runProjectDeployment(http, args)
30000
30178
  })));
30001
30179
  assign(captureTools((server) => registerDeployTools(server, http, {
30002
30180
  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.91.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": {