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