@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.
- package/LICENSE +21 -21
- package/README.md +248 -247
- package/bin/.motherbase/config.json +5 -0
- package/bin/motherbase.js +10 -10
- package/package.json +1 -1
- 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.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 +85 -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 +100 -23
- 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/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/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/network.js
CHANGED
|
@@ -1,83 +1,83 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Who may open a direct Postgres connection (Supabase's `network-restrictions`
|
|
4
|
-
// and `ssl-enforcement`). Both are project admin routes, so they work through
|
|
5
|
-
// the Console proxy and in direct mode alike.
|
|
6
|
-
|
|
7
|
-
const api = require('../api');
|
|
8
|
-
const { output } = require('./_util');
|
|
9
|
-
const { CliError } = require('../context');
|
|
10
|
-
|
|
11
|
-
function renderRestrictions(d, ui) {
|
|
12
|
-
ui.keyValue({
|
|
13
|
-
'IPv4 allowed': d.dbAllowedCidrs.join(', ') || '(none)',
|
|
14
|
-
'IPv6 allowed': d.dbAllowedCidrsV6.join(', ') || '(none)',
|
|
15
|
-
status: d.status,
|
|
16
|
-
exposure: d.exposure,
|
|
17
|
-
});
|
|
18
|
-
if (d.status !== 'applied') ui.warn('Stored only: this API does not manage its database access (not a Console stack).');
|
|
19
|
-
if (d.exposure !== 'public') ui.info('The database port is not public, so only the stack and SSH tunnels reach it today.');
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const networkRestrictions = {
|
|
23
|
-
summary: 'IP ranges allowed to connect to the database directly',
|
|
24
|
-
usage: 'motherbase network-restrictions <get|update> ...',
|
|
25
|
-
subcommands: {
|
|
26
|
-
get: {
|
|
27
|
-
summary: 'Show the allowed ranges',
|
|
28
|
-
usage: 'motherbase network-restrictions get',
|
|
29
|
-
async run(ctx) {
|
|
30
|
-
output(ctx, await api.adminRequest(ctx, 'GET', '/network-restrictions'), renderRestrictions);
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
update: {
|
|
34
|
-
summary: 'Replace the allowed ranges (repeat --db-allow-cidr; IPv4 and IPv6 are told apart)',
|
|
35
|
-
usage: 'motherbase network-restrictions update --db-allow-cidr <cidr> [--db-allow-cidr <cidr> ...]',
|
|
36
|
-
async run(ctx, _args, flags) {
|
|
37
|
-
const cidrs = [].concat(flags['db-allow-cidr'] ?? []).map(String);
|
|
38
|
-
if (!cidrs.length) {
|
|
39
|
-
throw new CliError('Pass at least one --db-allow-cidr (0.0.0.0/0 and ::/0 allow everyone).');
|
|
40
|
-
}
|
|
41
|
-
const body = {
|
|
42
|
-
dbAllowedCidrs: cidrs.filter(c => !c.includes(':')),
|
|
43
|
-
dbAllowedCidrsV6: cidrs.filter(c => c.includes(':')),
|
|
44
|
-
};
|
|
45
|
-
output(ctx, await api.adminRequest(ctx, 'PUT', '/network-restrictions', { body }), renderRestrictions);
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
function renderSsl(d, ui) {
|
|
52
|
-
ui.keyValue({ enforced: d.enforced, 'TLS active': d.sslActive, status: d.status });
|
|
53
|
-
if (d.enforced) ui.info('External clients must connect with sslmode=require.');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const sslEnforcement = {
|
|
57
|
-
summary: 'Require TLS for direct database connections',
|
|
58
|
-
usage: 'motherbase ssl-enforcement <get|update> ...',
|
|
59
|
-
subcommands: {
|
|
60
|
-
get: {
|
|
61
|
-
summary: 'Show whether TLS is enforced',
|
|
62
|
-
usage: 'motherbase ssl-enforcement get',
|
|
63
|
-
async run(ctx) {
|
|
64
|
-
output(ctx, await api.adminRequest(ctx, 'GET', '/ssl-enforcement'), renderSsl);
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
update: {
|
|
68
|
-
summary: 'Turn enforcement on or off',
|
|
69
|
-
usage: 'motherbase ssl-enforcement update --enable-db-ssl-enforcement | --disable-db-ssl-enforcement',
|
|
70
|
-
async run(ctx, _args, flags) {
|
|
71
|
-
const enable = Boolean(flags['enable-db-ssl-enforcement']);
|
|
72
|
-
const disable = Boolean(flags['disable-db-ssl-enforcement']);
|
|
73
|
-
if (enable === disable) {
|
|
74
|
-
throw new CliError('Pass exactly one of --enable-db-ssl-enforcement or --disable-db-ssl-enforcement.');
|
|
75
|
-
}
|
|
76
|
-
const data = await api.adminRequest(ctx, 'PUT', '/ssl-enforcement', { body: { enforced: enable } });
|
|
77
|
-
output(ctx, data, renderSsl);
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
module.exports = { networkRestrictions, sslEnforcement };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Who may open a direct Postgres connection (Supabase's `network-restrictions`
|
|
4
|
+
// and `ssl-enforcement`). Both are project admin routes, so they work through
|
|
5
|
+
// the Console proxy and in direct mode alike.
|
|
6
|
+
|
|
7
|
+
const api = require('../api');
|
|
8
|
+
const { output } = require('./_util');
|
|
9
|
+
const { CliError } = require('../context');
|
|
10
|
+
|
|
11
|
+
function renderRestrictions(d, ui) {
|
|
12
|
+
ui.keyValue({
|
|
13
|
+
'IPv4 allowed': d.dbAllowedCidrs.join(', ') || '(none)',
|
|
14
|
+
'IPv6 allowed': d.dbAllowedCidrsV6.join(', ') || '(none)',
|
|
15
|
+
status: d.status,
|
|
16
|
+
exposure: d.exposure,
|
|
17
|
+
});
|
|
18
|
+
if (d.status !== 'applied') ui.warn('Stored only: this API does not manage its database access (not a Console stack).');
|
|
19
|
+
if (d.exposure !== 'public') ui.info('The database port is not public, so only the stack and SSH tunnels reach it today.');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const networkRestrictions = {
|
|
23
|
+
summary: 'IP ranges allowed to connect to the database directly',
|
|
24
|
+
usage: 'motherbase network-restrictions <get|update> ...',
|
|
25
|
+
subcommands: {
|
|
26
|
+
get: {
|
|
27
|
+
summary: 'Show the allowed ranges',
|
|
28
|
+
usage: 'motherbase network-restrictions get',
|
|
29
|
+
async run(ctx) {
|
|
30
|
+
output(ctx, await api.adminRequest(ctx, 'GET', '/network-restrictions'), renderRestrictions);
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
update: {
|
|
34
|
+
summary: 'Replace the allowed ranges (repeat --db-allow-cidr; IPv4 and IPv6 are told apart)',
|
|
35
|
+
usage: 'motherbase network-restrictions update --db-allow-cidr <cidr> [--db-allow-cidr <cidr> ...]',
|
|
36
|
+
async run(ctx, _args, flags) {
|
|
37
|
+
const cidrs = [].concat(flags['db-allow-cidr'] ?? []).map(String);
|
|
38
|
+
if (!cidrs.length) {
|
|
39
|
+
throw new CliError('Pass at least one --db-allow-cidr (0.0.0.0/0 and ::/0 allow everyone).');
|
|
40
|
+
}
|
|
41
|
+
const body = {
|
|
42
|
+
dbAllowedCidrs: cidrs.filter(c => !c.includes(':')),
|
|
43
|
+
dbAllowedCidrsV6: cidrs.filter(c => c.includes(':')),
|
|
44
|
+
};
|
|
45
|
+
output(ctx, await api.adminRequest(ctx, 'PUT', '/network-restrictions', { body }), renderRestrictions);
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
function renderSsl(d, ui) {
|
|
52
|
+
ui.keyValue({ enforced: d.enforced, 'TLS active': d.sslActive, status: d.status });
|
|
53
|
+
if (d.enforced) ui.info('External clients must connect with sslmode=require.');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const sslEnforcement = {
|
|
57
|
+
summary: 'Require TLS for direct database connections',
|
|
58
|
+
usage: 'motherbase ssl-enforcement <get|update> ...',
|
|
59
|
+
subcommands: {
|
|
60
|
+
get: {
|
|
61
|
+
summary: 'Show whether TLS is enforced',
|
|
62
|
+
usage: 'motherbase ssl-enforcement get',
|
|
63
|
+
async run(ctx) {
|
|
64
|
+
output(ctx, await api.adminRequest(ctx, 'GET', '/ssl-enforcement'), renderSsl);
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
update: {
|
|
68
|
+
summary: 'Turn enforcement on or off',
|
|
69
|
+
usage: 'motherbase ssl-enforcement update --enable-db-ssl-enforcement | --disable-db-ssl-enforcement',
|
|
70
|
+
async run(ctx, _args, flags) {
|
|
71
|
+
const enable = Boolean(flags['enable-db-ssl-enforcement']);
|
|
72
|
+
const disable = Boolean(flags['disable-db-ssl-enforcement']);
|
|
73
|
+
if (enable === disable) {
|
|
74
|
+
throw new CliError('Pass exactly one of --enable-db-ssl-enforcement or --disable-db-ssl-enforcement.');
|
|
75
|
+
}
|
|
76
|
+
const data = await api.adminRequest(ctx, 'PUT', '/ssl-enforcement', { body: { enforced: enable } });
|
|
77
|
+
output(ctx, data, renderSsl);
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
module.exports = { networkRestrictions, sslEnforcement };
|
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
|
+
};
|