@supacloud/cli 0.3.3 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +104 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4418,7 +4418,7 @@ function registerDatabaseTools(server, http, config = {}) {
|
|
|
4418
4418
|
"project_url",
|
|
4419
4419
|
"generate_types"
|
|
4420
4420
|
];
|
|
4421
|
-
const writeActions = ["apply_migration", "push_migrations", "create_table_rls"];
|
|
4421
|
+
const writeActions = ["apply_migration", "push_migrations", "baseline_migrations", "create_table_rls"];
|
|
4422
4422
|
const allActions = readOnly ? actions : [...actions, ...writeActions];
|
|
4423
4423
|
server.tool("database", `Database operations: query, schema, RLS, migrations, stats.
|
|
4424
4424
|
Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
|
|
@@ -4426,8 +4426,8 @@ Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
|
|
|
4426
4426
|
ref: projectRef ? exports_external.string().optional() : exports_external.string().optional().describe("Project ref"),
|
|
4427
4427
|
sql: exports_external.string().optional().describe("[query/apply_migration] SQL statement"),
|
|
4428
4428
|
file: exports_external.string().optional().describe("[query/apply_migration] Read SQL from local file path (avoids shell escaping issues with $$ and multi-statement DDL)"),
|
|
4429
|
-
dir: exports_external.string().optional().describe("[push_migrations] Directory containing .sql migration files (default: supabase/migrations)"),
|
|
4430
|
-
dry_run: exports_external.boolean().optional().describe("[push_migrations]
|
|
4429
|
+
dir: exports_external.string().optional().describe("[push_migrations/baseline_migrations] Directory containing .sql migration files (default: supabase/migrations)"),
|
|
4430
|
+
dry_run: exports_external.boolean().optional().describe("[push_migrations/baseline_migrations] Preview changes without applying them"),
|
|
4431
4431
|
schema: exports_external.string().optional().describe("[*] Schema name (default: public)"),
|
|
4432
4432
|
table: exports_external.string().optional().describe("[describe_columns/indexes/constraints/rls_*] Table name"),
|
|
4433
4433
|
schemas: exports_external.array(exports_external.string()).optional().describe("[list_tables/generate_types] Schemas array"),
|
|
@@ -4447,7 +4447,7 @@ Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
|
|
|
4447
4447
|
return { content: [{ type: "text", text: `❌ Failed to read file ${args.file}: ${e.message}` }] };
|
|
4448
4448
|
}
|
|
4449
4449
|
}
|
|
4450
|
-
const execSql = async (sql) => http.post(`/v1/projects/${ref}/database/sql`, { sql });
|
|
4450
|
+
const execSql = async (sql, mode) => http.post(`/v1/projects/${ref}/database/sql`, mode ? { sql, mode } : { sql });
|
|
4451
4451
|
let text;
|
|
4452
4452
|
switch (action) {
|
|
4453
4453
|
case "query": {
|
|
@@ -4649,6 +4649,64 @@ Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
|
|
|
4649
4649
|
`);
|
|
4650
4650
|
break;
|
|
4651
4651
|
}
|
|
4652
|
+
case "baseline_migrations": {
|
|
4653
|
+
const dir = args.dir || "supabase/migrations";
|
|
4654
|
+
if (!existsSync2(dir) || !statSync(dir).isDirectory()) {
|
|
4655
|
+
throw new Error(`Migration directory not found: ${dir}`);
|
|
4656
|
+
}
|
|
4657
|
+
const files = readdirSync(dir).filter((file) => file.endsWith(".sql")).sort();
|
|
4658
|
+
if (!files.length) {
|
|
4659
|
+
text = `No .sql migration files found in ${dir}`;
|
|
4660
|
+
break;
|
|
4661
|
+
}
|
|
4662
|
+
const migrationFiles = files.map((file, i) => ({
|
|
4663
|
+
file,
|
|
4664
|
+
name: basename(file, ".sql"),
|
|
4665
|
+
version: migrationVersionFromFilename(file, i)
|
|
4666
|
+
}));
|
|
4667
|
+
const r = await http.get(`/v1/projects/${ref}/database/migrations`);
|
|
4668
|
+
if (!r.ok) {
|
|
4669
|
+
text = `❌ Failed to load applied migrations (${r.status}): ${JSON.stringify(r.data)}`;
|
|
4670
|
+
break;
|
|
4671
|
+
}
|
|
4672
|
+
const appliedKeys = appliedMigrationKeys(r.data);
|
|
4673
|
+
const missing = migrationFiles.filter(({ name, version }) => !appliedKeys.has(name) && !appliedKeys.has(String(version)));
|
|
4674
|
+
const alreadyApplied = migrationFiles.filter(({ name, version }) => appliedKeys.has(name) || appliedKeys.has(String(version)));
|
|
4675
|
+
if (args.dry_run) {
|
|
4676
|
+
text = [
|
|
4677
|
+
`Migration baseline dry run for ${dir}`,
|
|
4678
|
+
`Total: ${migrationFiles.length}`,
|
|
4679
|
+
"",
|
|
4680
|
+
"Would mark as applied:",
|
|
4681
|
+
...missing.length ? missing.map(({ file, version }) => ` - ${file} (${version})`) : [" - none"],
|
|
4682
|
+
"",
|
|
4683
|
+
"Already applied:",
|
|
4684
|
+
...alreadyApplied.length ? alreadyApplied.map(({ file, version }) => ` - ${file} (${version})`) : [" - none"]
|
|
4685
|
+
].join(`
|
|
4686
|
+
`);
|
|
4687
|
+
break;
|
|
4688
|
+
}
|
|
4689
|
+
if (!missing.length) {
|
|
4690
|
+
text = [
|
|
4691
|
+
`✅ Migration baseline already aligned for ${dir}`,
|
|
4692
|
+
`Already applied: ${alreadyApplied.length}`
|
|
4693
|
+
].join(`
|
|
4694
|
+
`);
|
|
4695
|
+
break;
|
|
4696
|
+
}
|
|
4697
|
+
const baselineSql = buildMigrationBaselineSql(missing);
|
|
4698
|
+
const baselineResult = await execSql(baselineSql, "migration");
|
|
4699
|
+
text = baselineResult.ok ? [
|
|
4700
|
+
`✅ Migration baseline completed for ${dir}`,
|
|
4701
|
+
`Marked applied: ${missing.length}`,
|
|
4702
|
+
`Already applied: ${alreadyApplied.length}`,
|
|
4703
|
+
"",
|
|
4704
|
+
"Marked files:",
|
|
4705
|
+
...missing.map(({ file, version }) => ` - ${file} (${version})`)
|
|
4706
|
+
].join(`
|
|
4707
|
+
`) : `❌ Failed (${baselineResult.status}): ${JSON.stringify(baselineResult.data)}`;
|
|
4708
|
+
break;
|
|
4709
|
+
}
|
|
4652
4710
|
case "create_table_rls": {
|
|
4653
4711
|
if (!args.table || !args.columns)
|
|
4654
4712
|
throw new Error("'table' and 'columns' required");
|
|
@@ -4663,6 +4721,48 @@ Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
|
|
|
4663
4721
|
return { content: [{ type: "text", text }] };
|
|
4664
4722
|
});
|
|
4665
4723
|
}
|
|
4724
|
+
function sqlString(value) {
|
|
4725
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
4726
|
+
}
|
|
4727
|
+
function buildMigrationBaselineSql(migrations) {
|
|
4728
|
+
const values = migrations.map(({ name, version }) => `(${version}, ${sqlString(name)}, ARRAY[${sqlString(`baseline:${name}`)}]::text[])`).join(`,
|
|
4729
|
+
`);
|
|
4730
|
+
return `
|
|
4731
|
+
CREATE SCHEMA IF NOT EXISTS supabase_migrations;
|
|
4732
|
+
|
|
4733
|
+
CREATE TABLE IF NOT EXISTS supabase_migrations.schema_migrations (
|
|
4734
|
+
version BIGINT PRIMARY KEY,
|
|
4735
|
+
statements TEXT[],
|
|
4736
|
+
name TEXT
|
|
4737
|
+
);
|
|
4738
|
+
|
|
4739
|
+
CREATE TABLE IF NOT EXISTS public.schema_migrations (
|
|
4740
|
+
version VARCHAR(255) PRIMARY KEY,
|
|
4741
|
+
statements TEXT[],
|
|
4742
|
+
name TEXT
|
|
4743
|
+
);
|
|
4744
|
+
|
|
4745
|
+
WITH baseline(version, name, statements) AS (
|
|
4746
|
+
VALUES
|
|
4747
|
+
${values}
|
|
4748
|
+
)
|
|
4749
|
+
INSERT INTO supabase_migrations.schema_migrations (version, statements, name)
|
|
4750
|
+
SELECT version, statements, name FROM baseline
|
|
4751
|
+
ON CONFLICT (version) DO UPDATE
|
|
4752
|
+
SET statements = EXCLUDED.statements,
|
|
4753
|
+
name = EXCLUDED.name;
|
|
4754
|
+
|
|
4755
|
+
WITH baseline(version, name, statements) AS (
|
|
4756
|
+
VALUES
|
|
4757
|
+
${values}
|
|
4758
|
+
)
|
|
4759
|
+
INSERT INTO public.schema_migrations (version, statements, name)
|
|
4760
|
+
SELECT version::text, statements, name FROM baseline
|
|
4761
|
+
ON CONFLICT (version) DO UPDATE
|
|
4762
|
+
SET statements = EXCLUDED.statements,
|
|
4763
|
+
name = EXCLUDED.name;
|
|
4764
|
+
`.trim();
|
|
4765
|
+
}
|
|
4666
4766
|
function formatSqlResult(data) {
|
|
4667
4767
|
if (!data || typeof data !== "object")
|
|
4668
4768
|
return JSON.stringify(data, null, 2);
|