@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/src/commands/branches.js
CHANGED
|
@@ -1,113 +1,113 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Preview branches (Supabase's `supabase branches`). A branch is a project of
|
|
4
|
-
// its own on the Console, cut from the linked project; these commands address
|
|
5
|
-
// it by its name (e.g. the git branch) or its project id.
|
|
6
|
-
|
|
7
|
-
const api = require('../api');
|
|
8
|
-
const config = require('../config');
|
|
9
|
-
const { output, requireArg, consoleProjectPath } = require('./_util');
|
|
10
|
-
|
|
11
|
-
const COLUMNS = [
|
|
12
|
-
{ key: 'name', label: 'NAME' },
|
|
13
|
-
{ key: 'project', label: 'PROJECT' },
|
|
14
|
-
{ key: 'gitBranch', label: 'GIT BRANCH' },
|
|
15
|
-
{ key: 'status', label: 'STATUS' },
|
|
16
|
-
{ key: 'running', label: 'RUNNING' },
|
|
17
|
-
{ key: 'createdAt', label: 'CREATED' },
|
|
18
|
-
];
|
|
19
|
-
|
|
20
|
-
const branchPath = (ctx, name, action = '') =>
|
|
21
|
-
consoleProjectPath(ctx, `/branches/${encodeURIComponent(requireArg(name, 'name'))}${action}`);
|
|
22
|
-
|
|
23
|
-
function action(verb, summary, render) {
|
|
24
|
-
return {
|
|
25
|
-
summary,
|
|
26
|
-
usage: `motherbase branches ${verb} <name>`,
|
|
27
|
-
async run(ctx, args) {
|
|
28
|
-
const data = await api.consoleRequest(ctx, 'POST', branchPath(ctx, args[0], `/${verb}`));
|
|
29
|
-
output(ctx, data, (d, ui) => render(d, ui));
|
|
30
|
-
},
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const subcommands = {
|
|
35
|
-
create: {
|
|
36
|
-
summary: 'Create a preview branch of the linked project',
|
|
37
|
-
usage: 'motherbase branches create <name> [--git-branch <ref>] [--with-data]',
|
|
38
|
-
async run(ctx, args, flags) {
|
|
39
|
-
const body = { name: requireArg(args[0], 'name'), withData: Boolean(flags['with-data']) };
|
|
40
|
-
if (flags['git-branch']) body.gitBranch = String(flags['git-branch']);
|
|
41
|
-
const data = await api.consoleRequest(ctx, 'POST', consoleProjectPath(ctx, '/branches'), { body });
|
|
42
|
-
output(ctx, data, (d, ui) => {
|
|
43
|
-
ui.success(`Creating branch ${d.name} as project ${d.project} (${d.domain})`);
|
|
44
|
-
ui.info('This takes a few minutes. Follow it with `motherbase branches get ' + d.name + '`.');
|
|
45
|
-
});
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
list: {
|
|
49
|
-
summary: 'List the branches of the linked project, main first',
|
|
50
|
-
usage: 'motherbase branches list',
|
|
51
|
-
async run(ctx) {
|
|
52
|
-
const rows = await api.consoleRequest(ctx, 'GET', consoleProjectPath(ctx, '/branches'));
|
|
53
|
-
output(ctx, { branches: rows }, (d, ui) => ui.table(d.branches, COLUMNS));
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
get: {
|
|
57
|
-
summary: 'Show one branch, with the migrations it is ahead and behind',
|
|
58
|
-
usage: 'motherbase branches get <name>',
|
|
59
|
-
async run(ctx, args) {
|
|
60
|
-
const data = await api.consoleRequest(ctx, 'GET', branchPath(ctx, args[0]));
|
|
61
|
-
output(ctx, data, (d, ui) => {
|
|
62
|
-
const { migrations, ...rest } = d;
|
|
63
|
-
ui.keyValue(rest);
|
|
64
|
-
if (migrations) ui.keyValue({ ahead: migrations.ahead.join(', ') || '-', behind: migrations.behind.join(', ') || '-' });
|
|
65
|
-
});
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
update: {
|
|
69
|
-
summary: 'Change a branch\'s git branch',
|
|
70
|
-
usage: 'motherbase branches update <name> --git-branch <ref>',
|
|
71
|
-
async run(ctx, args, flags) {
|
|
72
|
-
const body = {};
|
|
73
|
-
if (flags['git-branch'] !== undefined) body.gitBranch = String(flags['git-branch']);
|
|
74
|
-
const data = await api.consoleRequest(ctx, 'PATCH', branchPath(ctx, args[0]), { body });
|
|
75
|
-
output(ctx, data, (d, ui) => ui.success(`Updated branch ${d.name}`));
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
delete: {
|
|
79
|
-
summary: 'Delete a branch and its whole stack',
|
|
80
|
-
usage: 'motherbase branches delete <name>',
|
|
81
|
-
async run(ctx, args) {
|
|
82
|
-
const data = await api.consoleRequest(ctx, 'DELETE', branchPath(ctx, args[0]));
|
|
83
|
-
output(ctx, data, (d, ui) => ui.success(`Deleted branch ${d.name}`));
|
|
84
|
-
},
|
|
85
|
-
},
|
|
86
|
-
pause: action('pause', 'Stop a branch\'s stack, keeping its data', (d, ui) => ui.success(`Paused ${d.name}`)),
|
|
87
|
-
unpause: action('unpause', 'Start a paused branch again', (d, ui) => ui.success(`Unpaused ${d.name}`)),
|
|
88
|
-
reset: action('reset', 'Recreate a branch from the parent as it is now (its changes are lost)',
|
|
89
|
-
(d, ui) => ui.success(`Resetting ${d.name}; it is being cut again from ${d.parent}`)),
|
|
90
|
-
merge: action('merge', 'Apply the branch\'s new migrations to the parent',
|
|
91
|
-
(d, ui) => ui.success(d.applied.length ? `Merged ${d.applied.length} migration(s): ${d.applied.join(', ')}` : 'Nothing to merge')),
|
|
92
|
-
rebase: action('rebase', 'Apply the parent\'s newer migrations to the branch',
|
|
93
|
-
(d, ui) => ui.success(d.applied.length ? `Rebased ${d.applied.length} migration(s): ${d.applied.join(', ')}` : 'Already up to date')),
|
|
94
|
-
switch: {
|
|
95
|
-
summary: 'Point this directory\'s link at a branch (or back at main)',
|
|
96
|
-
usage: 'motherbase branches switch <name|main>',
|
|
97
|
-
async run(ctx, args) {
|
|
98
|
-
const name = requireArg(args[0], 'name');
|
|
99
|
-
const rows = await api.consoleRequest(ctx, 'GET', consoleProjectPath(ctx, '/branches'));
|
|
100
|
-
const match = rows.find(b => b.name === name || b.project === name || (name === 'main' && b.isDefault));
|
|
101
|
-
if (!match) throw new Error(`No branch ${name}. See \`motherbase branches list\`.`);
|
|
102
|
-
const link = config.readLink(ctx.cwd) || {};
|
|
103
|
-
config.writeLink(ctx.cwd, { ...link, consoleUrl: ctx.consoleUrl, projectId: match.project, projectName: match.project });
|
|
104
|
-
output(ctx, { project: match.project, branch: match.name }, (d, ui) => ui.success(`Linked ${ctx.cwd} → ${d.project} (${d.branch})`));
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
module.exports = {
|
|
110
|
-
summary: 'Manage preview branches of the linked project',
|
|
111
|
-
usage: 'motherbase branches <create|list|get|update|delete|pause|unpause|reset|merge|rebase|switch> ...',
|
|
112
|
-
subcommands,
|
|
113
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Preview branches (Supabase's `supabase branches`). A branch is a project of
|
|
4
|
+
// its own on the Console, cut from the linked project; these commands address
|
|
5
|
+
// it by its name (e.g. the git branch) or its project id.
|
|
6
|
+
|
|
7
|
+
const api = require('../api');
|
|
8
|
+
const config = require('../config');
|
|
9
|
+
const { output, requireArg, consoleProjectPath } = require('./_util');
|
|
10
|
+
|
|
11
|
+
const COLUMNS = [
|
|
12
|
+
{ key: 'name', label: 'NAME' },
|
|
13
|
+
{ key: 'project', label: 'PROJECT' },
|
|
14
|
+
{ key: 'gitBranch', label: 'GIT BRANCH' },
|
|
15
|
+
{ key: 'status', label: 'STATUS' },
|
|
16
|
+
{ key: 'running', label: 'RUNNING' },
|
|
17
|
+
{ key: 'createdAt', label: 'CREATED' },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const branchPath = (ctx, name, action = '') =>
|
|
21
|
+
consoleProjectPath(ctx, `/branches/${encodeURIComponent(requireArg(name, 'name'))}${action}`);
|
|
22
|
+
|
|
23
|
+
function action(verb, summary, render) {
|
|
24
|
+
return {
|
|
25
|
+
summary,
|
|
26
|
+
usage: `motherbase branches ${verb} <name>`,
|
|
27
|
+
async run(ctx, args) {
|
|
28
|
+
const data = await api.consoleRequest(ctx, 'POST', branchPath(ctx, args[0], `/${verb}`));
|
|
29
|
+
output(ctx, data, (d, ui) => render(d, ui));
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const subcommands = {
|
|
35
|
+
create: {
|
|
36
|
+
summary: 'Create a preview branch of the linked project',
|
|
37
|
+
usage: 'motherbase branches create <name> [--git-branch <ref>] [--with-data]',
|
|
38
|
+
async run(ctx, args, flags) {
|
|
39
|
+
const body = { name: requireArg(args[0], 'name'), withData: Boolean(flags['with-data']) };
|
|
40
|
+
if (flags['git-branch']) body.gitBranch = String(flags['git-branch']);
|
|
41
|
+
const data = await api.consoleRequest(ctx, 'POST', consoleProjectPath(ctx, '/branches'), { body });
|
|
42
|
+
output(ctx, data, (d, ui) => {
|
|
43
|
+
ui.success(`Creating branch ${d.name} as project ${d.project} (${d.domain})`);
|
|
44
|
+
ui.info('This takes a few minutes. Follow it with `motherbase branches get ' + d.name + '`.');
|
|
45
|
+
});
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
list: {
|
|
49
|
+
summary: 'List the branches of the linked project, main first',
|
|
50
|
+
usage: 'motherbase branches list',
|
|
51
|
+
async run(ctx) {
|
|
52
|
+
const rows = await api.consoleRequest(ctx, 'GET', consoleProjectPath(ctx, '/branches'));
|
|
53
|
+
output(ctx, { branches: rows }, (d, ui) => ui.table(d.branches, COLUMNS));
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
get: {
|
|
57
|
+
summary: 'Show one branch, with the migrations it is ahead and behind',
|
|
58
|
+
usage: 'motherbase branches get <name>',
|
|
59
|
+
async run(ctx, args) {
|
|
60
|
+
const data = await api.consoleRequest(ctx, 'GET', branchPath(ctx, args[0]));
|
|
61
|
+
output(ctx, data, (d, ui) => {
|
|
62
|
+
const { migrations, ...rest } = d;
|
|
63
|
+
ui.keyValue(rest);
|
|
64
|
+
if (migrations) ui.keyValue({ ahead: migrations.ahead.join(', ') || '-', behind: migrations.behind.join(', ') || '-' });
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
update: {
|
|
69
|
+
summary: 'Change a branch\'s git branch',
|
|
70
|
+
usage: 'motherbase branches update <name> --git-branch <ref>',
|
|
71
|
+
async run(ctx, args, flags) {
|
|
72
|
+
const body = {};
|
|
73
|
+
if (flags['git-branch'] !== undefined) body.gitBranch = String(flags['git-branch']);
|
|
74
|
+
const data = await api.consoleRequest(ctx, 'PATCH', branchPath(ctx, args[0]), { body });
|
|
75
|
+
output(ctx, data, (d, ui) => ui.success(`Updated branch ${d.name}`));
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
delete: {
|
|
79
|
+
summary: 'Delete a branch and its whole stack',
|
|
80
|
+
usage: 'motherbase branches delete <name>',
|
|
81
|
+
async run(ctx, args) {
|
|
82
|
+
const data = await api.consoleRequest(ctx, 'DELETE', branchPath(ctx, args[0]));
|
|
83
|
+
output(ctx, data, (d, ui) => ui.success(`Deleted branch ${d.name}`));
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
pause: action('pause', 'Stop a branch\'s stack, keeping its data', (d, ui) => ui.success(`Paused ${d.name}`)),
|
|
87
|
+
unpause: action('unpause', 'Start a paused branch again', (d, ui) => ui.success(`Unpaused ${d.name}`)),
|
|
88
|
+
reset: action('reset', 'Recreate a branch from the parent as it is now (its changes are lost)',
|
|
89
|
+
(d, ui) => ui.success(`Resetting ${d.name}; it is being cut again from ${d.parent}`)),
|
|
90
|
+
merge: action('merge', 'Apply the branch\'s new migrations to the parent',
|
|
91
|
+
(d, ui) => ui.success(d.applied.length ? `Merged ${d.applied.length} migration(s): ${d.applied.join(', ')}` : 'Nothing to merge')),
|
|
92
|
+
rebase: action('rebase', 'Apply the parent\'s newer migrations to the branch',
|
|
93
|
+
(d, ui) => ui.success(d.applied.length ? `Rebased ${d.applied.length} migration(s): ${d.applied.join(', ')}` : 'Already up to date')),
|
|
94
|
+
switch: {
|
|
95
|
+
summary: 'Point this directory\'s link at a branch (or back at main)',
|
|
96
|
+
usage: 'motherbase branches switch <name|main>',
|
|
97
|
+
async run(ctx, args) {
|
|
98
|
+
const name = requireArg(args[0], 'name');
|
|
99
|
+
const rows = await api.consoleRequest(ctx, 'GET', consoleProjectPath(ctx, '/branches'));
|
|
100
|
+
const match = rows.find(b => b.name === name || b.project === name || (name === 'main' && b.isDefault));
|
|
101
|
+
if (!match) throw new Error(`No branch ${name}. See \`motherbase branches list\`.`);
|
|
102
|
+
const link = config.readLink(ctx.cwd) || {};
|
|
103
|
+
config.writeLink(ctx.cwd, { ...link, consoleUrl: ctx.consoleUrl, projectId: match.project, projectName: match.project });
|
|
104
|
+
output(ctx, { project: match.project, branch: match.name }, (d, ui) => ui.success(`Linked ${ctx.cwd} → ${d.project} (${d.branch})`));
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
module.exports = {
|
|
110
|
+
summary: 'Manage preview branches of the linked project',
|
|
111
|
+
usage: 'motherbase branches <create|list|get|update|delete|pause|unpause|reset|merge|rebase|switch> ...',
|
|
112
|
+
subcommands,
|
|
113
|
+
};
|
|
@@ -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
|
+
};
|
package/src/commands/config.js
CHANGED
|
@@ -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
|
+
};
|
package/src/commands/cron.js
CHANGED
|
@@ -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
|
+
};
|