@supacloud/cli 0.97.1 → 0.99.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
@@ -1,4 +1,5 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env bun
2
+ // @bun
2
3
  import { createRequire } from "node:module";
3
4
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
4
5
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
@@ -8616,6 +8617,24 @@ var Delete = _Object_({
8616
8617
  path: String2()
8617
8618
  });
8618
8619
  var Edit = Union([Insert2, Update2, Delete]);
8620
+ // src/shared/schema.ts
8621
+ function schemaErrorPath(path) {
8622
+ return path.replace(/^\//, "").replaceAll("/", ".") || "args";
8623
+ }
8624
+ function schemaErrorLines(schema, input) {
8625
+ return [...Errors2(schema, input)].map((issue) => `- ${schemaErrorPath(issue.instancePath)}: ${issue.message}`);
8626
+ }
8627
+ function validateToolArguments(schema, input) {
8628
+ const issues = schemaErrorLines(_Object_(schema, { additionalProperties: false }), input);
8629
+ if (issues.length > 0)
8630
+ throw new Error(`Invalid arguments:
8631
+ ${issues.join(`
8632
+ `)}`);
8633
+ }
8634
+ function stringEnum(values, options) {
8635
+ return Enum([...values], options);
8636
+ }
8637
+
8619
8638
  // src/shared/schema.ts
8620
8639
  var DECODE_ID = "~supacloudDecodeId";
8621
8640
 
@@ -8633,19 +8652,19 @@ function decodeIdOf(schema) {
8633
8652
  const value = Object.getOwnPropertyDescriptor(schema, DECODE_ID)?.value;
8634
8653
  return typeof value === "symbol" ? value : undefined;
8635
8654
  }
8636
- function schemaErrorPath(path) {
8655
+ function schemaErrorPath2(path) {
8637
8656
  return path.replace(/^\//, "").replaceAll("/", ".") || "args";
8638
8657
  }
8639
- function schemaErrorLines(schema, input) {
8640
- return [...Errors2(schema, input)].map((issue) => `- ${schemaErrorPath(issue.instancePath)}: ${issue.message}`);
8658
+ function schemaErrorLines2(schema, input) {
8659
+ return [...Errors2(schema, input)].map((issue) => `- ${schemaErrorPath2(issue.instancePath)}: ${issue.message}`);
8641
8660
  }
8642
8661
  function transformErrorLine(error) {
8643
8662
  const message = error instanceof Error ? error.message : String(error);
8644
- const path = typeof error === "object" && error !== null && "instancePath" in error && typeof error.instancePath === "string" ? schemaErrorPath(error.instancePath) : "args";
8663
+ const path = typeof error === "object" && error !== null && "instancePath" in error && typeof error.instancePath === "string" ? schemaErrorPath2(error.instancePath) : "args";
8645
8664
  return `- ${path}: ${message}`;
8646
8665
  }
8647
- function validateToolArguments(schema, input) {
8648
- const issues = schemaErrorLines(_Object_(schema, { additionalProperties: false }), input);
8666
+ function validateToolArguments2(schema, input) {
8667
+ const issues = schemaErrorLines2(_Object_(schema, { additionalProperties: false }), input);
8649
8668
  if (issues.length > 0)
8650
8669
  throw new Error(`Invalid arguments:
8651
8670
  ${issues.join(`
@@ -8656,7 +8675,7 @@ function isRecord(value) {
8656
8675
  }
8657
8676
  function parseToolArguments(schema, input) {
8658
8677
  const objectSchema = _Object_(schema, { additionalProperties: false });
8659
- validateToolArguments(schema, input);
8678
+ validateToolArguments2(schema, input);
8660
8679
  let decoded;
8661
8680
  try {
8662
8681
  decoded = Decode10(objectSchema, input);
@@ -8678,7 +8697,7 @@ ${transformErrorLine(error)}`);
8678
8697
  }
8679
8698
  return decoded;
8680
8699
  }
8681
- function stringEnum(values, options) {
8700
+ function stringEnum2(values, options) {
8682
8701
  return Enum([...values], options);
8683
8702
  }
8684
8703
  function mergeSchemaOptions(schema, options) {
@@ -8701,7 +8720,7 @@ function decodedSchema(inputSchema, outputSchema, decode, options) {
8701
8720
  const transform = Decode(inputSchema, (input) => {
8702
8721
  const value = decode(input);
8703
8722
  const issues = [...Errors2(outputSchema, value)].map((issue) => ({
8704
- path: schemaErrorPath(issue.instancePath),
8723
+ path: schemaErrorPath2(issue.instancePath),
8705
8724
  message: issue.message
8706
8725
  }));
8707
8726
  if (issues.length > 0)
@@ -8767,7 +8786,7 @@ function outcomeUnknownGuidance(text) {
8767
8786
  }
8768
8787
 
8769
8788
  // src/shared/deployment-upgrade.ts
8770
- var DEPLOYMENT_UPGRADE = "DEPLOYMENT_UPGRADE_REQUIRED: Legacy deployment commands are no longer supported. Upgrade with `npm install -g @supacloud/cli@latest`, configure frontend in supacloud.json for static assets, or application.id and application.environmentId for executable application delivery, then run `supacloud deploy`. Use `supacloud deploy --assets_only` to publish only Web assets in a mixed repository. SupaCloud owns activation; do not retry with legacy commands.";
8789
+ var DEPLOYMENT_UPGRADE = "DEPLOYMENT_UPGRADE_REQUIRED: Legacy deployment commands are no longer supported. Upgrade with `npm install -g @supacloud/cli@latest`, configure frontend in supacloud.json for static assets, or application.id and application.environmentId for executable application delivery, then run `supacloud deploy`. With a frontend configured, deploy publishes only Web assets; use `supacloud deploy --backend` for the application backend. `supacloud deploy --assets_only` remains an explicit Web-only option. SupaCloud owns activation; do not retry with legacy commands.";
8771
8790
  var removedFrontendActions = new Set([
8772
8791
  "deploy_git",
8773
8792
  "deploy_upload",
@@ -8912,7 +8931,7 @@ async function runCli(cliTools, args, options = {}) {
8912
8931
  if (startIdx === 2)
8913
8932
  parsedArgs.action = args[1];
8914
8933
  assertCurrentDeploymentInput(args[0], parsedArgs);
8915
- validateToolArguments(tool.schema, parsedArgs);
8934
+ validateToolArguments2(tool.schema, parsedArgs);
8916
8935
  const result = await tool.callback(parsedArgs);
8917
8936
  if (result && result.content && Array.isArray(result.content)) {
8918
8937
  for (const c of result.content) {
@@ -8943,27 +8962,7 @@ import { homedir } from "node:os";
8943
8962
  import { resolve as resolve2 } from "node:path";
8944
8963
 
8945
8964
  // src/shared/global-options.ts
8946
- var GLOBAL_FLAGS = {
8947
- "--env": "environmentName",
8948
- "--env-file": "envFile",
8949
- "--confirm-production": "confirmProduction"
8950
- };
8951
8965
  var ENVIRONMENT_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
8952
- function globalFlag(arg) {
8953
- return Object.keys(GLOBAL_FLAGS).find((flag) => arg === flag || arg.startsWith(`${flag}=`)) ?? null;
8954
- }
8955
- function globalFlagValue(args, index, flag) {
8956
- const inlineValue = args[index].slice(flag.length + 1);
8957
- if (args[index].startsWith(`${flag}=`)) {
8958
- if (!inlineValue)
8959
- throw new Error(`${flag} requires a value`);
8960
- return { value: inlineValue, consumed: 1 };
8961
- }
8962
- const value = args[index + 1];
8963
- if (!value || value.startsWith("--"))
8964
- throw new Error(`${flag} requires a value`);
8965
- return { value, consumed: 2 };
8966
- }
8967
8966
  function normalizeEnvironmentName(name) {
8968
8967
  if (!ENVIRONMENT_NAME_PATTERN.test(name)) {
8969
8968
  throw new Error("--env and SUPACLOUD_ENV must match ^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$");
@@ -8971,30 +8970,6 @@ function normalizeEnvironmentName(name) {
8971
8970
  const normalized = name.toLowerCase();
8972
8971
  return normalized === "prod" || normalized === "production" ? "production" : normalized;
8973
8972
  }
8974
- function parseGlobalOptions(args) {
8975
- const values = {};
8976
- const remainingArgs = [];
8977
- for (let index = 0;index < args.length; ) {
8978
- const flag = globalFlag(args[index]);
8979
- if (!flag) {
8980
- remainingArgs.push(args[index]);
8981
- index += 1;
8982
- continue;
8983
- }
8984
- const key = GLOBAL_FLAGS[flag];
8985
- if (values[key] !== undefined)
8986
- throw new Error(`${flag} may be provided only once`);
8987
- const parsed = globalFlagValue(args, index, flag);
8988
- values[key] = parsed.value;
8989
- index += parsed.consumed;
8990
- }
8991
- if (values.environmentName && values.envFile) {
8992
- throw new Error("--env and --env-file are mutually exclusive");
8993
- }
8994
- if (values.environmentName)
8995
- normalizeEnvironmentName(values.environmentName);
8996
- return { ...values, args: remainingArgs };
8997
- }
8998
8973
 
8999
8974
  // src/shared/context.ts
9000
8975
  var CORE_CONTEXT_KEYS = [
@@ -9187,6 +9162,60 @@ function resolveSupaCloudContext(env = process.env, cwd = process.cwd(), selecti
9187
9162
  };
9188
9163
  }
9189
9164
 
9165
+ // src/shared/global-options.ts
9166
+ var GLOBAL_FLAGS = {
9167
+ "--env": "environmentName",
9168
+ "--env-file": "envFile",
9169
+ "--confirm-production": "confirmProduction"
9170
+ };
9171
+ var ENVIRONMENT_NAME_PATTERN2 = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
9172
+ function globalFlag(arg) {
9173
+ return Object.keys(GLOBAL_FLAGS).find((flag) => arg === flag || arg.startsWith(`${flag}=`)) ?? null;
9174
+ }
9175
+ function globalFlagValue(args, index, flag) {
9176
+ const inlineValue = args[index].slice(flag.length + 1);
9177
+ if (args[index].startsWith(`${flag}=`)) {
9178
+ if (!inlineValue)
9179
+ throw new Error(`${flag} requires a value`);
9180
+ return { value: inlineValue, consumed: 1 };
9181
+ }
9182
+ const value = args[index + 1];
9183
+ if (!value || value.startsWith("--"))
9184
+ throw new Error(`${flag} requires a value`);
9185
+ return { value, consumed: 2 };
9186
+ }
9187
+ function normalizeEnvironmentName2(name) {
9188
+ if (!ENVIRONMENT_NAME_PATTERN2.test(name)) {
9189
+ throw new Error("--env and SUPACLOUD_ENV must match ^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$");
9190
+ }
9191
+ const normalized = name.toLowerCase();
9192
+ return normalized === "prod" || normalized === "production" ? "production" : normalized;
9193
+ }
9194
+ function parseGlobalOptions(args) {
9195
+ const values = {};
9196
+ const remainingArgs = [];
9197
+ for (let index = 0;index < args.length; ) {
9198
+ const flag = globalFlag(args[index]);
9199
+ if (!flag) {
9200
+ remainingArgs.push(args[index]);
9201
+ index += 1;
9202
+ continue;
9203
+ }
9204
+ const key = GLOBAL_FLAGS[flag];
9205
+ if (values[key] !== undefined)
9206
+ throw new Error(`${flag} may be provided only once`);
9207
+ const parsed = globalFlagValue(args, index, flag);
9208
+ values[key] = parsed.value;
9209
+ index += parsed.consumed;
9210
+ }
9211
+ if (values.environmentName && values.envFile) {
9212
+ throw new Error("--env and --env-file are mutually exclusive");
9213
+ }
9214
+ if (values.environmentName)
9215
+ normalizeEnvironmentName2(values.environmentName);
9216
+ return { ...values, args: remainingArgs };
9217
+ }
9218
+
9190
9219
  // src/shared/execution-policy.ts
9191
9220
  var ACTION_POLICY = {
9192
9221
  deploy: { write: ["deploy"] },
@@ -9310,6 +9339,8 @@ function declaredMode(moduleName, action) {
9310
9339
  return;
9311
9340
  }
9312
9341
  function executionMode(moduleName, action, args) {
9342
+ if (moduleName === "deploy" && typeof args.status === "string")
9343
+ return "read";
9313
9344
  if (moduleName === "deploy" && (args.dry_run === true || args.plan === true || args.diff === true))
9314
9345
  return "read";
9315
9346
  if (moduleName === "database" && ["push_migrations", "baseline_migrations"].includes(action) && args.dry_run === true)
@@ -12664,11 +12695,11 @@ function registerDatabaseTools(server, http, config = {}) {
12664
12695
  const allActions = localOnly ? localActions : readOnly ? remoteActions : [...remoteActions, ...writeActions];
12665
12696
  registerTool(server, "database", `Database operations: query, schema, RLS, migrations, stats.
12666
12697
  Actions: ${allActions.join(", ")}${localOnly ? " (local-only mode)" : readOnly ? " (read-only mode)" : ""}`, {
12667
- action: withDescription(stringEnum(allActions), "Action"),
12698
+ action: withDescription(stringEnum2(allActions), "Action"),
12668
12699
  ref: projectRef ? Optional(String2()) : optional(String2(), "Project ref"),
12669
12700
  sql: optional(String2(), "[query/execute/apply_migration/lint_migrations/lint] SQL statement"),
12670
12701
  file: optional(String2(), "[query/execute/apply_migration/lint_migrations/lint] Read SQL from local file path (avoids shell escaping issues with $$ and multi-statement DDL)"),
12671
- mode: optional(stringEnum(["read", "migration", "admin"]), "[query/execute] SQL execution mode: read, migration, or admin. execute defaults to migration; query defaults to read/auto"),
12702
+ mode: optional(stringEnum2(["read", "migration", "admin"]), "[query/execute] SQL execution mode: read, migration, or admin. execute defaults to migration; query defaults to read/auto"),
12672
12703
  admin: optional(Boolean2(), "[query/execute] Admin execution flag (required when mode=admin)"),
12673
12704
  dir: optional(String2(), "[push_migrations/baseline_migrations/lint_migrations/lint/rebase_migrations] Directory containing .sql migration files (default: supabase/migrations)"),
12674
12705
  dry_run: optional(Boolean2(), "[push_migrations/baseline_migrations] Preview changes without applying them"),
@@ -12684,7 +12715,7 @@ Actions: ${allActions.join(", ")}${localOnly ? " (local-only mode)" : readOnly ?
12684
12715
  name: optional(String2(), "[apply_migration] Migration name"),
12685
12716
  delivery_manifest: optional(String2(), "[delivery_migration_plan] Local immutable delivery manifest"),
12686
12717
  delivery_target: optional(String2(), "[delivery_migration_plan] Target name from the delivery manifest"),
12687
- import_source: optional(stringEnum(["hasura", "nhost"]), "[migration_import] Source platform"),
12718
+ import_source: optional(stringEnum2(["hasura", "nhost"]), "[migration_import] Source platform"),
12688
12719
  metadata_file: optional(String2(), "[migration_import] Hasura metadata JSON file"),
12689
12720
  output_dir: optional(String2(), "[migration_import] Output migration directory when write=true"),
12690
12721
  write: optional(Boolean2(), "[migration_import] Write the reviewed plan to output_dir; never applies SQL"),
@@ -12693,7 +12724,7 @@ Actions: ${allActions.join(", ")}${localOnly ? " (local-only mode)" : readOnly ?
12693
12724
  baseline_name: optional(String2(), "[rebase_migrations] New baseline migration name"),
12694
12725
  retain_after_version: optional(String2(), "[rebase_migrations] Retain migrations newer than this version"),
12695
12726
  columns: optional(String2(), "[create_table_rls] Column definitions"),
12696
- policy_mode: optional(stringEnum(["deny_all", "owner"]), "[create_table_rls] RLS policy mode (default: deny_all)"),
12727
+ policy_mode: optional(stringEnum2(["deny_all", "owner"]), "[create_table_rls] RLS policy mode (default: deny_all)"),
12697
12728
  owner_column: optional(String2(), "[create_table_rls owner] UUID owner column matched to auth.uid()"),
12698
12729
  extension: optional(String2(), "[enable_extension/disable_extension] PostgreSQL extension or pgflow")
12699
12730
  }, async (args) => {
@@ -14055,7 +14086,7 @@ function formatProviders(data) {
14055
14086
  function registerAuthTools(server, http) {
14056
14087
  registerTool(server, "auth", `Auth & OAuth provider management, controlled user lookup, and login-link generation.
14057
14088
  Actions: list_users, get_user, generate_link, list_providers, get_provider, configure_provider, update_provider, disable_provider, supported_providers, wechat_mini, wechat_open, get_settings, update_settings, get_config, update_config, get_oauth_server, migrate_oauth_server`, {
14058
- action: withDescription(stringEnum([
14089
+ action: withDescription(stringEnum2([
14059
14090
  "list_users",
14060
14091
  "get_user",
14061
14092
  "generate_link",
@@ -14080,7 +14111,7 @@ Actions: list_users, get_user, generate_link, list_providers, get_provider, conf
14080
14111
  per_page: optional(Integer({ minimum: 1, maximum: 100 }), "[list_users] Users per page (1-100)"),
14081
14112
  search: optional(String2(), "[list_users] Search user email, phone, or UUID"),
14082
14113
  email_like: optional(String2(), "[list_users] Search user email or phone"),
14083
- type: optional(stringEnum(AUTH_LINK_TYPES), "[generate_link] magiclink, recovery, or invite"),
14114
+ type: optional(stringEnum2(AUTH_LINK_TYPES), "[generate_link] magiclink, recovery, or invite"),
14084
14115
  email: optional(String2(), "[generate_link] User email"),
14085
14116
  redirect_to: optional(String2(), "[generate_link] Absolute HTTPS or loopback callback"),
14086
14117
  provider: optional(String2(), "[*_provider] Provider name (github, google, wechat, etc.)"),
@@ -14390,7 +14421,7 @@ async function deleteReleaseCanaryClient(http, ref, id, redirectUri) {
14390
14421
  }
14391
14422
  function registerOAuthClientTools(server, http) {
14392
14423
  registerTool(server, "oauth_clients", "Dedicated release-canary public OAuth client lifecycle. It only manages the exact supacloud-release-canary public authorization-code client and never returns client secrets.", {
14393
- action: withDescription(stringEnum(["list", "get", "create", "delete"]), "OAuth client action"),
14424
+ action: withDescription(stringEnum2(["list", "get", "create", "delete"]), "OAuth client action"),
14394
14425
  ref: withDescription(String2(), "Central SupAuth project ref"),
14395
14426
  client_id: optional(String2(), "[get/delete] Exact release-canary public OAuth client ID"),
14396
14427
  redirect_uri: optional(String2(), "[create/delete] Exact HTTPS or port-bound RFC 8252 loopback callback")
@@ -14782,7 +14813,7 @@ function executeBucketAction(action, http, args) {
14782
14813
  function registerStorageTools(server, http) {
14783
14814
  registerTool(server, "storage", `S3/MinIO storage management.
14784
14815
  Actions: status, list_buckets, get_bucket, create_bucket, update_bucket, delete_bucket, list_files, upload_base64, delete_file, upload, upload_file`, {
14785
- action: withDescription(stringEnum([
14816
+ action: withDescription(stringEnum2([
14786
14817
  "status",
14787
14818
  "list_buckets",
14788
14819
  "get_bucket",
@@ -15911,7 +15942,7 @@ async function activateFunctionVersion(http, args, readOnly = false) {
15911
15942
  function registerAdvancedTools(server, http, environment = process.env, options = {}) {
15912
15943
  registerTool(server, "edge_functions", `Edge Function management (Deno/Bun serverless). Source deploys are bundled; verified prebuilt artifacts stay byte-exact.
15913
15944
  Actions: list, get_config, deploy, deploy_bundle, config, source, activate, delete, check, scaffold`, {
15914
- action: withDescription(stringEnum(["list", "get_config", "deploy", "deploy_bundle", "config", "source", "activate", "delete", "check", "scaffold"]), "Action"),
15945
+ action: withDescription(stringEnum2(["list", "get_config", "deploy", "deploy_bundle", "config", "source", "activate", "delete", "check", "scaffold"]), "Action"),
15915
15946
  ref: optional(String2(), "Project ref (not used by scaffold)"),
15916
15947
  slug: optional(String2(), "[get_config/deploy/deploy_bundle/config/source/activate/delete/check] Function name"),
15917
15948
  version: withDescription(functionVersionSchema, "[source/activate] Existing immutable Function version; source requires a positive version"),
@@ -15926,7 +15957,7 @@ Actions: list, get_config, deploy, deploy_bundle, config, source, activate, dele
15926
15957
  minify: optional(Boolean2(), "[deploy/deploy_bundle] Minify bundle"),
15927
15958
  verify_jwt: optional(Boolean2(), "[deploy/deploy_bundle/config] Set JWT verification for this function"),
15928
15959
  background_routes: withDescription(backgroundRoutesSchema, "[deploy/deploy_bundle/config] Background route paths; pass comma-separated or JSON array in CLI"),
15929
- framework: optional(withDescription(stringEnum(["fetch", "elysia", "hono", "sveltekit-function"]), "[deploy/deploy_bundle/config/scaffold] Fetch framework adapter profile")),
15960
+ framework: optional(withDescription(stringEnum2(["fetch", "elysia", "hono", "sveltekit-function"]), "[deploy/deploy_bundle/config/scaffold] Fetch framework adapter profile")),
15930
15961
  capabilities: optional(functionCapabilitiesSchema, "[deploy/deploy_bundle/config] Host capabilities: secrets, outbound_hosts, bindings, background"),
15931
15962
  limits: optional(functionLimitsSchema, "[deploy/deploy_bundle/config] Execution limits in milliseconds/bytes"),
15932
15963
  max_request_body_bytes: optional(Integer({ minimum: 1, maximum: Number.MAX_SAFE_INTEGER }), "[deploy/deploy_bundle/config] Max inbound request body size in bytes"),
@@ -16103,7 +16134,7 @@ ${deployCheck.err}`;
16103
16134
  });
16104
16135
  registerTool(server, "secrets", `Project secrets (environment variables for Edge Functions).
16105
16136
  Actions: list, upsert, delete`, {
16106
- action: withDescription(stringEnum(["list", "upsert", "delete"]), "Action"),
16137
+ action: withDescription(stringEnum2(["list", "upsert", "delete"]), "Action"),
16107
16138
  ref: withDescription(String2(), "Project ref"),
16108
16139
  secrets: withDescription(secretsSchema, "[upsert] Secret list as JSON array or KEY=VALUE,KEY2=VALUE2"),
16109
16140
  "from-env": withDescription(environmentSecretNamesSchema, "[upsert] Comma-separated environment variable names; values are read from the selected CLI environment"),
@@ -16141,7 +16172,7 @@ Actions: list, upsert, delete`, {
16141
16172
  });
16142
16173
  registerTool(server, "platform", `Platform monitoring, backups, network, and organizations.
16143
16174
  Actions: metrics, list_backups, create_backup, network, update_network, list_orgs, get_org`, {
16144
- action: withDescription(stringEnum([
16175
+ action: withDescription(stringEnum2([
16145
16176
  "metrics",
16146
16177
  "list_backups",
16147
16178
  "create_backup",
@@ -16199,7 +16230,7 @@ ${JSON.stringify(r.data, null, 2)}` : `❌ Failed (${r.status})`;
16199
16230
  });
16200
16231
  registerTool(server, "task_events", `Task lifecycle webhook configuration.
16201
16232
  Actions: register_webhook, unregister_webhook, inspect_webhook`, {
16202
- action: withDescription(stringEnum(["register_webhook", "unregister_webhook", "inspect_webhook"]), "Action"),
16233
+ action: withDescription(stringEnum2(["register_webhook", "unregister_webhook", "inspect_webhook"]), "Action"),
16203
16234
  ref: withDescription(String2(), "Project ref"),
16204
16235
  url: optional(String2(), "[register_webhook] HTTPS webhook URL for task lifecycle events"),
16205
16236
  secret: optional(String2(), "[register_webhook] Optional HMAC secret for webhook verification")
@@ -16235,7 +16266,7 @@ ${JSON.stringify(r.data, null, 2)}` : `❌ Failed (${r.status}): ${JSON.stringif
16235
16266
  });
16236
16267
  registerTool(server, "diagnostics", `Platform and project diagnostics: health checks, diagnostic runs, and repair.
16237
16268
  Actions: list_checks, run_checks, get_run, repair`, {
16238
- action: withDescription(stringEnum(["list_checks", "run_checks", "get_run", "repair"]), "Action"),
16269
+ action: withDescription(stringEnum2(["list_checks", "run_checks", "get_run", "repair"]), "Action"),
16239
16270
  ref: optional(String2(), "Project ref (for project-scoped diagnostics)"),
16240
16271
  run_id: optional(String2(), "[get_run/repair] Diagnostic run ID"),
16241
16272
  check_id: optional(String2(), "[repair] Check result ID to repair")
@@ -16279,72 +16310,311 @@ ${JSON.stringify(r.data, null, 2)}` : `❌ Failed (${r.status}): ${JSON.stringif
16279
16310
  }
16280
16311
 
16281
16312
  // src/shared/tools/frontend-release-control.ts
16282
- import { createHash as createHash5 } from "node:crypto";
16313
+ import { createHash as createHash6 } from "node:crypto";
16283
16314
  import { constants as fsConstants2 } from "node:fs";
16284
16315
  import { open as open2 } from "node:fs/promises";
16285
16316
  import { resolve as resolve7 } from "node:path";
16286
- var PROJECT_REF_PATTERN3 = /^[A-Za-z0-9_-]{1,20}$/u;
16287
- var DEPLOYMENT_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/u;
16288
- var RELEASE_ID_PATTERN = /^[0-9a-f]{64}$/u;
16289
- var MUTATION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;
16290
- var TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u;
16291
- var ARCHIVE_MAX_BYTES = 100 * 1024 * 1024;
16292
- var ARCHIVE_CHUNK_BYTES = 64 * 1024;
16293
- var RESPONSE_MAX_BYTES = 1024 * 1024;
16294
- var UPLOAD_REQUEST_TIMEOUT_MS = 10 * 60000;
16295
- var MUTATION_RESPONSE_TIMEOUT_MS = 5000;
16296
- var RELEASE_LIST_LIMIT_MAX = 100;
16297
- function toolResponse(payload) {
16298
- return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
16317
+
16318
+ // src/shared/mutation-protocol.ts
16319
+ import { createHash as createHash5 } from "node:crypto";
16320
+ var MUTATION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
16321
+ var FINGERPRINT_PATTERN = /^[0-9a-f]{64}$/;
16322
+ var OPERATION_PATTERN = /^[a-z0-9][a-z0-9._:-]{0,127}$/;
16323
+ var RESOURCE_KEY_PATTERN = /^v1\/(?:[a-z0-9][a-z0-9._-]{0,63})\/([A-Za-z0-9_-]{2,171})$/;
16324
+ var RESOURCE_ID_CONTROL_PATTERN = /[\u0000-\u001f\u007f-\u009f]/u;
16325
+ var FAILURE_CODE_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
16326
+ var LEASE_OWNER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:@/-]{0,254}$/;
16327
+ var TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
16328
+ var MAX_STATUS_RESPONSE_BYTES = 196608;
16329
+ var MAX_JSON_DEPTH = 32;
16330
+ var MAX_RESOURCE_ID_BYTES = 128;
16331
+ var FATAL_UTF8_DECODER = new TextDecoder("utf-8", { fatal: true });
16332
+ var MUTATION_STATUSES = new Set([
16333
+ "pending",
16334
+ "running",
16335
+ "succeeded",
16336
+ "failed_retryable",
16337
+ "failed_terminal",
16338
+ "outcome_unknown"
16339
+ ]);
16340
+ var MUTATION_RESPONSE_KEYS = ["project_ref", "mutation"];
16341
+ var MUTATION_KEYS = [
16342
+ "project_ref",
16343
+ "mutation_id",
16344
+ "operation",
16345
+ "resource_key",
16346
+ "request_fingerprint",
16347
+ "principal",
16348
+ "status",
16349
+ "checkpoint",
16350
+ "receipt",
16351
+ "response_status",
16352
+ "failure_code",
16353
+ "lease",
16354
+ "completed_at",
16355
+ "created_at",
16356
+ "updated_at"
16357
+ ];
16358
+ var PRINCIPAL_KEYS = ["type", "id"];
16359
+ var LEASE_KEYS = ["owner", "expires_at", "fencing_epoch"];
16360
+ function isMutationId(candidate) {
16361
+ return typeof candidate === "string" && MUTATION_ID_PATTERN.test(candidate);
16299
16362
  }
16300
- function releaseFailure(operation, code, status) {
16301
- return {
16302
- isError: true,
16303
- content: [{
16304
- type: "text",
16305
- text: JSON.stringify({ ok: false, operation, error: { code, http_status: status } })
16306
- }]
16307
- };
16363
+ function assertCanonicalJsonNode(candidate, seen, depth) {
16364
+ if (candidate === null || typeof candidate === "string" || typeof candidate === "boolean")
16365
+ return;
16366
+ if (typeof candidate === "number" && Number.isFinite(candidate))
16367
+ return;
16368
+ if (typeof candidate !== "object" || depth > MAX_JSON_DEPTH) {
16369
+ throw new Error("Mutation request must be bounded JSON");
16370
+ }
16371
+ if (seen.has(candidate))
16372
+ throw new Error("Mutation request must not contain cycles");
16373
+ seen.add(candidate);
16374
+ const prototype = Object.getPrototypeOf(candidate);
16375
+ const plainRecord = prototype === Object.prototype || prototype === null;
16376
+ const children = Array.isArray(candidate) ? candidate : plainRecord ? Object.values(candidate) : null;
16377
+ if (!children)
16378
+ throw new Error("Mutation request must contain only JSON objects and arrays");
16379
+ for (const child of children)
16380
+ assertCanonicalJsonNode(child, seen, depth + 1);
16381
+ seen.delete(candidate);
16308
16382
  }
16309
- function exactKeys(candidate, keys) {
16310
- const actual = Object.keys(candidate).sort();
16311
- const expected = [...keys].sort();
16312
- return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
16383
+ function stableStringifyNode(jsonValue) {
16384
+ if (jsonValue === null || typeof jsonValue !== "object") {
16385
+ const serialized = JSON.stringify(jsonValue);
16386
+ if (serialized === undefined)
16387
+ throw new Error("Mutation request must contain exact JSON values");
16388
+ return serialized;
16389
+ }
16390
+ if (Array.isArray(jsonValue))
16391
+ return `[${jsonValue.map(stableStringifyNode).join(",")}]`;
16392
+ const record = jsonValue;
16393
+ const fields = Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringifyNode(record[key])}`);
16394
+ return `{${fields.join(",")}}`;
16313
16395
  }
16314
- function releaseRecord(candidate) {
16315
- if (!candidate || typeof candidate !== "object" || Array.isArray(candidate))
16316
- return null;
16317
- const record = candidate;
16318
- const keys = [
16319
- "schema",
16320
- "project_ref",
16321
- "deployment_id",
16322
- "release_id",
16323
- "sha256",
16324
- "tree_sha256",
16325
- "size_bytes",
16326
- "file_count",
16327
- "created_at",
16328
- "kind",
16329
- "archive_format"
16330
- ];
16331
- if (!exactKeys(record, keys) || record.schema !== FRONTEND_ARCHIVE_RELEASE_SCHEMA || record.archive_format !== FRONTEND_ARCHIVE_FORMAT || typeof record.project_ref !== "string" || !PROJECT_REF_PATTERN3.test(record.project_ref) || typeof record.deployment_id !== "string" || !DEPLOYMENT_ID_PATTERN.test(record.deployment_id) || typeof record.release_id !== "string" || !RELEASE_ID_PATTERN.test(record.release_id) || record.sha256 !== record.release_id || typeof record.tree_sha256 !== "string" || !RELEASE_ID_PATTERN.test(record.tree_sha256) || !Number.isSafeInteger(record.size_bytes) || Number(record.size_bytes) < 1 || !Number.isSafeInteger(record.file_count) || Number(record.file_count) < 1 || !canonicalTimestamp(record.created_at) || record.kind !== "prebuilt_static")
16332
- return null;
16333
- return {
16334
- project_ref: record.project_ref,
16335
- deployment_id: record.deployment_id,
16336
- release_id: record.release_id,
16337
- sha256: record.release_id,
16338
- tree_sha256: record.tree_sha256,
16339
- size_bytes: Number(record.size_bytes),
16340
- file_count: Number(record.file_count),
16341
- created_at: record.created_at,
16342
- kind: "prebuilt_static",
16343
- archive_format: FRONTEND_ARCHIVE_FORMAT
16344
- };
16396
+ function mutationRequestFingerprint(normalizedRequest) {
16397
+ assertCanonicalJsonNode(normalizedRequest, new WeakSet, 0);
16398
+ return createHash5("sha256").update(stableStringifyNode(normalizedRequest)).digest("hex");
16345
16399
  }
16346
- function releaseEnvelope(candidate, expected) {
16347
- if (!candidate || typeof candidate !== "object" || Array.isArray(candidate))
16400
+ function objectRecord3(candidate) {
16401
+ return candidate && typeof candidate === "object" && !Array.isArray(candidate) ? candidate : null;
16402
+ }
16403
+ function exactRecord(candidate, keys) {
16404
+ const record = objectRecord3(candidate);
16405
+ if (!record || Object.keys(record).length !== keys.length)
16406
+ return null;
16407
+ return keys.every((key) => Object.hasOwn(record, key)) ? record : null;
16408
+ }
16409
+ function emptyProjection(candidate) {
16410
+ const record = objectRecord3(candidate);
16411
+ return record && Object.keys(record).length === 0 ? record : null;
16412
+ }
16413
+ function canonicalTimestamp(candidate) {
16414
+ if (typeof candidate !== "string" || !TIMESTAMP_PATTERN.test(candidate))
16415
+ return false;
16416
+ const milliseconds = Date.parse(candidate);
16417
+ return Number.isFinite(milliseconds) && new Date(milliseconds).toISOString() === candidate;
16418
+ }
16419
+ function nullableTimestamp(candidate) {
16420
+ return candidate === null || canonicalTimestamp(candidate);
16421
+ }
16422
+ function safePrincipal(candidate) {
16423
+ const principal = exactRecord(candidate, PRINCIPAL_KEYS);
16424
+ if (!principal || !["master", "admin", "project"].includes(String(principal["type"])))
16425
+ return null;
16426
+ if (typeof principal["id"] !== "string" || !principal["id"] || principal["id"].length > 320 || principal["id"].trim() !== principal["id"] || /[\u0000-\u001f\u007f]/u.test(principal["id"]))
16427
+ return null;
16428
+ return { type: principal["type"], id: principal["id"] };
16429
+ }
16430
+ function safeLease(candidate) {
16431
+ const lease = exactRecord(candidate, LEASE_KEYS);
16432
+ if (!lease || lease["owner"] !== null && (typeof lease["owner"] !== "string" || !LEASE_OWNER_PATTERN.test(lease["owner"])))
16433
+ return null;
16434
+ if (!nullableTimestamp(lease["expires_at"]) || !Number.isSafeInteger(lease["fencing_epoch"]) || Number(lease["fencing_epoch"]) < 0)
16435
+ return null;
16436
+ return {
16437
+ owner: lease["owner"],
16438
+ expires_at: lease["expires_at"],
16439
+ fencing_epoch: Number(lease["fencing_epoch"])
16440
+ };
16441
+ }
16442
+ function safeResponseStatus(candidate) {
16443
+ if (candidate === null)
16444
+ return null;
16445
+ return Number.isInteger(candidate) && Number(candidate) >= 100 && Number(candidate) <= 599 ? Number(candidate) : undefined;
16446
+ }
16447
+ function validLeaseState(status, lease) {
16448
+ const running = status === "running";
16449
+ return running ? lease.owner !== null && lease.expires_at !== null && lease.fencing_epoch > 0 : lease.owner === null && lease.expires_at === null;
16450
+ }
16451
+ function validMutationLifecycle(mutation, receipt, responseStatus) {
16452
+ const terminal = ["succeeded", "failed_terminal", "outcome_unknown"].includes(String(mutation["status"]));
16453
+ if (mutation["completed_at"] !== null !== terminal)
16454
+ return false;
16455
+ if (mutation["status"] === "succeeded") {
16456
+ return receipt !== null && responseStatus !== null && responseStatus >= 200 && responseStatus < 300 && mutation["failure_code"] === null;
16457
+ }
16458
+ if (mutation["status"] === "failed_terminal") {
16459
+ return receipt !== null && typeof mutation["failure_code"] === "string";
16460
+ }
16461
+ if (mutation["status"] === "failed_retryable" || mutation["status"] === "outcome_unknown") {
16462
+ return receipt !== null && typeof mutation["failure_code"] === "string";
16463
+ }
16464
+ if (mutation["status"] === "pending") {
16465
+ return receipt === null && responseStatus === null && mutation["failure_code"] === null;
16466
+ }
16467
+ return true;
16468
+ }
16469
+ function canonicalMutationResourceKey(candidate) {
16470
+ if (typeof candidate !== "string")
16471
+ return false;
16472
+ const match = RESOURCE_KEY_PATTERN.exec(candidate);
16473
+ if (!match)
16474
+ return false;
16475
+ const encodedResourceId = match[1];
16476
+ const resourceIdBytes = Buffer.from(encodedResourceId, "base64url");
16477
+ if (resourceIdBytes.byteLength < 1 || resourceIdBytes.byteLength > MAX_RESOURCE_ID_BYTES || resourceIdBytes.toString("base64url") !== encodedResourceId)
16478
+ return false;
16479
+ let resourceId;
16480
+ try {
16481
+ resourceId = FATAL_UTF8_DECODER.decode(resourceIdBytes);
16482
+ } catch (decodeError) {
16483
+ if (decodeError instanceof TypeError)
16484
+ return false;
16485
+ throw decodeError;
16486
+ }
16487
+ return resourceId.trim() === resourceId && !RESOURCE_ID_CONTROL_PATTERN.test(resourceId) && Buffer.from(resourceId, "utf8").equals(resourceIdBytes);
16488
+ }
16489
+ function validMutationIdentity(mutation) {
16490
+ if (!isMutationId(mutation["mutation_id"]) || typeof mutation["project_ref"] !== "string")
16491
+ return false;
16492
+ if (typeof mutation["operation"] !== "string" || !OPERATION_PATTERN.test(mutation["operation"]))
16493
+ return false;
16494
+ if (mutation["resource_key"] !== null && !canonicalMutationResourceKey(mutation["resource_key"]))
16495
+ return false;
16496
+ return typeof mutation["request_fingerprint"] === "string" && FINGERPRINT_PATTERN.test(mutation["request_fingerprint"]);
16497
+ }
16498
+ function validMutationTerminalFields(mutation) {
16499
+ if (typeof mutation["status"] !== "string" || !MUTATION_STATUSES.has(mutation["status"]))
16500
+ return false;
16501
+ if (mutation["failure_code"] !== null && (typeof mutation["failure_code"] !== "string" || !FAILURE_CODE_PATTERN.test(mutation["failure_code"])))
16502
+ return false;
16503
+ return nullableTimestamp(mutation["completed_at"]) && canonicalTimestamp(mutation["created_at"]) && canonicalTimestamp(mutation["updated_at"]);
16504
+ }
16505
+ function safeMutationStatus(candidate) {
16506
+ const mutation = exactRecord(candidate, MUTATION_KEYS);
16507
+ if (!mutation || !validMutationIdentity(mutation) || !validMutationTerminalFields(mutation))
16508
+ return null;
16509
+ const principal = safePrincipal(mutation["principal"]);
16510
+ const checkpoint = emptyProjection(mutation["checkpoint"]);
16511
+ const receipt = mutation["receipt"] === null ? null : emptyProjection(mutation["receipt"]);
16512
+ const responseStatus = safeResponseStatus(mutation["response_status"]);
16513
+ const lease = safeLease(mutation["lease"]);
16514
+ if (!principal || !checkpoint || mutation["receipt"] !== null && !receipt || responseStatus === undefined || !lease || !validLeaseState(String(mutation["status"]), lease) || !validMutationLifecycle(mutation, receipt, responseStatus))
16515
+ return null;
16516
+ return {
16517
+ project_ref: mutation["project_ref"],
16518
+ mutation_id: mutation["mutation_id"],
16519
+ operation: mutation["operation"],
16520
+ resource_key: mutation["resource_key"],
16521
+ request_fingerprint: mutation["request_fingerprint"],
16522
+ principal,
16523
+ status: mutation["status"],
16524
+ checkpoint,
16525
+ receipt,
16526
+ response_status: responseStatus,
16527
+ failure_code: mutation["failure_code"],
16528
+ lease,
16529
+ completed_at: mutation["completed_at"],
16530
+ created_at: mutation["created_at"],
16531
+ updated_at: mutation["updated_at"]
16532
+ };
16533
+ }
16534
+ function mutationStatusPath(ref, mutationId) {
16535
+ if (!isMutationId(mutationId))
16536
+ throw new Error("'mutation_id' must be a UUIDv4");
16537
+ return `/v1/projects/${projectRefPathSegment(ref, "Mutations")}/mutations/${encodeURIComponent(mutationId)}`;
16538
+ }
16539
+ function mutationStatusResponse(ref, mutationId, payload) {
16540
+ const response = exactRecord(payload, MUTATION_RESPONSE_KEYS);
16541
+ const mutation = safeMutationStatus(response?.["mutation"]);
16542
+ return response?.["project_ref"] === ref && mutation?.project_ref === ref && mutation.mutation_id === mutationId ? mutation : null;
16543
+ }
16544
+ async function fetchMutationStatus(http, ref, mutationId) {
16545
+ const response = await http.get(mutationStatusPath(ref, mutationId), {
16546
+ maxResponseBytes: MAX_STATUS_RESPONSE_BYTES
16547
+ });
16548
+ if (!response.ok) {
16549
+ return { kind: "unavailable", httpStatus: response.transportError ? null : response.status };
16550
+ }
16551
+ const mutation = mutationStatusResponse(ref, mutationId, response.data);
16552
+ return mutation ? { kind: "available", mutation } : { kind: "invalid" };
16553
+ }
16554
+
16555
+ // src/shared/tools/frontend-release-control.ts
16556
+ var PROJECT_REF_PATTERN3 = /^[A-Za-z0-9_-]{1,20}$/u;
16557
+ var DEPLOYMENT_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/u;
16558
+ var RELEASE_ID_PATTERN = /^[0-9a-f]{64}$/u;
16559
+ var MUTATION_ID_PATTERN2 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;
16560
+ var TIMESTAMP_PATTERN2 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u;
16561
+ var ARCHIVE_MAX_BYTES = 100 * 1024 * 1024;
16562
+ var ARCHIVE_CHUNK_BYTES = 64 * 1024;
16563
+ var RESPONSE_MAX_BYTES = 1024 * 1024;
16564
+ var UPLOAD_REQUEST_TIMEOUT_MS = 10 * 60000;
16565
+ var MUTATION_RESPONSE_TIMEOUT_MS = 5000;
16566
+ var RELEASE_LIST_LIMIT_MAX = 100;
16567
+ function toolResponse(payload) {
16568
+ return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
16569
+ }
16570
+ function releaseFailure(operation, code, status) {
16571
+ return {
16572
+ isError: true,
16573
+ content: [{
16574
+ type: "text",
16575
+ text: JSON.stringify({ ok: false, operation, error: { code, http_status: status } })
16576
+ }]
16577
+ };
16578
+ }
16579
+ function exactKeys(candidate, keys) {
16580
+ const actual = Object.keys(candidate).sort();
16581
+ const expected = [...keys].sort();
16582
+ return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
16583
+ }
16584
+ function releaseRecord(candidate) {
16585
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate))
16586
+ return null;
16587
+ const record = candidate;
16588
+ const keys = [
16589
+ "schema",
16590
+ "project_ref",
16591
+ "deployment_id",
16592
+ "release_id",
16593
+ "sha256",
16594
+ "tree_sha256",
16595
+ "size_bytes",
16596
+ "file_count",
16597
+ "created_at",
16598
+ "kind",
16599
+ "archive_format"
16600
+ ];
16601
+ if (!exactKeys(record, keys) || record.schema !== FRONTEND_ARCHIVE_RELEASE_SCHEMA || record.archive_format !== FRONTEND_ARCHIVE_FORMAT || typeof record.project_ref !== "string" || !PROJECT_REF_PATTERN3.test(record.project_ref) || typeof record.deployment_id !== "string" || !DEPLOYMENT_ID_PATTERN.test(record.deployment_id) || typeof record.release_id !== "string" || !RELEASE_ID_PATTERN.test(record.release_id) || record.sha256 !== record.release_id || typeof record.tree_sha256 !== "string" || !RELEASE_ID_PATTERN.test(record.tree_sha256) || !Number.isSafeInteger(record.size_bytes) || Number(record.size_bytes) < 1 || !Number.isSafeInteger(record.file_count) || Number(record.file_count) < 1 || !canonicalTimestamp2(record.created_at) || record.kind !== "prebuilt_static")
16602
+ return null;
16603
+ return {
16604
+ project_ref: record.project_ref,
16605
+ deployment_id: record.deployment_id,
16606
+ release_id: record.release_id,
16607
+ sha256: record.release_id,
16608
+ tree_sha256: record.tree_sha256,
16609
+ size_bytes: Number(record.size_bytes),
16610
+ file_count: Number(record.file_count),
16611
+ created_at: record.created_at,
16612
+ kind: "prebuilt_static",
16613
+ archive_format: FRONTEND_ARCHIVE_FORMAT
16614
+ };
16615
+ }
16616
+ function releaseEnvelope(candidate, expected) {
16617
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate))
16348
16618
  return null;
16349
16619
  const envelope = candidate;
16350
16620
  const release = exactKeys(envelope, ["project_ref", "deployment_id", "release"]) ? releaseRecord(envelope.release) : null;
@@ -16352,8 +16622,8 @@ function releaseEnvelope(candidate, expected) {
16352
16622
  return null;
16353
16623
  return release;
16354
16624
  }
16355
- function canonicalTimestamp(candidate) {
16356
- if (typeof candidate !== "string" || !TIMESTAMP_PATTERN.test(candidate))
16625
+ function canonicalTimestamp2(candidate) {
16626
+ if (typeof candidate !== "string" || !TIMESTAMP_PATTERN2.test(candidate))
16357
16627
  return false;
16358
16628
  const milliseconds = Date.parse(candidate);
16359
16629
  return Number.isFinite(milliseconds) && new Date(milliseconds).toISOString() === candidate;
@@ -16378,7 +16648,7 @@ function releaseInventory(candidate, expected) {
16378
16648
  if (!exactKeys(record, keys) || typeof record.project_ref !== "string" || !PROJECT_REF_PATTERN3.test(record.project_ref) || typeof record.deployment_id !== "string" || !DEPLOYMENT_ID_PATTERN.test(record.deployment_id) || !Array.isArray(record.releases))
16379
16649
  return null;
16380
16650
  const activeReleaseId = nullableIdentity(record.active_release_id, RELEASE_ID_PATTERN);
16381
- const activeActivationId = nullableIdentity(record.active_activation_id, MUTATION_ID_PATTERN);
16651
+ const activeActivationId = nullableIdentity(record.active_activation_id, MUTATION_ID_PATTERN2);
16382
16652
  const nextCursor = nullableIdentity(record.next_cursor, RELEASE_ID_PATTERN);
16383
16653
  const releases = record.releases.map(releaseRecord);
16384
16654
  if (activeReleaseId === undefined || activeActivationId === undefined || nextCursor === undefined || activeReleaseId === null !== (activeActivationId === null) || releases.some((release) => release === null))
@@ -16414,7 +16684,7 @@ function sameArchiveIdentity(left, right) {
16414
16684
  return left.dev === right.dev && left.ino === right.ino && left.size === right.size && left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs;
16415
16685
  }
16416
16686
  async function archiveSha256(handle, sizeBytes) {
16417
- const hash = createHash5("sha256");
16687
+ const hash = createHash6("sha256");
16418
16688
  const chunk = new Uint8Array(Math.min(sizeBytes, ARCHIVE_CHUNK_BYTES));
16419
16689
  for (let offset = 0;offset < sizeBytes; ) {
16420
16690
  const requested = Math.min(chunk.byteLength, sizeBytes - offset);
@@ -16489,13 +16759,13 @@ async function getFrontendRelease(http, projectRef, deploymentId, releaseId) {
16489
16759
  const response = await http.get(releasePath(projectRef, deploymentId, releaseId), {
16490
16760
  maxJsonBytes: RESPONSE_MAX_BYTES
16491
16761
  });
16492
- const release = response.ok ? releaseEnvelope(response.data, { projectRef, deploymentId, releaseId }) : null;
16762
+ const release = response.ok && response.status === 200 ? releaseEnvelope(response.data, { projectRef, deploymentId, releaseId }) : null;
16493
16763
  if (!release) {
16494
16764
  return releaseReadFailure("frontend.get_release", response);
16495
16765
  }
16496
16766
  return toolResponse({ project_ref: projectRef, deployment_id: deploymentId, release });
16497
16767
  }
16498
- async function uploadFrontendRelease(http, projectRef, deploymentId, archivePath) {
16768
+ async function uploadFrontendReleaseWithReadback(http, projectRef, deploymentId, archivePath, verifyReadback) {
16499
16769
  const endpoint = releaseEndpoint(projectRef, deploymentId);
16500
16770
  const archive = await verifiedArchive(archivePath);
16501
16771
  let response;
@@ -16514,13 +16784,15 @@ async function uploadFrontendRelease(http, projectRef, deploymentId, archivePath
16514
16784
  } finally {
16515
16785
  await archive.handle.close();
16516
16786
  }
16517
- const release = response.ok ? releaseEnvelope(response.data, { projectRef, deploymentId, releaseId: archive.sha256 }) : null;
16787
+ const release = response.ok && (response.status === 200 || response.status === 201) ? releaseEnvelope(response.data, { projectRef, deploymentId, releaseId: archive.sha256 }) : null;
16518
16788
  if (!release) {
16519
16789
  if (response.status >= 400 && response.status < 500 && response.status !== 408 && !response.transportError) {
16520
16790
  return releaseFailure("frontend.upload_release", "HTTP_ERROR", response.status);
16521
16791
  }
16522
16792
  return uploadReadback(http, projectRef, deploymentId, archive.sha256, response.status);
16523
16793
  }
16794
+ if (!verifyReadback)
16795
+ return toolResponse({ project_ref: projectRef, deployment_id: deploymentId, release });
16524
16796
  const readback = await http.get(`${endpoint}/${release.release_id}`, { maxJsonBytes: RESPONSE_MAX_BYTES });
16525
16797
  const verified = readback.ok ? releaseEnvelope(readback.data, { projectRef, deploymentId, releaseId: release.release_id }) : null;
16526
16798
  if (!verified || verified.tree_sha256 !== release.tree_sha256) {
@@ -16528,65 +16800,11 @@ async function uploadFrontendRelease(http, projectRef, deploymentId, archivePath
16528
16800
  }
16529
16801
  return toolResponse({ project_ref: projectRef, deployment_id: deploymentId, release: verified });
16530
16802
  }
16531
- async function uploadProjectWebRelease(http, projectRef, environmentId, deploymentId, archivePath) {
16532
- const endpoint = `/v1/projects/${encodeURIComponent(projectRef)}/environments/${encodeURIComponent(environmentId)}/deployments/web-release?deployment_id=${encodeURIComponent(deploymentId)}`;
16533
- const archive = await verifiedArchive(archivePath);
16534
- const successPayload = (value) => {
16535
- const release = value ? releaseRecord(value.release) : null;
16536
- if (!value || value.ok !== true || value.project_ref !== projectRef || value.environment_id !== environmentId || value.deployment_id !== deploymentId || value.release_id !== archive.sha256 || release?.release_id !== archive.sha256 || release.tree_sha256.length !== 64)
16537
- return null;
16538
- return toolResponse({
16539
- project_ref: projectRef,
16540
- environment_id: environmentId,
16541
- deployment_id: deploymentId,
16542
- release_id: archive.sha256,
16543
- release
16544
- });
16545
- };
16546
- try {
16547
- const response = await http.postBinary(endpoint, {
16548
- stream: archiveStream(archive),
16549
- byteLength: archive.sizeBytes
16550
- }, {
16551
- contentType: FRONTEND_ARCHIVE_CONTENT_TYPE,
16552
- contentLength: archive.sizeBytes,
16553
- contentSha256: archive.sha256,
16554
- maxJsonBytes: RESPONSE_MAX_BYTES,
16555
- timeoutMs: UPLOAD_REQUEST_TIMEOUT_MS,
16556
- responseTimeoutMs: MUTATION_RESPONSE_TIMEOUT_MS
16557
- });
16558
- const value = response.data && typeof response.data === "object" && !Array.isArray(response.data) ? response.data : null;
16559
- if (response.ok && response.status === 201) {
16560
- const success = successPayload(value);
16561
- if (success)
16562
- return success;
16563
- }
16564
- if (response.transportError || response.responseReadError || response.status >= 500 || response.status === 202) {
16565
- const readback = await getFrontendRelease(http, projectRef, deploymentId, archive.sha256);
16566
- if (!readback.isError) {
16567
- const readbackValue = JSON.parse(readback.content[0]?.text ?? "{}");
16568
- const release = readbackValue.release;
16569
- if (release && typeof release === "object" && !Array.isArray(release) && release.release_id === archive.sha256) {
16570
- return toolResponse({
16571
- project_ref: projectRef,
16572
- environment_id: environmentId,
16573
- deployment_id: deploymentId,
16574
- release_id: archive.sha256,
16575
- release
16576
- });
16577
- }
16578
- }
16579
- }
16580
- return releaseFailure("frontend.upload_project_web_release", response.status >= 400 && response.status < 500 && !response.transportError ? "HTTP_ERROR" : "OUTCOME_UNKNOWN", response.status);
16581
- } finally {
16582
- await archive.handle.close();
16583
- }
16584
- }
16585
16803
  async function uploadReadback(http, projectRef, deploymentId, releaseId, uploadStatus) {
16586
16804
  const response = await http.get(releasePath(projectRef, deploymentId, releaseId), {
16587
16805
  maxJsonBytes: RESPONSE_MAX_BYTES
16588
16806
  });
16589
- const release = response.ok ? releaseEnvelope(response.data, { projectRef, deploymentId, releaseId }) : null;
16807
+ const release = response.ok && response.status === 200 ? releaseEnvelope(response.data, { projectRef, deploymentId, releaseId }) : null;
16590
16808
  if (!release) {
16591
16809
  return releaseFailure("frontend.upload_release", "OUTCOME_UNKNOWN", uploadStatus);
16592
16810
  }
@@ -16601,7 +16819,7 @@ function stableJson(candidate) {
16601
16819
  return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`).join(",")}}`;
16602
16820
  }
16603
16821
  function activationFingerprint(identity) {
16604
- return createHash5("sha256").update(stableJson({
16822
+ return createHash6("sha256").update(stableJson({
16605
16823
  project_ref: identity.projectRef,
16606
16824
  deployment_id: identity.deploymentId,
16607
16825
  release_id: identity.releaseId,
@@ -16617,25 +16835,8 @@ function publicMutationReceipt(candidate, expected) {
16617
16835
  if (!candidate || typeof candidate !== "object" || Array.isArray(candidate))
16618
16836
  return null;
16619
16837
  const envelope = candidate;
16620
- const mutation = envelope.mutation;
16621
- const expectedKeys = [
16622
- "project_ref",
16623
- "mutation_id",
16624
- "operation",
16625
- "resource_key",
16626
- "request_fingerprint",
16627
- "principal",
16628
- "status",
16629
- "checkpoint",
16630
- "receipt",
16631
- "response_status",
16632
- "failure_code",
16633
- "lease",
16634
- "completed_at",
16635
- "created_at",
16636
- "updated_at"
16637
- ];
16638
- if (!mutation || !exactKeys(envelope, ["project_ref", "mutation"]) || !exactKeys(mutation, expectedKeys) || envelope.project_ref !== expected.projectRef || mutation.project_ref !== expected.projectRef || mutation.mutation_id !== expected.mutationId || mutation.resource_key !== expected.resourceKey || mutation.request_fingerprint !== expected.requestFingerprint || typeof mutation.operation !== "string" || typeof mutation.status !== "string" || !(mutation.response_status === null || Number.isSafeInteger(mutation.response_status)) || !(mutation.failure_code === null || typeof mutation.failure_code === "string"))
16838
+ const mutation = safeMutationStatus(envelope.mutation);
16839
+ if (!mutation || !exactKeys(envelope, ["project_ref", "mutation"]) || envelope.project_ref !== expected.projectRef || mutation.project_ref !== expected.projectRef || mutation.mutation_id !== expected.mutationId || mutation.resource_key !== expected.resourceKey || mutation.request_fingerprint !== expected.requestFingerprint)
16639
16840
  return null;
16640
16841
  return {
16641
16842
  operation: mutation.operation,
@@ -16662,6 +16863,18 @@ function activationReceipt(candidate, input) {
16662
16863
  return release;
16663
16864
  }
16664
16865
  async function activeReleaseReadback(http, identity) {
16866
+ const snapshot = await activeReleaseSnapshotReadback(http, identity);
16867
+ if (!snapshot.release)
16868
+ return snapshot;
16869
+ const releaseRead = await http.get(releasePath(identity.projectRef, identity.deploymentId, identity.releaseId), { maxJsonBytes: RESPONSE_MAX_BYTES });
16870
+ const release = releaseRead.ok && releaseRead.status === 200 ? releaseEnvelope(releaseRead.data, {
16871
+ projectRef: identity.projectRef,
16872
+ deploymentId: identity.deploymentId,
16873
+ releaseId: identity.releaseId
16874
+ }) : null;
16875
+ return { release, status: releaseRead.status };
16876
+ }
16877
+ async function activeReleaseSnapshotReadback(http, identity) {
16665
16878
  const snapshot = await readActiveFrontendRelease(http, identity.projectRef, identity.deploymentId);
16666
16879
  if ("failure" in snapshot)
16667
16880
  return { release: null, status: snapshot.status };
@@ -16669,13 +16882,7 @@ async function activeReleaseReadback(http, identity) {
16669
16882
  if (inventory.active_release_id !== identity.releaseId || inventory.active_activation_id !== identity.mutationId) {
16670
16883
  return { release: null, status: 200 };
16671
16884
  }
16672
- const releaseRead = await http.get(releasePath(identity.projectRef, identity.deploymentId, identity.releaseId), { maxJsonBytes: RESPONSE_MAX_BYTES });
16673
- const release = releaseRead.ok ? releaseEnvelope(releaseRead.data, {
16674
- projectRef: identity.projectRef,
16675
- deploymentId: identity.deploymentId,
16676
- releaseId: identity.releaseId
16677
- }) : null;
16678
- return { release, status: releaseRead.status };
16885
+ return { release: inventory.releases[0] ?? null, status: 200 };
16679
16886
  }
16680
16887
  async function readActiveFrontendRelease(http, projectRef, deploymentId) {
16681
16888
  const response = await http.get(`${deploymentEndpoint(projectRef, deploymentId)}/active-release`, {
@@ -16711,7 +16918,7 @@ async function getActiveFrontendRelease(http, projectRef, deploymentId) {
16711
16918
  const snapshot = await readActiveFrontendRelease(http, projectRef, deploymentId);
16712
16919
  return "failure" in snapshot ? snapshot.failure : toolResponse(snapshot.inventory);
16713
16920
  }
16714
- async function publishFrontendRelease(http, projectRef, deploymentId, archivePath, expectedTreeSha256) {
16921
+ async function publishFrontendRelease(http, projectRef, deploymentId, archiveSource, expectedTreeSha256) {
16715
16922
  const authority = await readActiveFrontendRelease(http, projectRef, deploymentId);
16716
16923
  if ("failure" in authority)
16717
16924
  return authority.failure;
@@ -16728,9 +16935,16 @@ async function publishFrontendRelease(http, projectRef, deploymentId, archivePat
16728
16935
  unchanged: true
16729
16936
  });
16730
16937
  }
16731
- const uploaded = await uploadFrontendRelease(http, projectRef, deploymentId, archivePath);
16732
- if (uploaded.isError)
16733
- return uploaded;
16938
+ const archive = typeof archiveSource === "string" ? { archivePath: archiveSource } : await archiveSource();
16939
+ let uploaded;
16940
+ try {
16941
+ uploaded = await uploadFrontendReleaseWithReadback(http, projectRef, deploymentId, archive.archivePath, false);
16942
+ } finally {
16943
+ if ("cleanup" in archive)
16944
+ await archive.cleanup();
16945
+ }
16946
+ if (uploaded.isError)
16947
+ return uploaded;
16734
16948
  const value = JSON.parse(uploaded.content[0].text);
16735
16949
  const envelope = value && typeof value === "object" && !Array.isArray(value) ? value : null;
16736
16950
  const candidate = envelope?.release;
@@ -16739,14 +16953,14 @@ async function publishFrontendRelease(http, projectRef, deploymentId, archivePat
16739
16953
  return releaseFailure("frontend.deploy", "INVALID_RESPONSE", 200);
16740
16954
  }
16741
16955
  const mutationId = crypto.randomUUID();
16742
- const activated = await activateFrontendRelease(http, {
16956
+ const activated = await activateFrontendReleaseWithReadback(http, {
16743
16957
  projectRef,
16744
16958
  deploymentId,
16745
16959
  releaseId: release.release_id,
16746
16960
  mutationId,
16747
16961
  expectedActiveReleaseId: inventory.active_release_id ?? "absent",
16748
16962
  expectedActivationId: inventory.active_activation_id ?? "absent"
16749
- });
16963
+ }, activeReleaseSnapshotReadback);
16750
16964
  const activatedPayload = JSON.parse(activated.content[0].text);
16751
16965
  const active = activatedPayload.release;
16752
16966
  const treeMismatch = !activated.isError && (!active || typeof active !== "object" || !("tree_sha256" in active) || active.tree_sha256 !== expectedTreeSha256);
@@ -16762,10 +16976,74 @@ async function publishFrontendRelease(http, projectRef, deploymentId, archivePat
16762
16976
  ...activated.isError || treeMismatch ? { isError: true } : {}
16763
16977
  };
16764
16978
  }
16765
- async function rollbackFrontendRelease(http, projectRef, deploymentId, releaseId) {
16979
+ function boundRollbackResponse(result, input) {
16980
+ const payload = JSON.parse(result.content[0].text);
16981
+ return {
16982
+ ...toolResponse({
16983
+ ...payload,
16984
+ operation: "frontend.rollback",
16985
+ project_ref: input.projectRef,
16986
+ deployment_id: input.deploymentId,
16987
+ target_release_id: input.releaseId,
16988
+ mutation_id: input.mutationId,
16989
+ expected_active_release_id: input.expectedActiveReleaseId,
16990
+ expected_activation_id: input.expectedActivationId,
16991
+ ...!result.isError ? {
16992
+ rollback: true,
16993
+ previous_release_id: input.expectedActiveReleaseId,
16994
+ previous_activation_id: input.expectedActivationId
16995
+ } : {}
16996
+ }),
16997
+ ...result.isError ? { isError: true } : {}
16998
+ };
16999
+ }
17000
+ async function rollbackBoundFrontendRelease(http, input) {
17001
+ const authority = await readActiveFrontendRelease(http, input.projectRef, input.deploymentId);
17002
+ if ("failure" in authority)
17003
+ return boundRollbackResponse(authority.failure, input);
17004
+ const current = authority.inventory;
17005
+ if (current.active_release_id === input.releaseId && current.active_activation_id === input.mutationId) {
17006
+ return boundRollbackResponse(await activationReadback(http, input, input.mutationId, 200), input);
17007
+ }
17008
+ if (current.active_release_id !== input.expectedActiveReleaseId || current.active_activation_id !== input.expectedActivationId) {
17009
+ return boundRollbackResponse(releaseFailure("frontend.rollback", "STALE_AUTHORITY", 409), input);
17010
+ }
17011
+ const target = await getFrontendRelease(http, input.projectRef, input.deploymentId, input.releaseId);
17012
+ if (target.isError)
17013
+ return boundRollbackResponse(target, input);
17014
+ return boundRollbackResponse(await activateFrontendRelease(http, input), input);
17015
+ }
17016
+ async function rollbackFrontendRelease(http, projectRef, deploymentId, releaseId, options = {}) {
16766
17017
  if (releaseId !== undefined && !RELEASE_ID_PATTERN.test(releaseId)) {
16767
17018
  throw new Error("'release_id' must be a SHA-256 digest");
16768
17019
  }
17020
+ const bound = options.expectedActiveReleaseId !== undefined || options.expectedActivationId !== undefined || options.mutationId !== undefined;
17021
+ if (bound) {
17022
+ const { expectedActiveReleaseId, expectedActivationId, mutationId } = options;
17023
+ if (releaseId === undefined || expectedActiveReleaseId === undefined || expectedActivationId === undefined || mutationId === undefined) {
17024
+ throw new Error("'release_id', 'expected_active_release_id', 'expected_activation_id', and 'mutation_id' are required together for a bound rollback");
17025
+ }
17026
+ if (!RELEASE_ID_PATTERN.test(expectedActiveReleaseId)) {
17027
+ throw new Error("'expected_active_release_id' must be a SHA-256 digest");
17028
+ }
17029
+ if (!MUTATION_ID_PATTERN2.test(expectedActivationId)) {
17030
+ throw new Error("'expected_activation_id' must be a UUIDv4");
17031
+ }
17032
+ if (!MUTATION_ID_PATTERN2.test(mutationId)) {
17033
+ throw new Error("'mutation_id' must be a UUIDv4");
17034
+ }
17035
+ if (releaseId === expectedActiveReleaseId || mutationId === expectedActivationId) {
17036
+ throw new Error("Bound rollback requires a different target release and a distinct mutation UUID");
17037
+ }
17038
+ return rollbackBoundFrontendRelease(http, {
17039
+ projectRef,
17040
+ deploymentId,
17041
+ releaseId,
17042
+ expectedActiveReleaseId,
17043
+ expectedActivationId,
17044
+ mutationId
17045
+ });
17046
+ }
16769
17047
  const authority = releaseId === undefined ? await readPreviousFrontendRelease(http, projectRef, deploymentId) : await readActiveFrontendRelease(http, projectRef, deploymentId);
16770
17048
  if ("failure" in authority)
16771
17049
  return authority.failure;
@@ -16834,8 +17112,8 @@ async function readPreviousFrontendRelease(http, projectRef, deploymentId) {
16834
17112
  const data = response.data;
16835
17113
  const value = data && typeof data === "object" && !Array.isArray(data) ? data : null;
16836
17114
  const activeReleaseId = value ? nullableIdentity(value.active_release_id, RELEASE_ID_PATTERN) : undefined;
16837
- const activeActivationId = value ? nullableIdentity(value.active_activation_id, MUTATION_ID_PATTERN) : undefined;
16838
- const previousActivationId = value ? nullableIdentity(value.previous_activation_id, MUTATION_ID_PATTERN) : undefined;
17115
+ const activeActivationId = value ? nullableIdentity(value.active_activation_id, MUTATION_ID_PATTERN2) : undefined;
17116
+ const previousActivationId = value ? nullableIdentity(value.previous_activation_id, MUTATION_ID_PATTERN2) : undefined;
16839
17117
  const previous = value?.previous_release === null ? null : releaseRecord(value?.previous_release);
16840
17118
  if (!response.ok || response.status !== 200 || !value || !exactKeys(value, [
16841
17119
  "schema",
@@ -16861,13 +17139,16 @@ async function readPreviousFrontendRelease(http, projectRef, deploymentId) {
16861
17139
  };
16862
17140
  }
16863
17141
  async function activateFrontendRelease(http, input) {
17142
+ return activateFrontendReleaseWithReadback(http, input, activeReleaseReadback);
17143
+ }
17144
+ async function activateFrontendReleaseWithReadback(http, input, readActive) {
16864
17145
  const mutationId = input.mutationId;
16865
- if (!MUTATION_ID_PATTERN.test(mutationId))
17146
+ if (!MUTATION_ID_PATTERN2.test(mutationId))
16866
17147
  throw new Error("'mutation_id' must be a UUIDv4");
16867
17148
  if (input.expectedActiveReleaseId !== "absent" && !RELEASE_ID_PATTERN.test(input.expectedActiveReleaseId)) {
16868
17149
  throw new Error("'expected_active_release_id' is invalid");
16869
17150
  }
16870
- if (input.expectedActivationId !== "absent" && !MUTATION_ID_PATTERN.test(input.expectedActivationId)) {
17151
+ if (input.expectedActivationId !== "absent" && !MUTATION_ID_PATTERN2.test(input.expectedActivationId)) {
16871
17152
  throw new Error("'expected_activation_id' is invalid");
16872
17153
  }
16873
17154
  const endpoint = `${releasePath(input.projectRef, input.deploymentId, input.releaseId)}/activate`;
@@ -16880,461 +17161,224 @@ async function activateFrontendRelease(http, input) {
16880
17161
  responseTimeoutMs: MUTATION_RESPONSE_TIMEOUT_MS
16881
17162
  });
16882
17163
  const release = activationReceipt(response.data, { ...input, mutationId });
16883
- if (!response.ok || !release) {
17164
+ if (!response.ok || response.status !== 200 || !release) {
16884
17165
  if (response.status >= 400 && response.status < 500 && response.status !== 408 && !response.transportError) {
16885
17166
  return releaseFailure("frontend.activate_release", "HTTP_ERROR", response.status);
16886
17167
  }
16887
17168
  return activationReadback(http, input, mutationId, response.status);
16888
17169
  }
16889
- const readback = await activeReleaseReadback(http, { ...input, mutationId });
17170
+ const readback = await readActive(http, { ...input, mutationId });
16890
17171
  if (!readback.release || readback.release.tree_sha256 !== release.tree_sha256) {
16891
17172
  return releaseFailure("frontend.activate_release", "OUTCOME_UNKNOWN", readback.status);
16892
17173
  }
16893
17174
  return toolResponse({
16894
- project_ref: input.projectRef,
16895
- deployment_id: input.deploymentId,
16896
- active_release_id: input.releaseId,
16897
- activation_id: mutationId,
16898
- release: readback.release
16899
- });
16900
- }
16901
- async function activationReadback(http, input, mutationId, activationStatus) {
16902
- const mutationRead = await http.get(`/v1/projects/${encodeURIComponent(input.projectRef)}/mutations/${mutationId}`, { maxJsonBytes: RESPONSE_MAX_BYTES });
16903
- const mutationEnvelope = mutationRead.data;
16904
- const mutation = mutationRead.ok ? publicMutationReceipt(mutationRead.data, {
16905
- projectRef: input.projectRef,
16906
- mutationId,
16907
- resourceKey: activationResourceKey(input.deploymentId),
16908
- requestFingerprint: activationFingerprint({ ...input, mutationId })
16909
- }) : null;
16910
- if (!mutation || mutationEnvelope?.project_ref !== input.projectRef || mutation.operation !== "frontend.release.activate" || mutation.status !== "succeeded" || mutation.responseStatus !== 200 || mutation.failureCode !== null) {
16911
- return releaseFailure("frontend.activate_release", "OUTCOME_UNKNOWN", activationStatus);
16912
- }
16913
- const readback = await activeReleaseReadback(http, { ...input, mutationId });
16914
- if (!readback.release) {
16915
- return releaseFailure("frontend.activate_release", "OUTCOME_UNKNOWN", activationStatus);
16916
- }
16917
- return toolResponse({
16918
- project_ref: input.projectRef,
16919
- deployment_id: input.deploymentId,
16920
- active_release_id: input.releaseId,
16921
- activation_id: mutationId,
16922
- release: readback.release
16923
- });
16924
- }
16925
-
16926
- // src/shared/tools/frontend-tools.ts
16927
- function registerFrontendTools(server, http) {
16928
- registerTool(server, "frontend", `Frontend hosting and immutable prebuilt releases. Supports: static, react, vue, svelte, sveltekit, sveltekit-static, nextjs, nuxt, astro.
16929
- 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.`, {
16930
- action: withDescription(stringEnum([
16931
- "list",
16932
- "get",
16933
- "create",
16934
- "update",
16935
- "delete",
16936
- "build_logs",
16937
- "add_domain",
16938
- "remove_domain",
16939
- "set_env",
16940
- "list_frameworks",
16941
- "list_records",
16942
- "list_releases",
16943
- "get_active_release",
16944
- "get_release",
16945
- "rollback"
16946
- ]), "Action"),
16947
- ref: optional(String2(), "Project ref"),
16948
- id: optional(String2(), "Deployment ID"),
16949
- name: optional(String2(), "[create] Deployment name"),
16950
- framework: optional(String2(), "[create] Framework (static|react|vue|svelte|sveltekit|sveltekit-static|nextjs|nuxt|astro)"),
16951
- domain: optional(String2(), "[create/update/add_domain/remove_domain] Custom domain"),
16952
- build_command: optional(String2(), "[create/update] Build command override"),
16953
- output_dir: optional(String2(), "[create/update] Output directory override"),
16954
- install_command: optional(String2(), "[create/update] Install command override"),
16955
- node_version: optional(String2(), "[create/update] Node.js version"),
16956
- health_check_path: optional(String2(), "[create/update] SSR readiness path (default: /)"),
16957
- env_vars: optional(Record(String2(), String2()), "[create/update/set_env] Environment variables"),
16958
- release_id: optional(String2(), "[get_release] SHA-256 release ID; [rollback] optional, defaults to journal-verified previous release"),
16959
- cursor: optional(String2(), "[list_releases] Last release SHA-256 cursor"),
16960
- limit: optional(Number2(), "[list_releases] Page size, 1-100 (default 50)")
16961
- }, async (args) => {
16962
- const {
16963
- action,
16964
- ref,
16965
- id,
16966
- name,
16967
- framework,
16968
- domain,
16969
- build_command,
16970
- output_dir,
16971
- install_command,
16972
- node_version,
16973
- health_check_path,
16974
- env_vars,
16975
- release_id,
16976
- cursor,
16977
- limit
16978
- } = args;
16979
- const need = (field, value) => {
16980
- if (value === undefined || value === null || value === "")
16981
- throw new Error(`'${field}' required for '${action}'`);
16982
- };
16983
- const ok = (res) => res.ok ? JSON.stringify(res.data, null, 2) : `❌ Failed (${res.status}): ${JSON.stringify(res.data)}`;
16984
- let text;
16985
- switch (action) {
16986
- case "list":
16987
- need("ref", ref);
16988
- text = ok(await http.get(`/v1/projects/${ref}/frontend/deployments`));
16989
- break;
16990
- case "get":
16991
- need("ref", ref);
16992
- need("id", id);
16993
- text = ok(await http.get(`/v1/projects/${ref}/frontend/deployments/${id}`));
16994
- break;
16995
- case "create":
16996
- need("ref", ref);
16997
- need("name", name);
16998
- need("framework", framework);
16999
- text = ok(await http.post(`/v1/projects/${ref}/frontend/deployments`, {
17000
- name,
17001
- framework,
17002
- domain,
17003
- build_command,
17004
- output_dir,
17005
- install_command,
17006
- node_version,
17007
- health_check_path,
17008
- env_vars
17009
- }));
17010
- break;
17011
- case "update":
17012
- need("ref", ref);
17013
- need("id", id);
17014
- text = ok(await http.patch(`/v1/projects/${ref}/frontend/deployments/${id}`, {
17015
- name,
17016
- domain,
17017
- build_command,
17018
- output_dir,
17019
- install_command,
17020
- node_version,
17021
- health_check_path,
17022
- env_vars
17023
- }));
17024
- break;
17025
- case "delete":
17026
- need("ref", ref);
17027
- need("id", id);
17028
- text = (await http.delete(`/v1/projects/${ref}/frontend/deployments/${id}`)).ok ? `✅ Deleted` : `❌ Failed`;
17029
- break;
17030
- case "build_logs":
17031
- need("ref", ref);
17032
- need("id", id);
17033
- const lr = await http.get(`/v1/projects/${ref}/frontend/deployments/${id}/logs`);
17034
- text = lr.ok ? typeof lr.data === "object" && lr.data !== null && "logs" in lr.data && typeof lr.data.logs === "string" ? lr.data.logs : "(no logs)" : `❌ Failed (${lr.status})`;
17035
- break;
17036
- case "add_domain":
17037
- need("ref", ref);
17038
- need("id", id);
17039
- need("domain", domain);
17040
- text = (await http.post(`/v1/projects/${ref}/frontend/deployments/${id}/domains`, { domain })).ok ? `✅ Domain ${domain} added` : `❌ Failed`;
17041
- break;
17042
- case "remove_domain":
17043
- need("ref", ref);
17044
- need("id", id);
17045
- need("domain", domain);
17046
- text = (await http.delete(`/v1/projects/${ref}/frontend/deployments/${id}/domains/${domain}`)).ok ? `✅ Domain ${domain} removed` : `❌ Failed`;
17047
- break;
17048
- case "set_env":
17049
- need("ref", ref);
17050
- need("id", id);
17051
- need("env_vars", env_vars);
17052
- text = (await http.put(`/v1/projects/${ref}/frontend/deployments/${id}/env`, { env_vars })).ok ? `✅ Set ${Object.keys(env_vars).length} env vars` : `❌ Failed`;
17053
- break;
17054
- case "list_frameworks":
17055
- text = ok(await http.get("/v1/projects/_/frontend/frameworks"));
17056
- break;
17057
- case "list_records":
17058
- need("ref", ref);
17059
- need("id", id);
17060
- text = ok(await http.get(`/v1/projects/${ref}/frontend/deployments/${id}/records`));
17061
- break;
17062
- case "list_releases":
17063
- need("ref", ref);
17064
- need("id", id);
17065
- return listFrontendReleases(http, ref, id, cursor, limit);
17066
- case "get_release":
17067
- need("ref", ref);
17068
- need("id", id);
17069
- need("release_id", release_id);
17070
- return getFrontendRelease(http, ref, id, release_id);
17071
- case "get_active_release":
17072
- need("ref", ref);
17073
- need("id", id);
17074
- return getActiveFrontendRelease(http, ref, id);
17075
- case "rollback":
17076
- need("ref", ref);
17077
- need("id", id);
17078
- return rollbackFrontendRelease(http, ref, id, release_id);
17079
- default:
17080
- text = `❌ Unknown action`;
17081
- }
17082
- return { content: [{ type: "text", text }] };
17083
- });
17084
- }
17085
-
17086
- // src/shared/tools/application-schemas.ts
17087
- var ApplicationIdSchema2 = ApplicationIdSchema;
17088
- var ApplicationReleaseIdSchema2 = ApplicationReleaseIdSchema;
17089
- var ApplicationReleaseRecordSchema2 = ApplicationReleaseRecordSchema;
17090
- var ApplicationConfigurationIdSchema2 = ApplicationConfigurationIdSchema;
17091
- var ApplicationActivationIdSchema2 = ApplicationActivationIdSchema;
17092
- var ApplicationActivationWriteSchema2 = ApplicationActivationWriteSchema;
17093
- var ApplicationActivationResultSchema2 = ApplicationActivationResultSchema;
17094
- var ApplicationActivationRetirementResultSchema2 = ApplicationActivationRetirementResultSchema;
17095
- var ApplicationRollbackSnapshotSchema2 = ApplicationRollbackSnapshotSchema;
17096
- var ApplicationActivationHistoryCursorSchema2 = ApplicationActivationHistoryCursorSchema;
17097
- var ApplicationPromotionPlanSchema2 = ApplicationPromotionPlanSchema;
17098
- var ApplicationDeployRequestSchema2 = ApplicationDeployRequestSchema;
17099
- var ApplicationDeployWebReleaseSchema2 = ApplicationDeployWebReleaseSchema;
17100
-
17101
- // src/shared/mutation-protocol.ts
17102
- import { createHash as createHash6 } from "node:crypto";
17103
- var MUTATION_ID_PATTERN2 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
17104
- var FINGERPRINT_PATTERN = /^[0-9a-f]{64}$/;
17105
- var OPERATION_PATTERN = /^[a-z0-9][a-z0-9._:-]{0,127}$/;
17106
- var RESOURCE_KEY_PATTERN = /^v1\/(?:[a-z0-9][a-z0-9._-]{0,63})\/([A-Za-z0-9_-]{2,171})$/;
17107
- var RESOURCE_ID_CONTROL_PATTERN = /[\u0000-\u001f\u007f-\u009f]/u;
17108
- var FAILURE_CODE_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
17109
- var LEASE_OWNER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:@/-]{0,254}$/;
17110
- var TIMESTAMP_PATTERN2 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
17111
- var MAX_STATUS_RESPONSE_BYTES = 196608;
17112
- var MAX_JSON_DEPTH = 32;
17113
- var MAX_RESOURCE_ID_BYTES = 128;
17114
- var FATAL_UTF8_DECODER = new TextDecoder("utf-8", { fatal: true });
17115
- var MUTATION_STATUSES = new Set([
17116
- "pending",
17117
- "running",
17118
- "succeeded",
17119
- "failed_retryable",
17120
- "failed_terminal",
17121
- "outcome_unknown"
17122
- ]);
17123
- var MUTATION_RESPONSE_KEYS = ["project_ref", "mutation"];
17124
- var MUTATION_KEYS = [
17125
- "project_ref",
17126
- "mutation_id",
17127
- "operation",
17128
- "resource_key",
17129
- "request_fingerprint",
17130
- "principal",
17131
- "status",
17132
- "checkpoint",
17133
- "receipt",
17134
- "response_status",
17135
- "failure_code",
17136
- "lease",
17137
- "completed_at",
17138
- "created_at",
17139
- "updated_at"
17140
- ];
17141
- var PRINCIPAL_KEYS = ["type", "id"];
17142
- var LEASE_KEYS = ["owner", "expires_at", "fencing_epoch"];
17143
- function isMutationId(candidate) {
17144
- return typeof candidate === "string" && MUTATION_ID_PATTERN2.test(candidate);
17145
- }
17146
- function assertCanonicalJsonNode(candidate, seen, depth) {
17147
- if (candidate === null || typeof candidate === "string" || typeof candidate === "boolean")
17148
- return;
17149
- if (typeof candidate === "number" && Number.isFinite(candidate))
17150
- return;
17151
- if (typeof candidate !== "object" || depth > MAX_JSON_DEPTH) {
17152
- throw new Error("Mutation request must be bounded JSON");
17153
- }
17154
- if (seen.has(candidate))
17155
- throw new Error("Mutation request must not contain cycles");
17156
- seen.add(candidate);
17157
- const prototype = Object.getPrototypeOf(candidate);
17158
- const plainRecord = prototype === Object.prototype || prototype === null;
17159
- const children = Array.isArray(candidate) ? candidate : plainRecord ? Object.values(candidate) : null;
17160
- if (!children)
17161
- throw new Error("Mutation request must contain only JSON objects and arrays");
17162
- for (const child of children)
17163
- assertCanonicalJsonNode(child, seen, depth + 1);
17164
- seen.delete(candidate);
17165
- }
17166
- function stableStringifyNode(jsonValue) {
17167
- if (jsonValue === null || typeof jsonValue !== "object") {
17168
- const serialized = JSON.stringify(jsonValue);
17169
- if (serialized === undefined)
17170
- throw new Error("Mutation request must contain exact JSON values");
17171
- return serialized;
17172
- }
17173
- if (Array.isArray(jsonValue))
17174
- return `[${jsonValue.map(stableStringifyNode).join(",")}]`;
17175
- const record = jsonValue;
17176
- const fields = Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringifyNode(record[key])}`);
17177
- return `{${fields.join(",")}}`;
17178
- }
17179
- function mutationRequestFingerprint(normalizedRequest) {
17180
- assertCanonicalJsonNode(normalizedRequest, new WeakSet, 0);
17181
- return createHash6("sha256").update(stableStringifyNode(normalizedRequest)).digest("hex");
17182
- }
17183
- function objectRecord3(candidate) {
17184
- return candidate && typeof candidate === "object" && !Array.isArray(candidate) ? candidate : null;
17185
- }
17186
- function exactRecord(candidate, keys) {
17187
- const record = objectRecord3(candidate);
17188
- if (!record || Object.keys(record).length !== keys.length)
17189
- return null;
17190
- return keys.every((key) => Object.hasOwn(record, key)) ? record : null;
17191
- }
17192
- function emptyProjection(candidate) {
17193
- const record = objectRecord3(candidate);
17194
- return record && Object.keys(record).length === 0 ? record : null;
17195
- }
17196
- function canonicalTimestamp2(candidate) {
17197
- if (typeof candidate !== "string" || !TIMESTAMP_PATTERN2.test(candidate))
17198
- return false;
17199
- const milliseconds = Date.parse(candidate);
17200
- return Number.isFinite(milliseconds) && new Date(milliseconds).toISOString() === candidate;
17201
- }
17202
- function nullableTimestamp(candidate) {
17203
- return candidate === null || canonicalTimestamp2(candidate);
17204
- }
17205
- function safePrincipal(candidate) {
17206
- const principal = exactRecord(candidate, PRINCIPAL_KEYS);
17207
- if (!principal || !["master", "admin", "project"].includes(String(principal["type"])))
17208
- return null;
17209
- if (typeof principal["id"] !== "string" || !principal["id"] || principal["id"].length > 320 || principal["id"].trim() !== principal["id"] || /[\u0000-\u001f\u007f]/u.test(principal["id"]))
17210
- return null;
17211
- return { type: principal["type"], id: principal["id"] };
17212
- }
17213
- function safeLease(candidate) {
17214
- const lease = exactRecord(candidate, LEASE_KEYS);
17215
- if (!lease || lease["owner"] !== null && (typeof lease["owner"] !== "string" || !LEASE_OWNER_PATTERN.test(lease["owner"])))
17216
- return null;
17217
- if (!nullableTimestamp(lease["expires_at"]) || !Number.isSafeInteger(lease["fencing_epoch"]) || Number(lease["fencing_epoch"]) < 0)
17218
- return null;
17219
- return {
17220
- owner: lease["owner"],
17221
- expires_at: lease["expires_at"],
17222
- fencing_epoch: Number(lease["fencing_epoch"])
17223
- };
17224
- }
17225
- function safeResponseStatus(candidate) {
17226
- if (candidate === null)
17227
- return null;
17228
- return Number.isInteger(candidate) && Number(candidate) >= 100 && Number(candidate) <= 599 ? Number(candidate) : undefined;
17229
- }
17230
- function validLeaseState(status, lease) {
17231
- const running = status === "running";
17232
- return running ? lease.owner !== null && lease.expires_at !== null && lease.fencing_epoch > 0 : lease.owner === null && lease.expires_at === null;
17233
- }
17234
- function validMutationLifecycle(mutation, receipt, responseStatus) {
17235
- const terminal = ["succeeded", "failed_terminal", "outcome_unknown"].includes(String(mutation["status"]));
17236
- if (mutation["completed_at"] !== null !== terminal)
17237
- return false;
17238
- if (mutation["status"] === "succeeded") {
17239
- return receipt !== null && responseStatus !== null && responseStatus >= 200 && responseStatus < 300 && mutation["failure_code"] === null;
17240
- }
17241
- if (mutation["status"] === "failed_terminal") {
17242
- return receipt !== null && typeof mutation["failure_code"] === "string";
17243
- }
17244
- if (mutation["status"] === "failed_retryable" || mutation["status"] === "outcome_unknown") {
17245
- return receipt !== null && typeof mutation["failure_code"] === "string";
17246
- }
17247
- if (mutation["status"] === "pending") {
17248
- return receipt === null && responseStatus === null && mutation["failure_code"] === null;
17249
- }
17250
- return true;
17251
- }
17252
- function canonicalMutationResourceKey(candidate) {
17253
- if (typeof candidate !== "string")
17254
- return false;
17255
- const match = RESOURCE_KEY_PATTERN.exec(candidate);
17256
- if (!match)
17257
- return false;
17258
- const encodedResourceId = match[1];
17259
- const resourceIdBytes = Buffer.from(encodedResourceId, "base64url");
17260
- if (resourceIdBytes.byteLength < 1 || resourceIdBytes.byteLength > MAX_RESOURCE_ID_BYTES || resourceIdBytes.toString("base64url") !== encodedResourceId)
17261
- return false;
17262
- let resourceId;
17263
- try {
17264
- resourceId = FATAL_UTF8_DECODER.decode(resourceIdBytes);
17265
- } catch (decodeError) {
17266
- if (decodeError instanceof TypeError)
17267
- return false;
17268
- throw decodeError;
17269
- }
17270
- return resourceId.trim() === resourceId && !RESOURCE_ID_CONTROL_PATTERN.test(resourceId) && Buffer.from(resourceId, "utf8").equals(resourceIdBytes);
17271
- }
17272
- function validMutationIdentity(mutation) {
17273
- if (!isMutationId(mutation["mutation_id"]) || typeof mutation["project_ref"] !== "string")
17274
- return false;
17275
- if (typeof mutation["operation"] !== "string" || !OPERATION_PATTERN.test(mutation["operation"]))
17276
- return false;
17277
- if (mutation["resource_key"] !== null && !canonicalMutationResourceKey(mutation["resource_key"]))
17278
- return false;
17279
- return typeof mutation["request_fingerprint"] === "string" && FINGERPRINT_PATTERN.test(mutation["request_fingerprint"]);
17280
- }
17281
- function validMutationTerminalFields(mutation) {
17282
- if (typeof mutation["status"] !== "string" || !MUTATION_STATUSES.has(mutation["status"]))
17283
- return false;
17284
- if (mutation["failure_code"] !== null && (typeof mutation["failure_code"] !== "string" || !FAILURE_CODE_PATTERN.test(mutation["failure_code"])))
17285
- return false;
17286
- return nullableTimestamp(mutation["completed_at"]) && canonicalTimestamp2(mutation["created_at"]) && canonicalTimestamp2(mutation["updated_at"]);
17287
- }
17288
- function safeMutationStatus(candidate) {
17289
- const mutation = exactRecord(candidate, MUTATION_KEYS);
17290
- if (!mutation || !validMutationIdentity(mutation) || !validMutationTerminalFields(mutation))
17291
- return null;
17292
- const principal = safePrincipal(mutation["principal"]);
17293
- const checkpoint = emptyProjection(mutation["checkpoint"]);
17294
- const receipt = mutation["receipt"] === null ? null : emptyProjection(mutation["receipt"]);
17295
- const responseStatus = safeResponseStatus(mutation["response_status"]);
17296
- const lease = safeLease(mutation["lease"]);
17297
- if (!principal || !checkpoint || mutation["receipt"] !== null && !receipt || responseStatus === undefined || !lease || !validLeaseState(String(mutation["status"]), lease) || !validMutationLifecycle(mutation, receipt, responseStatus))
17298
- return null;
17299
- return {
17300
- project_ref: mutation["project_ref"],
17301
- mutation_id: mutation["mutation_id"],
17302
- operation: mutation["operation"],
17303
- resource_key: mutation["resource_key"],
17304
- request_fingerprint: mutation["request_fingerprint"],
17305
- principal,
17306
- status: mutation["status"],
17307
- checkpoint,
17308
- receipt,
17309
- response_status: responseStatus,
17310
- failure_code: mutation["failure_code"],
17311
- lease,
17312
- completed_at: mutation["completed_at"],
17313
- created_at: mutation["created_at"],
17314
- updated_at: mutation["updated_at"]
17315
- };
17316
- }
17317
- function mutationStatusPath(ref, mutationId) {
17318
- if (!isMutationId(mutationId))
17319
- throw new Error("'mutation_id' must be a UUIDv4");
17320
- return `/v1/projects/${projectRefPathSegment(ref, "Mutations")}/mutations/${encodeURIComponent(mutationId)}`;
17321
- }
17322
- function mutationStatusResponse(ref, mutationId, payload) {
17323
- const response = exactRecord(payload, MUTATION_RESPONSE_KEYS);
17324
- const mutation = safeMutationStatus(response?.["mutation"]);
17325
- return response?.["project_ref"] === ref && mutation?.project_ref === ref && mutation.mutation_id === mutationId ? mutation : null;
17326
- }
17327
- async function fetchMutationStatus(http, ref, mutationId) {
17328
- const response = await http.get(mutationStatusPath(ref, mutationId), {
17329
- maxResponseBytes: MAX_STATUS_RESPONSE_BYTES
17175
+ project_ref: input.projectRef,
17176
+ deployment_id: input.deploymentId,
17177
+ active_release_id: input.releaseId,
17178
+ activation_id: mutationId,
17179
+ release: readback.release
17330
17180
  });
17331
- if (!response.ok) {
17332
- return { kind: "unavailable", httpStatus: response.transportError ? null : response.status };
17181
+ }
17182
+ async function activationReadback(http, input, mutationId, activationStatus) {
17183
+ const mutationRead = await http.get(`/v1/projects/${encodeURIComponent(input.projectRef)}/mutations/${mutationId}`, { maxJsonBytes: RESPONSE_MAX_BYTES });
17184
+ const mutationEnvelope = mutationRead.data;
17185
+ const mutation = mutationRead.ok && mutationRead.status === 200 ? publicMutationReceipt(mutationRead.data, {
17186
+ projectRef: input.projectRef,
17187
+ mutationId,
17188
+ resourceKey: activationResourceKey(input.deploymentId),
17189
+ requestFingerprint: activationFingerprint({ ...input, mutationId })
17190
+ }) : null;
17191
+ if (!mutation || mutationEnvelope?.project_ref !== input.projectRef || mutation.operation !== "frontend.release.activate" || mutation.status !== "succeeded" || mutation.responseStatus !== 200 || mutation.failureCode !== null) {
17192
+ return releaseFailure("frontend.activate_release", "OUTCOME_UNKNOWN", activationStatus);
17333
17193
  }
17334
- const mutation = mutationStatusResponse(ref, mutationId, response.data);
17335
- return mutation ? { kind: "available", mutation } : { kind: "invalid" };
17194
+ const readback = await activeReleaseReadback(http, { ...input, mutationId });
17195
+ if (!readback.release) {
17196
+ return releaseFailure("frontend.activate_release", "OUTCOME_UNKNOWN", activationStatus);
17197
+ }
17198
+ return toolResponse({
17199
+ project_ref: input.projectRef,
17200
+ deployment_id: input.deploymentId,
17201
+ active_release_id: input.releaseId,
17202
+ activation_id: mutationId,
17203
+ release: readback.release
17204
+ });
17205
+ }
17206
+
17207
+ // src/shared/tools/frontend-tools.ts
17208
+ function registerFrontendTools(server, http) {
17209
+ registerTool(server, "frontend", `Frontend hosting and immutable prebuilt releases. Supports: static, react, vue, svelte, sveltekit, sveltekit-static, nextjs, nuxt, astro.
17210
+ 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.`, {
17211
+ action: withDescription(stringEnum2([
17212
+ "list",
17213
+ "get",
17214
+ "create",
17215
+ "update",
17216
+ "delete",
17217
+ "build_logs",
17218
+ "add_domain",
17219
+ "remove_domain",
17220
+ "set_env",
17221
+ "list_frameworks",
17222
+ "list_records",
17223
+ "list_releases",
17224
+ "get_active_release",
17225
+ "get_release",
17226
+ "rollback"
17227
+ ]), "Action"),
17228
+ ref: optional(String2(), "Project ref"),
17229
+ id: optional(String2(), "Deployment ID"),
17230
+ name: optional(String2(), "[create] Deployment name"),
17231
+ framework: optional(String2(), "[create] Framework (static|react|vue|svelte|sveltekit|sveltekit-static|nextjs|nuxt|astro)"),
17232
+ domain: optional(String2(), "[create/update/add_domain/remove_domain] Custom domain"),
17233
+ build_command: optional(String2(), "[create/update] Build command override"),
17234
+ output_dir: optional(String2(), "[create/update] Output directory override"),
17235
+ install_command: optional(String2(), "[create/update] Install command override"),
17236
+ node_version: optional(String2(), "[create/update] Node.js version"),
17237
+ health_check_path: optional(String2(), "[create/update] SSR readiness path (default: /)"),
17238
+ env_vars: optional(Record(String2(), String2()), "[create/update/set_env] Environment variables"),
17239
+ release_id: optional(String2(), "[get_release] SHA-256 release ID; [rollback] optional, defaults to journal-verified previous release"),
17240
+ cursor: optional(String2(), "[list_releases] Last release SHA-256 cursor"),
17241
+ limit: optional(Number2(), "[list_releases] Page size, 1-100 (default 50)")
17242
+ }, async (args) => {
17243
+ const {
17244
+ action,
17245
+ ref,
17246
+ id,
17247
+ name,
17248
+ framework,
17249
+ domain,
17250
+ build_command,
17251
+ output_dir,
17252
+ install_command,
17253
+ node_version,
17254
+ health_check_path,
17255
+ env_vars,
17256
+ release_id,
17257
+ cursor,
17258
+ limit
17259
+ } = args;
17260
+ const need = (field, value) => {
17261
+ if (value === undefined || value === null || value === "")
17262
+ throw new Error(`'${field}' required for '${action}'`);
17263
+ };
17264
+ const ok = (res) => res.ok ? JSON.stringify(res.data, null, 2) : `❌ Failed (${res.status}): ${JSON.stringify(res.data)}`;
17265
+ let text;
17266
+ switch (action) {
17267
+ case "list":
17268
+ need("ref", ref);
17269
+ text = ok(await http.get(`/v1/projects/${ref}/frontend/deployments`));
17270
+ break;
17271
+ case "get":
17272
+ need("ref", ref);
17273
+ need("id", id);
17274
+ text = ok(await http.get(`/v1/projects/${ref}/frontend/deployments/${id}`));
17275
+ break;
17276
+ case "create":
17277
+ need("ref", ref);
17278
+ need("name", name);
17279
+ need("framework", framework);
17280
+ text = ok(await http.post(`/v1/projects/${ref}/frontend/deployments`, {
17281
+ name,
17282
+ framework,
17283
+ domain,
17284
+ build_command,
17285
+ output_dir,
17286
+ install_command,
17287
+ node_version,
17288
+ health_check_path,
17289
+ env_vars
17290
+ }));
17291
+ break;
17292
+ case "update":
17293
+ need("ref", ref);
17294
+ need("id", id);
17295
+ text = ok(await http.patch(`/v1/projects/${ref}/frontend/deployments/${id}`, {
17296
+ name,
17297
+ domain,
17298
+ build_command,
17299
+ output_dir,
17300
+ install_command,
17301
+ node_version,
17302
+ health_check_path,
17303
+ env_vars
17304
+ }));
17305
+ break;
17306
+ case "delete":
17307
+ need("ref", ref);
17308
+ need("id", id);
17309
+ text = (await http.delete(`/v1/projects/${ref}/frontend/deployments/${id}`)).ok ? `✅ Deleted` : `❌ Failed`;
17310
+ break;
17311
+ case "build_logs":
17312
+ need("ref", ref);
17313
+ need("id", id);
17314
+ const lr = await http.get(`/v1/projects/${ref}/frontend/deployments/${id}/logs`);
17315
+ text = lr.ok ? typeof lr.data === "object" && lr.data !== null && "logs" in lr.data && typeof lr.data.logs === "string" ? lr.data.logs : "(no logs)" : `❌ Failed (${lr.status})`;
17316
+ break;
17317
+ case "add_domain":
17318
+ need("ref", ref);
17319
+ need("id", id);
17320
+ need("domain", domain);
17321
+ text = (await http.post(`/v1/projects/${ref}/frontend/deployments/${id}/domains`, { domain })).ok ? `✅ Domain ${domain} added` : `❌ Failed`;
17322
+ break;
17323
+ case "remove_domain":
17324
+ need("ref", ref);
17325
+ need("id", id);
17326
+ need("domain", domain);
17327
+ text = (await http.delete(`/v1/projects/${ref}/frontend/deployments/${id}/domains/${domain}`)).ok ? `✅ Domain ${domain} removed` : `❌ Failed`;
17328
+ break;
17329
+ case "set_env":
17330
+ need("ref", ref);
17331
+ need("id", id);
17332
+ need("env_vars", env_vars);
17333
+ text = (await http.put(`/v1/projects/${ref}/frontend/deployments/${id}/env`, { env_vars })).ok ? `✅ Set ${Object.keys(env_vars).length} env vars` : `❌ Failed`;
17334
+ break;
17335
+ case "list_frameworks":
17336
+ text = ok(await http.get("/v1/projects/_/frontend/frameworks"));
17337
+ break;
17338
+ case "list_records":
17339
+ need("ref", ref);
17340
+ need("id", id);
17341
+ text = ok(await http.get(`/v1/projects/${ref}/frontend/deployments/${id}/records`));
17342
+ break;
17343
+ case "list_releases":
17344
+ need("ref", ref);
17345
+ need("id", id);
17346
+ return listFrontendReleases(http, ref, id, cursor, limit);
17347
+ case "get_release":
17348
+ need("ref", ref);
17349
+ need("id", id);
17350
+ need("release_id", release_id);
17351
+ return getFrontendRelease(http, ref, id, release_id);
17352
+ case "get_active_release":
17353
+ need("ref", ref);
17354
+ need("id", id);
17355
+ return getActiveFrontendRelease(http, ref, id);
17356
+ case "rollback":
17357
+ need("ref", ref);
17358
+ need("id", id);
17359
+ return rollbackFrontendRelease(http, ref, id, release_id);
17360
+ default:
17361
+ text = `❌ Unknown action`;
17362
+ }
17363
+ return { content: [{ type: "text", text }] };
17364
+ });
17336
17365
  }
17337
17366
 
17367
+ // src/shared/tools/application-schemas.ts
17368
+ var ApplicationIdSchema2 = ApplicationIdSchema;
17369
+ var ApplicationReleaseIdSchema2 = ApplicationReleaseIdSchema;
17370
+ var ApplicationReleaseRecordSchema2 = ApplicationReleaseRecordSchema;
17371
+ var ApplicationConfigurationIdSchema2 = ApplicationConfigurationIdSchema;
17372
+ var ApplicationActivationIdSchema2 = ApplicationActivationIdSchema;
17373
+ var ApplicationActivationWriteSchema2 = ApplicationActivationWriteSchema;
17374
+ var ApplicationActivationResultSchema2 = ApplicationActivationResultSchema;
17375
+ var ApplicationActivationRetirementResultSchema2 = ApplicationActivationRetirementResultSchema;
17376
+ var ApplicationRollbackSnapshotSchema2 = ApplicationRollbackSnapshotSchema;
17377
+ var ApplicationActivationHistoryCursorSchema2 = ApplicationActivationHistoryCursorSchema;
17378
+ var ApplicationPromotionPlanSchema2 = ApplicationPromotionPlanSchema;
17379
+ var ApplicationDeployRequestSchema2 = ApplicationDeployRequestSchema;
17380
+ var ApplicationDeployWebReleaseSchema2 = ApplicationDeployWebReleaseSchema;
17381
+
17338
17382
  // src/shared/tools/application-workflow-schemas.ts
17339
17383
  var closed9 = { additionalProperties: false };
17340
17384
  var ref5 = String2({ pattern: "^[A-Za-z0-9_-]{1,20}$" });
@@ -17545,13 +17589,13 @@ var APPLICATION_PREVIEW_FIELDS = {
17545
17589
  preview_id: optional(previewIdSchema, "[get_preview/reconcile_preview/cleanup_preview] Preview receipt ID; create/plan may pin a stable ID"),
17546
17590
  branch_ref: optional(branchRefSchema, "[get_preview_plan] Proposed branch ref; create assigns its own"),
17547
17591
  branch_name: optional(String2({ minLength: 1, maxLength: 80 }), "[create_preview] Branch display name"),
17548
- data_mode: optional(stringEnum(["schema_only", "full_clone"]), "[get_preview_plan/create_preview] Default schema_only; full_clone copies rows"),
17592
+ data_mode: optional(stringEnum2(["schema_only", "full_clone"]), "[get_preview_plan/create_preview] Default schema_only; full_clone copies rows"),
17549
17593
  ttl_seconds: optional(ttlSecondsSchema, "[get_preview_plan/create_preview] Lifetime from 300 to 604800 seconds; creation defaults to the platform TTL"),
17550
17594
  wait: optional(Boolean2(), "[create_preview/get_preview] Wait for verified readiness of this preview only"),
17551
17595
  timeout_seconds: optional(waitSecondsSchema, "[create_preview/get_preview] Observation budget with wait, from 1 to 3600 seconds (default 300)")
17552
17596
  };
17553
17597
  var strict = { additionalProperties: false };
17554
- var phase2 = stringEnum(["pending", "ready", "failed", "cleaned"]);
17598
+ var phase2 = stringEnum2(["pending", "ready", "failed", "cleaned"]);
17555
17599
  var check5 = String2({ pattern: "^[a-z][a-z0-9_]{0,63}$" });
17556
17600
  var checks2 = _Array_(check5, { maxItems: 64, uniqueItems: true });
17557
17601
  var revision = Union([ApplicationConfigurationIdSchema2, Null()]);
@@ -17563,10 +17607,10 @@ var receiptSchema = _Object_({
17563
17607
  environment_id: ApplicationIdSchema2,
17564
17608
  release_id: ApplicationReleaseIdSchema2,
17565
17609
  expires_at: Optional(Union([timestampSchema, Null()])),
17566
- status: stringEnum(["planned", "provisioning", "ready", "failed", "cleaned"]),
17610
+ status: stringEnum2(["planned", "provisioning", "ready", "failed", "cleaned"]),
17567
17611
  resources: _Object_({
17568
17612
  build_artifact: _Object_({ status: phase2, release_id: ApplicationReleaseIdSchema2 }, strict),
17569
- database_branch: _Object_({ status: phase2, branch_ref: branchRefSchema, data_mode: stringEnum(["schema_only", "full_clone"]) }, strict),
17613
+ database_branch: _Object_({ status: phase2, branch_ref: branchRefSchema, data_mode: stringEnum2(["schema_only", "full_clone"]) }, strict),
17570
17614
  queue_namespace: _Object_({ status: phase2, namespace: String2({ minLength: 1, maxLength: 128 }) }, strict),
17571
17615
  storage_namespace: _Object_({ status: phase2, namespace: branchRefSchema }, strict),
17572
17616
  test_secret: _Object_({ status: phase2, name: String2({ pattern: "^[A-Z0-9_-]{1,128}$" }), value_issued: Literal(false) }, strict),
@@ -17874,7 +17918,7 @@ async function applicationPreviewAction(http, args, project) {
17874
17918
 
17875
17919
  // src/shared/tools/application-tools.ts
17876
17920
  var APPLICATION_TOOL_SCHEMA = {
17877
- action: withDescription(stringEnum([
17921
+ action: withDescription(stringEnum2([
17878
17922
  "list_releases",
17879
17923
  "get_release",
17880
17924
  "upload_release",
@@ -19118,7 +19162,7 @@ function registerUserProjectCliTools(server, http, options = {}) {
19118
19162
  const { projectRef } = options;
19119
19163
  registerTool(server, "project", `Project-scoped inspection and developer operations.
19120
19164
  Actions: list (Admin guidance), get, endpoints, pause, restore, health, logs, api_keys, settings, tasks, task_detail, task_cancel, task_retry, task_stats, dlq, background_settings, update_background_settings`, {
19121
- action: withDescription(stringEnum([
19165
+ action: withDescription(stringEnum2([
19122
19166
  "list",
19123
19167
  "get",
19124
19168
  "endpoints",
@@ -19138,7 +19182,7 @@ Actions: list (Admin guidance), get, endpoints, pause, restore, health, logs, ap
19138
19182
  "update_background_settings"
19139
19183
  ]), "Action to perform"),
19140
19184
  ref: optional(String2(), projectRef ? "Optional override when not auto-linked" : "Project ref"),
19141
- log_type: optional(stringEnum(["all", "auth", "database", "api"]), "[logs] Filter by service"),
19185
+ log_type: optional(stringEnum2(["all", "auth", "database", "api"]), "[logs] Filter by service"),
19142
19186
  task_id: optional(String2(), "[task_detail/task_cancel/task_retry] Task ID"),
19143
19187
  limit: optional(Number2(), "[tasks/dlq] Max items to return"),
19144
19188
  concurrency: optional(Number2(), "[update_background_settings] Max concurrent background tasks"),
@@ -19339,7 +19383,7 @@ function registerQueueTools(server, http, options = {}) {
19339
19383
  const { projectRef } = options;
19340
19384
  registerTool(server, "queue", `Message queue operations for task-based messaging.
19341
19385
  Actions: list, stats, list_messages, dlq, get_message, send, receive, ack, release, fail, retry, delete_message, get_settings, update_settings`, {
19342
- action: withDescription(stringEnum([
19386
+ action: withDescription(stringEnum2([
19343
19387
  "list",
19344
19388
  "stats",
19345
19389
  "list_messages",
@@ -19579,7 +19623,7 @@ function registerGatewayTools(server, http, options = {}) {
19579
19623
  const { projectRef } = options;
19580
19624
  registerTool(server, "gateway", `Gateway / Caddy 配置(通过 JSON Admin API 注入)。要求 admin 权限。
19581
19625
  Actions: routes, upsert_route, update_route, delete_route, config, get_certificate, update_certificate, issue_certificate, deploy_certificate, rebuild, custom_hostname, set_custom_hostname, delete_custom_hostname, verify_custom_hostname`, {
19582
- action: withDescription(stringEnum([
19626
+ action: withDescription(stringEnum2([
19583
19627
  "routes",
19584
19628
  "upsert_route",
19585
19629
  "update_route",
@@ -19600,11 +19644,11 @@ Actions: routes, upsert_route, update_route, delete_route, config, get_certifica
19600
19644
  hosts: withDescription(stringArray, "[upsert_route/update_route] 主机名列表,逗号分隔或 JSON 数组(1-20)"),
19601
19645
  paths: withDescription(stringArray, "[upsert_route/update_route] 路径列表,逗号分隔或 JSON 数组(1-32)"),
19602
19646
  upstream: optional(String2(), "[upsert_route/update_route] 反代上游 host:port 或 http(s)://host[:port]"),
19603
- managed_upstream: optional(stringEnum(["edge-functions"]), "[upsert_route/update_route] 托管上游(同步 Edge Function)"),
19647
+ managed_upstream: optional(stringEnum2(["edge-functions"]), "[upsert_route/update_route] 托管上游(同步 Edge Function)"),
19604
19648
  upstream_tls_insecure_skip_verify: optional(Boolean2(), "[upsert_route/update_route] 上游 TLS 跳过校验"),
19605
19649
  static_root: optional(String2(), "[upsert_route/update_route] 静态站点根目录"),
19606
19650
  spa: optional(Boolean2(), "[upsert_route/update_route] 是否启用 SPA 单页回退"),
19607
- protocol: optional(stringEnum(["http", "https"]), "[upsert_route/update_route] 可选请求协议匹配"),
19651
+ protocol: optional(stringEnum2(["http", "https"]), "[upsert_route/update_route] 可选请求协议匹配"),
19608
19652
  redirect_to: optional(String2(), "[upsert_route/update_route] 带固定 host 的绝对 http(s) 目标,可在末尾使用 {http.request.uri}"),
19609
19653
  redirect_status: withDescription(redirectStatus, "[upsert_route/update_route] 重定向状态码,默认 308"),
19610
19654
  rewrite_uri: optional(String2(), "[upsert_route/update_route] 重写 URI(以 / 开头)"),
@@ -19613,12 +19657,12 @@ Actions: routes, upsert_route, update_route, delete_route, config, get_certifica
19613
19657
  cors: withDescription(stringArray, "[upsert_route/update_route] 额外 CORS 源,逗号分隔"),
19614
19658
  priority: optional(Number2(), "[upsert_route/update_route] 路由优先级"),
19615
19659
  enabled: optional(Boolean2(), "[upsert_route/update_route] 是否启用"),
19616
- rate_limit_tier: optional(stringEnum(["free", "pro", "enterprise"]), "[config] 限流档位"),
19660
+ rate_limit_tier: optional(stringEnum2(["free", "pro", "enterprise"]), "[config] 限流档位"),
19617
19661
  cors_origins: optional(String2(), "[config] CORS 源(逗号分隔)"),
19618
19662
  jwt_enabled: optional(Boolean2(), "[config] 是否启用 JWT"),
19619
19663
  jwt_secret: optional(String2(), "[config] JWT 密钥"),
19620
- cert_mode: optional(stringEnum(["lego", "manual"]), "[update_certificate] 证书模式"),
19621
- challenge: optional(stringEnum(["dns-01", "http-01"]), "[update_certificate/issue_certificate] ACME challenge"),
19664
+ cert_mode: optional(stringEnum2(["lego", "manual"]), "[update_certificate] 证书模式"),
19665
+ challenge: optional(stringEnum2(["dns-01", "http-01"]), "[update_certificate/issue_certificate] ACME challenge"),
19622
19666
  email: optional(String2(), "[update_certificate/issue_certificate] ACME 邮箱"),
19623
19667
  dns_provider: optional(String2(), "[update_certificate/issue_certificate] DNS 提供商"),
19624
19668
  dns_env: withDescription(stringArray, "[update_certificate/issue_certificate] DNS 环境变量 KEY=VALUE 列表"),
@@ -20132,7 +20176,7 @@ function formatPromotionResult(response) {
20132
20176
  }
20133
20177
  function registerBranchTools(server, http, options = {}) {
20134
20178
  registerTool(server, "branch", "Preview branch lifecycle and safe migration promotion. Whole-database replacement is intentionally not exposed by this project CLI.", {
20135
- action: withDescription(stringEnum([
20179
+ action: withDescription(stringEnum2([
20136
20180
  "list",
20137
20181
  "create",
20138
20182
  "delete",
@@ -20142,7 +20186,7 @@ function registerBranchTools(server, http, options = {}) {
20142
20186
  ref: optional(String2(), "[*] Optional parent project override"),
20143
20187
  branch_ref: optional(String2(), "[delete/promotion_plan/promote] Preview branch ref"),
20144
20188
  name: optional(String2(), "[create] Branch name"),
20145
- data_mode: optional(stringEnum(["schema_only", "full_clone"]), "[create] Preview data mode (default: schema_only)"),
20189
+ data_mode: optional(stringEnum2(["schema_only", "full_clone"]), "[create] Preview data mode (default: schema_only)"),
20146
20190
  plan_checksum: optional(String2(), "[promote] Reviewed plan checksum from promotion_plan"),
20147
20191
  confirm_destructive: optional(Boolean2(), "[promote] Confirm reviewed destructive migrations"),
20148
20192
  json: optional(Boolean2(), "[promotion_plan/promote] Return machine-readable JSON")
@@ -20737,7 +20781,7 @@ function registerSupabaseCliTools(server, options = {}) {
20737
20781
  executeOfficialCli: options.executeOfficialCli || ((request) => executeOfficialSupabaseCli(request, environment))
20738
20782
  };
20739
20783
  registerTool(server, "supabase", "Controlled adapter for the official open-source Supabase CLI. Remote push stays on the SupaCloud Management API and requires explicit Management credentials.", {
20740
- action: withDescription(stringEnum([
20784
+ action: withDescription(stringEnum2([
20741
20785
  "version",
20742
20786
  "init",
20743
20787
  "migration_new",
@@ -20770,16 +20814,16 @@ function registerSupabaseCliTools(server, options = {}) {
20770
20814
  experimental: optional(Boolean2(), "[db_schema_declarative_*] Enable the experimental pg-delta workflow"),
20771
20815
  strict_coverage: optional(Boolean2(), "[db_schema_declarative_*] Fail on unmanaged schema objects"),
20772
20816
  overwrite: optional(Boolean2(), "[db_schema_declarative_generate] Replace existing schema files"),
20773
- runtime: optional(stringEnum(["docker", "podman", "native"]), "[stack_*] Local stack runtime"),
20817
+ runtime: optional(stringEnum2(["docker", "podman", "native"]), "[stack_*] Local stack runtime"),
20774
20818
  eager: optional(Boolean2(), "[stack_start] Start all services before returning"),
20775
- preparation: optional(stringEnum(["background", "on-demand"]), "[stack_start] Download service archives in the background or on demand"),
20819
+ preparation: optional(stringEnum2(["background", "on-demand"]), "[stack_start] Download service archives in the background or on demand"),
20776
20820
  force: optional(Boolean2(), "[config_pull] Permit overwriting dirty tracked config"),
20777
20821
  yes: optional(Boolean2(), "[config_pull/stack_destroy] Confirm a write or destructive action"),
20778
20822
  confirm_destroy: optional(Boolean2(), "[stack_destroy] Explicitly confirm permanent local stack destruction"),
20779
20823
  no_seed: optional(Boolean2(), "[db_reset] Skip seed scripts"),
20780
- diff_engine: optional(stringEnum(["migra", "pg-delta", "pgadmin", "pg-schema"]), "[db_diff/db_pull] Official CLI diff engine"),
20781
- dump_mode: optional(stringEnum(["schema", "data", "roles"]), "[db_dump] Dump schema (default), data, or roles"),
20782
- language: optional(stringEnum(["typescript", "go", "swift", "python"]), "[gen_types] Output language (default: typescript)")
20824
+ diff_engine: optional(stringEnum2(["migra", "pg-delta", "pgadmin", "pg-schema"]), "[db_diff/db_pull] Official CLI diff engine"),
20825
+ dump_mode: optional(stringEnum2(["schema", "data", "roles"]), "[db_dump] Dump schema (default), data, or roles"),
20826
+ language: optional(stringEnum2(["typescript", "go", "swift", "python"]), "[gen_types] Output language (default: typescript)")
20783
20827
  }, (request) => executeSupabaseAction(request, runtime));
20784
20828
  }
20785
20829
 
@@ -20972,7 +21016,7 @@ function registerLiteCliTools(server, options = {}) {
20972
21016
  const fallbackWorkdir = options.currentWorkingDirectory || process.cwd();
20973
21017
  const execute = options.executeLiteCli || ((request) => executeLiteCli(request, environment, fallbackWorkdir));
20974
21018
  registerTool(server, "lite", "Controlled adapter for the local SupaCloud Lite CLI. Lite actions are local-only and never use the Management API or official Supabase CLI.", {
20975
- action: withDescription(stringEnum([
21019
+ action: withDescription(stringEnum2([
20976
21020
  "version",
20977
21021
  "start",
20978
21022
  "migrate",
@@ -20993,14 +21037,14 @@ function registerLiteCliTools(server, options = {}) {
20993
21037
  state_dir: optional(String2(), "[*] Lite state root"),
20994
21038
  data_dir: optional(String2(), "[*] PGlite/native data directory"),
20995
21039
  storage_dir: optional(String2(), "[*] Object storage directory"),
20996
- storage_backend: optional(stringEnum(["fs", "memory", "s3"]), "[*] Storage backend"),
21040
+ storage_backend: optional(stringEnum2(["fs", "memory", "s3"]), "[*] Storage backend"),
20997
21041
  s3_prefix: optional(String2(), "[*] S3 object key prefix"),
20998
- engine: optional(stringEnum(["pglite", "native"]), "[*] Database engine"),
21042
+ engine: optional(stringEnum2(["pglite", "native"]), "[*] Database engine"),
20999
21043
  host: optional(String2(), "[start] Listen host"),
21000
21044
  port: optional(Number2(), "[start] Listen port"),
21001
21045
  api_url: optional(String2(), "[start] Public API URL"),
21002
21046
  site_url: optional(String2(), "[start] Auth site URL"),
21003
- replication_profile: optional(stringEnum(["powersync"]), "[start/doctor] Replication profile"),
21047
+ replication_profile: optional(stringEnum2(["powersync"]), "[start/doctor] Replication profile"),
21004
21048
  replication_host: optional(String2(), "[start] Replication listener host"),
21005
21049
  replication_port: optional(Number2(), "[start] Replication listener port"),
21006
21050
  replication_allow_cidrs: optional(String2(), "[start] Replication client CIDRs"),
@@ -21181,7 +21225,7 @@ function textResponse(payload) {
21181
21225
  }
21182
21226
  function registerAiTools(server) {
21183
21227
  registerTool(server, "ai", "Install or inspect the bundled SupaCloud CLI AI skill. Actions: show_skill, install_skill", {
21184
- action: stringEnum(["show_skill", "install_skill"]),
21228
+ action: stringEnum2(["show_skill", "install_skill"]),
21185
21229
  target: optional(String2(), "[install_skill] Skill root directory; defaults to $CODEX_HOME/skills or ~/.codex/skills"),
21186
21230
  dry_run: optional(Boolean2(), "[install_skill] Preview the installation without writing files"),
21187
21231
  force: optional(Boolean2(), "[install_skill] Back up and replace different existing skill content")
@@ -24287,6 +24331,77 @@ function checkAppDatabaseSources(projectRoot) {
24287
24331
  }
24288
24332
  }
24289
24333
 
24334
+ // src/shared/tools/application-tools.ts
24335
+ var APPLICATION_TOOL_SCHEMA2 = {
24336
+ action: withDescription(stringEnum2([
24337
+ "list_releases",
24338
+ "get_release",
24339
+ "upload_release",
24340
+ "get_runtime",
24341
+ "get_deployment_evidence",
24342
+ "get_configuration",
24343
+ "put_configuration",
24344
+ "get_deploy_plan",
24345
+ "deploy_release",
24346
+ "deploy_application",
24347
+ "get_release_transfer_plan",
24348
+ "transfer_release",
24349
+ "get_promotion_plan",
24350
+ "promote_application",
24351
+ "get_promotion_status",
24352
+ "reconcile_promotion",
24353
+ "get_preview_plan",
24354
+ "list_previews",
24355
+ "create_preview",
24356
+ "get_preview",
24357
+ "cleanup_preview",
24358
+ "activate_release",
24359
+ "reconcile_activation",
24360
+ "retire_activation",
24361
+ "rollback_release",
24362
+ "get_rollback_snapshot",
24363
+ "get_history",
24364
+ ...APPLICATION_PREVIEW_ACTIONS,
24365
+ "logs"
24366
+ ]), "Action"),
24367
+ ref: withDescription(String2(), "Project ref"),
24368
+ id: withDescription(ApplicationIdSchema2, "Application ID"),
24369
+ environment_id: optional(withDescription(ApplicationIdSchema2, "[get_runtime/get_configuration/put_configuration/get_deploy_plan/deploy_release/get_rollback_snapshot/rollback_release/get_history] Environment ID")),
24370
+ configuration_id: optional(ApplicationConfigurationIdSchema2, "[get_configuration/get_deploy_plan/deploy_release/activate_release/rollback_release/get_history] Immutable revision; required for deployment"),
24371
+ activation_id: optional(ApplicationActivationIdSchema2, "[deploy_release/activate_release/reconcile_activation/retire_activation/rollback_release/get_history] Stable activation ID; generated for automatic deployment/rollback if omitted"),
24372
+ expected_activation_id: optional(Union([ApplicationActivationIdSchema2, Null(), Literal("absent")]), "[deploy_release/activate_release/rollback_release] Current activation ID, or absent for first activation; selected from verified plan for automatic deployment"),
24373
+ configuration_path: optional(String2(), "[put_configuration] Local configuration write JSON including revision and expected revision"),
24374
+ configuration_write: optional(Unknown(), "[deploy_application] Parsed immutable configuration write"),
24375
+ manifest_path: optional(String2(), "[upload_release] Local delivery.manifest.json"),
24376
+ web_release: optional(ApplicationDeployWebReleaseSchema2, "[deploy_application] Immutable staged Web release uploaded by the deploy profile"),
24377
+ release_id: optional(ApplicationReleaseIdSchema2, "[get_release/get_deploy_plan/deploy_release/activate_release/reconcile_activation/rollback_release] Immutable application release ID; platform selects previous for default rollback"),
24378
+ source_ref: optional(String2({ pattern: "^[A-Za-z0-9_-]{1,20}$" }), "[get_release_transfer_plan/transfer_release] Source project ref"),
24379
+ source_release_id: optional(ApplicationReleaseIdSchema2, "[get_release_transfer_plan/transfer_release] Source immutable release ID"),
24380
+ source_environment_id: optional(ApplicationIdSchema2, "[get_promotion_plan] Source environment"),
24381
+ mutation_id: optional(ApplicationActivationIdSchema2, "[deploy_application/promote_application/get_promotion_status/reconcile_promotion] Stable mutation ID"),
24382
+ approved_migration_digest: optional(ApplicationReleaseIdSchema2, "[deploy_application/promote_application] Exact reviewed migration execution-plan digest"),
24383
+ plan: optional(ApplicationPromotionPlanSchema2, "[promote_application] Immutable plan returned by get_promotion_plan"),
24384
+ cursor: optional(Union([ApplicationReleaseIdSchema2, ApplicationActivationHistoryCursorSchema2]), "[list_releases/get_history] Returned page cursor"),
24385
+ limit: optional(Integer({ minimum: 1, maximum: 100 }), "[list_releases/get_history] Page size, default 50/20"),
24386
+ offset: optional(Integer({ minimum: 0, maximum: 1e6 }), "[logs] Result offset"),
24387
+ service: optional(String2(), "[logs] Application target/service filter"),
24388
+ search: optional(String2(), "[logs] Full-text log filter"),
24389
+ start: optional(String2(), "[logs] ISO start timestamp"),
24390
+ end: optional(String2(), "[logs] ISO end timestamp"),
24391
+ ...APPLICATION_PREVIEW_FIELDS
24392
+ };
24393
+ var responseSchema2 = _Object_({
24394
+ project_ref: String2(),
24395
+ application_id: ApplicationIdSchema2,
24396
+ release: ApplicationReleaseRecordSchema2
24397
+ });
24398
+ var inventorySchema2 = _Object_({
24399
+ project_ref: String2(),
24400
+ application_id: ApplicationIdSchema2,
24401
+ releases: _Array_(ApplicationReleaseRecordSchema2, { maxItems: 100 }),
24402
+ next_cursor: Union([ApplicationReleaseIdSchema2, Null()])
24403
+ });
24404
+
24290
24405
  // src/shared/tools/app-resource.ts
24291
24406
  function resourceScaffold(name, className) {
24292
24407
  return {
@@ -25131,7 +25246,7 @@ var REMOTE_APP_DESCRIPTIONS = {
25131
25246
  cursor: "[history] Returned page cursor",
25132
25247
  limit: "[history] Page size, default 20 (1-100)"
25133
25248
  };
25134
- var { action: _remoteAction, plan: _remotePlan, ...remoteFields } = APPLICATION_TOOL_SCHEMA;
25249
+ var { action: _remoteAction, plan: _remotePlan, ...remoteFields } = APPLICATION_TOOL_SCHEMA2;
25135
25250
  var REMOTE_APP_SCHEMA = Partial(_Object_(remoteFields)).properties;
25136
25251
  for (const [name, schema] of Object.entries(REMOTE_APP_SCHEMA)) {
25137
25252
  const description = REMOTE_APP_DESCRIPTIONS[name];
@@ -26150,7 +26265,7 @@ async function runAppRelease(request, options, delegate) {
26150
26265
  id,
26151
26266
  manifest_path: manifestPath
26152
26267
  };
26153
- validateToolArguments(APPLICATION_TOOL_SCHEMA, uploadArgs);
26268
+ validateToolArguments2(APPLICATION_TOOL_SCHEMA2, uploadArgs);
26154
26269
  const uploaded = await delegate(uploadArgs);
26155
26270
  const uploadReceipt = resultObject(uploaded);
26156
26271
  if (uploaded.isError || !uploadReceipt?.ok) {
@@ -26190,7 +26305,7 @@ async function runAppRelease(request, options, delegate) {
26190
26305
  ...configurationWrite === undefined ? {} : { configuration_write: configurationWrite },
26191
26306
  ...request.web_release === undefined ? {} : { web_release: request.web_release }
26192
26307
  };
26193
- validateToolArguments(APPLICATION_TOOL_SCHEMA, deployArgs);
26308
+ validateToolArguments2(APPLICATION_TOOL_SCHEMA2, deployArgs);
26194
26309
  if (!options.projectDeployment) {
26195
26310
  throw new Error("Application release requires the unified project deployment handler");
26196
26311
  }
@@ -26210,6 +26325,7 @@ async function runAppRelease(request, options, delegate) {
26210
26325
  schema: RELEASE_CONTROL_RESPONSE_SCHEMA,
26211
26326
  ok: true,
26212
26327
  operation: "applications.release",
26328
+ ...deployReceipt.status === "accepted" ? { status: "accepted" } : {},
26213
26329
  project_ref: ref,
26214
26330
  application_id: id,
26215
26331
  environment_id: environmentId,
@@ -26220,16 +26336,17 @@ async function runAppRelease(request, options, delegate) {
26220
26336
  build: buildReceipt ? completed.build : { skipped: true, manifest_path: manifestPath },
26221
26337
  phases: completed,
26222
26338
  no_op: deployReceipt.action === "no-op" || deployReceipt.no_op === true,
26223
- execution_performed: deployReceipt.execution_performed === true,
26339
+ ...deployReceipt.status === "accepted" ? {} : { execution_performed: deployReceipt.execution_performed === true },
26224
26340
  ...typeof deployReceipt.activation_id === "string" ? { activation_id: deployReceipt.activation_id } : {},
26225
26341
  ...deployReceipt.web === undefined ? {} : { web: deployReceipt.web }
26226
26342
  };
26227
26343
  return request.format === "json" || request.json === true ? textResult(JSON.stringify(result, null, 2)) : textResult([
26228
- `${ref}/${id}/${environmentId}: ${result.no_op ? "no-op" : "released"}`,
26344
+ `${ref}/${id}/${environmentId}: ${deployReceipt.status === "accepted" ? "accepted" : result.no_op ? "no-op" : "released"}`,
26229
26345
  `Release: ${releaseId}`,
26230
26346
  `Configuration: ${configurationId}`,
26231
26347
  `Activation: ${result.activation_id ?? "not performed"}`,
26232
26348
  ...result.deployment_id === undefined ? [] : [`Deployment: ${result.deployment_id}`],
26349
+ ...deployReceipt.status === "accepted" && result.deployment_id !== undefined ? [`Status: supacloud deploy --status ${result.deployment_id}`] : [],
26233
26350
  `Build: ${request.skip_build ? "skipped" : "completed"}`
26234
26351
  ].join(`
26235
26352
  `));
@@ -26432,7 +26549,7 @@ async function runPromotion(request, options, delegate) {
26432
26549
  action: "get_promotion_plan",
26433
26550
  ref
26434
26551
  };
26435
- validateToolArguments(APPLICATION_TOOL_SCHEMA, planArgs);
26552
+ validateToolArguments2(APPLICATION_TOOL_SCHEMA2, planArgs);
26436
26553
  const planned = await delegate(planArgs);
26437
26554
  if (planned.isError)
26438
26555
  return planned;
@@ -26484,7 +26601,7 @@ Execution: skipped`);
26484
26601
  configuration_id: plan.target.configuration_id,
26485
26602
  plan
26486
26603
  };
26487
- validateToolArguments(APPLICATION_TOOL_SCHEMA, executeArgs);
26604
+ validateToolArguments2(APPLICATION_TOOL_SCHEMA2, executeArgs);
26488
26605
  const result = await delegate(executeArgs);
26489
26606
  return format === "json" || json ? result : formatPromotionExecution(result);
26490
26607
  }
@@ -26532,7 +26649,7 @@ async function runAppTool(request, options = {}) {
26532
26649
  action,
26533
26650
  ref: request.ref ?? options.projectRef
26534
26651
  };
26535
- validateToolArguments(APPLICATION_TOOL_SCHEMA, args);
26652
+ validateToolArguments2(APPLICATION_TOOL_SCHEMA2, args);
26536
26653
  const result = await delegate(args);
26537
26654
  if (request.action === "history" && format !== "json" && json !== true) {
26538
26655
  return historyText(result, request, args.ref);
@@ -26594,137 +26711,414 @@ async function runAppTool(request, options = {}) {
26594
26711
  case "fix":
26595
26712
  return runFix(request);
26596
26713
  default:
26597
- return textResult(`Unknown app action: ${String(request.action)}`, true);
26714
+ return textResult(`Unknown app action: ${String(request.action)}`, true);
26715
+ }
26716
+ }
26717
+ function registerAppTools(server, options = {}) {
26718
+ registerTool(server, "app", "Application authoring and delivery. Plan is read-only; build, upload and configure never deploy. Deploy/rollback explicitly activate a release; rollback never downgrades schema.", {
26719
+ ...REMOTE_APP_SCHEMA,
26720
+ action: withDescription(stringEnum2([
26721
+ "init",
26722
+ "generate",
26723
+ "dev",
26724
+ "watch",
26725
+ "verify-plan",
26726
+ "compile",
26727
+ "check",
26728
+ "graph",
26729
+ "explain",
26730
+ "export-tools",
26731
+ "context",
26732
+ "doctor",
26733
+ "fix",
26734
+ "plan",
26735
+ "build",
26736
+ "release",
26737
+ "upload",
26738
+ "configure",
26739
+ "transfer-plan",
26740
+ "transfer",
26741
+ "promote-plan",
26742
+ "deploy",
26743
+ "deploy-plan",
26744
+ "diff",
26745
+ "status",
26746
+ "rollback",
26747
+ "rollback-plan",
26748
+ "history",
26749
+ "reconcile",
26750
+ "retire",
26751
+ "logs",
26752
+ "preview-plan",
26753
+ "preview",
26754
+ "previews",
26755
+ "preview-list",
26756
+ "preview-create",
26757
+ "preview-status",
26758
+ "preview-cleanup",
26759
+ "preview-reconcile",
26760
+ "promote",
26761
+ "promote-status",
26762
+ "promote-reconcile"
26763
+ ]), "App action; promote obtains and verifies its plan before one mutation request"),
26764
+ kind: optional(stringEnum2(["module", "command", "query", "controller", "job", "contract", "resource", "feature"]), "[generate] Scaffold kind"),
26765
+ template: optional(stringEnum2(["minimal", "http", "command", "edge"]), "[init] Minimal application by default; explicit http/command/edge recipes"),
26766
+ name: optional(String2(), "[init/generate] Project or object name"),
26767
+ module: optional(String2(), "[generate] Target feature module (required for command/query/controller)"),
26768
+ permission: optional(String2(), "[generate feature] Explicit approval permission; not inferred from name"),
26769
+ rpc: optional(String2(), "[generate feature] Explicit durable RPC capability; must be registered by the host"),
26770
+ dir: optional(String2(), "[generate] Feature root directory (default: src/features)"),
26771
+ force: optional(Boolean2(), "[generate] Overwrite a single regular file; resource/feature/controller never overwrite"),
26772
+ dry_run: optional(Boolean2(), "[generate] Validate and preview the complete change set without writing"),
26773
+ "dry-run": optional(Boolean2(), "[generate] CLI alias of dry_run"),
26774
+ register_in: optional(String2(), "[generate] Existing parent module file for module/resource/feature; uses the compiler AST editor"),
26775
+ "register-in": optional(String2(), "[generate] CLI alias of register_in"),
26776
+ root: optional(String2(), "[dev/plan/build/release/compile/check] Project directory containing supacloud.config.ts (default: current directory)"),
26777
+ include: optional(String2(), "[dev/compile/check] Comma-separated glob patterns for source files"),
26778
+ out_dir: optional(String2(), "[dev/compile/plan/build/export-tools] Output directory (default: configured outDir)"),
26779
+ strict: optional(Boolean2(), "[dev/compile/check] Promote warnings to errors"),
26780
+ format: optional(stringEnum2(["text", "json"]), "[dev/generate/compile/check/plan/release/deploy-plan/diff/history/promote-plan/graph/export-tools] Output format (default: text)"),
26781
+ json: optional(Boolean2(), "[history/transfer-plan/transfer/promote-plan] Preserve or return the full machine-readable receipt"),
26782
+ profile: optional(stringEnum2(["fast", "integration"]), "[dev] Run dev or dev:integration; integration verifies an explicit loopback database"),
26783
+ once: optional(Boolean2(), "[dev] Validate and report once without watching"),
26784
+ watch: optional(Boolean2(), "[dev] Watch for changes (default: true; --once disables)"),
26785
+ database_url: optional(String2(), "[dev] Explicit loopback integration URL; watch/once only inspect its shape"),
26786
+ "database-url": optional(String2(), "[dev] CLI alias of database_url"),
26787
+ target: optional(String2(), "[explain/context/verify-plan] Provider class name / token name / command name / job name / module name"),
26788
+ fix: optional(String2(), "[fix] Path to a DiagnosticFix JSON file produced by `doctor --format json`"),
26789
+ write: optional(Boolean2(), "[fix] Write the fix to disk (default: preview only)"),
26790
+ skip_build: optional(Boolean2(), "[release] Reuse --manifest_path instead of running the local delivery build")
26791
+ }, (request) => runAppTool(request, options));
26792
+ }
26793
+ var APP_ALIAS_ACTIONS = ["generate", "compile", "check", "graph", "explain", "context", "doctor", "fix"];
26794
+ function registerAppAliases(server) {
26795
+ const schema = {
26796
+ kind: optional(stringEnum2(["module", "command", "query", "controller", "job", "contract", "resource", "feature"]), "[generate] Scaffold kind"),
26797
+ name: optional(String2(), "[generate] Object name"),
26798
+ module: optional(String2(), "[generate] Target feature module"),
26799
+ permission: optional(String2(), "[generate feature] Explicit approval permission; not inferred from name"),
26800
+ rpc: optional(String2(), "[generate feature] Explicit durable RPC capability; must be registered by the host"),
26801
+ dir: optional(String2(), "[generate] Feature root directory (default: src/features)"),
26802
+ force: optional(Boolean2(), "[generate] Overwrite a single regular file; resource/feature/controller never overwrite"),
26803
+ dry_run: optional(Boolean2(), "[generate] Validate and preview the complete change set without writing"),
26804
+ "dry-run": optional(Boolean2(), "[generate] CLI alias of dry_run"),
26805
+ register_in: optional(String2(), "[generate] Existing parent module file for module/resource/feature; uses the compiler AST editor"),
26806
+ "register-in": optional(String2(), "[generate] CLI alias of register_in"),
26807
+ root: optional(String2(), "Project directory containing supacloud.config.ts (default: current directory)"),
26808
+ include: optional(String2(), "[compile/check] Comma-separated glob patterns for source files"),
26809
+ out_dir: optional(String2(), "[compile/export-tools] Output directory (default: <root>/generated)"),
26810
+ strict: optional(Boolean2(), "[compile/check] Promote warnings to errors"),
26811
+ format: optional(stringEnum2(["text", "json"]), "Output format (default: text)"),
26812
+ target: optional(String2(), "[explain/context] Provider class name / token name / command name / job name / module name"),
26813
+ fix: optional(String2(), "[fix] Path to a DiagnosticFix JSON file produced by `doctor --format json`"),
26814
+ write: optional(Boolean2(), "[fix] Write the fix to disk (default: preview only)")
26815
+ };
26816
+ for (const action of APP_ALIAS_ACTIONS) {
26817
+ registerTool(server, action, `Top-level alias of \`app ${action}\`.`, schema, (request) => runAppTool({ ...request, action }));
26818
+ }
26819
+ }
26820
+
26821
+ // src/shared/tools/db-governance-tools.ts
26822
+ import { existsSync as existsSync13 } from "node:fs";
26823
+ import { join as join16, resolve as resolve20 } from "node:path";
26824
+ import {
26825
+ buildDatabaseManifest,
26826
+ explainObject,
26827
+ lintModule,
26828
+ readCatalog,
26829
+ reconcileModule
26830
+ } from "@supacloud/db";
26831
+
26832
+ // src/shared/tools/db-workflow.ts
26833
+ import { createHash as createHash7 } from "node:crypto";
26834
+ import { lstat as lstat4, mkdir as mkdir6, mkdtemp, readFile as readFile5, readdir as readdir6, rename as rename3, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
26835
+ import { tmpdir as tmpdir2 } from "node:os";
26836
+ import { dirname as dirname10, isAbsolute as isAbsolute7, join as join15, relative as relative8, resolve as resolve19 } from "node:path";
26837
+ import {
26838
+ assertApplicationRoleRestricted,
26839
+ readDatabaseRole,
26840
+ renderDatabaseRoleGuardSql,
26841
+ renderReverseSnapshot,
26842
+ reverseDatabase
26843
+ } from "@supacloud/db";
26844
+ import { parseDatabaseSourcesConfig } from "@supacloud/db/source-contracts";
26845
+
26846
+ // src/shared/tools/supabase-cli-tools.ts
26847
+ import { chmodSync as chmodSync2, existsSync as existsSync12, mkdirSync as mkdirSync4, statSync as statSync8, writeFileSync as writeFileSync3 } from "node:fs";
26848
+ import { dirname as dirname9, isAbsolute as isAbsolute6, join as join14, resolve as resolve18 } from "node:path";
26849
+ var SENSITIVE_ENV_KEY2 = /(?:^|_)(?:PASSWORD|PASS|SECRET|TOKEN|KEY|CREDENTIALS?|AUTHORIZATION|AUTH|SESSION|COOKIE|BEARER|DB_URI|DB_URL|DSN|DATABASE_URL|DATABASE_URI|CONNECTION_STRING|CONNECTION_URI)(?:_|$)/i;
26850
+ var VALID_VERSION2 = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
26851
+ var VALID_MIGRATION_NAME2 = /^[A-Za-z0-9][A-Za-z0-9_-]{0,100}$/;
26852
+ var VALID_SCHEMA2 = /^[A-Za-z_][A-Za-z0-9_$]*$/;
26853
+ var VALID_PROJECT_REF2 = /^[A-Za-z0-9_-]{1,64}$/;
26854
+ function isSensitiveEnvironmentKey2(key) {
26855
+ return key.toUpperCase().startsWith("PG") || SENSITIVE_ENV_KEY2.test(key);
26856
+ }
26857
+ function requireMigrationName2(name) {
26858
+ if (!name || !VALID_MIGRATION_NAME2.test(name)) {
26859
+ throw new Error("Invalid migration name; use letters, numbers, underscore, or hyphen");
26860
+ }
26861
+ return name;
26862
+ }
26863
+ function normalizeSchemaList2(schema) {
26864
+ if (!schema)
26865
+ return;
26866
+ const schemas = schema.split(",").map((schemaName) => schemaName.trim()).filter(Boolean);
26867
+ if (!schemas.length || schemas.some((schemaName) => !VALID_SCHEMA2.test(schemaName))) {
26868
+ throw new Error("Invalid schema list");
26869
+ }
26870
+ return schemas.join(",");
26871
+ }
26872
+ function requirePostgresUrl2(databaseUrl) {
26873
+ if (!databaseUrl || /[\r\n\0]/.test(databaseUrl)) {
26874
+ throw new Error("A Postgres database URL is required");
26875
+ }
26876
+ try {
26877
+ const url = new URL(databaseUrl);
26878
+ if (url.protocol !== "postgres:" && url.protocol !== "postgresql:") {
26879
+ throw new Error("unsupported protocol");
26880
+ }
26881
+ } catch {
26882
+ throw new Error("A valid Postgres database URL is required");
26883
+ }
26884
+ return databaseUrl;
26885
+ }
26886
+ function schemaArguments2(schema) {
26887
+ const normalized = normalizeSchemaList2(schema);
26888
+ return normalized ? ["--schema", normalized] : [];
26889
+ }
26890
+ function workdirArguments2(workdir) {
26891
+ if (!workdir)
26892
+ throw new Error("A workdir is required");
26893
+ return ["--workdir", resolve18(workdir)];
26894
+ }
26895
+ function resolveOutputPath2(workdir, file, label) {
26896
+ if (!file || /[\r\n\0]/.test(file))
26897
+ throw new Error(`${label} file is required`);
26898
+ return isAbsolute6(file) ? resolve18(file) : resolve18(workdir, file);
26899
+ }
26900
+ function databaseTargetArguments2(databaseUrl) {
26901
+ return databaseUrl ? ["--db-url", requirePostgresUrl2(databaseUrl)] : ["--local"];
26902
+ }
26903
+ function databaseDiffArguments2(request) {
26904
+ const engineFlags = {
26905
+ migra: "--use-migra",
26906
+ pgadmin: "--use-pgadmin",
26907
+ "pg-schema": "--use-pg-schema",
26908
+ "pg-delta": "--use-pg-delta"
26909
+ };
26910
+ return [
26911
+ "db",
26912
+ "diff",
26913
+ "--local",
26914
+ ...request.name ? ["--file", requireMigrationName2(request.name)] : [],
26915
+ ...schemaArguments2(request.schema),
26916
+ ...request.diff_engine ? [engineFlags[request.diff_engine]] : []
26917
+ ];
26918
+ }
26919
+ function databasePullArguments2(request) {
26920
+ if (request.diff_engine && request.diff_engine !== "migra" && request.diff_engine !== "pg-delta") {
26921
+ throw new Error("db_pull only supports migra or pg-delta diff engines");
26922
+ }
26923
+ return [
26924
+ "db",
26925
+ "pull",
26926
+ ...request.name ? [requireMigrationName2(request.name)] : [],
26927
+ "--db-url",
26928
+ requirePostgresUrl2(request.db_url),
26929
+ ...request.declarative ? ["--declarative"] : [],
26930
+ ...request.diff_engine ? ["--diff-engine", request.diff_engine] : [],
26931
+ ...schemaArguments2(request.schema)
26932
+ ];
26933
+ }
26934
+ function declarativeSyncArguments2(request) {
26935
+ return [
26936
+ "db",
26937
+ "schema",
26938
+ "declarative",
26939
+ "sync",
26940
+ ...request.name ? ["--name", requireMigrationName2(request.name)] : [],
26941
+ ...request.file ? ["--file", requireMigrationName2(request.file)] : [],
26942
+ ...request.apply === true ? ["--apply"] : ["--no-apply"],
26943
+ ...request.experimental === false ? [] : ["--experimental"],
26944
+ ...request.strict_coverage ? ["--strict-coverage"] : [],
26945
+ ...schemaArguments2(request.schema)
26946
+ ];
26947
+ }
26948
+ function declarativeGenerateArguments2(request) {
26949
+ const target = request.db_url ? ["--db-url", requirePostgresUrl2(request.db_url)] : ["--linked"];
26950
+ return [
26951
+ "db",
26952
+ "schema",
26953
+ "declarative",
26954
+ "generate",
26955
+ ...target,
26956
+ ...request.experimental === false ? [] : ["--experimental"],
26957
+ ...request.overwrite ? ["--overwrite"] : [],
26958
+ ...request.strict_coverage ? ["--strict-coverage"] : [],
26959
+ ...schemaArguments2(request.schema)
26960
+ ];
26961
+ }
26962
+ function stackRuntimeArguments2(request) {
26963
+ if (request.runtime && !["docker", "podman", "native"].includes(request.runtime)) {
26964
+ throw new Error("Invalid local stack runtime");
26965
+ }
26966
+ return request.runtime ? ["--runtime", request.runtime] : [];
26967
+ }
26968
+ function stackArguments2(request) {
26969
+ switch (request.action) {
26970
+ case "stack_start":
26971
+ return [
26972
+ "stack",
26973
+ "start",
26974
+ ...stackRuntimeArguments2({ ...request, runtime: request.runtime ?? "docker" }),
26975
+ ...request.eager ? ["--eager"] : [],
26976
+ ...request.preparation ? ["--preparation", request.preparation] : []
26977
+ ];
26978
+ case "stack_prepare":
26979
+ return ["stack", "prepare", ...stackRuntimeArguments2({ ...request, runtime: request.runtime ?? "docker" })];
26980
+ case "stack_status":
26981
+ return ["stack", "status", "--output-format", "json"];
26982
+ case "stack_stop":
26983
+ return ["stack", "stop"];
26984
+ case "stack_destroy":
26985
+ if (request.confirm_destroy !== true || request.yes !== true) {
26986
+ throw new Error("stack_destroy requires confirm_destroy=true and yes=true");
26987
+ }
26988
+ return ["stack", "destroy", "--yes"];
26989
+ default:
26990
+ throw new Error(`Unsupported local stack action: ${request.action}`);
26991
+ }
26992
+ }
26993
+ function databaseDumpArguments2(request, workdir) {
26994
+ return [
26995
+ "db",
26996
+ "dump",
26997
+ "--db-url",
26998
+ requirePostgresUrl2(request.db_url),
26999
+ "--file",
27000
+ resolveOutputPath2(workdir, request.file, "Database dump"),
27001
+ ...request.dump_mode === "data" ? ["--data-only"] : [],
27002
+ ...request.dump_mode === "roles" ? ["--role-only"] : [],
27003
+ ...schemaArguments2(request.schema)
27004
+ ];
27005
+ }
27006
+ function configPullArguments2(request) {
27007
+ if (!request.ref || !VALID_PROJECT_REF2.test(request.ref))
27008
+ throw new Error("Invalid project ref");
27009
+ if (request.dry_run === false && request.yes !== true) {
27010
+ throw new Error("config_pull apply requires yes=true");
27011
+ }
27012
+ return [
27013
+ "config",
27014
+ "pull",
27015
+ ...request.ref ? ["--project-ref", request.ref] : [],
27016
+ ...request.dry_run === true || request.dry_run === undefined ? ["--dry-run"] : [],
27017
+ ...request.force ? ["--force"] : [],
27018
+ ...request.dry_run === false ? ["--yes"] : []
27019
+ ];
27020
+ }
27021
+ function generateTypesArguments2(request, workdir) {
27022
+ resolveOutputPath2(workdir, request.file, "Generated types");
27023
+ return [
27024
+ "gen",
27025
+ "types",
27026
+ ...databaseTargetArguments2(request.db_url),
27027
+ "--lang",
27028
+ request.language || "typescript",
27029
+ ...schemaArguments2(request.schema)
27030
+ ];
27031
+ }
27032
+ function actionArguments2(request, workdir) {
27033
+ switch (request.action) {
27034
+ case "init":
27035
+ if (request.force)
27036
+ throw new Error("init never overwrites an existing project configuration");
27037
+ return ["init"];
27038
+ case "migration_new":
27039
+ return ["migration", "new", requireMigrationName2(request.name)];
27040
+ case "db_diff":
27041
+ return databaseDiffArguments2(request);
27042
+ case "db_reset":
27043
+ return ["db", "reset", "--local", ...request.no_seed ? ["--no-seed"] : [], "--yes"];
27044
+ case "db_pull":
27045
+ return databasePullArguments2(request);
27046
+ case "db_schema_declarative_sync":
27047
+ return declarativeSyncArguments2(request);
27048
+ case "db_schema_declarative_generate":
27049
+ return declarativeGenerateArguments2(request);
27050
+ case "db_dump":
27051
+ return databaseDumpArguments2(request, workdir);
27052
+ case "config_pull":
27053
+ return configPullArguments2(request);
27054
+ case "stack_start":
27055
+ case "stack_prepare":
27056
+ case "stack_status":
27057
+ case "stack_stop":
27058
+ case "stack_destroy":
27059
+ return stackArguments2(request);
27060
+ case "migration_list":
27061
+ return ["migration", "list", ...databaseTargetArguments2(request.db_url)];
27062
+ case "gen_types":
27063
+ return generateTypesArguments2(request, workdir);
27064
+ case "push":
27065
+ throw new Error("Remote push must use the SupaCloud Management API");
27066
+ default:
27067
+ throw new Error(`Unsupported official Supabase CLI action: ${String(request.action)}`);
26598
27068
  }
26599
27069
  }
26600
- function registerAppTools(server, options = {}) {
26601
- registerTool(server, "app", "Application authoring and delivery. Plan is read-only; build, upload and configure never deploy. Deploy/rollback explicitly activate a release; rollback never downgrades schema.", {
26602
- ...REMOTE_APP_SCHEMA,
26603
- action: withDescription(stringEnum([
26604
- "init",
26605
- "generate",
26606
- "dev",
26607
- "watch",
26608
- "verify-plan",
26609
- "compile",
26610
- "check",
26611
- "graph",
26612
- "explain",
26613
- "export-tools",
26614
- "context",
26615
- "doctor",
26616
- "fix",
26617
- "plan",
26618
- "build",
26619
- "release",
26620
- "upload",
26621
- "configure",
26622
- "transfer-plan",
26623
- "transfer",
26624
- "promote-plan",
26625
- "deploy",
26626
- "deploy-plan",
26627
- "diff",
26628
- "status",
26629
- "rollback",
26630
- "rollback-plan",
26631
- "history",
26632
- "reconcile",
26633
- "retire",
26634
- "logs",
26635
- "preview-plan",
26636
- "preview",
26637
- "previews",
26638
- "preview-list",
26639
- "preview-create",
26640
- "preview-status",
26641
- "preview-cleanup",
26642
- "preview-reconcile",
26643
- "promote",
26644
- "promote-status",
26645
- "promote-reconcile"
26646
- ]), "App action; promote obtains and verifies its plan before one mutation request"),
26647
- kind: optional(stringEnum(["module", "command", "query", "controller", "job", "contract", "resource", "feature"]), "[generate] Scaffold kind"),
26648
- template: optional(stringEnum(["minimal", "http", "command", "edge"]), "[init] Minimal application by default; explicit http/command/edge recipes"),
26649
- name: optional(String2(), "[init/generate] Project or object name"),
26650
- module: optional(String2(), "[generate] Target feature module (required for command/query/controller)"),
26651
- permission: optional(String2(), "[generate feature] Explicit approval permission; not inferred from name"),
26652
- rpc: optional(String2(), "[generate feature] Explicit durable RPC capability; must be registered by the host"),
26653
- dir: optional(String2(), "[generate] Feature root directory (default: src/features)"),
26654
- force: optional(Boolean2(), "[generate] Overwrite a single regular file; resource/feature/controller never overwrite"),
26655
- dry_run: optional(Boolean2(), "[generate] Validate and preview the complete change set without writing"),
26656
- "dry-run": optional(Boolean2(), "[generate] CLI alias of dry_run"),
26657
- register_in: optional(String2(), "[generate] Existing parent module file for module/resource/feature; uses the compiler AST editor"),
26658
- "register-in": optional(String2(), "[generate] CLI alias of register_in"),
26659
- root: optional(String2(), "[dev/plan/build/release/compile/check] Project directory containing supacloud.config.ts (default: current directory)"),
26660
- include: optional(String2(), "[dev/compile/check] Comma-separated glob patterns for source files"),
26661
- out_dir: optional(String2(), "[dev/compile/plan/build/export-tools] Output directory (default: configured outDir)"),
26662
- strict: optional(Boolean2(), "[dev/compile/check] Promote warnings to errors"),
26663
- format: optional(stringEnum(["text", "json"]), "[dev/generate/compile/check/plan/release/deploy-plan/diff/history/promote-plan/graph/export-tools] Output format (default: text)"),
26664
- json: optional(Boolean2(), "[history/transfer-plan/transfer/promote-plan] Preserve or return the full machine-readable receipt"),
26665
- profile: optional(stringEnum(["fast", "integration"]), "[dev] Run dev or dev:integration; integration verifies an explicit loopback database"),
26666
- once: optional(Boolean2(), "[dev] Validate and report once without watching"),
26667
- watch: optional(Boolean2(), "[dev] Watch for changes (default: true; --once disables)"),
26668
- database_url: optional(String2(), "[dev] Explicit loopback integration URL; watch/once only inspect its shape"),
26669
- "database-url": optional(String2(), "[dev] CLI alias of database_url"),
26670
- target: optional(String2(), "[explain/context/verify-plan] Provider class name / token name / command name / job name / module name"),
26671
- fix: optional(String2(), "[fix] Path to a DiagnosticFix JSON file produced by `doctor --format json`"),
26672
- write: optional(Boolean2(), "[fix] Write the fix to disk (default: preview only)"),
26673
- skip_build: optional(Boolean2(), "[release] Reuse --manifest_path instead of running the local delivery build")
26674
- }, (request) => runAppTool(request, options));
27070
+ function buildOfficialSupabaseArgs2(request) {
27071
+ if (request.action === "version")
27072
+ return ["--version"];
27073
+ const workdir = request.workdir ? resolve18(request.workdir) : undefined;
27074
+ if (!workdir)
27075
+ throw new Error("A workdir is required");
27076
+ return [...actionArguments2(request, workdir), ...workdirArguments2(workdir)];
26675
27077
  }
26676
- var APP_ALIAS_ACTIONS = ["generate", "compile", "check", "graph", "explain", "context", "doctor", "fix"];
26677
- function registerAppAliases(server) {
26678
- const schema = {
26679
- kind: optional(stringEnum(["module", "command", "query", "controller", "job", "contract", "resource", "feature"]), "[generate] Scaffold kind"),
26680
- name: optional(String2(), "[generate] Object name"),
26681
- module: optional(String2(), "[generate] Target feature module"),
26682
- permission: optional(String2(), "[generate feature] Explicit approval permission; not inferred from name"),
26683
- rpc: optional(String2(), "[generate feature] Explicit durable RPC capability; must be registered by the host"),
26684
- dir: optional(String2(), "[generate] Feature root directory (default: src/features)"),
26685
- force: optional(Boolean2(), "[generate] Overwrite a single regular file; resource/feature/controller never overwrite"),
26686
- dry_run: optional(Boolean2(), "[generate] Validate and preview the complete change set without writing"),
26687
- "dry-run": optional(Boolean2(), "[generate] CLI alias of dry_run"),
26688
- register_in: optional(String2(), "[generate] Existing parent module file for module/resource/feature; uses the compiler AST editor"),
26689
- "register-in": optional(String2(), "[generate] CLI alias of register_in"),
26690
- root: optional(String2(), "Project directory containing supacloud.config.ts (default: current directory)"),
26691
- include: optional(String2(), "[compile/check] Comma-separated glob patterns for source files"),
26692
- out_dir: optional(String2(), "[compile/export-tools] Output directory (default: <root>/generated)"),
26693
- strict: optional(Boolean2(), "[compile/check] Promote warnings to errors"),
26694
- format: optional(stringEnum(["text", "json"]), "Output format (default: text)"),
26695
- target: optional(String2(), "[explain/context] Provider class name / token name / command name / job name / module name"),
26696
- fix: optional(String2(), "[fix] Path to a DiagnosticFix JSON file produced by `doctor --format json`"),
26697
- write: optional(Boolean2(), "[fix] Write the fix to disk (default: preview only)")
26698
- };
26699
- for (const action of APP_ALIAS_ACTIONS) {
26700
- registerTool(server, action, `Top-level alias of \`app ${action}\`.`, schema, (request) => runAppTool({ ...request, action }));
27078
+ function createOfficialSupabaseEnvironment2(environment) {
27079
+ const safeEnvironment = {};
27080
+ for (const [key, environmentValue] of Object.entries(environment)) {
27081
+ if (environmentValue === undefined)
27082
+ continue;
27083
+ if (key === "SUPABASE_YES" || key === "SUPABASE_EXPERIMENTAL")
27084
+ continue;
27085
+ if (key === "SUPABASE_ACCESS_TOKEN" && environment.SUPACLOUD_FORWARD_SUPABASE_ACCESS_TOKEN === "1") {
27086
+ safeEnvironment[key] = environmentValue;
27087
+ continue;
27088
+ }
27089
+ if (isSensitiveEnvironmentKey2(key))
27090
+ continue;
27091
+ safeEnvironment[key] = environmentValue;
26701
27092
  }
27093
+ safeEnvironment.SUPABASE_TELEMETRY_DISABLED = "true";
27094
+ safeEnvironment.NO_COLOR = "1";
27095
+ return safeEnvironment;
27096
+ }
27097
+ function resolveOfficialSupabaseCommand2(workdir, environment = process.env) {
27098
+ const explicitBinary = environment.SUPACLOUD_SUPABASE_CLI_BIN?.trim();
27099
+ if (explicitBinary) {
27100
+ if (explicitBinary.includes("\x00"))
27101
+ throw new Error("Invalid SUPACLOUD_SUPABASE_CLI_BIN");
27102
+ return [explicitBinary];
27103
+ }
27104
+ const version = environment.SUPABASE_CLI_VERSION?.trim();
27105
+ if (version) {
27106
+ if (!VALID_VERSION2.test(version))
27107
+ throw new Error("Invalid SUPABASE_CLI_VERSION; use an exact version such as 2.120.0");
27108
+ if (process.versions.bun)
27109
+ return [process.execPath, "x", `supabase@${version}`];
27110
+ if (process.platform === "win32") {
27111
+ throw new Error("SUPABASE_CLI_VERSION bootstrap is unavailable under Node on Windows; install the official CLI or set SUPACLOUD_SUPABASE_CLI_BIN");
27112
+ }
27113
+ return ["npx", "--yes", `supabase@${version}`];
27114
+ }
27115
+ const localPackageEntry = join14(resolve18(workdir), "node_modules", "supabase", "dist", "supabase.js");
27116
+ if (existsSync12(localPackageEntry))
27117
+ return [process.execPath, localPackageEntry];
27118
+ return ["supabase"];
26702
27119
  }
26703
-
26704
- // src/shared/tools/db-governance-tools.ts
26705
- import { existsSync as existsSync12 } from "node:fs";
26706
- import { join as join15, resolve as resolve19 } from "node:path";
26707
- import {
26708
- buildDatabaseManifest,
26709
- explainObject,
26710
- lintModule,
26711
- readCatalog,
26712
- reconcileModule
26713
- } from "@supacloud/db";
26714
27120
 
26715
27121
  // src/shared/tools/db-workflow.ts
26716
- import { createHash as createHash7 } from "node:crypto";
26717
- import { lstat as lstat4, mkdir as mkdir6, mkdtemp, readFile as readFile5, readdir as readdir6, rename as rename3, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
26718
- import { tmpdir as tmpdir2 } from "node:os";
26719
- import { dirname as dirname9, isAbsolute as isAbsolute6, join as join14, relative as relative8, resolve as resolve18 } from "node:path";
26720
- import {
26721
- assertApplicationRoleRestricted,
26722
- readDatabaseRole,
26723
- renderDatabaseRoleGuardSql,
26724
- renderReverseSnapshot,
26725
- reverseDatabase
26726
- } from "@supacloud/db";
26727
- import { parseDatabaseSourcesConfig } from "@supacloud/db/source-contracts";
26728
27122
  var hash4 = (content) => createHash7("sha256").update(content).digest("hex");
26729
27123
  var result = (value, isError = false) => ({
26730
27124
  isError,
@@ -26732,12 +27126,12 @@ var result = (value, isError = false) => ({
26732
27126
  });
26733
27127
  var schemas = (value) => value?.split(",").map((entry) => entry.trim()).filter(Boolean) ?? ["public"];
26734
27128
  async function projectPath2(root, path) {
26735
- if (!path || isAbsolute6(path) || path.includes("\\") || path.split("/").some((part) => !part || part === "." || part === "..")) {
27129
+ if (!path || isAbsolute7(path) || path.includes("\\") || path.split("/").some((part) => !part || part === "." || part === "..")) {
26736
27130
  throw new Error("Use a normalized project-relative path");
26737
27131
  }
26738
27132
  let current = root;
26739
27133
  for (const part of path.split("/")) {
26740
- current = join14(current, part);
27134
+ current = join15(current, part);
26741
27135
  try {
26742
27136
  if ((await lstat4(current)).isSymbolicLink())
26743
27137
  throw new Error("Symlink workflow paths are forbidden");
@@ -26759,7 +27153,7 @@ async function exists2(path) {
26759
27153
  }
26760
27154
  }
26761
27155
  async function boundary(root) {
26762
- const config = join14(root, "database.sources.json");
27156
+ const config = join15(root, "database.sources.json");
26763
27157
  return await exists2(config) ? parseDatabaseSourcesConfig(JSON.parse(await readFile5(await projectPath2(root, "database.sources.json"), "utf8"))) : undefined;
26764
27158
  }
26765
27159
  var overlaps = (a, b) => a === b || a.startsWith(`${b}/`) || b.startsWith(`${a}/`);
@@ -26828,10 +27222,10 @@ async function withDatabase(args, options, work) {
26828
27222
  }
26829
27223
  async function runOfficial(root, workdir, args, environment, options) {
26830
27224
  const command = [
26831
- ...resolveOfficialSupabaseCommand(root, environment),
26832
- ...buildOfficialSupabaseArgs({ ...args, workdir })
27225
+ ...resolveOfficialSupabaseCommand2(root, environment),
27226
+ ...buildOfficialSupabaseArgs2({ ...args, workdir })
26833
27227
  ];
26834
- const safeEnvironment = createOfficialSupabaseEnvironment(environment);
27228
+ const safeEnvironment = createOfficialSupabaseEnvironment2(environment);
26835
27229
  delete safeEnvironment.SUPABASE_ACCESS_TOKEN;
26836
27230
  if (args.db_url)
26837
27231
  safeEnvironment.PGOPTIONS = "-c default_transaction_read_only=on";
@@ -26853,9 +27247,9 @@ async function runOfficial(root, workdir, args, environment, options) {
26853
27247
  async function initializeCandidate(stage, majorVersion = 17) {
26854
27248
  if (![15, 16, 17, 18].includes(majorVersion))
26855
27249
  throw new Error("Select a supported PostgreSQL major version (15-18)");
26856
- await mkdir6(join14(stage, "supabase/migrations"), { recursive: true });
26857
- await mkdir6(join14(stage, "supabase/schemas"), { recursive: true });
26858
- await writeFile5(join14(stage, "supabase/config.toml"), [
27250
+ await mkdir6(join15(stage, "supabase/migrations"), { recursive: true });
27251
+ await mkdir6(join15(stage, "supabase/schemas"), { recursive: true });
27252
+ await writeFile5(join15(stage, "supabase/config.toml"), [
26859
27253
  `project_id = "supacloud-candidate-${hash4(stage).slice(0, 12)}"`,
26860
27254
  "[db]",
26861
27255
  `major_version = ${majorVersion}`,
@@ -26873,9 +27267,9 @@ async function copySqlSources(root, source, destination) {
26873
27267
  const file = await projectPath2(root, `${source}/${name}`);
26874
27268
  const state = await lstat4(file);
26875
27269
  if (state.isDirectory())
26876
- count += await copySqlSources(root, `${source}/${name}`, join14(destination, name));
27270
+ count += await copySqlSources(root, `${source}/${name}`, join15(destination, name));
26877
27271
  else if (state.isFile() && name.endsWith(".sql")) {
26878
- await writeFile5(join14(destination, name), await readFile5(file));
27272
+ await writeFile5(join15(destination, name), await readFile5(file));
26879
27273
  count++;
26880
27274
  }
26881
27275
  }
@@ -26889,8 +27283,8 @@ async function reverse(args, root, options) {
26889
27283
  if (!selectedSchemas.length || selectedSchemas.some((schema) => !/^[a-z_][a-z0-9_$]*$/i.test(schema))) {
26890
27284
  throw new Error("Select valid PostgreSQL schemas");
26891
27285
  }
26892
- await mkdir6(dirname9(out), { recursive: true });
26893
- const stage = await mkdtemp(join14(dirname9(out), ".reverse-"));
27286
+ await mkdir6(dirname10(out), { recursive: true });
27287
+ const stage = await mkdtemp(join15(dirname10(out), ".reverse-"));
26894
27288
  try {
26895
27289
  const snapshot = await withDatabase(args, options, async (executor, url) => {
26896
27290
  const snapshot = await reverseDatabase(executor, selectedSchemas);
@@ -26906,10 +27300,10 @@ async function reverse(args, root, options) {
26906
27300
  }, options.environment ?? process.env, options);
26907
27301
  return snapshot;
26908
27302
  });
26909
- const generated = await readdir6(join14(stage, "supabase/schemas"), { recursive: true });
27303
+ const generated = await readdir6(join15(stage, "supabase/schemas"), { recursive: true });
26910
27304
  if (!generated.some((file) => file.endsWith(".sql")))
26911
27305
  throw new Error("Official reverse did not produce declarative SQL");
26912
- await writeFile5(join14(stage, "catalog.json"), renderReverseSnapshot(snapshot));
27306
+ await writeFile5(join15(stage, "catalog.json"), renderReverseSnapshot(snapshot));
26913
27307
  await rename3(stage, out);
26914
27308
  return result({
26915
27309
  ok: true,
@@ -26934,14 +27328,14 @@ async function diff2(args, root, options) {
26934
27328
  const input = await migrations(root, args.dir ?? (await boundary(root))?.migrations ?? "supabase/migrations");
26935
27329
  if (overlaps(input.dir, out))
26936
27330
  throw new Error("Candidate output overlaps reviewed migration history");
26937
- await mkdir6(dirname9(out), { recursive: true });
26938
- const stage = await mkdtemp(join14(dirname9(out), ".diff-"));
27331
+ await mkdir6(dirname10(out), { recursive: true });
27332
+ const stage = await mkdtemp(join15(dirname10(out), ".diff-"));
26939
27333
  try {
26940
27334
  await initializeCandidate(stage, args.db_major_version);
26941
- if (!await copySqlSources(root, sourcePath, join14(stage, "supabase/schemas")))
27335
+ if (!await copySqlSources(root, sourcePath, join15(stage, "supabase/schemas")))
26942
27336
  throw new Error("No declarative SQL sources");
26943
27337
  for (const { file, text } of input.files)
26944
- await writeFile5(join14(stage, "supabase/migrations", file), text);
27338
+ await writeFile5(join15(stage, "supabase/migrations", file), text);
26945
27339
  await runOfficial(root, stage, {
26946
27340
  action: "db_schema_declarative_sync",
26947
27341
  apply: false,
@@ -26951,15 +27345,15 @@ async function diff2(args, root, options) {
26951
27345
  }, options.environment ?? process.env, options);
26952
27346
  const history = new Map(input.files.map((file) => [file.file, file.text]));
26953
27347
  for (const [file, text] of history) {
26954
- if (await readFile5(join14(stage, "supabase/migrations", file), "utf8") !== text) {
27348
+ if (await readFile5(join15(stage, "supabase/migrations", file), "utf8") !== text) {
26955
27349
  throw new Error("Official diff changed reviewed migration history");
26956
27350
  }
26957
27351
  }
26958
- const names = (await readdir6(join14(stage, "supabase/migrations"))).filter((file) => file.endsWith(".sql") && !history.has(file));
26959
- const candidates = join14(stage, "candidates");
27352
+ const names = (await readdir6(join15(stage, "supabase/migrations"))).filter((file) => file.endsWith(".sql") && !history.has(file));
27353
+ const candidates = join15(stage, "candidates");
26960
27354
  await mkdir6(candidates);
26961
27355
  for (const file of names)
26962
- await writeFile5(join14(candidates, file), await readFile5(join14(stage, "supabase/migrations", file)));
27356
+ await writeFile5(join15(candidates, file), await readFile5(join15(stage, "supabase/migrations", file)));
26963
27357
  await rename3(candidates, out);
26964
27358
  } finally {
26965
27359
  await rm2(stage, { force: true, recursive: true });
@@ -26997,10 +27391,10 @@ async function migrationAction(args, root, options) {
26997
27391
  throw new Error("Select a Management API and project ref");
26998
27392
  const path = args.dir ?? (await boundary(root))?.migrations ?? "supabase/migrations";
26999
27393
  const input = await migrations(root, path);
27000
- const stage = await mkdtemp(join14(tmpdir2(), "supacloud-reviewed-migrations-"));
27394
+ const stage = await mkdtemp(join15(tmpdir2(), "supacloud-reviewed-migrations-"));
27001
27395
  try {
27002
27396
  for (const { file, text } of input.files)
27003
- await writeFile5(join14(stage, file), text);
27397
+ await writeFile5(join15(stage, file), text);
27004
27398
  const preview = await run({ action: "push_migrations", ref, dir: stage, dry_run: true, strict: true });
27005
27399
  const dryRun = {
27006
27400
  isError: preview.isError === true,
@@ -27027,7 +27421,7 @@ async function migrationAction(args, root, options) {
27027
27421
  }
27028
27422
  }
27029
27423
  async function runDbWorkflow(args, projectRoot, options = {}) {
27030
- const root = resolve18(projectRoot);
27424
+ const root = resolve19(projectRoot);
27031
27425
  if ((await lstat4(root)).isSymbolicLink())
27032
27426
  throw new Error("Workflow root must not be a symlink");
27033
27427
  switch (args.action) {
@@ -27088,8 +27482,8 @@ function normalizeModule(candidate, sourcePath) {
27088
27482
  };
27089
27483
  }
27090
27484
  async function loadDatabaseModules(root, moduleFile) {
27091
- const filePath = moduleFile ? resolve19(root, moduleFile) : join15(root, "db", "modules.ts");
27092
- if (!existsSync12(filePath)) {
27485
+ const filePath = moduleFile ? resolve20(root, moduleFile) : join16(root, "db", "modules.ts");
27486
+ if (!existsSync13(filePath)) {
27093
27487
  throw new Error(moduleFile ? `Module file not found: ${filePath}` : `Module file not found: ${filePath}(用 --module_file 指定一个导出 defineDatabaseModule(...) 的文件)`);
27094
27488
  }
27095
27489
  let imported;
@@ -27178,7 +27572,7 @@ function buildDatabaseAiContext(modules, root, moduleName, target) {
27178
27572
  const objects = target ? allObjects.filter((object) => objectMatchesTarget(object, target)) : allObjects;
27179
27573
  if (target && objects.length === 0)
27180
27574
  throw new Error(`未找到数据库对象: ${target}`);
27181
- const sqlFirst = existsSync12(join15(root, "supabase/schemas"));
27575
+ const sqlFirst = existsSync13(join16(root, "supabase/schemas"));
27182
27576
  return {
27183
27577
  version: 1,
27184
27578
  scope: "database-ai-context",
@@ -27224,7 +27618,7 @@ async function runLint(args, root) {
27224
27618
  const issues = [];
27225
27619
  for (const module of selected) {
27226
27620
  const readSqlFile = async (path) => {
27227
- const absolute = resolve19(root, path);
27621
+ const absolute = resolve20(root, path);
27228
27622
  try {
27229
27623
  return await Bun.file(absolute).text();
27230
27624
  } catch (error) {
@@ -27264,14 +27658,14 @@ var defaultLiteSpawn = async (command, cwd) => {
27264
27658
  return { exitCode, stdout, stderr };
27265
27659
  };
27266
27660
  async function runModuleCheckLite(args, root, spawn, liteBinary) {
27267
- const projectDir = resolve19(args.project_dir?.trim() || root);
27661
+ const projectDir = resolve20(args.project_dir?.trim() || root);
27268
27662
  const binary = liteBinary === undefined ? Bun.which("supacloud-lite") : liteBinary;
27269
27663
  if (!binary) {
27270
27664
  throw new Error("db module_check --lite 需要本机可用的 supacloud-lite(npm i -g @supacloud/lite,或加入 PATH)");
27271
27665
  }
27272
27666
  const command = [binary, "db", "check", "--project-dir", projectDir];
27273
27667
  if (args.module_file?.trim())
27274
- command.push("--module-file", resolve19(root, args.module_file.trim()));
27668
+ command.push("--module-file", resolve20(root, args.module_file.trim()));
27275
27669
  const result = await spawn(command, projectDir);
27276
27670
  const output = [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join(`
27277
27671
  `);
@@ -27312,7 +27706,7 @@ function registerDbGovernanceTools(server, options = {}) {
27312
27706
  const fallbackRoot = options.currentWorkingDirectory || process.cwd();
27313
27707
  const liteSpawn = options.liteSpawn || defaultLiteSpawn;
27314
27708
  registerTool(server, "db", "Database governance (@supacloud/db): inspect, reverse-adopt, diff, plan and apply through explicit source and migration boundaries. Actions: context, lint, explain, module_check, reverse, diff, plan, apply, role_check, role_sql", {
27315
- action: withDescription(stringEnum(["context", "lint", "explain", "module_check", "reverse", "diff", "plan", "apply", "role_check", "role_sql"]), "Database governance action"),
27709
+ action: withDescription(stringEnum2(["context", "lint", "explain", "module_check", "reverse", "diff", "plan", "apply", "role_check", "role_sql"]), "Database governance action"),
27316
27710
  module: optional(String2(), "[context/lint] Only select this manifest module (default: all)"),
27317
27711
  root: optional(String2(), "[*] Project root (default: current directory)"),
27318
27712
  module_file: optional(String2(), "[*] File exporting defineDatabaseModule(...) (default: <root>/db/modules.ts)"),
@@ -27331,7 +27725,7 @@ function registerDbGovernanceTools(server, options = {}) {
27331
27725
  migration_role: optional(String2(), "[role_sql] Privileged migration role"),
27332
27726
  database: optional(String2(), "[role_sql] PostgreSQL database name")
27333
27727
  }, async (request) => {
27334
- const root = resolve19(request.root || fallbackRoot);
27728
+ const root = resolve20(request.root || fallbackRoot);
27335
27729
  switch (request.action) {
27336
27730
  case "context":
27337
27731
  return runContext2(request, root);
@@ -27361,11 +27755,11 @@ function registerDbGovernanceTools(server, options = {}) {
27361
27755
 
27362
27756
  // src/shared/tools/scheduled-function-tools.ts
27363
27757
  import { randomUUID as randomUUID3 } from "node:crypto";
27364
- import { readFileSync as readFileSync9, statSync as statSync8 } from "node:fs";
27365
- import { resolve as resolve20 } from "node:path";
27758
+ import { readFileSync as readFileSync9, statSync as statSync9 } from "node:fs";
27759
+ import { resolve as resolve21 } from "node:path";
27366
27760
  import { isDeepStrictEqual } from "node:util";
27367
27761
  var HEADER_NAME_PATTERN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,128}$/;
27368
- var ENVIRONMENT_NAME_PATTERN2 = /^[A-Za-z_][A-Za-z0-9_]{0,255}$/;
27762
+ var ENVIRONMENT_NAME_PATTERN3 = /^[A-Za-z_][A-Za-z0-9_]{0,255}$/;
27369
27763
  var FORBIDDEN_HEADER_NAMES = new Set([
27370
27764
  "apikey",
27371
27765
  "authorization",
@@ -27446,8 +27840,8 @@ function validScheduledFunctionCron(expression) {
27446
27840
  function readScheduleBodyFile(bodyPathInput) {
27447
27841
  if (!bodyPathInput.trim())
27448
27842
  throw new Error("'body_file' must be a path");
27449
- const bodyPath = resolve20(bodyPathInput);
27450
- const bodyStat = statSync8(bodyPath);
27843
+ const bodyPath = resolve21(bodyPathInput);
27844
+ const bodyStat = statSync9(bodyPath);
27451
27845
  if (!bodyStat.isFile() || bodyStat.size > MAX_BODY_FILE_BYTES) {
27452
27846
  throw new Error("Scheduled Function body file must be a regular file no larger than 1 MiB");
27453
27847
  }
@@ -27473,7 +27867,7 @@ function scheduleBody(bodyFile) {
27473
27867
  }
27474
27868
  function resolvedHeaderEntry(headerName, environmentName, environment) {
27475
27869
  const normalizedName = headerName.toLowerCase();
27476
- if (!HEADER_NAME_PATTERN.test(headerName) || forbiddenHeaderName(normalizedName) || typeof environmentName !== "string" || !ENVIRONMENT_NAME_PATTERN2.test(environmentName)) {
27870
+ if (!HEADER_NAME_PATTERN.test(headerName) || forbiddenHeaderName(normalizedName) || typeof environmentName !== "string" || !ENVIRONMENT_NAME_PATTERN3.test(environmentName)) {
27477
27871
  throw new Error("SCHEDULE_HEADER_MAPPING_INVALID");
27478
27872
  }
27479
27873
  const headerValue = environment[environmentName];
@@ -27778,14 +28172,14 @@ function registerScheduledFunctionTools(server, http, environment = process.env,
27778
28172
  }
27779
28173
  var SCHEDULE_TOOL_DESCRIPTION = "Scheduled Edge Function lifecycle. Actions: list, get, create, update, delete";
27780
28174
  var SCHEDULE_TOOL_SCHEMA = {
27781
- action: withDescription(stringEnum(["list", "get", "create", "update", "delete"]), "Action"),
28175
+ action: withDescription(stringEnum2(["list", "get", "create", "update", "delete"]), "Action"),
27782
28176
  ref: withDescription(String2(), "Project ref"),
27783
28177
  schedule_id: optional(String2(), "[get/update/delete] Schedule ID"),
27784
28178
  expected_updated_at: optional(String2(), "[update/delete] Canonical updated_at from list"),
27785
28179
  name: optional(String2(), "[create/update] Display name"),
27786
28180
  slug: optional(String2(), "[create] Edge Function slug"),
27787
28181
  cron: optional(String2(), "[create/update] Five-field cron expression"),
27788
- method: optional(stringEnum(["GET", "POST"]), "[create/update] HTTP method"),
28182
+ method: optional(stringEnum2(["GET", "POST"]), "[create/update] HTTP method"),
27789
28183
  enabled: optional(Boolean2(), "[update] Enabled state"),
27790
28184
  body_file: optional(String2(), "[create/update] Local JSON object file; content is never printed"),
27791
28185
  header_env: withDescription(headerEnvironmentSchema, "[create/update] JSON map of HTTP header names to environment variable names")
@@ -27823,15 +28217,15 @@ function registerMutationTools(server, http) {
27823
28217
  registerTool(server, "mutations", "Durable mutation status readback", MUTATION_TOOL_SCHEMA, (args) => mutationStatus(http, args));
27824
28218
  }
27825
28219
  var MUTATION_TOOL_SCHEMA = {
27826
- action: withDescription(stringEnum(["status"]), "Action"),
28220
+ action: withDescription(stringEnum2(["status"]), "Action"),
27827
28221
  ref: withDescription(String2(), "[status] Project ref"),
27828
28222
  mutation_id: withDescription(String2(), "[status] Client mutation UUID")
27829
28223
  };
27830
28224
 
27831
28225
  // src/shared/tools/release-tools.ts
27832
28226
  import { createHash as createHash8 } from "node:crypto";
27833
- import { existsSync as existsSync13, mkdirSync as mkdirSync4, readdirSync as readdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync3 } from "node:fs";
27834
- import { dirname as dirname10, join as join16, relative as relative9, resolve as resolve21 } from "node:path";
28227
+ import { existsSync as existsSync14, mkdirSync as mkdirSync5, readdirSync as readdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "node:fs";
28228
+ import { dirname as dirname11, join as join17, relative as relative9, resolve as resolve22 } from "node:path";
27835
28229
  var SAFE_PROJECT_REF = /^[A-Za-z0-9_-]{1,64}$/;
27836
28230
  var SAFE_ENVIRONMENT_ID = /^[A-Za-z0-9_-]{1,64}$/;
27837
28231
  var BACKUP_ID = /^logical-full_[A-Za-z0-9_-]{1,64}_[a-f0-9]{32}$/;
@@ -28153,15 +28547,15 @@ function collectScopeFiles(cwd, file, files, dir, task) {
28153
28547
  const results = [];
28154
28548
  const resolvePathWithFallbacks = (filePath) => {
28155
28549
  const trimmed = filePath.trim();
28156
- const resolved = resolve21(cwd, trimmed);
28157
- if (existsSync13(resolved))
28550
+ const resolved = resolve22(cwd, trimmed);
28551
+ if (existsSync14(resolved))
28158
28552
  return resolved;
28159
28553
  const candidates = [
28160
- resolve21(cwd, "supacloud/fa/release-scopes", trimmed),
28161
- resolve21(cwd, "release-scopes", trimmed),
28162
- resolve21(cwd, "scopes", trimmed)
28554
+ resolve22(cwd, "supacloud/fa/release-scopes", trimmed),
28555
+ resolve22(cwd, "release-scopes", trimmed),
28556
+ resolve22(cwd, "scopes", trimmed)
28163
28557
  ];
28164
- const found = candidates.find(existsSync13);
28558
+ const found = candidates.find(existsSync14);
28165
28559
  if (found)
28166
28560
  return found;
28167
28561
  throw new Error(`Scope file not found: ${filePath}`);
@@ -28182,18 +28576,18 @@ function collectScopeFiles(cwd, file, files, dir, task) {
28182
28576
  if (!taskFilter && (!dir || typeof dir !== "string" || !dir.trim())) {
28183
28577
  throw new Error("At least one of --file, --files, or --task is required for scope_rebind");
28184
28578
  }
28185
- const candidates = typeof dir === "string" && dir.trim() ? [resolve21(cwd, dir.trim())] : [
28186
- resolve21(cwd, "supacloud/fa/release-scopes"),
28187
- resolve21(cwd, "release-scopes"),
28188
- resolve21(cwd, "scopes")
28579
+ const candidates = typeof dir === "string" && dir.trim() ? [resolve22(cwd, dir.trim())] : [
28580
+ resolve22(cwd, "supacloud/fa/release-scopes"),
28581
+ resolve22(cwd, "release-scopes"),
28582
+ resolve22(cwd, "scopes")
28189
28583
  ];
28190
28584
  for (const directory of candidates) {
28191
- if (existsSync13(directory)) {
28585
+ if (existsSync14(directory)) {
28192
28586
  try {
28193
28587
  const entries = readdirSync5(directory, { withFileTypes: true });
28194
28588
  for (const entry of entries) {
28195
28589
  if (entry.isFile() && entry.name.endsWith(".json")) {
28196
- const fullPath = join16(directory, entry.name);
28590
+ const fullPath = join17(directory, entry.name);
28197
28591
  if (!taskFilter) {
28198
28592
  results.push(fullPath);
28199
28593
  } else if (entry.name.toLowerCase().includes(taskFilter)) {
@@ -28219,7 +28613,7 @@ function collectScopeFiles(cwd, file, files, dir, task) {
28219
28613
  return [...new Set(results)];
28220
28614
  }
28221
28615
  function handleScopeRebind(args) {
28222
- const cwd = typeof args.cwd === "string" && args.cwd.trim() ? resolve21(args.cwd.trim()) : process.cwd();
28616
+ const cwd = typeof args.cwd === "string" && args.cwd.trim() ? resolve22(args.cwd.trim()) : process.cwd();
28223
28617
  const targetBaseCommit = resolveGitBaseCommit(cwd, args.base_commit);
28224
28618
  const requestedEnvironmentId = requestedScopeEnvironment(args, "scope_rebind");
28225
28619
  const scopeFiles = collectScopeFiles(cwd, args.file, args.files, args.dir, args.task);
@@ -28255,7 +28649,7 @@ function handleScopeRebind(args) {
28255
28649
  if (updated && !dryRun) {
28256
28650
  const formatted = JSON.stringify(canonicalValue(parsed), null, 2) + `
28257
28651
  `;
28258
- writeFileSync3(filePath, formatted, "utf8");
28652
+ writeFileSync4(filePath, formatted, "utf8");
28259
28653
  }
28260
28654
  scopes.push({
28261
28655
  file: relative9(cwd, filePath),
@@ -28277,20 +28671,20 @@ function handleScopeRebind(args) {
28277
28671
  });
28278
28672
  }
28279
28673
  function handleScopeInspect(args) {
28280
- const cwd = typeof args.cwd === "string" && args.cwd.trim() ? resolve21(args.cwd.trim()) : process.cwd();
28674
+ const cwd = typeof args.cwd === "string" && args.cwd.trim() ? resolve22(args.cwd.trim()) : process.cwd();
28281
28675
  const requestedEnvironmentId = requestedScopeEnvironment(args, "scope_inspect");
28282
28676
  if (typeof args.file !== "string" || !args.file.trim()) {
28283
28677
  throw new Error("'file' is required for scope_inspect");
28284
28678
  }
28285
28679
  const trimmed = args.file.trim();
28286
- let filePath = resolve21(cwd, trimmed);
28287
- if (!existsSync13(filePath)) {
28680
+ let filePath = resolve22(cwd, trimmed);
28681
+ if (!existsSync14(filePath)) {
28288
28682
  const candidates = [
28289
- resolve21(cwd, "supacloud/fa/release-scopes", trimmed),
28290
- resolve21(cwd, "release-scopes", trimmed),
28291
- resolve21(cwd, "scopes", trimmed)
28683
+ resolve22(cwd, "supacloud/fa/release-scopes", trimmed),
28684
+ resolve22(cwd, "release-scopes", trimmed),
28685
+ resolve22(cwd, "scopes", trimmed)
28292
28686
  ];
28293
- const found = candidates.find(existsSync13);
28687
+ const found = candidates.find(existsSync14);
28294
28688
  if (found)
28295
28689
  filePath = found;
28296
28690
  else
@@ -28353,7 +28747,7 @@ function parseCommaList(value) {
28353
28747
  return [];
28354
28748
  }
28355
28749
  function handleScopeCreate(args, fallbackRef) {
28356
- const cwd = typeof args.cwd === "string" && args.cwd.trim() ? resolve21(args.cwd.trim()) : process.cwd();
28750
+ const cwd = typeof args.cwd === "string" && args.cwd.trim() ? resolve22(args.cwd.trim()) : process.cwd();
28357
28751
  if (typeof args.file !== "string" || !args.file.trim()) {
28358
28752
  throw new Error("'file' is required for scope_create");
28359
28753
  }
@@ -28369,8 +28763,8 @@ function handleScopeCreate(args, fallbackRef) {
28369
28763
  throw new Error("'ref' is invalid for release controls");
28370
28764
  }
28371
28765
  const targetBaseCommit = resolveGitBaseCommit(cwd, args.base_commit);
28372
- const filePath = resolve21(cwd, args.file.trim());
28373
- mkdirSync4(dirname10(filePath), { recursive: true });
28766
+ const filePath = resolve22(cwd, args.file.trim());
28767
+ mkdirSync5(dirname11(filePath), { recursive: true });
28374
28768
  const scope = {
28375
28769
  baseCommit: targetBaseCommit,
28376
28770
  excludedFunctions: parseCommaList(args.excluded_functions),
@@ -28385,7 +28779,7 @@ function handleScopeCreate(args, fallbackRef) {
28385
28779
  const scopeSha256 = genericScopedReleaseScopeSha256(scope);
28386
28780
  const formatted = JSON.stringify(canonicalValue(scope), null, 2) + `
28387
28781
  `;
28388
- writeFileSync3(filePath, formatted, "utf8");
28782
+ writeFileSync4(filePath, formatted, "utf8");
28389
28783
  return releaseControlSuccess("release.scope_create", {
28390
28784
  file: relative9(cwd, filePath),
28391
28785
  task_id: scope.taskId,
@@ -28410,7 +28804,7 @@ function registerReleaseTools(server, http, options = {}) {
28410
28804
  ];
28411
28805
  const allActions = [...remoteActions, ...localActions];
28412
28806
  registerTool(server, "release", "Verified release controls. Scope management actions operate locally; management actions use the Management API; release canary stage/disable replay additionally require the selected project's SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY.", {
28413
- action: withDescription(stringEnum(allActions), "Release control action"),
28807
+ action: withDescription(stringEnum2(allActions), "Release control action"),
28414
28808
  ref: optional(String2(), options.projectRef ? "Optional override when not auto-linked" : "Project ref"),
28415
28809
  file: optional(String2(), "[scope_inspect/scope_rebind/scope_create] Path to release scope JSON file"),
28416
28810
  files: optional(String2(), "[scope_rebind] Comma-separated list of release scope JSON files"),
@@ -28618,7 +29012,7 @@ import { access, lstat as lstat5, mkdtemp as mkdtemp2, opendir, readFile as read
28618
29012
  import { spawn as spawn2 } from "node:child_process";
28619
29013
  import { createHash as createHash9 } from "node:crypto";
28620
29014
  import { tmpdir as tmpdir3 } from "node:os";
28621
- import { basename as basename8, dirname as dirname11, join as join17, relative as relative10, resolve as resolve22, sep as sep5 } from "node:path";
29015
+ import { basename as basename8, dirname as dirname12, join as join18, relative as relative10, resolve as resolve23, sep as sep5 } from "node:path";
28622
29016
 
28623
29017
  // src/shared/tools/project-deployment.ts
28624
29018
  async function requireProjectDeploymentProtocol(http, projectRef, environmentId) {
@@ -28637,6 +29031,16 @@ var scopeSchema = _Object_({
28637
29031
  application_id: ApplicationIdSchema,
28638
29032
  environment_id: ApplicationIdSchema
28639
29033
  });
29034
+ var acceptedSchema = _Object_({
29035
+ schema: Literal("supacloud.project-deployment-accepted.v1"),
29036
+ ok: Literal(true),
29037
+ status: Literal("accepted"),
29038
+ deployment_id: ApplicationDeployRequestSchema.properties.mutation_id,
29039
+ ...scopeSchema.properties,
29040
+ release_id: ApplicationDeployRequestSchema.properties.release_id,
29041
+ request_sha256: String2({ pattern: "^[a-f0-9]{64}$" }),
29042
+ replayed: Boolean2()
29043
+ }, { additionalProperties: false });
28640
29044
  var statusSchema = _Object_({
28641
29045
  schema: Literal("supacloud.project-deployment-status.v1"),
28642
29046
  deployment_id: ApplicationDeployRequestSchema.properties.mutation_id,
@@ -28661,71 +29065,25 @@ var statusSchema = _Object_({
28661
29065
  receipt: Optional(Unknown()),
28662
29066
  failure_code: Optional(String2({ pattern: "^[A-Z][A-Z0-9_]{0,63}$" }))
28663
29067
  }, { additionalProperties: false });
28664
- function responseReceipt(data, expected) {
28665
- if (!data || typeof data !== "object" || Array.isArray(data) || !("ok" in data) || data.ok !== true) {
28666
- throw new Error("Invalid project deployment response");
28667
- }
28668
- const { ok: _ok, ...receipt } = data;
28669
- return parseApplicationDeployResult(receipt, expected);
28670
- }
28671
- async function runProjectDeployment(http, args) {
28672
- const scope = { project_ref: args.ref, application_id: args.id, environment_id: args.environment_id };
28673
- const request = {
28674
- mutation_id: args.mutation_id,
28675
- release_id: args.release_id,
28676
- ...args.configuration_id === undefined ? {} : { configuration_id: args.configuration_id },
28677
- ...args.configuration_write === undefined ? {} : { configuration_write: args.configuration_write },
28678
- ...args.expected_plan_sha256 === undefined ? {} : { expected_plan_sha256: args.expected_plan_sha256 },
28679
- ...args.approved_migration_digest === undefined ? {} : { approved_migration_digest: args.approved_migration_digest },
28680
- ...args.web_release === undefined ? {} : { web_release: args.web_release }
28681
- };
28682
- if (!Check2(scopeSchema, scope) || !Check2(ApplicationDeployRequestSchema, request) || request.configuration_id !== undefined && request.configuration_write !== undefined && request.configuration_id !== request.configuration_write.configuration_id) {
29068
+ async function readProjectDeploymentStatus(http, projectRef, environmentId, deploymentId) {
29069
+ if (!/^[a-z0-9-]{1,20}$/.test(projectRef) || !Check2(ApplicationIdSchema, environmentId) || !Check2(ApplicationDeployRequestSchema.properties.mutation_id, deploymentId)) {
28683
29070
  return releaseControlFailure(operation2, "INVALID_RESPONSE", null);
28684
29071
  }
28685
- const expected = { ...scope, request };
28686
- const identity = {
28687
- ...scope,
28688
- deployment_id: request.mutation_id,
28689
- mutation_id: request.mutation_id,
28690
- release_id: request.release_id
28691
- };
28692
- const endpoint = `/v1/projects/${encodeURIComponent(scope.project_ref)}/environments/${encodeURIComponent(scope.environment_id)}/deployments`;
28693
- const { mutation_id, ...candidate } = request;
28694
- const result = await http.post(endpoint, {
28695
- ...candidate,
28696
- deployment_id: mutation_id,
28697
- application_id: scope.application_id
28698
- }, { timeoutMs: 120000, maxJsonBytes: 131072, responseTimeoutMs: 30000 });
28699
- if (result.ok && result.status === 200) {
28700
- try {
28701
- return releaseControlSuccess(operation2, {
28702
- ...responseReceipt(result.data, expected),
28703
- deployment_id: mutation_id
28704
- });
28705
- } catch {}
28706
- } else if (!result.ok && !result.transportError && !result.responseReadError && result.status < 500 && result.status !== 408) {
28707
- return releaseControlMutationFailure(operation2, result, identity);
28708
- }
28709
- const observed = await http.get(`${endpoint}/${encodeURIComponent(mutation_id)}`, {
28710
- retry: false,
28711
- totalTimeoutMs: 30000,
28712
- maxJsonBytes: 131072
28713
- });
29072
+ const path = `/v1/projects/${encodeURIComponent(projectRef)}/environments/${encodeURIComponent(environmentId)}/deployments/${deploymentId}`;
29073
+ const response = await http.get(path, { retry: false, totalTimeoutMs: 1e4, maxJsonBytes: 131072 });
28714
29074
  try {
28715
- if (!observed.ok || observed.status !== 200 || !Check2(statusSchema, observed.data))
29075
+ const state = response.data;
29076
+ if (!response.ok || response.status !== 200 || !Check2(statusSchema, state) || state.project_ref !== projectRef || state.environment_id !== environmentId || state.deployment_id !== deploymentId)
28716
29077
  throw new Error;
28717
- const state = observed.data;
28718
- 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")
29078
+ if (state.status === "succeeded") {
29079
+ const receipt = parseApplicationDeployResult(state.receipt);
29080
+ if (state.phase !== "verifying" || receipt.project_ref !== projectRef || receipt.environment_id !== environmentId || receipt.application_id !== state.application_id || receipt.mutation_id !== deploymentId)
29081
+ throw new Error;
29082
+ } else if (state.receipt !== undefined)
28719
29083
  throw new Error;
28720
- const receipt = parseApplicationDeployResult(state.receipt, expected);
28721
- return releaseControlSuccess(operation2, {
28722
- ...receipt,
28723
- deployment_id: mutation_id,
28724
- replayed: true,
28725
- execution_performed: false
28726
- });
29084
+ return state.status === "outcome_unknown" || state.status === "failed_terminal" ? releaseControlFailure(operation2, state.status === "outcome_unknown" ? "OUTCOME_UNKNOWN" : "MUTATION_NOT_SUCCEEDED", response.status, { ...state }) : releaseControlSuccess(operation2, { ...state });
28727
29085
  } catch {
28728
- return releaseControlFailure(operation2, "OUTCOME_UNKNOWN", result.transportError ? null : result.status, identity);
29086
+ return releaseControlFailure(operation2, "INVALID_RESPONSE", response.status);
28729
29087
  }
28730
29088
  }
28731
29089
 
@@ -28744,6 +29102,8 @@ var deployToolSchema = {
28744
29102
  cwd: optional(String2(), "Project directory (default: current directory)"),
28745
29103
  skip_build: optional(Boolean2(), "Use an existing output directory without building"),
28746
29104
  assets_only: optional(Boolean2(), "Publish only configured static assets; leave backend Functions, schedules and migrations unchanged"),
29105
+ backend: optional(Boolean2(), "Publish the application backend independently; do not build or activate frontend assets"),
29106
+ status: optional(String2({ format: "uuid" }), "Read a backend deployment status without publishing"),
28747
29107
  json: optional(Boolean2(), "Print only the final JSON result")
28748
29108
  };
28749
29109
  function record4(candidate) {
@@ -28778,15 +29138,15 @@ async function readJson(path) {
28778
29138
  }
28779
29139
  }
28780
29140
  async function findDeployConfigRoot(startDirectory, environmentName) {
28781
- let directory = resolve22(startDirectory);
29141
+ let directory = resolve23(startDirectory);
28782
29142
  while (true) {
28783
29143
  const environmentFile = environmentName ? `.env.supacloud.${environmentName}` : null;
28784
- if (await pathExists(join17(directory, "supacloud.json")) || await pathExists(join17(directory, "supacloud.config.ts")) || environmentFile !== null && await pathExists(join17(directory, environmentFile))) {
29144
+ if (await pathExists(join18(directory, "supacloud.json")) || await pathExists(join18(directory, "supacloud.config.ts")) || environmentFile !== null && await pathExists(join18(directory, environmentFile))) {
28785
29145
  return directory;
28786
29146
  }
28787
- const parent = dirname11(directory);
29147
+ const parent = dirname12(directory);
28788
29148
  if (parent === directory)
28789
- return resolve22(startDirectory);
29149
+ return resolve23(startDirectory);
28790
29150
  directory = parent;
28791
29151
  }
28792
29152
  }
@@ -28851,7 +29211,7 @@ function deployTargetConfig(candidate, name) {
28851
29211
  };
28852
29212
  }
28853
29213
  async function readDeployConfig(configRoot) {
28854
- const configPath = join17(configRoot, "supacloud.json");
29214
+ const configPath = join18(configRoot, "supacloud.json");
28855
29215
  const candidate = await readJson(configPath);
28856
29216
  if (candidate === null)
28857
29217
  return {};
@@ -28898,16 +29258,16 @@ async function readDeployConfig(configRoot) {
28898
29258
  };
28899
29259
  }
28900
29260
  async function packageName(projectDirectory) {
28901
- const candidate = record4(await readJson(join17(projectDirectory, "package.json")));
29261
+ const candidate = record4(await readJson(join18(projectDirectory, "package.json")));
28902
29262
  return typeof candidate?.name === "string" && candidate.name.trim() ? candidate.name.trim() : null;
28903
29263
  }
28904
29264
  async function pathExists(path) {
28905
29265
  return access(path).then(() => true, () => false);
28906
29266
  }
28907
29267
  async function hasProjectReleaseManifest(root) {
28908
- if (await pathExists(join17(root, "deploy-manifest.json")))
29268
+ if (await pathExists(join18(root, "deploy-manifest.json")))
28909
29269
  return true;
28910
- const directory = await opendir(join17(root, "supacloud")).catch((error) => {
29270
+ const directory = await opendir(join18(root, "supacloud")).catch((error) => {
28911
29271
  if (error instanceof Error && "code" in error && error.code === "ENOENT")
28912
29272
  return null;
28913
29273
  throw error;
@@ -28918,28 +29278,28 @@ async function hasProjectReleaseManifest(root) {
28918
29278
  for await (const entry of directory) {
28919
29279
  if (++entries > 128)
28920
29280
  throw new Error("Too many SupaCloud project roots to resolve a deployment");
28921
- if ((entry.isDirectory() || entry.isSymbolicLink()) && await pathExists(join17(root, "supacloud", entry.name, "deploy-manifest.json")))
29281
+ if ((entry.isDirectory() || entry.isSymbolicLink()) && await pathExists(join18(root, "supacloud", entry.name, "deploy-manifest.json")))
28922
29282
  return true;
28923
29283
  }
28924
29284
  return false;
28925
29285
  }
28926
29286
  async function defaultBuildCommand(projectDirectory, configRoot) {
28927
- const packageJson = record4(await readJson(join17(projectDirectory, "package.json")));
29287
+ const packageJson = record4(await readJson(join18(projectDirectory, "package.json")));
28928
29288
  const scripts = record4(packageJson?.scripts);
28929
29289
  if (typeof scripts?.build !== "string" || !scripts.build.trim()) {
28930
29290
  throw new Error("No build command was configured and package.json has no build script");
28931
29291
  }
28932
29292
  let directory = projectDirectory;
28933
29293
  while (true) {
28934
- if (await pathExists(join17(directory, "bun.lock")) || await pathExists(join17(directory, "bun.lockb")))
29294
+ if (await pathExists(join18(directory, "bun.lock")) || await pathExists(join18(directory, "bun.lockb")))
28935
29295
  return "bun run build";
28936
- if (await pathExists(join17(directory, "pnpm-lock.yaml")))
29296
+ if (await pathExists(join18(directory, "pnpm-lock.yaml")))
28937
29297
  return "pnpm run build";
28938
- if (await pathExists(join17(directory, "yarn.lock")))
29298
+ if (await pathExists(join18(directory, "yarn.lock")))
28939
29299
  return "yarn build";
28940
29300
  if (directory === configRoot)
28941
29301
  break;
28942
- const parent = dirname11(directory);
29302
+ const parent = dirname12(directory);
28943
29303
  if (parent === directory || !directory.startsWith(`${configRoot}${sep5}`))
28944
29304
  break;
28945
29305
  directory = parent;
@@ -29007,9 +29367,9 @@ function selectFrontendTarget(configRoot, config) {
29007
29367
  throw new Error(`Multiple frontend deploy targets found (${targets.map(([name]) => name).sort().join(", ")}). Set defaultTarget to the Web target`);
29008
29368
  }
29009
29369
  function targetRoot(configRoot, target, name) {
29010
- const root = resolve22(configRoot, target.root || (name === "frontend" ? "." : name));
29011
- const prefix = `${resolve22(configRoot)}${sep5}`;
29012
- if (root !== resolve22(configRoot) && !root.startsWith(prefix)) {
29370
+ const root = resolve23(configRoot, target.root || (name === "frontend" ? "." : name));
29371
+ const prefix = `${resolve23(configRoot)}${sep5}`;
29372
+ if (root !== resolve23(configRoot) && !root.startsWith(prefix)) {
29013
29373
  throw new Error(`Deploy target '${name}' root must stay inside the repository root`);
29014
29374
  }
29015
29375
  return root;
@@ -29027,8 +29387,8 @@ function defaultOutputDirectory(framework) {
29027
29387
  }
29028
29388
  }
29029
29389
  function resolveInsideProject(projectDirectory, candidate, allowRoot = false) {
29030
- const resolved = resolve22(projectDirectory, candidate);
29031
- const projectRoot = resolve22(projectDirectory);
29390
+ const resolved = resolve23(projectDirectory, candidate);
29391
+ const projectRoot = resolve23(projectDirectory);
29032
29392
  const projectPrefix = `${projectRoot}${sep5}`;
29033
29393
  if (!allowRoot && resolved === projectRoot || resolved !== projectRoot && !resolved.startsWith(projectPrefix)) {
29034
29394
  throw new Error("Frontend output directory must stay inside the project directory");
@@ -29082,7 +29442,7 @@ async function collectArchiveFiles(root) {
29082
29442
  entries.push(entry);
29083
29443
  entries.sort((left, right) => left.name.localeCompare(right.name, "en"));
29084
29444
  for (const entry of entries) {
29085
- const path = join17(directory, entry.name);
29445
+ const path = join18(directory, entry.name);
29086
29446
  const stats = await lstat5(path);
29087
29447
  if (stats.isSymbolicLink())
29088
29448
  throw new Error(`Frontend output cannot contain symbolic links: ${path}`);
@@ -29119,10 +29479,9 @@ async function collectArchiveFiles(root) {
29119
29479
  }
29120
29480
  return { files, bytes, count: paths.length, treeSha256: tree.digest("hex") };
29121
29481
  }
29122
- async function createFrontendArchive(outputDirectory) {
29123
- const collected = await collectArchiveFiles(outputDirectory);
29124
- const temporaryDirectory = await mkdtemp2(join17(tmpdir3(), "supacloud-deploy-"));
29125
- const archivePath = join17(temporaryDirectory, `${basename8(outputDirectory)}.tar.zst`);
29482
+ async function createCollectedFrontendArchive(outputDirectory, collected) {
29483
+ const temporaryDirectory = await mkdtemp2(join18(tmpdir3(), "supacloud-deploy-"));
29484
+ const archivePath = join18(temporaryDirectory, `${basename8(outputDirectory)}.tar.zst`);
29126
29485
  try {
29127
29486
  const archiveBytes = await createFrontendTarZstd(collected.files);
29128
29487
  await writeFile6(archivePath, archiveBytes);
@@ -29141,16 +29500,7 @@ async function createFrontendArchive(outputDirectory) {
29141
29500
  throw error;
29142
29501
  }
29143
29502
  }
29144
- function payload(response) {
29145
- if (response.isError)
29146
- throw new Error(response.content[0]?.text || "Deployment operation failed");
29147
- const text = response.content.find((chunk) => chunk.type === "text")?.text;
29148
- const parsed = text ? record4(JSON.parse(text)) : null;
29149
- if (!parsed)
29150
- throw new Error("Deployment operation returned an invalid response");
29151
- return parsed;
29152
- }
29153
- async function stageApplicationWebRelease(http, projectRef, environmentId, configRoot, skipBuild, json, environmentVariables, assetsOnly = false) {
29503
+ async function publishConfiguredFrontend(http, projectRef, configRoot, skipBuild, json, environmentVariables) {
29154
29504
  const target = selectFrontendTarget(configRoot, await readDeployConfig(configRoot));
29155
29505
  if (!target)
29156
29506
  return null;
@@ -29174,34 +29524,25 @@ async function stageApplicationWebRelease(http, projectRef, environmentId, confi
29174
29524
  if (!skipBuild) {
29175
29525
  await runBuild(buildCommand, projectDirectory, build.env, json);
29176
29526
  }
29177
- const archive = await createFrontendArchive(outputDirectory);
29178
- try {
29179
- if (assetsOnly) {
29180
- return { result: await publishFrontendRelease(http, projectRef, selected.id, archive.archivePath, archive.treeSha256) };
29181
- }
29182
- const uploaded = payload(await uploadProjectWebRelease(http, projectRef, environmentId, selected.id, archive.archivePath));
29183
- const release = record4(uploaded.release);
29184
- const releaseId = requiredString(release?.release_id, "release.release_id");
29185
- if (releaseId !== archive.sha256)
29186
- throw new Error("Uploaded Web release identity does not match the local archive");
29187
- if (release?.tree_sha256 !== archive.treeSha256)
29188
- throw new Error("Uploaded Web release tree does not match the local build");
29189
- return {
29190
- request: {
29191
- deployment_id: selected.id,
29192
- release_id: releaseId
29193
- }
29194
- };
29195
- } finally {
29196
- await archive.cleanup();
29197
- }
29527
+ const collected = await collectArchiveFiles(outputDirectory);
29528
+ return publishFrontendRelease(http, projectRef, selected.id, () => createCollectedFrontendArchive(outputDirectory, collected), collected.treeSha256);
29198
29529
  }
29199
29530
  function registerDeployTools(server, http, options = {}) {
29200
29531
  registerTool(server, "deploy", "Build and publish the linked project with one command", deployToolSchema, async (args) => {
29201
- const invocationDirectory = resolve22(String(args.cwd || options.cwd || process.cwd()));
29532
+ const invocationDirectory = resolve23(String(args.cwd || options.cwd || process.cwd()));
29202
29533
  const projectRef = String(args.ref || options.projectRef || "").trim();
29203
29534
  if (!projectRef)
29204
29535
  throw new Error("A linked project ref is required. Set SUPACLOUD_PROJECT_REF or pass --ref");
29536
+ if (args.assets_only === true && args.backend === true)
29537
+ throw new Error("Choose either frontend assets or backend deployment");
29538
+ if (typeof args.status === "string") {
29539
+ if (args.assets_only === true || args.backend === true)
29540
+ throw new Error("Status cannot be combined with publishing options");
29541
+ const environmentId = options.environment?.trim() || options.application?.environmentId?.trim();
29542
+ if (!environmentId)
29543
+ throw new Error("Deployment status requires SUPACLOUD_ENV");
29544
+ return readProjectDeploymentStatus(http, projectRef, environmentId, args.status);
29545
+ }
29205
29546
  const configRoot = await findDeployConfigRoot(invocationDirectory, options.environment ?? undefined);
29206
29547
  const config = await readDeployConfig(configRoot);
29207
29548
  const application = options.application?.id ? options.application : config.application ? {
@@ -29209,7 +29550,7 @@ function registerDeployTools(server, http, options = {}) {
29209
29550
  environmentId: options.environment || config.application.environmentId
29210
29551
  } : undefined;
29211
29552
  const legacyManifest = await hasProjectReleaseManifest(configRoot);
29212
- const assetsOnly = args.assets_only === true || !application?.id && selectFrontendTarget(configRoot, config) !== null && !legacyManifest && !Object.values(config.targets ?? {}).some((target) => target.type === "edge_function");
29553
+ const assetsOnly = args.assets_only === true || args.backend !== true && selectFrontendTarget(configRoot, config) !== null;
29213
29554
  if (assetsOnly) {
29214
29555
  if (!selectFrontendTarget(configRoot, config)) {
29215
29556
  throw new Error("Static asset deployment requires a frontend configuration in supacloud.json");
@@ -29219,10 +29560,10 @@ function registerDeployTools(server, http, options = {}) {
29219
29560
  throw new Error("Static asset deployment requires SUPACLOUD_ENV");
29220
29561
  }
29221
29562
  await requireProjectDeploymentProtocol(http, projectRef, environmentId);
29222
- const staged = await stageApplicationWebRelease(http, projectRef, environmentId, configRoot, args.skip_build === true, args.json === true, options.environmentVariables, true);
29223
- if (!staged?.result)
29563
+ const result = await publishConfiguredFrontend(http, projectRef, configRoot, args.skip_build === true, args.json === true, options.environmentVariables);
29564
+ if (!result)
29224
29565
  throw new Error("Static asset deployment did not produce a receipt");
29225
- return staged.result;
29566
+ return result;
29226
29567
  }
29227
29568
  if (legacyManifest) {
29228
29569
  throw new Error(`${DEPLOYMENT_UPGRADE} Replace deploy-manifest.json with the current application delivery configuration; no remote mutation was performed.`);
@@ -29244,7 +29585,6 @@ function registerDeployTools(server, http, options = {}) {
29244
29585
  throw new Error("Application profile must set only one of SUPACLOUD_APPLICATION_CONFIGURATION_PATH or SUPACLOUD_APPLICATION_CONFIGURATION_ID");
29245
29586
  }
29246
29587
  await requireProjectDeploymentProtocol(http, projectRef, environmentId);
29247
- const stagedWeb = await stageApplicationWebRelease(http, projectRef, environmentId, configRoot, args.skip_build === true, args.json === true, options.environmentVariables);
29248
29588
  const result = await options.applicationRelease({
29249
29589
  action: "release",
29250
29590
  ref: projectRef,
@@ -29255,7 +29595,6 @@ function registerDeployTools(server, http, options = {}) {
29255
29595
  ...configurationId ? { configuration_id: configurationId } : {},
29256
29596
  ...application.manifestPath ? { manifest_path: application.manifestPath } : {},
29257
29597
  ...args.skip_build === true ? { skip_build: true } : {},
29258
- ...stagedWeb?.request === undefined ? {} : { web_release: stagedWeb.request },
29259
29598
  ...args.json === true ? { format: "json", json: true } : {}
29260
29599
  });
29261
29600
  return result;
@@ -29265,17 +29604,17 @@ function registerDeployTools(server, http, options = {}) {
29265
29604
  }
29266
29605
 
29267
29606
  // src/shared/tools/remote-dev-tools.ts
29268
- import { existsSync as existsSync14 } from "node:fs";
29607
+ import { existsSync as existsSync15 } from "node:fs";
29269
29608
  import { readFile as readFile7 } from "node:fs/promises";
29270
29609
  import { readdir as readdir7 } from "node:fs/promises";
29271
29610
  import { createHash as createHash10 } from "node:crypto";
29272
- import { join as join18, resolve as resolve23 } from "node:path";
29611
+ import { join as join19, resolve as resolve24 } from "node:path";
29273
29612
  import { compileProject as compileProject2 } from "@supacloud/compiler";
29274
29613
  var SAFE_TOKEN = /^[A-Za-z0-9._:@/+,-]+$/;
29275
29614
  var SAFE_REMOTE_ROOT = /^\/(?:[A-Za-z0-9._-]+\/)*[A-Za-z0-9._-]+\/?$/;
29276
29615
  var remoteDevToolSchema = {
29277
- action: withDescription(stringEnum(["sync", "watch", "status", "migrate"]), "Remote development action"),
29278
- target: optional(stringEnum(["db", "functions", "frontend", "project"]), "Sync target (default: project)"),
29616
+ action: withDescription(stringEnum2(["sync", "watch", "status", "migrate"]), "Remote development action"),
29617
+ target: optional(stringEnum2(["db", "functions", "frontend", "project"]), "Sync target (default: project)"),
29279
29618
  project_dir: optional(String2(), "Local project directory (default: current directory)"),
29280
29619
  remote_root: optional(String2(), "Remote development root"),
29281
29620
  remote_host: optional(String2(), "Remote test server host"),
@@ -29304,8 +29643,8 @@ async function runProcess(executable, args, cwd) {
29304
29643
  function resolveDrizzleCommand(root, configured) {
29305
29644
  if (configured?.trim())
29306
29645
  return configured.trim();
29307
- const local = join18(root, "node_modules", ".bin", "drizzle-kit");
29308
- return existsSync14(local) ? local : "drizzle-kit";
29646
+ const local = join19(root, "node_modules", ".bin", "drizzle-kit");
29647
+ return existsSync15(local) ? local : "drizzle-kit";
29309
29648
  }
29310
29649
  function toolFailed(value) {
29311
29650
  if (!value || typeof value !== "object")
@@ -29315,14 +29654,14 @@ function toolFailed(value) {
29315
29654
  return result.isError === true || content.some((chunk) => chunk !== null && typeof chunk === "object" && ("text" in chunk) && typeof chunk.text === "string" && chunk.text.trimStart().startsWith("❌"));
29316
29655
  }
29317
29656
  async function migrateDatabase(args, options) {
29318
- const root = resolve23(String(args.project_dir || options.cwd || process.cwd()));
29657
+ const root = resolve24(String(args.project_dir || options.cwd || process.cwd()));
29319
29658
  const projectConfig = await readProjectConfig(root);
29320
29659
  const config = projectConfig.dev || {};
29321
29660
  const database = config.database || {};
29322
29661
  const execute = options.execute || ((command, commandArgs, cwd) => runProcess(command, commandArgs, cwd));
29323
- const drizzleConfig = resolve23(root, String(args.drizzle_config || database.drizzleConfig || "drizzle.config.ts"));
29662
+ const drizzleConfig = resolve24(root, String(args.drizzle_config || database.drizzleConfig || "drizzle.config.ts"));
29324
29663
  const migrationsDir = String(args.migrations_dir || database.migrationsDir || "supabase/migrations");
29325
- if (!existsSync14(drizzleConfig))
29664
+ if (!existsSync15(drizzleConfig))
29326
29665
  throw new Error(`Drizzle config not found: ${drizzleConfig}`);
29327
29666
  const generated = await execute(resolveDrizzleCommand(root, typeof args.drizzle_bin === "string" ? args.drizzle_bin : database.drizzleBin), ["generate", "--config", drizzleConfig], root);
29328
29667
  if (generated.exitCode !== 0)
@@ -29340,8 +29679,8 @@ async function migrateDatabase(args, options) {
29340
29679
  return { ok: true, mode: "dev", action: "migrate", generated: true, applied: true, migrations_dir: migrationsDir, result: applied };
29341
29680
  }
29342
29681
  async function readProjectConfig(root) {
29343
- const file = join18(root, "supacloud.json");
29344
- if (!existsSync14(file))
29682
+ const file = join19(root, "supacloud.json");
29683
+ if (!existsSync15(file))
29345
29684
  return {};
29346
29685
  try {
29347
29686
  const parsed = JSON.parse(await readFile7(file, "utf8"));
@@ -29359,7 +29698,7 @@ async function sourceFingerprint(root) {
29359
29698
  const visit = async (directory) => {
29360
29699
  const entries = await readdir7(directory, { withFileTypes: true });
29361
29700
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
29362
- const path = join18(directory, entry.name);
29701
+ const path = join19(directory, entry.name);
29363
29702
  if (entry.isDirectory() && ![".git", "node_modules", "dist", "generated", ".supacloud"].includes(entry.name))
29364
29703
  await visit(path);
29365
29704
  else if (entry.isFile())
@@ -29387,19 +29726,19 @@ function remoteRoot(value) {
29387
29726
  }
29388
29727
  function targetDirectory(root, target, functionSlug, config = {}) {
29389
29728
  if (target === "db")
29390
- return join18(root, "supabase", "migrations");
29729
+ return join19(root, "supabase", "migrations");
29391
29730
  if (target === "functions") {
29392
29731
  const targets = config["targets"];
29393
29732
  const entries = targets && typeof targets === "object" ? Object.values(targets) : [];
29394
29733
  const match = entries.find((entry) => entry !== null && typeof entry === "object" && ("type" in entry) && entry.type === "edge_function" && (!("root" in entry) || typeof entry.root === "string") && (!("slug" in entry) || typeof entry.slug === "string") && (!functionSlug || ("slug" in entry) && entry.slug === functionSlug));
29395
- const base = match?.root ? resolve23(root, match.root) : join18(root, "supabase", "functions");
29396
- return match?.root ? base : functionSlug ? join18(base, safeToken(functionSlug, "function")) : base;
29734
+ const base = match?.root ? resolve24(root, match.root) : join19(root, "supabase", "functions");
29735
+ return match?.root ? base : functionSlug ? join19(base, safeToken(functionSlug, "function")) : base;
29397
29736
  }
29398
29737
  if (target === "frontend") {
29399
29738
  const targets = config["targets"];
29400
29739
  const entries = targets && typeof targets === "object" ? Object.values(targets) : [];
29401
29740
  const match = entries.find((entry) => entry !== null && typeof entry === "object" && ("type" in entry) && entry.type === "frontend" && (!("root" in entry) || typeof entry.root === "string"));
29402
- return match?.root ? resolve23(root, match.root) : join18(root, "apps", "web");
29741
+ return match?.root ? resolve24(root, match.root) : join19(root, "apps", "web");
29403
29742
  }
29404
29743
  return root;
29405
29744
  }
@@ -29438,10 +29777,10 @@ function reloadCommand(config, target, projectRef, functionSlug) {
29438
29777
  return [...command.split(" "), "--project-ref", safeToken(projectRef || "test", "project_ref"), "--target", targetConfigPath(target), ...functionSlug ? ["--function", safeToken(functionSlug, "function")] : []];
29439
29778
  }
29440
29779
  function sshArgs(connection, remoteCommand) {
29441
- return ["-p", String(connection.port), "-i", resolve23(connection.key), "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=yes", `${connection.user}@${connection.host}`, ...remoteCommand];
29780
+ return ["-p", String(connection.port), "-i", resolve24(connection.key), "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=yes", `${connection.user}@${connection.host}`, ...remoteCommand];
29442
29781
  }
29443
29782
  function rsyncArgs(connection, source, destination, excludes, deleteRemote) {
29444
- const args = ["-az", "--checksum", "--partial", "--protect-args", "-e", `ssh -p ${connection.port} -i ${resolve23(connection.key)} -o BatchMode=yes -o StrictHostKeyChecking=yes`];
29783
+ const args = ["-az", "--checksum", "--partial", "--protect-args", "-e", `ssh -p ${connection.port} -i ${resolve24(connection.key)} -o BatchMode=yes -o StrictHostKeyChecking=yes`];
29445
29784
  if (deleteRemote)
29446
29785
  args.push("--delete-delay");
29447
29786
  for (const exclude of excludes) {
@@ -29453,17 +29792,17 @@ function rsyncArgs(connection, source, destination, excludes, deleteRemote) {
29453
29792
  return args;
29454
29793
  }
29455
29794
  async function syncOnce(args, options) {
29456
- const root = resolve23(String(args.project_dir || options.cwd || process.cwd()));
29795
+ const root = resolve24(String(args.project_dir || options.cwd || process.cwd()));
29457
29796
  const projectConfig = await readProjectConfig(root);
29458
29797
  const config = projectConfig.dev || {};
29459
29798
  const target = String(args.target || "project");
29460
29799
  const source = targetDirectory(root, target, typeof args.function === "string" ? args.function : undefined, projectConfig);
29461
- if (!existsSync14(source))
29800
+ if (!existsSync15(source))
29462
29801
  throw new Error(`Dev source directory not found: ${source}`);
29463
29802
  let compiled = false;
29464
29803
  if (config.compile === true && target !== "db") {
29465
- const compileRoot = resolve23(root, config.compileRoot || ".");
29466
- const compileOutDir = resolve23(root, config.compileOutDir || "generated");
29804
+ const compileRoot = resolve24(root, config.compileRoot || ".");
29805
+ const compileOutDir = resolve24(root, config.compileOutDir || "generated");
29467
29806
  const compilation = await compileProject2({
29468
29807
  rootDir: compileRoot,
29469
29808
  outDir: compileOutDir,
@@ -29501,7 +29840,7 @@ function registerRemoteDevTools(server, options = {}) {
29501
29840
  if (["production", "prod"].includes((options.environment || "").toLowerCase()))
29502
29841
  throw new Error("Remote dev mode is forbidden for production environments");
29503
29842
  if (args.action === "status") {
29504
- const root = resolve23(String(args.project_dir || options.cwd || process.cwd()));
29843
+ const root = resolve24(String(args.project_dir || options.cwd || process.cwd()));
29505
29844
  const config = await readDevConfig(root);
29506
29845
  const connection = connectionArgs(options, config, args);
29507
29846
  const execute = options.execute || ((command, commandArgs, cwd) => runProcess(command, commandArgs, cwd));
@@ -29515,7 +29854,7 @@ function registerRemoteDevTools(server, options = {}) {
29515
29854
  return { content: [{ type: "text", text: JSON.stringify(await migrateDatabase(args, options), null, 2) }] };
29516
29855
  }
29517
29856
  if (args.action === "watch") {
29518
- const root = resolve23(String(args.project_dir || options.cwd || process.cwd()));
29857
+ const root = resolve24(String(args.project_dir || options.cwd || process.cwd()));
29519
29858
  const interval = Math.max(100, Math.min(1e4, Number(args.interval_ms || 300)));
29520
29859
  let fingerprint = "";
29521
29860
  let lastSync = null;
@@ -29535,15 +29874,211 @@ function registerRemoteDevTools(server, options = {}) {
29535
29874
  }
29536
29875
 
29537
29876
  // src/shared/tools/developer-experience-tools.ts
29538
- import { existsSync as existsSync15 } from "node:fs";
29877
+ import { existsSync as existsSync17 } from "node:fs";
29539
29878
  import { mkdir as mkdir7, readdir as readdir8, readFile as readFile8, writeFile as writeFile7 } from "node:fs/promises";
29540
- import { dirname as dirname12, join as join19, resolve as resolve24 } from "node:path";
29879
+ import { dirname as dirname13, join as join20, resolve as resolve26 } from "node:path";
29880
+
29881
+ // src/shared/context.ts
29882
+ import { existsSync as existsSync16, readFileSync as readFileSync11 } from "node:fs";
29883
+ import { homedir as homedir3 } from "node:os";
29884
+ import { resolve as resolve25 } from "node:path";
29885
+ var CORE_CONTEXT_KEYS2 = [
29886
+ "SUPABASE_URL",
29887
+ "SUPABASE_SERVICE_ROLE_KEY",
29888
+ "SUPACLOUD_API_URL",
29889
+ "SUPACLOUD_MANAGEMENT_API_URL",
29890
+ "MANAGEMENT_API_URL",
29891
+ "SUPACLOUD_API_TOKEN",
29892
+ "SUPACLOUD_PROJECT_REF",
29893
+ "X_PROJECT_REF",
29894
+ "SUPACLOUD_HOST",
29895
+ "SUPACLOUD_APPLICATION_ID",
29896
+ "SUPACLOUD_APPLICATION_ENVIRONMENT_ID",
29897
+ "SUPACLOUD_APPLICATION_CONFIGURATION_PATH",
29898
+ "SUPACLOUD_APPLICATION_CONFIGURATION_ID",
29899
+ "SUPACLOUD_APPLICATION_MANIFEST_PATH"
29900
+ ];
29901
+ function unquotedEnvValue2(rawValue) {
29902
+ const value = rawValue.trim();
29903
+ const quote = value[0];
29904
+ return quote && (quote === '"' || quote === "'") && value.endsWith(quote) ? value.slice(1, -1) : value;
29905
+ }
29906
+ function parseEnvFile2(contents) {
29907
+ const values = {};
29908
+ for (const rawLine of contents.split(/\r?\n/)) {
29909
+ const line = rawLine.trim();
29910
+ if (!line || line.startsWith("#"))
29911
+ continue;
29912
+ const match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
29913
+ if (!match)
29914
+ continue;
29915
+ values[match[1]] = unquotedEnvValue2(match[2]);
29916
+ }
29917
+ return values;
29918
+ }
29919
+ function readEnvFile2(path, required) {
29920
+ if (!existsSync16(path)) {
29921
+ if (required)
29922
+ throw new Error(`SupaCloud environment file not found: ${path}`);
29923
+ return {};
29924
+ }
29925
+ try {
29926
+ return parseEnvFile2(readFileSync11(path, "utf8"));
29927
+ } catch (error) {
29928
+ const message = error instanceof Error ? error.message : String(error);
29929
+ throw new Error(`Failed to read SupaCloud environment file ${path}: ${message}`);
29930
+ }
29931
+ }
29932
+ function canonicalApiOrigin2(value) {
29933
+ const candidate = value.trim();
29934
+ if (!candidate)
29935
+ return "";
29936
+ try {
29937
+ const url = new URL(candidate);
29938
+ const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
29939
+ const allowedProtocol = url.protocol === "https:" || url.protocol === "http:" && loopback;
29940
+ const exactOrigin = candidate === url.origin || candidate === `${url.origin}/`;
29941
+ return allowedProtocol && !url.username && !url.password && exactOrigin && url.pathname === "/" && !url.search && !url.hash ? url.origin : "";
29942
+ } catch (error) {
29943
+ if (error instanceof TypeError)
29944
+ return "";
29945
+ throw error;
29946
+ }
29947
+ }
29948
+ function hostFromUrl2(value) {
29949
+ return value ? new URL(value).hostname : "";
29950
+ }
29951
+ function inferProjectRefFromSupabaseUrl2(value) {
29952
+ if (!value)
29953
+ return "";
29954
+ return new URL(value).hostname.match(/^([a-z0-9-]+)\.api\./i)?.[1] ?? "";
29955
+ }
29956
+ function sourceCredentialScope2(values, explicitApiUrl, supabaseUrl) {
29957
+ const hasManagementContext = Boolean(explicitApiUrl.trim() || values.SUPACLOUD_API_TOKEN?.trim() || values.SUPACLOUD_HOST?.trim());
29958
+ if (hasManagementContext)
29959
+ return "management";
29960
+ return supabaseUrl || values.SUPABASE_SERVICE_ROLE_KEY?.trim() ? "project_application" : "incomplete";
29961
+ }
29962
+ function sourceProjectCore2(values) {
29963
+ const supabaseUrl = canonicalApiOrigin2(values.SUPABASE_URL || "");
29964
+ const projectRef = (values.SUPACLOUD_PROJECT_REF || values.X_PROJECT_REF || "").trim() || inferProjectRefFromSupabaseUrl2(supabaseUrl);
29965
+ const explicitApiUrl = values.SUPACLOUD_API_URL || values.SUPACLOUD_MANAGEMENT_API_URL || values.MANAGEMENT_API_URL || "";
29966
+ const credentialScope = sourceCredentialScope2(values, explicitApiUrl, supabaseUrl);
29967
+ const managementUrl = explicitApiUrl || (values.SUPACLOUD_HOST ? `http://${values.SUPACLOUD_HOST}:9090` : "");
29968
+ const apiUrl = credentialScope === "management" ? canonicalApiOrigin2(managementUrl) : "";
29969
+ const apiToken = credentialScope === "management" ? values.SUPACLOUD_API_TOKEN || "" : "";
29970
+ return { apiUrl, apiToken, projectRef, supabaseUrl, credentialScope };
29971
+ }
29972
+ function processValues2(env) {
29973
+ return Object.fromEntries(Object.entries(env).filter((entry) => entry[1] !== undefined));
29974
+ }
29975
+ function hasProcessContext2(env) {
29976
+ return CORE_CONTEXT_KEYS2.some((key) => Object.hasOwn(env, key));
29977
+ }
29978
+ function completeProjectContext2(values) {
29979
+ const core = sourceProjectCore2(values);
29980
+ if (core.credentialScope === "project_application") {
29981
+ return Boolean(core.supabaseUrl && values.SUPABASE_SERVICE_ROLE_KEY?.trim() && core.projectRef);
29982
+ }
29983
+ return Boolean(core.apiUrl && core.apiToken && core.projectRef);
29984
+ }
29985
+ function parseInsecureTls2(values) {
29986
+ for (const key of ["SUPACLOUD_TLS_VERIFY", "SUPACLOUD_TLS_INSECURE"]) {
29987
+ const value = values[key]?.trim().toLowerCase();
29988
+ if (value === undefined)
29989
+ continue;
29990
+ if (["1", "true", "yes", "on"].includes(value))
29991
+ return key === "SUPACLOUD_TLS_INSECURE";
29992
+ if (["0", "false", "no", "off"].includes(value))
29993
+ return key === "SUPACLOUD_TLS_VERIFY";
29994
+ throw new Error(`${key} must be a boolean (true or false)`);
29995
+ }
29996
+ return true;
29997
+ }
29998
+ function namedEnvironmentSource2(cwd, selector) {
29999
+ const environment = normalizeEnvironmentName(selector);
30000
+ const path = resolve25(cwd, `.env.supacloud.${selector}`);
30001
+ const values = readEnvFile2(path, true);
30002
+ if (values.SUPACLOUD_ENV && normalizeEnvironmentName(values.SUPACLOUD_ENV) !== environment) {
30003
+ throw new Error(`SUPACLOUD_ENV in ${path} does not match selector ${selector}`);
30004
+ }
30005
+ return { values, kind: "named_env_file", path, environment };
30006
+ }
30007
+ function explicitEnvironmentSource2(cwd, envFile) {
30008
+ const path = resolve25(cwd, envFile);
30009
+ const values = readEnvFile2(path, true);
30010
+ if (!values.SUPACLOUD_ENV)
30011
+ throw new Error(`SUPACLOUD_ENV is required in ${path}`);
30012
+ return {
30013
+ values,
30014
+ kind: "explicit_env_file",
30015
+ path,
30016
+ environment: normalizeEnvironmentName(values.SUPACLOUD_ENV)
30017
+ };
30018
+ }
30019
+ function contextSource2(env, cwd, selection) {
30020
+ if (selection.environmentName && selection.envFile)
30021
+ throw new Error("Environment selectors are mutually exclusive");
30022
+ if (selection.environmentName)
30023
+ return namedEnvironmentSource2(cwd, selection.environmentName);
30024
+ if (selection.envFile)
30025
+ return explicitEnvironmentSource2(cwd, selection.envFile);
30026
+ if (env.SUPACLOUD_ENV) {
30027
+ const environment = normalizeEnvironmentName(env.SUPACLOUD_ENV);
30028
+ const values = processValues2(env);
30029
+ if (completeProjectContext2(values)) {
30030
+ return { values, kind: "process_env", path: null, environment };
30031
+ }
30032
+ return namedEnvironmentSource2(cwd, env.SUPACLOUD_ENV);
30033
+ }
30034
+ if (hasProcessContext2(env)) {
30035
+ return { values: processValues2(env), kind: "process_env", path: null, environment: "" };
30036
+ }
30037
+ const path = resolve25(cwd, ".env");
30038
+ const values = readEnvFile2(path, false);
30039
+ const environment = values.SUPACLOUD_ENV ? normalizeEnvironmentName(values.SUPACLOUD_ENV) : "";
30040
+ return { values, kind: Object.keys(values).length ? "legacy_dotenv" : "none", path: existsSync16(path) ? path : null, environment };
30041
+ }
30042
+ function resolveSupaCloudContext2(env = process.env, cwd = process.cwd(), selection = {}) {
30043
+ const source = contextSource2(env, cwd, selection);
30044
+ const core = sourceProjectCore2(source.values);
30045
+ const host = source.values.SUPACLOUD_HOST || hostFromUrl2(core.apiUrl || core.supabaseUrl);
30046
+ const readOnly = env.SUPACLOUD_READ_ONLY === "true" || source.values.SUPACLOUD_READ_ONLY === "true";
30047
+ const insecureTls = parseInsecureTls2(source.values);
30048
+ return {
30049
+ host,
30050
+ sshUser: env.SUPACLOUD_SSH_USER ?? "root",
30051
+ sshPort: parseInt(env.SUPACLOUD_SSH_PORT ?? "22", 10),
30052
+ sshKey: env.SUPACLOUD_SSH_KEY ?? resolve25(homedir3(), ".ssh", "id_rsa"),
30053
+ sshPass: env.SUPACLOUD_SSH_PASS ?? "",
30054
+ apiUrl: core.apiUrl,
30055
+ apiToken: core.apiToken,
30056
+ projectRef: core.projectRef,
30057
+ readOnly,
30058
+ environment: source.environment,
30059
+ production: source.environment === "prod" || source.environment === "production",
30060
+ inferredSupabaseUrl: core.supabaseUrl,
30061
+ inferredServiceRoleKey: source.values.SUPABASE_SERVICE_ROLE_KEY || "",
30062
+ credentialScope: core.credentialScope,
30063
+ source: source.kind,
30064
+ sourcePath: source.path,
30065
+ environmentVariables: source.values,
30066
+ applicationId: source.values.SUPACLOUD_APPLICATION_ID?.trim() || "",
30067
+ applicationEnvironmentId: source.values.SUPACLOUD_APPLICATION_ENVIRONMENT_ID?.trim() || source.environment,
30068
+ applicationConfigurationPath: source.values.SUPACLOUD_APPLICATION_CONFIGURATION_PATH?.trim() || "",
30069
+ applicationConfigurationId: source.values.SUPACLOUD_APPLICATION_CONFIGURATION_ID?.trim() || "",
30070
+ applicationManifestPath: source.values.SUPACLOUD_APPLICATION_MANIFEST_PATH?.trim() || "",
30071
+ insecureTls
30072
+ };
30073
+ }
30074
+
30075
+ // src/shared/tools/developer-experience-tools.ts
29541
30076
  var initToolSchema = {
29542
30077
  root: optional(String2(), "Project directory to initialize (default: current directory)"),
29543
30078
  json: optional(Boolean2(), "Emit one machine-readable JSON result")
29544
30079
  };
29545
30080
  var environmentToolSchema = {
29546
- action: withDescription(stringEnum(["list", "check"]), "Environment profile inspection action"),
30081
+ action: withDescription(stringEnum2(["list", "check"]), "Environment profile inspection action"),
29547
30082
  root: optional(String2(), "Project directory containing .env.supacloud.* files"),
29548
30083
  json: optional(Boolean2(), "Emit one machine-readable JSON result")
29549
30084
  };
@@ -29589,8 +30124,8 @@ function outputDirectoryFor(manifest) {
29589
30124
  return "dist";
29590
30125
  }
29591
30126
  async function readPackageManifest(root) {
29592
- const path = join19(root, "package.json");
29593
- if (!existsSync15(path))
30127
+ const path = join20(root, "package.json");
30128
+ if (!existsSync17(path))
29594
30129
  return {};
29595
30130
  try {
29596
30131
  const parsed = JSON.parse(await readFile8(path, "utf8"));
@@ -29620,8 +30155,8 @@ async function writeNewFile(path, content) {
29620
30155
  }
29621
30156
  }
29622
30157
  async function ensureGitignore(root) {
29623
- const path = join19(root, ".gitignore");
29624
- const existing = existsSync15(path) ? await readFile8(path, "utf8") : "";
30158
+ const path = join20(root, ".gitignore");
30159
+ const existing = existsSync17(path) ? await readFile8(path, "utf8") : "";
29625
30160
  const required = [".env.supacloud.*", "!.env.supacloud.*.example"];
29626
30161
  const missing = required.filter((entry) => !existing.split(/\r?\n/).includes(entry));
29627
30162
  if (missing.length === 0)
@@ -29635,10 +30170,10 @@ async function ensureGitignore(root) {
29635
30170
  return true;
29636
30171
  }
29637
30172
  async function initializeProject(request, options) {
29638
- const root = resolve24(request.root || options.cwd || process.cwd());
30173
+ const root = resolve26(request.root || options.cwd || process.cwd());
29639
30174
  await mkdir7(root, { recursive: true });
29640
- const configPath = join19(root, "supacloud.json");
29641
- if (existsSync15(configPath)) {
30175
+ const configPath = join20(root, "supacloud.json");
30176
+ if (existsSync17(configPath)) {
29642
30177
  throw new Error(`supacloud.json already exists in ${root}; init never overwrites project configuration`);
29643
30178
  }
29644
30179
  const manifest = await readPackageManifest(root);
@@ -29665,10 +30200,10 @@ async function initializeProject(request, options) {
29665
30200
  throw error;
29666
30201
  }
29667
30202
  created.push("supacloud.json");
29668
- if (await writeNewFile(join19(root, ".env.supacloud.test.example"), TEST_PROFILE)) {
30203
+ if (await writeNewFile(join20(root, ".env.supacloud.test.example"), TEST_PROFILE)) {
29669
30204
  created.push(".env.supacloud.test.example");
29670
30205
  }
29671
- if (await writeNewFile(join19(root, ".env.supacloud.production.example"), PRODUCTION_PROFILE)) {
30206
+ if (await writeNewFile(join20(root, ".env.supacloud.production.example"), PRODUCTION_PROFILE)) {
29672
30207
  created.push(".env.supacloud.production.example");
29673
30208
  }
29674
30209
  if (await ensureGitignore(root))
@@ -29679,7 +30214,7 @@ async function initializeProject(request, options) {
29679
30214
  root,
29680
30215
  created,
29681
30216
  detected: {
29682
- package_json: existsSync15(join19(root, "package.json")),
30217
+ package_json: existsSync17(join20(root, "package.json")),
29683
30218
  build_script: hasBuildScript,
29684
30219
  output_directory: outputDirectory
29685
30220
  },
@@ -29713,7 +30248,7 @@ async function environmentProfiles(root) {
29713
30248
  });
29714
30249
  return entries.filter((entry) => entry.isFile()).map((entry) => {
29715
30250
  const name = profileNameFromFile(entry.name);
29716
- return name ? { name, path: join19(root, entry.name), exists: true } : null;
30251
+ return name ? { name, path: join20(root, entry.name), exists: true } : null;
29717
30252
  }).filter((profile) => profile !== null).sort((left, right) => left.name.localeCompare(right.name));
29718
30253
  }
29719
30254
  function statusMissing(context) {
@@ -29748,11 +30283,11 @@ function deployMissing(context, application, hasFrontend = false) {
29748
30283
  }
29749
30284
  async function inspectProfile(profile) {
29750
30285
  try {
29751
- const context = resolveSupaCloudContext({}, dirname12(profile.path), {
30286
+ const context = resolveSupaCloudContext2({}, dirname13(profile.path), {
29752
30287
  environmentName: profile.name
29753
30288
  });
29754
30289
  const status = statusMissing(context);
29755
- const configText = await readFile8(join19(dirname12(profile.path), "supacloud.json"), "utf8").catch((error) => {
30290
+ const configText = await readFile8(join20(dirname13(profile.path), "supacloud.json"), "utf8").catch((error) => {
29756
30291
  if (error instanceof Error && "code" in error && error.code === "ENOENT")
29757
30292
  return "{}";
29758
30293
  throw error;
@@ -29796,7 +30331,7 @@ async function inspectProfile(profile) {
29796
30331
  }
29797
30332
  }
29798
30333
  async function inspectEnvironment(request, options) {
29799
- const root = resolve24(request.root || options.cwd || process.cwd());
30334
+ const root = resolve26(request.root || options.cwd || process.cwd());
29800
30335
  const profiles = await environmentProfiles(root);
29801
30336
  if (request.action === "list") {
29802
30337
  const active = options.activeContext?.environment || null;
@@ -29852,10 +30387,133 @@ function registerDeveloperExperienceTools(server, options = {}) {
29852
30387
  registerTool(server, "init", "Create SupaCloud deployment configuration and non-secret environment examples without overwriting project files.", initToolSchema, (request) => initializeProject(request, options));
29853
30388
  registerTool(server, "env", "List or locally validate named SupaCloud environment profiles without contacting the platform or printing secrets.", environmentToolSchema, (request) => inspectEnvironment(request, options));
29854
30389
  }
30390
+
30391
+ // src/shared/tools/project-deployment.ts
30392
+ var operation3 = "applications.project_deploy";
30393
+ var scopeSchema2 = _Object_({
30394
+ project_ref: String2({ pattern: "^[a-z0-9-]{1,20}$" }),
30395
+ application_id: ApplicationIdSchema,
30396
+ environment_id: ApplicationIdSchema
30397
+ });
30398
+ var acceptedSchema2 = _Object_({
30399
+ schema: Literal("supacloud.project-deployment-accepted.v1"),
30400
+ ok: Literal(true),
30401
+ status: Literal("accepted"),
30402
+ deployment_id: ApplicationDeployRequestSchema.properties.mutation_id,
30403
+ ...scopeSchema2.properties,
30404
+ release_id: ApplicationDeployRequestSchema.properties.release_id,
30405
+ request_sha256: String2({ pattern: "^[a-f0-9]{64}$" }),
30406
+ replayed: Boolean2()
30407
+ }, { additionalProperties: false });
30408
+ var statusSchema2 = _Object_({
30409
+ schema: Literal("supacloud.project-deployment-status.v1"),
30410
+ deployment_id: ApplicationDeployRequestSchema.properties.mutation_id,
30411
+ status: Union([
30412
+ Literal("pending"),
30413
+ Literal("running"),
30414
+ Literal("succeeded"),
30415
+ Literal("failed_retryable"),
30416
+ Literal("failed_terminal"),
30417
+ Literal("outcome_unknown")
30418
+ ]),
30419
+ phase: Union([
30420
+ Literal("queued"),
30421
+ Literal("prepared"),
30422
+ Literal("migrating"),
30423
+ Literal("activating"),
30424
+ Literal("binding-web"),
30425
+ Literal("verifying")
30426
+ ]),
30427
+ operation: Literal("application.release.deploy"),
30428
+ ...scopeSchema2.properties,
30429
+ receipt: Optional(Unknown()),
30430
+ failure_code: Optional(String2({ pattern: "^[A-Z][A-Z0-9_]{0,63}$" }))
30431
+ }, { additionalProperties: false });
30432
+ function responseReceipt(data, expected) {
30433
+ if (!data || typeof data !== "object" || Array.isArray(data) || !("ok" in data) || data.ok !== true) {
30434
+ throw new Error("Invalid project deployment response");
30435
+ }
30436
+ const { ok: _ok, ...receipt } = data;
30437
+ return parseApplicationDeployResult(receipt, expected);
30438
+ }
30439
+ async function runProjectDeployment(http, args) {
30440
+ const scope = { project_ref: args.ref, application_id: args.id, environment_id: args.environment_id };
30441
+ const request = {
30442
+ mutation_id: args.mutation_id,
30443
+ release_id: args.release_id,
30444
+ ...args.configuration_id === undefined ? {} : { configuration_id: args.configuration_id },
30445
+ ...args.configuration_write === undefined ? {} : { configuration_write: args.configuration_write },
30446
+ ...args.expected_plan_sha256 === undefined ? {} : { expected_plan_sha256: args.expected_plan_sha256 },
30447
+ ...args.approved_migration_digest === undefined ? {} : { approved_migration_digest: args.approved_migration_digest },
30448
+ ...args.web_release === undefined ? {} : { web_release: args.web_release }
30449
+ };
30450
+ if (!Check2(scopeSchema2, scope) || !Check2(ApplicationDeployRequestSchema, request) || request.configuration_id !== undefined && request.configuration_write !== undefined && request.configuration_id !== request.configuration_write.configuration_id) {
30451
+ return releaseControlFailure(operation3, "INVALID_RESPONSE", null);
30452
+ }
30453
+ const expected = { ...scope, request };
30454
+ const identity = {
30455
+ ...scope,
30456
+ deployment_id: request.mutation_id,
30457
+ mutation_id: request.mutation_id,
30458
+ release_id: request.release_id
30459
+ };
30460
+ const endpoint = `/v1/projects/${encodeURIComponent(scope.project_ref)}/environments/${encodeURIComponent(scope.environment_id)}/deployments`;
30461
+ const { mutation_id, ...candidate } = request;
30462
+ const result = await http.post(endpoint, {
30463
+ ...candidate,
30464
+ deployment_id: mutation_id,
30465
+ application_id: scope.application_id
30466
+ }, { timeoutMs: 120000, maxJsonBytes: 131072, responseTimeoutMs: 30000 });
30467
+ if (result.ok && result.status === 202) {
30468
+ const accepted = result.data;
30469
+ if (Check2(acceptedSchema2, accepted) && accepted.deployment_id === mutation_id && accepted.project_ref === scope.project_ref && accepted.application_id === scope.application_id && accepted.environment_id === scope.environment_id && accepted.release_id === request.release_id && accepted.request_sha256 === mutationRequestFingerprint(request)) {
30470
+ return releaseControlSuccess(operation3, {
30471
+ status: "accepted",
30472
+ deployment_id: mutation_id,
30473
+ project_ref: scope.project_ref,
30474
+ application_id: scope.application_id,
30475
+ environment_id: scope.environment_id,
30476
+ release_id: request.release_id,
30477
+ replayed: accepted.replayed,
30478
+ status_path: `${endpoint}/${mutation_id}`
30479
+ });
30480
+ }
30481
+ } else if (result.ok && result.status === 200) {
30482
+ try {
30483
+ return releaseControlSuccess(operation3, {
30484
+ ...responseReceipt(result.data, expected),
30485
+ deployment_id: mutation_id
30486
+ });
30487
+ } catch {}
30488
+ } else if (!result.ok && !result.transportError && !result.responseReadError && result.status < 500 && result.status !== 408) {
30489
+ return releaseControlMutationFailure(operation3, result, identity);
30490
+ }
30491
+ const observed = await http.get(`${endpoint}/${encodeURIComponent(mutation_id)}`, {
30492
+ retry: false,
30493
+ totalTimeoutMs: 30000,
30494
+ maxJsonBytes: 131072
30495
+ });
30496
+ try {
30497
+ if (!observed.ok || observed.status !== 200 || !Check2(statusSchema2, observed.data))
30498
+ throw new Error;
30499
+ const state = observed.data;
30500
+ 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")
30501
+ throw new Error;
30502
+ const receipt = parseApplicationDeployResult(state.receipt, expected);
30503
+ return releaseControlSuccess(operation3, {
30504
+ ...receipt,
30505
+ deployment_id: mutation_id,
30506
+ replayed: true,
30507
+ execution_performed: false
30508
+ });
30509
+ } catch {
30510
+ return releaseControlFailure(operation3, "OUTCOME_UNKNOWN", result.transportError ? null : result.status, identity);
30511
+ }
30512
+ }
29855
30513
  // package.json
29856
30514
  var package_default = {
29857
30515
  name: "@supacloud/cli",
29858
- version: "0.97.1",
30516
+ version: "0.99.0",
29859
30517
  description: "Project-scoped CLI for SupaCloud users",
29860
30518
  type: "module",
29861
30519
  main: "./dist/index.js",
@@ -29889,7 +30547,7 @@ var package_default = {
29889
30547
  build: "bun build src/index.ts --outdir dist --target node --conditions=supacloud-source --external @supacloud/compiler --external @supacloud/db && bun run build:tool-runtime && bun run build:types",
29890
30548
  "build:tool-runtime": "bun build src/tool-runtime.ts --outdir dist --target node --external typebox && tsc -p tsconfig.tool-runtime.json --emitDeclarationOnly",
29891
30549
  "build:types": "tsc --emitDeclarationOnly",
29892
- "check:release-bundle": "node scripts/check-release-bundle.mjs",
30550
+ "check:release-bundle": "bun scripts/check-release-bundle.mjs",
29893
30551
  pretypecheck: "bun install --cwd ../delivery --frozen-lockfile && bun install --cwd ../compiler --frozen-lockfile && bun run --cwd ../compiler build && bun run prepare:db",
29894
30552
  prepublishOnly: "bun run build && bun run check:release-bundle",
29895
30553
  typecheck: "tsc --noEmit"
@@ -30081,10 +30739,10 @@ function captureTools(register) {
30081
30739
  function printHelp(context) {
30082
30740
  const autoLink = context.inferredSupabaseUrl ? `Project context: ${context.inferredSupabaseUrl} (${context.source})` : "Project context: not detected";
30083
30741
  console.error(`
30084
- ╔═══════════════════════════════════════════════════════════╗
30085
- ║ supacloud ║
30086
- ║ Project CLI for SupaCloud users ║
30087
- ╚═══════════════════════════════════════════════════════════╝
30742
+ \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
30743
+ \u2551 supacloud \u2551
30744
+ \u2551 Project CLI for SupaCloud users \u2551
30745
+ \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
30088
30746
 
30089
30747
  USAGE
30090
30748
 
@@ -30302,7 +30960,7 @@ function createCliTools(context, confirmProduction) {
30302
30960
  {
30303
30961
  type: "text",
30304
30962
  text: [
30305
- "⚠️ Project commands need a Management API context.",
30963
+ "\u26A0\uFE0F Project commands need a Management API context.",
30306
30964
  "",
30307
30965
  "Provide one of these sources:",
30308
30966
  " - --env <name> for .env.supacloud.<name>",
@@ -30330,7 +30988,7 @@ function createCliTools(context, confirmProduction) {
30330
30988
  content: [
30331
30989
  {
30332
30990
  type: "text",
30333
- text: `⚠️ This command requires Management API context. Run \`${preferredCommand} status\` to inspect current detection.`
30991
+ text: `\u26A0\uFE0F This command requires Management API context. Run \`${preferredCommand} status\` to inspect current detection.`
30334
30992
  }
30335
30993
  ]
30336
30994
  })
@@ -30363,7 +31021,7 @@ function createCliTools(context, confirmProduction) {
30363
31021
  isError: true,
30364
31022
  content: [{
30365
31023
  type: "text",
30366
- text: `⚠️ Deploy requires Management API context. Run \`${preferredCommand} status\` to inspect current detection.`
31024
+ text: `\u26A0\uFE0F Deploy requires Management API context. Run \`${preferredCommand} status\` to inspect current detection.`
30367
31025
  }]
30368
31026
  })
30369
31027
  };
@@ -30391,7 +31049,7 @@ function createCliTools(context, confirmProduction) {
30391
31049
  {
30392
31050
  type: "text",
30393
31051
  text: [
30394
- `⚠️ No project context found for ${preferredCommand}.`,
31052
+ `\u26A0\uFE0F No project context found for ${preferredCommand}.`,
30395
31053
  "",
30396
31054
  `${preferredCommand} remote tools require Management API credentials.`,
30397
31055
  "Provide one of these sources:",