@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.
Files changed (3) hide show
  1. package/README.md +25 -8
  2. package/dist/index.js +133 -25
  3. 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` defaults to the current workspace's project context. If you do not pass explicit flags, it tries to auto-link from `.env`:
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
- npx @supacloud/cli status
14
- npx @supacloud/cli project get
15
- npx @supacloud/cli project logs --log_type database
16
- npx @supacloud/cli database query --sql "select now()"
17
- npx @supacloud/cli database query --ref abc123 --file ./queries/vector-search.sql
18
- npx @supacloud/cli database push_migrations --ref abc123 --dir supabase/migrations --dry_run
19
- npx @supacloud/cli frontend list --ref abc123
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] List pending migration files without applying them"),
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
- supacloud <module> <action> [--flags]
5681
- supacloud status
5682
- supacloud --help
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
- supacloud status
5696
- supacloud project get
5697
- supacloud project logs --log_type database
5698
- supacloud frontend list --ref abc123
5699
- supacloud database query --sql "select now()"
5700
- supacloud database query --ref abc123 --file ./queries/vector-search.sql
5701
- supacloud database push_migrations --ref abc123 --dir supabase/migrations --dry_run
5702
- supacloud edge_functions deploy --ref abc123 --slug hello --path ./supabase/functions/hello
5703
- supacloud edge_functions config --ref abc123 --slug hello --verify_jwt false --background_routes "/queue/*,/render/*"
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
- " supacloud project get",
5751
- " supacloud project logs --log_type database"
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: "⚠️ This command requires project-scoped API context. Run `supacloud status` to inspect current detection."
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
- "⚠️ No project context found for supacloud.",
5889
+ `⚠️ No project context found for ${preferredCommand}.`,
5782
5890
  "",
5783
- "supacloud expects project-scoped credentials by default.",
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: "supacloud" });
5952
+ await runCli(cliTools, args, { commandName });
5845
5953
  }
5846
5954
  main().catch((error) => {
5847
- console.error("supacloud failed:", 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.1",
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": [