@motherbase/cli 0.2.6 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +248 -247
  3. package/bin/.motherbase/config.json +5 -0
  4. package/bin/motherbase.js +10 -10
  5. package/package.json +1 -1
  6. package/src/api.js +39 -16
  7. package/src/commands/_util.js +106 -106
  8. package/src/commands/backups.js +16 -16
  9. package/src/commands/branches.js +113 -113
  10. package/src/commands/completion.js +90 -90
  11. package/src/commands/config.js +47 -47
  12. package/src/commands/cron.js +65 -65
  13. package/src/commands/db.js +103 -103
  14. package/src/commands/domains.js +124 -124
  15. package/src/commands/gen.js +178 -172
  16. package/src/commands/import.js +55 -55
  17. package/src/commands/index.js +85 -85
  18. package/src/commands/inspect.js +178 -178
  19. package/src/commands/keys.js +72 -72
  20. package/src/commands/link.js +35 -35
  21. package/src/commands/local.js +100 -23
  22. package/src/commands/login.js +9 -3
  23. package/src/commands/logout.js +16 -16
  24. package/src/commands/mcp.js +61 -61
  25. package/src/commands/migrate.js +0 -5
  26. package/src/commands/migration.js +1 -1
  27. package/src/commands/network.js +83 -83
  28. package/src/commands/orgs.js +40 -40
  29. package/src/commands/postgres-config.js +113 -113
  30. package/src/commands/projects.js +131 -130
  31. package/src/commands/queues.js +133 -133
  32. package/src/commands/realtime.js +35 -35
  33. package/src/commands/rls.js +176 -176
  34. package/src/commands/secrets.js +66 -66
  35. package/src/commands/signing.js +130 -130
  36. package/src/commands/snippets.js +44 -44
  37. package/src/commands/status.js +42 -42
  38. package/src/commands/storage.js +261 -261
  39. package/src/commands/test.js +9 -9
  40. package/src/commands/unlink.js +15 -15
  41. package/src/commands/vault.js +82 -82
  42. package/src/commands/webhooks.js +62 -62
  43. package/src/commands/whoami.js +20 -20
  44. package/src/commands/wrappers.js +150 -150
  45. package/src/config.js +98 -98
  46. package/src/context.js +106 -106
  47. package/src/edge-templates.js +194 -194
  48. package/src/format.js +92 -92
  49. package/src/inspect/csv.js +22 -22
  50. package/src/inspect/queries.js +562 -562
  51. package/src/mcp/platform-tools.js +233 -233
  52. package/src/mcp/protocol.js +152 -152
  53. package/src/mcp/storage-tools.js +93 -93
  54. package/src/mcp/tools.js +478 -478
  55. package/src/migration/commands.js +2 -2
  56. package/src/migration/files.js +13 -0
  57. package/src/prompt.js +91 -91
  58. package/src/storage/remote.js +62 -62
  59. package/src/ui.js +111 -111
