@supacloud/cli 0.97.1 → 0.98.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)
@@ -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"] },
@@ -12664,11 +12693,11 @@ function registerDatabaseTools(server, http, config = {}) {
12664
12693
  const allActions = localOnly ? localActions : readOnly ? remoteActions : [...remoteActions, ...writeActions];
12665
12694
  registerTool(server, "database", `Database operations: query, schema, RLS, migrations, stats.
12666
12695
  Actions: ${allActions.join(", ")}${localOnly ? " (local-only mode)" : readOnly ? " (read-only mode)" : ""}`, {
12667
- action: withDescription(stringEnum(allActions), "Action"),
12696
+ action: withDescription(stringEnum2(allActions), "Action"),
12668
12697
  ref: projectRef ? Optional(String2()) : optional(String2(), "Project ref"),
12669
12698
  sql: optional(String2(), "[query/execute/apply_migration/lint_migrations/lint] SQL statement"),
12670
12699
  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"),
12700
+ 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
12701
  admin: optional(Boolean2(), "[query/execute] Admin execution flag (required when mode=admin)"),
12673
12702
  dir: optional(String2(), "[push_migrations/baseline_migrations/lint_migrations/lint/rebase_migrations] Directory containing .sql migration files (default: supabase/migrations)"),
12674
12703
  dry_run: optional(Boolean2(), "[push_migrations/baseline_migrations] Preview changes without applying them"),
@@ -12684,7 +12713,7 @@ Actions: ${allActions.join(", ")}${localOnly ? " (local-only mode)" : readOnly ?
12684
12713
  name: optional(String2(), "[apply_migration] Migration name"),
12685
12714
  delivery_manifest: optional(String2(), "[delivery_migration_plan] Local immutable delivery manifest"),
12686
12715
  delivery_target: optional(String2(), "[delivery_migration_plan] Target name from the delivery manifest"),
12687
- import_source: optional(stringEnum(["hasura", "nhost"]), "[migration_import] Source platform"),
12716
+ import_source: optional(stringEnum2(["hasura", "nhost"]), "[migration_import] Source platform"),
12688
12717
  metadata_file: optional(String2(), "[migration_import] Hasura metadata JSON file"),
12689
12718
  output_dir: optional(String2(), "[migration_import] Output migration directory when write=true"),
12690
12719
  write: optional(Boolean2(), "[migration_import] Write the reviewed plan to output_dir; never applies SQL"),
@@ -12693,7 +12722,7 @@ Actions: ${allActions.join(", ")}${localOnly ? " (local-only mode)" : readOnly ?
12693
12722
  baseline_name: optional(String2(), "[rebase_migrations] New baseline migration name"),
12694
12723
  retain_after_version: optional(String2(), "[rebase_migrations] Retain migrations newer than this version"),
12695
12724
  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)"),
12725
+ policy_mode: optional(stringEnum2(["deny_all", "owner"]), "[create_table_rls] RLS policy mode (default: deny_all)"),
12697
12726
  owner_column: optional(String2(), "[create_table_rls owner] UUID owner column matched to auth.uid()"),
12698
12727
  extension: optional(String2(), "[enable_extension/disable_extension] PostgreSQL extension or pgflow")
12699
12728
  }, async (args) => {
@@ -14055,7 +14084,7 @@ function formatProviders(data) {
14055
14084
  function registerAuthTools(server, http) {
14056
14085
  registerTool(server, "auth", `Auth & OAuth provider management, controlled user lookup, and login-link generation.
14057
14086
  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([
14087
+ action: withDescription(stringEnum2([
14059
14088
  "list_users",
14060
14089
  "get_user",
14061
14090
  "generate_link",
@@ -14080,7 +14109,7 @@ Actions: list_users, get_user, generate_link, list_providers, get_provider, conf
14080
14109
  per_page: optional(Integer({ minimum: 1, maximum: 100 }), "[list_users] Users per page (1-100)"),
14081
14110
  search: optional(String2(), "[list_users] Search user email, phone, or UUID"),
14082
14111
  email_like: optional(String2(), "[list_users] Search user email or phone"),
14083
- type: optional(stringEnum(AUTH_LINK_TYPES), "[generate_link] magiclink, recovery, or invite"),
14112
+ type: optional(stringEnum2(AUTH_LINK_TYPES), "[generate_link] magiclink, recovery, or invite"),
14084
14113
  email: optional(String2(), "[generate_link] User email"),
14085
14114
  redirect_to: optional(String2(), "[generate_link] Absolute HTTPS or loopback callback"),
14086
14115
  provider: optional(String2(), "[*_provider] Provider name (github, google, wechat, etc.)"),
@@ -14390,7 +14419,7 @@ async function deleteReleaseCanaryClient(http, ref, id, redirectUri) {
14390
14419
  }
14391
14420
  function registerOAuthClientTools(server, http) {
14392
14421
  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"),
14422
+ action: withDescription(stringEnum2(["list", "get", "create", "delete"]), "OAuth client action"),
14394
14423
  ref: withDescription(String2(), "Central SupAuth project ref"),
14395
14424
  client_id: optional(String2(), "[get/delete] Exact release-canary public OAuth client ID"),
14396
14425
  redirect_uri: optional(String2(), "[create/delete] Exact HTTPS or port-bound RFC 8252 loopback callback")
@@ -14782,7 +14811,7 @@ function executeBucketAction(action, http, args) {
14782
14811
  function registerStorageTools(server, http) {
14783
14812
  registerTool(server, "storage", `S3/MinIO storage management.
14784
14813
  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([
14814
+ action: withDescription(stringEnum2([
14786
14815
  "status",
14787
14816
  "list_buckets",
14788
14817
  "get_bucket",
@@ -15911,7 +15940,7 @@ async function activateFunctionVersion(http, args, readOnly = false) {
15911
15940
  function registerAdvancedTools(server, http, environment = process.env, options = {}) {
15912
15941
  registerTool(server, "edge_functions", `Edge Function management (Deno/Bun serverless). Source deploys are bundled; verified prebuilt artifacts stay byte-exact.
15913
15942
  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"),
15943
+ action: withDescription(stringEnum2(["list", "get_config", "deploy", "deploy_bundle", "config", "source", "activate", "delete", "check", "scaffold"]), "Action"),
15915
15944
  ref: optional(String2(), "Project ref (not used by scaffold)"),
15916
15945
  slug: optional(String2(), "[get_config/deploy/deploy_bundle/config/source/activate/delete/check] Function name"),
15917
15946
  version: withDescription(functionVersionSchema, "[source/activate] Existing immutable Function version; source requires a positive version"),
@@ -15926,7 +15955,7 @@ Actions: list, get_config, deploy, deploy_bundle, config, source, activate, dele
15926
15955
  minify: optional(Boolean2(), "[deploy/deploy_bundle] Minify bundle"),
15927
15956
  verify_jwt: optional(Boolean2(), "[deploy/deploy_bundle/config] Set JWT verification for this function"),
15928
15957
  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")),
15958
+ framework: optional(withDescription(stringEnum2(["fetch", "elysia", "hono", "sveltekit-function"]), "[deploy/deploy_bundle/config/scaffold] Fetch framework adapter profile")),
15930
15959
  capabilities: optional(functionCapabilitiesSchema, "[deploy/deploy_bundle/config] Host capabilities: secrets, outbound_hosts, bindings, background"),
15931
15960
  limits: optional(functionLimitsSchema, "[deploy/deploy_bundle/config] Execution limits in milliseconds/bytes"),
15932
15961
  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 +16132,7 @@ ${deployCheck.err}`;
16103
16132
  });
16104
16133
  registerTool(server, "secrets", `Project secrets (environment variables for Edge Functions).
16105
16134
  Actions: list, upsert, delete`, {
16106
- action: withDescription(stringEnum(["list", "upsert", "delete"]), "Action"),
16135
+ action: withDescription(stringEnum2(["list", "upsert", "delete"]), "Action"),
16107
16136
  ref: withDescription(String2(), "Project ref"),
16108
16137
  secrets: withDescription(secretsSchema, "[upsert] Secret list as JSON array or KEY=VALUE,KEY2=VALUE2"),
16109
16138
  "from-env": withDescription(environmentSecretNamesSchema, "[upsert] Comma-separated environment variable names; values are read from the selected CLI environment"),
@@ -16141,7 +16170,7 @@ Actions: list, upsert, delete`, {
16141
16170
  });
16142
16171
  registerTool(server, "platform", `Platform monitoring, backups, network, and organizations.
16143
16172
  Actions: metrics, list_backups, create_backup, network, update_network, list_orgs, get_org`, {
16144
- action: withDescription(stringEnum([
16173
+ action: withDescription(stringEnum2([
16145
16174
  "metrics",
16146
16175
  "list_backups",
16147
16176
  "create_backup",
@@ -16199,7 +16228,7 @@ ${JSON.stringify(r.data, null, 2)}` : `❌ Failed (${r.status})`;
16199
16228
  });
16200
16229
  registerTool(server, "task_events", `Task lifecycle webhook configuration.
16201
16230
  Actions: register_webhook, unregister_webhook, inspect_webhook`, {
16202
- action: withDescription(stringEnum(["register_webhook", "unregister_webhook", "inspect_webhook"]), "Action"),
16231
+ action: withDescription(stringEnum2(["register_webhook", "unregister_webhook", "inspect_webhook"]), "Action"),
16203
16232
  ref: withDescription(String2(), "Project ref"),
16204
16233
  url: optional(String2(), "[register_webhook] HTTPS webhook URL for task lifecycle events"),
16205
16234
  secret: optional(String2(), "[register_webhook] Optional HMAC secret for webhook verification")
@@ -16235,7 +16264,7 @@ ${JSON.stringify(r.data, null, 2)}` : `❌ Failed (${r.status}): ${JSON.stringif
16235
16264
  });
16236
16265
  registerTool(server, "diagnostics", `Platform and project diagnostics: health checks, diagnostic runs, and repair.
16237
16266
  Actions: list_checks, run_checks, get_run, repair`, {
16238
- action: withDescription(stringEnum(["list_checks", "run_checks", "get_run", "repair"]), "Action"),
16267
+ action: withDescription(stringEnum2(["list_checks", "run_checks", "get_run", "repair"]), "Action"),
16239
16268
  ref: optional(String2(), "Project ref (for project-scoped diagnostics)"),
16240
16269
  run_id: optional(String2(), "[get_run/repair] Diagnostic run ID"),
16241
16270
  check_id: optional(String2(), "[repair] Check result ID to repair")
@@ -16279,81 +16308,320 @@ ${JSON.stringify(r.data, null, 2)}` : `❌ Failed (${r.status}): ${JSON.stringif
16279
16308
  }
16280
16309
 
16281
16310
  // src/shared/tools/frontend-release-control.ts
16282
- import { createHash as createHash5 } from "node:crypto";
16311
+ import { createHash as createHash6 } from "node:crypto";
16283
16312
  import { constants as fsConstants2 } from "node:fs";
16284
16313
  import { open as open2 } from "node:fs/promises";
16285
16314
  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) }] };
16315
+
16316
+ // src/shared/mutation-protocol.ts
16317
+ import { createHash as createHash5 } from "node:crypto";
16318
+ 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}$/;
16319
+ var FINGERPRINT_PATTERN = /^[0-9a-f]{64}$/;
16320
+ var OPERATION_PATTERN = /^[a-z0-9][a-z0-9._:-]{0,127}$/;
16321
+ var RESOURCE_KEY_PATTERN = /^v1\/(?:[a-z0-9][a-z0-9._-]{0,63})\/([A-Za-z0-9_-]{2,171})$/;
16322
+ var RESOURCE_ID_CONTROL_PATTERN = /[\u0000-\u001f\u007f-\u009f]/u;
16323
+ var FAILURE_CODE_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
16324
+ var LEASE_OWNER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:@/-]{0,254}$/;
16325
+ var TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
16326
+ var MAX_STATUS_RESPONSE_BYTES = 196608;
16327
+ var MAX_JSON_DEPTH = 32;
16328
+ var MAX_RESOURCE_ID_BYTES = 128;
16329
+ var FATAL_UTF8_DECODER = new TextDecoder("utf-8", { fatal: true });
16330
+ var MUTATION_STATUSES = new Set([
16331
+ "pending",
16332
+ "running",
16333
+ "succeeded",
16334
+ "failed_retryable",
16335
+ "failed_terminal",
16336
+ "outcome_unknown"
16337
+ ]);
16338
+ var MUTATION_RESPONSE_KEYS = ["project_ref", "mutation"];
16339
+ var MUTATION_KEYS = [
16340
+ "project_ref",
16341
+ "mutation_id",
16342
+ "operation",
16343
+ "resource_key",
16344
+ "request_fingerprint",
16345
+ "principal",
16346
+ "status",
16347
+ "checkpoint",
16348
+ "receipt",
16349
+ "response_status",
16350
+ "failure_code",
16351
+ "lease",
16352
+ "completed_at",
16353
+ "created_at",
16354
+ "updated_at"
16355
+ ];
16356
+ var PRINCIPAL_KEYS = ["type", "id"];
16357
+ var LEASE_KEYS = ["owner", "expires_at", "fencing_epoch"];
16358
+ function isMutationId(candidate) {
16359
+ return typeof candidate === "string" && MUTATION_ID_PATTERN.test(candidate);
16299
16360
  }
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
- };
16361
+ function assertCanonicalJsonNode(candidate, seen, depth) {
16362
+ if (candidate === null || typeof candidate === "string" || typeof candidate === "boolean")
16363
+ return;
16364
+ if (typeof candidate === "number" && Number.isFinite(candidate))
16365
+ return;
16366
+ if (typeof candidate !== "object" || depth > MAX_JSON_DEPTH) {
16367
+ throw new Error("Mutation request must be bounded JSON");
16368
+ }
16369
+ if (seen.has(candidate))
16370
+ throw new Error("Mutation request must not contain cycles");
16371
+ seen.add(candidate);
16372
+ const prototype = Object.getPrototypeOf(candidate);
16373
+ const plainRecord = prototype === Object.prototype || prototype === null;
16374
+ const children = Array.isArray(candidate) ? candidate : plainRecord ? Object.values(candidate) : null;
16375
+ if (!children)
16376
+ throw new Error("Mutation request must contain only JSON objects and arrays");
16377
+ for (const child of children)
16378
+ assertCanonicalJsonNode(child, seen, depth + 1);
16379
+ seen.delete(candidate);
16308
16380
  }
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]);
16381
+ function stableStringifyNode(jsonValue) {
16382
+ if (jsonValue === null || typeof jsonValue !== "object") {
16383
+ const serialized = JSON.stringify(jsonValue);
16384
+ if (serialized === undefined)
16385
+ throw new Error("Mutation request must contain exact JSON values");
16386
+ return serialized;
16387
+ }
16388
+ if (Array.isArray(jsonValue))
16389
+ return `[${jsonValue.map(stableStringifyNode).join(",")}]`;
16390
+ const record = jsonValue;
16391
+ const fields = Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringifyNode(record[key])}`);
16392
+ return `{${fields.join(",")}}`;
16313
16393
  }
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
- };
16394
+ function mutationRequestFingerprint(normalizedRequest) {
16395
+ assertCanonicalJsonNode(normalizedRequest, new WeakSet, 0);
16396
+ return createHash5("sha256").update(stableStringifyNode(normalizedRequest)).digest("hex");
16345
16397
  }
16346
- function releaseEnvelope(candidate, expected) {
16347
- if (!candidate || typeof candidate !== "object" || Array.isArray(candidate))
16398
+ function objectRecord3(candidate) {
16399
+ return candidate && typeof candidate === "object" && !Array.isArray(candidate) ? candidate : null;
16400
+ }
16401
+ function exactRecord(candidate, keys) {
16402
+ const record = objectRecord3(candidate);
16403
+ if (!record || Object.keys(record).length !== keys.length)
16348
16404
  return null;
16349
- const envelope = candidate;
16405
+ return keys.every((key) => Object.hasOwn(record, key)) ? record : null;
16406
+ }
16407
+ function emptyProjection(candidate) {
16408
+ const record = objectRecord3(candidate);
16409
+ return record && Object.keys(record).length === 0 ? record : null;
16410
+ }
16411
+ function canonicalTimestamp(candidate) {
16412
+ if (typeof candidate !== "string" || !TIMESTAMP_PATTERN.test(candidate))
16413
+ return false;
16414
+ const milliseconds = Date.parse(candidate);
16415
+ return Number.isFinite(milliseconds) && new Date(milliseconds).toISOString() === candidate;
16416
+ }
16417
+ function nullableTimestamp(candidate) {
16418
+ return candidate === null || canonicalTimestamp(candidate);
16419
+ }
16420
+ function safePrincipal(candidate) {
16421
+ const principal = exactRecord(candidate, PRINCIPAL_KEYS);
16422
+ if (!principal || !["master", "admin", "project"].includes(String(principal["type"])))
16423
+ return null;
16424
+ if (typeof principal["id"] !== "string" || !principal["id"] || principal["id"].length > 320 || principal["id"].trim() !== principal["id"] || /[\u0000-\u001f\u007f]/u.test(principal["id"]))
16425
+ return null;
16426
+ return { type: principal["type"], id: principal["id"] };
16427
+ }
16428
+ function safeLease(candidate) {
16429
+ const lease = exactRecord(candidate, LEASE_KEYS);
16430
+ if (!lease || lease["owner"] !== null && (typeof lease["owner"] !== "string" || !LEASE_OWNER_PATTERN.test(lease["owner"])))
16431
+ return null;
16432
+ if (!nullableTimestamp(lease["expires_at"]) || !Number.isSafeInteger(lease["fencing_epoch"]) || Number(lease["fencing_epoch"]) < 0)
16433
+ return null;
16434
+ return {
16435
+ owner: lease["owner"],
16436
+ expires_at: lease["expires_at"],
16437
+ fencing_epoch: Number(lease["fencing_epoch"])
16438
+ };
16439
+ }
16440
+ function safeResponseStatus(candidate) {
16441
+ if (candidate === null)
16442
+ return null;
16443
+ return Number.isInteger(candidate) && Number(candidate) >= 100 && Number(candidate) <= 599 ? Number(candidate) : undefined;
16444
+ }
16445
+ function validLeaseState(status, lease) {
16446
+ const running = status === "running";
16447
+ return running ? lease.owner !== null && lease.expires_at !== null && lease.fencing_epoch > 0 : lease.owner === null && lease.expires_at === null;
16448
+ }
16449
+ function validMutationLifecycle(mutation, receipt, responseStatus) {
16450
+ const terminal = ["succeeded", "failed_terminal", "outcome_unknown"].includes(String(mutation["status"]));
16451
+ if (mutation["completed_at"] !== null !== terminal)
16452
+ return false;
16453
+ if (mutation["status"] === "succeeded") {
16454
+ return receipt !== null && responseStatus !== null && responseStatus >= 200 && responseStatus < 300 && mutation["failure_code"] === null;
16455
+ }
16456
+ if (mutation["status"] === "failed_terminal") {
16457
+ return receipt !== null && typeof mutation["failure_code"] === "string";
16458
+ }
16459
+ if (mutation["status"] === "failed_retryable" || mutation["status"] === "outcome_unknown") {
16460
+ return receipt !== null && typeof mutation["failure_code"] === "string";
16461
+ }
16462
+ if (mutation["status"] === "pending") {
16463
+ return receipt === null && responseStatus === null && mutation["failure_code"] === null;
16464
+ }
16465
+ return true;
16466
+ }
16467
+ function canonicalMutationResourceKey(candidate) {
16468
+ if (typeof candidate !== "string")
16469
+ return false;
16470
+ const match = RESOURCE_KEY_PATTERN.exec(candidate);
16471
+ if (!match)
16472
+ return false;
16473
+ const encodedResourceId = match[1];
16474
+ const resourceIdBytes = Buffer.from(encodedResourceId, "base64url");
16475
+ if (resourceIdBytes.byteLength < 1 || resourceIdBytes.byteLength > MAX_RESOURCE_ID_BYTES || resourceIdBytes.toString("base64url") !== encodedResourceId)
16476
+ return false;
16477
+ let resourceId;
16478
+ try {
16479
+ resourceId = FATAL_UTF8_DECODER.decode(resourceIdBytes);
16480
+ } catch (decodeError) {
16481
+ if (decodeError instanceof TypeError)
16482
+ return false;
16483
+ throw decodeError;
16484
+ }
16485
+ return resourceId.trim() === resourceId && !RESOURCE_ID_CONTROL_PATTERN.test(resourceId) && Buffer.from(resourceId, "utf8").equals(resourceIdBytes);
16486
+ }
16487
+ function validMutationIdentity(mutation) {
16488
+ if (!isMutationId(mutation["mutation_id"]) || typeof mutation["project_ref"] !== "string")
16489
+ return false;
16490
+ if (typeof mutation["operation"] !== "string" || !OPERATION_PATTERN.test(mutation["operation"]))
16491
+ return false;
16492
+ if (mutation["resource_key"] !== null && !canonicalMutationResourceKey(mutation["resource_key"]))
16493
+ return false;
16494
+ return typeof mutation["request_fingerprint"] === "string" && FINGERPRINT_PATTERN.test(mutation["request_fingerprint"]);
16495
+ }
16496
+ function validMutationTerminalFields(mutation) {
16497
+ if (typeof mutation["status"] !== "string" || !MUTATION_STATUSES.has(mutation["status"]))
16498
+ return false;
16499
+ if (mutation["failure_code"] !== null && (typeof mutation["failure_code"] !== "string" || !FAILURE_CODE_PATTERN.test(mutation["failure_code"])))
16500
+ return false;
16501
+ return nullableTimestamp(mutation["completed_at"]) && canonicalTimestamp(mutation["created_at"]) && canonicalTimestamp(mutation["updated_at"]);
16502
+ }
16503
+ function safeMutationStatus(candidate) {
16504
+ const mutation = exactRecord(candidate, MUTATION_KEYS);
16505
+ if (!mutation || !validMutationIdentity(mutation) || !validMutationTerminalFields(mutation))
16506
+ return null;
16507
+ const principal = safePrincipal(mutation["principal"]);
16508
+ const checkpoint = emptyProjection(mutation["checkpoint"]);
16509
+ const receipt = mutation["receipt"] === null ? null : emptyProjection(mutation["receipt"]);
16510
+ const responseStatus = safeResponseStatus(mutation["response_status"]);
16511
+ const lease = safeLease(mutation["lease"]);
16512
+ if (!principal || !checkpoint || mutation["receipt"] !== null && !receipt || responseStatus === undefined || !lease || !validLeaseState(String(mutation["status"]), lease) || !validMutationLifecycle(mutation, receipt, responseStatus))
16513
+ return null;
16514
+ return {
16515
+ project_ref: mutation["project_ref"],
16516
+ mutation_id: mutation["mutation_id"],
16517
+ operation: mutation["operation"],
16518
+ resource_key: mutation["resource_key"],
16519
+ request_fingerprint: mutation["request_fingerprint"],
16520
+ principal,
16521
+ status: mutation["status"],
16522
+ checkpoint,
16523
+ receipt,
16524
+ response_status: responseStatus,
16525
+ failure_code: mutation["failure_code"],
16526
+ lease,
16527
+ completed_at: mutation["completed_at"],
16528
+ created_at: mutation["created_at"],
16529
+ updated_at: mutation["updated_at"]
16530
+ };
16531
+ }
16532
+ function mutationStatusPath(ref, mutationId) {
16533
+ if (!isMutationId(mutationId))
16534
+ throw new Error("'mutation_id' must be a UUIDv4");
16535
+ return `/v1/projects/${projectRefPathSegment(ref, "Mutations")}/mutations/${encodeURIComponent(mutationId)}`;
16536
+ }
16537
+ function mutationStatusResponse(ref, mutationId, payload) {
16538
+ const response = exactRecord(payload, MUTATION_RESPONSE_KEYS);
16539
+ const mutation = safeMutationStatus(response?.["mutation"]);
16540
+ return response?.["project_ref"] === ref && mutation?.project_ref === ref && mutation.mutation_id === mutationId ? mutation : null;
16541
+ }
16542
+ async function fetchMutationStatus(http, ref, mutationId) {
16543
+ const response = await http.get(mutationStatusPath(ref, mutationId), {
16544
+ maxResponseBytes: MAX_STATUS_RESPONSE_BYTES
16545
+ });
16546
+ if (!response.ok) {
16547
+ return { kind: "unavailable", httpStatus: response.transportError ? null : response.status };
16548
+ }
16549
+ const mutation = mutationStatusResponse(ref, mutationId, response.data);
16550
+ return mutation ? { kind: "available", mutation } : { kind: "invalid" };
16551
+ }
16552
+
16553
+ // src/shared/tools/frontend-release-control.ts
16554
+ var PROJECT_REF_PATTERN3 = /^[A-Za-z0-9_-]{1,20}$/u;
16555
+ var DEPLOYMENT_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/u;
16556
+ var RELEASE_ID_PATTERN = /^[0-9a-f]{64}$/u;
16557
+ 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;
16558
+ var TIMESTAMP_PATTERN2 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u;
16559
+ var ARCHIVE_MAX_BYTES = 100 * 1024 * 1024;
16560
+ var ARCHIVE_CHUNK_BYTES = 64 * 1024;
16561
+ var RESPONSE_MAX_BYTES = 1024 * 1024;
16562
+ var UPLOAD_REQUEST_TIMEOUT_MS = 10 * 60000;
16563
+ var MUTATION_RESPONSE_TIMEOUT_MS = 5000;
16564
+ var RELEASE_LIST_LIMIT_MAX = 100;
16565
+ function toolResponse(payload) {
16566
+ return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
16567
+ }
16568
+ function releaseFailure(operation, code, status) {
16569
+ return {
16570
+ isError: true,
16571
+ content: [{
16572
+ type: "text",
16573
+ text: JSON.stringify({ ok: false, operation, error: { code, http_status: status } })
16574
+ }]
16575
+ };
16576
+ }
16577
+ function exactKeys(candidate, keys) {
16578
+ const actual = Object.keys(candidate).sort();
16579
+ const expected = [...keys].sort();
16580
+ return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
16581
+ }
16582
+ function releaseRecord(candidate) {
16583
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate))
16584
+ return null;
16585
+ const record = candidate;
16586
+ const keys = [
16587
+ "schema",
16588
+ "project_ref",
16589
+ "deployment_id",
16590
+ "release_id",
16591
+ "sha256",
16592
+ "tree_sha256",
16593
+ "size_bytes",
16594
+ "file_count",
16595
+ "created_at",
16596
+ "kind",
16597
+ "archive_format"
16598
+ ];
16599
+ 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")
16600
+ return null;
16601
+ return {
16602
+ project_ref: record.project_ref,
16603
+ deployment_id: record.deployment_id,
16604
+ release_id: record.release_id,
16605
+ sha256: record.release_id,
16606
+ tree_sha256: record.tree_sha256,
16607
+ size_bytes: Number(record.size_bytes),
16608
+ file_count: Number(record.file_count),
16609
+ created_at: record.created_at,
16610
+ kind: "prebuilt_static",
16611
+ archive_format: FRONTEND_ARCHIVE_FORMAT
16612
+ };
16613
+ }
16614
+ function releaseEnvelope(candidate, expected) {
16615
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate))
16616
+ return null;
16617
+ const envelope = candidate;
16350
16618
  const release = exactKeys(envelope, ["project_ref", "deployment_id", "release"]) ? releaseRecord(envelope.release) : null;
16351
16619
  if (!release || envelope.project_ref !== expected.projectRef || envelope.deployment_id !== expected.deploymentId || release.project_ref !== expected.projectRef || release.deployment_id !== expected.deploymentId || release.release_id !== expected.releaseId)
16352
16620
  return null;
16353
16621
  return release;
16354
16622
  }
16355
- function canonicalTimestamp(candidate) {
16356
- if (typeof candidate !== "string" || !TIMESTAMP_PATTERN.test(candidate))
16623
+ function canonicalTimestamp2(candidate) {
16624
+ if (typeof candidate !== "string" || !TIMESTAMP_PATTERN2.test(candidate))
16357
16625
  return false;
16358
16626
  const milliseconds = Date.parse(candidate);
16359
16627
  return Number.isFinite(milliseconds) && new Date(milliseconds).toISOString() === candidate;
@@ -16378,7 +16646,7 @@ function releaseInventory(candidate, expected) {
16378
16646
  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
16647
  return null;
16380
16648
  const activeReleaseId = nullableIdentity(record.active_release_id, RELEASE_ID_PATTERN);
16381
- const activeActivationId = nullableIdentity(record.active_activation_id, MUTATION_ID_PATTERN);
16649
+ const activeActivationId = nullableIdentity(record.active_activation_id, MUTATION_ID_PATTERN2);
16382
16650
  const nextCursor = nullableIdentity(record.next_cursor, RELEASE_ID_PATTERN);
16383
16651
  const releases = record.releases.map(releaseRecord);
16384
16652
  if (activeReleaseId === undefined || activeActivationId === undefined || nextCursor === undefined || activeReleaseId === null !== (activeActivationId === null) || releases.some((release) => release === null))
@@ -16414,7 +16682,7 @@ function sameArchiveIdentity(left, right) {
16414
16682
  return left.dev === right.dev && left.ino === right.ino && left.size === right.size && left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs;
16415
16683
  }
16416
16684
  async function archiveSha256(handle, sizeBytes) {
16417
- const hash = createHash5("sha256");
16685
+ const hash = createHash6("sha256");
16418
16686
  const chunk = new Uint8Array(Math.min(sizeBytes, ARCHIVE_CHUNK_BYTES));
16419
16687
  for (let offset = 0;offset < sizeBytes; ) {
16420
16688
  const requested = Math.min(chunk.byteLength, sizeBytes - offset);
@@ -16489,7 +16757,7 @@ async function getFrontendRelease(http, projectRef, deploymentId, releaseId) {
16489
16757
  const response = await http.get(releasePath(projectRef, deploymentId, releaseId), {
16490
16758
  maxJsonBytes: RESPONSE_MAX_BYTES
16491
16759
  });
16492
- const release = response.ok ? releaseEnvelope(response.data, { projectRef, deploymentId, releaseId }) : null;
16760
+ const release = response.ok && response.status === 200 ? releaseEnvelope(response.data, { projectRef, deploymentId, releaseId }) : null;
16493
16761
  if (!release) {
16494
16762
  return releaseReadFailure("frontend.get_release", response);
16495
16763
  }
@@ -16601,7 +16869,7 @@ function stableJson(candidate) {
16601
16869
  return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`).join(",")}}`;
16602
16870
  }
16603
16871
  function activationFingerprint(identity) {
16604
- return createHash5("sha256").update(stableJson({
16872
+ return createHash6("sha256").update(stableJson({
16605
16873
  project_ref: identity.projectRef,
16606
16874
  deployment_id: identity.deploymentId,
16607
16875
  release_id: identity.releaseId,
@@ -16617,25 +16885,8 @@ function publicMutationReceipt(candidate, expected) {
16617
16885
  if (!candidate || typeof candidate !== "object" || Array.isArray(candidate))
16618
16886
  return null;
16619
16887
  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"))
16888
+ const mutation = safeMutationStatus(envelope.mutation);
16889
+ 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
16890
  return null;
16640
16891
  return {
16641
16892
  operation: mutation.operation,
@@ -16670,7 +16921,7 @@ async function activeReleaseReadback(http, identity) {
16670
16921
  return { release: null, status: 200 };
16671
16922
  }
16672
16923
  const releaseRead = await http.get(releasePath(identity.projectRef, identity.deploymentId, identity.releaseId), { maxJsonBytes: RESPONSE_MAX_BYTES });
16673
- const release = releaseRead.ok ? releaseEnvelope(releaseRead.data, {
16924
+ const release = releaseRead.ok && releaseRead.status === 200 ? releaseEnvelope(releaseRead.data, {
16674
16925
  projectRef: identity.projectRef,
16675
16926
  deploymentId: identity.deploymentId,
16676
16927
  releaseId: identity.releaseId
@@ -16762,10 +17013,74 @@ async function publishFrontendRelease(http, projectRef, deploymentId, archivePat
16762
17013
  ...activated.isError || treeMismatch ? { isError: true } : {}
16763
17014
  };
16764
17015
  }
16765
- async function rollbackFrontendRelease(http, projectRef, deploymentId, releaseId) {
17016
+ function boundRollbackResponse(result, input) {
17017
+ const payload = JSON.parse(result.content[0].text);
17018
+ return {
17019
+ ...toolResponse({
17020
+ ...payload,
17021
+ operation: "frontend.rollback",
17022
+ project_ref: input.projectRef,
17023
+ deployment_id: input.deploymentId,
17024
+ target_release_id: input.releaseId,
17025
+ mutation_id: input.mutationId,
17026
+ expected_active_release_id: input.expectedActiveReleaseId,
17027
+ expected_activation_id: input.expectedActivationId,
17028
+ ...!result.isError ? {
17029
+ rollback: true,
17030
+ previous_release_id: input.expectedActiveReleaseId,
17031
+ previous_activation_id: input.expectedActivationId
17032
+ } : {}
17033
+ }),
17034
+ ...result.isError ? { isError: true } : {}
17035
+ };
17036
+ }
17037
+ async function rollbackBoundFrontendRelease(http, input) {
17038
+ const authority = await readActiveFrontendRelease(http, input.projectRef, input.deploymentId);
17039
+ if ("failure" in authority)
17040
+ return boundRollbackResponse(authority.failure, input);
17041
+ const current = authority.inventory;
17042
+ if (current.active_release_id === input.releaseId && current.active_activation_id === input.mutationId) {
17043
+ return boundRollbackResponse(await activationReadback(http, input, input.mutationId, 200), input);
17044
+ }
17045
+ if (current.active_release_id !== input.expectedActiveReleaseId || current.active_activation_id !== input.expectedActivationId) {
17046
+ return boundRollbackResponse(releaseFailure("frontend.rollback", "STALE_AUTHORITY", 409), input);
17047
+ }
17048
+ const target = await getFrontendRelease(http, input.projectRef, input.deploymentId, input.releaseId);
17049
+ if (target.isError)
17050
+ return boundRollbackResponse(target, input);
17051
+ return boundRollbackResponse(await activateFrontendRelease(http, input), input);
17052
+ }
17053
+ async function rollbackFrontendRelease(http, projectRef, deploymentId, releaseId, options = {}) {
16766
17054
  if (releaseId !== undefined && !RELEASE_ID_PATTERN.test(releaseId)) {
16767
17055
  throw new Error("'release_id' must be a SHA-256 digest");
16768
17056
  }
17057
+ const bound = options.expectedActiveReleaseId !== undefined || options.expectedActivationId !== undefined || options.mutationId !== undefined;
17058
+ if (bound) {
17059
+ const { expectedActiveReleaseId, expectedActivationId, mutationId } = options;
17060
+ if (releaseId === undefined || expectedActiveReleaseId === undefined || expectedActivationId === undefined || mutationId === undefined) {
17061
+ throw new Error("'release_id', 'expected_active_release_id', 'expected_activation_id', and 'mutation_id' are required together for a bound rollback");
17062
+ }
17063
+ if (!RELEASE_ID_PATTERN.test(expectedActiveReleaseId)) {
17064
+ throw new Error("'expected_active_release_id' must be a SHA-256 digest");
17065
+ }
17066
+ if (!MUTATION_ID_PATTERN2.test(expectedActivationId)) {
17067
+ throw new Error("'expected_activation_id' must be a UUIDv4");
17068
+ }
17069
+ if (!MUTATION_ID_PATTERN2.test(mutationId)) {
17070
+ throw new Error("'mutation_id' must be a UUIDv4");
17071
+ }
17072
+ if (releaseId === expectedActiveReleaseId || mutationId === expectedActivationId) {
17073
+ throw new Error("Bound rollback requires a different target release and a distinct mutation UUID");
17074
+ }
17075
+ return rollbackBoundFrontendRelease(http, {
17076
+ projectRef,
17077
+ deploymentId,
17078
+ releaseId,
17079
+ expectedActiveReleaseId,
17080
+ expectedActivationId,
17081
+ mutationId
17082
+ });
17083
+ }
16769
17084
  const authority = releaseId === undefined ? await readPreviousFrontendRelease(http, projectRef, deploymentId) : await readActiveFrontendRelease(http, projectRef, deploymentId);
16770
17085
  if ("failure" in authority)
16771
17086
  return authority.failure;
@@ -16834,8 +17149,8 @@ async function readPreviousFrontendRelease(http, projectRef, deploymentId) {
16834
17149
  const data = response.data;
16835
17150
  const value = data && typeof data === "object" && !Array.isArray(data) ? data : null;
16836
17151
  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;
17152
+ const activeActivationId = value ? nullableIdentity(value.active_activation_id, MUTATION_ID_PATTERN2) : undefined;
17153
+ const previousActivationId = value ? nullableIdentity(value.previous_activation_id, MUTATION_ID_PATTERN2) : undefined;
16839
17154
  const previous = value?.previous_release === null ? null : releaseRecord(value?.previous_release);
16840
17155
  if (!response.ok || response.status !== 200 || !value || !exactKeys(value, [
16841
17156
  "schema",
@@ -16862,12 +17177,12 @@ async function readPreviousFrontendRelease(http, projectRef, deploymentId) {
16862
17177
  }
16863
17178
  async function activateFrontendRelease(http, input) {
16864
17179
  const mutationId = input.mutationId;
16865
- if (!MUTATION_ID_PATTERN.test(mutationId))
17180
+ if (!MUTATION_ID_PATTERN2.test(mutationId))
16866
17181
  throw new Error("'mutation_id' must be a UUIDv4");
16867
17182
  if (input.expectedActiveReleaseId !== "absent" && !RELEASE_ID_PATTERN.test(input.expectedActiveReleaseId)) {
16868
17183
  throw new Error("'expected_active_release_id' is invalid");
16869
17184
  }
16870
- if (input.expectedActivationId !== "absent" && !MUTATION_ID_PATTERN.test(input.expectedActivationId)) {
17185
+ if (input.expectedActivationId !== "absent" && !MUTATION_ID_PATTERN2.test(input.expectedActivationId)) {
16871
17186
  throw new Error("'expected_activation_id' is invalid");
16872
17187
  }
16873
17188
  const endpoint = `${releasePath(input.projectRef, input.deploymentId, input.releaseId)}/activate`;
@@ -16880,7 +17195,7 @@ async function activateFrontendRelease(http, input) {
16880
17195
  responseTimeoutMs: MUTATION_RESPONSE_TIMEOUT_MS
16881
17196
  });
16882
17197
  const release = activationReceipt(response.data, { ...input, mutationId });
16883
- if (!response.ok || !release) {
17198
+ if (!response.ok || response.status !== 200 || !release) {
16884
17199
  if (response.status >= 400 && response.status < 500 && response.status !== 408 && !response.transportError) {
16885
17200
  return releaseFailure("frontend.activate_release", "HTTP_ERROR", response.status);
16886
17201
  }
@@ -16901,7 +17216,7 @@ async function activateFrontendRelease(http, input) {
16901
17216
  async function activationReadback(http, input, mutationId, activationStatus) {
16902
17217
  const mutationRead = await http.get(`/v1/projects/${encodeURIComponent(input.projectRef)}/mutations/${mutationId}`, { maxJsonBytes: RESPONSE_MAX_BYTES });
16903
17218
  const mutationEnvelope = mutationRead.data;
16904
- const mutation = mutationRead.ok ? publicMutationReceipt(mutationRead.data, {
17219
+ const mutation = mutationRead.ok && mutationRead.status === 200 ? publicMutationReceipt(mutationRead.data, {
16905
17220
  projectRef: input.projectRef,
16906
17221
  mutationId,
16907
17222
  resourceKey: activationResourceKey(input.deploymentId),
@@ -16912,429 +17227,192 @@ async function activationReadback(http, input, mutationId, activationStatus) {
16912
17227
  }
16913
17228
  const readback = await activeReleaseReadback(http, { ...input, mutationId });
16914
17229
  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;
17230
+ return releaseFailure("frontend.activate_release", "OUTCOME_UNKNOWN", activationStatus);
17231
+ }
17232
+ return toolResponse({
17233
+ project_ref: input.projectRef,
17234
+ deployment_id: input.deploymentId,
17235
+ active_release_id: input.releaseId,
17236
+ activation_id: mutationId,
17237
+ release: readback.release
17238
+ });
17326
17239
  }
17327
- async function fetchMutationStatus(http, ref, mutationId) {
17328
- const response = await http.get(mutationStatusPath(ref, mutationId), {
17329
- maxResponseBytes: MAX_STATUS_RESPONSE_BYTES
17240
+
17241
+ // src/shared/tools/frontend-tools.ts
17242
+ function registerFrontendTools(server, http) {
17243
+ registerTool(server, "frontend", `Frontend hosting and immutable prebuilt releases. Supports: static, react, vue, svelte, sveltekit, sveltekit-static, nextjs, nuxt, astro.
17244
+ 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.`, {
17245
+ action: withDescription(stringEnum2([
17246
+ "list",
17247
+ "get",
17248
+ "create",
17249
+ "update",
17250
+ "delete",
17251
+ "build_logs",
17252
+ "add_domain",
17253
+ "remove_domain",
17254
+ "set_env",
17255
+ "list_frameworks",
17256
+ "list_records",
17257
+ "list_releases",
17258
+ "get_active_release",
17259
+ "get_release",
17260
+ "rollback"
17261
+ ]), "Action"),
17262
+ ref: optional(String2(), "Project ref"),
17263
+ id: optional(String2(), "Deployment ID"),
17264
+ name: optional(String2(), "[create] Deployment name"),
17265
+ framework: optional(String2(), "[create] Framework (static|react|vue|svelte|sveltekit|sveltekit-static|nextjs|nuxt|astro)"),
17266
+ domain: optional(String2(), "[create/update/add_domain/remove_domain] Custom domain"),
17267
+ build_command: optional(String2(), "[create/update] Build command override"),
17268
+ output_dir: optional(String2(), "[create/update] Output directory override"),
17269
+ install_command: optional(String2(), "[create/update] Install command override"),
17270
+ node_version: optional(String2(), "[create/update] Node.js version"),
17271
+ health_check_path: optional(String2(), "[create/update] SSR readiness path (default: /)"),
17272
+ env_vars: optional(Record(String2(), String2()), "[create/update/set_env] Environment variables"),
17273
+ release_id: optional(String2(), "[get_release] SHA-256 release ID; [rollback] optional, defaults to journal-verified previous release"),
17274
+ cursor: optional(String2(), "[list_releases] Last release SHA-256 cursor"),
17275
+ limit: optional(Number2(), "[list_releases] Page size, 1-100 (default 50)")
17276
+ }, async (args) => {
17277
+ const {
17278
+ action,
17279
+ ref,
17280
+ id,
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
+ release_id,
17291
+ cursor,
17292
+ limit
17293
+ } = args;
17294
+ const need = (field, value) => {
17295
+ if (value === undefined || value === null || value === "")
17296
+ throw new Error(`'${field}' required for '${action}'`);
17297
+ };
17298
+ const ok = (res) => res.ok ? JSON.stringify(res.data, null, 2) : `❌ Failed (${res.status}): ${JSON.stringify(res.data)}`;
17299
+ let text;
17300
+ switch (action) {
17301
+ case "list":
17302
+ need("ref", ref);
17303
+ text = ok(await http.get(`/v1/projects/${ref}/frontend/deployments`));
17304
+ break;
17305
+ case "get":
17306
+ need("ref", ref);
17307
+ need("id", id);
17308
+ text = ok(await http.get(`/v1/projects/${ref}/frontend/deployments/${id}`));
17309
+ break;
17310
+ case "create":
17311
+ need("ref", ref);
17312
+ need("name", name);
17313
+ need("framework", framework);
17314
+ text = ok(await http.post(`/v1/projects/${ref}/frontend/deployments`, {
17315
+ name,
17316
+ framework,
17317
+ domain,
17318
+ build_command,
17319
+ output_dir,
17320
+ install_command,
17321
+ node_version,
17322
+ health_check_path,
17323
+ env_vars
17324
+ }));
17325
+ break;
17326
+ case "update":
17327
+ need("ref", ref);
17328
+ need("id", id);
17329
+ text = ok(await http.patch(`/v1/projects/${ref}/frontend/deployments/${id}`, {
17330
+ name,
17331
+ domain,
17332
+ build_command,
17333
+ output_dir,
17334
+ install_command,
17335
+ node_version,
17336
+ health_check_path,
17337
+ env_vars
17338
+ }));
17339
+ break;
17340
+ case "delete":
17341
+ need("ref", ref);
17342
+ need("id", id);
17343
+ text = (await http.delete(`/v1/projects/${ref}/frontend/deployments/${id}`)).ok ? `✅ Deleted` : `❌ Failed`;
17344
+ break;
17345
+ case "build_logs":
17346
+ need("ref", ref);
17347
+ need("id", id);
17348
+ const lr = await http.get(`/v1/projects/${ref}/frontend/deployments/${id}/logs`);
17349
+ 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})`;
17350
+ break;
17351
+ case "add_domain":
17352
+ need("ref", ref);
17353
+ need("id", id);
17354
+ need("domain", domain);
17355
+ text = (await http.post(`/v1/projects/${ref}/frontend/deployments/${id}/domains`, { domain })).ok ? `✅ Domain ${domain} added` : `❌ Failed`;
17356
+ break;
17357
+ case "remove_domain":
17358
+ need("ref", ref);
17359
+ need("id", id);
17360
+ need("domain", domain);
17361
+ text = (await http.delete(`/v1/projects/${ref}/frontend/deployments/${id}/domains/${domain}`)).ok ? `✅ Domain ${domain} removed` : `❌ Failed`;
17362
+ break;
17363
+ case "set_env":
17364
+ need("ref", ref);
17365
+ need("id", id);
17366
+ need("env_vars", env_vars);
17367
+ text = (await http.put(`/v1/projects/${ref}/frontend/deployments/${id}/env`, { env_vars })).ok ? `✅ Set ${Object.keys(env_vars).length} env vars` : `❌ Failed`;
17368
+ break;
17369
+ case "list_frameworks":
17370
+ text = ok(await http.get("/v1/projects/_/frontend/frameworks"));
17371
+ break;
17372
+ case "list_records":
17373
+ need("ref", ref);
17374
+ need("id", id);
17375
+ text = ok(await http.get(`/v1/projects/${ref}/frontend/deployments/${id}/records`));
17376
+ break;
17377
+ case "list_releases":
17378
+ need("ref", ref);
17379
+ need("id", id);
17380
+ return listFrontendReleases(http, ref, id, cursor, limit);
17381
+ case "get_release":
17382
+ need("ref", ref);
17383
+ need("id", id);
17384
+ need("release_id", release_id);
17385
+ return getFrontendRelease(http, ref, id, release_id);
17386
+ case "get_active_release":
17387
+ need("ref", ref);
17388
+ need("id", id);
17389
+ return getActiveFrontendRelease(http, ref, id);
17390
+ case "rollback":
17391
+ need("ref", ref);
17392
+ need("id", id);
17393
+ return rollbackFrontendRelease(http, ref, id, release_id);
17394
+ default:
17395
+ text = `❌ Unknown action`;
17396
+ }
17397
+ return { content: [{ type: "text", text }] };
17330
17398
  });
17331
- if (!response.ok) {
17332
- return { kind: "unavailable", httpStatus: response.transportError ? null : response.status };
17333
- }
17334
- const mutation = mutationStatusResponse(ref, mutationId, response.data);
17335
- return mutation ? { kind: "available", mutation } : { kind: "invalid" };
17336
17399
  }
