create-db 1.0.1 → 1.1.2-pr44-DC-4828-json-flag-17109503084.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.
Files changed (2) hide show
  1. package/index.js +173 -99
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -3,14 +3,7 @@
3
3
  import dotenv from "dotenv";
4
4
  dotenv.config();
5
5
 
6
- import {
7
- select,
8
- spinner,
9
- intro,
10
- outro,
11
- log,
12
- cancel,
13
- } from "@clack/prompts";
6
+ import { select, spinner, intro, outro, log, cancel } from "@clack/prompts";
14
7
  import chalk from "chalk";
15
8
  import terminalLink from "terminal-link";
16
9
  import { analytics } from "./analytics.js";
@@ -71,9 +64,7 @@ async function showHelp() {
71
64
  if (regions && regions.length > 0) {
72
65
  regionExamples = regions.map((r) => r.id).join(", ");
73
66
  }
74
- } catch {
75
- // Fallback to default examples if fetching fails
76
- }
67
+ } catch {}
77
68
 
78
69
  console.log(`
79
70
  ${chalk.cyan.bold("Prisma Postgres Create DB")}
@@ -84,6 +75,8 @@ Usage:
84
75
  Options:
85
76
  ${chalk.yellow(`--region <region>, -r <region>`)} Specify the region (e.g., ${regionExamples})
86
77
  ${chalk.yellow("--interactive, -i")} Run in interactive mode to select a region and create the database
78
+ ${chalk.yellow("--json, -j")} Output machine-readable JSON and exit
79
+ ${chalk.yellow("--list-regions")} List available regions and exit
87
80
  ${chalk.yellow("--help, -h")} Show this help message
88
81
 
89
82
  Examples:
@@ -91,20 +84,27 @@ Examples:
91
84
  ${chalk.gray(`npx ${CLI_NAME} -r us-east-1`)}
92
85
  ${chalk.gray(`npx ${CLI_NAME} --interactive`)}
93
86
  ${chalk.gray(`npx ${CLI_NAME} -i`)}
87
+ ${chalk.gray(`npx ${CLI_NAME} --json --region us-east-1`)}
94
88
  `);
95
89
  process.exit(0);
96
90
  }
97
91
 
