@supacloud/cli 0.1.0 → 0.3.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 +20 -0
- package/dist/index.js +267 -70
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,9 +14,29 @@ npx @supacloud/cli status
|
|
|
14
14
|
npx @supacloud/cli project get
|
|
15
15
|
npx @supacloud/cli project logs --log_type database
|
|
16
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
|
|
17
19
|
npx @supacloud/cli frontend list --ref abc123
|
|
18
20
|
```
|
|
19
21
|
|
|
22
|
+
Use `database query --file` for complex SQL, pgvector queries, and single-request transaction blocks.
|
|
23
|
+
|
|
24
|
+
```sql
|
|
25
|
+
CREATE EXTENSION IF NOT EXISTS vector;
|
|
26
|
+
|
|
27
|
+
CREATE TABLE documents (
|
|
28
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
29
|
+
content text NOT NULL,
|
|
30
|
+
embedding vector(1536)
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
CREATE INDEX documents_embedding_hnsw_idx
|
|
34
|
+
ON documents
|
|
35
|
+
USING hnsw (embedding vector_cosine_ops);
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Transaction boundary: SupaCloud supports transaction blocks inside one SQL request and transactional migrations. It does not expose long-lived HTTP transaction sessions; use a direct Postgres DSN for application-side long transactions.
|
|
39
|
+
|
|
20
40
|
Project commands owned by this CLI:
|
|
21
41
|
|
|
22
42
|
- `project get`
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __returnValue = (v) => v;
|
|
5
4
|
function __exportSetter(name, newValue) {
|
|
@@ -14,7 +13,6 @@ var __export = (target, all) => {
|
|
|
14
13
|
set: __exportSetter.bind(all, name)
|
|
15
14
|
});
|
|
16
15
|
};
|
|
17
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
18
16
|
|
|
19
17
|
// node_modules/zod/v3/external.js
|
|
20
18
|
var exports_external = {};
|
|
@@ -4347,20 +4345,57 @@ class HttpTransport {
|
|
|
4347
4345
|
}
|
|
4348
4346
|
|
|
4349
4347
|
// src/shared/tools/database-tools.ts
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
const
|
|
4354
|
-
if (
|
|
4355
|
-
return
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4348
|
+
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
|
|
4349
|
+
import { basename, join } from "node:path";
|
|
4350
|
+
function migrationVersionFromFilename(file, fallbackIndex = 0) {
|
|
4351
|
+
const match = basename(file).match(/^(\d{8,20})[_-]/);
|
|
4352
|
+
if (match)
|
|
4353
|
+
return Number(match[1]);
|
|
4354
|
+
return Date.now() * 1000 + fallbackIndex;
|
|
4355
|
+
}
|
|
4356
|
+
function appliedMigrationKeys(data) {
|
|
4357
|
+
const rows = Array.isArray(data) ? data : data?.rows || [];
|
|
4358
|
+
const keys = new Set;
|
|
4359
|
+
for (const row of rows) {
|
|
4360
|
+
if (!row || typeof row !== "object")
|
|
4361
|
+
continue;
|
|
4362
|
+
const migration = row;
|
|
4363
|
+
if (migration.version != null)
|
|
4364
|
+
keys.add(String(migration.version));
|
|
4365
|
+
if (migration.name != null)
|
|
4366
|
+
keys.add(String(migration.name));
|
|
4367
|
+
}
|
|
4368
|
+
return keys;
|
|
4369
|
+
}
|
|
4370
|
+
function sqlReferencesVector(sql) {
|
|
4371
|
+
return /\bvector\s*\(\s*\d+\s*\)/i.test(sql) || /::\s*vector\b/i.test(sql) || /\bvector_(cosine|l2|ip)_ops\b/i.test(sql) || /<=>|<#>|<->/.test(sql);
|
|
4372
|
+
}
|
|
4373
|
+
function sqlCreatesVectorExtension(sql) {
|
|
4374
|
+
return /\bCREATE\s+EXTENSION\s+(?:IF\s+NOT\s+EXISTS\s+)?["']?vector["']?/i.test(sql);
|
|
4375
|
+
}
|
|
4376
|
+
function extensionRows(data) {
|
|
4377
|
+
if (Array.isArray(data))
|
|
4378
|
+
return data;
|
|
4379
|
+
const shaped = data;
|
|
4380
|
+
const rows = shaped?.rows || [];
|
|
4381
|
+
return Array.isArray(rows) ? rows : [];
|
|
4382
|
+
}
|
|
4383
|
+
function vectorWarningsForPendingMigrations(migrations, vectorEnabled) {
|
|
4384
|
+
const warnings = [];
|
|
4385
|
+
const vectorUsers = migrations.filter(({ sql }) => sqlReferencesVector(sql));
|
|
4386
|
+
const vectorCreators = migrations.filter(({ sql }) => sqlCreatesVectorExtension(sql));
|
|
4387
|
+
if (!vectorUsers.length && !vectorCreators.length)
|
|
4388
|
+
return warnings;
|
|
4389
|
+
if (vectorEnabled === false && vectorUsers.length && !vectorCreators.length) {
|
|
4390
|
+
warnings.push(`vector extension is not enabled, but pending migrations use vector types/operators: ${vectorUsers.map(({ file }) => file).join(", ")}`);
|
|
4391
|
+
}
|
|
4392
|
+
if (vectorEnabled === false && vectorCreators.length) {
|
|
4393
|
+
warnings.push(`pending migrations will enable pgvector: ${vectorCreators.map(({ file }) => file).join(", ")}`);
|
|
4394
|
+
}
|
|
4395
|
+
if (vectorEnabled === null) {
|
|
4396
|
+
warnings.push("could not verify whether vector extension is enabled");
|
|
4397
|
+
}
|
|
4398
|
+
return warnings;
|
|
4364
4399
|
}
|
|
4365
4400
|
function registerDatabaseTools(server, http, config = {}) {
|
|
4366
4401
|
const { readOnly = false, projectRef } = config;
|
|
@@ -4382,7 +4417,7 @@ function registerDatabaseTools(server, http, config = {}) {
|
|
|
4382
4417
|
"project_url",
|
|
4383
4418
|
"generate_types"
|
|
4384
4419
|
];
|
|
4385
|
-
const writeActions = ["apply_migration", "create_table_rls"];
|
|
4420
|
+
const writeActions = ["apply_migration", "push_migrations", "create_table_rls"];
|
|
4386
4421
|
const allActions = readOnly ? actions : [...actions, ...writeActions];
|
|
4387
4422
|
server.tool("database", `Database operations: query, schema, RLS, migrations, stats.
|
|
4388
4423
|
Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
|
|
@@ -4390,6 +4425,8 @@ Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
|
|
|
4390
4425
|
ref: projectRef ? exports_external.string().optional() : exports_external.string().optional().describe("Project ref"),
|
|
4391
4426
|
sql: exports_external.string().optional().describe("[query/apply_migration] SQL statement"),
|
|
4392
4427
|
file: exports_external.string().optional().describe("[query/apply_migration] Read SQL from local file path (avoids shell escaping issues with $$ and multi-statement DDL)"),
|
|
4428
|
+
dir: exports_external.string().optional().describe("[push_migrations] Directory containing .sql migration files (default: supabase/migrations)"),
|
|
4429
|
+
dry_run: exports_external.boolean().optional().describe("[push_migrations] List pending migration files without applying them"),
|
|
4393
4430
|
schema: exports_external.string().optional().describe("[*] Schema name (default: public)"),
|
|
4394
4431
|
table: exports_external.string().optional().describe("[describe_columns/indexes/constraints/rls_*] Table name"),
|
|
4395
4432
|
schemas: exports_external.array(exports_external.string()).optional().describe("[list_tables/generate_types] Schemas array"),
|
|
@@ -4404,12 +4441,12 @@ Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
|
|
|
4404
4441
|
const schemas = args.schemas || ["public"];
|
|
4405
4442
|
if (args.file && !args.sql) {
|
|
4406
4443
|
try {
|
|
4407
|
-
args.sql =
|
|
4444
|
+
args.sql = readFileSync2(args.file, "utf-8");
|
|
4408
4445
|
} catch (e) {
|
|
4409
4446
|
return { content: [{ type: "text", text: `❌ Failed to read file ${args.file}: ${e.message}` }] };
|
|
4410
4447
|
}
|
|
4411
4448
|
}
|
|
4412
|
-
const execSql = async (sql) =>
|
|
4449
|
+
const execSql = async (sql) => http.post(`/v1/projects/${ref}/database/sql`, { sql });
|
|
4413
4450
|
let text;
|
|
4414
4451
|
switch (action) {
|
|
4415
4452
|
case "query": {
|
|
@@ -4532,6 +4569,85 @@ Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
|
|
|
4532
4569
|
text = r.ok ? `✅ Migration '${args.name}' applied` : `❌ Failed (${r.status}): ${JSON.stringify(r.data)}`;
|
|
4533
4570
|
break;
|
|
4534
4571
|
}
|
|
4572
|
+
case "push_migrations": {
|
|
4573
|
+
const dir = args.dir || "supabase/migrations";
|
|
4574
|
+
if (!existsSync2(dir) || !statSync(dir).isDirectory()) {
|
|
4575
|
+
throw new Error(`Migration directory not found: ${dir}`);
|
|
4576
|
+
}
|
|
4577
|
+
const files = readdirSync(dir).filter((file) => file.endsWith(".sql")).sort();
|
|
4578
|
+
if (!files.length) {
|
|
4579
|
+
text = `No .sql migration files found in ${dir}`;
|
|
4580
|
+
break;
|
|
4581
|
+
}
|
|
4582
|
+
const migrationFiles = files.map((file, i) => ({
|
|
4583
|
+
file,
|
|
4584
|
+
name: basename(file, ".sql"),
|
|
4585
|
+
version: migrationVersionFromFilename(file, i)
|
|
4586
|
+
}));
|
|
4587
|
+
if (args.dry_run) {
|
|
4588
|
+
const r = await http.get(`/v1/projects/${ref}/database/migrations`);
|
|
4589
|
+
if (!r.ok) {
|
|
4590
|
+
text = `❌ Failed to load applied migrations (${r.status}): ${JSON.stringify(r.data)}`;
|
|
4591
|
+
break;
|
|
4592
|
+
}
|
|
4593
|
+
const appliedKeys = appliedMigrationKeys(r.data);
|
|
4594
|
+
const pending = migrationFiles.filter(({ name, version }) => !appliedKeys.has(name) && !appliedKeys.has(String(version)));
|
|
4595
|
+
const alreadyApplied = migrationFiles.filter(({ name, version }) => appliedKeys.has(name) || appliedKeys.has(String(version)));
|
|
4596
|
+
const pendingWithSql = pending.map((migration) => ({
|
|
4597
|
+
...migration,
|
|
4598
|
+
sql: readFileSync2(join(dir, migration.file), "utf-8")
|
|
4599
|
+
}));
|
|
4600
|
+
let vectorEnabled = null;
|
|
4601
|
+
if (pendingWithSql.some(({ sql }) => sqlReferencesVector(sql) || sqlCreatesVectorExtension(sql))) {
|
|
4602
|
+
const extResult = await execSql("SELECT extname AS name FROM pg_extension WHERE extname = 'vector';");
|
|
4603
|
+
vectorEnabled = extResult.ok ? extensionRows(extResult.data).some((row) => row.name === "vector" || row.extname === "vector") : null;
|
|
4604
|
+
}
|
|
4605
|
+
const warnings = vectorWarningsForPendingMigrations(pendingWithSql, vectorEnabled);
|
|
4606
|
+
text = [
|
|
4607
|
+
`Migration dry run for ${dir}`,
|
|
4608
|
+
`Total: ${migrationFiles.length}`,
|
|
4609
|
+
"",
|
|
4610
|
+
"Pending:",
|
|
4611
|
+
...pending.length ? pending.map(({ file, version }) => ` - ${file} (${version})`) : [" - none"],
|
|
4612
|
+
"",
|
|
4613
|
+
"Already applied:",
|
|
4614
|
+
...alreadyApplied.length ? alreadyApplied.map(({ file, version }) => ` - ${file} (${version})`) : [" - none"],
|
|
4615
|
+
...warnings.length ? ["", "Warnings:", ...warnings.map((warning) => ` - ${warning}`)] : []
|
|
4616
|
+
].join(`
|
|
4617
|
+
`);
|
|
4618
|
+
break;
|
|
4619
|
+
}
|
|
4620
|
+
const applied = [];
|
|
4621
|
+
const skipped = [];
|
|
4622
|
+
for (const { file, name, version } of migrationFiles) {
|
|
4623
|
+
const sql = readFileSync2(join(dir, file), "utf-8");
|
|
4624
|
+
const r = await http.post(`/v1/projects/${ref}/database/migrations`, { name, sql, version });
|
|
4625
|
+
if (r.ok) {
|
|
4626
|
+
applied.push(file);
|
|
4627
|
+
} else if (r.status === 409) {
|
|
4628
|
+
skipped.push(file);
|
|
4629
|
+
} else {
|
|
4630
|
+
text = [
|
|
4631
|
+
`❌ Failed to apply ${file} (${r.status})`,
|
|
4632
|
+
JSON.stringify(r.data, null, 2),
|
|
4633
|
+
"",
|
|
4634
|
+
`Applied before failure: ${applied.length}`,
|
|
4635
|
+
`Skipped before failure: ${skipped.length}`
|
|
4636
|
+
].join(`
|
|
4637
|
+
`);
|
|
4638
|
+
return { content: [{ type: "text", text }] };
|
|
4639
|
+
}
|
|
4640
|
+
}
|
|
4641
|
+
text = [
|
|
4642
|
+
`✅ Migration push completed for ${dir}`,
|
|
4643
|
+
`Applied: ${applied.length}`,
|
|
4644
|
+
`Skipped: ${skipped.length}`,
|
|
4645
|
+
...applied.length ? ["", "Applied files:", ...applied.map((file) => ` - ${file}`)] : [],
|
|
4646
|
+
...skipped.length ? ["", "Skipped files:", ...skipped.map((file) => ` - ${file}`)] : []
|
|
4647
|
+
].join(`
|
|
4648
|
+
`);
|
|
4649
|
+
break;
|
|
4650
|
+
}
|
|
4535
4651
|
case "create_table_rls": {
|
|
4536
4652
|
if (!args.table || !args.columns)
|
|
4537
4653
|
throw new Error("'table' and 'columns' required");
|
|
@@ -5028,69 +5144,125 @@ Actions: status, list_buckets, list_files, upload_base64, delete_file`, {
|
|
|
5028
5144
|
}
|
|
5029
5145
|
|
|
5030
5146
|
// src/shared/tools/advanced-tools.ts
|
|
5147
|
+
import { existsSync as existsSync3, mkdtempSync, readFileSync as readFileSync3, rmSync, statSync as statSync2, writeFileSync } from "node:fs";
|
|
5148
|
+
import { tmpdir } from "node:os";
|
|
5149
|
+
import { basename as basename2, join as join2, resolve as resolve2 } from "node:path";
|
|
5150
|
+
import { promisify } from "node:util";
|
|
5151
|
+
import { execFile } from "node:child_process";
|
|
5152
|
+
var execFileAsync = promisify(execFile);
|
|
5153
|
+
async function runBunBuild(args) {
|
|
5154
|
+
try {
|
|
5155
|
+
return await execFileAsync("bun", ["build", ...args], { maxBuffer: 10 * 1024 * 1024 });
|
|
5156
|
+
} catch (error) {
|
|
5157
|
+
const e = error;
|
|
5158
|
+
if (e.code === "ENOENT") {
|
|
5159
|
+
throw new Error("Bun is required for local edge function bundling. Install Bun or use deploy_bundle with explicit files.");
|
|
5160
|
+
}
|
|
5161
|
+
throw error;
|
|
5162
|
+
}
|
|
5163
|
+
}
|
|
5164
|
+
async function bundleEdgeFunctionPath(pathArg) {
|
|
5165
|
+
const entrypoint = resolveEntrypoint(pathArg);
|
|
5166
|
+
const tmpDir = mkdtempSync(join2(tmpdir(), "supacloud-edge-"));
|
|
5167
|
+
const outfile = join2(tmpDir, `${basename2(entrypoint).replace(/\.[^.]+$/, "") || "index"}.js`);
|
|
5168
|
+
try {
|
|
5169
|
+
const { stderr } = await runBunBuild([entrypoint, "--target", "bun", "--outfile", outfile]);
|
|
5170
|
+
if (!existsSync3(outfile))
|
|
5171
|
+
throw new Error(`Bundle failed: ${stderr}`);
|
|
5172
|
+
return readFileSync3(outfile, "utf-8");
|
|
5173
|
+
} finally {
|
|
5174
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
5175
|
+
}
|
|
5176
|
+
}
|
|
5177
|
+
function resolveEntrypoint(pathArg) {
|
|
5178
|
+
const resolved = resolve2(pathArg);
|
|
5179
|
+
const stat = statSync2(resolved);
|
|
5180
|
+
if (!stat.isDirectory())
|
|
5181
|
+
return resolved;
|
|
5182
|
+
const entrypoint = join2(resolved, "index.ts");
|
|
5183
|
+
if (!existsSync3(entrypoint)) {
|
|
5184
|
+
throw new Error(`Directory provided but no index.ts found at ${entrypoint}`);
|
|
5185
|
+
}
|
|
5186
|
+
return entrypoint;
|
|
5187
|
+
}
|
|
5188
|
+
var backgroundRoutesSchema = exports_external.preprocess((value) => {
|
|
5189
|
+
if (typeof value !== "string")
|
|
5190
|
+
return value;
|
|
5191
|
+
const trimmed = value.trim();
|
|
5192
|
+
if (!trimmed)
|
|
5193
|
+
return [];
|
|
5194
|
+
if (!trimmed.startsWith("["))
|
|
5195
|
+
return trimmed.split(",").map((route) => route.trim()).filter(Boolean);
|
|
5196
|
+
try {
|
|
5197
|
+
return JSON.parse(trimmed);
|
|
5198
|
+
} catch {
|
|
5199
|
+
return [trimmed];
|
|
5200
|
+
}
|
|
5201
|
+
}, exports_external.array(exports_external.string()).optional().superRefine((routes, ctx) => {
|
|
5202
|
+
if (!routes)
|
|
5203
|
+
return;
|
|
5204
|
+
for (const route of routes) {
|
|
5205
|
+
if (route.trim().startsWith("[")) {
|
|
5206
|
+
ctx.addIssue({
|
|
5207
|
+
code: exports_external.ZodIssueCode.custom,
|
|
5208
|
+
message: "Invalid background_routes JSON array. Use a valid JSON array or comma-separated routes like /queue/*,/render/*."
|
|
5209
|
+
});
|
|
5210
|
+
return;
|
|
5211
|
+
}
|
|
5212
|
+
}
|
|
5213
|
+
}));
|
|
5031
5214
|
function registerAdvancedTools(server, http) {
|
|
5032
5215
|
server.tool("edge_functions", `Edge Function management (Deno/Bun serverless). Server auto-bundles dependencies.
|
|
5033
|
-
Actions: list, deploy, deploy_bundle, source, delete, check`, {
|
|
5034
|
-
action: exports_external.enum(["list", "deploy", "deploy_bundle", "source", "delete", "check"]).describe("Action"),
|
|
5216
|
+
Actions: list, deploy, deploy_bundle, config, source, delete, check`, {
|
|
5217
|
+
action: exports_external.enum(["list", "deploy", "deploy_bundle", "config", "source", "delete", "check"]).describe("Action"),
|
|
5035
5218
|
ref: exports_external.string().describe("Project ref"),
|
|
5036
|
-
slug: exports_external.string().optional().describe("[deploy/deploy_bundle/source/delete/check] Function name"),
|
|
5219
|
+
slug: exports_external.string().optional().describe("[deploy/deploy_bundle/config/source/delete/check] Function name"),
|
|
5037
5220
|
code: exports_external.string().optional().describe("[deploy/check] Function source code (TypeScript)"),
|
|
5038
5221
|
path: exports_external.string().optional().describe("[deploy/check] Local file path to read code from (alternative to code)"),
|
|
5039
5222
|
files: exports_external.record(exports_external.string()).optional().describe("[deploy_bundle] File map: { 'index.ts': '...', '_shared/x.ts': '...' }"),
|
|
5040
5223
|
entrypoint: exports_external.string().optional().describe("[deploy_bundle] Entrypoint file (default: index.ts)"),
|
|
5041
|
-
minify: exports_external.boolean().optional().describe("[deploy/deploy_bundle] Minify bundle")
|
|
5224
|
+
minify: exports_external.boolean().optional().describe("[deploy/deploy_bundle] Minify bundle"),
|
|
5225
|
+
verify_jwt: exports_external.boolean().optional().describe("[deploy/deploy_bundle/config] Set JWT verification for this function"),
|
|
5226
|
+
background_routes: backgroundRoutesSchema.describe("[deploy/deploy_bundle/config] Background route paths; pass comma-separated or JSON array in CLI")
|
|
5042
5227
|
}, async (args) => {
|
|
5043
|
-
const { action, ref, slug, path: pathArg, files, entrypoint, minify } = args;
|
|
5228
|
+
const { action, ref, slug, path: pathArg, files, entrypoint, minify, verify_jwt, background_routes } = args;
|
|
5044
5229
|
let code = args.code;
|
|
5045
5230
|
const need = (f, v) => {
|
|
5046
5231
|
if (!v)
|
|
5047
5232
|
throw new Error(`'${f}' required for '${action}'`);
|
|
5048
5233
|
};
|
|
5049
5234
|
let text;
|
|
5235
|
+
const functionConfig = () => ({
|
|
5236
|
+
...typeof verify_jwt === "boolean" ? { verify_jwt } : {},
|
|
5237
|
+
...Array.isArray(background_routes) ? { background_routes } : {}
|
|
5238
|
+
});
|
|
5239
|
+
const hasFunctionConfig = () => Object.keys(functionConfig()).length > 0;
|
|
5240
|
+
const updateFunctionConfig = async () => {
|
|
5241
|
+
need("slug", slug);
|
|
5242
|
+
if (!hasFunctionConfig()) {
|
|
5243
|
+
throw new Error("'verify_jwt' or 'background_routes' required for 'config'");
|
|
5244
|
+
}
|
|
5245
|
+
const cr = await http.patch(`/v1/projects/${ref}/functions/${slug}/config`, functionConfig());
|
|
5246
|
+
return cr.ok ? `✅ Function ${slug} config updated
|
|
5247
|
+
${JSON.stringify(cr.data, null, 2)}` : `❌ Config update failed (${cr.status}): ${JSON.stringify(cr.data)}`;
|
|
5248
|
+
};
|
|
5050
5249
|
const checkSyntax = async (sourceCode) => {
|
|
5051
|
-
const
|
|
5052
|
-
const
|
|
5053
|
-
|
|
5054
|
-
const execAsync = promisify(__require("child_process").exec);
|
|
5055
|
-
const tmpFile = `${os.tmpdir()}/supacloud_edge_${Date.now()}.ts`;
|
|
5056
|
-
fs.writeFileSync(tmpFile, sourceCode);
|
|
5250
|
+
const tmpDir = mkdtempSync(join2(tmpdir(), "supacloud-edge-check-"));
|
|
5251
|
+
const tmpFile = join2(tmpDir, "index.ts");
|
|
5252
|
+
writeFileSync(tmpFile, sourceCode);
|
|
5057
5253
|
try {
|
|
5058
|
-
await
|
|
5254
|
+
await runBunBuild([tmpFile, "--external", "*"]);
|
|
5059
5255
|
return { ok: true };
|
|
5060
5256
|
} catch (e) {
|
|
5061
|
-
return { ok: false, err: e.stdout
|
|
5062
|
-
|
|
5257
|
+
return { ok: false, err: `${e.stdout || ""}
|
|
5258
|
+
${e.stderr || e.message}` };
|
|
5063
5259
|
} finally {
|
|
5064
|
-
|
|
5065
|
-
fs.unlinkSync(tmpFile);
|
|
5066
|
-
} catch (e) {}
|
|
5260
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
5067
5261
|
}
|
|
5068
5262
|
};
|
|
5069
5263
|
if (pathArg && !code) {
|
|
5070
5264
|
try {
|
|
5071
|
-
|
|
5072
|
-
const os = __require("os");
|
|
5073
|
-
const { promisify } = __require("util");
|
|
5074
|
-
const execAsync = promisify(__require("child_process").exec);
|
|
5075
|
-
const stat = fs.statSync(pathArg);
|
|
5076
|
-
let entrypoint2 = pathArg;
|
|
5077
|
-
if (stat.isDirectory()) {
|
|
5078
|
-
entrypoint2 = `${pathArg}/index.ts`;
|
|
5079
|
-
if (!fs.existsSync(entrypoint2)) {
|
|
5080
|
-
throw new Error(`Directory provided but no index.ts found at ${entrypoint2}`);
|
|
5081
|
-
}
|
|
5082
|
-
}
|
|
5083
|
-
const tmpOut = `${os.tmpdir()}/supacloud_bundled_${Date.now()}.js`;
|
|
5084
|
-
try {
|
|
5085
|
-
const { stderr } = await execAsync(`bun build ${entrypoint2} --target bun --outfile ${tmpOut}`);
|
|
5086
|
-
if (!fs.existsSync(tmpOut))
|
|
5087
|
-
throw new Error(`Bundle failed: ${stderr}`);
|
|
5088
|
-
code = fs.readFileSync(tmpOut, "utf-8");
|
|
5089
|
-
} finally {
|
|
5090
|
-
try {
|
|
5091
|
-
fs.unlinkSync(tmpOut);
|
|
5092
|
-
} catch (e) {}
|
|
5093
|
-
}
|
|
5265
|
+
code = await bundleEdgeFunctionPath(pathArg);
|
|
5094
5266
|
} catch (e) {
|
|
5095
5267
|
throw new Error(`Failed to bundle/read path ${pathArg}: ${e.message}`);
|
|
5096
5268
|
}
|
|
@@ -5119,13 +5291,26 @@ ${deployCheck.err}`;
|
|
|
5119
5291
|
break;
|
|
5120
5292
|
}
|
|
5121
5293
|
const dr = await http.post(`/v1/projects/${ref}/functions/${slug}`, { code, minify });
|
|
5122
|
-
|
|
5294
|
+
if (!dr.ok) {
|
|
5295
|
+
text = `❌ Failed (${dr.status}): ${JSON.stringify(dr.data)}`;
|
|
5296
|
+
break;
|
|
5297
|
+
}
|
|
5298
|
+
text = hasFunctionConfig() ? `✅ Function ${slug} deployed
|
|
5299
|
+
${await updateFunctionConfig()}` : `✅ Function ${slug} deployed`;
|
|
5123
5300
|
break;
|
|
5124
5301
|
case "deploy_bundle":
|
|
5125
5302
|
need("slug", slug);
|
|
5126
5303
|
need("files", files);
|
|
5127
5304
|
const br = await http.post(`/v1/projects/${ref}/functions/${slug}/bundle`, { files, entrypoint, minify });
|
|
5128
|
-
|
|
5305
|
+
if (!br.ok) {
|
|
5306
|
+
text = `❌ Failed (${br.status}): ${JSON.stringify(br.data)}`;
|
|
5307
|
+
break;
|
|
5308
|
+
}
|
|
5309
|
+
text = hasFunctionConfig() ? `✅ Function ${slug} bundle deployed (${Object.keys(files).length} files)
|
|
5310
|
+
${await updateFunctionConfig()}` : `✅ Function ${slug} bundle deployed (${Object.keys(files).length} files)`;
|
|
5311
|
+
break;
|
|
5312
|
+
case "config":
|
|
5313
|
+
text = await updateFunctionConfig();
|
|
5129
5314
|
break;
|
|
5130
5315
|
case "source":
|
|
5131
5316
|
need("slug", slug);
|
|
@@ -5230,7 +5415,8 @@ ${JSON.stringify(r.data, null, 2)}` : `❌ Failed (${r.status})`;
|
|
|
5230
5415
|
}
|
|
5231
5416
|
|
|
5232
5417
|
// src/shared/tools/frontend-tools.ts
|
|
5233
|
-
import {
|
|
5418
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
|
|
5419
|
+
import { basename as basename3 } from "node:path";
|
|
5234
5420
|
function registerFrontendTools(server, http) {
|
|
5235
5421
|
server.tool("frontend", `Frontend hosting (static sites & SSR). Supports: static, react, vue, svelte, sveltekit, nextjs, nuxt, astro.
|
|
5236
5422
|
Actions: list, get, create, update, delete, deploy_git, deploy_upload, redeploy, build_logs, add_domain, remove_domain, set_env, list_frameworks, list_records`, {
|
|
@@ -5324,14 +5510,12 @@ Actions: list, get, create, update, delete, deploy_git, deploy_upload, redeploy,
|
|
|
5324
5510
|
need("ref", ref);
|
|
5325
5511
|
need("id", id);
|
|
5326
5512
|
need("zip_path", zip_path);
|
|
5327
|
-
|
|
5328
|
-
if (!await file.exists()) {
|
|
5513
|
+
if (!existsSync4(zip_path)) {
|
|
5329
5514
|
throw new Error(`Zip file not found: ${zip_path}`);
|
|
5330
5515
|
}
|
|
5516
|
+
const zipBuffer = readFileSync4(zip_path);
|
|
5331
5517
|
const form = new FormData;
|
|
5332
|
-
form.append("file", new
|
|
5333
|
-
type: "application/zip"
|
|
5334
|
-
}));
|
|
5518
|
+
form.append("file", new Blob([zipBuffer], { type: "application/zip" }), basename3(zip_path));
|
|
5335
5519
|
text = ok(await http.postMultipart(`/v1/projects/${ref}/frontend/deployments/${id}/deploy/upload`, form));
|
|
5336
5520
|
break;
|
|
5337
5521
|
case "redeploy":
|
|
@@ -5462,6 +5646,15 @@ function registerUserProjectCliTools(server, http, options = {}) {
|
|
|
5462
5646
|
// src/index.ts
|
|
5463
5647
|
var projectActionSchema = exports_external.enum(["get", "health", "logs", "api_keys", "settings", "tasks"]);
|
|
5464
5648
|
var genericActionSchema = exports_external.string();
|
|
5649
|
+
function unwrapMcpSchema(schema) {
|
|
5650
|
+
if (schema && typeof schema === "object" && !Array.isArray(schema) && "args" in schema) {
|
|
5651
|
+
const argsSchema = schema.args;
|
|
5652
|
+
if (argsSchema && typeof argsSchema === "object" && !Array.isArray(argsSchema)) {
|
|
5653
|
+
return argsSchema;
|
|
5654
|
+
}
|
|
5655
|
+
}
|
|
5656
|
+
return schema;
|
|
5657
|
+
}
|
|
5465
5658
|
function captureTools(register) {
|
|
5466
5659
|
const tools = {};
|
|
5467
5660
|
const server = {
|
|
@@ -5469,7 +5662,7 @@ function captureTools(register) {
|
|
|
5469
5662
|
if (typeof schemaOrCallback === "function") {
|
|
5470
5663
|
tools[name] = { schema: {}, callback: schemaOrCallback };
|
|
5471
5664
|
} else {
|
|
5472
|
-
tools[name] = { schema: schemaOrCallback, callback };
|
|
5665
|
+
tools[name] = { schema: unwrapMcpSchema(schemaOrCallback), callback };
|
|
5473
5666
|
}
|
|
5474
5667
|
}
|
|
5475
5668
|
};
|
|
@@ -5506,7 +5699,10 @@ EXAMPLES
|
|
|
5506
5699
|
supacloud project logs --log_type database
|
|
5507
5700
|
supacloud frontend list --ref abc123
|
|
5508
5701
|
supacloud database query --sql "select now()"
|
|
5702
|
+
supacloud database query --ref abc123 --file ./queries/vector-search.sql
|
|
5703
|
+
supacloud database push_migrations --ref abc123 --dir supabase/migrations --dry_run
|
|
5509
5704
|
supacloud edge_functions deploy --ref abc123 --slug hello --path ./supabase/functions/hello
|
|
5705
|
+
supacloud edge_functions config --ref abc123 --slug hello --verify_jwt false --background_routes "/queue/*,/render/*"
|
|
5510
5706
|
|
|
5511
5707
|
SEPARATE ADMIN CLI
|
|
5512
5708
|
|
|
@@ -5615,10 +5811,11 @@ function createCliTools() {
|
|
|
5615
5811
|
assign(captureTools((server) => registerUserProjectCliTools(server, http, {
|
|
5616
5812
|
projectRef: context.projectRef || undefined
|
|
5617
5813
|
})));
|
|
5618
|
-
|
|
5814
|
+
const databaseTools = captureTools((server) => registerDatabaseTools(server, http, {
|
|
5619
5815
|
projectRef: context.projectRef || undefined,
|
|
5620
5816
|
readOnly: context.readOnly
|
|
5621
|
-
}))
|
|
5817
|
+
}));
|
|
5818
|
+
assign(databaseTools);
|
|
5622
5819
|
assign(captureTools((server) => registerAuthTools(server, http)));
|
|
5623
5820
|
assign(captureTools((server) => registerStorageTools(server, http)));
|
|
5624
5821
|
assign(captureTools((server) => registerAdvancedTools(server, http)));
|