@nometria-ai/nom 0.2.9 → 0.3.1
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/package.json +1 -1
- package/src/cli.js +43 -3
- package/src/commands/cron.js +162 -0
- package/src/commands/db.js +267 -0
- package/src/commands/deploy.js +154 -22
- package/src/commands/env.js +159 -10
- package/src/commands/init.js +50 -2
- package/src/commands/list.js +51 -0
- package/src/commands/rollback.js +95 -0
- package/src/commands/setup.js +145 -1
- package/src/commands/ssh.js +88 -0
- package/src/lib/cache.js +56 -0
- package/src/lib/detect.js +107 -1
- package/src/lib/telemetry.js +123 -0
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -29,6 +29,11 @@ import { domain } from './commands/domain.js';
|
|
|
29
29
|
import { env } from './commands/env.js';
|
|
30
30
|
import { scan } from './commands/scan.js';
|
|
31
31
|
import { setup } from './commands/setup.js';
|
|
32
|
+
import { list } from './commands/list.js';
|
|
33
|
+
import { rollback } from './commands/rollback.js';
|
|
34
|
+
import { ssh } from './commands/ssh.js';
|
|
35
|
+
import { db } from './commands/db.js';
|
|
36
|
+
import { cron } from './commands/cron.js';
|
|
32
37
|
|
|
33
38
|
const { values, positionals } = parseArgs({
|
|
34
39
|
allowPositionals: true,
|
|
@@ -41,6 +46,9 @@ const { values, positionals } = parseArgs({
|
|
|
41
46
|
from: { type: 'string' },
|
|
42
47
|
'api-key': { type: 'boolean', default: false },
|
|
43
48
|
token: { type: 'boolean', default: false },
|
|
49
|
+
'dry-run': { type: 'boolean', default: false },
|
|
50
|
+
preview: { type: 'boolean', default: false },
|
|
51
|
+
production:{ type: 'boolean', default: false },
|
|
44
52
|
message: { type: 'string', short: 'm' },
|
|
45
53
|
},
|
|
46
54
|
strict: false,
|
|
@@ -86,6 +94,11 @@ const commands = {
|
|
|
86
94
|
env,
|
|
87
95
|
scan,
|
|
88
96
|
setup,
|
|
97
|
+
list,
|
|
98
|
+
rollback,
|
|
99
|
+
ssh,
|
|
100
|
+
db,
|
|
101
|
+
cron,
|
|
89
102
|
};
|
|
90
103
|
|
|
91
104
|
const handler = commands[command];
|
|
@@ -99,12 +112,25 @@ try {
|
|
|
99
112
|
await handler(values, positionals.slice(1));
|
|
100
113
|
} catch (err) {
|
|
101
114
|
if (err.code === 'ERR_AUTH') {
|
|
102
|
-
console.error(`\n Not authenticated. Run: nom login
|
|
115
|
+
console.error(`\n Not authenticated. Run: nom login`);
|
|
116
|
+
console.error(` Get your API key at: https://nometria.com/settings/api-keys\n`);
|
|
103
117
|
} else if (err.code === 'ERR_CONFIG') {
|
|
104
|
-
console.error(`\n No nometria.json found. Run: nom init
|
|
118
|
+
console.error(`\n No nometria.json found. Run: nom init`);
|
|
119
|
+
console.error(` Docs: https://docs.nometria.com/cli/install\n`);
|
|
120
|
+
} else if (err.status === 402) {
|
|
121
|
+
console.error(`\n Payment required. Upgrade your plan:`);
|
|
122
|
+
console.error(` https://nometria.com/pricing\n`);
|
|
123
|
+
} else if (err.status === 404) {
|
|
124
|
+
console.error(`\n App not found. List your apps: nom list`);
|
|
125
|
+
console.error(` Dashboard: https://nometria.com/dashboard\n`);
|
|
126
|
+
} else if (err.status === 429) {
|
|
127
|
+
console.error(`\n Rate limited. Wait a moment and try again.\n`);
|
|
105
128
|
} else {
|
|
106
|
-
console.error(`\n Error: ${err.message}
|
|
129
|
+
console.error(`\n Error: ${err.message}`);
|
|
130
|
+
console.error(` Docs: https://docs.nometria.com`);
|
|
131
|
+
console.error(` Dashboard: https://nometria.com/dashboard`);
|
|
107
132
|
if (process.env.NOM_DEBUG) console.error(err.stack);
|
|
133
|
+
console.error();
|
|
108
134
|
}
|
|
109
135
|
process.exit(1);
|
|
110
136
|
}
|
|
@@ -121,6 +147,8 @@ function printHelp() {
|
|
|
121
147
|
deploy Deploy to production (default)
|
|
122
148
|
preview Deploy a staging preview
|
|
123
149
|
status Check deployment status
|
|
150
|
+
list List all your apps
|
|
151
|
+
rollback [id] Roll back to a previous deployment
|
|
124
152
|
logs [-f] View deployment logs
|
|
125
153
|
login Sign in via browser (or --api-key)
|
|
126
154
|
whoami Show current user
|
|
@@ -134,12 +162,21 @@ function printHelp() {
|
|
|
134
162
|
stop Stop a running instance
|
|
135
163
|
terminate Terminate instance permanently
|
|
136
164
|
upgrade <size> Upgrade instance (2gb/4gb/8gb/16gb)
|
|
165
|
+
ssh [command] Connect to instance or run remote command
|
|
166
|
+
|
|
167
|
+
db backup Create a database backup
|
|
168
|
+
db restore <id> Restore from a backup
|
|
169
|
+
db shell Show database connection details
|
|
170
|
+
db migrate Run pending migrations
|
|
137
171
|
|
|
138
172
|
domain add <domain> Add custom domain
|
|
139
173
|
env set KEY=VALUE Set environment variables
|
|
140
174
|
env list List environment variables
|
|
141
175
|
env delete KEY Delete environment variable
|
|
142
176
|
scan Run AI security scan
|
|
177
|
+
cron add <s> <cmd> Add a scheduled task
|
|
178
|
+
cron list List cron jobs
|
|
179
|
+
cron delete <id> Delete a cron job
|
|
143
180
|
|
|
144
181
|
Options:
|
|
145
182
|
-h, --help Show this help
|
|
@@ -148,6 +185,9 @@ function printHelp() {
|
|
|
148
185
|
-f, --follow Follow logs in real-time
|
|
149
186
|
-m, --message Commit message (for github push)
|
|
150
187
|
--json Output as JSON
|
|
188
|
+
--dry-run Validate config and build without deploying
|
|
189
|
+
--preview Target preview environment (for nom env)
|
|
190
|
+
--production Target production environment (for nom env)
|
|
151
191
|
--api-key Login with API key paste instead of browser
|
|
152
192
|
--from <url> Deploy from a GitHub URL
|
|
153
193
|
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nom cron — Manage scheduled tasks on deployed instances.
|
|
3
|
+
*
|
|
4
|
+
* Subcommands:
|
|
5
|
+
* add <schedule> <command> Add a cron job (e.g., nom cron add "0 3 * * *" "npm run cleanup")
|
|
6
|
+
* list List active cron jobs
|
|
7
|
+
* delete <id> Delete a cron job
|
|
8
|
+
*/
|
|
9
|
+
import { readConfig } from '../lib/config.js';
|
|
10
|
+
import { requireApiKey } from '../lib/auth.js';
|
|
11
|
+
import { apiRequest } from '../lib/api.js';
|
|
12
|
+
|
|
13
|
+
export async function cron(flags, positionals) {
|
|
14
|
+
const sub = positionals[0];
|
|
15
|
+
|
|
16
|
+
switch (sub) {
|
|
17
|
+
case 'add':
|
|
18
|
+
return cronAdd(flags, positionals.slice(1));
|
|
19
|
+
case 'list':
|
|
20
|
+
case 'ls':
|
|
21
|
+
return cronList(flags);
|
|
22
|
+
case 'delete':
|
|
23
|
+
case 'rm':
|
|
24
|
+
return cronDelete(flags, positionals[1]);
|
|
25
|
+
default:
|
|
26
|
+
console.log(`
|
|
27
|
+
Usage: nom cron <command>
|
|
28
|
+
|
|
29
|
+
Commands:
|
|
30
|
+
add <schedule> <cmd> Add a cron job
|
|
31
|
+
list List active cron jobs
|
|
32
|
+
delete <id> Delete a cron job
|
|
33
|
+
|
|
34
|
+
Schedule format: standard cron expression
|
|
35
|
+
"0 3 * * *" Daily at 3am UTC
|
|
36
|
+
"*/15 * * * *" Every 15 minutes
|
|
37
|
+
"0 0 * * 0" Weekly on Sunday
|
|
38
|
+
|
|
39
|
+
Examples:
|
|
40
|
+
nom cron add "0 3 * * *" "npm run cleanup"
|
|
41
|
+
nom cron add "*/5 * * * *" "curl -sf https://myapp.com/health"
|
|
42
|
+
nom cron list
|
|
43
|
+
nom cron delete cron_abc123
|
|
44
|
+
|
|
45
|
+
Help: https://docs.nometria.com/deploy/environment
|
|
46
|
+
`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function cronAdd(flags, args) {
|
|
51
|
+
const apiKey = requireApiKey();
|
|
52
|
+
const config = readConfig();
|
|
53
|
+
const appId = config.app_id;
|
|
54
|
+
|
|
55
|
+
if (!appId) {
|
|
56
|
+
console.error('\n No app_id in nometria.json. Deploy first: nom deploy\n');
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (args.length < 2) {
|
|
61
|
+
console.error('\n Usage: nom cron add "<schedule>" "<command>"');
|
|
62
|
+
console.error(' Example: nom cron add "0 3 * * *" "npm run cleanup"\n');
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const schedule = args[0];
|
|
67
|
+
const command = args.slice(1).join(' ');
|
|
68
|
+
|
|
69
|
+
// Basic cron validation
|
|
70
|
+
const parts = schedule.split(/\s+/);
|
|
71
|
+
if (parts.length < 5 || parts.length > 6) {
|
|
72
|
+
console.error(`\n Invalid cron schedule: "${schedule}"`);
|
|
73
|
+
console.error(' Expected format: "minute hour day month weekday"');
|
|
74
|
+
console.error(' Example: "0 3 * * *" (daily at 3am UTC)\n');
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const result = await apiRequest('/cli/cron', {
|
|
80
|
+
apiKey,
|
|
81
|
+
body: { app_id: appId, action: 'add', schedule, command },
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
console.log(`\n Cron job added: ${result.cron_id || 'pending'}`);
|
|
85
|
+
console.log(` Schedule: ${schedule}`);
|
|
86
|
+
console.log(` Command: ${command}`);
|
|
87
|
+
console.log(` Timezone: UTC\n`);
|
|
88
|
+
} catch (err) {
|
|
89
|
+
console.error(`\n Failed to add cron job: ${err.message}`);
|
|
90
|
+
console.error(' Help: https://docs.nometria.com/deploy/environment\n');
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function cronList(flags) {
|
|
96
|
+
const apiKey = requireApiKey();
|
|
97
|
+
const config = readConfig();
|
|
98
|
+
const appId = config.app_id;
|
|
99
|
+
|
|
100
|
+
if (!appId) {
|
|
101
|
+
console.error('\n No app_id in nometria.json.\n');
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
const result = await apiRequest('/cli/cron', {
|
|
107
|
+
apiKey,
|
|
108
|
+
body: { app_id: appId, action: 'list' },
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const jobs = result.jobs || [];
|
|
112
|
+
if (!jobs.length) {
|
|
113
|
+
console.log('\n No cron jobs configured. Add one: nom cron add "0 3 * * *" "npm run cleanup"\n');
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (flags.json) {
|
|
118
|
+
console.log(JSON.stringify(jobs, null, 2));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
console.log(`\n Cron jobs (${jobs.length}):\n`);
|
|
123
|
+
for (const job of jobs) {
|
|
124
|
+
console.log(` ${job.id || '—'}`);
|
|
125
|
+
console.log(` Schedule: ${job.schedule}`);
|
|
126
|
+
console.log(` Command: ${job.command}`);
|
|
127
|
+
if (job.last_run) console.log(` Last run: ${job.last_run}`);
|
|
128
|
+
console.log();
|
|
129
|
+
}
|
|
130
|
+
} catch (err) {
|
|
131
|
+
console.error(`\n Failed to list cron jobs: ${err.message}\n`);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function cronDelete(flags, cronId) {
|
|
137
|
+
const apiKey = requireApiKey();
|
|
138
|
+
const config = readConfig();
|
|
139
|
+
const appId = config.app_id;
|
|
140
|
+
|
|
141
|
+
if (!appId) {
|
|
142
|
+
console.error('\n No app_id in nometria.json.\n');
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!cronId) {
|
|
147
|
+
console.error('\n Usage: nom cron delete <cron_id>');
|
|
148
|
+
console.error(' List jobs first: nom cron list\n');
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
await apiRequest('/cli/cron', {
|
|
154
|
+
apiKey,
|
|
155
|
+
body: { app_id: appId, action: 'delete', cron_id: cronId },
|
|
156
|
+
});
|
|
157
|
+
console.log(`\n Cron job ${cronId} deleted.\n`);
|
|
158
|
+
} catch (err) {
|
|
159
|
+
console.error(`\n Failed to delete cron job: ${err.message}\n`);
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nom db — Database management commands.
|
|
3
|
+
*
|
|
4
|
+
* Subcommands:
|
|
5
|
+
* backup Create a database backup
|
|
6
|
+
* restore <id> Restore from a backup
|
|
7
|
+
* shell Show connection instructions
|
|
8
|
+
* migrate Run pending migrations
|
|
9
|
+
*/
|
|
10
|
+
import { readConfig } from '../lib/config.js';
|
|
11
|
+
import { requireApiKey } from '../lib/auth.js';
|
|
12
|
+
import { apiRequest } from '../lib/api.js';
|
|
13
|
+
|
|
14
|
+
export async function db(flags, positionals) {
|
|
15
|
+
const sub = positionals[0];
|
|
16
|
+
|
|
17
|
+
switch (sub) {
|
|
18
|
+
case 'backup':
|
|
19
|
+
return dbBackup(flags);
|
|
20
|
+
case 'restore':
|
|
21
|
+
return dbRestore(flags, positionals[1]);
|
|
22
|
+
case 'shell':
|
|
23
|
+
return dbShell(flags);
|
|
24
|
+
case 'migrate':
|
|
25
|
+
return dbMigrate(flags);
|
|
26
|
+
case 'ownership':
|
|
27
|
+
case 'own':
|
|
28
|
+
return dbOwnership(flags);
|
|
29
|
+
default:
|
|
30
|
+
console.log(`
|
|
31
|
+
Usage: nom db <command>
|
|
32
|
+
|
|
33
|
+
Commands:
|
|
34
|
+
backup Create a database backup
|
|
35
|
+
restore <id> Restore from a backup
|
|
36
|
+
shell Show database connection details
|
|
37
|
+
migrate Run pending migrations
|
|
38
|
+
ownership Transfer database to your own Supabase project
|
|
39
|
+
|
|
40
|
+
Help: https://docs.nometria.com/deploy/environment
|
|
41
|
+
`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function dbBackup(flags) {
|
|
46
|
+
const apiKey = requireApiKey();
|
|
47
|
+
const config = readConfig();
|
|
48
|
+
const appId = config.app_id;
|
|
49
|
+
|
|
50
|
+
if (!appId) {
|
|
51
|
+
console.error('\n No app_id in nometria.json. Deploy first: nom deploy\n');
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log('\n Creating database backup...\n');
|
|
56
|
+
try {
|
|
57
|
+
const result = await apiRequest('/cli/db', {
|
|
58
|
+
apiKey,
|
|
59
|
+
body: { app_id: appId, action: 'backup' },
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (flags.json) {
|
|
63
|
+
console.log(JSON.stringify(result, null, 2));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log(` Backup created: ${result.backup_id || 'pending'}`);
|
|
68
|
+
if (result.size) console.log(` Size: ${result.size}`);
|
|
69
|
+
if (result.s3_path) console.log(` Stored: ${result.s3_path}`);
|
|
70
|
+
console.log();
|
|
71
|
+
} catch (err) {
|
|
72
|
+
console.error(`\n Backup failed: ${err.message}`);
|
|
73
|
+
console.error(' This may require the app to have a running database.');
|
|
74
|
+
console.error(' Help: https://docs.nometria.com/deploy/environment\n');
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function dbRestore(flags, backupId) {
|
|
80
|
+
const apiKey = requireApiKey();
|
|
81
|
+
const config = readConfig();
|
|
82
|
+
const appId = config.app_id;
|
|
83
|
+
|
|
84
|
+
if (!appId) {
|
|
85
|
+
console.error('\n No app_id in nometria.json.\n');
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!backupId) {
|
|
90
|
+
// List available backups
|
|
91
|
+
console.log('\n Fetching available backups...\n');
|
|
92
|
+
try {
|
|
93
|
+
const result = await apiRequest('/cli/db', {
|
|
94
|
+
apiKey,
|
|
95
|
+
body: { app_id: appId, action: 'list_backups' },
|
|
96
|
+
});
|
|
97
|
+
const backups = result.backups || [];
|
|
98
|
+
if (!backups.length) {
|
|
99
|
+
console.log(' No backups found. Create one: nom db backup\n');
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
console.log(' Available backups:\n');
|
|
103
|
+
for (const b of backups) {
|
|
104
|
+
console.log(` ${b.id} ${b.created_at || '—'} ${b.size || '—'}`);
|
|
105
|
+
}
|
|
106
|
+
console.log('\n Restore: nom db restore <backup_id>\n');
|
|
107
|
+
} catch (err) {
|
|
108
|
+
console.error(`\n Failed to list backups: ${err.message}\n`);
|
|
109
|
+
}
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
console.log(`\n Restoring from backup: ${backupId}...\n`);
|
|
114
|
+
try {
|
|
115
|
+
const result = await apiRequest('/cli/db', {
|
|
116
|
+
apiKey,
|
|
117
|
+
body: { app_id: appId, action: 'restore', backup_id: backupId },
|
|
118
|
+
});
|
|
119
|
+
console.log(` Restore ${result.success ? 'completed' : 'failed'}.`);
|
|
120
|
+
if (result.message) console.log(` ${result.message}`);
|
|
121
|
+
console.log();
|
|
122
|
+
} catch (err) {
|
|
123
|
+
console.error(`\n Restore failed: ${err.message}\n`);
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function dbShell(flags) {
|
|
129
|
+
const apiKey = requireApiKey();
|
|
130
|
+
const config = readConfig();
|
|
131
|
+
const appId = config.app_id;
|
|
132
|
+
|
|
133
|
+
if (!appId) {
|
|
134
|
+
console.error('\n No app_id in nometria.json.\n');
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Get instance details for connection info
|
|
139
|
+
try {
|
|
140
|
+
const result = await apiRequest('/checkAwsStatus', {
|
|
141
|
+
apiKey,
|
|
142
|
+
body: { app_id: appId },
|
|
143
|
+
});
|
|
144
|
+
const ip = result.data?.ipAddress;
|
|
145
|
+
|
|
146
|
+
console.log(`\n Database connection for: ${appId}\n`);
|
|
147
|
+
console.log(' Via SSH tunnel (recommended):');
|
|
148
|
+
if (ip) {
|
|
149
|
+
console.log(` ssh -L 5432:localhost:5432 ubuntu@${ip}`);
|
|
150
|
+
console.log(' psql postgresql://postgres:postgres@localhost:5432/postgres\n');
|
|
151
|
+
}
|
|
152
|
+
console.log(' Via SSM:');
|
|
153
|
+
const instanceId = result.data?.instanceId;
|
|
154
|
+
if (instanceId) {
|
|
155
|
+
console.log(` aws ssm start-session --target ${instanceId} \\`);
|
|
156
|
+
console.log(' --document-name AWS-StartPortForwardingSession \\');
|
|
157
|
+
console.log(" --parameters '{\"portNumber\":[\"5432\"],\"localPortNumber\":[\"5432\"]}'\n");
|
|
158
|
+
}
|
|
159
|
+
console.log(' Direct on instance:');
|
|
160
|
+
console.log(' psql postgresql://postgres:postgres@localhost:5432/postgres\n');
|
|
161
|
+
} catch (err) {
|
|
162
|
+
console.error(`\n Could not fetch instance details: ${err.message}\n`);
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function dbMigrate(flags) {
|
|
168
|
+
const apiKey = requireApiKey();
|
|
169
|
+
const config = readConfig();
|
|
170
|
+
const appId = config.app_id;
|
|
171
|
+
|
|
172
|
+
if (!appId) {
|
|
173
|
+
console.error('\n No app_id in nometria.json.\n');
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
console.log('\n Running database migrations...\n');
|
|
178
|
+
try {
|
|
179
|
+
const result = await apiRequest('/cli/db', {
|
|
180
|
+
apiKey,
|
|
181
|
+
body: { app_id: appId, action: 'migrate' },
|
|
182
|
+
});
|
|
183
|
+
console.log(` ${result.message || 'Migrations complete.'}\n`);
|
|
184
|
+
} catch (err) {
|
|
185
|
+
console.error(`\n Migration failed: ${err.message}`);
|
|
186
|
+
console.error(' Check migration files in db/seeds/ or schema.sql\n');
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function dbOwnership(flags) {
|
|
192
|
+
const apiKey = requireApiKey();
|
|
193
|
+
const config = readConfig();
|
|
194
|
+
const appId = config.app_id;
|
|
195
|
+
|
|
196
|
+
if (!appId) {
|
|
197
|
+
console.error('\n No app_id in nometria.json.\n');
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
console.log(`
|
|
202
|
+
Database Ownership Transfer
|
|
203
|
+
═══════════════════════════
|
|
204
|
+
|
|
205
|
+
This transfers your app's database to a Supabase project YOU own.
|
|
206
|
+
|
|
207
|
+
What happens:
|
|
208
|
+
1. Export schema + data from current managed database
|
|
209
|
+
2. Create a new Supabase project under your account
|
|
210
|
+
3. Import schema + data into your project
|
|
211
|
+
4. Update your app's .env to point to your Supabase
|
|
212
|
+
5. Resync the app to use the new database
|
|
213
|
+
|
|
214
|
+
After transfer:
|
|
215
|
+
- You own the Supabase project directly
|
|
216
|
+
- You control backups, scaling, and access
|
|
217
|
+
- Nometria no longer manages the database
|
|
218
|
+
- Your app keeps running with no downtime
|
|
219
|
+
`);
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
console.log(' Step 1: Exporting current database...');
|
|
223
|
+
const exportResult = await apiRequest('/cli/db', {
|
|
224
|
+
apiKey,
|
|
225
|
+
body: { app_id: appId, action: 'export_schema' },
|
|
226
|
+
});
|
|
227
|
+
console.log(` Exported: ${exportResult.tables || '?'} tables, ${exportResult.rows || '?'} rows`);
|
|
228
|
+
|
|
229
|
+
console.log(' Step 2: Creating your Supabase project...');
|
|
230
|
+
const createResult = await apiRequest('/cli/db', {
|
|
231
|
+
apiKey,
|
|
232
|
+
body: { app_id: appId, action: 'create_owned_supabase' },
|
|
233
|
+
});
|
|
234
|
+
const supaUrl = createResult.supabase_url;
|
|
235
|
+
console.log(` Supabase project: ${supaUrl || 'pending'}`);
|
|
236
|
+
|
|
237
|
+
console.log(' Step 3: Importing schema + data...');
|
|
238
|
+
const importResult = await apiRequest('/cli/db', {
|
|
239
|
+
apiKey,
|
|
240
|
+
body: { app_id: appId, action: 'import_to_owned', supabase_url: supaUrl },
|
|
241
|
+
});
|
|
242
|
+
console.log(` Imported: ${importResult.message || 'complete'}`);
|
|
243
|
+
|
|
244
|
+
console.log(' Step 4: Updating app configuration...');
|
|
245
|
+
const updateResult = await apiRequest('/cli/db', {
|
|
246
|
+
apiKey,
|
|
247
|
+
body: { app_id: appId, action: 'switch_to_owned', supabase_url: supaUrl },
|
|
248
|
+
});
|
|
249
|
+
console.log(` ${updateResult.message || 'Configuration updated'}`);
|
|
250
|
+
|
|
251
|
+
console.log(`
|
|
252
|
+
Database ownership transferred!
|
|
253
|
+
|
|
254
|
+
Your Supabase project: ${supaUrl || 'See Supabase dashboard'}
|
|
255
|
+
Dashboard: https://supabase.com/dashboard
|
|
256
|
+
App: https://nometria.com/AppDetails?app_id=${appId}
|
|
257
|
+
|
|
258
|
+
Your app is now using YOUR Supabase project.
|
|
259
|
+
You have full control over backups, scaling, and access.
|
|
260
|
+
`);
|
|
261
|
+
} catch (err) {
|
|
262
|
+
console.error(`\n Ownership transfer failed: ${err.message}`);
|
|
263
|
+
console.error(' This feature requires a running instance with Supabase.');
|
|
264
|
+
console.error(' Help: https://docs.nometria.com/deploy/environment\n');
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
}
|