@supacloud/cli 0.114.0 → 0.115.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 +34 -34
- package/dist/index.js +888 -859
- package/dist/shared/cli.test.d.ts +1 -0
- package/dist/shared/tools/ai-tools.d.ts +8 -0
- package/dist/shared/tools/app-starter-database.d.ts +1 -1
- package/dist/shared/tools/app-starter-reactive.d.ts +1 -1
- package/dist/shared/tools/app-starter-templates.d.ts +3 -3
- package/dist/shared/tools/app-tools.d.ts +3 -16
- package/dist/shared/tools/db-governance-tools.d.ts +1 -0
- package/dist/shared/tools/deploy-tools.d.ts +3 -1
- package/dist/shared/tools/developer-experience-tools.d.ts +6 -1
- package/dist/shared/tools/lite-cli-tools.d.ts +1 -1
- package/dist/shared/tools/remote-dev-tools.d.ts +0 -1
- package/package.json +5 -5
- package/skills/supacloud/SKILL.md +28 -9
- package/skills/supacloud/references/app-workflow.md +52 -0
- package/skills/supacloud/references/command-map.md +11 -3
package/dist/index.js
CHANGED
|
@@ -8901,9 +8901,11 @@ function parseCliFlags(args, startIndex, schema) {
|
|
|
8901
8901
|
if (!flag)
|
|
8902
8902
|
continue;
|
|
8903
8903
|
index = flag.nextIndex;
|
|
8904
|
-
|
|
8904
|
+
const normalizedName = flag.name.replaceAll("-", "_");
|
|
8905
|
+
const schemaName = Object.hasOwn(fields, flag.name) ? flag.name : Object.hasOwn(fields, normalizedName) ? normalizedName : flag.name;
|
|
8906
|
+
if (Object.hasOwn(parsedFlags, schemaName))
|
|
8905
8907
|
throw new Error(DUPLICATE_CLI_FLAG_MESSAGE);
|
|
8906
|
-
parsedFlags[
|
|
8908
|
+
parsedFlags[schemaName] = flag.rawValue === undefined ? true : schemaCompatibleCliValue(fields[schemaName], flag.rawValue);
|
|
8907
8909
|
}
|
|
8908
8910
|
return parsedFlags;
|
|
8909
8911
|
}
|
|
@@ -8918,18 +8920,19 @@ async function runCli(cliTools, args, options = {}) {
|
|
|
8918
8920
|
const shape = getSchemaShape(tool.schema);
|
|
8919
8921
|
const actionField = shape.action;
|
|
8920
8922
|
const actionOptions = getEnumOptions(actionField);
|
|
8921
|
-
const actionless =
|
|
8923
|
+
const actionless = actionOptions.length === 0;
|
|
8922
8924
|
const otherFields = Object.entries(shape).filter(([name]) => name !== "action");
|
|
8923
8925
|
const actionLines = actionOptions.length ? `Available actions:
|
|
8924
8926
|
${actionOptions.join(`
|
|
8925
8927
|
`)}` : actionless ? "" : "This command does not declare action metadata.";
|
|
8928
|
+
const actionUsage = actionOptions.length ? toolName === "deploy" ? " [action]" : " <action>" : "";
|
|
8926
8929
|
const argLines = otherFields.length ? otherFields.map(([name, field]) => {
|
|
8927
8930
|
const description = getDescription(field) || "(no description)";
|
|
8928
8931
|
return ` --${name} ${description}`;
|
|
8929
8932
|
}).join(`
|
|
8930
8933
|
`) : " (no additional flags)";
|
|
8931
8934
|
return [
|
|
8932
|
-
`Usage: ${commandName} ${toolName}${
|
|
8935
|
+
`Usage: ${commandName} ${toolName}${actionUsage} [--flags]`,
|
|
8933
8936
|
"",
|
|
8934
8937
|
...actionLines ? [actionLines, ""] : [],
|
|
8935
8938
|
"Flags:",
|
|
@@ -9028,11 +9031,8 @@ var CORE_CONTEXT_KEYS = [
|
|
|
9028
9031
|
"SUPABASE_URL",
|
|
9029
9032
|
"SUPABASE_SERVICE_ROLE_KEY",
|
|
9030
9033
|
"SUPACLOUD_API_URL",
|
|
9031
|
-
"SUPACLOUD_MANAGEMENT_API_URL",
|
|
9032
|
-
"MANAGEMENT_API_URL",
|
|
9033
9034
|
"SUPACLOUD_API_TOKEN",
|
|
9034
9035
|
"SUPACLOUD_PROJECT_REF",
|
|
9035
|
-
"X_PROJECT_REF",
|
|
9036
9036
|
"SUPACLOUD_HOST",
|
|
9037
9037
|
"SUPACLOUD_APPLICATION_ID",
|
|
9038
9038
|
"SUPACLOUD_APPLICATION_ENVIRONMENT_ID",
|
|
@@ -9103,8 +9103,8 @@ function sourceCredentialScope(values, explicitApiUrl, supabaseUrl) {
|
|
|
9103
9103
|
}
|
|
9104
9104
|
function sourceProjectCore(values) {
|
|
9105
9105
|
const supabaseUrl = canonicalApiOrigin(values.SUPABASE_URL || "");
|
|
9106
|
-
const projectRef = (values.SUPACLOUD_PROJECT_REF ||
|
|
9107
|
-
const explicitApiUrl = values.SUPACLOUD_API_URL ||
|
|
9106
|
+
const projectRef = (values.SUPACLOUD_PROJECT_REF || "").trim() || inferProjectRefFromSupabaseUrl(supabaseUrl);
|
|
9107
|
+
const explicitApiUrl = values.SUPACLOUD_API_URL || "";
|
|
9108
9108
|
const credentialScope = sourceCredentialScope(values, explicitApiUrl, supabaseUrl);
|
|
9109
9109
|
const managementUrl = explicitApiUrl || (values.SUPACLOUD_HOST ? `http://${values.SUPACLOUD_HOST}:9090` : "");
|
|
9110
9110
|
const apiUrl = credentialScope === "management" ? canonicalApiOrigin(managementUrl) : "";
|
|
@@ -9125,17 +9125,14 @@ function completeProjectContext(values) {
|
|
|
9125
9125
|
return Boolean(core.apiUrl && core.apiToken && core.projectRef);
|
|
9126
9126
|
}
|
|
9127
9127
|
function parseInsecureTls(values) {
|
|
9128
|
-
|
|
9129
|
-
|
|
9130
|
-
|
|
9131
|
-
|
|
9132
|
-
|
|
9133
|
-
|
|
9134
|
-
|
|
9135
|
-
|
|
9136
|
-
throw new Error(`${key} must be a boolean (true or false)`);
|
|
9137
|
-
}
|
|
9138
|
-
return true;
|
|
9128
|
+
const value = values.SUPACLOUD_TLS_VERIFY?.trim().toLowerCase();
|
|
9129
|
+
if (value === undefined)
|
|
9130
|
+
return true;
|
|
9131
|
+
if (["1", "true", "yes", "on"].includes(value))
|
|
9132
|
+
return false;
|
|
9133
|
+
if (["0", "false", "no", "off"].includes(value))
|
|
9134
|
+
return true;
|
|
9135
|
+
throw new Error("SUPACLOUD_TLS_VERIFY must be a boolean (true or false)");
|
|
9139
9136
|
}
|
|
9140
9137
|
function namedEnvironmentSource(cwd, selector) {
|
|
9141
9138
|
const environment = normalizeEnvironmentName(selector);
|
|
@@ -9270,7 +9267,7 @@ function parseGlobalOptions(args) {
|
|
|
9270
9267
|
|
|
9271
9268
|
// src/shared/execution-policy.ts
|
|
9272
9269
|
var ACTION_POLICY = {
|
|
9273
|
-
deploy: { write: ["deploy"] },
|
|
9270
|
+
deploy: { local: ["init"], write: ["deploy"] },
|
|
9274
9271
|
applications: {
|
|
9275
9272
|
read: ["list_releases", "get_release", "get_runtime", "get_deployment_evidence", "get_configuration", "get_release_transfer_plan", "get_promotion_plan", "get_promotion_status", "get_rollback_snapshot", "get_history", "get_deploy_plan", "logs", "get_preview_plan", "list_previews", "get_preview"],
|
|
9276
9273
|
write: ["upload_release", "put_configuration", "deploy_application", "transfer_release", "promote_application", "reconcile_promotion", "create_preview", "reconcile_preview", "cleanup_preview", "activate_release", "deploy_release", "rollback_release", "reconcile_activation", "retire_activation"]
|
|
@@ -9281,7 +9278,7 @@ var ACTION_POLICY = {
|
|
|
9281
9278
|
local: ["list"]
|
|
9282
9279
|
},
|
|
9283
9280
|
database: {
|
|
9284
|
-
read: ["list_tables", "describe_columns", "export_svadmin_schema", "list_indexes", "list_constraints", "list_extensions", "extension_catalog", "rls_status", "rls_policies", "list_auth_users", "get_auth_user", "connections", "stats", "slow_queries", "list_migrations", "migration_inventory", "migration_import", "delivery_migration_plan", "project_url", "generate_types", "database_lint", "db_lint", "rpc_catalog", "list_rpcs"],
|
|
9281
|
+
read: ["list_tables", "describe_columns", "export_svadmin_schema", "list_indexes", "list_constraints", "list_extensions", "extension_catalog", "rls_status", "rls_policies", "list_auth_users", "get_auth_user", "connections", "stats", "slow_queries", "list_migrations", "migration_inventory", "migration_import", "delivery_migration_plan", "project_url", "generate_types", "database_lint", "db_lint", "rpc_catalog", "list_rpcs", "governance_check", "role_check"],
|
|
9285
9282
|
local: ["lint_migrations", "lint", "rebase_migrations"],
|
|
9286
9283
|
write: ["query", "execute", "apply_migration", "push_migrations", "baseline_migrations", "create_table_rls", "enable_extension", "disable_extension"]
|
|
9287
9284
|
},
|
|
@@ -12930,7 +12927,9 @@ function registerDatabaseTools(server, http, config = {}) {
|
|
|
12930
12927
|
"db_lint",
|
|
12931
12928
|
"rpc_catalog",
|
|
12932
12929
|
"list_rpcs",
|
|
12933
|
-
"migration_import"
|
|
12930
|
+
"migration_import",
|
|
12931
|
+
"governance_check",
|
|
12932
|
+
"role_check"
|
|
12934
12933
|
];
|
|
12935
12934
|
const writeActions = ["execute", "apply_migration", "push_migrations", "baseline_migrations", "create_table_rls", "enable_extension", "disable_extension"];
|
|
12936
12935
|
const remoteActions = [...readActions, ...localActions];
|
|
@@ -12948,10 +12947,11 @@ Actions: ${allActions.join(", ")}${localOnly ? " (local-only mode)" : readOnly ?
|
|
|
12948
12947
|
strict: optional(Boolean2(), "[push_migrations/lint_migrations/lint/database_lint] Exit with error if high-risk issues or migrations are detected"),
|
|
12949
12948
|
fail_on_high: optional(Boolean2(), "[push_migrations/lint_migrations/lint/database_lint] Alias for --strict"),
|
|
12950
12949
|
fail_on_medium: optional(Boolean2(), "[push_migrations/lint_migrations/lint] Exit with error if medium-risk or high-risk migrations are detected"),
|
|
12951
|
-
|
|
12950
|
+
format: optional(stringEnum2(["text", "json"]), "[lint_migrations/lint/database_lint/rpc_catalog] Output format (default: text)"),
|
|
12952
12951
|
schema: optional(String2(), "[describe_columns/list_indexes/list_constraints/rls_status/rls_policies/create_table_rls/database_lint] Schema name (default: public)"),
|
|
12953
12952
|
table: optional(String2(), "[describe_columns/indexes/constraints/rls_*] Table name"),
|
|
12954
|
-
schemas: optional(_Array_(String2()), "[list_tables/generate_types/rpc_catalog] Schemas array"),
|
|
12953
|
+
schemas: optional(_Array_(String2()), "[list_tables/generate_types/rpc_catalog/governance_check/role_check] Schemas array"),
|
|
12954
|
+
modules: optional(_Array_(Record(String2(), Unknown()), { maxItems: 64 }), "[governance_check] JSON-safe database module declarations"),
|
|
12955
12955
|
user_id: optional(String2(), "[get_auth_user] User UUID"),
|
|
12956
12956
|
limit: optional(Number2(), "[list_auth_users] Max users (default: 20)"),
|
|
12957
12957
|
name: optional(String2(), "[apply_migration] Migration name"),
|
|
@@ -13109,17 +13109,21 @@ Actions: ${allActions.join(", ")}${localOnly ? " (local-only mode)" : readOnly ?
|
|
|
13109
13109
|
case "export_svadmin_schema": {
|
|
13110
13110
|
if (!args.table)
|
|
13111
13111
|
throw new Error("'table' required");
|
|
13112
|
-
const
|
|
13113
|
-
const
|
|
13114
|
-
const
|
|
13112
|
+
const schemaName = validatedIdentifier(schema, "schema");
|
|
13113
|
+
const tableName = validatedIdentifier(args.table, "table");
|
|
13114
|
+
const schemaLiteral = `'${schemaName}'`;
|
|
13115
|
+
const tableLiteral = `'${tableName}'`;
|
|
13116
|
+
const regclassLiteral = `'${schemaName}.${tableName}'`;
|
|
13115
13117
|
const colSql = `SELECT column_name, data_type, is_nullable, column_default, character_maximum_length FROM information_schema.columns WHERE table_schema = ${schemaLiteral} AND table_name = ${tableLiteral} ORDER BY ordinal_position;`;
|
|
13116
13118
|
const colRes = await execSql(colSql);
|
|
13117
13119
|
if (!colRes.ok)
|
|
13118
13120
|
throw new Error(`Failed to inspect columns (${colRes.status})`);
|
|
13119
|
-
const conSql = `SELECT conname as name, contype as type, pg_get_constraintdef(oid) as definition FROM pg_constraint WHERE connamespace = ${schemaLiteral}::regnamespace AND conrelid = ${
|
|
13121
|
+
const conSql = `SELECT conname as name, contype as type, pg_get_constraintdef(oid) as definition FROM pg_constraint WHERE connamespace = ${schemaLiteral}::regnamespace AND conrelid = ${regclassLiteral}::regclass;`;
|
|
13120
13122
|
const conRes = await execSql(conSql);
|
|
13121
|
-
|
|
13122
|
-
|
|
13123
|
+
if (!conRes.ok)
|
|
13124
|
+
throw new Error(`Failed to inspect constraints (${conRes.status})`);
|
|
13125
|
+
const cols = buildSvadminColumns(colRes.data, conRes.data);
|
|
13126
|
+
if (args.format === "json") {
|
|
13123
13127
|
return {
|
|
13124
13128
|
content: [{
|
|
13125
13129
|
type: "text",
|
|
@@ -13295,7 +13299,7 @@ Actions: ${allActions.join(", ")}${localOnly ? " (local-only mode)" : readOnly ?
|
|
|
13295
13299
|
migrationFiles = sortMigrationFiles(files.map((file) => readMigrationFile(dir, file)));
|
|
13296
13300
|
}
|
|
13297
13301
|
const analysis = analyzeMigrationFiles(migrationFiles);
|
|
13298
|
-
if (args.json) {
|
|
13302
|
+
if (args.format === "json") {
|
|
13299
13303
|
text = JSON.stringify(analysis, null, 2);
|
|
13300
13304
|
} else {
|
|
13301
13305
|
text = formatMigrationRiskReport(analysis);
|
|
@@ -13530,7 +13534,7 @@ ${riskReport}`
|
|
|
13530
13534
|
if (!r.ok) {
|
|
13531
13535
|
return { content: [{ type: "text", text: `❌ Database lint request failed (${r.status})` }], isError: true };
|
|
13532
13536
|
}
|
|
13533
|
-
if (args.json) {
|
|
13537
|
+
if (args.format === "json") {
|
|
13534
13538
|
text = JSON.stringify(r.data, null, 2);
|
|
13535
13539
|
} else {
|
|
13536
13540
|
text = formatDatabaseLintReport(r.data);
|
|
@@ -13545,6 +13549,45 @@ ${riskReport}`
|
|
|
13545
13549
|
}
|
|
13546
13550
|
break;
|
|
13547
13551
|
}
|
|
13552
|
+
case "governance_check": {
|
|
13553
|
+
const schemasList = args.schemas && args.schemas.length > 0 ? args.schemas : args.schema ? [args.schema] : ["public"];
|
|
13554
|
+
const safeRef = projectRefPathSegment(ref, "governance_check");
|
|
13555
|
+
const r = await managementHttp().post(`/v1/projects/${safeRef}/database/module-check`, {
|
|
13556
|
+
modules: args.modules,
|
|
13557
|
+
schemas: schemasList
|
|
13558
|
+
});
|
|
13559
|
+
if (!r.ok) {
|
|
13560
|
+
return { content: [{ type: "text", text: `❌ Database module check failed (${r.status})` }], isError: true };
|
|
13561
|
+
}
|
|
13562
|
+
const result = recordPayload(r.data);
|
|
13563
|
+
if (!result || typeof result["ok"] !== "boolean" || !Array.isArray(result["reports"])) {
|
|
13564
|
+
throw new Error("Invalid database module check response");
|
|
13565
|
+
}
|
|
13566
|
+
text = formatModuleCheckReport(r.data);
|
|
13567
|
+
if (!result["ok"]) {
|
|
13568
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
13569
|
+
}
|
|
13570
|
+
break;
|
|
13571
|
+
}
|
|
13572
|
+
case "role_check": {
|
|
13573
|
+
const schemasList = args.schemas && args.schemas.length > 0 ? args.schemas : args.schema ? [args.schema] : ["public"];
|
|
13574
|
+
const safeRef = projectRefPathSegment(ref, "role_check");
|
|
13575
|
+
const r = await managementHttp().post(`/v1/projects/${safeRef}/database/role-check`, {
|
|
13576
|
+
schemas: schemasList
|
|
13577
|
+
});
|
|
13578
|
+
if (!r.ok) {
|
|
13579
|
+
return { content: [{ type: "text", text: `❌ Database role check failed (${r.status})` }], isError: true };
|
|
13580
|
+
}
|
|
13581
|
+
const result = recordPayload(r.data);
|
|
13582
|
+
if (!result || typeof result["ok"] !== "boolean" || !recordPayload(result["state"]) || result["error"] !== undefined && typeof result["error"] !== "string") {
|
|
13583
|
+
throw new Error("Invalid database role check response");
|
|
13584
|
+
}
|
|
13585
|
+
text = JSON.stringify(r.data, null, 2);
|
|
13586
|
+
if (!result["ok"]) {
|
|
13587
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
13588
|
+
}
|
|
13589
|
+
break;
|
|
13590
|
+
}
|
|
13548
13591
|
case "rpc_catalog":
|
|
13549
13592
|
case "list_rpcs": {
|
|
13550
13593
|
const schemasList = args.schemas && args.schemas.length > 0 ? args.schemas : args.schema ? [args.schema] : ["public", "api"];
|
|
@@ -13553,7 +13596,7 @@ ${riskReport}`
|
|
|
13553
13596
|
if (!r.ok) {
|
|
13554
13597
|
return { content: [{ type: "text", text: `❌ RPC catalog request failed (${r.status})` }], isError: true };
|
|
13555
13598
|
}
|
|
13556
|
-
if (args.json) {
|
|
13599
|
+
if (args.format === "json") {
|
|
13557
13600
|
text = JSON.stringify(r.data, null, 2);
|
|
13558
13601
|
} else {
|
|
13559
13602
|
text = formatRpcCatalog(r.data);
|
|
@@ -13569,9 +13612,6 @@ ${riskReport}`
|
|
|
13569
13612
|
function quoteIdentifier(value, label) {
|
|
13570
13613
|
return `"${validatedIdentifier(value, label)}"`;
|
|
13571
13614
|
}
|
|
13572
|
-
function sqlStringLiteral(value, label) {
|
|
13573
|
-
return `'${validatedIdentifier(value, label)}'`;
|
|
13574
|
-
}
|
|
13575
13615
|
function validatedIdentifier(value, label) {
|
|
13576
13616
|
if (typeof value !== "string" || !/^[A-Za-z_][A-Za-z0-9_]{0,62}$/.test(value)) {
|
|
13577
13617
|
throw new Error(`Invalid ${label} identifier`);
|
|
@@ -13632,6 +13672,34 @@ function formatDatabaseLintReport(data) {
|
|
|
13632
13672
|
return lines.join(`
|
|
13633
13673
|
`).trimEnd();
|
|
13634
13674
|
}
|
|
13675
|
+
function formatModuleCheckReport(data) {
|
|
13676
|
+
const result = recordPayload(data);
|
|
13677
|
+
const reports = Array.isArray(result?.["reports"]) ? result["reports"] : [];
|
|
13678
|
+
const lines = [];
|
|
13679
|
+
for (const raw of reports) {
|
|
13680
|
+
const report = recordPayload(raw);
|
|
13681
|
+
if (!report)
|
|
13682
|
+
continue;
|
|
13683
|
+
const name = typeof report["module"] === "string" ? report["module"] : "unknown";
|
|
13684
|
+
const ok = report["ok"] === true;
|
|
13685
|
+
lines.push(`module: ${name} (${ok ? "ok" : "FAILED"})`);
|
|
13686
|
+
const issues = Array.isArray(report["issues"]) ? report["issues"] : [];
|
|
13687
|
+
if (issues.length === 0)
|
|
13688
|
+
lines.push(" no issues");
|
|
13689
|
+
for (const rawIssue of issues) {
|
|
13690
|
+
const issue = recordPayload(rawIssue);
|
|
13691
|
+
if (!issue)
|
|
13692
|
+
continue;
|
|
13693
|
+
const severity = typeof issue["severity"] === "string" ? issue["severity"] : "error";
|
|
13694
|
+
const code = typeof issue["code"] === "string" ? issue["code"] : "unknown";
|
|
13695
|
+
const object = typeof issue["object"] === "string" ? issue["object"] : "?";
|
|
13696
|
+
const message = typeof issue["message"] === "string" ? issue["message"] : "";
|
|
13697
|
+
lines.push(` ${severity} ${code} ${object} ${message}`);
|
|
13698
|
+
}
|
|
13699
|
+
}
|
|
13700
|
+
return lines.length > 0 ? lines.join(`
|
|
13701
|
+
`) : JSON.stringify(data, null, 2);
|
|
13702
|
+
}
|
|
13635
13703
|
function formatRpcCatalog(data) {
|
|
13636
13704
|
if (!data || typeof data !== "object")
|
|
13637
13705
|
return JSON.stringify(data, null, 2);
|
|
@@ -13795,17 +13863,17 @@ function buildSvadminColumns(colData, conData) {
|
|
|
13795
13863
|
});
|
|
13796
13864
|
}
|
|
13797
13865
|
function formatSvadminSchemaExport(table, columns) {
|
|
13798
|
-
const
|
|
13799
|
-
|
|
13866
|
+
const payload = {
|
|
13867
|
+
resource: table,
|
|
13868
|
+
sourceType: "databaseColumns",
|
|
13869
|
+
columns
|
|
13870
|
+
};
|
|
13871
|
+
return `SVAdmin schema for '${table}'
|
|
13800
13872
|
|
|
13801
|
-
` + `Pass this
|
|
13802
|
-
` +
|
|
13803
|
-
` + ` resource: '${table}',
|
|
13804
|
-
` + ` sourceType: 'databaseColumns',
|
|
13805
|
-
` + ` payload: ${JSON.stringify(jsonPayload)}
|
|
13806
|
-
` + ` })
|
|
13873
|
+
` + `Pass this object to svadmin_generateResource. The CLI does not generate the resource.
|
|
13874
|
+
` + `${JSON.stringify(payload, null, 2)}
|
|
13807
13875
|
|
|
13808
|
-
` + `Column
|
|
13876
|
+
` + `Column metadata (${columns.length}):
|
|
13809
13877
|
` + columns.map((c) => ` - ${c.name}: ${c.type}${c.isPrimary ? " [PK]" : ""}${c.isForeignKey ? ` [FK -> ${c.referencesResource ?? "inferred"}]` : ""}${c.nullable ? " (nullable)" : " (required)"}`).join(`
|
|
13810
13878
|
`);
|
|
13811
13879
|
}
|
|
@@ -15629,7 +15697,7 @@ function scaffoldFiles(framework, slug) {
|
|
|
15629
15697
|
"package.json": JSON.stringify({
|
|
15630
15698
|
private: true,
|
|
15631
15699
|
type: "module",
|
|
15632
|
-
dependencies: { elysia: "2.0.0-rc.
|
|
15700
|
+
dependencies: { elysia: "2.0.0-rc.1" }
|
|
15633
15701
|
}, null, 2) + `
|
|
15634
15702
|
`,
|
|
15635
15703
|
"index.ts": `import { Elysia } from "elysia";
|
|
@@ -20801,13 +20869,13 @@ function registerBranchTools(server, http, options = {}) {
|
|
|
20801
20869
|
data_mode: optional(stringEnum2(["schema_only", "full_clone"]), "[create] Preview data mode (default: schema_only)"),
|
|
20802
20870
|
plan_checksum: optional(String2(), "[promote] Reviewed plan checksum from promotion_plan"),
|
|
20803
20871
|
confirm_destructive: optional(Boolean2(), "[promote] Confirm reviewed destructive migrations"),
|
|
20804
|
-
|
|
20872
|
+
format: optional(stringEnum2(["text", "json"]), "[promotion_plan/promote] Output format (default: text)")
|
|
20805
20873
|
}, async (args) => {
|
|
20806
20874
|
const action = requireString(args.action, "action");
|
|
20807
20875
|
const ref = resolveProjectRef(args.ref, options.projectRef);
|
|
20808
20876
|
const writeAction = action === "create" || action === "delete" || action === "promote";
|
|
20809
20877
|
if (writeAction && options.readOnly) {
|
|
20810
|
-
return args.json && action === "promote" ? releaseControlFailure("branch.promote", "MUTATION_NOT_SUCCEEDED", null, {
|
|
20878
|
+
return args.format === "json" && action === "promote" ? releaseControlFailure("branch.promote", "MUTATION_NOT_SUCCEEDED", null, {
|
|
20811
20879
|
project_ref: ref,
|
|
20812
20880
|
branch_ref: args.branch_ref,
|
|
20813
20881
|
reason: "READ_ONLY"
|
|
@@ -20833,13 +20901,13 @@ function registerBranchTools(server, http, options = {}) {
|
|
|
20833
20901
|
responseTimeoutMs: 5000
|
|
20834
20902
|
});
|
|
20835
20903
|
if (!result.ok) {
|
|
20836
|
-
return promotionFailure(releaseControlFailure("branch.promotion_plan", "HTTP_ERROR", result.transportError ? null : result.status, scope), args.
|
|
20904
|
+
return promotionFailure(releaseControlFailure("branch.promotion_plan", "HTTP_ERROR", result.transportError ? null : result.status, scope), args.format === "json", false);
|
|
20837
20905
|
}
|
|
20838
20906
|
const plan = parsePromotionPlan(result.data, { parentRef: ref, branchRef });
|
|
20839
20907
|
if (result.status !== 200 || !plan) {
|
|
20840
|
-
return promotionFailure(releaseControlFailure("branch.promotion_plan", "INVALID_RESPONSE", result.status, scope), args.
|
|
20908
|
+
return promotionFailure(releaseControlFailure("branch.promotion_plan", "INVALID_RESPONSE", result.status, scope), args.format === "json", false);
|
|
20841
20909
|
}
|
|
20842
|
-
return args.json ? releaseControlSuccess("branch.promotion_plan", { ...scope, plan }) : { content: [{ type: "text", text: formatPromotionPlan(plan) }] };
|
|
20910
|
+
return args.format === "json" ? releaseControlSuccess("branch.promotion_plan", { ...scope, plan }) : { content: [{ type: "text", text: formatPromotionPlan(plan) }] };
|
|
20843
20911
|
} else if (action === "promote") {
|
|
20844
20912
|
const branchRef = requireString(args.branch_ref, "branch_ref");
|
|
20845
20913
|
const planChecksum = requireString(args.plan_checksum, "plan_checksum");
|
|
@@ -20858,18 +20926,18 @@ function registerBranchTools(server, http, options = {}) {
|
|
|
20858
20926
|
});
|
|
20859
20927
|
const beforeMutation = { ...state, mutation_sent: false };
|
|
20860
20928
|
if (!preflight.ok) {
|
|
20861
|
-
return promotionFailure(releaseControlFailure("branch.promote", "HTTP_ERROR", preflight.transportError ? null : preflight.status, { ...beforeMutation, reason: "PREFLIGHT_FAILED" }), args.
|
|
20929
|
+
return promotionFailure(releaseControlFailure("branch.promote", "HTTP_ERROR", preflight.transportError ? null : preflight.status, { ...beforeMutation, reason: "PREFLIGHT_FAILED" }), args.format === "json", false);
|
|
20862
20930
|
}
|
|
20863
20931
|
const plan = parsePromotionPlan(preflight.data, { parentRef: ref, branchRef });
|
|
20864
20932
|
if (preflight.status !== 200 || !plan) {
|
|
20865
|
-
return promotionFailure(releaseControlFailure("branch.promote", "INVALID_RESPONSE", preflight.status, { ...beforeMutation, reason: "PREFLIGHT_UNVERIFIED" }), args.
|
|
20933
|
+
return promotionFailure(releaseControlFailure("branch.promote", "INVALID_RESPONSE", preflight.status, { ...beforeMutation, reason: "PREFLIGHT_UNVERIFIED" }), args.format === "json", false);
|
|
20866
20934
|
}
|
|
20867
20935
|
const preflightReason = plan.plan_checksum !== planChecksum ? "PLAN_CHANGED" : !plan.safe_to_apply ? "PLAN_BLOCKED" : plan.requires_destructive_confirmation && args.confirm_destructive !== true ? "DESTRUCTIVE_CONFIRMATION_REQUIRED" : null;
|
|
20868
20936
|
if (preflightReason) {
|
|
20869
|
-
return promotionFailure(releaseControlFailure("branch.promote", "MUTATION_NOT_SUCCEEDED", null, { ...beforeMutation, reason: preflightReason, observed_plan_checksum: plan.plan_checksum }), args.
|
|
20937
|
+
return promotionFailure(releaseControlFailure("branch.promote", "MUTATION_NOT_SUCCEEDED", null, { ...beforeMutation, reason: preflightReason, observed_plan_checksum: plan.plan_checksum }), args.format === "json", false);
|
|
20870
20938
|
}
|
|
20871
20939
|
if (plan.pending.length === 0) {
|
|
20872
|
-
return args.json ? releaseControlSuccess("branch.promote", {
|
|
20940
|
+
return args.format === "json" ? releaseControlSuccess("branch.promote", {
|
|
20873
20941
|
...beforeMutation,
|
|
20874
20942
|
promoted: false,
|
|
20875
20943
|
unchanged: true,
|
|
@@ -20895,13 +20963,13 @@ function registerBranchTools(server, http, options = {}) {
|
|
|
20895
20963
|
mutation_sent: true,
|
|
20896
20964
|
reported_applied_versions: appliedVersions(result),
|
|
20897
20965
|
...safePromotionCode(result) ? { server_code: safePromotionCode(result) } : {}
|
|
20898
|
-
}), args.
|
|
20966
|
+
}), args.format === "json", result.transportError === true || result.responseReadError === true || result.status === 408 || result.status >= 500);
|
|
20899
20967
|
}
|
|
20900
20968
|
const promoted = parsePromotionResult(result.data, { parentRef: ref, branchRef }, plan.pending);
|
|
20901
20969
|
if (result.status !== 200 || !promoted) {
|
|
20902
|
-
return promotionFailure(releaseControlFailure("branch.promote", "OUTCOME_UNKNOWN", result.status, { ...state, mutation_sent: true }), args.
|
|
20970
|
+
return promotionFailure(releaseControlFailure("branch.promote", "OUTCOME_UNKNOWN", result.status, { ...state, mutation_sent: true }), args.format === "json", true);
|
|
20903
20971
|
}
|
|
20904
|
-
return args.json ? releaseControlSuccess("branch.promote", {
|
|
20972
|
+
return args.format === "json" ? releaseControlSuccess("branch.promote", {
|
|
20905
20973
|
...promoted,
|
|
20906
20974
|
unchanged: false,
|
|
20907
20975
|
mutation_sent: true,
|
|
@@ -21574,7 +21642,8 @@ function buildLiteArgs(request) {
|
|
|
21574
21642
|
booleanFlag(args, "--service-role", request.service_role);
|
|
21575
21643
|
booleanFlag(args, "--force", request.force);
|
|
21576
21644
|
booleanFlag(args, "--memory", request.memory);
|
|
21577
|
-
|
|
21645
|
+
if (request.format === "json")
|
|
21646
|
+
args.push("--json");
|
|
21578
21647
|
if (request.baseline && request.action !== "db_pull")
|
|
21579
21648
|
throw new Error("baseline requires db_pull");
|
|
21580
21649
|
booleanFlag(args, "--baseline", request.baseline);
|
|
@@ -21699,7 +21768,7 @@ function registerLiteCliTools(server, options = {}) {
|
|
|
21699
21768
|
service_role: optional(Boolean2(), "[keys] Also print the service_role key"),
|
|
21700
21769
|
force: optional(Boolean2(), "[snapshot_restore] Replace non-empty restore targets"),
|
|
21701
21770
|
memory: optional(Boolean2(), "[*] Use an in-memory PGlite database"),
|
|
21702
|
-
|
|
21771
|
+
format: optional(stringEnum2(["text", "json"]), "[doctor] Output format (default: text)"),
|
|
21703
21772
|
baseline: optional(Boolean2(), "[db_pull] Record already-present schema in local migration history after review"),
|
|
21704
21773
|
production: optional(Boolean2(), "[doctor] Add single-node production readiness checks"),
|
|
21705
21774
|
runtime_mode: optional(stringEnum2(["development", "strict"]), "[*] Lite runtime mode"),
|
|
@@ -21858,6 +21927,26 @@ function resolveDefaultCodexSkillRoot(environment = process.env, homeDirectory =
|
|
|
21858
21927
|
const codexHome = environment.CODEX_HOME?.trim();
|
|
21859
21928
|
return join8(resolve12(codexHome || join8(homeDirectory, ".codex")), "skills");
|
|
21860
21929
|
}
|
|
21930
|
+
var SKILL_TARGET_ALIASES = ["codex", "claude", "agents", "project"];
|
|
21931
|
+
function resolveSkillTargetRoot(target, options = {}) {
|
|
21932
|
+
const environment = options.environment ?? process.env;
|
|
21933
|
+
const homeDirectory = options.homeDirectory ?? homedir2();
|
|
21934
|
+
const workingDirectory = options.workingDirectory ?? process.cwd();
|
|
21935
|
+
const normalized = target?.trim().toLowerCase();
|
|
21936
|
+
if (!normalized || normalized === "codex")
|
|
21937
|
+
return resolveDefaultCodexSkillRoot(environment, homeDirectory);
|
|
21938
|
+
if (normalized === "claude") {
|
|
21939
|
+
const config = environment.CLAUDE_CONFIG_DIR?.trim();
|
|
21940
|
+
return join8(resolve12(config || join8(homeDirectory, ".claude")), "skills");
|
|
21941
|
+
}
|
|
21942
|
+
if (normalized === "agents") {
|
|
21943
|
+
const config = environment.AGENTS_HOME?.trim();
|
|
21944
|
+
return join8(resolve12(config || join8(homeDirectory, ".agents")), "skills");
|
|
21945
|
+
}
|
|
21946
|
+
if (normalized === "project")
|
|
21947
|
+
return join8(resolve12(workingDirectory), ".agents", "skills");
|
|
21948
|
+
return resolve12(target);
|
|
21949
|
+
}
|
|
21861
21950
|
function resolveBundledSkillDirectory(moduleUrl = import.meta.url) {
|
|
21862
21951
|
const moduleDirectory = dirname5(fileURLToPath(moduleUrl));
|
|
21863
21952
|
const candidates = [
|
|
@@ -21875,7 +21964,7 @@ function textResponse(payload) {
|
|
|
21875
21964
|
function registerAiTools(server) {
|
|
21876
21965
|
registerTool(server, "ai", "Install or inspect the bundled SupaCloud CLI AI skill. Actions: show_skill, install_skill", {
|
|
21877
21966
|
action: stringEnum2(["show_skill", "install_skill"]),
|
|
21878
|
-
target: optional(String2(), "[install_skill]
|
|
21967
|
+
target: optional(String2(), "[install_skill] codex|claude|agents|project or an explicit Skill root directory"),
|
|
21879
21968
|
dry_run: optional(Boolean2(), "[install_skill] Preview the installation without writing files"),
|
|
21880
21969
|
force: optional(Boolean2(), "[install_skill] Back up and replace different existing skill content")
|
|
21881
21970
|
}, async (request) => {
|
|
@@ -21886,12 +21975,17 @@ function registerAiTools(server) {
|
|
|
21886
21975
|
name: SKILL_NAME,
|
|
21887
21976
|
sourceDirectory,
|
|
21888
21977
|
defaultTargetRoot,
|
|
21889
|
-
defaultDestination: join8(defaultTargetRoot, SKILL_NAME)
|
|
21978
|
+
defaultDestination: join8(defaultTargetRoot, SKILL_NAME),
|
|
21979
|
+
targetAliases: Object.fromEntries(SKILL_TARGET_ALIASES.map((alias) => [
|
|
21980
|
+
alias,
|
|
21981
|
+
resolveSkillTargetRoot(alias)
|
|
21982
|
+
]))
|
|
21890
21983
|
});
|
|
21891
21984
|
}
|
|
21985
|
+
const targetRoot = resolveSkillTargetRoot(request.target);
|
|
21892
21986
|
return textResponse(installSkill({
|
|
21893
21987
|
sourceDirectory,
|
|
21894
|
-
targetRoot
|
|
21988
|
+
targetRoot,
|
|
21895
21989
|
mode: request.dry_run ? "dry-run" : "write",
|
|
21896
21990
|
conflictPolicy: request.force ? "replace" : "reject",
|
|
21897
21991
|
now: new Date
|
|
@@ -22091,16 +22185,16 @@ var starter_metadata_default = {
|
|
|
22091
22185
|
generatedBy: "supacloud/starter-metadata-v1",
|
|
22092
22186
|
packages: {
|
|
22093
22187
|
compiler: {
|
|
22094
|
-
version: "0.
|
|
22188
|
+
version: "0.54.0"
|
|
22095
22189
|
},
|
|
22096
22190
|
app: {
|
|
22097
|
-
version: "0.
|
|
22191
|
+
version: "0.36.0",
|
|
22098
22192
|
dependencies: {
|
|
22099
22193
|
rxjs: "7.8.2"
|
|
22100
22194
|
}
|
|
22101
22195
|
},
|
|
22102
22196
|
elysia: {
|
|
22103
|
-
version: "0.
|
|
22197
|
+
version: "0.38.0"
|
|
22104
22198
|
},
|
|
22105
22199
|
commands: {
|
|
22106
22200
|
version: "0.11.2"
|
|
@@ -22109,7 +22203,7 @@ var starter_metadata_default = {
|
|
|
22109
22203
|
version: "0.15.2"
|
|
22110
22204
|
},
|
|
22111
22205
|
db: {
|
|
22112
|
-
version: "0.20.
|
|
22206
|
+
version: "0.20.4",
|
|
22113
22207
|
peerDependencies: {
|
|
22114
22208
|
"drizzle-orm": "1.0.0-rc.5-5935859"
|
|
22115
22209
|
},
|
|
@@ -22195,6 +22289,10 @@ requirement and equivalent lifecycle tests.
|
|
|
22195
22289
|
Bind subscriptions to an explicit owner and propagate cancellation to native I/O.
|
|
22196
22290
|
Never automatically retry a write or turn observable delivery into durable ack.
|
|
22197
22291
|
Do not bypass authorization, transactions, idempotency or audit to simplify code.
|
|
22292
|
+
Start with \`supacloud app context --format json\` and \`supacloud app doctor --format json\`;
|
|
22293
|
+
do not scan the whole repository first or treat generated/ as source. Apply only
|
|
22294
|
+
reviewed diagnostic fixes with \`supacloud app fix --fix <file>\`, then re-run check
|
|
22295
|
+
and doctor. Never weaken compiler governance to hide a diagnostic.
|
|
22198
22296
|
Run bun run check; include negative and cleanup tests. Never store live credentials.
|
|
22199
22297
|
`;
|
|
22200
22298
|
var STARTER_REACTIVE_TEST = `import { expect, test } from "bun:test";
|
|
@@ -22436,7 +22534,6 @@ export function createCompiledModules(): never {
|
|
|
22436
22534
|
}
|
|
22437
22535
|
`;
|
|
22438
22536
|
function baseFiles(name, sdkDependencies) {
|
|
22439
|
-
const minimal = sdkDependencies !== undefined;
|
|
22440
22537
|
return {
|
|
22441
22538
|
"package.json": json({
|
|
22442
22539
|
name,
|
|
@@ -22462,8 +22559,8 @@ function baseFiles(name, sdkDependencies) {
|
|
|
22462
22559
|
"@supacloud/app": `^${appMetadata.version}`,
|
|
22463
22560
|
"@supacloud/elysia": `^${elysiaMetadata.version}`,
|
|
22464
22561
|
effect: "4.0.5",
|
|
22465
|
-
elysia: "2.0.0-rc.
|
|
22466
|
-
...sdkDependencies
|
|
22562
|
+
elysia: "2.0.0-rc.1",
|
|
22563
|
+
...sdkDependencies
|
|
22467
22564
|
},
|
|
22468
22565
|
devDependencies: {
|
|
22469
22566
|
"@supacloud/compiler": `^${compilerMetadata.version}`,
|
|
@@ -22508,253 +22605,16 @@ PORT=3000
|
|
|
22508
22605
|
`,
|
|
22509
22606
|
"supacloud.config.ts": `import { defineSupacloudConfig } from "@supacloud/compiler";
|
|
22510
22607
|
|
|
22511
|
-
export default defineSupacloudConfig(
|
|
22512
|
-
root: "src",
|
|
22513
|
-
outDir: "generated",
|
|
22514
|
-
strict: true,
|
|
22515
|
-
moduleBoundaryPreset: "modular-monolith",
|
|
22516
|
-
effect: {
|
|
22517
|
-
requireRouteEffects: true,
|
|
22518
|
-
requireErrorMappings: true,
|
|
22519
|
-
requireDependencies: true,
|
|
22520
|
-
requireTaggedErrorTypes: true,
|
|
22521
|
-
requireExactDependencyTypes: true,
|
|
22522
|
-
requireTimeoutForDependencies: true,
|
|
22523
|
-
forbidDirectRuntimeExecution: true,
|
|
22524
|
-
forbidDirectThrows: true,
|
|
22525
|
-
},
|
|
22526
|
-
typeSafety: { scanProductionSource: true, noAnyInGenerated: true },
|
|
22527
|
-
disallowControllerDirectDb: true,
|
|
22528
|
-
detectOrphanModules: true,
|
|
22529
|
-
commandCapabilities: { permission: true, transaction: true, idempotency: true, audit: true },
|
|
22530
|
-
});
|
|
22608
|
+
export default defineSupacloudConfig();
|
|
22531
22609
|
`,
|
|
22532
22610
|
"src/application.ts": APPLICATION_SOURCE(name),
|
|
22533
22611
|
"generated/application.ts": GENERATED_BOOTSTRAP,
|
|
22534
|
-
...minimal ? {} : {
|
|
22535
|
-
"REACTIVE.md": STARTER_REACTIVE_GUIDE,
|
|
22536
|
-
"AGENTS.md": STARTER_REACTIVE_AGENTS,
|
|
22537
|
-
"tests/reactive.test.ts": STARTER_REACTIVE_TEST
|
|
22538
|
-
},
|
|
22539
22612
|
"scripts/environment.ts": STARTER_ENVIRONMENT,
|
|
22540
22613
|
"tests/environment.test.ts": STARTER_ENVIRONMENT_TEST,
|
|
22541
22614
|
"scripts/dev.ts": DEV_SCRIPT,
|
|
22542
22615
|
"scripts/serve.ts": SERVE_SCRIPT
|
|
22543
22616
|
};
|
|
22544
22617
|
}
|
|
22545
|
-
function httpTemplate(name) {
|
|
22546
|
-
return {
|
|
22547
|
-
...baseFiles(name),
|
|
22548
|
-
"src/orders/contracts.ts": `import { t } from "elysia";
|
|
22549
|
-
export const OrderParams = t.Object({ id: t.String({ minLength: 1 }) });
|
|
22550
|
-
export const CreateOrderBody = t.Object({ name: t.String({ minLength: 1 }) });
|
|
22551
|
-
export const OrderResult = t.Object({ id: t.String(), name: t.String() });
|
|
22552
|
-
export const HealthResult = t.Object({ ok: t.Boolean() });
|
|
22553
|
-
`,
|
|
22554
|
-
"src/orders/orders.ts": `import { Effect } from "effect";
|
|
22555
|
-
import { Body, Controller, Get, Param, Post, defineFeatureSlice } from "@supacloud/app";
|
|
22556
|
-
import { status } from "elysia";
|
|
22557
|
-
import { OrderParams, CreateOrderBody, OrderResult, HealthResult } from "./contracts";
|
|
22558
|
-
@Controller("/orders")
|
|
22559
|
-
export class OrdersController {
|
|
22560
|
-
@Get("/health", {
|
|
22561
|
-
responses: { 200: HealthResult },
|
|
22562
|
-
effect: { required: true, dependencies: [], errors: [], retry: "none" },
|
|
22563
|
-
})
|
|
22564
|
-
health(): Effect.Effect<{ ok: boolean }, never, never> { return Effect.succeed({ ok: true }); }
|
|
22565
|
-
|
|
22566
|
-
@Get("/:id", {
|
|
22567
|
-
params: OrderParams,
|
|
22568
|
-
responses: { 200: OrderResult },
|
|
22569
|
-
effect: { required: true, dependencies: [], errors: [], retry: "none" },
|
|
22570
|
-
})
|
|
22571
|
-
get(@Param("id") id: string): Effect.Effect<{ id: string; name: string }, never, never> {
|
|
22572
|
-
return Effect.succeed({ id, name: "demo" });
|
|
22573
|
-
}
|
|
22574
|
-
|
|
22575
|
-
@Post("/", {
|
|
22576
|
-
body: CreateOrderBody,
|
|
22577
|
-
responses: { 201: OrderResult },
|
|
22578
|
-
effect: { required: true, dependencies: [], errors: [], retry: "none" },
|
|
22579
|
-
})
|
|
22580
|
-
create(@Body() body: { name: string }) {
|
|
22581
|
-
return Effect.succeed(status(201, { id: "demo", name: body.name }));
|
|
22582
|
-
}
|
|
22583
|
-
}
|
|
22584
|
-
|
|
22585
|
-
export const OrdersFeature = defineFeatureSlice({
|
|
22586
|
-
name: "orders",
|
|
22587
|
-
tags: ["type:feature"],
|
|
22588
|
-
controllers: [OrdersController],
|
|
22589
|
-
});
|
|
22590
|
-
`,
|
|
22591
|
-
"scripts/sandbox.ts": `import { createMemorySandbox } from "@supacloud/elysia";
|
|
22592
|
-
import { createCompiledModules } from "../generated/application";
|
|
22593
|
-
|
|
22594
|
-
export function createDemo() {
|
|
22595
|
-
if (process.env["APP_ENV"] !== "development" && process.env["APP_ENV"] !== "test") {
|
|
22596
|
-
throw new Error("Memory adapters are restricted to development and test");
|
|
22597
|
-
}
|
|
22598
|
-
return createMemorySandbox({
|
|
22599
|
-
modules: createCompiledModules(),
|
|
22600
|
-
identity: { authenticated: true, subject: "local-demo" },
|
|
22601
|
-
});
|
|
22602
|
-
}
|
|
22603
|
-
`,
|
|
22604
|
-
"tests/orders.test.ts": `import { expect, test } from "bun:test";
|
|
22605
|
-
import { createDemo } from "../scripts/sandbox";
|
|
22606
|
-
|
|
22607
|
-
test("health, read and create routes follow the declared HTTP contract", async () => {
|
|
22608
|
-
const sandbox = createDemo();
|
|
22609
|
-
expect((await sandbox.request("/orders/health")).status).toBe(200);
|
|
22610
|
-
expect((await sandbox.request("/orders/1")).status).toBe(200);
|
|
22611
|
-
const created = await sandbox.request("/orders", {
|
|
22612
|
-
method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ name: "first" }),
|
|
22613
|
-
});
|
|
22614
|
-
expect(created.status).toBe(201);
|
|
22615
|
-
expect(await created.json()).toEqual({ id: "demo", name: "first" });
|
|
22616
|
-
const invalid = await sandbox.request("/orders", {
|
|
22617
|
-
method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({}),
|
|
22618
|
-
});
|
|
22619
|
-
expect(invalid.status).toBe(422);
|
|
22620
|
-
});
|
|
22621
|
-
`,
|
|
22622
|
-
"README.md": `# ${name}
|
|
22623
|
-
|
|
22624
|
-
HTTP API golden path.
|
|
22625
|
-
|
|
22626
|
-
## Local Development
|
|
22627
|
-
|
|
22628
|
-
\`\`\`sh
|
|
22629
|
-
bun install
|
|
22630
|
-
bun run check
|
|
22631
|
-
bun run dev
|
|
22632
|
-
\`\`\`
|
|
22633
|
-
|
|
22634
|
-
The server binds only to 127.0.0.1:3000 by default.
|
|
22635
|
-
|
|
22636
|
-
\`\`\`sh
|
|
22637
|
-
curl http://127.0.0.1:3000/orders/health
|
|
22638
|
-
curl http://127.0.0.1:3000/orders/1
|
|
22639
|
-
curl -X POST http://127.0.0.1:3000/orders \\
|
|
22640
|
-
-H 'Content-Type: application/json' -d '{"name":"first"}'
|
|
22641
|
-
\`\`\`
|
|
22642
|
-
|
|
22643
|
-
The generated feature declares explicit route schemas so the compiler can detect
|
|
22644
|
-
route/schema drift. Keep request and response shapes in \`src/orders/orders.ts\`;
|
|
22645
|
-
do not widen them to \`unknown\` just to accept unvalidated data.
|
|
22646
|
-
|
|
22647
|
-
## Production Boundary
|
|
22648
|
-
|
|
22649
|
-
\`bun run build\` emits an application factory, not a server. Import \`createApp\`
|
|
22650
|
-
from \`output/supacloud/application.js\` in your trusted host and supply real dependency,
|
|
22651
|
-
request-context and command-governance adapters. The memory sandbox is
|
|
22652
|
-
development/test only.
|
|
22653
|
-
|
|
22654
|
-
Keep backend bundles outside the frontend output directory. Vite normally cleans
|
|
22655
|
-
\`dist/\` before building; server artifacts placed there can be deleted or included
|
|
22656
|
-
in a public frontend archive. Use disjoint output directories, not a disabled
|
|
22657
|
-
frontend clean step. Compiler source artifacts remain in \`generated/\`.
|
|
22658
|
-
|
|
22659
|
-
## Inspection And Repair
|
|
22660
|
-
|
|
22661
|
-
\`\`\`sh
|
|
22662
|
-
supacloud context --root . --target orders --format json
|
|
22663
|
-
supacloud doctor --root .
|
|
22664
|
-
\`\`\`
|
|
22665
|
-
`
|
|
22666
|
-
};
|
|
22667
|
-
}
|
|
22668
|
-
function edgeTemplate(name) {
|
|
22669
|
-
return {
|
|
22670
|
-
...baseFiles(name),
|
|
22671
|
-
"src/sync/sync.ts": `import { Injectable, Job, defineFeatureSlice } from "@supacloud/app";
|
|
22672
|
-
|
|
22673
|
-
/**
|
|
22674
|
-
* Worker/edge golden path: a declared, retry-bounded job with an explicit
|
|
22675
|
-
* idempotency requirement. The platform owns scheduling, retry and DLQ; the
|
|
22676
|
-
* handler stays side-effect explicit and must not assume exactly-once execution.
|
|
22677
|
-
*/
|
|
22678
|
-
@Injectable()
|
|
22679
|
-
@Job({
|
|
22680
|
-
name: "sync.orders",
|
|
22681
|
-
mode: "task",
|
|
22682
|
-
idempotency: "required",
|
|
22683
|
-
timeoutSec: 60,
|
|
22684
|
-
maxAttempts: 3,
|
|
22685
|
-
})
|
|
22686
|
-
export class SyncOrdersJob {
|
|
22687
|
-
async run(): Promise<{ ok: boolean }> {
|
|
22688
|
-
return { ok: true };
|
|
22689
|
-
}
|
|
22690
|
-
}
|
|
22691
|
-
|
|
22692
|
-
export const SyncFeature = defineFeatureSlice({
|
|
22693
|
-
name: "sync",
|
|
22694
|
-
tags: ["type:feature"],
|
|
22695
|
-
providers: [SyncOrdersJob],
|
|
22696
|
-
jobs: [SyncOrdersJob],
|
|
22697
|
-
});
|
|
22698
|
-
`,
|
|
22699
|
-
"scripts/sandbox.ts": `import { createMemorySandbox } from "@supacloud/elysia";
|
|
22700
|
-
import { createCompiledModules } from "../generated/application";
|
|
22701
|
-
|
|
22702
|
-
export function createDemo() {
|
|
22703
|
-
if (process.env["APP_ENV"] !== "development" && process.env["APP_ENV"] !== "test") {
|
|
22704
|
-
throw new Error("Memory adapters are restricted to development and test");
|
|
22705
|
-
}
|
|
22706
|
-
return createMemorySandbox({
|
|
22707
|
-
modules: createCompiledModules(),
|
|
22708
|
-
identity: { authenticated: true, subject: "local-worker" },
|
|
22709
|
-
});
|
|
22710
|
-
}
|
|
22711
|
-
`,
|
|
22712
|
-
"tests/sync.test.ts": `import { expect, test } from "bun:test";
|
|
22713
|
-
import { SyncOrdersJob } from "../src/sync/sync";
|
|
22714
|
-
|
|
22715
|
-
test("the declared job handler is deterministic and idempotent by construction", async () => {
|
|
22716
|
-
const job = new SyncOrdersJob();
|
|
22717
|
-
expect(await job.run()).toEqual({ ok: true });
|
|
22718
|
-
expect(await job.run()).toEqual({ ok: true });
|
|
22719
|
-
});
|
|
22720
|
-
`,
|
|
22721
|
-
"README.md": `# ${name}
|
|
22722
|
-
|
|
22723
|
-
Worker / Edge golden path.
|
|
22724
|
-
|
|
22725
|
-
## Local Development
|
|
22726
|
-
|
|
22727
|
-
\`\`\`sh
|
|
22728
|
-
bun install
|
|
22729
|
-
bun run check
|
|
22730
|
-
bun run test
|
|
22731
|
-
\`\`\`
|
|
22732
|
-
|
|
22733
|
-
\`src/sync/sync.ts\` declares a platform job with an explicit mode, timeout,
|
|
22734
|
-
attempt bound and idempotency requirement. The platform schedules and retries;
|
|
22735
|
-
the handler must be safe to run more than once and must not assume exactly-once
|
|
22736
|
-
execution.
|
|
22737
|
-
|
|
22738
|
-
## Production Boundary
|
|
22739
|
-
|
|
22740
|
-
The job runs through the platform worker/edge adapter, not the local memory
|
|
22741
|
-
sandbox. Bind persistence, authorization and audit to durable adapters before
|
|
22742
|
-
invoking it in production. Do not schedule destructive work without an
|
|
22743
|
-
idempotency key.
|
|
22744
|
-
|
|
22745
|
-
\`bun run build\` emits \`output/supacloud/application.js\`, an application factory,
|
|
22746
|
-
not a deployed Worker. Keep it outside any frontend output directory such as
|
|
22747
|
-
\`dist/\`; frontend clean steps and public archives must not own server bundles.
|
|
22748
|
-
|
|
22749
|
-
## Inspection And Repair
|
|
22750
|
-
|
|
22751
|
-
\`\`\`sh
|
|
22752
|
-
supacloud context --root . --target sync --format json
|
|
22753
|
-
supacloud doctor --root .
|
|
22754
|
-
\`\`\`
|
|
22755
|
-
`
|
|
22756
|
-
};
|
|
22757
|
-
}
|
|
22758
22618
|
function minimalTemplate(name, sdkDependencies) {
|
|
22759
22619
|
return {
|
|
22760
22620
|
...baseFiles(name, sdkDependencies),
|
|
@@ -22809,8 +22669,12 @@ export function createDemo() {
|
|
|
22809
22669
|
- Cross-table business writes require trusted authorization, transactions,
|
|
22810
22670
|
idempotency and audit. Never put service-role or Management tokens in a browser.
|
|
22811
22671
|
- Do not edit generated/. Do not rewrite historical migrations.
|
|
22672
|
+
- Start with \`supacloud app context --format json\` and \`supacloud app doctor --format json\`.
|
|
22673
|
+
Do not scan the whole repository first or treat generated/ as source.
|
|
22674
|
+
- Apply only reviewed diagnostic fixes with \`supacloud app fix --fix <file>\`, then
|
|
22675
|
+
re-run check and doctor. Never weaken compiler governance to hide a diagnostic.
|
|
22812
22676
|
- Run the directly related single test file and git diff --check for daily changes.
|
|
22813
|
-
- Use app verify-plan --target <module
|
|
22677
|
+
- Use \`supacloud app verify-plan --target <module>\` to inspect the focused verification plan.
|
|
22814
22678
|
- Do not introduce another workflow engine, DI container or session store.
|
|
22815
22679
|
`,
|
|
22816
22680
|
"README.md": `# ${name}
|
|
@@ -22844,8 +22708,8 @@ Use fixed trusted HTTPS endpoints and never retry an uncertain write.
|
|
|
22844
22708
|
|
|
22845
22709
|
GraphQL is optional; this starter has no synthetic GraphQL schema.
|
|
22846
22710
|
For a complete persistent command/attachment example, create a separate reference
|
|
22847
|
-
project with app init --template command.
|
|
22848
|
-
|
|
22711
|
+
project with app init --template command. Do not install every recipe into this
|
|
22712
|
+
application.
|
|
22849
22713
|
|
|
22850
22714
|
## Integration and delivery
|
|
22851
22715
|
|
|
@@ -22862,58 +22726,9 @@ regeneration, then validate types, database permissions and authenticated behavi
|
|
|
22862
22726
|
};
|
|
22863
22727
|
}
|
|
22864
22728
|
function appTemplateFiles(name, template, sdkDependencies) {
|
|
22865
|
-
if (template
|
|
22866
|
-
if (!sdkDependencies)
|
|
22867
|
-
throw new Error("Minimal template requires the initializer's SDK dependency versions");
|
|
22868
|
-
return minimalTemplate(name, sdkDependencies);
|
|
22869
|
-
}
|
|
22870
|
-
if (template !== "http" && template !== "edge")
|
|
22729
|
+
if (template !== "minimal")
|
|
22871
22730
|
throw new Error("Use appStarterFiles for the command recipe");
|
|
22872
|
-
|
|
22873
|
-
const feature = template === "http" ? "OrdersFeature" : "SyncFeature";
|
|
22874
|
-
const source = template === "http" ? "./orders/orders" : "./sync/sync";
|
|
22875
|
-
return {
|
|
22876
|
-
...files,
|
|
22877
|
-
"src/app.module.ts": `import { Module } from "@supacloud/app";
|
|
22878
|
-
import { ${feature} } from "${source}";
|
|
22879
|
-
|
|
22880
|
-
// Compose feature modules here. Do not make unrelated features import each other.
|
|
22881
|
-
@Module({
|
|
22882
|
-
name: "application-root",
|
|
22883
|
-
tags: ["type:app"],
|
|
22884
|
-
imports: [${feature}],
|
|
22885
|
-
})
|
|
22886
|
-
export class AppModule {}
|
|
22887
|
-
`,
|
|
22888
|
-
"README.md": files["README.md"] + `
|
|
22889
|
-
## Application Composition
|
|
22890
|
-
|
|
22891
|
-
\`src/app.module.ts\` is the application composition root. Register additional
|
|
22892
|
-
feature modules there, rather than importing them from another feature.
|
|
22893
|
-
The compiler discovers this declaration; no runtime container lookup is needed.
|
|
22894
|
-
|
|
22895
|
-
With the matching candidate/released CLI installed:
|
|
22896
|
-
|
|
22897
|
-
\`\`\`sh
|
|
22898
|
-
supacloud app generate --kind resource --name inventory --register-in src/app.module.ts --dry-run --format json
|
|
22899
|
-
supacloud app generate --kind resource --name inventory --register-in src/app.module.ts
|
|
22900
|
-
bun run check
|
|
22901
|
-
bun run inspect
|
|
22902
|
-
\`\`\`
|
|
22903
|
-
|
|
22904
|
-
The resource is an asynchronous read skeleton, not implemented CRUD. Its Service
|
|
22905
|
-
rejects until an authorized data adapter is supplied; implement it and replace the
|
|
22906
|
-
placeholder test together. Generation does not grant access or connect a database.
|
|
22907
|
-
|
|
22908
|
-
\`bun run inspect\` delegates to the installed compiler and prints the current
|
|
22909
|
-
source graph as JSON without starting the app or regenerating artifacts. This is
|
|
22910
|
-
not a live mounted-route report. Configuration remains trusted project code.
|
|
22911
|
-
After changing source, compile before using artifact-backed CLI graph/explain.
|
|
22912
|
-
|
|
22913
|
-
In CI, run \`bun run check:generated\` before commands that regenerate artifacts;
|
|
22914
|
-
\`bun run check\` is the convenient bootstrap path for a newly created project.
|
|
22915
|
-
`
|
|
22916
|
-
};
|
|
22731
|
+
return minimalTemplate(name, sdkDependencies);
|
|
22917
22732
|
}
|
|
22918
22733
|
|
|
22919
22734
|
// src/shared/tools/app-starter-review-job.ts
|
|
@@ -24214,9 +24029,9 @@ After installing dependencies, run \`bun run db:generate\` once and commit db/co
|
|
|
24214
24029
|
The default \`bun run check\` and build fail on stale database contracts before any
|
|
24215
24030
|
compiler regeneration. After an intentional source change, run db:generate again,
|
|
24216
24031
|
review the diff, then run check.
|
|
24217
|
-
The command starter
|
|
24218
|
-
|
|
24219
|
-
the offline source-contract gate without a database. Use \`bun run dev:integration\`
|
|
24032
|
+
The command starter uses the default database governance module path
|
|
24033
|
+
\`db/modules.ts\` and fixes \`databaseGovernance.mode = "check"\`: compile/check/build
|
|
24034
|
+
run the offline source-contract gate without a database. Use \`bun run dev:integration\`
|
|
24220
24035
|
with the CLI's explicit loopback database URL, or set databaseGovernance.mode to
|
|
24221
24036
|
"enforce" with an explicitly supplied \`DATABASE_URL\`, to run SQL lint and live
|
|
24222
24037
|
Catalog reconciliation. Missing connections, stale source contracts and Catalog
|
|
@@ -25628,7 +25443,7 @@ function appStarterFiles(name) {
|
|
|
25628
25443
|
"@supacloud/js": `^${sdkMetadata.version}`,
|
|
25629
25444
|
"@supabase/supabase-js": sdkMetadata.peerDependencies["@supabase/supabase-js"],
|
|
25630
25445
|
effect: "4.0.5",
|
|
25631
|
-
elysia: "2.0.0-rc.
|
|
25446
|
+
elysia: "2.0.0-rc.1",
|
|
25632
25447
|
rxjs: appMetadata2.dependencies.rxjs
|
|
25633
25448
|
},
|
|
25634
25449
|
devDependencies: {
|
|
@@ -25687,25 +25502,7 @@ PORT=3000
|
|
|
25687
25502
|
"supacloud.config.ts": `import { defineSupacloudConfig } from "@supacloud/compiler";
|
|
25688
25503
|
|
|
25689
25504
|
export default defineSupacloudConfig({
|
|
25690
|
-
|
|
25691
|
-
outDir: "generated",
|
|
25692
|
-
strict: true,
|
|
25693
|
-
moduleBoundaryPreset: "modular-monolith",
|
|
25694
|
-
databaseGovernance: { mode: "check", moduleFile: "db/modules.ts", databaseUrlEnv: "DATABASE_URL" },
|
|
25695
|
-
effect: {
|
|
25696
|
-
requireRouteEffects: true,
|
|
25697
|
-
requireErrorMappings: true,
|
|
25698
|
-
requireDependencies: true,
|
|
25699
|
-
requireTaggedErrorTypes: true,
|
|
25700
|
-
requireExactDependencyTypes: true,
|
|
25701
|
-
requireTimeoutForDependencies: true,
|
|
25702
|
-
forbidDirectRuntimeExecution: true,
|
|
25703
|
-
forbidDirectThrows: true,
|
|
25704
|
-
},
|
|
25705
|
-
typeSafety: { scanProductionSource: true, noAnyInGenerated: true },
|
|
25706
|
-
disallowControllerDirectDb: true,
|
|
25707
|
-
detectOrphanModules: true,
|
|
25708
|
-
commandCapabilities: { permission: true, transaction: true, idempotency: true, audit: true },
|
|
25505
|
+
databaseGovernance: { mode: "check" },
|
|
25709
25506
|
delivery: {
|
|
25710
25507
|
version: 1,
|
|
25711
25508
|
build: {
|
|
@@ -26496,7 +26293,7 @@ and static execution plans, without provider values or source expressions.
|
|
|
26496
26293
|
The app context entry point retains its module workflow and adds a separate
|
|
26497
26294
|
execution evidence block when --events and --request-id are provided together.
|
|
26498
26295
|
It requires an explicit --target and --format json, rejects --write, and
|
|
26499
|
-
resolves relative event paths from --root. The
|
|
26296
|
+
resolves relative event paths from --root. The app context action supports
|
|
26500
26297
|
the same flags. Omit both execution flags for the unchanged authoring context.
|
|
26501
26298
|
The optional --events example expects a trusted host
|
|
26502
26299
|
to capture approved onExecution metadata in { "version": 1, "events": [...] }.
|
|
@@ -28255,13 +28052,27 @@ async function initProject(args) {
|
|
|
28255
28052
|
throw new Error("app init never overwrites files; choose an empty directory");
|
|
28256
28053
|
const template = args.template ?? "minimal";
|
|
28257
28054
|
const { root, name, files } = await initializeAppProject({ root: args.root, name: args.name, template });
|
|
28258
|
-
|
|
28259
|
-
`Initialized ${name} in ${root} (${files.length} files, template: ${template}).`,
|
|
28260
|
-
`cd '${root.replaceAll("'", "'\\''")}'`,
|
|
28055
|
+
const next = [
|
|
28261
28056
|
"bun install",
|
|
28262
28057
|
...template === "command" ? ["bun run db:generate"] : [],
|
|
28263
28058
|
"bun run check",
|
|
28264
|
-
"bun run dev"
|
|
28059
|
+
"bun run dev"
|
|
28060
|
+
];
|
|
28061
|
+
if (args.format === "json")
|
|
28062
|
+
return textResult(JSON.stringify({
|
|
28063
|
+
version: 1,
|
|
28064
|
+
ok: true,
|
|
28065
|
+
root,
|
|
28066
|
+
name,
|
|
28067
|
+
template,
|
|
28068
|
+
files,
|
|
28069
|
+
next,
|
|
28070
|
+
note: "Local demo only. Production requires persistent governance and identity adapters; see README.md."
|
|
28071
|
+
}, null, 2));
|
|
28072
|
+
return textResult([
|
|
28073
|
+
`Initialized ${name} in ${root} (${files.length} files, template: ${template}).`,
|
|
28074
|
+
`cd '${root.replaceAll("'", "'\\''")}'`,
|
|
28075
|
+
...next,
|
|
28265
28076
|
"Local demo only. Production requires persistent governance and identity adapters; see README.md."
|
|
28266
28077
|
].join(`
|
|
28267
28078
|
`));
|
|
@@ -28660,9 +28471,9 @@ async function runContext(args) {
|
|
|
28660
28471
|
`));
|
|
28661
28472
|
}
|
|
28662
28473
|
function doctorFixPlan(doctor) {
|
|
28663
|
-
return createDiagnosticRepairPlan(doctor.diagnostics ?? []).map((repair) => ({
|
|
28474
|
+
return createDiagnosticRepairPlan(doctor.diagnostics ?? []).map((repair, index) => ({
|
|
28664
28475
|
...repair,
|
|
28665
|
-
command:
|
|
28476
|
+
command: `supacloud app fix --from-doctor <doctor.json> --fix-index ${index}`
|
|
28666
28477
|
}));
|
|
28667
28478
|
}
|
|
28668
28479
|
async function findUnwiredContracts(rootDir) {
|
|
@@ -28718,6 +28529,11 @@ async function runDoctor(args) {
|
|
|
28718
28529
|
const autoFixable = fixPlan.filter((repair) => repair.readiness === "preview").length;
|
|
28719
28530
|
const inputRequired = fixPlan.filter((repair) => repair.readiness === "input-required").length;
|
|
28720
28531
|
const manualFixes = fixPlan.filter((repair) => repair.readiness === "manual").length;
|
|
28532
|
+
const next = doctor.errors === 0 ? ["supacloud app compile", "supacloud app verify-plan --target <module>"] : [
|
|
28533
|
+
...fixPlan.filter((repair) => repair.readiness === "preview").slice(0, 1).map((repair) => repair.command),
|
|
28534
|
+
"supacloud app check --format json",
|
|
28535
|
+
"supacloud app doctor --format json"
|
|
28536
|
+
];
|
|
28721
28537
|
const unwiredContracts = context ? [] : await findUnwiredContracts(root);
|
|
28722
28538
|
const scope = context ? {
|
|
28723
28539
|
target: context.subject,
|
|
@@ -28729,6 +28545,7 @@ async function runDoctor(args) {
|
|
|
28729
28545
|
if (args.format === "json") {
|
|
28730
28546
|
return textResult(JSON.stringify({
|
|
28731
28547
|
ok: doctor.errors === 0,
|
|
28548
|
+
next,
|
|
28732
28549
|
autoFixable,
|
|
28733
28550
|
inputRequired,
|
|
28734
28551
|
manualFixes,
|
|
@@ -28747,7 +28564,7 @@ async function runDoctor(args) {
|
|
|
28747
28564
|
lines.push("");
|
|
28748
28565
|
lines.push(doctor.errors === 0 ? "No blocking issues. Run `supacloud app compile` to refresh generated artifacts." : `${doctor.errors} blocking issue(s). Run \`supacloud app check\` for the full diagnostic list.`);
|
|
28749
28566
|
if (autoFixable > 0) {
|
|
28750
|
-
lines.push(`${autoFixable} preview-ready fix(es): run \`supacloud app doctor --format json
|
|
28567
|
+
lines.push(`${autoFixable} preview-ready fix(es): run \`supacloud app doctor --format json > doctor.json\`, then preview with \`supacloud app fix --from-doctor doctor.json --fix-index <n>\`. Add --write only after review.`);
|
|
28751
28568
|
}
|
|
28752
28569
|
if (inputRequired > 0 || manualFixes > 0) {
|
|
28753
28570
|
lines.push(`${inputRequired} fix(es) require explicit input; ${manualFixes} require manual implementation.`);
|
|
@@ -28764,13 +28581,49 @@ async function runDoctor(args) {
|
|
|
28764
28581
|
async function runFix(args) {
|
|
28765
28582
|
const root = resolve18(args.root || process.cwd());
|
|
28766
28583
|
const fixPath = args.fix?.trim();
|
|
28767
|
-
|
|
28768
|
-
|
|
28769
|
-
|
|
28584
|
+
const doctorPath = args.from_doctor?.trim();
|
|
28585
|
+
if (fixPath && doctorPath)
|
|
28586
|
+
throw new Error("app fix accepts either --fix or --from-doctor, not both");
|
|
28587
|
+
if (!fixPath && !doctorPath)
|
|
28588
|
+
throw new Error("app fix requires --fix <fix.json> or --from-doctor <doctor.json> --fix-index <n>");
|
|
28589
|
+
let parsed;
|
|
28590
|
+
let source = "fix";
|
|
28591
|
+
let repair;
|
|
28592
|
+
if (doctorPath) {
|
|
28593
|
+
const rawIndex = args.fix_index;
|
|
28594
|
+
if (!Number.isSafeInteger(rawIndex) || rawIndex === undefined || rawIndex < 0) {
|
|
28595
|
+
throw new Error("app fix --from-doctor requires a non-negative --fix-index <n>");
|
|
28596
|
+
}
|
|
28597
|
+
const doctor = JSON.parse(await readFile4(resolve18(root, doctorPath), "utf8"));
|
|
28598
|
+
if (!doctor || typeof doctor !== "object" || !Array.isArray(doctor.fixPlan)) {
|
|
28599
|
+
throw new Error("Invalid doctor output: expected a fixPlan array");
|
|
28600
|
+
}
|
|
28601
|
+
const plan = doctor.fixPlan;
|
|
28602
|
+
if (rawIndex >= plan.length)
|
|
28603
|
+
throw new Error(`--fix-index ${rawIndex} is outside fixPlan (0..${plan.length - 1})`);
|
|
28604
|
+
const entry = plan[rawIndex];
|
|
28605
|
+
if (!entry || typeof entry !== "object" || !("fix" in entry)) {
|
|
28606
|
+
throw new Error(`fixPlan[${rawIndex}] does not contain a fix payload`);
|
|
28607
|
+
}
|
|
28608
|
+
const repairEntry = entry;
|
|
28609
|
+
if (repairEntry.readiness !== undefined && repairEntry.readiness !== "preview") {
|
|
28610
|
+
throw new Error(`fixPlan[${rawIndex}] is ${repairEntry.readiness}; only preview-ready repairs can be applied automatically`);
|
|
28611
|
+
}
|
|
28612
|
+
repair = repairEntry;
|
|
28613
|
+
parsed = repairEntry.fix;
|
|
28614
|
+
source = "doctor";
|
|
28615
|
+
} else {
|
|
28616
|
+
parsed = JSON.parse(await readFile4(resolve18(root, fixPath), "utf8"));
|
|
28617
|
+
}
|
|
28770
28618
|
const write = args.write === true;
|
|
28771
28619
|
const applied = await applyDiagnosticFix(parsed, { rootDir: root, dryRun: !write });
|
|
28772
28620
|
return textResult(JSON.stringify({
|
|
28773
28621
|
ok: true,
|
|
28622
|
+
source,
|
|
28623
|
+
...source === "doctor" ? {
|
|
28624
|
+
index: args.fix_index,
|
|
28625
|
+
...repair?.code === undefined ? {} : { code: repair.code }
|
|
28626
|
+
} : {},
|
|
28774
28627
|
written: write,
|
|
28775
28628
|
file: applied.file,
|
|
28776
28629
|
changed: applied.changed,
|
|
@@ -28990,11 +28843,14 @@ async function runCheck(args) {
|
|
|
28990
28843
|
strict: args.strict ?? loadedConfig.strict ?? false
|
|
28991
28844
|
}, root);
|
|
28992
28845
|
const result = await checkProject(config);
|
|
28993
|
-
const
|
|
28846
|
+
const diagnosticError = result.diagnostics.some((diagnostic) => diagnostic.severity === "error");
|
|
28847
|
+
const hasError = diagnosticError || result.mismatches.length > 0 || database?.ok === false;
|
|
28848
|
+
const next = hasError ? diagnosticError || database?.ok === false ? ["supacloud app doctor --format json"] : ["supacloud app compile", "supacloud app check --format json"] : ["supacloud app verify-plan --target <module>"];
|
|
28994
28849
|
if (args.format === "json")
|
|
28995
28850
|
return textResult(JSON.stringify({
|
|
28996
28851
|
version: 1,
|
|
28997
28852
|
ok: !hasError,
|
|
28853
|
+
next,
|
|
28998
28854
|
source: "declaration",
|
|
28999
28855
|
diagnostics: result.diagnostics,
|
|
29000
28856
|
mismatches: result.mismatches,
|
|
@@ -29420,7 +29276,7 @@ async function runAppRelease(request, options, delegate) {
|
|
|
29420
29276
|
...typeof deployReceipt.activation_id === "string" ? { activation_id: deployReceipt.activation_id } : {},
|
|
29421
29277
|
...deployReceipt.web === undefined ? {} : { web: deployReceipt.web }
|
|
29422
29278
|
};
|
|
29423
|
-
return request.format === "json"
|
|
29279
|
+
return request.format === "json" ? textResult(JSON.stringify(result, null, 2)) : textResult([
|
|
29424
29280
|
`${ref}/${id}/${environmentId}: ${deployReceipt.status === "accepted" ? "accepted" : result.no_op ? "no-op" : "released"}`,
|
|
29425
29281
|
`Release: ${releaseId}`,
|
|
29426
29282
|
`Configuration: ${configurationId}`,
|
|
@@ -29623,7 +29479,7 @@ async function runPromotion(request, options, delegate) {
|
|
|
29623
29479
|
const ref = request.ref ?? options.projectRef;
|
|
29624
29480
|
if (request.mutation_id !== undefined)
|
|
29625
29481
|
throw new Error("app promote generates a new mutation ID; use promote-status or promote-reconcile to inspect an existing mutation");
|
|
29626
|
-
const { format,
|
|
29482
|
+
const { format, ...remoteRequest } = request;
|
|
29627
29483
|
const planArgs = {
|
|
29628
29484
|
...remoteRequest,
|
|
29629
29485
|
action: "get_promotion_plan",
|
|
@@ -29661,7 +29517,7 @@ async function runPromotion(request, options, delegate) {
|
|
|
29661
29517
|
no_op: true,
|
|
29662
29518
|
execution_performed: false
|
|
29663
29519
|
});
|
|
29664
|
-
return format === "json"
|
|
29520
|
+
return format === "json" ? noOp : textResult(`${plan.project_ref}/${plan.application_id}/${plan.environment_id}: no-op
|
|
29665
29521
|
Execution: skipped`);
|
|
29666
29522
|
}
|
|
29667
29523
|
if (plan.blockers.some((code) => !["MIGRATION_PENDING", "BACKUP_REQUIRED"].includes(code))) {
|
|
@@ -29683,23 +29539,9 @@ Execution: skipped`);
|
|
|
29683
29539
|
};
|
|
29684
29540
|
validateToolArguments2(APPLICATION_TOOL_SCHEMA2, executeArgs);
|
|
29685
29541
|
const result = await delegate(executeArgs);
|
|
29686
|
-
return format === "json"
|
|
29542
|
+
return format === "json" ? result : formatPromotionExecution(result);
|
|
29687
29543
|
}
|
|
29688
29544
|
async function runAppTool(request, options = {}) {
|
|
29689
|
-
if (request.dry_run !== undefined && request["dry-run"] !== undefined || request.register_in !== undefined && request["register-in"] !== undefined || request.database_url !== undefined && request["database-url"] !== undefined || request.database_source !== undefined && request["database-source"] !== undefined || request.database_table !== undefined && request["database-table"] !== undefined || request.database_type !== undefined && request["database-type"] !== undefined || request.read_permission !== undefined && request["read-permission"] !== undefined || request.request_id !== undefined && request["request-id"] !== undefined) {
|
|
29690
|
-
throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", "Do not combine hyphenated and underscored aliases of the same flag");
|
|
29691
|
-
}
|
|
29692
|
-
request = {
|
|
29693
|
-
...request,
|
|
29694
|
-
...request["dry-run"] === undefined ? {} : { dry_run: request["dry-run"] },
|
|
29695
|
-
...request["register-in"] === undefined ? {} : { register_in: request["register-in"] },
|
|
29696
|
-
...request["database-url"] === undefined ? {} : { database_url: request["database-url"] },
|
|
29697
|
-
...request["database-source"] === undefined ? {} : { database_source: request["database-source"] },
|
|
29698
|
-
...request["database-table"] === undefined ? {} : { database_table: request["database-table"] },
|
|
29699
|
-
...request["database-type"] === undefined ? {} : { database_type: request["database-type"] },
|
|
29700
|
-
...request["read-permission"] === undefined ? {} : { read_permission: request["read-permission"] },
|
|
29701
|
-
...request["request-id"] === undefined ? {} : { request_id: request["request-id"] }
|
|
29702
|
-
};
|
|
29703
29545
|
if (request.events !== undefined || request.request_id !== undefined) {
|
|
29704
29546
|
if (request.action !== "context" || !request.events?.trim() || !request.target?.trim() || !request.request_id || !/^[A-Za-z0-9._:-]{1,256}$/.test(request.request_id) || request.format !== "json" || request.write !== undefined) {
|
|
29705
29547
|
throw new Error("Execution context requires app context, --target, --events, --request-id and --format json; --write is not supported");
|
|
@@ -29711,10 +29553,6 @@ async function runAppTool(request, options = {}) {
|
|
|
29711
29553
|
if (request.database_url !== undefined && request.action !== "dev" && request.action !== "watch") {
|
|
29712
29554
|
throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", "--database-url applies only to app dev --profile integration");
|
|
29713
29555
|
}
|
|
29714
|
-
if (request.json !== undefined && !Object.hasOwn(REMOTE_APP_ACTIONS, request.action) && request.action !== "promote" && request.action !== "release")
|
|
29715
|
-
throw new Error("--json applies only to remote app actions");
|
|
29716
|
-
if (request.json && request.format === "text")
|
|
29717
|
-
throw new Error("--json and --format text cannot be combined");
|
|
29718
29556
|
if (request.action === "promote") {
|
|
29719
29557
|
const delegate = options.getApplications?.();
|
|
29720
29558
|
if (!delegate)
|
|
@@ -29733,7 +29571,7 @@ async function runAppTool(request, options = {}) {
|
|
|
29733
29571
|
return textResult("App remote actions require a Management API context.", true);
|
|
29734
29572
|
const promotionDiff = request.action === "diff" && request.source_ref !== undefined && request.source_environment_id !== undefined && request.source_release_id !== undefined;
|
|
29735
29573
|
const action = promotionDiff ? "get_promotion_plan" : REMOTE_APP_ACTIONS[request.action];
|
|
29736
|
-
const { format,
|
|
29574
|
+
const { format, ...remoteRequest } = request;
|
|
29737
29575
|
const args = {
|
|
29738
29576
|
...remoteRequest,
|
|
29739
29577
|
action,
|
|
@@ -29741,7 +29579,7 @@ async function runAppTool(request, options = {}) {
|
|
|
29741
29579
|
};
|
|
29742
29580
|
validateToolArguments2(APPLICATION_TOOL_SCHEMA2, args);
|
|
29743
29581
|
const result = await delegate(args);
|
|
29744
|
-
if (request.action === "evidence" && format !== "json" &&
|
|
29582
|
+
if (request.action === "evidence" && format !== "json" && !result.isError) {
|
|
29745
29583
|
try {
|
|
29746
29584
|
const payload = JSON.parse(result.content[0].text);
|
|
29747
29585
|
if (payload === null || typeof payload !== "object" || !("project_ref" in payload) || payload.project_ref !== args.ref || !("application_id" in payload) || payload.application_id !== request.id || !("environment_id" in payload) || payload.environment_id !== request.environment_id || !("evidence" in payload))
|
|
@@ -29756,17 +29594,17 @@ async function runAppTool(request, options = {}) {
|
|
|
29756
29594
|
return textResult("DEPLOYMENT unknown: invalid or mismatched evidence; no mutation was attempted.", true);
|
|
29757
29595
|
}
|
|
29758
29596
|
}
|
|
29759
|
-
if (request.action === "history" && format !== "json"
|
|
29597
|
+
if (request.action === "history" && format !== "json") {
|
|
29760
29598
|
return historyText(result, request, args.ref);
|
|
29761
29599
|
}
|
|
29762
29600
|
if ((request.action === "deploy-plan" || request.action === "diff") && format !== "json" && (request.action === "deploy-plan" || request.source_ref === undefined || request.source_environment_id === undefined || request.source_release_id === undefined)) {
|
|
29763
29601
|
return formatRemoteDeployPlan(result);
|
|
29764
29602
|
}
|
|
29765
29603
|
if (request.action === "promote-plan" || request.action === "diff" && request.source_ref !== undefined && request.source_environment_id !== undefined && request.source_release_id !== undefined) {
|
|
29766
|
-
if (!result.isError && format !== "json"
|
|
29604
|
+
if (!result.isError && format !== "json")
|
|
29767
29605
|
return formatPromotionPlan2(result);
|
|
29768
29606
|
}
|
|
29769
|
-
return (request.action === "transfer-plan" || request.action === "transfer") && format !== "json"
|
|
29607
|
+
return (request.action === "transfer-plan" || request.action === "transfer") && format !== "json" ? formatReleaseTransfer(result) : format !== "json" ? formatWorkflowObservation(result, request.action) : result;
|
|
29770
29608
|
}
|
|
29771
29609
|
switch (request.action) {
|
|
29772
29610
|
case "verify-plan": {
|
|
@@ -29868,83 +29706,38 @@ function registerAppTools(server, options = {}) {
|
|
|
29868
29706
|
"promote-reconcile"
|
|
29869
29707
|
]), "App action; promote obtains and verifies its plan before one mutation request"),
|
|
29870
29708
|
kind: optional(stringEnum2(["module", "command", "query", "controller", "job", "contract", "resource", "feature"]), "[generate] Scaffold kind"),
|
|
29871
|
-
template: optional(stringEnum2(["minimal", "
|
|
29709
|
+
template: optional(stringEnum2(["minimal", "command"]), "[init] Minimal application by default; command adds the persistent governed-command reference"),
|
|
29872
29710
|
name: optional(String2(), "[init/generate] Project or object name"),
|
|
29873
29711
|
module: optional(String2(), "[generate] Target feature module (required for command/query/controller)"),
|
|
29874
29712
|
permission: optional(String2(), "[generate feature] Explicit approval permission; not inferred from name"),
|
|
29875
29713
|
read_permission: optional(String2(), "[generate feature] Explicit read permission; not inferred from name"),
|
|
29876
|
-
"read-permission": optional(String2(), "[generate feature] CLI alias of read_permission"),
|
|
29877
29714
|
rpc: optional(String2(), "[generate feature] Explicit durable RPC capability; must be registered by the host"),
|
|
29878
29715
|
database_source: optional(String2(), "[generate feature] Project-relative database source path"),
|
|
29879
29716
|
database_table: optional(String2(), "[generate feature] Schema-qualified table owned by the feature"),
|
|
29880
29717
|
database_type: optional(String2(), "[generate feature] Exported row type derived from the database source"),
|
|
29881
|
-
"database-source": optional(String2(), "[generate feature] CLI alias of database_source"),
|
|
29882
|
-
"database-table": optional(String2(), "[generate feature] CLI alias of database_table"),
|
|
29883
|
-
"database-type": optional(String2(), "[generate feature] CLI alias of database_type"),
|
|
29884
29718
|
dir: optional(String2(), "[generate] Feature root directory (default: src/features)"),
|
|
29885
29719
|
force: optional(Boolean2(), "[generate] Overwrite a single regular file; resource/feature/controller never overwrite"),
|
|
29886
29720
|
dry_run: optional(Boolean2(), "[generate] Validate and preview the complete change set without writing"),
|
|
29887
|
-
"dry-run": optional(Boolean2(), "[generate] CLI alias of dry_run"),
|
|
29888
29721
|
register_in: optional(String2(), "[generate] Existing parent module file for module/resource/feature; uses the compiler AST editor"),
|
|
29889
|
-
"register-in": optional(String2(), "[generate] CLI alias of register_in"),
|
|
29890
29722
|
root: optional(String2(), "[dev/plan/build/release/compile/check] Project directory containing supacloud.config.ts (default: current directory)"),
|
|
29891
29723
|
include: optional(String2(), "[dev/compile/check] Comma-separated glob patterns for source files"),
|
|
29892
29724
|
out_dir: optional(String2(), "[dev/compile/plan/build/export-tools] Output directory (default: configured outDir)"),
|
|
29893
29725
|
strict: optional(Boolean2(), "[dev/compile/check] Promote warnings to errors"),
|
|
29894
|
-
format: optional(stringEnum2(["text", "json"]), "[
|
|
29895
|
-
json: optional(Boolean2(), "[history/transfer-plan/transfer/promote-plan] Preserve or return the full machine-readable receipt"),
|
|
29726
|
+
format: optional(stringEnum2(["text", "json"]), "[*] Output format (default: text)"),
|
|
29896
29727
|
profile: optional(stringEnum2(["fast", "integration"]), "[dev] Run dev or dev:integration; integration verifies an explicit loopback database"),
|
|
29897
29728
|
once: optional(Boolean2(), "[dev] Validate and report once without watching"),
|
|
29898
29729
|
watch: optional(Boolean2(), "[dev] Watch for changes (default: true; --once disables)"),
|
|
29899
29730
|
database_url: optional(String2(), "[dev] Explicit loopback integration URL; watch/once only inspect its shape"),
|
|
29900
|
-
"database-url": optional(String2(), "[dev] CLI alias of database_url"),
|
|
29901
29731
|
target: optional(String2(), "[explain/context/verify-plan] Provider class name / token name / command name / job name / module name"),
|
|
29902
29732
|
events: optional(String2(), "[context] Project-relative execution metadata JSON or generated JSON-lines file"),
|
|
29903
29733
|
request_id: optional(String2(), "[context] Opaque request ID or Workflow run ID selected from --events"),
|
|
29904
|
-
"request-id": optional(String2(), "[context] CLI alias of request_id"),
|
|
29905
29734
|
fix: optional(String2(), "[fix] Path to a DiagnosticFix JSON file produced by `doctor --format json`"),
|
|
29735
|
+
from_doctor: optional(String2(), "[fix] Read one fix from a doctor --format json output"),
|
|
29736
|
+
fix_index: optional(Number2({ minimum: 0, multipleOf: 1 }), "[fix] Zero-based fixPlan index used with --from-doctor"),
|
|
29906
29737
|
write: optional(Boolean2(), "[fix] Write the fix to disk (default: preview only)"),
|
|
29907
29738
|
skip_build: optional(Boolean2(), "[release] Reuse --manifest_path instead of running the local delivery build")
|
|
29908
29739
|
}, (request) => runAppTool(request, options));
|
|
29909
29740
|
}
|
|
29910
|
-
var APP_ALIAS_ACTIONS = ["generate", "compile", "check", "graph", "explain", "context", "doctor", "fix"];
|
|
29911
|
-
function registerAppAliases(server) {
|
|
29912
|
-
const schema = {
|
|
29913
|
-
kind: optional(stringEnum2(["module", "command", "query", "controller", "job", "contract", "resource", "feature"]), "[generate] Scaffold kind"),
|
|
29914
|
-
name: optional(String2(), "[generate] Object name"),
|
|
29915
|
-
module: optional(String2(), "[generate] Target feature module"),
|
|
29916
|
-
permission: optional(String2(), "[generate feature] Explicit approval permission; not inferred from name"),
|
|
29917
|
-
read_permission: optional(String2(), "[generate feature] Explicit read permission; not inferred from name"),
|
|
29918
|
-
"read-permission": optional(String2(), "[generate feature] CLI alias of read_permission"),
|
|
29919
|
-
rpc: optional(String2(), "[generate feature] Explicit durable RPC capability; must be registered by the host"),
|
|
29920
|
-
database_source: optional(String2(), "[generate feature] Project-relative database source path"),
|
|
29921
|
-
database_table: optional(String2(), "[generate feature] Schema-qualified table owned by the feature"),
|
|
29922
|
-
database_type: optional(String2(), "[generate feature] Exported row type derived from the database source"),
|
|
29923
|
-
"database-source": optional(String2(), "[generate feature] CLI alias of database_source"),
|
|
29924
|
-
"database-table": optional(String2(), "[generate feature] CLI alias of database_table"),
|
|
29925
|
-
"database-type": optional(String2(), "[generate feature] CLI alias of database_type"),
|
|
29926
|
-
dir: optional(String2(), "[generate] Feature root directory (default: src/features)"),
|
|
29927
|
-
force: optional(Boolean2(), "[generate] Overwrite a single regular file; resource/feature/controller never overwrite"),
|
|
29928
|
-
dry_run: optional(Boolean2(), "[generate] Validate and preview the complete change set without writing"),
|
|
29929
|
-
"dry-run": optional(Boolean2(), "[generate] CLI alias of dry_run"),
|
|
29930
|
-
register_in: optional(String2(), "[generate] Existing parent module file for module/resource/feature; uses the compiler AST editor"),
|
|
29931
|
-
"register-in": optional(String2(), "[generate] CLI alias of register_in"),
|
|
29932
|
-
root: optional(String2(), "Project directory containing supacloud.config.ts (default: current directory)"),
|
|
29933
|
-
include: optional(String2(), "[compile/check] Comma-separated glob patterns for source files"),
|
|
29934
|
-
out_dir: optional(String2(), "[compile/export-tools] Output directory (default: <root>/generated)"),
|
|
29935
|
-
strict: optional(Boolean2(), "[compile/check] Promote warnings to errors"),
|
|
29936
|
-
format: optional(stringEnum2(["text", "json"]), "Output format (default: text)"),
|
|
29937
|
-
target: optional(String2(), "[explain/context] Provider class name / token name / command name / job name / module name"),
|
|
29938
|
-
events: optional(String2(), "[context] Project-relative execution metadata JSON or generated JSON-lines file"),
|
|
29939
|
-
request_id: optional(String2(), "[context] Opaque request ID or Workflow run ID selected from --events"),
|
|
29940
|
-
"request-id": optional(String2(), "[context] CLI alias of request_id"),
|
|
29941
|
-
fix: optional(String2(), "[fix] Path to a DiagnosticFix JSON file produced by `doctor --format json`"),
|
|
29942
|
-
write: optional(Boolean2(), "[fix] Write the fix to disk (default: preview only)")
|
|
29943
|
-
};
|
|
29944
|
-
for (const action of APP_ALIAS_ACTIONS) {
|
|
29945
|
-
registerTool(server, action, `Top-level alias of \`app ${action}\`.`, schema, (request) => runAppTool({ ...request, action }));
|
|
29946
|
-
}
|
|
29947
|
-
}
|
|
29948
29741
|
|
|
29949
29742
|
// src/shared/tools/db-governance-tools.ts
|
|
29950
29743
|
import { existsSync as existsSync13 } from "node:fs";
|
|
@@ -30387,7 +30180,7 @@ async function runOfficial(root, workdir, args, environment, options) {
|
|
|
30387
30180
|
if (code !== 0)
|
|
30388
30181
|
throw new Error(`Official Supabase candidate generation failed (exit ${code}); no candidate was published. Select a compatible pinned CLI; no fallback engine is used.`);
|
|
30389
30182
|
}
|
|
30390
|
-
async function initializeCandidate(stage, majorVersion =
|
|
30183
|
+
async function initializeCandidate(stage, majorVersion = 18) {
|
|
30391
30184
|
if (![15, 16, 17, 18].includes(majorVersion))
|
|
30392
30185
|
throw new Error("Select a supported PostgreSQL major version (15-18)");
|
|
30393
30186
|
await mkdir6(join15(stage, "supabase/migrations"), { recursive: true });
|
|
@@ -30814,12 +30607,71 @@ async function runModuleCheckLite(args, root, spawn, liteBinary) {
|
|
|
30814
30607
|
`);
|
|
30815
30608
|
return textResult2(output || "lite db check 无输出", result.exitCode !== 0);
|
|
30816
30609
|
}
|
|
30817
|
-
|
|
30610
|
+
function remoteModuleManifest(modules) {
|
|
30611
|
+
return modules.map((module) => ({
|
|
30612
|
+
name: module.name,
|
|
30613
|
+
tables: module.tables,
|
|
30614
|
+
policies: module.policies.map((policy) => ({
|
|
30615
|
+
name: policy.name,
|
|
30616
|
+
table: policy.table,
|
|
30617
|
+
operation: policy.operation,
|
|
30618
|
+
roles: policy.roles,
|
|
30619
|
+
...policy.usingExpr === undefined ? {} : { usingExpr: policy.usingExpr },
|
|
30620
|
+
...policy.checkExpr === undefined ? {} : { checkExpr: policy.checkExpr }
|
|
30621
|
+
})),
|
|
30622
|
+
functions: module.functions.map((fn) => ({
|
|
30623
|
+
name: fn.name,
|
|
30624
|
+
security: fn.security,
|
|
30625
|
+
...fn.signature === undefined ? {} : { signature: fn.signature }
|
|
30626
|
+
})),
|
|
30627
|
+
triggers: module.triggers.map((trigger) => ({
|
|
30628
|
+
name: trigger.name,
|
|
30629
|
+
table: trigger.table,
|
|
30630
|
+
...trigger.enabled === undefined ? {} : { enabled: trigger.enabled }
|
|
30631
|
+
})),
|
|
30632
|
+
grants: module.grants.map((grant) => ({
|
|
30633
|
+
object: grant.object,
|
|
30634
|
+
privilege: grant.privilege,
|
|
30635
|
+
role: grant.role
|
|
30636
|
+
}))
|
|
30637
|
+
}));
|
|
30638
|
+
}
|
|
30639
|
+
async function runModuleCheckRemote(args, root, options) {
|
|
30640
|
+
const run = options.runDatabase?.();
|
|
30641
|
+
if (!run)
|
|
30642
|
+
throw new Error("db module_check --remote 需要 Management API context(SUPACLOUD_API_URL + SUPACLOUD_API_TOKEN)");
|
|
30643
|
+
const ref = args.ref?.trim() || options.projectRef?.trim();
|
|
30644
|
+
if (!ref)
|
|
30645
|
+
throw new Error("db module_check --remote 需要 --ref 或 SUPACLOUD_PROJECT_REF");
|
|
30646
|
+
const modules = await loadDatabaseModules(root, args.module_file);
|
|
30647
|
+
const schemas = args.schema?.split(",").map((entry) => entry.trim()).filter(Boolean) ?? ["public"];
|
|
30648
|
+
return await run({
|
|
30649
|
+
action: "governance_check",
|
|
30650
|
+
ref,
|
|
30651
|
+
modules: remoteModuleManifest(modules),
|
|
30652
|
+
schemas
|
|
30653
|
+
});
|
|
30654
|
+
}
|
|
30655
|
+
async function runRoleCheckRemote(args, options) {
|
|
30656
|
+
const run = options.runDatabase?.();
|
|
30657
|
+
if (!run)
|
|
30658
|
+
throw new Error("db role_check --remote 需要 Management API context(SUPACLOUD_API_URL + SUPACLOUD_API_TOKEN)");
|
|
30659
|
+
const ref = args.ref?.trim() || options.projectRef?.trim();
|
|
30660
|
+
if (!ref)
|
|
30661
|
+
throw new Error("db role_check --remote 需要 --ref 或 SUPACLOUD_PROJECT_REF");
|
|
30662
|
+
const schemas = args.schema?.split(",").map((entry) => entry.trim()).filter(Boolean) ?? ["public"];
|
|
30663
|
+
return await run({ action: "role_check", ref, schemas });
|
|
30664
|
+
}
|
|
30665
|
+
async function runModuleCheckAction(args, root, environment, spawn, liteBinary, options) {
|
|
30666
|
+
if (args.lite && args.remote)
|
|
30667
|
+
throw new Error("db module_check 只能选择 --lite 或 --remote");
|
|
30668
|
+
if (args.remote)
|
|
30669
|
+
return runModuleCheckRemote(args, root, options);
|
|
30818
30670
|
if (args.lite)
|
|
30819
30671
|
return runModuleCheckLite(args, root, spawn, liteBinary);
|
|
30820
30672
|
const databaseUrl = args.database_url?.trim() || environment.DATABASE_URL?.trim();
|
|
30821
30673
|
if (!databaseUrl) {
|
|
30822
|
-
throw new Error("db module_check requires --database_url、环境变量 DATABASE_URL 或 --
|
|
30674
|
+
throw new Error("db module_check requires --database_url、环境变量 DATABASE_URL、--lite 或 --remote");
|
|
30823
30675
|
}
|
|
30824
30676
|
const modules = await loadDatabaseModules(root, args.module_file);
|
|
30825
30677
|
const schemas = parseSchemas(args.schema);
|
|
@@ -30857,12 +30709,13 @@ function registerDbGovernanceTools(server, options = {}) {
|
|
|
30857
30709
|
database_url: optional(String2(), "[module_check] Postgres connection URL (default: DATABASE_URL)"),
|
|
30858
30710
|
schema: optional(String2(), "[module_check] Comma-separated schemas to inspect (default: public)"),
|
|
30859
30711
|
lite: optional(Boolean2(), "[module_check] Reconcile against a local SupaCloud Lite project (runs supacloud-lite db check)"),
|
|
30712
|
+
remote: optional(Boolean2(), "[module_check/role_check] Use Management API without a local database URL"),
|
|
30860
30713
|
project_dir: optional(String2(), "[module_check] Lite project directory (with --lite; default: --root)"),
|
|
30861
30714
|
schema_dir: optional(String2(), "[diff] Maintained declarative SQL directory (default: supabase/schemas)"),
|
|
30862
|
-
db_major_version: optional(Number2(), "[reverse/diff] PostgreSQL shadow major version (default:
|
|
30715
|
+
db_major_version: optional(Number2(), "[reverse/diff] PostgreSQL shadow major version (default: 18); set only when matching a non-18 source"),
|
|
30863
30716
|
out: optional(String2(), "[reverse/diff] Candidate output path (reverse requires a new directory)"),
|
|
30864
30717
|
dir: optional(String2(), "[diff/plan/apply] Reviewed forward migration directory"),
|
|
30865
|
-
ref: optional(String2(), "[plan/apply] Target project ref"),
|
|
30718
|
+
ref: optional(String2(), "[module_check/role_check/plan/apply] Target project ref (remote checks default to SUPACLOUD_PROJECT_REF)"),
|
|
30866
30719
|
approved_digest: optional(String2(), "[apply] Exact digest returned by db plan"),
|
|
30867
30720
|
application_role: optional(String2(), "[role_sql] Application database role"),
|
|
30868
30721
|
migration_role: optional(String2(), "[role_sql] Privileged migration role"),
|
|
@@ -30877,12 +30730,20 @@ function registerDbGovernanceTools(server, options = {}) {
|
|
|
30877
30730
|
case "explain":
|
|
30878
30731
|
return runExplain2(request, root);
|
|
30879
30732
|
case "module_check":
|
|
30880
|
-
return runModuleCheckAction(request, root, environment, liteSpawn, options.liteBinary);
|
|
30733
|
+
return runModuleCheckAction(request, root, environment, liteSpawn, options.liteBinary, options);
|
|
30734
|
+
case "role_check":
|
|
30735
|
+
if (request.remote)
|
|
30736
|
+
return runRoleCheckRemote(request, options);
|
|
30737
|
+
return runDbWorkflow({ ...request, action: request.action }, root, {
|
|
30738
|
+
environment,
|
|
30739
|
+
projectRef: options.projectRef,
|
|
30740
|
+
apiUrl: options.apiUrl,
|
|
30741
|
+
runDatabase: options.runDatabase
|
|
30742
|
+
});
|
|
30881
30743
|
case "reverse":
|
|
30882
30744
|
case "diff":
|
|
30883
30745
|
case "plan":
|
|
30884
30746
|
case "apply":
|
|
30885
|
-
case "role_check":
|
|
30886
30747
|
case "role_sql":
|
|
30887
30748
|
return runDbWorkflow({ ...request, action: request.action }, root, {
|
|
30888
30749
|
environment,
|
|
@@ -32222,11 +32083,11 @@ function registerReleaseTools(server, http, options = {}) {
|
|
|
32222
32083
|
}
|
|
32223
32084
|
|
|
32224
32085
|
// src/shared/tools/deploy-tools.ts
|
|
32225
|
-
import { access, lstat as lstat5, mkdtemp as mkdtemp2, opendir, readFile as
|
|
32086
|
+
import { access, lstat as lstat5, mkdtemp as mkdtemp2, opendir, readFile as readFile7, rm as rm3, writeFile as writeFile7 } from "node:fs/promises";
|
|
32226
32087
|
import { spawn as spawn2 } from "node:child_process";
|
|
32227
32088
|
import { createHash as createHash11 } from "node:crypto";
|
|
32228
32089
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
32229
|
-
import { basename as basename8, dirname as
|
|
32090
|
+
import { basename as basename8, dirname as dirname14, join as join19, relative as relative10, resolve as resolve26, sep as sep5 } from "node:path";
|
|
32230
32091
|
|
|
32231
32092
|
// src/shared/tools/project-deployment.ts
|
|
32232
32093
|
async function requireProjectDeploymentProtocol(http, projectRef, environmentId) {
|
|
@@ -32301,26 +32162,387 @@ async function readProjectDeploymentStatus(http, projectRef, environmentId, depl
|
|
|
32301
32162
|
}
|
|
32302
32163
|
}
|
|
32303
32164
|
|
|
32304
|
-
// src/shared/tools/
|
|
32305
|
-
|
|
32306
|
-
|
|
32307
|
-
|
|
32308
|
-
|
|
32309
|
-
|
|
32310
|
-
}
|
|
32311
|
-
|
|
32312
|
-
|
|
32313
|
-
|
|
32314
|
-
|
|
32315
|
-
|
|
32316
|
-
|
|
32317
|
-
|
|
32318
|
-
|
|
32319
|
-
|
|
32320
|
-
|
|
32321
|
-
|
|
32322
|
-
|
|
32323
|
-
|
|
32165
|
+
// src/shared/tools/developer-experience-tools.ts
|
|
32166
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
32167
|
+
import { mkdir as mkdir7, readdir as readdir7, readFile as readFile6, writeFile as writeFile6 } from "node:fs/promises";
|
|
32168
|
+
import { dirname as dirname13, join as join18, resolve as resolve25 } from "node:path";
|
|
32169
|
+
|
|
32170
|
+
// src/shared/context.ts
|
|
32171
|
+
import { existsSync as existsSync15, readFileSync as readFileSync12 } from "node:fs";
|
|
32172
|
+
import { homedir as homedir3 } from "node:os";
|
|
32173
|
+
import { resolve as resolve24 } from "node:path";
|
|
32174
|
+
var CORE_CONTEXT_KEYS2 = [
|
|
32175
|
+
"SUPABASE_URL",
|
|
32176
|
+
"SUPABASE_SERVICE_ROLE_KEY",
|
|
32177
|
+
"SUPACLOUD_API_URL",
|
|
32178
|
+
"SUPACLOUD_API_TOKEN",
|
|
32179
|
+
"SUPACLOUD_PROJECT_REF",
|
|
32180
|
+
"SUPACLOUD_HOST",
|
|
32181
|
+
"SUPACLOUD_APPLICATION_ID",
|
|
32182
|
+
"SUPACLOUD_APPLICATION_ENVIRONMENT_ID",
|
|
32183
|
+
"SUPACLOUD_APPLICATION_CONFIGURATION_PATH",
|
|
32184
|
+
"SUPACLOUD_APPLICATION_CONFIGURATION_ID",
|
|
32185
|
+
"SUPACLOUD_APPLICATION_MANIFEST_PATH"
|
|
32186
|
+
];
|
|
32187
|
+
function unquotedEnvValue2(rawValue) {
|
|
32188
|
+
const value = rawValue.trim();
|
|
32189
|
+
const quote = value[0];
|
|
32190
|
+
return quote && (quote === '"' || quote === "'") && value.endsWith(quote) ? value.slice(1, -1) : value;
|
|
32191
|
+
}
|
|
32192
|
+
function parseEnvFile2(contents) {
|
|
32193
|
+
const values = {};
|
|
32194
|
+
for (const rawLine of contents.split(/\r?\n/)) {
|
|
32195
|
+
const line = rawLine.trim();
|
|
32196
|
+
if (!line || line.startsWith("#"))
|
|
32197
|
+
continue;
|
|
32198
|
+
const match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
|
|
32199
|
+
if (!match)
|
|
32200
|
+
continue;
|
|
32201
|
+
values[match[1]] = unquotedEnvValue2(match[2]);
|
|
32202
|
+
}
|
|
32203
|
+
return values;
|
|
32204
|
+
}
|
|
32205
|
+
function readEnvFile2(path, required) {
|
|
32206
|
+
if (!existsSync15(path)) {
|
|
32207
|
+
if (required)
|
|
32208
|
+
throw new Error(`SupaCloud environment file not found: ${path}`);
|
|
32209
|
+
return {};
|
|
32210
|
+
}
|
|
32211
|
+
try {
|
|
32212
|
+
return parseEnvFile2(readFileSync12(path, "utf8"));
|
|
32213
|
+
} catch (error) {
|
|
32214
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
32215
|
+
throw new Error(`Failed to read SupaCloud environment file ${path}: ${message}`);
|
|
32216
|
+
}
|
|
32217
|
+
}
|
|
32218
|
+
function canonicalApiOrigin2(value) {
|
|
32219
|
+
const candidate = value.trim();
|
|
32220
|
+
if (!candidate)
|
|
32221
|
+
return "";
|
|
32222
|
+
try {
|
|
32223
|
+
const url = new URL(candidate);
|
|
32224
|
+
const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
|
|
32225
|
+
const allowedProtocol = url.protocol === "https:" || url.protocol === "http:" && loopback;
|
|
32226
|
+
const exactOrigin = candidate === url.origin || candidate === `${url.origin}/`;
|
|
32227
|
+
return allowedProtocol && !url.username && !url.password && exactOrigin && url.pathname === "/" && !url.search && !url.hash ? url.origin : "";
|
|
32228
|
+
} catch (error) {
|
|
32229
|
+
if (error instanceof TypeError)
|
|
32230
|
+
return "";
|
|
32231
|
+
throw error;
|
|
32232
|
+
}
|
|
32233
|
+
}
|
|
32234
|
+
function hostFromUrl2(value) {
|
|
32235
|
+
return value ? new URL(value).hostname : "";
|
|
32236
|
+
}
|
|
32237
|
+
function inferProjectRefFromSupabaseUrl2(value) {
|
|
32238
|
+
if (!value)
|
|
32239
|
+
return "";
|
|
32240
|
+
return new URL(value).hostname.match(/^([a-z0-9-]+)\.api\./i)?.[1] ?? "";
|
|
32241
|
+
}
|
|
32242
|
+
function sourceCredentialScope2(values, explicitApiUrl, supabaseUrl) {
|
|
32243
|
+
const hasManagementContext = Boolean(explicitApiUrl.trim() || values.SUPACLOUD_API_TOKEN?.trim() || values.SUPACLOUD_HOST?.trim());
|
|
32244
|
+
if (hasManagementContext)
|
|
32245
|
+
return "management";
|
|
32246
|
+
return supabaseUrl || values.SUPABASE_SERVICE_ROLE_KEY?.trim() ? "project_application" : "incomplete";
|
|
32247
|
+
}
|
|
32248
|
+
function sourceProjectCore2(values) {
|
|
32249
|
+
const supabaseUrl = canonicalApiOrigin2(values.SUPABASE_URL || "");
|
|
32250
|
+
const projectRef = (values.SUPACLOUD_PROJECT_REF || "").trim() || inferProjectRefFromSupabaseUrl2(supabaseUrl);
|
|
32251
|
+
const explicitApiUrl = values.SUPACLOUD_API_URL || "";
|
|
32252
|
+
const credentialScope = sourceCredentialScope2(values, explicitApiUrl, supabaseUrl);
|
|
32253
|
+
const managementUrl = explicitApiUrl || (values.SUPACLOUD_HOST ? `http://${values.SUPACLOUD_HOST}:9090` : "");
|
|
32254
|
+
const apiUrl = credentialScope === "management" ? canonicalApiOrigin2(managementUrl) : "";
|
|
32255
|
+
const apiToken = credentialScope === "management" ? values.SUPACLOUD_API_TOKEN || "" : "";
|
|
32256
|
+
return { apiUrl, apiToken, projectRef, supabaseUrl, credentialScope };
|
|
32257
|
+
}
|
|
32258
|
+
function processValues2(env) {
|
|
32259
|
+
return Object.fromEntries(Object.entries(env).filter((entry) => entry[1] !== undefined));
|
|
32260
|
+
}
|
|
32261
|
+
function hasProcessContext2(env) {
|
|
32262
|
+
return CORE_CONTEXT_KEYS2.some((key) => Object.hasOwn(env, key));
|
|
32263
|
+
}
|
|
32264
|
+
function completeProjectContext2(values) {
|
|
32265
|
+
const core = sourceProjectCore2(values);
|
|
32266
|
+
if (core.credentialScope === "project_application") {
|
|
32267
|
+
return Boolean(core.supabaseUrl && values.SUPABASE_SERVICE_ROLE_KEY?.trim() && core.projectRef);
|
|
32268
|
+
}
|
|
32269
|
+
return Boolean(core.apiUrl && core.apiToken && core.projectRef);
|
|
32270
|
+
}
|
|
32271
|
+
function parseInsecureTls2(values) {
|
|
32272
|
+
const value = values.SUPACLOUD_TLS_VERIFY?.trim().toLowerCase();
|
|
32273
|
+
if (value === undefined)
|
|
32274
|
+
return true;
|
|
32275
|
+
if (["1", "true", "yes", "on"].includes(value))
|
|
32276
|
+
return false;
|
|
32277
|
+
if (["0", "false", "no", "off"].includes(value))
|
|
32278
|
+
return true;
|
|
32279
|
+
throw new Error("SUPACLOUD_TLS_VERIFY must be a boolean (true or false)");
|
|
32280
|
+
}
|
|
32281
|
+
function namedEnvironmentSource2(cwd, selector) {
|
|
32282
|
+
const environment = normalizeEnvironmentName(selector);
|
|
32283
|
+
const path = resolve24(cwd, `.env.supacloud.${selector}`);
|
|
32284
|
+
const values = readEnvFile2(path, true);
|
|
32285
|
+
if (values.SUPACLOUD_ENV && normalizeEnvironmentName(values.SUPACLOUD_ENV) !== environment) {
|
|
32286
|
+
throw new Error(`SUPACLOUD_ENV in ${path} does not match selector ${selector}`);
|
|
32287
|
+
}
|
|
32288
|
+
return { values, kind: "named_env_file", path, environment };
|
|
32289
|
+
}
|
|
32290
|
+
function explicitEnvironmentSource2(cwd, envFile) {
|
|
32291
|
+
const path = resolve24(cwd, envFile);
|
|
32292
|
+
const values = readEnvFile2(path, true);
|
|
32293
|
+
if (!values.SUPACLOUD_ENV)
|
|
32294
|
+
throw new Error(`SUPACLOUD_ENV is required in ${path}`);
|
|
32295
|
+
return {
|
|
32296
|
+
values,
|
|
32297
|
+
kind: "explicit_env_file",
|
|
32298
|
+
path,
|
|
32299
|
+
environment: normalizeEnvironmentName(values.SUPACLOUD_ENV)
|
|
32300
|
+
};
|
|
32301
|
+
}
|
|
32302
|
+
function contextSource2(env, cwd, selection) {
|
|
32303
|
+
if (selection.environmentName && selection.envFile)
|
|
32304
|
+
throw new Error("Environment selectors are mutually exclusive");
|
|
32305
|
+
if (selection.environmentName)
|
|
32306
|
+
return namedEnvironmentSource2(cwd, selection.environmentName);
|
|
32307
|
+
if (selection.envFile)
|
|
32308
|
+
return explicitEnvironmentSource2(cwd, selection.envFile);
|
|
32309
|
+
if (env.SUPACLOUD_ENV) {
|
|
32310
|
+
const environment = normalizeEnvironmentName(env.SUPACLOUD_ENV);
|
|
32311
|
+
const values = processValues2(env);
|
|
32312
|
+
if (completeProjectContext2(values)) {
|
|
32313
|
+
return { values, kind: "process_env", path: null, environment };
|
|
32314
|
+
}
|
|
32315
|
+
return namedEnvironmentSource2(cwd, env.SUPACLOUD_ENV);
|
|
32316
|
+
}
|
|
32317
|
+
if (hasProcessContext2(env)) {
|
|
32318
|
+
return { values: processValues2(env), kind: "process_env", path: null, environment: "" };
|
|
32319
|
+
}
|
|
32320
|
+
const path = resolve24(cwd, ".env");
|
|
32321
|
+
const values = readEnvFile2(path, false);
|
|
32322
|
+
const environment = values.SUPACLOUD_ENV ? normalizeEnvironmentName(values.SUPACLOUD_ENV) : "";
|
|
32323
|
+
return { values, kind: Object.keys(values).length ? "legacy_dotenv" : "none", path: existsSync15(path) ? path : null, environment };
|
|
32324
|
+
}
|
|
32325
|
+
function resolveSupaCloudContext2(env = process.env, cwd = process.cwd(), selection = {}) {
|
|
32326
|
+
const source = contextSource2(env, cwd, selection);
|
|
32327
|
+
const core = sourceProjectCore2(source.values);
|
|
32328
|
+
const host = source.values.SUPACLOUD_HOST || hostFromUrl2(core.apiUrl || core.supabaseUrl);
|
|
32329
|
+
const readOnly = env.SUPACLOUD_READ_ONLY === "true" || source.values.SUPACLOUD_READ_ONLY === "true";
|
|
32330
|
+
const insecureTls = parseInsecureTls2(source.values);
|
|
32331
|
+
return {
|
|
32332
|
+
host,
|
|
32333
|
+
sshUser: env.SUPACLOUD_SSH_USER ?? "root",
|
|
32334
|
+
sshPort: parseInt(env.SUPACLOUD_SSH_PORT ?? "22", 10),
|
|
32335
|
+
sshKey: env.SUPACLOUD_SSH_KEY ?? resolve24(homedir3(), ".ssh", "id_rsa"),
|
|
32336
|
+
sshPass: env.SUPACLOUD_SSH_PASS ?? "",
|
|
32337
|
+
apiUrl: core.apiUrl,
|
|
32338
|
+
apiToken: core.apiToken,
|
|
32339
|
+
projectRef: core.projectRef,
|
|
32340
|
+
readOnly,
|
|
32341
|
+
environment: source.environment,
|
|
32342
|
+
production: source.environment === "prod" || source.environment === "production",
|
|
32343
|
+
inferredSupabaseUrl: core.supabaseUrl,
|
|
32344
|
+
inferredServiceRoleKey: source.values.SUPABASE_SERVICE_ROLE_KEY || "",
|
|
32345
|
+
credentialScope: core.credentialScope,
|
|
32346
|
+
source: source.kind,
|
|
32347
|
+
sourcePath: source.path,
|
|
32348
|
+
environmentVariables: source.values,
|
|
32349
|
+
applicationId: source.values.SUPACLOUD_APPLICATION_ID?.trim() || "",
|
|
32350
|
+
applicationEnvironmentId: source.values.SUPACLOUD_APPLICATION_ENVIRONMENT_ID?.trim() || source.environment,
|
|
32351
|
+
applicationConfigurationPath: source.values.SUPACLOUD_APPLICATION_CONFIGURATION_PATH?.trim() || "",
|
|
32352
|
+
applicationConfigurationId: source.values.SUPACLOUD_APPLICATION_CONFIGURATION_ID?.trim() || "",
|
|
32353
|
+
applicationManifestPath: source.values.SUPACLOUD_APPLICATION_MANIFEST_PATH?.trim() || "",
|
|
32354
|
+
insecureTls
|
|
32355
|
+
};
|
|
32356
|
+
}
|
|
32357
|
+
|
|
32358
|
+
// src/shared/tools/developer-experience-tools.ts
|
|
32359
|
+
var initToolSchema = {
|
|
32360
|
+
root: optional(String2(), "Project directory to initialize (default: current directory)"),
|
|
32361
|
+
format: optional(stringEnum2(["text", "json"]), "Output format (default: text)")
|
|
32362
|
+
};
|
|
32363
|
+
var environmentToolSchema = {
|
|
32364
|
+
action: withDescription(stringEnum2(["list", "check"]), "Environment profile inspection action"),
|
|
32365
|
+
root: optional(String2(), "Project directory containing .env.supacloud.* files"),
|
|
32366
|
+
format: optional(stringEnum2(["text", "json"]), "Output format (default: text)")
|
|
32367
|
+
};
|
|
32368
|
+
var TEST_PROFILE = [
|
|
32369
|
+
"SUPACLOUD_ENV=test",
|
|
32370
|
+
"SUPACLOUD_API_URL=https://management.test.example.com",
|
|
32371
|
+
"SUPACLOUD_API_TOKEN=<test-management-api-token>",
|
|
32372
|
+
"SUPACLOUD_PROJECT_REF=<test-project-ref>",
|
|
32373
|
+
"# Optional for executable application delivery: SUPACLOUD_APPLICATION_ID=<application-id>",
|
|
32374
|
+
""
|
|
32375
|
+
].join(`
|
|
32376
|
+
`);
|
|
32377
|
+
var PRODUCTION_PROFILE = [
|
|
32378
|
+
"SUPACLOUD_ENV=production",
|
|
32379
|
+
"SUPACLOUD_API_URL=https://management.example.com",
|
|
32380
|
+
"SUPACLOUD_API_TOKEN=<production-management-api-token>",
|
|
32381
|
+
"SUPACLOUD_PROJECT_REF=<production-project-ref>",
|
|
32382
|
+
"# Optional for executable application delivery: SUPACLOUD_APPLICATION_ID=<application-id>",
|
|
32383
|
+
""
|
|
32384
|
+
].join(`
|
|
32385
|
+
`);
|
|
32386
|
+
function textResult3(text, isError = false) {
|
|
32387
|
+
return { isError, content: [{ type: "text", text }] };
|
|
32388
|
+
}
|
|
32389
|
+
function jsonResult(value, isError = false) {
|
|
32390
|
+
return textResult3(JSON.stringify(value, null, 2), isError);
|
|
32391
|
+
}
|
|
32392
|
+
function record5(value) {
|
|
32393
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
32394
|
+
}
|
|
32395
|
+
function packageDependencies(manifest) {
|
|
32396
|
+
return new Set([
|
|
32397
|
+
...Object.keys(manifest.dependencies ?? {}),
|
|
32398
|
+
...Object.keys(manifest.devDependencies ?? {})
|
|
32399
|
+
]);
|
|
32400
|
+
}
|
|
32401
|
+
function outputDirectoryFor(manifest) {
|
|
32402
|
+
const dependencies = packageDependencies(manifest);
|
|
32403
|
+
if (dependencies.has("next"))
|
|
32404
|
+
return "out";
|
|
32405
|
+
if (dependencies.has("@sveltejs/kit") || dependencies.has("svelte"))
|
|
32406
|
+
return "build";
|
|
32407
|
+
return "dist";
|
|
32408
|
+
}
|
|
32409
|
+
async function readPackageManifest(root) {
|
|
32410
|
+
const path = join18(root, "package.json");
|
|
32411
|
+
if (!existsSync16(path))
|
|
32412
|
+
return {};
|
|
32413
|
+
try {
|
|
32414
|
+
const parsed = JSON.parse(await readFile6(path, "utf8"));
|
|
32415
|
+
const value = record5(parsed);
|
|
32416
|
+
if (!value)
|
|
32417
|
+
throw new Error("package.json must contain an object");
|
|
32418
|
+
const scripts = record5(value.scripts);
|
|
32419
|
+
const dependencies = record5(value.dependencies);
|
|
32420
|
+
const devDependencies = record5(value.devDependencies);
|
|
32421
|
+
return {
|
|
32422
|
+
scripts: scripts ?? undefined,
|
|
32423
|
+
dependencies: dependencies ?? undefined,
|
|
32424
|
+
devDependencies: devDependencies ?? undefined
|
|
32425
|
+
};
|
|
32426
|
+
} catch (error) {
|
|
32427
|
+
throw new Error(`Invalid package.json: ${error instanceof Error ? error.message : String(error)}`);
|
|
32428
|
+
}
|
|
32429
|
+
}
|
|
32430
|
+
async function writeNewFile(path, content) {
|
|
32431
|
+
try {
|
|
32432
|
+
await writeFile6(path, content, { encoding: "utf8", flag: "wx", mode: 384 });
|
|
32433
|
+
return true;
|
|
32434
|
+
} catch (error) {
|
|
32435
|
+
if (error instanceof Error && "code" in error && error.code === "EEXIST")
|
|
32436
|
+
return false;
|
|
32437
|
+
throw error;
|
|
32438
|
+
}
|
|
32439
|
+
}
|
|
32440
|
+
async function ensureGitignore(root) {
|
|
32441
|
+
const path = join18(root, ".gitignore");
|
|
32442
|
+
const existing = existsSync16(path) ? await readFile6(path, "utf8") : "";
|
|
32443
|
+
const required = [".env.supacloud.*", "!.env.supacloud.*.example"];
|
|
32444
|
+
const missing = required.filter((entry) => !existing.split(/\r?\n/).includes(entry));
|
|
32445
|
+
if (missing.length === 0)
|
|
32446
|
+
return false;
|
|
32447
|
+
const prefix = existing.length > 0 && !existing.endsWith(`
|
|
32448
|
+
`) ? `${existing}
|
|
32449
|
+
` : existing;
|
|
32450
|
+
await writeFile6(path, `${prefix}${missing.join(`
|
|
32451
|
+
`)}
|
|
32452
|
+
`, "utf8");
|
|
32453
|
+
return true;
|
|
32454
|
+
}
|
|
32455
|
+
async function initializeProject(request, options = {}) {
|
|
32456
|
+
const root = resolve25(request.root || options.cwd || process.cwd());
|
|
32457
|
+
await mkdir7(root, { recursive: true });
|
|
32458
|
+
const configPath = join18(root, "supacloud.json");
|
|
32459
|
+
if (existsSync16(configPath)) {
|
|
32460
|
+
throw new Error(`supacloud.json already exists in ${root}; init never overwrites project configuration`);
|
|
32461
|
+
}
|
|
32462
|
+
const manifest = await readPackageManifest(root);
|
|
32463
|
+
const hasBuildScript = typeof manifest.scripts?.build === "string" && Boolean(manifest.scripts.build.trim());
|
|
32464
|
+
const outputDirectory = outputDirectoryFor(manifest);
|
|
32465
|
+
const config = {
|
|
32466
|
+
frontend: {
|
|
32467
|
+
root: ".",
|
|
32468
|
+
outputDirectory
|
|
32469
|
+
}
|
|
32470
|
+
};
|
|
32471
|
+
const created = [];
|
|
32472
|
+
try {
|
|
32473
|
+
await writeFile6(configPath, `${JSON.stringify(config, null, 2)}
|
|
32474
|
+
`, {
|
|
32475
|
+
encoding: "utf8",
|
|
32476
|
+
flag: "wx",
|
|
32477
|
+
mode: 420
|
|
32478
|
+
});
|
|
32479
|
+
} catch (error) {
|
|
32480
|
+
if (error instanceof Error && "code" in error && error.code === "EEXIST") {
|
|
32481
|
+
throw new Error(`supacloud.json already exists in ${root}; init never overwrites project configuration`);
|
|
32482
|
+
}
|
|
32483
|
+
throw error;
|
|
32484
|
+
}
|
|
32485
|
+
created.push("supacloud.json");
|
|
32486
|
+
if (await writeNewFile(join18(root, ".env.supacloud.test.example"), TEST_PROFILE)) {
|
|
32487
|
+
created.push(".env.supacloud.test.example");
|
|
32488
|
+
}
|
|
32489
|
+
if (await writeNewFile(join18(root, ".env.supacloud.production.example"), PRODUCTION_PROFILE)) {
|
|
32490
|
+
created.push(".env.supacloud.production.example");
|
|
32491
|
+
}
|
|
32492
|
+
if (await ensureGitignore(root))
|
|
32493
|
+
created.push(".gitignore");
|
|
32494
|
+
const result = {
|
|
32495
|
+
schema: "supacloud.developer-experience.init.v1",
|
|
32496
|
+
ok: true,
|
|
32497
|
+
root,
|
|
32498
|
+
created,
|
|
32499
|
+
detected: {
|
|
32500
|
+
package_json: existsSync16(join18(root, "package.json")),
|
|
32501
|
+
build_script: hasBuildScript,
|
|
32502
|
+
output_directory: outputDirectory
|
|
32503
|
+
},
|
|
32504
|
+
next: [
|
|
32505
|
+
"cp .env.supacloud.test.example .env.supacloud.test",
|
|
32506
|
+
"Configure frontend assets; set SUPACLOUD_APPLICATION_ID only for executable application delivery.",
|
|
32507
|
+
"supacloud env check",
|
|
32508
|
+
"supacloud --env test deploy"
|
|
32509
|
+
]
|
|
32510
|
+
};
|
|
32511
|
+
if (request.format === "json")
|
|
32512
|
+
return jsonResult(result);
|
|
32513
|
+
return textResult3([
|
|
32514
|
+
`Initialized SupaCloud deployment in ${root}.`,
|
|
32515
|
+
`Created: ${created.join(", ")}.`,
|
|
32516
|
+
hasBuildScript ? `Detected package.json build script; deploy output defaults to ${outputDirectory}.` : "No package.json build script detected; add one before deploying.",
|
|
32517
|
+
"",
|
|
32518
|
+
...result.next
|
|
32519
|
+
].join(`
|
|
32520
|
+
`));
|
|
32521
|
+
}
|
|
32522
|
+
|
|
32523
|
+
// src/shared/tools/deploy-tools.ts
|
|
32524
|
+
var MAX_SOURCE_BYTES = 256 * 1024 * 1024;
|
|
32525
|
+
var PUBLIC_BUILD_ENV_PREFIXES = ["PUBLIC_", "VITE_", "NEXT_PUBLIC_", "NUXT_PUBLIC_", "ASTRO_PUBLIC_"];
|
|
32526
|
+
var SENSITIVE_BUILD_ENV = /(?:^|_)(?:PASSWORD|PASS|SECRET|TOKEN|KEY|CREDENTIAL|AUTHORIZATION|COOKIE|SESSION)(?:_|$)|(?:DATABASE_URL|DATABASE_URI|CONNECTION_STRING|DSN|PRIVATE_KEY)/i;
|
|
32527
|
+
function isPublicBuildEnv(name) {
|
|
32528
|
+
return PUBLIC_BUILD_ENV_PREFIXES.some((prefix) => name.startsWith(prefix));
|
|
32529
|
+
}
|
|
32530
|
+
function isProtectedBuildEnv(name) {
|
|
32531
|
+
return name.startsWith("SUPACLOUD_") || name === "SUPABASE_SERVICE_ROLE_KEY" || name === "SUPABASE_ACCESS_TOKEN" || name === "MANAGEMENT_API_TOKEN";
|
|
32532
|
+
}
|
|
32533
|
+
var deployToolSchema = {
|
|
32534
|
+
action: optional(stringEnum2(["init"]), "[init] Create local deployment configuration and environment examples"),
|
|
32535
|
+
...initToolSchema,
|
|
32536
|
+
root: optional(String2(), "[init] Project directory to initialize (default: current directory)"),
|
|
32537
|
+
ref: optional(String2(), "Project ref (defaults to the linked project)"),
|
|
32538
|
+
cwd: optional(String2(), "Project directory (default: current directory)"),
|
|
32539
|
+
skip_build: optional(Boolean2(), "Use an existing output directory without building"),
|
|
32540
|
+
assets_only: optional(Boolean2(), "Publish only configured static assets; leave backend Functions, schedules and migrations unchanged"),
|
|
32541
|
+
backend: optional(Boolean2(), "Publish the application backend independently; do not build or activate frontend assets"),
|
|
32542
|
+
status: optional(String2({ format: "uuid" }), "Read a backend deployment status without publishing"),
|
|
32543
|
+
format: optional(stringEnum2(["text", "json"]), "Output format (default: text)")
|
|
32544
|
+
};
|
|
32545
|
+
function record6(candidate) {
|
|
32324
32546
|
return candidate && typeof candidate === "object" && !Array.isArray(candidate) ? candidate : null;
|
|
32325
32547
|
}
|
|
32326
32548
|
function requiredString(candidate, field) {
|
|
@@ -32330,7 +32552,7 @@ function requiredString(candidate, field) {
|
|
|
32330
32552
|
return candidate.trim();
|
|
32331
32553
|
}
|
|
32332
32554
|
function frontendDeployment(candidate) {
|
|
32333
|
-
const value =
|
|
32555
|
+
const value = record6(candidate);
|
|
32334
32556
|
if (!value)
|
|
32335
32557
|
throw new Error("Invalid frontend deployment response");
|
|
32336
32558
|
return {
|
|
@@ -32344,7 +32566,7 @@ function frontendDeployment(candidate) {
|
|
|
32344
32566
|
}
|
|
32345
32567
|
async function readJson(path) {
|
|
32346
32568
|
try {
|
|
32347
|
-
return JSON.parse(await
|
|
32569
|
+
return JSON.parse(await readFile7(path, "utf8"));
|
|
32348
32570
|
} catch (error) {
|
|
32349
32571
|
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
32350
32572
|
return null;
|
|
@@ -32352,15 +32574,15 @@ async function readJson(path) {
|
|
|
32352
32574
|
}
|
|
32353
32575
|
}
|
|
32354
32576
|
async function findDeployConfigRoot(startDirectory, environmentName) {
|
|
32355
|
-
let directory =
|
|
32577
|
+
let directory = resolve26(startDirectory);
|
|
32356
32578
|
while (true) {
|
|
32357
32579
|
const environmentFile = environmentName ? `.env.supacloud.${environmentName}` : null;
|
|
32358
|
-
if (await pathExists(
|
|
32580
|
+
if (await pathExists(join19(directory, "supacloud.json")) || await pathExists(join19(directory, "supacloud.config.ts")) || environmentFile !== null && await pathExists(join19(directory, environmentFile))) {
|
|
32359
32581
|
return directory;
|
|
32360
32582
|
}
|
|
32361
|
-
const parent =
|
|
32583
|
+
const parent = dirname14(directory);
|
|
32362
32584
|
if (parent === directory)
|
|
32363
|
-
return
|
|
32585
|
+
return resolve26(startDirectory);
|
|
32364
32586
|
directory = parent;
|
|
32365
32587
|
}
|
|
32366
32588
|
}
|
|
@@ -32400,7 +32622,7 @@ function optionalStringArray(value, field) {
|
|
|
32400
32622
|
});
|
|
32401
32623
|
}
|
|
32402
32624
|
function deployTargetConfig(candidate, name) {
|
|
32403
|
-
const value =
|
|
32625
|
+
const value = record6(candidate);
|
|
32404
32626
|
if (!value || value.type !== "frontend" && value.type !== "edge_function") {
|
|
32405
32627
|
throw new Error(`supacloud.json targets.${name}.type must be 'frontend' or 'edge_function'`);
|
|
32406
32628
|
}
|
|
@@ -32425,14 +32647,14 @@ function deployTargetConfig(candidate, name) {
|
|
|
32425
32647
|
};
|
|
32426
32648
|
}
|
|
32427
32649
|
async function readDeployConfig(configRoot) {
|
|
32428
|
-
const configPath =
|
|
32650
|
+
const configPath = join19(configRoot, "supacloud.json");
|
|
32429
32651
|
const candidate = await readJson(configPath);
|
|
32430
32652
|
if (candidate === null)
|
|
32431
32653
|
return {};
|
|
32432
|
-
const config =
|
|
32433
|
-
const frontend =
|
|
32434
|
-
const application =
|
|
32435
|
-
const targetValues =
|
|
32654
|
+
const config = record6(candidate);
|
|
32655
|
+
const frontend = record6(config?.frontend);
|
|
32656
|
+
const application = record6(config?.application);
|
|
32657
|
+
const targetValues = record6(config?.targets);
|
|
32436
32658
|
if (!config || config.frontend !== undefined && !frontend || config.application !== undefined && !application || config.targets !== undefined && !targetValues) {
|
|
32437
32659
|
throw new Error("supacloud.json must contain object-valued 'application', 'frontend' or 'targets' configuration");
|
|
32438
32660
|
}
|
|
@@ -32472,16 +32694,16 @@ async function readDeployConfig(configRoot) {
|
|
|
32472
32694
|
};
|
|
32473
32695
|
}
|
|
32474
32696
|
async function packageName(projectDirectory) {
|
|
32475
|
-
const candidate =
|
|
32697
|
+
const candidate = record6(await readJson(join19(projectDirectory, "package.json")));
|
|
32476
32698
|
return typeof candidate?.name === "string" && candidate.name.trim() ? candidate.name.trim() : null;
|
|
32477
32699
|
}
|
|
32478
32700
|
async function pathExists(path) {
|
|
32479
32701
|
return access(path).then(() => true, () => false);
|
|
32480
32702
|
}
|
|
32481
32703
|
async function hasProjectReleaseManifest(root) {
|
|
32482
|
-
if (await pathExists(
|
|
32704
|
+
if (await pathExists(join19(root, "deploy-manifest.json")))
|
|
32483
32705
|
return true;
|
|
32484
|
-
const directory = await opendir(
|
|
32706
|
+
const directory = await opendir(join19(root, "supacloud")).catch((error) => {
|
|
32485
32707
|
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
32486
32708
|
return null;
|
|
32487
32709
|
throw error;
|
|
@@ -32492,28 +32714,28 @@ async function hasProjectReleaseManifest(root) {
|
|
|
32492
32714
|
for await (const entry of directory) {
|
|
32493
32715
|
if (++entries > 128)
|
|
32494
32716
|
throw new Error("Too many SupaCloud project roots to resolve a deployment");
|
|
32495
|
-
if ((entry.isDirectory() || entry.isSymbolicLink()) && await pathExists(
|
|
32717
|
+
if ((entry.isDirectory() || entry.isSymbolicLink()) && await pathExists(join19(root, "supacloud", entry.name, "deploy-manifest.json")))
|
|
32496
32718
|
return true;
|
|
32497
32719
|
}
|
|
32498
32720
|
return false;
|
|
32499
32721
|
}
|
|
32500
32722
|
async function defaultBuildCommand(projectDirectory, configRoot) {
|
|
32501
|
-
const packageJson =
|
|
32502
|
-
const scripts =
|
|
32723
|
+
const packageJson = record6(await readJson(join19(projectDirectory, "package.json")));
|
|
32724
|
+
const scripts = record6(packageJson?.scripts);
|
|
32503
32725
|
if (typeof scripts?.build !== "string" || !scripts.build.trim()) {
|
|
32504
32726
|
throw new Error("No build command was configured and package.json has no build script");
|
|
32505
32727
|
}
|
|
32506
32728
|
let directory = projectDirectory;
|
|
32507
32729
|
while (true) {
|
|
32508
|
-
if (await pathExists(
|
|
32730
|
+
if (await pathExists(join19(directory, "bun.lock")) || await pathExists(join19(directory, "bun.lockb")))
|
|
32509
32731
|
return "bun run build";
|
|
32510
|
-
if (await pathExists(
|
|
32732
|
+
if (await pathExists(join19(directory, "pnpm-lock.yaml")))
|
|
32511
32733
|
return "pnpm run build";
|
|
32512
|
-
if (await pathExists(
|
|
32734
|
+
if (await pathExists(join19(directory, "yarn.lock")))
|
|
32513
32735
|
return "yarn build";
|
|
32514
32736
|
if (directory === configRoot)
|
|
32515
32737
|
break;
|
|
32516
|
-
const parent =
|
|
32738
|
+
const parent = dirname14(directory);
|
|
32517
32739
|
if (parent === directory || !directory.startsWith(`${configRoot}${sep5}`))
|
|
32518
32740
|
break;
|
|
32519
32741
|
directory = parent;
|
|
@@ -32521,7 +32743,7 @@ async function defaultBuildCommand(projectDirectory, configRoot) {
|
|
|
32521
32743
|
return "npm run build";
|
|
32522
32744
|
}
|
|
32523
32745
|
function deploymentList(candidate) {
|
|
32524
|
-
const values = Array.isArray(candidate) ? candidate : Array.isArray(
|
|
32746
|
+
const values = Array.isArray(candidate) ? candidate : Array.isArray(record6(candidate)?.deployments) ? record6(candidate)?.deployments : null;
|
|
32525
32747
|
if (!values)
|
|
32526
32748
|
throw new Error("Invalid frontend deployment list response");
|
|
32527
32749
|
return values.map(frontendDeployment);
|
|
@@ -32581,9 +32803,9 @@ function selectFrontendTarget(configRoot, config) {
|
|
|
32581
32803
|
throw new Error(`Multiple frontend deploy targets found (${targets.map(([name]) => name).sort().join(", ")}). Set defaultTarget to the Web target`);
|
|
32582
32804
|
}
|
|
32583
32805
|
function targetRoot(configRoot, target, name) {
|
|
32584
|
-
const root =
|
|
32585
|
-
const prefix = `${
|
|
32586
|
-
if (root !==
|
|
32806
|
+
const root = resolve26(configRoot, target.root || (name === "frontend" ? "." : name));
|
|
32807
|
+
const prefix = `${resolve26(configRoot)}${sep5}`;
|
|
32808
|
+
if (root !== resolve26(configRoot) && !root.startsWith(prefix)) {
|
|
32587
32809
|
throw new Error(`Deploy target '${name}' root must stay inside the repository root`);
|
|
32588
32810
|
}
|
|
32589
32811
|
return root;
|
|
@@ -32601,8 +32823,8 @@ function defaultOutputDirectory(framework) {
|
|
|
32601
32823
|
}
|
|
32602
32824
|
}
|
|
32603
32825
|
function resolveInsideProject(projectDirectory, candidate, allowRoot = false) {
|
|
32604
|
-
const resolved =
|
|
32605
|
-
const projectRoot =
|
|
32826
|
+
const resolved = resolve26(projectDirectory, candidate);
|
|
32827
|
+
const projectRoot = resolve26(projectDirectory);
|
|
32606
32828
|
const projectPrefix = `${projectRoot}${sep5}`;
|
|
32607
32829
|
if (!allowRoot && resolved === projectRoot || resolved !== projectRoot && !resolved.startsWith(projectPrefix)) {
|
|
32608
32830
|
throw new Error("Frontend output directory must stay inside the project directory");
|
|
@@ -32656,7 +32878,7 @@ async function collectArchiveFiles(root) {
|
|
|
32656
32878
|
entries.push(entry);
|
|
32657
32879
|
entries.sort((left, right) => left.name.localeCompare(right.name, "en"));
|
|
32658
32880
|
for (const entry of entries) {
|
|
32659
|
-
const path =
|
|
32881
|
+
const path = join19(directory, entry.name);
|
|
32660
32882
|
const stats = await lstat5(path);
|
|
32661
32883
|
if (stats.isSymbolicLink())
|
|
32662
32884
|
throw new Error(`Frontend output cannot contain symbolic links: ${path}`);
|
|
@@ -32675,7 +32897,7 @@ async function collectArchiveFiles(root) {
|
|
|
32675
32897
|
const files = new Map;
|
|
32676
32898
|
const digests = [];
|
|
32677
32899
|
for (const path of paths) {
|
|
32678
|
-
const data = new Uint8Array(await
|
|
32900
|
+
const data = new Uint8Array(await readFile7(path));
|
|
32679
32901
|
bytes += data.byteLength;
|
|
32680
32902
|
if (bytes > MAX_SOURCE_BYTES)
|
|
32681
32903
|
throw new Error(`Frontend output exceeds ${MAX_SOURCE_BYTES} bytes`);
|
|
@@ -32694,11 +32916,11 @@ async function collectArchiveFiles(root) {
|
|
|
32694
32916
|
return { files, bytes, count: paths.length, treeSha256: tree.digest("hex") };
|
|
32695
32917
|
}
|
|
32696
32918
|
async function createCollectedFrontendArchive(outputDirectory, collected) {
|
|
32697
|
-
const temporaryDirectory = await mkdtemp2(
|
|
32698
|
-
const archivePath =
|
|
32919
|
+
const temporaryDirectory = await mkdtemp2(join19(tmpdir3(), "supacloud-deploy-"));
|
|
32920
|
+
const archivePath = join19(temporaryDirectory, `${basename8(outputDirectory)}.tar.zst`);
|
|
32699
32921
|
try {
|
|
32700
32922
|
const archiveBytes = await createFrontendTarZstd(collected.files);
|
|
32701
|
-
await
|
|
32923
|
+
await writeFile7(archivePath, archiveBytes);
|
|
32702
32924
|
const sha256 = createHash11("sha256").update(archiveBytes).digest("hex");
|
|
32703
32925
|
return {
|
|
32704
32926
|
archivePath,
|
|
@@ -32743,7 +32965,13 @@ async function publishConfiguredFrontend(http, projectRef, configRoot, skipBuild
|
|
|
32743
32965
|
}
|
|
32744
32966
|
function registerDeployTools(server, http, options = {}) {
|
|
32745
32967
|
registerTool(server, "deploy", "Build and publish the linked project with one command", deployToolSchema, async (args) => {
|
|
32746
|
-
const invocationDirectory =
|
|
32968
|
+
const invocationDirectory = resolve26(String(args.cwd || options.cwd || process.cwd()));
|
|
32969
|
+
if (args.action === "init") {
|
|
32970
|
+
return initializeProject({
|
|
32971
|
+
...typeof args.root === "string" ? { root: args.root } : {},
|
|
32972
|
+
...args.format === "json" ? { format: "json" } : {}
|
|
32973
|
+
}, { cwd: invocationDirectory });
|
|
32974
|
+
}
|
|
32747
32975
|
const projectRef = String(args.ref || options.projectRef || "").trim();
|
|
32748
32976
|
if (!projectRef)
|
|
32749
32977
|
throw new Error("A linked project ref is required. Set SUPACLOUD_PROJECT_REF or pass --ref");
|
|
@@ -32774,7 +33002,7 @@ function registerDeployTools(server, http, options = {}) {
|
|
|
32774
33002
|
throw new Error("Static asset deployment requires SUPACLOUD_ENV");
|
|
32775
33003
|
}
|
|
32776
33004
|
await requireProjectDeploymentProtocol(http, projectRef, environmentId);
|
|
32777
|
-
const result = await publishConfiguredFrontend(http, projectRef, configRoot, args.skip_build === true, args.
|
|
33005
|
+
const result = await publishConfiguredFrontend(http, projectRef, configRoot, args.skip_build === true, args.format === "json", options.environmentVariables);
|
|
32778
33006
|
if (!result)
|
|
32779
33007
|
throw new Error("Static asset deployment did not produce a receipt");
|
|
32780
33008
|
return result;
|
|
@@ -32809,7 +33037,7 @@ function registerDeployTools(server, http, options = {}) {
|
|
|
32809
33037
|
...configurationId ? { configuration_id: configurationId } : {},
|
|
32810
33038
|
...application.manifestPath ? { manifest_path: application.manifestPath } : {},
|
|
32811
33039
|
...args.skip_build === true ? { skip_build: true } : {},
|
|
32812
|
-
...args.
|
|
33040
|
+
...args.format === "json" ? { format: "json" } : {}
|
|
32813
33041
|
});
|
|
32814
33042
|
return result;
|
|
32815
33043
|
}
|
|
@@ -32818,11 +33046,11 @@ function registerDeployTools(server, http, options = {}) {
|
|
|
32818
33046
|
}
|
|
32819
33047
|
|
|
32820
33048
|
// src/shared/tools/remote-dev-tools.ts
|
|
32821
|
-
import { existsSync as
|
|
32822
|
-
import { readFile as
|
|
32823
|
-
import { readdir as
|
|
33049
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
33050
|
+
import { readFile as readFile8 } from "node:fs/promises";
|
|
33051
|
+
import { readdir as readdir8 } from "node:fs/promises";
|
|
32824
33052
|
import { createHash as createHash12 } from "node:crypto";
|
|
32825
|
-
import { join as
|
|
33053
|
+
import { join as join20, resolve as resolve27 } from "node:path";
|
|
32826
33054
|
import { compileProject as compileProject2 } from "@supacloud/compiler";
|
|
32827
33055
|
var SAFE_TOKEN = /^[A-Za-z0-9._:@/+,-]+$/;
|
|
32828
33056
|
var SAFE_REMOTE_ROOT = /^\/(?:[A-Za-z0-9._-]+\/)*[A-Za-z0-9._-]+\/?$/;
|
|
@@ -32839,7 +33067,6 @@ var remoteDevToolSchema = {
|
|
|
32839
33067
|
delete: optional(Boolean2(), "Delete remote files absent locally"),
|
|
32840
33068
|
reload: optional(Boolean2(), "Reload the affected target after sync (default: true)"),
|
|
32841
33069
|
interval_ms: optional(Number2(), "Watch debounce interval in milliseconds (default: 300)"),
|
|
32842
|
-
json: optional(Boolean2(), "Emit machine-readable JSON"),
|
|
32843
33070
|
apply: optional(Boolean2(), "Apply generated migrations to the selected test database"),
|
|
32844
33071
|
drizzle_config: optional(String2(), "Drizzle config path"),
|
|
32845
33072
|
migrations_dir: optional(String2(), "Migration directory"),
|
|
@@ -32857,8 +33084,8 @@ async function runProcess(executable, args, cwd) {
|
|
|
32857
33084
|
function resolveDrizzleCommand(root, configured) {
|
|
32858
33085
|
if (configured?.trim())
|
|
32859
33086
|
return configured.trim();
|
|
32860
|
-
const local =
|
|
32861
|
-
return
|
|
33087
|
+
const local = join20(root, "node_modules", ".bin", "drizzle-kit");
|
|
33088
|
+
return existsSync17(local) ? local : "drizzle-kit";
|
|
32862
33089
|
}
|
|
32863
33090
|
function toolFailed(value) {
|
|
32864
33091
|
if (!value || typeof value !== "object")
|
|
@@ -32868,14 +33095,14 @@ function toolFailed(value) {
|
|
|
32868
33095
|
return result.isError === true || content.some((chunk) => chunk !== null && typeof chunk === "object" && ("text" in chunk) && typeof chunk.text === "string" && chunk.text.trimStart().startsWith("❌"));
|
|
32869
33096
|
}
|
|
32870
33097
|
async function migrateDatabase(args, options) {
|
|
32871
|
-
const root =
|
|
33098
|
+
const root = resolve27(String(args.project_dir || options.cwd || process.cwd()));
|
|
32872
33099
|
const projectConfig = await readProjectConfig(root);
|
|
32873
33100
|
const config = projectConfig.dev || {};
|
|
32874
33101
|
const database = config.database || {};
|
|
32875
33102
|
const execute = options.execute || ((command, commandArgs, cwd) => runProcess(command, commandArgs, cwd));
|
|
32876
|
-
const drizzleConfig =
|
|
33103
|
+
const drizzleConfig = resolve27(root, String(args.drizzle_config || database.drizzleConfig || "drizzle.config.ts"));
|
|
32877
33104
|
const migrationsDir = String(args.migrations_dir || database.migrationsDir || "supabase/migrations");
|
|
32878
|
-
if (!
|
|
33105
|
+
if (!existsSync17(drizzleConfig))
|
|
32879
33106
|
throw new Error(`Drizzle config not found: ${drizzleConfig}`);
|
|
32880
33107
|
const generated = await execute(resolveDrizzleCommand(root, typeof args.drizzle_bin === "string" ? args.drizzle_bin : database.drizzleBin), ["generate", "--config", drizzleConfig], root);
|
|
32881
33108
|
if (generated.exitCode !== 0)
|
|
@@ -32893,11 +33120,11 @@ async function migrateDatabase(args, options) {
|
|
|
32893
33120
|
return { ok: true, mode: "dev", action: "migrate", generated: true, applied: true, migrations_dir: migrationsDir, result: applied };
|
|
32894
33121
|
}
|
|
32895
33122
|
async function readProjectConfig(root) {
|
|
32896
|
-
const file =
|
|
32897
|
-
if (!
|
|
33123
|
+
const file = join20(root, "supacloud.json");
|
|
33124
|
+
if (!existsSync17(file))
|
|
32898
33125
|
return {};
|
|
32899
33126
|
try {
|
|
32900
|
-
const parsed = JSON.parse(await
|
|
33127
|
+
const parsed = JSON.parse(await readFile8(file, "utf8"));
|
|
32901
33128
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
32902
33129
|
} catch (error) {
|
|
32903
33130
|
throw new Error(`Invalid supacloud.json: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -32910,9 +33137,9 @@ async function readDevConfig(root) {
|
|
|
32910
33137
|
async function sourceFingerprint(root) {
|
|
32911
33138
|
const files = [];
|
|
32912
33139
|
const visit = async (directory) => {
|
|
32913
|
-
const entries = await
|
|
33140
|
+
const entries = await readdir8(directory, { withFileTypes: true });
|
|
32914
33141
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
32915
|
-
const path =
|
|
33142
|
+
const path = join20(directory, entry.name);
|
|
32916
33143
|
if (entry.isDirectory() && ![".git", "node_modules", "dist", "generated", ".supacloud"].includes(entry.name))
|
|
32917
33144
|
await visit(path);
|
|
32918
33145
|
else if (entry.isFile())
|
|
@@ -32923,7 +33150,7 @@ async function sourceFingerprint(root) {
|
|
|
32923
33150
|
const hash = createHash12("sha256");
|
|
32924
33151
|
for (const file of files) {
|
|
32925
33152
|
hash.update(file.slice(root.length).replace(/\\/g, "/"));
|
|
32926
|
-
hash.update(await
|
|
33153
|
+
hash.update(await readFile8(file));
|
|
32927
33154
|
}
|
|
32928
33155
|
return hash.digest("hex");
|
|
32929
33156
|
}
|
|
@@ -32940,19 +33167,19 @@ function remoteRoot(value) {
|
|
|
32940
33167
|
}
|
|
32941
33168
|
function targetDirectory(root, target, functionSlug, config = {}) {
|
|
32942
33169
|
if (target === "db")
|
|
32943
|
-
return
|
|
33170
|
+
return join20(root, "supabase", "migrations");
|
|
32944
33171
|
if (target === "functions") {
|
|
32945
33172
|
const targets = config["targets"];
|
|
32946
33173
|
const entries = targets && typeof targets === "object" ? Object.values(targets) : [];
|
|
32947
33174
|
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));
|
|
32948
|
-
const base = match?.root ?
|
|
32949
|
-
return match?.root ? base : functionSlug ?
|
|
33175
|
+
const base = match?.root ? resolve27(root, match.root) : join20(root, "supabase", "functions");
|
|
33176
|
+
return match?.root ? base : functionSlug ? join20(base, safeToken(functionSlug, "function")) : base;
|
|
32950
33177
|
}
|
|
32951
33178
|
if (target === "frontend") {
|
|
32952
33179
|
const targets = config["targets"];
|
|
32953
33180
|
const entries = targets && typeof targets === "object" ? Object.values(targets) : [];
|
|
32954
33181
|
const match = entries.find((entry) => entry !== null && typeof entry === "object" && ("type" in entry) && entry.type === "frontend" && (!("root" in entry) || typeof entry.root === "string"));
|
|
32955
|
-
return match?.root ?
|
|
33182
|
+
return match?.root ? resolve27(root, match.root) : join20(root, "apps", "web");
|
|
32956
33183
|
}
|
|
32957
33184
|
return root;
|
|
32958
33185
|
}
|
|
@@ -32991,10 +33218,10 @@ function reloadCommand(config, target, projectRef, functionSlug) {
|
|
|
32991
33218
|
return [...command.split(" "), "--project-ref", safeToken(projectRef || "test", "project_ref"), "--target", targetConfigPath(target), ...functionSlug ? ["--function", safeToken(functionSlug, "function")] : []];
|
|
32992
33219
|
}
|
|
32993
33220
|
function sshArgs(connection, remoteCommand) {
|
|
32994
|
-
return ["-p", String(connection.port), "-i",
|
|
33221
|
+
return ["-p", String(connection.port), "-i", resolve27(connection.key), "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=yes", `${connection.user}@${connection.host}`, ...remoteCommand];
|
|
32995
33222
|
}
|
|
32996
33223
|
function rsyncArgs(connection, source, destination, excludes, deleteRemote) {
|
|
32997
|
-
const args = ["-az", "--checksum", "--partial", "--protect-args", "-e", `ssh -p ${connection.port} -i ${
|
|
33224
|
+
const args = ["-az", "--checksum", "--partial", "--protect-args", "-e", `ssh -p ${connection.port} -i ${resolve27(connection.key)} -o BatchMode=yes -o StrictHostKeyChecking=yes`];
|
|
32998
33225
|
if (deleteRemote)
|
|
32999
33226
|
args.push("--delete-delay");
|
|
33000
33227
|
for (const exclude of excludes) {
|
|
@@ -33006,17 +33233,17 @@ function rsyncArgs(connection, source, destination, excludes, deleteRemote) {
|
|
|
33006
33233
|
return args;
|
|
33007
33234
|
}
|
|
33008
33235
|
async function syncOnce(args, options) {
|
|
33009
|
-
const root =
|
|
33236
|
+
const root = resolve27(String(args.project_dir || options.cwd || process.cwd()));
|
|
33010
33237
|
const projectConfig = await readProjectConfig(root);
|
|
33011
33238
|
const config = projectConfig.dev || {};
|
|
33012
33239
|
const target = String(args.target || "project");
|
|
33013
33240
|
const source = targetDirectory(root, target, typeof args.function === "string" ? args.function : undefined, projectConfig);
|
|
33014
|
-
if (!
|
|
33241
|
+
if (!existsSync17(source))
|
|
33015
33242
|
throw new Error(`Dev source directory not found: ${source}`);
|
|
33016
33243
|
let compiled = false;
|
|
33017
33244
|
if (config.compile === true && target !== "db") {
|
|
33018
|
-
const compileRoot =
|
|
33019
|
-
const compileOutDir =
|
|
33245
|
+
const compileRoot = resolve27(root, config.compileRoot || ".");
|
|
33246
|
+
const compileOutDir = resolve27(root, config.compileOutDir || "generated");
|
|
33020
33247
|
const compilation = await compileProject2({
|
|
33021
33248
|
rootDir: compileRoot,
|
|
33022
33249
|
outDir: compileOutDir,
|
|
@@ -33054,7 +33281,7 @@ function registerRemoteDevTools(server, options = {}) {
|
|
|
33054
33281
|
if (["production", "prod"].includes((options.environment || "").toLowerCase()))
|
|
33055
33282
|
throw new Error("Remote dev mode is forbidden for production environments");
|
|
33056
33283
|
if (args.action === "status") {
|
|
33057
|
-
const root =
|
|
33284
|
+
const root = resolve27(String(args.project_dir || options.cwd || process.cwd()));
|
|
33058
33285
|
const config = await readDevConfig(root);
|
|
33059
33286
|
const connection = connectionArgs(options, config, args);
|
|
33060
33287
|
const execute = options.execute || ((command, commandArgs, cwd) => runProcess(command, commandArgs, cwd));
|
|
@@ -33068,7 +33295,7 @@ function registerRemoteDevTools(server, options = {}) {
|
|
|
33068
33295
|
return { content: [{ type: "text", text: JSON.stringify(await migrateDatabase(args, options), null, 2) }] };
|
|
33069
33296
|
}
|
|
33070
33297
|
if (args.action === "watch") {
|
|
33071
|
-
const root =
|
|
33298
|
+
const root = resolve27(String(args.project_dir || options.cwd || process.cwd()));
|
|
33072
33299
|
const interval = Math.max(100, Math.min(1e4, Number(args.interval_ms || 300)));
|
|
33073
33300
|
let fingerprint = "";
|
|
33074
33301
|
let lastSync = null;
|
|
@@ -33088,215 +33315,19 @@ function registerRemoteDevTools(server, options = {}) {
|
|
|
33088
33315
|
}
|
|
33089
33316
|
|
|
33090
33317
|
// src/shared/tools/developer-experience-tools.ts
|
|
33091
|
-
import { existsSync as
|
|
33092
|
-
import { mkdir as
|
|
33093
|
-
import { dirname as
|
|
33094
|
-
|
|
33095
|
-
// src/shared/context.ts
|
|
33096
|
-
import { existsSync as existsSync16, readFileSync as readFileSync12 } from "node:fs";
|
|
33097
|
-
import { homedir as homedir3 } from "node:os";
|
|
33098
|
-
import { resolve as resolve26 } from "node:path";
|
|
33099
|
-
var CORE_CONTEXT_KEYS2 = [
|
|
33100
|
-
"SUPABASE_URL",
|
|
33101
|
-
"SUPABASE_SERVICE_ROLE_KEY",
|
|
33102
|
-
"SUPACLOUD_API_URL",
|
|
33103
|
-
"SUPACLOUD_MANAGEMENT_API_URL",
|
|
33104
|
-
"MANAGEMENT_API_URL",
|
|
33105
|
-
"SUPACLOUD_API_TOKEN",
|
|
33106
|
-
"SUPACLOUD_PROJECT_REF",
|
|
33107
|
-
"X_PROJECT_REF",
|
|
33108
|
-
"SUPACLOUD_HOST",
|
|
33109
|
-
"SUPACLOUD_APPLICATION_ID",
|
|
33110
|
-
"SUPACLOUD_APPLICATION_ENVIRONMENT_ID",
|
|
33111
|
-
"SUPACLOUD_APPLICATION_CONFIGURATION_PATH",
|
|
33112
|
-
"SUPACLOUD_APPLICATION_CONFIGURATION_ID",
|
|
33113
|
-
"SUPACLOUD_APPLICATION_MANIFEST_PATH"
|
|
33114
|
-
];
|
|
33115
|
-
function unquotedEnvValue2(rawValue) {
|
|
33116
|
-
const value = rawValue.trim();
|
|
33117
|
-
const quote = value[0];
|
|
33118
|
-
return quote && (quote === '"' || quote === "'") && value.endsWith(quote) ? value.slice(1, -1) : value;
|
|
33119
|
-
}
|
|
33120
|
-
function parseEnvFile2(contents) {
|
|
33121
|
-
const values = {};
|
|
33122
|
-
for (const rawLine of contents.split(/\r?\n/)) {
|
|
33123
|
-
const line = rawLine.trim();
|
|
33124
|
-
if (!line || line.startsWith("#"))
|
|
33125
|
-
continue;
|
|
33126
|
-
const match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
|
|
33127
|
-
if (!match)
|
|
33128
|
-
continue;
|
|
33129
|
-
values[match[1]] = unquotedEnvValue2(match[2]);
|
|
33130
|
-
}
|
|
33131
|
-
return values;
|
|
33132
|
-
}
|
|
33133
|
-
function readEnvFile2(path, required) {
|
|
33134
|
-
if (!existsSync16(path)) {
|
|
33135
|
-
if (required)
|
|
33136
|
-
throw new Error(`SupaCloud environment file not found: ${path}`);
|
|
33137
|
-
return {};
|
|
33138
|
-
}
|
|
33139
|
-
try {
|
|
33140
|
-
return parseEnvFile2(readFileSync12(path, "utf8"));
|
|
33141
|
-
} catch (error) {
|
|
33142
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
33143
|
-
throw new Error(`Failed to read SupaCloud environment file ${path}: ${message}`);
|
|
33144
|
-
}
|
|
33145
|
-
}
|
|
33146
|
-
function canonicalApiOrigin2(value) {
|
|
33147
|
-
const candidate = value.trim();
|
|
33148
|
-
if (!candidate)
|
|
33149
|
-
return "";
|
|
33150
|
-
try {
|
|
33151
|
-
const url = new URL(candidate);
|
|
33152
|
-
const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
|
|
33153
|
-
const allowedProtocol = url.protocol === "https:" || url.protocol === "http:" && loopback;
|
|
33154
|
-
const exactOrigin = candidate === url.origin || candidate === `${url.origin}/`;
|
|
33155
|
-
return allowedProtocol && !url.username && !url.password && exactOrigin && url.pathname === "/" && !url.search && !url.hash ? url.origin : "";
|
|
33156
|
-
} catch (error) {
|
|
33157
|
-
if (error instanceof TypeError)
|
|
33158
|
-
return "";
|
|
33159
|
-
throw error;
|
|
33160
|
-
}
|
|
33161
|
-
}
|
|
33162
|
-
function hostFromUrl2(value) {
|
|
33163
|
-
return value ? new URL(value).hostname : "";
|
|
33164
|
-
}
|
|
33165
|
-
function inferProjectRefFromSupabaseUrl2(value) {
|
|
33166
|
-
if (!value)
|
|
33167
|
-
return "";
|
|
33168
|
-
return new URL(value).hostname.match(/^([a-z0-9-]+)\.api\./i)?.[1] ?? "";
|
|
33169
|
-
}
|
|
33170
|
-
function sourceCredentialScope2(values, explicitApiUrl, supabaseUrl) {
|
|
33171
|
-
const hasManagementContext = Boolean(explicitApiUrl.trim() || values.SUPACLOUD_API_TOKEN?.trim() || values.SUPACLOUD_HOST?.trim());
|
|
33172
|
-
if (hasManagementContext)
|
|
33173
|
-
return "management";
|
|
33174
|
-
return supabaseUrl || values.SUPABASE_SERVICE_ROLE_KEY?.trim() ? "project_application" : "incomplete";
|
|
33175
|
-
}
|
|
33176
|
-
function sourceProjectCore2(values) {
|
|
33177
|
-
const supabaseUrl = canonicalApiOrigin2(values.SUPABASE_URL || "");
|
|
33178
|
-
const projectRef = (values.SUPACLOUD_PROJECT_REF || values.X_PROJECT_REF || "").trim() || inferProjectRefFromSupabaseUrl2(supabaseUrl);
|
|
33179
|
-
const explicitApiUrl = values.SUPACLOUD_API_URL || values.SUPACLOUD_MANAGEMENT_API_URL || values.MANAGEMENT_API_URL || "";
|
|
33180
|
-
const credentialScope = sourceCredentialScope2(values, explicitApiUrl, supabaseUrl);
|
|
33181
|
-
const managementUrl = explicitApiUrl || (values.SUPACLOUD_HOST ? `http://${values.SUPACLOUD_HOST}:9090` : "");
|
|
33182
|
-
const apiUrl = credentialScope === "management" ? canonicalApiOrigin2(managementUrl) : "";
|
|
33183
|
-
const apiToken = credentialScope === "management" ? values.SUPACLOUD_API_TOKEN || "" : "";
|
|
33184
|
-
return { apiUrl, apiToken, projectRef, supabaseUrl, credentialScope };
|
|
33185
|
-
}
|
|
33186
|
-
function processValues2(env) {
|
|
33187
|
-
return Object.fromEntries(Object.entries(env).filter((entry) => entry[1] !== undefined));
|
|
33188
|
-
}
|
|
33189
|
-
function hasProcessContext2(env) {
|
|
33190
|
-
return CORE_CONTEXT_KEYS2.some((key) => Object.hasOwn(env, key));
|
|
33191
|
-
}
|
|
33192
|
-
function completeProjectContext2(values) {
|
|
33193
|
-
const core = sourceProjectCore2(values);
|
|
33194
|
-
if (core.credentialScope === "project_application") {
|
|
33195
|
-
return Boolean(core.supabaseUrl && values.SUPABASE_SERVICE_ROLE_KEY?.trim() && core.projectRef);
|
|
33196
|
-
}
|
|
33197
|
-
return Boolean(core.apiUrl && core.apiToken && core.projectRef);
|
|
33198
|
-
}
|
|
33199
|
-
function parseInsecureTls2(values) {
|
|
33200
|
-
for (const key of ["SUPACLOUD_TLS_VERIFY", "SUPACLOUD_TLS_INSECURE"]) {
|
|
33201
|
-
const value = values[key]?.trim().toLowerCase();
|
|
33202
|
-
if (value === undefined)
|
|
33203
|
-
continue;
|
|
33204
|
-
if (["1", "true", "yes", "on"].includes(value))
|
|
33205
|
-
return key === "SUPACLOUD_TLS_INSECURE";
|
|
33206
|
-
if (["0", "false", "no", "off"].includes(value))
|
|
33207
|
-
return key === "SUPACLOUD_TLS_VERIFY";
|
|
33208
|
-
throw new Error(`${key} must be a boolean (true or false)`);
|
|
33209
|
-
}
|
|
33210
|
-
return true;
|
|
33211
|
-
}
|
|
33212
|
-
function namedEnvironmentSource2(cwd, selector) {
|
|
33213
|
-
const environment = normalizeEnvironmentName(selector);
|
|
33214
|
-
const path = resolve26(cwd, `.env.supacloud.${selector}`);
|
|
33215
|
-
const values = readEnvFile2(path, true);
|
|
33216
|
-
if (values.SUPACLOUD_ENV && normalizeEnvironmentName(values.SUPACLOUD_ENV) !== environment) {
|
|
33217
|
-
throw new Error(`SUPACLOUD_ENV in ${path} does not match selector ${selector}`);
|
|
33218
|
-
}
|
|
33219
|
-
return { values, kind: "named_env_file", path, environment };
|
|
33220
|
-
}
|
|
33221
|
-
function explicitEnvironmentSource2(cwd, envFile) {
|
|
33222
|
-
const path = resolve26(cwd, envFile);
|
|
33223
|
-
const values = readEnvFile2(path, true);
|
|
33224
|
-
if (!values.SUPACLOUD_ENV)
|
|
33225
|
-
throw new Error(`SUPACLOUD_ENV is required in ${path}`);
|
|
33226
|
-
return {
|
|
33227
|
-
values,
|
|
33228
|
-
kind: "explicit_env_file",
|
|
33229
|
-
path,
|
|
33230
|
-
environment: normalizeEnvironmentName(values.SUPACLOUD_ENV)
|
|
33231
|
-
};
|
|
33232
|
-
}
|
|
33233
|
-
function contextSource2(env, cwd, selection) {
|
|
33234
|
-
if (selection.environmentName && selection.envFile)
|
|
33235
|
-
throw new Error("Environment selectors are mutually exclusive");
|
|
33236
|
-
if (selection.environmentName)
|
|
33237
|
-
return namedEnvironmentSource2(cwd, selection.environmentName);
|
|
33238
|
-
if (selection.envFile)
|
|
33239
|
-
return explicitEnvironmentSource2(cwd, selection.envFile);
|
|
33240
|
-
if (env.SUPACLOUD_ENV) {
|
|
33241
|
-
const environment = normalizeEnvironmentName(env.SUPACLOUD_ENV);
|
|
33242
|
-
const values = processValues2(env);
|
|
33243
|
-
if (completeProjectContext2(values)) {
|
|
33244
|
-
return { values, kind: "process_env", path: null, environment };
|
|
33245
|
-
}
|
|
33246
|
-
return namedEnvironmentSource2(cwd, env.SUPACLOUD_ENV);
|
|
33247
|
-
}
|
|
33248
|
-
if (hasProcessContext2(env)) {
|
|
33249
|
-
return { values: processValues2(env), kind: "process_env", path: null, environment: "" };
|
|
33250
|
-
}
|
|
33251
|
-
const path = resolve26(cwd, ".env");
|
|
33252
|
-
const values = readEnvFile2(path, false);
|
|
33253
|
-
const environment = values.SUPACLOUD_ENV ? normalizeEnvironmentName(values.SUPACLOUD_ENV) : "";
|
|
33254
|
-
return { values, kind: Object.keys(values).length ? "legacy_dotenv" : "none", path: existsSync16(path) ? path : null, environment };
|
|
33255
|
-
}
|
|
33256
|
-
function resolveSupaCloudContext2(env = process.env, cwd = process.cwd(), selection = {}) {
|
|
33257
|
-
const source = contextSource2(env, cwd, selection);
|
|
33258
|
-
const core = sourceProjectCore2(source.values);
|
|
33259
|
-
const host = source.values.SUPACLOUD_HOST || hostFromUrl2(core.apiUrl || core.supabaseUrl);
|
|
33260
|
-
const readOnly = env.SUPACLOUD_READ_ONLY === "true" || source.values.SUPACLOUD_READ_ONLY === "true";
|
|
33261
|
-
const insecureTls = parseInsecureTls2(source.values);
|
|
33262
|
-
return {
|
|
33263
|
-
host,
|
|
33264
|
-
sshUser: env.SUPACLOUD_SSH_USER ?? "root",
|
|
33265
|
-
sshPort: parseInt(env.SUPACLOUD_SSH_PORT ?? "22", 10),
|
|
33266
|
-
sshKey: env.SUPACLOUD_SSH_KEY ?? resolve26(homedir3(), ".ssh", "id_rsa"),
|
|
33267
|
-
sshPass: env.SUPACLOUD_SSH_PASS ?? "",
|
|
33268
|
-
apiUrl: core.apiUrl,
|
|
33269
|
-
apiToken: core.apiToken,
|
|
33270
|
-
projectRef: core.projectRef,
|
|
33271
|
-
readOnly,
|
|
33272
|
-
environment: source.environment,
|
|
33273
|
-
production: source.environment === "prod" || source.environment === "production",
|
|
33274
|
-
inferredSupabaseUrl: core.supabaseUrl,
|
|
33275
|
-
inferredServiceRoleKey: source.values.SUPABASE_SERVICE_ROLE_KEY || "",
|
|
33276
|
-
credentialScope: core.credentialScope,
|
|
33277
|
-
source: source.kind,
|
|
33278
|
-
sourcePath: source.path,
|
|
33279
|
-
environmentVariables: source.values,
|
|
33280
|
-
applicationId: source.values.SUPACLOUD_APPLICATION_ID?.trim() || "",
|
|
33281
|
-
applicationEnvironmentId: source.values.SUPACLOUD_APPLICATION_ENVIRONMENT_ID?.trim() || source.environment,
|
|
33282
|
-
applicationConfigurationPath: source.values.SUPACLOUD_APPLICATION_CONFIGURATION_PATH?.trim() || "",
|
|
33283
|
-
applicationConfigurationId: source.values.SUPACLOUD_APPLICATION_CONFIGURATION_ID?.trim() || "",
|
|
33284
|
-
applicationManifestPath: source.values.SUPACLOUD_APPLICATION_MANIFEST_PATH?.trim() || "",
|
|
33285
|
-
insecureTls
|
|
33286
|
-
};
|
|
33287
|
-
}
|
|
33288
|
-
|
|
33289
|
-
// src/shared/tools/developer-experience-tools.ts
|
|
33290
|
-
var initToolSchema = {
|
|
33318
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
33319
|
+
import { mkdir as mkdir8, readdir as readdir9, readFile as readFile9, writeFile as writeFile8 } from "node:fs/promises";
|
|
33320
|
+
import { dirname as dirname15, join as join21, resolve as resolve28 } from "node:path";
|
|
33321
|
+
var initToolSchema2 = {
|
|
33291
33322
|
root: optional(String2(), "Project directory to initialize (default: current directory)"),
|
|
33292
|
-
|
|
33323
|
+
format: optional(stringEnum2(["text", "json"]), "Output format (default: text)")
|
|
33293
33324
|
};
|
|
33294
|
-
var
|
|
33325
|
+
var environmentToolSchema2 = {
|
|
33295
33326
|
action: withDescription(stringEnum2(["list", "check"]), "Environment profile inspection action"),
|
|
33296
33327
|
root: optional(String2(), "Project directory containing .env.supacloud.* files"),
|
|
33297
|
-
|
|
33328
|
+
format: optional(stringEnum2(["text", "json"]), "Output format (default: text)")
|
|
33298
33329
|
};
|
|
33299
|
-
var
|
|
33330
|
+
var TEST_PROFILE2 = [
|
|
33300
33331
|
"SUPACLOUD_ENV=test",
|
|
33301
33332
|
"SUPACLOUD_API_URL=https://management.test.example.com",
|
|
33302
33333
|
"SUPACLOUD_API_TOKEN=<test-management-api-token>",
|
|
@@ -33305,7 +33336,7 @@ var TEST_PROFILE = [
|
|
|
33305
33336
|
""
|
|
33306
33337
|
].join(`
|
|
33307
33338
|
`);
|
|
33308
|
-
var
|
|
33339
|
+
var PRODUCTION_PROFILE2 = [
|
|
33309
33340
|
"SUPACLOUD_ENV=production",
|
|
33310
33341
|
"SUPACLOUD_API_URL=https://management.example.com",
|
|
33311
33342
|
"SUPACLOUD_API_TOKEN=<production-management-api-token>",
|
|
@@ -33314,41 +33345,41 @@ var PRODUCTION_PROFILE = [
|
|
|
33314
33345
|
""
|
|
33315
33346
|
].join(`
|
|
33316
33347
|
`);
|
|
33317
|
-
function
|
|
33348
|
+
function textResult4(text, isError = false) {
|
|
33318
33349
|
return { isError, content: [{ type: "text", text }] };
|
|
33319
33350
|
}
|
|
33320
|
-
function
|
|
33321
|
-
return
|
|
33351
|
+
function jsonResult2(value, isError = false) {
|
|
33352
|
+
return textResult4(JSON.stringify(value, null, 2), isError);
|
|
33322
33353
|
}
|
|
33323
|
-
function
|
|
33354
|
+
function record7(value) {
|
|
33324
33355
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
33325
33356
|
}
|
|
33326
|
-
function
|
|
33357
|
+
function packageDependencies2(manifest) {
|
|
33327
33358
|
return new Set([
|
|
33328
33359
|
...Object.keys(manifest.dependencies ?? {}),
|
|
33329
33360
|
...Object.keys(manifest.devDependencies ?? {})
|
|
33330
33361
|
]);
|
|
33331
33362
|
}
|
|
33332
|
-
function
|
|
33333
|
-
const dependencies =
|
|
33363
|
+
function outputDirectoryFor2(manifest) {
|
|
33364
|
+
const dependencies = packageDependencies2(manifest);
|
|
33334
33365
|
if (dependencies.has("next"))
|
|
33335
33366
|
return "out";
|
|
33336
33367
|
if (dependencies.has("@sveltejs/kit") || dependencies.has("svelte"))
|
|
33337
33368
|
return "build";
|
|
33338
33369
|
return "dist";
|
|
33339
33370
|
}
|
|
33340
|
-
async function
|
|
33341
|
-
const path =
|
|
33342
|
-
if (!
|
|
33371
|
+
async function readPackageManifest2(root) {
|
|
33372
|
+
const path = join21(root, "package.json");
|
|
33373
|
+
if (!existsSync18(path))
|
|
33343
33374
|
return {};
|
|
33344
33375
|
try {
|
|
33345
|
-
const parsed = JSON.parse(await
|
|
33346
|
-
const value =
|
|
33376
|
+
const parsed = JSON.parse(await readFile9(path, "utf8"));
|
|
33377
|
+
const value = record7(parsed);
|
|
33347
33378
|
if (!value)
|
|
33348
33379
|
throw new Error("package.json must contain an object");
|
|
33349
|
-
const scripts =
|
|
33350
|
-
const dependencies =
|
|
33351
|
-
const devDependencies =
|
|
33380
|
+
const scripts = record7(value.scripts);
|
|
33381
|
+
const dependencies = record7(value.dependencies);
|
|
33382
|
+
const devDependencies = record7(value.devDependencies);
|
|
33352
33383
|
return {
|
|
33353
33384
|
scripts: scripts ?? undefined,
|
|
33354
33385
|
dependencies: dependencies ?? undefined,
|
|
@@ -33358,9 +33389,9 @@ async function readPackageManifest(root) {
|
|
|
33358
33389
|
throw new Error(`Invalid package.json: ${error instanceof Error ? error.message : String(error)}`);
|
|
33359
33390
|
}
|
|
33360
33391
|
}
|
|
33361
|
-
async function
|
|
33392
|
+
async function writeNewFile2(path, content) {
|
|
33362
33393
|
try {
|
|
33363
|
-
await
|
|
33394
|
+
await writeFile8(path, content, { encoding: "utf8", flag: "wx", mode: 384 });
|
|
33364
33395
|
return true;
|
|
33365
33396
|
} catch (error) {
|
|
33366
33397
|
if (error instanceof Error && "code" in error && error.code === "EEXIST")
|
|
@@ -33368,9 +33399,9 @@ async function writeNewFile(path, content) {
|
|
|
33368
33399
|
throw error;
|
|
33369
33400
|
}
|
|
33370
33401
|
}
|
|
33371
|
-
async function
|
|
33372
|
-
const path =
|
|
33373
|
-
const existing =
|
|
33402
|
+
async function ensureGitignore2(root) {
|
|
33403
|
+
const path = join21(root, ".gitignore");
|
|
33404
|
+
const existing = existsSync18(path) ? await readFile9(path, "utf8") : "";
|
|
33374
33405
|
const required = [".env.supacloud.*", "!.env.supacloud.*.example"];
|
|
33375
33406
|
const missing = required.filter((entry) => !existing.split(/\r?\n/).includes(entry));
|
|
33376
33407
|
if (missing.length === 0)
|
|
@@ -33378,21 +33409,21 @@ async function ensureGitignore(root) {
|
|
|
33378
33409
|
const prefix = existing.length > 0 && !existing.endsWith(`
|
|
33379
33410
|
`) ? `${existing}
|
|
33380
33411
|
` : existing;
|
|
33381
|
-
await
|
|
33412
|
+
await writeFile8(path, `${prefix}${missing.join(`
|
|
33382
33413
|
`)}
|
|
33383
33414
|
`, "utf8");
|
|
33384
33415
|
return true;
|
|
33385
33416
|
}
|
|
33386
|
-
async function
|
|
33387
|
-
const root =
|
|
33388
|
-
await
|
|
33389
|
-
const configPath =
|
|
33390
|
-
if (
|
|
33417
|
+
async function initializeProject2(request, options = {}) {
|
|
33418
|
+
const root = resolve28(request.root || options.cwd || process.cwd());
|
|
33419
|
+
await mkdir8(root, { recursive: true });
|
|
33420
|
+
const configPath = join21(root, "supacloud.json");
|
|
33421
|
+
if (existsSync18(configPath)) {
|
|
33391
33422
|
throw new Error(`supacloud.json already exists in ${root}; init never overwrites project configuration`);
|
|
33392
33423
|
}
|
|
33393
|
-
const manifest = await
|
|
33424
|
+
const manifest = await readPackageManifest2(root);
|
|
33394
33425
|
const hasBuildScript = typeof manifest.scripts?.build === "string" && Boolean(manifest.scripts.build.trim());
|
|
33395
|
-
const outputDirectory =
|
|
33426
|
+
const outputDirectory = outputDirectoryFor2(manifest);
|
|
33396
33427
|
const config = {
|
|
33397
33428
|
frontend: {
|
|
33398
33429
|
root: ".",
|
|
@@ -33401,7 +33432,7 @@ async function initializeProject(request, options) {
|
|
|
33401
33432
|
};
|
|
33402
33433
|
const created = [];
|
|
33403
33434
|
try {
|
|
33404
|
-
await
|
|
33435
|
+
await writeFile8(configPath, `${JSON.stringify(config, null, 2)}
|
|
33405
33436
|
`, {
|
|
33406
33437
|
encoding: "utf8",
|
|
33407
33438
|
flag: "wx",
|
|
@@ -33414,13 +33445,13 @@ async function initializeProject(request, options) {
|
|
|
33414
33445
|
throw error;
|
|
33415
33446
|
}
|
|
33416
33447
|
created.push("supacloud.json");
|
|
33417
|
-
if (await
|
|
33448
|
+
if (await writeNewFile2(join21(root, ".env.supacloud.test.example"), TEST_PROFILE2)) {
|
|
33418
33449
|
created.push(".env.supacloud.test.example");
|
|
33419
33450
|
}
|
|
33420
|
-
if (await
|
|
33451
|
+
if (await writeNewFile2(join21(root, ".env.supacloud.production.example"), PRODUCTION_PROFILE2)) {
|
|
33421
33452
|
created.push(".env.supacloud.production.example");
|
|
33422
33453
|
}
|
|
33423
|
-
if (await
|
|
33454
|
+
if (await ensureGitignore2(root))
|
|
33424
33455
|
created.push(".gitignore");
|
|
33425
33456
|
const result = {
|
|
33426
33457
|
schema: "supacloud.developer-experience.init.v1",
|
|
@@ -33428,7 +33459,7 @@ async function initializeProject(request, options) {
|
|
|
33428
33459
|
root,
|
|
33429
33460
|
created,
|
|
33430
33461
|
detected: {
|
|
33431
|
-
package_json:
|
|
33462
|
+
package_json: existsSync18(join21(root, "package.json")),
|
|
33432
33463
|
build_script: hasBuildScript,
|
|
33433
33464
|
output_directory: outputDirectory
|
|
33434
33465
|
},
|
|
@@ -33439,9 +33470,9 @@ async function initializeProject(request, options) {
|
|
|
33439
33470
|
"supacloud --env test deploy"
|
|
33440
33471
|
]
|
|
33441
33472
|
};
|
|
33442
|
-
if (request.
|
|
33443
|
-
return
|
|
33444
|
-
return
|
|
33473
|
+
if (request.format === "json")
|
|
33474
|
+
return jsonResult2(result);
|
|
33475
|
+
return textResult4([
|
|
33445
33476
|
`Initialized SupaCloud deployment in ${root}.`,
|
|
33446
33477
|
`Created: ${created.join(", ")}.`,
|
|
33447
33478
|
hasBuildScript ? `Detected package.json build script; deploy output defaults to ${outputDirectory}.` : "No package.json build script detected; add one before deploying.",
|
|
@@ -33455,14 +33486,14 @@ function profileNameFromFile(name) {
|
|
|
33455
33486
|
return match?.[1] ?? null;
|
|
33456
33487
|
}
|
|
33457
33488
|
async function environmentProfiles(root) {
|
|
33458
|
-
const entries = await
|
|
33489
|
+
const entries = await readdir9(root, { withFileTypes: true }).catch((error) => {
|
|
33459
33490
|
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
33460
33491
|
return [];
|
|
33461
33492
|
throw error;
|
|
33462
33493
|
});
|
|
33463
33494
|
return entries.filter((entry) => entry.isFile()).map((entry) => {
|
|
33464
33495
|
const name = profileNameFromFile(entry.name);
|
|
33465
|
-
return name ? { name, path:
|
|
33496
|
+
return name ? { name, path: join21(root, entry.name), exists: true } : null;
|
|
33466
33497
|
}).filter((profile) => profile !== null).sort((left, right) => left.name.localeCompare(right.name));
|
|
33467
33498
|
}
|
|
33468
33499
|
function statusMissing(context) {
|
|
@@ -33497,18 +33528,18 @@ function deployMissing(context, application, hasFrontend = false) {
|
|
|
33497
33528
|
}
|
|
33498
33529
|
async function inspectProfile(profile) {
|
|
33499
33530
|
try {
|
|
33500
|
-
const context = resolveSupaCloudContext2({},
|
|
33531
|
+
const context = resolveSupaCloudContext2({}, dirname15(profile.path), {
|
|
33501
33532
|
environmentName: profile.name
|
|
33502
33533
|
});
|
|
33503
33534
|
const status = statusMissing(context);
|
|
33504
|
-
const configText = await
|
|
33535
|
+
const configText = await readFile9(join21(dirname15(profile.path), "supacloud.json"), "utf8").catch((error) => {
|
|
33505
33536
|
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
33506
33537
|
return "{}";
|
|
33507
33538
|
throw error;
|
|
33508
33539
|
});
|
|
33509
|
-
const config =
|
|
33510
|
-
const hasFrontend =
|
|
33511
|
-
const deploy = deployMissing(context,
|
|
33540
|
+
const config = record7(JSON.parse(configText));
|
|
33541
|
+
const hasFrontend = record7(config?.frontend) !== null || Object.values(record7(config?.targets) ?? {}).some((target) => record7(target)?.type === "frontend");
|
|
33542
|
+
const deploy = deployMissing(context, record7(config?.application), hasFrontend);
|
|
33512
33543
|
return {
|
|
33513
33544
|
name: profile.name,
|
|
33514
33545
|
path: profile.path,
|
|
@@ -33545,7 +33576,7 @@ async function inspectProfile(profile) {
|
|
|
33545
33576
|
}
|
|
33546
33577
|
}
|
|
33547
33578
|
async function inspectEnvironment(request, options) {
|
|
33548
|
-
const root =
|
|
33579
|
+
const root = resolve28(request.root || options.cwd || process.cwd());
|
|
33549
33580
|
const profiles = await environmentProfiles(root);
|
|
33550
33581
|
if (request.action === "list") {
|
|
33551
33582
|
const active = options.activeContext?.environment || null;
|
|
@@ -33560,13 +33591,13 @@ async function inspectEnvironment(request, options) {
|
|
|
33560
33591
|
active: profile.name.toLowerCase() === active?.toLowerCase()
|
|
33561
33592
|
}))
|
|
33562
33593
|
};
|
|
33563
|
-
if (request.
|
|
33564
|
-
return
|
|
33594
|
+
if (request.format === "json")
|
|
33595
|
+
return jsonResult2(result);
|
|
33565
33596
|
if (profiles.length === 0) {
|
|
33566
|
-
return
|
|
33567
|
-
Run \`supacloud init\` to create safe examples.`);
|
|
33597
|
+
return textResult4(`No .env.supacloud.<name> profiles found in ${root}.
|
|
33598
|
+
Run \`supacloud deploy init\` to create safe examples.`);
|
|
33568
33599
|
}
|
|
33569
|
-
return
|
|
33600
|
+
return textResult4([
|
|
33570
33601
|
`Environment profiles in ${root}:`,
|
|
33571
33602
|
...result.environments.map((profile) => `${profile.active ? "*" : " "} ${profile.name} ${profile.path}`)
|
|
33572
33603
|
].join(`
|
|
@@ -33580,11 +33611,11 @@ Run \`supacloud init\` to create safe examples.`);
|
|
|
33580
33611
|
active: options.activeContext?.environment || null,
|
|
33581
33612
|
environments: reports
|
|
33582
33613
|
};
|
|
33583
|
-
if (request.
|
|
33584
|
-
return
|
|
33614
|
+
if (request.format === "json")
|
|
33615
|
+
return jsonResult2(result, !result.ok);
|
|
33585
33616
|
if (reports.length === 0) {
|
|
33586
|
-
return
|
|
33587
|
-
Run \`supacloud init\` and copy an example profile.`, true);
|
|
33617
|
+
return textResult4(`No environment profiles found in ${root}.
|
|
33618
|
+
Run \`supacloud deploy init\` and copy an example profile.`, true);
|
|
33588
33619
|
}
|
|
33589
33620
|
const lines = reports.map((report) => {
|
|
33590
33621
|
if (report.error)
|
|
@@ -33594,12 +33625,11 @@ Run \`supacloud init\` and copy an example profile.`, true);
|
|
|
33594
33625
|
const ref = report.projectRef ? `project=${report.projectRef}` : "project=unknown";
|
|
33595
33626
|
return `${status} ${report.name} ${deploy} ${ref}`;
|
|
33596
33627
|
});
|
|
33597
|
-
return
|
|
33628
|
+
return textResult4(lines.join(`
|
|
33598
33629
|
`), !result.ok);
|
|
33599
33630
|
}
|
|
33600
33631
|
function registerDeveloperExperienceTools(server, options = {}) {
|
|
33601
|
-
registerTool(server, "
|
|
33602
|
-
registerTool(server, "env", "List or locally validate named SupaCloud environment profiles without contacting the platform or printing secrets.", environmentToolSchema, (request) => inspectEnvironment(request, options));
|
|
33632
|
+
registerTool(server, "env", "List or locally validate named SupaCloud environment profiles without contacting the platform or printing secrets.", environmentToolSchema2, (request) => inspectEnvironment(request, options));
|
|
33603
33633
|
}
|
|
33604
33634
|
|
|
33605
33635
|
// src/shared/tools/project-deployment.ts
|
|
@@ -33727,7 +33757,7 @@ async function runProjectDeployment(http, args) {
|
|
|
33727
33757
|
// package.json
|
|
33728
33758
|
var package_default = {
|
|
33729
33759
|
name: "@supacloud/cli",
|
|
33730
|
-
version: "0.
|
|
33760
|
+
version: "0.115.0",
|
|
33731
33761
|
description: "Project-scoped CLI for SupaCloud users",
|
|
33732
33762
|
type: "module",
|
|
33733
33763
|
main: "./dist/index.js",
|
|
@@ -33779,9 +33809,9 @@ var package_default = {
|
|
|
33779
33809
|
directory: "packages/cli"
|
|
33780
33810
|
},
|
|
33781
33811
|
dependencies: {
|
|
33782
|
-
"@supacloud/compiler": "^0.
|
|
33812
|
+
"@supacloud/compiler": "^0.54.0",
|
|
33783
33813
|
"@supacloud/delivery": "0.24.0",
|
|
33784
|
-
"@supacloud/db": "0.20.
|
|
33814
|
+
"@supacloud/db": "0.20.4",
|
|
33785
33815
|
typebox: "1.4.3"
|
|
33786
33816
|
},
|
|
33787
33817
|
overrides: {
|
|
@@ -33791,10 +33821,10 @@ var package_default = {
|
|
|
33791
33821
|
},
|
|
33792
33822
|
devDependencies: {
|
|
33793
33823
|
"@supabase/supabase-js": "2.118.0",
|
|
33794
|
-
"@supacloud/elysia": "0.
|
|
33824
|
+
"@supacloud/elysia": "0.38.0",
|
|
33795
33825
|
"@types/bun": "^1.4.4",
|
|
33796
33826
|
effect: "4.0.5",
|
|
33797
|
-
elysia: "2.0.0-rc.
|
|
33827
|
+
elysia: "2.0.0-rc.1",
|
|
33798
33828
|
typescript: "^7.0.2"
|
|
33799
33829
|
}
|
|
33800
33830
|
};
|
|
@@ -33963,7 +33993,7 @@ USAGE
|
|
|
33963
33993
|
|
|
33964
33994
|
${preferredCommand} [global flags] deploy [--flags]
|
|
33965
33995
|
${preferredCommand} [global flags] <module> <action> [--flags]
|
|
33966
|
-
${preferredCommand} init [--root <directory>]
|
|
33996
|
+
${preferredCommand} deploy init [--root <directory>]
|
|
33967
33997
|
${preferredCommand} env <list|check> [--root <directory>]
|
|
33968
33998
|
${preferredCommand} [global flags] status
|
|
33969
33999
|
${preferredCommand} --help
|
|
@@ -33997,9 +34027,9 @@ DEFAULT CONTEXT
|
|
|
33997
34027
|
|
|
33998
34028
|
EXAMPLES
|
|
33999
34029
|
|
|
34000
|
-
${preferredCommand} init
|
|
34030
|
+
${preferredCommand} deploy init
|
|
34001
34031
|
${preferredCommand} env check
|
|
34002
|
-
${preferredCommand} env check --json
|
|
34032
|
+
${preferredCommand} env check --format json
|
|
34003
34033
|
${preferredCommand} status
|
|
34004
34034
|
${preferredCommand} deploy
|
|
34005
34035
|
${preferredCommand} --env test deploy
|
|
@@ -34034,7 +34064,7 @@ EXAMPLES
|
|
|
34034
34064
|
${preferredCommand} supabase db_dump --db_url "postgresql://..." --file backups/schema.sql
|
|
34035
34065
|
${preferredCommand} lite migrate --project_dir .
|
|
34036
34066
|
${preferredCommand} lite start --project_dir . --port 54321
|
|
34037
|
-
${preferredCommand} lite doctor --project_dir . --json
|
|
34067
|
+
${preferredCommand} lite doctor --project_dir . --format json
|
|
34038
34068
|
${preferredCommand} branch create --name feature-auth --data_mode schema_only
|
|
34039
34069
|
${preferredCommand} branch promotion_plan --branch_ref preview123
|
|
34040
34070
|
${preferredCommand} branch promote --branch_ref preview123 --plan_checksum <sha256>
|
|
@@ -34042,8 +34072,6 @@ EXAMPLES
|
|
|
34042
34072
|
${preferredCommand} ai install_skill --dry_run
|
|
34043
34073
|
${preferredCommand} mcp config
|
|
34044
34074
|
${preferredCommand} app init --name my-service
|
|
34045
|
-
${preferredCommand} app init --name my-api --template http
|
|
34046
|
-
${preferredCommand} app init --name my-worker --template edge
|
|
34047
34075
|
${preferredCommand} app generate --kind module --name billing
|
|
34048
34076
|
${preferredCommand} app generate --kind command --module billing --name issue-invoice
|
|
34049
34077
|
${preferredCommand} app generate --kind job --module billing --name sync-orders
|
|
@@ -34084,13 +34112,6 @@ EXAMPLES
|
|
|
34084
34112
|
${preferredCommand} app context --root . --target CaseModule --events execution.ndjson --request-id request-123 --format json
|
|
34085
34113
|
${preferredCommand} app doctor --root .
|
|
34086
34114
|
${preferredCommand} app fix --root . --fix fix.json --write
|
|
34087
|
-
${preferredCommand} generate --kind module --name billing
|
|
34088
|
-
${preferredCommand} generate --kind job --module billing --name sync-orders
|
|
34089
|
-
${preferredCommand} generate --kind contract --module billing --name issue-invoice
|
|
34090
|
-
${preferredCommand} check --root . --strict
|
|
34091
|
-
${preferredCommand} context --format json
|
|
34092
|
-
${preferredCommand} context --target CaseModule --format json
|
|
34093
|
-
${preferredCommand} doctor
|
|
34094
34115
|
${preferredCommand} db context --root . --module_file db/modules.ts --module cases
|
|
34095
34116
|
${preferredCommand} db context --root . --module_file db/modules.ts --target public.case_create
|
|
34096
34117
|
${preferredCommand} db lint --root . --module_file db/modules.ts
|
|
@@ -34102,6 +34123,7 @@ EXAMPLES
|
|
|
34102
34123
|
${preferredCommand} db plan --dir supabase/migrations
|
|
34103
34124
|
${preferredCommand} db apply --dir supabase/migrations --approved_digest <reviewed-digest>
|
|
34104
34125
|
${preferredCommand} db role_check --schema public
|
|
34126
|
+
${preferredCommand} db role_check --remote --schema public
|
|
34105
34127
|
${preferredCommand} db role_sql --application_role app_user --migration_role app_migrator --database app_db
|
|
34106
34128
|
${preferredCommand} edge_functions get_config --ref abc123 --slug hello
|
|
34107
34129
|
${preferredCommand} edge_functions deploy --ref abc123 --slug hello --path ./supabase/functions/hello --expected-active-version absent --expected-activation-id legacy
|
|
@@ -34164,7 +34186,6 @@ function createCliTools(context, confirmProduction) {
|
|
|
34164
34186
|
activeContext: context
|
|
34165
34187
|
})));
|
|
34166
34188
|
Object.assign(tools, captureTools((server) => registerAppTools(server, { onDevProgress: writeDevProgress })));
|
|
34167
|
-
Object.assign(tools, captureTools((server) => registerAppAliases(server)));
|
|
34168
34189
|
Object.assign(tools, captureTools((server) => registerDbGovernanceTools(server, {
|
|
34169
34190
|
projectRef: context.projectRef || undefined,
|
|
34170
34191
|
apiUrl: context.apiUrl || undefined,
|
|
@@ -34199,7 +34220,7 @@ function createCliTools(context, confirmProduction) {
|
|
|
34199
34220
|
]
|
|
34200
34221
|
})
|
|
34201
34222
|
};
|
|
34202
|
-
for (const name of ["database", "auth", "oauth_clients", "storage", "edge_functions", "secrets", "frontend", "
|
|
34223
|
+
for (const name of ["database", "auth", "oauth_clients", "storage", "edge_functions", "secrets", "frontend", "queue", "task_events", "scheduled_functions", "mutations", "diagnostics", "gateway", "branch", "release"]) {
|
|
34203
34224
|
tools[name] = {
|
|
34204
34225
|
schema: { action: genericActionSchema },
|
|
34205
34226
|
callback: async () => ({
|
|
@@ -34226,7 +34247,6 @@ function createCliTools(context, confirmProduction) {
|
|
|
34226
34247
|
tools.branch = { schema: branchHelpTool.schema, callback: branchContextCallback };
|
|
34227
34248
|
}
|
|
34228
34249
|
const frontendContextCallback = tools.frontend.callback;
|
|
34229
|
-
tools.applications = { schema: APPLICATION_TOOL_SCHEMA, callback: tools.applications.callback };
|
|
34230
34250
|
const frontendHelpTool = captureTools((server) => registerFrontendTools(server, {})).frontend;
|
|
34231
34251
|
if (frontendHelpTool) {
|
|
34232
34252
|
tools.frontend = { schema: frontendHelpTool.schema, callback: frontendContextCallback };
|
|
@@ -34236,13 +34256,22 @@ function createCliTools(context, confirmProduction) {
|
|
|
34236
34256
|
registerContextAwareHelp();
|
|
34237
34257
|
tools.deploy = {
|
|
34238
34258
|
schema: deployToolSchema,
|
|
34239
|
-
callback: async () =>
|
|
34240
|
-
|
|
34241
|
-
|
|
34242
|
-
|
|
34243
|
-
|
|
34244
|
-
|
|
34245
|
-
|
|
34259
|
+
callback: async (args) => {
|
|
34260
|
+
const input = args && typeof args === "object" ? args : {};
|
|
34261
|
+
if (input.action === "init") {
|
|
34262
|
+
return initializeProject2({
|
|
34263
|
+
...typeof input.root === "string" ? { root: input.root } : {},
|
|
34264
|
+
...input.format === "json" ? { format: "json" } : {}
|
|
34265
|
+
}, { cwd: process.cwd() });
|
|
34266
|
+
}
|
|
34267
|
+
return {
|
|
34268
|
+
isError: true,
|
|
34269
|
+
content: [{
|
|
34270
|
+
type: "text",
|
|
34271
|
+
text: `\u26A0\uFE0F Deploy requires Management API context. Run \`${preferredCommand} status\` to inspect current detection.`
|
|
34272
|
+
}]
|
|
34273
|
+
};
|
|
34274
|
+
}
|
|
34246
34275
|
};
|
|
34247
34276
|
Object.assign(tools, captureTools((server) => registerDatabaseTools(server, undefined, {
|
|
34248
34277
|
localOnly: true
|
|
@@ -34341,9 +34370,9 @@ function createCliTools(context, confirmProduction) {
|
|
|
34341
34370
|
applicationOrigin: context.inferredSupabaseUrl || undefined
|
|
34342
34371
|
})));
|
|
34343
34372
|
assign(captureTools((server) => registerFrontendTools(server, http)));
|
|
34344
|
-
|
|
34373
|
+
const applicationTools = captureTools((server) => registerApplicationTools(server, http));
|
|
34345
34374
|
Object.assign(tools, captureTools((server) => registerAppTools(server, {
|
|
34346
|
-
getApplications: () =>
|
|
34375
|
+
getApplications: () => applicationTools.applications?.callback,
|
|
34347
34376
|
onDevProgress: writeDevProgress,
|
|
34348
34377
|
projectRef: context.projectRef || undefined,
|
|
34349
34378
|
projectDeployment: (args) => runProjectDeployment(http, args)
|
|
@@ -34398,7 +34427,7 @@ async function main() {
|
|
|
34398
34427
|
}
|
|
34399
34428
|
const cliTools = createCliTools(context, globalOptions.confirmProduction);
|
|
34400
34429
|
if (args.length === 1 && !["ai", "supabase", "lite", "app", "db", "env"].includes(args[0]) && cliTools[args[0]]) {
|
|
34401
|
-
if ("action" in cliTools[args[0]].schema) {
|
|
34430
|
+
if ("action" in cliTools[args[0]].schema && args[0] !== "deploy") {
|
|
34402
34431
|
if (context.credentialScope !== "management" || !context.apiUrl || !context.apiToken) {
|
|
34403
34432
|
console.error(`This command requires Management API context. Run \`${preferredCommand} status\` to inspect current detection.`);
|
|
34404
34433
|
process.exitCode = 1;
|