@motherbase/cli 0.2.4 → 0.2.6
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/LICENSE +21 -0
- package/README.md +131 -18
- package/bin/motherbase.js +10 -10
- package/package.json +35 -26
- package/src/api.js +233 -229
- package/src/commands/_util.js +106 -50
- package/src/commands/backups.js +16 -0
- package/src/commands/branches.js +113 -0
- package/src/commands/completion.js +90 -0
- package/src/commands/config.js +47 -47
- package/src/commands/cron.js +65 -65
- package/src/commands/db-catalog.js +326 -0
- package/src/commands/db.js +103 -92
- package/src/commands/domains.js +124 -0
- package/src/commands/functions.js +63 -33
- package/src/commands/gen.js +63 -17
- package/src/commands/import.js +55 -35
- package/src/commands/index.js +85 -64
- package/src/commands/inspect.js +178 -41
- package/src/commands/link.js +35 -35
- package/src/commands/local.js +23 -2
- package/src/commands/login.js +39 -39
- package/src/commands/logout.js +16 -16
- package/src/commands/mcp.js +27 -9
- package/src/commands/migrate.js +207 -0
- package/src/commands/migration.js +108 -33
- package/src/commands/network.js +83 -0
- package/src/commands/orgs.js +40 -0
- package/src/commands/postgres-config.js +113 -0
- package/src/commands/projects.js +130 -125
- package/src/commands/queues.js +133 -0
- package/src/commands/realtime.js +35 -35
- package/src/commands/rls.js +176 -176
- package/src/commands/secrets.js +66 -45
- package/src/commands/signing.js +130 -0
- package/src/commands/snippets.js +44 -0
- package/src/commands/status.js +42 -42
- package/src/commands/storage.js +261 -113
- package/src/commands/test.js +9 -0
- package/src/commands/unlink.js +15 -15
- package/src/commands/users.js +142 -113
- package/src/commands/vault.js +82 -0
- package/src/commands/webhooks.js +62 -62
- package/src/commands/whoami.js +20 -20
- package/src/commands/wrappers.js +150 -0
- package/src/config.js +98 -98
- package/src/context.js +106 -102
- package/src/docs.js +68 -0
- package/src/edge-templates.js +194 -0
- package/src/format.js +92 -0
- package/src/inspect/csv.js +22 -0
- package/src/inspect/queries.js +562 -0
- package/src/main.js +224 -160
- package/src/mcp/platform-tools.js +233 -0
- package/src/mcp/protocol.js +4 -1
- package/src/mcp/storage-tools.js +93 -0
- package/src/mcp/tools.js +318 -6
- package/src/migration/commands.js +375 -0
- package/src/migration/files.js +245 -0
- package/src/prompt.js +91 -91
- package/src/storage/remote.js +62 -0
- package/src/ui.js +111 -111
- package/src/commands/migrations.js +0 -85
|
@@ -4,8 +4,9 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const api = require('../api');
|
|
6
6
|
const ui = require('../ui');
|
|
7
|
-
const { output, requireArg } = require('./_util');
|
|
7
|
+
const { output, alias, requireArg, resolveInside } = require('./_util');
|
|
8
8
|
const { CliError } = require('../context');
|
|
9
|
+
const { templates, DEFAULT_TEMPLATE, findTemplate, renderTemplate } = require('../edge-templates');
|
|
9
10
|
|
|
10
11
|
// Functions live as repo files, one directory per function, the layout Supabase
|
|
11
12
|
// projects already use:
|
|
@@ -20,20 +21,30 @@ const DEFAULT_DIR = 'functions';
|
|
|
20
21
|
const LEGACY_DIR = path.join('motherbase', 'functions');
|
|
21
22
|
const ENTRY_NAMES = ['index.ts', 'index.js', 'index.mjs'];
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
// import { z } from 'zod';
|
|
30
|
-
//
|
|
31
|
-
// \`env\` holds this project's edge secrets.
|
|
24
|
+
// Written next to the functions so an editor knows the runtime globals the
|
|
25
|
+
// Deno edge runtime injects. Without it every `env.X` / `sendMail(...)` is a red squiggle.
|
|
26
|
+
const TYPES_FILE = 'motherbase.d.ts';
|
|
27
|
+
const TYPES = `// Generated by \`motherbase functions new\`. Runtime globals injected by the
|
|
28
|
+
// Motherbase edge runtime. Safe to re-generate: delete it and scaffold again.
|
|
32
29
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
/** This project's edge secrets (Studio > Edge functions > Secrets). */
|
|
31
|
+
declare const env: Record<string, string>;
|
|
32
|
+
|
|
33
|
+
/** The invoke payload, for the legacy expression-shaped function form. */
|
|
34
|
+
declare const context: Record<string, unknown>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Sends an email through this project's SMTP settings, the same transport the
|
|
38
|
+
* auth emails use: the project API sends it, so there is no mail API to sign up for.
|
|
39
|
+
* Rejects when SMTP is unconfigured, when \`to\` is not an address, or when
|
|
40
|
+
* neither \`text\` nor \`html\` is given.
|
|
41
|
+
*/
|
|
42
|
+
declare function sendMail(options: {
|
|
43
|
+
to: string;
|
|
44
|
+
subject: string;
|
|
45
|
+
text?: string;
|
|
46
|
+
html?: string;
|
|
47
|
+
}): Promise<{ ok: true }>;
|
|
37
48
|
`;
|
|
38
49
|
|
|
39
50
|
function functionsDir(ctx) {
|
|
@@ -75,16 +86,19 @@ function resolveLocal(ctx, name) {
|
|
|
75
86
|
}
|
|
76
87
|
|
|
77
88
|
/** Upsert by route, so deploying twice updates instead of conflicting. */
|
|
89
|
+
async function upsertFunction(ctx, body) {
|
|
90
|
+
const listing = await api.adminRequest(ctx, 'GET', '/edge-functions');
|
|
91
|
+
const existing = ((listing && listing.functions) || []).find(f => f.route === body.route);
|
|
92
|
+
if (existing) return api.adminRequest(ctx, 'PATCH', `/edge-functions/${existing.id}`, { body });
|
|
93
|
+
return api.adminRequest(ctx, 'POST', '/edge-functions', { body: { ...body, enabled: true } });
|
|
94
|
+
}
|
|
95
|
+
|
|
78
96
|
async function deployOne(ctx, fn, flags) {
|
|
79
97
|
const code = fs.readFileSync(fn.entry, 'utf8');
|
|
80
98
|
const body = { name: fn.name, route: fn.name, code };
|
|
81
99
|
if (flags && flags.memory) body.memoryLimitMb = Number(flags.memory);
|
|
82
100
|
if (flags && flags.timeout) body.timeoutMs = Number(flags.timeout);
|
|
83
|
-
|
|
84
|
-
const listing = await api.adminRequest(ctx, 'GET', '/edge-functions');
|
|
85
|
-
const existing = ((listing && listing.functions) || []).find(f => f.route === fn.name);
|
|
86
|
-
if (existing) return api.adminRequest(ctx, 'PATCH', `/edge-functions/${existing.id}`, { body });
|
|
87
|
-
return api.adminRequest(ctx, 'POST', '/edge-functions', { body: { ...body, enabled: true } });
|
|
101
|
+
return upsertFunction(ctx, body);
|
|
88
102
|
}
|
|
89
103
|
|
|
90
104
|
const subcommands = {
|
|
@@ -115,9 +129,13 @@ const subcommands = {
|
|
|
115
129
|
},
|
|
116
130
|
new: {
|
|
117
131
|
summary: 'Scaffold functions/<name>/index.ts',
|
|
118
|
-
usage:
|
|
119
|
-
async run(ctx, args) {
|
|
132
|
+
usage: `motherbase functions new <name> [--template ${templates.map(t => t.id).join('|')}]`,
|
|
133
|
+
async run(ctx, args, flags) {
|
|
120
134
|
const name = requireArg(args[0], 'name');
|
|
135
|
+
const template = findTemplate(flags.template || DEFAULT_TEMPLATE);
|
|
136
|
+
if (!template) {
|
|
137
|
+
throw new CliError(`Unknown template "${flags.template}". Use one of: ${templates.map(t => t.id).join(', ')}.`);
|
|
138
|
+
}
|
|
121
139
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(name)) {
|
|
122
140
|
throw new CliError('Function names are lowercase letters, digits and hyphens (the name is also the route).');
|
|
123
141
|
}
|
|
@@ -125,8 +143,13 @@ const subcommands = {
|
|
|
125
143
|
if (entryFile(dir)) throw new CliError(`${path.relative(ctx.cwd, dir)} already has an entry file.`);
|
|
126
144
|
fs.mkdirSync(dir, { recursive: true });
|
|
127
145
|
const file = path.join(dir, 'index.ts');
|
|
128
|
-
fs.writeFileSync(file,
|
|
146
|
+
fs.writeFileSync(file, renderTemplate(template, name));
|
|
129
147
|
ui.success(`Created ${path.relative(ctx.cwd, file)}`);
|
|
148
|
+
const types = path.join(functionsDir(ctx), TYPES_FILE);
|
|
149
|
+
if (!fs.existsSync(types)) {
|
|
150
|
+
fs.writeFileSync(types, TYPES);
|
|
151
|
+
ui.success(`Created ${path.relative(ctx.cwd, types)}`);
|
|
152
|
+
}
|
|
130
153
|
ui.info(`Deploy it with: motherbase functions deploy ${name}`);
|
|
131
154
|
},
|
|
132
155
|
},
|
|
@@ -141,11 +164,7 @@ const subcommands = {
|
|
|
141
164
|
if (!route) throw new CliError('--route is required with --file');
|
|
142
165
|
const code = fs.readFileSync(path.resolve(ctx.cwd, flags.file), 'utf8');
|
|
143
166
|
const name = flags.name || route;
|
|
144
|
-
const
|
|
145
|
-
const existing = ((listing && listing.functions) || []).find(f => f.route === route);
|
|
146
|
-
const data = existing
|
|
147
|
-
? await api.adminRequest(ctx, 'PATCH', `/edge-functions/${existing.id}`, { body: { name, route, code } })
|
|
148
|
-
: await api.adminRequest(ctx, 'POST', '/edge-functions', { body: { name, route, code, enabled: true } });
|
|
167
|
+
const data = await upsertFunction(ctx, { name, route, code });
|
|
149
168
|
return output(ctx, data, (_d, u) => u.success(`Deployed function ${name} (${route})`));
|
|
150
169
|
}
|
|
151
170
|
|
|
@@ -177,7 +196,7 @@ const subcommands = {
|
|
|
177
196
|
// The list endpoint may omit code, so read the function itself.
|
|
178
197
|
const detail = await api.adminRequest(ctx, 'GET', `/edge-functions/${fn.id}`);
|
|
179
198
|
const code = (detail && detail.function ? detail.function.code : detail && detail.code) || '';
|
|
180
|
-
const dir =
|
|
199
|
+
const dir = resolveInside(functionsDir(ctx), fn.route || fn.name);
|
|
181
200
|
fs.mkdirSync(dir, { recursive: true });
|
|
182
201
|
const file = path.join(dir, 'index.ts');
|
|
183
202
|
fs.writeFileSync(file, code);
|
|
@@ -190,8 +209,8 @@ const subcommands = {
|
|
|
190
209
|
summary: 'Watch local functions and redeploy on every save',
|
|
191
210
|
usage: 'motherbase functions serve [name]',
|
|
192
211
|
async run(ctx, args) {
|
|
193
|
-
// Deliberately not a second local runtime. The
|
|
194
|
-
// the
|
|
212
|
+
// Deliberately not a second local runtime. The runtime, the secrets and
|
|
213
|
+
// the mail bridge all live with the project API, and a local re-implementation
|
|
195
214
|
// would differ from production in exactly the ways that matter. This
|
|
196
215
|
// watches and redeploys instead, so the edit-invoke loop is one save long.
|
|
197
216
|
const targets = resolveLocal(ctx, args[0]);
|
|
@@ -240,7 +259,7 @@ const subcommands = {
|
|
|
240
259
|
},
|
|
241
260
|
},
|
|
242
261
|
invoke: {
|
|
243
|
-
summary: 'Invoke
|
|
262
|
+
summary: 'Invoke a deployed edge function by route, as the operator',
|
|
244
263
|
usage: 'motherbase functions invoke <route> [--body <json>]',
|
|
245
264
|
async run(ctx, args, flags) {
|
|
246
265
|
const route = requireArg(args[0], 'route');
|
|
@@ -248,7 +267,14 @@ const subcommands = {
|
|
|
248
267
|
if (flags.body) {
|
|
249
268
|
try { body = JSON.parse(flags.body); } catch { throw new CliError('--body must be valid JSON'); }
|
|
250
269
|
}
|
|
251
|
-
|
|
270
|
+
// Through the admin surface, as Studio's "Run" does. The public /edge
|
|
271
|
+
// route authenticates the function's own callers: the console proxy
|
|
272
|
+
// strips the operator's session before forwarding there (operator code
|
|
273
|
+
// must never see it), so the CLI arrived anonymous and got a 401.
|
|
274
|
+
const listing = await api.adminRequest(ctx, 'GET', '/edge-functions');
|
|
275
|
+
const fn = ((listing && listing.functions) || []).find(f => f.route === route || f.name === route);
|
|
276
|
+
if (!fn) throw new CliError(`No deployed function "${route}". See: motherbase functions list`);
|
|
277
|
+
const data = await api.adminRequest(ctx, 'POST', `/edge-functions/${fn.id}/invoke`, { body });
|
|
252
278
|
output(ctx, data, (d, u) => u.json(d));
|
|
253
279
|
},
|
|
254
280
|
},
|
|
@@ -267,8 +293,12 @@ const subcommands = {
|
|
|
267
293
|
},
|
|
268
294
|
};
|
|
269
295
|
|
|
296
|
+
// Supabase's name for `pull`.
|
|
297
|
+
subcommands.download = alias(subcommands.pull, 'motherbase functions download [name]');
|
|
298
|
+
|
|
270
299
|
module.exports = {
|
|
271
300
|
summary: 'Edge functions (repo files, deploy, invoke, logs)',
|
|
272
|
-
usage: 'motherbase functions <list|get|new|deploy|pull|serve|delete|invoke|logs> ...',
|
|
301
|
+
usage: 'motherbase functions <list|get|new|deploy|pull|download|serve|delete|invoke|logs> ...',
|
|
273
302
|
subcommands,
|
|
303
|
+
upsertFunction,
|
|
274
304
|
};
|
package/src/commands/gen.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// Code generation from the live schema
|
|
3
|
+
// Code generation from the live schema: TypeScript types and zod schemas.
|
|
4
4
|
//
|
|
5
|
-
// The server owns
|
|
5
|
+
// The server owns both emitters (apps/server/services/admin/typegen/).
|
|
6
6
|
// This command is a transport: it asks `GET /admin/schema/types` for the
|
|
7
7
|
// rendered file and writes it. Re-implementing the emitter here would guarantee
|
|
8
8
|
// drift between the CLI, the Studio download and the MCP tool within a release.
|
|
@@ -12,9 +12,11 @@ const path = require('path');
|
|
|
12
12
|
const api = require('../api');
|
|
13
13
|
const config = require('../config');
|
|
14
14
|
const { output } = require('./_util');
|
|
15
|
+
const { outputFormat } = require('../format');
|
|
15
16
|
const { CliError } = require('../context');
|
|
16
17
|
|
|
17
18
|
const DEFAULT_OUTPUT = 'database.types.ts';
|
|
19
|
+
const DEFAULT_ZOD_OUTPUT = 'database.schemas.ts';
|
|
18
20
|
const DEFAULT_LOCAL_URL = 'http://localhost:5001';
|
|
19
21
|
|
|
20
22
|
/**
|
|
@@ -51,31 +53,36 @@ function firstDifference(a, b) {
|
|
|
51
53
|
return null;
|
|
52
54
|
}
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Both targets are the same transport: ask the server for the rendered file,
|
|
58
|
+
* then write it, diff it or print it. `field` names which rendering to take
|
|
59
|
+
* from the one response, so a new target is a few lines rather than a copy of
|
|
60
|
+
* this whole flow.
|
|
61
|
+
*/
|
|
62
|
+
function emitter({ field, defaultOutput, label, command }) {
|
|
63
|
+
return async function run(ctx, args, flags) {
|
|
59
64
|
const schema = flags.schema || 'public';
|
|
60
65
|
const data = await api.adminRequest(resolveTarget(ctx, flags), 'GET', '/schema/types', {
|
|
61
66
|
query: { schema },
|
|
62
67
|
});
|
|
63
68
|
|
|
64
|
-
const types = data && typeof data
|
|
69
|
+
const types = data && typeof data[field] === 'string' ? data[field] : null;
|
|
65
70
|
if (!types) {
|
|
66
71
|
throw new CliError(
|
|
67
|
-
|
|
72
|
+
`The server returned no generated ${label}. Check that the project serves ` +
|
|
73
|
+
`GET /admin/schema/types with a build new enough for \`${command}\`.`,
|
|
68
74
|
);
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
const outputPath = flags.output && flags.output !== true ? String(flags.output) : null;
|
|
78
|
+
const DEFAULT_OUTPUT = defaultOutput;
|
|
72
79
|
|
|
73
80
|
// --check is the CI drift gate: compare the committed file with a fresh
|
|
74
81
|
// generation and fail loudly, without ever writing.
|
|
75
82
|
if (flags.check) {
|
|
76
83
|
const target = path.resolve(ctx.cwd, outputPath || DEFAULT_OUTPUT);
|
|
77
84
|
if (!fs.existsSync(target)) {
|
|
78
|
-
throw new CliError(`${target} does not exist. Run
|
|
85
|
+
throw new CliError(`${target} does not exist. Run \`${command} --output ${outputPath || DEFAULT_OUTPUT}\`.`);
|
|
79
86
|
}
|
|
80
87
|
const onDisk = fs.readFileSync(target, 'utf8');
|
|
81
88
|
const diff = firstDifference(onDisk, types);
|
|
@@ -96,7 +103,7 @@ const typescript = {
|
|
|
96
103
|
// No --output: write the file to stdout so `... > database.types.ts` works.
|
|
97
104
|
// Nothing else may go to stdout on this path or it lands in the file.
|
|
98
105
|
if (!outputPath) {
|
|
99
|
-
if (ctx.flags
|
|
106
|
+
if (outputFormat(ctx.flags) !== 'pretty') {
|
|
100
107
|
output(ctx, { schema, types }, () => {});
|
|
101
108
|
return;
|
|
102
109
|
}
|
|
@@ -110,17 +117,56 @@ const typescript = {
|
|
|
110
117
|
output(ctx, { schema, output: target, bytes: Buffer.byteLength(types) }, (d, ui) =>
|
|
111
118
|
ui.success(`Wrote ${target} (${schema} schema)`),
|
|
112
119
|
);
|
|
113
|
-
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const typescript = {
|
|
124
|
+
summary: 'Generate TypeScript types for the database schema',
|
|
125
|
+
usage:
|
|
126
|
+
'motherbase gen types typescript [--schema public] [--local] [--output database.types.ts] [--check]',
|
|
127
|
+
// Here `--output` names the file to write, not the `-o` output format.
|
|
128
|
+
outputIsPath: true,
|
|
129
|
+
run: emitter({
|
|
130
|
+
field: 'types',
|
|
131
|
+
defaultOutput: DEFAULT_OUTPUT,
|
|
132
|
+
label: 'types',
|
|
133
|
+
command: 'motherbase gen types typescript',
|
|
134
|
+
}),
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const zod = {
|
|
138
|
+
summary: 'Generate zod schemas for the database schema',
|
|
139
|
+
usage:
|
|
140
|
+
'motherbase gen types zod [--schema public] [--local] [--output database.schemas.ts] [--check]',
|
|
141
|
+
outputIsPath: true,
|
|
142
|
+
run: emitter({
|
|
143
|
+
field: 'zod',
|
|
144
|
+
defaultOutput: DEFAULT_ZOD_OUTPUT,
|
|
145
|
+
label: 'schemas',
|
|
146
|
+
command: 'motherbase gen types zod',
|
|
147
|
+
}),
|
|
114
148
|
};
|
|
115
149
|
|
|
150
|
+
const LANGS = { typescript, zod };
|
|
151
|
+
|
|
152
|
+
// `gen types --lang typescript` is the Supabase spelling; `gen types typescript`
|
|
153
|
+
// reaches the same emitter through the subcommand.
|
|
116
154
|
const types = {
|
|
117
155
|
summary: 'Generate types from the database schema',
|
|
118
|
-
usage: 'motherbase gen types typescript [flags]',
|
|
119
|
-
subcommands:
|
|
156
|
+
usage: 'motherbase gen types [--lang typescript|zod] [flags] | motherbase gen types <typescript|zod> [flags]',
|
|
157
|
+
subcommands: LANGS,
|
|
158
|
+
outputIsPath: true,
|
|
159
|
+
async run(ctx, args, flags) {
|
|
160
|
+
const lang = flags.lang || 'typescript';
|
|
161
|
+
if (!LANGS[lang]) throw new CliError(`--lang must be one of: ${Object.keys(LANGS).join(', ')}`);
|
|
162
|
+
return LANGS[lang].run(ctx, args, flags);
|
|
163
|
+
},
|
|
120
164
|
};
|
|
121
165
|
|
|
166
|
+
const signing = require('./signing');
|
|
167
|
+
|
|
122
168
|
module.exports = {
|
|
123
|
-
summary: 'Generate code from the live schema',
|
|
124
|
-
usage: 'motherbase gen types
|
|
125
|
-
subcommands: { types },
|
|
169
|
+
summary: 'Generate code from the live schema, and signing keys, tokens and secrets',
|
|
170
|
+
usage: 'motherbase gen <types|signing-key|bearer-jwt|keys> ...',
|
|
171
|
+
subcommands: { types, 'signing-key': signing.signingKey, 'bearer-jwt': signing.bearerJwt, keys: signing.keys },
|
|
126
172
|
};
|
package/src/commands/import.js
CHANGED
|
@@ -1,35 +1,55 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const api = require('../api');
|
|
4
|
-
const prompt = require('../prompt');
|
|
5
|
-
const { output } = require('./_util');
|
|
6
|
-
const { CliError } = require('../context');
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const api = require('../api');
|
|
4
|
+
const prompt = require('../prompt');
|
|
5
|
+
const { output } = require('./_util');
|
|
6
|
+
const { CliError } = require('../context');
|
|
7
|
+
|
|
8
|
+
// Sections beyond the database need their own credential, because that is where
|
|
9
|
+
// Supabase keeps them: object bytes behind the Storage API (service_role key),
|
|
10
|
+
// edge functions and project secrets behind the Management API (a personal
|
|
11
|
+
// access token). Both are optional; without one, that section reports what it
|
|
12
|
+
// could not read instead of failing the import.
|
|
13
|
+
module.exports = {
|
|
14
|
+
summary: 'Import a Supabase project (schema, data, auth, storage, edge functions, secrets)',
|
|
15
|
+
usage:
|
|
16
|
+
'motherbase import --from-supabase <connectionString> [--service-role-key <key>] [--management-token <sbp_…>] ' +
|
|
17
|
+
'[--preview] [--no-data] [--no-auth] [--no-storage] [--no-edge-functions] [--no-secrets]',
|
|
18
|
+
async run(ctx, args, flags) {
|
|
19
|
+
const connectionString = flags['from-supabase'] || flags.fromSupabase;
|
|
20
|
+
if (!connectionString) throw new CliError('--from-supabase <connectionString> is required');
|
|
21
|
+
|
|
22
|
+
const include = {
|
|
23
|
+
schema: true,
|
|
24
|
+
data: !flags['no-data'],
|
|
25
|
+
auth: !flags['no-auth'],
|
|
26
|
+
storage: !flags['no-storage'],
|
|
27
|
+
edgeFunctions: !flags['no-edge-functions'],
|
|
28
|
+
secrets: !flags['no-secrets'],
|
|
29
|
+
};
|
|
30
|
+
const preview = Boolean(flags.preview);
|
|
31
|
+
|
|
32
|
+
const body = { connectionString, include };
|
|
33
|
+
const serviceRoleKey = flags['service-role-key'] || flags.serviceRoleKey;
|
|
34
|
+
const managementToken = flags['management-token'] || flags.managementToken;
|
|
35
|
+
if (serviceRoleKey) body.serviceRoleKey = serviceRoleKey;
|
|
36
|
+
if (managementToken) body.managementToken = managementToken;
|
|
37
|
+
if (flags['project-ref'] || flags.projectRef) body.projectRef = flags['project-ref'] || flags.projectRef;
|
|
38
|
+
|
|
39
|
+
if (preview) {
|
|
40
|
+
body.preview = true;
|
|
41
|
+
} else {
|
|
42
|
+
if (!flags.yes) {
|
|
43
|
+
const ok = await prompt.confirm('This will import into the linked project. Continue?');
|
|
44
|
+
if (!ok) throw new CliError('Aborted.');
|
|
45
|
+
}
|
|
46
|
+
body.confirm = 'IMPORT';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const data = await api.adminRequest(ctx, 'POST', '/import/supabase', { body });
|
|
50
|
+
output(ctx, data, (d, ui) => {
|
|
51
|
+
if (preview) { ui.info('Import preview (dry run):'); ui.json(d); }
|
|
52
|
+
else { ui.success('Import complete'); ui.json(d); }
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
};
|
package/src/commands/index.js
CHANGED
|
@@ -1,64 +1,85 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Command registry: name -> { summary, usage, subcommands?, run? }.
|
|
4
|
-
// `main.js` builds all help/dispatch from this single map.
|
|
5
|
-
|
|
6
|
-
const local = require('./local');
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
// ──
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Command registry: name -> { summary, usage, subcommands?, run? }.
|
|
4
|
+
// `main.js` builds all help/dispatch from this single map.
|
|
5
|
+
|
|
6
|
+
const local = require('./local');
|
|
7
|
+
const { alias } = require('./_util');
|
|
8
|
+
const migration = require('./migration');
|
|
9
|
+
|
|
10
|
+
const registry = {
|
|
11
|
+
// ── Connection ─────────────────────────────────────────────────────────────
|
|
12
|
+
login: require('./login'),
|
|
13
|
+
logout: require('./logout'),
|
|
14
|
+
whoami: require('./whoami'),
|
|
15
|
+
link: require('./link'),
|
|
16
|
+
unlink: require('./unlink'),
|
|
17
|
+
status: require('./status'),
|
|
18
|
+
|
|
19
|
+
// ── Orgs & projects (Platform Console) ─────────────────────────────────────
|
|
20
|
+
orgs: require('./orgs'),
|
|
21
|
+
projects: require('./projects'),
|
|
22
|
+
branches: require('./branches'),
|
|
23
|
+
domains: require('./domains').domains,
|
|
24
|
+
'vanity-subdomains': require('./domains').vanitySubdomains,
|
|
25
|
+
|
|
26
|
+
// ── Local self-host (docker compose) ───────────────────────────────────────
|
|
27
|
+
init: local.init,
|
|
28
|
+
start: local.start,
|
|
29
|
+
stop: local.stop,
|
|
30
|
+
deploy: local.deploy,
|
|
31
|
+
logs: local.logs,
|
|
32
|
+
|
|
33
|
+
// ── Database / migrations / import ─────────────────────────────────────────
|
|
34
|
+
db: require('./db'),
|
|
35
|
+
backups: require('./backups'),
|
|
36
|
+
migration,
|
|
37
|
+
// The name Motherbase used before it matched Supabase's `migration`.
|
|
38
|
+
migrations: alias(migration, 'motherbase migrations <new|list|up|down|repair|squash|fetch|diff|get|create|apply|rollback> ... (alias of `migration`)'),
|
|
39
|
+
import: require('./import'),
|
|
40
|
+
migrate: require('./migrate'),
|
|
41
|
+
test: require('./test'),
|
|
42
|
+
'postgres-config': require('./postgres-config'),
|
|
43
|
+
|
|
44
|
+
// ── Resource domains ───────────────────────────────────────────────────────
|
|
45
|
+
rls: require('./rls'),
|
|
46
|
+
users: require('./users'),
|
|
47
|
+
storage: require('./storage'),
|
|
48
|
+
functions: require('./functions'),
|
|
49
|
+
secrets: require('./secrets'),
|
|
50
|
+
keys: require('./keys'),
|
|
51
|
+
realtime: require('./realtime'),
|
|
52
|
+
webhooks: require('./webhooks'),
|
|
53
|
+
cron: require('./cron'),
|
|
54
|
+
queues: require('./queues'),
|
|
55
|
+
vault: require('./vault'),
|
|
56
|
+
wrappers: require('./wrappers'),
|
|
57
|
+
config: require('./config'),
|
|
58
|
+
inspect: require('./inspect'),
|
|
59
|
+
'network-restrictions': require('./network').networkRestrictions,
|
|
60
|
+
'ssl-enforcement': require('./network').sslEnforcement,
|
|
61
|
+
snippets: require('./snippets'),
|
|
62
|
+
|
|
63
|
+
// ── Codegen ────────────────────────────────────────────────────────────────
|
|
64
|
+
gen: require('./gen'),
|
|
65
|
+
|
|
66
|
+
// ── AI agents ──────────────────────────────────────────────────────────────
|
|
67
|
+
mcp: require('./mcp'),
|
|
68
|
+
|
|
69
|
+
// ── Shell ──────────────────────────────────────────────────────────────────
|
|
70
|
+
completion: require('./completion'),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Grouping used only to render a tidy top-level help listing.
|
|
74
|
+
const groups = [
|
|
75
|
+
{ title: 'Connection', names: ['login', 'logout', 'whoami', 'link', 'unlink', 'status'] },
|
|
76
|
+
{ title: 'Orgs & projects (Console)', names: ['orgs', 'projects', 'branches', 'domains', 'vanity-subdomains'] },
|
|
77
|
+
{ title: 'Local self-host', names: ['init', 'start', 'stop', 'deploy', 'logs'] },
|
|
78
|
+
{ title: 'Database', names: ['db', 'backups', 'migrations', 'migration', 'import', 'test', 'postgres-config'] },
|
|
79
|
+
{ title: 'Resources', names: ['rls', 'users', 'storage', 'functions', 'secrets', 'keys', 'realtime', 'webhooks', 'cron', 'queues', 'vault', 'wrappers', 'config', 'inspect', 'snippets', 'network-restrictions', 'ssl-enforcement'] },
|
|
80
|
+
{ title: 'Codegen', names: ['gen'] },
|
|
81
|
+
{ title: 'AI agents', names: ['mcp'] },
|
|
82
|
+
{ title: 'Shell', names: ['completion'] },
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
module.exports = { registry, groups };
|