@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/orgs.js
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Tenant organizations on the Platform Console. Projects sit under an org, as
|
|
4
|
-
// they do on Supabase.
|
|
5
|
-
|
|
6
|
-
const api = require('../api');
|
|
7
|
-
const { output, requireArg } = require('./_util');
|
|
8
|
-
|
|
9
|
-
const subcommands = {
|
|
10
|
-
list: {
|
|
11
|
-
summary: 'List the organizations you belong to',
|
|
12
|
-
usage: 'motherbase orgs list',
|
|
13
|
-
async run(ctx) {
|
|
14
|
-
const data = await api.consoleRequest(ctx, 'GET', '/orgs');
|
|
15
|
-
output(ctx, data, (rows, ui) =>
|
|
16
|
-
ui.table(Array.isArray(rows) ? rows : [], [
|
|
17
|
-
{ key: 'id', label: 'ID' },
|
|
18
|
-
{ key: 'name', label: 'NAME' },
|
|
19
|
-
{ key: 'role', label: 'ROLE' },
|
|
20
|
-
{ key: 'projectCount', label: 'PROJECTS' },
|
|
21
|
-
]),
|
|
22
|
-
);
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
create: {
|
|
26
|
-
summary: 'Create an organization',
|
|
27
|
-
usage: 'motherbase orgs create <name>',
|
|
28
|
-
async run(ctx, args) {
|
|
29
|
-
const name = requireArg(args[0], 'name');
|
|
30
|
-
const data = await api.consoleRequest(ctx, 'POST', '/orgs', { body: { name } });
|
|
31
|
-
output(ctx, data, (d, ui) => ui.success(`Created org ${d.name} (${d.id})`));
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
module.exports = {
|
|
37
|
-
summary: 'Manage organizations on the Platform Console',
|
|
38
|
-
usage: 'motherbase orgs <list|create> ...',
|
|
39
|
-
subcommands,
|
|
40
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Tenant organizations on the Platform Console. Projects sit under an org, as
|
|
4
|
+
// they do on Supabase.
|
|
5
|
+
|
|
6
|
+
const api = require('../api');
|
|
7
|
+
const { output, requireArg } = require('./_util');
|
|
8
|
+
|
|
9
|
+
const subcommands = {
|
|
10
|
+
list: {
|
|
11
|
+
summary: 'List the organizations you belong to',
|
|
12
|
+
usage: 'motherbase orgs list',
|
|
13
|
+
async run(ctx) {
|
|
14
|
+
const data = await api.consoleRequest(ctx, 'GET', '/orgs');
|
|
15
|
+
output(ctx, data, (rows, ui) =>
|
|
16
|
+
ui.table(Array.isArray(rows) ? rows : [], [
|
|
17
|
+
{ key: 'id', label: 'ID' },
|
|
18
|
+
{ key: 'name', label: 'NAME' },
|
|
19
|
+
{ key: 'role', label: 'ROLE' },
|
|
20
|
+
{ key: 'projectCount', label: 'PROJECTS' },
|
|
21
|
+
]),
|
|
22
|
+
);
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
create: {
|
|
26
|
+
summary: 'Create an organization',
|
|
27
|
+
usage: 'motherbase orgs create <name>',
|
|
28
|
+
async run(ctx, args) {
|
|
29
|
+
const name = requireArg(args[0], 'name');
|
|
30
|
+
const data = await api.consoleRequest(ctx, 'POST', '/orgs', { body: { name } });
|
|
31
|
+
output(ctx, data, (d, ui) => ui.success(`Created org ${d.name} (${d.id})`));
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
module.exports = {
|
|
37
|
+
summary: 'Manage organizations on the Platform Console',
|
|
38
|
+
usage: 'motherbase orgs <list|create> ...',
|
|
39
|
+
subcommands,
|
|
40
|
+
};
|
|
@@ -1,113 +1,113 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// `motherbase postgres-config get|update|delete`, Supabase's command of the
|
|
4
|
-
// same name. The one difference is where a value lands: the server picks the
|
|
5
|
-
// narrowest scope Postgres allows (a per-database override for
|
|
6
|
-
// `statement_timeout`, ALTER SYSTEM for `shared_buffers`), and `--scope`
|
|
7
|
-
// overrides that. Motherbase never restarts Postgres itself, so `--no-restart`
|
|
8
|
-
// is accepted for script compatibility and a restart-only change is reported.
|
|
9
|
-
|
|
10
|
-
const api = require('../api');
|
|
11
|
-
const { output, parseKeyValues } = require('./_util');
|
|
12
|
-
const { CliError } = require('../context');
|
|
13
|
-
|
|
14
|
-
const SCOPES = ['database', 'role', 'system'];
|
|
15
|
-
|
|
16
|
-
function scopeFlags(flags) {
|
|
17
|
-
if (flags.scope !== undefined && !SCOPES.includes(flags.scope)) {
|
|
18
|
-
throw new CliError(`--scope must be one of: ${SCOPES.join(', ')}`);
|
|
19
|
-
}
|
|
20
|
-
if (flags.scope === 'role' && !flags.role) throw new CliError('--scope role needs --role <name>');
|
|
21
|
-
return {
|
|
22
|
-
...(flags.scope ? { scope: flags.scope } : {}),
|
|
23
|
-
...(flags.role ? { role: String(flags.role) } : {}),
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// What `get` prints: one row per setting that has an override, like Supabase,
|
|
28
|
-
// or every curated setting with --all.
|
|
29
|
-
function overrideRows(config, all) {
|
|
30
|
-
return config.settings.flatMap(setting => {
|
|
31
|
-
if (!setting.overrides.length) {
|
|
32
|
-
return all ? [{ name: setting.name, value: setting.value, scope: '', role: '', restart: setting.context === 'postmaster' }] : [];
|
|
33
|
-
}
|
|
34
|
-
return setting.overrides.map(o => ({
|
|
35
|
-
name: setting.name,
|
|
36
|
-
value: o.value,
|
|
37
|
-
scope: o.scope,
|
|
38
|
-
role: o.role || '',
|
|
39
|
-
restart: setting.context === 'postmaster',
|
|
40
|
-
}));
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function reportChange(result, ui) {
|
|
45
|
-
for (const change of result.applied) {
|
|
46
|
-
ui.success(`${change.name}: ${change.scope}${change.role ? ` (${change.role})` : ''}`);
|
|
47
|
-
}
|
|
48
|
-
if (result.restartRequired.length) {
|
|
49
|
-
ui.warn(`Restart Postgres to apply: ${result.restartRequired.join(', ')}`);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const subcommands = {
|
|
54
|
-
get: {
|
|
55
|
-
summary: 'Show Postgres config overrides (--all for every managed setting)',
|
|
56
|
-
usage: 'motherbase postgres-config get [--all]',
|
|
57
|
-
async run(ctx, args, flags) {
|
|
58
|
-
const data = await api.adminRequest(ctx, 'GET', '/database/config');
|
|
59
|
-
output(ctx, data, (d, ui) => {
|
|
60
|
-
ui.table(overrideRows(d, Boolean(flags.all)), [
|
|
61
|
-
{ key: 'name', label: 'PARAMETER' },
|
|
62
|
-
{ key: 'value', label: 'VALUE' },
|
|
63
|
-
{ key: 'scope', label: 'SCOPE' },
|
|
64
|
-
{ key: 'role', label: 'ROLE' },
|
|
65
|
-
{ key: 'restart', label: 'NEEDS RESTART' },
|
|
66
|
-
]);
|
|
67
|
-
if (!d.systemOverridesReadable) ui.warn('System-level overrides could not be read with this connection.');
|
|
68
|
-
});
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
update: {
|
|
72
|
-
summary: 'Set Postgres config values',
|
|
73
|
-
usage: 'motherbase postgres-config update --config key=value [--config ...] [--scope database|role|system] [--role r] [--replace-existing-overrides]',
|
|
74
|
-
async run(ctx, args, flags) {
|
|
75
|
-
const values = parseKeyValues([].concat(flags.config || []).filter(Boolean), { coerce: false });
|
|
76
|
-
if (!Object.keys(values).length) throw new CliError('Provide at least one --config key=value.');
|
|
77
|
-
const target = scopeFlags(flags);
|
|
78
|
-
const changes = Object.entries(values).map(([name, value]) => ({ name, value, ...target }));
|
|
79
|
-
|
|
80
|
-
// Supabase's flag: anything overridden before and not named now goes back to its default.
|
|
81
|
-
if (flags['replace-existing-overrides']) {
|
|
82
|
-
const current = await api.adminRequest(ctx, 'GET', '/database/config');
|
|
83
|
-
for (const setting of current.settings) {
|
|
84
|
-
if (setting.name in values) continue;
|
|
85
|
-
for (const o of setting.overrides) {
|
|
86
|
-
changes.push({ name: setting.name, value: null, scope: o.scope, ...(o.role ? { role: o.role } : {}) });
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const data = await api.adminRequest(ctx, 'PATCH', '/database/config', { body: { changes } });
|
|
92
|
-
output(ctx, data, (d, ui) => reportChange(d, ui));
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
delete: {
|
|
96
|
-
summary: 'Remove Postgres config overrides, restoring the defaults',
|
|
97
|
-
usage: 'motherbase postgres-config delete --config key1,key2 [--scope database|role|system] [--role r]',
|
|
98
|
-
async run(ctx, args, flags) {
|
|
99
|
-
const names = [].concat(flags.config || []).flatMap(v => String(v).split(',')).map(s => s.trim()).filter(Boolean);
|
|
100
|
-
if (!names.length) throw new CliError('Provide the settings to reset with --config key1,key2.');
|
|
101
|
-
const target = scopeFlags(flags);
|
|
102
|
-
const changes = names.map(name => ({ name, value: null, ...target }));
|
|
103
|
-
const data = await api.adminRequest(ctx, 'PATCH', '/database/config', { body: { changes } });
|
|
104
|
-
output(ctx, data, (d, ui) => reportChange(d, ui));
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
module.exports = {
|
|
110
|
-
summary: 'Postgres configuration (statement_timeout, work_mem, max_connections, ...)',
|
|
111
|
-
usage: 'motherbase postgres-config <get|update|delete> ...',
|
|
112
|
-
subcommands,
|
|
113
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// `motherbase postgres-config get|update|delete`, Supabase's command of the
|
|
4
|
+
// same name. The one difference is where a value lands: the server picks the
|
|
5
|
+
// narrowest scope Postgres allows (a per-database override for
|
|
6
|
+
// `statement_timeout`, ALTER SYSTEM for `shared_buffers`), and `--scope`
|
|
7
|
+
// overrides that. Motherbase never restarts Postgres itself, so `--no-restart`
|
|
8
|
+
// is accepted for script compatibility and a restart-only change is reported.
|
|
9
|
+
|
|
10
|
+
const api = require('../api');
|
|
11
|
+
const { output, parseKeyValues } = require('./_util');
|
|
12
|
+
const { CliError } = require('../context');
|
|
13
|
+
|
|
14
|
+
const SCOPES = ['database', 'role', 'system'];
|
|
15
|
+
|
|
16
|
+
function scopeFlags(flags) {
|
|
17
|
+
if (flags.scope !== undefined && !SCOPES.includes(flags.scope)) {
|
|
18
|
+
throw new CliError(`--scope must be one of: ${SCOPES.join(', ')}`);
|
|
19
|
+
}
|
|
20
|
+
if (flags.scope === 'role' && !flags.role) throw new CliError('--scope role needs --role <name>');
|
|
21
|
+
return {
|
|
22
|
+
...(flags.scope ? { scope: flags.scope } : {}),
|
|
23
|
+
...(flags.role ? { role: String(flags.role) } : {}),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// What `get` prints: one row per setting that has an override, like Supabase,
|
|
28
|
+
// or every curated setting with --all.
|
|
29
|
+
function overrideRows(config, all) {
|
|
30
|
+
return config.settings.flatMap(setting => {
|
|
31
|
+
if (!setting.overrides.length) {
|
|
32
|
+
return all ? [{ name: setting.name, value: setting.value, scope: '', role: '', restart: setting.context === 'postmaster' }] : [];
|
|
33
|
+
}
|
|
34
|
+
return setting.overrides.map(o => ({
|
|
35
|
+
name: setting.name,
|
|
36
|
+
value: o.value,
|
|
37
|
+
scope: o.scope,
|
|
38
|
+
role: o.role || '',
|
|
39
|
+
restart: setting.context === 'postmaster',
|
|
40
|
+
}));
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function reportChange(result, ui) {
|
|
45
|
+
for (const change of result.applied) {
|
|
46
|
+
ui.success(`${change.name}: ${change.scope}${change.role ? ` (${change.role})` : ''}`);
|
|
47
|
+
}
|
|
48
|
+
if (result.restartRequired.length) {
|
|
49
|
+
ui.warn(`Restart Postgres to apply: ${result.restartRequired.join(', ')}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const subcommands = {
|
|
54
|
+
get: {
|
|
55
|
+
summary: 'Show Postgres config overrides (--all for every managed setting)',
|
|
56
|
+
usage: 'motherbase postgres-config get [--all]',
|
|
57
|
+
async run(ctx, args, flags) {
|
|
58
|
+
const data = await api.adminRequest(ctx, 'GET', '/database/config');
|
|
59
|
+
output(ctx, data, (d, ui) => {
|
|
60
|
+
ui.table(overrideRows(d, Boolean(flags.all)), [
|
|
61
|
+
{ key: 'name', label: 'PARAMETER' },
|
|
62
|
+
{ key: 'value', label: 'VALUE' },
|
|
63
|
+
{ key: 'scope', label: 'SCOPE' },
|
|
64
|
+
{ key: 'role', label: 'ROLE' },
|
|
65
|
+
{ key: 'restart', label: 'NEEDS RESTART' },
|
|
66
|
+
]);
|
|
67
|
+
if (!d.systemOverridesReadable) ui.warn('System-level overrides could not be read with this connection.');
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
update: {
|
|
72
|
+
summary: 'Set Postgres config values',
|
|
73
|
+
usage: 'motherbase postgres-config update --config key=value [--config ...] [--scope database|role|system] [--role r] [--replace-existing-overrides]',
|
|
74
|
+
async run(ctx, args, flags) {
|
|
75
|
+
const values = parseKeyValues([].concat(flags.config || []).filter(Boolean), { coerce: false });
|
|
76
|
+
if (!Object.keys(values).length) throw new CliError('Provide at least one --config key=value.');
|
|
77
|
+
const target = scopeFlags(flags);
|
|
78
|
+
const changes = Object.entries(values).map(([name, value]) => ({ name, value, ...target }));
|
|
79
|
+
|
|
80
|
+
// Supabase's flag: anything overridden before and not named now goes back to its default.
|
|
81
|
+
if (flags['replace-existing-overrides']) {
|
|
82
|
+
const current = await api.adminRequest(ctx, 'GET', '/database/config');
|
|
83
|
+
for (const setting of current.settings) {
|
|
84
|
+
if (setting.name in values) continue;
|
|
85
|
+
for (const o of setting.overrides) {
|
|
86
|
+
changes.push({ name: setting.name, value: null, scope: o.scope, ...(o.role ? { role: o.role } : {}) });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const data = await api.adminRequest(ctx, 'PATCH', '/database/config', { body: { changes } });
|
|
92
|
+
output(ctx, data, (d, ui) => reportChange(d, ui));
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
delete: {
|
|
96
|
+
summary: 'Remove Postgres config overrides, restoring the defaults',
|
|
97
|
+
usage: 'motherbase postgres-config delete --config key1,key2 [--scope database|role|system] [--role r]',
|
|
98
|
+
async run(ctx, args, flags) {
|
|
99
|
+
const names = [].concat(flags.config || []).flatMap(v => String(v).split(',')).map(s => s.trim()).filter(Boolean);
|
|
100
|
+
if (!names.length) throw new CliError('Provide the settings to reset with --config key1,key2.');
|
|
101
|
+
const target = scopeFlags(flags);
|
|
102
|
+
const changes = names.map(name => ({ name, value: null, ...target }));
|
|
103
|
+
const data = await api.adminRequest(ctx, 'PATCH', '/database/config', { body: { changes } });
|
|
104
|
+
output(ctx, data, (d, ui) => reportChange(d, ui));
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
module.exports = {
|
|
110
|
+
summary: 'Postgres configuration (statement_timeout, work_mem, max_connections, ...)',
|
|
111
|
+
usage: 'motherbase postgres-config <get|update|delete> ...',
|
|
112
|
+
subcommands,
|
|
113
|
+
};
|
package/src/commands/projects.js
CHANGED
|
@@ -1,130 +1,131 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const api = require('../api');
|
|
4
|
-
const keys = require('./keys');
|
|
5
|
-
const { output, alias, requireArg } = require('./_util');
|
|
6
|
-
const { CliError } = require('../context');
|
|
7
|
-
const { outputFormat } = require('../format');
|
|
8
|
-
|
|
9
|
-
const LIFECYCLE = ['start', 'stop', 'restart', 'update', 'rebuild'];
|
|
10
|
-
|
|
11
|
-
function lifecycleCmd(action) {
|
|
12
|
-
return {
|
|
13
|
-
summary: `${action} a project`,
|
|
14
|
-
usage: `motherbase projects ${action} <id>`,
|
|
15
|
-
async run(ctx, args) {
|
|
16
|
-
const id = requireArg(args[0], 'id');
|
|
17
|
-
const data = await api.consoleRequest(ctx, 'POST', `/projects/${id}/${action}`);
|
|
18
|
-
output(ctx, data, (d, ui) => ui.success(`${action} → ${id}`));
|
|
19
|
-
},
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const subcommands = {
|
|
24
|
-
list: {
|
|
25
|
-
summary: 'List all projects on the console',
|
|
26
|
-
usage: 'motherbase projects list',
|
|
27
|
-
async run(ctx) {
|
|
28
|
-
const data = await api.consoleRequest(ctx, 'GET', '/projects');
|
|
29
|
-
output(ctx, data, (rows, ui) =>
|
|
30
|
-
ui.table(Array.isArray(rows) ? rows : [], [
|
|
31
|
-
{ key: 'name', label: 'NAME' },
|
|
32
|
-
{ key: 'status', label: 'STATUS' },
|
|
33
|
-
{ key: 'domain', label: 'DOMAIN' },
|
|
34
|
-
]),
|
|
35
|
-
);
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
create: {
|
|
39
|
-
summary: 'Create a new project',
|
|
40
|
-
usage: 'motherbase projects create <name> [--domain <domain>]',
|
|
41
|
-
async run(ctx, args, flags) {
|
|
42
|
-
const name = requireArg(args[0], 'name');
|
|
43
|
-
const body = { name };
|
|
44
|
-
if (flags.domain) body.domain = flags.domain;
|
|
45
|
-
const data = await api.consoleRequest(ctx, 'POST', '/projects', { body });
|
|
46
|
-
output(ctx, data, (d, ui) => ui.success(`Created project ${name}`));
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
delete: {
|
|
50
|
-
summary: 'Delete a project (requires --confirm <name>)',
|
|
51
|
-
usage: 'motherbase projects delete <id> --confirm <name>',
|
|
52
|
-
async run(ctx, args, flags) {
|
|
53
|
-
const id = requireArg(args[0], 'id');
|
|
54
|
-
const confirm = flags.confirm;
|
|
55
|
-
if (confirm !== id) {
|
|
56
|
-
throw new CliError(`Refusing to delete. Pass --confirm ${id} to proceed.`);
|
|
57
|
-
}
|
|
58
|
-
const data = await api.consoleRequest(ctx, 'DELETE', `/projects/${id}`, { body: { confirm } });
|
|
59
|
-
output(ctx, data, (d, ui) => ui.success(`Deleted project ${id}`));
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
logs: {
|
|
63
|
-
summary: 'Show recent logs for a project',
|
|
64
|
-
usage: 'motherbase projects logs <id> [-f]',
|
|
65
|
-
async run(ctx, args, flags) {
|
|
66
|
-
const id = requireArg(args[0], 'id');
|
|
67
|
-
const fetchOnce = () => api.consoleRequest(ctx, 'GET', `/projects/${id}/logs`);
|
|
68
|
-
const data = await fetchOnce();
|
|
69
|
-
output(ctx, data, (d, ui) => {
|
|
70
|
-
if (d && d.stdout) ui.print(d.stdout);
|
|
71
|
-
if (d && d.stderr) ui.printErr(d.stderr);
|
|
72
|
-
});
|
|
73
|
-
if (flags.f && outputFormat(ctx.flags) === 'pretty') {
|
|
74
|
-
// The console logs endpoint returns a snapshot; approximate -f by polling.
|
|
75
|
-
const ui = require('../ui');
|
|
76
|
-
ui.warn('(-f) polling every 3s; Ctrl-C to stop');
|
|
77
|
-
setInterval(async () => {
|
|
78
|
-
try {
|
|
79
|
-
const next = await fetchOnce();
|
|
80
|
-
if (next && next.stdout) process.stdout.write(next.stdout);
|
|
81
|
-
} catch (e) { ui.warn(e.message); }
|
|
82
|
-
}, 3000);
|
|
83
|
-
await new Promise(() => {});
|
|
84
|
-
}
|
|
85
|
-
},
|
|
86
|
-
},
|
|
87
|
-
info: {
|
|
88
|
-
summary: 'Show project details and DNS checks',
|
|
89
|
-
usage: 'motherbase projects info <id>',
|
|
90
|
-
async run(ctx, args) {
|
|
91
|
-
const id = requireArg(args[0], 'id');
|
|
92
|
-
const list = await api.consoleRequest(ctx, 'GET', '/projects');
|
|
93
|
-
const project = (Array.isArray(list) ? list : []).find(p => p.name === id || p.id === id) || { name: id };
|
|
94
|
-
let dns = null;
|
|
95
|
-
try { dns = await api.consoleRequest(ctx, 'GET', `/projects/${id}/dns`); } catch { /* optional */ }
|
|
96
|
-
const data = { ...project, dns: dns && dns.checks };
|
|
97
|
-
output(ctx, data, (d, ui) => ui.keyValue(d));
|
|
98
|
-
},
|
|
99
|
-
},
|
|
100
|
-
limits: {
|
|
101
|
-
summary: 'Get or set per-project resource limits',
|
|
102
|
-
usage: 'motherbase projects limits <id> [--api-cpus n --api-mem mb --db-cpus n --db-mem mb --minio-cpus n --minio-mem mb]',
|
|
103
|
-
async run(ctx, args, flags) {
|
|
104
|
-
const id = requireArg(args[0], 'id');
|
|
105
|
-
const map = {
|
|
106
|
-
'api-cpus': 'apiCpus', 'api-mem': 'apiMemMb',
|
|
107
|
-
'db-cpus': 'dbCpus', 'db-mem': 'dbMemMb',
|
|
108
|
-
'minio-cpus': 'minioCpus', 'minio-mem': 'minioMemMb',
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const api = require('../api');
|
|
4
|
+
const keys = require('./keys');
|
|
5
|
+
const { output, alias, requireArg } = require('./_util');
|
|
6
|
+
const { CliError } = require('../context');
|
|
7
|
+
const { outputFormat } = require('../format');
|
|
8
|
+
|
|
9
|
+
const LIFECYCLE = ['start', 'stop', 'restart', 'update', 'rebuild'];
|
|
10
|
+
|
|
11
|
+
function lifecycleCmd(action) {
|
|
12
|
+
return {
|
|
13
|
+
summary: `${action} a project`,
|
|
14
|
+
usage: `motherbase projects ${action} <id>`,
|
|
15
|
+
async run(ctx, args) {
|
|
16
|
+
const id = requireArg(args[0], 'id');
|
|
17
|
+
const data = await api.consoleRequest(ctx, 'POST', `/projects/${id}/${action}`);
|
|
18
|
+
output(ctx, data, (d, ui) => ui.success(`${action} → ${id}`));
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const subcommands = {
|
|
24
|
+
list: {
|
|
25
|
+
summary: 'List all projects on the console',
|
|
26
|
+
usage: 'motherbase projects list',
|
|
27
|
+
async run(ctx) {
|
|
28
|
+
const data = await api.consoleRequest(ctx, 'GET', '/projects');
|
|
29
|
+
output(ctx, data, (rows, ui) =>
|
|
30
|
+
ui.table(Array.isArray(rows) ? rows : [], [
|
|
31
|
+
{ key: 'name', label: 'NAME' },
|
|
32
|
+
{ key: 'status', label: 'STATUS' },
|
|
33
|
+
{ key: 'domain', label: 'DOMAIN' },
|
|
34
|
+
]),
|
|
35
|
+
);
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
create: {
|
|
39
|
+
summary: 'Create a new project',
|
|
40
|
+
usage: 'motherbase projects create <name> [--domain <domain>]',
|
|
41
|
+
async run(ctx, args, flags) {
|
|
42
|
+
const name = requireArg(args[0], 'name');
|
|
43
|
+
const body = { name };
|
|
44
|
+
if (flags.domain) body.domain = flags.domain;
|
|
45
|
+
const data = await api.consoleRequest(ctx, 'POST', '/projects', { body });
|
|
46
|
+
output(ctx, data, (d, ui) => ui.success(`Created project ${name}`));
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
delete: {
|
|
50
|
+
summary: 'Delete a project (requires --confirm <name>)',
|
|
51
|
+
usage: 'motherbase projects delete <id> --confirm <name>',
|
|
52
|
+
async run(ctx, args, flags) {
|
|
53
|
+
const id = requireArg(args[0], 'id');
|
|
54
|
+
const confirm = flags.confirm;
|
|
55
|
+
if (confirm !== id) {
|
|
56
|
+
throw new CliError(`Refusing to delete. Pass --confirm ${id} to proceed.`);
|
|
57
|
+
}
|
|
58
|
+
const data = await api.consoleRequest(ctx, 'DELETE', `/projects/${id}`, { body: { confirm } });
|
|
59
|
+
output(ctx, data, (d, ui) => ui.success(`Deleted project ${id}`));
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
logs: {
|
|
63
|
+
summary: 'Show recent logs for a project',
|
|
64
|
+
usage: 'motherbase projects logs <id> [-f]',
|
|
65
|
+
async run(ctx, args, flags) {
|
|
66
|
+
const id = requireArg(args[0], 'id');
|
|
67
|
+
const fetchOnce = () => api.consoleRequest(ctx, 'GET', `/projects/${id}/logs`);
|
|
68
|
+
const data = await fetchOnce();
|
|
69
|
+
output(ctx, data, (d, ui) => {
|
|
70
|
+
if (d && d.stdout) ui.print(d.stdout);
|
|
71
|
+
if (d && d.stderr) ui.printErr(d.stderr);
|
|
72
|
+
});
|
|
73
|
+
if (flags.f && outputFormat(ctx.flags) === 'pretty') {
|
|
74
|
+
// The console logs endpoint returns a snapshot; approximate -f by polling.
|
|
75
|
+
const ui = require('../ui');
|
|
76
|
+
ui.warn('(-f) polling every 3s; Ctrl-C to stop');
|
|
77
|
+
setInterval(async () => {
|
|
78
|
+
try {
|
|
79
|
+
const next = await fetchOnce();
|
|
80
|
+
if (next && next.stdout) process.stdout.write(next.stdout);
|
|
81
|
+
} catch (e) { ui.warn(e.message); }
|
|
82
|
+
}, 3000);
|
|
83
|
+
await new Promise(() => {});
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
info: {
|
|
88
|
+
summary: 'Show project details and DNS checks',
|
|
89
|
+
usage: 'motherbase projects info <id>',
|
|
90
|
+
async run(ctx, args) {
|
|
91
|
+
const id = requireArg(args[0], 'id');
|
|
92
|
+
const list = await api.consoleRequest(ctx, 'GET', '/projects');
|
|
93
|
+
const project = (Array.isArray(list) ? list : []).find(p => p.name === id || p.id === id) || { name: id };
|
|
94
|
+
let dns = null;
|
|
95
|
+
try { dns = await api.consoleRequest(ctx, 'GET', `/projects/${id}/dns`); } catch { /* optional */ }
|
|
96
|
+
const data = { ...project, dns: dns && dns.checks };
|
|
97
|
+
output(ctx, data, (d, ui) => ui.keyValue(d));
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
limits: {
|
|
101
|
+
summary: 'Get or set per-project resource limits',
|
|
102
|
+
usage: 'motherbase projects limits <id> [--api-cpus n --api-mem mb --db-cpus n --db-mem mb --minio-cpus n --minio-mem mb --edge-cpus n --edge-mem mb]',
|
|
103
|
+
async run(ctx, args, flags) {
|
|
104
|
+
const id = requireArg(args[0], 'id');
|
|
105
|
+
const map = {
|
|
106
|
+
'api-cpus': 'apiCpus', 'api-mem': 'apiMemMb',
|
|
107
|
+
'db-cpus': 'dbCpus', 'db-mem': 'dbMemMb',
|
|
108
|
+
'minio-cpus': 'minioCpus', 'minio-mem': 'minioMemMb',
|
|
109
|
+
'edge-cpus': 'edgeCpus', 'edge-mem': 'edgeMemMb',
|
|
110
|
+
};
|
|
111
|
+
const body = {};
|
|
112
|
+
for (const [flag, key] of Object.entries(map)) {
|
|
113
|
+
if (flags[flag] !== undefined) body[key] = Number(flags[flag]);
|
|
114
|
+
}
|
|
115
|
+
const method = Object.keys(body).length ? 'PATCH' : 'GET';
|
|
116
|
+
const data = await api.consoleRequest(ctx, method, `/projects/${id}/limits`, method === 'PATCH' ? { body } : {});
|
|
117
|
+
output(ctx, data, (d, ui) => ui.keyValue(d || {}));
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
for (const action of LIFECYCLE) subcommands[action] = lifecycleCmd(action);
|
|
123
|
+
|
|
124
|
+
// Supabase's `projects api-keys`; the keys themselves live under `keys`.
|
|
125
|
+
subcommands['api-keys'] = alias(keys.subcommands.list, 'motherbase projects api-keys [--project-ref <id>]');
|
|
126
|
+
|
|
127
|
+
module.exports = {
|
|
128
|
+
summary: 'Manage projects on the Platform Console',
|
|
129
|
+
usage: 'motherbase projects <list|create|start|stop|restart|update|rebuild|delete|logs|info|limits|api-keys> ...',
|
|
130
|
+
subcommands,
|
|
131
|
+
};
|