@motherbase/cli 0.2.6 → 0.2.9
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 -21
- package/README.md +8 -1
- package/bin/.motherbase/config.json +5 -0
- package/bin/motherbase.js +10 -10
- package/package.json +4 -4
- package/src/api.js +39 -16
- package/src/commands/_util.js +106 -106
- package/src/commands/backups.js +16 -16
- package/src/commands/branches.js +113 -113
- package/src/commands/completion.js +90 -90
- package/src/commands/config.js +47 -47
- package/src/commands/cron.js +65 -65
- package/src/commands/db-catalog.js +39 -1
- package/src/commands/db.js +103 -103
- package/src/commands/domains.js +124 -124
- package/src/commands/gen.js +178 -172
- package/src/commands/import.js +55 -55
- package/src/commands/index.js +86 -85
- package/src/commands/inspect.js +178 -178
- package/src/commands/keys.js +72 -72
- package/src/commands/link.js +35 -35
- package/src/commands/local.js +101 -24
- package/src/commands/login.js +9 -3
- package/src/commands/logout.js +16 -16
- package/src/commands/mcp.js +61 -61
- package/src/commands/migrate.js +0 -5
- package/src/commands/migration.js +1 -1
- package/src/commands/network.js +83 -83
- package/src/commands/orgs.js +40 -40
- package/src/commands/postgres-config.js +113 -113
- package/src/commands/projects.js +131 -130
- package/src/commands/queues.js +133 -133
- package/src/commands/realtime.js +35 -35
- package/src/commands/rls.js +176 -176
- package/src/commands/secrets.js +66 -66
- package/src/commands/signing.js +130 -130
- package/src/commands/snippets.js +44 -44
- package/src/commands/status.js +42 -42
- package/src/commands/storage.js +261 -261
- package/src/commands/test.js +9 -9
- package/src/commands/unlink.js +15 -15
- package/src/commands/users.js +29 -1
- package/src/commands/vault.js +82 -82
- package/src/commands/webhooks.js +62 -62
- package/src/commands/whoami.js +20 -20
- package/src/commands/wrappers.js +150 -150
- package/src/config.js +98 -98
- package/src/context.js +106 -106
- package/src/edge-templates.js +194 -194
- package/src/format.js +92 -92
- package/src/inspect/csv.js +22 -22
- package/src/inspect/queries.js +562 -562
- package/src/main.js +145 -66
- package/src/mcp/platform-tools.js +233 -233
- package/src/mcp/protocol.js +152 -152
- package/src/mcp/storage-tools.js +93 -93
- package/src/mcp/tools.js +478 -478
- package/src/migration/commands.js +2 -2
- package/src/migration/files.js +13 -0
- package/src/prompt.js +91 -91
- package/src/storage/remote.js +62 -62
- package/src/ui.js +111 -111
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Motherbase contributors
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Motherbase contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -146,10 +146,17 @@ emits, so `createClient<Database>(url, key)` types every row, rejects unknown
|
|
|
146
146
|
columns and types `.rpc()` calls:
|
|
147
147
|
|
|
148
148
|
```bash
|
|
149
|
-
motherbase gen types typescript
|
|
149
|
+
motherbase gen types typescript # in a terminal: writes ./database.types.ts
|
|
150
|
+
motherbase gen types typescript > database.types.ts # piped: prints the file
|
|
150
151
|
motherbase gen types typescript --schema shop --output src/shop.types.ts
|
|
151
152
|
```
|
|
152
153
|
|
|
154
|
+
The same `Database` also names the project's edge functions and buckets, under
|
|
155
|
+
`__InternalMotherbase`, so `client.functions.invoke('…')` and
|
|
156
|
+
`client.storage.from('…')` autocomplete with no extra generic. A name created
|
|
157
|
+
after the file was generated still compiles. Since they are part of the file,
|
|
158
|
+
adding a function or a bucket is drift for `--check` below.
|
|
159
|
+
|
|
153
160
|
With no `--output` the file goes to stdout and nothing else does, so the
|
|
154
161
|
redirect above produces a compiling file. `--local` skips the Console proxy and
|
|
155
162
|
talks to a project API on this machine (`MOTHERBASE_URL`, default
|
package/bin/motherbase.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const { main } = require('../src/main');
|
|
5
|
-
|
|
6
|
-
main(process.argv.slice(2))
|
|
7
|
-
.then(code => { process.exitCode = code || 0; })
|
|
8
|
-
.catch(err => {
|
|
9
|
-
process.stderr.write((err && err.message ? err.message : String(err)) + '\n');
|
|
10
|
-
process.exitCode = 1;
|
|
11
|
-
});
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { main } = require('../src/main');
|
|
5
|
+
|
|
6
|
+
main(process.argv.slice(2))
|
|
7
|
+
.then(code => { process.exitCode = code || 0; })
|
|
8
|
+
.catch(err => {
|
|
9
|
+
process.stderr.write((err && err.message ? err.message : String(err)) + '\n');
|
|
10
|
+
process.exitCode = 1;
|
|
11
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@motherbase/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "Motherbase operator CLI: drives the Platform Console and linked projects.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
|
@@ -25,11 +25,11 @@
|
|
|
25
25
|
"license": "MIT",
|
|
26
26
|
"repository": {
|
|
27
27
|
"type": "git",
|
|
28
|
-
"url": "git+https://github.com/JulienGuinot/
|
|
28
|
+
"url": "git+https://github.com/JulienGuinot/motherbase.git",
|
|
29
29
|
"directory": "packages/cli"
|
|
30
30
|
},
|
|
31
|
-
"homepage": "https://github.com/JulienGuinot/
|
|
31
|
+
"homepage": "https://github.com/JulienGuinot/motherbase#readme",
|
|
32
32
|
"bugs": {
|
|
33
|
-
"url": "https://github.com/JulienGuinot/
|
|
33
|
+
"url": "https://github.com/JulienGuinot/motherbase/issues"
|
|
34
34
|
}
|
|
35
35
|
}
|
package/src/api.js
CHANGED
|
@@ -107,35 +107,57 @@ function consoleRequest(ctx, method, apiPath, { body, query } = {}) {
|
|
|
107
107
|
return doFetch(url, { method, token, body });
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
// Login is special: no token yet
|
|
111
|
-
// the
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
110
|
+
// Login is special: no token yet, so it talks to Better Auth's native endpoints
|
|
111
|
+
// directly. Returns the raw response next to the parsed body because the token
|
|
112
|
+
// lives in the `set-auth-token` header (bearer plugin) and the 2FA step needs
|
|
113
|
+
// the response cookies.
|
|
114
|
+
async function authPost(url, body, headers = {}) {
|
|
115
|
+
const finalHeaders = { 'content-type': 'application/json', ...headers };
|
|
116
|
+
const origin = originFor(url);
|
|
117
|
+
if (origin) finalHeaders['origin'] = origin;
|
|
115
118
|
let res;
|
|
116
119
|
try {
|
|
117
|
-
|
|
118
|
-
const origin = originFor(url);
|
|
119
|
-
if (origin) headers['origin'] = origin;
|
|
120
|
-
res = await fetch(url, {
|
|
121
|
-
method: 'POST',
|
|
122
|
-
headers,
|
|
123
|
-
body: JSON.stringify({ email, password }),
|
|
124
|
-
});
|
|
120
|
+
res = await fetch(url, { method: 'POST', headers: finalHeaders, body: JSON.stringify(body) });
|
|
125
121
|
} catch (err) {
|
|
126
122
|
throw new CliError(`Cannot reach ${url}: ${err.message}`);
|
|
127
123
|
}
|
|
128
124
|
trace('POST', url, res);
|
|
129
125
|
const text = await res.text();
|
|
130
|
-
let
|
|
131
|
-
if (text) { try {
|
|
126
|
+
let parsed;
|
|
127
|
+
if (text) { try { parsed = JSON.parse(text); } catch { parsed = undefined; } }
|
|
132
128
|
if (!res.ok) {
|
|
133
|
-
throw new CliError(errorMessage(res.status,
|
|
129
|
+
throw new CliError(errorMessage(res.status, parsed, text));
|
|
134
130
|
}
|
|
131
|
+
return { res, body: parsed };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// The `two_factor` cookie (prefixed `__Secure-` on https) scopes the
|
|
135
|
+
// verify-totp call to the pending sign-in.
|
|
136
|
+
function twoFactorCookie(res) {
|
|
137
|
+
const setCookies = typeof res.headers.getSetCookie === 'function' ? res.headers.getSetCookie() : [];
|
|
138
|
+
const pair = setCookies.map(c => c.split(';')[0]).find(c => /two_factor=./.test(c));
|
|
139
|
+
if (!pair) throw new CliError('two-factor sign-in started but the server sent no two_factor cookie');
|
|
140
|
+
return pair;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Returns `{ token, user }`, or `{ twoFactorCookie }` when the account has 2FA
|
|
144
|
+
// enrolled. In that case Better Auth answers `{ twoFactorRedirect: true }` and
|
|
145
|
+
// the `set-auth-token` header still carries the pre-2FA session it has just
|
|
146
|
+
// deleted, so the header must not be trusted (see the console login route).
|
|
147
|
+
async function loginRequest(consoleUrl, email, password) {
|
|
148
|
+
const { res, body } = await authPost(`${consoleUrl}/api/auth/sign-in/email`, { email, password });
|
|
149
|
+
if (body && body.twoFactorRedirect) return { twoFactorCookie: twoFactorCookie(res) };
|
|
135
150
|
const token = (body && body.token) || res.headers.get('set-auth-token') || undefined;
|
|
136
151
|
return { token, user: body && body.user };
|
|
137
152
|
}
|
|
138
153
|
|
|
154
|
+
// Second step of a 2FA sign-in: trade the TOTP code for the real session token.
|
|
155
|
+
async function verifyTotpRequest(consoleUrl, cookie, code) {
|
|
156
|
+
const { res, body } = await authPost(`${consoleUrl}/api/auth/two-factor/verify-totp`, { code }, { cookie });
|
|
157
|
+
const token = res.headers.get('set-auth-token') || (body && body.token) || undefined;
|
|
158
|
+
return { token, user: body && body.user };
|
|
159
|
+
}
|
|
160
|
+
|
|
139
161
|
// ── Admin / resource (via proxy or direct) ───────────────────────────────────
|
|
140
162
|
|
|
141
163
|
function adminBase(target) {
|
|
@@ -224,6 +246,7 @@ async function downloadFile(ctx, apiPath, destPath, { query } = {}) {
|
|
|
224
246
|
module.exports = {
|
|
225
247
|
consoleRequest,
|
|
226
248
|
loginRequest,
|
|
249
|
+
verifyTotpRequest,
|
|
227
250
|
adminRequest,
|
|
228
251
|
projectUrl,
|
|
229
252
|
uploadFile,
|
package/src/commands/_util.js
CHANGED
|
@@ -1,106 +1,106 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Small shared helpers for command modules.
|
|
4
|
-
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const { CliError } = require('../context');
|
|
7
|
-
const { outputFormat, toYaml, toEnv } = require('../format');
|
|
8
|
-
|
|
9
|
-
// Parse `key=value` tokens into an object. Values that look like numbers/bools are
|
|
10
|
-
// coerced unless `raw` is set. Supports dotted keys via `--json`-style callers.
|
|
11
|
-
function parseKeyValues(tokens, { coerce = true } = {}) {
|
|
12
|
-
const out = {};
|
|
13
|
-
for (const tok of tokens) {
|
|
14
|
-
const idx = tok.indexOf('=');
|
|
15
|
-
if (idx <= 0) throw new CliError(`expected key=value, got "${tok}"`);
|
|
16
|
-
const key = tok.slice(0, idx).trim();
|
|
17
|
-
let value = tok.slice(idx + 1);
|
|
18
|
-
if (coerce) {
|
|
19
|
-
if (value === 'true') value = true;
|
|
20
|
-
else if (value === 'false') value = false;
|
|
21
|
-
else if (value !== '' && !Number.isNaN(Number(value)) && /^-?\d+(\.\d+)?$/.test(value)) value = Number(value);
|
|
22
|
-
}
|
|
23
|
-
out[key] = value;
|
|
24
|
-
}
|
|
25
|
-
return out;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Render a command's result in the selected `-o/--output` format; `pretty` is
|
|
29
|
-
// the command's own human renderer.
|
|
30
|
-
function output(ctx, data, humanFn) {
|
|
31
|
-
const ui = require('../ui');
|
|
32
|
-
switch (outputFormat(ctx.flags)) {
|
|
33
|
-
case 'json': ui.json(data); return;
|
|
34
|
-
case 'yaml': process.stdout.write(toYaml(data)); return;
|
|
35
|
-
case 'env': process.stdout.write(toEnv(data)); return;
|
|
36
|
-
default: humanFn(data, ui);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// The same command under a second (Supabase) name: one implementation, with
|
|
41
|
-
// help that shows the name it was reached by.
|
|
42
|
-
function alias(entry, usage, summary = entry.summary) {
|
|
43
|
-
return { ...entry, usage, summary };
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Parse a dotenv file: `KEY=value`, `export KEY=value`, `#` comments, and
|
|
47
|
-
// single- or double-quoted values (double quotes expand \n).
|
|
48
|
-
function parseDotenv(text) {
|
|
49
|
-
const out = {};
|
|
50
|
-
for (const raw of String(text).split(/\r?\n/)) {
|
|
51
|
-
const line = raw.trim().replace(/^export\s+/, '');
|
|
52
|
-
if (!line || line.startsWith('#')) continue;
|
|
53
|
-
const idx = line.indexOf('=');
|
|
54
|
-
if (idx <= 0) throw new CliError(`invalid env line: "${raw}"`);
|
|
55
|
-
const key = line.slice(0, idx).trim();
|
|
56
|
-
let value = line.slice(idx + 1).trim();
|
|
57
|
-
const quote = value[0];
|
|
58
|
-
if ((quote === '"' || quote === "'") && value.endsWith(quote) && value.length > 1) {
|
|
59
|
-
value = value.slice(1, -1);
|
|
60
|
-
if (quote === '"') value = value.replace(/\\n/g, '\n');
|
|
61
|
-
} else {
|
|
62
|
-
value = value.replace(/\s+#.*$/, '');
|
|
63
|
-
}
|
|
64
|
-
out[key] = value;
|
|
65
|
-
}
|
|
66
|
-
return out;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function requireArg(value, name) {
|
|
70
|
-
if (value === undefined || value === null || value === '') {
|
|
71
|
-
throw new CliError(`missing required argument: <${name}>`);
|
|
72
|
-
}
|
|
73
|
-
return value;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Split "bucket:path" into { bucket, rest }.
|
|
77
|
-
function splitRef(ref) {
|
|
78
|
-
const idx = ref.indexOf(':');
|
|
79
|
-
if (idx < 0) return { bucket: undefined, rest: ref };
|
|
80
|
-
return { bucket: ref.slice(0, idx), rest: ref.slice(idx + 1) };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// `/projects/<linked project>` on the Console, for the per-project platform
|
|
84
|
-
// surfaces (branches, domains) that live on the Console rather than the
|
|
85
|
-
// project API. They need a Console, so direct mode cannot reach them.
|
|
86
|
-
function consoleProjectPath(ctx, suffix = '') {
|
|
87
|
-
ctx.requireConsole();
|
|
88
|
-
if (!ctx.projectId) {
|
|
89
|
-
throw new CliError('No project linked. Run `motherbase link` or pass --project-ref <id>.');
|
|
90
|
-
}
|
|
91
|
-
return `/projects/${encodeURIComponent(ctx.projectId)}${suffix}`;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Join `parts` under `root`, refusing a result that escapes it. Names that come
|
|
95
|
-
// from the server (object keys, function routes) must not write outside the
|
|
96
|
-
// directory the user picked: `../../.bashrc` is a valid object key.
|
|
97
|
-
function resolveInside(root, ...parts) {
|
|
98
|
-
const target = path.resolve(root, ...parts);
|
|
99
|
-
const rel = path.relative(root, target);
|
|
100
|
-
if (rel === '..' || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) {
|
|
101
|
-
throw new CliError(`Refusing to write outside ${root}: ${parts.join('/')}`);
|
|
102
|
-
}
|
|
103
|
-
return target;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
module.exports = { parseKeyValues, output, alias, parseDotenv, requireArg, splitRef, consoleProjectPath, resolveInside };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Small shared helpers for command modules.
|
|
4
|
+
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { CliError } = require('../context');
|
|
7
|
+
const { outputFormat, toYaml, toEnv } = require('../format');
|
|
8
|
+
|
|
9
|
+
// Parse `key=value` tokens into an object. Values that look like numbers/bools are
|
|
10
|
+
// coerced unless `raw` is set. Supports dotted keys via `--json`-style callers.
|
|
11
|
+
function parseKeyValues(tokens, { coerce = true } = {}) {
|
|
12
|
+
const out = {};
|
|
13
|
+
for (const tok of tokens) {
|
|
14
|
+
const idx = tok.indexOf('=');
|
|
15
|
+
if (idx <= 0) throw new CliError(`expected key=value, got "${tok}"`);
|
|
16
|
+
const key = tok.slice(0, idx).trim();
|
|
17
|
+
let value = tok.slice(idx + 1);
|
|
18
|
+
if (coerce) {
|
|
19
|
+
if (value === 'true') value = true;
|
|
20
|
+
else if (value === 'false') value = false;
|
|
21
|
+
else if (value !== '' && !Number.isNaN(Number(value)) && /^-?\d+(\.\d+)?$/.test(value)) value = Number(value);
|
|
22
|
+
}
|
|
23
|
+
out[key] = value;
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Render a command's result in the selected `-o/--output` format; `pretty` is
|
|
29
|
+
// the command's own human renderer.
|
|
30
|
+
function output(ctx, data, humanFn) {
|
|
31
|
+
const ui = require('../ui');
|
|
32
|
+
switch (outputFormat(ctx.flags)) {
|
|
33
|
+
case 'json': ui.json(data); return;
|
|
34
|
+
case 'yaml': process.stdout.write(toYaml(data)); return;
|
|
35
|
+
case 'env': process.stdout.write(toEnv(data)); return;
|
|
36
|
+
default: humanFn(data, ui);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// The same command under a second (Supabase) name: one implementation, with
|
|
41
|
+
// help that shows the name it was reached by.
|
|
42
|
+
function alias(entry, usage, summary = entry.summary) {
|
|
43
|
+
return { ...entry, usage, summary };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Parse a dotenv file: `KEY=value`, `export KEY=value`, `#` comments, and
|
|
47
|
+
// single- or double-quoted values (double quotes expand \n).
|
|
48
|
+
function parseDotenv(text) {
|
|
49
|
+
const out = {};
|
|
50
|
+
for (const raw of String(text).split(/\r?\n/)) {
|
|
51
|
+
const line = raw.trim().replace(/^export\s+/, '');
|
|
52
|
+
if (!line || line.startsWith('#')) continue;
|
|
53
|
+
const idx = line.indexOf('=');
|
|
54
|
+
if (idx <= 0) throw new CliError(`invalid env line: "${raw}"`);
|
|
55
|
+
const key = line.slice(0, idx).trim();
|
|
56
|
+
let value = line.slice(idx + 1).trim();
|
|
57
|
+
const quote = value[0];
|
|
58
|
+
if ((quote === '"' || quote === "'") && value.endsWith(quote) && value.length > 1) {
|
|
59
|
+
value = value.slice(1, -1);
|
|
60
|
+
if (quote === '"') value = value.replace(/\\n/g, '\n');
|
|
61
|
+
} else {
|
|
62
|
+
value = value.replace(/\s+#.*$/, '');
|
|
63
|
+
}
|
|
64
|
+
out[key] = value;
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function requireArg(value, name) {
|
|
70
|
+
if (value === undefined || value === null || value === '') {
|
|
71
|
+
throw new CliError(`missing required argument: <${name}>`);
|
|
72
|
+
}
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Split "bucket:path" into { bucket, rest }.
|
|
77
|
+
function splitRef(ref) {
|
|
78
|
+
const idx = ref.indexOf(':');
|
|
79
|
+
if (idx < 0) return { bucket: undefined, rest: ref };
|
|
80
|
+
return { bucket: ref.slice(0, idx), rest: ref.slice(idx + 1) };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// `/projects/<linked project>` on the Console, for the per-project platform
|
|
84
|
+
// surfaces (branches, domains) that live on the Console rather than the
|
|
85
|
+
// project API. They need a Console, so direct mode cannot reach them.
|
|
86
|
+
function consoleProjectPath(ctx, suffix = '') {
|
|
87
|
+
ctx.requireConsole();
|
|
88
|
+
if (!ctx.projectId) {
|
|
89
|
+
throw new CliError('No project linked. Run `motherbase link` or pass --project-ref <id>.');
|
|
90
|
+
}
|
|
91
|
+
return `/projects/${encodeURIComponent(ctx.projectId)}${suffix}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Join `parts` under `root`, refusing a result that escapes it. Names that come
|
|
95
|
+
// from the server (object keys, function routes) must not write outside the
|
|
96
|
+
// directory the user picked: `../../.bashrc` is a valid object key.
|
|
97
|
+
function resolveInside(root, ...parts) {
|
|
98
|
+
const target = path.resolve(root, ...parts);
|
|
99
|
+
const rel = path.relative(root, target);
|
|
100
|
+
if (rel === '..' || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) {
|
|
101
|
+
throw new CliError(`Refusing to write outside ${root}: ${parts.join('/')}`);
|
|
102
|
+
}
|
|
103
|
+
return target;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = { parseKeyValues, output, alias, parseDotenv, requireArg, splitRef, consoleProjectPath, resolveInside };
|
package/src/commands/backups.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Supabase's top-level `backups`. The implementations live under `db`, which
|
|
4
|
-
// keeps its own names for them.
|
|
5
|
-
|
|
6
|
-
const db = require('./db');
|
|
7
|
-
const { alias } = require('./_util');
|
|
8
|
-
|
|
9
|
-
module.exports = {
|
|
10
|
-
summary: 'List and restore database backups',
|
|
11
|
-
usage: 'motherbase backups <list|restore> ...',
|
|
12
|
-
subcommands: {
|
|
13
|
-
list: alias(db.subcommands.backups, 'motherbase backups list', 'List database backups'),
|
|
14
|
-
restore: alias(db.subcommands.restore, 'motherbase backups restore <file> | --key <remoteKey> --confirm'),
|
|
15
|
-
},
|
|
16
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Supabase's top-level `backups`. The implementations live under `db`, which
|
|
4
|
+
// keeps its own names for them.
|
|
5
|
+
|
|
6
|
+
const db = require('./db');
|
|
7
|
+
const { alias } = require('./_util');
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
summary: 'List and restore database backups',
|
|
11
|
+
usage: 'motherbase backups <list|restore> ...',
|
|
12
|
+
subcommands: {
|
|
13
|
+
list: alias(db.subcommands.backups, 'motherbase backups list', 'List database backups'),
|
|
14
|
+
restore: alias(db.subcommands.restore, 'motherbase backups restore <file> | --key <remoteKey> --confirm'),
|
|
15
|
+
},
|
|
16
|
+
};
|