@motherbase/cli 0.2.4 → 0.2.5
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 +247 -134
- package/package.json +2 -2
- package/src/api.js +14 -2
- package/src/commands/_util.js +49 -6
- package/src/commands/backups.js +16 -0
- package/src/commands/branches.js +113 -0
- package/src/commands/completion.js +90 -0
- package/src/commands/db-catalog.js +326 -0
- package/src/commands/db.js +16 -5
- package/src/commands/domains.js +124 -0
- package/src/commands/functions.js +297 -274
- package/src/commands/gen.js +172 -126
- package/src/commands/import.js +23 -3
- package/src/commands/index.js +27 -6
- package/src/commands/inspect.js +142 -5
- package/src/commands/keys.js +72 -72
- package/src/commands/local.js +273 -273
- package/src/commands/mcp.js +61 -43
- package/src/commands/migrate.js +179 -0
- package/src/commands/migration.js +81 -6
- 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 +8 -3
- package/src/commands/queues.js +133 -0
- package/src/commands/secrets.js +29 -8
- package/src/commands/signing.js +130 -0
- package/src/commands/snippets.js +44 -0
- package/src/commands/storage.js +192 -44
- package/src/commands/test.js +9 -0
- package/src/commands/users.js +30 -1
- package/src/commands/vault.js +82 -0
- package/src/commands/wrappers.js +150 -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 +41 -7
- package/src/mcp/platform-tools.js +233 -0
- package/src/mcp/protocol.js +149 -149
- package/src/mcp/storage-tools.js +93 -0
- package/src/mcp/tools.js +478 -166
- package/src/migration/commands.js +375 -0
- package/src/migration/files.js +245 -0
- package/src/prompt.js +1 -1
- package/src/storage/remote.js +62 -0
- package/src/commands/migrations.js +0 -85
|
@@ -1,274 +1,297 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const api = require('../api');
|
|
6
|
-
const ui = require('../ui');
|
|
7
|
-
const { output, requireArg } = require('./_util');
|
|
8
|
-
const { CliError } = require('../context');
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
const
|
|
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
|
-
}
|
|
65
|
-
|
|
66
|
-
function
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const api = require('../api');
|
|
6
|
+
const ui = require('../ui');
|
|
7
|
+
const { output, alias, requireArg } = require('./_util');
|
|
8
|
+
const { CliError } = require('../context');
|
|
9
|
+
const { templates, DEFAULT_TEMPLATE, findTemplate, renderTemplate } = require('../edge-templates');
|
|
10
|
+
|
|
11
|
+
// Functions live as repo files, one directory per function, the layout Supabase
|
|
12
|
+
// projects already use:
|
|
13
|
+
//
|
|
14
|
+
// functions/
|
|
15
|
+
// stripe-webhook/
|
|
16
|
+
// index.ts
|
|
17
|
+
//
|
|
18
|
+
// The directory name is both the function name and its route, so deploying is
|
|
19
|
+
// `motherbase functions deploy` with nothing to remember.
|
|
20
|
+
const DEFAULT_DIR = 'functions';
|
|
21
|
+
const LEGACY_DIR = path.join('motherbase', 'functions');
|
|
22
|
+
const ENTRY_NAMES = ['index.ts', 'index.js', 'index.mjs'];
|
|
23
|
+
|
|
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.
|
|
29
|
+
|
|
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 }>;
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
function functionsDir(ctx) {
|
|
51
|
+
for (const candidate of [DEFAULT_DIR, LEGACY_DIR]) {
|
|
52
|
+
if (fs.existsSync(path.join(ctx.cwd, candidate))) return path.join(ctx.cwd, candidate);
|
|
53
|
+
}
|
|
54
|
+
return path.join(ctx.cwd, DEFAULT_DIR);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Entry file of a function directory, or null when it has none. */
|
|
58
|
+
function entryFile(dir) {
|
|
59
|
+
for (const name of ENTRY_NAMES) {
|
|
60
|
+
const file = path.join(dir, name);
|
|
61
|
+
if (fs.existsSync(file)) return file;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Every locally defined function: `{ name, dir, entry }`. */
|
|
67
|
+
function localFunctions(ctx) {
|
|
68
|
+
const root = functionsDir(ctx);
|
|
69
|
+
if (!fs.existsSync(root)) return [];
|
|
70
|
+
return fs
|
|
71
|
+
.readdirSync(root, { withFileTypes: true })
|
|
72
|
+
.filter(item => item.isDirectory())
|
|
73
|
+
.map(item => ({ name: item.name, dir: path.join(root, item.name), entry: entryFile(path.join(root, item.name)) }))
|
|
74
|
+
.filter(fn => fn.entry);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function resolveLocal(ctx, name) {
|
|
78
|
+
const all = localFunctions(ctx);
|
|
79
|
+
if (!name) return all;
|
|
80
|
+
const hit = all.find(fn => fn.name === name);
|
|
81
|
+
if (!hit) {
|
|
82
|
+
const expected = path.relative(ctx.cwd, path.join(functionsDir(ctx), name, 'index.ts'));
|
|
83
|
+
throw new CliError(`No local function "${name}". Expected ${expected}.`);
|
|
84
|
+
}
|
|
85
|
+
return [hit];
|
|
86
|
+
}
|
|
87
|
+
|
|
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
|
+
|
|
96
|
+
async function deployOne(ctx, fn, flags) {
|
|
97
|
+
const code = fs.readFileSync(fn.entry, 'utf8');
|
|
98
|
+
const body = { name: fn.name, route: fn.name, code };
|
|
99
|
+
if (flags && flags.memory) body.memoryLimitMb = Number(flags.memory);
|
|
100
|
+
if (flags && flags.timeout) body.timeoutMs = Number(flags.timeout);
|
|
101
|
+
return upsertFunction(ctx, body);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const subcommands = {
|
|
105
|
+
list: {
|
|
106
|
+
summary: 'List edge functions',
|
|
107
|
+
usage: 'motherbase functions list',
|
|
108
|
+
async run(ctx) {
|
|
109
|
+
const data = await api.adminRequest(ctx, 'GET', '/edge-functions');
|
|
110
|
+
const rows = (data && data.functions) || [];
|
|
111
|
+
output(ctx, data, (_d, u) =>
|
|
112
|
+
u.table(rows, [
|
|
113
|
+
{ key: 'id', label: 'ID' },
|
|
114
|
+
{ key: 'name', label: 'NAME' },
|
|
115
|
+
{ key: 'route', label: 'ROUTE' },
|
|
116
|
+
{ key: 'enabled', label: 'ENABLED' },
|
|
117
|
+
]),
|
|
118
|
+
);
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
get: {
|
|
122
|
+
summary: 'Get an edge function by id',
|
|
123
|
+
usage: 'motherbase functions get <id>',
|
|
124
|
+
async run(ctx, args) {
|
|
125
|
+
const id = requireArg(args[0], 'id');
|
|
126
|
+
const data = await api.adminRequest(ctx, 'GET', `/edge-functions/${id}`);
|
|
127
|
+
output(ctx, data, (d, u) => u.keyValue((d && d.function) || d || {}));
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
new: {
|
|
131
|
+
summary: 'Scaffold functions/<name>/index.ts',
|
|
132
|
+
usage: `motherbase functions new <name> [--template ${templates.map(t => t.id).join('|')}]`,
|
|
133
|
+
async run(ctx, args, flags) {
|
|
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
|
+
}
|
|
139
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(name)) {
|
|
140
|
+
throw new CliError('Function names are lowercase letters, digits and hyphens (the name is also the route).');
|
|
141
|
+
}
|
|
142
|
+
const dir = path.join(functionsDir(ctx), name);
|
|
143
|
+
if (entryFile(dir)) throw new CliError(`${path.relative(ctx.cwd, dir)} already has an entry file.`);
|
|
144
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
145
|
+
const file = path.join(dir, 'index.ts');
|
|
146
|
+
fs.writeFileSync(file, renderTemplate(template, name));
|
|
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
|
+
}
|
|
153
|
+
ui.info(`Deploy it with: motherbase functions deploy ${name}`);
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
deploy: {
|
|
157
|
+
summary: 'Deploy local functions (all of them, or one by name)',
|
|
158
|
+
usage: 'motherbase functions deploy [name] [--memory <mb>] [--timeout <ms>] [--file <f> --route <r>]',
|
|
159
|
+
async run(ctx, args, flags) {
|
|
160
|
+
// The single-file form predates the directory layout and stays supported:
|
|
161
|
+
// scripts and older docs pass --file.
|
|
162
|
+
if (flags.file) {
|
|
163
|
+
const route = flags.route || args[0];
|
|
164
|
+
if (!route) throw new CliError('--route is required with --file');
|
|
165
|
+
const code = fs.readFileSync(path.resolve(ctx.cwd, flags.file), 'utf8');
|
|
166
|
+
const name = flags.name || route;
|
|
167
|
+
const data = await upsertFunction(ctx, { name, route, code });
|
|
168
|
+
return output(ctx, data, (_d, u) => u.success(`Deployed function ${name} (${route})`));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const targets = resolveLocal(ctx, args[0]);
|
|
172
|
+
if (!targets.length) {
|
|
173
|
+
const where = path.relative(ctx.cwd, functionsDir(ctx));
|
|
174
|
+
throw new CliError(`No functions in ${where}. Create one with: motherbase functions new <name>`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const deployed = [];
|
|
178
|
+
for (const fn of targets) {
|
|
179
|
+
await deployOne(ctx, fn, flags);
|
|
180
|
+
deployed.push(fn.name);
|
|
181
|
+
}
|
|
182
|
+
output(ctx, { deployed }, (_d, u) => u.success(`Deployed ${deployed.join(', ')}`));
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
pull: {
|
|
186
|
+
summary: 'Write deployed function source back to disk',
|
|
187
|
+
usage: 'motherbase functions pull [name]',
|
|
188
|
+
async run(ctx, args) {
|
|
189
|
+
const listing = await api.adminRequest(ctx, 'GET', '/edge-functions');
|
|
190
|
+
const all = (listing && listing.functions) || [];
|
|
191
|
+
const wanted = args[0] ? all.filter(f => f.route === args[0] || f.name === args[0]) : all;
|
|
192
|
+
if (!wanted.length) throw new CliError(args[0] ? `No deployed function "${args[0]}"` : 'No deployed functions');
|
|
193
|
+
|
|
194
|
+
const written = [];
|
|
195
|
+
for (const fn of wanted) {
|
|
196
|
+
// The list endpoint may omit code, so read the function itself.
|
|
197
|
+
const detail = await api.adminRequest(ctx, 'GET', `/edge-functions/${fn.id}`);
|
|
198
|
+
const code = (detail && detail.function ? detail.function.code : detail && detail.code) || '';
|
|
199
|
+
const dir = path.join(functionsDir(ctx), fn.route || fn.name);
|
|
200
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
201
|
+
const file = path.join(dir, 'index.ts');
|
|
202
|
+
fs.writeFileSync(file, code);
|
|
203
|
+
written.push(path.relative(ctx.cwd, file));
|
|
204
|
+
}
|
|
205
|
+
output(ctx, { written }, (_d, u) => u.success(`Wrote ${written.join(', ')}`));
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
serve: {
|
|
209
|
+
summary: 'Watch local functions and redeploy on every save',
|
|
210
|
+
usage: 'motherbase functions serve [name]',
|
|
211
|
+
async run(ctx, args) {
|
|
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
|
|
214
|
+
// would differ from production in exactly the ways that matter. This
|
|
215
|
+
// watches and redeploys instead, so the edit-invoke loop is one save long.
|
|
216
|
+
const targets = resolveLocal(ctx, args[0]);
|
|
217
|
+
if (!targets.length) {
|
|
218
|
+
throw new CliError('Nothing to serve. Create a function with: motherbase functions new <name>');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
for (const fn of targets) {
|
|
222
|
+
await deployOne(ctx, fn);
|
|
223
|
+
ui.success(`Deployed ${fn.name}`);
|
|
224
|
+
ui.info(` POST ${api.projectUrl(ctx, `/edge/${fn.name}`)}`);
|
|
225
|
+
}
|
|
226
|
+
ui.info('Watching for changes. Ctrl-C to stop.');
|
|
227
|
+
|
|
228
|
+
const pending = new Map();
|
|
229
|
+
for (const fn of targets) {
|
|
230
|
+
fs.watch(fn.dir, { recursive: true }, () => {
|
|
231
|
+
// Editors save in bursts (write, rename, truncate). Coalescing them
|
|
232
|
+
// keeps one save from becoming three deploys.
|
|
233
|
+
clearTimeout(pending.get(fn.name));
|
|
234
|
+
pending.set(
|
|
235
|
+
fn.name,
|
|
236
|
+
setTimeout(async () => {
|
|
237
|
+
try {
|
|
238
|
+
await deployOne(ctx, fn);
|
|
239
|
+
ui.success(`Redeployed ${fn.name} at ${new Date().toLocaleTimeString()}`);
|
|
240
|
+
} catch (error) {
|
|
241
|
+
ui.warn(`${fn.name}: ${error.message}`);
|
|
242
|
+
}
|
|
243
|
+
}, 150),
|
|
244
|
+
);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Hold the process open; the watchers are the work.
|
|
249
|
+
await new Promise(() => {});
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
delete: {
|
|
253
|
+
summary: 'Delete an edge function by id',
|
|
254
|
+
usage: 'motherbase functions delete <id>',
|
|
255
|
+
async run(ctx, args) {
|
|
256
|
+
const id = requireArg(args[0], 'id');
|
|
257
|
+
const data = await api.adminRequest(ctx, 'DELETE', `/edge-functions/${id}`);
|
|
258
|
+
output(ctx, data, (d, u) => u.success(`Deleted function ${id}`));
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
invoke: {
|
|
262
|
+
summary: 'Invoke an edge function by route',
|
|
263
|
+
usage: 'motherbase functions invoke <route> [--body <json>]',
|
|
264
|
+
async run(ctx, args, flags) {
|
|
265
|
+
const route = requireArg(args[0], 'route');
|
|
266
|
+
let body = {};
|
|
267
|
+
if (flags.body) {
|
|
268
|
+
try { body = JSON.parse(flags.body); } catch { throw new CliError('--body must be valid JSON'); }
|
|
269
|
+
}
|
|
270
|
+
const data = await api.edgeInvokeRequest(ctx, route, body);
|
|
271
|
+
output(ctx, data, (d, u) => u.json(d));
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
logs: {
|
|
275
|
+
summary: 'Show logs for an edge function',
|
|
276
|
+
usage: 'motherbase functions logs <id>',
|
|
277
|
+
async run(ctx, args) {
|
|
278
|
+
const id = requireArg(args[0], 'id');
|
|
279
|
+
const data = await api.adminRequest(ctx, 'GET', `/edge-functions/${id}/logs`);
|
|
280
|
+
output(ctx, data, (d, u) => {
|
|
281
|
+
const logs = (d && d.logs) || d || [];
|
|
282
|
+
if (Array.isArray(logs)) logs.forEach(l => u.print(typeof l === 'string' ? l : JSON.stringify(l)));
|
|
283
|
+
else u.json(d);
|
|
284
|
+
});
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
// Supabase's name for `pull`.
|
|
290
|
+
subcommands.download = alias(subcommands.pull, 'motherbase functions download [name]');
|
|
291
|
+
|
|
292
|
+
module.exports = {
|
|
293
|
+
summary: 'Edge functions (repo files, deploy, invoke, logs)',
|
|
294
|
+
usage: 'motherbase functions <list|get|new|deploy|pull|download|serve|delete|invoke|logs> ...',
|
|
295
|
+
subcommands,
|
|
296
|
+
upsertFunction,
|
|
297
|
+
};
|