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