17337
17400
 
17401
+ // src/shared/tools/application-schemas.ts
17402
+ var ApplicationIdSchema2 = ApplicationIdSchema;
17403
+ var ApplicationReleaseIdSchema2 = ApplicationReleaseIdSchema;
17404
+ var ApplicationReleaseRecordSchema2 = ApplicationReleaseRecordSchema;
17405
+ var ApplicationConfigurationIdSchema2 = ApplicationConfigurationIdSchema;
17406
+ var ApplicationActivationIdSchema2 = ApplicationActivationIdSchema;
17407
+ var ApplicationActivationWriteSchema2 = ApplicationActivationWriteSchema;
17408
+ var ApplicationActivationResultSchema2 = ApplicationActivationResultSchema;
17409
+ var ApplicationActivationRetirementResultSchema2 = ApplicationActivationRetirementResultSchema;
17410
+ var ApplicationRollbackSnapshotSchema2 = ApplicationRollbackSnapshotSchema;
17411
+ var ApplicationActivationHistoryCursorSchema2 = ApplicationActivationHistoryCursorSchema;
17412
+ var ApplicationPromotionPlanSchema2 = ApplicationPromotionPlanSchema;
17413
+ var ApplicationDeployRequestSchema2 = ApplicationDeployRequestSchema;
17414
+ var ApplicationDeployWebReleaseSchema2 = ApplicationDeployWebReleaseSchema;
17415
+
17338
17416
  // src/shared/tools/application-workflow-schemas.ts
