nexusapp-cli 3.1.0 → 3.1.2
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/dist/index.js +1 -1
- package/package.json +6 -2
- package/src/client.ts +0 -68
- package/src/commands/auth.ts +0 -186
- package/src/commands/bucket.ts +0 -261
- package/src/commands/database.ts +0 -305
- package/src/commands/deploy.ts +0 -904
- package/src/commands/domain.ts +0 -167
- package/src/commands/exec.ts +0 -154
- package/src/commands/managedDb.ts +0 -170
- package/src/commands/member.ts +0 -168
- package/src/commands/project.ts +0 -81
- package/src/commands/secret.ts +0 -117
- package/src/commands/token.ts +0 -173
- package/src/commands/volume.ts +0 -113
- package/src/config.ts +0 -56
- package/src/index.ts +0 -39
- package/src/output.ts +0 -65
- package/tsconfig.json +0 -19
package/src/commands/database.ts
DELETED
|
@@ -1,305 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import inquirer from 'inquirer';
|
|
3
|
-
import * as fs from 'fs';
|
|
4
|
-
import * as path from 'path';
|
|
5
|
-
import { client, apiError, unwrap } from '../client.js';
|
|
6
|
-
import { printTable, printJson, success, errorMsg, spinner, timeAgo } from '../output.js';
|
|
7
|
-
|
|
8
|
-
function formatBytes(bytes: bigint | number): string {
|
|
9
|
-
const n = typeof bytes === 'bigint' ? Number(bytes) : bytes;
|
|
10
|
-
if (n < 1024) return `${n} B`;
|
|
11
|
-
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
12
|
-
if (n < 1024 * 1024 * 1024) return `${(n / (1024 * 1024)).toFixed(1)} MB`;
|
|
13
|
-
return `${(n / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
17
|
-
|
|
18
|
-
/** Resolve a deployment name or UUID to its ID, or return undefined to list all services. */
|
|
19
|
-
async function resolveDeploymentId(nameOrId?: string): Promise<string | undefined> {
|
|
20
|
-
if (!nameOrId) return undefined;
|
|
21
|
-
if (UUID_RE.test(nameOrId)) return nameOrId;
|
|
22
|
-
const listRes = await client.get('/api/deployments');
|
|
23
|
-
const raw = unwrap(listRes.data);
|
|
24
|
-
const all: any[] = Array.isArray(raw) ? raw : raw.deployments || [];
|
|
25
|
-
const match = all.find((d) => d.name === nameOrId || d.displayName === nameOrId);
|
|
26
|
-
if (!match) throw new Error(`Deployment not found: "${nameOrId}"`);
|
|
27
|
-
return match.id;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function registerDatabase(program: Command): void {
|
|
31
|
-
const db = program
|
|
32
|
-
.command('db')
|
|
33
|
-
.description('Database service and backup commands (run `nexus db services` to list service IDs)');
|
|
34
|
-
|
|
35
|
-
// services (list)
|
|
36
|
-
db
|
|
37
|
-
.command('services [deployment-name-or-id]')
|
|
38
|
-
.description('List database services and their IDs (optionally filtered by deployment)')
|
|
39
|
-
.option('--json', 'Output raw JSON')
|
|
40
|
-
.action(async (deployment, opts) => {
|
|
41
|
-
try {
|
|
42
|
-
const deploymentId = await resolveDeploymentId(deployment);
|
|
43
|
-
const res = await client.get('/api/deployment-services', {
|
|
44
|
-
params: deploymentId ? { deployment: deploymentId } : {},
|
|
45
|
-
});
|
|
46
|
-
const services: any[] = unwrap(res.data) || [];
|
|
47
|
-
|
|
48
|
-
if (opts.json) { printJson(services); return; }
|
|
49
|
-
if (!services.length) {
|
|
50
|
-
console.log('No database services found. Provision one with `nexus deploy source ... --services postgresql,redis`.');
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
printTable(
|
|
55
|
-
['SERVICE ID', 'TYPE', 'NAME', 'DEPLOYMENT', 'STATUS', 'AUTO-BACKUP'],
|
|
56
|
-
services.map((s: any) => [
|
|
57
|
-
s.id,
|
|
58
|
-
s.serviceType || '—',
|
|
59
|
-
s.displayName || s.serviceName || '—',
|
|
60
|
-
s.deployment?.displayName || s.deployment?.name || s.deploymentId || '—',
|
|
61
|
-
s.status || '—',
|
|
62
|
-
s.backupEnabled ? 'on' : 'off',
|
|
63
|
-
])
|
|
64
|
-
);
|
|
65
|
-
} catch (err) {
|
|
66
|
-
errorMsg(apiError(err));
|
|
67
|
-
process.exit(1);
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
// backup
|
|
72
|
-
db
|
|
73
|
-
.command('backup <service-id>')
|
|
74
|
-
.description('Create a database backup for a deployment service')
|
|
75
|
-
.option('--json', 'Output raw JSON')
|
|
76
|
-
.action(async (serviceId, opts) => {
|
|
77
|
-
const spin = spinner('Creating backup...');
|
|
78
|
-
try {
|
|
79
|
-
const res = await client.post(`/api/deployment-services/${serviceId}/backup`);
|
|
80
|
-
const backup = unwrap(res.data);
|
|
81
|
-
spin.stop();
|
|
82
|
-
if (opts.json) { printJson(backup); return; }
|
|
83
|
-
printTable(
|
|
84
|
-
['ID', 'TYPE', 'SIZE', 'CREATED'],
|
|
85
|
-
[[
|
|
86
|
-
backup.id,
|
|
87
|
-
backup.serviceType || '—',
|
|
88
|
-
backup.fileSizeBytes != null ? formatBytes(backup.fileSizeBytes) : '—',
|
|
89
|
-
backup.createdAt ? timeAgo(backup.createdAt) : '—',
|
|
90
|
-
]]
|
|
91
|
-
);
|
|
92
|
-
} catch (err) {
|
|
93
|
-
spin.stop();
|
|
94
|
-
errorMsg(apiError(err));
|
|
95
|
-
process.exit(1);
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
// backups (list)
|
|
100
|
-
db
|
|
101
|
-
.command('backups <service-id>')
|
|
102
|
-
.description('List backups for a deployment service')
|
|
103
|
-
.option('--json', 'Output raw JSON')
|
|
104
|
-
.action(async (serviceId, opts) => {
|
|
105
|
-
try {
|
|
106
|
-
const res = await client.get(`/api/deployment-services/${serviceId}/backups`);
|
|
107
|
-
const raw = unwrap(res.data);
|
|
108
|
-
const backups: any[] = Array.isArray(raw) ? raw : raw.backups || [];
|
|
109
|
-
|
|
110
|
-
if (opts.json) { printJson(backups); return; }
|
|
111
|
-
if (!backups.length) { console.log('No backups found.'); return; }
|
|
112
|
-
|
|
113
|
-
printTable(
|
|
114
|
-
['ID', 'TYPE', 'SIZE', 'STATUS', 'CREATED'],
|
|
115
|
-
backups.map((b: any) => [
|
|
116
|
-
b.id,
|
|
117
|
-
b.serviceType || '—',
|
|
118
|
-
b.fileSizeBytes != null ? formatBytes(b.fileSizeBytes) : '—',
|
|
119
|
-
b.status || '—',
|
|
120
|
-
b.createdAt ? timeAgo(b.createdAt) : '—',
|
|
121
|
-
])
|
|
122
|
-
);
|
|
123
|
-
} catch (err) {
|
|
124
|
-
errorMsg(apiError(err));
|
|
125
|
-
process.exit(1);
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
// restore
|
|
130
|
-
db
|
|
131
|
-
.command('restore <service-id> <backup-id>')
|
|
132
|
-
.description('Restore a deployment service from a backup')
|
|
133
|
-
.option('--yes', 'Skip confirmation prompt')
|
|
134
|
-
.option('--json', 'Output raw JSON')
|
|
135
|
-
.action(async (serviceId, backupId, opts) => {
|
|
136
|
-
if (!opts.yes) {
|
|
137
|
-
const { confirm } = await inquirer.prompt([
|
|
138
|
-
{
|
|
139
|
-
type: 'confirm',
|
|
140
|
-
name: 'confirm',
|
|
141
|
-
message: 'Restore will overwrite current data. Continue?',
|
|
142
|
-
default: false,
|
|
143
|
-
},
|
|
144
|
-
]);
|
|
145
|
-
if (!confirm) { console.log('Cancelled.'); return; }
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const spin = spinner('Restoring database...');
|
|
149
|
-
try {
|
|
150
|
-
const res = await client.post(`/api/deployment-services/${serviceId}/restore`, { backupId });
|
|
151
|
-
const result = unwrap(res.data);
|
|
152
|
-
spin.stop();
|
|
153
|
-
if (opts.json) { printJson(result); return; }
|
|
154
|
-
success('Database restored successfully.');
|
|
155
|
-
} catch (err) {
|
|
156
|
-
spin.stop();
|
|
157
|
-
errorMsg(apiError(err));
|
|
158
|
-
process.exit(1);
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// backup-download
|
|
163
|
-
db
|
|
164
|
-
.command('backup-download <service-id> <backup-id>')
|
|
165
|
-
.description('Download a backup file to your local machine (works for offline restore)')
|
|
166
|
-
.option('--out <path>', 'Output file path (default: ./<original-filename>)')
|
|
167
|
-
.option('--share', 'Print a short-lived signed URL instead of downloading directly')
|
|
168
|
-
.option('--ttl <seconds>', 'Lifetime for --share URL in seconds (30-3600, default 300)', '300')
|
|
169
|
-
.option('--json', 'Output raw JSON (only meaningful with --share)')
|
|
170
|
-
.action(async (serviceId, backupId, opts) => {
|
|
171
|
-
try {
|
|
172
|
-
if (opts.share) {
|
|
173
|
-
const res = await client.post(
|
|
174
|
-
`/api/deployment-services/${serviceId}/backups/${backupId}/download-url`,
|
|
175
|
-
{ ttlSeconds: parseInt(opts.ttl, 10) || 300 }
|
|
176
|
-
);
|
|
177
|
-
const data = unwrap(res.data);
|
|
178
|
-
if (opts.json) { printJson(data); return; }
|
|
179
|
-
console.log(`URL: ${data.url}`);
|
|
180
|
-
console.log(`File: ${data.fileName}`);
|
|
181
|
-
console.log(`Size: ${formatBytes(data.fileSizeBytes)}`);
|
|
182
|
-
console.log(`Expires at: ${data.expiresAt}`);
|
|
183
|
-
console.log('');
|
|
184
|
-
console.log('Anyone with this URL can download the backup until it expires.');
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const spin = spinner('Downloading backup...');
|
|
189
|
-
const res = await client.get(
|
|
190
|
-
`/api/deployment-services/${serviceId}/backups/${backupId}/download`,
|
|
191
|
-
{ responseType: 'stream' }
|
|
192
|
-
);
|
|
193
|
-
|
|
194
|
-
const disposition: string = res.headers['content-disposition'] || '';
|
|
195
|
-
const matched = disposition.match(/filename="?([^"]+)"?/);
|
|
196
|
-
const fileName = matched?.[1] || `backup-${backupId}`;
|
|
197
|
-
const outPath = path.resolve(opts.out || fileName);
|
|
198
|
-
|
|
199
|
-
const dir = path.dirname(outPath);
|
|
200
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
201
|
-
|
|
202
|
-
await new Promise<void>((resolve, reject) => {
|
|
203
|
-
const writer = fs.createWriteStream(outPath);
|
|
204
|
-
res.data.pipe(writer);
|
|
205
|
-
writer.on('finish', () => resolve());
|
|
206
|
-
writer.on('error', reject);
|
|
207
|
-
res.data.on('error', reject);
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
const size = fs.statSync(outPath).size;
|
|
211
|
-
spin.stop();
|
|
212
|
-
success(`Downloaded ${formatBytes(size)} → ${outPath}`);
|
|
213
|
-
} catch (err) {
|
|
214
|
-
errorMsg(apiError(err));
|
|
215
|
-
process.exit(1);
|
|
216
|
-
}
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
// restore-to (cross-service restore)
|
|
220
|
-
db
|
|
221
|
-
.command('restore-to <target-service-id> <backup-id>')
|
|
222
|
-
.description('Restore a backup into a DIFFERENT deployment service in the same org (engines must match)')
|
|
223
|
-
.option('--yes', 'Skip confirmation prompt')
|
|
224
|
-
.option('--json', 'Output raw JSON')
|
|
225
|
-
.action(async (targetServiceId, backupId, opts) => {
|
|
226
|
-
if (!opts.yes) {
|
|
227
|
-
const { confirm } = await inquirer.prompt([
|
|
228
|
-
{
|
|
229
|
-
type: 'confirm',
|
|
230
|
-
name: 'confirm',
|
|
231
|
-
message: `Restore backup ${backupId} into service ${targetServiceId}? Existing data on the target will be overwritten.`,
|
|
232
|
-
default: false,
|
|
233
|
-
},
|
|
234
|
-
]);
|
|
235
|
-
if (!confirm) { console.log('Cancelled.'); return; }
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const spin = spinner('Restoring database into target service...');
|
|
239
|
-
try {
|
|
240
|
-
const res = await client.post(
|
|
241
|
-
`/api/deployment-services/${targetServiceId}/restore-from/${backupId}`
|
|
242
|
-
);
|
|
243
|
-
const result = unwrap(res.data);
|
|
244
|
-
spin.stop();
|
|
245
|
-
if (opts.json) { printJson(result); return; }
|
|
246
|
-
success('Database restored successfully into target service.');
|
|
247
|
-
} catch (err) {
|
|
248
|
-
spin.stop();
|
|
249
|
-
errorMsg(apiError(err));
|
|
250
|
-
process.exit(1);
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
// backup-delete
|
|
255
|
-
db
|
|
256
|
-
.command('backup-delete <service-id> <backup-id>')
|
|
257
|
-
.description('Delete a backup')
|
|
258
|
-
.option('--yes', 'Skip confirmation prompt')
|
|
259
|
-
.action(async (serviceId, backupId, opts) => {
|
|
260
|
-
if (!opts.yes) {
|
|
261
|
-
const { confirm } = await inquirer.prompt([
|
|
262
|
-
{
|
|
263
|
-
type: 'confirm',
|
|
264
|
-
name: 'confirm',
|
|
265
|
-
message: 'Permanently delete this backup?',
|
|
266
|
-
default: false,
|
|
267
|
-
},
|
|
268
|
-
]);
|
|
269
|
-
if (!confirm) { console.log('Cancelled.'); return; }
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
try {
|
|
273
|
-
await client.delete(`/api/deployment-services/${serviceId}/backups/${backupId}`);
|
|
274
|
-
success('Backup deleted.');
|
|
275
|
-
} catch (err) {
|
|
276
|
-
errorMsg(apiError(err));
|
|
277
|
-
process.exit(1);
|
|
278
|
-
}
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
// backup-schedule
|
|
282
|
-
db
|
|
283
|
-
.command('backup-schedule <service-id>')
|
|
284
|
-
.description('Enable or disable daily automatic backups for a deployment service')
|
|
285
|
-
.option('--enable', 'Enable daily auto-backups')
|
|
286
|
-
.option('--disable', 'Disable daily auto-backups')
|
|
287
|
-
.option('--json', 'Output raw JSON')
|
|
288
|
-
.action(async (serviceId, opts) => {
|
|
289
|
-
if (!opts.enable && !opts.disable) {
|
|
290
|
-
errorMsg('Specify either --enable or --disable.');
|
|
291
|
-
process.exit(1);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
const enabled = Boolean(opts.enable);
|
|
295
|
-
try {
|
|
296
|
-
const res = await client.patch(`/api/deployment-services/${serviceId}/backup/schedule`, { enabled });
|
|
297
|
-
const result = unwrap(res.data);
|
|
298
|
-
if (opts.json) { printJson(result); return; }
|
|
299
|
-
success(enabled ? 'Daily backups enabled.' : 'Daily backups disabled.');
|
|
300
|
-
} catch (err) {
|
|
301
|
-
errorMsg(apiError(err));
|
|
302
|
-
process.exit(1);
|
|
303
|
-
}
|
|
304
|
-
});
|
|
305
|
-
}
|