create-db 1.1.1-pr66-version-bump-19346362281.0 ā 1.1.1-pr67-overhaul-20068192317.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/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +8 -0
- package/dist/index.d.mts +84 -0
- package/dist/index.mjs +4 -0
- package/dist/src-CFVkopQv.mjs +528 -0
- package/package.json +33 -15
- package/index.js +0 -836
package/index.js
DELETED
|
@@ -1,836 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { select, spinner, intro, outro, log, cancel } from "@clack/prompts";
|
|
4
|
-
import { randomUUID } from "crypto";
|
|
5
|
-
import dotenv from "dotenv";
|
|
6
|
-
import fs from "fs";
|
|
7
|
-
import path from "path";
|
|
8
|
-
import terminalLink from "terminal-link";
|
|
9
|
-
import chalk from "chalk";
|
|
10
|
-
|
|
11
|
-
dotenv.config();
|
|
12
|
-
|
|
13
|
-
const CREATE_DB_WORKER_URL =
|
|
14
|
-
process.env.CREATE_DB_WORKER_URL || "https://create-db-temp.prisma.io";
|
|
15
|
-
const CLAIM_DB_WORKER_URL =
|
|
16
|
-
process.env.CLAIM_DB_WORKER_URL || "https://create-db.prisma.io";
|
|
17
|
-
|
|
18
|
-
// Track pending analytics promises to ensure they complete before exit
|
|
19
|
-
const pendingAnalytics = [];
|
|
20
|
-
|
|
21
|
-
async function sendAnalyticsToWorker(eventName, properties, cliRunId) {
|
|
22
|
-
const controller = new AbortController();
|
|
23
|
-
const timer = setTimeout(() => controller.abort(), 5000);
|
|
24
|
-
|
|
25
|
-
const analyticsPromise = (async () => {
|
|
26
|
-
try {
|
|
27
|
-
const payload = {
|
|
28
|
-
eventName,
|
|
29
|
-
properties: { distinct_id: cliRunId, ...(properties || {}) },
|
|
30
|
-
};
|
|
31
|
-
await fetch(`${CREATE_DB_WORKER_URL}/analytics`, {
|
|
32
|
-
method: "POST",
|
|
33
|
-
headers: { "Content-Type": "application/json" },
|
|
34
|
-
body: JSON.stringify(payload),
|
|
35
|
-
signal: controller.signal,
|
|
36
|
-
});
|
|
37
|
-
} catch (error) {
|
|
38
|
-
// Silently fail - analytics shouldn't block CLI
|
|
39
|
-
} finally {
|
|
40
|
-
clearTimeout(timer);
|
|
41
|
-
}
|
|
42
|
-
})();
|
|
43
|
-
|
|
44
|
-
pendingAnalytics.push(analyticsPromise);
|
|
45
|
-
return analyticsPromise;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Wait for all pending analytics with a timeout
|
|
49
|
-
async function flushAnalytics(maxWaitMs = 500) {
|
|
50
|
-
if (pendingAnalytics.length === 0) return;
|
|
51
|
-
|
|
52
|
-
const timeout = new Promise((resolve) => setTimeout(resolve, maxWaitMs));
|
|
53
|
-
const allAnalytics = Promise.all(pendingAnalytics);
|
|
54
|
-
|
|
55
|
-
await Promise.race([allAnalytics, timeout]);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async function detectUserLocation() {
|
|
59
|
-
try {
|
|
60
|
-
const response = await fetch("https://ipapi.co/json/", {
|
|
61
|
-
method: "GET",
|
|
62
|
-
headers: {
|
|
63
|
-
"User-Agent": "create-db-cli/1.0",
|
|
64
|
-
},
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
if (!response.ok) {
|
|
68
|
-
throw new Error(`Failed to fetch location data: ${response.status}`);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const data = await response.json();
|
|
72
|
-
return {
|
|
73
|
-
country: data.country_code,
|
|
74
|
-
continent: data.continent_code,
|
|
75
|
-
city: data.city,
|
|
76
|
-
region: data.region,
|
|
77
|
-
latitude: data.latitude,
|
|
78
|
-
longitude: data.longitude,
|
|
79
|
-
};
|
|
80
|
-
} catch (error) {
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const REGION_COORDINATES = {
|
|
86
|
-
"ap-southeast-1": { lat: 1.3521, lng: 103.8198 }, // Singapore
|
|
87
|
-
"ap-northeast-1": { lat: 35.6762, lng: 139.6503 }, // Tokyo
|
|
88
|
-
"eu-central-1": { lat: 50.1109, lng: 8.6821 }, // Frankfurt
|
|
89
|
-
"eu-west-3": { lat: 48.8566, lng: 2.3522 }, // Paris
|
|
90
|
-
"us-east-1": { lat: 38.9072, lng: -77.0369 }, // N. Virginia
|
|
91
|
-
"us-west-1": { lat: 37.7749, lng: -122.4194 }, // N. California
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
export function getRegionClosestToLocation(userLocation) {
|
|
95
|
-
if (!userLocation) return null;
|
|
96
|
-
|
|
97
|
-
const userLat = parseFloat(userLocation.latitude);
|
|
98
|
-
const userLng = parseFloat(userLocation.longitude);
|
|
99
|
-
|
|
100
|
-
let closestRegion = null;
|
|
101
|
-
let minDistance = Infinity;
|
|
102
|
-
|
|
103
|
-
for (const [region, coordinates] of Object.entries(REGION_COORDINATES)) {
|
|
104
|
-
// Simple distance calculation using Haversine formula
|
|
105
|
-
const latDiff = ((userLat - coordinates.lat) * Math.PI) / 180;
|
|
106
|
-
const lngDiff = ((userLng - coordinates.lng) * Math.PI) / 180;
|
|
107
|
-
const a =
|
|
108
|
-
Math.sin(latDiff / 2) * Math.sin(latDiff / 2) +
|
|
109
|
-
Math.cos((userLat * Math.PI) / 180) *
|
|
110
|
-
Math.cos((coordinates.lat * Math.PI) / 180) *
|
|
111
|
-
Math.sin(lngDiff / 2) *
|
|
112
|
-
Math.sin(lngDiff / 2);
|
|
113
|
-
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
114
|
-
const distance = 6371 * c; // Earth radius in km
|
|
115
|
-
|
|
116
|
-
if (distance < minDistance) {
|
|
117
|
-
minDistance = distance;
|
|
118
|
-
closestRegion = region;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return closestRegion;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
async function listRegions() {
|
|
126
|
-
try {
|
|
127
|
-
const regions = await getRegions();
|
|
128
|
-
console.log(chalk.cyan.bold("\nš Available Prisma Postgres regions:\n"));
|
|
129
|
-
regions.forEach((r) =>
|
|
130
|
-
console.log(`- ${chalk.green(r.id)}, ${r.name || r.id}`)
|
|
131
|
-
);
|
|
132
|
-
console.log("");
|
|
133
|
-
} catch (e) {
|
|
134
|
-
handleError("Failed to fetch regions.", e);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
async function isOffline() {
|
|
139
|
-
const healthUrl = `${CREATE_DB_WORKER_URL}/health`;
|
|
140
|
-
|
|
141
|
-
try {
|
|
142
|
-
const res = await fetch(healthUrl, { method: "GET" });
|
|
143
|
-
if (!res.ok) {
|
|
144
|
-
throw new Error(`Prisma Postgres API returned a status of ${res.status}`);
|
|
145
|
-
}
|
|
146
|
-
return false; // Online
|
|
147
|
-
} catch {
|
|
148
|
-
console.error(
|
|
149
|
-
chalk.red.bold("\nā Error: Cannot reach Prisma Postgres API server.\n")
|
|
150
|
-
);
|
|
151
|
-
console.error(
|
|
152
|
-
chalk.gray(
|
|
153
|
-
`Check your internet connection or visit ${chalk.green("https://www.prisma-status.com/\n")}`
|
|
154
|
-
)
|
|
155
|
-
);
|
|
156
|
-
await flushAnalytics();
|
|
157
|
-
process.exit(1);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export function getCommandName() {
|
|
162
|
-
const executable = process.argv[1] || "create-db";
|
|
163
|
-
if (executable.includes("create-pg")) return "create-pg";
|
|
164
|
-
if (executable.includes("create-postgres")) return "create-postgres";
|
|
165
|
-
return "create-db";
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const CLI_NAME = getCommandName();
|
|
169
|
-
|
|
170
|
-
function readUserEnvFile() {
|
|
171
|
-
const userCwd = process.cwd();
|
|
172
|
-
const envPath = path.join(userCwd, ".env");
|
|
173
|
-
|
|
174
|
-
if (!fs.existsSync(envPath)) {
|
|
175
|
-
return {};
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const envContent = fs.readFileSync(envPath, "utf8");
|
|
179
|
-
const envVars = {};
|
|
180
|
-
|
|
181
|
-
envContent.split("\n").forEach((line) => {
|
|
182
|
-
const trimmed = line.trim();
|
|
183
|
-
if (trimmed && !trimmed.startsWith("#")) {
|
|
184
|
-
const [key, ...valueParts] = trimmed.split("=");
|
|
185
|
-
if (key && valueParts.length > 0) {
|
|
186
|
-
const value = valueParts.join("=").replace(/^["']|["']$/g, "");
|
|
187
|
-
envVars[key.trim()] = value.trim();
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
return envVars;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
async function showHelp() {
|
|
196
|
-
let regionExamples = "us-east-1, eu-west-1";
|
|
197
|
-
try {
|
|
198
|
-
const regions = await getRegions();
|
|
199
|
-
if (regions && regions.length > 0) {
|
|
200
|
-
regionExamples = regions.map((r) => r.id).join(", ");
|
|
201
|
-
}
|
|
202
|
-
} catch {}
|
|
203
|
-
|
|
204
|
-
console.log(`
|
|
205
|
-
${chalk.cyan.bold("Prisma Postgres Create DB")}
|
|
206
|
-
|
|
207
|
-
Usage:
|
|
208
|
-
${chalk.green(`npx ${CLI_NAME} [options]`)}
|
|
209
|
-
|
|
210
|
-
Options:
|
|
211
|
-
${chalk.yellow(`--region <region>, -r <region>`)} Specify the region (e.g., ${regionExamples})
|
|
212
|
-
${chalk.yellow("--interactive, -i")} Run in interactive mode to select a region and create the database
|
|
213
|
-
${chalk.yellow("--json, -j")} Output machine-readable JSON and exit
|
|
214
|
-
${chalk.yellow("--list-regions")} List available regions and exit
|
|
215
|
-
${chalk.yellow("--help, -h")} Show this help message
|
|
216
|
-
${chalk.yellow("--env, -e")} Prints DATABASE_URL to the terminal. ${chalk.gray(`To write to .env, use --env >> .env`)}
|
|
217
|
-
|
|
218
|
-
Examples:
|
|
219
|
-
${chalk.gray(`npx ${CLI_NAME} --region us-east-1`)}
|
|
220
|
-
${chalk.gray(`npx ${CLI_NAME} -r us-east-1`)}
|
|
221
|
-
${chalk.gray(`npx ${CLI_NAME} --interactive`)}
|
|
222
|
-
${chalk.gray(`npx ${CLI_NAME} -i`)}
|
|
223
|
-
${chalk.gray(`npx ${CLI_NAME} --json --region us-east-1`)}
|
|
224
|
-
${chalk.gray(`npx ${CLI_NAME} --env --region us-east-1`)}
|
|
225
|
-
${chalk.gray(`npx ${CLI_NAME} --env >> .env`)}
|
|
226
|
-
`);
|
|
227
|
-
await flushAnalytics();
|
|
228
|
-
process.exit(0);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
async function parseArgs() {
|
|
232
|
-
const args = process.argv.slice(2);
|
|
233
|
-
const flags = {};
|
|
234
|
-
|
|
235
|
-
const allowedFlags = [
|
|
236
|
-
"region",
|
|
237
|
-
"help",
|
|
238
|
-
"list-regions",
|
|
239
|
-
"interactive",
|
|
240
|
-
"json",
|
|
241
|
-
"env",
|
|
242
|
-
];
|
|
243
|
-
const shorthandMap = {
|
|
244
|
-
r: "region",
|
|
245
|
-
i: "interactive",
|
|
246
|
-
h: "help",
|
|
247
|
-
j: "json",
|
|
248
|
-
e: "env",
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
const exitWithError = (message) => {
|
|
252
|
-
console.error(chalk.red.bold("\nā " + message));
|
|
253
|
-
console.error(chalk.gray("\nUse --help or -h to see available options.\n"));
|
|
254
|
-
process.exit(1);
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
for (let i = 0; i < args.length; i++) {
|
|
258
|
-
const arg = args[i];
|
|
259
|
-
|
|
260
|
-
if (arg.startsWith("--")) {
|
|
261
|
-
const flag = arg.slice(2);
|
|
262
|
-
if (flag === "help") await showHelp();
|
|
263
|
-
if (!allowedFlags.includes(flag))
|
|
264
|
-
exitWithError(`Invalid flag: --${flag}`);
|
|
265
|
-
if (flag === "region") {
|
|
266
|
-
const region = args[i + 1];
|
|
267
|
-
if (!region || region.startsWith("-"))
|
|
268
|
-
exitWithError("Missing value for --region flag.");
|
|
269
|
-
flags.region = region;
|
|
270
|
-
i++;
|
|
271
|
-
} else {
|
|
272
|
-
flags[flag] = true;
|
|
273
|
-
}
|
|
274
|
-
continue;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
if (arg.startsWith("-")) {
|
|
278
|
-
const short = arg.slice(1);
|
|
279
|
-
|
|
280
|
-
if (shorthandMap[short]) {
|
|
281
|
-
const mappedFlag = shorthandMap[short];
|
|
282
|
-
if (mappedFlag === "help") showHelp();
|
|
283
|
-
if (mappedFlag === "region") {
|
|
284
|
-
const region = args[i + 1];
|
|
285
|
-
if (!region || region.startsWith("-"))
|
|
286
|
-
exitWithError("Missing value for -r flag.");
|
|
287
|
-
flags.region = region;
|
|
288
|
-
i++;
|
|
289
|
-
} else {
|
|
290
|
-
flags[mappedFlag] = true;
|
|
291
|
-
}
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
for (const letter of short.split("")) {
|
|
296
|
-
const mappedFlag = shorthandMap[letter];
|
|
297
|
-
if (!mappedFlag) exitWithError(`Invalid flag: -${letter}`);
|
|
298
|
-
if (mappedFlag === "help") {
|
|
299
|
-
await showHelp();
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
if (mappedFlag === "region") {
|
|
303
|
-
const region = args[i + 1];
|
|
304
|
-
if (!region || region.startsWith("-"))
|
|
305
|
-
exitWithError("Missing value for -r flag.");
|
|
306
|
-
flags.region = region;
|
|
307
|
-
i++;
|
|
308
|
-
} else {
|
|
309
|
-
flags[mappedFlag] = true;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
continue;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
exitWithError(`Invalid argument: ${arg}`);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
return { flags };
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
function validateFlagCombinations(flags) {
|
|
322
|
-
const conflictingFlags = [
|
|
323
|
-
["env", "json"],
|
|
324
|
-
["list-regions", "env"],
|
|
325
|
-
["list-regions", "json"],
|
|
326
|
-
["list-regions", "interactive"],
|
|
327
|
-
["list-regions", "region"],
|
|
328
|
-
["interactive", "env"],
|
|
329
|
-
["interactive", "json"],
|
|
330
|
-
];
|
|
331
|
-
|
|
332
|
-
for (const [flag1, flag2] of conflictingFlags) {
|
|
333
|
-
if (flags[flag1] && flags[flag2]) {
|
|
334
|
-
console.error(
|
|
335
|
-
chalk.red.bold(
|
|
336
|
-
`\nā Error: Cannot use --${flag1} and --${flag2} together.\n`
|
|
337
|
-
)
|
|
338
|
-
);
|
|
339
|
-
console.error(chalk.gray("Use --help or -h to see available options.\n"));
|
|
340
|
-
process.exit(1);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
export async function getRegions(returnJson = false) {
|
|
346
|
-
const url = `${CREATE_DB_WORKER_URL}/regions`;
|
|
347
|
-
const res = await fetch(url);
|
|
348
|
-
|
|
349
|
-
if (!res.ok) {
|
|
350
|
-
if (returnJson) {
|
|
351
|
-
throw new Error(
|
|
352
|
-
`Failed to fetch regions. Status: ${res.status} ${res.statusText}`
|
|
353
|
-
);
|
|
354
|
-
}
|
|
355
|
-
handleError(
|
|
356
|
-
`Failed to fetch regions. Status: ${res.status} ${res.statusText}`
|
|
357
|
-
);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
try {
|
|
361
|
-
const data = await res.json();
|
|
362
|
-
const regions = Array.isArray(data) ? data : data.data;
|
|
363
|
-
return regions.filter((region) => region.status === "available");
|
|
364
|
-
} catch (e) {
|
|
365
|
-
if (returnJson) {
|
|
366
|
-
throw new Error("Failed to parse JSON from /regions endpoint.");
|
|
367
|
-
}
|
|
368
|
-
handleError("Failed to parse JSON from /regions endpoint.", e);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
export async function validateRegion(region, returnJson = false) {
|
|
373
|
-
const regions = await getRegions(returnJson);
|
|
374
|
-
const regionIds = regions.map((r) => r.id);
|
|
375
|
-
|
|
376
|
-
if (!regionIds.includes(region)) {
|
|
377
|
-
if (returnJson) {
|
|
378
|
-
throw new Error(
|
|
379
|
-
`Invalid region: ${region}. Available regions: ${regionIds.join(", ")}`
|
|
380
|
-
);
|
|
381
|
-
}
|
|
382
|
-
handleError(
|
|
383
|
-
`Invalid region: ${chalk.yellow(region)}.\nAvailable regions: ${chalk.green(
|
|
384
|
-
regionIds.join(", ")
|
|
385
|
-
)}`
|
|
386
|
-
);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
return region;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
function handleError(message, extra = "") {
|
|
393
|
-
console.error(
|
|
394
|
-
"\n" +
|
|
395
|
-
chalk.red.bold("ā An error occurred!") +
|
|
396
|
-
"\n\n" +
|
|
397
|
-
chalk.white("Message: ") +
|
|
398
|
-
chalk.yellow(message) +
|
|
399
|
-
(extra
|
|
400
|
-
? "\n" + chalk.white("Available regions: ") + chalk.green(extra)
|
|
401
|
-
: "") +
|
|
402
|
-
"\n"
|
|
403
|
-
);
|
|
404
|
-
process.exit(1);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
async function promptForRegion(defaultRegion, userAgent, cliRunId) {
|
|
408
|
-
let regions;
|
|
409
|
-
try {
|
|
410
|
-
regions = await getRegions();
|
|
411
|
-
} catch (e) {
|
|
412
|
-
handleError("Failed to fetch regions.", e);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
if (!regions || regions.length === 0) {
|
|
416
|
-
handleError("No regions available to select.");
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
const region = await select({
|
|
420
|
-
message: "Choose a region:",
|
|
421
|
-
options: regions.map((r) => ({ value: r.id, label: r.id })),
|
|
422
|
-
initialValue:
|
|
423
|
-
regions.find((r) => r.id === defaultRegion)?.id || regions[0]?.id,
|
|
424
|
-
});
|
|
425
|
-
|
|
426
|
-
if (region === null) {
|
|
427
|
-
cancel(chalk.red("Operation cancelled."));
|
|
428
|
-
await flushAnalytics();
|
|
429
|
-
process.exit(0);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
void sendAnalyticsToWorker(
|
|
433
|
-
"create_db:region_selected",
|
|
434
|
-
{
|
|
435
|
-
command: CLI_NAME,
|
|
436
|
-
region: region,
|
|
437
|
-
"selection-method": "interactive",
|
|
438
|
-
"user-agent": userAgent,
|
|
439
|
-
},
|
|
440
|
-
cliRunId
|
|
441
|
-
);
|
|
442
|
-
|
|
443
|
-
return region;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
async function createDatabase(
|
|
447
|
-
name,
|
|
448
|
-
region,
|
|
449
|
-
userAgent,
|
|
450
|
-
cliRunId,
|
|
451
|
-
silent = false
|
|
452
|
-
) {
|
|
453
|
-
let s;
|
|
454
|
-
if (!silent) {
|
|
455
|
-
s = spinner();
|
|
456
|
-
s.start("Creating your database...");
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
const resp = await fetch(`${CREATE_DB_WORKER_URL}/create`, {
|
|
460
|
-
method: "POST",
|
|
461
|
-
headers: { "Content-Type": "application/json" },
|
|
462
|
-
body: JSON.stringify({
|
|
463
|
-
region,
|
|
464
|
-
name,
|
|
465
|
-
utm_source: CLI_NAME,
|
|
466
|
-
userAgent,
|
|
467
|
-
}),
|
|
468
|
-
});
|
|
469
|
-
|
|
470
|
-
if (resp.status === 429) {
|
|
471
|
-
if (silent) {
|
|
472
|
-
return {
|
|
473
|
-
error: "rate_limit_exceeded",
|
|
474
|
-
message:
|
|
475
|
-
"We're experiencing a high volume of requests. Please try again later.",
|
|
476
|
-
status: 429,
|
|
477
|
-
};
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
if (s) {
|
|
481
|
-
s.stop(
|
|
482
|
-
"We're experiencing a high volume of requests. Please try again later."
|
|
483
|
-
);
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
void sendAnalyticsToWorker(
|
|
487
|
-
"create_db:database_creation_failed",
|
|
488
|
-
{
|
|
489
|
-
command: CLI_NAME,
|
|
490
|
-
region: region,
|
|
491
|
-
"error-type": "rate_limit",
|
|
492
|
-
"status-code": 429,
|
|
493
|
-
"user-agent": userAgent,
|
|
494
|
-
},
|
|
495
|
-
cliRunId
|
|
496
|
-
);
|
|
497
|
-
|
|
498
|
-
await flushAnalytics();
|
|
499
|
-
process.exit(1);
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
let result;
|
|
503
|
-
let raw;
|
|
504
|
-
try {
|
|
505
|
-
raw = await resp.text();
|
|
506
|
-
result = JSON.parse(raw);
|
|
507
|
-
} catch (e) {
|
|
508
|
-
if (silent) {
|
|
509
|
-
return {
|
|
510
|
-
error: "invalid_json",
|
|
511
|
-
message: "Unexpected response from create service.",
|
|
512
|
-
raw,
|
|
513
|
-
status: resp.status,
|
|
514
|
-
};
|
|
515
|
-
}
|
|
516
|
-
if (s) {
|
|
517
|
-
s.stop("Unexpected response from create service.");
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
void sendAnalyticsToWorker(
|
|
521
|
-
"create_db:database_creation_failed",
|
|
522
|
-
{
|
|
523
|
-
command: CLI_NAME,
|
|
524
|
-
region,
|
|
525
|
-
"error-type": "invalid_json",
|
|
526
|
-
"status-code": resp.status,
|
|
527
|
-
"user-agent": userAgent,
|
|
528
|
-
},
|
|
529
|
-
cliRunId
|
|
530
|
-
);
|
|
531
|
-
|
|
532
|
-
await flushAnalytics();
|
|
533
|
-
process.exit(1);
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
const database = result.data ? result.data.database : result.databases?.[0];
|
|
537
|
-
const projectId = result.data ? result.data.id : result.id;
|
|
538
|
-
|
|
539
|
-
const directConnDetails = result.data
|
|
540
|
-
? database?.apiKeys?.[0]?.directConnection
|
|
541
|
-
: result.databases?.[0]?.apiKeys?.[0]?.ppgDirectConnection;
|
|
542
|
-
const directUser = directConnDetails?.user
|
|
543
|
-
? encodeURIComponent(directConnDetails.user)
|
|
544
|
-
: "";
|
|
545
|
-
const directPass = directConnDetails?.pass
|
|
546
|
-
? encodeURIComponent(directConnDetails.pass)
|
|
547
|
-
: "";
|
|
548
|
-
const directHost = directConnDetails?.host;
|
|
549
|
-
const directPort = directConnDetails?.port
|
|
550
|
-
? `:${directConnDetails.port}`
|
|
551
|
-
: "";
|
|
552
|
-
const directDbName = directConnDetails?.database || "postgres";
|
|
553
|
-
const directConn =
|
|
554
|
-
directConnDetails && directHost
|
|
555
|
-
? `postgresql://${directUser}:${directPass}@${directHost}${directPort}/${directDbName}?sslmode=require`
|
|
556
|
-
: null;
|
|
557
|
-
|
|
558
|
-
const claimUrl = `${CLAIM_DB_WORKER_URL}/claim?projectID=${projectId}&utm_source=${userAgent || CLI_NAME}&utm_medium=cli`;
|
|
559
|
-
const expiryDate = new Date(Date.now() + 24 * 60 * 60 * 1000);
|
|
560
|
-
|
|
561
|
-
if (silent && !result.error) {
|
|
562
|
-
const jsonResponse = {
|
|
563
|
-
connectionString: directConn,
|
|
564
|
-
claimUrl: claimUrl,
|
|
565
|
-
deletionDate: expiryDate.toISOString(),
|
|
566
|
-
region: database?.region?.id || region,
|
|
567
|
-
name: database?.name,
|
|
568
|
-
projectId: projectId,
|
|
569
|
-
};
|
|
570
|
-
|
|
571
|
-
if (userAgent) {
|
|
572
|
-
jsonResponse.userAgent = userAgent;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
return jsonResponse;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
if (result.error) {
|
|
579
|
-
if (silent) {
|
|
580
|
-
return {
|
|
581
|
-
error: "api_error",
|
|
582
|
-
message: result.error.message || "Unknown error",
|
|
583
|
-
details: result.error,
|
|
584
|
-
status: result.error.status ?? resp.status,
|
|
585
|
-
};
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
if (s) {
|
|
589
|
-
s.stop(
|
|
590
|
-
`Error creating database: ${result.error.message || "Unknown error"}`
|
|
591
|
-
);
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
void sendAnalyticsToWorker(
|
|
595
|
-
"create_db:database_creation_failed",
|
|
596
|
-
{
|
|
597
|
-
command: CLI_NAME,
|
|
598
|
-
region: region,
|
|
599
|
-
"error-type": "api_error",
|
|
600
|
-
"error-message": result.error.message,
|
|
601
|
-
"user-agent": userAgent,
|
|
602
|
-
},
|
|
603
|
-
cliRunId
|
|
604
|
-
);
|
|
605
|
-
|
|
606
|
-
await flushAnalytics();
|
|
607
|
-
process.exit(1);
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
if (s) {
|
|
611
|
-
s.stop("Database created successfully!");
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
const expiryFormatted = expiryDate.toLocaleString();
|
|
615
|
-
|
|
616
|
-
log.message("");
|
|
617
|
-
log.info(chalk.bold("Database Connection"));
|
|
618
|
-
log.message("");
|
|
619
|
-
|
|
620
|
-
// Direct connection (only output this one)
|
|
621
|
-
if (directConn) {
|
|
622
|
-
log.message(chalk.cyan(" Connection String:"));
|
|
623
|
-
log.message(" " + chalk.yellow(directConn));
|
|
624
|
-
log.message("");
|
|
625
|
-
} else {
|
|
626
|
-
log.warning(chalk.yellow(" Connection details are not available."));
|
|
627
|
-
log.message("");
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
// Claim database section
|
|
632
|
-
const clickableUrl = terminalLink(claimUrl, claimUrl, { fallback: false });
|
|
633
|
-
log.success(chalk.bold("Claim Your Database"));
|
|
634
|
-
log.message(chalk.cyan(" Keep your database for free:"));
|
|
635
|
-
log.message(" " + chalk.yellow(clickableUrl));
|
|
636
|
-
log.message(
|
|
637
|
-
chalk.italic(
|
|
638
|
-
chalk.gray(
|
|
639
|
-
` Database will be deleted on ${expiryFormatted} if not claimed.`
|
|
640
|
-
)
|
|
641
|
-
)
|
|
642
|
-
);
|
|
643
|
-
|
|
644
|
-
void sendAnalyticsToWorker(
|
|
645
|
-
"create_db:database_created",
|
|
646
|
-
{
|
|
647
|
-
command: CLI_NAME,
|
|
648
|
-
region,
|
|
649
|
-
utm_source: CLI_NAME,
|
|
650
|
-
},
|
|
651
|
-
cliRunId
|
|
652
|
-
);
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
export async function main() {
|
|
656
|
-
try {
|
|
657
|
-
// Generate unique ID for this CLI run
|
|
658
|
-
const cliRunId = randomUUID();
|
|
659
|
-
|
|
660
|
-
const rawArgs = process.argv.slice(2);
|
|
661
|
-
|
|
662
|
-
const { flags } = await parseArgs();
|
|
663
|
-
|
|
664
|
-
validateFlagCombinations(flags);
|
|
665
|
-
|
|
666
|
-
let userAgent;
|
|
667
|
-
const userEnvVars = readUserEnvFile();
|
|
668
|
-
if (userEnvVars.PRISMA_ACTOR_NAME && userEnvVars.PRISMA_ACTOR_PROJECT) {
|
|
669
|
-
userAgent = `${userEnvVars.PRISMA_ACTOR_NAME}/${userEnvVars.PRISMA_ACTOR_PROJECT}`;
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
void sendAnalyticsToWorker(
|
|
673
|
-
"create_db:cli_command_ran",
|
|
674
|
-
{
|
|
675
|
-
command: CLI_NAME,
|
|
676
|
-
"full-command": `${CLI_NAME} ${rawArgs.join(" ")}`.trim(),
|
|
677
|
-
"has-region-flag":
|
|
678
|
-
rawArgs.includes("--region") || rawArgs.includes("-r"),
|
|
679
|
-
"has-interactive-flag":
|
|
680
|
-
rawArgs.includes("--interactive") || rawArgs.includes("-i"),
|
|
681
|
-
"has-help-flag": rawArgs.includes("--help") || rawArgs.includes("-h"),
|
|
682
|
-
"has-list-regions-flag": rawArgs.includes("--list-regions"),
|
|
683
|
-
"has-json-flag": rawArgs.includes("--json") || rawArgs.includes("-j"),
|
|
684
|
-
"has-env-flag": rawArgs.includes("--env") || rawArgs.includes("-e"),
|
|
685
|
-
"has-user-agent-from-env": !!userAgent,
|
|
686
|
-
"node-version": process.version,
|
|
687
|
-
platform: process.platform,
|
|
688
|
-
arch: process.arch,
|
|
689
|
-
"user-agent": userAgent,
|
|
690
|
-
},
|
|
691
|
-
cliRunId
|
|
692
|
-
);
|
|
693
|
-
|
|
694
|
-
if (!flags.help && !flags.json) {
|
|
695
|
-
await isOffline();
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
let name = new Date().toISOString();
|
|
699
|
-
let region = flags.region || "us-east-1";
|
|
700
|
-
if (!flags.region || !flags.interactive) {
|
|
701
|
-
const userLocation = await detectUserLocation();
|
|
702
|
-
region = getRegionClosestToLocation(userLocation) || region;
|
|
703
|
-
}
|
|
704
|
-
let chooseRegionPrompt = false;
|
|
705
|
-
|
|
706
|
-
if (flags.help) {
|
|
707
|
-
return;
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
if (flags["list-regions"]) {
|
|
711
|
-
await listRegions();
|
|
712
|
-
await flushAnalytics();
|
|
713
|
-
process.exit(0);
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
if (flags.region) {
|
|
717
|
-
region = flags.region;
|
|
718
|
-
|
|
719
|
-
void sendAnalyticsToWorker(
|
|
720
|
-
"create_db:region_selected",
|
|
721
|
-
{
|
|
722
|
-
command: CLI_NAME,
|
|
723
|
-
region: region,
|
|
724
|
-
"selection-method": "flag",
|
|
725
|
-
"user-agent": userAgent,
|
|
726
|
-
},
|
|
727
|
-
cliRunId
|
|
728
|
-
);
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
if (flags.interactive) {
|
|
732
|
-
chooseRegionPrompt = true;
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
if (flags.json) {
|
|
736
|
-
try {
|
|
737
|
-
if (chooseRegionPrompt) {
|
|
738
|
-
region = await promptForRegion(region, userAgent, cliRunId);
|
|
739
|
-
} else {
|
|
740
|
-
await validateRegion(region, true);
|
|
741
|
-
}
|
|
742
|
-
const result = await createDatabase(
|
|
743
|
-
name,
|
|
744
|
-
region,
|
|
745
|
-
userAgent,
|
|
746
|
-
cliRunId,
|
|
747
|
-
true
|
|
748
|
-
);
|
|
749
|
-
console.log(JSON.stringify(result, null, 2));
|
|
750
|
-
await flushAnalytics();
|
|
751
|
-
process.exit(0);
|
|
752
|
-
} catch (e) {
|
|
753
|
-
console.log(
|
|
754
|
-
JSON.stringify(
|
|
755
|
-
{ error: "cli_error", message: e?.message || String(e) },
|
|
756
|
-
null,
|
|
757
|
-
2
|
|
758
|
-
)
|
|
759
|
-
);
|
|
760
|
-
await flushAnalytics();
|
|
761
|
-
process.exit(1);
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
if (flags.env) {
|
|
766
|
-
try {
|
|
767
|
-
if (chooseRegionPrompt) {
|
|
768
|
-
region = await promptForRegion(region, userAgent, cliRunId);
|
|
769
|
-
} else {
|
|
770
|
-
await validateRegion(region, true);
|
|
771
|
-
}
|
|
772
|
-
const result = await createDatabase(
|
|
773
|
-
name,
|
|
774
|
-
region,
|
|
775
|
-
userAgent,
|
|
776
|
-
cliRunId,
|
|
777
|
-
true
|
|
778
|
-
);
|
|
779
|
-
if (result.error) {
|
|
780
|
-
console.error(result.message || "Unknown error");
|
|
781
|
-
await flushAnalytics();
|
|
782
|
-
process.exit(1);
|
|
783
|
-
}
|
|
784
|
-
console.log(`DATABASE_URL="${result.connectionString}"`);
|
|
785
|
-
console.error("\n# Claim your database at: " + result.claimUrl);
|
|
786
|
-
await flushAnalytics();
|
|
787
|
-
process.exit(0);
|
|
788
|
-
} catch (e) {
|
|
789
|
-
console.error(e?.message || String(e));
|
|
790
|
-
await flushAnalytics();
|
|
791
|
-
process.exit(1);
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
intro(chalk.cyan.bold("š Creating a Prisma Postgres database"));
|
|
796
|
-
log.message(
|
|
797
|
-
chalk.white(`Provisioning a temporary database in ${region}...`)
|
|
798
|
-
);
|
|
799
|
-
log.message(
|
|
800
|
-
chalk.gray(
|
|
801
|
-
`It will be automatically deleted in 24 hours, but you can claim it.`
|
|
802
|
-
)
|
|
803
|
-
);
|
|
804
|
-
if (chooseRegionPrompt) {
|
|
805
|
-
region = await promptForRegion(region, userAgent, cliRunId);
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
region = await validateRegion(region);
|
|
809
|
-
|
|
810
|
-
await createDatabase(name, region, userAgent, cliRunId);
|
|
811
|
-
|
|
812
|
-
outro("");
|
|
813
|
-
await flushAnalytics();
|
|
814
|
-
} catch (error) {
|
|
815
|
-
console.error("Error:", error.message);
|
|
816
|
-
await flushAnalytics();
|
|
817
|
-
process.exit(1);
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
// Run main() if this file is being executed directly
|
|
822
|
-
const isDirectExecution =
|
|
823
|
-
import.meta.url.endsWith("/index.js") ||
|
|
824
|
-
process.argv[1] === import.meta.url.replace("file://", "") ||
|
|
825
|
-
process.argv[1].includes("create-db") ||
|
|
826
|
-
process.argv[1].includes("create-pg") ||
|
|
827
|
-
process.argv[1].includes("create-postgres");
|
|
828
|
-
|
|
829
|
-
if (isDirectExecution && !process.env.__CREATE_DB_EXECUTING) {
|
|
830
|
-
process.env.__CREATE_DB_EXECUTING = "true";
|
|
831
|
-
main().catch(console.error);
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
// if (import.meta.url.endsWith('/index.js') || process.argv[1] === import.meta.url.replace('file://', '')) {
|
|
835
|
-
// main().catch(console.error);
|
|
836
|
-
// }
|