create-db 1.0.10 → 1.1.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 +197 -115
  2. package/package.json +13 -3
package/index.js CHANGED
@@ -10,31 +10,49 @@ import chalk from "chalk";
10
10
 
11
11
  dotenv.config();
12
12
 
13
- const CLI_RUN_ID = randomUUID();
14
-
15
13
  const CREATE_DB_WORKER_URL =
16
14
  process.env.CREATE_DB_WORKER_URL || "https://create-db-temp.prisma.io";
17
15
  const CLAIM_DB_WORKER_URL =
18
16
  process.env.CLAIM_DB_WORKER_URL || "https://create-db.prisma.io";
19
17
 
20
- async function sendAnalyticsToWorker(eventName, properties) {
18
+ // Track pending analytics promises to ensure they complete before exit
19
+ const pendingAnalytics = [];
20
+
21
+ async function sendAnalyticsToWorker(eventName, properties, cliRunId) {
21
22
  const controller = new AbortController();
22
- const timer = setTimeout(() => controller.abort(), 2000);
23
- try {
24
- const payload = {
25
- eventName,
26
- properties: { distinct_id: CLI_RUN_ID, ...(properties || {}) },
27
- };
28
- await fetch(`${CREATE_DB_WORKER_URL}/analytics`, {
29
- method: "POST",
30
- headers: { "Content-Type": "application/json" },
31
- body: JSON.stringify(payload),
32
- signal: controller.signal,
33
- });
34
- } catch (error) {
35
- } finally {
36
- clearTimeout(timer);
37
- }
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]);
38
56
  }
39
57
 
40
58
  async function detectUserLocation() {
@@ -73,7 +91,7 @@ const REGION_COORDINATES = {
73
91
  "us-west-1": { lat: 37.7749, lng: -122.4194 }, // N. California
74
92
  };
75
93
 
76
- function getRegionClosestToLocation(userLocation) {
94
+ export function getRegionClosestToLocation(userLocation) {
77
95
  if (!userLocation) return null;
78
96
 
79
97
  const userLat = parseFloat(userLocation.latitude);
@@ -135,11 +153,12 @@ async function isOffline() {
135
153
  `Check your internet connection or visit ${chalk.green("https://www.prisma-status.com/\n")}`
136
154
  )
137
155
  );
156
+ await flushAnalytics();
138
157
  process.exit(1);
139
158
  }
140
159
  }
141
160
 
