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.
- package/index.js +197 -115
- 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
|
-
|
|
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(),
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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(
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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(
|
|
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:
|
|
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(
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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(
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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:
|
|
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(
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
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(
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
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(
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
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(
|
|
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(
|
|
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.
|
|
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
|
|
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
|
}
|