98
- // Parse command line arguments into flags and positional arguments
99
92
  async function parseArgs() {
100
93
  const args = process.argv.slice(2);
101
94
  const flags = {};
102
95
 
103
- const allowedFlags = ["region", "help", "list-regions", "interactive"];
96
+ const allowedFlags = [
97
+ "region",
98
+ "help",
99
+ "list-regions",
100
+ "interactive",
101
+ "json",
102
+ ];
104
103
  const shorthandMap = {
105
104
  r: "region",
106
105
  i: "interactive",
107
106
  h: "help",
107
+ j: "json",
108
108
  };
109
109
 
110
110
  const exitWithError = (message) => {
@@ -116,7 +116,6 @@ async function parseArgs() {
116
116
  for (let i = 0; i < args.length; i++) {
117
117
  const arg = args[i];
118
118
 
119
- // Handle long flags (--region, --help, etc.)
120
119
  if (arg.startsWith("--")) {
121
120
  const flag = arg.slice(2);
122
121
  if (flag === "help") await showHelp();
@@ -134,11 +133,9 @@ async function parseArgs() {
134
133
  continue;
135
134
  }
136
135
 
137
- // Handle short and multi-letter shorthand flags
138
136
  if (arg.startsWith("-")) {
139
137
  const short = arg.slice(1);
140
138
 
141
- // Check if it's a multi-letter shorthand like -cs or -lr
142
139
  if (shorthandMap[short]) {
143
140
  const mappedFlag = shorthandMap[short];
144
141
  if (mappedFlag === "help") showHelp();
@@ -154,7 +151,6 @@ async function parseArgs() {
154
151
  continue;
155
152
  }
156
153
 
157
- // Fall back to single-letter flags like -r -l
158
154
  for (const letter of short.split("")) {
159
155
  const mappedFlag = shorthandMap[letter];
160
156
  if (!mappedFlag) exitWithError(`Invalid flag: -${letter}`);
@@ -181,14 +177,16 @@ async function parseArgs() {
181
177
  return { flags };
182
178
  }
183
179
 
184
- /**
185
- * Fetch available regions from the API.
186
- */
187
- export async function getRegions() {
180
+ export async function getRegions(returnJson = false) {
188
181
  const url = `${CREATE_DB_WORKER_URL}/regions`;
189
182
  const res = await fetch(url);
190
183
 
191
184
  if (!res.ok) {
185
+ if (returnJson) {
186
+ throw new Error(
187
+ `Failed to fetch regions. Status: ${res.status} ${res.statusText}`
188
+ );
189
+ }
192
190
  handleError(
193
191
  `Failed to fetch regions. Status: ${res.status} ${res.statusText}`
194
192
  );
@@ -199,18 +197,23 @@ export async function getRegions() {
199
197
  const regions = Array.isArray(data) ? data : data.data;
200
198
  return regions.filter((region) => region.status === "available");
201
199
  } catch (e) {
200
+ if (returnJson) {
201
+ throw new Error("Failed to parse JSON from /regions endpoint.");
202
+ }
202
203
  handleError("Failed to parse JSON from /regions endpoint.", e);
203
204
  }
204
205
  }
205
206
 
206
- /**
207
- * Validate the provided region against the available list.
208
- */
209
- export async function validateRegion(region) {
210
- const regions = await getRegions();
207
+ export async function validateRegion(region, returnJson = false) {
208
+ const regions = await getRegions(returnJson);
211
209
  const regionIds = regions.map((r) => r.id);
212
210
 
213
211
  if (!regionIds.includes(region)) {
212
+ if (returnJson) {
213
+ throw new Error(
214
+ `Invalid region: ${region}. Available regions: ${regionIds.join(", ")}`
215
+ );
216
+ }
214
217
  handleError(
215
218
  `Invalid region: ${chalk.yellow(region)}.\nAvailable regions: ${chalk.green(
216
219
  regionIds.join(", ")
@@ -221,9 +224,6 @@ export async function validateRegion(region) {
221
224
  return region;
222
225
  }
223
226
 
224
- /**
225
- * Prettified error handler
226
- */
227
227
  function handleError(message, extra = "") {
228
228
  console.error(
229
229
  "\n" +
@@ -239,8 +239,6 @@ function handleError(message, extra = "") {
239
239
  process.exit(1);
240
240
  }
241
241
 
242
- // Get region from user input
243
-
244
242
  async function promptForRegion(defaultRegion) {
245
243
  let regions;
246
244
  try {
@@ -265,24 +263,23 @@ async function promptForRegion(defaultRegion) {
265
263
  process.exit(0);
266
264
  }
267
265
 
268
- // Track region selection event
269
266
  try {
270
267
  await analytics.capture("create_db:region_selected", {
271
268
  command: CLI_NAME,
272
269
  region: region,
273
- "selection-method": "interactive"
270
+ "selection-method": "interactive",
274
271
  });
275
- } catch (error) {
276
- // Silently fail analytics
277
- }
272
+ } catch (error) {}
278
273
 
279
274
  return region;
280
275
  }
281
276
 
282
- // Create a database
283
- async function createDatabase(name, region) {
284
- const s = spinner();
285
- s.start("Creating your database...");
277
+ async function createDatabase(name, region, returnJson = false) {
278
+ let s;
279
+ if (!returnJson) {
280
+ s = spinner();
281
+ s.start("Creating your database...");
282
+ }
286
283
 
287
284
  const resp = await fetch(`${CREATE_DB_WORKER_URL}/create`, {
288
285
  method: "POST",
@@ -290,13 +287,22 @@ async function createDatabase(name, region) {
290
287
  body: JSON.stringify({ region, name, utm_source: CLI_NAME }),
291
288
  });
292
289
 
293
- // Rate limit exceeded
294
290
  if (resp.status === 429) {
295
- s.stop(
296
- "We're experiencing a high volume of requests. Please try again later."
297
- );
298
-
299
- // Track database creation failure
291
+ if (returnJson) {
292
+ return {
293
+ error: "rate_limit_exceeded",
294
+ message:
295
+ "We're experiencing a high volume of requests. Please try again later.",
296
+ status: 429,
297
+ };
298
+ }
299
+
300
+ if (s) {
301
+ s.stop(
302
+ "We're experiencing a high volume of requests. Please try again later."
303
+ );
304
+ }
305
+
300
306
  try {
301
307
  await analytics.capture("create_db:database_creation_failed", {
302
308
  command: CLI_NAME,
@@ -304,21 +310,92 @@ async function createDatabase(name, region) {
304
310
  "error-type": "rate_limit",
305
311
  "status-code": 429,
306
312
  });
307
- } catch (error) {
308
- // Silently fail analytics
313
+ } catch (error) {}
314
+
315
+ process.exit(1);
316
+ }
317
+
318
+ let result;
319
+ let raw;
320
+ try {
321
+ raw = await resp.text();
322
+ result = JSON.parse(raw);
323
+ } catch (e) {
324
+ if (returnJson) {
325
+ return {
326
+ error: "invalid_json",
327
+ message: "Unexpected response from create service.",
328
+ raw,
329
+ status: resp.status,
330
+ };
309
331
  }
310
-
332
+ if (s) {
333
+ s.stop("Unexpected response from create service.");
334
+ }
335
+ try {
336
+ await analytics.capture("create_db:database_creation_failed", {
337
+ command: CLI_NAME,
338
+ region,
339
+ "error-type": "invalid_json",
340
+ "status-code": resp.status,
341
+ });
342
+ } catch {}
311
343
  process.exit(1);
312
344
  }
313
345
 
314
- const result = await resp.json();
346
+ const database = result.data ? result.data.database : result.databases?.[0];
347
+ const projectId = result.data ? result.data.id : result.id;
348
+ const prismaConn = database?.connectionString;
349
+
350
+ const directConnDetails = result.data
351
+ ? database?.apiKeys?.[0]?.directConnection
352
+ : result.databases?.[0]?.apiKeys?.[0]?.ppgDirectConnection;
353
+ const directUser = directConnDetails?.user
354
+ ? encodeURIComponent(directConnDetails.user)
355
+ : "";
356
+ const directPass = directConnDetails?.pass
357
+ ? encodeURIComponent(directConnDetails.pass)
358
+ : "";
359
+ const directHost = directConnDetails?.host;
360
+ const directPort = directConnDetails?.port
361
+ ? `:${directConnDetails.port}`
362
+ : "";
363
+ const directDbName = directConnDetails?.database || "postgres";
364
+ const directConn =
365
+ directConnDetails && directHost
366
+ ? `postgresql://${directUser}:${directPass}@${directHost}${directPort}/${directDbName}`
367
+ : null;
368
+
369
+ const claimUrl = `${CLAIM_DB_WORKER_URL}?projectID=${projectId}&utm_source=${CLI_NAME}&utm_medium=cli`;
370
+ const expiryDate = new Date(Date.now() + 24 * 60 * 60 * 1000);
371
+
372
+ if (returnJson && !result.error) {
373
+ return {
374
+ connectionString: prismaConn,
375
+ directConnectionString: directConn,
376
+ claimUrl: claimUrl,
377
+ deletionDate: expiryDate.toISOString(),
378
+ region: database?.region?.id || region,
379
+ name: database?.name,
380
+ projectId: projectId,
381
+ };
382
+ }
315
383
 
316
384
  if (result.error) {
317
- s.stop(
318
- `Error creating database: ${result.error.message || "Unknown error"}`
319
- );
320
-
321
- // Track database creation failure
385
+ if (returnJson) {
386
+ return {
387
+ error: "api_error",
388
+ message: result.error.message || "Unknown error",
389
+ details: result.error,
390
+ };
391
+ }
392
+
393
+ if (s) {
394
+ s.stop(
395
+ `Error creating database: ${result.error.message || "Unknown error"}`
396
+ );
397
+ }
398
+
322
399
  try {
323
400
  await analytics.capture("create_db:database_creation_failed", {
324
401
  command: CLI_NAME,
@@ -326,31 +403,20 @@ async function createDatabase(name, region) {
326
403
  "error-type": "api_error",
327
404
  "error-message": result.error.message,
328
405
  });
329
- } catch (error) {
330
- // Silently fail analytics
331
- }
406
+ } catch (error) {}
332
407
  process.exit(1);
333
408
  }
334
409
 
335
- s.stop("Database created successfully!");
410
+ if (s) {
411
+ s.stop("Database created successfully!");
412
+ }
336
413
 
337
- const expiryDate = new Date(Date.now() + 24 * 60 * 60 * 1000);
338
414
  const expiryFormatted = expiryDate.toLocaleString();
339
415
 
340
416
  log.message("");
341
- // Determine which connection string to display
342
- const database = result.data ? result.data.database : result.databases?.[0];
343
- const prismaConn = database?.connectionString;
344
- const directConnDetails = result.data
345
- ? database?.apiKeys?.[0]?.directConnection
346
- : result.databases?.[0]?.apiKeys?.[0]?.ppgDirectConnection;
347
- const directConn = directConnDetails
348
- ? `postgresql://${directConnDetails.user}:${directConnDetails.pass}@${directConnDetails.host}/postgres`
349
- : null;
350
417
 
351
418
  log.info(chalk.bold("Connect to your database →"));
352
419
 
353
- // Show Prisma Postgres connection string
354
420
  if (prismaConn) {
355
421
  log.message(
356
422
  chalk.magenta(" Use this connection string optimized for Prisma ORM:")
@@ -359,7 +425,6 @@ async function createDatabase(name, region) {
359
425
  log.message("");
360
426
  }
361
427
 
362
- // Show Direct connection string (if available)
363
428
  if (directConn) {
364
429
  log.message(
365
430
  chalk.cyan(" Use this connection string for everything else:")
@@ -374,9 +439,6 @@ async function createDatabase(name, region) {
374
439
  );
375
440
  }
376
441
 
377
- // Claim Database
378
- const projectId = result.data ? result.data.id : result.id;
379
- const claimUrl = `${CLAIM_DB_WORKER_URL}?projectID=${projectId}&utm_source=${CLI_NAME}&utm_medium=cli`;
380
442
  const clickableUrl = terminalLink(claimUrl, claimUrl, { fallback: false });
381
443
  log.success(`${chalk.bold("Claim your database →")}`);
382
444
  log.message(
@@ -394,35 +456,32 @@ async function createDatabase(name, region) {
394
456
  );
395
457
  }
396
458
 
397
- // Main function
398
-
399
459
  async function main() {
400
460
  try {
401
461
  const rawArgs = process.argv.slice(2);
402
462
  try {
403
463
  await analytics.capture("create_db:cli_command_ran", {
404
464
  command: CLI_NAME,
405
- "full-command": `${CLI_NAME} ${rawArgs.join(' ')}`.trim(),
406
- "has-region-flag": rawArgs.includes('--region') || rawArgs.includes('-r'),
407
- "has-interactive-flag": rawArgs.includes('--interactive') || rawArgs.includes('-i'),
408
- "has-help-flag": rawArgs.includes('--help') || rawArgs.includes('-h'),
409
- "has-list-regions-flag": rawArgs.includes('--list-regions'),
465
+ "full-command": `${CLI_NAME} ${rawArgs.join(" ")}`.trim(),
466
+ "has-region-flag":
467
+ rawArgs.includes("--region") || rawArgs.includes("-r"),
468
+ "has-interactive-flag":
469
+ rawArgs.includes("--interactive") || rawArgs.includes("-i"),
470
+ "has-help-flag": rawArgs.includes("--help") || rawArgs.includes("-h"),
471
+ "has-list-regions-flag": rawArgs.includes("--list-regions"),
472
+ "has-json-flag": rawArgs.includes("--json") || rawArgs.includes("-j"),
410
473
  "node-version": process.version,
411
474
  platform: process.platform,
412
- arch: process.arch
475
+ arch: process.arch,
413
476
  });
414
- } catch (error) {
415
- // Silently fail analytics
416
- }
477
+ } catch (error) {}
417
478
 
418
- // Parse command line arguments
419
479
  const { flags } = await parseArgs();
420
480
 
421
- if (!flags.help) {
481
+ if (!flags.help && !flags.json) {
422
482
  await isOffline();
423
483
  }
424
484
 
425
- // Set default values
426
485
  let name = new Date().toISOString();
427
486
  let region = "us-east-1";
428
487
  let chooseRegionPrompt = false;
@@ -436,25 +495,44 @@ async function main() {
436
495
  process.exit(0);
437
496
  }
438
497
 
439
- // Apply command line flags
440
498
  if (flags.region) {
441
499
  region = flags.region;
442
-
443
- // Track region selection via flag
500
+
444
501
  try {
445
502
  await analytics.capture("create_db:region_selected", {
446
503
  command: CLI_NAME,
447
504
  region: region,
448
- "selection-method": "flag"
505
+ "selection-method": "flag",
449
506
  });
450
- } catch (error) {
451
- // Silently fail analytics
452
- }
507
+ } catch (error) {}
453
508
  }
509
+
454
510
  if (flags.interactive) {
455
511
  chooseRegionPrompt = true;
456
512
  }
457
513
 
514
+ if (flags.json) {
515
+ try {
516
+ if (chooseRegionPrompt) {
517
+ region = await promptForRegion(region);
518
+ } else {
519
+ await validateRegion(region, true);
520
+ }
521
+ const result = await createDatabase(name, region, true);
522
+ console.log(JSON.stringify(result, null, 2));
523
+ process.exit(0);
524
+ } catch (e) {
525
+ console.log(
526
+ JSON.stringify(
527
+ { error: "cli_error", message: e?.message || String(e) },
528
+ null,
529
+ 2
530
+ )
531
+ );
532
+ process.exit(1);
533
+ }
534
+ }
535
+
458
536
  intro(chalk.cyan.bold("🚀 Creating a Prisma Postgres database"));
459
537
  log.message(
460
538
  chalk.white(`Provisioning a temporary database in ${region}...`)
@@ -464,16 +542,12 @@ async function main() {
464
542
  `It will be automatically deleted in 24 hours, but you can claim it.`
465
543
  )
466
544
  );
467
- // Interactive mode prompts
468
545
  if (chooseRegionPrompt) {
469
- // Prompt for region
470
546
  region = await promptForRegion(region);
471
547
  }
472
548
 
473
- // Validate the region
474
549
  region = await validateRegion(region);
475
550
 
476
- // Create the database
477
551
  await createDatabase(name, region);
478
552
 
479
553
  outro("");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-db",
3
- "version": "1.0.1",
3
+ "version": "1.1.2-pr44-DC-4828-json-flag-17109503084.0",
4
4
  "description": "Instantly create a temporary Prisma Postgres database with one command, then claim and persist it in your Prisma Data Platform project when ready.",
5
5
  "main": "index.js",
6
6
  "author": "",