142
- function getCommandName() {
161
+ export function getCommandName() {
143
162
  const executable = process.argv[1] || "create-db";
144
163
  if (executable.includes("create-pg")) return "create-pg";
145
164
  if (executable.includes("create-postgres")) return "create-postgres";
@@ -205,6 +224,7 @@ Examples:
205
224
  ${chalk.gray(`npx ${CLI_NAME} --env --region us-east-1`)}
206
225
  ${chalk.gray(`npx ${CLI_NAME} --env >> .env`)}
207
226
  `);
227
+ await flushAnalytics();
208
228
  process.exit(0);
209
229
  }
210
230
 
@@ -384,7 +404,7 @@ function handleError(message, extra = "") {
384
404
  process.exit(1);
385
405
  }
386
406
 
387
- async function promptForRegion(defaultRegion, userAgent) {
407
+ async function promptForRegion(defaultRegion, userAgent, cliRunId) {
388
408
  let regions;
389
409
  try {
390
410
  regions = await getRegions();
@@ -405,20 +425,31 @@ async function promptForRegion(defaultRegion, userAgent) {
405
425
 
406
426
  if (region === null) {
407
427
  cancel(chalk.red("Operation cancelled."));
428
+ await flushAnalytics();
408
429
  process.exit(0);
409
430
  }
410
431
 
411
- void sendAnalyticsToWorker("create_db:region_selected", {
412
- command: CLI_NAME,
413
- region: region,
414
- "selection-method": "interactive",
415
- "user-agent": userAgent,
416
- });
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
+ );
417
442
 
418
443
  return region;
419
444
  }
420
445
 
421
- async function createDatabase(name, region, userAgent, silent = false) {
446
+ async function createDatabase(
447
+ name,
448
+ region,
449
+ userAgent,
450
+ cliRunId,
451
+ silent = false
452
+ ) {
422
453
  let s;
423
454
  if (!silent) {
424
455
  s = spinner();
@@ -431,7 +462,8 @@ async function createDatabase(name, region, userAgent, silent = false) {
431
462
  body: JSON.stringify({
432
463
  region,
433
464
  name,
434
- utm_source: userAgent || CLI_NAME,
465
+ utm_source: CLI_NAME,
466
+ userAgent,
435
467
  }),
436
468
  });
437
469
 
@@ -451,14 +483,19 @@ async function createDatabase(name, region, userAgent, silent = false) {
451
483
  );
452
484
  }
453
485
 
454
- void sendAnalyticsToWorker("create_db:database_creation_failed", {
455
- command: CLI_NAME,
456
- region: region,
457
- "error-type": "rate_limit",
458
- "status-code": 429,
459
- "user-agent": userAgent,
460
- });
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
+ );
461
497
 
498
+ await flushAnalytics();
462
499
  process.exit(1);
463
500
  }
464
501
 
@@ -480,20 +517,24 @@ async function createDatabase(name, region, userAgent, silent = false) {
480
517
  s.stop("Unexpected response from create service.");
481
518
  }
482
519
 
483
- void sendAnalyticsToWorker("create_db:database_creation_failed", {
484
- command: CLI_NAME,
485
- region,
486
- "error-type": "invalid_json",
487
- "status-code": resp.status,
488
- "user-agent": userAgent,
489
- });
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
+ );
490
531
 
532
+ await flushAnalytics();
491
533
  process.exit(1);
492
534
  }
493
535
 
494
536
  const database = result.data ? result.data.database : result.databases?.[0];
495
537
  const projectId = result.data ? result.data.id : result.id;
496
- const prismaConn = database?.connectionString;
497
538
 
498
539
  const directConnDetails = result.data
499
540
  ? database?.apiKeys?.[0]?.directConnection
@@ -519,8 +560,7 @@ async function createDatabase(name, region, userAgent, silent = false) {
519
560
 
520
561
  if (silent && !result.error) {
521
562
  const jsonResponse = {
522
- connectionString: prismaConn,
523
- directConnectionString: directConn,
563
+ connectionString: directConn,
524
564
  claimUrl: claimUrl,
525
565
  deletionDate: expiryDate.toISOString(),
526
566
  region: database?.region?.id || region,
@@ -551,14 +591,19 @@ async function createDatabase(name, region, userAgent, silent = false) {
551
591
  );
552
592
  }
553
593
 
554
- void sendAnalyticsToWorker("create_db:database_creation_failed", {
555
- command: CLI_NAME,
556
- region: region,
557
- "error-type": "api_error",
558
- "error-message": result.error.message,
559
- "user-agent": userAgent,
560
- });
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
+ );
561
605
 
606
+ await flushAnalytics();
562
607
  process.exit(1);
563
608
  }
564
609
 
@@ -568,57 +613,50 @@ async function createDatabase(name, region, userAgent, silent = false) {
568
613
 
569
614
  const expiryFormatted = expiryDate.toLocaleString();
570
615
 
616
+ log.message("");
617
+ log.info(chalk.bold("Database Connection"));
571
618
  log.message("");
572
619
 
573
- log.info(chalk.bold("Connect to your database →"));
574
-
575
- if (prismaConn) {
576
- log.message(
577
- chalk.magenta(" Use this connection string optimized for Prisma ORM:")
578
- );
579
- log.message(" " + chalk.yellow(prismaConn));
580
- log.message("");
581
- }
582
-
620
+ // Direct connection (only output this one)
583
621
  if (directConn) {
584
- log.message(
585
- chalk.cyan(" Use this connection string for everything else:")
586
- );
622
+ log.message(chalk.cyan(" Connection String:"));
587
623
  log.message(" " + chalk.yellow(directConn));
588
624
  log.message("");
589
625
  } else {
590
- log.warning(
591
- chalk.yellow(
592
- "Direct connection details are not available in the API response."
593
- )
594
- );
626
+ log.warning(chalk.yellow(" Connection details are not available."));
627
+ log.message("");
595
628
  }
596
629
 
630
+
631
+ // Claim database section
597
632
  const clickableUrl = terminalLink(claimUrl, claimUrl, { fallback: false });
598
- log.success(`${chalk.bold("Claim your database →")}`);
599
- log.message(
600
- chalk.cyan(" Want to keep your database? Claim for free via this link:")
601
- );
633
+ log.success(chalk.bold("Claim Your Database"));
634
+ log.message(chalk.cyan(" Keep your database for free:"));
602
635
  log.message(" " + chalk.yellow(clickableUrl));
603
636
  log.message(
604
637
  chalk.italic(
605
638
  chalk.gray(
606
- " Your database will be deleted on " +
607
- expiryFormatted +
608
- " if not claimed."
639
+ ` Database will be deleted on ${expiryFormatted} if not claimed.`
609
640
  )
610
641
  )
611
642
  );
612
643
 
613
- void sendAnalyticsToWorker("create_db:database_created", {
614
- command: CLI_NAME,
615
- region,
616
- utm_source: CLI_NAME,
617
- });
644
+ void sendAnalyticsToWorker(
645
+ "create_db:database_created",
646
+ {
647
+ command: CLI_NAME,
648
+ region,
649
+ utm_source: CLI_NAME,
650
+ },
651
+ cliRunId
652
+ );
618
653
  }
619
654
 
620
- async function main() {
655
+ export async function main() {
621
656
  try {
657
+ // Generate unique ID for this CLI run
658
+ const cliRunId = randomUUID();
659
+
622
660
  const rawArgs = process.argv.slice(2);
623
661
 
624
662
  const { flags } = await parseArgs();
@@ -631,22 +669,27 @@ async function main() {
631
669
  userAgent = `${userEnvVars.PRISMA_ACTOR_NAME}/${userEnvVars.PRISMA_ACTOR_PROJECT}`;
632
670
  }
633
671
 
634
- void sendAnalyticsToWorker("create_db:cli_command_ran", {
635
- command: CLI_NAME,
636
- "full-command": `${CLI_NAME} ${rawArgs.join(" ")}`.trim(),
637
- "has-region-flag": rawArgs.includes("--region") || rawArgs.includes("-r"),
638
- "has-interactive-flag":
639
- rawArgs.includes("--interactive") || rawArgs.includes("-i"),
640
- "has-help-flag": rawArgs.includes("--help") || rawArgs.includes("-h"),
641
- "has-list-regions-flag": rawArgs.includes("--list-regions"),
642
- "has-json-flag": rawArgs.includes("--json") || rawArgs.includes("-j"),
643
- "has-env-flag": rawArgs.includes("--env") || rawArgs.includes("-e"),
644
- "has-user-agent-from-env": !!userAgent,
645
- "node-version": process.version,
646
- platform: process.platform,
647
- arch: process.arch,
648
- "user-agent": userAgent,
649
- });
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
+ );
650
693
 
651
694
  if (!flags.help && !flags.json) {
652
695
  await isOffline();
@@ -666,18 +709,23 @@ async function main() {
666
709
 
667
710
  if (flags["list-regions"]) {
668
711
  await listRegions();
712
+ await flushAnalytics();
669
713
  process.exit(0);
670
714
  }
671
715
 
672
716
  if (flags.region) {
673
717
  region = flags.region;
674
718
 
675
- void sendAnalyticsToWorker("create_db:region_selected", {
676
- command: CLI_NAME,
677
- region: region,
678
- "selection-method": "flag",
679
- "user-agent": userAgent,
680
- });
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
+ );
681
729
  }
682
730
 
683
731
  if (flags.interactive) {
@@ -687,12 +735,19 @@ async function main() {
687
735
  if (flags.json) {
688
736
  try {
689
737
  if (chooseRegionPrompt) {
690
- region = await promptForRegion(region, userAgent);
738
+ region = await promptForRegion(region, userAgent, cliRunId);
691
739
  } else {
692
740
  await validateRegion(region, true);
693
741
  }
694
- const result = await createDatabase(name, region, userAgent, true);
742
+ const result = await createDatabase(
743
+ name,
744
+ region,
745
+ userAgent,
746
+ cliRunId,
747
+ true
748
+ );
695
749
  console.log(JSON.stringify(result, null, 2));
750
+ await flushAnalytics();
696
751
  process.exit(0);
697
752
  } catch (e) {
698
753
  console.log(
@@ -702,6 +757,7 @@ async function main() {
702
757
  2
703
758
  )
704
759
  );
760
+ await flushAnalytics();
705
761
  process.exit(1);
706
762
  }
707
763
  }
@@ -709,20 +765,29 @@ async function main() {
709
765
  if (flags.env) {
710
766
  try {
711
767
  if (chooseRegionPrompt) {
712
- region = await promptForRegion(region, userAgent);
768
+ region = await promptForRegion(region, userAgent, cliRunId);
713
769
  } else {
714
770
  await validateRegion(region, true);
715
771
  }
716
- const result = await createDatabase(name, region, userAgent, true);
772
+ const result = await createDatabase(
773
+ name,
774
+ region,
775
+ userAgent,
776
+ cliRunId,
777
+ true
778
+ );
717
779
  if (result.error) {
718
780
  console.error(result.message || "Unknown error");
781
+ await flushAnalytics();
719
782
  process.exit(1);
720
783
  }
721
- console.log(`DATABASE_URL="${result.directConnectionString}"`);
784
+ console.log(`DATABASE_URL="${result.connectionString}"`);
722
785
  console.error("\n# Claim your database at: " + result.claimUrl);
786
+ await flushAnalytics();
723
787
  process.exit(0);
724
788
  } catch (e) {
725
789
  console.error(e?.message || String(e));
790
+ await flushAnalytics();
726
791
  process.exit(1);
727
792
  }
728
793
  }
@@ -737,18 +802,35 @@ async function main() {
737
802
  )
738
803
  );
739
804
  if (chooseRegionPrompt) {
740
- region = await promptForRegion(region, userAgent);
805
+ region = await promptForRegion(region, userAgent, cliRunId);
741
806
  }
742
807
 
743
808
  region = await validateRegion(region);
744
809
 
745
- await createDatabase(name, region, userAgent);
810
+ await createDatabase(name, region, userAgent, cliRunId);
746
811
 
747
812
  outro("");
813
+ await flushAnalytics();
748
814
  } catch (error) {
749
815
  console.error("Error:", error.message);
816
+ await flushAnalytics();
750
817
  process.exit(1);
751
818
  }
752
819
  }
753
820
 
754
- main();
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
+ // }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "create-db",
3
- "version": "1.0.10",
3
+ "version": "1.1.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
- "author": "",
6
+ "author": "prisma",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "git+https://github.com/prisma/create-db.git"
@@ -40,5 +40,15 @@
40
40
  "index.js",
41
41
  "README.md",
42
42
  "analytics.js"
43
- ]
43
+ ],
44
+ "devDependencies": {
45
+ "execa": "^9.6.0",
46
+ "vitest": "^3.2.4"
47
+ },
48
+ "scripts": {
49
+ "test": "vitest run --reporter=verbose",
50
+ "test:watch": "vitest watch",
51
+ "test:coverage": "vitest run --coverage",
52
+ "test:clean": "vitest run"
53
+ }
44
54
  }