@supacloud/cli 0.3.1 → 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/README.md +25 -8
- package/dist/index.js +133 -25
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -2,7 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
Project-scoped CLI for SupaCloud users.
|
|
4
4
|
|
|
5
|
-
`supacloud`
|
|
5
|
+
Use the explicit `supacloud-cli` command for project workflows. The old `supacloud`
|
|
6
|
+
binary name is kept as a compatibility alias only; it is easy to confuse with the
|
|
7
|
+
server binary installed at `/usr/local/bin/supacloud`.
|
|
8
|
+
|
|
9
|
+
Install:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g @supacloud/cli
|
|
13
|
+
supacloud-cli status
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
One-off execution:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm exec --package @supacloud/cli -- supacloud-cli status
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`supacloud-cli` defaults to the current workspace's project context. If you do not pass explicit flags, it tries to auto-link from `.env`:
|
|
6
23
|
|
|
7
24
|
- `SUPABASE_URL` or `SUPACLOUD_API_URL`
|
|
8
25
|
- `SUPABASE_SERVICE_ROLE_KEY` or `SUPACLOUD_API_TOKEN`
|
|
@@ -10,13 +27,13 @@ Project-scoped CLI for SupaCloud users.
|
|
|
10
27
|
Examples:
|
|
11
28
|
|
|
12
29
|
```bash
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
30
|
+
supacloud-cli status
|
|
31
|
+
supacloud-cli project get
|
|
32
|
+
supacloud-cli project logs --log_type database
|
|
33
|
+
supacloud-cli database query --sql "select now()"
|
|
34
|
+
supacloud-cli database query --ref abc123 --file ./queries/vector-search.sql
|
|
35
|
+
supacloud-cli database push_migrations --ref abc123 --dir supabase/migrations --dry_run
|
|
36
|
+
supacloud-cli frontend list --ref abc123
|
|
20
37
|
```
|
|
21
38
|
|
|
22
39
|
Use `database query --file` for complex SQL, pgvector queries, and single-request transaction blocks.
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,9 @@ var __export = (target, all) => {
|
|
|
14
14
|
});
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
+
// src/index.ts
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
|
|
17
20
|
// node_modules/zod/v3/external.js
|
|
18
21
|
var exports_external = {};
|
|
19
22
|
__export(exports_external, {
|
|
@@ -4354,7 +4357,7 @@ function migrationVersionFromFilename(file, fallbackIndex = 0) {
|
|
|
4354
4357
|
return Date.now() * 1000 + fallbackIndex;
|
|
4355
4358
|
}
|
|
4356
4359
|
function appliedMigrationKeys(data) {
|
|
4357
|
-
const rows = data?.rows || [];
|
|
4360
|
+
const rows = Array.isArray(data) ? data : data?.rows || [];
|
|
4358
4361
|
const keys = new Set;
|
|
4359
4362
|
for (const row of rows) {
|
|
4360
4363
|
if (!row || typeof row !== "object")
|
|
@@ -4415,7 +4418,7 @@ function registerDatabaseTools(server, http, config = {}) {
|
|
|
4415
4418
|
"project_url",
|
|
4416
4419
|
"generate_types"
|
|
4417
4420
|
];
|
|
4418
|
-
const writeActions = ["apply_migration", "push_migrations", "create_table_rls"];
|
|
4421
|
+
const writeActions = ["apply_migration", "push_migrations", "baseline_migrations", "create_table_rls"];
|
|
4419
4422
|
const allActions = readOnly ? actions : [...actions, ...writeActions];
|
|
4420
4423
|
server.tool("database", `Database operations: query, schema, RLS, migrations, stats.
|
|
4421
4424
|
Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
|
|
@@ -4423,8 +4426,8 @@ Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
|
|
|
4423
4426
|
ref: projectRef ? exports_external.string().optional() : exports_external.string().optional().describe("Project ref"),
|
|
4424
4427
|
sql: exports_external.string().optional().describe("[query/apply_migration] SQL statement"),
|
|
4425
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)"),
|
|
4426
|
-
dir: exports_external.string().optional().describe("[push_migrations] Directory containing .sql migration files (default: supabase/migrations)"),
|
|
4427
|
-
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"),
|
|
4428
4431
|
schema: exports_external.string().optional().describe("[*] Schema name (default: public)"),
|
|
4429
4432
|
table: exports_external.string().optional().describe("[describe_columns/indexes/constraints/rls_*] Table name"),
|
|
4430
4433
|
schemas: exports_external.array(exports_external.string()).optional().describe("[list_tables/generate_types] Schemas array"),
|
|
@@ -4444,7 +4447,7 @@ Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
|
|
|
4444
4447
|
return { content: [{ type: "text", text: `❌ Failed to read file ${args.file}: ${e.message}` }] };
|
|
4445
4448
|
}
|
|
4446
4449
|
}
|
|
4447
|
-
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 });
|
|
4448
4451
|
let text;
|
|
4449
4452
|
switch (action) {
|
|
4450
4453
|
case "query": {
|
|
@@ -4646,6 +4649,64 @@ Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
|
|
|
4646
4649
|
`);
|
|
4647
4650
|
break;
|
|
4648
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
|
+
}
|
|
4649
4710
|
case "create_table_rls": {
|
|
4650
4711
|
if (!args.table || !args.columns)
|
|
4651
4712
|
throw new Error("'table' and 'columns' required");
|
|
@@ -4660,6 +4721,48 @@ Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
|
|
|
4660
4721
|
return { content: [{ type: "text", text }] };
|
|
4661
4722
|
});
|
|
4662
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
|
+
}
|
|
4663
4766
|
function formatSqlResult(data) {
|
|
4664
4767
|
if (!data || typeof data !== "object")
|
|
4665
4768
|
return JSON.stringify(data, null, 2);
|
|
@@ -5642,6 +5745,9 @@ function registerUserProjectCliTools(server, http, options = {}) {
|
|
|
5642
5745
|
}
|
|
5643
5746
|
|
|
5644
5747
|
// src/index.ts
|
|
5748
|
+
var invokedCommand = path.basename(process.argv[1] || "supacloud-cli");
|
|
5749
|
+
var commandName = invokedCommand === "supacloud" ? "supacloud" : "supacloud-cli";
|
|
5750
|
+
var preferredCommand = "supacloud-cli";
|
|
5645
5751
|
var projectActionSchema = exports_external.enum(["get", "health", "logs", "api_keys", "settings", "tasks"]);
|
|
5646
5752
|
var genericActionSchema = exports_external.string();
|
|
5647
5753
|
function unwrapMcpSchema(schema) {
|
|
@@ -5671,15 +5777,17 @@ function printHelp(context = resolveSupaCloudContext()) {
|
|
|
5671
5777
|
const autoLink = context.inferredSupabaseUrl ? `Project context: ${context.inferredSupabaseUrl} (${context.source})` : "Project context: not detected";
|
|
5672
5778
|
console.error(`
|
|
5673
5779
|
╔═══════════════════════════════════════════════════════════╗
|
|
5674
|
-
║ supacloud
|
|
5780
|
+
║ supacloud-cli ║
|
|
5675
5781
|
║ Project CLI for SupaCloud users ║
|
|
5676
5782
|
╚═══════════════════════════════════════════════════════════╝
|
|
5677
5783
|
|
|
5784
|
+
${commandName === "supacloud" ? "NOTE\n\n `supacloud` is kept as a compatibility alias. Prefer `supacloud-cli`\n to avoid confusion with the server binary at /usr/local/bin/supacloud.\n" : ""}
|
|
5785
|
+
|
|
5678
5786
|
USAGE
|
|
5679
5787
|
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5788
|
+
${preferredCommand} <module> <action> [--flags]
|
|
5789
|
+
${preferredCommand} status
|
|
5790
|
+
${preferredCommand} --help
|
|
5683
5791
|
|
|
5684
5792
|
DEFAULT CONTEXT
|
|
5685
5793
|
|
|
@@ -5692,15 +5800,15 @@ DEFAULT CONTEXT
|
|
|
5692
5800
|
|
|
5693
5801
|
EXAMPLES
|
|
5694
5802
|
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5803
|
+
${preferredCommand} status
|
|
5804
|
+
${preferredCommand} project get
|
|
5805
|
+
${preferredCommand} project logs --log_type database
|
|
5806
|
+
${preferredCommand} frontend list --ref abc123
|
|
5807
|
+
${preferredCommand} database query --sql "select now()"
|
|
5808
|
+
${preferredCommand} database query --ref abc123 --file ./queries/vector-search.sql
|
|
5809
|
+
${preferredCommand} database push_migrations --ref abc123 --dir supabase/migrations --dry_run
|
|
5810
|
+
${preferredCommand} edge_functions deploy --ref abc123 --slug hello --path ./supabase/functions/hello
|
|
5811
|
+
${preferredCommand} edge_functions config --ref abc123 --slug hello --verify_jwt false --background_routes "/queue/*,/render/*"
|
|
5704
5812
|
|
|
5705
5813
|
SEPARATE ADMIN CLI
|
|
5706
5814
|
|
|
@@ -5747,8 +5855,8 @@ function createCliTools() {
|
|
|
5747
5855
|
" - SUPACLOUD_API_URL + SUPACLOUD_API_TOKEN",
|
|
5748
5856
|
"",
|
|
5749
5857
|
"Then retry commands such as:",
|
|
5750
|
-
|
|
5751
|
-
|
|
5858
|
+
` ${preferredCommand} project get`,
|
|
5859
|
+
` ${preferredCommand} project logs --log_type database`
|
|
5752
5860
|
].join(`
|
|
5753
5861
|
`)
|
|
5754
5862
|
}
|
|
@@ -5762,7 +5870,7 @@ function createCliTools() {
|
|
|
5762
5870
|
content: [
|
|
5763
5871
|
{
|
|
5764
5872
|
type: "text",
|
|
5765
|
-
text:
|
|
5873
|
+
text: `⚠️ This command requires project-scoped API context. Run \`${preferredCommand} status\` to inspect current detection.`
|
|
5766
5874
|
}
|
|
5767
5875
|
]
|
|
5768
5876
|
})
|
|
@@ -5778,9 +5886,9 @@ function createCliTools() {
|
|
|
5778
5886
|
{
|
|
5779
5887
|
type: "text",
|
|
5780
5888
|
text: [
|
|
5781
|
-
|
|
5889
|
+
`⚠️ No project context found for ${preferredCommand}.`,
|
|
5782
5890
|
"",
|
|
5783
|
-
|
|
5891
|
+
`${preferredCommand} expects project-scoped credentials by default.`,
|
|
5784
5892
|
"Provide one of these sources:",
|
|
5785
5893
|
"",
|
|
5786
5894
|
" 1. Current workspace .env",
|
|
@@ -5841,9 +5949,9 @@ async function main() {
|
|
|
5841
5949
|
console.log(JSON.stringify(result, null, 2));
|
|
5842
5950
|
return;
|
|
5843
5951
|
}
|
|
5844
|
-
await runCli(cliTools, args, { commandName
|
|
5952
|
+
await runCli(cliTools, args, { commandName });
|
|
5845
5953
|
}
|
|
5846
5954
|
main().catch((error) => {
|
|
5847
|
-
console.error(
|
|
5955
|
+
console.error(`${commandName} failed:`, error);
|
|
5848
5956
|
process.exit(1);
|
|
5849
5957
|
});
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supacloud/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "Project-scoped CLI for SupaCloud users",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
|
+
"supacloud-cli": "dist/index.js",
|
|
8
9
|
"supacloud": "dist/index.js"
|
|
9
10
|
},
|
|
10
11
|
"files": [
|