relight-cli 0.1.0 → 0.2.0
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/README.md +77 -34
- package/package.json +12 -4
- package/src/cli.js +305 -1
- package/src/commands/apps.js +128 -0
- package/src/commands/auth.js +75 -4
- package/src/commands/config.js +282 -0
- package/src/commands/cost.js +593 -0
- package/src/commands/db.js +531 -0
- package/src/commands/deploy.js +298 -0
- package/src/commands/doctor.js +41 -9
- package/src/commands/domains.js +223 -0
- package/src/commands/logs.js +111 -0
- package/src/commands/open.js +42 -0
- package/src/commands/ps.js +121 -0
- package/src/commands/scale.js +132 -0
- package/src/lib/clouds/aws.js +309 -35
- package/src/lib/clouds/cf.js +401 -2
- package/src/lib/clouds/gcp.js +234 -3
- package/src/lib/clouds/slicervm.js +139 -0
- package/src/lib/config.js +40 -0
- package/src/lib/docker.js +34 -0
- package/src/lib/link.js +20 -5
- package/src/lib/providers/aws/app.js +481 -0
- package/src/lib/providers/aws/db.js +513 -0
- package/src/lib/providers/aws/dns.js +232 -0
- package/src/lib/providers/aws/registry.js +59 -0
- package/src/lib/providers/cf/app.js +596 -0
- package/src/lib/providers/cf/bundle.js +70 -0
- package/src/lib/providers/cf/db.js +279 -0
- package/src/lib/providers/cf/dns.js +148 -0
- package/src/lib/providers/cf/registry.js +17 -0
- package/src/lib/providers/gcp/app.js +429 -0
- package/src/lib/providers/gcp/db.js +457 -0
- package/src/lib/providers/gcp/dns.js +166 -0
- package/src/lib/providers/gcp/registry.js +30 -0
- package/src/lib/providers/resolve.js +49 -0
- package/src/lib/providers/slicervm/app.js +396 -0
- package/src/lib/providers/slicervm/db.js +33 -0
- package/src/lib/providers/slicervm/dns.js +58 -0
- package/src/lib/providers/slicervm/registry.js +7 -0
- package/worker-template/package.json +10 -0
- package/worker-template/src/index.js +260 -0
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
import { phase, status, success, fatal, hint, fmt, table } from "../lib/output.js";
|
|
2
|
+
import { resolveAppName, resolveDb, readLink, linkApp } from "../lib/link.js";
|
|
3
|
+
import { resolveCloudId, getCloudCfg, getProvider } from "../lib/providers/resolve.js";
|
|
4
|
+
import { createInterface } from "readline";
|
|
5
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
6
|
+
|
|
7
|
+
// Resolve app cloud and db cloud from options + .relight.yaml
|
|
8
|
+
function resolveDbClouds(options) {
|
|
9
|
+
var appCloud = resolveCloudId(options.cloud);
|
|
10
|
+
var dbFlag = options.db || resolveDb();
|
|
11
|
+
var crossCloud = dbFlag && resolveCloudId(dbFlag) !== appCloud;
|
|
12
|
+
var dbCloud = crossCloud ? resolveCloudId(dbFlag) : appCloud;
|
|
13
|
+
return { appCloud, dbCloud, crossCloud };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function dbCreate(name, options) {
|
|
17
|
+
name = resolveAppName(name);
|
|
18
|
+
var { appCloud, dbCloud, crossCloud } = resolveDbClouds(options);
|
|
19
|
+
var dbCfg = getCloudCfg(dbCloud);
|
|
20
|
+
var dbProvider = await getProvider(dbCloud, "db");
|
|
21
|
+
|
|
22
|
+
phase("Creating database");
|
|
23
|
+
if (options.jurisdiction) status(`relight-${name} (jurisdiction: ${options.jurisdiction})...`);
|
|
24
|
+
else if (options.location) status(`relight-${name} (location: ${options.location})...`);
|
|
25
|
+
else status(`relight-${name}...`);
|
|
26
|
+
|
|
27
|
+
var result;
|
|
28
|
+
try {
|
|
29
|
+
result = await dbProvider.createDatabase(dbCfg, name, {
|
|
30
|
+
location: options.location,
|
|
31
|
+
jurisdiction: options.jurisdiction,
|
|
32
|
+
skipAppConfig: crossCloud,
|
|
33
|
+
});
|
|
34
|
+
} catch (e) {
|
|
35
|
+
fatal(e.message);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Cross-cloud: inject DB env vars into the app cloud's config
|
|
39
|
+
if (crossCloud) {
|
|
40
|
+
var appCfg = getCloudCfg(appCloud);
|
|
41
|
+
var appProvider = await getProvider(appCloud, "app");
|
|
42
|
+
status(`Injecting DB config into ${appCloud} app...`);
|
|
43
|
+
|
|
44
|
+
var appConfig = await appProvider.getAppConfig(appCfg, name);
|
|
45
|
+
if (!appConfig) {
|
|
46
|
+
fatal(`App ${name} not found on ${appCloud}.`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
appConfig.dbId = result.dbId;
|
|
50
|
+
appConfig.dbName = result.dbName;
|
|
51
|
+
|
|
52
|
+
if (!appConfig.envKeys) appConfig.envKeys = [];
|
|
53
|
+
if (!appConfig.secretKeys) appConfig.secretKeys = [];
|
|
54
|
+
if (!appConfig.env) appConfig.env = {};
|
|
55
|
+
|
|
56
|
+
if (result.connectionUrl) {
|
|
57
|
+
// CF uses DB_URL, GCP/AWS use DATABASE_URL
|
|
58
|
+
var urlKey = dbCloud === "cf" ? "DB_URL" : "DATABASE_URL";
|
|
59
|
+
appConfig.env[urlKey] = result.connectionUrl;
|
|
60
|
+
if (!appConfig.envKeys.includes(urlKey)) appConfig.envKeys.push(urlKey);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
appConfig.env["DB_TOKEN"] = "[hidden]";
|
|
64
|
+
appConfig.secretKeys = appConfig.secretKeys.filter((k) => k !== "DB_TOKEN");
|
|
65
|
+
appConfig.secretKeys.push("DB_TOKEN");
|
|
66
|
+
appConfig.envKeys = appConfig.envKeys.filter((k) => k !== "DB_TOKEN");
|
|
67
|
+
|
|
68
|
+
await appProvider.pushAppConfig(appCfg, name, appConfig, {
|
|
69
|
+
newSecrets: { DB_TOKEN: result.dbToken },
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Persist db cloud in .relight.yaml
|
|
73
|
+
var linked = readLink();
|
|
74
|
+
if (linked && !linked.db) {
|
|
75
|
+
linkApp(linked.app, linked.cloud, linked.dns, dbCloud);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (options.json) {
|
|
80
|
+
console.log(JSON.stringify({
|
|
81
|
+
name,
|
|
82
|
+
dbId: result.dbId,
|
|
83
|
+
dbName: result.dbName,
|
|
84
|
+
dbToken: result.dbToken,
|
|
85
|
+
connectionUrl: result.connectionUrl,
|
|
86
|
+
}, null, 2));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
success(`Database ${fmt.app(result.dbName)} created!`);
|
|
91
|
+
console.log(` ${fmt.bold("DB ID:")} ${result.dbId}`);
|
|
92
|
+
console.log(` ${fmt.bold("DB Name:")} ${result.dbName}`);
|
|
93
|
+
if (result.connectionUrl) {
|
|
94
|
+
console.log(` ${fmt.bold("DB URL:")} ${fmt.url(result.connectionUrl)}`);
|
|
95
|
+
}
|
|
96
|
+
console.log(` ${fmt.bold("Token:")} ${result.dbToken}`);
|
|
97
|
+
if (crossCloud) {
|
|
98
|
+
console.log(` ${fmt.bold("DB Cloud:")} ${fmt.cloud(dbCloud)}`);
|
|
99
|
+
}
|
|
100
|
+
hint("Next", `relight db shell ${name}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function dbDestroy(name, options) {
|
|
104
|
+
name = resolveAppName(name);
|
|
105
|
+
var { appCloud, dbCloud, crossCloud } = resolveDbClouds(options);
|
|
106
|
+
|
|
107
|
+
if (options.confirm !== name) {
|
|
108
|
+
if (process.stdin.isTTY) {
|
|
109
|
+
var rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
110
|
+
var answer = await new Promise((resolve) =>
|
|
111
|
+
rl.question(`Type "${name}" to confirm database destruction: `, resolve)
|
|
112
|
+
);
|
|
113
|
+
rl.close();
|
|
114
|
+
if (answer.trim() !== name) {
|
|
115
|
+
fatal("Confirmation did not match. Aborting.");
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
fatal(
|
|
119
|
+
`Destroying database requires confirmation.`,
|
|
120
|
+
`Run: relight db destroy ${name} --confirm ${name}`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
phase("Destroying database");
|
|
126
|
+
|
|
127
|
+
if (crossCloud) {
|
|
128
|
+
// Read dbId from app cloud config
|
|
129
|
+
var appCfg = getCloudCfg(appCloud);
|
|
130
|
+
var appProvider = await getProvider(appCloud, "app");
|
|
131
|
+
var appConfig = await appProvider.getAppConfig(appCfg, name);
|
|
132
|
+
if (!appConfig || !appConfig.dbId) {
|
|
133
|
+
fatal(`App ${name} does not have a database.`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
var dbId = appConfig.dbId;
|
|
137
|
+
var dbCfg = getCloudCfg(dbCloud);
|
|
138
|
+
var dbProvider = await getProvider(dbCloud, "db");
|
|
139
|
+
|
|
140
|
+
// Destroy DB on db cloud
|
|
141
|
+
try {
|
|
142
|
+
await dbProvider.destroyDatabase(dbCfg, name, { dbId });
|
|
143
|
+
} catch (e) {
|
|
144
|
+
fatal(e.message);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Clean up app config on app cloud
|
|
148
|
+
status(`Cleaning up app config on ${appCloud}...`);
|
|
149
|
+
delete appConfig.dbId;
|
|
150
|
+
delete appConfig.dbName;
|
|
151
|
+
|
|
152
|
+
if (appConfig.env) {
|
|
153
|
+
delete appConfig.env["DB_URL"];
|
|
154
|
+
delete appConfig.env["DB_TOKEN"];
|
|
155
|
+
delete appConfig.env["DATABASE_URL"];
|
|
156
|
+
}
|
|
157
|
+
if (appConfig.envKeys) appConfig.envKeys = appConfig.envKeys.filter((k) => k !== "DB_URL" && k !== "DATABASE_URL");
|
|
158
|
+
if (appConfig.secretKeys) appConfig.secretKeys = appConfig.secretKeys.filter((k) => k !== "DB_TOKEN");
|
|
159
|
+
|
|
160
|
+
await appProvider.pushAppConfig(appCfg, name, appConfig);
|
|
161
|
+
} else {
|
|
162
|
+
var dbCfg = getCloudCfg(dbCloud);
|
|
163
|
+
var dbProvider = await getProvider(dbCloud, "db");
|
|
164
|
+
try {
|
|
165
|
+
await dbProvider.destroyDatabase(dbCfg, name);
|
|
166
|
+
} catch (e) {
|
|
167
|
+
fatal(e.message);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
success(`Database for ${fmt.app(name)} destroyed.`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// In cross-cloud mode, read dbId from app cloud config
|
|
175
|
+
async function getDbIdFromAppCloud(appCloud, name) {
|
|
176
|
+
var appCfg = getCloudCfg(appCloud);
|
|
177
|
+
var appProvider = await getProvider(appCloud, "app");
|
|
178
|
+
var appConfig = await appProvider.getAppConfig(appCfg, name);
|
|
179
|
+
if (!appConfig || !appConfig.dbId) {
|
|
180
|
+
throw new Error(`App ${name} does not have a database.`);
|
|
181
|
+
}
|
|
182
|
+
return appConfig.dbId;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export async function dbInfo(name, options) {
|
|
186
|
+
name = resolveAppName(name);
|
|
187
|
+
var { appCloud, dbCloud, crossCloud } = resolveDbClouds(options);
|
|
188
|
+
var dbCfg = getCloudCfg(dbCloud);
|
|
189
|
+
var dbProvider = await getProvider(dbCloud, "db");
|
|
190
|
+
|
|
191
|
+
var dbId = crossCloud ? await getDbIdFromAppCloud(appCloud, name) : undefined;
|
|
192
|
+
|
|
193
|
+
var info;
|
|
194
|
+
try {
|
|
195
|
+
info = await dbProvider.getDatabaseInfo(dbCfg, name, { dbId });
|
|
196
|
+
} catch (e) {
|
|
197
|
+
fatal(e.message);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (options.json) {
|
|
201
|
+
console.log(JSON.stringify({
|
|
202
|
+
name,
|
|
203
|
+
dbId: info.dbId,
|
|
204
|
+
dbName: info.dbName,
|
|
205
|
+
connectionUrl: info.connectionUrl,
|
|
206
|
+
size: info.size,
|
|
207
|
+
numTables: info.numTables,
|
|
208
|
+
createdAt: info.createdAt,
|
|
209
|
+
}, null, 2));
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
console.log("");
|
|
214
|
+
console.log(`${fmt.bold("Database:")} ${fmt.app(info.dbName)}`);
|
|
215
|
+
console.log(`${fmt.bold("DB ID:")} ${info.dbId}`);
|
|
216
|
+
if (info.size != null) {
|
|
217
|
+
var sizeKb = (info.size / 1024).toFixed(1);
|
|
218
|
+
console.log(`${fmt.bold("Size:")} ${sizeKb} KB`);
|
|
219
|
+
}
|
|
220
|
+
if (info.numTables != null) {
|
|
221
|
+
console.log(`${fmt.bold("Tables:")} ${info.numTables}`);
|
|
222
|
+
}
|
|
223
|
+
if (info.connectionUrl) {
|
|
224
|
+
console.log(`${fmt.bold("DB URL:")} ${fmt.url(info.connectionUrl)}`);
|
|
225
|
+
}
|
|
226
|
+
console.log(`${fmt.bold("Token:")} ${fmt.dim("[hidden]")}`);
|
|
227
|
+
if (info.createdAt) {
|
|
228
|
+
console.log(`${fmt.bold("Created:")} ${info.createdAt}`);
|
|
229
|
+
}
|
|
230
|
+
console.log("");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export async function dbShell(name, options) {
|
|
234
|
+
name = resolveAppName(name);
|
|
235
|
+
var { appCloud, dbCloud, crossCloud } = resolveDbClouds(options);
|
|
236
|
+
var dbCfg = getCloudCfg(dbCloud);
|
|
237
|
+
var dbProvider = await getProvider(dbCloud, "db");
|
|
238
|
+
|
|
239
|
+
var dbId = crossCloud ? await getDbIdFromAppCloud(appCloud, name) : undefined;
|
|
240
|
+
|
|
241
|
+
// Verify database exists
|
|
242
|
+
try {
|
|
243
|
+
await dbProvider.getDatabaseInfo(dbCfg, name, { dbId });
|
|
244
|
+
} catch (e) {
|
|
245
|
+
fatal(e.message);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
var rl = createInterface({
|
|
249
|
+
input: process.stdin,
|
|
250
|
+
output: process.stderr,
|
|
251
|
+
prompt: "sql> ",
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
process.stderr.write(`Connected to ${fmt.app(`relight-${name}`)}. Type .exit to quit.\n\n`);
|
|
255
|
+
rl.prompt();
|
|
256
|
+
|
|
257
|
+
rl.on("line", async (line) => {
|
|
258
|
+
line = line.trim();
|
|
259
|
+
if (!line) {
|
|
260
|
+
rl.prompt();
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (line === ".exit" || line === ".quit") {
|
|
265
|
+
rl.close();
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
var sql;
|
|
271
|
+
if (line === ".tables") {
|
|
272
|
+
if (dbCloud === "gcp" || dbCloud === "aws") {
|
|
273
|
+
sql = "SELECT tablename AS name FROM pg_tables WHERE schemaname = 'public' ORDER BY tablename";
|
|
274
|
+
} else {
|
|
275
|
+
sql = "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '_cf_%' ORDER BY name";
|
|
276
|
+
}
|
|
277
|
+
} else if (line.startsWith(".schema")) {
|
|
278
|
+
var tableName = line.split(/\s+/)[1];
|
|
279
|
+
if (!tableName) {
|
|
280
|
+
process.stderr.write("Usage: .schema <table>\n");
|
|
281
|
+
rl.prompt();
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (dbCloud === "gcp" || dbCloud === "aws") {
|
|
285
|
+
sql = `SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_name = '${tableName}' AND table_schema = 'public' ORDER BY ordinal_position`;
|
|
286
|
+
} else {
|
|
287
|
+
sql = `SELECT sql FROM sqlite_master WHERE name='${tableName}'`;
|
|
288
|
+
}
|
|
289
|
+
} else {
|
|
290
|
+
sql = line;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
var results = await dbProvider.queryDatabase(dbCfg, name, sql, undefined, { dbId });
|
|
294
|
+
var result = Array.isArray(results) ? results[0] : results;
|
|
295
|
+
|
|
296
|
+
if (result && result.results && result.results.length > 0) {
|
|
297
|
+
var cols = Object.keys(result.results[0]);
|
|
298
|
+
var rows = result.results.map((r) => cols.map((c) => String(r[c] ?? "")));
|
|
299
|
+
console.log(table(cols, rows));
|
|
300
|
+
} else if (result && result.meta) {
|
|
301
|
+
process.stderr.write(
|
|
302
|
+
fmt.dim(`OK. ${result.meta.changes || 0} changes, ${result.meta.rows_read || 0} rows read.\n`)
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
} catch (e) {
|
|
306
|
+
process.stderr.write(`${fmt.dim("Error:")} ${e.message}\n`);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
rl.prompt();
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
rl.on("close", () => {
|
|
313
|
+
process.stderr.write("\n");
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
await new Promise((resolve) => rl.on("close", resolve));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export async function dbQuery(args, options) {
|
|
320
|
+
var name;
|
|
321
|
+
var sql;
|
|
322
|
+
var joined = args.join(" ");
|
|
323
|
+
|
|
324
|
+
var sqlKeywords = /^(SELECT|INSERT|UPDATE|DELETE|CREATE|DROP|ALTER|PRAGMA|WITH|EXPLAIN|BEGIN|COMMIT|ROLLBACK|REPLACE|VACUUM|REINDEX|ATTACH|DETACH)\b/i;
|
|
325
|
+
if (args.length >= 2 && !args[0].includes(" ") && !sqlKeywords.test(args[0])) {
|
|
326
|
+
name = args[0];
|
|
327
|
+
sql = args.slice(1).join(" ");
|
|
328
|
+
} else {
|
|
329
|
+
sql = joined;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
name = resolveAppName(name);
|
|
333
|
+
var { appCloud, dbCloud, crossCloud } = resolveDbClouds(options);
|
|
334
|
+
var dbCfg = getCloudCfg(dbCloud);
|
|
335
|
+
var dbProvider = await getProvider(dbCloud, "db");
|
|
336
|
+
|
|
337
|
+
var dbId = crossCloud ? await getDbIdFromAppCloud(appCloud, name) : undefined;
|
|
338
|
+
|
|
339
|
+
var results;
|
|
340
|
+
try {
|
|
341
|
+
results = await dbProvider.queryDatabase(dbCfg, name, sql, undefined, { dbId });
|
|
342
|
+
} catch (e) {
|
|
343
|
+
fatal(e.message);
|
|
344
|
+
}
|
|
345
|
+
var result = Array.isArray(results) ? results[0] : results;
|
|
346
|
+
|
|
347
|
+
if (options.json) {
|
|
348
|
+
console.log(JSON.stringify(result, null, 2));
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (result && result.results && result.results.length > 0) {
|
|
353
|
+
var cols = Object.keys(result.results[0]);
|
|
354
|
+
var rows = result.results.map((r) => cols.map((c) => String(r[c] ?? "")));
|
|
355
|
+
console.log(table(cols, rows));
|
|
356
|
+
} else if (result && result.meta) {
|
|
357
|
+
process.stderr.write(
|
|
358
|
+
fmt.dim(`OK. ${result.meta.changes || 0} changes, ${result.meta.rows_read || 0} rows read.\n`)
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export async function dbImport(args, options) {
|
|
364
|
+
var name;
|
|
365
|
+
var filepath;
|
|
366
|
+
if (args.length >= 2) {
|
|
367
|
+
name = args[0];
|
|
368
|
+
filepath = args[1];
|
|
369
|
+
} else if (args.length === 1) {
|
|
370
|
+
filepath = args[0];
|
|
371
|
+
} else {
|
|
372
|
+
fatal("Usage: relight db import [name] <path>");
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
name = resolveAppName(name);
|
|
376
|
+
var { appCloud, dbCloud, crossCloud } = resolveDbClouds(options);
|
|
377
|
+
var dbCfg = getCloudCfg(dbCloud);
|
|
378
|
+
var dbProvider = await getProvider(dbCloud, "db");
|
|
379
|
+
|
|
380
|
+
var dbId = crossCloud ? await getDbIdFromAppCloud(appCloud, name) : undefined;
|
|
381
|
+
|
|
382
|
+
var sqlContent;
|
|
383
|
+
try {
|
|
384
|
+
sqlContent = readFileSync(filepath, "utf-8");
|
|
385
|
+
} catch (e) {
|
|
386
|
+
fatal(`Could not read file: ${filepath}`, e.message);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
phase("Importing SQL");
|
|
390
|
+
status(`File: ${filepath} (${(sqlContent.length / 1024).toFixed(1)} KB)`);
|
|
391
|
+
|
|
392
|
+
try {
|
|
393
|
+
await dbProvider.importDatabase(dbCfg, name, sqlContent, { dbId });
|
|
394
|
+
} catch (e) {
|
|
395
|
+
fatal(e.message);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
success(`Imported ${filepath} into ${fmt.app(`relight-${name}`)}`);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export async function dbExport(name, options) {
|
|
402
|
+
name = resolveAppName(name);
|
|
403
|
+
var { appCloud, dbCloud, crossCloud } = resolveDbClouds(options);
|
|
404
|
+
var dbCfg = getCloudCfg(dbCloud);
|
|
405
|
+
var dbProvider = await getProvider(dbCloud, "db");
|
|
406
|
+
|
|
407
|
+
var dbId = crossCloud ? await getDbIdFromAppCloud(appCloud, name) : undefined;
|
|
408
|
+
|
|
409
|
+
phase("Exporting database");
|
|
410
|
+
status("Initiating export...");
|
|
411
|
+
|
|
412
|
+
var dump;
|
|
413
|
+
try {
|
|
414
|
+
dump = await dbProvider.exportDatabase(dbCfg, name, { dbId });
|
|
415
|
+
} catch (e) {
|
|
416
|
+
fatal(e.message);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (options.output) {
|
|
420
|
+
writeFileSync(options.output, dump);
|
|
421
|
+
success(`Exported to ${options.output}`);
|
|
422
|
+
} else {
|
|
423
|
+
process.stdout.write(dump);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export async function dbToken(name, options) {
|
|
428
|
+
name = resolveAppName(name);
|
|
429
|
+
var { appCloud, dbCloud, crossCloud } = resolveDbClouds(options);
|
|
430
|
+
var dbCfg = getCloudCfg(dbCloud);
|
|
431
|
+
var dbProvider = await getProvider(dbCloud, "db");
|
|
432
|
+
|
|
433
|
+
if (options.rotate) {
|
|
434
|
+
var dbId = crossCloud ? await getDbIdFromAppCloud(appCloud, name) : undefined;
|
|
435
|
+
|
|
436
|
+
var result;
|
|
437
|
+
try {
|
|
438
|
+
result = await dbProvider.rotateToken(dbCfg, name, {
|
|
439
|
+
dbId,
|
|
440
|
+
skipAppConfig: crossCloud,
|
|
441
|
+
});
|
|
442
|
+
} catch (e) {
|
|
443
|
+
fatal(e.message);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Cross-cloud: update env vars on app cloud
|
|
447
|
+
if (crossCloud) {
|
|
448
|
+
var appCfg = getCloudCfg(appCloud);
|
|
449
|
+
var appProvider = await getProvider(appCloud, "app");
|
|
450
|
+
var appConfig = await appProvider.getAppConfig(appCfg, name);
|
|
451
|
+
|
|
452
|
+
if (!appConfig.envKeys) appConfig.envKeys = [];
|
|
453
|
+
if (!appConfig.secretKeys) appConfig.secretKeys = [];
|
|
454
|
+
if (!appConfig.env) appConfig.env = {};
|
|
455
|
+
|
|
456
|
+
appConfig.env["DB_TOKEN"] = "[hidden]";
|
|
457
|
+
if (!appConfig.secretKeys.includes("DB_TOKEN")) appConfig.secretKeys.push("DB_TOKEN");
|
|
458
|
+
appConfig.envKeys = appConfig.envKeys.filter((k) => k !== "DB_TOKEN");
|
|
459
|
+
|
|
460
|
+
if (result.connectionUrl) {
|
|
461
|
+
var urlKey = dbCloud === "cf" ? "DB_URL" : "DATABASE_URL";
|
|
462
|
+
appConfig.env[urlKey] = result.connectionUrl;
|
|
463
|
+
if (!appConfig.envKeys.includes(urlKey)) appConfig.envKeys.push(urlKey);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
await appProvider.pushAppConfig(appCfg, name, appConfig, {
|
|
467
|
+
newSecrets: { DB_TOKEN: result.dbToken },
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
success("Token rotated.");
|
|
472
|
+
console.log(`${fmt.bold("Token:")} ${result.dbToken}`);
|
|
473
|
+
if (result.connectionUrl) {
|
|
474
|
+
console.log(`${fmt.bold("DB URL:")} ${fmt.url(result.connectionUrl)}`);
|
|
475
|
+
}
|
|
476
|
+
} else {
|
|
477
|
+
console.log(`${fmt.bold("Token:")} ${fmt.dim("[hidden] - use --rotate to generate a new token")}`);
|
|
478
|
+
// Try to show connection URL
|
|
479
|
+
try {
|
|
480
|
+
var dbId = crossCloud ? await getDbIdFromAppCloud(appCloud, name) : undefined;
|
|
481
|
+
var info = await dbProvider.getDatabaseInfo(dbCfg, name, { dbId });
|
|
482
|
+
if (info.connectionUrl) {
|
|
483
|
+
console.log(`${fmt.bold("DB URL:")} ${fmt.url(info.connectionUrl)}`);
|
|
484
|
+
}
|
|
485
|
+
} catch {}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
export async function dbReset(name, options) {
|
|
490
|
+
name = resolveAppName(name);
|
|
491
|
+
var { appCloud, dbCloud, crossCloud } = resolveDbClouds(options);
|
|
492
|
+
var dbCfg = getCloudCfg(dbCloud);
|
|
493
|
+
var dbProvider = await getProvider(dbCloud, "db");
|
|
494
|
+
|
|
495
|
+
if (options.confirm !== name) {
|
|
496
|
+
if (process.stdin.isTTY) {
|
|
497
|
+
var rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
498
|
+
var answer = await new Promise((resolve) =>
|
|
499
|
+
rl.question(`Type "${name}" to confirm database reset: `, resolve)
|
|
500
|
+
);
|
|
501
|
+
rl.close();
|
|
502
|
+
if (answer.trim() !== name) {
|
|
503
|
+
fatal("Confirmation did not match. Aborting.");
|
|
504
|
+
}
|
|
505
|
+
} else {
|
|
506
|
+
fatal(
|
|
507
|
+
`Resetting database requires confirmation.`,
|
|
508
|
+
`Run: relight db reset ${name} --confirm ${name}`
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
var dbId = crossCloud ? await getDbIdFromAppCloud(appCloud, name) : undefined;
|
|
514
|
+
|
|
515
|
+
phase("Resetting database");
|
|
516
|
+
status("Listing tables...");
|
|
517
|
+
|
|
518
|
+
var tables;
|
|
519
|
+
try {
|
|
520
|
+
tables = await dbProvider.resetDatabase(dbCfg, name, { dbId });
|
|
521
|
+
} catch (e) {
|
|
522
|
+
fatal(e.message);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (tables.length === 0) {
|
|
526
|
+
process.stderr.write("No user tables found.\n");
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
success(`Dropped ${tables.length} table${tables.length === 1 ? "" : "s"}.`);
|
|
531
|
+
}
|