17339
17417
  var closed9 = { additionalProperties: false };
17340
17418
  var ref5 = String2({ pattern: "^[A-Za-z0-9_-]{1,20}$" });
@@ -17545,13 +17623,13 @@ var APPLICATION_PREVIEW_FIELDS = {
17545
17623
  preview_id: optional(previewIdSchema, "[get_preview/reconcile_preview/cleanup_preview] Preview receipt ID; create/plan may pin a stable ID"),
17546
17624
  branch_ref: optional(branchRefSchema, "[get_preview_plan] Proposed branch ref; create assigns its own"),
17547
17625
  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"),
17626
+ data_mode: optional(stringEnum2(["schema_only", "full_clone"]), "[get_preview_plan/create_preview] Default schema_only; full_clone copies rows"),
17549
17627
  ttl_seconds: optional(ttlSecondsSchema, "[get_preview_plan/create_preview] Lifetime from 300 to 604800 seconds; creation defaults to the platform TTL"),
17550
17628
  wait: optional(Boolean2(), "[create_preview/get_preview] Wait for verified readiness of this preview only"),
17551
17629
  timeout_seconds: optional(waitSecondsSchema, "[create_preview/get_preview] Observation budget with wait, from 1 to 3600 seconds (default 300)")
17552
17630
  };
17553
17631
  var strict = { additionalProperties: false };
17554
- var phase2 = stringEnum(["pending", "ready", "failed", "cleaned"]);
17632
+ var phase2 = stringEnum2(["pending", "ready", "failed", "cleaned"]);
17555
17633
  var check5 = String2({ pattern: "^[a-z][a-z0-9_]{0,63}$" });
17556
17634
  var checks2 = _Array_(check5, { maxItems: 64, uniqueItems: true });
17557
17635
  var revision = Union([ApplicationConfigurationIdSchema2, Null()]);