@@ -1,90 +1,90 @@
1
- 'use strict';
2
-
3
- // `completion <shell>`: a completion script generated from the command
4
- // registry, so it can never list a command that does not exist.
5
-
6
- const { requireArg } = require('./_util');
7
- const { CliError } = require('../context');
8
-
9
- // Every command path that has children, with those children:
10
- // [{ path: 'storage buckets', children: [{ name: 'list', summary }] }, ...].
11
- // The root is the path ''.
12
- function commandPaths(registry) {
13
- const out = [];
14
- const walk = (path, subcommands) => {
15
- const names = Object.keys(subcommands);
16
- out.push({ path, children: names.map(name => ({ name, summary: subcommands[name].summary || '' })) });
17
- for (const name of names) {
18
- const sub = subcommands[name].subcommands;
19
- if (sub) walk(path ? `${path} ${name}` : name, sub);
20
- }
21
- };
22
- walk('', registry);
23
- return out;
24
- }
25
-
26
- // The words typed so far, flags skipped, are the path whose children complete.
27
- function bash(paths) {
28
- const cases = paths
29
- .map(p => ` "${p.path}") words="${p.children.map(c => c.name).join(' ')}" ;;`)
30
- .join('\n');
31
- return `# motherbase bash completion. Load with: source <(motherbase completion bash)
32
- _motherbase() {
33
- local cur="\${COMP_WORDS[COMP_CWORD]}" path="" words="" i
34
- for ((i = 1; i < COMP_CWORD; i++)); do
35
- [[ \${COMP_WORDS[i]} == -* ]] && continue
36
- path="\${path:+$path }\${COMP_WORDS[i]}"
37
- done
38
- case "$path" in
39
- ${cases}
40
- esac
41
- COMPREPLY=($(compgen -W "$words" -- "$cur"))
42
- }
43
- complete -F _motherbase motherbase
44
- `;
45
- }
46
-
47
- // zsh runs the bash function through its bashcompinit shim.
48
- function zsh(paths) {
49
- return `# motherbase zsh completion. Load with: source <(motherbase completion zsh)
50
- autoload -U +X bashcompinit && bashcompinit
51
- ${bash(paths).split('\n').slice(1).join('\n')}`;
52
- }
53
-
54
- function fishQuote(text) {
55
- return `'${String(text).replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
56
- }
57
-
58
- function fish(paths) {
59
- const lines = paths.flatMap(p =>
60
- p.children.map(
61
- c => `complete -c motherbase -f -n ${fishQuote(`__motherbase_at '${p.path}'`)} -a ${fishQuote(c.name)} -d ${fishQuote(c.summary)}`,
62
- ),
63
- );
64
- return `# motherbase fish completion. Load with: motherbase completion fish | source
65
- function __motherbase_at
66
- set -l path (string join ' ' -- (string match -v -- '-*' (commandline -opc)[2..-1]))
67
- test "$path" = "$argv[1]"
68
- end
69
- ${lines.join('\n')}
70
- `;
71
- }
72
-
73
- const SHELLS = { bash, zsh, fish };
74
-
75
- function completionScript(shell, registry) {
76
- if (!SHELLS[shell]) throw new CliError(`shell must be one of: ${Object.keys(SHELLS).join(', ')}`);
77
- return SHELLS[shell](commandPaths(registry));
78
- }
79
-
80
- module.exports = {
81
- summary: 'Generate the autocompletion script for a shell',
82
- usage: 'motherbase completion <bash|zsh|fish>',
83
- run(ctx, args) {
84
- const shell = requireArg(args[0], 'shell');
85
- // Required here, not at the top: the registry itself requires this file.
86
- const { registry } = require('./index');
87
- process.stdout.write(completionScript(shell, registry));
88
- },
89
- completionScript,
90
- };
1
+ 'use strict';
2
+
3
+ // `completion <shell>`: a completion script generated from the command
4
+ // registry, so it can never list a command that does not exist.
5
+
6
+ const { requireArg } = require('./_util');
7
+ const { CliError } = require('../context');
8
+
9
+ // Every command path that has children, with those children:
10
+ // [{ path: 'storage buckets', children: [{ name: 'list', summary }] }, ...].
11
+ // The root is the path ''.
12
+ function commandPaths(registry) {
13
+ const out = [];
14
+ const walk = (path, subcommands) => {
15
+ const names = Object.keys(subcommands);
16
+ out.push({ path, children: names.map(name => ({ name, summary: subcommands[name].summary || '' })) });
17
+ for (const name of names) {
18
+ const sub = subcommands[name].subcommands;
19
+ if (sub) walk(path ? `${path} ${name}` : name, sub);
20
+ }
21
+ };
22
+ walk('', registry);
23
+ return out;
24
+ }
25
+
26
+ // The words typed so far, flags skipped, are the path whose children complete.
27
+ function bash(paths) {
28
+ const cases = paths
29
+ .map(p => ` "${p.path}") words="${p.children.map(c => c.name).join(' ')}" ;;`)
30
+ .join('\n');
31
+ return `# motherbase bash completion. Load with: source <(motherbase completion bash)
32
+ _motherbase() {
33
+ local cur="\${COMP_WORDS[COMP_CWORD]}" path="" words="" i
34
+ for ((i = 1; i < COMP_CWORD; i++)); do
35
+ [[ \${COMP_WORDS[i]} == -* ]] && continue
36
+ path="\${path:+$path }\${COMP_WORDS[i]}"
37
+ done
38
+ case "$path" in
39
+ ${cases}
40
+ esac
41
+ COMPREPLY=($(compgen -W "$words" -- "$cur"))
42
+ }
43
+ complete -F _motherbase motherbase
44
+ `;
45
+ }
46
+
47
+ // zsh runs the bash function through its bashcompinit shim.
48
+ function zsh(paths) {
49
+ return `# motherbase zsh completion. Load with: source <(motherbase completion zsh)
50
+ autoload -U +X bashcompinit && bashcompinit
51
+ ${bash(paths).split('\n').slice(1).join('\n')}`;
52
+ }
53
+
54
+ function fishQuote(text) {
55
+ return `'${String(text).replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
56
+ }
57
+
58
+ function fish(paths) {
59
+ const lines = paths.flatMap(p =>
60
+ p.children.map(
61
+ c => `complete -c motherbase -f -n ${fishQuote(`__motherbase_at '${p.path}'`)} -a ${fishQuote(c.name)} -d ${fishQuote(c.summary)}`,
62
+ ),
63
+ );
64
+ return `# motherbase fish completion. Load with: motherbase completion fish | source
65
+ function __motherbase_at
66
+ set -l path (string join ' ' -- (string match -v -- '-*' (commandline -opc)[2..-1]))
67
+ test "$path" = "$argv[1]"
68
+ end
69
+ ${lines.join('\n')}
70
+ `;
71
+ }
72
+
73
+ const SHELLS = { bash, zsh, fish };
74
+
75
+ function completionScript(shell, registry) {
76
+ if (!SHELLS[shell]) throw new CliError(`shell must be one of: ${Object.keys(SHELLS).join(', ')}`);
77
+ return SHELLS[shell](commandPaths(registry));
78
+ }
79
+
80
+ module.exports = {
81
+ summary: 'Generate the autocompletion script for a shell',
82
+ usage: 'motherbase completion <bash|zsh|fish>',
83
+ run(ctx, args) {
84
+ const shell = requireArg(args[0], 'shell');
85
+ // Required here, not at the top: the registry itself requires this file.
86
+ const { registry } = require('./index');
87
+ process.stdout.write(completionScript(shell, registry));
88
+ },
89
+ completionScript,
90
+ };
@@ -1,47 +1,47 @@
1
- 'use strict';
2
-
3
- const api = require('../api');
4
- const { output, requireArg, parseKeyValues } = require('./_util');
5
-
6
- const subcommands = {
7
- list: {
8
- summary: 'List setting groups',
9
- usage: 'motherbase config list',
10
- async run(ctx) {
11
- const data = await api.adminRequest(ctx, 'GET', '/settings');
12
- const groups = (data && data.groups) || [];
13
- output(ctx, data, (_d, ui) =>
14
- ui.table(groups, [
15
- { key: 'key', label: 'GROUP' },
16
- { key: 'label', label: 'LABEL' },
17
- { key: 'source', label: 'SOURCE' },
18
- ]),
19
- );
20
- },
21
- },
22
- get: {
23
- summary: 'Get a settings group',
24
- usage: 'motherbase config get <group>',
25
- async run(ctx, args) {
26
- const group = requireArg(args[0], 'group');
27
- const data = await api.adminRequest(ctx, 'GET', `/settings/${group}`);
28
- output(ctx, data, (d, ui) => ui.keyValue(d || {}));
29
- },
30
- },
31
- set: {
32
- summary: 'Set values in a settings group',
33
- usage: 'motherbase config set <group> key=value [key2=value2 ...]',
34
- async run(ctx, args) {
35
- const group = requireArg(args[0], 'group');
36
- const body = parseKeyValues(args.slice(1));
37
- const data = await api.adminRequest(ctx, 'PUT', `/settings/${group}`, { body });
38
- output(ctx, data, (d, ui) => ui.success(`Saved settings for ${group}`));
39
- },
40
- },
41
- };
42
-
43
- module.exports = {
44
- summary: 'Project settings groups',
45
- usage: 'motherbase config <list|get|set> ...',
46
- subcommands,
47
- };
1
+ 'use strict';
2
+
3
+ const api = require('../api');
4
+ const { output, requireArg, parseKeyValues } = require('./_util');
5
+
6
+ const subcommands = {
7
+ list: {
8
+ summary: 'List setting groups',
9
+ usage: 'motherbase config list',
10
+ async run(ctx) {
11
+ const data = await api.adminRequest(ctx, 'GET', '/settings');
12
+ const groups = (data && data.groups) || [];
13
+ output(ctx, data, (_d, ui) =>
14
+ ui.table(groups, [
15
+ { key: 'key', label: 'GROUP' },
16
+ { key: 'label', label: 'LABEL' },
17
+ { key: 'source', label: 'SOURCE' },
18
+ ]),
19
+ );
20
+ },
21
+ },
22
+ get: {
23
+ summary: 'Get a settings group',
24
+ usage: 'motherbase config get <group>',
25
+ async run(ctx, args) {
26
+ const group = requireArg(args[0], 'group');
27
+ const data = await api.adminRequest(ctx, 'GET', `/settings/${group}`);
28
+ output(ctx, data, (d, ui) => ui.keyValue(d || {}));
29
+ },
30
+ },
31
+ set: {
32
+ summary: 'Set values in a settings group',
33
+ usage: 'motherbase config set <group> key=value [key2=value2 ...]',
34
+ async run(ctx, args) {
35
+ const group = requireArg(args[0], 'group');
36
+ const body = parseKeyValues(args.slice(1));
37
+ const data = await api.adminRequest(ctx, 'PUT', `/settings/${group}`, { body });
38
+ output(ctx, data, (d, ui) => ui.success(`Saved settings for ${group}`));
39
+ },
40
+ },
41
+ };
42
+
43
+ module.exports = {
44
+ summary: 'Project settings groups',
45
+ usage: 'motherbase config <list|get|set> ...',
46
+ subcommands,
47
+ };
@@ -1,65 +1,65 @@
1
- 'use strict';
2
-
3
- const api = require('../api');
4
- const { output, requireArg } = require('./_util');
5
- const { CliError } = require('../context');
6
-
7
- const subcommands = {
8
- list: {
9
- summary: 'List pg_cron jobs (and app jobs with --app)',
10
- usage: 'motherbase cron list [--app]',
11
- async run(ctx, args, flags) {
12
- if (flags.app) {
13
- const data = await api.adminRequest(ctx, 'GET', '/cron/app/jobs');
14
- const jobs = (data && data.jobs) || [];
15
- output(ctx, data, (_d, ui) => ui.table(jobs, [{ key: 'name', label: 'NAME' }, { key: 'schedule', label: 'SCHEDULE' }]));
16
- return;
17
- }
18
- const data = await api.adminRequest(ctx, 'GET', '/cron/pg/jobs');
19
- const jobs = Array.isArray(data) ? data : (data && data.jobs) || [];
20
- output(ctx, data, (_d, ui) =>
21
- ui.table(jobs, [
22
- { key: 'jobid', label: 'ID' },
23
- { key: 'jobname', label: 'NAME' },
24
- { key: 'schedule', label: 'SCHEDULE' },
25
- { key: 'active', label: 'ACTIVE' },
26
- ]),
27
- );
28
- },
29
- },
30
- create: {
31
- summary: 'Create a pg_cron job',
32
- usage: 'motherbase cron create --name <n> --schedule "<cron>" --command "<sql>"',
33
- async run(ctx, args, flags) {
34
- if (!flags.name || !flags.schedule || !flags.command) throw new CliError('--name, --schedule and --command are required');
35
- const body = { name: flags.name, schedule: flags.schedule, command: flags.command };
36
- const data = await api.adminRequest(ctx, 'POST', '/cron/pg/jobs', { body });
37
- output(ctx, data, (d, ui) => ui.success(`Created cron job ${flags.name}`));
38
- },
39
- },
40
- delete: {
41
- summary: 'Delete a pg_cron job by numeric id',
42
- usage: 'motherbase cron delete <id>',
43
- async run(ctx, args) {
44
- const id = requireArg(args[0], 'id');
45
- const data = await api.adminRequest(ctx, 'DELETE', `/cron/pg/jobs/${id}`);
46
- output(ctx, data, (d, ui) => ui.success(`Deleted cron job ${id}`));
47
- },
48
- },
49
- toggle: {
50
- summary: 'Enable/disable a pg_cron job',
51
- usage: 'motherbase cron toggle <id> --active <true|false>',
52
- async run(ctx, args, flags) {
53
- const id = requireArg(args[0], 'id');
54
- const active = flags.active === undefined ? true : flags.active === 'true' || flags.active === true;
55
- const data = await api.adminRequest(ctx, 'PATCH', `/cron/pg/jobs/${id}`, { body: { active } });
56
- output(ctx, data, (d, ui) => ui.success(`Cron job ${id} active=${active}`));
57
- },
58
- },
59
- };
60
-
61
- module.exports = {
62
- summary: 'Scheduled jobs (pg_cron)',
63
- usage: 'motherbase cron <list|create|delete|toggle> ...',
64
- subcommands,
65
- };
1
+ 'use strict';
2
+
3
+ const api = require('../api');
4
+ const { output, requireArg } = require('./_util');
5
+ const { CliError } = require('../context');
6
+
7
+ const subcommands = {
8
+ list: {
9
+ summary: 'List pg_cron jobs (and app jobs with --app)',
10
+ usage: 'motherbase cron list [--app]',
11
+ async run(ctx, args, flags) {
12
+ if (flags.app) {
13
+ const data = await api.adminRequest(ctx, 'GET', '/cron/app/jobs');
14
+ const jobs = (data && data.jobs) || [];
15
+ output(ctx, data, (_d, ui) => ui.table(jobs, [{ key: 'name', label: 'NAME' }, { key: 'schedule', label: 'SCHEDULE' }]));
16
+ return;
17
+ }
18
+ const data = await api.adminRequest(ctx, 'GET', '/cron/pg/jobs');
19
+ const jobs = Array.isArray(data) ? data : (data && data.jobs) || [];
20
+ output(ctx, data, (_d, ui) =>
21
+ ui.table(jobs, [
22
+ { key: 'jobid', label: 'ID' },
23
+ { key: 'jobname', label: 'NAME' },
24
+ { key: 'schedule', label: 'SCHEDULE' },
25
+ { key: 'active', label: 'ACTIVE' },
26
+ ]),
27
+ );
28
+ },
29
+ },
30
+ create: {
31
+ summary: 'Create a pg_cron job',
32
+ usage: 'motherbase cron create --name <n> --schedule "<cron>" --command "<sql>"',
33
+ async run(ctx, args, flags) {
34
+ if (!flags.name || !flags.schedule || !flags.command) throw new CliError('--name, --schedule and --command are required');
35
+ const body = { name: flags.name, schedule: flags.schedule, command: flags.command };
36
+ const data = await api.adminRequest(ctx, 'POST', '/cron/pg/jobs', { body });
37
+ output(ctx, data, (d, ui) => ui.success(`Created cron job ${flags.name}`));
38
+ },
39
+ },
40
+ delete: {
41
+ summary: 'Delete a pg_cron job by numeric id',
42
+ usage: 'motherbase cron delete <id>',
43
+ async run(ctx, args) {
44
+ const id = requireArg(args[0], 'id');
45
+ const data = await api.adminRequest(ctx, 'DELETE', `/cron/pg/jobs/${id}`);
46
+ output(ctx, data, (d, ui) => ui.success(`Deleted cron job ${id}`));
47
+ },
48
+ },
49
+ toggle: {
50
+ summary: 'Enable/disable a pg_cron job',
51
+ usage: 'motherbase cron toggle <id> --active <true|false>',
52
+ async run(ctx, args, flags) {
53
+ const id = requireArg(args[0], 'id');
54
+ const active = flags.active === undefined ? true : flags.active === 'true' || flags.active === true;
55
+ const data = await api.adminRequest(ctx, 'PATCH', `/cron/pg/jobs/${id}`, { body: { active } });
56
+ output(ctx, data, (d, ui) => ui.success(`Cron job ${id} active=${active}`));
57
+ },
58
+ },
59
+ };
60
+
61
+ module.exports = {
62
+ summary: 'Scheduled jobs (pg_cron)',
63
+ usage: 'motherbase cron <list|create|delete|toggle> ...',
64
+ subcommands,
65
+ };
@@ -1,103 +1,103 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const api = require('../api');
6
- const prompt = require('../prompt');
7
- const rls = require('./rls');
8
- const { output, alias, parseKeyValues } = require('./_util');
9
- const { CliError } = require('../context');
10
-
11
- const subcommands = {
12
- tables: {
13
- summary: 'List database tables',
14
- usage: 'motherbase db tables',
15
- async run(ctx) {
16
- const data = await api.adminRequest(ctx, 'GET', '/schema/tables');
17
- const tables = (data && data.tables) || data || [];
18
- output(ctx, data, (_d, ui) =>
19
- ui.table(Array.isArray(tables) ? tables : [], [
20
- { key: 'name', label: 'TABLE' },
21
- { key: 'schema', label: 'SCHEMA' },
22
- ]),
23
- );
24
- },
25
- },
26
- sql: {
27
- summary: 'Run SQL from -e "..." or a file',
28
- usage: 'motherbase db sql -e "select 1" | motherbase db sql <file.sql>',
29
- async run(ctx, args, flags) {
30
- let sql = flags.e || flags.execute;
31
- if (!sql && args[0]) sql = fs.readFileSync(path.resolve(ctx.cwd, args[0]), 'utf8');
32
- if (!sql) throw new CliError('Provide SQL via -e "..." or a file path.');
33
- const data = await api.adminRequest(ctx, 'POST', '/sql', { body: { sql } });
34
- output(ctx, data, (d, ui) => {
35
- if (d && Array.isArray(d.rows)) ui.table(d.rows);
36
- else if (Array.isArray(d)) ui.table(d);
37
- else ui.json(d);
38
- });
39
- },
40
- },
41
- // `db dump` is Supabase's local dump (../migration/commands); this is the
42
- // server-side backup it used to be.
43
- backup: {
44
- summary: 'Create a database backup on the server',
45
- usage: 'motherbase db backup [file]',
46
- async run(ctx, args) {
47
- const body = args[0] ? { file: args[0] } : {};
48
- const data = await api.adminRequest(ctx, 'POST', '/backups', { body });
49
- output(ctx, data, (d, ui) => ui.success(`Backup created${d && d.file ? `: ${d.file}` : ''}`));
50
- },
51
- },
52
- restore: {
53
- summary: 'Restore the database from a server dump or offsite key',
54
- usage: 'motherbase db restore <file> | --key <remoteKey> --confirm',
55
- async run(ctx, args, flags) {
56
- const file = args[0];
57
- const key = flags.key;
58
- if (!file && !key) throw new CliError('Provide a <file> or --key <remoteKey>.');
59
- if (!flags.confirm && !flags.yes) {
60
- const ok = await prompt.confirm('This will overwrite the database. Continue?');
61
- if (!ok) throw new CliError('Aborted.');
62
- }
63
- const body = key ? { key, confirm: 'RESTORE' } : { file, confirm: 'RESTORE' };
64
- const data = await api.adminRequest(ctx, 'POST', '/backups/restore', { body });
65
- output(ctx, data, (d, ui) => ui.success('Restore complete'));
66
- },
67
- },
68
- backups: {
69
- summary: 'List backups, or manage backup config (`db backups config`)',
70
- usage: 'motherbase db backups [list] | motherbase db backups config [--set key=value ...]',
71
- async run(ctx, args, flags) {
72
- const sub = args[0];
73
- if (sub === 'config') {
74
- const sets = [].concat(flags.set || []).filter(Boolean);
75
- if (sets.length) {
76
- const body = parseKeyValues(sets);
77
- const data = await api.adminRequest(ctx, 'PUT', '/backups/config', { body });
78
- output(ctx, data, (d, ui) => ui.success('Backup config saved'));
79
- } else {
80
- const data = await api.adminRequest(ctx, 'GET', '/backups/config');
81
- output(ctx, data, (d, ui) => ui.keyValue(d || {}));
82
- }
83
- return;
84
- }
85
- const data = await api.adminRequest(ctx, 'GET', '/backups/list');
86
- output(ctx, data, (d, ui) => ui.json(d));
87
- },
88
- },
89
- };
90
-
91
- // Supabase's local-files workflow: push, pull, diff, reset, dump, seed.
92
- Object.assign(subcommands, require('../migration/commands').dbSubcommands);
93
- // Studio's Database section: indexes, enum types, roles, column privileges, publications.
94
- Object.assign(subcommands, require('./db-catalog'));
95
-
96
- // Supabase's `db lint`: the same advisor as `rls lint`.
97
- subcommands.lint = alias(rls.subcommands.lint, 'motherbase db lint [--level critical|warning|info]');
98
-
99
- module.exports = {
100
- summary: 'Database: tables, SQL, dumps, restores, indexes, types, roles, publications',
101
- usage: 'motherbase db <push|pull|diff|reset|dump|seed|tables|sql|backup|restore|backups|lint|indexes|types|roles|column-privileges|publications> ...',
102
- subcommands,
103
- };
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const api = require('../api');
6
+ const prompt = require('../prompt');
7
+ const rls = require('./rls');
8
+ const { output, alias, parseKeyValues } = require('./_util');
9
+ const { CliError } = require('../context');
10
+
11
+ const subcommands = {
12
+ tables: {
13
+ summary: 'List database tables',
14
+ usage: 'motherbase db tables',
15
+ async run(ctx) {
16
+ const data = await api.adminRequest(ctx, 'GET', '/schema/tables');
17
+ const tables = (data && data.tables) || data || [];
18
+ output(ctx, data, (_d, ui) =>
19
+ ui.table(Array.isArray(tables) ? tables : [], [
20
+ { key: 'name', label: 'TABLE' },
21
+ { key: 'schema', label: 'SCHEMA' },
22
+ ]),
23
+ );
24
+ },
25
+ },
26
+ sql: {
27
+ summary: 'Run SQL from -e "..." or a file',
28
+ usage: 'motherbase db sql -e "select 1" | motherbase db sql <file.sql>',
29
+ async run(ctx, args, flags) {
30
+ let sql = flags.e || flags.execute;
31
+ if (!sql && args[0]) sql = fs.readFileSync(path.resolve(ctx.cwd, args[0]), 'utf8');
32
+ if (!sql) throw new CliError('Provide SQL via -e "..." or a file path.');
33
+ const data = await api.adminRequest(ctx, 'POST', '/sql', { body: { sql } });
34
+ output(ctx, data, (d, ui) => {
35
+ if (d && Array.isArray(d.rows)) ui.table(d.rows);
36
+ else if (Array.isArray(d)) ui.table(d);
37
+ else ui.json(d);
38
+ });
39
+ },
40
+ },
41
+ // `db dump` is Supabase's local dump (../migration/commands); this is the
42
+ // server-side backup it used to be.
43
+ backup: {
44
+ summary: 'Create a database backup on the server',
45
+ usage: 'motherbase db backup [file]',
46
+ async run(ctx, args) {
47
+ const body = args[0] ? { file: args[0] } : {};
48
+ const data = await api.adminRequest(ctx, 'POST', '/backups', { body });
49
+ output(ctx, data, (d, ui) => ui.success(`Backup created${d && d.file ? `: ${d.file}` : ''}`));
50
+ },
51
+ },
52
+ restore: {
53
+ summary: 'Restore the database from a server dump or offsite key',
54
+ usage: 'motherbase db restore <file> | --key <remoteKey> --confirm',
55
+ async run(ctx, args, flags) {
56
+ const file = args[0];
57
+ const key = flags.key;
58
+ if (!file && !key) throw new CliError('Provide a <file> or --key <remoteKey>.');
59
+ if (!flags.confirm && !flags.yes) {
60
+ const ok = await prompt.confirm('This will overwrite the database. Continue?');
61
+ if (!ok) throw new CliError('Aborted.');
62
+ }
63
+ const body = key ? { key, confirm: 'RESTORE' } : { file, confirm: 'RESTORE' };
64
+ const data = await api.adminRequest(ctx, 'POST', '/backups/restore', { body });
65
+ output(ctx, data, (d, ui) => ui.success('Restore complete'));
66
+ },
67
+ },
68
+ backups: {
69
+ summary: 'List backups, or manage backup config (`db backups config`)',
70
+ usage: 'motherbase db backups [list] | motherbase db backups config [--set key=value ...]',
71
+ async run(ctx, args, flags) {
72
+ const sub = args[0];
73
+ if (sub === 'config') {
74
+ const sets = [].concat(flags.set || []).filter(Boolean);
75
+ if (sets.length) {
76
+ const body = parseKeyValues(sets);
77
+ const data = await api.adminRequest(ctx, 'PUT', '/backups/config', { body });
78
+ output(ctx, data, (d, ui) => ui.success('Backup config saved'));
79
+ } else {
80
+ const data = await api.adminRequest(ctx, 'GET', '/backups/config');
81
+ output(ctx, data, (d, ui) => ui.keyValue(d || {}));
82
+ }
83
+ return;
84
+ }
85
+ const data = await api.adminRequest(ctx, 'GET', '/backups/list');
86
+ output(ctx, data, (d, ui) => ui.json(d));
87
+ },
88
+ },
89
+ };
90
+
91
+ // Supabase's local-files workflow: push, pull, diff, reset, dump, seed.
92
+ Object.assign(subcommands, require('../migration/commands').dbSubcommands);
93
+ // Studio's Database section: indexes, enum types, roles, column privileges, publications.
94
+ Object.assign(subcommands, require('./db-catalog'));
95
+
96
+ // Supabase's `db lint`: the same advisor as `rls lint`.
97
+ subcommands.lint = alias(rls.subcommands.lint, 'motherbase db lint [--level critical|warning|info]');
98
+
99
+ module.exports = {
100
+ summary: 'Database: tables, SQL, dumps, restores, indexes, types, roles, publications',
101
+ usage: 'motherbase db <push|pull|diff|reset|dump|seed|tables|sql|backup|restore|backups|lint|indexes|types|roles|column-privileges|publications> ...',
102
+ subcommands,
103
+ };