@motherbase/cli 0.2.5 → 0.2.6
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 -0
- package/README.md +247 -247
- package/bin/motherbase.js +10 -10
- package/package.json +35 -26
- package/src/api.js +233 -241
- package/src/commands/_util.js +106 -93
- 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 +326 -326
- package/src/commands/db.js +103 -103
- package/src/commands/domains.js +124 -124
- package/src/commands/functions.js +304 -297
- package/src/commands/gen.js +172 -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 +294 -273
- package/src/commands/login.js +39 -39
- package/src/commands/logout.js +16 -16
- package/src/commands/mcp.js +61 -61
- package/src/commands/migrate.js +207 -179
- package/src/commands/migration.js +108 -108
- 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 +130 -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 +142 -142
- 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 -102
- package/src/docs.js +68 -0
- 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 +224 -194
- package/src/mcp/platform-tools.js +233 -233
- package/src/mcp/protocol.js +152 -149
- package/src/mcp/storage-tools.js +93 -93
- package/src/mcp/tools.js +478 -478
- package/src/migration/commands.js +375 -375
- package/src/migration/files.js +245 -245
- package/src/prompt.js +91 -91
- package/src/storage/remote.js +62 -62
- package/src/ui.js +111 -111
package/src/commands/wrappers.js
CHANGED
|
@@ -1,150 +1,150 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const api = require('../api');
|
|
4
|
-
const { output, requireArg, parseKeyValues } = require('./_util');
|
|
5
|
-
const { CliError } = require('../context');
|
|
6
|
-
|
|
7
|
-
const enc = encodeURIComponent;
|
|
8
|
-
|
|
9
|
-
// `--option host=db.example.com --option port=5432`, repeatable.
|
|
10
|
-
function options(flags) {
|
|
11
|
-
return parseKeyValues([].concat(flags.option || []), { coerce: false });
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// `schema.name` -> [schema, name]
|
|
15
|
-
function qualified(ref) {
|
|
16
|
-
const [schema, name, ...rest] = requireArg(ref, 'schema.table').split('.');
|
|
17
|
-
if (!schema || !name || rest.length) throw new CliError('expected <schema>.<table>');
|
|
18
|
-
return [schema, name];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// `--column id:integer --column label:text`, repeatable.
|
|
22
|
-
function columns(flags) {
|
|
23
|
-
const specs = [].concat(flags.column || []);
|
|
24
|
-
if (!specs.length) throw new CliError('at least one --column <name>:<type> is required');
|
|
25
|
-
return specs.map(spec => {
|
|
26
|
-
const idx = String(spec).indexOf(':');
|
|
27
|
-
if (idx <= 0) throw new CliError(`expected <name>:<type>, got "${spec}"`);
|
|
28
|
-
return { name: spec.slice(0, idx), type: spec.slice(idx + 1) };
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const subcommands = {
|
|
33
|
-
list: {
|
|
34
|
-
summary: 'List wrappers, foreign servers and foreign tables',
|
|
35
|
-
usage: 'motherbase wrappers list',
|
|
36
|
-
async run(ctx) {
|
|
37
|
-
const data = await api.adminRequest(ctx, 'GET', '/wrappers');
|
|
38
|
-
output(ctx, data, (d, ui) => {
|
|
39
|
-
ui.table((d && d.wrappers) || [], [
|
|
40
|
-
{ key: 'name', label: 'WRAPPER' },
|
|
41
|
-
{ key: 'available', label: 'AVAILABLE' },
|
|
42
|
-
{ key: 'installed', label: 'INSTALLED' },
|
|
43
|
-
]);
|
|
44
|
-
ui.table((d && d.servers) || [], [
|
|
45
|
-
{ key: 'name', label: 'SERVER' },
|
|
46
|
-
{ key: 'wrapper', label: 'WRAPPER' },
|
|
47
|
-
{ key: 'remoteUser', label: 'REMOTE USER' },
|
|
48
|
-
{ key: 'foreignTables', label: 'TABLES' },
|
|
49
|
-
]);
|
|
50
|
-
ui.table((d && d.tables) || [], [
|
|
51
|
-
{ key: 'schema', label: 'SCHEMA' },
|
|
52
|
-
{ key: 'name', label: 'TABLE' },
|
|
53
|
-
{ key: 'server', label: 'SERVER' },
|
|
54
|
-
{ key: 'columns', label: 'COLUMNS' },
|
|
55
|
-
]);
|
|
56
|
-
});
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
enable: {
|
|
60
|
-
summary: 'Install a wrapper extension',
|
|
61
|
-
usage: 'motherbase wrappers enable <postgres_fdw|file_fdw>',
|
|
62
|
-
async run(ctx, args) {
|
|
63
|
-
const wrapper = requireArg(args[0], 'wrapper');
|
|
64
|
-
const data = await api.adminRequest(ctx, 'POST', `/wrappers/${enc(wrapper)}/enable`);
|
|
65
|
-
output(ctx, data, (_d, ui) => ui.success(`Enabled ${wrapper}`));
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
'create-server': {
|
|
69
|
-
summary: 'Create a foreign server; the password goes to the Vault',
|
|
70
|
-
usage:
|
|
71
|
-
'motherbase wrappers create-server <name> --wrapper <postgres_fdw|file_fdw> [--option k=v ...] [--user <u> --password <p>]',
|
|
72
|
-
async run(ctx, args, flags) {
|
|
73
|
-
const name = requireArg(args[0], 'name');
|
|
74
|
-
const body = { name, wrapper: requireArg(flags.wrapper, '--wrapper'), options: options(flags) };
|
|
75
|
-
if (typeof flags.user === 'string') body.user = flags.user;
|
|
76
|
-
if (typeof flags.password === 'string') body.password = flags.password;
|
|
77
|
-
const data = await api.adminRequest(ctx, 'POST', '/wrappers/servers', { body });
|
|
78
|
-
output(ctx, data, (_d, ui) => ui.success(`Created foreign server ${name}`));
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
'drop-server': {
|
|
82
|
-
summary: 'Drop a foreign server',
|
|
83
|
-
usage: 'motherbase wrappers drop-server <name>',
|
|
84
|
-
async run(ctx, args) {
|
|
85
|
-
const name = requireArg(args[0], 'name');
|
|
86
|
-
const data = await api.adminRequest(ctx, 'DELETE', `/wrappers/servers/${enc(name)}`);
|
|
87
|
-
output(ctx, data, (_d, ui) => ui.success(`Dropped foreign server ${name}`));
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
credentials: {
|
|
91
|
-
summary: 'Replace the remote credentials of a foreign server',
|
|
92
|
-
usage: 'motherbase wrappers credentials <server> --user <u> --password <p>',
|
|
93
|
-
async run(ctx, args, flags) {
|
|
94
|
-
const name = requireArg(args[0], 'server');
|
|
95
|
-
if (typeof flags.user !== 'string' || typeof flags.password !== 'string') {
|
|
96
|
-
throw new CliError('--user and --password are required');
|
|
97
|
-
}
|
|
98
|
-
const body = { user: flags.user, password: flags.password };
|
|
99
|
-
const data = await api.adminRequest(ctx, 'PUT', `/wrappers/servers/${enc(name)}/credentials`, { body });
|
|
100
|
-
output(ctx, data, (_d, ui) => ui.success(`Updated credentials of ${name}`));
|
|
101
|
-
},
|
|
102
|
-
},
|
|
103
|
-
import: {
|
|
104
|
-
summary: 'Import a remote schema as foreign tables',
|
|
105
|
-
usage:
|
|
106
|
-
'motherbase wrappers import <server> --remote-schema <s> --local-schema <s> [--tables a,b]',
|
|
107
|
-
async run(ctx, args, flags) {
|
|
108
|
-
const name = requireArg(args[0], 'server');
|
|
109
|
-
const body = {
|
|
110
|
-
remoteSchema: requireArg(flags['remote-schema'], '--remote-schema'),
|
|
111
|
-
localSchema: requireArg(flags['local-schema'], '--local-schema'),
|
|
112
|
-
};
|
|
113
|
-
if (typeof flags.tables === 'string') body.tables = flags.tables.split(',').map(t => t.trim()).filter(Boolean);
|
|
114
|
-
const data = await api.adminRequest(ctx, 'POST', `/wrappers/servers/${enc(name)}/import`, { body });
|
|
115
|
-
output(ctx, data, (_d, ui) => ui.success(`Imported ${body.remoteSchema} into ${body.localSchema}`));
|
|
116
|
-
},
|
|
117
|
-
},
|
|
118
|
-
'create-table': {
|
|
119
|
-
summary: 'Create one foreign table',
|
|
120
|
-
usage:
|
|
121
|
-
'motherbase wrappers create-table <schema>.<table> --server <s> --column <name>:<type> ... [--option k=v ...]',
|
|
122
|
-
async run(ctx, args, flags) {
|
|
123
|
-
const [schema, name] = qualified(args[0]);
|
|
124
|
-
const body = {
|
|
125
|
-
server: requireArg(flags.server, '--server'),
|
|
126
|
-
schema,
|
|
127
|
-
name,
|
|
128
|
-
columns: columns(flags),
|
|
129
|
-
options: options(flags),
|
|
130
|
-
};
|
|
131
|
-
const data = await api.adminRequest(ctx, 'POST', '/wrappers/tables', { body });
|
|
132
|
-
output(ctx, data, (_d, ui) => ui.success(`Created foreign table ${schema}.${name}`));
|
|
133
|
-
},
|
|
134
|
-
},
|
|
135
|
-
'drop-table': {
|
|
136
|
-
summary: 'Drop a foreign table',
|
|
137
|
-
usage: 'motherbase wrappers drop-table <schema>.<table>',
|
|
138
|
-
async run(ctx, args) {
|
|
139
|
-
const [schema, name] = qualified(args[0]);
|
|
140
|
-
const data = await api.adminRequest(ctx, 'DELETE', `/wrappers/tables/${enc(schema)}/${enc(name)}`);
|
|
141
|
-
output(ctx, data, (_d, ui) => ui.success(`Dropped foreign table ${schema}.${name}`));
|
|
142
|
-
},
|
|
143
|
-
},
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
module.exports = {
|
|
147
|
-
summary: 'Foreign data wrappers (postgres_fdw, file_fdw)',
|
|
148
|
-
usage: 'motherbase wrappers <list|enable|create-server|drop-server|credentials|import|create-table|drop-table> ...',
|
|
149
|
-
subcommands,
|
|
150
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const api = require('../api');
|
|
4
|
+
const { output, requireArg, parseKeyValues } = require('./_util');
|
|
5
|
+
const { CliError } = require('../context');
|
|
6
|
+
|
|
7
|
+
const enc = encodeURIComponent;
|
|
8
|
+
|
|
9
|
+
// `--option host=db.example.com --option port=5432`, repeatable.
|
|
10
|
+
function options(flags) {
|
|
11
|
+
return parseKeyValues([].concat(flags.option || []), { coerce: false });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// `schema.name` -> [schema, name]
|
|
15
|
+
function qualified(ref) {
|
|
16
|
+
const [schema, name, ...rest] = requireArg(ref, 'schema.table').split('.');
|
|
17
|
+
if (!schema || !name || rest.length) throw new CliError('expected <schema>.<table>');
|
|
18
|
+
return [schema, name];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// `--column id:integer --column label:text`, repeatable.
|
|
22
|
+
function columns(flags) {
|
|
23
|
+
const specs = [].concat(flags.column || []);
|
|
24
|
+
if (!specs.length) throw new CliError('at least one --column <name>:<type> is required');
|
|
25
|
+
return specs.map(spec => {
|
|
26
|
+
const idx = String(spec).indexOf(':');
|
|
27
|
+
if (idx <= 0) throw new CliError(`expected <name>:<type>, got "${spec}"`);
|
|
28
|
+
return { name: spec.slice(0, idx), type: spec.slice(idx + 1) };
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const subcommands = {
|
|
33
|
+
list: {
|
|
34
|
+
summary: 'List wrappers, foreign servers and foreign tables',
|
|
35
|
+
usage: 'motherbase wrappers list',
|
|
36
|
+
async run(ctx) {
|
|
37
|
+
const data = await api.adminRequest(ctx, 'GET', '/wrappers');
|
|
38
|
+
output(ctx, data, (d, ui) => {
|
|
39
|
+
ui.table((d && d.wrappers) || [], [
|
|
40
|
+
{ key: 'name', label: 'WRAPPER' },
|
|
41
|
+
{ key: 'available', label: 'AVAILABLE' },
|
|
42
|
+
{ key: 'installed', label: 'INSTALLED' },
|
|
43
|
+
]);
|
|
44
|
+
ui.table((d && d.servers) || [], [
|
|
45
|
+
{ key: 'name', label: 'SERVER' },
|
|
46
|
+
{ key: 'wrapper', label: 'WRAPPER' },
|
|
47
|
+
{ key: 'remoteUser', label: 'REMOTE USER' },
|
|
48
|
+
{ key: 'foreignTables', label: 'TABLES' },
|
|
49
|
+
]);
|
|
50
|
+
ui.table((d && d.tables) || [], [
|
|
51
|
+
{ key: 'schema', label: 'SCHEMA' },
|
|
52
|
+
{ key: 'name', label: 'TABLE' },
|
|
53
|
+
{ key: 'server', label: 'SERVER' },
|
|
54
|
+
{ key: 'columns', label: 'COLUMNS' },
|
|
55
|
+
]);
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
enable: {
|
|
60
|
+
summary: 'Install a wrapper extension',
|
|
61
|
+
usage: 'motherbase wrappers enable <postgres_fdw|file_fdw>',
|
|
62
|
+
async run(ctx, args) {
|
|
63
|
+
const wrapper = requireArg(args[0], 'wrapper');
|
|
64
|
+
const data = await api.adminRequest(ctx, 'POST', `/wrappers/${enc(wrapper)}/enable`);
|
|
65
|
+
output(ctx, data, (_d, ui) => ui.success(`Enabled ${wrapper}`));
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
'create-server': {
|
|
69
|
+
summary: 'Create a foreign server; the password goes to the Vault',
|
|
70
|
+
usage:
|
|
71
|
+
'motherbase wrappers create-server <name> --wrapper <postgres_fdw|file_fdw> [--option k=v ...] [--user <u> --password <p>]',
|
|
72
|
+
async run(ctx, args, flags) {
|
|
73
|
+
const name = requireArg(args[0], 'name');
|
|
74
|
+
const body = { name, wrapper: requireArg(flags.wrapper, '--wrapper'), options: options(flags) };
|
|
75
|
+
if (typeof flags.user === 'string') body.user = flags.user;
|
|
76
|
+
if (typeof flags.password === 'string') body.password = flags.password;
|
|
77
|
+
const data = await api.adminRequest(ctx, 'POST', '/wrappers/servers', { body });
|
|
78
|
+
output(ctx, data, (_d, ui) => ui.success(`Created foreign server ${name}`));
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
'drop-server': {
|
|
82
|
+
summary: 'Drop a foreign server',
|
|
83
|
+
usage: 'motherbase wrappers drop-server <name>',
|
|
84
|
+
async run(ctx, args) {
|
|
85
|
+
const name = requireArg(args[0], 'name');
|
|
86
|
+
const data = await api.adminRequest(ctx, 'DELETE', `/wrappers/servers/${enc(name)}`);
|
|
87
|
+
output(ctx, data, (_d, ui) => ui.success(`Dropped foreign server ${name}`));
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
credentials: {
|
|
91
|
+
summary: 'Replace the remote credentials of a foreign server',
|
|
92
|
+
usage: 'motherbase wrappers credentials <server> --user <u> --password <p>',
|
|
93
|
+
async run(ctx, args, flags) {
|
|
94
|
+
const name = requireArg(args[0], 'server');
|
|
95
|
+
if (typeof flags.user !== 'string' || typeof flags.password !== 'string') {
|
|
96
|
+
throw new CliError('--user and --password are required');
|
|
97
|
+
}
|
|
98
|
+
const body = { user: flags.user, password: flags.password };
|
|
99
|
+
const data = await api.adminRequest(ctx, 'PUT', `/wrappers/servers/${enc(name)}/credentials`, { body });
|
|
100
|
+
output(ctx, data, (_d, ui) => ui.success(`Updated credentials of ${name}`));
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
import: {
|
|
104
|
+
summary: 'Import a remote schema as foreign tables',
|
|
105
|
+
usage:
|
|
106
|
+
'motherbase wrappers import <server> --remote-schema <s> --local-schema <s> [--tables a,b]',
|
|
107
|
+
async run(ctx, args, flags) {
|
|
108
|
+
const name = requireArg(args[0], 'server');
|
|
109
|
+
const body = {
|
|
110
|
+
remoteSchema: requireArg(flags['remote-schema'], '--remote-schema'),
|
|
111
|
+
localSchema: requireArg(flags['local-schema'], '--local-schema'),
|
|
112
|
+
};
|
|
113
|
+
if (typeof flags.tables === 'string') body.tables = flags.tables.split(',').map(t => t.trim()).filter(Boolean);
|
|
114
|
+
const data = await api.adminRequest(ctx, 'POST', `/wrappers/servers/${enc(name)}/import`, { body });
|
|
115
|
+
output(ctx, data, (_d, ui) => ui.success(`Imported ${body.remoteSchema} into ${body.localSchema}`));
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
'create-table': {
|
|
119
|
+
summary: 'Create one foreign table',
|
|
120
|
+
usage:
|
|
121
|
+
'motherbase wrappers create-table <schema>.<table> --server <s> --column <name>:<type> ... [--option k=v ...]',
|
|
122
|
+
async run(ctx, args, flags) {
|
|
123
|
+
const [schema, name] = qualified(args[0]);
|
|
124
|
+
const body = {
|
|
125
|
+
server: requireArg(flags.server, '--server'),
|
|
126
|
+
schema,
|
|
127
|
+
name,
|
|
128
|
+
columns: columns(flags),
|
|
129
|
+
options: options(flags),
|
|
130
|
+
};
|
|
131
|
+
const data = await api.adminRequest(ctx, 'POST', '/wrappers/tables', { body });
|
|
132
|
+
output(ctx, data, (_d, ui) => ui.success(`Created foreign table ${schema}.${name}`));
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
'drop-table': {
|
|
136
|
+
summary: 'Drop a foreign table',
|
|
137
|
+
usage: 'motherbase wrappers drop-table <schema>.<table>',
|
|
138
|
+
async run(ctx, args) {
|
|
139
|
+
const [schema, name] = qualified(args[0]);
|
|
140
|
+
const data = await api.adminRequest(ctx, 'DELETE', `/wrappers/tables/${enc(schema)}/${enc(name)}`);
|
|
141
|
+
output(ctx, data, (_d, ui) => ui.success(`Dropped foreign table ${schema}.${name}`));
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
module.exports = {
|
|
147
|
+
summary: 'Foreign data wrappers (postgres_fdw, file_fdw)',
|
|
148
|
+
usage: 'motherbase wrappers <list|enable|create-server|drop-server|credentials|import|create-table|drop-table> ...',
|
|
149
|
+
subcommands,
|
|
150
|
+
};
|
package/src/config.js
CHANGED
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Config persistence. Two files:
|
|
4
|
-
// Global creds : ~/.motherbase/config.json (contains tokens; written 0o600)
|
|
5
|
-
// { "consoles": { "<url>": { "token": "...", "email": "..." } }, "current": "<url>" }
|
|
6
|
-
// Project link : ./.motherbase/config.json (versionable, non-secret)
|
|
7
|
-
// { "consoleUrl": "<url>", "projectId": "abc", "projectName": "my-app" }
|
|
8
|
-
|
|
9
|
-
const fs = require('fs');
|
|
10
|
-
const os = require('os');
|
|
11
|
-
const path = require('path');
|
|
12
|
-
|
|
13
|
-
const GLOBAL_DIR = path.join(os.homedir(), '.motherbase');
|
|
14
|
-
const GLOBAL_FILE = path.join(GLOBAL_DIR, 'config.json');
|
|
15
|
-
const LINK_DIRNAME = '.motherbase';
|
|
16
|
-
const LINK_BASENAME = 'config.json';
|
|
17
|
-
|
|
18
|
-
function normalizeUrl(url) {
|
|
19
|
-
return String(url || '').replace(/\/+$/, '');
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function readJson(file) {
|
|
23
|
-
try {
|
|
24
|
-
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
25
|
-
} catch {
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// ── Global credentials ───────────────────────────────────────────────────────
|
|
31
|
-
|
|
32
|
-
function readGlobal() {
|
|
33
|
-
return readJson(GLOBAL_FILE) || { consoles: {}, current: null };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function writeGlobal(cfg) {
|
|
37
|
-
fs.mkdirSync(GLOBAL_DIR, { recursive: true });
|
|
38
|
-
fs.writeFileSync(GLOBAL_FILE, JSON.stringify(cfg, null, 2) + '\n', { mode: 0o600 });
|
|
39
|
-
// Best-effort tighten perms even if the file already existed with looser mode.
|
|
40
|
-
try { fs.chmodSync(GLOBAL_FILE, 0o600); } catch { /* windows / noop */ }
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function saveConsoleToken(url, token, email) {
|
|
44
|
-
const u = normalizeUrl(url);
|
|
45
|
-
const cfg = readGlobal();
|
|
46
|
-
cfg.consoles = cfg.consoles || {};
|
|
47
|
-
cfg.consoles[u] = { ...(cfg.consoles[u] || {}), token, email };
|
|
48
|
-
cfg.current = u;
|
|
49
|
-
writeGlobal(cfg);
|
|
50
|
-
return u;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function clearConsoleToken(url) {
|
|
54
|
-
const cfg = readGlobal();
|
|
55
|
-
const u = normalizeUrl(url);
|
|
56
|
-
if (cfg.consoles && cfg.consoles[u]) delete cfg.consoles[u];
|
|
57
|
-
if (cfg.current === u) cfg.current = null;
|
|
58
|
-
writeGlobal(cfg);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function consoleEntry(url) {
|
|
62
|
-
const cfg = readGlobal();
|
|
63
|
-
return (cfg.consoles && cfg.consoles[normalizeUrl(url)]) || null;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// ── Per-project link ─────────────────────────────────────────────────────────
|
|
67
|
-
|
|
68
|
-
function linkPath(cwd) {
|
|
69
|
-
return path.join(cwd || process.cwd(), LINK_DIRNAME, LINK_BASENAME);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function readLink(cwd) {
|
|
73
|
-
return readJson(linkPath(cwd));
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function writeLink(cwd, data) {
|
|
77
|
-
const dir = path.join(cwd || process.cwd(), LINK_DIRNAME);
|
|
78
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
79
|
-
fs.writeFileSync(linkPath(cwd), JSON.stringify(data, null, 2) + '\n');
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function removeLink(cwd) {
|
|
83
|
-
try { fs.unlinkSync(linkPath(cwd)); return true; } catch { return false; }
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
module.exports = {
|
|
87
|
-
GLOBAL_FILE,
|
|
88
|
-
normalizeUrl,
|
|
89
|
-
readGlobal,
|
|
90
|
-
writeGlobal,
|
|
91
|
-
saveConsoleToken,
|
|
92
|
-
clearConsoleToken,
|
|
93
|
-
consoleEntry,
|
|
94
|
-
readLink,
|
|
95
|
-
writeLink,
|
|
96
|
-
removeLink,
|
|
97
|
-
linkPath,
|
|
98
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Config persistence. Two files:
|
|
4
|
+
// Global creds : ~/.motherbase/config.json (contains tokens; written 0o600)
|
|
5
|
+
// { "consoles": { "<url>": { "token": "...", "email": "..." } }, "current": "<url>" }
|
|
6
|
+
// Project link : ./.motherbase/config.json (versionable, non-secret)
|
|
7
|
+
// { "consoleUrl": "<url>", "projectId": "abc", "projectName": "my-app" }
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const os = require('os');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
const GLOBAL_DIR = path.join(os.homedir(), '.motherbase');
|
|
14
|
+
const GLOBAL_FILE = path.join(GLOBAL_DIR, 'config.json');
|
|
15
|
+
const LINK_DIRNAME = '.motherbase';
|
|
16
|
+
const LINK_BASENAME = 'config.json';
|
|
17
|
+
|
|
18
|
+
function normalizeUrl(url) {
|
|
19
|
+
return String(url || '').replace(/\/+$/, '');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function readJson(file) {
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ── Global credentials ───────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
function readGlobal() {
|
|
33
|
+
return readJson(GLOBAL_FILE) || { consoles: {}, current: null };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function writeGlobal(cfg) {
|
|
37
|
+
fs.mkdirSync(GLOBAL_DIR, { recursive: true });
|
|
38
|
+
fs.writeFileSync(GLOBAL_FILE, JSON.stringify(cfg, null, 2) + '\n', { mode: 0o600 });
|
|
39
|
+
// Best-effort tighten perms even if the file already existed with looser mode.
|
|
40
|
+
try { fs.chmodSync(GLOBAL_FILE, 0o600); } catch { /* windows / noop */ }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function saveConsoleToken(url, token, email) {
|
|
44
|
+
const u = normalizeUrl(url);
|
|
45
|
+
const cfg = readGlobal();
|
|
46
|
+
cfg.consoles = cfg.consoles || {};
|
|
47
|
+
cfg.consoles[u] = { ...(cfg.consoles[u] || {}), token, email };
|
|
48
|
+
cfg.current = u;
|
|
49
|
+
writeGlobal(cfg);
|
|
50
|
+
return u;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function clearConsoleToken(url) {
|
|
54
|
+
const cfg = readGlobal();
|
|
55
|
+
const u = normalizeUrl(url);
|
|
56
|
+
if (cfg.consoles && cfg.consoles[u]) delete cfg.consoles[u];
|
|
57
|
+
if (cfg.current === u) cfg.current = null;
|
|
58
|
+
writeGlobal(cfg);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function consoleEntry(url) {
|
|
62
|
+
const cfg = readGlobal();
|
|
63
|
+
return (cfg.consoles && cfg.consoles[normalizeUrl(url)]) || null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ── Per-project link ─────────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
function linkPath(cwd) {
|
|
69
|
+
return path.join(cwd || process.cwd(), LINK_DIRNAME, LINK_BASENAME);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function readLink(cwd) {
|
|
73
|
+
return readJson(linkPath(cwd));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function writeLink(cwd, data) {
|
|
77
|
+
const dir = path.join(cwd || process.cwd(), LINK_DIRNAME);
|
|
78
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
79
|
+
fs.writeFileSync(linkPath(cwd), JSON.stringify(data, null, 2) + '\n');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function removeLink(cwd) {
|
|
83
|
+
try { fs.unlinkSync(linkPath(cwd)); return true; } catch { return false; }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = {
|
|
87
|
+
GLOBAL_FILE,
|
|
88
|
+
normalizeUrl,
|
|
89
|
+
readGlobal,
|
|
90
|
+
writeGlobal,
|
|
91
|
+
saveConsoleToken,
|
|
92
|
+
clearConsoleToken,
|
|
93
|
+
consoleEntry,
|
|
94
|
+
readLink,
|
|
95
|
+
writeLink,
|
|
96
|
+
removeLink,
|
|
97
|
+
linkPath,
|
|
98
|
+
};
|