@@ -17563,10 +17641,10 @@ var receiptSchema = _Object_({
17563
17641
  environment_id: ApplicationIdSchema2,
17564
17642
  release_id: ApplicationReleaseIdSchema2,
17565
17643
  expires_at: Optional(Union([timestampSchema, Null()])),
17566
- status: stringEnum(["planned", "provisioning", "ready", "failed", "cleaned"]),
17644
+ status: stringEnum2(["planned", "provisioning", "ready", "failed", "cleaned"]),
17567
17645
  resources: _Object_({
17568
17646
  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),
17647
+ database_branch: _Object_({ status: phase2, branch_ref: branchRefSchema, data_mode: stringEnum2(["schema_only", "full_clone"]) }, strict),
17570
17648
  queue_namespace: _Object_({ status: phase2, namespace: String2({ minLength: 1, maxLength: 128 }) }, strict),
17571
17649
  storage_namespace: _Object_({ status: phase2, namespace: branchRefSchema }, strict),
17572
17650
  test_secret: _Object_({ status: phase2, name: String2({ pattern: "^[A-Z0-9_-]{1,128}$" }), value_issued: Literal(false) }, strict),
@@ -17874,7 +17952,7 @@ async function applicationPreviewAction(http, args, project) {
17874
17952
 
17875
17953
  // src/shared/tools/application-tools.ts
17876
17954
  var APPLICATION_TOOL_SCHEMA = {
17877
- action: withDescription(stringEnum([
17955
+ action: withDescription(stringEnum2([
17878
17956
  "list_releases",
17879
17957
  "get_release",
17880
17958
  "upload_release",
@@ -19118,7 +19196,7 @@ function registerUserProjectCliTools(server, http, options = {}) {
19118
19196
  const { projectRef } = options;
19119
19197
  registerTool(server, "project", `Project-scoped inspection and developer operations.
19120
19198
  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([
19199
+ action: withDescription(stringEnum2([
19122
19200
  "list",
19123
19201
  "get",
19124
19202
  "endpoints",
@@ -19138,7 +19216,7 @@ Actions: list (Admin guidance), get, endpoints, pause, restore, health, logs, ap
19138
19216
  "update_background_settings"
19139
19217
  ]), "Action to perform"),
19140
19218
  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"),
19219
+ log_type: optional(stringEnum2(["all", "auth", "database", "api"]), "[logs] Filter by service"),
19142
19220
  task_id: optional(String2(), "[task_detail/task_cancel/task_retry] Task ID"),
19143
19221
  limit: optional(Number2(), "[tasks/dlq] Max items to return"),
19144
19222
  concurrency: optional(Number2(), "[update_background_settings] Max concurrent background tasks"),
@@ -19339,7 +19417,7 @@ function registerQueueTools(server, http, options = {}) {
19339
19417
  const { projectRef } = options;
19340
19418
  registerTool(server, "queue", `Message queue operations for task-based messaging.
19341
19419
  Actions: list, stats, list_messages, dlq, get_message, send, receive, ack, release, fail, retry, delete_message, get_settings, update_settings`, {
19342
- action: withDescription(stringEnum([
19420
+ action: withDescription(stringEnum2([
19343
19421
  "list",
19344
19422
  "stats",
19345
19423
  "list_messages",
@@ -19579,7 +19657,7 @@ function registerGatewayTools(server, http, options = {}) {
19579
19657
  const { projectRef } = options;
19580
19658
  registerTool(server, "gateway", `Gateway / Caddy 配置(通过 JSON Admin API 注入)。要求 admin 权限。
19581
19659
  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([
19660
+ action: withDescription(stringEnum2([
19583
19661
  "routes",
19584
19662
  "upsert_route",
19585
19663
  "update_route",
@@ -19600,11 +19678,11 @@ Actions: routes, upsert_route, update_route, delete_route, config, get_certifica
19600
19678
  hosts: withDescription(stringArray, "[upsert_route/update_route] 主机名列表,逗号分隔或 JSON 数组(1-20)"),
19601
19679
  paths: withDescription(stringArray, "[upsert_route/update_route] 路径列表,逗号分隔或 JSON 数组(1-32)"),
19602
19680
  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)"),
19681
+ managed_upstream: optional(stringEnum2(["edge-functions"]), "[upsert_route/update_route] 托管上游(同步 Edge Function)"),
19604
19682
  upstream_tls_insecure_skip_verify: optional(Boolean2(), "[upsert_route/update_route] 上游 TLS 跳过校验"),
19605
19683
  static_root: optional(String2(), "[upsert_route/update_route] 静态站点根目录"),
19606
19684
  spa: optional(Boolean2(), "[upsert_route/update_route] 是否启用 SPA 单页回退"),
19607
- protocol: optional(stringEnum(["http", "https"]), "[upsert_route/update_route] 可选请求协议匹配"),
19685
+ protocol: optional(stringEnum2(["http", "https"]), "[upsert_route/update_route] 可选请求协议匹配"),
19608
19686
  redirect_to: optional(String2(), "[upsert_route/update_route] 带固定 host 的绝对 http(s) 目标,可在末尾使用 {http.request.uri}"),
19609
19687
  redirect_status: withDescription(redirectStatus, "[upsert_route/update_route] 重定向状态码,默认 308"),
19610
19688
  rewrite_uri: optional(String2(), "[upsert_route/update_route] 重写 URI(以 / 开头)"),
@@ -19613,12 +19691,12 @@ Actions: routes, upsert_route, update_route, delete_route, config, get_certifica
19613
19691
  cors: withDescription(stringArray, "[upsert_route/update_route] 额外 CORS 源,逗号分隔"),
19614
19692
  priority: optional(Number2(), "[upsert_route/update_route] 路由优先级"),
19615
19693
  enabled: optional(Boolean2(), "[upsert_route/update_route] 是否启用"),
19616
- rate_limit_tier: optional(stringEnum(["free", "pro", "enterprise"]), "[config] 限流档位"),
19694
+ rate_limit_tier: optional(stringEnum2(["free", "pro", "enterprise"]), "[config] 限流档位"),
19617
19695
  cors_origins: optional(String2(), "[config] CORS 源(逗号分隔)"),
19618
19696
  jwt_enabled: optional(Boolean2(), "[config] 是否启用 JWT"),
19619
19697
  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"),
19698
+ cert_mode: optional(stringEnum2(["lego", "manual"]), "[update_certificate] 证书模式"),
19699
+ challenge: optional(stringEnum2(["dns-01", "http-01"]), "[update_certificate/issue_certificate] ACME challenge"),
19622
19700
  email: optional(String2(), "[update_certificate/issue_certificate] ACME 邮箱"),
19623
19701
  dns_provider: optional(String2(), "[update_certificate/issue_certificate] DNS 提供商"),
19624
19702
  dns_env: withDescription(stringArray, "[update_certificate/issue_certificate] DNS 环境变量 KEY=VALUE 列表"),
@@ -20132,7 +20210,7 @@ function formatPromotionResult(response) {
20132
20210
  }
20133
20211
  function registerBranchTools(server, http, options = {}) {
20134
20212
  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([
20213
+ action: withDescription(stringEnum2([
20136
20214
  "list",
20137
20215
  "create",
20138
20216
  "delete",
@@ -20142,7 +20220,7 @@ function registerBranchTools(server, http, options = {}) {
20142
20220
  ref: optional(String2(), "[*] Optional parent project override"),
20143
20221
  branch_ref: optional(String2(), "[delete/promotion_plan/promote] Preview branch ref"),
20144
20222
  name: optional(String2(), "[create] Branch name"),
20145
- data_mode: optional(stringEnum(["schema_only", "full_clone"]), "[create] Preview data mode (default: schema_only)"),
20223
+ data_mode: optional(stringEnum2(["schema_only", "full_clone"]), "[create] Preview data mode (default: schema_only)"),
20146
20224
  plan_checksum: optional(String2(), "[promote] Reviewed plan checksum from promotion_plan"),
20147
20225
  confirm_destructive: optional(Boolean2(), "[promote] Confirm reviewed destructive migrations"),
20148
20226
  json: optional(Boolean2(), "[promotion_plan/promote] Return machine-readable JSON")
@@ -20737,7 +20815,7 @@ function registerSupabaseCliTools(server, options = {}) {
20737
20815
  executeOfficialCli: options.executeOfficialCli || ((request) => executeOfficialSupabaseCli(request, environment))
20738
20816
  };
20739
20817
  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([
20818
+ action: withDescription(stringEnum2([
20741
20819
  "version",
20742
20820
  "init",
20743
20821
  "migration_new",
@@ -20770,16 +20848,16 @@ function registerSupabaseCliTools(server, options = {}) {
20770
20848
  experimental: optional(Boolean2(), "[db_schema_declarative_*] Enable the experimental pg-delta workflow"),
20771
20849
  strict_coverage: optional(Boolean2(), "[db_schema_declarative_*] Fail on unmanaged schema objects"),
20772
20850
  overwrite: optional(Boolean2(), "[db_schema_declarative_generate] Replace existing schema files"),
20773
- runtime: optional(stringEnum(["docker", "podman", "native"]), "[stack_*] Local stack runtime"),
20851
+ runtime: optional(stringEnum2(["docker", "podman", "native"]), "[stack_*] Local stack runtime"),
20774
20852
  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"),
20853
+ preparation: optional(stringEnum2(["background", "on-demand"]), "[stack_start] Download service archives in the background or on demand"),
20776
20854
  force: optional(Boolean2(), "[config_pull] Permit overwriting dirty tracked config"),
20777
20855
  yes: optional(Boolean2(), "[config_pull/stack_destroy] Confirm a write or destructive action"),
20778
20856
  confirm_destroy: optional(Boolean2(), "[stack_destroy] Explicitly confirm permanent local stack destruction"),
20779
20857
  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)")
20858
+ diff_engine: optional(stringEnum2(["migra", "pg-delta", "pgadmin", "pg-schema"]), "[db_diff/db_pull] Official CLI diff engine"),
20859
+ dump_mode: optional(stringEnum2(["schema", "data", "roles"]), "[db_dump] Dump schema (default), data, or roles"),
20860
+ language: optional(stringEnum2(["typescript", "go", "swift", "python"]), "[gen_types] Output language (default: typescript)")
20783
20861
  }, (request) => executeSupabaseAction(request, runtime));
20784
20862
  }
20785
20863
 
@@ -20972,7 +21050,7 @@ function registerLiteCliTools(server, options = {}) {
20972
21050
  const fallbackWorkdir = options.currentWorkingDirectory || process.cwd();
20973
21051
  const execute = options.executeLiteCli || ((request) => executeLiteCli(request, environment, fallbackWorkdir));
20974
21052
  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([
21053
+ action: withDescription(stringEnum2([
20976
21054
  "version",
20977
21055
  "start",
20978
21056
  "migrate",
@@ -20993,14 +21071,14 @@ function registerLiteCliTools(server, options = {}) {
20993
21071
  state_dir: optional(String2(), "[*] Lite state root"),
20994
21072
  data_dir: optional(String2(), "[*] PGlite/native data directory"),
20995
21073
  storage_dir: optional(String2(), "[*] Object storage directory"),
20996
- storage_backend: optional(stringEnum(["fs", "memory", "s3"]), "[*] Storage backend"),
21074
+ storage_backend: optional(stringEnum2(["fs", "memory", "s3"]), "[*] Storage backend"),
20997
21075
  s3_prefix: optional(String2(), "[*] S3 object key prefix"),
20998
- engine: optional(stringEnum(["pglite", "native"]), "[*] Database engine"),
21076
+ engine: optional(stringEnum2(["pglite", "native"]), "[*] Database engine"),
20999
21077
  host: optional(String2(), "[start] Listen host"),
21000
21078
  port: optional(Number2(), "[start] Listen port"),
21001
21079
  api_url: optional(String2(), "[start] Public API URL"),
21002
21080
  site_url: optional(String2(), "[start] Auth site URL"),
21003
- replication_profile: optional(stringEnum(["powersync"]), "[start/doctor] Replication profile"),
21081
+ replication_profile: optional(stringEnum2(["powersync"]), "[start/doctor] Replication profile"),
21004
21082
  replication_host: optional(String2(), "[start] Replication listener host"),
21005
21083
  replication_port: optional(Number2(), "[start] Replication listener port"),
21006
21084
  replication_allow_cidrs: optional(String2(), "[start] Replication client CIDRs"),
@@ -21181,7 +21259,7 @@ function textResponse(payload) {
21181
21259
  }
21182
21260
  function registerAiTools(server) {
21183
21261
  registerTool(server, "ai", "Install or inspect the bundled SupaCloud CLI AI skill. Actions: show_skill, install_skill", {
21184
- action: stringEnum(["show_skill", "install_skill"]),
21262
+ action: stringEnum2(["show_skill", "install_skill"]),
21185
21263
  target: optional(String2(), "[install_skill] Skill root directory; defaults to $CODEX_HOME/skills or ~/.codex/skills"),
21186
21264
  dry_run: optional(Boolean2(), "[install_skill] Preview the installation without writing files"),
21187
21265
  force: optional(Boolean2(), "[install_skill] Back up and replace different existing skill content")
@@ -24287,6 +24365,77 @@ function checkAppDatabaseSources(projectRoot) {
24287
24365
  }
24288
24366
  }
24289
24367
 
24368
+ // src/shared/tools/application-tools.ts
24369
+ var APPLICATION_TOOL_SCHEMA2 = {
24370
+ action: withDescription(stringEnum2([
24371
+ "list_releases",
24372
+ "get_release",
24373
+ "upload_release",
24374
+ "get_runtime",
24375
+ "get_deployment_evidence",
24376
+ "get_configuration",
24377
+ "put_configuration",
24378
+ "get_deploy_plan",
24379
+ "deploy_release",
24380
+ "deploy_application",
24381
+ "get_release_transfer_plan",
24382
+ "transfer_release",
24383
+ "get_promotion_plan",
24384
+ "promote_application",
24385
+ "get_promotion_status",
24386
+ "reconcile_promotion",
24387
+ "get_preview_plan",
24388
+ "list_previews",
24389
+ "create_preview",
24390
+ "get_preview",
24391
+ "cleanup_preview",
24392
+ "activate_release",
24393
+ "reconcile_activation",
24394
+ "retire_activation",
24395
+ "rollback_release",
24396
+ "get_rollback_snapshot",
24397
+ "get_history",
24398
+ ...APPLICATION_PREVIEW_ACTIONS,
24399
+ "logs"
24400
+ ]), "Action"),
24401
+ ref: withDescription(String2(), "Project ref"),
24402
+ id: withDescription(ApplicationIdSchema2, "Application ID"),
24403
+ 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")),
24404
+ configuration_id: optional(ApplicationConfigurationIdSchema2, "[get_configuration/get_deploy_plan/deploy_release/activate_release/rollback_release/get_history] Immutable revision; required for deployment"),
24405
+ 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"),
24406
+ 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"),
24407
+ configuration_path: optional(String2(), "[put_configuration] Local configuration write JSON including revision and expected revision"),
24408
+ configuration_write: optional(Unknown(), "[deploy_application] Parsed immutable configuration write"),
24409
+ manifest_path: optional(String2(), "[upload_release] Local delivery.manifest.json"),
24410
+ web_release: optional(ApplicationDeployWebReleaseSchema2, "[deploy_application] Immutable staged Web release uploaded by the deploy profile"),
24411
+ 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"),
24412
+ source_ref: optional(String2({ pattern: "^[A-Za-z0-9_-]{1,20}$" }), "[get_release_transfer_plan/transfer_release] Source project ref"),
24413
+ source_release_id: optional(ApplicationReleaseIdSchema2, "[get_release_transfer_plan/transfer_release] Source immutable release ID"),
24414
+ source_environment_id: optional(ApplicationIdSchema2, "[get_promotion_plan] Source environment"),
24415
+ mutation_id: optional(ApplicationActivationIdSchema2, "[deploy_application/promote_application/get_promotion_status/reconcile_promotion] Stable mutation ID"),
24416
+ approved_migration_digest: optional(ApplicationReleaseIdSchema2, "[deploy_application/promote_application] Exact reviewed migration execution-plan digest"),
24417
+ plan: optional(ApplicationPromotionPlanSchema2, "[promote_application] Immutable plan returned by get_promotion_plan"),
24418
+ cursor: optional(Union([ApplicationReleaseIdSchema2, ApplicationActivationHistoryCursorSchema2]), "[list_releases/get_history] Returned page cursor"),
24419
+ limit: optional(Integer({ minimum: 1, maximum: 100 }), "[list_releases/get_history] Page size, default 50/20"),
24420
+ offset: optional(Integer({ minimum: 0, maximum: 1e6 }), "[logs] Result offset"),
24421
+ service: optional(String2(), "[logs] Application target/service filter"),
24422
+ search: optional(String2(), "[logs] Full-text log filter"),
24423
+ start: optional(String2(), "[logs] ISO start timestamp"),
24424
+ end: optional(String2(), "[logs] ISO end timestamp"),
24425
+ ...APPLICATION_PREVIEW_FIELDS
24426
+ };
24427
+ var responseSchema2 = _Object_({
24428
+ project_ref: String2(),
24429
+ application_id: ApplicationIdSchema2,
24430
+ release: ApplicationReleaseRecordSchema2
24431
+ });
24432
+ var inventorySchema2 = _Object_({
24433
+ project_ref: String2(),
24434
+ application_id: ApplicationIdSchema2,
24435
+ releases: _Array_(ApplicationReleaseRecordSchema2, { maxItems: 100 }),
24436
+ next_cursor: Union([ApplicationReleaseIdSchema2, Null()])
24437
+ });
24438
+
24290
24439
  // src/shared/tools/app-resource.ts
24291
24440
  function resourceScaffold(name, className) {
24292
24441
  return {
@@ -25131,7 +25280,7 @@ var REMOTE_APP_DESCRIPTIONS = {
25131
25280
  cursor: "[history] Returned page cursor",
25132
25281
  limit: "[history] Page size, default 20 (1-100)"
25133
25282
  };
25134
- var { action: _remoteAction, plan: _remotePlan, ...remoteFields } = APPLICATION_TOOL_SCHEMA;
25283
+ var { action: _remoteAction, plan: _remotePlan, ...remoteFields } = APPLICATION_TOOL_SCHEMA2;
25135
25284
  var REMOTE_APP_SCHEMA = Partial(_Object_(remoteFields)).properties;
25136
25285
  for (const [name, schema] of Object.entries(REMOTE_APP_SCHEMA)) {
25137
25286
  const description = REMOTE_APP_DESCRIPTIONS[name];
@@ -26150,7 +26299,7 @@ async function runAppRelease(request, options, delegate) {
26150
26299
  id,
26151
26300
  manifest_path: manifestPath
26152
26301
  };
26153
- validateToolArguments(APPLICATION_TOOL_SCHEMA, uploadArgs);
26302
+ validateToolArguments2(APPLICATION_TOOL_SCHEMA2, uploadArgs);
26154
26303
  const uploaded = await delegate(uploadArgs);
26155
26304
  const uploadReceipt = resultObject(uploaded);
26156
26305
  if (uploaded.isError || !uploadReceipt?.ok) {
@@ -26190,7 +26339,7 @@ async function runAppRelease(request, options, delegate) {
26190
26339
  ...configurationWrite === undefined ? {} : { configuration_write: configurationWrite },
26191
26340
  ...request.web_release === undefined ? {} : { web_release: request.web_release }
26192
26341
  };
26193
- validateToolArguments(APPLICATION_TOOL_SCHEMA, deployArgs);
26342
+ validateToolArguments2(APPLICATION_TOOL_SCHEMA2, deployArgs);
26194
26343
  if (!options.projectDeployment) {
26195
26344
  throw new Error("Application release requires the unified project deployment handler");
26196
26345
  }
@@ -26432,7 +26581,7 @@ async function runPromotion(request, options, delegate) {
26432
26581
  action: "get_promotion_plan",
26433
26582
  ref
26434
26583
  };
26435
- validateToolArguments(APPLICATION_TOOL_SCHEMA, planArgs);
26584
+ validateToolArguments2(APPLICATION_TOOL_SCHEMA2, planArgs);
26436
26585
  const planned = await delegate(planArgs);
26437
26586
  if (planned.isError)
26438
26587
  return planned;
@@ -26484,7 +26633,7 @@ Execution: skipped`);
26484
26633
  configuration_id: plan.target.configuration_id,
26485
26634
  plan
26486
26635
  };
26487
- validateToolArguments(APPLICATION_TOOL_SCHEMA, executeArgs);
26636
+ validateToolArguments2(APPLICATION_TOOL_SCHEMA2, executeArgs);
26488
26637
  const result = await delegate(executeArgs);
26489
26638
  return format === "json" || json ? result : formatPromotionExecution(result);
26490
26639
  }
@@ -26532,7 +26681,7 @@ async function runAppTool(request, options = {}) {
26532
26681
  action,
26533
26682
  ref: request.ref ?? options.projectRef
26534
26683
  };
26535
- validateToolArguments(APPLICATION_TOOL_SCHEMA, args);
26684
+ validateToolArguments2(APPLICATION_TOOL_SCHEMA2, args);
26536
26685
  const result = await delegate(args);
26537
26686
  if (request.action === "history" && format !== "json" && json !== true) {
26538
26687
  return historyText(result, request, args.ref);
@@ -26594,137 +26743,414 @@ async function runAppTool(request, options = {}) {
26594
26743
  case "fix":
26595
26744
  return runFix(request);
26596
26745
  default:
26597
- return textResult(`Unknown app action: ${String(request.action)}`, true);
26746
+ return textResult(`Unknown app action: ${String(request.action)}`, true);
26747
+ }
26748
+ }
26749
+ function registerAppTools(server, options = {}) {
26750
+ 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.", {
26751
+ ...REMOTE_APP_SCHEMA,
26752
+ action: withDescription(stringEnum2([
26753
+ "init",
26754
+ "generate",
26755
+ "dev",
26756
+ "watch",
26757
+ "verify-plan",
26758
+ "compile",
26759
+ "check",
26760
+ "graph",
26761
+ "explain",
26762
+ "export-tools",
26763
+ "context",
26764
+ "doctor",
26765
+ "fix",
26766
+ "plan",
26767
+ "build",
26768
+ "release",
26769
+ "upload",
26770
+ "configure",
26771
+ "transfer-plan",
26772
+ "transfer",
26773
+ "promote-plan",
26774
+ "deploy",
26775
+ "deploy-plan",
26776
+ "diff",
26777
+ "status",
26778
+ "rollback",
26779
+ "rollback-plan",
26780
+ "history",
26781
+ "reconcile",
26782
+ "retire",
26783
+ "logs",
26784
+ "preview-plan",
26785
+ "preview",
26786
+ "previews",
26787
+ "preview-list",
26788
+ "preview-create",
26789
+ "preview-status",
26790
+ "preview-cleanup",
26791
+ "preview-reconcile",
26792
+ "promote",
26793
+ "promote-status",
26794
+ "promote-reconcile"
26795
+ ]), "App action; promote obtains and verifies its plan before one mutation request"),
26796
+ kind: optional(stringEnum2(["module", "command", "query", "controller", "job", "contract", "resource", "feature"]), "[generate] Scaffold kind"),
26797
+ template: optional(stringEnum2(["minimal", "http", "command", "edge"]), "[init] Minimal application by default; explicit http/command/edge recipes"),
26798
+ name: optional(String2(), "[init/generate] Project or object name"),
26799
+ module: optional(String2(), "[generate] Target feature module (required for command/query/controller)"),
26800
+ permission: optional(String2(), "[generate feature] Explicit approval permission; not inferred from name"),
26801
+ rpc: optional(String2(), "[generate feature] Explicit durable RPC capability; must be registered by the host"),
26802
+ dir: optional(String2(), "[generate] Feature root directory (default: src/features)"),
26803
+ force: optional(Boolean2(), "[generate] Overwrite a single regular file; resource/feature/controller never overwrite"),
26804
+ dry_run: optional(Boolean2(), "[generate] Validate and preview the complete change set without writing"),
26805
+ "dry-run": optional(Boolean2(), "[generate] CLI alias of dry_run"),
26806
+ register_in: optional(String2(), "[generate] Existing parent module file for module/resource/feature; uses the compiler AST editor"),
26807
+ "register-in": optional(String2(), "[generate] CLI alias of register_in"),
26808
+ root: optional(String2(), "[dev/plan/build/release/compile/check] Project directory containing supacloud.config.ts (default: current directory)"),
26809
+ include: optional(String2(), "[dev/compile/check] Comma-separated glob patterns for source files"),
26810
+ out_dir: optional(String2(), "[dev/compile/plan/build/export-tools] Output directory (default: configured outDir)"),
26811
+ strict: optional(Boolean2(), "[dev/compile/check] Promote warnings to errors"),
26812
+ format: optional(stringEnum2(["text", "json"]), "[dev/generate/compile/check/plan/release/deploy-plan/diff/history/promote-plan/graph/export-tools] Output format (default: text)"),
26813
+ json: optional(Boolean2(), "[history/transfer-plan/transfer/promote-plan] Preserve or return the full machine-readable receipt"),
26814
+ profile: optional(stringEnum2(["fast", "integration"]), "[dev] Run dev or dev:integration; integration verifies an explicit loopback database"),
26815
+ once: optional(Boolean2(), "[dev] Validate and report once without watching"),
26816
+ watch: optional(Boolean2(), "[dev] Watch for changes (default: true; --once disables)"),
26817
+ database_url: optional(String2(), "[dev] Explicit loopback integration URL; watch/once only inspect its shape"),
26818
+ "database-url": optional(String2(), "[dev] CLI alias of database_url"),
26819
+ target: optional(String2(), "[explain/context/verify-plan] Provider class name / token name / command name / job name / module name"),
26820
+ fix: optional(String2(), "[fix] Path to a DiagnosticFix JSON file produced by `doctor --format json`"),
26821
+ write: optional(Boolean2(), "[fix] Write the fix to disk (default: preview only)"),
26822
+ skip_build: optional(Boolean2(), "[release] Reuse --manifest_path instead of running the local delivery build")
26823
+ }, (request) => runAppTool(request, options));
26824
+ }
26825
+ var APP_ALIAS_ACTIONS = ["generate", "compile", "check", "graph", "explain", "context", "doctor", "fix"];
26826
+ function registerAppAliases(server) {
26827
+ const schema = {
26828
+ kind: optional(stringEnum2(["module", "command", "query", "controller", "job", "contract", "resource", "feature"]), "[generate] Scaffold kind"),
26829
+ name: optional(String2(), "[generate] Object name"),
26830
+ module: optional(String2(), "[generate] Target feature module"),
26831
+ permission: optional(String2(), "[generate feature] Explicit approval permission; not inferred from name"),
26832
+ rpc: optional(String2(), "[generate feature] Explicit durable RPC capability; must be registered by the host"),
26833
+ dir: optional(String2(), "[generate] Feature root directory (default: src/features)"),
26834
+ force: optional(Boolean2(), "[generate] Overwrite a single regular file; resource/feature/controller never overwrite"),
26835
+ dry_run: optional(Boolean2(), "[generate] Validate and preview the complete change set without writing"),
26836
+ "dry-run": optional(Boolean2(), "[generate] CLI alias of dry_run"),
26837
+ register_in: optional(String2(), "[generate] Existing parent module file for module/resource/feature; uses the compiler AST editor"),
26838
+ "register-in": optional(String2(), "[generate] CLI alias of register_in"),
26839
+ root: optional(String2(), "Project directory containing supacloud.config.ts (default: current directory)"),
26840
+ include: optional(String2(), "[compile/check] Comma-separated glob patterns for source files"),
26841
+ out_dir: optional(String2(), "[compile/export-tools] Output directory (default: <root>/generated)"),
26842
+ strict: optional(Boolean2(), "[compile/check] Promote warnings to errors"),
26843
+ format: optional(stringEnum2(["text", "json"]), "Output format (default: text)"),
26844
+ target: optional(String2(), "[explain/context] Provider class name / token name / command name / job name / module name"),
26845
+ fix: optional(String2(), "[fix] Path to a DiagnosticFix JSON file produced by `doctor --format json`"),
26846
+ write: optional(Boolean2(), "[fix] Write the fix to disk (default: preview only)")
26847
+ };
26848
+ for (const action of APP_ALIAS_ACTIONS) {
26849
+ registerTool(server, action, `Top-level alias of \`app ${action}\`.`, schema, (request) => runAppTool({ ...request, action }));
26850
+ }
26851
+ }
26852
+
26853
+ // src/shared/tools/db-governance-tools.ts
26854
+ import { existsSync as existsSync13 } from "node:fs";
26855
+ import { join as join16, resolve as resolve20 } from "node:path";
26856
+ import {
26857
+ buildDatabaseManifest,
26858
+ explainObject,
26859
+ lintModule,
26860
+ readCatalog,
26861
+ reconcileModule
26862
+ } from "@supacloud/db";
26863
+
26864
+ // src/shared/tools/db-workflow.ts
26865
+ import { createHash as createHash7 } from "node:crypto";
26866
+ 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";
26867
+ import { tmpdir as tmpdir2 } from "node:os";
26868
+ import { dirname as dirname10, isAbsolute as isAbsolute7, join as join15, relative as relative8, resolve as resolve19 } from "node:path";
26869
+ import {
26870
+ assertApplicationRoleRestricted,
26871
+ readDatabaseRole,
26872
+ renderDatabaseRoleGuardSql,
26873
+ renderReverseSnapshot,
26874
+ reverseDatabase
26875
+ } from "@supacloud/db";
26876
+ import { parseDatabaseSourcesConfig } from "@supacloud/db/source-contracts";
26877
+
26878
+ // src/shared/tools/supabase-cli-tools.ts
26879
+ import { chmodSync as chmodSync2, existsSync as existsSync12, mkdirSync as mkdirSync4, statSync as statSync8, writeFileSync as writeFileSync3 } from "node:fs";
26880
+ import { dirname as dirname9, isAbsolute as isAbsolute6, join as join14, resolve as resolve18 } from "node:path";
26881
+ 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;
26882
+ var VALID_VERSION2 = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
26883
+ var VALID_MIGRATION_NAME2 = /^[A-Za-z0-9][A-Za-z0-9_-]{0,100}$/;
26884
+ var VALID_SCHEMA2 = /^[A-Za-z_][A-Za-z0-9_$]*$/;
26885
+ var VALID_PROJECT_REF2 = /^[A-Za-z0-9_-]{1,64}$/;
26886
+ function isSensitiveEnvironmentKey2(key) {
26887
+ return key.toUpperCase().startsWith("PG") || SENSITIVE_ENV_KEY2.test(key);
26888
+ }
26889
+ function requireMigrationName2(name) {
26890
+ if (!name || !VALID_MIGRATION_NAME2.test(name)) {
26891
+ throw new Error("Invalid migration name; use letters, numbers, underscore, or hyphen");
26892
+ }
26893
+ return name;
26894
+ }
26895
+ function normalizeSchemaList2(schema) {
26896
+ if (!schema)
26897
+ return;
26898
+ const schemas = schema.split(",").map((schemaName) => schemaName.trim()).filter(Boolean);
26899
+ if (!schemas.length || schemas.some((schemaName) => !VALID_SCHEMA2.test(schemaName))) {
26900
+ throw new Error("Invalid schema list");
26901
+ }
26902
+ return schemas.join(",");
26903
+ }
26904
+ function requirePostgresUrl2(databaseUrl) {
26905
+ if (!databaseUrl || /[\r\n\0]/.test(databaseUrl)) {
26906
+ throw new Error("A Postgres database URL is required");
26907
+ }
26908
+ try {
26909
+ const url = new URL(databaseUrl);
26910
+ if (url.protocol !== "postgres:" && url.protocol !== "postgresql:") {
26911
+ throw new Error("unsupported protocol");
26912
+ }
26913
+ } catch {
26914
+ throw new Error("A valid Postgres database URL is required");
26915
+ }
26916
+ return databaseUrl;
26917
+ }
26918
+ function schemaArguments2(schema) {
26919
+ const normalized = normalizeSchemaList2(schema);
26920
+ return normalized ? ["--schema", normalized] : [];
26921
+ }
26922
+ function workdirArguments2(workdir) {
26923
+ if (!workdir)
26924
+ throw new Error("A workdir is required");
26925
+ return ["--workdir", resolve18(workdir)];
26926
+ }
26927
+ function resolveOutputPath2(workdir, file, label) {
26928
+ if (!file || /[\r\n\0]/.test(file))
26929
+ throw new Error(`${label} file is required`);
26930
+ return isAbsolute6(file) ? resolve18(file) : resolve18(workdir, file);
26931
+ }
26932
+ function databaseTargetArguments2(databaseUrl) {
26933
+ return databaseUrl ? ["--db-url", requirePostgresUrl2(databaseUrl)] : ["--local"];
26934
+ }
26935
+ function databaseDiffArguments2(request) {
26936
+ const engineFlags = {
26937
+ migra: "--use-migra",
26938
+ pgadmin: "--use-pgadmin",
26939
+ "pg-schema": "--use-pg-schema",
26940
+ "pg-delta": "--use-pg-delta"
26941
+ };
26942
+ return [
26943
+ "db",
26944
+ "diff",
26945
+ "--local",
26946
+ ...request.name ? ["--file", requireMigrationName2(request.name)] : [],
26947
+ ...schemaArguments2(request.schema),
26948
+ ...request.diff_engine ? [engineFlags[request.diff_engine]] : []
26949
+ ];
26950
+ }
26951
+ function databasePullArguments2(request) {
26952
+ if (request.diff_engine && request.diff_engine !== "migra" && request.diff_engine !== "pg-delta") {
26953
+ throw new Error("db_pull only supports migra or pg-delta diff engines");
26954
+ }
26955
+ return [
26956
+ "db",
26957
+ "pull",
26958
+ ...request.name ? [requireMigrationName2(request.name)] : [],
26959
+ "--db-url",
26960
+ requirePostgresUrl2(request.db_url),
26961
+ ...request.declarative ? ["--declarative"] : [],
26962
+ ...request.diff_engine ? ["--diff-engine", request.diff_engine] : [],
26963
+ ...schemaArguments2(request.schema)
26964
+ ];
26965
+ }
26966
+ function declarativeSyncArguments2(request) {
26967
+ return [
26968
+ "db",
26969
+ "schema",
26970
+ "declarative",
26971
+ "sync",
26972
+ ...request.name ? ["--name", requireMigrationName2(request.name)] : [],
26973
+ ...request.file ? ["--file", requireMigrationName2(request.file)] : [],
26974
+ ...request.apply === true ? ["--apply"] : ["--no-apply"],
26975
+ ...request.experimental === false ? [] : ["--experimental"],
26976
+ ...request.strict_coverage ? ["--strict-coverage"] : [],
26977
+ ...schemaArguments2(request.schema)
26978
+ ];
26979
+ }
26980
+ function declarativeGenerateArguments2(request) {
26981
+ const target = request.db_url ? ["--db-url", requirePostgresUrl2(request.db_url)] : ["--linked"];
26982
+ return [
26983
+ "db",
26984
+ "schema",
26985
+ "declarative",
26986
+ "generate",
26987
+ ...target,
26988
+ ...request.experimental === false ? [] : ["--experimental"],
26989
+ ...request.overwrite ? ["--overwrite"] : [],
26990
+ ...request.strict_coverage ? ["--strict-coverage"] : [],
26991
+ ...schemaArguments2(request.schema)
26992
+ ];
26993
+ }
26994
+ function stackRuntimeArguments2(request) {
26995
+ if (request.runtime && !["docker", "podman", "native"].includes(request.runtime)) {
26996
+ throw new Error("Invalid local stack runtime");
26997
+ }
26998
+ return request.runtime ? ["--runtime", request.runtime] : [];
26999
+ }
27000
+ function stackArguments2(request) {
27001
+ switch (request.action) {
27002
+ case "stack_start":
27003
+ return [
27004
+ "stack",
27005
+ "start",
27006
+ ...stackRuntimeArguments2({ ...request, runtime: request.runtime ?? "docker" }),
27007
+ ...request.eager ? ["--eager"] : [],
27008
+ ...request.preparation ? ["--preparation", request.preparation] : []
27009
+ ];
27010
+ case "stack_prepare":
27011
+ return ["stack", "prepare", ...stackRuntimeArguments2({ ...request, runtime: request.runtime ?? "docker" })];
27012
+ case "stack_status":
27013
+ return ["stack", "status", "--output-format", "json"];
27014
+ case "stack_stop":
27015
+ return ["stack", "stop"];
27016
+ case "stack_destroy":
27017
+ if (request.confirm_destroy !== true || request.yes !== true) {
27018
+ throw new Error("stack_destroy requires confirm_destroy=true and yes=true");
27019
+ }
27020
+ return ["stack", "destroy", "--yes"];
27021
+ default:
27022
+ throw new Error(`Unsupported local stack action: ${request.action}`);
27023
+ }
27024
+ }
27025
+ function databaseDumpArguments2(request, workdir) {
27026
+ return [
27027
+ "db",
27028
+ "dump",
27029
+ "--db-url",
27030
+ requirePostgresUrl2(request.db_url),
27031
+ "--file",
27032
+ resolveOutputPath2(workdir, request.file, "Database dump"),
27033
+ ...request.dump_mode === "data" ? ["--data-only"] : [],
27034
+ ...request.dump_mode === "roles" ? ["--role-only"] : [],
27035
+ ...schemaArguments2(request.schema)
27036
+ ];
27037
+ }
27038
+ function configPullArguments2(request) {
27039
+ if (!request.ref || !VALID_PROJECT_REF2.test(request.ref))
27040
+ throw new Error("Invalid project ref");
27041
+ if (request.dry_run === false && request.yes !== true) {
27042
+ throw new Error("config_pull apply requires yes=true");
27043
+ }
27044
+ return [
27045
+ "config",
27046
+ "pull",
27047
+ ...request.ref ? ["--project-ref", request.ref] : [],
27048
+ ...request.dry_run === true || request.dry_run === undefined ? ["--dry-run"] : [],
27049
+ ...request.force ? ["--force"] : [],
27050
+ ...request.dry_run === false ? ["--yes"] : []
27051
+ ];
27052
+ }
27053
+ function generateTypesArguments2(request, workdir) {
27054
+ resolveOutputPath2(workdir, request.file, "Generated types");
27055
+ return [
27056
+ "gen",
27057
+ "types",
27058
+ ...databaseTargetArguments2(request.db_url),
27059
+ "--lang",
27060
+ request.language || "typescript",
27061
+ ...schemaArguments2(request.schema)
27062
+ ];
27063
+ }
27064
+ function actionArguments2(request, workdir) {
27065
+ switch (request.action) {
27066
+ case "init":
27067
+ if (request.force)
27068
+ throw new Error("init never overwrites an existing project configuration");
27069
+ return ["init"];
27070
+ case "migration_new":
27071
+ return ["migration", "new", requireMigrationName2(request.name)];
27072
+ case "db_diff":
27073
+ return databaseDiffArguments2(request);
27074
+ case "db_reset":
27075
+ return ["db", "reset", "--local", ...request.no_seed ? ["--no-seed"] : [], "--yes"];
27076
+ case "db_pull":
27077
+ return databasePullArguments2(request);
27078
+ case "db_schema_declarative_sync":
27079
+ return declarativeSyncArguments2(request);
27080
+ case "db_schema_declarative_generate":
27081
+ return declarativeGenerateArguments2(request);
27082
+ case "db_dump":
27083
+ return databaseDumpArguments2(request, workdir);
27084
+ case "config_pull":
27085
+ return configPullArguments2(request);
27086
+ case "stack_start":
27087
+ case "stack_prepare":
27088
+ case "stack_status":
27089
+ case "stack_stop":
27090
+ case "stack_destroy":
27091
+ return stackArguments2(request);
27092
+ case "migration_list":
27093
+ return ["migration", "list", ...databaseTargetArguments2(request.db_url)];
27094
+ case "gen_types":
27095
+ return generateTypesArguments2(request, workdir);
27096
+ case "push":
27097
+ throw new Error("Remote push must use the SupaCloud Management API");
27098
+ default:
27099
+ throw new Error(`Unsupported official Supabase CLI action: ${String(request.action)}`);
26598
27100
  }
26599
27101
  }
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));
27102
+ function buildOfficialSupabaseArgs2(request) {
27103
+ if (request.action === "version")
27104
+ return ["--version"];
27105
+ const workdir = request.workdir ? resolve18(request.workdir) : undefined;
27106
+ if (!workdir)
27107
+ throw new Error("A workdir is required");
27108
+ return [...actionArguments2(request, workdir), ...workdirArguments2(workdir)];
26675
27109
  }
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 }));
27110
+ function createOfficialSupabaseEnvironment2(environment) {
27111
+ const safeEnvironment = {};
27112
+ for (const [key, environmentValue] of Object.entries(environment)) {
27113
+ if (environmentValue === undefined)
27114
+ continue;
27115
+ if (key === "SUPABASE_YES" || key === "SUPABASE_EXPERIMENTAL")
27116
+ continue;
27117
+ if (key === "SUPABASE_ACCESS_TOKEN" && environment.SUPACLOUD_FORWARD_SUPABASE_ACCESS_TOKEN === "1") {
27118
+ safeEnvironment[key] = environmentValue;
27119
+ continue;
27120
+ }
27121
+ if (isSensitiveEnvironmentKey2(key))
27122
+ continue;
27123
+ safeEnvironment[key] = environmentValue;
26701
27124
  }
27125
+ safeEnvironment.SUPABASE_TELEMETRY_DISABLED = "true";
27126
+ safeEnvironment.NO_COLOR = "1";
27127
+ return safeEnvironment;
27128
+ }
27129
+ function resolveOfficialSupabaseCommand2(workdir, environment = process.env) {
27130
+ const explicitBinary = environment.SUPACLOUD_SUPABASE_CLI_BIN?.trim();
27131
+ if (explicitBinary) {
27132
+ if (explicitBinary.includes("\x00"))
27133
+ throw new Error("Invalid SUPACLOUD_SUPABASE_CLI_BIN");
27134
+ return [explicitBinary];
27135
+ }
27136
+ const version = environment.SUPABASE_CLI_VERSION?.trim();
27137
+ if (version) {
27138
+ if (!VALID_VERSION2.test(version))
27139
+ throw new Error("Invalid SUPABASE_CLI_VERSION; use an exact version such as 2.120.0");
27140
+ if (process.versions.bun)
27141
+ return [process.execPath, "x", `supabase@${version}`];
27142
+ if (process.platform === "win32") {
27143
+ throw new Error("SUPABASE_CLI_VERSION bootstrap is unavailable under Node on Windows; install the official CLI or set SUPACLOUD_SUPABASE_CLI_BIN");
27144
+ }
27145
+ return ["npx", "--yes", `supabase@${version}`];
27146
+ }
27147
+ const localPackageEntry = join14(resolve18(workdir), "node_modules", "supabase", "dist", "supabase.js");
27148
+ if (existsSync12(localPackageEntry))
27149
+ return [process.execPath, localPackageEntry];
27150
+ return ["supabase"];
26702
27151
  }
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
27152
 
26715
27153
  // 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
27154
  var hash4 = (content) => createHash7("sha256").update(content).digest("hex");
26729
27155
  var result = (value, isError = false) => ({
26730
27156
  isError,
@@ -26732,12 +27158,12 @@ var result = (value, isError = false) => ({
26732
27158
  });
26733
27159
  var schemas = (value) => value?.split(",").map((entry) => entry.trim()).filter(Boolean) ?? ["public"];
26734
27160
  async function projectPath2(root, path) {
26735
- if (!path || isAbsolute6(path) || path.includes("\\") || path.split("/").some((part) => !part || part === "." || part === "..")) {
27161
+ if (!path || isAbsolute7(path) || path.includes("\\") || path.split("/").some((part) => !part || part === "." || part === "..")) {
26736
27162
  throw new Error("Use a normalized project-relative path");
26737
27163
  }
26738
27164
  let current = root;
26739
27165
  for (const part of path.split("/")) {
26740
- current = join14(current, part);
27166
+ current = join15(current, part);
26741
27167
  try {
26742
27168
  if ((await lstat4(current)).isSymbolicLink())
26743
27169
  throw new Error("Symlink workflow paths are forbidden");
@@ -26759,7 +27185,7 @@ async function exists2(path) {
26759
27185
  }
26760
27186
  }
26761
27187
  async function boundary(root) {
26762
- const config = join14(root, "database.sources.json");
27188
+ const config = join15(root, "database.sources.json");
26763
27189
  return await exists2(config) ? parseDatabaseSourcesConfig(JSON.parse(await readFile5(await projectPath2(root, "database.sources.json"), "utf8"))) : undefined;
26764
27190
  }
26765
27191
  var overlaps = (a, b) => a === b || a.startsWith(`${b}/`) || b.startsWith(`${a}/`);
@@ -26828,10 +27254,10 @@ async function withDatabase(args, options, work) {
26828
27254
  }
26829
27255
  async function runOfficial(root, workdir, args, environment, options) {
26830
27256
  const command = [
26831
- ...resolveOfficialSupabaseCommand(root, environment),
26832
- ...buildOfficialSupabaseArgs({ ...args, workdir })
27257
+ ...resolveOfficialSupabaseCommand2(root, environment),
27258
+ ...buildOfficialSupabaseArgs2({ ...args, workdir })
26833
27259
  ];
26834
- const safeEnvironment = createOfficialSupabaseEnvironment(environment);
27260
+ const safeEnvironment = createOfficialSupabaseEnvironment2(environment);
26835
27261
  delete safeEnvironment.SUPABASE_ACCESS_TOKEN;
26836
27262
  if (args.db_url)
26837
27263
  safeEnvironment.PGOPTIONS = "-c default_transaction_read_only=on";
@@ -26853,9 +27279,9 @@ async function runOfficial(root, workdir, args, environment, options) {
26853
27279
  async function initializeCandidate(stage, majorVersion = 17) {
26854
27280
  if (![15, 16, 17, 18].includes(majorVersion))
26855
27281
  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"), [
27282
+ await mkdir6(join15(stage, "supabase/migrations"), { recursive: true });
27283
+ await mkdir6(join15(stage, "supabase/schemas"), { recursive: true });
27284
+ await writeFile5(join15(stage, "supabase/config.toml"), [
26859
27285
  `project_id = "supacloud-candidate-${hash4(stage).slice(0, 12)}"`,
26860
27286
  "[db]",
26861
27287
  `major_version = ${majorVersion}`,
@@ -26873,9 +27299,9 @@ async function copySqlSources(root, source, destination) {
26873
27299
  const file = await projectPath2(root, `${source}/${name}`);
26874
27300
  const state = await lstat4(file);
26875
27301
  if (state.isDirectory())
26876
- count += await copySqlSources(root, `${source}/${name}`, join14(destination, name));
27302
+ count += await copySqlSources(root, `${source}/${name}`, join15(destination, name));
26877
27303
  else if (state.isFile() && name.endsWith(".sql")) {
26878
- await writeFile5(join14(destination, name), await readFile5(file));
27304
+ await writeFile5(join15(destination, name), await readFile5(file));
26879
27305
  count++;
26880
27306
  }
26881
27307
  }
@@ -26889,8 +27315,8 @@ async function reverse(args, root, options) {
26889
27315
  if (!selectedSchemas.length || selectedSchemas.some((schema) => !/^[a-z_][a-z0-9_$]*$/i.test(schema))) {
26890
27316
  throw new Error("Select valid PostgreSQL schemas");
26891
27317
  }
26892
- await mkdir6(dirname9(out), { recursive: true });
26893
- const stage = await mkdtemp(join14(dirname9(out), ".reverse-"));
27318
+ await mkdir6(dirname10(out), { recursive: true });
27319
+ const stage = await mkdtemp(join15(dirname10(out), ".reverse-"));
26894
27320
  try {
26895
27321
  const snapshot = await withDatabase(args, options, async (executor, url) => {
26896
27322
  const snapshot = await reverseDatabase(executor, selectedSchemas);
@@ -26906,10 +27332,10 @@ async function reverse(args, root, options) {
26906
27332
  }, options.environment ?? process.env, options);
26907
27333
  return snapshot;
26908
27334
  });
26909
- const generated = await readdir6(join14(stage, "supabase/schemas"), { recursive: true });
27335
+ const generated = await readdir6(join15(stage, "supabase/schemas"), { recursive: true });
26910
27336
  if (!generated.some((file) => file.endsWith(".sql")))
26911
27337
  throw new Error("Official reverse did not produce declarative SQL");
26912
- await writeFile5(join14(stage, "catalog.json"), renderReverseSnapshot(snapshot));
27338
+ await writeFile5(join15(stage, "catalog.json"), renderReverseSnapshot(snapshot));
26913
27339
  await rename3(stage, out);
26914
27340
  return result({
26915
27341
  ok: true,
@@ -26934,14 +27360,14 @@ async function diff2(args, root, options) {
26934
27360
  const input = await migrations(root, args.dir ?? (await boundary(root))?.migrations ?? "supabase/migrations");
26935
27361
  if (overlaps(input.dir, out))
26936
27362
  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-"));
27363
+ await mkdir6(dirname10(out), { recursive: true });
27364
+ const stage = await mkdtemp(join15(dirname10(out), ".diff-"));
26939
27365
  try {
26940
27366
  await initializeCandidate(stage, args.db_major_version);
26941
- if (!await copySqlSources(root, sourcePath, join14(stage, "supabase/schemas")))
27367
+ if (!await copySqlSources(root, sourcePath, join15(stage, "supabase/schemas")))
26942
27368
  throw new Error("No declarative SQL sources");
26943
27369
  for (const { file, text } of input.files)
26944
- await writeFile5(join14(stage, "supabase/migrations", file), text);
27370
+ await writeFile5(join15(stage, "supabase/migrations", file), text);
26945
27371
  await runOfficial(root, stage, {
26946
27372
  action: "db_schema_declarative_sync",
26947
27373
  apply: false,
@@ -26951,15 +27377,15 @@ async function diff2(args, root, options) {
26951
27377
  }, options.environment ?? process.env, options);
26952
27378
  const history = new Map(input.files.map((file) => [file.file, file.text]));
26953
27379
  for (const [file, text] of history) {
26954
- if (await readFile5(join14(stage, "supabase/migrations", file), "utf8") !== text) {
27380
+ if (await readFile5(join15(stage, "supabase/migrations", file), "utf8") !== text) {
26955
27381
  throw new Error("Official diff changed reviewed migration history");
26956
27382
  }
26957
27383
  }
26958
- const names = (await readdir6(join14(stage, "supabase/migrations"))).filter((file) => file.endsWith(".sql") && !history.has(file));
26959
- const candidates = join14(stage, "candidates");
27384
+ const names = (await readdir6(join15(stage, "supabase/migrations"))).filter((file) => file.endsWith(".sql") && !history.has(file));
27385
+ const candidates = join15(stage, "candidates");
26960
27386
  await mkdir6(candidates);
26961
27387
  for (const file of names)
26962
- await writeFile5(join14(candidates, file), await readFile5(join14(stage, "supabase/migrations", file)));
27388
+ await writeFile5(join15(candidates, file), await readFile5(join15(stage, "supabase/migrations", file)));
26963
27389
  await rename3(candidates, out);
26964
27390
  } finally {
26965
27391
  await rm2(stage, { force: true, recursive: true });
@@ -26997,10 +27423,10 @@ async function migrationAction(args, root, options) {
26997
27423
  throw new Error("Select a Management API and project ref");
26998
27424
  const path = args.dir ?? (await boundary(root))?.migrations ?? "supabase/migrations";
26999
27425
  const input = await migrations(root, path);
27000
- const stage = await mkdtemp(join14(tmpdir2(), "supacloud-reviewed-migrations-"));
27426
+ const stage = await mkdtemp(join15(tmpdir2(), "supacloud-reviewed-migrations-"));
27001
27427
  try {
27002
27428
  for (const { file, text } of input.files)
27003
- await writeFile5(join14(stage, file), text);
27429
+ await writeFile5(join15(stage, file), text);
27004
27430
  const preview = await run({ action: "push_migrations", ref, dir: stage, dry_run: true, strict: true });
27005
27431
  const dryRun = {
27006
27432
  isError: preview.isError === true,
@@ -27027,7 +27453,7 @@ async function migrationAction(args, root, options) {
27027
27453
  }
27028
27454
  }
27029
27455
  async function runDbWorkflow(args, projectRoot, options = {}) {
27030
- const root = resolve18(projectRoot);
27456
+ const root = resolve19(projectRoot);
27031
27457
  if ((await lstat4(root)).isSymbolicLink())
27032
27458
  throw new Error("Workflow root must not be a symlink");
27033
27459
  switch (args.action) {
@@ -27088,8 +27514,8 @@ function normalizeModule(candidate, sourcePath) {
27088
27514
  };
27089
27515
  }
27090
27516
  async function loadDatabaseModules(root, moduleFile) {
27091
- const filePath = moduleFile ? resolve19(root, moduleFile) : join15(root, "db", "modules.ts");
27092
- if (!existsSync12(filePath)) {
27517
+ const filePath = moduleFile ? resolve20(root, moduleFile) : join16(root, "db", "modules.ts");
27518
+ if (!existsSync13(filePath)) {
27093
27519
  throw new Error(moduleFile ? `Module file not found: ${filePath}` : `Module file not found: ${filePath}(用 --module_file 指定一个导出 defineDatabaseModule(...) 的文件)`);
27094
27520
  }
27095
27521
  let imported;
@@ -27178,7 +27604,7 @@ function buildDatabaseAiContext(modules, root, moduleName, target) {
27178
27604
  const objects = target ? allObjects.filter((object) => objectMatchesTarget(object, target)) : allObjects;
27179
27605
  if (target && objects.length === 0)
27180
27606
  throw new Error(`未找到数据库对象: ${target}`);
27181
- const sqlFirst = existsSync12(join15(root, "supabase/schemas"));
27607
+ const sqlFirst = existsSync13(join16(root, "supabase/schemas"));
27182
27608
  return {
27183
27609
  version: 1,
27184
27610
  scope: "database-ai-context",
@@ -27224,7 +27650,7 @@ async function runLint(args, root) {
27224
27650
  const issues = [];
27225
27651
  for (const module of selected) {
27226
27652
  const readSqlFile = async (path) => {
27227
- const absolute = resolve19(root, path);
27653
+ const absolute = resolve20(root, path);
27228
27654
  try {
27229
27655
  return await Bun.file(absolute).text();
27230
27656
  } catch (error) {
@@ -27264,14 +27690,14 @@ var defaultLiteSpawn = async (command, cwd) => {
27264
27690
  return { exitCode, stdout, stderr };
27265
27691
  };
27266
27692
  async function runModuleCheckLite(args, root, spawn, liteBinary) {
27267
- const projectDir = resolve19(args.project_dir?.trim() || root);
27693
+ const projectDir = resolve20(args.project_dir?.trim() || root);
27268
27694
  const binary = liteBinary === undefined ? Bun.which("supacloud-lite") : liteBinary;
27269
27695
  if (!binary) {
27270
27696
  throw new Error("db module_check --lite 需要本机可用的 supacloud-lite(npm i -g @supacloud/lite,或加入 PATH)");
27271
27697
  }
27272
27698
  const command = [binary, "db", "check", "--project-dir", projectDir];
27273
27699
  if (args.module_file?.trim())
27274
- command.push("--module-file", resolve19(root, args.module_file.trim()));
27700
+ command.push("--module-file", resolve20(root, args.module_file.trim()));
27275
27701
  const result = await spawn(command, projectDir);
27276
27702
  const output = [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join(`
27277
27703
  `);
@@ -27312,7 +27738,7 @@ function registerDbGovernanceTools(server, options = {}) {
27312
27738
  const fallbackRoot = options.currentWorkingDirectory || process.cwd();
27313
27739
  const liteSpawn = options.liteSpawn || defaultLiteSpawn;
27314
27740
  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"),
27741
+ action: withDescription(stringEnum2(["context", "lint", "explain", "module_check", "reverse", "diff", "plan", "apply", "role_check", "role_sql"]), "Database governance action"),
27316
27742
  module: optional(String2(), "[context/lint] Only select this manifest module (default: all)"),
27317
27743
  root: optional(String2(), "[*] Project root (default: current directory)"),
27318
27744
  module_file: optional(String2(), "[*] File exporting defineDatabaseModule(...) (default: <root>/db/modules.ts)"),
@@ -27331,7 +27757,7 @@ function registerDbGovernanceTools(server, options = {}) {
27331
27757
  migration_role: optional(String2(), "[role_sql] Privileged migration role"),
27332
27758
  database: optional(String2(), "[role_sql] PostgreSQL database name")
27333
27759
  }, async (request) => {
27334
- const root = resolve19(request.root || fallbackRoot);
27760
+ const root = resolve20(request.root || fallbackRoot);
27335
27761
  switch (request.action) {
27336
27762
  case "context":
27337
27763
  return runContext2(request, root);
@@ -27361,11 +27787,11 @@ function registerDbGovernanceTools(server, options = {}) {
27361
27787
 
27362
27788
  // src/shared/tools/scheduled-function-tools.ts
27363
27789
  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";
27790
+ import { readFileSync as readFileSync9, statSync as statSync9 } from "node:fs";
27791
+ import { resolve as resolve21 } from "node:path";
27366
27792
  import { isDeepStrictEqual } from "node:util";
27367
27793
  var HEADER_NAME_PATTERN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,128}$/;
27368
- var ENVIRONMENT_NAME_PATTERN2 = /^[A-Za-z_][A-Za-z0-9_]{0,255}$/;
27794
+ var ENVIRONMENT_NAME_PATTERN3 = /^[A-Za-z_][A-Za-z0-9_]{0,255}$/;
27369
27795
  var FORBIDDEN_HEADER_NAMES = new Set([
27370
27796
  "apikey",
27371
27797
  "authorization",
@@ -27446,8 +27872,8 @@ function validScheduledFunctionCron(expression) {
27446
27872
  function readScheduleBodyFile(bodyPathInput) {
27447
27873
  if (!bodyPathInput.trim())
27448
27874
  throw new Error("'body_file' must be a path");
27449
- const bodyPath = resolve20(bodyPathInput);
27450
- const bodyStat = statSync8(bodyPath);
27875
+ const bodyPath = resolve21(bodyPathInput);
27876
+ const bodyStat = statSync9(bodyPath);
27451
27877
  if (!bodyStat.isFile() || bodyStat.size > MAX_BODY_FILE_BYTES) {
27452
27878
  throw new Error("Scheduled Function body file must be a regular file no larger than 1 MiB");
27453
27879
  }
@@ -27473,7 +27899,7 @@ function scheduleBody(bodyFile) {
27473
27899
  }
27474
27900
  function resolvedHeaderEntry(headerName, environmentName, environment) {
27475
27901
  const normalizedName = headerName.toLowerCase();
27476
- if (!HEADER_NAME_PATTERN.test(headerName) || forbiddenHeaderName(normalizedName) || typeof environmentName !== "string" || !ENVIRONMENT_NAME_PATTERN2.test(environmentName)) {
27902
+ if (!HEADER_NAME_PATTERN.test(headerName) || forbiddenHeaderName(normalizedName) || typeof environmentName !== "string" || !ENVIRONMENT_NAME_PATTERN3.test(environmentName)) {
27477
27903
  throw new Error("SCHEDULE_HEADER_MAPPING_INVALID");
27478
27904
  }
27479
27905
  const headerValue = environment[environmentName];
@@ -27778,14 +28204,14 @@ function registerScheduledFunctionTools(server, http, environment = process.env,
27778
28204
  }
27779
28205
  var SCHEDULE_TOOL_DESCRIPTION = "Scheduled Edge Function lifecycle. Actions: list, get, create, update, delete";
27780
28206
  var SCHEDULE_TOOL_SCHEMA = {
27781
- action: withDescription(stringEnum(["list", "get", "create", "update", "delete"]), "Action"),
28207
+ action: withDescription(stringEnum2(["list", "get", "create", "update", "delete"]), "Action"),
27782
28208
  ref: withDescription(String2(), "Project ref"),
27783
28209
  schedule_id: optional(String2(), "[get/update/delete] Schedule ID"),
27784
28210
  expected_updated_at: optional(String2(), "[update/delete] Canonical updated_at from list"),
27785
28211
  name: optional(String2(), "[create/update] Display name"),
27786
28212
  slug: optional(String2(), "[create] Edge Function slug"),
27787
28213
  cron: optional(String2(), "[create/update] Five-field cron expression"),
27788
- method: optional(stringEnum(["GET", "POST"]), "[create/update] HTTP method"),
28214
+ method: optional(stringEnum2(["GET", "POST"]), "[create/update] HTTP method"),
27789
28215
  enabled: optional(Boolean2(), "[update] Enabled state"),
27790
28216
  body_file: optional(String2(), "[create/update] Local JSON object file; content is never printed"),
27791
28217
  header_env: withDescription(headerEnvironmentSchema, "[create/update] JSON map of HTTP header names to environment variable names")
@@ -27823,15 +28249,15 @@ function registerMutationTools(server, http) {
27823
28249
  registerTool(server, "mutations", "Durable mutation status readback", MUTATION_TOOL_SCHEMA, (args) => mutationStatus(http, args));
27824
28250
  }
27825
28251
  var MUTATION_TOOL_SCHEMA = {
27826
- action: withDescription(stringEnum(["status"]), "Action"),
28252
+ action: withDescription(stringEnum2(["status"]), "Action"),
27827
28253
  ref: withDescription(String2(), "[status] Project ref"),
27828
28254
  mutation_id: withDescription(String2(), "[status] Client mutation UUID")
27829
28255
  };
27830
28256
 
27831
28257
  // src/shared/tools/release-tools.ts
27832
28258
  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";
28259
+ import { existsSync as existsSync14, mkdirSync as mkdirSync5, readdirSync as readdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "node:fs";
28260
+ import { dirname as dirname11, join as join17, relative as relative9, resolve as resolve22 } from "node:path";
27835
28261
  var SAFE_PROJECT_REF = /^[A-Za-z0-9_-]{1,64}$/;
27836
28262
  var SAFE_ENVIRONMENT_ID = /^[A-Za-z0-9_-]{1,64}$/;
27837
28263
  var BACKUP_ID = /^logical-full_[A-Za-z0-9_-]{1,64}_[a-f0-9]{32}$/;
@@ -28153,15 +28579,15 @@ function collectScopeFiles(cwd, file, files, dir, task) {
28153
28579
  const results = [];
28154
28580
  const resolvePathWithFallbacks = (filePath) => {
28155
28581
  const trimmed = filePath.trim();
28156
- const resolved = resolve21(cwd, trimmed);
28157
- if (existsSync13(resolved))
28582
+ const resolved = resolve22(cwd, trimmed);
28583
+ if (existsSync14(resolved))
28158
28584
  return resolved;
28159
28585
  const candidates = [
28160
- resolve21(cwd, "supacloud/fa/release-scopes", trimmed),
28161
- resolve21(cwd, "release-scopes", trimmed),
28162
- resolve21(cwd, "scopes", trimmed)
28586
+ resolve22(cwd, "supacloud/fa/release-scopes", trimmed),
28587
+ resolve22(cwd, "release-scopes", trimmed),
28588
+ resolve22(cwd, "scopes", trimmed)
28163
28589
  ];
28164
- const found = candidates.find(existsSync13);
28590
+ const found = candidates.find(existsSync14);
28165
28591
  if (found)
28166
28592
  return found;
28167
28593
  throw new Error(`Scope file not found: ${filePath}`);
@@ -28182,18 +28608,18 @@ function collectScopeFiles(cwd, file, files, dir, task) {
28182
28608
  if (!taskFilter && (!dir || typeof dir !== "string" || !dir.trim())) {
28183
28609
  throw new Error("At least one of --file, --files, or --task is required for scope_rebind");
28184
28610
  }
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")
28611
+ const candidates = typeof dir === "string" && dir.trim() ? [resolve22(cwd, dir.trim())] : [
28612
+ resolve22(cwd, "supacloud/fa/release-scopes"),
28613
+ resolve22(cwd, "release-scopes"),
28614
+ resolve22(cwd, "scopes")
28189
28615
  ];
28190
28616
  for (const directory of candidates) {
28191
- if (existsSync13(directory)) {
28617
+ if (existsSync14(directory)) {
28192
28618
  try {
28193
28619
  const entries = readdirSync5(directory, { withFileTypes: true });
28194
28620
  for (const entry of entries) {
28195
28621
  if (entry.isFile() && entry.name.endsWith(".json")) {
28196
- const fullPath = join16(directory, entry.name);
28622
+ const fullPath = join17(directory, entry.name);
28197
28623
  if (!taskFilter) {
28198
28624
  results.push(fullPath);
28199
28625
  } else if (entry.name.toLowerCase().includes(taskFilter)) {
@@ -28219,7 +28645,7 @@ function collectScopeFiles(cwd, file, files, dir, task) {
28219
28645
  return [...new Set(results)];
28220
28646
  }
28221
28647
  function handleScopeRebind(args) {
28222
- const cwd = typeof args.cwd === "string" && args.cwd.trim() ? resolve21(args.cwd.trim()) : process.cwd();
28648
+ const cwd = typeof args.cwd === "string" && args.cwd.trim() ? resolve22(args.cwd.trim()) : process.cwd();
28223
28649
  const targetBaseCommit = resolveGitBaseCommit(cwd, args.base_commit);
28224
28650
  const requestedEnvironmentId = requestedScopeEnvironment(args, "scope_rebind");
28225
28651
  const scopeFiles = collectScopeFiles(cwd, args.file, args.files, args.dir, args.task);
@@ -28255,7 +28681,7 @@ function handleScopeRebind(args) {
28255
28681
  if (updated && !dryRun) {
28256
28682
  const formatted = JSON.stringify(canonicalValue(parsed), null, 2) + `
28257
28683
  `;
28258
- writeFileSync3(filePath, formatted, "utf8");
28684
+ writeFileSync4(filePath, formatted, "utf8");
28259
28685
  }
28260
28686
  scopes.push({
28261
28687
  file: relative9(cwd, filePath),
@@ -28277,20 +28703,20 @@ function handleScopeRebind(args) {
28277
28703
  });
28278
28704
  }
28279
28705
  function handleScopeInspect(args) {
28280
- const cwd = typeof args.cwd === "string" && args.cwd.trim() ? resolve21(args.cwd.trim()) : process.cwd();
28706
+ const cwd = typeof args.cwd === "string" && args.cwd.trim() ? resolve22(args.cwd.trim()) : process.cwd();
28281
28707
  const requestedEnvironmentId = requestedScopeEnvironment(args, "scope_inspect");
28282
28708
  if (typeof args.file !== "string" || !args.file.trim()) {
28283
28709
  throw new Error("'file' is required for scope_inspect");
28284
28710
  }
28285
28711
  const trimmed = args.file.trim();
28286
- let filePath = resolve21(cwd, trimmed);
28287
- if (!existsSync13(filePath)) {
28712
+ let filePath = resolve22(cwd, trimmed);
28713
+ if (!existsSync14(filePath)) {
28288
28714
  const candidates = [
28289
- resolve21(cwd, "supacloud/fa/release-scopes", trimmed),
28290
- resolve21(cwd, "release-scopes", trimmed),
28291
- resolve21(cwd, "scopes", trimmed)
28715
+ resolve22(cwd, "supacloud/fa/release-scopes", trimmed),
28716
+ resolve22(cwd, "release-scopes", trimmed),
28717
+ resolve22(cwd, "scopes", trimmed)
28292
28718
  ];
28293
- const found = candidates.find(existsSync13);
28719
+ const found = candidates.find(existsSync14);
28294
28720
  if (found)
28295
28721
  filePath = found;
28296
28722
  else
@@ -28353,7 +28779,7 @@ function parseCommaList(value) {
28353
28779
  return [];
28354
28780
  }
28355
28781
  function handleScopeCreate(args, fallbackRef) {
28356
- const cwd = typeof args.cwd === "string" && args.cwd.trim() ? resolve21(args.cwd.trim()) : process.cwd();
28782
+ const cwd = typeof args.cwd === "string" && args.cwd.trim() ? resolve22(args.cwd.trim()) : process.cwd();
28357
28783
  if (typeof args.file !== "string" || !args.file.trim()) {
28358
28784
  throw new Error("'file' is required for scope_create");
28359
28785
  }
@@ -28369,8 +28795,8 @@ function handleScopeCreate(args, fallbackRef) {
28369
28795
  throw new Error("'ref' is invalid for release controls");
28370
28796
  }
28371
28797
  const targetBaseCommit = resolveGitBaseCommit(cwd, args.base_commit);
28372
- const filePath = resolve21(cwd, args.file.trim());
28373
- mkdirSync4(dirname10(filePath), { recursive: true });
28798
+ const filePath = resolve22(cwd, args.file.trim());
28799
+ mkdirSync5(dirname11(filePath), { recursive: true });
28374
28800
  const scope = {
28375
28801
  baseCommit: targetBaseCommit,
28376
28802
  excludedFunctions: parseCommaList(args.excluded_functions),
@@ -28385,7 +28811,7 @@ function handleScopeCreate(args, fallbackRef) {
28385
28811
  const scopeSha256 = genericScopedReleaseScopeSha256(scope);
28386
28812
  const formatted = JSON.stringify(canonicalValue(scope), null, 2) + `
28387
28813
  `;
28388
- writeFileSync3(filePath, formatted, "utf8");
28814
+ writeFileSync4(filePath, formatted, "utf8");
28389
28815
  return releaseControlSuccess("release.scope_create", {
28390
28816
  file: relative9(cwd, filePath),
28391
28817
  task_id: scope.taskId,
@@ -28410,7 +28836,7 @@ function registerReleaseTools(server, http, options = {}) {
28410
28836
  ];
28411
28837
  const allActions = [...remoteActions, ...localActions];
28412
28838
  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"),
28839
+ action: withDescription(stringEnum2(allActions), "Release control action"),
28414
28840
  ref: optional(String2(), options.projectRef ? "Optional override when not auto-linked" : "Project ref"),
28415
28841
  file: optional(String2(), "[scope_inspect/scope_rebind/scope_create] Path to release scope JSON file"),
28416
28842
  files: optional(String2(), "[scope_rebind] Comma-separated list of release scope JSON files"),
@@ -28618,7 +29044,7 @@ import { access, lstat as lstat5, mkdtemp as mkdtemp2, opendir, readFile as read
28618
29044
  import { spawn as spawn2 } from "node:child_process";
28619
29045
  import { createHash as createHash9 } from "node:crypto";
28620
29046
  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";
29047
+ import { basename as basename8, dirname as dirname12, join as join18, relative as relative10, resolve as resolve23, sep as sep5 } from "node:path";
28622
29048
 
28623
29049
  // src/shared/tools/project-deployment.ts
28624
29050
  async function requireProjectDeploymentProtocol(http, projectRef, environmentId) {
@@ -28631,7 +29057,6 @@ async function requireProjectDeploymentProtocol(http, projectRef, environmentId)
28631
29057
  throw new Error("DEPLOYMENT_PROTOCOL_MISMATCH: Upgrade the SupaCloud server and CLI together (`npm install -g @supacloud/cli@latest`), then retry `supacloud deploy`. No upload or deployment was attempted; legacy endpoints will not be used.");
28632
29058
  }
28633
29059
  }
28634
- var operation2 = "applications.project_deploy";
28635
29060
  var scopeSchema = _Object_({
28636
29061
  project_ref: String2({ pattern: "^[a-z0-9-]{1,20}$" }),
28637
29062
  application_id: ApplicationIdSchema,
@@ -28651,83 +29076,16 @@ var statusSchema = _Object_({
28651
29076
  phase: Union([
28652
29077
  Literal("queued"),
28653
29078
  Literal("prepared"),
28654
- Literal("migrating"),
28655
- Literal("activating"),
28656
- Literal("binding-web"),
28657
- Literal("verifying")
28658
- ]),
28659
- operation: Literal("application.release.deploy"),
28660
- ...scopeSchema.properties,
28661
- receipt: Optional(Unknown()),
28662
- failure_code: Optional(String2({ pattern: "^[A-Z][A-Z0-9_]{0,63}$" }))
28663
- }, { 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) {
28683
- return releaseControlFailure(operation2, "INVALID_RESPONSE", null);
28684
- }
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
- });
28714
- try {
28715
- if (!observed.ok || observed.status !== 200 || !Check2(statusSchema, observed.data))
28716
- 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")
28719
- 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
- });
28727
- } catch {
28728
- return releaseControlFailure(operation2, "OUTCOME_UNKNOWN", result.transportError ? null : result.status, identity);
28729
- }
28730
- }
29079
+ Literal("migrating"),
29080
+ Literal("activating"),
29081
+ Literal("binding-web"),
29082
+ Literal("verifying")
29083
+ ]),
29084
+ operation: Literal("application.release.deploy"),
29085
+ ...scopeSchema.properties,
29086
+ receipt: Optional(Unknown()),
29087
+ failure_code: Optional(String2({ pattern: "^[A-Z][A-Z0-9_]{0,63}$" }))
29088
+ }, { additionalProperties: false });
28731
29089
 
28732
29090
  // src/shared/tools/deploy-tools.ts
28733
29091
  var MAX_SOURCE_BYTES = 256 * 1024 * 1024;
@@ -28778,15 +29136,15 @@ async function readJson(path) {
28778
29136
  }
28779
29137
  }
28780
29138
  async function findDeployConfigRoot(startDirectory, environmentName) {
28781
- let directory = resolve22(startDirectory);
29139
+ let directory = resolve23(startDirectory);
28782
29140
  while (true) {
28783
29141
  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))) {
29142
+ if (await pathExists(join18(directory, "supacloud.json")) || await pathExists(join18(directory, "supacloud.config.ts")) || environmentFile !== null && await pathExists(join18(directory, environmentFile))) {
28785
29143
  return directory;
28786
29144
  }
28787
- const parent = dirname11(directory);
29145
+ const parent = dirname12(directory);
28788
29146
  if (parent === directory)
28789
- return resolve22(startDirectory);
29147
+ return resolve23(startDirectory);
28790
29148
  directory = parent;
28791
29149
  }
28792
29150
  }
@@ -28851,7 +29209,7 @@ function deployTargetConfig(candidate, name) {
28851
29209
  };
28852
29210
  }
28853
29211
  async function readDeployConfig(configRoot) {
28854
- const configPath = join17(configRoot, "supacloud.json");
29212
+ const configPath = join18(configRoot, "supacloud.json");
28855
29213
  const candidate = await readJson(configPath);
28856
29214
  if (candidate === null)
28857
29215
  return {};
@@ -28898,16 +29256,16 @@ async function readDeployConfig(configRoot) {
28898
29256
  };
28899
29257
  }
28900
29258
  async function packageName(projectDirectory) {
28901
- const candidate = record4(await readJson(join17(projectDirectory, "package.json")));
29259
+ const candidate = record4(await readJson(join18(projectDirectory, "package.json")));
28902
29260
  return typeof candidate?.name === "string" && candidate.name.trim() ? candidate.name.trim() : null;
28903
29261
  }
28904
29262
  async function pathExists(path) {
28905
29263
  return access(path).then(() => true, () => false);
28906
29264
  }
28907
29265
  async function hasProjectReleaseManifest(root) {
28908
- if (await pathExists(join17(root, "deploy-manifest.json")))
29266
+ if (await pathExists(join18(root, "deploy-manifest.json")))
28909
29267
  return true;
28910
- const directory = await opendir(join17(root, "supacloud")).catch((error) => {
29268
+ const directory = await opendir(join18(root, "supacloud")).catch((error) => {
28911
29269
  if (error instanceof Error && "code" in error && error.code === "ENOENT")
28912
29270
  return null;
28913
29271
  throw error;
@@ -28918,28 +29276,28 @@ async function hasProjectReleaseManifest(root) {
28918
29276
  for await (const entry of directory) {
28919
29277
  if (++entries > 128)
28920
29278
  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")))
29279
+ if ((entry.isDirectory() || entry.isSymbolicLink()) && await pathExists(join18(root, "supacloud", entry.name, "deploy-manifest.json")))
28922
29280
  return true;
28923
29281
  }
28924
29282
  return false;
28925
29283
  }
28926
29284
  async function defaultBuildCommand(projectDirectory, configRoot) {
28927
- const packageJson = record4(await readJson(join17(projectDirectory, "package.json")));
29285
+ const packageJson = record4(await readJson(join18(projectDirectory, "package.json")));
28928
29286
  const scripts = record4(packageJson?.scripts);
28929
29287
  if (typeof scripts?.build !== "string" || !scripts.build.trim()) {
28930
29288
  throw new Error("No build command was configured and package.json has no build script");
28931
29289
  }
28932
29290
  let directory = projectDirectory;
28933
29291
  while (true) {
28934
- if (await pathExists(join17(directory, "bun.lock")) || await pathExists(join17(directory, "bun.lockb")))
29292
+ if (await pathExists(join18(directory, "bun.lock")) || await pathExists(join18(directory, "bun.lockb")))
28935
29293
  return "bun run build";
28936
- if (await pathExists(join17(directory, "pnpm-lock.yaml")))
29294
+ if (await pathExists(join18(directory, "pnpm-lock.yaml")))
28937
29295
  return "pnpm run build";
28938
- if (await pathExists(join17(directory, "yarn.lock")))
29296
+ if (await pathExists(join18(directory, "yarn.lock")))
28939
29297
  return "yarn build";
28940
29298
  if (directory === configRoot)
28941
29299
  break;
28942
- const parent = dirname11(directory);
29300
+ const parent = dirname12(directory);
28943
29301
  if (parent === directory || !directory.startsWith(`${configRoot}${sep5}`))
28944
29302
  break;
28945
29303
  directory = parent;
@@ -29007,9 +29365,9 @@ function selectFrontendTarget(configRoot, config) {
29007
29365
  throw new Error(`Multiple frontend deploy targets found (${targets.map(([name]) => name).sort().join(", ")}). Set defaultTarget to the Web target`);
29008
29366
  }
29009
29367
  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)) {
29368
+ const root = resolve23(configRoot, target.root || (name === "frontend" ? "." : name));
29369
+ const prefix = `${resolve23(configRoot)}${sep5}`;
29370
+ if (root !== resolve23(configRoot) && !root.startsWith(prefix)) {
29013
29371
  throw new Error(`Deploy target '${name}' root must stay inside the repository root`);
29014
29372
  }
29015
29373
  return root;
@@ -29027,8 +29385,8 @@ function defaultOutputDirectory(framework) {
29027
29385
  }
29028
29386
  }
29029
29387
  function resolveInsideProject(projectDirectory, candidate, allowRoot = false) {
29030
- const resolved = resolve22(projectDirectory, candidate);
29031
- const projectRoot = resolve22(projectDirectory);
29388
+ const resolved = resolve23(projectDirectory, candidate);
29389
+ const projectRoot = resolve23(projectDirectory);
29032
29390
  const projectPrefix = `${projectRoot}${sep5}`;
29033
29391
  if (!allowRoot && resolved === projectRoot || resolved !== projectRoot && !resolved.startsWith(projectPrefix)) {
29034
29392
  throw new Error("Frontend output directory must stay inside the project directory");
@@ -29082,7 +29440,7 @@ async function collectArchiveFiles(root) {
29082
29440
  entries.push(entry);
29083
29441
  entries.sort((left, right) => left.name.localeCompare(right.name, "en"));
29084
29442
  for (const entry of entries) {
29085
- const path = join17(directory, entry.name);
29443
+ const path = join18(directory, entry.name);
29086
29444
  const stats = await lstat5(path);
29087
29445
  if (stats.isSymbolicLink())
29088
29446
  throw new Error(`Frontend output cannot contain symbolic links: ${path}`);
@@ -29121,8 +29479,8 @@ async function collectArchiveFiles(root) {
29121
29479
  }
29122
29480
  async function createFrontendArchive(outputDirectory) {
29123
29481
  const collected = await collectArchiveFiles(outputDirectory);
29124
- const temporaryDirectory = await mkdtemp2(join17(tmpdir3(), "supacloud-deploy-"));
29125
- const archivePath = join17(temporaryDirectory, `${basename8(outputDirectory)}.tar.zst`);
29482
+ const temporaryDirectory = await mkdtemp2(join18(tmpdir3(), "supacloud-deploy-"));
29483
+ const archivePath = join18(temporaryDirectory, `${basename8(outputDirectory)}.tar.zst`);
29126
29484
  try {
29127
29485
  const archiveBytes = await createFrontendTarZstd(collected.files);
29128
29486
  await writeFile6(archivePath, archiveBytes);
@@ -29198,7 +29556,7 @@ async function stageApplicationWebRelease(http, projectRef, environmentId, confi
29198
29556
  }
29199
29557
  function registerDeployTools(server, http, options = {}) {
29200
29558
  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()));
29559
+ const invocationDirectory = resolve23(String(args.cwd || options.cwd || process.cwd()));
29202
29560
  const projectRef = String(args.ref || options.projectRef || "").trim();
29203
29561
  if (!projectRef)
29204
29562
  throw new Error("A linked project ref is required. Set SUPACLOUD_PROJECT_REF or pass --ref");
@@ -29265,17 +29623,17 @@ function registerDeployTools(server, http, options = {}) {
29265
29623
  }
29266
29624
 
29267
29625
  // src/shared/tools/remote-dev-tools.ts
29268
- import { existsSync as existsSync14 } from "node:fs";
29626
+ import { existsSync as existsSync15 } from "node:fs";
29269
29627
  import { readFile as readFile7 } from "node:fs/promises";
29270
29628
  import { readdir as readdir7 } from "node:fs/promises";
29271
29629
  import { createHash as createHash10 } from "node:crypto";
29272
- import { join as join18, resolve as resolve23 } from "node:path";
29630
+ import { join as join19, resolve as resolve24 } from "node:path";
29273
29631
  import { compileProject as compileProject2 } from "@supacloud/compiler";
29274
29632
  var SAFE_TOKEN = /^[A-Za-z0-9._:@/+,-]+$/;
29275
29633
  var SAFE_REMOTE_ROOT = /^\/(?:[A-Za-z0-9._-]+\/)*[A-Za-z0-9._-]+\/?$/;
29276
29634
  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)"),
29635
+ action: withDescription(stringEnum2(["sync", "watch", "status", "migrate"]), "Remote development action"),
29636
+ target: optional(stringEnum2(["db", "functions", "frontend", "project"]), "Sync target (default: project)"),
29279
29637
  project_dir: optional(String2(), "Local project directory (default: current directory)"),
29280
29638
  remote_root: optional(String2(), "Remote development root"),
29281
29639
  remote_host: optional(String2(), "Remote test server host"),
@@ -29304,8 +29662,8 @@ async function runProcess(executable, args, cwd) {
29304
29662
  function resolveDrizzleCommand(root, configured) {
29305
29663
  if (configured?.trim())
29306
29664
  return configured.trim();
29307
- const local = join18(root, "node_modules", ".bin", "drizzle-kit");
29308
- return existsSync14(local) ? local : "drizzle-kit";
29665
+ const local = join19(root, "node_modules", ".bin", "drizzle-kit");
29666
+ return existsSync15(local) ? local : "drizzle-kit";
29309
29667
  }
29310
29668
  function toolFailed(value) {
29311
29669
  if (!value || typeof value !== "object")
@@ -29315,14 +29673,14 @@ function toolFailed(value) {
29315
29673
  return result.isError === true || content.some((chunk) => chunk !== null && typeof chunk === "object" && ("text" in chunk) && typeof chunk.text === "string" && chunk.text.trimStart().startsWith("❌"));
29316
29674
  }
29317
29675
  async function migrateDatabase(args, options) {
29318
- const root = resolve23(String(args.project_dir || options.cwd || process.cwd()));
29676
+ const root = resolve24(String(args.project_dir || options.cwd || process.cwd()));
29319
29677
  const projectConfig = await readProjectConfig(root);
29320
29678
  const config = projectConfig.dev || {};
29321
29679
  const database = config.database || {};
29322
29680
  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"));
29681
+ const drizzleConfig = resolve24(root, String(args.drizzle_config || database.drizzleConfig || "drizzle.config.ts"));
29324
29682
  const migrationsDir = String(args.migrations_dir || database.migrationsDir || "supabase/migrations");
29325
- if (!existsSync14(drizzleConfig))
29683
+ if (!existsSync15(drizzleConfig))
29326
29684
  throw new Error(`Drizzle config not found: ${drizzleConfig}`);
29327
29685
  const generated = await execute(resolveDrizzleCommand(root, typeof args.drizzle_bin === "string" ? args.drizzle_bin : database.drizzleBin), ["generate", "--config", drizzleConfig], root);
29328
29686
  if (generated.exitCode !== 0)
@@ -29340,8 +29698,8 @@ async function migrateDatabase(args, options) {
29340
29698
  return { ok: true, mode: "dev", action: "migrate", generated: true, applied: true, migrations_dir: migrationsDir, result: applied };
29341
29699
  }
29342
29700
  async function readProjectConfig(root) {
29343
- const file = join18(root, "supacloud.json");
29344
- if (!existsSync14(file))
29701
+ const file = join19(root, "supacloud.json");
29702
+ if (!existsSync15(file))
29345
29703
  return {};
29346
29704
  try {
29347
29705
  const parsed = JSON.parse(await readFile7(file, "utf8"));
@@ -29359,7 +29717,7 @@ async function sourceFingerprint(root) {
29359
29717
  const visit = async (directory) => {
29360
29718
  const entries = await readdir7(directory, { withFileTypes: true });
29361
29719
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
29362
- const path = join18(directory, entry.name);
29720
+ const path = join19(directory, entry.name);
29363
29721
  if (entry.isDirectory() && ![".git", "node_modules", "dist", "generated", ".supacloud"].includes(entry.name))
29364
29722
  await visit(path);
29365
29723
  else if (entry.isFile())
@@ -29387,19 +29745,19 @@ function remoteRoot(value) {
29387
29745
  }
29388
29746
  function targetDirectory(root, target, functionSlug, config = {}) {
29389
29747
  if (target === "db")
29390
- return join18(root, "supabase", "migrations");
29748
+ return join19(root, "supabase", "migrations");
29391
29749
  if (target === "functions") {
29392
29750
  const targets = config["targets"];
29393
29751
  const entries = targets && typeof targets === "object" ? Object.values(targets) : [];
29394
29752
  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;
29753
+ const base = match?.root ? resolve24(root, match.root) : join19(root, "supabase", "functions");
29754
+ return match?.root ? base : functionSlug ? join19(base, safeToken(functionSlug, "function")) : base;
29397
29755
  }
29398
29756
  if (target === "frontend") {
29399
29757
  const targets = config["targets"];
29400
29758
  const entries = targets && typeof targets === "object" ? Object.values(targets) : [];
29401
29759
  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");
29760
+ return match?.root ? resolve24(root, match.root) : join19(root, "apps", "web");
29403
29761
  }
29404
29762
  return root;
29405
29763
  }
@@ -29438,10 +29796,10 @@ function reloadCommand(config, target, projectRef, functionSlug) {
29438
29796
  return [...command.split(" "), "--project-ref", safeToken(projectRef || "test", "project_ref"), "--target", targetConfigPath(target), ...functionSlug ? ["--function", safeToken(functionSlug, "function")] : []];
29439
29797
  }
29440
29798
  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];
29799
+ return ["-p", String(connection.port), "-i", resolve24(connection.key), "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=yes", `${connection.user}@${connection.host}`, ...remoteCommand];
29442
29800
  }
29443
29801
  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`];
29802
+ const args = ["-az", "--checksum", "--partial", "--protect-args", "-e", `ssh -p ${connection.port} -i ${resolve24(connection.key)} -o BatchMode=yes -o StrictHostKeyChecking=yes`];
29445
29803
  if (deleteRemote)
29446
29804
  args.push("--delete-delay");
29447
29805
  for (const exclude of excludes) {
@@ -29453,17 +29811,17 @@ function rsyncArgs(connection, source, destination, excludes, deleteRemote) {
29453
29811
  return args;
29454
29812
  }
29455
29813
  async function syncOnce(args, options) {
29456
- const root = resolve23(String(args.project_dir || options.cwd || process.cwd()));
29814
+ const root = resolve24(String(args.project_dir || options.cwd || process.cwd()));
29457
29815
  const projectConfig = await readProjectConfig(root);
29458
29816
  const config = projectConfig.dev || {};
29459
29817
  const target = String(args.target || "project");
29460
29818
  const source = targetDirectory(root, target, typeof args.function === "string" ? args.function : undefined, projectConfig);
29461
- if (!existsSync14(source))
29819
+ if (!existsSync15(source))
29462
29820
  throw new Error(`Dev source directory not found: ${source}`);
29463
29821
  let compiled = false;
29464
29822
  if (config.compile === true && target !== "db") {
29465
- const compileRoot = resolve23(root, config.compileRoot || ".");
29466
- const compileOutDir = resolve23(root, config.compileOutDir || "generated");
29823
+ const compileRoot = resolve24(root, config.compileRoot || ".");
29824
+ const compileOutDir = resolve24(root, config.compileOutDir || "generated");
29467
29825
  const compilation = await compileProject2({
29468
29826
  rootDir: compileRoot,
29469
29827
  outDir: compileOutDir,
@@ -29501,7 +29859,7 @@ function registerRemoteDevTools(server, options = {}) {
29501
29859
  if (["production", "prod"].includes((options.environment || "").toLowerCase()))
29502
29860
  throw new Error("Remote dev mode is forbidden for production environments");
29503
29861
  if (args.action === "status") {
29504
- const root = resolve23(String(args.project_dir || options.cwd || process.cwd()));
29862
+ const root = resolve24(String(args.project_dir || options.cwd || process.cwd()));
29505
29863
  const config = await readDevConfig(root);
29506
29864
  const connection = connectionArgs(options, config, args);
29507
29865
  const execute = options.execute || ((command, commandArgs, cwd) => runProcess(command, commandArgs, cwd));
@@ -29515,7 +29873,7 @@ function registerRemoteDevTools(server, options = {}) {
29515
29873
  return { content: [{ type: "text", text: JSON.stringify(await migrateDatabase(args, options), null, 2) }] };
29516
29874
  }
29517
29875
  if (args.action === "watch") {
29518
- const root = resolve23(String(args.project_dir || options.cwd || process.cwd()));
29876
+ const root = resolve24(String(args.project_dir || options.cwd || process.cwd()));
29519
29877
  const interval = Math.max(100, Math.min(1e4, Number(args.interval_ms || 300)));
29520
29878
  let fingerprint = "";
29521
29879
  let lastSync = null;
@@ -29535,15 +29893,211 @@ function registerRemoteDevTools(server, options = {}) {
29535
29893
  }
29536
29894
 
29537
29895
  // src/shared/tools/developer-experience-tools.ts
29538
- import { existsSync as existsSync15 } from "node:fs";
29896
+ import { existsSync as existsSync17 } from "node:fs";
29539
29897
  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";
29898
+ import { dirname as dirname13, join as join20, resolve as resolve26 } from "node:path";
29899
+
29900
+ // src/shared/context.ts
29901
+ import { existsSync as existsSync16, readFileSync as readFileSync11 } from "node:fs";
29902
+ import { homedir as homedir3 } from "node:os";
29903
+ import { resolve as resolve25 } from "node:path";
29904
+ var CORE_CONTEXT_KEYS2 = [
29905
+ "SUPABASE_URL",
29906
+ "SUPABASE_SERVICE_ROLE_KEY",
29907
+ "SUPACLOUD_API_URL",
29908
+ "SUPACLOUD_MANAGEMENT_API_URL",
29909
+ "MANAGEMENT_API_URL",
29910
+ "SUPACLOUD_API_TOKEN",
29911
+ "SUPACLOUD_PROJECT_REF",
29912
+ "X_PROJECT_REF",
29913
+ "SUPACLOUD_HOST",
29914
+ "SUPACLOUD_APPLICATION_ID",
29915
+ "SUPACLOUD_APPLICATION_ENVIRONMENT_ID",
29916
+ "SUPACLOUD_APPLICATION_CONFIGURATION_PATH",
29917
+ "SUPACLOUD_APPLICATION_CONFIGURATION_ID",
29918
+ "SUPACLOUD_APPLICATION_MANIFEST_PATH"
29919
+ ];
29920
+ function unquotedEnvValue2(rawValue) {
29921
+ const value = rawValue.trim();
29922
+ const quote = value[0];
29923
+ return quote && (quote === '"' || quote === "'") && value.endsWith(quote) ? value.slice(1, -1) : value;
29924
+ }
29925
+ function parseEnvFile2(contents) {
29926
+ const values = {};
29927
+ for (const rawLine of contents.split(/\r?\n/)) {
29928
+ const line = rawLine.trim();
29929
+ if (!line || line.startsWith("#"))
29930
+ continue;
29931
+ const match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
29932
+ if (!match)
29933
+ continue;
29934
+ values[match[1]] = unquotedEnvValue2(match[2]);
29935
+ }
29936
+ return values;
29937
+ }
29938
+ function readEnvFile2(path, required) {
29939
+ if (!existsSync16(path)) {
29940
+ if (required)
29941
+ throw new Error(`SupaCloud environment file not found: ${path}`);
29942
+ return {};
29943
+ }
29944
+ try {
29945
+ return parseEnvFile2(readFileSync11(path, "utf8"));
29946
+ } catch (error) {
29947
+ const message = error instanceof Error ? error.message : String(error);
29948
+ throw new Error(`Failed to read SupaCloud environment file ${path}: ${message}`);
29949
+ }
29950
+ }
29951
+ function canonicalApiOrigin2(value) {
29952
+ const candidate = value.trim();
29953
+ if (!candidate)
29954
+ return "";
29955
+ try {
29956
+ const url = new URL(candidate);
29957
+ const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
29958
+ const allowedProtocol = url.protocol === "https:" || url.protocol === "http:" && loopback;
29959
+ const exactOrigin = candidate === url.origin || candidate === `${url.origin}/`;
29960
+ return allowedProtocol && !url.username && !url.password && exactOrigin && url.pathname === "/" && !url.search && !url.hash ? url.origin : "";
29961
+ } catch (error) {
29962
+ if (error instanceof TypeError)
29963
+ return "";
29964
+ throw error;
29965
+ }
29966
+ }
29967
+ function hostFromUrl2(value) {
29968
+ return value ? new URL(value).hostname : "";
29969
+ }
29970
+ function inferProjectRefFromSupabaseUrl2(value) {
29971
+ if (!value)
29972
+ return "";
29973
+ return new URL(value).hostname.match(/^([a-z0-9-]+)\.api\./i)?.[1] ?? "";
29974
+ }
29975
+ function sourceCredentialScope2(values, explicitApiUrl, supabaseUrl) {
29976
+ const hasManagementContext = Boolean(explicitApiUrl.trim() || values.SUPACLOUD_API_TOKEN?.trim() || values.SUPACLOUD_HOST?.trim());
29977
+ if (hasManagementContext)
29978
+ return "management";
29979
+ return supabaseUrl || values.SUPABASE_SERVICE_ROLE_KEY?.trim() ? "project_application" : "incomplete";
29980
+ }
29981
+ function sourceProjectCore2(values) {
29982
+ const supabaseUrl = canonicalApiOrigin2(values.SUPABASE_URL || "");
29983
+ const projectRef = (values.SUPACLOUD_PROJECT_REF || values.X_PROJECT_REF || "").trim() || inferProjectRefFromSupabaseUrl2(supabaseUrl);
29984
+ const explicitApiUrl = values.SUPACLOUD_API_URL || values.SUPACLOUD_MANAGEMENT_API_URL || values.MANAGEMENT_API_URL || "";
29985
+ const credentialScope = sourceCredentialScope2(values, explicitApiUrl, supabaseUrl);
29986
+ const managementUrl = explicitApiUrl || (values.SUPACLOUD_HOST ? `http://${values.SUPACLOUD_HOST}:9090` : "");
29987
+ const apiUrl = credentialScope === "management" ? canonicalApiOrigin2(managementUrl) : "";
29988
+ const apiToken = credentialScope === "management" ? values.SUPACLOUD_API_TOKEN || "" : "";
29989
+ return { apiUrl, apiToken, projectRef, supabaseUrl, credentialScope };
29990
+ }
29991
+ function processValues2(env) {
29992
+ return Object.fromEntries(Object.entries(env).filter((entry) => entry[1] !== undefined));
29993
+ }
29994
+ function hasProcessContext2(env) {
29995
+ return CORE_CONTEXT_KEYS2.some((key) => Object.hasOwn(env, key));
29996
+ }
29997
+ function completeProjectContext2(values) {
29998
+ const core = sourceProjectCore2(values);
29999
+ if (core.credentialScope === "project_application") {
30000
+ return Boolean(core.supabaseUrl && values.SUPABASE_SERVICE_ROLE_KEY?.trim() && core.projectRef);
30001
+ }
30002
+ return Boolean(core.apiUrl && core.apiToken && core.projectRef);
30003
+ }
30004
+ function parseInsecureTls2(values) {
30005
+ for (const key of ["SUPACLOUD_TLS_VERIFY", "SUPACLOUD_TLS_INSECURE"]) {
30006
+ const value = values[key]?.trim().toLowerCase();
30007
+ if (value === undefined)
30008
+ continue;
30009
+ if (["1", "true", "yes", "on"].includes(value))
30010
+ return key === "SUPACLOUD_TLS_INSECURE";
30011
+ if (["0", "false", "no", "off"].includes(value))
30012
+ return key === "SUPACLOUD_TLS_VERIFY";
30013
+ throw new Error(`${key} must be a boolean (true or false)`);
30014
+ }
30015
+ return true;
30016
+ }
30017
+ function namedEnvironmentSource2(cwd, selector) {
30018
+ const environment = normalizeEnvironmentName(selector);
30019
+ const path = resolve25(cwd, `.env.supacloud.${selector}`);
30020
+ const values = readEnvFile2(path, true);
30021
+ if (values.SUPACLOUD_ENV && normalizeEnvironmentName(values.SUPACLOUD_ENV) !== environment) {
30022
+ throw new Error(`SUPACLOUD_ENV in ${path} does not match selector ${selector}`);
30023
+ }
30024
+ return { values, kind: "named_env_file", path, environment };
30025
+ }
30026
+ function explicitEnvironmentSource2(cwd, envFile) {
30027
+ const path = resolve25(cwd, envFile);
30028
+ const values = readEnvFile2(path, true);
30029
+ if (!values.SUPACLOUD_ENV)
30030
+ throw new Error(`SUPACLOUD_ENV is required in ${path}`);
30031
+ return {
30032
+ values,
30033
+ kind: "explicit_env_file",
30034
+ path,
30035
+ environment: normalizeEnvironmentName(values.SUPACLOUD_ENV)
30036
+ };
30037
+ }
30038
+ function contextSource2(env, cwd, selection) {
30039
+ if (selection.environmentName && selection.envFile)
30040
+ throw new Error("Environment selectors are mutually exclusive");
30041
+ if (selection.environmentName)
30042
+ return namedEnvironmentSource2(cwd, selection.environmentName);
30043
+ if (selection.envFile)
30044
+ return explicitEnvironmentSource2(cwd, selection.envFile);
30045
+ if (env.SUPACLOUD_ENV) {
30046
+ const environment = normalizeEnvironmentName(env.SUPACLOUD_ENV);
30047
+ const values = processValues2(env);
30048
+ if (completeProjectContext2(values)) {
30049
+ return { values, kind: "process_env", path: null, environment };
30050
+ }
30051
+ return namedEnvironmentSource2(cwd, env.SUPACLOUD_ENV);
30052
+ }
30053
+ if (hasProcessContext2(env)) {
30054
+ return { values: processValues2(env), kind: "process_env", path: null, environment: "" };
30055
+ }
30056
+ const path = resolve25(cwd, ".env");
30057
+ const values = readEnvFile2(path, false);
30058
+ const environment = values.SUPACLOUD_ENV ? normalizeEnvironmentName(values.SUPACLOUD_ENV) : "";
30059
+ return { values, kind: Object.keys(values).length ? "legacy_dotenv" : "none", path: existsSync16(path) ? path : null, environment };
30060
+ }
30061
+ function resolveSupaCloudContext2(env = process.env, cwd = process.cwd(), selection = {}) {
30062
+ const source = contextSource2(env, cwd, selection);
30063
+ const core = sourceProjectCore2(source.values);
30064
+ const host = source.values.SUPACLOUD_HOST || hostFromUrl2(core.apiUrl || core.supabaseUrl);
30065
+ const readOnly = env.SUPACLOUD_READ_ONLY === "true" || source.values.SUPACLOUD_READ_ONLY === "true";
30066
+ const insecureTls = parseInsecureTls2(source.values);
30067
+ return {
30068
+ host,
30069
+ sshUser: env.SUPACLOUD_SSH_USER ?? "root",
30070
+ sshPort: parseInt(env.SUPACLOUD_SSH_PORT ?? "22", 10),
30071
+ sshKey: env.SUPACLOUD_SSH_KEY ?? resolve25(homedir3(), ".ssh", "id_rsa"),
30072
+ sshPass: env.SUPACLOUD_SSH_PASS ?? "",
30073
+ apiUrl: core.apiUrl,
30074
+ apiToken: core.apiToken,
30075
+ projectRef: core.projectRef,
30076
+ readOnly,
30077
+ environment: source.environment,
30078
+ production: source.environment === "prod" || source.environment === "production",
30079
+ inferredSupabaseUrl: core.supabaseUrl,
30080
+ inferredServiceRoleKey: source.values.SUPABASE_SERVICE_ROLE_KEY || "",
30081
+ credentialScope: core.credentialScope,
30082
+ source: source.kind,
30083
+ sourcePath: source.path,
30084
+ environmentVariables: source.values,
30085
+ applicationId: source.values.SUPACLOUD_APPLICATION_ID?.trim() || "",
30086
+ applicationEnvironmentId: source.values.SUPACLOUD_APPLICATION_ENVIRONMENT_ID?.trim() || source.environment,
30087
+ applicationConfigurationPath: source.values.SUPACLOUD_APPLICATION_CONFIGURATION_PATH?.trim() || "",
30088
+ applicationConfigurationId: source.values.SUPACLOUD_APPLICATION_CONFIGURATION_ID?.trim() || "",
30089
+ applicationManifestPath: source.values.SUPACLOUD_APPLICATION_MANIFEST_PATH?.trim() || "",
30090
+ insecureTls
30091
+ };
30092
+ }
30093
+
30094
+ // src/shared/tools/developer-experience-tools.ts
29541
30095
  var initToolSchema = {
29542
30096
  root: optional(String2(), "Project directory to initialize (default: current directory)"),
29543
30097
  json: optional(Boolean2(), "Emit one machine-readable JSON result")
29544
30098
  };
29545
30099
  var environmentToolSchema = {
29546
- action: withDescription(stringEnum(["list", "check"]), "Environment profile inspection action"),
30100
+ action: withDescription(stringEnum2(["list", "check"]), "Environment profile inspection action"),
29547
30101
  root: optional(String2(), "Project directory containing .env.supacloud.* files"),
29548
30102
  json: optional(Boolean2(), "Emit one machine-readable JSON result")
29549
30103
  };
@@ -29589,8 +30143,8 @@ function outputDirectoryFor(manifest) {
29589
30143
  return "dist";
29590
30144
  }
29591
30145
  async function readPackageManifest(root) {
29592
- const path = join19(root, "package.json");
29593
- if (!existsSync15(path))
30146
+ const path = join20(root, "package.json");
30147
+ if (!existsSync17(path))
29594
30148
  return {};
29595
30149
  try {
29596
30150
  const parsed = JSON.parse(await readFile8(path, "utf8"));
@@ -29620,8 +30174,8 @@ async function writeNewFile(path, content) {
29620
30174
  }
29621
30175
  }
29622
30176
  async function ensureGitignore(root) {
29623
- const path = join19(root, ".gitignore");
29624
- const existing = existsSync15(path) ? await readFile8(path, "utf8") : "";
30177
+ const path = join20(root, ".gitignore");
30178
+ const existing = existsSync17(path) ? await readFile8(path, "utf8") : "";
29625
30179
  const required = [".env.supacloud.*", "!.env.supacloud.*.example"];
29626
30180
  const missing = required.filter((entry) => !existing.split(/\r?\n/).includes(entry));
29627
30181
  if (missing.length === 0)
@@ -29635,10 +30189,10 @@ async function ensureGitignore(root) {
29635
30189
  return true;
29636
30190
  }
29637
30191
  async function initializeProject(request, options) {
29638
- const root = resolve24(request.root || options.cwd || process.cwd());
30192
+ const root = resolve26(request.root || options.cwd || process.cwd());
29639
30193
  await mkdir7(root, { recursive: true });
29640
- const configPath = join19(root, "supacloud.json");
29641
- if (existsSync15(configPath)) {
30194
+ const configPath = join20(root, "supacloud.json");
30195
+ if (existsSync17(configPath)) {
29642
30196
  throw new Error(`supacloud.json already exists in ${root}; init never overwrites project configuration`);
29643
30197
  }
29644
30198
  const manifest = await readPackageManifest(root);
@@ -29665,10 +30219,10 @@ async function initializeProject(request, options) {
29665
30219
  throw error;
29666
30220
  }
29667
30221
  created.push("supacloud.json");
29668
- if (await writeNewFile(join19(root, ".env.supacloud.test.example"), TEST_PROFILE)) {
30222
+ if (await writeNewFile(join20(root, ".env.supacloud.test.example"), TEST_PROFILE)) {
29669
30223
  created.push(".env.supacloud.test.example");
29670
30224
  }
29671
- if (await writeNewFile(join19(root, ".env.supacloud.production.example"), PRODUCTION_PROFILE)) {
30225
+ if (await writeNewFile(join20(root, ".env.supacloud.production.example"), PRODUCTION_PROFILE)) {
29672
30226
  created.push(".env.supacloud.production.example");
29673
30227
  }
29674
30228
  if (await ensureGitignore(root))
@@ -29679,7 +30233,7 @@ async function initializeProject(request, options) {
29679
30233
  root,
29680
30234
  created,
29681
30235
  detected: {
29682
- package_json: existsSync15(join19(root, "package.json")),
30236
+ package_json: existsSync17(join20(root, "package.json")),
29683
30237
  build_script: hasBuildScript,
29684
30238
  output_directory: outputDirectory
29685
30239
  },
@@ -29713,7 +30267,7 @@ async function environmentProfiles(root) {
29713
30267
  });
29714
30268
  return entries.filter((entry) => entry.isFile()).map((entry) => {
29715
30269
  const name = profileNameFromFile(entry.name);
29716
- return name ? { name, path: join19(root, entry.name), exists: true } : null;
30270
+ return name ? { name, path: join20(root, entry.name), exists: true } : null;
29717
30271
  }).filter((profile) => profile !== null).sort((left, right) => left.name.localeCompare(right.name));
29718
30272
  }
29719
30273
  function statusMissing(context) {
@@ -29748,11 +30302,11 @@ function deployMissing(context, application, hasFrontend = false) {
29748
30302
  }
29749
30303
  async function inspectProfile(profile) {
29750
30304
  try {
29751
- const context = resolveSupaCloudContext({}, dirname12(profile.path), {
30305
+ const context = resolveSupaCloudContext2({}, dirname13(profile.path), {
29752
30306
  environmentName: profile.name
29753
30307
  });
29754
30308
  const status = statusMissing(context);
29755
- const configText = await readFile8(join19(dirname12(profile.path), "supacloud.json"), "utf8").catch((error) => {
30309
+ const configText = await readFile8(join20(dirname13(profile.path), "supacloud.json"), "utf8").catch((error) => {
29756
30310
  if (error instanceof Error && "code" in error && error.code === "ENOENT")
29757
30311
  return "{}";
29758
30312
  throw error;
@@ -29796,7 +30350,7 @@ async function inspectProfile(profile) {
29796
30350
  }
29797
30351
  }
29798
30352
  async function inspectEnvironment(request, options) {
29799
- const root = resolve24(request.root || options.cwd || process.cwd());
30353
+ const root = resolve26(request.root || options.cwd || process.cwd());
29800
30354
  const profiles = await environmentProfiles(root);
29801
30355
  if (request.action === "list") {
29802
30356
  const active = options.activeContext?.environment || null;
@@ -29852,10 +30406,109 @@ function registerDeveloperExperienceTools(server, options = {}) {
29852
30406
  registerTool(server, "init", "Create SupaCloud deployment configuration and non-secret environment examples without overwriting project files.", initToolSchema, (request) => initializeProject(request, options));
29853
30407
  registerTool(server, "env", "List or locally validate named SupaCloud environment profiles without contacting the platform or printing secrets.", environmentToolSchema, (request) => inspectEnvironment(request, options));
29854
30408
  }
30409
+
30410
+ // src/shared/tools/project-deployment.ts
30411
+ var operation2 = "applications.project_deploy";
30412
+ var scopeSchema2 = _Object_({
30413
+ project_ref: String2({ pattern: "^[a-z0-9-]{1,20}$" }),
30414
+ application_id: ApplicationIdSchema,
30415
+ environment_id: ApplicationIdSchema
30416
+ });
30417
+ var statusSchema2 = _Object_({
30418
+ schema: Literal("supacloud.project-deployment-status.v1"),
30419
+ deployment_id: ApplicationDeployRequestSchema.properties.mutation_id,
30420
+ status: Union([
30421
+ Literal("pending"),
30422
+ Literal("running"),
30423
+ Literal("succeeded"),
30424
+ Literal("failed_retryable"),
30425
+ Literal("failed_terminal"),
30426
+ Literal("outcome_unknown")
30427
+ ]),
30428
+ phase: Union([
30429
+ Literal("queued"),
30430
+ Literal("prepared"),
30431
+ Literal("migrating"),
30432
+ Literal("activating"),
30433
+ Literal("binding-web"),
30434
+ Literal("verifying")
30435
+ ]),
30436
+ operation: Literal("application.release.deploy"),
30437
+ ...scopeSchema2.properties,
30438
+ receipt: Optional(Unknown()),
30439
+ failure_code: Optional(String2({ pattern: "^[A-Z][A-Z0-9_]{0,63}$" }))
30440
+ }, { additionalProperties: false });
30441
+ function responseReceipt(data, expected) {
30442
+ if (!data || typeof data !== "object" || Array.isArray(data) || !("ok" in data) || data.ok !== true) {
30443
+ throw new Error("Invalid project deployment response");
30444
+ }
30445
+ const { ok: _ok, ...receipt } = data;
30446
+ return parseApplicationDeployResult(receipt, expected);
30447
+ }
30448
+ async function runProjectDeployment(http, args) {
30449
+ const scope = { project_ref: args.ref, application_id: args.id, environment_id: args.environment_id };
30450
+ const request = {
30451
+ mutation_id: args.mutation_id,
30452
+ release_id: args.release_id,
30453
+ ...args.configuration_id === undefined ? {} : { configuration_id: args.configuration_id },
30454
+ ...args.configuration_write === undefined ? {} : { configuration_write: args.configuration_write },
30455
+ ...args.expected_plan_sha256 === undefined ? {} : { expected_plan_sha256: args.expected_plan_sha256 },
30456
+ ...args.approved_migration_digest === undefined ? {} : { approved_migration_digest: args.approved_migration_digest },
30457
+ ...args.web_release === undefined ? {} : { web_release: args.web_release }
30458
+ };
30459
+ if (!Check2(scopeSchema2, scope) || !Check2(ApplicationDeployRequestSchema, request) || request.configuration_id !== undefined && request.configuration_write !== undefined && request.configuration_id !== request.configuration_write.configuration_id) {
30460
+ return releaseControlFailure(operation2, "INVALID_RESPONSE", null);
30461
+ }
30462
+ const expected = { ...scope, request };
30463
+ const identity = {
30464
+ ...scope,
30465
+ deployment_id: request.mutation_id,
30466
+ mutation_id: request.mutation_id,
30467
+ release_id: request.release_id
30468
+ };
30469
+ const endpoint = `/v1/projects/${encodeURIComponent(scope.project_ref)}/environments/${encodeURIComponent(scope.environment_id)}/deployments`;
30470
+ const { mutation_id, ...candidate } = request;
30471
+ const result = await http.post(endpoint, {
30472
+ ...candidate,
30473
+ deployment_id: mutation_id,
30474
+ application_id: scope.application_id
30475
+ }, { timeoutMs: 120000, maxJsonBytes: 131072, responseTimeoutMs: 30000 });
30476
+ if (result.ok && result.status === 200) {
30477
+ try {
30478
+ return releaseControlSuccess(operation2, {
30479
+ ...responseReceipt(result.data, expected),
30480
+ deployment_id: mutation_id
30481
+ });
30482
+ } catch {}
30483
+ } else if (!result.ok && !result.transportError && !result.responseReadError && result.status < 500 && result.status !== 408) {
30484
+ return releaseControlMutationFailure(operation2, result, identity);
30485
+ }
30486
+ const observed = await http.get(`${endpoint}/${encodeURIComponent(mutation_id)}`, {
30487
+ retry: false,
30488
+ totalTimeoutMs: 30000,
30489
+ maxJsonBytes: 131072
30490
+ });
30491
+ try {
30492
+ if (!observed.ok || observed.status !== 200 || !Check2(statusSchema2, observed.data))
30493
+ throw new Error;
30494
+ const state = observed.data;
30495
+ 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")
30496
+ throw new Error;
30497
+ const receipt = parseApplicationDeployResult(state.receipt, expected);
30498
+ return releaseControlSuccess(operation2, {
30499
+ ...receipt,
30500
+ deployment_id: mutation_id,
30501
+ replayed: true,
30502
+ execution_performed: false
30503
+ });
30504
+ } catch {
30505
+ return releaseControlFailure(operation2, "OUTCOME_UNKNOWN", result.transportError ? null : result.status, identity);
30506
+ }
30507
+ }
29855
30508
  // package.json
29856
30509
  var package_default = {
29857
30510
  name: "@supacloud/cli",
29858
- version: "0.97.1",
30511
+ version: "0.98.0",
29859
30512
  description: "Project-scoped CLI for SupaCloud users",
29860
30513
  type: "module",
29861
30514
  main: "./dist/index.js",
@@ -29889,7 +30542,7 @@ var package_default = {
29889
30542
  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
30543
  "build:tool-runtime": "bun build src/tool-runtime.ts --outdir dist --target node --external typebox && tsc -p tsconfig.tool-runtime.json --emitDeclarationOnly",
29891
30544
  "build:types": "tsc --emitDeclarationOnly",
29892
- "check:release-bundle": "node scripts/check-release-bundle.mjs",
30545
+ "check:release-bundle": "bun scripts/check-release-bundle.mjs",
29893
30546
  pretypecheck: "bun install --cwd ../delivery --frozen-lockfile && bun install --cwd ../compiler --frozen-lockfile && bun run --cwd ../compiler build && bun run prepare:db",
29894
30547
  prepublishOnly: "bun run build && bun run check:release-bundle",
29895
30548
  typecheck: "tsc --noEmit"
@@ -30081,10 +30734,10 @@ function captureTools(register) {
30081
30734
  function printHelp(context) {
30082
30735
  const autoLink = context.inferredSupabaseUrl ? `Project context: ${context.inferredSupabaseUrl} (${context.source})` : "Project context: not detected";
30083
30736
  console.error(`
30084
- ╔═══════════════════════════════════════════════════════════╗
30085
- ║ supacloud ║
30086
- ║ Project CLI for SupaCloud users ║
30087
- ╚═══════════════════════════════════════════════════════════╝
30737
+ \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
30738
+ \u2551 supacloud \u2551
30739
+ \u2551 Project CLI for SupaCloud users \u2551
30740
+ \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
30741
 
30089
30742
  USAGE
30090
30743
 
@@ -30302,7 +30955,7 @@ function createCliTools(context, confirmProduction) {
30302
30955
  {
30303
30956
  type: "text",
30304
30957
  text: [
30305
- "⚠️ Project commands need a Management API context.",
30958
+ "\u26A0\uFE0F Project commands need a Management API context.",
30306
30959
  "",
30307
30960
  "Provide one of these sources:",
30308
30961
  " - --env <name> for .env.supacloud.<name>",
@@ -30330,7 +30983,7 @@ function createCliTools(context, confirmProduction) {
30330
30983
  content: [
30331
30984
  {
30332
30985
  type: "text",
30333
- text: `⚠️ This command requires Management API context. Run \`${preferredCommand} status\` to inspect current detection.`
30986
+ text: `\u26A0\uFE0F This command requires Management API context. Run \`${preferredCommand} status\` to inspect current detection.`
30334
30987
  }
30335
30988
  ]
30336
30989
  })
@@ -30363,7 +31016,7 @@ function createCliTools(context, confirmProduction) {
30363
31016
  isError: true,
30364
31017
  content: [{
30365
31018
  type: "text",
30366
- text: `⚠️ Deploy requires Management API context. Run \`${preferredCommand} status\` to inspect current detection.`
31019
+ text: `\u26A0\uFE0F Deploy requires Management API context. Run \`${preferredCommand} status\` to inspect current detection.`
30367
31020
  }]
30368
31021
  })
30369
31022
  };
@@ -30391,7 +31044,7 @@ function createCliTools(context, confirmProduction) {
30391
31044
  {
30392
31045
  type: "text",
30393
31046
  text: [
30394
- `⚠️ No project context found for ${preferredCommand}.`,
31047
+ `\u26A0\uFE0F No project context found for ${preferredCommand}.`,
30395
31048
  "",
30396
31049
  `${preferredCommand} remote tools require Management API credentials.`,
30397
31050
  "Provide one of these sources:",