@supacloud/cli 0.113.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 +967 -842
- 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/database-tools.d.ts +11 -0
- 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", "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
|
},
|
|
@@ -12909,6 +12906,7 @@ function registerDatabaseTools(server, http, config = {}) {
|
|
|
12909
12906
|
"query",
|
|
12910
12907
|
"list_tables",
|
|
12911
12908
|
"describe_columns",
|
|
12909
|
+
"export_svadmin_schema",
|
|
12912
12910
|
"list_indexes",
|
|
12913
12911
|
"list_constraints",
|
|
12914
12912
|
"list_extensions",
|
|
@@ -12929,7 +12927,9 @@ function registerDatabaseTools(server, http, config = {}) {
|
|
|
12929
12927
|
"db_lint",
|
|
12930
12928
|
"rpc_catalog",
|
|
12931
12929
|
"list_rpcs",
|
|
12932
|
-
"migration_import"
|
|
12930
|
+
"migration_import",
|
|
12931
|
+
"governance_check",
|
|
12932
|
+
"role_check"
|
|
12933
12933
|
];
|
|
12934
12934
|
const writeActions = ["execute", "apply_migration", "push_migrations", "baseline_migrations", "create_table_rls", "enable_extension", "disable_extension"];
|
|
12935
12935
|
const remoteActions = [...readActions, ...localActions];
|
|
@@ -12947,10 +12947,11 @@ Actions: ${allActions.join(", ")}${localOnly ? " (local-only mode)" : readOnly ?
|
|
|
12947
12947
|
strict: optional(Boolean2(), "[push_migrations/lint_migrations/lint/database_lint] Exit with error if high-risk issues or migrations are detected"),
|
|
12948
12948
|
fail_on_high: optional(Boolean2(), "[push_migrations/lint_migrations/lint/database_lint] Alias for --strict"),
|
|
12949
12949
|
fail_on_medium: optional(Boolean2(), "[push_migrations/lint_migrations/lint] Exit with error if medium-risk or high-risk migrations are detected"),
|
|
12950
|
-
|
|
12950
|
+
format: optional(stringEnum2(["text", "json"]), "[lint_migrations/lint/database_lint/rpc_catalog] Output format (default: text)"),
|
|
12951
12951
|
schema: optional(String2(), "[describe_columns/list_indexes/list_constraints/rls_status/rls_policies/create_table_rls/database_lint] Schema name (default: public)"),
|
|
12952
12952
|
table: optional(String2(), "[describe_columns/indexes/constraints/rls_*] Table name"),
|
|
12953
|
-
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"),
|
|
12954
12955
|
user_id: optional(String2(), "[get_auth_user] User UUID"),
|
|
12955
12956
|
limit: optional(Number2(), "[list_auth_users] Max users (default: 20)"),
|
|
12956
12957
|
name: optional(String2(), "[apply_migration] Migration name"),
|
|
@@ -13105,6 +13106,38 @@ Actions: ${allActions.join(", ")}${localOnly ? " (local-only mode)" : readOnly ?
|
|
|
13105
13106
|
text = r.ok ? formatColumnsList(r.data, schema, args.table) : `❌ Failed (${r.status})`;
|
|
13106
13107
|
break;
|
|
13107
13108
|
}
|
|
13109
|
+
case "export_svadmin_schema": {
|
|
13110
|
+
if (!args.table)
|
|
13111
|
+
throw new Error("'table' required");
|
|
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}'`;
|
|
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;`;
|
|
13118
|
+
const colRes = await execSql(colSql);
|
|
13119
|
+
if (!colRes.ok)
|
|
13120
|
+
throw new Error(`Failed to inspect columns (${colRes.status})`);
|
|
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;`;
|
|
13122
|
+
const conRes = await execSql(conSql);
|
|
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") {
|
|
13127
|
+
return {
|
|
13128
|
+
content: [{
|
|
13129
|
+
type: "text",
|
|
13130
|
+
text: JSON.stringify({
|
|
13131
|
+
resource: args.table,
|
|
13132
|
+
sourceType: "databaseColumns",
|
|
13133
|
+
columns: cols
|
|
13134
|
+
}, null, 2)
|
|
13135
|
+
}]
|
|
13136
|
+
};
|
|
13137
|
+
}
|
|
13138
|
+
text = formatSvadminSchemaExport(args.table, cols);
|
|
13139
|
+
break;
|
|
13140
|
+
}
|
|
13108
13141
|
case "list_indexes": {
|
|
13109
13142
|
if (!args.table)
|
|
13110
13143
|
throw new Error("'table' required");
|
|
@@ -13266,7 +13299,7 @@ Actions: ${allActions.join(", ")}${localOnly ? " (local-only mode)" : readOnly ?
|
|
|
13266
13299
|
migrationFiles = sortMigrationFiles(files.map((file) => readMigrationFile(dir, file)));
|
|
13267
13300
|
}
|
|
13268
13301
|
const analysis = analyzeMigrationFiles(migrationFiles);
|
|
13269
|
-
if (args.json) {
|
|
13302
|
+
if (args.format === "json") {
|
|
13270
13303
|
text = JSON.stringify(analysis, null, 2);
|
|
13271
13304
|
} else {
|
|
13272
13305
|
text = formatMigrationRiskReport(analysis);
|
|
@@ -13501,7 +13534,7 @@ ${riskReport}`
|
|
|
13501
13534
|
if (!r.ok) {
|
|
13502
13535
|
return { content: [{ type: "text", text: `❌ Database lint request failed (${r.status})` }], isError: true };
|
|
13503
13536
|
}
|
|
13504
|
-
if (args.json) {
|
|
13537
|
+
if (args.format === "json") {
|
|
13505
13538
|
text = JSON.stringify(r.data, null, 2);
|
|
13506
13539
|
} else {
|
|
13507
13540
|
text = formatDatabaseLintReport(r.data);
|
|
@@ -13516,6 +13549,45 @@ ${riskReport}`
|
|
|
13516
13549
|
}
|
|
13517
13550
|
break;
|
|
13518
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
|
+
}
|
|
13519
13591
|
case "rpc_catalog":
|
|
13520
13592
|
case "list_rpcs": {
|
|
13521
13593
|
const schemasList = args.schemas && args.schemas.length > 0 ? args.schemas : args.schema ? [args.schema] : ["public", "api"];
|
|
@@ -13524,7 +13596,7 @@ ${riskReport}`
|
|
|
13524
13596
|
if (!r.ok) {
|
|
13525
13597
|
return { content: [{ type: "text", text: `❌ RPC catalog request failed (${r.status})` }], isError: true };
|
|
13526
13598
|
}
|
|
13527
|
-
if (args.json) {
|
|
13599
|
+
if (args.format === "json") {
|
|
13528
13600
|
text = JSON.stringify(r.data, null, 2);
|
|
13529
13601
|
} else {
|
|
13530
13602
|
text = formatRpcCatalog(r.data);
|
|
@@ -13538,10 +13610,13 @@ ${riskReport}`
|
|
|
13538
13610
|
});
|
|
13539
13611
|
}
|
|
13540
13612
|
function quoteIdentifier(value, label) {
|
|
13613
|
+
return `"${validatedIdentifier(value, label)}"`;
|
|
13614
|
+
}
|
|
13615
|
+
function validatedIdentifier(value, label) {
|
|
13541
13616
|
if (typeof value !== "string" || !/^[A-Za-z_][A-Za-z0-9_]{0,62}$/.test(value)) {
|
|
13542
13617
|
throw new Error(`Invalid ${label} identifier`);
|
|
13543
13618
|
}
|
|
13544
|
-
return
|
|
13619
|
+
return value;
|
|
13545
13620
|
}
|
|
13546
13621
|
function validateColumnDefinitions(value) {
|
|
13547
13622
|
if (typeof value !== "string")
|
|
@@ -13597,6 +13672,34 @@ function formatDatabaseLintReport(data) {
|
|
|
13597
13672
|
return lines.join(`
|
|
13598
13673
|
`).trimEnd();
|
|
13599
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
|
+
}
|
|
13600
13703
|
function formatRpcCatalog(data) {
|
|
13601
13704
|
if (!data || typeof data !== "object")
|
|
13602
13705
|
return JSON.stringify(data, null, 2);
|
|
@@ -13713,6 +13816,67 @@ function formatConstraintList(data) {
|
|
|
13713
13816
|
`).join(`
|
|
13714
13817
|
`);
|
|
13715
13818
|
}
|
|
13819
|
+
function buildSvadminColumns(colData, conData) {
|
|
13820
|
+
const colRows = responseRows(colData, _Object_({
|
|
13821
|
+
column_name: String2(),
|
|
13822
|
+
data_type: String2(),
|
|
13823
|
+
is_nullable: String2(),
|
|
13824
|
+
character_maximum_length: Optional(Union([Number2(), Null()])),
|
|
13825
|
+
column_default: Optional(Union([String2(), Null()]))
|
|
13826
|
+
}));
|
|
13827
|
+
const conRows = responseRows(conData, _Object_({
|
|
13828
|
+
name: String2(),
|
|
13829
|
+
type: String2(),
|
|
13830
|
+
definition: String2()
|
|
13831
|
+
}));
|
|
13832
|
+
const pkConstraint = conRows.find((c) => c.type === "p");
|
|
13833
|
+
const pkCols = new Set;
|
|
13834
|
+
if (pkConstraint) {
|
|
13835
|
+
const match = pkConstraint.definition.match(/\(([^)]+)\)/);
|
|
13836
|
+
if (match?.[1]) {
|
|
13837
|
+
match[1].split(",").map((s) => s.trim().replace(/"/g, "")).forEach((c) => pkCols.add(c));
|
|
13838
|
+
}
|
|
13839
|
+
}
|
|
13840
|
+
const fkMap = new Map;
|
|
13841
|
+
for (const c of conRows) {
|
|
13842
|
+
if (c.type === "f") {
|
|
13843
|
+
const match = c.definition.match(/FOREIGN KEY\s*\(([^)]+)\)\s*REFERENCES\s*([^(]+)/i);
|
|
13844
|
+
if (match?.[1] && match?.[2]) {
|
|
13845
|
+
const colName = match[1].trim().replace(/"/g, "");
|
|
13846
|
+
const refTable = match[2].trim().split(".").pop()?.replace(/"/g, "") ?? "";
|
|
13847
|
+
fkMap.set(colName, refTable);
|
|
13848
|
+
}
|
|
13849
|
+
}
|
|
13850
|
+
}
|
|
13851
|
+
return colRows.map((c) => {
|
|
13852
|
+
const isPrimary = pkCols.has(c.column_name) || c.column_name === "id";
|
|
13853
|
+
const ref = fkMap.get(c.column_name);
|
|
13854
|
+
return {
|
|
13855
|
+
name: c.column_name,
|
|
13856
|
+
type: c.data_type,
|
|
13857
|
+
nullable: c.is_nullable === "YES",
|
|
13858
|
+
isPrimary,
|
|
13859
|
+
...c.column_default ? { defaultValue: c.column_default } : {},
|
|
13860
|
+
isForeignKey: Boolean(ref || c.column_name.endsWith("_id")),
|
|
13861
|
+
...ref ? { referencesResource: ref } : {}
|
|
13862
|
+
};
|
|
13863
|
+
});
|
|
13864
|
+
}
|
|
13865
|
+
function formatSvadminSchemaExport(table, columns) {
|
|
13866
|
+
const payload = {
|
|
13867
|
+
resource: table,
|
|
13868
|
+
sourceType: "databaseColumns",
|
|
13869
|
+
columns
|
|
13870
|
+
};
|
|
13871
|
+
return `SVAdmin schema for '${table}'
|
|
13872
|
+
|
|
13873
|
+
` + `Pass this object to svadmin_generateResource. The CLI does not generate the resource.
|
|
13874
|
+
` + `${JSON.stringify(payload, null, 2)}
|
|
13875
|
+
|
|
13876
|
+
` + `Column metadata (${columns.length}):
|
|
13877
|
+
` + columns.map((c) => ` - ${c.name}: ${c.type}${c.isPrimary ? " [PK]" : ""}${c.isForeignKey ? ` [FK -> ${c.referencesResource ?? "inferred"}]` : ""}${c.nullable ? " (nullable)" : " (required)"}`).join(`
|
|
13878
|
+
`);
|
|
13879
|
+
}
|
|
13716
13880
|
function formatExtensionList(data) {
|
|
13717
13881
|
const rows = responseRows(data, _Object_({ name: String2(), version: String2(), schema: String2() }));
|
|
13718
13882
|
if (!rows.length)
|
|
@@ -15533,7 +15697,7 @@ function scaffoldFiles(framework, slug) {
|
|
|
15533
15697
|
"package.json": JSON.stringify({
|
|
15534
15698
|
private: true,
|
|
15535
15699
|
type: "module",
|
|
15536
|
-
dependencies: { elysia: "2.0.0-rc.
|
|
15700
|
+
dependencies: { elysia: "2.0.0-rc.1" }
|
|
15537
15701
|
}, null, 2) + `
|
|
15538
15702
|
`,
|
|
15539
15703
|
"index.ts": `import { Elysia } from "elysia";
|
|
@@ -20705,13 +20869,13 @@ function registerBranchTools(server, http, options = {}) {
|
|
|
20705
20869
|
data_mode: optional(stringEnum2(["schema_only", "full_clone"]), "[create] Preview data mode (default: schema_only)"),
|
|
20706
20870
|
plan_checksum: optional(String2(), "[promote] Reviewed plan checksum from promotion_plan"),
|
|
20707
20871
|
confirm_destructive: optional(Boolean2(), "[promote] Confirm reviewed destructive migrations"),
|
|
20708
|
-
|
|
20872
|
+
format: optional(stringEnum2(["text", "json"]), "[promotion_plan/promote] Output format (default: text)")
|
|
20709
20873
|
}, async (args) => {
|
|
20710
20874
|
const action = requireString(args.action, "action");
|
|
20711
20875
|
const ref = resolveProjectRef(args.ref, options.projectRef);
|
|
20712
20876
|
const writeAction = action === "create" || action === "delete" || action === "promote";
|
|
20713
20877
|
if (writeAction && options.readOnly) {
|
|
20714
|
-
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, {
|
|
20715
20879
|
project_ref: ref,
|
|
20716
20880
|
branch_ref: args.branch_ref,
|
|
20717
20881
|
reason: "READ_ONLY"
|
|
@@ -20737,13 +20901,13 @@ function registerBranchTools(server, http, options = {}) {
|
|
|
20737
20901
|
responseTimeoutMs: 5000
|
|
20738
20902
|
});
|
|
20739
20903
|
if (!result.ok) {
|
|
20740
|
-
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);
|
|
20741
20905
|
}
|
|
20742
20906
|
const plan = parsePromotionPlan(result.data, { parentRef: ref, branchRef });
|
|
20743
20907
|
if (result.status !== 200 || !plan) {
|
|
20744
|
-
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);
|
|
20745
20909
|
}
|
|
20746
|
-
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) }] };
|
|
20747
20911
|
} else if (action === "promote") {
|
|
20748
20912
|
const branchRef = requireString(args.branch_ref, "branch_ref");
|
|
20749
20913
|
const planChecksum = requireString(args.plan_checksum, "plan_checksum");
|
|
@@ -20762,18 +20926,18 @@ function registerBranchTools(server, http, options = {}) {
|
|
|
20762
20926
|
});
|
|
20763
20927
|
const beforeMutation = { ...state, mutation_sent: false };
|
|
20764
20928
|
if (!preflight.ok) {
|
|
20765
|
-
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);
|
|
20766
20930
|
}
|
|
20767
20931
|
const plan = parsePromotionPlan(preflight.data, { parentRef: ref, branchRef });
|
|
20768
20932
|
if (preflight.status !== 200 || !plan) {
|
|
20769
|
-
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);
|
|
20770
20934
|
}
|
|
20771
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;
|
|
20772
20936
|
if (preflightReason) {
|
|
20773
|
-
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);
|
|
20774
20938
|
}
|
|
20775
20939
|
if (plan.pending.length === 0) {
|
|
20776
|
-
return args.json ? releaseControlSuccess("branch.promote", {
|
|
20940
|
+
return args.format === "json" ? releaseControlSuccess("branch.promote", {
|
|
20777
20941
|
...beforeMutation,
|
|
20778
20942
|
promoted: false,
|
|
20779
20943
|
unchanged: true,
|
|
@@ -20799,13 +20963,13 @@ function registerBranchTools(server, http, options = {}) {
|
|
|
20799
20963
|
mutation_sent: true,
|
|
20800
20964
|
reported_applied_versions: appliedVersions(result),
|
|
20801
20965
|
...safePromotionCode(result) ? { server_code: safePromotionCode(result) } : {}
|
|
20802
|
-
}), args.
|
|
20966
|
+
}), args.format === "json", result.transportError === true || result.responseReadError === true || result.status === 408 || result.status >= 500);
|
|
20803
20967
|
}
|
|
20804
20968
|
const promoted = parsePromotionResult(result.data, { parentRef: ref, branchRef }, plan.pending);
|
|
20805
20969
|
if (result.status !== 200 || !promoted) {
|
|
20806
|
-
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);
|
|
20807
20971
|
}
|
|
20808
|
-
return args.json ? releaseControlSuccess("branch.promote", {
|
|
20972
|
+
return args.format === "json" ? releaseControlSuccess("branch.promote", {
|
|
20809
20973
|
...promoted,
|
|
20810
20974
|
unchanged: false,
|
|
20811
20975
|
mutation_sent: true,
|
|
@@ -21478,7 +21642,8 @@ function buildLiteArgs(request) {
|
|
|
21478
21642
|
booleanFlag(args, "--service-role", request.service_role);
|
|
21479
21643
|
booleanFlag(args, "--force", request.force);
|
|
21480
21644
|
booleanFlag(args, "--memory", request.memory);
|
|
21481
|
-
|
|
21645
|
+
if (request.format === "json")
|
|
21646
|
+
args.push("--json");
|
|
21482
21647
|
if (request.baseline && request.action !== "db_pull")
|
|
21483
21648
|
throw new Error("baseline requires db_pull");
|
|
21484
21649
|
booleanFlag(args, "--baseline", request.baseline);
|
|
@@ -21603,7 +21768,7 @@ function registerLiteCliTools(server, options = {}) {
|
|
|
21603
21768
|
service_role: optional(Boolean2(), "[keys] Also print the service_role key"),
|
|
21604
21769
|
force: optional(Boolean2(), "[snapshot_restore] Replace non-empty restore targets"),
|
|
21605
21770
|
memory: optional(Boolean2(), "[*] Use an in-memory PGlite database"),
|
|
21606
|
-
|
|
21771
|
+
format: optional(stringEnum2(["text", "json"]), "[doctor] Output format (default: text)"),
|
|
21607
21772
|
baseline: optional(Boolean2(), "[db_pull] Record already-present schema in local migration history after review"),
|
|
21608
21773
|
production: optional(Boolean2(), "[doctor] Add single-node production readiness checks"),
|
|
21609
21774
|
runtime_mode: optional(stringEnum2(["development", "strict"]), "[*] Lite runtime mode"),
|
|
@@ -21762,6 +21927,26 @@ function resolveDefaultCodexSkillRoot(environment = process.env, homeDirectory =
|
|
|
21762
21927
|
const codexHome = environment.CODEX_HOME?.trim();
|
|
21763
21928
|
return join8(resolve12(codexHome || join8(homeDirectory, ".codex")), "skills");
|
|
21764
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
|
+
}
|
|
21765
21950
|
function resolveBundledSkillDirectory(moduleUrl = import.meta.url) {
|
|
21766
21951
|
const moduleDirectory = dirname5(fileURLToPath(moduleUrl));
|
|
21767
21952
|
const candidates = [
|
|
@@ -21779,7 +21964,7 @@ function textResponse(payload) {
|
|
|
21779
21964
|
function registerAiTools(server) {
|
|
21780
21965
|
registerTool(server, "ai", "Install or inspect the bundled SupaCloud CLI AI skill. Actions: show_skill, install_skill", {
|
|
21781
21966
|
action: stringEnum2(["show_skill", "install_skill"]),
|
|
21782
|
-
target: optional(String2(), "[install_skill]
|
|
21967
|
+
target: optional(String2(), "[install_skill] codex|claude|agents|project or an explicit Skill root directory"),
|
|
21783
21968
|
dry_run: optional(Boolean2(), "[install_skill] Preview the installation without writing files"),
|
|
21784
21969
|
force: optional(Boolean2(), "[install_skill] Back up and replace different existing skill content")
|
|
21785
21970
|
}, async (request) => {
|
|
@@ -21790,12 +21975,17 @@ function registerAiTools(server) {
|
|
|
21790
21975
|
name: SKILL_NAME,
|
|
21791
21976
|
sourceDirectory,
|
|
21792
21977
|
defaultTargetRoot,
|
|
21793
|
-
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
|
+
]))
|
|
21794
21983
|
});
|
|
21795
21984
|
}
|
|
21985
|
+
const targetRoot = resolveSkillTargetRoot(request.target);
|
|
21796
21986
|
return textResponse(installSkill({
|
|
21797
21987
|
sourceDirectory,
|
|
21798
|
-
targetRoot
|
|
21988
|
+
targetRoot,
|
|
21799
21989
|
mode: request.dry_run ? "dry-run" : "write",
|
|
21800
21990
|
conflictPolicy: request.force ? "replace" : "reject",
|
|
21801
21991
|
now: new Date
|
|
@@ -21995,16 +22185,16 @@ var starter_metadata_default = {
|
|
|
21995
22185
|
generatedBy: "supacloud/starter-metadata-v1",
|
|
21996
22186
|
packages: {
|
|
21997
22187
|
compiler: {
|
|
21998
|
-
version: "0.
|
|
22188
|
+
version: "0.54.0"
|
|
21999
22189
|
},
|
|
22000
22190
|
app: {
|
|
22001
|
-
version: "0.
|
|
22191
|
+
version: "0.36.0",
|
|
22002
22192
|
dependencies: {
|
|
22003
22193
|
rxjs: "7.8.2"
|
|
22004
22194
|
}
|
|
22005
22195
|
},
|
|
22006
22196
|
elysia: {
|
|
22007
|
-
version: "0.
|
|
22197
|
+
version: "0.38.0"
|
|
22008
22198
|
},
|
|
22009
22199
|
commands: {
|
|
22010
22200
|
version: "0.11.2"
|
|
@@ -22013,7 +22203,7 @@ var starter_metadata_default = {
|
|
|
22013
22203
|
version: "0.15.2"
|
|
22014
22204
|
},
|
|
22015
22205
|
db: {
|
|
22016
|
-
version: "0.20.
|
|
22206
|
+
version: "0.20.4",
|
|
22017
22207
|
peerDependencies: {
|
|
22018
22208
|
"drizzle-orm": "1.0.0-rc.5-5935859"
|
|
22019
22209
|
},
|
|
@@ -22099,6 +22289,10 @@ requirement and equivalent lifecycle tests.
|
|
|
22099
22289
|
Bind subscriptions to an explicit owner and propagate cancellation to native I/O.
|
|
22100
22290
|
Never automatically retry a write or turn observable delivery into durable ack.
|
|
22101
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.
|
|
22102
22296
|
Run bun run check; include negative and cleanup tests. Never store live credentials.
|
|
22103
22297
|
`;
|
|
22104
22298
|
var STARTER_REACTIVE_TEST = `import { expect, test } from "bun:test";
|
|
@@ -22340,7 +22534,6 @@ export function createCompiledModules(): never {
|
|
|
22340
22534
|
}
|
|
22341
22535
|
`;
|
|
22342
22536
|
function baseFiles(name, sdkDependencies) {
|
|
22343
|
-
const minimal = sdkDependencies !== undefined;
|
|
22344
22537
|
return {
|
|
22345
22538
|
"package.json": json({
|
|
22346
22539
|
name,
|
|
@@ -22366,8 +22559,8 @@ function baseFiles(name, sdkDependencies) {
|
|
|
22366
22559
|
"@supacloud/app": `^${appMetadata.version}`,
|
|
22367
22560
|
"@supacloud/elysia": `^${elysiaMetadata.version}`,
|
|
22368
22561
|
effect: "4.0.5",
|
|
22369
|
-
elysia: "2.0.0-rc.
|
|
22370
|
-
...sdkDependencies
|
|
22562
|
+
elysia: "2.0.0-rc.1",
|
|
22563
|
+
...sdkDependencies
|
|
22371
22564
|
},
|
|
22372
22565
|
devDependencies: {
|
|
22373
22566
|
"@supacloud/compiler": `^${compilerMetadata.version}`,
|
|
@@ -22412,253 +22605,16 @@ PORT=3000
|
|
|
22412
22605
|
`,
|
|
22413
22606
|
"supacloud.config.ts": `import { defineSupacloudConfig } from "@supacloud/compiler";
|
|
22414
22607
|
|
|
22415
|
-
export default defineSupacloudConfig(
|
|
22416
|
-
root: "src",
|
|
22417
|
-
outDir: "generated",
|
|
22418
|
-
strict: true,
|
|
22419
|
-
moduleBoundaryPreset: "modular-monolith",
|
|
22420
|
-
effect: {
|
|
22421
|
-
requireRouteEffects: true,
|
|
22422
|
-
requireErrorMappings: true,
|
|
22423
|
-
requireDependencies: true,
|
|
22424
|
-
requireTaggedErrorTypes: true,
|
|
22425
|
-
requireExactDependencyTypes: true,
|
|
22426
|
-
requireTimeoutForDependencies: true,
|
|
22427
|
-
forbidDirectRuntimeExecution: true,
|
|
22428
|
-
forbidDirectThrows: true,
|
|
22429
|
-
},
|
|
22430
|
-
typeSafety: { scanProductionSource: true, noAnyInGenerated: true },
|
|
22431
|
-
disallowControllerDirectDb: true,
|
|
22432
|
-
detectOrphanModules: true,
|
|
22433
|
-
commandCapabilities: { permission: true, transaction: true, idempotency: true, audit: true },
|
|
22434
|
-
});
|
|
22608
|
+
export default defineSupacloudConfig();
|
|
22435
22609
|
`,
|
|
22436
22610
|
"src/application.ts": APPLICATION_SOURCE(name),
|
|
22437
22611
|
"generated/application.ts": GENERATED_BOOTSTRAP,
|
|
22438
|
-
...minimal ? {} : {
|
|
22439
|
-
"REACTIVE.md": STARTER_REACTIVE_GUIDE,
|
|
22440
|
-
"AGENTS.md": STARTER_REACTIVE_AGENTS,
|
|
22441
|
-
"tests/reactive.test.ts": STARTER_REACTIVE_TEST
|
|
22442
|
-
},
|
|
22443
22612
|
"scripts/environment.ts": STARTER_ENVIRONMENT,
|
|
22444
22613
|
"tests/environment.test.ts": STARTER_ENVIRONMENT_TEST,
|
|
22445
22614
|
"scripts/dev.ts": DEV_SCRIPT,
|
|
22446
22615
|
"scripts/serve.ts": SERVE_SCRIPT
|
|
22447
22616
|
};
|
|
22448
22617
|
}
|
|
22449
|
-
function httpTemplate(name) {
|
|
22450
|
-
return {
|
|
22451
|
-
...baseFiles(name),
|
|
22452
|
-
"src/orders/contracts.ts": `import { t } from "elysia";
|
|
22453
|
-
export const OrderParams = t.Object({ id: t.String({ minLength: 1 }) });
|
|
22454
|
-
export const CreateOrderBody = t.Object({ name: t.String({ minLength: 1 }) });
|
|
22455
|
-
export const OrderResult = t.Object({ id: t.String(), name: t.String() });
|
|
22456
|
-
export const HealthResult = t.Object({ ok: t.Boolean() });
|
|
22457
|
-
`,
|
|
22458
|
-
"src/orders/orders.ts": `import { Effect } from "effect";
|
|
22459
|
-
import { Body, Controller, Get, Param, Post, defineFeatureSlice } from "@supacloud/app";
|
|
22460
|
-
import { status } from "elysia";
|
|
22461
|
-
import { OrderParams, CreateOrderBody, OrderResult, HealthResult } from "./contracts";
|
|
22462
|
-
@Controller("/orders")
|
|
22463
|
-
export class OrdersController {
|
|
22464
|
-
@Get("/health", {
|
|
22465
|
-
responses: { 200: HealthResult },
|
|
22466
|
-
effect: { required: true, dependencies: [], errors: [], retry: "none" },
|
|
22467
|
-
})
|
|
22468
|
-
health(): Effect.Effect<{ ok: boolean }, never, never> { return Effect.succeed({ ok: true }); }
|
|
22469
|
-
|
|
22470
|
-
@Get("/:id", {
|
|
22471
|
-
params: OrderParams,
|
|
22472
|
-
responses: { 200: OrderResult },
|
|
22473
|
-
effect: { required: true, dependencies: [], errors: [], retry: "none" },
|
|
22474
|
-
})
|
|
22475
|
-
get(@Param("id") id: string): Effect.Effect<{ id: string; name: string }, never, never> {
|
|
22476
|
-
return Effect.succeed({ id, name: "demo" });
|
|
22477
|
-
}
|
|
22478
|
-
|
|
22479
|
-
@Post("/", {
|
|
22480
|
-
body: CreateOrderBody,
|
|
22481
|
-
responses: { 201: OrderResult },
|
|
22482
|
-
effect: { required: true, dependencies: [], errors: [], retry: "none" },
|
|
22483
|
-
})
|
|
22484
|
-
create(@Body() body: { name: string }) {
|
|
22485
|
-
return Effect.succeed(status(201, { id: "demo", name: body.name }));
|
|
22486
|
-
}
|
|
22487
|
-
}
|
|
22488
|
-
|
|
22489
|
-
export const OrdersFeature = defineFeatureSlice({
|
|
22490
|
-
name: "orders",
|
|
22491
|
-
tags: ["type:feature"],
|
|
22492
|
-
controllers: [OrdersController],
|
|
22493
|
-
});
|
|
22494
|
-
`,
|
|
22495
|
-
"scripts/sandbox.ts": `import { createMemorySandbox } from "@supacloud/elysia";
|
|
22496
|
-
import { createCompiledModules } from "../generated/application";
|
|
22497
|
-
|
|
22498
|
-
export function createDemo() {
|
|
22499
|
-
if (process.env["APP_ENV"] !== "development" && process.env["APP_ENV"] !== "test") {
|
|
22500
|
-
throw new Error("Memory adapters are restricted to development and test");
|
|
22501
|
-
}
|
|
22502
|
-
return createMemorySandbox({
|
|
22503
|
-
modules: createCompiledModules(),
|
|
22504
|
-
identity: { authenticated: true, subject: "local-demo" },
|
|
22505
|
-
});
|
|
22506
|
-
}
|
|
22507
|
-
`,
|
|
22508
|
-
"tests/orders.test.ts": `import { expect, test } from "bun:test";
|
|
22509
|
-
import { createDemo } from "../scripts/sandbox";
|
|
22510
|
-
|
|
22511
|
-
test("health, read and create routes follow the declared HTTP contract", async () => {
|
|
22512
|
-
const sandbox = createDemo();
|
|
22513
|
-
expect((await sandbox.request("/orders/health")).status).toBe(200);
|
|
22514
|
-
expect((await sandbox.request("/orders/1")).status).toBe(200);
|
|
22515
|
-
const created = await sandbox.request("/orders", {
|
|
22516
|
-
method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ name: "first" }),
|
|
22517
|
-
});
|
|
22518
|
-
expect(created.status).toBe(201);
|
|
22519
|
-
expect(await created.json()).toEqual({ id: "demo", name: "first" });
|
|
22520
|
-
const invalid = await sandbox.request("/orders", {
|
|
22521
|
-
method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({}),
|
|
22522
|
-
});
|
|
22523
|
-
expect(invalid.status).toBe(422);
|
|
22524
|
-
});
|
|
22525
|
-
`,
|
|
22526
|
-
"README.md": `# ${name}
|
|
22527
|
-
|
|
22528
|
-
HTTP API golden path.
|
|
22529
|
-
|
|
22530
|
-
## Local Development
|
|
22531
|
-
|
|
22532
|
-
\`\`\`sh
|
|
22533
|
-
bun install
|
|
22534
|
-
bun run check
|
|
22535
|
-
bun run dev
|
|
22536
|
-
\`\`\`
|
|
22537
|
-
|
|
22538
|
-
The server binds only to 127.0.0.1:3000 by default.
|
|
22539
|
-
|
|
22540
|
-
\`\`\`sh
|
|
22541
|
-
curl http://127.0.0.1:3000/orders/health
|
|
22542
|
-
curl http://127.0.0.1:3000/orders/1
|
|
22543
|
-
curl -X POST http://127.0.0.1:3000/orders \\
|
|
22544
|
-
-H 'Content-Type: application/json' -d '{"name":"first"}'
|
|
22545
|
-
\`\`\`
|
|
22546
|
-
|
|
22547
|
-
The generated feature declares explicit route schemas so the compiler can detect
|
|
22548
|
-
route/schema drift. Keep request and response shapes in \`src/orders/orders.ts\`;
|
|
22549
|
-
do not widen them to \`unknown\` just to accept unvalidated data.
|
|
22550
|
-
|
|
22551
|
-
## Production Boundary
|
|
22552
|
-
|
|
22553
|
-
\`bun run build\` emits an application factory, not a server. Import \`createApp\`
|
|
22554
|
-
from \`output/supacloud/application.js\` in your trusted host and supply real dependency,
|
|
22555
|
-
request-context and command-governance adapters. The memory sandbox is
|
|
22556
|
-
development/test only.
|
|
22557
|
-
|
|
22558
|
-
Keep backend bundles outside the frontend output directory. Vite normally cleans
|
|
22559
|
-
\`dist/\` before building; server artifacts placed there can be deleted or included
|
|
22560
|
-
in a public frontend archive. Use disjoint output directories, not a disabled
|
|
22561
|
-
frontend clean step. Compiler source artifacts remain in \`generated/\`.
|
|
22562
|
-
|
|
22563
|
-
## Inspection And Repair
|
|
22564
|
-
|
|
22565
|
-
\`\`\`sh
|
|
22566
|
-
supacloud context --root . --target orders --format json
|
|
22567
|
-
supacloud doctor --root .
|
|
22568
|
-
\`\`\`
|
|
22569
|
-
`
|
|
22570
|
-
};
|
|
22571
|
-
}
|
|
22572
|
-
function edgeTemplate(name) {
|
|
22573
|
-
return {
|
|
22574
|
-
...baseFiles(name),
|
|
22575
|
-
"src/sync/sync.ts": `import { Injectable, Job, defineFeatureSlice } from "@supacloud/app";
|
|
22576
|
-
|
|
22577
|
-
/**
|
|
22578
|
-
* Worker/edge golden path: a declared, retry-bounded job with an explicit
|
|
22579
|
-
* idempotency requirement. The platform owns scheduling, retry and DLQ; the
|
|
22580
|
-
* handler stays side-effect explicit and must not assume exactly-once execution.
|
|
22581
|
-
*/
|
|
22582
|
-
@Injectable()
|
|
22583
|
-
@Job({
|
|
22584
|
-
name: "sync.orders",
|
|
22585
|
-
mode: "task",
|
|
22586
|
-
idempotency: "required",
|
|
22587
|
-
timeoutSec: 60,
|
|
22588
|
-
maxAttempts: 3,
|
|
22589
|
-
})
|
|
22590
|
-
export class SyncOrdersJob {
|
|
22591
|
-
async run(): Promise<{ ok: boolean }> {
|
|
22592
|
-
return { ok: true };
|
|
22593
|
-
}
|
|
22594
|
-
}
|
|
22595
|
-
|
|
22596
|
-
export const SyncFeature = defineFeatureSlice({
|
|
22597
|
-
name: "sync",
|
|
22598
|
-
tags: ["type:feature"],
|
|
22599
|
-
providers: [SyncOrdersJob],
|
|
22600
|
-
jobs: [SyncOrdersJob],
|
|
22601
|
-
});
|
|
22602
|
-
`,
|
|
22603
|
-
"scripts/sandbox.ts": `import { createMemorySandbox } from "@supacloud/elysia";
|
|
22604
|
-
import { createCompiledModules } from "../generated/application";
|
|
22605
|
-
|
|
22606
|
-
export function createDemo() {
|
|
22607
|
-
if (process.env["APP_ENV"] !== "development" && process.env["APP_ENV"] !== "test") {
|
|
22608
|
-
throw new Error("Memory adapters are restricted to development and test");
|
|
22609
|
-
}
|
|
22610
|
-
return createMemorySandbox({
|
|
22611
|
-
modules: createCompiledModules(),
|
|
22612
|
-
identity: { authenticated: true, subject: "local-worker" },
|
|
22613
|
-
});
|
|
22614
|
-
}
|
|
22615
|
-
`,
|
|
22616
|
-
"tests/sync.test.ts": `import { expect, test } from "bun:test";
|
|
22617
|
-
import { SyncOrdersJob } from "../src/sync/sync";
|
|
22618
|
-
|
|
22619
|
-
test("the declared job handler is deterministic and idempotent by construction", async () => {
|
|
22620
|
-
const job = new SyncOrdersJob();
|
|
22621
|
-
expect(await job.run()).toEqual({ ok: true });
|
|
22622
|
-
expect(await job.run()).toEqual({ ok: true });
|
|
22623
|
-
});
|
|
22624
|
-
`,
|
|
22625
|
-
"README.md": `# ${name}
|
|
22626
|
-
|
|
22627
|
-
Worker / Edge golden path.
|
|
22628
|
-
|
|
22629
|
-
## Local Development
|
|
22630
|
-
|
|
22631
|
-
\`\`\`sh
|
|
22632
|
-
bun install
|
|
22633
|
-
bun run check
|
|
22634
|
-
bun run test
|
|
22635
|
-
\`\`\`
|
|
22636
|
-
|
|
22637
|
-
\`src/sync/sync.ts\` declares a platform job with an explicit mode, timeout,
|
|
22638
|
-
attempt bound and idempotency requirement. The platform schedules and retries;
|
|
22639
|
-
the handler must be safe to run more than once and must not assume exactly-once
|
|
22640
|
-
execution.
|
|
22641
|
-
|
|
22642
|
-
## Production Boundary
|
|
22643
|
-
|
|
22644
|
-
The job runs through the platform worker/edge adapter, not the local memory
|
|
22645
|
-
sandbox. Bind persistence, authorization and audit to durable adapters before
|
|
22646
|
-
invoking it in production. Do not schedule destructive work without an
|
|
22647
|
-
idempotency key.
|
|
22648
|
-
|
|
22649
|
-
\`bun run build\` emits \`output/supacloud/application.js\`, an application factory,
|
|
22650
|
-
not a deployed Worker. Keep it outside any frontend output directory such as
|
|
22651
|
-
\`dist/\`; frontend clean steps and public archives must not own server bundles.
|
|
22652
|
-
|
|
22653
|
-
## Inspection And Repair
|
|
22654
|
-
|
|
22655
|
-
\`\`\`sh
|
|
22656
|
-
supacloud context --root . --target sync --format json
|
|
22657
|
-
supacloud doctor --root .
|
|
22658
|
-
\`\`\`
|
|
22659
|
-
`
|
|
22660
|
-
};
|
|
22661
|
-
}
|
|
22662
22618
|
function minimalTemplate(name, sdkDependencies) {
|
|
22663
22619
|
return {
|
|
22664
22620
|
...baseFiles(name, sdkDependencies),
|
|
@@ -22713,8 +22669,12 @@ export function createDemo() {
|
|
|
22713
22669
|
- Cross-table business writes require trusted authorization, transactions,
|
|
22714
22670
|
idempotency and audit. Never put service-role or Management tokens in a browser.
|
|
22715
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.
|
|
22716
22676
|
- Run the directly related single test file and git diff --check for daily changes.
|
|
22717
|
-
- Use app verify-plan --target <module
|
|
22677
|
+
- Use \`supacloud app verify-plan --target <module>\` to inspect the focused verification plan.
|
|
22718
22678
|
- Do not introduce another workflow engine, DI container or session store.
|
|
22719
22679
|
`,
|
|
22720
22680
|
"README.md": `# ${name}
|
|
@@ -22748,8 +22708,8 @@ Use fixed trusted HTTPS endpoints and never retry an uncertain write.
|
|
|
22748
22708
|
|
|
22749
22709
|
GraphQL is optional; this starter has no synthetic GraphQL schema.
|
|
22750
22710
|
For a complete persistent command/attachment example, create a separate reference
|
|
22751
|
-
project with app init --template command.
|
|
22752
|
-
|
|
22711
|
+
project with app init --template command. Do not install every recipe into this
|
|
22712
|
+
application.
|
|
22753
22713
|
|
|
22754
22714
|
## Integration and delivery
|
|
22755
22715
|
|
|
@@ -22766,58 +22726,9 @@ regeneration, then validate types, database permissions and authenticated behavi
|
|
|
22766
22726
|
};
|
|
22767
22727
|
}
|
|
22768
22728
|
function appTemplateFiles(name, template, sdkDependencies) {
|
|
22769
|
-
if (template
|
|
22770
|
-
if (!sdkDependencies)
|
|
22771
|
-
throw new Error("Minimal template requires the initializer's SDK dependency versions");
|
|
22772
|
-
return minimalTemplate(name, sdkDependencies);
|
|
22773
|
-
}
|
|
22774
|
-
if (template !== "http" && template !== "edge")
|
|
22729
|
+
if (template !== "minimal")
|
|
22775
22730
|
throw new Error("Use appStarterFiles for the command recipe");
|
|
22776
|
-
|
|
22777
|
-
const feature = template === "http" ? "OrdersFeature" : "SyncFeature";
|
|
22778
|
-
const source = template === "http" ? "./orders/orders" : "./sync/sync";
|
|
22779
|
-
return {
|
|
22780
|
-
...files,
|
|
22781
|
-
"src/app.module.ts": `import { Module } from "@supacloud/app";
|
|
22782
|
-
import { ${feature} } from "${source}";
|
|
22783
|
-
|
|
22784
|
-
// Compose feature modules here. Do not make unrelated features import each other.
|
|
22785
|
-
@Module({
|
|
22786
|
-
name: "application-root",
|
|
22787
|
-
tags: ["type:app"],
|
|
22788
|
-
imports: [${feature}],
|
|
22789
|
-
})
|
|
22790
|
-
export class AppModule {}
|
|
22791
|
-
`,
|
|
22792
|
-
"README.md": files["README.md"] + `
|
|
22793
|
-
## Application Composition
|
|
22794
|
-
|
|
22795
|
-
\`src/app.module.ts\` is the application composition root. Register additional
|
|
22796
|
-
feature modules there, rather than importing them from another feature.
|
|
22797
|
-
The compiler discovers this declaration; no runtime container lookup is needed.
|
|
22798
|
-
|
|
22799
|
-
With the matching candidate/released CLI installed:
|
|
22800
|
-
|
|
22801
|
-
\`\`\`sh
|
|
22802
|
-
supacloud app generate --kind resource --name inventory --register-in src/app.module.ts --dry-run --format json
|
|
22803
|
-
supacloud app generate --kind resource --name inventory --register-in src/app.module.ts
|
|
22804
|
-
bun run check
|
|
22805
|
-
bun run inspect
|
|
22806
|
-
\`\`\`
|
|
22807
|
-
|
|
22808
|
-
The resource is an asynchronous read skeleton, not implemented CRUD. Its Service
|
|
22809
|
-
rejects until an authorized data adapter is supplied; implement it and replace the
|
|
22810
|
-
placeholder test together. Generation does not grant access or connect a database.
|
|
22811
|
-
|
|
22812
|
-
\`bun run inspect\` delegates to the installed compiler and prints the current
|
|
22813
|
-
source graph as JSON without starting the app or regenerating artifacts. This is
|
|
22814
|
-
not a live mounted-route report. Configuration remains trusted project code.
|
|
22815
|
-
After changing source, compile before using artifact-backed CLI graph/explain.
|
|
22816
|
-
|
|
22817
|
-
In CI, run \`bun run check:generated\` before commands that regenerate artifacts;
|
|
22818
|
-
\`bun run check\` is the convenient bootstrap path for a newly created project.
|
|
22819
|
-
`
|
|
22820
|
-
};
|
|
22731
|
+
return minimalTemplate(name, sdkDependencies);
|
|
22821
22732
|
}
|
|
22822
22733
|
|
|
22823
22734
|
// src/shared/tools/app-starter-review-job.ts
|
|
@@ -24118,9 +24029,9 @@ After installing dependencies, run \`bun run db:generate\` once and commit db/co
|
|
|
24118
24029
|
The default \`bun run check\` and build fail on stale database contracts before any
|
|
24119
24030
|
compiler regeneration. After an intentional source change, run db:generate again,
|
|
24120
24031
|
review the diff, then run check.
|
|
24121
|
-
The command starter
|
|
24122
|
-
|
|
24123
|
-
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\`
|
|
24124
24035
|
with the CLI's explicit loopback database URL, or set databaseGovernance.mode to
|
|
24125
24036
|
"enforce" with an explicitly supplied \`DATABASE_URL\`, to run SQL lint and live
|
|
24126
24037
|
Catalog reconciliation. Missing connections, stale source contracts and Catalog
|
|
@@ -25532,7 +25443,7 @@ function appStarterFiles(name) {
|
|
|
25532
25443
|
"@supacloud/js": `^${sdkMetadata.version}`,
|
|
25533
25444
|
"@supabase/supabase-js": sdkMetadata.peerDependencies["@supabase/supabase-js"],
|
|
25534
25445
|
effect: "4.0.5",
|
|
25535
|
-
elysia: "2.0.0-rc.
|
|
25446
|
+
elysia: "2.0.0-rc.1",
|
|
25536
25447
|
rxjs: appMetadata2.dependencies.rxjs
|
|
25537
25448
|
},
|
|
25538
25449
|
devDependencies: {
|
|
@@ -25591,25 +25502,7 @@ PORT=3000
|
|
|
25591
25502
|
"supacloud.config.ts": `import { defineSupacloudConfig } from "@supacloud/compiler";
|
|
25592
25503
|
|
|
25593
25504
|
export default defineSupacloudConfig({
|
|
25594
|
-
|
|
25595
|
-
outDir: "generated",
|
|
25596
|
-
strict: true,
|
|
25597
|
-
moduleBoundaryPreset: "modular-monolith",
|
|
25598
|
-
databaseGovernance: { mode: "check", moduleFile: "db/modules.ts", databaseUrlEnv: "DATABASE_URL" },
|
|
25599
|
-
effect: {
|
|
25600
|
-
requireRouteEffects: true,
|
|
25601
|
-
requireErrorMappings: true,
|
|
25602
|
-
requireDependencies: true,
|
|
25603
|
-
requireTaggedErrorTypes: true,
|
|
25604
|
-
requireExactDependencyTypes: true,
|
|
25605
|
-
requireTimeoutForDependencies: true,
|
|
25606
|
-
forbidDirectRuntimeExecution: true,
|
|
25607
|
-
forbidDirectThrows: true,
|
|
25608
|
-
},
|
|
25609
|
-
typeSafety: { scanProductionSource: true, noAnyInGenerated: true },
|
|
25610
|
-
disallowControllerDirectDb: true,
|
|
25611
|
-
detectOrphanModules: true,
|
|
25612
|
-
commandCapabilities: { permission: true, transaction: true, idempotency: true, audit: true },
|
|
25505
|
+
databaseGovernance: { mode: "check" },
|
|
25613
25506
|
delivery: {
|
|
25614
25507
|
version: 1,
|
|
25615
25508
|
build: {
|
|
@@ -26400,7 +26293,7 @@ and static execution plans, without provider values or source expressions.
|
|
|
26400
26293
|
The app context entry point retains its module workflow and adds a separate
|
|
26401
26294
|
execution evidence block when --events and --request-id are provided together.
|
|
26402
26295
|
It requires an explicit --target and --format json, rejects --write, and
|
|
26403
|
-
resolves relative event paths from --root. The
|
|
26296
|
+
resolves relative event paths from --root. The app context action supports
|
|
26404
26297
|
the same flags. Omit both execution flags for the unchanged authoring context.
|
|
26405
26298
|
The optional --events example expects a trusted host
|
|
26406
26299
|
to capture approved onExecution metadata in { "version": 1, "events": [...] }.
|
|
@@ -28159,13 +28052,27 @@ async function initProject(args) {
|
|
|
28159
28052
|
throw new Error("app init never overwrites files; choose an empty directory");
|
|
28160
28053
|
const template = args.template ?? "minimal";
|
|
28161
28054
|
const { root, name, files } = await initializeAppProject({ root: args.root, name: args.name, template });
|
|
28162
|
-
|
|
28163
|
-
`Initialized ${name} in ${root} (${files.length} files, template: ${template}).`,
|
|
28164
|
-
`cd '${root.replaceAll("'", "'\\''")}'`,
|
|
28055
|
+
const next = [
|
|
28165
28056
|
"bun install",
|
|
28166
28057
|
...template === "command" ? ["bun run db:generate"] : [],
|
|
28167
28058
|
"bun run check",
|
|
28168
|
-
"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,
|
|
28169
28076
|
"Local demo only. Production requires persistent governance and identity adapters; see README.md."
|
|
28170
28077
|
].join(`
|
|
28171
28078
|
`));
|
|
@@ -28564,9 +28471,9 @@ async function runContext(args) {
|
|
|
28564
28471
|
`));
|
|
28565
28472
|
}
|
|
28566
28473
|
function doctorFixPlan(doctor) {
|
|
28567
|
-
return createDiagnosticRepairPlan(doctor.diagnostics ?? []).map((repair) => ({
|
|
28474
|
+
return createDiagnosticRepairPlan(doctor.diagnostics ?? []).map((repair, index) => ({
|
|
28568
28475
|
...repair,
|
|
28569
|
-
command:
|
|
28476
|
+
command: `supacloud app fix --from-doctor <doctor.json> --fix-index ${index}`
|
|
28570
28477
|
}));
|
|
28571
28478
|
}
|
|
28572
28479
|
async function findUnwiredContracts(rootDir) {
|
|
@@ -28622,6 +28529,11 @@ async function runDoctor(args) {
|
|
|
28622
28529
|
const autoFixable = fixPlan.filter((repair) => repair.readiness === "preview").length;
|
|
28623
28530
|
const inputRequired = fixPlan.filter((repair) => repair.readiness === "input-required").length;
|
|
28624
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
|
+
];
|
|
28625
28537
|
const unwiredContracts = context ? [] : await findUnwiredContracts(root);
|
|
28626
28538
|
const scope = context ? {
|
|
28627
28539
|
target: context.subject,
|
|
@@ -28633,6 +28545,7 @@ async function runDoctor(args) {
|
|
|
28633
28545
|
if (args.format === "json") {
|
|
28634
28546
|
return textResult(JSON.stringify({
|
|
28635
28547
|
ok: doctor.errors === 0,
|
|
28548
|
+
next,
|
|
28636
28549
|
autoFixable,
|
|
28637
28550
|
inputRequired,
|
|
28638
28551
|
manualFixes,
|
|
@@ -28651,7 +28564,7 @@ async function runDoctor(args) {
|
|
|
28651
28564
|
lines.push("");
|
|
28652
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.`);
|
|
28653
28566
|
if (autoFixable > 0) {
|
|
28654
|
-
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.`);
|
|
28655
28568
|
}
|
|
28656
28569
|
if (inputRequired > 0 || manualFixes > 0) {
|
|
28657
28570
|
lines.push(`${inputRequired} fix(es) require explicit input; ${manualFixes} require manual implementation.`);
|
|
@@ -28668,13 +28581,49 @@ async function runDoctor(args) {
|
|
|
28668
28581
|
async function runFix(args) {
|
|
28669
28582
|
const root = resolve18(args.root || process.cwd());
|
|
28670
28583
|
const fixPath = args.fix?.trim();
|
|
28671
|
-
|
|
28672
|
-
|
|
28673
|
-
|
|
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
|
+
}
|
|
28674
28618
|
const write = args.write === true;
|
|
28675
28619
|
const applied = await applyDiagnosticFix(parsed, { rootDir: root, dryRun: !write });
|
|
28676
28620
|
return textResult(JSON.stringify({
|
|
28677
28621
|
ok: true,
|
|
28622
|
+
source,
|
|
28623
|
+
...source === "doctor" ? {
|
|
28624
|
+
index: args.fix_index,
|
|
28625
|
+
...repair?.code === undefined ? {} : { code: repair.code }
|
|
28626
|
+
} : {},
|
|
28678
28627
|
written: write,
|
|
28679
28628
|
file: applied.file,
|
|
28680
28629
|
changed: applied.changed,
|
|
@@ -28894,11 +28843,14 @@ async function runCheck(args) {
|
|
|
28894
28843
|
strict: args.strict ?? loadedConfig.strict ?? false
|
|
28895
28844
|
}, root);
|
|
28896
28845
|
const result = await checkProject(config);
|
|
28897
|
-
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>"];
|
|
28898
28849
|
if (args.format === "json")
|
|
28899
28850
|
return textResult(JSON.stringify({
|
|
28900
28851
|
version: 1,
|
|
28901
28852
|
ok: !hasError,
|
|
28853
|
+
next,
|
|
28902
28854
|
source: "declaration",
|
|
28903
28855
|
diagnostics: result.diagnostics,
|
|
28904
28856
|
mismatches: result.mismatches,
|
|
@@ -29324,7 +29276,7 @@ async function runAppRelease(request, options, delegate) {
|
|
|
29324
29276
|
...typeof deployReceipt.activation_id === "string" ? { activation_id: deployReceipt.activation_id } : {},
|
|
29325
29277
|
...deployReceipt.web === undefined ? {} : { web: deployReceipt.web }
|
|
29326
29278
|
};
|
|
29327
|
-
return request.format === "json"
|
|
29279
|
+
return request.format === "json" ? textResult(JSON.stringify(result, null, 2)) : textResult([
|
|
29328
29280
|
`${ref}/${id}/${environmentId}: ${deployReceipt.status === "accepted" ? "accepted" : result.no_op ? "no-op" : "released"}`,
|
|
29329
29281
|
`Release: ${releaseId}`,
|
|
29330
29282
|
`Configuration: ${configurationId}`,
|
|
@@ -29527,7 +29479,7 @@ async function runPromotion(request, options, delegate) {
|
|
|
29527
29479
|
const ref = request.ref ?? options.projectRef;
|
|
29528
29480
|
if (request.mutation_id !== undefined)
|
|
29529
29481
|
throw new Error("app promote generates a new mutation ID; use promote-status or promote-reconcile to inspect an existing mutation");
|
|
29530
|
-
const { format,
|
|
29482
|
+
const { format, ...remoteRequest } = request;
|
|
29531
29483
|
const planArgs = {
|
|
29532
29484
|
...remoteRequest,
|
|
29533
29485
|
action: "get_promotion_plan",
|
|
@@ -29565,7 +29517,7 @@ async function runPromotion(request, options, delegate) {
|
|
|
29565
29517
|
no_op: true,
|
|
29566
29518
|
execution_performed: false
|
|
29567
29519
|
});
|
|
29568
|
-
return format === "json"
|
|
29520
|
+
return format === "json" ? noOp : textResult(`${plan.project_ref}/${plan.application_id}/${plan.environment_id}: no-op
|
|
29569
29521
|
Execution: skipped`);
|
|
29570
29522
|
}
|
|
29571
29523
|
if (plan.blockers.some((code) => !["MIGRATION_PENDING", "BACKUP_REQUIRED"].includes(code))) {
|
|
@@ -29587,23 +29539,9 @@ Execution: skipped`);
|
|
|
29587
29539
|
};
|
|
29588
29540
|
validateToolArguments2(APPLICATION_TOOL_SCHEMA2, executeArgs);
|
|
29589
29541
|
const result = await delegate(executeArgs);
|
|
29590
|
-
return format === "json"
|
|
29542
|
+
return format === "json" ? result : formatPromotionExecution(result);
|
|
29591
29543
|
}
|
|
29592
29544
|
async function runAppTool(request, options = {}) {
|
|
29593
|
-
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) {
|
|
29594
|
-
throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", "Do not combine hyphenated and underscored aliases of the same flag");
|
|
29595
|
-
}
|
|
29596
|
-
request = {
|
|
29597
|
-
...request,
|
|
29598
|
-
...request["dry-run"] === undefined ? {} : { dry_run: request["dry-run"] },
|
|
29599
|
-
...request["register-in"] === undefined ? {} : { register_in: request["register-in"] },
|
|
29600
|
-
...request["database-url"] === undefined ? {} : { database_url: request["database-url"] },
|
|
29601
|
-
...request["database-source"] === undefined ? {} : { database_source: request["database-source"] },
|
|
29602
|
-
...request["database-table"] === undefined ? {} : { database_table: request["database-table"] },
|
|
29603
|
-
...request["database-type"] === undefined ? {} : { database_type: request["database-type"] },
|
|
29604
|
-
...request["read-permission"] === undefined ? {} : { read_permission: request["read-permission"] },
|
|
29605
|
-
...request["request-id"] === undefined ? {} : { request_id: request["request-id"] }
|
|
29606
|
-
};
|
|
29607
29545
|
if (request.events !== undefined || request.request_id !== undefined) {
|
|
29608
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) {
|
|
29609
29547
|
throw new Error("Execution context requires app context, --target, --events, --request-id and --format json; --write is not supported");
|
|
@@ -29615,10 +29553,6 @@ async function runAppTool(request, options = {}) {
|
|
|
29615
29553
|
if (request.database_url !== undefined && request.action !== "dev" && request.action !== "watch") {
|
|
29616
29554
|
throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", "--database-url applies only to app dev --profile integration");
|
|
29617
29555
|
}
|
|
29618
|
-
if (request.json !== undefined && !Object.hasOwn(REMOTE_APP_ACTIONS, request.action) && request.action !== "promote" && request.action !== "release")
|
|
29619
|
-
throw new Error("--json applies only to remote app actions");
|
|
29620
|
-
if (request.json && request.format === "text")
|
|
29621
|
-
throw new Error("--json and --format text cannot be combined");
|
|
29622
29556
|
if (request.action === "promote") {
|
|
29623
29557
|
const delegate = options.getApplications?.();
|
|
29624
29558
|
if (!delegate)
|
|
@@ -29637,7 +29571,7 @@ async function runAppTool(request, options = {}) {
|
|
|
29637
29571
|
return textResult("App remote actions require a Management API context.", true);
|
|
29638
29572
|
const promotionDiff = request.action === "diff" && request.source_ref !== undefined && request.source_environment_id !== undefined && request.source_release_id !== undefined;
|
|
29639
29573
|
const action = promotionDiff ? "get_promotion_plan" : REMOTE_APP_ACTIONS[request.action];
|
|
29640
|
-
const { format,
|
|
29574
|
+
const { format, ...remoteRequest } = request;
|
|
29641
29575
|
const args = {
|
|
29642
29576
|
...remoteRequest,
|
|
29643
29577
|
action,
|
|
@@ -29645,7 +29579,7 @@ async function runAppTool(request, options = {}) {
|
|
|
29645
29579
|
};
|
|
29646
29580
|
validateToolArguments2(APPLICATION_TOOL_SCHEMA2, args);
|
|
29647
29581
|
const result = await delegate(args);
|
|
29648
|
-
if (request.action === "evidence" && format !== "json" &&
|
|
29582
|
+
if (request.action === "evidence" && format !== "json" && !result.isError) {
|
|
29649
29583
|
try {
|
|
29650
29584
|
const payload = JSON.parse(result.content[0].text);
|
|
29651
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))
|
|
@@ -29660,17 +29594,17 @@ async function runAppTool(request, options = {}) {
|
|
|
29660
29594
|
return textResult("DEPLOYMENT unknown: invalid or mismatched evidence; no mutation was attempted.", true);
|
|
29661
29595
|
}
|
|
29662
29596
|
}
|
|
29663
|
-
if (request.action === "history" && format !== "json"
|
|
29597
|
+
if (request.action === "history" && format !== "json") {
|
|
29664
29598
|
return historyText(result, request, args.ref);
|
|
29665
29599
|
}
|
|
29666
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)) {
|
|
29667
29601
|
return formatRemoteDeployPlan(result);
|
|
29668
29602
|
}
|
|
29669
29603
|
if (request.action === "promote-plan" || request.action === "diff" && request.source_ref !== undefined && request.source_environment_id !== undefined && request.source_release_id !== undefined) {
|
|
29670
|
-
if (!result.isError && format !== "json"
|
|
29604
|
+
if (!result.isError && format !== "json")
|
|
29671
29605
|
return formatPromotionPlan2(result);
|
|
29672
29606
|
}
|
|
29673
|
-
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;
|
|
29674
29608
|
}
|
|
29675
29609
|
switch (request.action) {
|
|
29676
29610
|
case "verify-plan": {
|
|
@@ -29772,83 +29706,38 @@ function registerAppTools(server, options = {}) {
|
|
|
29772
29706
|
"promote-reconcile"
|
|
29773
29707
|
]), "App action; promote obtains and verifies its plan before one mutation request"),
|
|
29774
29708
|
kind: optional(stringEnum2(["module", "command", "query", "controller", "job", "contract", "resource", "feature"]), "[generate] Scaffold kind"),
|
|
29775
|
-
template: optional(stringEnum2(["minimal", "
|
|
29709
|
+
template: optional(stringEnum2(["minimal", "command"]), "[init] Minimal application by default; command adds the persistent governed-command reference"),
|
|
29776
29710
|
name: optional(String2(), "[init/generate] Project or object name"),
|
|
29777
29711
|
module: optional(String2(), "[generate] Target feature module (required for command/query/controller)"),
|
|
29778
29712
|
permission: optional(String2(), "[generate feature] Explicit approval permission; not inferred from name"),
|
|
29779
29713
|
read_permission: optional(String2(), "[generate feature] Explicit read permission; not inferred from name"),
|
|
29780
|
-
"read-permission": optional(String2(), "[generate feature] CLI alias of read_permission"),
|
|
29781
29714
|
rpc: optional(String2(), "[generate feature] Explicit durable RPC capability; must be registered by the host"),
|
|
29782
29715
|
database_source: optional(String2(), "[generate feature] Project-relative database source path"),
|
|
29783
29716
|
database_table: optional(String2(), "[generate feature] Schema-qualified table owned by the feature"),
|
|
29784
29717
|
database_type: optional(String2(), "[generate feature] Exported row type derived from the database source"),
|
|
29785
|
-
"database-source": optional(String2(), "[generate feature] CLI alias of database_source"),
|
|
29786
|
-
"database-table": optional(String2(), "[generate feature] CLI alias of database_table"),
|
|
29787
|
-
"database-type": optional(String2(), "[generate feature] CLI alias of database_type"),
|
|
29788
29718
|
dir: optional(String2(), "[generate] Feature root directory (default: src/features)"),
|
|
29789
29719
|
force: optional(Boolean2(), "[generate] Overwrite a single regular file; resource/feature/controller never overwrite"),
|
|
29790
29720
|
dry_run: optional(Boolean2(), "[generate] Validate and preview the complete change set without writing"),
|
|
29791
|
-
"dry-run": optional(Boolean2(), "[generate] CLI alias of dry_run"),
|
|
29792
29721
|
register_in: optional(String2(), "[generate] Existing parent module file for module/resource/feature; uses the compiler AST editor"),
|
|
29793
|
-
"register-in": optional(String2(), "[generate] CLI alias of register_in"),
|
|
29794
29722
|
root: optional(String2(), "[dev/plan/build/release/compile/check] Project directory containing supacloud.config.ts (default: current directory)"),
|
|
29795
29723
|
include: optional(String2(), "[dev/compile/check] Comma-separated glob patterns for source files"),
|
|
29796
29724
|
out_dir: optional(String2(), "[dev/compile/plan/build/export-tools] Output directory (default: configured outDir)"),
|
|
29797
29725
|
strict: optional(Boolean2(), "[dev/compile/check] Promote warnings to errors"),
|
|
29798
|
-
format: optional(stringEnum2(["text", "json"]), "[
|
|
29799
|
-
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)"),
|
|
29800
29727
|
profile: optional(stringEnum2(["fast", "integration"]), "[dev] Run dev or dev:integration; integration verifies an explicit loopback database"),
|
|
29801
29728
|
once: optional(Boolean2(), "[dev] Validate and report once without watching"),
|
|
29802
29729
|
watch: optional(Boolean2(), "[dev] Watch for changes (default: true; --once disables)"),
|
|
29803
29730
|
database_url: optional(String2(), "[dev] Explicit loopback integration URL; watch/once only inspect its shape"),
|
|
29804
|
-
"database-url": optional(String2(), "[dev] CLI alias of database_url"),
|
|
29805
29731
|
target: optional(String2(), "[explain/context/verify-plan] Provider class name / token name / command name / job name / module name"),
|
|
29806
29732
|
events: optional(String2(), "[context] Project-relative execution metadata JSON or generated JSON-lines file"),
|
|
29807
29733
|
request_id: optional(String2(), "[context] Opaque request ID or Workflow run ID selected from --events"),
|
|
29808
|
-
"request-id": optional(String2(), "[context] CLI alias of request_id"),
|
|
29809
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"),
|
|
29810
29737
|
write: optional(Boolean2(), "[fix] Write the fix to disk (default: preview only)"),
|
|
29811
29738
|
skip_build: optional(Boolean2(), "[release] Reuse --manifest_path instead of running the local delivery build")
|
|
29812
29739
|
}, (request) => runAppTool(request, options));
|
|
29813
29740
|
}
|
|
29814
|
-
var APP_ALIAS_ACTIONS = ["generate", "compile", "check", "graph", "explain", "context", "doctor", "fix"];
|
|
29815
|
-
function registerAppAliases(server) {
|
|
29816
|
-
const schema = {
|
|
29817
|
-
kind: optional(stringEnum2(["module", "command", "query", "controller", "job", "contract", "resource", "feature"]), "[generate] Scaffold kind"),
|
|
29818
|
-
name: optional(String2(), "[generate] Object name"),
|
|
29819
|
-
module: optional(String2(), "[generate] Target feature module"),
|
|
29820
|
-
permission: optional(String2(), "[generate feature] Explicit approval permission; not inferred from name"),
|
|
29821
|
-
read_permission: optional(String2(), "[generate feature] Explicit read permission; not inferred from name"),
|
|
29822
|
-
"read-permission": optional(String2(), "[generate feature] CLI alias of read_permission"),
|
|
29823
|
-
rpc: optional(String2(), "[generate feature] Explicit durable RPC capability; must be registered by the host"),
|
|
29824
|
-
database_source: optional(String2(), "[generate feature] Project-relative database source path"),
|
|
29825
|
-
database_table: optional(String2(), "[generate feature] Schema-qualified table owned by the feature"),
|
|
29826
|
-
database_type: optional(String2(), "[generate feature] Exported row type derived from the database source"),
|
|
29827
|
-
"database-source": optional(String2(), "[generate feature] CLI alias of database_source"),
|
|
29828
|
-
"database-table": optional(String2(), "[generate feature] CLI alias of database_table"),
|
|
29829
|
-
"database-type": optional(String2(), "[generate feature] CLI alias of database_type"),
|
|
29830
|
-
dir: optional(String2(), "[generate] Feature root directory (default: src/features)"),
|
|
29831
|
-
force: optional(Boolean2(), "[generate] Overwrite a single regular file; resource/feature/controller never overwrite"),
|
|
29832
|
-
dry_run: optional(Boolean2(), "[generate] Validate and preview the complete change set without writing"),
|
|
29833
|
-
"dry-run": optional(Boolean2(), "[generate] CLI alias of dry_run"),
|
|
29834
|
-
register_in: optional(String2(), "[generate] Existing parent module file for module/resource/feature; uses the compiler AST editor"),
|
|
29835
|
-
"register-in": optional(String2(), "[generate] CLI alias of register_in"),
|
|
29836
|
-
root: optional(String2(), "Project directory containing supacloud.config.ts (default: current directory)"),
|
|
29837
|
-
include: optional(String2(), "[compile/check] Comma-separated glob patterns for source files"),
|
|
29838
|
-
out_dir: optional(String2(), "[compile/export-tools] Output directory (default: <root>/generated)"),
|
|
29839
|
-
strict: optional(Boolean2(), "[compile/check] Promote warnings to errors"),
|
|
29840
|
-
format: optional(stringEnum2(["text", "json"]), "Output format (default: text)"),
|
|
29841
|
-
target: optional(String2(), "[explain/context] Provider class name / token name / command name / job name / module name"),
|
|
29842
|
-
events: optional(String2(), "[context] Project-relative execution metadata JSON or generated JSON-lines file"),
|
|
29843
|
-
request_id: optional(String2(), "[context] Opaque request ID or Workflow run ID selected from --events"),
|
|
29844
|
-
"request-id": optional(String2(), "[context] CLI alias of request_id"),
|
|
29845
|
-
fix: optional(String2(), "[fix] Path to a DiagnosticFix JSON file produced by `doctor --format json`"),
|
|
29846
|
-
write: optional(Boolean2(), "[fix] Write the fix to disk (default: preview only)")
|
|
29847
|
-
};
|
|
29848
|
-
for (const action of APP_ALIAS_ACTIONS) {
|
|
29849
|
-
registerTool(server, action, `Top-level alias of \`app ${action}\`.`, schema, (request) => runAppTool({ ...request, action }));
|
|
29850
|
-
}
|
|
29851
|
-
}
|
|
29852
29741
|
|
|
29853
29742
|
// src/shared/tools/db-governance-tools.ts
|
|
29854
29743
|
import { existsSync as existsSync13 } from "node:fs";
|
|
@@ -30291,7 +30180,7 @@ async function runOfficial(root, workdir, args, environment, options) {
|
|
|
30291
30180
|
if (code !== 0)
|
|
30292
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.`);
|
|
30293
30182
|
}
|
|
30294
|
-
async function initializeCandidate(stage, majorVersion =
|
|
30183
|
+
async function initializeCandidate(stage, majorVersion = 18) {
|
|
30295
30184
|
if (![15, 16, 17, 18].includes(majorVersion))
|
|
30296
30185
|
throw new Error("Select a supported PostgreSQL major version (15-18)");
|
|
30297
30186
|
await mkdir6(join15(stage, "supabase/migrations"), { recursive: true });
|
|
@@ -30718,25 +30607,84 @@ async function runModuleCheckLite(args, root, spawn, liteBinary) {
|
|
|
30718
30607
|
`);
|
|
30719
30608
|
return textResult2(output || "lite db check 无输出", result.exitCode !== 0);
|
|
30720
30609
|
}
|
|
30721
|
-
|
|
30722
|
-
|
|
30723
|
-
|
|
30724
|
-
|
|
30725
|
-
|
|
30726
|
-
|
|
30727
|
-
|
|
30728
|
-
|
|
30729
|
-
|
|
30730
|
-
|
|
30731
|
-
|
|
30732
|
-
|
|
30733
|
-
|
|
30734
|
-
|
|
30735
|
-
|
|
30736
|
-
|
|
30737
|
-
|
|
30738
|
-
|
|
30739
|
-
|
|
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);
|
|
30670
|
+
if (args.lite)
|
|
30671
|
+
return runModuleCheckLite(args, root, spawn, liteBinary);
|
|
30672
|
+
const databaseUrl = args.database_url?.trim() || environment.DATABASE_URL?.trim();
|
|
30673
|
+
if (!databaseUrl) {
|
|
30674
|
+
throw new Error("db module_check requires --database_url、环境变量 DATABASE_URL、--lite 或 --remote");
|
|
30675
|
+
}
|
|
30676
|
+
const modules = await loadDatabaseModules(root, args.module_file);
|
|
30677
|
+
const schemas = parseSchemas(args.schema);
|
|
30678
|
+
const client = new Bun.SQL(databaseUrl);
|
|
30679
|
+
const executor = {
|
|
30680
|
+
async query(sql, params) {
|
|
30681
|
+
return await client.unsafe(sql, params ?? []);
|
|
30682
|
+
}
|
|
30683
|
+
};
|
|
30684
|
+
try {
|
|
30685
|
+
const reports = [];
|
|
30686
|
+
for (const module of modules) {
|
|
30687
|
+
reports.push(await runModuleCheck(module, executor, schemas));
|
|
30740
30688
|
}
|
|
30741
30689
|
const hasError = reports.some((report) => !report.ok);
|
|
30742
30690
|
return textResult2(reports.map(formatReconcileReport).join(`
|
|
@@ -30761,12 +30709,13 @@ function registerDbGovernanceTools(server, options = {}) {
|
|
|
30761
30709
|
database_url: optional(String2(), "[module_check] Postgres connection URL (default: DATABASE_URL)"),
|
|
30762
30710
|
schema: optional(String2(), "[module_check] Comma-separated schemas to inspect (default: public)"),
|
|
30763
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"),
|
|
30764
30713
|
project_dir: optional(String2(), "[module_check] Lite project directory (with --lite; default: --root)"),
|
|
30765
30714
|
schema_dir: optional(String2(), "[diff] Maintained declarative SQL directory (default: supabase/schemas)"),
|
|
30766
|
-
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"),
|
|
30767
30716
|
out: optional(String2(), "[reverse/diff] Candidate output path (reverse requires a new directory)"),
|
|
30768
30717
|
dir: optional(String2(), "[diff/plan/apply] Reviewed forward migration directory"),
|
|
30769
|
-
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)"),
|
|
30770
30719
|
approved_digest: optional(String2(), "[apply] Exact digest returned by db plan"),
|
|
30771
30720
|
application_role: optional(String2(), "[role_sql] Application database role"),
|
|
30772
30721
|
migration_role: optional(String2(), "[role_sql] Privileged migration role"),
|
|
@@ -30781,12 +30730,20 @@ function registerDbGovernanceTools(server, options = {}) {
|
|
|
30781
30730
|
case "explain":
|
|
30782
30731
|
return runExplain2(request, root);
|
|
30783
30732
|
case "module_check":
|
|
30784
|
-
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
|
+
});
|
|
30785
30743
|
case "reverse":
|
|
30786
30744
|
case "diff":
|
|
30787
30745
|
case "plan":
|
|
30788
30746
|
case "apply":
|
|
30789
|
-
case "role_check":
|
|
30790
30747
|
case "role_sql":
|
|
30791
30748
|
return runDbWorkflow({ ...request, action: request.action }, root, {
|
|
30792
30749
|
environment,
|
|
@@ -32126,11 +32083,11 @@ function registerReleaseTools(server, http, options = {}) {
|
|
|
32126
32083
|
}
|
|
32127
32084
|
|
|
32128
32085
|
// src/shared/tools/deploy-tools.ts
|
|
32129
|
-
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";
|
|
32130
32087
|
import { spawn as spawn2 } from "node:child_process";
|
|
32131
32088
|
import { createHash as createHash11 } from "node:crypto";
|
|
32132
32089
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
32133
|
-
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";
|
|
32134
32091
|
|
|
32135
32092
|
// src/shared/tools/project-deployment.ts
|
|
32136
32093
|
async function requireProjectDeploymentProtocol(http, projectRef, environmentId) {
|
|
@@ -32205,6 +32162,364 @@ async function readProjectDeploymentStatus(http, projectRef, environmentId, depl
|
|
|
32205
32162
|
}
|
|
32206
32163
|
}
|
|
32207
32164
|
|
|
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
|
+
|
|
32208
32523
|
// src/shared/tools/deploy-tools.ts
|
|
32209
32524
|
var MAX_SOURCE_BYTES = 256 * 1024 * 1024;
|
|
32210
32525
|
var PUBLIC_BUILD_ENV_PREFIXES = ["PUBLIC_", "VITE_", "NEXT_PUBLIC_", "NUXT_PUBLIC_", "ASTRO_PUBLIC_"];
|
|
@@ -32213,18 +32528,21 @@ function isPublicBuildEnv(name) {
|
|
|
32213
32528
|
return PUBLIC_BUILD_ENV_PREFIXES.some((prefix) => name.startsWith(prefix));
|
|
32214
32529
|
}
|
|
32215
32530
|
function isProtectedBuildEnv(name) {
|
|
32216
|
-
return name.startsWith("SUPACLOUD_") || name === "SUPABASE_SERVICE_ROLE_KEY" || name === "SUPABASE_ACCESS_TOKEN" || name === "
|
|
32531
|
+
return name.startsWith("SUPACLOUD_") || name === "SUPABASE_SERVICE_ROLE_KEY" || name === "SUPABASE_ACCESS_TOKEN" || name === "MANAGEMENT_API_TOKEN";
|
|
32217
32532
|
}
|
|
32218
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)"),
|
|
32219
32537
|
ref: optional(String2(), "Project ref (defaults to the linked project)"),
|
|
32220
32538
|
cwd: optional(String2(), "Project directory (default: current directory)"),
|
|
32221
32539
|
skip_build: optional(Boolean2(), "Use an existing output directory without building"),
|
|
32222
32540
|
assets_only: optional(Boolean2(), "Publish only configured static assets; leave backend Functions, schedules and migrations unchanged"),
|
|
32223
32541
|
backend: optional(Boolean2(), "Publish the application backend independently; do not build or activate frontend assets"),
|
|
32224
32542
|
status: optional(String2({ format: "uuid" }), "Read a backend deployment status without publishing"),
|
|
32225
|
-
|
|
32543
|
+
format: optional(stringEnum2(["text", "json"]), "Output format (default: text)")
|
|
32226
32544
|
};
|
|
32227
|
-
function
|
|
32545
|
+
function record6(candidate) {
|
|
32228
32546
|
return candidate && typeof candidate === "object" && !Array.isArray(candidate) ? candidate : null;
|
|
32229
32547
|
}
|
|
32230
32548
|
function requiredString(candidate, field) {
|
|
@@ -32234,7 +32552,7 @@ function requiredString(candidate, field) {
|
|
|
32234
32552
|
return candidate.trim();
|
|
32235
32553
|
}
|
|
32236
32554
|
function frontendDeployment(candidate) {
|
|
32237
|
-
const value =
|
|
32555
|
+
const value = record6(candidate);
|
|
32238
32556
|
if (!value)
|
|
32239
32557
|
throw new Error("Invalid frontend deployment response");
|
|
32240
32558
|
return {
|
|
@@ -32248,7 +32566,7 @@ function frontendDeployment(candidate) {
|
|
|
32248
32566
|
}
|
|
32249
32567
|
async function readJson(path) {
|
|
32250
32568
|
try {
|
|
32251
|
-
return JSON.parse(await
|
|
32569
|
+
return JSON.parse(await readFile7(path, "utf8"));
|
|
32252
32570
|
} catch (error) {
|
|
32253
32571
|
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
32254
32572
|
return null;
|
|
@@ -32256,15 +32574,15 @@ async function readJson(path) {
|
|
|
32256
32574
|
}
|
|
32257
32575
|
}
|
|
32258
32576
|
async function findDeployConfigRoot(startDirectory, environmentName) {
|
|
32259
|
-
let directory =
|
|
32577
|
+
let directory = resolve26(startDirectory);
|
|
32260
32578
|
while (true) {
|
|
32261
32579
|
const environmentFile = environmentName ? `.env.supacloud.${environmentName}` : null;
|
|
32262
|
-
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))) {
|
|
32263
32581
|
return directory;
|
|
32264
32582
|
}
|
|
32265
|
-
const parent =
|
|
32583
|
+
const parent = dirname14(directory);
|
|
32266
32584
|
if (parent === directory)
|
|
32267
|
-
return
|
|
32585
|
+
return resolve26(startDirectory);
|
|
32268
32586
|
directory = parent;
|
|
32269
32587
|
}
|
|
32270
32588
|
}
|
|
@@ -32304,7 +32622,7 @@ function optionalStringArray(value, field) {
|
|
|
32304
32622
|
});
|
|
32305
32623
|
}
|
|
32306
32624
|
function deployTargetConfig(candidate, name) {
|
|
32307
|
-
const value =
|
|
32625
|
+
const value = record6(candidate);
|
|
32308
32626
|
if (!value || value.type !== "frontend" && value.type !== "edge_function") {
|
|
32309
32627
|
throw new Error(`supacloud.json targets.${name}.type must be 'frontend' or 'edge_function'`);
|
|
32310
32628
|
}
|
|
@@ -32329,14 +32647,14 @@ function deployTargetConfig(candidate, name) {
|
|
|
32329
32647
|
};
|
|
32330
32648
|
}
|
|
32331
32649
|
async function readDeployConfig(configRoot) {
|
|
32332
|
-
const configPath =
|
|
32650
|
+
const configPath = join19(configRoot, "supacloud.json");
|
|
32333
32651
|
const candidate = await readJson(configPath);
|
|
32334
32652
|
if (candidate === null)
|
|
32335
32653
|
return {};
|
|
32336
|
-
const config =
|
|
32337
|
-
const frontend =
|
|
32338
|
-
const application =
|
|
32339
|
-
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);
|
|
32340
32658
|
if (!config || config.frontend !== undefined && !frontend || config.application !== undefined && !application || config.targets !== undefined && !targetValues) {
|
|
32341
32659
|
throw new Error("supacloud.json must contain object-valued 'application', 'frontend' or 'targets' configuration");
|
|
32342
32660
|
}
|
|
@@ -32376,16 +32694,16 @@ async function readDeployConfig(configRoot) {
|
|
|
32376
32694
|
};
|
|
32377
32695
|
}
|
|
32378
32696
|
async function packageName(projectDirectory) {
|
|
32379
|
-
const candidate =
|
|
32697
|
+
const candidate = record6(await readJson(join19(projectDirectory, "package.json")));
|
|
32380
32698
|
return typeof candidate?.name === "string" && candidate.name.trim() ? candidate.name.trim() : null;
|
|
32381
32699
|
}
|
|
32382
32700
|
async function pathExists(path) {
|
|
32383
32701
|
return access(path).then(() => true, () => false);
|
|
32384
32702
|
}
|
|
32385
32703
|
async function hasProjectReleaseManifest(root) {
|
|
32386
|
-
if (await pathExists(
|
|
32704
|
+
if (await pathExists(join19(root, "deploy-manifest.json")))
|
|
32387
32705
|
return true;
|
|
32388
|
-
const directory = await opendir(
|
|
32706
|
+
const directory = await opendir(join19(root, "supacloud")).catch((error) => {
|
|
32389
32707
|
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
32390
32708
|
return null;
|
|
32391
32709
|
throw error;
|
|
@@ -32396,28 +32714,28 @@ async function hasProjectReleaseManifest(root) {
|
|
|
32396
32714
|
for await (const entry of directory) {
|
|
32397
32715
|
if (++entries > 128)
|
|
32398
32716
|
throw new Error("Too many SupaCloud project roots to resolve a deployment");
|
|
32399
|
-
if ((entry.isDirectory() || entry.isSymbolicLink()) && await pathExists(
|
|
32717
|
+
if ((entry.isDirectory() || entry.isSymbolicLink()) && await pathExists(join19(root, "supacloud", entry.name, "deploy-manifest.json")))
|
|
32400
32718
|
return true;
|
|
32401
32719
|
}
|
|
32402
32720
|
return false;
|
|
32403
32721
|
}
|
|
32404
32722
|
async function defaultBuildCommand(projectDirectory, configRoot) {
|
|
32405
|
-
const packageJson =
|
|
32406
|
-
const scripts =
|
|
32723
|
+
const packageJson = record6(await readJson(join19(projectDirectory, "package.json")));
|
|
32724
|
+
const scripts = record6(packageJson?.scripts);
|
|
32407
32725
|
if (typeof scripts?.build !== "string" || !scripts.build.trim()) {
|
|
32408
32726
|
throw new Error("No build command was configured and package.json has no build script");
|
|
32409
32727
|
}
|
|
32410
32728
|
let directory = projectDirectory;
|
|
32411
32729
|
while (true) {
|
|
32412
|
-
if (await pathExists(
|
|
32730
|
+
if (await pathExists(join19(directory, "bun.lock")) || await pathExists(join19(directory, "bun.lockb")))
|
|
32413
32731
|
return "bun run build";
|
|
32414
|
-
if (await pathExists(
|
|
32732
|
+
if (await pathExists(join19(directory, "pnpm-lock.yaml")))
|
|
32415
32733
|
return "pnpm run build";
|
|
32416
|
-
if (await pathExists(
|
|
32734
|
+
if (await pathExists(join19(directory, "yarn.lock")))
|
|
32417
32735
|
return "yarn build";
|
|
32418
32736
|
if (directory === configRoot)
|
|
32419
32737
|
break;
|
|
32420
|
-
const parent =
|
|
32738
|
+
const parent = dirname14(directory);
|
|
32421
32739
|
if (parent === directory || !directory.startsWith(`${configRoot}${sep5}`))
|
|
32422
32740
|
break;
|
|
32423
32741
|
directory = parent;
|
|
@@ -32425,7 +32743,7 @@ async function defaultBuildCommand(projectDirectory, configRoot) {
|
|
|
32425
32743
|
return "npm run build";
|
|
32426
32744
|
}
|
|
32427
32745
|
function deploymentList(candidate) {
|
|
32428
|
-
const values = Array.isArray(candidate) ? candidate : Array.isArray(
|
|
32746
|
+
const values = Array.isArray(candidate) ? candidate : Array.isArray(record6(candidate)?.deployments) ? record6(candidate)?.deployments : null;
|
|
32429
32747
|
if (!values)
|
|
32430
32748
|
throw new Error("Invalid frontend deployment list response");
|
|
32431
32749
|
return values.map(frontendDeployment);
|
|
@@ -32485,9 +32803,9 @@ function selectFrontendTarget(configRoot, config) {
|
|
|
32485
32803
|
throw new Error(`Multiple frontend deploy targets found (${targets.map(([name]) => name).sort().join(", ")}). Set defaultTarget to the Web target`);
|
|
32486
32804
|
}
|
|
32487
32805
|
function targetRoot(configRoot, target, name) {
|
|
32488
|
-
const root =
|
|
32489
|
-
const prefix = `${
|
|
32490
|
-
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)) {
|
|
32491
32809
|
throw new Error(`Deploy target '${name}' root must stay inside the repository root`);
|
|
32492
32810
|
}
|
|
32493
32811
|
return root;
|
|
@@ -32505,8 +32823,8 @@ function defaultOutputDirectory(framework) {
|
|
|
32505
32823
|
}
|
|
32506
32824
|
}
|
|
32507
32825
|
function resolveInsideProject(projectDirectory, candidate, allowRoot = false) {
|
|
32508
|
-
const resolved =
|
|
32509
|
-
const projectRoot =
|
|
32826
|
+
const resolved = resolve26(projectDirectory, candidate);
|
|
32827
|
+
const projectRoot = resolve26(projectDirectory);
|
|
32510
32828
|
const projectPrefix = `${projectRoot}${sep5}`;
|
|
32511
32829
|
if (!allowRoot && resolved === projectRoot || resolved !== projectRoot && !resolved.startsWith(projectPrefix)) {
|
|
32512
32830
|
throw new Error("Frontend output directory must stay inside the project directory");
|
|
@@ -32560,7 +32878,7 @@ async function collectArchiveFiles(root) {
|
|
|
32560
32878
|
entries.push(entry);
|
|
32561
32879
|
entries.sort((left, right) => left.name.localeCompare(right.name, "en"));
|
|
32562
32880
|
for (const entry of entries) {
|
|
32563
|
-
const path =
|
|
32881
|
+
const path = join19(directory, entry.name);
|
|
32564
32882
|
const stats = await lstat5(path);
|
|
32565
32883
|
if (stats.isSymbolicLink())
|
|
32566
32884
|
throw new Error(`Frontend output cannot contain symbolic links: ${path}`);
|
|
@@ -32579,7 +32897,7 @@ async function collectArchiveFiles(root) {
|
|
|
32579
32897
|
const files = new Map;
|
|
32580
32898
|
const digests = [];
|
|
32581
32899
|
for (const path of paths) {
|
|
32582
|
-
const data = new Uint8Array(await
|
|
32900
|
+
const data = new Uint8Array(await readFile7(path));
|
|
32583
32901
|
bytes += data.byteLength;
|
|
32584
32902
|
if (bytes > MAX_SOURCE_BYTES)
|
|
32585
32903
|
throw new Error(`Frontend output exceeds ${MAX_SOURCE_BYTES} bytes`);
|
|
@@ -32598,11 +32916,11 @@ async function collectArchiveFiles(root) {
|
|
|
32598
32916
|
return { files, bytes, count: paths.length, treeSha256: tree.digest("hex") };
|
|
32599
32917
|
}
|
|
32600
32918
|
async function createCollectedFrontendArchive(outputDirectory, collected) {
|
|
32601
|
-
const temporaryDirectory = await mkdtemp2(
|
|
32602
|
-
const archivePath =
|
|
32919
|
+
const temporaryDirectory = await mkdtemp2(join19(tmpdir3(), "supacloud-deploy-"));
|
|
32920
|
+
const archivePath = join19(temporaryDirectory, `${basename8(outputDirectory)}.tar.zst`);
|
|
32603
32921
|
try {
|
|
32604
32922
|
const archiveBytes = await createFrontendTarZstd(collected.files);
|
|
32605
|
-
await
|
|
32923
|
+
await writeFile7(archivePath, archiveBytes);
|
|
32606
32924
|
const sha256 = createHash11("sha256").update(archiveBytes).digest("hex");
|
|
32607
32925
|
return {
|
|
32608
32926
|
archivePath,
|
|
@@ -32647,7 +32965,13 @@ async function publishConfiguredFrontend(http, projectRef, configRoot, skipBuild
|
|
|
32647
32965
|
}
|
|
32648
32966
|
function registerDeployTools(server, http, options = {}) {
|
|
32649
32967
|
registerTool(server, "deploy", "Build and publish the linked project with one command", deployToolSchema, async (args) => {
|
|
32650
|
-
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
|
+
}
|
|
32651
32975
|
const projectRef = String(args.ref || options.projectRef || "").trim();
|
|
32652
32976
|
if (!projectRef)
|
|
32653
32977
|
throw new Error("A linked project ref is required. Set SUPACLOUD_PROJECT_REF or pass --ref");
|
|
@@ -32678,7 +33002,7 @@ function registerDeployTools(server, http, options = {}) {
|
|
|
32678
33002
|
throw new Error("Static asset deployment requires SUPACLOUD_ENV");
|
|
32679
33003
|
}
|
|
32680
33004
|
await requireProjectDeploymentProtocol(http, projectRef, environmentId);
|
|
32681
|
-
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);
|
|
32682
33006
|
if (!result)
|
|
32683
33007
|
throw new Error("Static asset deployment did not produce a receipt");
|
|
32684
33008
|
return result;
|
|
@@ -32713,7 +33037,7 @@ function registerDeployTools(server, http, options = {}) {
|
|
|
32713
33037
|
...configurationId ? { configuration_id: configurationId } : {},
|
|
32714
33038
|
...application.manifestPath ? { manifest_path: application.manifestPath } : {},
|
|
32715
33039
|
...args.skip_build === true ? { skip_build: true } : {},
|
|
32716
|
-
...args.
|
|
33040
|
+
...args.format === "json" ? { format: "json" } : {}
|
|
32717
33041
|
});
|
|
32718
33042
|
return result;
|
|
32719
33043
|
}
|
|
@@ -32722,11 +33046,11 @@ function registerDeployTools(server, http, options = {}) {
|
|
|
32722
33046
|
}
|
|
32723
33047
|
|
|
32724
33048
|
// src/shared/tools/remote-dev-tools.ts
|
|
32725
|
-
import { existsSync as
|
|
32726
|
-
import { readFile as
|
|
32727
|
-
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";
|
|
32728
33052
|
import { createHash as createHash12 } from "node:crypto";
|
|
32729
|
-
import { join as
|
|
33053
|
+
import { join as join20, resolve as resolve27 } from "node:path";
|
|
32730
33054
|
import { compileProject as compileProject2 } from "@supacloud/compiler";
|
|
32731
33055
|
var SAFE_TOKEN = /^[A-Za-z0-9._:@/+,-]+$/;
|
|
32732
33056
|
var SAFE_REMOTE_ROOT = /^\/(?:[A-Za-z0-9._-]+\/)*[A-Za-z0-9._-]+\/?$/;
|
|
@@ -32743,7 +33067,6 @@ var remoteDevToolSchema = {
|
|
|
32743
33067
|
delete: optional(Boolean2(), "Delete remote files absent locally"),
|
|
32744
33068
|
reload: optional(Boolean2(), "Reload the affected target after sync (default: true)"),
|
|
32745
33069
|
interval_ms: optional(Number2(), "Watch debounce interval in milliseconds (default: 300)"),
|
|
32746
|
-
json: optional(Boolean2(), "Emit machine-readable JSON"),
|
|
32747
33070
|
apply: optional(Boolean2(), "Apply generated migrations to the selected test database"),
|
|
32748
33071
|
drizzle_config: optional(String2(), "Drizzle config path"),
|
|
32749
33072
|
migrations_dir: optional(String2(), "Migration directory"),
|
|
@@ -32761,8 +33084,8 @@ async function runProcess(executable, args, cwd) {
|
|
|
32761
33084
|
function resolveDrizzleCommand(root, configured) {
|
|
32762
33085
|
if (configured?.trim())
|
|
32763
33086
|
return configured.trim();
|
|
32764
|
-
const local =
|
|
32765
|
-
return
|
|
33087
|
+
const local = join20(root, "node_modules", ".bin", "drizzle-kit");
|
|
33088
|
+
return existsSync17(local) ? local : "drizzle-kit";
|
|
32766
33089
|
}
|
|
32767
33090
|
function toolFailed(value) {
|
|
32768
33091
|
if (!value || typeof value !== "object")
|
|
@@ -32772,14 +33095,14 @@ function toolFailed(value) {
|
|
|
32772
33095
|
return result.isError === true || content.some((chunk) => chunk !== null && typeof chunk === "object" && ("text" in chunk) && typeof chunk.text === "string" && chunk.text.trimStart().startsWith("❌"));
|
|
32773
33096
|
}
|
|
32774
33097
|
async function migrateDatabase(args, options) {
|
|
32775
|
-
const root =
|
|
33098
|
+
const root = resolve27(String(args.project_dir || options.cwd || process.cwd()));
|
|
32776
33099
|
const projectConfig = await readProjectConfig(root);
|
|
32777
33100
|
const config = projectConfig.dev || {};
|
|
32778
33101
|
const database = config.database || {};
|
|
32779
33102
|
const execute = options.execute || ((command, commandArgs, cwd) => runProcess(command, commandArgs, cwd));
|
|
32780
|
-
const drizzleConfig =
|
|
33103
|
+
const drizzleConfig = resolve27(root, String(args.drizzle_config || database.drizzleConfig || "drizzle.config.ts"));
|
|
32781
33104
|
const migrationsDir = String(args.migrations_dir || database.migrationsDir || "supabase/migrations");
|
|
32782
|
-
if (!
|
|
33105
|
+
if (!existsSync17(drizzleConfig))
|
|
32783
33106
|
throw new Error(`Drizzle config not found: ${drizzleConfig}`);
|
|
32784
33107
|
const generated = await execute(resolveDrizzleCommand(root, typeof args.drizzle_bin === "string" ? args.drizzle_bin : database.drizzleBin), ["generate", "--config", drizzleConfig], root);
|
|
32785
33108
|
if (generated.exitCode !== 0)
|
|
@@ -32797,11 +33120,11 @@ async function migrateDatabase(args, options) {
|
|
|
32797
33120
|
return { ok: true, mode: "dev", action: "migrate", generated: true, applied: true, migrations_dir: migrationsDir, result: applied };
|
|
32798
33121
|
}
|
|
32799
33122
|
async function readProjectConfig(root) {
|
|
32800
|
-
const file =
|
|
32801
|
-
if (!
|
|
33123
|
+
const file = join20(root, "supacloud.json");
|
|
33124
|
+
if (!existsSync17(file))
|
|
32802
33125
|
return {};
|
|
32803
33126
|
try {
|
|
32804
|
-
const parsed = JSON.parse(await
|
|
33127
|
+
const parsed = JSON.parse(await readFile8(file, "utf8"));
|
|
32805
33128
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
32806
33129
|
} catch (error) {
|
|
32807
33130
|
throw new Error(`Invalid supacloud.json: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -32814,9 +33137,9 @@ async function readDevConfig(root) {
|
|
|
32814
33137
|
async function sourceFingerprint(root) {
|
|
32815
33138
|
const files = [];
|
|
32816
33139
|
const visit = async (directory) => {
|
|
32817
|
-
const entries = await
|
|
33140
|
+
const entries = await readdir8(directory, { withFileTypes: true });
|
|
32818
33141
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
32819
|
-
const path =
|
|
33142
|
+
const path = join20(directory, entry.name);
|
|
32820
33143
|
if (entry.isDirectory() && ![".git", "node_modules", "dist", "generated", ".supacloud"].includes(entry.name))
|
|
32821
33144
|
await visit(path);
|
|
32822
33145
|
else if (entry.isFile())
|
|
@@ -32827,7 +33150,7 @@ async function sourceFingerprint(root) {
|
|
|
32827
33150
|
const hash = createHash12("sha256");
|
|
32828
33151
|
for (const file of files) {
|
|
32829
33152
|
hash.update(file.slice(root.length).replace(/\\/g, "/"));
|
|
32830
|
-
hash.update(await
|
|
33153
|
+
hash.update(await readFile8(file));
|
|
32831
33154
|
}
|
|
32832
33155
|
return hash.digest("hex");
|
|
32833
33156
|
}
|
|
@@ -32844,19 +33167,19 @@ function remoteRoot(value) {
|
|
|
32844
33167
|
}
|
|
32845
33168
|
function targetDirectory(root, target, functionSlug, config = {}) {
|
|
32846
33169
|
if (target === "db")
|
|
32847
|
-
return
|
|
33170
|
+
return join20(root, "supabase", "migrations");
|
|
32848
33171
|
if (target === "functions") {
|
|
32849
33172
|
const targets = config["targets"];
|
|
32850
33173
|
const entries = targets && typeof targets === "object" ? Object.values(targets) : [];
|
|
32851
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));
|
|
32852
|
-
const base = match?.root ?
|
|
32853
|
-
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;
|
|
32854
33177
|
}
|
|
32855
33178
|
if (target === "frontend") {
|
|
32856
33179
|
const targets = config["targets"];
|
|
32857
33180
|
const entries = targets && typeof targets === "object" ? Object.values(targets) : [];
|
|
32858
33181
|
const match = entries.find((entry) => entry !== null && typeof entry === "object" && ("type" in entry) && entry.type === "frontend" && (!("root" in entry) || typeof entry.root === "string"));
|
|
32859
|
-
return match?.root ?
|
|
33182
|
+
return match?.root ? resolve27(root, match.root) : join20(root, "apps", "web");
|
|
32860
33183
|
}
|
|
32861
33184
|
return root;
|
|
32862
33185
|
}
|
|
@@ -32895,10 +33218,10 @@ function reloadCommand(config, target, projectRef, functionSlug) {
|
|
|
32895
33218
|
return [...command.split(" "), "--project-ref", safeToken(projectRef || "test", "project_ref"), "--target", targetConfigPath(target), ...functionSlug ? ["--function", safeToken(functionSlug, "function")] : []];
|
|
32896
33219
|
}
|
|
32897
33220
|
function sshArgs(connection, remoteCommand) {
|
|
32898
|
-
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];
|
|
32899
33222
|
}
|
|
32900
33223
|
function rsyncArgs(connection, source, destination, excludes, deleteRemote) {
|
|
32901
|
-
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`];
|
|
32902
33225
|
if (deleteRemote)
|
|
32903
33226
|
args.push("--delete-delay");
|
|
32904
33227
|
for (const exclude of excludes) {
|
|
@@ -32910,17 +33233,17 @@ function rsyncArgs(connection, source, destination, excludes, deleteRemote) {
|
|
|
32910
33233
|
return args;
|
|
32911
33234
|
}
|
|
32912
33235
|
async function syncOnce(args, options) {
|
|
32913
|
-
const root =
|
|
33236
|
+
const root = resolve27(String(args.project_dir || options.cwd || process.cwd()));
|
|
32914
33237
|
const projectConfig = await readProjectConfig(root);
|
|
32915
33238
|
const config = projectConfig.dev || {};
|
|
32916
33239
|
const target = String(args.target || "project");
|
|
32917
33240
|
const source = targetDirectory(root, target, typeof args.function === "string" ? args.function : undefined, projectConfig);
|
|
32918
|
-
if (!
|
|
33241
|
+
if (!existsSync17(source))
|
|
32919
33242
|
throw new Error(`Dev source directory not found: ${source}`);
|
|
32920
33243
|
let compiled = false;
|
|
32921
33244
|
if (config.compile === true && target !== "db") {
|
|
32922
|
-
const compileRoot =
|
|
32923
|
-
const compileOutDir =
|
|
33245
|
+
const compileRoot = resolve27(root, config.compileRoot || ".");
|
|
33246
|
+
const compileOutDir = resolve27(root, config.compileOutDir || "generated");
|
|
32924
33247
|
const compilation = await compileProject2({
|
|
32925
33248
|
rootDir: compileRoot,
|
|
32926
33249
|
outDir: compileOutDir,
|
|
@@ -32958,7 +33281,7 @@ function registerRemoteDevTools(server, options = {}) {
|
|
|
32958
33281
|
if (["production", "prod"].includes((options.environment || "").toLowerCase()))
|
|
32959
33282
|
throw new Error("Remote dev mode is forbidden for production environments");
|
|
32960
33283
|
if (args.action === "status") {
|
|
32961
|
-
const root =
|
|
33284
|
+
const root = resolve27(String(args.project_dir || options.cwd || process.cwd()));
|
|
32962
33285
|
const config = await readDevConfig(root);
|
|
32963
33286
|
const connection = connectionArgs(options, config, args);
|
|
32964
33287
|
const execute = options.execute || ((command, commandArgs, cwd) => runProcess(command, commandArgs, cwd));
|
|
@@ -32972,7 +33295,7 @@ function registerRemoteDevTools(server, options = {}) {
|
|
|
32972
33295
|
return { content: [{ type: "text", text: JSON.stringify(await migrateDatabase(args, options), null, 2) }] };
|
|
32973
33296
|
}
|
|
32974
33297
|
if (args.action === "watch") {
|
|
32975
|
-
const root =
|
|
33298
|
+
const root = resolve27(String(args.project_dir || options.cwd || process.cwd()));
|
|
32976
33299
|
const interval = Math.max(100, Math.min(1e4, Number(args.interval_ms || 300)));
|
|
32977
33300
|
let fingerprint = "";
|
|
32978
33301
|
let lastSync = null;
|
|
@@ -32992,215 +33315,19 @@ function registerRemoteDevTools(server, options = {}) {
|
|
|
32992
33315
|
}
|
|
32993
33316
|
|
|
32994
33317
|
// src/shared/tools/developer-experience-tools.ts
|
|
32995
|
-
import { existsSync as
|
|
32996
|
-
import { mkdir as
|
|
32997
|
-
import { dirname as
|
|
32998
|
-
|
|
32999
|
-
// src/shared/context.ts
|
|
33000
|
-
import { existsSync as existsSync16, readFileSync as readFileSync12 } from "node:fs";
|
|
33001
|
-
import { homedir as homedir3 } from "node:os";
|
|
33002
|
-
import { resolve as resolve26 } from "node:path";
|
|
33003
|
-
var CORE_CONTEXT_KEYS2 = [
|
|
33004
|
-
"SUPABASE_URL",
|
|
33005
|
-
"SUPABASE_SERVICE_ROLE_KEY",
|
|
33006
|
-
"SUPACLOUD_API_URL",
|
|
33007
|
-
"SUPACLOUD_MANAGEMENT_API_URL",
|
|
33008
|
-
"MANAGEMENT_API_URL",
|
|
33009
|
-
"SUPACLOUD_API_TOKEN",
|
|
33010
|
-
"SUPACLOUD_PROJECT_REF",
|
|
33011
|
-
"X_PROJECT_REF",
|
|
33012
|
-
"SUPACLOUD_HOST",
|
|
33013
|
-
"SUPACLOUD_APPLICATION_ID",
|
|
33014
|
-
"SUPACLOUD_APPLICATION_ENVIRONMENT_ID",
|
|
33015
|
-
"SUPACLOUD_APPLICATION_CONFIGURATION_PATH",
|
|
33016
|
-
"SUPACLOUD_APPLICATION_CONFIGURATION_ID",
|
|
33017
|
-
"SUPACLOUD_APPLICATION_MANIFEST_PATH"
|
|
33018
|
-
];
|
|
33019
|
-
function unquotedEnvValue2(rawValue) {
|
|
33020
|
-
const value = rawValue.trim();
|
|
33021
|
-
const quote = value[0];
|
|
33022
|
-
return quote && (quote === '"' || quote === "'") && value.endsWith(quote) ? value.slice(1, -1) : value;
|
|
33023
|
-
}
|
|
33024
|
-
function parseEnvFile2(contents) {
|
|
33025
|
-
const values = {};
|
|
33026
|
-
for (const rawLine of contents.split(/\r?\n/)) {
|
|
33027
|
-
const line = rawLine.trim();
|
|
33028
|
-
if (!line || line.startsWith("#"))
|
|
33029
|
-
continue;
|
|
33030
|
-
const match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
|
|
33031
|
-
if (!match)
|
|
33032
|
-
continue;
|
|
33033
|
-
values[match[1]] = unquotedEnvValue2(match[2]);
|
|
33034
|
-
}
|
|
33035
|
-
return values;
|
|
33036
|
-
}
|
|
33037
|
-
function readEnvFile2(path, required) {
|
|
33038
|
-
if (!existsSync16(path)) {
|
|
33039
|
-
if (required)
|
|
33040
|
-
throw new Error(`SupaCloud environment file not found: ${path}`);
|
|
33041
|
-
return {};
|
|
33042
|
-
}
|
|
33043
|
-
try {
|
|
33044
|
-
return parseEnvFile2(readFileSync12(path, "utf8"));
|
|
33045
|
-
} catch (error) {
|
|
33046
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
33047
|
-
throw new Error(`Failed to read SupaCloud environment file ${path}: ${message}`);
|
|
33048
|
-
}
|
|
33049
|
-
}
|
|
33050
|
-
function canonicalApiOrigin2(value) {
|
|
33051
|
-
const candidate = value.trim();
|
|
33052
|
-
if (!candidate)
|
|
33053
|
-
return "";
|
|
33054
|
-
try {
|
|
33055
|
-
const url = new URL(candidate);
|
|
33056
|
-
const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
|
|
33057
|
-
const allowedProtocol = url.protocol === "https:" || url.protocol === "http:" && loopback;
|
|
33058
|
-
const exactOrigin = candidate === url.origin || candidate === `${url.origin}/`;
|
|
33059
|
-
return allowedProtocol && !url.username && !url.password && exactOrigin && url.pathname === "/" && !url.search && !url.hash ? url.origin : "";
|
|
33060
|
-
} catch (error) {
|
|
33061
|
-
if (error instanceof TypeError)
|
|
33062
|
-
return "";
|
|
33063
|
-
throw error;
|
|
33064
|
-
}
|
|
33065
|
-
}
|
|
33066
|
-
function hostFromUrl2(value) {
|
|
33067
|
-
return value ? new URL(value).hostname : "";
|
|
33068
|
-
}
|
|
33069
|
-
function inferProjectRefFromSupabaseUrl2(value) {
|
|
33070
|
-
if (!value)
|
|
33071
|
-
return "";
|
|
33072
|
-
return new URL(value).hostname.match(/^([a-z0-9-]+)\.api\./i)?.[1] ?? "";
|
|
33073
|
-
}
|
|
33074
|
-
function sourceCredentialScope2(values, explicitApiUrl, supabaseUrl) {
|
|
33075
|
-
const hasManagementContext = Boolean(explicitApiUrl.trim() || values.SUPACLOUD_API_TOKEN?.trim() || values.SUPACLOUD_HOST?.trim());
|
|
33076
|
-
if (hasManagementContext)
|
|
33077
|
-
return "management";
|
|
33078
|
-
return supabaseUrl || values.SUPABASE_SERVICE_ROLE_KEY?.trim() ? "project_application" : "incomplete";
|
|
33079
|
-
}
|
|
33080
|
-
function sourceProjectCore2(values) {
|
|
33081
|
-
const supabaseUrl = canonicalApiOrigin2(values.SUPABASE_URL || "");
|
|
33082
|
-
const projectRef = (values.SUPACLOUD_PROJECT_REF || values.X_PROJECT_REF || "").trim() || inferProjectRefFromSupabaseUrl2(supabaseUrl);
|
|
33083
|
-
const explicitApiUrl = values.SUPACLOUD_API_URL || values.SUPACLOUD_MANAGEMENT_API_URL || values.MANAGEMENT_API_URL || "";
|
|
33084
|
-
const credentialScope = sourceCredentialScope2(values, explicitApiUrl, supabaseUrl);
|
|
33085
|
-
const managementUrl = explicitApiUrl || (values.SUPACLOUD_HOST ? `http://${values.SUPACLOUD_HOST}:9090` : "");
|
|
33086
|
-
const apiUrl = credentialScope === "management" ? canonicalApiOrigin2(managementUrl) : "";
|
|
33087
|
-
const apiToken = credentialScope === "management" ? values.SUPACLOUD_API_TOKEN || "" : "";
|
|
33088
|
-
return { apiUrl, apiToken, projectRef, supabaseUrl, credentialScope };
|
|
33089
|
-
}
|
|
33090
|
-
function processValues2(env) {
|
|
33091
|
-
return Object.fromEntries(Object.entries(env).filter((entry) => entry[1] !== undefined));
|
|
33092
|
-
}
|
|
33093
|
-
function hasProcessContext2(env) {
|
|
33094
|
-
return CORE_CONTEXT_KEYS2.some((key) => Object.hasOwn(env, key));
|
|
33095
|
-
}
|
|
33096
|
-
function completeProjectContext2(values) {
|
|
33097
|
-
const core = sourceProjectCore2(values);
|
|
33098
|
-
if (core.credentialScope === "project_application") {
|
|
33099
|
-
return Boolean(core.supabaseUrl && values.SUPABASE_SERVICE_ROLE_KEY?.trim() && core.projectRef);
|
|
33100
|
-
}
|
|
33101
|
-
return Boolean(core.apiUrl && core.apiToken && core.projectRef);
|
|
33102
|
-
}
|
|
33103
|
-
function parseInsecureTls2(values) {
|
|
33104
|
-
for (const key of ["SUPACLOUD_TLS_VERIFY", "SUPACLOUD_TLS_INSECURE"]) {
|
|
33105
|
-
const value = values[key]?.trim().toLowerCase();
|
|
33106
|
-
if (value === undefined)
|
|
33107
|
-
continue;
|
|
33108
|
-
if (["1", "true", "yes", "on"].includes(value))
|
|
33109
|
-
return key === "SUPACLOUD_TLS_INSECURE";
|
|
33110
|
-
if (["0", "false", "no", "off"].includes(value))
|
|
33111
|
-
return key === "SUPACLOUD_TLS_VERIFY";
|
|
33112
|
-
throw new Error(`${key} must be a boolean (true or false)`);
|
|
33113
|
-
}
|
|
33114
|
-
return true;
|
|
33115
|
-
}
|
|
33116
|
-
function namedEnvironmentSource2(cwd, selector) {
|
|
33117
|
-
const environment = normalizeEnvironmentName(selector);
|
|
33118
|
-
const path = resolve26(cwd, `.env.supacloud.${selector}`);
|
|
33119
|
-
const values = readEnvFile2(path, true);
|
|
33120
|
-
if (values.SUPACLOUD_ENV && normalizeEnvironmentName(values.SUPACLOUD_ENV) !== environment) {
|
|
33121
|
-
throw new Error(`SUPACLOUD_ENV in ${path} does not match selector ${selector}`);
|
|
33122
|
-
}
|
|
33123
|
-
return { values, kind: "named_env_file", path, environment };
|
|
33124
|
-
}
|
|
33125
|
-
function explicitEnvironmentSource2(cwd, envFile) {
|
|
33126
|
-
const path = resolve26(cwd, envFile);
|
|
33127
|
-
const values = readEnvFile2(path, true);
|
|
33128
|
-
if (!values.SUPACLOUD_ENV)
|
|
33129
|
-
throw new Error(`SUPACLOUD_ENV is required in ${path}`);
|
|
33130
|
-
return {
|
|
33131
|
-
values,
|
|
33132
|
-
kind: "explicit_env_file",
|
|
33133
|
-
path,
|
|
33134
|
-
environment: normalizeEnvironmentName(values.SUPACLOUD_ENV)
|
|
33135
|
-
};
|
|
33136
|
-
}
|
|
33137
|
-
function contextSource2(env, cwd, selection) {
|
|
33138
|
-
if (selection.environmentName && selection.envFile)
|
|
33139
|
-
throw new Error("Environment selectors are mutually exclusive");
|
|
33140
|
-
if (selection.environmentName)
|
|
33141
|
-
return namedEnvironmentSource2(cwd, selection.environmentName);
|
|
33142
|
-
if (selection.envFile)
|
|
33143
|
-
return explicitEnvironmentSource2(cwd, selection.envFile);
|
|
33144
|
-
if (env.SUPACLOUD_ENV) {
|
|
33145
|
-
const environment = normalizeEnvironmentName(env.SUPACLOUD_ENV);
|
|
33146
|
-
const values = processValues2(env);
|
|
33147
|
-
if (completeProjectContext2(values)) {
|
|
33148
|
-
return { values, kind: "process_env", path: null, environment };
|
|
33149
|
-
}
|
|
33150
|
-
return namedEnvironmentSource2(cwd, env.SUPACLOUD_ENV);
|
|
33151
|
-
}
|
|
33152
|
-
if (hasProcessContext2(env)) {
|
|
33153
|
-
return { values: processValues2(env), kind: "process_env", path: null, environment: "" };
|
|
33154
|
-
}
|
|
33155
|
-
const path = resolve26(cwd, ".env");
|
|
33156
|
-
const values = readEnvFile2(path, false);
|
|
33157
|
-
const environment = values.SUPACLOUD_ENV ? normalizeEnvironmentName(values.SUPACLOUD_ENV) : "";
|
|
33158
|
-
return { values, kind: Object.keys(values).length ? "legacy_dotenv" : "none", path: existsSync16(path) ? path : null, environment };
|
|
33159
|
-
}
|
|
33160
|
-
function resolveSupaCloudContext2(env = process.env, cwd = process.cwd(), selection = {}) {
|
|
33161
|
-
const source = contextSource2(env, cwd, selection);
|
|
33162
|
-
const core = sourceProjectCore2(source.values);
|
|
33163
|
-
const host = source.values.SUPACLOUD_HOST || hostFromUrl2(core.apiUrl || core.supabaseUrl);
|
|
33164
|
-
const readOnly = env.SUPACLOUD_READ_ONLY === "true" || source.values.SUPACLOUD_READ_ONLY === "true";
|
|
33165
|
-
const insecureTls = parseInsecureTls2(source.values);
|
|
33166
|
-
return {
|
|
33167
|
-
host,
|
|
33168
|
-
sshUser: env.SUPACLOUD_SSH_USER ?? "root",
|
|
33169
|
-
sshPort: parseInt(env.SUPACLOUD_SSH_PORT ?? "22", 10),
|
|
33170
|
-
sshKey: env.SUPACLOUD_SSH_KEY ?? resolve26(homedir3(), ".ssh", "id_rsa"),
|
|
33171
|
-
sshPass: env.SUPACLOUD_SSH_PASS ?? "",
|
|
33172
|
-
apiUrl: core.apiUrl,
|
|
33173
|
-
apiToken: core.apiToken,
|
|
33174
|
-
projectRef: core.projectRef,
|
|
33175
|
-
readOnly,
|
|
33176
|
-
environment: source.environment,
|
|
33177
|
-
production: source.environment === "prod" || source.environment === "production",
|
|
33178
|
-
inferredSupabaseUrl: core.supabaseUrl,
|
|
33179
|
-
inferredServiceRoleKey: source.values.SUPABASE_SERVICE_ROLE_KEY || "",
|
|
33180
|
-
credentialScope: core.credentialScope,
|
|
33181
|
-
source: source.kind,
|
|
33182
|
-
sourcePath: source.path,
|
|
33183
|
-
environmentVariables: source.values,
|
|
33184
|
-
applicationId: source.values.SUPACLOUD_APPLICATION_ID?.trim() || "",
|
|
33185
|
-
applicationEnvironmentId: source.values.SUPACLOUD_APPLICATION_ENVIRONMENT_ID?.trim() || source.environment,
|
|
33186
|
-
applicationConfigurationPath: source.values.SUPACLOUD_APPLICATION_CONFIGURATION_PATH?.trim() || "",
|
|
33187
|
-
applicationConfigurationId: source.values.SUPACLOUD_APPLICATION_CONFIGURATION_ID?.trim() || "",
|
|
33188
|
-
applicationManifestPath: source.values.SUPACLOUD_APPLICATION_MANIFEST_PATH?.trim() || "",
|
|
33189
|
-
insecureTls
|
|
33190
|
-
};
|
|
33191
|
-
}
|
|
33192
|
-
|
|
33193
|
-
// src/shared/tools/developer-experience-tools.ts
|
|
33194
|
-
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 = {
|
|
33195
33322
|
root: optional(String2(), "Project directory to initialize (default: current directory)"),
|
|
33196
|
-
|
|
33323
|
+
format: optional(stringEnum2(["text", "json"]), "Output format (default: text)")
|
|
33197
33324
|
};
|
|
33198
|
-
var
|
|
33325
|
+
var environmentToolSchema2 = {
|
|
33199
33326
|
action: withDescription(stringEnum2(["list", "check"]), "Environment profile inspection action"),
|
|
33200
33327
|
root: optional(String2(), "Project directory containing .env.supacloud.* files"),
|
|
33201
|
-
|
|
33328
|
+
format: optional(stringEnum2(["text", "json"]), "Output format (default: text)")
|
|
33202
33329
|
};
|
|
33203
|
-
var
|
|
33330
|
+
var TEST_PROFILE2 = [
|
|
33204
33331
|
"SUPACLOUD_ENV=test",
|
|
33205
33332
|
"SUPACLOUD_API_URL=https://management.test.example.com",
|
|
33206
33333
|
"SUPACLOUD_API_TOKEN=<test-management-api-token>",
|
|
@@ -33209,7 +33336,7 @@ var TEST_PROFILE = [
|
|
|
33209
33336
|
""
|
|
33210
33337
|
].join(`
|
|
33211
33338
|
`);
|
|
33212
|
-
var
|
|
33339
|
+
var PRODUCTION_PROFILE2 = [
|
|
33213
33340
|
"SUPACLOUD_ENV=production",
|
|
33214
33341
|
"SUPACLOUD_API_URL=https://management.example.com",
|
|
33215
33342
|
"SUPACLOUD_API_TOKEN=<production-management-api-token>",
|
|
@@ -33218,41 +33345,41 @@ var PRODUCTION_PROFILE = [
|
|
|
33218
33345
|
""
|
|
33219
33346
|
].join(`
|
|
33220
33347
|
`);
|
|
33221
|
-
function
|
|
33348
|
+
function textResult4(text, isError = false) {
|
|
33222
33349
|
return { isError, content: [{ type: "text", text }] };
|
|
33223
33350
|
}
|
|
33224
|
-
function
|
|
33225
|
-
return
|
|
33351
|
+
function jsonResult2(value, isError = false) {
|
|
33352
|
+
return textResult4(JSON.stringify(value, null, 2), isError);
|
|
33226
33353
|
}
|
|
33227
|
-
function
|
|
33354
|
+
function record7(value) {
|
|
33228
33355
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
33229
33356
|
}
|
|
33230
|
-
function
|
|
33357
|
+
function packageDependencies2(manifest) {
|
|
33231
33358
|
return new Set([
|
|
33232
33359
|
...Object.keys(manifest.dependencies ?? {}),
|
|
33233
33360
|
...Object.keys(manifest.devDependencies ?? {})
|
|
33234
33361
|
]);
|
|
33235
33362
|
}
|
|
33236
|
-
function
|
|
33237
|
-
const dependencies =
|
|
33363
|
+
function outputDirectoryFor2(manifest) {
|
|
33364
|
+
const dependencies = packageDependencies2(manifest);
|
|
33238
33365
|
if (dependencies.has("next"))
|
|
33239
33366
|
return "out";
|
|
33240
33367
|
if (dependencies.has("@sveltejs/kit") || dependencies.has("svelte"))
|
|
33241
33368
|
return "build";
|
|
33242
33369
|
return "dist";
|
|
33243
33370
|
}
|
|
33244
|
-
async function
|
|
33245
|
-
const path =
|
|
33246
|
-
if (!
|
|
33371
|
+
async function readPackageManifest2(root) {
|
|
33372
|
+
const path = join21(root, "package.json");
|
|
33373
|
+
if (!existsSync18(path))
|
|
33247
33374
|
return {};
|
|
33248
33375
|
try {
|
|
33249
|
-
const parsed = JSON.parse(await
|
|
33250
|
-
const value =
|
|
33376
|
+
const parsed = JSON.parse(await readFile9(path, "utf8"));
|
|
33377
|
+
const value = record7(parsed);
|
|
33251
33378
|
if (!value)
|
|
33252
33379
|
throw new Error("package.json must contain an object");
|
|
33253
|
-
const scripts =
|
|
33254
|
-
const dependencies =
|
|
33255
|
-
const devDependencies =
|
|
33380
|
+
const scripts = record7(value.scripts);
|
|
33381
|
+
const dependencies = record7(value.dependencies);
|
|
33382
|
+
const devDependencies = record7(value.devDependencies);
|
|
33256
33383
|
return {
|
|
33257
33384
|
scripts: scripts ?? undefined,
|
|
33258
33385
|
dependencies: dependencies ?? undefined,
|
|
@@ -33262,9 +33389,9 @@ async function readPackageManifest(root) {
|
|
|
33262
33389
|
throw new Error(`Invalid package.json: ${error instanceof Error ? error.message : String(error)}`);
|
|
33263
33390
|
}
|
|
33264
33391
|
}
|
|
33265
|
-
async function
|
|
33392
|
+
async function writeNewFile2(path, content) {
|
|
33266
33393
|
try {
|
|
33267
|
-
await
|
|
33394
|
+
await writeFile8(path, content, { encoding: "utf8", flag: "wx", mode: 384 });
|
|
33268
33395
|
return true;
|
|
33269
33396
|
} catch (error) {
|
|
33270
33397
|
if (error instanceof Error && "code" in error && error.code === "EEXIST")
|
|
@@ -33272,9 +33399,9 @@ async function writeNewFile(path, content) {
|
|
|
33272
33399
|
throw error;
|
|
33273
33400
|
}
|
|
33274
33401
|
}
|
|
33275
|
-
async function
|
|
33276
|
-
const path =
|
|
33277
|
-
const existing =
|
|
33402
|
+
async function ensureGitignore2(root) {
|
|
33403
|
+
const path = join21(root, ".gitignore");
|
|
33404
|
+
const existing = existsSync18(path) ? await readFile9(path, "utf8") : "";
|
|
33278
33405
|
const required = [".env.supacloud.*", "!.env.supacloud.*.example"];
|
|
33279
33406
|
const missing = required.filter((entry) => !existing.split(/\r?\n/).includes(entry));
|
|
33280
33407
|
if (missing.length === 0)
|
|
@@ -33282,21 +33409,21 @@ async function ensureGitignore(root) {
|
|
|
33282
33409
|
const prefix = existing.length > 0 && !existing.endsWith(`
|
|
33283
33410
|
`) ? `${existing}
|
|
33284
33411
|
` : existing;
|
|
33285
|
-
await
|
|
33412
|
+
await writeFile8(path, `${prefix}${missing.join(`
|
|
33286
33413
|
`)}
|
|
33287
33414
|
`, "utf8");
|
|
33288
33415
|
return true;
|
|
33289
33416
|
}
|
|
33290
|
-
async function
|
|
33291
|
-
const root =
|
|
33292
|
-
await
|
|
33293
|
-
const configPath =
|
|
33294
|
-
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)) {
|
|
33295
33422
|
throw new Error(`supacloud.json already exists in ${root}; init never overwrites project configuration`);
|
|
33296
33423
|
}
|
|
33297
|
-
const manifest = await
|
|
33424
|
+
const manifest = await readPackageManifest2(root);
|
|
33298
33425
|
const hasBuildScript = typeof manifest.scripts?.build === "string" && Boolean(manifest.scripts.build.trim());
|
|
33299
|
-
const outputDirectory =
|
|
33426
|
+
const outputDirectory = outputDirectoryFor2(manifest);
|
|
33300
33427
|
const config = {
|
|
33301
33428
|
frontend: {
|
|
33302
33429
|
root: ".",
|
|
@@ -33305,7 +33432,7 @@ async function initializeProject(request, options) {
|
|
|
33305
33432
|
};
|
|
33306
33433
|
const created = [];
|
|
33307
33434
|
try {
|
|
33308
|
-
await
|
|
33435
|
+
await writeFile8(configPath, `${JSON.stringify(config, null, 2)}
|
|
33309
33436
|
`, {
|
|
33310
33437
|
encoding: "utf8",
|
|
33311
33438
|
flag: "wx",
|
|
@@ -33318,13 +33445,13 @@ async function initializeProject(request, options) {
|
|
|
33318
33445
|
throw error;
|
|
33319
33446
|
}
|
|
33320
33447
|
created.push("supacloud.json");
|
|
33321
|
-
if (await
|
|
33448
|
+
if (await writeNewFile2(join21(root, ".env.supacloud.test.example"), TEST_PROFILE2)) {
|
|
33322
33449
|
created.push(".env.supacloud.test.example");
|
|
33323
33450
|
}
|
|
33324
|
-
if (await
|
|
33451
|
+
if (await writeNewFile2(join21(root, ".env.supacloud.production.example"), PRODUCTION_PROFILE2)) {
|
|
33325
33452
|
created.push(".env.supacloud.production.example");
|
|
33326
33453
|
}
|
|
33327
|
-
if (await
|
|
33454
|
+
if (await ensureGitignore2(root))
|
|
33328
33455
|
created.push(".gitignore");
|
|
33329
33456
|
const result = {
|
|
33330
33457
|
schema: "supacloud.developer-experience.init.v1",
|
|
@@ -33332,7 +33459,7 @@ async function initializeProject(request, options) {
|
|
|
33332
33459
|
root,
|
|
33333
33460
|
created,
|
|
33334
33461
|
detected: {
|
|
33335
|
-
package_json:
|
|
33462
|
+
package_json: existsSync18(join21(root, "package.json")),
|
|
33336
33463
|
build_script: hasBuildScript,
|
|
33337
33464
|
output_directory: outputDirectory
|
|
33338
33465
|
},
|
|
@@ -33343,9 +33470,9 @@ async function initializeProject(request, options) {
|
|
|
33343
33470
|
"supacloud --env test deploy"
|
|
33344
33471
|
]
|
|
33345
33472
|
};
|
|
33346
|
-
if (request.
|
|
33347
|
-
return
|
|
33348
|
-
return
|
|
33473
|
+
if (request.format === "json")
|
|
33474
|
+
return jsonResult2(result);
|
|
33475
|
+
return textResult4([
|
|
33349
33476
|
`Initialized SupaCloud deployment in ${root}.`,
|
|
33350
33477
|
`Created: ${created.join(", ")}.`,
|
|
33351
33478
|
hasBuildScript ? `Detected package.json build script; deploy output defaults to ${outputDirectory}.` : "No package.json build script detected; add one before deploying.",
|
|
@@ -33359,14 +33486,14 @@ function profileNameFromFile(name) {
|
|
|
33359
33486
|
return match?.[1] ?? null;
|
|
33360
33487
|
}
|
|
33361
33488
|
async function environmentProfiles(root) {
|
|
33362
|
-
const entries = await
|
|
33489
|
+
const entries = await readdir9(root, { withFileTypes: true }).catch((error) => {
|
|
33363
33490
|
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
33364
33491
|
return [];
|
|
33365
33492
|
throw error;
|
|
33366
33493
|
});
|
|
33367
33494
|
return entries.filter((entry) => entry.isFile()).map((entry) => {
|
|
33368
33495
|
const name = profileNameFromFile(entry.name);
|
|
33369
|
-
return name ? { name, path:
|
|
33496
|
+
return name ? { name, path: join21(root, entry.name), exists: true } : null;
|
|
33370
33497
|
}).filter((profile) => profile !== null).sort((left, right) => left.name.localeCompare(right.name));
|
|
33371
33498
|
}
|
|
33372
33499
|
function statusMissing(context) {
|
|
@@ -33401,18 +33528,18 @@ function deployMissing(context, application, hasFrontend = false) {
|
|
|
33401
33528
|
}
|
|
33402
33529
|
async function inspectProfile(profile) {
|
|
33403
33530
|
try {
|
|
33404
|
-
const context = resolveSupaCloudContext2({},
|
|
33531
|
+
const context = resolveSupaCloudContext2({}, dirname15(profile.path), {
|
|
33405
33532
|
environmentName: profile.name
|
|
33406
33533
|
});
|
|
33407
33534
|
const status = statusMissing(context);
|
|
33408
|
-
const configText = await
|
|
33535
|
+
const configText = await readFile9(join21(dirname15(profile.path), "supacloud.json"), "utf8").catch((error) => {
|
|
33409
33536
|
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
33410
33537
|
return "{}";
|
|
33411
33538
|
throw error;
|
|
33412
33539
|
});
|
|
33413
|
-
const config =
|
|
33414
|
-
const hasFrontend =
|
|
33415
|
-
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);
|
|
33416
33543
|
return {
|
|
33417
33544
|
name: profile.name,
|
|
33418
33545
|
path: profile.path,
|
|
@@ -33449,7 +33576,7 @@ async function inspectProfile(profile) {
|
|
|
33449
33576
|
}
|
|
33450
33577
|
}
|
|
33451
33578
|
async function inspectEnvironment(request, options) {
|
|
33452
|
-
const root =
|
|
33579
|
+
const root = resolve28(request.root || options.cwd || process.cwd());
|
|
33453
33580
|
const profiles = await environmentProfiles(root);
|
|
33454
33581
|
if (request.action === "list") {
|
|
33455
33582
|
const active = options.activeContext?.environment || null;
|
|
@@ -33464,13 +33591,13 @@ async function inspectEnvironment(request, options) {
|
|
|
33464
33591
|
active: profile.name.toLowerCase() === active?.toLowerCase()
|
|
33465
33592
|
}))
|
|
33466
33593
|
};
|
|
33467
|
-
if (request.
|
|
33468
|
-
return
|
|
33594
|
+
if (request.format === "json")
|
|
33595
|
+
return jsonResult2(result);
|
|
33469
33596
|
if (profiles.length === 0) {
|
|
33470
|
-
return
|
|
33471
|
-
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.`);
|
|
33472
33599
|
}
|
|
33473
|
-
return
|
|
33600
|
+
return textResult4([
|
|
33474
33601
|
`Environment profiles in ${root}:`,
|
|
33475
33602
|
...result.environments.map((profile) => `${profile.active ? "*" : " "} ${profile.name} ${profile.path}`)
|
|
33476
33603
|
].join(`
|
|
@@ -33484,11 +33611,11 @@ Run \`supacloud init\` to create safe examples.`);
|
|
|
33484
33611
|
active: options.activeContext?.environment || null,
|
|
33485
33612
|
environments: reports
|
|
33486
33613
|
};
|
|
33487
|
-
if (request.
|
|
33488
|
-
return
|
|
33614
|
+
if (request.format === "json")
|
|
33615
|
+
return jsonResult2(result, !result.ok);
|
|
33489
33616
|
if (reports.length === 0) {
|
|
33490
|
-
return
|
|
33491
|
-
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);
|
|
33492
33619
|
}
|
|
33493
33620
|
const lines = reports.map((report) => {
|
|
33494
33621
|
if (report.error)
|
|
@@ -33498,12 +33625,11 @@ Run \`supacloud init\` and copy an example profile.`, true);
|
|
|
33498
33625
|
const ref = report.projectRef ? `project=${report.projectRef}` : "project=unknown";
|
|
33499
33626
|
return `${status} ${report.name} ${deploy} ${ref}`;
|
|
33500
33627
|
});
|
|
33501
|
-
return
|
|
33628
|
+
return textResult4(lines.join(`
|
|
33502
33629
|
`), !result.ok);
|
|
33503
33630
|
}
|
|
33504
33631
|
function registerDeveloperExperienceTools(server, options = {}) {
|
|
33505
|
-
registerTool(server, "
|
|
33506
|
-
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));
|
|
33507
33633
|
}
|
|
33508
33634
|
|
|
33509
33635
|
// src/shared/tools/project-deployment.ts
|
|
@@ -33631,7 +33757,7 @@ async function runProjectDeployment(http, args) {
|
|
|
33631
33757
|
// package.json
|
|
33632
33758
|
var package_default = {
|
|
33633
33759
|
name: "@supacloud/cli",
|
|
33634
|
-
version: "0.
|
|
33760
|
+
version: "0.115.0",
|
|
33635
33761
|
description: "Project-scoped CLI for SupaCloud users",
|
|
33636
33762
|
type: "module",
|
|
33637
33763
|
main: "./dist/index.js",
|
|
@@ -33683,9 +33809,9 @@ var package_default = {
|
|
|
33683
33809
|
directory: "packages/cli"
|
|
33684
33810
|
},
|
|
33685
33811
|
dependencies: {
|
|
33686
|
-
"@supacloud/compiler": "^0.
|
|
33812
|
+
"@supacloud/compiler": "^0.54.0",
|
|
33687
33813
|
"@supacloud/delivery": "0.24.0",
|
|
33688
|
-
"@supacloud/db": "0.20.
|
|
33814
|
+
"@supacloud/db": "0.20.4",
|
|
33689
33815
|
typebox: "1.4.3"
|
|
33690
33816
|
},
|
|
33691
33817
|
overrides: {
|
|
@@ -33695,10 +33821,10 @@ var package_default = {
|
|
|
33695
33821
|
},
|
|
33696
33822
|
devDependencies: {
|
|
33697
33823
|
"@supabase/supabase-js": "2.118.0",
|
|
33698
|
-
"@supacloud/elysia": "0.
|
|
33824
|
+
"@supacloud/elysia": "0.38.0",
|
|
33699
33825
|
"@types/bun": "^1.4.4",
|
|
33700
33826
|
effect: "4.0.5",
|
|
33701
|
-
elysia: "2.0.0-rc.
|
|
33827
|
+
elysia: "2.0.0-rc.1",
|
|
33702
33828
|
typescript: "^7.0.2"
|
|
33703
33829
|
}
|
|
33704
33830
|
};
|
|
@@ -33867,7 +33993,7 @@ USAGE
|
|
|
33867
33993
|
|
|
33868
33994
|
${preferredCommand} [global flags] deploy [--flags]
|
|
33869
33995
|
${preferredCommand} [global flags] <module> <action> [--flags]
|
|
33870
|
-
${preferredCommand} init [--root <directory>]
|
|
33996
|
+
${preferredCommand} deploy init [--root <directory>]
|
|
33871
33997
|
${preferredCommand} env <list|check> [--root <directory>]
|
|
33872
33998
|
${preferredCommand} [global flags] status
|
|
33873
33999
|
${preferredCommand} --help
|
|
@@ -33901,9 +34027,9 @@ DEFAULT CONTEXT
|
|
|
33901
34027
|
|
|
33902
34028
|
EXAMPLES
|
|
33903
34029
|
|
|
33904
|
-
${preferredCommand} init
|
|
34030
|
+
${preferredCommand} deploy init
|
|
33905
34031
|
${preferredCommand} env check
|
|
33906
|
-
${preferredCommand} env check --json
|
|
34032
|
+
${preferredCommand} env check --format json
|
|
33907
34033
|
${preferredCommand} status
|
|
33908
34034
|
${preferredCommand} deploy
|
|
33909
34035
|
${preferredCommand} --env test deploy
|
|
@@ -33938,7 +34064,7 @@ EXAMPLES
|
|
|
33938
34064
|
${preferredCommand} supabase db_dump --db_url "postgresql://..." --file backups/schema.sql
|
|
33939
34065
|
${preferredCommand} lite migrate --project_dir .
|
|
33940
34066
|
${preferredCommand} lite start --project_dir . --port 54321
|
|
33941
|
-
${preferredCommand} lite doctor --project_dir . --json
|
|
34067
|
+
${preferredCommand} lite doctor --project_dir . --format json
|
|
33942
34068
|
${preferredCommand} branch create --name feature-auth --data_mode schema_only
|
|
33943
34069
|
${preferredCommand} branch promotion_plan --branch_ref preview123
|
|
33944
34070
|
${preferredCommand} branch promote --branch_ref preview123 --plan_checksum <sha256>
|
|
@@ -33946,8 +34072,6 @@ EXAMPLES
|
|
|
33946
34072
|
${preferredCommand} ai install_skill --dry_run
|
|
33947
34073
|
${preferredCommand} mcp config
|
|
33948
34074
|
${preferredCommand} app init --name my-service
|
|
33949
|
-
${preferredCommand} app init --name my-api --template http
|
|
33950
|
-
${preferredCommand} app init --name my-worker --template edge
|
|
33951
34075
|
${preferredCommand} app generate --kind module --name billing
|
|
33952
34076
|
${preferredCommand} app generate --kind command --module billing --name issue-invoice
|
|
33953
34077
|
${preferredCommand} app generate --kind job --module billing --name sync-orders
|
|
@@ -33988,13 +34112,6 @@ EXAMPLES
|
|
|
33988
34112
|
${preferredCommand} app context --root . --target CaseModule --events execution.ndjson --request-id request-123 --format json
|
|
33989
34113
|
${preferredCommand} app doctor --root .
|
|
33990
34114
|
${preferredCommand} app fix --root . --fix fix.json --write
|
|
33991
|
-
${preferredCommand} generate --kind module --name billing
|
|
33992
|
-
${preferredCommand} generate --kind job --module billing --name sync-orders
|
|
33993
|
-
${preferredCommand} generate --kind contract --module billing --name issue-invoice
|
|
33994
|
-
${preferredCommand} check --root . --strict
|
|
33995
|
-
${preferredCommand} context --format json
|
|
33996
|
-
${preferredCommand} context --target CaseModule --format json
|
|
33997
|
-
${preferredCommand} doctor
|
|
33998
34115
|
${preferredCommand} db context --root . --module_file db/modules.ts --module cases
|
|
33999
34116
|
${preferredCommand} db context --root . --module_file db/modules.ts --target public.case_create
|
|
34000
34117
|
${preferredCommand} db lint --root . --module_file db/modules.ts
|
|
@@ -34006,6 +34123,7 @@ EXAMPLES
|
|
|
34006
34123
|
${preferredCommand} db plan --dir supabase/migrations
|
|
34007
34124
|
${preferredCommand} db apply --dir supabase/migrations --approved_digest <reviewed-digest>
|
|
34008
34125
|
${preferredCommand} db role_check --schema public
|
|
34126
|
+
${preferredCommand} db role_check --remote --schema public
|
|
34009
34127
|
${preferredCommand} db role_sql --application_role app_user --migration_role app_migrator --database app_db
|
|
34010
34128
|
${preferredCommand} edge_functions get_config --ref abc123 --slug hello
|
|
34011
34129
|
${preferredCommand} edge_functions deploy --ref abc123 --slug hello --path ./supabase/functions/hello --expected-active-version absent --expected-activation-id legacy
|
|
@@ -34068,7 +34186,6 @@ function createCliTools(context, confirmProduction) {
|
|
|
34068
34186
|
activeContext: context
|
|
34069
34187
|
})));
|
|
34070
34188
|
Object.assign(tools, captureTools((server) => registerAppTools(server, { onDevProgress: writeDevProgress })));
|
|
34071
|
-
Object.assign(tools, captureTools((server) => registerAppAliases(server)));
|
|
34072
34189
|
Object.assign(tools, captureTools((server) => registerDbGovernanceTools(server, {
|
|
34073
34190
|
projectRef: context.projectRef || undefined,
|
|
34074
34191
|
apiUrl: context.apiUrl || undefined,
|
|
@@ -34103,7 +34220,7 @@ function createCliTools(context, confirmProduction) {
|
|
|
34103
34220
|
]
|
|
34104
34221
|
})
|
|
34105
34222
|
};
|
|
34106
|
-
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"]) {
|
|
34107
34224
|
tools[name] = {
|
|
34108
34225
|
schema: { action: genericActionSchema },
|
|
34109
34226
|
callback: async () => ({
|
|
@@ -34130,7 +34247,6 @@ function createCliTools(context, confirmProduction) {
|
|
|
34130
34247
|
tools.branch = { schema: branchHelpTool.schema, callback: branchContextCallback };
|
|
34131
34248
|
}
|
|
34132
34249
|
const frontendContextCallback = tools.frontend.callback;
|
|
34133
|
-
tools.applications = { schema: APPLICATION_TOOL_SCHEMA, callback: tools.applications.callback };
|
|
34134
34250
|
const frontendHelpTool = captureTools((server) => registerFrontendTools(server, {})).frontend;
|
|
34135
34251
|
if (frontendHelpTool) {
|
|
34136
34252
|
tools.frontend = { schema: frontendHelpTool.schema, callback: frontendContextCallback };
|
|
@@ -34140,13 +34256,22 @@ function createCliTools(context, confirmProduction) {
|
|
|
34140
34256
|
registerContextAwareHelp();
|
|
34141
34257
|
tools.deploy = {
|
|
34142
34258
|
schema: deployToolSchema,
|
|
34143
|
-
callback: async () =>
|
|
34144
|
-
|
|
34145
|
-
|
|
34146
|
-
|
|
34147
|
-
|
|
34148
|
-
|
|
34149
|
-
|
|
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
|
+
}
|
|
34150
34275
|
};
|
|
34151
34276
|
Object.assign(tools, captureTools((server) => registerDatabaseTools(server, undefined, {
|
|
34152
34277
|
localOnly: true
|
|
@@ -34245,9 +34370,9 @@ function createCliTools(context, confirmProduction) {
|
|
|
34245
34370
|
applicationOrigin: context.inferredSupabaseUrl || undefined
|
|
34246
34371
|
})));
|
|
34247
34372
|
assign(captureTools((server) => registerFrontendTools(server, http)));
|
|
34248
|
-
|
|
34373
|
+
const applicationTools = captureTools((server) => registerApplicationTools(server, http));
|
|
34249
34374
|
Object.assign(tools, captureTools((server) => registerAppTools(server, {
|
|
34250
|
-
getApplications: () =>
|
|
34375
|
+
getApplications: () => applicationTools.applications?.callback,
|
|
34251
34376
|
onDevProgress: writeDevProgress,
|
|
34252
34377
|
projectRef: context.projectRef || undefined,
|
|
34253
34378
|
projectDeployment: (args) => runProjectDeployment(http, args)
|
|
@@ -34302,7 +34427,7 @@ async function main() {
|
|
|
34302
34427
|
}
|
|
34303
34428
|
const cliTools = createCliTools(context, globalOptions.confirmProduction);
|
|
34304
34429
|
if (args.length === 1 && !["ai", "supabase", "lite", "app", "db", "env"].includes(args[0]) && cliTools[args[0]]) {
|
|
34305
|
-
if ("action" in cliTools[args[0]].schema) {
|
|
34430
|
+
if ("action" in cliTools[args[0]].schema && args[0] !== "deploy") {
|
|
34306
34431
|
if (context.credentialScope !== "management" || !context.apiUrl || !context.apiToken) {
|
|
34307
34432
|
console.error(`This command requires Management API context. Run \`${preferredCommand} status\` to inspect current detection.`);
|
|
34308
34433
|
process.exitCode = 1;
|