create-kubojs 0.1.1 → 0.2.1
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 +25 -22
- package/dist/cli.mjs +27 -22
- package/dist/index.d.mts +33 -62
- package/dist/index.mjs +2 -2
- package/dist/{src-BAYXN3Za.mjs → src-CvkZZND8.mjs} +996 -1796
- package/package.json +5 -4
|
@@ -11,17 +11,40 @@ import path from "node:path";
|
|
|
11
11
|
import envPaths from "env-paths";
|
|
12
12
|
import fs from "fs-extra";
|
|
13
13
|
import { fileURLToPath } from "node:url";
|
|
14
|
-
import { desktopWebFrontends as desktopWebFrontends$3 } from "@kubojs/types";
|
|
15
|
-
import { EMBEDDED_TEMPLATES, EMBEDDED_TEMPLATES as EMBEDDED_TEMPLATES$1, GeneratorError, GeneratorError as GeneratorError$1, TEMPLATE_COUNT, VirtualFileSystem, VirtualFileSystem as VirtualFileSystem$1, dependencyVersionMap, generate, generate as generate$1, generateReproducibleCommand, processAddonTemplates, processAddonsDeps,
|
|
14
|
+
import { desktopWebFrontends as desktopWebFrontends$3, getPaymentCompatibilityIssue, normalizePayments } from "@kubojs/types";
|
|
15
|
+
import { EMBEDDED_TEMPLATES, EMBEDDED_TEMPLATES as EMBEDDED_TEMPLATES$1, GeneratorError, GeneratorError as GeneratorError$1, TEMPLATE_COUNT, VirtualFileSystem, VirtualFileSystem as VirtualFileSystem$1, dependencyVersionMap, generate, generate as generate$1, generateReproducibleCommand, processAddonTemplates, processAddonsDeps, processEnvVariables, processPackageConfigs, processTemplateString, processTurboConfig, processVitePlusConfig } from "@kubojs/template-generator";
|
|
16
16
|
import { consola, createConsola } from "consola";
|
|
17
17
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
18
18
|
import gradient from "gradient-string";
|
|
19
19
|
import { $, execa } from "execa";
|
|
20
20
|
import { writeTree } from "@kubojs/template-generator/fs-writer";
|
|
21
|
-
import { ConfirmPrompt, GroupMultiSelectPrompt, MultiSelectPrompt, SelectPrompt, isCancel as isCancel$1 } from "@clack/core";
|
|
21
|
+
import { ConfirmPrompt, GroupMultiSelectPrompt, MultiSelectPrompt, SelectPrompt, TextPrompt, isCancel as isCancel$1, isCancel as isCancel$2 } from "@clack/core";
|
|
22
22
|
import { applyEdits, modify, parse } from "jsonc-parser";
|
|
23
23
|
import os from "node:os";
|
|
24
24
|
import { format } from "oxfmt";
|
|
25
|
+
import { Generator, configSchema } from "@tanstack/router-generator";
|
|
26
|
+
//#region src/utils/cli-colors.ts
|
|
27
|
+
const KUBO_COLORS = {
|
|
28
|
+
signal: "#c49314",
|
|
29
|
+
bright: "#d6a72b",
|
|
30
|
+
orange: "#e08a2e",
|
|
31
|
+
cream: "#f2ede0"
|
|
32
|
+
};
|
|
33
|
+
const supportsTrueColor = process.env.NO_COLOR === void 0 && process.env.FORCE_COLOR !== "0" && (process.env.FORCE_COLOR !== void 0 || process.stdout.isTTY === true);
|
|
34
|
+
function createColor(hex, fallback) {
|
|
35
|
+
if (!supportsTrueColor) return fallback;
|
|
36
|
+
const red = Number.parseInt(hex.slice(1, 3), 16);
|
|
37
|
+
const green = Number.parseInt(hex.slice(3, 5), 16);
|
|
38
|
+
const blue = Number.parseInt(hex.slice(5, 7), 16);
|
|
39
|
+
return (text) => `\u001B[38;2;${red};${green};${blue}m${text}\u001B[39m`;
|
|
40
|
+
}
|
|
41
|
+
const cliColors = {
|
|
42
|
+
signal: createColor(KUBO_COLORS.signal, pc.yellow),
|
|
43
|
+
bright: createColor(KUBO_COLORS.bright, pc.yellow),
|
|
44
|
+
orange: createColor(KUBO_COLORS.orange, pc.yellow),
|
|
45
|
+
cream: createColor(KUBO_COLORS.cream, pc.white)
|
|
46
|
+
};
|
|
47
|
+
//#endregion
|
|
25
48
|
//#region src/utils/get-package-manager.ts
|
|
26
49
|
const getUserPkgManager = () => {
|
|
27
50
|
const userAgent = process.env.npm_config_user_agent;
|
|
@@ -41,10 +64,12 @@ const DEFAULT_CONFIG_BASE = {
|
|
|
41
64
|
database: "sqlite",
|
|
42
65
|
orm: "drizzle",
|
|
43
66
|
auth: "better-auth",
|
|
44
|
-
payments:
|
|
45
|
-
observability: "
|
|
67
|
+
payments: [],
|
|
68
|
+
observability: ["getmonitor"],
|
|
69
|
+
communication: "none",
|
|
46
70
|
addons: ["turborepo"],
|
|
47
71
|
examples: [],
|
|
72
|
+
testing: [],
|
|
48
73
|
git: true,
|
|
49
74
|
install: true,
|
|
50
75
|
dbSetup: "none",
|
|
@@ -60,8 +85,11 @@ function getDefaultConfig() {
|
|
|
60
85
|
projectDir: path.resolve(process.cwd(), DEFAULT_CONFIG_BASE.projectName),
|
|
61
86
|
packageManager: getUserPkgManager(),
|
|
62
87
|
frontend: [...DEFAULT_CONFIG_BASE.frontend],
|
|
88
|
+
payments: [...DEFAULT_CONFIG_BASE.payments],
|
|
89
|
+
observability: [...DEFAULT_CONFIG_BASE.observability],
|
|
63
90
|
addons: [...DEFAULT_CONFIG_BASE.addons],
|
|
64
|
-
examples: [...DEFAULT_CONFIG_BASE.examples]
|
|
91
|
+
examples: [...DEFAULT_CONFIG_BASE.examples],
|
|
92
|
+
testing: [...DEFAULT_CONFIG_BASE.testing]
|
|
65
93
|
};
|
|
66
94
|
}
|
|
67
95
|
const DEFAULT_CONFIG = getDefaultConfig();
|
|
@@ -78,17 +106,12 @@ const ADDON_COMPATIBILITY = {
|
|
|
78
106
|
husky: [],
|
|
79
107
|
lefthook: [],
|
|
80
108
|
turborepo: [],
|
|
81
|
-
nx: [],
|
|
82
109
|
"vite-plus": [],
|
|
83
|
-
starlight: [],
|
|
84
|
-
ultracite: [],
|
|
85
110
|
mcp: [],
|
|
86
111
|
oxlint: [],
|
|
87
|
-
fumadocs: [],
|
|
88
112
|
opentui: [],
|
|
89
|
-
wxt: [],
|
|
90
113
|
skills: [],
|
|
91
|
-
|
|
114
|
+
"s3-storage": [],
|
|
92
115
|
none: []
|
|
93
116
|
};
|
|
94
117
|
//#endregion
|
|
@@ -383,9 +406,11 @@ async function addToHistory(config, reproducibleCommand) {
|
|
|
383
406
|
auth: config.auth,
|
|
384
407
|
payments: config.payments,
|
|
385
408
|
observability: config.observability,
|
|
409
|
+
communication: config.communication,
|
|
386
410
|
api: config.api,
|
|
387
411
|
addons: config.addons,
|
|
388
412
|
examples: config.examples,
|
|
413
|
+
testing: config.testing,
|
|
389
414
|
dbSetup: config.dbSetup,
|
|
390
415
|
packageManager: config.packageManager
|
|
391
416
|
},
|
|
@@ -423,24 +448,26 @@ const TITLE_TEXT = `
|
|
|
423
448
|
██║ ██╗╚██████╔╝██████╔╝╚██████╔╝
|
|
424
449
|
╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝
|
|
425
450
|
`;
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
451
|
+
const KUBO_TITLE_COLORS = [
|
|
452
|
+
"#D6A72B",
|
|
453
|
+
"#F5D76E",
|
|
454
|
+
"#E0B43E",
|
|
455
|
+
"#E08A2E",
|
|
456
|
+
"#E0B43E",
|
|
457
|
+
"#E8A43A",
|
|
458
|
+
"#E08A2E",
|
|
459
|
+
"#E08A2E",
|
|
460
|
+
"#C49314",
|
|
461
|
+
"#E8C978",
|
|
462
|
+
"#E5D3A5"
|
|
463
|
+
];
|
|
439
464
|
const renderTitle = () => {
|
|
440
465
|
const terminalWidth = process.stdout.columns || 80;
|
|
441
466
|
const titleLines = TITLE_TEXT.split("\n");
|
|
442
|
-
if (terminalWidth < Math.max(...titleLines.map((line) => line.length)))
|
|
443
|
-
|
|
467
|
+
if (terminalWidth < Math.max(...titleLines.map((line) => line.length))) {
|
|
468
|
+
const simplifiedTitle = `Kubo`;
|
|
469
|
+
console.log(gradient([...KUBO_TITLE_COLORS]).multiline(simplifiedTitle));
|
|
470
|
+
} else console.log(gradient([...KUBO_TITLE_COLORS]).multiline(TITLE_TEXT));
|
|
444
471
|
};
|
|
445
472
|
//#endregion
|
|
446
473
|
//#region src/commands/history.ts
|
|
@@ -487,10 +514,10 @@ async function historyHandler(input) {
|
|
|
487
514
|
return;
|
|
488
515
|
}
|
|
489
516
|
renderTitle();
|
|
490
|
-
intro(
|
|
517
|
+
intro(cliColors.signal(`Project History (${entries.length} entries)`));
|
|
491
518
|
for (const [index, entry] of entries.entries()) {
|
|
492
519
|
const num = pc.dim(`${index + 1}.`);
|
|
493
|
-
const name =
|
|
520
|
+
const name = cliColors.bright(pc.bold(entry.projectName));
|
|
494
521
|
const stack = pc.dim(formatStackSummary(entry));
|
|
495
522
|
log.message(`${num} ${name}`);
|
|
496
523
|
log.message(` ${pc.dim("Created:")} ${formatDate(entry.createdAt)}`);
|
|
@@ -516,169 +543,6 @@ async function openUrl(url) {
|
|
|
516
543
|
await $({ stdio: "ignore" })`xdg-open ${url}`;
|
|
517
544
|
}
|
|
518
545
|
//#endregion
|
|
519
|
-
//#region src/utils/sponsors.ts
|
|
520
|
-
const SPONSORS_JSON_URL = "https://sponsors.kubojs.dev/sponsors.json";
|
|
521
|
-
const GITHUB_SPONSOR_URL = "https://github.com/sponsors/AmanVarshney01";
|
|
522
|
-
const nullableString = z.string().nullish().transform((value) => value ?? void 0);
|
|
523
|
-
const nullableNumber = z.number().nullish().transform((value) => value ?? void 0);
|
|
524
|
-
const sponsorSchema = z.object({
|
|
525
|
-
name: nullableString,
|
|
526
|
-
githubId: z.string(),
|
|
527
|
-
avatarUrl: z.string(),
|
|
528
|
-
websiteUrl: nullableString,
|
|
529
|
-
githubUrl: z.string(),
|
|
530
|
-
tierName: nullableString,
|
|
531
|
-
sinceWhen: z.string(),
|
|
532
|
-
transactionCount: z.number(),
|
|
533
|
-
totalProcessedAmount: nullableNumber,
|
|
534
|
-
formattedAmount: nullableString
|
|
535
|
-
});
|
|
536
|
-
const sponsorSummarySchema = z.object({
|
|
537
|
-
total_sponsors: z.number(),
|
|
538
|
-
total_lifetime_amount: z.number(),
|
|
539
|
-
total_current_monthly: z.number(),
|
|
540
|
-
special_sponsors: z.number(),
|
|
541
|
-
current_sponsors: z.number(),
|
|
542
|
-
past_sponsors: z.number(),
|
|
543
|
-
backers: z.number(),
|
|
544
|
-
top_sponsor: z.object({
|
|
545
|
-
name: z.string(),
|
|
546
|
-
amount: z.number()
|
|
547
|
-
}).nullish().transform((value) => value ?? void 0)
|
|
548
|
-
});
|
|
549
|
-
const sponsorEntrySchema = z.object({
|
|
550
|
-
generated_at: z.string(),
|
|
551
|
-
summary: sponsorSummarySchema,
|
|
552
|
-
specialSponsors: z.array(sponsorSchema),
|
|
553
|
-
sponsors: z.array(sponsorSchema),
|
|
554
|
-
pastSponsors: z.array(sponsorSchema),
|
|
555
|
-
backers: z.array(sponsorSchema)
|
|
556
|
-
});
|
|
557
|
-
async function fetchSponsors(url = SPONSORS_JSON_URL) {
|
|
558
|
-
return fetchSponsorsData({
|
|
559
|
-
url,
|
|
560
|
-
withSpinner: true
|
|
561
|
-
});
|
|
562
|
-
}
|
|
563
|
-
async function fetchSponsorsQuietly({ url = SPONSORS_JSON_URL, timeoutMs = 1500 } = {}) {
|
|
564
|
-
return fetchSponsorsData({
|
|
565
|
-
url,
|
|
566
|
-
withSpinner: false,
|
|
567
|
-
timeoutMs
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
|
-
function displaySponsors(sponsors) {
|
|
571
|
-
const { total_sponsors } = sponsors.summary;
|
|
572
|
-
if (total_sponsors === 0) {
|
|
573
|
-
log.info("No sponsors found. You can be the first one! ✨");
|
|
574
|
-
outro(pc.cyan(`Visit ${GITHUB_SPONSOR_URL} to become a sponsor.`));
|
|
575
|
-
return;
|
|
576
|
-
}
|
|
577
|
-
displaySponsorsBox(sponsors);
|
|
578
|
-
if (total_sponsors - sponsors.specialSponsors.length > 0) log.message(pc.blue(`+${total_sponsors - sponsors.specialSponsors.length} more amazing sponsors.\n`));
|
|
579
|
-
outro(pc.magenta(`Visit ${GITHUB_SPONSOR_URL} to become a sponsor.`));
|
|
580
|
-
}
|
|
581
|
-
function displaySponsorsBox(sponsors) {
|
|
582
|
-
if (sponsors.specialSponsors.length === 0) return;
|
|
583
|
-
let output = `${pc.bold(pc.cyan("-> Special Sponsors"))}\n\n`;
|
|
584
|
-
sponsors.specialSponsors.forEach((sponsor, idx) => {
|
|
585
|
-
const displayName = sponsor.name ?? sponsor.githubId;
|
|
586
|
-
const tier = sponsor.tierName ? ` ${pc.yellow(`(${sponsor.tierName})`)}` : "";
|
|
587
|
-
output += `${pc.green(`• ${displayName}`)}${tier}\n`;
|
|
588
|
-
output += ` ${pc.dim("GitHub:")} https://github.com/${sponsor.githubId}\n`;
|
|
589
|
-
const website = sponsor.websiteUrl ?? sponsor.githubUrl;
|
|
590
|
-
if (website) output += ` ${pc.dim("Website:")} ${website}\n`;
|
|
591
|
-
if (idx < sponsors.specialSponsors.length - 1) output += "\n";
|
|
592
|
-
});
|
|
593
|
-
cliConsola.box(output);
|
|
594
|
-
}
|
|
595
|
-
function formatPostInstallSpecialSponsorsSection(sponsors) {
|
|
596
|
-
if (sponsors.specialSponsors.length === 0) return "";
|
|
597
|
-
const wrappedSponsorLines = wrapSponsorTokens(sponsors.specialSponsors.map((sponsor) => {
|
|
598
|
-
return `• ${sponsor.name ?? sponsor.githubId}`;
|
|
599
|
-
}), getPostInstallSponsorLineWidth());
|
|
600
|
-
let output = `${pc.bold("Special sponsors")}\n`;
|
|
601
|
-
wrappedSponsorLines.forEach((line) => {
|
|
602
|
-
output += `${line}\n`;
|
|
603
|
-
});
|
|
604
|
-
return output.trimEnd();
|
|
605
|
-
}
|
|
606
|
-
function getPostInstallSponsorLineWidth() {
|
|
607
|
-
const terminalWidth = process.stdout.columns;
|
|
608
|
-
if (!terminalWidth || terminalWidth <= 0) return 72;
|
|
609
|
-
const availableWidth = Math.max(8, terminalWidth - 24);
|
|
610
|
-
return Math.min(72, availableWidth);
|
|
611
|
-
}
|
|
612
|
-
function wrapSponsorTokens(tokens, maxLineWidth) {
|
|
613
|
-
const lines = [];
|
|
614
|
-
const separator = " ";
|
|
615
|
-
let currentLine = "";
|
|
616
|
-
tokens.forEach((token) => {
|
|
617
|
-
const candidateLine = currentLine ? `${currentLine}${separator}${token}` : token;
|
|
618
|
-
if (candidateLine.length <= maxLineWidth) {
|
|
619
|
-
currentLine = candidateLine;
|
|
620
|
-
return;
|
|
621
|
-
}
|
|
622
|
-
if (currentLine) {
|
|
623
|
-
lines.push(currentLine);
|
|
624
|
-
currentLine = token;
|
|
625
|
-
return;
|
|
626
|
-
}
|
|
627
|
-
lines.push(token);
|
|
628
|
-
});
|
|
629
|
-
if (currentLine) lines.push(currentLine);
|
|
630
|
-
return lines;
|
|
631
|
-
}
|
|
632
|
-
async function fetchSponsorsData({ url = SPONSORS_JSON_URL, withSpinner = false, timeoutMs }) {
|
|
633
|
-
const s = withSpinner ? spinner() : null;
|
|
634
|
-
if (s) s.start("Fetching sponsors…");
|
|
635
|
-
const controller = timeoutMs ? new AbortController() : null;
|
|
636
|
-
const timeout = timeoutMs ? setTimeout(() => {
|
|
637
|
-
controller?.abort();
|
|
638
|
-
}, timeoutMs) : null;
|
|
639
|
-
try {
|
|
640
|
-
const response = await fetch(url, controller ? { signal: controller.signal } : void 0);
|
|
641
|
-
if (!response.ok) {
|
|
642
|
-
const message = `Failed to fetch sponsors: ${response.statusText || String(response.status)}`;
|
|
643
|
-
if (s) s.stop(pc.red(message));
|
|
644
|
-
return Result.err(new CLIError({ message }));
|
|
645
|
-
}
|
|
646
|
-
const rawSponsors = await response.json();
|
|
647
|
-
const parseResult = sponsorEntrySchema.safeParse(rawSponsors);
|
|
648
|
-
if (!parseResult.success) {
|
|
649
|
-
const message = `Failed to fetch sponsors: invalid response format at "${parseResult.error.issues[0]?.path?.join(".") || "unknown"}"`;
|
|
650
|
-
if (s) s.stop(pc.red(message));
|
|
651
|
-
return Result.err(new CLIError({
|
|
652
|
-
message,
|
|
653
|
-
cause: parseResult.error
|
|
654
|
-
}));
|
|
655
|
-
}
|
|
656
|
-
if (s) s.stop("Sponsors fetched successfully!");
|
|
657
|
-
return Result.ok(parseResult.data);
|
|
658
|
-
} catch (error) {
|
|
659
|
-
const normalizedError = normalizeSponsorFetchError(error);
|
|
660
|
-
if (s) s.stop(pc.red(normalizedError.message));
|
|
661
|
-
return Result.err(normalizedError);
|
|
662
|
-
} finally {
|
|
663
|
-
if (timeout) clearTimeout(timeout);
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
function normalizeSponsorFetchError(error) {
|
|
667
|
-
if (error instanceof Error && error.name === "AbortError") return new CLIError({
|
|
668
|
-
message: "Failed to fetch sponsors: request timed out",
|
|
669
|
-
cause: error
|
|
670
|
-
});
|
|
671
|
-
if (CLIError.is(error)) return error;
|
|
672
|
-
if (error instanceof Error) return new CLIError({
|
|
673
|
-
message: error.message.startsWith("Failed to fetch sponsors:") ? error.message : `Failed to fetch sponsors: ${error.message}`,
|
|
674
|
-
cause: error
|
|
675
|
-
});
|
|
676
|
-
return new CLIError({
|
|
677
|
-
message: `Failed to fetch sponsors: ${String(error)}`,
|
|
678
|
-
cause: error
|
|
679
|
-
});
|
|
680
|
-
}
|
|
681
|
-
//#endregion
|
|
682
546
|
//#region src/commands/meta.ts
|
|
683
547
|
const DOCS_URL = "https://kubojs.dev/docs";
|
|
684
548
|
const BUILDER_URL = "https://kubojs.dev/new";
|
|
@@ -686,20 +550,9 @@ async function openExternalUrl(url, successMessage) {
|
|
|
686
550
|
if ((await Result.tryPromise({
|
|
687
551
|
try: () => openUrl(url),
|
|
688
552
|
catch: () => null
|
|
689
|
-
})).isOk()) log.success(
|
|
553
|
+
})).isOk()) log.success(cliColors.signal(successMessage));
|
|
690
554
|
else log.message(`Please visit ${url}`);
|
|
691
555
|
}
|
|
692
|
-
async function showSponsorsCommand() {
|
|
693
|
-
renderTitle();
|
|
694
|
-
intro(pc.magenta("I dont know Sponsors"));
|
|
695
|
-
const sponsorsResult = await fetchSponsors();
|
|
696
|
-
if (sponsorsResult.isErr()) {
|
|
697
|
-
displayError(sponsorsResult.error);
|
|
698
|
-
process.exit(1);
|
|
699
|
-
return;
|
|
700
|
-
}
|
|
701
|
-
displaySponsors(sponsorsResult.value);
|
|
702
|
-
}
|
|
703
556
|
async function openDocsCommand() {
|
|
704
557
|
await openExternalUrl(DOCS_URL, "Opened docs in your default browser.");
|
|
705
558
|
}
|
|
@@ -725,12 +578,32 @@ const WEB_FRAMEWORKS = [
|
|
|
725
578
|
];
|
|
726
579
|
//#endregion
|
|
727
580
|
//#region src/utils/compatibility-rules.ts
|
|
728
|
-
const TASK_RUNNER_ADDONS$1 = [
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
"vite-plus"
|
|
732
|
-
];
|
|
581
|
+
const TASK_RUNNER_ADDONS$1 = ["turborepo", "vite-plus"];
|
|
582
|
+
/** Mutually exclusive code-quality linters (one slot). */
|
|
583
|
+
const LINTER_ADDONS = ["biome", "oxlint"];
|
|
733
584
|
const STATIC_DESKTOP_ADDONS = ["tauri", "electrobun"];
|
|
585
|
+
function isLinterAddon(addon) {
|
|
586
|
+
return LINTER_ADDONS.includes(addon);
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* When adding a code-quality linter, drop sibling linters so only one remains.
|
|
590
|
+
* Last-selected linter wins. Task runners stay validation-only (reject on dual).
|
|
591
|
+
*/
|
|
592
|
+
function mergeAddonsExclusive(existingAddons, addonsToAdd) {
|
|
593
|
+
const removed = /* @__PURE__ */ new Set();
|
|
594
|
+
let result = [...existingAddons];
|
|
595
|
+
for (const addon of addonsToAdd) {
|
|
596
|
+
if (LINTER_ADDONS.includes(addon)) {
|
|
597
|
+
for (const current of result) if (LINTER_ADDONS.includes(current) && current !== addon) removed.add(current);
|
|
598
|
+
result = result.filter((current) => !LINTER_ADDONS.includes(current) || current === addon);
|
|
599
|
+
}
|
|
600
|
+
if (!result.includes(addon) && addon !== "none") result.push(addon);
|
|
601
|
+
}
|
|
602
|
+
return {
|
|
603
|
+
updatedAddons: result,
|
|
604
|
+
removedAddons: [...removed]
|
|
605
|
+
};
|
|
606
|
+
}
|
|
734
607
|
const TAURI_STATIC_EXPORT_FRONTENDS = ["next", "tanstack-start"];
|
|
735
608
|
const CONVEX_BETTER_AUTH_INCOMPATIBLE_FRONTENDS = [
|
|
736
609
|
"nuxt",
|
|
@@ -753,6 +626,13 @@ function validationErr$1(message) {
|
|
|
753
626
|
function isWebFrontend(value) {
|
|
754
627
|
return WEB_FRAMEWORKS.includes(value);
|
|
755
628
|
}
|
|
629
|
+
function isPlaywrightAllowed(frontends = []) {
|
|
630
|
+
return frontends.some((frontend) => isWebFrontend(frontend));
|
|
631
|
+
}
|
|
632
|
+
function validateTestingAgainstFrontends(testing = [], frontends = []) {
|
|
633
|
+
if (testing.includes("playwright") && !isPlaywrightAllowed(frontends)) return validationErr$1("playwright testing requires a web frontend");
|
|
634
|
+
return Result.ok(void 0);
|
|
635
|
+
}
|
|
756
636
|
function splitFrontends(values = []) {
|
|
757
637
|
return {
|
|
758
638
|
web: values.filter((f) => isWebFrontend(f)),
|
|
@@ -772,23 +652,6 @@ const FULLSTACK_FRONTENDS$1 = [
|
|
|
772
652
|
"svelte",
|
|
773
653
|
"astro"
|
|
774
654
|
];
|
|
775
|
-
const EVLOG_SERVER_BACKENDS = [
|
|
776
|
-
"hono",
|
|
777
|
-
"express",
|
|
778
|
-
"fastify",
|
|
779
|
-
"elysia"
|
|
780
|
-
];
|
|
781
|
-
const EVLOG_FULLSTACK_FRONTENDS = FULLSTACK_FRONTENDS$1;
|
|
782
|
-
const evlogCompatibilityMessage = "evlog addon supports Hono, Express, Fastify, Elysia, or backend self with Next.js, TanStack Start, Nuxt, SvelteKit, or Astro. Convex and backend none are not supported yet.";
|
|
783
|
-
function supportsEvlogAddon(frontend = [], backend, _runtime) {
|
|
784
|
-
if (!backend) return true;
|
|
785
|
-
if (EVLOG_SERVER_BACKENDS.includes(backend)) return true;
|
|
786
|
-
if (backend === "self") {
|
|
787
|
-
if (frontend.length === 0) return true;
|
|
788
|
-
return frontend.some((f) => EVLOG_FULLSTACK_FRONTENDS.includes(f));
|
|
789
|
-
}
|
|
790
|
-
return false;
|
|
791
|
-
}
|
|
792
655
|
function validateSelfBackendCompatibility(providedFlags, options, config) {
|
|
793
656
|
const backend = config.backend || options.backend;
|
|
794
657
|
const frontends = config.frontend || options.frontend || [];
|
|
@@ -834,7 +697,7 @@ function isFrontendAllowedWithBackend(frontend, backend, auth) {
|
|
|
834
697
|
function supportsConvexBetterAuth(frontends = []) {
|
|
835
698
|
return frontends.some((frontend) => CONVEX_BETTER_AUTH_SUPPORTED_FRONTENDS.includes(frontend));
|
|
836
699
|
}
|
|
837
|
-
function allowedApisForFrontends(frontends = []) {
|
|
700
|
+
function allowedApisForFrontends(frontends = [], backend) {
|
|
838
701
|
const includesNuxt = frontends.includes("nuxt");
|
|
839
702
|
const includesSvelte = frontends.includes("svelte");
|
|
840
703
|
const includesSolid = frontends.includes("solid");
|
|
@@ -844,9 +707,17 @@ function allowedApisForFrontends(frontends = []) {
|
|
|
844
707
|
"orpc",
|
|
845
708
|
"none"
|
|
846
709
|
];
|
|
847
|
-
if (
|
|
710
|
+
if (backend === "hono") base.splice(2, 0, "orval");
|
|
711
|
+
if (includesNuxt || includesSvelte || includesSolid || includesAstro) return backend === "hono" ? [
|
|
712
|
+
"orpc",
|
|
713
|
+
"orval",
|
|
714
|
+
"none"
|
|
715
|
+
] : ["orpc", "none"];
|
|
848
716
|
return base;
|
|
849
717
|
}
|
|
718
|
+
function isApiCompatibleWithBackend(api, backend) {
|
|
719
|
+
return api !== "orval" || backend === "hono";
|
|
720
|
+
}
|
|
850
721
|
function isExampleTodoAllowed(backend, database, api) {
|
|
851
722
|
if (backend === "convex") return true;
|
|
852
723
|
if (database === "none" || api === "none") return false;
|
|
@@ -906,10 +777,6 @@ function validateDockerWebDeployDesktopAddons(webDeploy, addons, frontend, backe
|
|
|
906
777
|
return validationErr$1(`'--web-deploy docker' is not compatible with the ${desktopAddons.join(", ")} addon on '${affected}' because desktop addons switch the web build to a static export, which the docker image cannot serve. Remove the addon or use a static-serving frontend (tanstack-router, solid).`);
|
|
907
778
|
}
|
|
908
779
|
function validateAddonCompatibility(addon, frontend, auth, backend, runtime) {
|
|
909
|
-
if (addon === "evlog" && !supportsEvlogAddon(frontend, backend, runtime)) return {
|
|
910
|
-
isCompatible: false,
|
|
911
|
-
reason: evlogCompatibilityMessage
|
|
912
|
-
};
|
|
913
780
|
if (STATIC_DESKTOP_ADDONS.includes(addon) && auth === "clerk" && frontend.includes("react-router")) return {
|
|
914
781
|
isCompatible: false,
|
|
915
782
|
reason: `${addon} addon forces React Router into a static export, but Clerk on React Router requires SSR middleware. Remove the addon or use a different auth/frontend.`
|
|
@@ -922,9 +789,13 @@ function validateAddonCompatibility(addon, frontend, auth, backend, runtime) {
|
|
|
922
789
|
isCompatible: false,
|
|
923
790
|
reason: "tauri addon is not compatible with Convex Better Auth on Next.js or TanStack Start because those templates use server auth bootstrap and cannot be exported as static desktop assets."
|
|
924
791
|
};
|
|
792
|
+
if (!(addon in ADDON_COMPATIBILITY)) return {
|
|
793
|
+
isCompatible: false,
|
|
794
|
+
reason: `${addon} is not a supported addon`
|
|
795
|
+
};
|
|
925
796
|
const compatibleFrontends = ADDON_COMPATIBILITY[addon];
|
|
926
797
|
if (compatibleFrontends.length > 0) {
|
|
927
|
-
if (!frontend.some((f) => compatibleFrontends.
|
|
798
|
+
if (!frontend.some((f) => compatibleFrontends.some((compatibleFrontend) => compatibleFrontend === f))) return {
|
|
928
799
|
isCompatible: false,
|
|
929
800
|
reason: `${addon} addon requires one of these frontends: ${compatibleFrontends.join(", ")}`
|
|
930
801
|
};
|
|
@@ -941,7 +812,8 @@ function getCompatibleAddons(allAddons, frontend, existingAddons = [], auth, bac
|
|
|
941
812
|
});
|
|
942
813
|
}
|
|
943
814
|
function validateAddonsAgainstFrontends(addons = [], frontends = [], auth, backend, runtime) {
|
|
944
|
-
if (addons.filter((addon) => TASK_RUNNER_ADDONS$1.includes(addon)).length > 1) return validationErr$1("Cannot combine 'turborepo'
|
|
815
|
+
if (addons.filter((addon) => TASK_RUNNER_ADDONS$1.includes(addon)).length > 1) return validationErr$1("Cannot combine 'turborepo' and 'vite-plus' addons. Choose one task runner.");
|
|
816
|
+
if ([...new Set(addons.filter((addon) => LINTER_ADDONS.includes(addon)))].length > 1) return validationErr$1("Cannot combine 'biome' and 'oxlint' addons. Choose one code-quality linter.");
|
|
945
817
|
for (const addon of addons) {
|
|
946
818
|
if (addon === "none") continue;
|
|
947
819
|
const { isCompatible, reason } = validateAddonCompatibility(addon, frontends, auth, backend, runtime);
|
|
@@ -952,18 +824,31 @@ function validateAddonsAgainstFrontends(addons = [], frontends = [], auth, backe
|
|
|
952
824
|
function validateAddonsAgainstConfig(addons = [], config) {
|
|
953
825
|
return validateAddonsAgainstFrontends(addons, config.frontend ?? [], config.auth, config.backend, config.runtime);
|
|
954
826
|
}
|
|
827
|
+
function validateCommunicationCompatibility(communication, backend) {
|
|
828
|
+
if (!communication || communication === "none") return Result.ok(void 0);
|
|
829
|
+
if ((communication === "resend" || communication === "notifique" || communication === "arara") && backend === "none") return validationErr$1(`${communication === "notifique" ? "Notifique" : communication === "arara" ? "AraraHQ" : "Resend"} communication requires a server backend. Please choose a backend or use '--communication none'.`);
|
|
830
|
+
return Result.ok(void 0);
|
|
831
|
+
}
|
|
832
|
+
function validateAraraRuntimeCompatibility(communication, backend, runtime, serverDeploy) {
|
|
833
|
+
if (communication !== "arara" || backend === "convex") return Result.ok(void 0);
|
|
834
|
+
if (runtime === "workers" || serverDeploy === "cloudflare") return validationErr$1("AraraHQ requires the official Node SDK and is not compatible with Edge/Workers runtimes. Use a Node/Bun server deployment or Convex Node Action.");
|
|
835
|
+
return Result.ok(void 0);
|
|
836
|
+
}
|
|
955
837
|
function validatePaymentsCompatibility(payments, auth, backend, frontends = [], orm, database) {
|
|
956
|
-
if (!payments || payments ===
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
if (
|
|
838
|
+
if (!payments || payments.length === 0) return Result.ok(void 0);
|
|
839
|
+
for (const provider of payments) {
|
|
840
|
+
const issue = getPaymentCompatibilityIssue({
|
|
841
|
+
provider,
|
|
842
|
+
backend,
|
|
843
|
+
frontends,
|
|
844
|
+
database,
|
|
845
|
+
orm
|
|
846
|
+
});
|
|
847
|
+
const label = provider === "stripe" ? "Stripe" : "AbacatePay";
|
|
848
|
+
if (issue === "convex-unsupported") return validationErr$1(`${label} payments is not compatible with '--backend convex'. Please use a server backend or backend 'self'.`);
|
|
849
|
+
if (issue === "requires-web-frontend") return validationErr$1(`${label} payments requires a web frontend. Please choose next, tanstack-start, tanstack-router, react-router, nuxt, svelte, solid, or astro.`);
|
|
850
|
+
if (issue === "native-only-unsupported") return validationErr$1(`${label} payments is not compatible with native-only frontends. Please add a supported web frontend or leave payments unselected.`);
|
|
851
|
+
if (issue === "sql-database-required") return validationErr$1("AbacatePay payments v1 requires a SQL database with Drizzle or Prisma. MongoDB and Mongoose are not supported.");
|
|
967
852
|
}
|
|
968
853
|
return Result.ok(void 0);
|
|
969
854
|
}
|
|
@@ -998,12 +883,12 @@ function isGoBack(value) {
|
|
|
998
883
|
* These prompts return GO_BACK_SYMBOL when 'b' is pressed (instead of canceling)
|
|
999
884
|
*/
|
|
1000
885
|
const unicode = process.platform !== "win32";
|
|
1001
|
-
const S_STEP_ACTIVE = unicode ? "◆" : "*";
|
|
886
|
+
const S_STEP_ACTIVE$1 = unicode ? "◆" : "*";
|
|
1002
887
|
const S_STEP_CANCEL = unicode ? "■" : "x";
|
|
1003
888
|
const S_STEP_ERROR = unicode ? "▲" : "x";
|
|
1004
889
|
const S_STEP_SUBMIT = unicode ? "◇" : "o";
|
|
1005
|
-
const S_BAR = unicode ? "│" : "|";
|
|
1006
|
-
const S_BAR_END = unicode ? "└" : "—";
|
|
890
|
+
const S_BAR$1 = unicode ? "│" : "|";
|
|
891
|
+
const S_BAR_END$1 = unicode ? "└" : "—";
|
|
1007
892
|
const S_RADIO_ACTIVE = unicode ? "●" : ">";
|
|
1008
893
|
const S_RADIO_INACTIVE = unicode ? "○" : " ";
|
|
1009
894
|
const S_CHECKBOX_ACTIVE = unicode ? "◻" : "[•]";
|
|
@@ -1012,10 +897,10 @@ const S_CHECKBOX_INACTIVE = unicode ? "◻" : "[ ]";
|
|
|
1012
897
|
function symbol(state) {
|
|
1013
898
|
switch (state) {
|
|
1014
899
|
case "initial":
|
|
1015
|
-
case "active": return
|
|
900
|
+
case "active": return cliColors.orange(S_STEP_ACTIVE$1);
|
|
1016
901
|
case "cancel": return pc.red(S_STEP_CANCEL);
|
|
1017
902
|
case "error": return pc.yellow(S_STEP_ERROR);
|
|
1018
|
-
case "submit": return
|
|
903
|
+
case "submit": return cliColors.signal(S_STEP_SUBMIT);
|
|
1019
904
|
}
|
|
1020
905
|
}
|
|
1021
906
|
const KEYBOARD_HINT = pc.dim(`${pc.gray("↑/↓")} navigate • ${pc.gray("enter")} confirm • ${pc.gray("b")} back • ${pc.gray("ctrl+c")} cancel`);
|
|
@@ -1050,7 +935,7 @@ async function navigableSelect(opts) {
|
|
|
1050
935
|
switch (state) {
|
|
1051
936
|
case "disabled": return `${pc.gray(S_RADIO_INACTIVE)} ${pc.gray(label)}${option.hint ? ` ${pc.dim(`(${option.hint ?? "disabled"})`)}` : ""}`;
|
|
1052
937
|
case "selected": return `${pc.dim(label)}`;
|
|
1053
|
-
case "active": return `${
|
|
938
|
+
case "active": return `${cliColors.bright(S_RADIO_ACTIVE)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
1054
939
|
case "cancelled": return `${pc.strikethrough(pc.dim(label))}`;
|
|
1055
940
|
default: return `${pc.dim(S_RADIO_INACTIVE)} ${pc.dim(label)}`;
|
|
1056
941
|
}
|
|
@@ -1059,14 +944,14 @@ async function navigableSelect(opts) {
|
|
|
1059
944
|
options: opts.options,
|
|
1060
945
|
initialValue: opts.initialValue,
|
|
1061
946
|
render() {
|
|
1062
|
-
const title = `${pc.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
947
|
+
const title = `${pc.gray(S_BAR$1)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
1063
948
|
switch (this.state) {
|
|
1064
|
-
case "submit": return `${title}${pc.gray(S_BAR)} ${opt(this.options[this.cursor], "selected")}`;
|
|
1065
|
-
case "cancel": return `${title}${pc.gray(S_BAR)} ${opt(this.options[this.cursor], "cancelled")}\n${pc.gray(S_BAR)}`;
|
|
949
|
+
case "submit": return `${title}${pc.gray(S_BAR$1)} ${opt(this.options[this.cursor], "selected")}`;
|
|
950
|
+
case "cancel": return `${title}${pc.gray(S_BAR$1)} ${opt(this.options[this.cursor], "cancelled")}\n${pc.gray(S_BAR$1)}`;
|
|
1066
951
|
default: {
|
|
1067
|
-
const optionsText = this.options.map((option, i) => opt(option, option.disabled ? "disabled" : i === this.cursor ? "active" : "inactive")).join(`\n${
|
|
1068
|
-
const hint = `\n${pc.gray(S_BAR)} ${getHint()}`;
|
|
1069
|
-
return `${title}${
|
|
952
|
+
const optionsText = this.options.map((option, i) => opt(option, option.disabled ? "disabled" : i === this.cursor ? "active" : "inactive")).join(`\n${cliColors.signal(S_BAR$1)} `);
|
|
953
|
+
const hint = `\n${pc.gray(S_BAR$1)} ${getHint()}`;
|
|
954
|
+
return `${title}${cliColors.signal(S_BAR$1)} ${optionsText}\n${cliColors.signal(S_BAR_END$1)}${hint}\n`;
|
|
1070
955
|
}
|
|
1071
956
|
}
|
|
1072
957
|
}
|
|
@@ -1077,10 +962,10 @@ async function navigableMultiselect(opts) {
|
|
|
1077
962
|
const opt = (option, state) => {
|
|
1078
963
|
const label = option.label ?? String(option.value);
|
|
1079
964
|
if (state === "disabled") return `${pc.gray(S_CHECKBOX_INACTIVE)} ${pc.strikethrough(pc.gray(label))}${option.hint ? ` ${pc.dim(`(${option.hint ?? "disabled"})`)}` : ""}`;
|
|
1080
|
-
if (state === "active") return `${
|
|
1081
|
-
if (state === "selected") return `${
|
|
965
|
+
if (state === "active") return `${cliColors.orange(S_CHECKBOX_ACTIVE)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
966
|
+
if (state === "selected") return `${cliColors.bright(S_CHECKBOX_SELECTED)} ${pc.dim(label)}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
1082
967
|
if (state === "cancelled") return `${pc.strikethrough(pc.dim(label))}`;
|
|
1083
|
-
if (state === "active-selected") return `${
|
|
968
|
+
if (state === "active-selected") return `${cliColors.bright(S_CHECKBOX_SELECTED)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
1084
969
|
if (state === "submitted") return `${pc.dim(label)}`;
|
|
1085
970
|
return `${pc.dim(S_CHECKBOX_INACTIVE)} ${pc.dim(label)}`;
|
|
1086
971
|
};
|
|
@@ -1093,7 +978,7 @@ async function navigableMultiselect(opts) {
|
|
|
1093
978
|
return normalizeValidationMessage(opts.validate?.(selected));
|
|
1094
979
|
},
|
|
1095
980
|
render() {
|
|
1096
|
-
const title = `${pc.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
981
|
+
const title = `${pc.gray(S_BAR$1)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
1097
982
|
const value = this.value ?? [];
|
|
1098
983
|
const styleOption = (option, active) => {
|
|
1099
984
|
if (option.disabled) return opt(option, "disabled");
|
|
@@ -1105,21 +990,21 @@ async function navigableMultiselect(opts) {
|
|
|
1105
990
|
switch (this.state) {
|
|
1106
991
|
case "submit": {
|
|
1107
992
|
const submitText = this.options.filter(({ value: optionValue }) => value.includes(optionValue)).map((option) => opt(option, "submitted")).join(pc.dim(", ")) || pc.dim("none");
|
|
1108
|
-
return `${title}${pc.gray(S_BAR)} ${submitText}`;
|
|
993
|
+
return `${title}${pc.gray(S_BAR$1)} ${submitText}`;
|
|
1109
994
|
}
|
|
1110
995
|
case "cancel": {
|
|
1111
996
|
const label = this.options.filter(({ value: optionValue }) => value.includes(optionValue)).map((option) => opt(option, "cancelled")).join(pc.dim(", "));
|
|
1112
|
-
return `${title}${pc.gray(S_BAR)} ${label}\n${pc.gray(S_BAR)}`;
|
|
997
|
+
return `${title}${pc.gray(S_BAR$1)} ${label}\n${pc.gray(S_BAR$1)}`;
|
|
1113
998
|
}
|
|
1114
999
|
case "error": {
|
|
1115
|
-
const footer = this.error.split("\n").map((ln, i) => i === 0 ? `${pc.yellow(S_BAR_END)} ${pc.yellow(ln)}` : ` ${ln}`).join("\n");
|
|
1116
|
-
const optionsText = this.options.map((option, i) => styleOption(option, i === this.cursor)).join(`\n${pc.yellow(S_BAR)} `);
|
|
1117
|
-
return `${title}${pc.yellow(S_BAR)} ${optionsText}\n${footer}\n`;
|
|
1000
|
+
const footer = this.error.split("\n").map((ln, i) => i === 0 ? `${pc.yellow(S_BAR_END$1)} ${pc.yellow(ln)}` : ` ${ln}`).join("\n");
|
|
1001
|
+
const optionsText = this.options.map((option, i) => styleOption(option, i === this.cursor)).join(`\n${pc.yellow(S_BAR$1)} `);
|
|
1002
|
+
return `${title}${pc.yellow(S_BAR$1)} ${optionsText}\n${footer}\n`;
|
|
1118
1003
|
}
|
|
1119
1004
|
default: {
|
|
1120
|
-
const optionsText = this.options.map((option, i) => styleOption(option, i === this.cursor)).join(`\n${
|
|
1121
|
-
const hint = `\n${pc.gray(S_BAR)} ${getMultiHint()}`;
|
|
1122
|
-
return `${title}${
|
|
1005
|
+
const optionsText = this.options.map((option, i) => styleOption(option, i === this.cursor)).join(`\n${cliColors.signal(S_BAR$1)} `);
|
|
1006
|
+
const hint = `\n${pc.gray(S_BAR$1)} ${getMultiHint()}`;
|
|
1007
|
+
return `${title}${cliColors.signal(S_BAR$1)} ${optionsText}\n${cliColors.signal(S_BAR_END$1)}${hint}\n`;
|
|
1123
1008
|
}
|
|
1124
1009
|
}
|
|
1125
1010
|
}
|
|
@@ -1133,14 +1018,14 @@ async function navigableConfirm(opts) {
|
|
|
1133
1018
|
inactive,
|
|
1134
1019
|
initialValue: opts.initialValue ?? true,
|
|
1135
1020
|
render() {
|
|
1136
|
-
const title = `${pc.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
1021
|
+
const title = `${pc.gray(S_BAR$1)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
1137
1022
|
const value = this.value ? active : inactive;
|
|
1138
1023
|
switch (this.state) {
|
|
1139
|
-
case "submit": return `${title}${pc.gray(S_BAR)} ${pc.dim(value)}`;
|
|
1140
|
-
case "cancel": return `${title}${pc.gray(S_BAR)} ${pc.strikethrough(pc.dim(value))}\n${pc.gray(S_BAR)}`;
|
|
1024
|
+
case "submit": return `${title}${pc.gray(S_BAR$1)} ${pc.dim(value)}`;
|
|
1025
|
+
case "cancel": return `${title}${pc.gray(S_BAR$1)} ${pc.strikethrough(pc.dim(value))}\n${pc.gray(S_BAR$1)}`;
|
|
1141
1026
|
default: {
|
|
1142
|
-
const hint = `\n${pc.gray(S_BAR)} ${getHint()}`;
|
|
1143
|
-
return `${title}${
|
|
1027
|
+
const hint = `\n${pc.gray(S_BAR$1)} ${getHint()}`;
|
|
1028
|
+
return `${title}${cliColors.signal(S_BAR$1)} ${this.value ? `${pc.green(S_RADIO_ACTIVE)} ${active}` : `${pc.dim(S_RADIO_INACTIVE)} ${pc.dim(active)}`} ${pc.dim("/")} ${!this.value ? `${pc.green(S_RADIO_ACTIVE)} ${inactive}` : `${pc.dim(S_RADIO_INACTIVE)} ${pc.dim(inactive)}`}\n${cliColors.signal(S_BAR_END$1)}${hint}\n`;
|
|
1144
1029
|
}
|
|
1145
1030
|
}
|
|
1146
1031
|
}
|
|
@@ -1153,16 +1038,16 @@ async function navigableGroupMultiselect(opts) {
|
|
|
1153
1038
|
const isItem = typeof option.group === "string";
|
|
1154
1039
|
const next = isItem && (options[options.indexOf(option) + 1] ?? { group: true });
|
|
1155
1040
|
const isLast = isItem && next && next.group === true;
|
|
1156
|
-
const prefix = isItem ? `${isLast ? S_BAR_END : S_BAR} ` : "";
|
|
1157
|
-
if (state === "active") return `${pc.dim(prefix)}${
|
|
1158
|
-
if (state === "group-active") return `${prefix}${
|
|
1159
|
-
if (state === "group-active-selected") return `${prefix}${
|
|
1041
|
+
const prefix = isItem ? `${isLast ? S_BAR_END$1 : S_BAR$1} ` : "";
|
|
1042
|
+
if (state === "active") return `${pc.dim(prefix)}${cliColors.orange(S_CHECKBOX_ACTIVE)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
1043
|
+
if (state === "group-active") return `${prefix}${cliColors.orange(S_CHECKBOX_ACTIVE)} ${pc.dim(label)}`;
|
|
1044
|
+
if (state === "group-active-selected") return `${prefix}${cliColors.bright(S_CHECKBOX_SELECTED)} ${pc.dim(label)}`;
|
|
1160
1045
|
if (state === "selected") {
|
|
1161
|
-
const selectedCheckbox = isItem ?
|
|
1046
|
+
const selectedCheckbox = isItem ? cliColors.bright(S_CHECKBOX_SELECTED) : "";
|
|
1162
1047
|
return `${pc.dim(prefix)}${selectedCheckbox} ${pc.dim(label)}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
1163
1048
|
}
|
|
1164
1049
|
if (state === "cancelled") return `${pc.strikethrough(pc.dim(label))}`;
|
|
1165
|
-
if (state === "active-selected") return `${pc.dim(prefix)}${
|
|
1050
|
+
if (state === "active-selected") return `${pc.dim(prefix)}${cliColors.bright(S_CHECKBOX_SELECTED)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
1166
1051
|
if (state === "submitted") return `${pc.dim(label)}`;
|
|
1167
1052
|
const unselectedCheckbox = isItem ? pc.dim(S_CHECKBOX_INACTIVE) : "";
|
|
1168
1053
|
return `${pc.dim(prefix)}${unselectedCheckbox} ${pc.dim(label)}`;
|
|
@@ -1177,20 +1062,20 @@ async function navigableGroupMultiselect(opts) {
|
|
|
1177
1062
|
return normalizeValidationMessage(opts.validate?.(selected));
|
|
1178
1063
|
},
|
|
1179
1064
|
render() {
|
|
1180
|
-
const title = `${pc.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
1065
|
+
const title = `${pc.gray(S_BAR$1)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
1181
1066
|
const value = this.value ?? [];
|
|
1182
1067
|
switch (this.state) {
|
|
1183
1068
|
case "submit": {
|
|
1184
1069
|
const selectedOptions = this.options.filter(({ value: optionValue }) => value.includes(optionValue)).map((option) => opt(option, "submitted"));
|
|
1185
1070
|
const optionsText = selectedOptions.length === 0 ? "" : ` ${selectedOptions.join(pc.dim(", "))}`;
|
|
1186
|
-
return `${title}${pc.gray(S_BAR)}${optionsText}`;
|
|
1071
|
+
return `${title}${pc.gray(S_BAR$1)}${optionsText}`;
|
|
1187
1072
|
}
|
|
1188
1073
|
case "cancel": {
|
|
1189
1074
|
const label = this.options.filter(({ value: optionValue }) => value.includes(optionValue)).map((option) => opt(option, "cancelled")).join(pc.dim(", "));
|
|
1190
|
-
return `${title}${pc.gray(S_BAR)} ${label.trim() ? `${label}\n${pc.gray(S_BAR)}` : ""}`;
|
|
1075
|
+
return `${title}${pc.gray(S_BAR$1)} ${label.trim() ? `${label}\n${pc.gray(S_BAR$1)}` : ""}`;
|
|
1191
1076
|
}
|
|
1192
1077
|
case "error": {
|
|
1193
|
-
const footer = this.error.split("\n").map((ln, i) => i === 0 ? `${pc.yellow(S_BAR_END)} ${pc.yellow(ln)}` : ` ${ln}`).join("\n");
|
|
1078
|
+
const footer = this.error.split("\n").map((ln, i) => i === 0 ? `${pc.yellow(S_BAR_END$1)} ${pc.yellow(ln)}` : ` ${ln}`).join("\n");
|
|
1194
1079
|
const optionsText = this.options.map((option, i, options) => {
|
|
1195
1080
|
const selected = value.includes(option.value) || option.group === true && this.isGroupSelected(`${option.value}`);
|
|
1196
1081
|
const active = i === this.cursor;
|
|
@@ -1198,8 +1083,8 @@ async function navigableGroupMultiselect(opts) {
|
|
|
1198
1083
|
if (active && selected) return opt(option, "active-selected", options);
|
|
1199
1084
|
if (selected) return opt(option, "selected", options);
|
|
1200
1085
|
return opt(option, active ? "active" : "inactive", options);
|
|
1201
|
-
}).join(`\n${pc.yellow(S_BAR)} `);
|
|
1202
|
-
return `${title}${pc.yellow(S_BAR)} ${optionsText}\n${footer}\n`;
|
|
1086
|
+
}).join(`\n${pc.yellow(S_BAR$1)} `);
|
|
1087
|
+
return `${title}${pc.yellow(S_BAR$1)} ${optionsText}\n${footer}\n`;
|
|
1203
1088
|
}
|
|
1204
1089
|
default: {
|
|
1205
1090
|
const optionsText = this.options.map((option, i, options) => {
|
|
@@ -1212,10 +1097,10 @@ async function navigableGroupMultiselect(opts) {
|
|
|
1212
1097
|
else if (selected) optionText = opt(option, "selected", options);
|
|
1213
1098
|
else optionText = opt(option, active ? "active" : "inactive", options);
|
|
1214
1099
|
return `${i !== 0 && !optionText.startsWith("\n") ? " " : ""}${optionText}`;
|
|
1215
|
-
}).join(`\n${
|
|
1100
|
+
}).join(`\n${cliColors.signal(S_BAR$1)}`);
|
|
1216
1101
|
const optionsPrefix = optionsText.startsWith("\n") ? "" : " ";
|
|
1217
|
-
const hint = `\n${pc.gray(S_BAR)} ${getMultiHint()}`;
|
|
1218
|
-
return `${title}${
|
|
1102
|
+
const hint = `\n${pc.gray(S_BAR$1)} ${getMultiHint()}`;
|
|
1103
|
+
return `${title}${cliColors.signal(S_BAR$1)}${optionsPrefix}${optionsText}\n${cliColors.signal(S_BAR_END$1)}${hint}\n`;
|
|
1219
1104
|
}
|
|
1220
1105
|
}
|
|
1221
1106
|
}
|
|
@@ -1235,10 +1120,6 @@ function getAddonDisplay(addon) {
|
|
|
1235
1120
|
label = "Turborepo";
|
|
1236
1121
|
hint = "High-performance build system";
|
|
1237
1122
|
break;
|
|
1238
|
-
case "nx":
|
|
1239
|
-
label = "Nx";
|
|
1240
|
-
hint = "Smart monorepo orchestration and task graph";
|
|
1241
|
-
break;
|
|
1242
1123
|
case "vite-plus":
|
|
1243
1124
|
label = "Vite+";
|
|
1244
1125
|
hint = "Unified Vite toolchain and workspace task runner";
|
|
@@ -1263,10 +1144,6 @@ function getAddonDisplay(addon) {
|
|
|
1263
1144
|
label = "Oxlint";
|
|
1264
1145
|
hint = "Oxlint + Oxfmt (linting & formatting)";
|
|
1265
1146
|
break;
|
|
1266
|
-
case "ultracite":
|
|
1267
|
-
label = "Ultracite";
|
|
1268
|
-
hint = "Zero-config preset for Biome or Oxlint with AI integration";
|
|
1269
|
-
break;
|
|
1270
1147
|
case "lefthook":
|
|
1271
1148
|
label = "Lefthook";
|
|
1272
1149
|
hint = "Fast and powerful Git hooks manager";
|
|
@@ -1275,33 +1152,21 @@ function getAddonDisplay(addon) {
|
|
|
1275
1152
|
label = "Husky";
|
|
1276
1153
|
hint = "Modern native Git hooks made easy";
|
|
1277
1154
|
break;
|
|
1278
|
-
case "starlight":
|
|
1279
|
-
label = "Starlight";
|
|
1280
|
-
hint = "Build stellar docs with astro";
|
|
1281
|
-
break;
|
|
1282
|
-
case "fumadocs":
|
|
1283
|
-
label = "Fumadocs";
|
|
1284
|
-
hint = "Build excellent documentation site";
|
|
1285
|
-
break;
|
|
1286
1155
|
case "opentui":
|
|
1287
1156
|
label = "OpenTUI";
|
|
1288
1157
|
hint = "Build terminal user interfaces";
|
|
1289
1158
|
break;
|
|
1290
|
-
case "wxt":
|
|
1291
|
-
label = "WXT";
|
|
1292
|
-
hint = "Build browser extensions";
|
|
1293
|
-
break;
|
|
1294
1159
|
case "skills":
|
|
1295
1160
|
label = "Skills";
|
|
1296
1161
|
hint = "AI coding agent skills for your stack";
|
|
1297
1162
|
break;
|
|
1298
1163
|
case "mcp":
|
|
1299
1164
|
label = "MCP";
|
|
1300
|
-
hint = "Install MCP servers, including
|
|
1165
|
+
hint = "Install MCP servers, including kubojs, via add-mcp";
|
|
1301
1166
|
break;
|
|
1302
|
-
case "
|
|
1303
|
-
label = "
|
|
1304
|
-
hint = "
|
|
1167
|
+
case "s3-storage":
|
|
1168
|
+
label = "S3-compatible Storage";
|
|
1169
|
+
hint = "Add object storage for AWS S3, Cloudflare R2, MinIO, or Backblaze";
|
|
1305
1170
|
break;
|
|
1306
1171
|
default:
|
|
1307
1172
|
label = addon;
|
|
@@ -1313,27 +1178,20 @@ function getAddonDisplay(addon) {
|
|
|
1313
1178
|
};
|
|
1314
1179
|
}
|
|
1315
1180
|
const ADDON_GROUPS = {
|
|
1316
|
-
"Monorepo & Tasks": [
|
|
1317
|
-
"turborepo",
|
|
1318
|
-
"nx",
|
|
1319
|
-
"vite-plus"
|
|
1320
|
-
],
|
|
1181
|
+
"Monorepo & Tasks": ["turborepo", "vite-plus"],
|
|
1321
1182
|
"Code Quality": [
|
|
1322
1183
|
"biome",
|
|
1323
1184
|
"oxlint",
|
|
1324
|
-
"ultracite",
|
|
1325
1185
|
"husky",
|
|
1326
1186
|
"lefthook"
|
|
1327
1187
|
],
|
|
1328
|
-
Documentation: ["starlight", "fumadocs"],
|
|
1329
1188
|
"Platform Extensions": [
|
|
1330
1189
|
"pwa",
|
|
1331
1190
|
"tauri",
|
|
1332
1191
|
"electrobun",
|
|
1333
1192
|
"opentui",
|
|
1334
|
-
"
|
|
1193
|
+
"s3-storage"
|
|
1335
1194
|
],
|
|
1336
|
-
Observability: ["evlog"],
|
|
1337
1195
|
"AI & Agent Tools": ["skills", "mcp"]
|
|
1338
1196
|
};
|
|
1339
1197
|
function createGroupedOptions() {
|
|
@@ -1358,11 +1216,8 @@ function sortAndPruneGroupedOptions(groupedOptions) {
|
|
|
1358
1216
|
});
|
|
1359
1217
|
}
|
|
1360
1218
|
function validateAddonSelection(selected) {
|
|
1361
|
-
if ((selected?.filter((addon) => [
|
|
1362
|
-
|
|
1363
|
-
"nx",
|
|
1364
|
-
"vite-plus"
|
|
1365
|
-
].includes(addon)) ?? []).length > 1) return "Choose Turborepo, Nx, or Vite+ as your task runner, not more than one.";
|
|
1219
|
+
if ((selected?.filter((addon) => ["turborepo", "vite-plus"].includes(addon)) ?? []).length > 1) return "Choose Turborepo or Vite+ as your task runner, not both.";
|
|
1220
|
+
if ((selected?.filter((addon) => ["biome", "oxlint"].includes(addon)) ?? []).length > 1) return "Choose Biome or Oxlint as your code-quality tool, not both.";
|
|
1366
1221
|
}
|
|
1367
1222
|
async function getAddonsChoice(addons, frontends, auth, backend, runtime, previousValue) {
|
|
1368
1223
|
if (addons !== void 0) return addons;
|
|
@@ -1387,7 +1242,7 @@ async function getAddonsChoice(addons, frontends, auth, backend, runtime, previo
|
|
|
1387
1242
|
required: false,
|
|
1388
1243
|
validate: validateAddonSelection
|
|
1389
1244
|
});
|
|
1390
|
-
if (isCancel$
|
|
1245
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
1391
1246
|
return response;
|
|
1392
1247
|
}
|
|
1393
1248
|
async function getAddonsToAdd(config) {
|
|
@@ -1410,18 +1265,38 @@ async function getAddonsToAdd(config) {
|
|
|
1410
1265
|
required: false,
|
|
1411
1266
|
validate: validateAddonSelection
|
|
1412
1267
|
});
|
|
1413
|
-
if (isCancel$
|
|
1268
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
1414
1269
|
return response;
|
|
1415
1270
|
}
|
|
1416
1271
|
//#endregion
|
|
1417
|
-
//#region src/utils/
|
|
1418
|
-
|
|
1272
|
+
//#region src/utils/input-hardening.ts
|
|
1273
|
+
function hasControlCharacters(value) {
|
|
1274
|
+
for (const char of value) {
|
|
1275
|
+
const charCode = char.charCodeAt(0);
|
|
1276
|
+
if (charCode < 32 || charCode === 127) return true;
|
|
1277
|
+
}
|
|
1278
|
+
return false;
|
|
1279
|
+
}
|
|
1280
|
+
function hardeningError(field, value, message) {
|
|
1281
|
+
return Result.err(new ValidationError({
|
|
1282
|
+
field,
|
|
1283
|
+
value,
|
|
1284
|
+
message
|
|
1285
|
+
}));
|
|
1286
|
+
}
|
|
1287
|
+
function validateAgentSafePathInput(value, field) {
|
|
1288
|
+
if (hasControlCharacters(value)) return hardeningError(field, value, `Invalid ${field}: control characters are not allowed.`);
|
|
1289
|
+
return Result.ok(void 0);
|
|
1290
|
+
}
|
|
1291
|
+
//#endregion
|
|
1292
|
+
//#region src/utils/kubojs-config.ts
|
|
1293
|
+
const KUBOJS_CONFIG_FILE = "kubojs.jsonrc";
|
|
1419
1294
|
/**
|
|
1420
|
-
* Reads the
|
|
1295
|
+
* Reads the kubojs configuration file from the project directory.
|
|
1421
1296
|
*/
|
|
1422
|
-
async function
|
|
1297
|
+
async function readKubojsConfig(projectDir) {
|
|
1423
1298
|
try {
|
|
1424
|
-
const configPath = path.join(projectDir,
|
|
1299
|
+
const configPath = path.join(projectDir, KUBOJS_CONFIG_FILE);
|
|
1425
1300
|
if (!await fs.pathExists(configPath)) return null;
|
|
1426
1301
|
return parse(await fs.readFile(configPath, "utf-8"));
|
|
1427
1302
|
} catch {
|
|
@@ -1429,11 +1304,11 @@ async function readBtsConfig(projectDir) {
|
|
|
1429
1304
|
}
|
|
1430
1305
|
}
|
|
1431
1306
|
/**
|
|
1432
|
-
* Updates specific fields in the
|
|
1307
|
+
* Updates specific fields in the kubojs configuration file.
|
|
1433
1308
|
*/
|
|
1434
|
-
async function
|
|
1309
|
+
async function updateKubojsConfig(projectDir, updates) {
|
|
1435
1310
|
try {
|
|
1436
|
-
const configPath = path.join(projectDir,
|
|
1311
|
+
const configPath = path.join(projectDir, KUBOJS_CONFIG_FILE);
|
|
1437
1312
|
if (!await fs.pathExists(configPath)) return;
|
|
1438
1313
|
let content = await fs.readFile(configPath, "utf-8");
|
|
1439
1314
|
for (const [key, value] of Object.entries(updates)) {
|
|
@@ -1444,26 +1319,6 @@ async function updateBtsConfig(projectDir, updates) {
|
|
|
1444
1319
|
} catch {}
|
|
1445
1320
|
}
|
|
1446
1321
|
//#endregion
|
|
1447
|
-
//#region src/utils/input-hardening.ts
|
|
1448
|
-
function hasControlCharacters(value) {
|
|
1449
|
-
for (const char of value) {
|
|
1450
|
-
const charCode = char.charCodeAt(0);
|
|
1451
|
-
if (charCode < 32 || charCode === 127) return true;
|
|
1452
|
-
}
|
|
1453
|
-
return false;
|
|
1454
|
-
}
|
|
1455
|
-
function hardeningError(field, value, message) {
|
|
1456
|
-
return Result.err(new ValidationError({
|
|
1457
|
-
field,
|
|
1458
|
-
value,
|
|
1459
|
-
message
|
|
1460
|
-
}));
|
|
1461
|
-
}
|
|
1462
|
-
function validateAgentSafePathInput(value, field) {
|
|
1463
|
-
if (hasControlCharacters(value)) return hardeningError(field, value, `Invalid ${field}: control characters are not allowed.`);
|
|
1464
|
-
return Result.ok(void 0);
|
|
1465
|
-
}
|
|
1466
|
-
//#endregion
|
|
1467
1322
|
//#region src/utils/add-package-deps.ts
|
|
1468
1323
|
const addPackageDependency = async (opts) => {
|
|
1469
1324
|
const { dependencies = [], devDependencies = [], customDependencies = {}, customDevDependencies = {}, projectDir } = opts;
|
|
@@ -1486,515 +1341,6 @@ const addPackageDependency = async (opts) => {
|
|
|
1486
1341
|
await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
|
1487
1342
|
};
|
|
1488
1343
|
//#endregion
|
|
1489
|
-
//#region src/helpers/addons/evlog-setup.ts
|
|
1490
|
-
const evlogBackends = [
|
|
1491
|
-
"hono",
|
|
1492
|
-
"express",
|
|
1493
|
-
"fastify",
|
|
1494
|
-
"elysia"
|
|
1495
|
-
];
|
|
1496
|
-
const evlogWebFrontends = [
|
|
1497
|
-
"next",
|
|
1498
|
-
"nuxt",
|
|
1499
|
-
"svelte",
|
|
1500
|
-
"tanstack-start",
|
|
1501
|
-
"astro"
|
|
1502
|
-
];
|
|
1503
|
-
function isEvlogBackend(backend) {
|
|
1504
|
-
return evlogBackends.includes(backend);
|
|
1505
|
-
}
|
|
1506
|
-
function getEvlogWebFrontend(frontends) {
|
|
1507
|
-
return frontends.find((frontend) => evlogWebFrontends.includes(frontend));
|
|
1508
|
-
}
|
|
1509
|
-
function shouldIdentifyWebAuth(config) {
|
|
1510
|
-
return config.auth === "better-auth" && config.backend === "self";
|
|
1511
|
-
}
|
|
1512
|
-
function prependMissingImports(content, imports) {
|
|
1513
|
-
const missingImports = imports.filter((line) => !content.includes(line));
|
|
1514
|
-
if (missingImports.length === 0) return content;
|
|
1515
|
-
const importBlock = `${missingImports.join("\n")}\n`;
|
|
1516
|
-
const referenceMatch = content.match(/^(?:\/\/\/ <reference[^\n]*>\n)+/);
|
|
1517
|
-
if (referenceMatch) return `${referenceMatch[0]}${importBlock}${content.slice(referenceMatch[0].length)}`;
|
|
1518
|
-
return `${importBlock}${content}`;
|
|
1519
|
-
}
|
|
1520
|
-
function addNamedImport(content, moduleName, names) {
|
|
1521
|
-
const importRegex = new RegExp(`import \\{([^}]+)\\} from "${moduleName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}";`);
|
|
1522
|
-
const match = content.match(importRegex);
|
|
1523
|
-
if (!match) return prependMissingImports(content, [`import { ${names.join(", ")} } from "${moduleName}";`]);
|
|
1524
|
-
const nextNames = [...match[1].split(",").map((name) => name.trim()).filter(Boolean)];
|
|
1525
|
-
for (const name of names) if (!nextNames.includes(name)) nextNames.push(name);
|
|
1526
|
-
return content.replace(match[0], `import { ${nextNames.join(", ")} } from "${moduleName}";`);
|
|
1527
|
-
}
|
|
1528
|
-
function insertBeforeOnce(content, marker, snippet, alreadyPresent) {
|
|
1529
|
-
if (content.includes(alreadyPresent)) return content;
|
|
1530
|
-
if (!content.includes(marker)) return content;
|
|
1531
|
-
return content.replace(marker, `${snippet}${marker}`);
|
|
1532
|
-
}
|
|
1533
|
-
function insertAfterOnce(content, marker, snippet, alreadyPresent) {
|
|
1534
|
-
if (content.includes(alreadyPresent)) return content;
|
|
1535
|
-
if (!content.includes(marker)) return content;
|
|
1536
|
-
return content.replace(marker, `${marker}${snippet}`);
|
|
1537
|
-
}
|
|
1538
|
-
async function writeFileIfChanged(filePath, content) {
|
|
1539
|
-
if ((await fs.pathExists(filePath) ? await fs.readFile(filePath, "utf-8") : void 0) === content) return;
|
|
1540
|
-
await fs.ensureDir(path.dirname(filePath));
|
|
1541
|
-
await fs.writeFile(filePath, content);
|
|
1542
|
-
}
|
|
1543
|
-
async function updateFileIfExists(filePath, update) {
|
|
1544
|
-
if (!await fs.pathExists(filePath)) return;
|
|
1545
|
-
const content = await fs.readFile(filePath, "utf-8");
|
|
1546
|
-
const nextContent = update(content);
|
|
1547
|
-
if (nextContent !== content) await fs.writeFile(filePath, nextContent);
|
|
1548
|
-
}
|
|
1549
|
-
function usesCreateAuthFactory(config) {
|
|
1550
|
-
return config.runtime === "workers" || config.serverDeploy === "cloudflare" || config.backend === "self" && config.webDeploy === "cloudflare";
|
|
1551
|
-
}
|
|
1552
|
-
function getAuthImportLine(config) {
|
|
1553
|
-
return usesCreateAuthFactory(config) ? `import { createAuth } from "@${config.projectName}/auth";` : `import { auth } from "@${config.projectName}/auth";`;
|
|
1554
|
-
}
|
|
1555
|
-
function getAuthExpression(config) {
|
|
1556
|
-
return usesCreateAuthFactory(config) ? "createAuth()" : "auth";
|
|
1557
|
-
}
|
|
1558
|
-
function addEvlogBetterAuthServerSetup(content, backend, authExpression) {
|
|
1559
|
-
let nextContent = addNamedImport(content, "evlog/better-auth", ["createAuthMiddleware", "type BetterAuthInstance"]);
|
|
1560
|
-
const usesAuthFactory = authExpression.endsWith("()");
|
|
1561
|
-
const evlogAuthExpression = `${authExpression} as BetterAuthInstance`;
|
|
1562
|
-
const authOptions = "{ exclude: [\"/api/auth/**\"], maskEmail: true }";
|
|
1563
|
-
const identifySnippet = usesAuthFactory ? "" : `const identifyUser = createAuthMiddleware(${evlogAuthExpression}, ${authOptions});\n\n`;
|
|
1564
|
-
const identifyUserSetup = usesAuthFactory ? `\n\tconst identifyUser = createAuthMiddleware(${evlogAuthExpression}, ${authOptions});` : "";
|
|
1565
|
-
if (backend === "hono") {
|
|
1566
|
-
nextContent = insertBeforeOnce(nextContent, "const app = new Hono", identifySnippet, "createAuthMiddleware(");
|
|
1567
|
-
return insertAfterOnce(nextContent, "app.use(evlog());", `\napp.use("*", async (c, next) => {${identifyUserSetup}\n\tawait identifyUser(c.get("log"), c.req.raw.headers, c.req.path);\n\tawait next();\n});`, "identifyUser(c.get(\"log\")");
|
|
1568
|
-
}
|
|
1569
|
-
if (backend === "express") {
|
|
1570
|
-
nextContent = insertBeforeOnce(nextContent, "const app = express();", identifySnippet, "createAuthMiddleware(");
|
|
1571
|
-
return insertAfterOnce(nextContent, "app.use(evlog());", `\napp.use(async (req, _res, next) => {${identifyUserSetup}\n\tawait identifyUser(req.log, req.headers, req.path);\n\tnext();\n});`, "identifyUser(req.log");
|
|
1572
|
-
}
|
|
1573
|
-
if (backend === "fastify") {
|
|
1574
|
-
nextContent = addNamedImport(nextContent, "evlog/fastify", ["useLogger"]);
|
|
1575
|
-
nextContent = insertBeforeOnce(nextContent, "const fastify = Fastify", identifySnippet, "createAuthMiddleware(");
|
|
1576
|
-
return insertAfterOnce(nextContent, "fastify.register(evlog);", `\nfastify.addHook("preHandler", async (request) => {${identifyUserSetup}\n\tawait identifyUser(useLogger(), request.headers, request.url);\n});`, "identifyUser(useLogger()");
|
|
1577
|
-
}
|
|
1578
|
-
nextContent = insertBeforeOnce(nextContent, "new Elysia", identifySnippet, "createAuthMiddleware(");
|
|
1579
|
-
return insertAfterOnce(nextContent, ".use(evlog())", `\n\t.derive(async ({ request, log }) => {${identifyUserSetup.replace(/\n\t/g, "\n ")}\n\t\tawait identifyUser(log, request.headers, new URL(request.url).pathname);\n\t\treturn {};\n\t})`, "identifyUser(log");
|
|
1580
|
-
}
|
|
1581
|
-
function addEvlogServerSetup(content, backend, serviceName) {
|
|
1582
|
-
const initSnippet = `initLogger({\n\tenv: { service: "${serviceName}" },\n});\n\n`;
|
|
1583
|
-
if (backend === "hono") {
|
|
1584
|
-
let nextContent = prependMissingImports(content, ["import { initLogger } from \"evlog\";", "import { evlog, type EvlogVariables } from \"evlog/hono\";"]);
|
|
1585
|
-
nextContent = insertBeforeOnce(nextContent, "const app = new Hono", initSnippet, "initLogger({");
|
|
1586
|
-
nextContent = nextContent.replace("const app = new Hono();", "const app = new Hono<EvlogVariables>();");
|
|
1587
|
-
nextContent = nextContent.replace("import { logger } from \"hono/logger\";\n", "").replace(/\napp\.use\(logger\(\)\);/, "");
|
|
1588
|
-
return insertAfterOnce(nextContent, "const app = new Hono<EvlogVariables>();", "\n\napp.use(evlog());", "app.use(evlog());");
|
|
1589
|
-
}
|
|
1590
|
-
if (backend === "express") {
|
|
1591
|
-
let nextContent = prependMissingImports(content, ["import { initLogger } from \"evlog\";", "import { evlog } from \"evlog/express\";"]);
|
|
1592
|
-
nextContent = insertBeforeOnce(nextContent, "const app = express();", initSnippet, "initLogger({");
|
|
1593
|
-
return insertAfterOnce(nextContent, "const app = express();", "\n\napp.use(evlog());", "app.use(evlog());");
|
|
1594
|
-
}
|
|
1595
|
-
if (backend === "fastify") {
|
|
1596
|
-
let nextContent = prependMissingImports(content, ["import { initLogger } from \"evlog\";", "import { evlog } from \"evlog/fastify\";"]);
|
|
1597
|
-
nextContent = insertBeforeOnce(nextContent, "const fastify = Fastify", initSnippet, "initLogger({");
|
|
1598
|
-
return insertBeforeOnce(nextContent, "fastify.register(fastifyCors", "fastify.register(evlog);\n", "fastify.register(evlog);");
|
|
1599
|
-
}
|
|
1600
|
-
let nextContent = prependMissingImports(content, ["import { initLogger } from \"evlog\";", "import { evlog } from \"evlog/elysia\";"]);
|
|
1601
|
-
nextContent = insertBeforeOnce(nextContent, "new Elysia", initSnippet, "initLogger({");
|
|
1602
|
-
for (const marker of ["new Elysia({ adapter: node() })", "new Elysia()"]) nextContent = insertAfterOnce(nextContent, marker, "\n .use(evlog())", ".use(evlog())");
|
|
1603
|
-
return nextContent;
|
|
1604
|
-
}
|
|
1605
|
-
function addNuxtEvlogSetup(content, serviceName) {
|
|
1606
|
-
let nextContent = content;
|
|
1607
|
-
if (!nextContent.includes("\"evlog/nuxt\"") && !nextContent.includes("'evlog/nuxt'")) nextContent = nextContent.replace(/modules:\s*\[/, (match) => `${match}\n "evlog/nuxt",`);
|
|
1608
|
-
if (!nextContent.includes("evlog:")) nextContent = nextContent.replace(/\n\}\)\s*$/, (match) => {
|
|
1609
|
-
const contentBeforeConfigClose = nextContent.slice(0, -match.length);
|
|
1610
|
-
return `${!/[,{]\s*$/.test(contentBeforeConfigClose) ? "," : ""}\n evlog: {\n env: { service: "${serviceName}" },\n },\n})`;
|
|
1611
|
-
});
|
|
1612
|
-
return nextContent;
|
|
1613
|
-
}
|
|
1614
|
-
function addSvelteViteEvlogSetup(content, serviceName) {
|
|
1615
|
-
let nextContent = prependMissingImports(content, ["import evlog from \"evlog/vite\";"]);
|
|
1616
|
-
if (nextContent.includes("evlog({")) return nextContent;
|
|
1617
|
-
return nextContent.replace("plugins: [tailwindcss(), sveltekit()],", `plugins: [\n tailwindcss(),\n sveltekit(),\n evlog({ service: "${serviceName}" }),\n ],`);
|
|
1618
|
-
}
|
|
1619
|
-
function addSvelteHooksEvlogSetup(content) {
|
|
1620
|
-
let nextContent = prependMissingImports(content, ["import { createEvlogHooks } from \"evlog/sveltekit\";"]);
|
|
1621
|
-
if (!nextContent.includes("export const handle") && !nextContent.includes("const authHandle")) {
|
|
1622
|
-
if (!nextContent.includes("createEvlogHooks()")) nextContent = `${nextContent.trimEnd()}\n\nexport const { handle, handleError } = createEvlogHooks();\n`;
|
|
1623
|
-
return nextContent;
|
|
1624
|
-
}
|
|
1625
|
-
nextContent = prependMissingImports(nextContent, ["import { sequence } from \"@sveltejs/kit/hooks\";"]);
|
|
1626
|
-
if (!nextContent.includes("const { handle: evlogHandle, handleError }")) nextContent = nextContent.replace(/((?:import .+\n)+)/, `$1\nconst { handle: evlogHandle, handleError } = createEvlogHooks();\n\n`);
|
|
1627
|
-
nextContent = nextContent.replace(/export const handle(:\s*Handle)?\s*=\s*async/, (_match, typeAnnotation) => `const authHandle${typeAnnotation ?? ""} = async`);
|
|
1628
|
-
if (!nextContent.includes("sequence(evlogHandle, authHandle)")) nextContent = `${nextContent.trimEnd()}\n\nexport const handle = sequence(evlogHandle as Handle, authHandle);\nexport { handleError };\n`;
|
|
1629
|
-
return nextContent;
|
|
1630
|
-
}
|
|
1631
|
-
function addSvelteLocalsType(content) {
|
|
1632
|
-
let nextContent = prependMissingImports(content, ["import type { RequestLogger } from \"evlog\";"]);
|
|
1633
|
-
if (nextContent.includes("log: RequestLogger")) return nextContent;
|
|
1634
|
-
if (nextContent.includes("// interface Locals {}")) return nextContent.replace("// interface Locals {}", "interface Locals {\n log: RequestLogger;\n }");
|
|
1635
|
-
return nextContent.replace("namespace App {", "namespace App {\n interface Locals {\n log: RequestLogger;\n }\n");
|
|
1636
|
-
}
|
|
1637
|
-
function addTanstackStartRootEvlogSetup(content) {
|
|
1638
|
-
let nextContent = prependMissingImports(content, ["import { createMiddleware } from \"@tanstack/react-start\";", "import { evlogErrorHandler } from \"evlog/nitro/v3\";"]);
|
|
1639
|
-
const middlewareEntry = "createMiddleware().server(evlogErrorHandler)";
|
|
1640
|
-
if (nextContent.includes(`middleware: [${middlewareEntry}]`)) return nextContent;
|
|
1641
|
-
if (nextContent.includes("middleware: [")) return nextContent.replace("middleware: [", `middleware: [${middlewareEntry}, `);
|
|
1642
|
-
if (/server:\s*{/.test(nextContent)) return nextContent.replace(/server:\s*{\n/, `server: {\n middleware: [${middlewareEntry}],\n`);
|
|
1643
|
-
return nextContent.replace("head: () => ({", `server: {\n middleware: [${middlewareEntry}],\n },\n\n head: () => ({`);
|
|
1644
|
-
}
|
|
1645
|
-
function addAstroMiddlewareEvlogSetup(content, serviceName) {
|
|
1646
|
-
let nextContent = prependMissingImports(content, ["import { createRequestLogger, initLogger } from \"evlog\";"]);
|
|
1647
|
-
const initSnippet = `initLogger({\n env: { service: "${serviceName}" },\n});\n\n`;
|
|
1648
|
-
nextContent = insertBeforeOnce(nextContent, "export const onRequest", initSnippet, "initLogger({");
|
|
1649
|
-
if (nextContent.includes("createRequestLogger({")) return nextContent;
|
|
1650
|
-
const contextMarker = "export const onRequest = defineMiddleware(async (context, next) => {";
|
|
1651
|
-
if (nextContent.includes(contextMarker)) {
|
|
1652
|
-
nextContent = insertAfterOnce(nextContent, contextMarker, `\n const url = new URL(context.request.url);\n const log = createRequestLogger({\n method: context.request.method,\n path: url.pathname,\n });\n\n context.locals.log = log;\n`, "const log = createRequestLogger({");
|
|
1653
|
-
return nextContent.replace("return next();", "const response = await next();\n log.emit();\n return response;");
|
|
1654
|
-
}
|
|
1655
|
-
const localsMarker = "export const onRequest = defineMiddleware(async ({ request, locals }, next) => {";
|
|
1656
|
-
if (nextContent.includes(localsMarker)) {
|
|
1657
|
-
nextContent = insertAfterOnce(nextContent, localsMarker, `\n const url = new URL(request.url);\n const log = createRequestLogger({\n method: request.method,\n path: url.pathname,\n });\n\n locals.log = log;\n`, "const log = createRequestLogger({");
|
|
1658
|
-
return nextContent.replace("return next();", "const response = await next();\n log.emit();\n return response;");
|
|
1659
|
-
}
|
|
1660
|
-
return nextContent;
|
|
1661
|
-
}
|
|
1662
|
-
function addAstroLocalsType(content) {
|
|
1663
|
-
let nextContent = prependMissingImports(content, ["import type { RequestLogger } from \"evlog\";"]);
|
|
1664
|
-
if (nextContent.includes("log: RequestLogger")) return nextContent;
|
|
1665
|
-
if (nextContent.includes("interface Locals {")) return nextContent.replace("interface Locals {", "interface Locals {\n log: RequestLogger;");
|
|
1666
|
-
if (nextContent.includes("declare namespace App {")) return nextContent.replace("declare namespace App {", "declare namespace App {\n interface Locals {\n log: RequestLogger;\n }\n");
|
|
1667
|
-
return `${nextContent.trimEnd()}\n\ndeclare namespace App {\n interface Locals {\n log: RequestLogger;\n }\n}\n`;
|
|
1668
|
-
}
|
|
1669
|
-
function addNextRouteWrappers(content) {
|
|
1670
|
-
let nextContent = prependMissingImports(content, ["import { withEvlog } from \"@/lib/evlog\";"]);
|
|
1671
|
-
if (nextContent.includes("withEvlog(handler)") || nextContent.includes("withEvlog(handleRequest)")) return nextContent;
|
|
1672
|
-
nextContent = nextContent.replace("export { handler as GET, handler as POST };", "export const GET = withEvlog(handler);\nexport const POST = withEvlog(handler);");
|
|
1673
|
-
for (const method of [
|
|
1674
|
-
"GET",
|
|
1675
|
-
"POST",
|
|
1676
|
-
"PUT",
|
|
1677
|
-
"PATCH",
|
|
1678
|
-
"DELETE"
|
|
1679
|
-
]) nextContent = nextContent.replace(`export const ${method} = handleRequest;`, `export const ${method} = withEvlog(handleRequest);`);
|
|
1680
|
-
return nextContent;
|
|
1681
|
-
}
|
|
1682
|
-
function addNextAiEvlogSetup(content) {
|
|
1683
|
-
let nextContent = addNamedImport(content, "@/lib/evlog", ["withEvlog"]);
|
|
1684
|
-
if (!nextContent.includes("withEvlog(async (req: Request)")) {
|
|
1685
|
-
nextContent = nextContent.replace("export async function POST(req: Request) {", "export const POST = withEvlog(async (req: Request) => {");
|
|
1686
|
-
if (nextContent.includes("export const POST = withEvlog(async (req: Request) => {")) nextContent = nextContent.replace(/\n}\s*$/, "\n});\n");
|
|
1687
|
-
}
|
|
1688
|
-
return nextContent;
|
|
1689
|
-
}
|
|
1690
|
-
function addNextBetterAuthToRoute(content) {
|
|
1691
|
-
let nextContent = addNamedImport(content, "@/lib/evlog-auth", ["identifyEvlogUser"]);
|
|
1692
|
-
nextContent = nextContent.replace("function handler(req:", "async function handler(req:");
|
|
1693
|
-
for (const marker of [
|
|
1694
|
-
"async function handler(req: NextRequest) {",
|
|
1695
|
-
"async function handleRequest(req: NextRequest) {",
|
|
1696
|
-
"export const POST = withEvlog(async (req: Request) => {"
|
|
1697
|
-
]) nextContent = insertAfterOnce(nextContent, marker, "\n await identifyEvlogUser(req);", "identifyEvlogUser(req)");
|
|
1698
|
-
return nextContent;
|
|
1699
|
-
}
|
|
1700
|
-
function addSvelteBetterAuthEvlogSetup(content, config) {
|
|
1701
|
-
if (!content.includes("authHandle") || content.includes("evlogAuthHandle")) return content;
|
|
1702
|
-
let nextContent = addNamedImport(content, "evlog/better-auth", ["createAuthMiddleware", "type BetterAuthInstance"]);
|
|
1703
|
-
if (!nextContent.includes(`@${config.projectName}/auth`)) nextContent = prependMissingImports(nextContent, [getAuthImportLine(config)]);
|
|
1704
|
-
if (usesCreateAuthFactory(config) && config.webDeploy === "cloudflare" && !nextContent.includes(`@${config.projectName}/env/server`)) nextContent = prependMissingImports(nextContent, [`import { env as localEnv } from "@${config.projectName}/env/server";`]);
|
|
1705
|
-
const authExpression = getAuthExpression(config);
|
|
1706
|
-
const authOptions = "{ exclude: [\"/api/auth/**\"], maskEmail: true }";
|
|
1707
|
-
const authHandleSnippet = usesCreateAuthFactory(config) && config.webDeploy === "cloudflare" ? `const evlogAuthHandle: Handle = async ({ event, resolve }) => {\n\tif (building) {\n\t\treturn resolve(event);\n\t}\n\n\tconst authEnv = event.platform?.env ?? localEnv;\n\tconst identifyUser = createAuthMiddleware(createAuth(authEnv) as BetterAuthInstance, ${authOptions});\n\tawait identifyUser(event.locals.log, event.request.headers, event.url.pathname);\n\treturn resolve(event);\n};\n\n` : `const identifyUser = createAuthMiddleware(${authExpression} as BetterAuthInstance, ${authOptions});\n\nconst evlogAuthHandle: Handle = async ({ event, resolve }) => {\n\tawait identifyUser(event.locals.log, event.request.headers, event.url.pathname);\n\treturn resolve(event);\n};\n\n`;
|
|
1708
|
-
nextContent = insertAfterOnce(nextContent, "const { handle: evlogHandle, handleError } = createEvlogHooks();\n\n", authHandleSnippet, "evlogAuthHandle");
|
|
1709
|
-
return nextContent.replace("sequence(evlogHandle as Handle, authHandle)", "sequence(evlogHandle as Handle, evlogAuthHandle, authHandle)").replace("sequence(evlogHandle, authHandle)", "sequence(evlogHandle as Handle, evlogAuthHandle, authHandle)");
|
|
1710
|
-
}
|
|
1711
|
-
function addAstroBetterAuthEvlogSetup(content, config) {
|
|
1712
|
-
if (content.includes("createAuthMiddleware(")) return content;
|
|
1713
|
-
let nextContent = addNamedImport(content, "evlog/better-auth", ["createAuthMiddleware", "type BetterAuthInstance"]);
|
|
1714
|
-
if (!nextContent.includes(`@${config.projectName}/auth`)) nextContent = prependMissingImports(nextContent, [getAuthImportLine(config)]);
|
|
1715
|
-
const authExpression = getAuthExpression(config);
|
|
1716
|
-
const authOptions = "{ exclude: [\"/api/auth/**\"], maskEmail: true }";
|
|
1717
|
-
const usesFactory = usesCreateAuthFactory(config);
|
|
1718
|
-
if (!usesFactory) nextContent = insertBeforeOnce(nextContent, "export const onRequest", `const identifyUser = createAuthMiddleware(${authExpression} as BetterAuthInstance, ${authOptions});\n\n`, "const identifyUser = createAuthMiddleware(");
|
|
1719
|
-
for (const marker of ["context.locals.log = log;", "locals.log = log;"]) {
|
|
1720
|
-
if (!nextContent.includes(marker)) continue;
|
|
1721
|
-
const requestExpression = marker.startsWith("context") ? "context.request" : "request";
|
|
1722
|
-
const identifySnippet = usesFactory ? `\n\n const identifyUser = createAuthMiddleware(${authExpression} as BetterAuthInstance, ${authOptions});\n await identifyUser(log, ${requestExpression}.headers, url.pathname);` : `\n\n await identifyUser(log, ${requestExpression}.headers, url.pathname);`;
|
|
1723
|
-
return insertAfterOnce(nextContent, marker, identifySnippet, "identifyUser(log");
|
|
1724
|
-
}
|
|
1725
|
-
return nextContent;
|
|
1726
|
-
}
|
|
1727
|
-
function getNextEvlogFile(serviceName) {
|
|
1728
|
-
return `import { createEvlog } from "evlog/next";
|
|
1729
|
-
import { createInstrumentation } from "evlog/next/instrumentation/create";
|
|
1730
|
-
|
|
1731
|
-
export const { withEvlog, useLogger, log, createError } = createEvlog({
|
|
1732
|
-
service: "${serviceName}",
|
|
1733
|
-
});
|
|
1734
|
-
|
|
1735
|
-
export const { register, onRequestError } = createInstrumentation({
|
|
1736
|
-
service: "${serviceName}",
|
|
1737
|
-
});
|
|
1738
|
-
`;
|
|
1739
|
-
}
|
|
1740
|
-
function getNextInstrumentationFile() {
|
|
1741
|
-
return `import { defineNodeInstrumentation } from "evlog/next/instrumentation";
|
|
1742
|
-
|
|
1743
|
-
export const { register, onRequestError } = defineNodeInstrumentation(() => import("./src/lib/evlog"));
|
|
1744
|
-
`;
|
|
1745
|
-
}
|
|
1746
|
-
function getNextProxyFile() {
|
|
1747
|
-
return `import { evlogMiddleware } from "evlog/next";
|
|
1748
|
-
|
|
1749
|
-
export const proxy = evlogMiddleware();
|
|
1750
|
-
|
|
1751
|
-
export const config = {
|
|
1752
|
-
matcher: ["/api/:path*"],
|
|
1753
|
-
};
|
|
1754
|
-
`;
|
|
1755
|
-
}
|
|
1756
|
-
function getNextEvlogAuthFile(config) {
|
|
1757
|
-
if (usesCreateAuthFactory(config)) return `${getAuthImportLine(config)}
|
|
1758
|
-
import { createAuthMiddleware, type BetterAuthInstance } from "evlog/better-auth";
|
|
1759
|
-
import { useLogger } from "@/lib/evlog";
|
|
1760
|
-
|
|
1761
|
-
export async function identifyEvlogUser(request: Request) {
|
|
1762
|
-
const identifyUser = createAuthMiddleware(${getAuthExpression(config)} as BetterAuthInstance, {
|
|
1763
|
-
exclude: ["/api/auth/**"],
|
|
1764
|
-
maskEmail: true,
|
|
1765
|
-
});
|
|
1766
|
-
await identifyUser(useLogger(), request.headers, new URL(request.url).pathname);
|
|
1767
|
-
}
|
|
1768
|
-
`;
|
|
1769
|
-
return `${getAuthImportLine(config)}
|
|
1770
|
-
import { createAuthMiddleware, type BetterAuthInstance } from "evlog/better-auth";
|
|
1771
|
-
import { useLogger } from "@/lib/evlog";
|
|
1772
|
-
|
|
1773
|
-
const identifyUser = createAuthMiddleware(${getAuthExpression(config)} as BetterAuthInstance, {
|
|
1774
|
-
exclude: ["/api/auth/**"],
|
|
1775
|
-
maskEmail: true,
|
|
1776
|
-
});
|
|
1777
|
-
|
|
1778
|
-
export async function identifyEvlogUser(request: Request) {
|
|
1779
|
-
await identifyUser(useLogger(), request.headers, new URL(request.url).pathname);
|
|
1780
|
-
}
|
|
1781
|
-
`;
|
|
1782
|
-
}
|
|
1783
|
-
function getNitroEvlogAuthPluginFile(config) {
|
|
1784
|
-
if (usesCreateAuthFactory(config)) return `${getAuthImportLine(config)}
|
|
1785
|
-
import { createAuthIdentifier, type BetterAuthInstance } from "evlog/better-auth";
|
|
1786
|
-
|
|
1787
|
-
export default defineNitroPlugin((nitroApp) => {
|
|
1788
|
-
nitroApp.hooks.hook("request", async (event) => {
|
|
1789
|
-
const identify = createAuthIdentifier(${getAuthExpression(config)} as BetterAuthInstance, {
|
|
1790
|
-
exclude: ["/api/auth/**"],
|
|
1791
|
-
maskEmail: true,
|
|
1792
|
-
});
|
|
1793
|
-
await identify(event);
|
|
1794
|
-
});
|
|
1795
|
-
});
|
|
1796
|
-
`;
|
|
1797
|
-
return `${getAuthImportLine(config)}
|
|
1798
|
-
import { createAuthIdentifier, type BetterAuthInstance } from "evlog/better-auth";
|
|
1799
|
-
|
|
1800
|
-
export default defineNitroPlugin((nitroApp) => {
|
|
1801
|
-
nitroApp.hooks.hook(
|
|
1802
|
-
"request",
|
|
1803
|
-
createAuthIdentifier(${getAuthExpression(config)} as BetterAuthInstance, {
|
|
1804
|
-
exclude: ["/api/auth/**"],
|
|
1805
|
-
maskEmail: true,
|
|
1806
|
-
}),
|
|
1807
|
-
);
|
|
1808
|
-
});
|
|
1809
|
-
`;
|
|
1810
|
-
}
|
|
1811
|
-
function getNuxtEvlogAuthMiddlewareFile(config) {
|
|
1812
|
-
if (usesCreateAuthFactory(config)) return `${getAuthImportLine(config)}
|
|
1813
|
-
import { createAuthMiddleware, type BetterAuthInstance } from "evlog/better-auth";
|
|
1814
|
-
|
|
1815
|
-
export default defineEventHandler(async (event) => {
|
|
1816
|
-
if (!event.context.log) return;
|
|
1817
|
-
const identify = createAuthMiddleware(${getAuthExpression(config)} as BetterAuthInstance, {
|
|
1818
|
-
exclude: ["/api/auth/**"],
|
|
1819
|
-
maskEmail: true,
|
|
1820
|
-
});
|
|
1821
|
-
await identify(event.context.log, event.headers, event.path);
|
|
1822
|
-
});
|
|
1823
|
-
`;
|
|
1824
|
-
return `${getAuthImportLine(config)}
|
|
1825
|
-
import { createAuthMiddleware, type BetterAuthInstance } from "evlog/better-auth";
|
|
1826
|
-
|
|
1827
|
-
const identify = createAuthMiddleware(${getAuthExpression(config)} as BetterAuthInstance, {
|
|
1828
|
-
exclude: ["/api/auth/**"],
|
|
1829
|
-
maskEmail: true,
|
|
1830
|
-
});
|
|
1831
|
-
|
|
1832
|
-
export default defineEventHandler(async (event) => {
|
|
1833
|
-
if (!event.context.log) return;
|
|
1834
|
-
await identify(event.context.log, event.headers, event.path);
|
|
1835
|
-
});
|
|
1836
|
-
`;
|
|
1837
|
-
}
|
|
1838
|
-
function getTanstackNitroConfigFile(serviceName) {
|
|
1839
|
-
return `import { defineConfig } from "nitro";
|
|
1840
|
-
import evlog from "evlog/nitro/v3";
|
|
1841
|
-
|
|
1842
|
-
export default defineConfig({
|
|
1843
|
-
experimental: {
|
|
1844
|
-
asyncContext: true,
|
|
1845
|
-
},
|
|
1846
|
-
modules: [
|
|
1847
|
-
evlog({
|
|
1848
|
-
env: { service: "${serviceName}" },
|
|
1849
|
-
}),
|
|
1850
|
-
],
|
|
1851
|
-
});
|
|
1852
|
-
`;
|
|
1853
|
-
}
|
|
1854
|
-
function getAstroMiddlewareFile(serviceName) {
|
|
1855
|
-
return `import { defineMiddleware } from "astro:middleware";
|
|
1856
|
-
import { createRequestLogger, initLogger } from "evlog";
|
|
1857
|
-
|
|
1858
|
-
initLogger({
|
|
1859
|
-
env: { service: "${serviceName}" },
|
|
1860
|
-
});
|
|
1861
|
-
|
|
1862
|
-
export const onRequest = defineMiddleware(async ({ request, locals }, next) => {
|
|
1863
|
-
const url = new URL(request.url);
|
|
1864
|
-
const log = createRequestLogger({
|
|
1865
|
-
method: request.method,
|
|
1866
|
-
path: url.pathname,
|
|
1867
|
-
});
|
|
1868
|
-
|
|
1869
|
-
locals.log = log;
|
|
1870
|
-
|
|
1871
|
-
try {
|
|
1872
|
-
const response = await next();
|
|
1873
|
-
log.emit();
|
|
1874
|
-
return response;
|
|
1875
|
-
} catch (error) {
|
|
1876
|
-
log.error(error instanceof Error ? error : new Error(String(error)));
|
|
1877
|
-
log.emit();
|
|
1878
|
-
throw error;
|
|
1879
|
-
}
|
|
1880
|
-
});
|
|
1881
|
-
`;
|
|
1882
|
-
}
|
|
1883
|
-
function getAstroEnvFile() {
|
|
1884
|
-
return `/// <reference types="astro/client" />
|
|
1885
|
-
|
|
1886
|
-
import type { RequestLogger } from "evlog";
|
|
1887
|
-
|
|
1888
|
-
declare namespace App {
|
|
1889
|
-
interface Locals {
|
|
1890
|
-
log: RequestLogger;
|
|
1891
|
-
}
|
|
1892
|
-
}
|
|
1893
|
-
`;
|
|
1894
|
-
}
|
|
1895
|
-
async function setupNextEvlog(config, serviceName) {
|
|
1896
|
-
const webDir = path.join(config.projectDir, "apps/web");
|
|
1897
|
-
const evlogPath = path.join(webDir, "src/lib/evlog.ts");
|
|
1898
|
-
if (!await fs.pathExists(evlogPath)) await writeFileIfChanged(evlogPath, getNextEvlogFile(serviceName));
|
|
1899
|
-
const identifyWebAuth = shouldIdentifyWebAuth(config);
|
|
1900
|
-
if (identifyWebAuth) {
|
|
1901
|
-
const evlogAuthPath = path.join(webDir, "src/lib/evlog-auth.ts");
|
|
1902
|
-
if (!await fs.pathExists(evlogAuthPath)) await writeFileIfChanged(evlogAuthPath, getNextEvlogAuthFile(config));
|
|
1903
|
-
}
|
|
1904
|
-
const instrumentationPath = path.join(webDir, "instrumentation.ts");
|
|
1905
|
-
if (!await fs.pathExists(instrumentationPath)) await writeFileIfChanged(instrumentationPath, getNextInstrumentationFile());
|
|
1906
|
-
const proxyPath = path.join(webDir, "src/proxy.ts");
|
|
1907
|
-
const rootProxyPath = path.join(webDir, "proxy.ts");
|
|
1908
|
-
if (!await fs.pathExists(proxyPath) && !await fs.pathExists(rootProxyPath)) await writeFileIfChanged(proxyPath, getNextProxyFile());
|
|
1909
|
-
const updateNextApiRoute = (content) => {
|
|
1910
|
-
let nextContent = addNextRouteWrappers(content);
|
|
1911
|
-
if (identifyWebAuth) nextContent = addNextBetterAuthToRoute(nextContent);
|
|
1912
|
-
return nextContent;
|
|
1913
|
-
};
|
|
1914
|
-
await updateFileIfExists(path.join(webDir, "src/app/api/trpc/[trpc]/route.ts"), updateNextApiRoute);
|
|
1915
|
-
await updateFileIfExists(path.join(webDir, "src/app/api/rpc/[[...rest]]/route.ts"), updateNextApiRoute);
|
|
1916
|
-
if (config.examples.includes("ai")) await updateFileIfExists(path.join(webDir, "src/app/api/ai/route.ts"), (content) => {
|
|
1917
|
-
let nextContent = addNextAiEvlogSetup(content);
|
|
1918
|
-
if (identifyWebAuth) nextContent = addNextBetterAuthToRoute(nextContent);
|
|
1919
|
-
return nextContent;
|
|
1920
|
-
});
|
|
1921
|
-
}
|
|
1922
|
-
async function setupNuxtEvlog(config, serviceName) {
|
|
1923
|
-
const webDir = path.join(config.projectDir, "apps/web");
|
|
1924
|
-
await updateFileIfExists(path.join(webDir, "nuxt.config.ts"), (content) => addNuxtEvlogSetup(content, serviceName));
|
|
1925
|
-
if (shouldIdentifyWebAuth(config)) {
|
|
1926
|
-
const oldAuthPluginPath = path.join(webDir, "server/plugins/evlog-auth.ts");
|
|
1927
|
-
if (await fs.pathExists(oldAuthPluginPath)) {
|
|
1928
|
-
if ((await fs.readFile(oldAuthPluginPath, "utf-8")).includes("evlog/better-auth")) await fs.remove(oldAuthPluginPath);
|
|
1929
|
-
}
|
|
1930
|
-
const authMiddlewarePath = path.join(webDir, "server/middleware/evlog-auth.ts");
|
|
1931
|
-
if (!await fs.pathExists(authMiddlewarePath)) await writeFileIfChanged(authMiddlewarePath, getNuxtEvlogAuthMiddlewareFile(config));
|
|
1932
|
-
}
|
|
1933
|
-
}
|
|
1934
|
-
async function setupSvelteEvlog(config, serviceName) {
|
|
1935
|
-
const webDir = path.join(config.projectDir, "apps/web");
|
|
1936
|
-
await updateFileIfExists(path.join(webDir, "vite.config.ts"), (content) => addSvelteViteEvlogSetup(content, serviceName));
|
|
1937
|
-
const hooksPath = path.join(webDir, "src/hooks.server.ts");
|
|
1938
|
-
if (await fs.pathExists(hooksPath)) await updateFileIfExists(hooksPath, addSvelteHooksEvlogSetup);
|
|
1939
|
-
else await writeFileIfChanged(hooksPath, `import { createEvlogHooks } from "evlog/sveltekit";
|
|
1940
|
-
|
|
1941
|
-
export const { handle, handleError } = createEvlogHooks();
|
|
1942
|
-
`);
|
|
1943
|
-
await updateFileIfExists(path.join(webDir, "src/app.d.ts"), addSvelteLocalsType);
|
|
1944
|
-
if (shouldIdentifyWebAuth(config)) await updateFileIfExists(path.join(webDir, "src/hooks.server.ts"), (content) => addSvelteBetterAuthEvlogSetup(content, config));
|
|
1945
|
-
}
|
|
1946
|
-
async function setupTanstackStartEvlog(config, serviceName) {
|
|
1947
|
-
const webDir = path.join(config.projectDir, "apps/web");
|
|
1948
|
-
const nitroConfigPath = path.join(webDir, "nitro.config.ts");
|
|
1949
|
-
if (!await fs.pathExists(nitroConfigPath)) await writeFileIfChanged(nitroConfigPath, getTanstackNitroConfigFile(serviceName));
|
|
1950
|
-
await updateFileIfExists(path.join(webDir, "src/routes/__root.tsx"), addTanstackStartRootEvlogSetup);
|
|
1951
|
-
if (shouldIdentifyWebAuth(config)) {
|
|
1952
|
-
const authPluginPath = path.join(webDir, "server/plugins/evlog-auth.ts");
|
|
1953
|
-
if (!await fs.pathExists(authPluginPath)) await writeFileIfChanged(authPluginPath, getNitroEvlogAuthPluginFile(config));
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
1956
|
-
async function setupAstroEvlog(config, serviceName) {
|
|
1957
|
-
const webDir = path.join(config.projectDir, "apps/web");
|
|
1958
|
-
const middlewarePath = path.join(webDir, "src/middleware.ts");
|
|
1959
|
-
if (!await fs.pathExists(middlewarePath)) await writeFileIfChanged(middlewarePath, getAstroMiddlewareFile(serviceName));
|
|
1960
|
-
else await updateFileIfExists(middlewarePath, (content) => addAstroMiddlewareEvlogSetup(content, serviceName));
|
|
1961
|
-
const envPath = path.join(webDir, "src/env.d.ts");
|
|
1962
|
-
if (!await fs.pathExists(envPath)) await writeFileIfChanged(envPath, getAstroEnvFile());
|
|
1963
|
-
else await updateFileIfExists(envPath, addAstroLocalsType);
|
|
1964
|
-
if (shouldIdentifyWebAuth(config)) await updateFileIfExists(middlewarePath, (content) => addAstroBetterAuthEvlogSetup(content, config));
|
|
1965
|
-
}
|
|
1966
|
-
async function setupEvlogWeb(config) {
|
|
1967
|
-
const frontend = getEvlogWebFrontend(config.frontend);
|
|
1968
|
-
if (!frontend) return;
|
|
1969
|
-
const serviceName = `${config.projectName}-web`;
|
|
1970
|
-
if (frontend === "next") await setupNextEvlog(config, serviceName);
|
|
1971
|
-
else if (frontend === "nuxt") await setupNuxtEvlog(config, serviceName);
|
|
1972
|
-
else if (frontend === "svelte") await setupSvelteEvlog(config, serviceName);
|
|
1973
|
-
else if (frontend === "tanstack-start") await setupTanstackStartEvlog(config, serviceName);
|
|
1974
|
-
else if (frontend === "astro") await setupAstroEvlog(config, serviceName);
|
|
1975
|
-
}
|
|
1976
|
-
async function setupEvlog(config) {
|
|
1977
|
-
return Result.tryPromise({
|
|
1978
|
-
try: async () => {
|
|
1979
|
-
if (isEvlogBackend(config.backend)) {
|
|
1980
|
-
const serverIndexPath = path.join(config.projectDir, "apps/server/src/index.ts");
|
|
1981
|
-
if (await fs.pathExists(serverIndexPath)) {
|
|
1982
|
-
const content = await fs.readFile(serverIndexPath, "utf-8");
|
|
1983
|
-
let nextContent = addEvlogServerSetup(content, config.backend, `${config.projectName}-server`);
|
|
1984
|
-
if (config.auth === "better-auth") nextContent = addEvlogBetterAuthServerSetup(nextContent, config.backend, getAuthExpression(config));
|
|
1985
|
-
if (nextContent !== content) await fs.writeFile(serverIndexPath, nextContent);
|
|
1986
|
-
}
|
|
1987
|
-
}
|
|
1988
|
-
await setupEvlogWeb(config);
|
|
1989
|
-
},
|
|
1990
|
-
catch: (error) => new AddonSetupError({
|
|
1991
|
-
addon: "evlog",
|
|
1992
|
-
message: `Failed to set up evlog: ${error instanceof Error ? error.message : String(error)}`,
|
|
1993
|
-
cause: error
|
|
1994
|
-
})
|
|
1995
|
-
});
|
|
1996
|
-
}
|
|
1997
|
-
//#endregion
|
|
1998
1344
|
//#region src/prompts/navigable-group.ts
|
|
1999
1345
|
/**
|
|
2000
1346
|
* Navigable group - a group of prompts that allows going back
|
|
@@ -2037,7 +1383,7 @@ async function navigableGroup(prompts, opts) {
|
|
|
2037
1383
|
goingBack = false;
|
|
2038
1384
|
continue;
|
|
2039
1385
|
}
|
|
2040
|
-
if (isCancel$
|
|
1386
|
+
if (isCancel$2(result)) {
|
|
2041
1387
|
if (typeof opts?.onCancel === "function") {
|
|
2042
1388
|
results[name] = "canceled";
|
|
2043
1389
|
opts.onCancel({ results });
|
|
@@ -2158,206 +1504,6 @@ function getPackageRunnerPrefix(packageManager) {
|
|
|
2158
1504
|
}
|
|
2159
1505
|
}
|
|
2160
1506
|
//#endregion
|
|
2161
|
-
//#region src/helpers/addons/fumadocs-setup.ts
|
|
2162
|
-
const TEMPLATES$2 = {
|
|
2163
|
-
"next-mdx": {
|
|
2164
|
-
label: "Next.js: Fumadocs MDX",
|
|
2165
|
-
hint: "recommended",
|
|
2166
|
-
value: "+next+fuma-docs-mdx"
|
|
2167
|
-
},
|
|
2168
|
-
"next-mdx-static": {
|
|
2169
|
-
label: "Next.js Static: Fumadocs MDX",
|
|
2170
|
-
value: "+next+fuma-docs-mdx+static"
|
|
2171
|
-
},
|
|
2172
|
-
waku: {
|
|
2173
|
-
label: "Waku: Fumadocs MDX",
|
|
2174
|
-
value: "waku"
|
|
2175
|
-
},
|
|
2176
|
-
"react-router": {
|
|
2177
|
-
label: "React Router: Fumadocs MDX (not RSC)",
|
|
2178
|
-
value: "react-router"
|
|
2179
|
-
},
|
|
2180
|
-
"react-router-spa": {
|
|
2181
|
-
label: "React Router SPA: Fumadocs MDX (not RSC)",
|
|
2182
|
-
hint: "SPA mode allows you to host the site statically, compatible with a CDN.",
|
|
2183
|
-
value: "react-router-spa"
|
|
2184
|
-
},
|
|
2185
|
-
"tanstack-start": {
|
|
2186
|
-
label: "Tanstack Start: Fumadocs MDX (not RSC)",
|
|
2187
|
-
value: "tanstack-start"
|
|
2188
|
-
},
|
|
2189
|
-
"tanstack-start-spa": {
|
|
2190
|
-
label: "Tanstack Start SPA: Fumadocs MDX (not RSC)",
|
|
2191
|
-
hint: "SPA mode allows you to host the site statically, compatible with a CDN.",
|
|
2192
|
-
value: "tanstack-start-spa"
|
|
2193
|
-
}
|
|
2194
|
-
};
|
|
2195
|
-
const DEFAULT_TEMPLATE$2 = "next-mdx";
|
|
2196
|
-
const DEFAULT_DEV_PORT$1 = 4e3;
|
|
2197
|
-
function aiChatDisabledForTemplate(template) {
|
|
2198
|
-
return template === "next-mdx-static" || template.endsWith("-spa");
|
|
2199
|
-
}
|
|
2200
|
-
function getFumadocsLinter(addons) {
|
|
2201
|
-
if (addons.includes("oxlint")) return "oxlint";
|
|
2202
|
-
if (addons.includes("biome") || addons.includes("ultracite")) return "biome";
|
|
2203
|
-
if (addons.includes("vite-plus")) return "oxlint";
|
|
2204
|
-
}
|
|
2205
|
-
function getFumadocsAddonContext(currentAddons, persistedAddons) {
|
|
2206
|
-
return Array.from(new Set([...persistedAddons ?? [], ...currentAddons]));
|
|
2207
|
-
}
|
|
2208
|
-
async function setupFumadocs(config) {
|
|
2209
|
-
if (shouldSkipExternalCommands()) return Result.ok(void 0);
|
|
2210
|
-
const { packageManager, projectDir } = config;
|
|
2211
|
-
cliLog.info("Setting up Fumadocs...");
|
|
2212
|
-
const configuredOptions = config.addonOptions?.fumadocs;
|
|
2213
|
-
let template = configuredOptions?.template;
|
|
2214
|
-
let search = configuredOptions?.search;
|
|
2215
|
-
let ogImage = configuredOptions?.ogImage;
|
|
2216
|
-
let aiChat = configuredOptions?.aiChat;
|
|
2217
|
-
if (isSilent()) template = template ?? DEFAULT_TEMPLATE$2;
|
|
2218
|
-
else {
|
|
2219
|
-
const promptResult = await Result.tryPromise({
|
|
2220
|
-
try: () => navigableGroup({
|
|
2221
|
-
template: async () => {
|
|
2222
|
-
if (template !== void 0) return template;
|
|
2223
|
-
return navigableSelect({
|
|
2224
|
-
message: "Choose a template",
|
|
2225
|
-
options: Object.entries(TEMPLATES$2).map(([key, t]) => ({
|
|
2226
|
-
value: key,
|
|
2227
|
-
label: t.label,
|
|
2228
|
-
hint: "hint" in t ? t.hint : void 0
|
|
2229
|
-
})),
|
|
2230
|
-
initialValue: DEFAULT_TEMPLATE$2
|
|
2231
|
-
});
|
|
2232
|
-
},
|
|
2233
|
-
search: async () => {
|
|
2234
|
-
if (search !== void 0) return search;
|
|
2235
|
-
return navigableSelect({
|
|
2236
|
-
message: "Choose a search solution?",
|
|
2237
|
-
options: [{
|
|
2238
|
-
value: "orama",
|
|
2239
|
-
label: "Default",
|
|
2240
|
-
hint: "local search powered by Orama, recommended"
|
|
2241
|
-
}, {
|
|
2242
|
-
value: "orama-cloud",
|
|
2243
|
-
label: "Orama Cloud",
|
|
2244
|
-
hint: "3rd party search solution, signup needed"
|
|
2245
|
-
}],
|
|
2246
|
-
initialValue: "orama"
|
|
2247
|
-
});
|
|
2248
|
-
},
|
|
2249
|
-
ogImage: async ({ results }) => {
|
|
2250
|
-
if (ogImage !== void 0) return ogImage;
|
|
2251
|
-
if (!(results.template ?? template ?? DEFAULT_TEMPLATE$2).startsWith("next-")) return "skip";
|
|
2252
|
-
return navigableSelect({
|
|
2253
|
-
message: "Configure Open Graph Image generation?",
|
|
2254
|
-
options: [{
|
|
2255
|
-
value: "next-og",
|
|
2256
|
-
label: "next/og",
|
|
2257
|
-
hint: "Next.js built-in solution"
|
|
2258
|
-
}, {
|
|
2259
|
-
value: "takumi",
|
|
2260
|
-
label: "Takumi",
|
|
2261
|
-
hint: "Output WebP format, framework-agnostic"
|
|
2262
|
-
}],
|
|
2263
|
-
initialValue: "next-og"
|
|
2264
|
-
});
|
|
2265
|
-
},
|
|
2266
|
-
aiChat: async ({ results }) => {
|
|
2267
|
-
if (aiChat !== void 0) return aiChat;
|
|
2268
|
-
if (aiChatDisabledForTemplate(results.template ?? template ?? DEFAULT_TEMPLATE$2)) return "none";
|
|
2269
|
-
return navigableSelect({
|
|
2270
|
-
message: "Configure AI Chat?",
|
|
2271
|
-
options: [
|
|
2272
|
-
{
|
|
2273
|
-
value: "none",
|
|
2274
|
-
label: "No"
|
|
2275
|
-
},
|
|
2276
|
-
{
|
|
2277
|
-
value: "openrouter",
|
|
2278
|
-
label: "AI SDK",
|
|
2279
|
-
hint: "default to OpenRouter"
|
|
2280
|
-
},
|
|
2281
|
-
{
|
|
2282
|
-
value: "llmgateway",
|
|
2283
|
-
label: "LLM Gateway"
|
|
2284
|
-
},
|
|
2285
|
-
{
|
|
2286
|
-
value: "inkeep",
|
|
2287
|
-
label: "Inkeep AI",
|
|
2288
|
-
hint: "API key required"
|
|
2289
|
-
}
|
|
2290
|
-
],
|
|
2291
|
-
initialValue: "none"
|
|
2292
|
-
});
|
|
2293
|
-
}
|
|
2294
|
-
}),
|
|
2295
|
-
catch: (e) => new AddonSetupError({
|
|
2296
|
-
addon: "fumadocs",
|
|
2297
|
-
message: `Failed to run Fumadocs prompts: ${e instanceof Error ? e.message : String(e)}`,
|
|
2298
|
-
cause: e
|
|
2299
|
-
})
|
|
2300
|
-
});
|
|
2301
|
-
if (promptResult.isErr()) return Result.err(promptResult.error);
|
|
2302
|
-
const results = promptResult.value;
|
|
2303
|
-
if (results.template === void 0 || results.search === void 0 || results.ogImage === void 0 || results.aiChat === void 0) return userCancelled("Operation cancelled");
|
|
2304
|
-
template = results.template;
|
|
2305
|
-
search = results.search;
|
|
2306
|
-
ogImage = results.ogImage === "skip" ? void 0 : results.ogImage;
|
|
2307
|
-
aiChat = results.aiChat === "none" ? void 0 : results.aiChat;
|
|
2308
|
-
}
|
|
2309
|
-
if (!template) return userCancelled("Operation cancelled");
|
|
2310
|
-
const isNextTemplate = template.startsWith("next-");
|
|
2311
|
-
if (!isNextTemplate) ogImage = void 0;
|
|
2312
|
-
if (aiChatDisabledForTemplate(template)) aiChat = void 0;
|
|
2313
|
-
const templateArg = TEMPLATES$2[template].value;
|
|
2314
|
-
const devPort = configuredOptions?.devPort ?? DEFAULT_DEV_PORT$1;
|
|
2315
|
-
const options = [
|
|
2316
|
-
`--template ${templateArg}`,
|
|
2317
|
-
`--pm ${packageManager}`,
|
|
2318
|
-
"--no-git"
|
|
2319
|
-
];
|
|
2320
|
-
if (isNextTemplate) options.push("--src");
|
|
2321
|
-
const persistedConfig = await readBtsConfig(projectDir);
|
|
2322
|
-
const linter = getFumadocsLinter(getFumadocsAddonContext(config.addons, persistedConfig?.addons));
|
|
2323
|
-
if (linter) options.push(`--linter ${linter}`);
|
|
2324
|
-
if (search) options.push(`--search ${search}`);
|
|
2325
|
-
if (ogImage) options.push(`--og-image ${ogImage}`);
|
|
2326
|
-
if (aiChat) options.push(`--ai-chat ${aiChat}`);
|
|
2327
|
-
const args = getPackageExecutionArgs(packageManager, `create-fumadocs-app@latest fumadocs ${options.join(" ")}`);
|
|
2328
|
-
const appsDir = path.join(projectDir, "apps");
|
|
2329
|
-
await fs.ensureDir(appsDir);
|
|
2330
|
-
const s = createSpinner();
|
|
2331
|
-
s.start("Running Fumadocs create command...");
|
|
2332
|
-
const result = await Result.tryPromise({
|
|
2333
|
-
try: async () => {
|
|
2334
|
-
await $({
|
|
2335
|
-
cwd: appsDir,
|
|
2336
|
-
env: { CI: "true" }
|
|
2337
|
-
})`${args}`;
|
|
2338
|
-
const fumadocsDir = path.join(projectDir, "apps", "fumadocs");
|
|
2339
|
-
const packageJsonPath = path.join(fumadocsDir, "package.json");
|
|
2340
|
-
if (await fs.pathExists(packageJsonPath)) {
|
|
2341
|
-
const packageJson = await fs.readJson(packageJsonPath);
|
|
2342
|
-
packageJson.name = "fumadocs";
|
|
2343
|
-
if (packageJson.scripts?.dev) packageJson.scripts.dev = `${packageJson.scripts.dev} --port=${devPort}`;
|
|
2344
|
-
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2345
|
-
}
|
|
2346
|
-
},
|
|
2347
|
-
catch: (e) => new AddonSetupError({
|
|
2348
|
-
addon: "fumadocs",
|
|
2349
|
-
message: `Failed to set up Fumadocs: ${e instanceof Error ? e.message : String(e)}`,
|
|
2350
|
-
cause: e
|
|
2351
|
-
})
|
|
2352
|
-
});
|
|
2353
|
-
if (result.isErr()) {
|
|
2354
|
-
s.stop("Failed to set up Fumadocs");
|
|
2355
|
-
return result;
|
|
2356
|
-
}
|
|
2357
|
-
s.stop("Fumadocs setup complete!");
|
|
2358
|
-
return Result.ok(void 0);
|
|
2359
|
-
}
|
|
2360
|
-
//#endregion
|
|
2361
1507
|
//#region src/helpers/addons/mcp-setup.ts
|
|
2362
1508
|
const MCP_AGENTS = [
|
|
2363
1509
|
{
|
|
@@ -2432,7 +1578,7 @@ const MCP_AGENTS = [
|
|
|
2432
1578
|
}
|
|
2433
1579
|
];
|
|
2434
1580
|
const DEFAULT_SCOPE$1 = "project";
|
|
2435
|
-
const DEFAULT_AGENTS$
|
|
1581
|
+
const DEFAULT_AGENTS$1 = [
|
|
2436
1582
|
"cursor",
|
|
2437
1583
|
"claude-code",
|
|
2438
1584
|
"vscode"
|
|
@@ -2450,7 +1596,7 @@ function getAllMcpServers(config) {
|
|
|
2450
1596
|
return [
|
|
2451
1597
|
{
|
|
2452
1598
|
key: "kubojs",
|
|
2453
|
-
label: "
|
|
1599
|
+
label: "kubojs",
|
|
2454
1600
|
name: "kubojs",
|
|
2455
1601
|
target: getPackageExecutionCommand(config.packageManager, "kubojs@latest mcp")
|
|
2456
1602
|
},
|
|
@@ -2556,7 +1702,6 @@ function getAllMcpServers(config) {
|
|
|
2556
1702
|
function getRecommendedMcpServers(config, scope) {
|
|
2557
1703
|
const serversByKey = new Map(getAllMcpServers(config).map((server) => [server.key, server]));
|
|
2558
1704
|
const recommendedServerKeys = ["kubojs", "context7"];
|
|
2559
|
-
if (scope === "project" && config.addons.includes("nx")) recommendedServerKeys.push("nx");
|
|
2560
1705
|
if (config.runtime === "workers" || config.webDeploy === "cloudflare" || config.serverDeploy === "cloudflare") recommendedServerKeys.push("cloudflare-docs");
|
|
2561
1706
|
if (config.backend === "convex") recommendedServerKeys.push("convex");
|
|
2562
1707
|
if (hasReactBasedFrontend$1(config.frontend)) recommendedServerKeys.push("shadcn");
|
|
@@ -2595,7 +1740,7 @@ async function setupMcp(config) {
|
|
|
2595
1740
|
selectedServerKeys = configuredServerKeys?.filter((k) => availableServerKeys.has(k)) ?? serverOptions;
|
|
2596
1741
|
if (selectedServerKeys.length === 0) return Result.ok(void 0);
|
|
2597
1742
|
const agentOptions = filterAgentsForScope(scope);
|
|
2598
|
-
const defaultAgents = uniqueValues$1(DEFAULT_AGENTS$
|
|
1743
|
+
const defaultAgents = uniqueValues$1(DEFAULT_AGENTS$1.filter((a) => agentOptions.some((o) => o.value === a)));
|
|
2599
1744
|
selectedAgents = configuredAgents?.filter((a) => agentOptions.some((o) => o.value === a)) ?? defaultAgents;
|
|
2600
1745
|
if (selectedAgents.length === 0) return Result.ok(void 0);
|
|
2601
1746
|
} else {
|
|
@@ -2638,7 +1783,7 @@ async function setupMcp(config) {
|
|
|
2638
1783
|
if (currentServers !== void 0 && currentServers.length === 0) return [];
|
|
2639
1784
|
const agentOpts = filterAgentsForScope(currentScope);
|
|
2640
1785
|
if (agentOpts.length === 0) return [];
|
|
2641
|
-
const defaults = uniqueValues$1(DEFAULT_AGENTS$
|
|
1786
|
+
const defaults = uniqueValues$1(DEFAULT_AGENTS$1.filter((a) => agentOpts.some((o) => o.value === a)));
|
|
2642
1787
|
if (configuredAgents !== void 0) return configuredAgents.filter((a) => agentOpts.some((o) => o.value === a));
|
|
2643
1788
|
return navigableMultiselect({
|
|
2644
1789
|
message: "Select agents to install MCP servers to",
|
|
@@ -2884,7 +2029,7 @@ const AVAILABLE_AGENTS = [
|
|
|
2884
2029
|
}
|
|
2885
2030
|
];
|
|
2886
2031
|
const DEFAULT_SCOPE = "project";
|
|
2887
|
-
const DEFAULT_AGENTS
|
|
2032
|
+
const DEFAULT_AGENTS = [
|
|
2888
2033
|
"cursor",
|
|
2889
2034
|
"claude-code",
|
|
2890
2035
|
"github-copilot"
|
|
@@ -2919,8 +2064,6 @@ function getRecommendedSourceKeys(config) {
|
|
|
2919
2064
|
if (backend === "elysia") sources.push("elysiajs/skills");
|
|
2920
2065
|
if (backend === "convex") sources.push("waynesutton/convexskills");
|
|
2921
2066
|
if (addons.includes("opentui")) sources.push("msmps/opentui-skill");
|
|
2922
|
-
if (addons.includes("ultracite")) sources.push("haydenbleasel/ultracite");
|
|
2923
|
-
if (addons.includes("evlog")) sources.push("https://www.evlog.dev");
|
|
2924
2067
|
return sources;
|
|
2925
2068
|
}
|
|
2926
2069
|
const CURATED_SKILLS_BY_SOURCE = {
|
|
@@ -3001,11 +2144,11 @@ function uniqueValues(values) {
|
|
|
3001
2144
|
async function setupSkills(config) {
|
|
3002
2145
|
if (shouldSkipExternalCommands()) return Result.ok(void 0);
|
|
3003
2146
|
const { packageManager, projectDir } = config;
|
|
3004
|
-
const
|
|
3005
|
-
const fullConfig =
|
|
2147
|
+
const kubojsConfig = await readKubojsConfig(projectDir);
|
|
2148
|
+
const fullConfig = kubojsConfig ? {
|
|
3006
2149
|
...config,
|
|
3007
|
-
addons:
|
|
3008
|
-
addonOptions:
|
|
2150
|
+
addons: kubojsConfig.addons ?? config.addons,
|
|
2151
|
+
addonOptions: kubojsConfig.addonOptions ?? config.addonOptions
|
|
3009
2152
|
} : config;
|
|
3010
2153
|
const recommendedSourceKeys = getRecommendedSourceKeys(fullConfig);
|
|
3011
2154
|
const skillsOptions = fullConfig.addonOptions?.skills;
|
|
@@ -3032,7 +2175,7 @@ async function setupSkills(config) {
|
|
|
3032
2175
|
scope = configuredScope ?? DEFAULT_SCOPE;
|
|
3033
2176
|
selectedSkills = configuredSelections !== void 0 ? configuredSelections.flatMap((selection) => selection.skills.map((skill) => `${selection.source}::${skill}`)) : allSkillValues;
|
|
3034
2177
|
if (selectedSkills.length === 0) return Result.ok(void 0);
|
|
3035
|
-
selectedAgents = configuredAgents ? [...configuredAgents] : [...DEFAULT_AGENTS
|
|
2178
|
+
selectedAgents = configuredAgents ? [...configuredAgents] : [...DEFAULT_AGENTS];
|
|
3036
2179
|
if (selectedAgents.length === 0) return Result.ok(void 0);
|
|
3037
2180
|
} else {
|
|
3038
2181
|
const results = await navigableGroup({
|
|
@@ -3069,7 +2212,7 @@ async function setupSkills(config) {
|
|
|
3069
2212
|
message: "Select agents to install skills to",
|
|
3070
2213
|
options: AVAILABLE_AGENTS,
|
|
3071
2214
|
required: false,
|
|
3072
|
-
initialValues: [...DEFAULT_AGENTS
|
|
2215
|
+
initialValues: [...DEFAULT_AGENTS]
|
|
3073
2216
|
});
|
|
3074
2217
|
}
|
|
3075
2218
|
});
|
|
@@ -3118,44 +2261,6 @@ async function setupSkills(config) {
|
|
|
3118
2261
|
return Result.ok(void 0);
|
|
3119
2262
|
}
|
|
3120
2263
|
//#endregion
|
|
3121
|
-
//#region src/helpers/addons/starlight-setup.ts
|
|
3122
|
-
async function setupStarlight(config) {
|
|
3123
|
-
if (shouldSkipExternalCommands()) return Result.ok(void 0);
|
|
3124
|
-
const { packageManager, projectDir } = config;
|
|
3125
|
-
const s = createSpinner();
|
|
3126
|
-
s.start("Setting up Starlight docs...");
|
|
3127
|
-
const args = getPackageExecutionArgs(packageManager, `create-astro@latest ${[
|
|
3128
|
-
"docs",
|
|
3129
|
-
"--template",
|
|
3130
|
-
"starlight",
|
|
3131
|
-
"--yes",
|
|
3132
|
-
"--no-install",
|
|
3133
|
-
"--no-git",
|
|
3134
|
-
"--skip-houston"
|
|
3135
|
-
].join(" ")}`);
|
|
3136
|
-
const appsDir = path.join(projectDir, "apps");
|
|
3137
|
-
await fs.ensureDir(appsDir);
|
|
3138
|
-
const result = await Result.tryPromise({
|
|
3139
|
-
try: async () => {
|
|
3140
|
-
await $({
|
|
3141
|
-
cwd: appsDir,
|
|
3142
|
-
env: { CI: "true" }
|
|
3143
|
-
})`${args}`;
|
|
3144
|
-
},
|
|
3145
|
-
catch: (e) => new AddonSetupError({
|
|
3146
|
-
addon: "starlight",
|
|
3147
|
-
message: `Failed to set up Starlight docs: ${e instanceof Error ? e.message : String(e)}`,
|
|
3148
|
-
cause: e
|
|
3149
|
-
})
|
|
3150
|
-
});
|
|
3151
|
-
if (result.isErr()) {
|
|
3152
|
-
s.stop("Failed to set up Starlight docs");
|
|
3153
|
-
return result;
|
|
3154
|
-
}
|
|
3155
|
-
s.stop("Starlight docs setup successfully!");
|
|
3156
|
-
return Result.ok(void 0);
|
|
3157
|
-
}
|
|
3158
|
-
//#endregion
|
|
3159
2264
|
//#region src/helpers/addons/tauri-setup.ts
|
|
3160
2265
|
function getWebFrontend(frontend) {
|
|
3161
2266
|
return frontend.find((value) => types_exports.desktopWebFrontends.includes(value));
|
|
@@ -3236,7 +2341,7 @@ async function setupTauri(config) {
|
|
|
3236
2341
|
}
|
|
3237
2342
|
//#endregion
|
|
3238
2343
|
//#region src/helpers/addons/tui-setup.ts
|
|
3239
|
-
const TEMPLATES
|
|
2344
|
+
const TEMPLATES = {
|
|
3240
2345
|
core: {
|
|
3241
2346
|
label: "Core",
|
|
3242
2347
|
hint: "Basic OpenTUI template"
|
|
@@ -3250,7 +2355,7 @@ const TEMPLATES$1 = {
|
|
|
3250
2355
|
hint: "SolidJS-based OpenTUI template"
|
|
3251
2356
|
}
|
|
3252
2357
|
};
|
|
3253
|
-
const DEFAULT_TEMPLATE
|
|
2358
|
+
const DEFAULT_TEMPLATE = "core";
|
|
3254
2359
|
const TUI_LOCKFILES = [
|
|
3255
2360
|
"bun.lock",
|
|
3256
2361
|
"package-lock.json",
|
|
@@ -3260,7 +2365,7 @@ const TUI_LOCKFILES = [
|
|
|
3260
2365
|
function resolveTuiTemplate(config) {
|
|
3261
2366
|
const configuredTemplate = config.addonOptions?.opentui?.template;
|
|
3262
2367
|
if (configuredTemplate) return configuredTemplate;
|
|
3263
|
-
if (isSilent()) return DEFAULT_TEMPLATE
|
|
2368
|
+
if (isSilent()) return DEFAULT_TEMPLATE;
|
|
3264
2369
|
}
|
|
3265
2370
|
async function setupTui(config) {
|
|
3266
2371
|
if (shouldSkipExternalCommands()) return Result.ok(void 0);
|
|
@@ -3271,14 +2376,14 @@ async function setupTui(config) {
|
|
|
3271
2376
|
setIsFirstPrompt(true);
|
|
3272
2377
|
const selectedTemplate = await navigableSelect({
|
|
3273
2378
|
message: "Choose a template",
|
|
3274
|
-
options: Object.entries(TEMPLATES
|
|
2379
|
+
options: Object.entries(TEMPLATES).map(([key, templateOption]) => ({
|
|
3275
2380
|
value: key,
|
|
3276
2381
|
label: templateOption.label,
|
|
3277
2382
|
hint: templateOption.hint
|
|
3278
2383
|
})),
|
|
3279
|
-
initialValue: DEFAULT_TEMPLATE
|
|
2384
|
+
initialValue: DEFAULT_TEMPLATE
|
|
3280
2385
|
});
|
|
3281
|
-
if (isCancel$
|
|
2386
|
+
if (isCancel$2(selectedTemplate)) return userCancelled("Operation cancelled");
|
|
3282
2387
|
template = selectedTemplate;
|
|
3283
2388
|
}
|
|
3284
2389
|
const args = getPackageExecutionArgs(packageManager, `create-tui@latest --template ${template} --no-git --no-install tui`);
|
|
@@ -3356,324 +2461,6 @@ async function postProcessTuiWorkspace(tuiDir) {
|
|
|
3356
2461
|
return Result.ok(void 0);
|
|
3357
2462
|
}
|
|
3358
2463
|
//#endregion
|
|
3359
|
-
//#region src/helpers/addons/ultracite-setup.ts
|
|
3360
|
-
const LINTERS = {
|
|
3361
|
-
biome: { label: "Biome (Recommended)" },
|
|
3362
|
-
oxlint: { label: "Oxlint + Oxfmt" }
|
|
3363
|
-
};
|
|
3364
|
-
const AGENTS = {
|
|
3365
|
-
universal: { label: "Universal (AGENTS.md — covers all agents)" },
|
|
3366
|
-
claude: { label: "Claude Code" },
|
|
3367
|
-
codex: { label: "Codex" },
|
|
3368
|
-
jules: { label: "Jules" },
|
|
3369
|
-
replit: { label: "Replit Agent" },
|
|
3370
|
-
devin: { label: "Devin" },
|
|
3371
|
-
lovable: { label: "Lovable" },
|
|
3372
|
-
zencoder: { label: "Zencoder" },
|
|
3373
|
-
ona: { label: "Ona" },
|
|
3374
|
-
openclaw: { label: "OpenClaw" },
|
|
3375
|
-
continue: { label: "Continue" },
|
|
3376
|
-
"snowflake-cortex": { label: "Snowflake Cortex" },
|
|
3377
|
-
deepagents: { label: "Deepagents" },
|
|
3378
|
-
qoder: { label: "Qoder" },
|
|
3379
|
-
"kimi-cli": { label: "Kimi CLI" },
|
|
3380
|
-
mcpjam: { label: "MCPJam" },
|
|
3381
|
-
mux: { label: "Mux" },
|
|
3382
|
-
pi: { label: "Pi" },
|
|
3383
|
-
adal: { label: "AdaL" },
|
|
3384
|
-
copilot: { label: "GitHub Copilot" },
|
|
3385
|
-
cline: { label: "Cline" },
|
|
3386
|
-
amp: { label: "AMP" },
|
|
3387
|
-
aider: { label: "Aider" },
|
|
3388
|
-
"firebase-studio": { label: "Firebase Studio" },
|
|
3389
|
-
"open-hands": { label: "OpenHands" },
|
|
3390
|
-
gemini: { label: "Gemini" },
|
|
3391
|
-
junie: { label: "Junie" },
|
|
3392
|
-
augmentcode: { label: "Augment Code" },
|
|
3393
|
-
bob: { label: "IBM Bob" },
|
|
3394
|
-
"kilo-code": { label: "Kilo Code" },
|
|
3395
|
-
goose: { label: "Goose" },
|
|
3396
|
-
"roo-code": { label: "Roo Code" },
|
|
3397
|
-
warp: { label: "Warp" },
|
|
3398
|
-
droid: { label: "Droid" },
|
|
3399
|
-
opencode: { label: "OpenCode" },
|
|
3400
|
-
crush: { label: "Crush" },
|
|
3401
|
-
qwen: { label: "Qwen Code" },
|
|
3402
|
-
"amazon-q-cli": { label: "Amazon Q CLI" },
|
|
3403
|
-
firebender: { label: "Firebender" },
|
|
3404
|
-
"cursor-cli": { label: "Cursor CLI" },
|
|
3405
|
-
"mistral-vibe": { label: "Mistral Vibe" },
|
|
3406
|
-
vercel: { label: "Vercel Agent" }
|
|
3407
|
-
};
|
|
3408
|
-
const HOOKS = {
|
|
3409
|
-
cursor: { label: "Cursor" },
|
|
3410
|
-
windsurf: { label: "Windsurf" },
|
|
3411
|
-
codebuddy: { label: "CodeBuddy" },
|
|
3412
|
-
claude: { label: "Claude Code" },
|
|
3413
|
-
copilot: { label: "GitHub Copilot" }
|
|
3414
|
-
};
|
|
3415
|
-
const ULTRACITE_VERSION = "7.9.3";
|
|
3416
|
-
const DEFAULT_LINTER = "biome";
|
|
3417
|
-
const DEFAULT_EDITORS = ["vscode"];
|
|
3418
|
-
const DEFAULT_AGENTS = ["universal"];
|
|
3419
|
-
const DEFAULT_HOOKS = [];
|
|
3420
|
-
function getFrameworksFromFrontend(frontend) {
|
|
3421
|
-
const frameworkMap = {
|
|
3422
|
-
"tanstack-router": ["react", "tanstack"],
|
|
3423
|
-
"react-router": ["react", "remix"],
|
|
3424
|
-
"tanstack-start": ["react", "tanstack"],
|
|
3425
|
-
next: ["react", "next"],
|
|
3426
|
-
nuxt: ["vue"],
|
|
3427
|
-
"native-bare": ["react"],
|
|
3428
|
-
"native-uniwind": ["react"],
|
|
3429
|
-
"native-unistyles": ["react"],
|
|
3430
|
-
svelte: ["svelte"],
|
|
3431
|
-
solid: ["solid"],
|
|
3432
|
-
astro: ["astro"]
|
|
3433
|
-
};
|
|
3434
|
-
const frameworks = /* @__PURE__ */ new Set();
|
|
3435
|
-
for (const f of frontend) for (const framework of frameworkMap[f] ?? []) frameworks.add(framework);
|
|
3436
|
-
return Array.from(frameworks);
|
|
3437
|
-
}
|
|
3438
|
-
function buildUltraciteInitArgs({ packageManager, linter, frameworks, editors, agents, hooks, gitHooks }) {
|
|
3439
|
-
const ultraciteArgs = [
|
|
3440
|
-
"init",
|
|
3441
|
-
"--pm",
|
|
3442
|
-
packageManager,
|
|
3443
|
-
"--linter",
|
|
3444
|
-
linter
|
|
3445
|
-
];
|
|
3446
|
-
if (frameworks.length > 0) ultraciteArgs.push("--frameworks", ...frameworks);
|
|
3447
|
-
if (editors.length > 0) ultraciteArgs.push("--editors", ...editors);
|
|
3448
|
-
if (agents.length > 0) ultraciteArgs.push("--agents", ...agents);
|
|
3449
|
-
if (hooks.length > 0) ultraciteArgs.push("--hooks", ...hooks);
|
|
3450
|
-
if (gitHooks.length > 0) ultraciteArgs.push("--integrations", ...gitHooks);
|
|
3451
|
-
return [
|
|
3452
|
-
...getPackageRunnerPrefix(packageManager),
|
|
3453
|
-
`ultracite@${ULTRACITE_VERSION}`,
|
|
3454
|
-
...ultraciteArgs,
|
|
3455
|
-
"--skip-install",
|
|
3456
|
-
"--quiet"
|
|
3457
|
-
];
|
|
3458
|
-
}
|
|
3459
|
-
async function removeHuskySampleHook(projectDir) {
|
|
3460
|
-
const hookPath = path.join(projectDir, ".husky", "pre-commit");
|
|
3461
|
-
if (!await fs.pathExists(hookPath)) return;
|
|
3462
|
-
const content = await fs.readFile(hookPath, "utf-8");
|
|
3463
|
-
const cleaned = content.split("\n").filter((line) => !/^(?:npm|pnpm|yarn|bun|deno)(?: run)? test$/.test(line.trim())).join("\n");
|
|
3464
|
-
if (cleaned !== content) await fs.writeFile(hookPath, cleaned);
|
|
3465
|
-
}
|
|
3466
|
-
async function setupUltracite(config, gitHooks) {
|
|
3467
|
-
if (shouldSkipExternalCommands()) return Result.ok(void 0);
|
|
3468
|
-
const { packageManager, projectDir, frontend } = config;
|
|
3469
|
-
cliLog.info("Setting up Ultracite...");
|
|
3470
|
-
const configuredOptions = config.addonOptions?.ultracite;
|
|
3471
|
-
let linter = configuredOptions?.linter;
|
|
3472
|
-
let editors = configuredOptions?.editors;
|
|
3473
|
-
let agents = configuredOptions?.agents;
|
|
3474
|
-
let hooks = configuredOptions?.hooks;
|
|
3475
|
-
if (!linter || !editors || !agents || !hooks) if (isSilent()) {
|
|
3476
|
-
linter = linter ?? DEFAULT_LINTER;
|
|
3477
|
-
editors = editors ?? [...DEFAULT_EDITORS];
|
|
3478
|
-
agents = agents ?? [...DEFAULT_AGENTS];
|
|
3479
|
-
hooks = hooks ?? [...DEFAULT_HOOKS];
|
|
3480
|
-
} else {
|
|
3481
|
-
const results = await navigableGroup({
|
|
3482
|
-
linter: async () => {
|
|
3483
|
-
if (linter !== void 0) return linter;
|
|
3484
|
-
return navigableSelect({
|
|
3485
|
-
message: "Which linter do you want to use?",
|
|
3486
|
-
options: Object.entries(LINTERS).map(([key, linterOption]) => ({
|
|
3487
|
-
value: key,
|
|
3488
|
-
label: linterOption.label
|
|
3489
|
-
})),
|
|
3490
|
-
initialValue: linter ?? DEFAULT_LINTER
|
|
3491
|
-
});
|
|
3492
|
-
},
|
|
3493
|
-
editors: async () => {
|
|
3494
|
-
if (editors !== void 0) return editors;
|
|
3495
|
-
return navigableMultiselect({
|
|
3496
|
-
message: "Which editors do you want to configure (recommended)?",
|
|
3497
|
-
required: false,
|
|
3498
|
-
options: [{
|
|
3499
|
-
value: "vscode",
|
|
3500
|
-
label: "VSCode / Cursor / Windsurf"
|
|
3501
|
-
}, {
|
|
3502
|
-
value: "zed",
|
|
3503
|
-
label: "Zed"
|
|
3504
|
-
}]
|
|
3505
|
-
});
|
|
3506
|
-
},
|
|
3507
|
-
agents: async () => {
|
|
3508
|
-
if (agents !== void 0) return agents;
|
|
3509
|
-
return navigableMultiselect({
|
|
3510
|
-
message: "Which agent files do you want to add (optional)?",
|
|
3511
|
-
required: false,
|
|
3512
|
-
options: Object.entries(AGENTS).map(([key, agent]) => ({
|
|
3513
|
-
value: key,
|
|
3514
|
-
label: agent.label
|
|
3515
|
-
}))
|
|
3516
|
-
});
|
|
3517
|
-
},
|
|
3518
|
-
hooks: async () => {
|
|
3519
|
-
if (hooks !== void 0) return hooks;
|
|
3520
|
-
return navigableMultiselect({
|
|
3521
|
-
message: "Which agent hooks do you want to enable (optional)?",
|
|
3522
|
-
required: false,
|
|
3523
|
-
options: Object.entries(HOOKS).map(([key, hook]) => ({
|
|
3524
|
-
value: key,
|
|
3525
|
-
label: hook.label
|
|
3526
|
-
}))
|
|
3527
|
-
});
|
|
3528
|
-
}
|
|
3529
|
-
});
|
|
3530
|
-
if (results.linter === void 0 || results.editors === void 0 || results.agents === void 0 || results.hooks === void 0) return userCancelled("Operation cancelled");
|
|
3531
|
-
linter = results.linter;
|
|
3532
|
-
editors = results.editors;
|
|
3533
|
-
agents = results.agents;
|
|
3534
|
-
hooks = results.hooks;
|
|
3535
|
-
}
|
|
3536
|
-
const frameworks = getFrameworksFromFrontend(frontend);
|
|
3537
|
-
const args = buildUltraciteInitArgs({
|
|
3538
|
-
packageManager,
|
|
3539
|
-
linter,
|
|
3540
|
-
frameworks,
|
|
3541
|
-
editors,
|
|
3542
|
-
agents,
|
|
3543
|
-
hooks,
|
|
3544
|
-
gitHooks
|
|
3545
|
-
});
|
|
3546
|
-
const s = createSpinner();
|
|
3547
|
-
s.start("Running Ultracite init command...");
|
|
3548
|
-
const initResult = await Result.tryPromise({
|
|
3549
|
-
try: async () => {
|
|
3550
|
-
await $({
|
|
3551
|
-
cwd: projectDir,
|
|
3552
|
-
env: { CI: "true" }
|
|
3553
|
-
})`${args}`;
|
|
3554
|
-
},
|
|
3555
|
-
catch: (e) => {
|
|
3556
|
-
s.stop(pc.red("Failed to run Ultracite init command"));
|
|
3557
|
-
return new AddonSetupError({
|
|
3558
|
-
addon: "ultracite",
|
|
3559
|
-
message: `Failed to set up Ultracite: ${e instanceof Error ? e.message : String(e)}`,
|
|
3560
|
-
cause: e
|
|
3561
|
-
});
|
|
3562
|
-
}
|
|
3563
|
-
});
|
|
3564
|
-
if (initResult.isErr()) {
|
|
3565
|
-
cliLog.error(pc.red("Failed to set up Ultracite"));
|
|
3566
|
-
return initResult;
|
|
3567
|
-
}
|
|
3568
|
-
if (gitHooks.includes("husky")) await Result.tryPromise({
|
|
3569
|
-
try: () => removeHuskySampleHook(projectDir),
|
|
3570
|
-
catch: (e) => e
|
|
3571
|
-
});
|
|
3572
|
-
s.stop("Ultracite setup successfully!");
|
|
3573
|
-
return Result.ok(void 0);
|
|
3574
|
-
}
|
|
3575
|
-
//#endregion
|
|
3576
|
-
//#region src/helpers/addons/wxt-setup.ts
|
|
3577
|
-
const TEMPLATES = {
|
|
3578
|
-
vanilla: {
|
|
3579
|
-
label: "Vanilla",
|
|
3580
|
-
hint: "Vanilla JavaScript template"
|
|
3581
|
-
},
|
|
3582
|
-
vue: {
|
|
3583
|
-
label: "Vue",
|
|
3584
|
-
hint: "Vue.js template"
|
|
3585
|
-
},
|
|
3586
|
-
react: {
|
|
3587
|
-
label: "React",
|
|
3588
|
-
hint: "React template"
|
|
3589
|
-
},
|
|
3590
|
-
solid: {
|
|
3591
|
-
label: "Solid",
|
|
3592
|
-
hint: "SolidJS template"
|
|
3593
|
-
},
|
|
3594
|
-
svelte: {
|
|
3595
|
-
label: "Svelte",
|
|
3596
|
-
hint: "Svelte template"
|
|
3597
|
-
}
|
|
3598
|
-
};
|
|
3599
|
-
const DEFAULT_TEMPLATE = "react";
|
|
3600
|
-
const DEFAULT_DEV_PORT = 5555;
|
|
3601
|
-
async function setupWxt(config) {
|
|
3602
|
-
if (shouldSkipExternalCommands()) return Result.ok(void 0);
|
|
3603
|
-
const { packageManager, projectDir } = config;
|
|
3604
|
-
cliLog.info("Setting up WXT...");
|
|
3605
|
-
const configuredOptions = config.addonOptions?.wxt;
|
|
3606
|
-
let template = configuredOptions?.template;
|
|
3607
|
-
if (!template) if (isSilent()) template = DEFAULT_TEMPLATE;
|
|
3608
|
-
else {
|
|
3609
|
-
setIsFirstPrompt(true);
|
|
3610
|
-
const selectedTemplate = await navigableSelect({
|
|
3611
|
-
message: "Choose a template",
|
|
3612
|
-
options: Object.entries(TEMPLATES).map(([key, templateOption]) => ({
|
|
3613
|
-
value: key,
|
|
3614
|
-
label: templateOption.label,
|
|
3615
|
-
hint: templateOption.hint
|
|
3616
|
-
})),
|
|
3617
|
-
initialValue: DEFAULT_TEMPLATE
|
|
3618
|
-
});
|
|
3619
|
-
if (isCancel$1(selectedTemplate)) return userCancelled("Operation cancelled");
|
|
3620
|
-
template = selectedTemplate;
|
|
3621
|
-
}
|
|
3622
|
-
const devPort = configuredOptions?.devPort ?? DEFAULT_DEV_PORT;
|
|
3623
|
-
const args = getPackageExecutionArgs(packageManager, `wxt@latest init extension --template ${template} --pm ${packageManager}`);
|
|
3624
|
-
const appsDir = path.join(projectDir, "apps");
|
|
3625
|
-
const ensureDirResult = await Result.tryPromise({
|
|
3626
|
-
try: () => fs.ensureDir(appsDir),
|
|
3627
|
-
catch: (e) => new AddonSetupError({
|
|
3628
|
-
addon: "wxt",
|
|
3629
|
-
message: `Failed to create directory: ${e instanceof Error ? e.message : String(e)}`,
|
|
3630
|
-
cause: e
|
|
3631
|
-
})
|
|
3632
|
-
});
|
|
3633
|
-
if (ensureDirResult.isErr()) return ensureDirResult;
|
|
3634
|
-
const s = createSpinner();
|
|
3635
|
-
s.start("Running WXT init command...");
|
|
3636
|
-
const initResult = await Result.tryPromise({
|
|
3637
|
-
try: async () => {
|
|
3638
|
-
await $({
|
|
3639
|
-
cwd: appsDir,
|
|
3640
|
-
env: { CI: "true" }
|
|
3641
|
-
})`${args}`;
|
|
3642
|
-
},
|
|
3643
|
-
catch: (e) => {
|
|
3644
|
-
s.stop(pc.red("Failed to run WXT init command"));
|
|
3645
|
-
return new AddonSetupError({
|
|
3646
|
-
addon: "wxt",
|
|
3647
|
-
message: `Failed to set up WXT: ${e instanceof Error ? e.message : String(e)}`,
|
|
3648
|
-
cause: e
|
|
3649
|
-
});
|
|
3650
|
-
}
|
|
3651
|
-
});
|
|
3652
|
-
if (initResult.isErr()) {
|
|
3653
|
-
cliLog.error(pc.red("Failed to set up WXT"));
|
|
3654
|
-
return initResult;
|
|
3655
|
-
}
|
|
3656
|
-
const extensionDir = path.join(projectDir, "apps", "extension");
|
|
3657
|
-
const packageJsonPath = path.join(extensionDir, "package.json");
|
|
3658
|
-
if ((await Result.tryPromise({
|
|
3659
|
-
try: async () => {
|
|
3660
|
-
if (await fs.pathExists(packageJsonPath)) {
|
|
3661
|
-
const packageJson = await fs.readJson(packageJsonPath);
|
|
3662
|
-
packageJson.name = "extension";
|
|
3663
|
-
if (packageJson.scripts?.dev) packageJson.scripts.dev = `${packageJson.scripts.dev} --port ${devPort}`;
|
|
3664
|
-
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
3665
|
-
}
|
|
3666
|
-
},
|
|
3667
|
-
catch: (e) => new AddonSetupError({
|
|
3668
|
-
addon: "wxt",
|
|
3669
|
-
message: `Failed to update package.json: ${e instanceof Error ? e.message : String(e)}`,
|
|
3670
|
-
cause: e
|
|
3671
|
-
})
|
|
3672
|
-
})).isErr()) cliLog.warn(pc.yellow("WXT setup completed but failed to update package.json"));
|
|
3673
|
-
s.stop("WXT setup complete!");
|
|
3674
|
-
return Result.ok(void 0);
|
|
3675
|
-
}
|
|
3676
|
-
//#endregion
|
|
3677
2464
|
//#region src/helpers/addons/addons-setup.ts
|
|
3678
2465
|
async function runSetup(setupFn) {
|
|
3679
2466
|
const result = await setupFn();
|
|
@@ -3697,36 +2484,24 @@ async function setupAddons(config) {
|
|
|
3697
2484
|
const { addons, frontend, projectDir } = config;
|
|
3698
2485
|
const hasWebFrontend = frontend.some((value) => types_exports.desktopWebFrontends.includes(value));
|
|
3699
2486
|
if (addons.includes("tauri") && hasWebFrontend) await runSetup(() => setupTauri(config));
|
|
3700
|
-
const
|
|
3701
|
-
const hasBiome = addons.includes("biome");
|
|
2487
|
+
const hasOxlint = addons.includes("oxlint");
|
|
2488
|
+
const hasBiome = !hasOxlint && addons.includes("biome");
|
|
3702
2489
|
const hasHusky = addons.includes("husky");
|
|
3703
2490
|
const hasLefthook = addons.includes("lefthook");
|
|
3704
|
-
const hasOxlint = addons.includes("oxlint");
|
|
3705
2491
|
const hasVitePlus = addons.includes("vite-plus");
|
|
3706
|
-
if (
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
if (
|
|
3713
|
-
if (
|
|
3714
|
-
if (
|
|
3715
|
-
let linter;
|
|
3716
|
-
if (hasOxlint) linter = "oxlint";
|
|
3717
|
-
else if (hasBiome) linter = "biome";
|
|
3718
|
-
else if (hasVitePlus) linter = "vite-plus";
|
|
3719
|
-
if (hasHusky) await runAddonStep("husky", () => setupHusky(projectDir, linter));
|
|
3720
|
-
if (hasLefthook) await runAddonStep("lefthook", () => setupLefthook(projectDir));
|
|
3721
|
-
}
|
|
2492
|
+
if (hasBiome) await runAddonStep("biome", () => setupBiome(projectDir));
|
|
2493
|
+
else if (hasOxlint) await runSetup(() => setupOxlint(projectDir, config.packageManager));
|
|
2494
|
+
if (hasHusky || hasLefthook) {
|
|
2495
|
+
let linter;
|
|
2496
|
+
if (hasOxlint) linter = "oxlint";
|
|
2497
|
+
else if (hasBiome) linter = "biome";
|
|
2498
|
+
else if (hasVitePlus) linter = "vite-plus";
|
|
2499
|
+
if (hasHusky) await runAddonStep("husky", () => setupHusky(projectDir, linter));
|
|
2500
|
+
if (hasLefthook) await runAddonStep("lefthook", () => setupLefthook(projectDir));
|
|
3722
2501
|
}
|
|
3723
|
-
if (addons.includes("starlight")) await runSetup(() => setupStarlight(config));
|
|
3724
|
-
if (addons.includes("fumadocs")) await runSetup(() => setupFumadocs(config));
|
|
3725
2502
|
if (addons.includes("opentui")) await runSetup(() => setupTui(config));
|
|
3726
|
-
if (addons.includes("wxt")) await runSetup(() => setupWxt(config));
|
|
3727
2503
|
if (addons.includes("skills")) await runSetup(() => setupSkills(config));
|
|
3728
2504
|
if (addons.includes("mcp")) await runSetup(() => setupMcp(config));
|
|
3729
|
-
if (addons.includes("evlog")) await runSetup(() => setupEvlog(config));
|
|
3730
2505
|
}
|
|
3731
2506
|
async function setupBiome(projectDir) {
|
|
3732
2507
|
await addPackageDependency({
|
|
@@ -3743,57 +2518,168 @@ async function setupBiome(projectDir) {
|
|
|
3743
2518
|
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
3744
2519
|
}
|
|
3745
2520
|
}
|
|
3746
|
-
async function setupHusky(projectDir, linter) {
|
|
3747
|
-
await addPackageDependency({
|
|
3748
|
-
devDependencies: ["husky", "lint-staged"],
|
|
3749
|
-
projectDir
|
|
3750
|
-
});
|
|
3751
|
-
const packageJsonPath = path.join(projectDir, "package.json");
|
|
3752
|
-
if (await fs.pathExists(packageJsonPath)) {
|
|
3753
|
-
const packageJson = await fs.readJson(packageJsonPath);
|
|
3754
|
-
packageJson.scripts = {
|
|
3755
|
-
...packageJson.scripts,
|
|
3756
|
-
prepare: "husky"
|
|
3757
|
-
};
|
|
3758
|
-
if (linter === "oxlint") packageJson["lint-staged"] = { "*": ["oxlint", "oxfmt --write"] };
|
|
3759
|
-
else if (linter === "biome") packageJson["lint-staged"] = { "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": ["biome check --write ."] };
|
|
3760
|
-
else if (linter === "vite-plus") packageJson["lint-staged"] = { "*.{js,ts,jsx,tsx,vue,svelte,json,jsonc,css,md}": ["vp check --fix"] };
|
|
3761
|
-
else packageJson["lint-staged"] = { "**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "" };
|
|
3762
|
-
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
3763
|
-
}
|
|
2521
|
+
async function setupHusky(projectDir, linter) {
|
|
2522
|
+
await addPackageDependency({
|
|
2523
|
+
devDependencies: ["husky", "lint-staged"],
|
|
2524
|
+
projectDir
|
|
2525
|
+
});
|
|
2526
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
2527
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
2528
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
2529
|
+
packageJson.scripts = {
|
|
2530
|
+
...packageJson.scripts,
|
|
2531
|
+
prepare: "husky"
|
|
2532
|
+
};
|
|
2533
|
+
if (linter === "oxlint") packageJson["lint-staged"] = { "*": ["oxlint", "oxfmt --write"] };
|
|
2534
|
+
else if (linter === "biome") packageJson["lint-staged"] = { "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": ["biome check --write ."] };
|
|
2535
|
+
else if (linter === "vite-plus") packageJson["lint-staged"] = { "*.{js,ts,jsx,tsx,vue,svelte,json,jsonc,css,md}": ["vp check --fix"] };
|
|
2536
|
+
else packageJson["lint-staged"] = { "**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "" };
|
|
2537
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
async function setupLefthook(projectDir) {
|
|
2541
|
+
await addPackageDependency({
|
|
2542
|
+
devDependencies: ["lefthook"],
|
|
2543
|
+
projectDir
|
|
2544
|
+
});
|
|
2545
|
+
}
|
|
2546
|
+
//#endregion
|
|
2547
|
+
//#region src/helpers/testing/testing-setup.ts
|
|
2548
|
+
async function updateScripts(projectDir, scripts) {
|
|
2549
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
2550
|
+
if (!await fs.pathExists(packageJsonPath)) return;
|
|
2551
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
2552
|
+
packageJson.scripts = {
|
|
2553
|
+
...packageJson.scripts,
|
|
2554
|
+
...scripts
|
|
2555
|
+
};
|
|
2556
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2557
|
+
}
|
|
2558
|
+
async function setupTesting(projectDir, testing) {
|
|
2559
|
+
if (testing.includes("vitest")) {
|
|
2560
|
+
await addPackageDependency({
|
|
2561
|
+
devDependencies: ["vitest"],
|
|
2562
|
+
projectDir
|
|
2563
|
+
});
|
|
2564
|
+
await updateScripts(projectDir, {
|
|
2565
|
+
test: "vitest run",
|
|
2566
|
+
"test:watch": "vitest"
|
|
2567
|
+
});
|
|
2568
|
+
}
|
|
2569
|
+
if (testing.includes("playwright")) {
|
|
2570
|
+
await addPackageDependency({
|
|
2571
|
+
devDependencies: ["@playwright/test"],
|
|
2572
|
+
projectDir
|
|
2573
|
+
});
|
|
2574
|
+
await updateScripts(projectDir, { "test:e2e": "playwright test" });
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
/** Best-effort browser install after deps; never fails project creation. */
|
|
2578
|
+
async function installPlaywrightBrowsers(projectDir, packageManager) {
|
|
2579
|
+
if (shouldSkipExternalCommands()) return;
|
|
2580
|
+
const s = createSpinner();
|
|
2581
|
+
s.start("Installing Playwright browsers...");
|
|
2582
|
+
const args = getPackageExecutionArgs(packageManager, "playwright install");
|
|
2583
|
+
try {
|
|
2584
|
+
await $({ cwd: projectDir })`${args}`;
|
|
2585
|
+
s.stop(pc.green("Playwright browsers installed"));
|
|
2586
|
+
} catch (e) {
|
|
2587
|
+
s.stop(pc.yellow("Playwright browser install skipped (run playwright install later)"));
|
|
2588
|
+
cliLog.info(pc.dim(`Playwright browsers were not installed automatically: ${e instanceof Error ? e.message : String(e)}`));
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
//#endregion
|
|
2592
|
+
//#region src/utils/config-processing.ts
|
|
2593
|
+
function processArrayOption(options) {
|
|
2594
|
+
if (!options || options.length === 0) return [];
|
|
2595
|
+
if (options.includes("none")) return [];
|
|
2596
|
+
return options.filter((item) => item !== "none");
|
|
2597
|
+
}
|
|
2598
|
+
function deriveProjectName(projectName, projectDirectory) {
|
|
2599
|
+
if (projectName) return projectName;
|
|
2600
|
+
if (projectDirectory) return path.basename(path.resolve(process.cwd(), projectDirectory));
|
|
2601
|
+
return "";
|
|
2602
|
+
}
|
|
2603
|
+
function processFlags(options, projectName) {
|
|
2604
|
+
const config = {};
|
|
2605
|
+
if (options.api) config.api = options.api;
|
|
2606
|
+
if (options.addonOptions) config.addonOptions = options.addonOptions;
|
|
2607
|
+
if (options.dbSetupOptions) config.dbSetupOptions = options.dbSetupOptions;
|
|
2608
|
+
if (options.backend) config.backend = options.backend;
|
|
2609
|
+
if (options.database) config.database = options.database;
|
|
2610
|
+
if (options.orm) config.orm = options.orm;
|
|
2611
|
+
if (options.auth !== void 0) config.auth = options.auth;
|
|
2612
|
+
if (options.payments !== void 0) config.payments = normalizePayments(options.payments);
|
|
2613
|
+
if (options.observability !== void 0) config.observability = normalizeObservability(options.observability);
|
|
2614
|
+
if (options.disableObservability) config.observability = [];
|
|
2615
|
+
if (options.communication !== void 0) config.communication = options.communication;
|
|
2616
|
+
if (options.git !== void 0) config.git = options.git;
|
|
2617
|
+
if (options.install !== void 0) config.install = options.install;
|
|
2618
|
+
if (options.runtime) config.runtime = options.runtime;
|
|
2619
|
+
if (options.dbSetup) config.dbSetup = options.dbSetup;
|
|
2620
|
+
if (options.packageManager) config.packageManager = options.packageManager;
|
|
2621
|
+
if (options.webDeploy) config.webDeploy = options.webDeploy;
|
|
2622
|
+
if (options.serverDeploy) config.serverDeploy = options.serverDeploy;
|
|
2623
|
+
const derivedName = deriveProjectName(projectName, options.projectDirectory);
|
|
2624
|
+
if (derivedName) config.projectName = projectName || derivedName;
|
|
2625
|
+
if (options.frontend && options.frontend.length > 0) config.frontend = processArrayOption(options.frontend);
|
|
2626
|
+
if (options.addons && options.addons.length > 0) config.addons = processArrayOption(options.addons);
|
|
2627
|
+
if (options.examples && options.examples.length > 0) config.examples = processArrayOption(options.examples);
|
|
2628
|
+
if (options.testing && options.testing.length > 0) config.testing = processArrayOption(options.testing);
|
|
2629
|
+
return config;
|
|
2630
|
+
}
|
|
2631
|
+
function normalizeObservability(value) {
|
|
2632
|
+
if (value === "none" || value === void 0) return [];
|
|
2633
|
+
return [...new Set((Array.isArray(value) ? value : [value]).filter((item) => item === "getmonitor" || item === "himetrica"))];
|
|
3764
2634
|
}
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
2635
|
+
function getProvidedFlags(options) {
|
|
2636
|
+
return new Set(Object.keys(options).filter((key) => options[key] !== void 0));
|
|
2637
|
+
}
|
|
2638
|
+
function validateNoneExclusivity(options, optionName) {
|
|
2639
|
+
if (!options || options.length === 0) return Result.ok(void 0);
|
|
2640
|
+
if (options.includes("none") && options.length > 1) return Result.err(new ValidationError({ message: `Cannot combine 'none' with other ${optionName}.` }));
|
|
2641
|
+
return Result.ok(void 0);
|
|
2642
|
+
}
|
|
2643
|
+
function validateArrayOptions(options) {
|
|
2644
|
+
if (options.disableObservability && options.observability !== void 0) return Result.err(new ValidationError({ message: "Cannot combine '--disable-observability' with '--observability'." }));
|
|
2645
|
+
const frontendResult = validateNoneExclusivity(options.frontend, "frontend options");
|
|
2646
|
+
if (frontendResult.isErr()) return frontendResult;
|
|
2647
|
+
const addonsResult = validateNoneExclusivity(options.addons, "addons");
|
|
2648
|
+
if (addonsResult.isErr()) return addonsResult;
|
|
2649
|
+
const examplesResult = validateNoneExclusivity(options.examples, "examples");
|
|
2650
|
+
if (examplesResult.isErr()) return examplesResult;
|
|
2651
|
+
const testingResult = validateNoneExclusivity(options.testing, "testing");
|
|
2652
|
+
if (testingResult.isErr()) return testingResult;
|
|
2653
|
+
return Result.ok(void 0);
|
|
3770
2654
|
}
|
|
3771
2655
|
//#endregion
|
|
3772
2656
|
//#region src/helpers/core/detect-project-config.ts
|
|
3773
2657
|
async function detectProjectConfig(projectDir) {
|
|
3774
2658
|
const result = await Result.tryPromise({
|
|
3775
2659
|
try: async () => {
|
|
3776
|
-
const
|
|
3777
|
-
if (
|
|
2660
|
+
const kubojsConfig = await readKubojsConfig(projectDir);
|
|
2661
|
+
if (kubojsConfig) return {
|
|
3778
2662
|
projectDir,
|
|
3779
2663
|
projectName: path.basename(projectDir),
|
|
3780
|
-
addonOptions:
|
|
3781
|
-
dbSetupOptions:
|
|
3782
|
-
database:
|
|
3783
|
-
orm:
|
|
3784
|
-
backend:
|
|
3785
|
-
runtime:
|
|
3786
|
-
frontend:
|
|
3787
|
-
addons:
|
|
3788
|
-
examples:
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
2664
|
+
addonOptions: kubojsConfig.addonOptions,
|
|
2665
|
+
dbSetupOptions: kubojsConfig.dbSetupOptions,
|
|
2666
|
+
database: kubojsConfig.database,
|
|
2667
|
+
orm: kubojsConfig.orm,
|
|
2668
|
+
backend: kubojsConfig.backend,
|
|
2669
|
+
runtime: kubojsConfig.runtime,
|
|
2670
|
+
frontend: kubojsConfig.frontend,
|
|
2671
|
+
addons: kubojsConfig.addons,
|
|
2672
|
+
examples: kubojsConfig.examples,
|
|
2673
|
+
testing: kubojsConfig.testing,
|
|
2674
|
+
auth: kubojsConfig.auth,
|
|
2675
|
+
payments: kubojsConfig.payments,
|
|
2676
|
+
observability: normalizeObservability(kubojsConfig.observability),
|
|
2677
|
+
communication: kubojsConfig.communication ?? "none",
|
|
2678
|
+
packageManager: kubojsConfig.packageManager,
|
|
2679
|
+
dbSetup: kubojsConfig.dbSetup,
|
|
2680
|
+
api: kubojsConfig.api,
|
|
2681
|
+
webDeploy: kubojsConfig.webDeploy,
|
|
2682
|
+
serverDeploy: kubojsConfig.serverDeploy
|
|
3797
2683
|
};
|
|
3798
2684
|
return null;
|
|
3799
2685
|
},
|
|
@@ -3832,9 +2718,8 @@ const ADD_PACKAGE_JSON_PATHS = [
|
|
|
3832
2718
|
"apps/web/package.json",
|
|
3833
2719
|
"apps/native/package.json",
|
|
3834
2720
|
"apps/desktop/package.json",
|
|
3835
|
-
"apps/fumadocs/package.json",
|
|
3836
2721
|
"apps/docs/package.json",
|
|
3837
|
-
"
|
|
2722
|
+
"apps/api/package.json",
|
|
3838
2723
|
"packages/db/package.json",
|
|
3839
2724
|
"packages/auth/package.json",
|
|
3840
2725
|
"packages/backend/package.json",
|
|
@@ -3844,17 +2729,21 @@ const ADD_PACKAGE_JSON_PATHS = [
|
|
|
3844
2729
|
"packages/ui/package.json"
|
|
3845
2730
|
];
|
|
3846
2731
|
const ADD_TEXT_FILE_PATHS = ["apps/web/vite.config.ts", "lefthook.yml"];
|
|
2732
|
+
const ADD_ENV_FILE_PATHS = [
|
|
2733
|
+
".env",
|
|
2734
|
+
"apps/web/.env",
|
|
2735
|
+
"apps/server/.env",
|
|
2736
|
+
"apps/native/.env",
|
|
2737
|
+
"packages/backend/.env.local",
|
|
2738
|
+
"packages/infra/.env"
|
|
2739
|
+
];
|
|
3847
2740
|
const HOOK_ADDONS = ["husky", "lefthook"];
|
|
3848
2741
|
const HOOK_LINTER_ADDONS = [
|
|
3849
2742
|
"biome",
|
|
3850
2743
|
"oxlint",
|
|
3851
2744
|
"vite-plus"
|
|
3852
2745
|
];
|
|
3853
|
-
const TASK_RUNNER_ADDONS = [
|
|
3854
|
-
"turborepo",
|
|
3855
|
-
"nx",
|
|
3856
|
-
"vite-plus"
|
|
3857
|
-
];
|
|
2746
|
+
const TASK_RUNNER_ADDONS = ["turborepo", "vite-plus"];
|
|
3858
2747
|
function mergeAddonOptions(existingAddonOptions, nextAddonOptions) {
|
|
3859
2748
|
if (!existingAddonOptions && !nextAddonOptions) return;
|
|
3860
2749
|
const mergedAddonOptions = { ...existingAddonOptions };
|
|
@@ -3883,6 +2772,37 @@ function refreshLefthookTemplate(vfs, config) {
|
|
|
3883
2772
|
if (!template) return;
|
|
3884
2773
|
vfs.writeFile("lefthook.yml", processTemplateString(template, config));
|
|
3885
2774
|
}
|
|
2775
|
+
/** Remove files/deps left by a replaced exclusive linter. */
|
|
2776
|
+
async function cleanupRemovedLinters(projectDir, removedAddons) {
|
|
2777
|
+
const removedLinters = removedAddons.filter(isLinterAddon);
|
|
2778
|
+
if (removedLinters.length === 0) return;
|
|
2779
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
2780
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
2781
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
2782
|
+
packageJson.devDependencies = packageJson.devDependencies ?? {};
|
|
2783
|
+
for (const removed of removedLinters) {
|
|
2784
|
+
if (removed === "biome") delete packageJson.devDependencies["@biomejs/biome"];
|
|
2785
|
+
if (removed === "oxlint") {
|
|
2786
|
+
delete packageJson.devDependencies.oxlint;
|
|
2787
|
+
delete packageJson.devDependencies.oxfmt;
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
if (typeof packageJson.scripts?.check === "string" && removedLinters.some((linter) => {
|
|
2791
|
+
if (linter === "biome") return packageJson.scripts.check.includes("biome");
|
|
2792
|
+
if (linter === "oxlint") return packageJson.scripts.check.includes("oxlint");
|
|
2793
|
+
return false;
|
|
2794
|
+
})) delete packageJson.scripts.check;
|
|
2795
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2796
|
+
}
|
|
2797
|
+
for (const removed of removedLinters) {
|
|
2798
|
+
if (removed === "biome") await fs.remove(path.join(projectDir, "biome.json"));
|
|
2799
|
+
if (removed === "oxlint") {
|
|
2800
|
+
await fs.remove(path.join(projectDir, ".oxlintrc.json"));
|
|
2801
|
+
await fs.remove(path.join(projectDir, ".oxfmtrc.json"));
|
|
2802
|
+
await fs.remove(path.join(projectDir, ".oxfmtrc.jsonc"));
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
3886
2806
|
function updateViteConfigImportsForVitePlus(vfs) {
|
|
3887
2807
|
for (const viteConfigPath of ["apps/web/vite.config.ts"]) {
|
|
3888
2808
|
const content = vfs.readFile(viteConfigPath);
|
|
@@ -3925,10 +2845,10 @@ async function addHandlerInternal(input) {
|
|
|
3925
2845
|
}));
|
|
3926
2846
|
if (!isSilent()) {
|
|
3927
2847
|
renderTitle();
|
|
3928
|
-
intro(
|
|
2848
|
+
intro(cliColors.signal("Add addons to your kubojs project"));
|
|
3929
2849
|
}
|
|
3930
2850
|
const existingConfig = await detectProjectConfig(projectDir);
|
|
3931
|
-
if (!existingConfig) return Result.err(new CLIError({ message: `No
|
|
2851
|
+
if (!existingConfig) return Result.err(new CLIError({ message: `No kubojs project found in ${projectDir}. Make sure kubojs.jsonrc exists.` }));
|
|
3932
2852
|
if (!isSilent()) log.info(pc.dim(`Detected project: ${existingConfig.projectName}`));
|
|
3933
2853
|
let addonsToAdd;
|
|
3934
2854
|
if (input.addons && input.addons.length > 0) {
|
|
@@ -3958,7 +2878,7 @@ async function addHandlerInternal(input) {
|
|
|
3958
2878
|
if (selectedAddons.length === 0) {
|
|
3959
2879
|
if (!isSilent()) {
|
|
3960
2880
|
log.info(pc.dim("No addons selected."));
|
|
3961
|
-
outro(
|
|
2881
|
+
outro(cliColors.signal("Nothing to add."));
|
|
3962
2882
|
}
|
|
3963
2883
|
return Result.ok({
|
|
3964
2884
|
success: true,
|
|
@@ -3968,10 +2888,14 @@ async function addHandlerInternal(input) {
|
|
|
3968
2888
|
}
|
|
3969
2889
|
addonsToAdd = selectedAddons;
|
|
3970
2890
|
}
|
|
3971
|
-
const updatedAddons =
|
|
2891
|
+
const { updatedAddons, removedAddons } = mergeAddonsExclusive(existingConfig.addons, addonsToAdd);
|
|
2892
|
+
const updatedTesting = existingConfig.testing ?? [];
|
|
3972
2893
|
const addonsValidationResult = validateAddonsAgainstConfig(updatedAddons, existingConfig);
|
|
3973
2894
|
if (addonsValidationResult.isErr()) return Result.err(new CLIError({ message: addonsValidationResult.error.message }));
|
|
3974
|
-
if (!isSilent())
|
|
2895
|
+
if (!isSilent()) {
|
|
2896
|
+
log.info(cliColors.bright(`Adding addons: ${addonsToAdd.join(", ")}`));
|
|
2897
|
+
if (removedAddons.length > 0) log.info(pc.dim(`Replacing exclusive addons: ${removedAddons.join(", ")}`));
|
|
2898
|
+
}
|
|
3975
2899
|
const mergedAddonOptions = mergeAddonOptions(existingConfig.addonOptions, input.addonOptions);
|
|
3976
2900
|
const config = {
|
|
3977
2901
|
projectName: existingConfig.projectName,
|
|
@@ -3988,6 +2912,8 @@ async function addHandlerInternal(input) {
|
|
|
3988
2912
|
auth: existingConfig.auth,
|
|
3989
2913
|
payments: existingConfig.payments,
|
|
3990
2914
|
observability: existingConfig.observability,
|
|
2915
|
+
communication: existingConfig.communication ?? "none",
|
|
2916
|
+
testing: updatedTesting,
|
|
3991
2917
|
git: false,
|
|
3992
2918
|
packageManager: input.packageManager || existingConfig.packageManager,
|
|
3993
2919
|
install: input.install ?? false,
|
|
@@ -4016,10 +2942,17 @@ async function addHandlerInternal(input) {
|
|
|
4016
2942
|
vfs.writeFile(filePath, content);
|
|
4017
2943
|
}
|
|
4018
2944
|
}
|
|
2945
|
+
for (const filePath of ADD_ENV_FILE_PATHS) {
|
|
2946
|
+
const fullPath = path.join(projectDir, filePath);
|
|
2947
|
+
if (await fs.pathExists(fullPath)) {
|
|
2948
|
+
const content = await fs.readFile(fullPath, "utf-8");
|
|
2949
|
+
vfs.writeFile(filePath, content);
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
4019
2952
|
await processAddonTemplates(vfs, EMBEDDED_TEMPLATES, config);
|
|
4020
2953
|
processAddonsDeps(vfs, config);
|
|
2954
|
+
processEnvVariables(vfs, updatedConfig);
|
|
4021
2955
|
if (addonsToAdd.includes("turborepo")) processTurboConfig(vfs, updatedConfig);
|
|
4022
|
-
if (addonsToAdd.includes("nx")) processNxConfig(vfs, updatedConfig);
|
|
4023
2956
|
if (addonsToAdd.includes("vite-plus")) processVitePlusConfig(vfs, updatedConfig);
|
|
4024
2957
|
if (updatedAddons.some((addon) => TASK_RUNNER_ADDONS.includes(addon))) processPackageConfigs(vfs, updatedConfig);
|
|
4025
2958
|
if (updatedAddons.includes("vite-plus")) updateViteConfigImportsForVitePlus(vfs);
|
|
@@ -4034,7 +2967,7 @@ async function addHandlerInternal(input) {
|
|
|
4034
2967
|
if (!isSilent()) {
|
|
4035
2968
|
log.success(pc.green("Dry run validation passed. No addon files were written."));
|
|
4036
2969
|
log.info(pc.dim(`Planned addon files: ${vfs.getFileCount()}`));
|
|
4037
|
-
outro(
|
|
2970
|
+
outro(cliColors.signal("Dry run complete."));
|
|
4038
2971
|
}
|
|
4039
2972
|
return Result.ok({
|
|
4040
2973
|
success: true,
|
|
@@ -4047,6 +2980,7 @@ async function addHandlerInternal(input) {
|
|
|
4047
2980
|
const writeResult = await writeTree(tree, projectDir);
|
|
4048
2981
|
if (writeResult.isErr()) return Result.err(new CLIError({ message: `Failed to write addon files: ${writeResult.error.message}` }));
|
|
4049
2982
|
if (vfs.getFileCount() > 0 && !isSilent()) log.info(pc.dim(`Wrote ${vfs.getFileCount()} addon files`));
|
|
2983
|
+
await cleanupRemovedLinters(projectDir, removedAddons);
|
|
4050
2984
|
const setupResult = await Result.tryPromise({
|
|
4051
2985
|
try: () => setupAddons({
|
|
4052
2986
|
...config,
|
|
@@ -4061,9 +2995,18 @@ async function addHandlerInternal(input) {
|
|
|
4061
2995
|
}
|
|
4062
2996
|
});
|
|
4063
2997
|
if (setupResult.isErr()) return Result.err(setupResult.error);
|
|
4064
|
-
await
|
|
2998
|
+
const testingSetupResult = await Result.tryPromise({
|
|
2999
|
+
try: () => setupTesting(projectDir, updatedTesting),
|
|
3000
|
+
catch: (e) => new CLIError({
|
|
3001
|
+
message: e instanceof Error ? e.message : String(e),
|
|
3002
|
+
cause: e
|
|
3003
|
+
})
|
|
3004
|
+
});
|
|
3005
|
+
if (testingSetupResult.isErr()) return Result.err(testingSetupResult.error);
|
|
3006
|
+
await updateKubojsConfig(projectDir, {
|
|
4065
3007
|
addons: updatedAddons,
|
|
4066
|
-
addonOptions: updatedConfig.addonOptions
|
|
3008
|
+
addonOptions: updatedConfig.addonOptions,
|
|
3009
|
+
testing: updatedTesting
|
|
4067
3010
|
});
|
|
4068
3011
|
if (input.install) {
|
|
4069
3012
|
if (!isSilent()) log.info(pc.dim("Installing dependencies..."));
|
|
@@ -4075,7 +3018,7 @@ async function addHandlerInternal(input) {
|
|
|
4075
3018
|
if (!isSilent()) {
|
|
4076
3019
|
log.success(pc.green(`Successfully added: ${addonsToAdd.join(", ")}`));
|
|
4077
3020
|
if (!input.install) log.info(pc.yellow(`Run '${config.packageManager === "npm" ? "npm install" : `${config.packageManager} install`}' to install new dependencies.`));
|
|
4078
|
-
outro(
|
|
3021
|
+
outro(cliColors.signal("Addons added successfully!"));
|
|
4079
3022
|
}
|
|
4080
3023
|
return Result.ok({
|
|
4081
3024
|
success: true,
|
|
@@ -4088,7 +3031,7 @@ async function addHandlerInternal(input) {
|
|
|
4088
3031
|
//#region src/prompts/api.ts
|
|
4089
3032
|
async function getApiChoice(Api, frontend, backend, previousValue) {
|
|
4090
3033
|
if (backend === "convex" || backend === "none") return "none";
|
|
4091
|
-
const allowed = allowedApisForFrontends(frontend ?? []);
|
|
3034
|
+
const allowed = allowedApisForFrontends(frontend ?? [], backend);
|
|
4092
3035
|
if (Api) {
|
|
4093
3036
|
const compat = validateApiFrontendCompatibility(Api, frontend ?? []);
|
|
4094
3037
|
if (compat.isErr()) throw compat.error;
|
|
@@ -4102,6 +3045,10 @@ async function getApiChoice(Api, frontend, backend, previousValue) {
|
|
|
4102
3045
|
value: "orpc",
|
|
4103
3046
|
label: "oRPC",
|
|
4104
3047
|
hint: "End-to-end type-safe APIs that adhere to OpenAPI standards"
|
|
3048
|
+
} : a === "orval" ? {
|
|
3049
|
+
value: "orval",
|
|
3050
|
+
label: "Orval",
|
|
3051
|
+
hint: "OpenAPI REST APIs with generated Hono handlers and Fetch clients"
|
|
4105
3052
|
} : {
|
|
4106
3053
|
value: "none",
|
|
4107
3054
|
label: "None",
|
|
@@ -4112,7 +3059,7 @@ async function getApiChoice(Api, frontend, backend, previousValue) {
|
|
|
4112
3059
|
options: apiOptions,
|
|
4113
3060
|
initialValue: preferValidInitial(apiOptions, previousValue, apiOptions[0].value)
|
|
4114
3061
|
});
|
|
4115
|
-
if (isCancel$
|
|
3062
|
+
if (isCancel$2(apiType)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4116
3063
|
return apiType;
|
|
4117
3064
|
}
|
|
4118
3065
|
//#endregion
|
|
@@ -4164,7 +3111,7 @@ async function getAuthChoice(auth, backend, frontend = [], previousValue) {
|
|
|
4164
3111
|
options,
|
|
4165
3112
|
initialValue: preferValidInitial(options, previousValue, options.some((option) => option.value === DEFAULT_CONFIG.auth) ? DEFAULT_CONFIG.auth : "none")
|
|
4166
3113
|
});
|
|
4167
|
-
if (isCancel$
|
|
3114
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4168
3115
|
return response;
|
|
4169
3116
|
}
|
|
4170
3117
|
//#endregion
|
|
@@ -4218,7 +3165,42 @@ async function getBackendFrameworkChoice(backendFramework, frontends, previousVa
|
|
|
4218
3165
|
options: backendOptions,
|
|
4219
3166
|
initialValue: preferValidInitial(backendOptions, previousValue, hasFullstackFrontend ? "self" : DEFAULT_CONFIG.backend)
|
|
4220
3167
|
});
|
|
4221
|
-
if (isCancel$
|
|
3168
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
3169
|
+
return response;
|
|
3170
|
+
}
|
|
3171
|
+
//#endregion
|
|
3172
|
+
//#region src/prompts/communication.ts
|
|
3173
|
+
async function getCommunicationChoice(communication, backend, previousValue) {
|
|
3174
|
+
if (communication !== void 0) return communication;
|
|
3175
|
+
if (backend === "none") return "none";
|
|
3176
|
+
const options = [
|
|
3177
|
+
{
|
|
3178
|
+
value: "none",
|
|
3179
|
+
label: "None",
|
|
3180
|
+
hint: "No email/communication provider"
|
|
3181
|
+
},
|
|
3182
|
+
{
|
|
3183
|
+
value: "resend",
|
|
3184
|
+
label: "Resend",
|
|
3185
|
+
hint: "Transactional email for developers"
|
|
3186
|
+
},
|
|
3187
|
+
{
|
|
3188
|
+
value: "notifique",
|
|
3189
|
+
label: "Notifique",
|
|
3190
|
+
hint: "Omnichannel BR messaging API"
|
|
3191
|
+
},
|
|
3192
|
+
{
|
|
3193
|
+
value: "arara",
|
|
3194
|
+
label: "AraraHQ",
|
|
3195
|
+
hint: "WhatsApp messaging via the official Node SDK"
|
|
3196
|
+
}
|
|
3197
|
+
];
|
|
3198
|
+
const response = await navigableSelect({
|
|
3199
|
+
message: "Select communication provider",
|
|
3200
|
+
options,
|
|
3201
|
+
initialValue: preferValidInitial(options, previousValue, DEFAULT_CONFIG.communication)
|
|
3202
|
+
});
|
|
3203
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4222
3204
|
return response;
|
|
4223
3205
|
}
|
|
4224
3206
|
//#endregion
|
|
@@ -4258,7 +3240,7 @@ async function getDatabaseChoice(database, backend, runtime, previousValue) {
|
|
|
4258
3240
|
options: databaseOptions,
|
|
4259
3241
|
initialValue: preferValidInitial(databaseOptions, previousValue, DEFAULT_CONFIG.database)
|
|
4260
3242
|
});
|
|
4261
|
-
if (isCancel$
|
|
3243
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4262
3244
|
return response;
|
|
4263
3245
|
}
|
|
4264
3246
|
//#endregion
|
|
@@ -4357,7 +3339,7 @@ async function getDBSetupChoice(databaseType, dbSetup, _orm, backend, runtime, p
|
|
|
4357
3339
|
options,
|
|
4358
3340
|
initialValue: preferValidInitial(options, previousValue, "none")
|
|
4359
3341
|
});
|
|
4360
|
-
if (isCancel$
|
|
3342
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4361
3343
|
return response;
|
|
4362
3344
|
}
|
|
4363
3345
|
//#endregion
|
|
@@ -4384,7 +3366,7 @@ async function getExamplesChoice(examples, database, frontends, backend, api, pr
|
|
|
4384
3366
|
required: false,
|
|
4385
3367
|
initialValues: (previousValue ?? DEFAULT_CONFIG.examples)?.filter((ex) => options.some((o) => o.value === ex))
|
|
4386
3368
|
});
|
|
4387
|
-
if (isCancel$
|
|
3369
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4388
3370
|
return response;
|
|
4389
3371
|
}
|
|
4390
3372
|
//#endregion
|
|
@@ -4420,7 +3402,7 @@ async function getFrontendChoice(frontendOptions, backend, auth, previousValue)
|
|
|
4420
3402
|
initialValues: previousValue ? [...previousWeb ? ["web"] : [], ...previousNative ? ["native"] : []] : ["web"]
|
|
4421
3403
|
});
|
|
4422
3404
|
if (isGoBack(frontendTypes)) return GO_BACK_SYMBOL;
|
|
4423
|
-
if (isCancel$
|
|
3405
|
+
if (isCancel$2(frontendTypes)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4424
3406
|
setIsFirstPrompt(false);
|
|
4425
3407
|
const result = [];
|
|
4426
3408
|
let shouldRestart = false;
|
|
@@ -4473,7 +3455,7 @@ async function getFrontendChoice(frontendOptions, backend, auth, previousValue)
|
|
|
4473
3455
|
initialValue: preferValidInitial(webOptions, previousWeb, DEFAULT_CONFIG.frontend[0])
|
|
4474
3456
|
});
|
|
4475
3457
|
if (isGoBack(webFramework)) shouldRestart = true;
|
|
4476
|
-
else if (isCancel$
|
|
3458
|
+
else if (isCancel$2(webFramework)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4477
3459
|
else result.push(webFramework);
|
|
4478
3460
|
}
|
|
4479
3461
|
if (shouldRestart) {
|
|
@@ -4507,7 +3489,7 @@ async function getFrontendChoice(frontendOptions, backend, auth, previousValue)
|
|
|
4507
3489
|
setIsFirstPrompt(wasFirstPrompt);
|
|
4508
3490
|
continue;
|
|
4509
3491
|
}
|
|
4510
|
-
else if (isCancel$
|
|
3492
|
+
else if (isCancel$2(nativeFramework)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4511
3493
|
else result.push(nativeFramework);
|
|
4512
3494
|
}
|
|
4513
3495
|
if (shouldRestart) {
|
|
@@ -4525,7 +3507,7 @@ async function getGitChoice(git, previousValue) {
|
|
|
4525
3507
|
message: "Initialize git repository?",
|
|
4526
3508
|
initialValue: previousValue ?? DEFAULT_CONFIG.git
|
|
4527
3509
|
});
|
|
4528
|
-
if (isCancel$
|
|
3510
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4529
3511
|
return response;
|
|
4530
3512
|
}
|
|
4531
3513
|
//#endregion
|
|
@@ -4536,7 +3518,7 @@ async function getinstallChoice(install, previousValue) {
|
|
|
4536
3518
|
message: "Install dependencies?",
|
|
4537
3519
|
initialValue: previousValue ?? DEFAULT_CONFIG.install
|
|
4538
3520
|
});
|
|
4539
|
-
if (isCancel$
|
|
3521
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4540
3522
|
return response;
|
|
4541
3523
|
}
|
|
4542
3524
|
//#endregion
|
|
@@ -4544,20 +3526,21 @@ async function getinstallChoice(install, previousValue) {
|
|
|
4544
3526
|
const options = [{
|
|
4545
3527
|
value: "getmonitor",
|
|
4546
3528
|
label: "GetMonitor",
|
|
4547
|
-
hint: "
|
|
3529
|
+
hint: "Recommended — JS/TS error tracking for browser and server"
|
|
4548
3530
|
}, {
|
|
4549
|
-
value: "
|
|
4550
|
-
label: "
|
|
4551
|
-
hint: "
|
|
3531
|
+
value: "himetrica",
|
|
3532
|
+
label: "Himetrica",
|
|
3533
|
+
hint: "Product analytics, errors, and Web Vitals for web apps"
|
|
4552
3534
|
}];
|
|
4553
3535
|
async function getObservabilityChoice(observability, previousValue) {
|
|
4554
3536
|
if (observability !== void 0) return observability;
|
|
4555
|
-
const response = await
|
|
4556
|
-
message: "Select observability
|
|
3537
|
+
const response = await navigableMultiselect({
|
|
3538
|
+
message: "Select observability providers",
|
|
4557
3539
|
options,
|
|
4558
|
-
|
|
3540
|
+
required: false,
|
|
3541
|
+
initialValues: previousValue ?? DEFAULT_CONFIG.observability
|
|
4559
3542
|
});
|
|
4560
|
-
if (isCancel$
|
|
3543
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4561
3544
|
return response;
|
|
4562
3545
|
}
|
|
4563
3546
|
//#endregion
|
|
@@ -4675,7 +3658,8 @@ function validateBackendNoneConstraints(config, providedFlags) {
|
|
|
4675
3658
|
if (has("orm") && config.orm !== "none") return validationErr("Backend 'none' requires '--orm none'. Please remove the --orm flag or set it to 'none'.");
|
|
4676
3659
|
if (has("api") && config.api !== "none") return validationErr("Backend 'none' requires '--api none'. Please remove the --api flag or set it to 'none'.");
|
|
4677
3660
|
if (has("auth") && config.auth !== "none") return validationErr("Backend 'none' requires '--auth none'. Please remove the --auth flag or set it to 'none'.");
|
|
4678
|
-
if (has("payments") && config.payments
|
|
3661
|
+
if (has("payments") && (config.payments?.length ?? 0) > 0) return validationErr("Backend 'none' requires '--payments none'. Please remove the --payments flag or set it to 'none'.");
|
|
3662
|
+
if (has("communication") && config.communication !== "none") return validationErr("Backend 'none' requires '--communication none'. Please remove the --communication flag or set it to 'none'.");
|
|
4679
3663
|
if (has("dbSetup") && config.dbSetup !== "none") return validationErr("Backend 'none' requires '--db-setup none'. Please remove the --db-setup flag or set it to 'none'.");
|
|
4680
3664
|
if (has("serverDeploy") && config.serverDeploy !== "none") return validationErr("Backend 'none' requires '--server-deploy none'. Please remove the --server-deploy flag or set it to 'none'.");
|
|
4681
3665
|
return Result.ok(void 0);
|
|
@@ -4723,6 +3707,8 @@ function validateFrontendConstraints(config, providedFlags) {
|
|
|
4723
3707
|
return Result.ok(void 0);
|
|
4724
3708
|
}
|
|
4725
3709
|
function validateApiConstraints(config, options) {
|
|
3710
|
+
if (!isApiCompatibleWithBackend(config.api, config.backend)) return validationErr("Orval API requires the Hono backend in this version. Please use '--backend hono' or choose tRPC/oRPC.");
|
|
3711
|
+
if (config.api === "orval" && options.examples?.includes("todo")) return validationErr("The Orval API layer does not support the generated todo example yet. Please remove 'todo' from --examples or choose tRPC/oRPC.");
|
|
4726
3712
|
if (config.api === "none") {
|
|
4727
3713
|
if (options.examples?.includes("todo") && options.backend !== "convex" && options.backend !== "none") return validationErr("Cannot use '--examples todo' when '--api' is set to 'none'. The todo example requires an API layer. Please remove 'todo' from --examples or choose an API type.");
|
|
4728
3714
|
}
|
|
@@ -4737,6 +3723,7 @@ function validateFullConfig(config, providedFlags, options) {
|
|
|
4737
3723
|
yield* validateSelfBackendConstraints(config, providedFlags);
|
|
4738
3724
|
yield* validateBackendConstraints(config, providedFlags, options);
|
|
4739
3725
|
yield* validateFrontendConstraints(config, providedFlags);
|
|
3726
|
+
yield* validateTestingAgainstFrontends(config.testing ?? [], config.frontend ?? []);
|
|
4740
3727
|
yield* validateApiConstraints(config, options);
|
|
4741
3728
|
yield* validateServerDeployRequiresBackend(config.serverDeploy, config.backend);
|
|
4742
3729
|
yield* validateDockerServerDeploy(config.serverDeploy, config.backend, config.runtime);
|
|
@@ -4753,6 +3740,8 @@ function validateFullConfig(config, providedFlags, options) {
|
|
|
4753
3740
|
}
|
|
4754
3741
|
yield* validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? [], config.api);
|
|
4755
3742
|
yield* validatePaymentsCompatibility(config.payments, config.auth, config.backend, config.frontend ?? [], config.orm, config.database);
|
|
3743
|
+
yield* validateCommunicationCompatibility(config.communication, config.backend);
|
|
3744
|
+
yield* validateAraraRuntimeCompatibility(config.communication, config.backend, config.runtime, config.serverDeploy);
|
|
4756
3745
|
return Result.ok(void 0);
|
|
4757
3746
|
});
|
|
4758
3747
|
}
|
|
@@ -4761,7 +3750,10 @@ function validateConfigForProgrammaticUse(config) {
|
|
|
4761
3750
|
yield* validateDatabaseOrmAuth(config);
|
|
4762
3751
|
if (config.frontend && config.frontend.length > 0) yield* ensureSingleWebAndNative(config.frontend);
|
|
4763
3752
|
yield* validateApiFrontendCompatibility(config.api, config.frontend);
|
|
3753
|
+
yield* validateTestingAgainstFrontends(config.testing ?? [], config.frontend ?? []);
|
|
4764
3754
|
yield* validatePaymentsCompatibility(config.payments, config.auth, config.backend, config.frontend, config.orm, config.database);
|
|
3755
|
+
yield* validateCommunicationCompatibility(config.communication, config.backend);
|
|
3756
|
+
yield* validateAraraRuntimeCompatibility(config.communication, config.backend, config.runtime, config.serverDeploy);
|
|
4765
3757
|
if (config.addons && config.addons.length > 0) yield* validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth, config.backend, config.runtime);
|
|
4766
3758
|
yield* validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? [], config.api);
|
|
4767
3759
|
return Result.ok(void 0);
|
|
@@ -4800,7 +3792,7 @@ async function getORMChoice(orm, hasDatabase, database, backend, runtime, previo
|
|
|
4800
3792
|
options,
|
|
4801
3793
|
initialValue: preferValidInitial(options, previousValue, database === "mongodb" ? "prisma" : runtime === "workers" ? "drizzle" : DEFAULT_CONFIG.orm)
|
|
4802
3794
|
});
|
|
4803
|
-
if (isCancel$
|
|
3795
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4804
3796
|
return response;
|
|
4805
3797
|
}
|
|
4806
3798
|
//#endregion
|
|
@@ -4829,47 +3821,48 @@ async function getPackageManagerChoice(packageManager, previousValue) {
|
|
|
4829
3821
|
],
|
|
4830
3822
|
initialValue: previousValue ?? detectedPackageManager
|
|
4831
3823
|
});
|
|
4832
|
-
if (isCancel$
|
|
3824
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4833
3825
|
return response;
|
|
4834
3826
|
}
|
|
4835
3827
|
//#endregion
|
|
4836
3828
|
//#region src/prompts/payments.ts
|
|
4837
3829
|
async function getPaymentsChoice(payments, auth, backend, frontends = [], previousValue) {
|
|
4838
3830
|
if (payments !== void 0) return payments;
|
|
4839
|
-
if (backend === "none") return
|
|
3831
|
+
if (backend === "none") return [];
|
|
4840
3832
|
const options = [{
|
|
4841
|
-
value: "none",
|
|
4842
|
-
label: "None",
|
|
4843
|
-
hint: "No payments integration"
|
|
4844
|
-
}, {
|
|
4845
3833
|
value: "abacatepay",
|
|
4846
3834
|
label: "AbacatePay",
|
|
4847
3835
|
hint: "Hosted checkout with webhook reconciliation"
|
|
3836
|
+
}, {
|
|
3837
|
+
value: "stripe",
|
|
3838
|
+
label: "Stripe",
|
|
3839
|
+
hint: "Embedded Checkout with a fulfillment webhook"
|
|
4848
3840
|
}];
|
|
4849
|
-
const
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
"
|
|
4856
|
-
"
|
|
4857
|
-
"
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
3841
|
+
const getDisabledReason = (provider) => {
|
|
3842
|
+
const issue = getPaymentCompatibilityIssue({
|
|
3843
|
+
provider,
|
|
3844
|
+
backend,
|
|
3845
|
+
frontends
|
|
3846
|
+
});
|
|
3847
|
+
if (issue === "requires-web-frontend") return "Requires a web frontend";
|
|
3848
|
+
if (issue === "convex-unsupported") return "Not supported with Convex";
|
|
3849
|
+
if (issue === "native-only-unsupported") return "Not supported with native-only frontends";
|
|
3850
|
+
if (issue === "sql-database-required") return "Requires a SQL database with Prisma or Drizzle";
|
|
3851
|
+
};
|
|
3852
|
+
const response = await navigableMultiselect({
|
|
3853
|
+
message: "Select payments providers",
|
|
3854
|
+
options: options.map((option) => {
|
|
3855
|
+
const disabledReason = getDisabledReason(option.value);
|
|
3856
|
+
return {
|
|
3857
|
+
...option,
|
|
3858
|
+
hint: disabledReason ?? option.hint,
|
|
3859
|
+
disabled: disabledReason !== void 0
|
|
3860
|
+
};
|
|
3861
|
+
}),
|
|
3862
|
+
required: false,
|
|
3863
|
+
initialValues: previousValue ?? DEFAULT_CONFIG.payments
|
|
4871
3864
|
});
|
|
4872
|
-
if (isCancel$
|
|
3865
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4873
3866
|
return response;
|
|
4874
3867
|
}
|
|
4875
3868
|
//#endregion
|
|
@@ -4896,7 +3889,7 @@ async function getRuntimeChoice(runtime, backend, previousValue) {
|
|
|
4896
3889
|
options: runtimeOptions,
|
|
4897
3890
|
initialValue: preferValidInitial(runtimeOptions, previousValue, DEFAULT_CONFIG.runtime)
|
|
4898
3891
|
});
|
|
4899
|
-
if (isCancel$
|
|
3892
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4900
3893
|
return response;
|
|
4901
3894
|
}
|
|
4902
3895
|
//#endregion
|
|
@@ -4954,7 +3947,30 @@ async function getServerDeploymentChoice(deployment, runtime, backend, _webDeplo
|
|
|
4954
3947
|
options,
|
|
4955
3948
|
initialValue: preferValidInitial(options, previousValue, DEFAULT_CONFIG.serverDeploy)
|
|
4956
3949
|
});
|
|
4957
|
-
if (isCancel$
|
|
3950
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
3951
|
+
return response;
|
|
3952
|
+
}
|
|
3953
|
+
//#endregion
|
|
3954
|
+
//#region src/prompts/testing.ts
|
|
3955
|
+
async function getTestingChoice(testing, frontends, previousValue) {
|
|
3956
|
+
if (testing !== void 0) return testing;
|
|
3957
|
+
const options = [{
|
|
3958
|
+
value: "vitest",
|
|
3959
|
+
label: "Vitest",
|
|
3960
|
+
hint: "Fast unit test runner"
|
|
3961
|
+
}];
|
|
3962
|
+
if (isPlaywrightAllowed(frontends ?? [])) options.push({
|
|
3963
|
+
value: "playwright",
|
|
3964
|
+
label: "Playwright",
|
|
3965
|
+
hint: "End-to-end browser testing"
|
|
3966
|
+
});
|
|
3967
|
+
const response = await navigableMultiselect({
|
|
3968
|
+
message: "Select testing tools",
|
|
3969
|
+
options,
|
|
3970
|
+
required: false,
|
|
3971
|
+
initialValues: (previousValue ?? DEFAULT_CONFIG.testing)?.filter((t) => options.some((o) => o.value === t))
|
|
3972
|
+
});
|
|
3973
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4958
3974
|
return response;
|
|
4959
3975
|
}
|
|
4960
3976
|
//#endregion
|
|
@@ -5007,7 +4023,7 @@ async function getDeploymentChoice(deployment, _runtime, backend, frontend = [],
|
|
|
5007
4023
|
options,
|
|
5008
4024
|
initialValue: preferValidInitial(options, previousValue, DEFAULT_CONFIG.webDeploy)
|
|
5009
4025
|
});
|
|
5010
|
-
if (isCancel$
|
|
4026
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
5011
4027
|
return response;
|
|
5012
4028
|
}
|
|
5013
4029
|
//#endregion
|
|
@@ -5027,8 +4043,10 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
|
|
|
5027
4043
|
auth: flags.auth ?? DEFAULT_CONFIG.auth,
|
|
5028
4044
|
payments: flags.payments ?? DEFAULT_CONFIG.payments,
|
|
5029
4045
|
observability: flags.observability ?? DEFAULT_CONFIG.observability,
|
|
4046
|
+
communication: flags.communication ?? DEFAULT_CONFIG.communication,
|
|
5030
4047
|
addons: flags.addons ?? [...DEFAULT_CONFIG.addons],
|
|
5031
4048
|
examples: flags.examples ?? [...DEFAULT_CONFIG.examples],
|
|
4049
|
+
testing: flags.testing ?? [...DEFAULT_CONFIG.testing],
|
|
5032
4050
|
git: flags.git ?? DEFAULT_CONFIG.git,
|
|
5033
4051
|
packageManager: flags.packageManager ?? DEFAULT_CONFIG.packageManager,
|
|
5034
4052
|
install: flags.install ?? DEFAULT_CONFIG.install,
|
|
@@ -5047,6 +4065,8 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
|
|
|
5047
4065
|
auth: ({ results, previousAnswer }) => getAuthChoice(flags.auth, results.backend, results.frontend, previousAnswer),
|
|
5048
4066
|
payments: ({ results, previousAnswer }) => getPaymentsChoice(flags.payments, results.auth, results.backend, results.frontend, previousAnswer),
|
|
5049
4067
|
observability: ({ previousAnswer }) => getObservabilityChoice(flags.observability, previousAnswer),
|
|
4068
|
+
communication: ({ results, previousAnswer }) => getCommunicationChoice(flags.communication, results.backend, previousAnswer),
|
|
4069
|
+
testing: ({ results, previousAnswer }) => getTestingChoice(flags.testing, results.frontend, previousAnswer),
|
|
5050
4070
|
addons: ({ results, previousAnswer }) => getAddonsChoice(flags.addons, results.frontend, results.auth, results.backend, results.runtime, previousAnswer),
|
|
5051
4071
|
examples: ({ results, previousAnswer }) => getExamplesChoice(flags.examples, results.database, results.frontend, results.backend, results.api, previousAnswer),
|
|
5052
4072
|
dbSetup: ({ results, previousAnswer }) => getDBSetupChoice(results.database ?? "none", flags.dbSetup, results.orm, results.backend, results.runtime, previousAnswer),
|
|
@@ -5072,8 +4092,10 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
|
|
|
5072
4092
|
auth: result.auth,
|
|
5073
4093
|
payments: result.payments,
|
|
5074
4094
|
observability: result.observability,
|
|
4095
|
+
communication: result.communication,
|
|
5075
4096
|
addons: result.addons,
|
|
5076
4097
|
examples: result.examples,
|
|
4098
|
+
testing: result.testing,
|
|
5077
4099
|
git: result.git,
|
|
5078
4100
|
packageManager: result.packageManager,
|
|
5079
4101
|
install: result.install,
|
|
@@ -5085,6 +4107,32 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
|
|
|
5085
4107
|
}
|
|
5086
4108
|
//#endregion
|
|
5087
4109
|
//#region src/prompts/project-name.ts
|
|
4110
|
+
const S_BAR = "│";
|
|
4111
|
+
const S_BAR_END = "└";
|
|
4112
|
+
const S_STEP_ACTIVE = "◆";
|
|
4113
|
+
function kuboText(options) {
|
|
4114
|
+
return new TextPrompt({
|
|
4115
|
+
validate: options.validate,
|
|
4116
|
+
placeholder: options.placeholder,
|
|
4117
|
+
defaultValue: options.defaultValue,
|
|
4118
|
+
initialValue: options.initialValue,
|
|
4119
|
+
render() {
|
|
4120
|
+
const title = `${cliColors.signal(S_BAR)}\n${cliColors.bright(S_STEP_ACTIVE)} ${options.message}\n`;
|
|
4121
|
+
const placeholder = options.placeholder ? pc.inverse(options.placeholder[0]) + pc.dim(options.placeholder.slice(1)) : pc.inverse(pc.hidden("_"));
|
|
4122
|
+
const input = this.userInput ? this.userInputWithCursor : placeholder;
|
|
4123
|
+
const value = this.value ?? "";
|
|
4124
|
+
switch (this.state) {
|
|
4125
|
+
case "error": {
|
|
4126
|
+
const error = this.error ? ` ${pc.yellow(this.error)}` : "";
|
|
4127
|
+
return `${title.trim()}\n${cliColors.signal(S_BAR)} ${input}\n${pc.yellow(S_BAR_END)}${error}\n`;
|
|
4128
|
+
}
|
|
4129
|
+
case "submit": return `${title}${pc.gray(S_BAR)}${value ? ` ${pc.dim(value)}` : ""}`;
|
|
4130
|
+
case "cancel": return `${title}${pc.gray(S_BAR)}${value ? ` ${pc.strikethrough(pc.dim(value))}` : ""}${value.trim() ? `\n${pc.gray(S_BAR)}` : ""}`;
|
|
4131
|
+
default: return `${title}${cliColors.signal(S_BAR)} ${input}\n${cliColors.signal(S_BAR_END)}\n`;
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
}).prompt();
|
|
4135
|
+
}
|
|
5088
4136
|
function isPathWithinCwd$1(targetPath) {
|
|
5089
4137
|
const resolved = path.resolve(targetPath);
|
|
5090
4138
|
const rel = path.relative(process.cwd(), resolved);
|
|
@@ -5112,7 +4160,7 @@ async function getProjectName(initialName) {
|
|
|
5112
4160
|
counter++;
|
|
5113
4161
|
}
|
|
5114
4162
|
while (!isValid) {
|
|
5115
|
-
const response = await
|
|
4163
|
+
const response = await kuboText({
|
|
5116
4164
|
message: "Enter your project name or path (relative to current directory)",
|
|
5117
4165
|
placeholder: defaultName,
|
|
5118
4166
|
initialValue: initialName,
|
|
@@ -5126,7 +4174,7 @@ async function getProjectName(initialName) {
|
|
|
5126
4174
|
}
|
|
5127
4175
|
}
|
|
5128
4176
|
});
|
|
5129
|
-
if (isCancel(response)) throw new UserCancelledError({ message: "Operation cancelled." });
|
|
4177
|
+
if (isCancel$1(response)) throw new UserCancelledError({ message: "Operation cancelled." });
|
|
5130
4178
|
projectPath = response || defaultName;
|
|
5131
4179
|
isValid = true;
|
|
5132
4180
|
}
|
|
@@ -5167,42 +4215,48 @@ async function trackProjectCreation(config, disableAnalytics = false) {
|
|
|
5167
4215
|
//#region src/utils/display-config.ts
|
|
5168
4216
|
function displayConfig(config) {
|
|
5169
4217
|
const configDisplay = [];
|
|
5170
|
-
if (config.projectName) configDisplay.push(`${
|
|
4218
|
+
if (config.projectName) configDisplay.push(`${cliColors.signal("Project Name:")} ${config.projectName}`);
|
|
5171
4219
|
if (config.frontend !== void 0) {
|
|
5172
4220
|
const frontend = Array.isArray(config.frontend) ? config.frontend : [config.frontend];
|
|
5173
4221
|
const frontendText = frontend.length > 0 && frontend[0] !== void 0 ? frontend.join(", ") : "none";
|
|
5174
|
-
configDisplay.push(`${
|
|
5175
|
-
}
|
|
5176
|
-
if (config.backend !== void 0) configDisplay.push(`${
|
|
5177
|
-
if (config.runtime !== void 0) configDisplay.push(`${
|
|
5178
|
-
if (config.api !== void 0) configDisplay.push(`${
|
|
5179
|
-
if (config.database !== void 0) configDisplay.push(`${
|
|
5180
|
-
if (config.orm !== void 0) configDisplay.push(`${
|
|
5181
|
-
if (config.auth !== void 0) configDisplay.push(`${
|
|
5182
|
-
if (config.payments !== void 0) configDisplay.push(`${
|
|
5183
|
-
if (config.
|
|
4222
|
+
configDisplay.push(`${cliColors.signal("Frontend:")} ${frontendText}`);
|
|
4223
|
+
}
|
|
4224
|
+
if (config.backend !== void 0) configDisplay.push(`${cliColors.signal("Backend:")} ${String(config.backend)}`);
|
|
4225
|
+
if (config.runtime !== void 0) configDisplay.push(`${cliColors.signal("Runtime:")} ${String(config.runtime)}`);
|
|
4226
|
+
if (config.api !== void 0) configDisplay.push(`${cliColors.signal("API:")} ${String(config.api)}`);
|
|
4227
|
+
if (config.database !== void 0) configDisplay.push(`${cliColors.signal("Database:")} ${String(config.database)}`);
|
|
4228
|
+
if (config.orm !== void 0) configDisplay.push(`${cliColors.signal("ORM:")} ${String(config.orm)}`);
|
|
4229
|
+
if (config.auth !== void 0) configDisplay.push(`${cliColors.signal("Auth:")} ${String(config.auth)}`);
|
|
4230
|
+
if (config.payments !== void 0) configDisplay.push(`${cliColors.signal("Payments:")} ${config.payments.join(", ") || "none"}`);
|
|
4231
|
+
if (config.communication !== void 0) configDisplay.push(`${cliColors.signal("Communication:")} ${String(config.communication)}`);
|
|
4232
|
+
if (config.observability !== void 0) configDisplay.push(`${cliColors.signal("Observability:")} ${config.observability.join(", ") || "none"}`);
|
|
5184
4233
|
if (config.addons !== void 0) {
|
|
5185
4234
|
const addons = Array.isArray(config.addons) ? config.addons : [config.addons];
|
|
5186
4235
|
const addonsText = addons.length > 0 && addons[0] !== void 0 ? addons.join(", ") : "none";
|
|
5187
|
-
configDisplay.push(`${
|
|
4236
|
+
configDisplay.push(`${cliColors.signal("Addons:")} ${addonsText}`);
|
|
5188
4237
|
}
|
|
5189
4238
|
if (config.examples !== void 0) {
|
|
5190
4239
|
const examples = Array.isArray(config.examples) ? config.examples : [config.examples];
|
|
5191
4240
|
const examplesText = examples.length > 0 && examples[0] !== void 0 ? examples.join(", ") : "none";
|
|
5192
|
-
configDisplay.push(`${
|
|
4241
|
+
configDisplay.push(`${cliColors.signal("Examples:")} ${examplesText}`);
|
|
4242
|
+
}
|
|
4243
|
+
if (config.testing !== void 0) {
|
|
4244
|
+
const testing = Array.isArray(config.testing) ? config.testing : [config.testing];
|
|
4245
|
+
const testingText = testing.length > 0 && testing[0] !== void 0 ? testing.join(", ") : "none";
|
|
4246
|
+
configDisplay.push(`${cliColors.signal("Testing:")} ${testingText}`);
|
|
5193
4247
|
}
|
|
5194
4248
|
if (config.git !== void 0) {
|
|
5195
4249
|
const gitText = typeof config.git === "boolean" ? config.git ? "Yes" : "No" : String(config.git);
|
|
5196
|
-
configDisplay.push(`${
|
|
4250
|
+
configDisplay.push(`${cliColors.signal("Git Init:")} ${gitText}`);
|
|
5197
4251
|
}
|
|
5198
|
-
if (config.packageManager !== void 0) configDisplay.push(`${
|
|
4252
|
+
if (config.packageManager !== void 0) configDisplay.push(`${cliColors.signal("Package Manager:")} ${String(config.packageManager)}`);
|
|
5199
4253
|
if (config.install !== void 0) {
|
|
5200
4254
|
const installText = typeof config.install === "boolean" ? config.install ? "Yes" : "No" : String(config.install);
|
|
5201
|
-
configDisplay.push(`${
|
|
4255
|
+
configDisplay.push(`${cliColors.signal("Install Dependencies:")} ${installText}`);
|
|
5202
4256
|
}
|
|
5203
|
-
if (config.dbSetup !== void 0) configDisplay.push(`${
|
|
5204
|
-
if (config.webDeploy !== void 0) configDisplay.push(`${
|
|
5205
|
-
if (config.serverDeploy !== void 0) configDisplay.push(`${
|
|
4257
|
+
if (config.dbSetup !== void 0) configDisplay.push(`${cliColors.signal("Database Setup:")} ${String(config.dbSetup)}`);
|
|
4258
|
+
if (config.webDeploy !== void 0) configDisplay.push(`${cliColors.signal("Web Deployment:")} ${String(config.webDeploy)}`);
|
|
4259
|
+
if (config.serverDeploy !== void 0) configDisplay.push(`${cliColors.signal("Server Deployment:")} ${String(config.serverDeploy)}`);
|
|
5206
4260
|
if (configDisplay.length === 0) return pc.yellow("No configuration selected.");
|
|
5207
4261
|
return configDisplay.join("\n");
|
|
5208
4262
|
}
|
|
@@ -5332,8 +4386,9 @@ const TEMPLATE_PRESETS = {
|
|
|
5332
4386
|
frontend: ["react-router"],
|
|
5333
4387
|
api: "orpc",
|
|
5334
4388
|
auth: "better-auth",
|
|
5335
|
-
payments:
|
|
5336
|
-
observability: "
|
|
4389
|
+
payments: [],
|
|
4390
|
+
observability: ["getmonitor"],
|
|
4391
|
+
communication: "none",
|
|
5337
4392
|
addons: ["turborepo"],
|
|
5338
4393
|
examples: ["todo"],
|
|
5339
4394
|
dbSetup: "mongodb-atlas",
|
|
@@ -5348,8 +4403,9 @@ const TEMPLATE_PRESETS = {
|
|
|
5348
4403
|
frontend: ["tanstack-router"],
|
|
5349
4404
|
api: "trpc",
|
|
5350
4405
|
auth: "better-auth",
|
|
5351
|
-
payments:
|
|
5352
|
-
observability: "
|
|
4406
|
+
payments: [],
|
|
4407
|
+
observability: ["getmonitor"],
|
|
4408
|
+
communication: "none",
|
|
5353
4409
|
addons: ["turborepo"],
|
|
5354
4410
|
examples: ["todo"],
|
|
5355
4411
|
dbSetup: "none",
|
|
@@ -5364,8 +4420,9 @@ const TEMPLATE_PRESETS = {
|
|
|
5364
4420
|
frontend: ["next"],
|
|
5365
4421
|
api: "trpc",
|
|
5366
4422
|
auth: "better-auth",
|
|
5367
|
-
payments:
|
|
5368
|
-
observability: "
|
|
4423
|
+
payments: [],
|
|
4424
|
+
observability: ["getmonitor"],
|
|
4425
|
+
communication: "none",
|
|
5369
4426
|
addons: ["biome", "turborepo"],
|
|
5370
4427
|
examples: ["none"],
|
|
5371
4428
|
dbSetup: "none",
|
|
@@ -5380,8 +4437,9 @@ const TEMPLATE_PRESETS = {
|
|
|
5380
4437
|
frontend: ["native-uniwind"],
|
|
5381
4438
|
api: "none",
|
|
5382
4439
|
auth: "none",
|
|
5383
|
-
payments:
|
|
5384
|
-
observability:
|
|
4440
|
+
payments: [],
|
|
4441
|
+
observability: [],
|
|
4442
|
+
communication: "none",
|
|
5385
4443
|
addons: ["none"],
|
|
5386
4444
|
examples: ["none"],
|
|
5387
4445
|
dbSetup: "none",
|
|
@@ -5406,60 +4464,6 @@ function getTemplateDescription(template) {
|
|
|
5406
4464
|
}[template] || "";
|
|
5407
4465
|
}
|
|
5408
4466
|
//#endregion
|
|
5409
|
-
//#region src/utils/config-processing.ts
|
|
5410
|
-
function processArrayOption(options) {
|
|
5411
|
-
if (!options || options.length === 0) return [];
|
|
5412
|
-
if (options.includes("none")) return [];
|
|
5413
|
-
return options.filter((item) => item !== "none");
|
|
5414
|
-
}
|
|
5415
|
-
function deriveProjectName(projectName, projectDirectory) {
|
|
5416
|
-
if (projectName) return projectName;
|
|
5417
|
-
if (projectDirectory) return path.basename(path.resolve(process.cwd(), projectDirectory));
|
|
5418
|
-
return "";
|
|
5419
|
-
}
|
|
5420
|
-
function processFlags(options, projectName) {
|
|
5421
|
-
const config = {};
|
|
5422
|
-
if (options.api) config.api = options.api;
|
|
5423
|
-
if (options.addonOptions) config.addonOptions = options.addonOptions;
|
|
5424
|
-
if (options.dbSetupOptions) config.dbSetupOptions = options.dbSetupOptions;
|
|
5425
|
-
if (options.backend) config.backend = options.backend;
|
|
5426
|
-
if (options.database) config.database = options.database;
|
|
5427
|
-
if (options.orm) config.orm = options.orm;
|
|
5428
|
-
if (options.auth !== void 0) config.auth = options.auth;
|
|
5429
|
-
if (options.payments !== void 0) config.payments = options.payments;
|
|
5430
|
-
if (options.observability !== void 0) config.observability = options.observability;
|
|
5431
|
-
if (options.git !== void 0) config.git = options.git;
|
|
5432
|
-
if (options.install !== void 0) config.install = options.install;
|
|
5433
|
-
if (options.runtime) config.runtime = options.runtime;
|
|
5434
|
-
if (options.dbSetup) config.dbSetup = options.dbSetup;
|
|
5435
|
-
if (options.packageManager) config.packageManager = options.packageManager;
|
|
5436
|
-
if (options.webDeploy) config.webDeploy = options.webDeploy;
|
|
5437
|
-
if (options.serverDeploy) config.serverDeploy = options.serverDeploy;
|
|
5438
|
-
const derivedName = deriveProjectName(projectName, options.projectDirectory);
|
|
5439
|
-
if (derivedName) config.projectName = projectName || derivedName;
|
|
5440
|
-
if (options.frontend && options.frontend.length > 0) config.frontend = processArrayOption(options.frontend);
|
|
5441
|
-
if (options.addons && options.addons.length > 0) config.addons = processArrayOption(options.addons);
|
|
5442
|
-
if (options.examples && options.examples.length > 0) config.examples = processArrayOption(options.examples);
|
|
5443
|
-
return config;
|
|
5444
|
-
}
|
|
5445
|
-
function getProvidedFlags(options) {
|
|
5446
|
-
return new Set(Object.keys(options).filter((key) => options[key] !== void 0));
|
|
5447
|
-
}
|
|
5448
|
-
function validateNoneExclusivity(options, optionName) {
|
|
5449
|
-
if (!options || options.length === 0) return Result.ok(void 0);
|
|
5450
|
-
if (options.includes("none") && options.length > 1) return Result.err(new ValidationError({ message: `Cannot combine 'none' with other ${optionName}.` }));
|
|
5451
|
-
return Result.ok(void 0);
|
|
5452
|
-
}
|
|
5453
|
-
function validateArrayOptions(options) {
|
|
5454
|
-
const frontendResult = validateNoneExclusivity(options.frontend, "frontend options");
|
|
5455
|
-
if (frontendResult.isErr()) return frontendResult;
|
|
5456
|
-
const addonsResult = validateNoneExclusivity(options.addons, "addons");
|
|
5457
|
-
if (addonsResult.isErr()) return addonsResult;
|
|
5458
|
-
const examplesResult = validateNoneExclusivity(options.examples, "examples");
|
|
5459
|
-
if (examplesResult.isErr()) return examplesResult;
|
|
5460
|
-
return Result.ok(void 0);
|
|
5461
|
-
}
|
|
5462
|
-
//#endregion
|
|
5463
4467
|
//#region src/validation.ts
|
|
5464
4468
|
const CORE_STACK_FLAGS = new Set([
|
|
5465
4469
|
"database",
|
|
@@ -5469,10 +4473,13 @@ const CORE_STACK_FLAGS = new Set([
|
|
|
5469
4473
|
"frontend",
|
|
5470
4474
|
"addons",
|
|
5471
4475
|
"examples",
|
|
4476
|
+
"testing",
|
|
5472
4477
|
"auth",
|
|
5473
4478
|
"dbSetup",
|
|
5474
4479
|
"payments",
|
|
5475
4480
|
"observability",
|
|
4481
|
+
"disableObservability",
|
|
4482
|
+
"communication",
|
|
5476
4483
|
"api",
|
|
5477
4484
|
"webDeploy",
|
|
5478
4485
|
"serverDeploy"
|
|
@@ -5525,6 +4532,49 @@ const formatOptions = {
|
|
|
5525
4532
|
experimentalSortPackageJson: true,
|
|
5526
4533
|
experimentalSortImports: { order: "asc" }
|
|
5527
4534
|
};
|
|
4535
|
+
/** Files that must not be rewritten by post-scaffold formatters. */
|
|
4536
|
+
const SKIP_FILE_NAMES = new Set([
|
|
4537
|
+
"kubojs.jsonrc",
|
|
4538
|
+
"package-lock.json",
|
|
4539
|
+
"pnpm-lock.yaml",
|
|
4540
|
+
"yarn.lock",
|
|
4541
|
+
"bun.lock",
|
|
4542
|
+
"bun.lockb",
|
|
4543
|
+
"Cargo.lock",
|
|
4544
|
+
"composer.lock"
|
|
4545
|
+
]);
|
|
4546
|
+
const SKIP_EXTENSIONS = new Set([
|
|
4547
|
+
".png",
|
|
4548
|
+
".jpg",
|
|
4549
|
+
".jpeg",
|
|
4550
|
+
".gif",
|
|
4551
|
+
".webp",
|
|
4552
|
+
".ico",
|
|
4553
|
+
".svg",
|
|
4554
|
+
".woff",
|
|
4555
|
+
".woff2",
|
|
4556
|
+
".ttf",
|
|
4557
|
+
".eot",
|
|
4558
|
+
".mp4",
|
|
4559
|
+
".mp3",
|
|
4560
|
+
".wasm",
|
|
4561
|
+
".zip",
|
|
4562
|
+
".gz",
|
|
4563
|
+
".tgz",
|
|
4564
|
+
".br",
|
|
4565
|
+
".pdf",
|
|
4566
|
+
".db",
|
|
4567
|
+
".sqlite"
|
|
4568
|
+
]);
|
|
4569
|
+
function shouldSkipFile(fileName) {
|
|
4570
|
+
if (SKIP_FILE_NAMES.has(fileName)) return true;
|
|
4571
|
+
const ext = path.extname(fileName).toLowerCase();
|
|
4572
|
+
return SKIP_EXTENSIONS.has(ext);
|
|
4573
|
+
}
|
|
4574
|
+
function shouldUseBiomeFormatter(addons) {
|
|
4575
|
+
if (!addons) return false;
|
|
4576
|
+
return addons.includes("biome");
|
|
4577
|
+
}
|
|
5528
4578
|
async function formatCode(filePath, content) {
|
|
5529
4579
|
const result = await Result.tryPromise({
|
|
5530
4580
|
try: async () => {
|
|
@@ -5536,25 +4586,61 @@ async function formatCode(filePath, content) {
|
|
|
5536
4586
|
});
|
|
5537
4587
|
return result.isOk() ? result.value : null;
|
|
5538
4588
|
}
|
|
5539
|
-
async function
|
|
4589
|
+
async function formatProjectWithOxfmt(projectDir) {
|
|
4590
|
+
async function formatDirectory(dir) {
|
|
4591
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
4592
|
+
await Promise.all(entries.map(async (entry) => {
|
|
4593
|
+
const fullPath = path.join(dir, entry.name);
|
|
4594
|
+
if (entry.isDirectory()) {
|
|
4595
|
+
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist" || entry.name === ".turbo") return;
|
|
4596
|
+
await formatDirectory(fullPath);
|
|
4597
|
+
} else if (entry.isFile()) {
|
|
4598
|
+
if (shouldSkipFile(entry.name)) return;
|
|
4599
|
+
await Result.tryPromise({
|
|
4600
|
+
try: async () => {
|
|
4601
|
+
const content = await fs.readFile(fullPath, "utf-8");
|
|
4602
|
+
const formatted = await formatCode(fullPath, content);
|
|
4603
|
+
if (formatted && formatted !== content) await fs.writeFile(fullPath, formatted, "utf-8");
|
|
4604
|
+
},
|
|
4605
|
+
catch: () => void 0
|
|
4606
|
+
});
|
|
4607
|
+
}
|
|
4608
|
+
}));
|
|
4609
|
+
}
|
|
4610
|
+
await formatDirectory(projectDir);
|
|
4611
|
+
}
|
|
4612
|
+
/**
|
|
4613
|
+
* Format with Biome so scaffold matches `biome check` (source of truth).
|
|
4614
|
+
* Uses bunx so it works before local install. Best-effort: failures are ignored
|
|
4615
|
+
* so offline CI/tests still scaffold.
|
|
4616
|
+
*/
|
|
4617
|
+
async function formatProjectWithBiome(projectDir) {
|
|
4618
|
+
if (shouldSkipExternalCommands()) return;
|
|
4619
|
+
await Result.tryPromise({
|
|
4620
|
+
try: async () => {
|
|
4621
|
+
await $({
|
|
4622
|
+
cwd: projectDir,
|
|
4623
|
+
reject: false,
|
|
4624
|
+
stdio: "pipe"
|
|
4625
|
+
})`bunx --bun @biomejs/biome@2.5.8 check --write .`;
|
|
4626
|
+
},
|
|
4627
|
+
catch: () => void 0
|
|
4628
|
+
});
|
|
4629
|
+
}
|
|
4630
|
+
/**
|
|
4631
|
+
* Post-scaffold format.
|
|
4632
|
+
* - biome → Biome `check --write` (not oxfmt)
|
|
4633
|
+
* - oxlint / none → oxfmt
|
|
4634
|
+
* Always skips kubojs.jsonrc, lockfiles, and binary extensions (oxfmt path).
|
|
4635
|
+
*/
|
|
4636
|
+
async function formatProject(projectDir, options = {}) {
|
|
5540
4637
|
return Result.tryPromise({
|
|
5541
4638
|
try: async () => {
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
const fullPath = path.join(dir, entry.name);
|
|
5546
|
-
if (entry.isDirectory()) await formatDirectory(fullPath);
|
|
5547
|
-
else if (entry.isFile()) await Result.tryPromise({
|
|
5548
|
-
try: async () => {
|
|
5549
|
-
const content = await fs.readFile(fullPath, "utf-8");
|
|
5550
|
-
const formatted = await formatCode(fullPath, content);
|
|
5551
|
-
if (formatted && formatted !== content) await fs.writeFile(fullPath, formatted, "utf-8");
|
|
5552
|
-
},
|
|
5553
|
-
catch: () => void 0
|
|
5554
|
-
});
|
|
5555
|
-
}));
|
|
4639
|
+
if (shouldUseBiomeFormatter(options.addons)) {
|
|
4640
|
+
await formatProjectWithBiome(projectDir);
|
|
4641
|
+
return;
|
|
5556
4642
|
}
|
|
5557
|
-
await
|
|
4643
|
+
await formatProjectWithOxfmt(projectDir);
|
|
5558
4644
|
},
|
|
5559
4645
|
catch: (e) => new ProjectCreationError({
|
|
5560
4646
|
phase: "formatting",
|
|
@@ -5564,6 +4650,57 @@ async function formatProject(projectDir) {
|
|
|
5564
4650
|
});
|
|
5565
4651
|
}
|
|
5566
4652
|
//#endregion
|
|
4653
|
+
//#region src/utils/generate-route-tree.ts
|
|
4654
|
+
const TANSTACK_ROUTE_TREE_FRONTENDS = [
|
|
4655
|
+
"tanstack-router",
|
|
4656
|
+
"tanstack-start",
|
|
4657
|
+
"solid"
|
|
4658
|
+
];
|
|
4659
|
+
function resolveRouteTreeTarget(frontend) {
|
|
4660
|
+
if (frontend.includes("solid")) return "solid";
|
|
4661
|
+
if (frontend.includes("tanstack-router") || frontend.includes("tanstack-start")) return "react";
|
|
4662
|
+
return null;
|
|
4663
|
+
}
|
|
4664
|
+
function needsRouteTree(frontend) {
|
|
4665
|
+
return frontend.some((value) => TANSTACK_ROUTE_TREE_FRONTENDS.includes(value));
|
|
4666
|
+
}
|
|
4667
|
+
/**
|
|
4668
|
+
* Generate apps/web/src/routeTree.gen.ts from the scaffolded file routes.
|
|
4669
|
+
* TanStack recommends committing this file (runtime source, not a build artifact).
|
|
4670
|
+
* Running at create-time makes `check-types` pass without a prior vite build.
|
|
4671
|
+
*/
|
|
4672
|
+
async function generateRouteTreeIfNeeded(projectDir, config) {
|
|
4673
|
+
if (!needsRouteTree(config.frontend)) return Result.ok(void 0);
|
|
4674
|
+
const target = resolveRouteTreeTarget(config.frontend);
|
|
4675
|
+
if (!target) return Result.ok(void 0);
|
|
4676
|
+
const webAppDir = path.join(projectDir, "apps/web");
|
|
4677
|
+
const routesDir = path.join(webAppDir, "src/routes");
|
|
4678
|
+
if (!await fs.pathExists(routesDir)) return Result.ok(void 0);
|
|
4679
|
+
return Result.tryPromise({
|
|
4680
|
+
try: async () => {
|
|
4681
|
+
await new Generator({
|
|
4682
|
+
config: configSchema.parse({
|
|
4683
|
+
target,
|
|
4684
|
+
routesDirectory: path.join(webAppDir, "src/routes"),
|
|
4685
|
+
generatedRouteTree: path.join(webAppDir, "src/routeTree.gen.ts"),
|
|
4686
|
+
tmpDir: path.join(webAppDir, ".tanstack/tmp"),
|
|
4687
|
+
autoCodeSplitting: true,
|
|
4688
|
+
disableLogging: true,
|
|
4689
|
+
enableRouteTreeFormatting: true,
|
|
4690
|
+
quoteStyle: "single",
|
|
4691
|
+
semicolons: false
|
|
4692
|
+
}),
|
|
4693
|
+
root: webAppDir
|
|
4694
|
+
}).run();
|
|
4695
|
+
},
|
|
4696
|
+
catch: (e) => new ProjectCreationError({
|
|
4697
|
+
phase: "route-tree-generation",
|
|
4698
|
+
message: `Failed to generate TanStack route tree: ${e instanceof Error ? e.message : String(e)}`,
|
|
4699
|
+
cause: e
|
|
4700
|
+
})
|
|
4701
|
+
});
|
|
4702
|
+
}
|
|
4703
|
+
//#endregion
|
|
5567
4704
|
//#region src/utils/env-utils.ts
|
|
5568
4705
|
async function addEnvVariablesToFile(envPath, variables) {
|
|
5569
4706
|
let content = "";
|
|
@@ -5940,7 +5077,7 @@ async function writeEnvFile$2(projectDir, backend, config) {
|
|
|
5940
5077
|
}
|
|
5941
5078
|
async function setupWithNeonDb(projectDir, packageManager, backend) {
|
|
5942
5079
|
const s = createSpinner();
|
|
5943
|
-
s.start("Creating Neon database using
|
|
5080
|
+
s.start("Creating Neon database using neon-new...");
|
|
5944
5081
|
const targetApp = backend === "self" ? "apps/web" : "apps/server";
|
|
5945
5082
|
const targetDir = path.join(projectDir, targetApp);
|
|
5946
5083
|
const ensureDirResult = await Result.tryPromise({
|
|
@@ -5955,17 +5092,17 @@ async function setupWithNeonDb(projectDir, packageManager, backend) {
|
|
|
5955
5092
|
s.stop(pc.red("Failed to create directory"));
|
|
5956
5093
|
return ensureDirResult;
|
|
5957
5094
|
}
|
|
5958
|
-
const packageArgs = getPackageExecutionArgs(packageManager, `
|
|
5095
|
+
const packageArgs = getPackageExecutionArgs(packageManager, `neon-new@latest --yes --ref "sbA3tIe"`);
|
|
5959
5096
|
return Result.tryPromise({
|
|
5960
5097
|
try: async () => {
|
|
5961
5098
|
await $({ cwd: targetDir })`${packageArgs}`;
|
|
5962
5099
|
s.stop(pc.green("Neon database created successfully!"));
|
|
5963
5100
|
},
|
|
5964
5101
|
catch: (e) => {
|
|
5965
|
-
s.stop(pc.red("Failed to create database with
|
|
5102
|
+
s.stop(pc.red("Failed to create database with neon-new"));
|
|
5966
5103
|
return new DatabaseSetupError({
|
|
5967
5104
|
provider: "neon",
|
|
5968
|
-
message: `Failed to create database with
|
|
5105
|
+
message: `Failed to create database with neon-new: ${e instanceof Error ? e.message : String(e)}`,
|
|
5969
5106
|
cause: e
|
|
5970
5107
|
});
|
|
5971
5108
|
}
|
|
@@ -5974,7 +5111,7 @@ async function setupWithNeonDb(projectDir, packageManager, backend) {
|
|
|
5974
5111
|
function displayManualSetupInstructions$2(target) {
|
|
5975
5112
|
cliLog.info(`Manual Neon PostgreSQL Setup Instructions:
|
|
5976
5113
|
|
|
5977
|
-
1. Get Neon with
|
|
5114
|
+
1. Get Neon with kubojs referral: https://get.neon.com/sbA3tIe
|
|
5978
5115
|
2. Create a new project from the dashboard
|
|
5979
5116
|
3. Get your connection string
|
|
5980
5117
|
4. Add the database URL to the .env file in ${target}/.env
|
|
@@ -6031,7 +5168,7 @@ async function setupNeonPostgres(config, cliInput) {
|
|
|
6031
5168
|
const promptedSetupMethod = await select({
|
|
6032
5169
|
message: "Choose your Neon setup method:",
|
|
6033
5170
|
options: [{
|
|
6034
|
-
label: "Quick setup with
|
|
5171
|
+
label: "Quick setup with neon-new",
|
|
6035
5172
|
value: "neondb",
|
|
6036
5173
|
hint: "fastest, no auth required"
|
|
6037
5174
|
}, {
|
|
@@ -6053,7 +5190,7 @@ async function setupNeonPostgres(config, cliInput) {
|
|
|
6053
5190
|
displayManualSetupInstructions$2(target);
|
|
6054
5191
|
return Result.ok(void 0);
|
|
6055
5192
|
}
|
|
6056
|
-
cliLog.info(`Get Neon with
|
|
5193
|
+
cliLog.info(`Get Neon with kubojs referral: ${pc.cyan("https://get.neon.com/sbA3tIe")}`);
|
|
6057
5194
|
return Result.ok(void 0);
|
|
6058
5195
|
}
|
|
6059
5196
|
const suggestedProjectName = path.basename(projectDir);
|
|
@@ -6095,7 +5232,7 @@ async function setupNeonPostgres(config, cliInput) {
|
|
|
6095
5232
|
return envResult;
|
|
6096
5233
|
}
|
|
6097
5234
|
finalSpinner.stop("Neon database configured!");
|
|
6098
|
-
cliLog.info(`Get Neon with
|
|
5235
|
+
cliLog.info(`Get Neon with kubojs referral: ${pc.cyan("https://get.neon.com/sbA3tIe")}`);
|
|
6099
5236
|
return Result.ok(void 0);
|
|
6100
5237
|
}
|
|
6101
5238
|
//#endregion
|
|
@@ -6196,7 +5333,7 @@ const AVAILABLE_REGIONS = [
|
|
|
6196
5333
|
label: "US West (N. California)"
|
|
6197
5334
|
}
|
|
6198
5335
|
];
|
|
6199
|
-
const CREATE_DB_USER_AGENT = "
|
|
5336
|
+
const CREATE_DB_USER_AGENT = "kubojs/kubojs";
|
|
6200
5337
|
async function setupWithCreateDb(serverDir, packageManager, regionId) {
|
|
6201
5338
|
cliLog.info("Starting Prisma Postgres setup with create-db.");
|
|
6202
5339
|
let selectedRegion = regionId;
|
|
@@ -6963,6 +6100,10 @@ async function getDockerStatus(database) {
|
|
|
6963
6100
|
}
|
|
6964
6101
|
//#endregion
|
|
6965
6102
|
//#region src/helpers/core/post-installation.ts
|
|
6103
|
+
const pc$1 = {
|
|
6104
|
+
...pc,
|
|
6105
|
+
cyan: cliColors.signal
|
|
6106
|
+
};
|
|
6966
6107
|
function getDesktopStaticBuildNote(frontend) {
|
|
6967
6108
|
const staticBuildFrontends = new Map([
|
|
6968
6109
|
["tanstack-start", "TanStack Start"],
|
|
@@ -6973,10 +6114,10 @@ function getDesktopStaticBuildNote(frontend) {
|
|
|
6973
6114
|
]);
|
|
6974
6115
|
const staticBuildFrontend = frontend.find((value) => staticBuildFrontends.has(value));
|
|
6975
6116
|
if (!staticBuildFrontend) return "";
|
|
6976
|
-
return `${pc.yellow("NOTE:")} Desktop builds package static web assets.\n ${staticBuildFrontends.get(staticBuildFrontend)} needs a static/export web build before desktop packaging will work.`;
|
|
6117
|
+
return `${pc$1.yellow("NOTE:")} Desktop builds package static web assets.\n ${staticBuildFrontends.get(staticBuildFrontend)} needs a static/export web build before desktop packaging will work.`;
|
|
6977
6118
|
}
|
|
6978
6119
|
async function displayPostInstallInstructions(config) {
|
|
6979
|
-
const { api, database, relativePath, packageManager, depsInstalled, orm, addons, runtime, frontend, backend, dbSetup, webDeploy, serverDeploy, observability } = config;
|
|
6120
|
+
const { api, database, relativePath, packageManager, depsInstalled, orm, addons, testing, runtime, frontend, backend, dbSetup, webDeploy, serverDeploy, observability, communication } = config;
|
|
6980
6121
|
const isConvex = backend === "convex";
|
|
6981
6122
|
const isBackendSelf = backend === "self";
|
|
6982
6123
|
const runCmd = packageManager === "npm" ? "npm run" : packageManager === "pnpm" ? "pnpm run" : "bun run";
|
|
@@ -6995,50 +6136,52 @@ async function displayPostInstallInstructions(config) {
|
|
|
6995
6136
|
const lintingInstructions = hasGitHooksOrLinting ? getLintingInstructions(runCmd) : "";
|
|
6996
6137
|
const nativeInstructions = (frontend?.includes("native-bare") || frontend?.includes("native-uniwind") || frontend?.includes("native-unistyles")) && backend !== "none" ? getNativeInstructions(isConvex, isBackendSelf, frontend || [], runCmd) : "";
|
|
6997
6138
|
const pwaInstructions = addons?.includes("pwa") && frontend?.includes("react-router") ? getPwaInstructions() : "";
|
|
6998
|
-
const
|
|
6139
|
+
const playwrightInstructions = testing?.includes("playwright") ? getPlaywrightInstructions(runCmd, packageManager) : "";
|
|
6999
6140
|
const clerkInstructions = config.auth === "clerk" ? getClerkInstructions(frontend || [], backend, api) : "";
|
|
7000
6141
|
const alchemyDeployInstructions = getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy, backend);
|
|
7001
|
-
const getMonitorInstructions = observability
|
|
6142
|
+
const getMonitorInstructions = observability.includes("getmonitor") ? getGetMonitorInstructions() : "";
|
|
6143
|
+
const resendInstructions = communication === "resend" ? getResendInstructions() : "";
|
|
6144
|
+
const notifiqueInstructions = communication === "notifique" ? getNotifiqueInstructions() : "";
|
|
6145
|
+
const araraInstructions = communication === "arara" ? getAraraInstructions(backend) : "";
|
|
7002
6146
|
const hasWeb = frontend?.some((f) => types_exports.desktopWebFrontends.includes(f));
|
|
7003
6147
|
const hasNative = frontend?.includes("native-bare") || frontend?.includes("native-uniwind") || frontend?.includes("native-unistyles");
|
|
7004
|
-
const
|
|
7005
|
-
const hasSvelte = frontend?.includes("svelte");
|
|
7006
|
-
const hasAstro = frontend?.includes("astro");
|
|
7007
|
-
const webPort = hasReactRouter || hasSvelte ? "5173" : hasAstro ? "4321" : "3001";
|
|
6148
|
+
const webPort = (0, types_exports.getWebPort)(frontend ?? []);
|
|
7008
6149
|
const betterAuthConvexInstructions = isConvex && config.auth === "better-auth" ? getBetterAuthConvexInstructions(hasWeb ?? false, webPort, packageManager, runCmd) : "";
|
|
7009
6150
|
const bunWebNativeWarning = packageManager === "bun" && hasNative && hasWeb ? getBunWebNativeWarning() : "";
|
|
7010
6151
|
const noOrmWarning = !isConvex && database !== "none" && orm === "none" ? getNoOrmWarning() : "";
|
|
7011
|
-
let output = `${pc.bold("Next steps")}\n${pc.cyan("1.")} ${cdCmd}\n`;
|
|
6152
|
+
let output = `${pc$1.bold("Next steps")}\n${pc$1.cyan("1.")} ${cdCmd}\n`;
|
|
7012
6153
|
let stepCounter = 2;
|
|
7013
|
-
if (!depsInstalled) output += `${pc.cyan(`${stepCounter++}.`)} ${packageManager} install\n`;
|
|
7014
|
-
if (database === "sqlite" && dbSetup
|
|
6154
|
+
if (!depsInstalled) output += `${pc$1.cyan(`${stepCounter++}.`)} ${packageManager} install\n`;
|
|
6155
|
+
if (database === "sqlite" && dbSetup === "turso") output += `${pc$1.cyan(`${stepCounter++}.`)} ${runCmd} db:local\n${pc$1.dim(" (optional - starts local Turso/libsql replica; requires Turso CLI)")}\n`;
|
|
7015
6156
|
if (isConvex) {
|
|
7016
|
-
output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev:setup\n${pc.dim(" (this will guide you through Convex project setup)")}\n`;
|
|
7017
|
-
output += `${pc.cyan(`${stepCounter++}.`)} Copy environment variables from\n${pc.white(" packages/backend/.env.local")} to ${pc.white("apps/*/.env")}\n`;
|
|
7018
|
-
output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n\n`;
|
|
7019
|
-
} else if (isBackendSelf) output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n`;
|
|
6157
|
+
output += `${pc$1.cyan(`${stepCounter++}.`)} ${runCmd} dev:setup\n${pc$1.dim(" (this will guide you through Convex project setup)")}\n`;
|
|
6158
|
+
output += `${pc$1.cyan(`${stepCounter++}.`)} Copy environment variables from\n${pc$1.white(" packages/backend/.env.local")} to ${pc$1.white("apps/*/.env")}\n`;
|
|
6159
|
+
output += `${pc$1.cyan(`${stepCounter++}.`)} ${runCmd} dev\n\n`;
|
|
6160
|
+
} else if (isBackendSelf) output += `${pc$1.cyan(`${stepCounter++}.`)} ${runCmd} dev\n`;
|
|
7020
6161
|
else {
|
|
7021
|
-
if (runtime !== "workers") output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n`;
|
|
6162
|
+
if (runtime !== "workers") output += `${pc$1.cyan(`${stepCounter++}.`)} ${runCmd} dev\n`;
|
|
7022
6163
|
if (runtime === "workers") {
|
|
7023
|
-
if (dbSetup === "d1") output += `${pc.yellow("IMPORTANT:")} Complete D1 database setup first\n (see Database commands below)\n`;
|
|
7024
|
-
output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n`;
|
|
6164
|
+
if (dbSetup === "d1") output += `${pc$1.yellow("IMPORTANT:")} Complete D1 database setup first\n (see Database commands below)\n`;
|
|
6165
|
+
output += `${pc$1.cyan(`${stepCounter++}.`)} ${runCmd} dev\n`;
|
|
7025
6166
|
}
|
|
7026
6167
|
}
|
|
7027
6168
|
const hasStandaloneBackend = backend !== "none";
|
|
7028
|
-
if (hasWeb || hasStandaloneBackend
|
|
7029
|
-
output += `${pc.bold("Your project will be available at:")}\n`;
|
|
7030
|
-
if (hasWeb) output += `${pc.cyan("•")} Frontend: http://localhost:${webPort}\n`;
|
|
7031
|
-
else if (!hasNative
|
|
6169
|
+
if (hasWeb || hasStandaloneBackend) {
|
|
6170
|
+
output += `${pc$1.bold("Your project will be available at:")}\n`;
|
|
6171
|
+
if (hasWeb) output += `${pc$1.cyan("•")} Frontend: http://localhost:${webPort}\n`;
|
|
6172
|
+
else if (!hasNative) output += `${pc$1.yellow("NOTE:")} You are creating a backend-only app\n (no frontend selected)\n`;
|
|
7032
6173
|
if (!isConvex && !isBackendSelf && hasStandaloneBackend) {
|
|
7033
|
-
output += `${pc.cyan("•")} Backend API: http://localhost:3000\n`;
|
|
7034
|
-
if (api === "orpc") output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:3000/api-reference\n`;
|
|
6174
|
+
output += `${pc$1.cyan("•")} Backend API: http://localhost:3000\n`;
|
|
6175
|
+
if (api === "orpc") output += `${pc$1.cyan("•")} OpenAPI (Scalar UI): http://localhost:3000/api-reference\n`;
|
|
6176
|
+
if (api === "orval") {
|
|
6177
|
+
output += `${pc$1.cyan("•")} OpenAPI contract: apps/api/openapi.yaml\n`;
|
|
6178
|
+
output += `${pc$1.cyan("•")} Regenerate API code: bun run api:generate\n`;
|
|
6179
|
+
}
|
|
7035
6180
|
}
|
|
7036
6181
|
if (isBackendSelf && api === "orpc") {
|
|
7037
6182
|
const rpcPath = frontend?.includes("next") || frontend?.includes("tanstack-start") ? "/api/rpc" : "/rpc";
|
|
7038
|
-
output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:${webPort}${rpcPath}/api-reference\n`;
|
|
6183
|
+
output += `${pc$1.cyan("•")} OpenAPI (Scalar UI): http://localhost:${webPort}${rpcPath}/api-reference\n`;
|
|
7039
6184
|
}
|
|
7040
|
-
if (addons?.includes("starlight")) output += `${pc.cyan("•")} Docs: http://localhost:4321\n`;
|
|
7041
|
-
if (addons?.includes("fumadocs")) output += `${pc.cyan("•")} Fumadocs: http://localhost:4000\n`;
|
|
7042
6185
|
}
|
|
7043
6186
|
if (nativeInstructions) output += `\n${nativeInstructions.trim()}\n`;
|
|
7044
6187
|
if (databaseInstructions) output += `\n${databaseInstructions.trim()}\n`;
|
|
@@ -7049,18 +6192,18 @@ async function displayPostInstallInstructions(config) {
|
|
|
7049
6192
|
if (vitePlusNativeHooksInstructions) output += `\n${vitePlusNativeHooksInstructions.trim()}\n`;
|
|
7050
6193
|
if (lintingInstructions) output += `\n${lintingInstructions.trim()}\n`;
|
|
7051
6194
|
if (pwaInstructions) output += `\n${pwaInstructions.trim()}\n`;
|
|
7052
|
-
if (
|
|
6195
|
+
if (playwrightInstructions) output += `\n${playwrightInstructions.trim()}\n`;
|
|
7053
6196
|
if (clerkInstructions) output += `\n${clerkInstructions.trim()}\n`;
|
|
7054
6197
|
if (betterAuthConvexInstructions) output += `\n${betterAuthConvexInstructions.trim()}\n`;
|
|
7055
6198
|
if (getMonitorInstructions) output += `\n${getMonitorInstructions.trim()}\n`;
|
|
6199
|
+
if (resendInstructions) output += `\n${resendInstructions.trim()}\n`;
|
|
6200
|
+
if (notifiqueInstructions) output += `\n${notifiqueInstructions.trim()}\n`;
|
|
6201
|
+
if (araraInstructions) output += `\n${araraInstructions.trim()}\n`;
|
|
7056
6202
|
if (alchemyDeployInstructions) output += `\n${alchemyDeployInstructions.trim()}\n`;
|
|
7057
6203
|
if (noOrmWarning) output += `\n${noOrmWarning.trim()}\n`;
|
|
7058
6204
|
if (bunWebNativeWarning) output += `\n${bunWebNativeWarning.trim()}\n`;
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
if (specialSponsorsSection) output += `\n${specialSponsorsSection.trim()}\n`;
|
|
7062
|
-
output += `\n${pc.bold("Like I dont know?")} Please consider giving us a star\n on GitHub:\n`;
|
|
7063
|
-
output += pc.cyan("https://github.com/albuquerquesz/kubo");
|
|
6205
|
+
output += `\n${pc$1.bold("Like kubojs?")} Please consider giving us a star\n on GitHub:\n`;
|
|
6206
|
+
output += pc$1.cyan("https://github.com/albuquerquesz/kubo");
|
|
7064
6207
|
cliConsola.box(output);
|
|
7065
6208
|
}
|
|
7066
6209
|
function getNativeInstructions(isConvex, isBackendSelf, frontend, runCmd) {
|
|
@@ -7069,23 +6212,27 @@ function getNativeInstructions(isConvex, isBackendSelf, frontend, runCmd) {
|
|
|
7069
6212
|
const exampleUrl = isConvex ? "https://example.convex.cloud" : isBackendSelf ? `http://<YOUR_LOCAL_IP>:${selfBackendPort}` : "http://<YOUR_LOCAL_IP>:3000";
|
|
7070
6213
|
const envFileName = ".env";
|
|
7071
6214
|
const ipNote = isConvex ? "your Convex deployment URL (find after running 'dev:setup')" : "your local IP address";
|
|
7072
|
-
let instructions = `${pc.yellow("NOTE:")} For Expo connectivity issues, update\n apps/native/${envFileName} with ${ipNote}:\n ${`${envVar}=${exampleUrl}`}\n`;
|
|
7073
|
-
if (isConvex) instructions += `\n${pc.yellow("IMPORTANT:")} When using local development with Convex and native apps,\n ensure you use your local IP address instead of localhost or 127.0.0.1\n for proper connectivity.\n`;
|
|
7074
|
-
if (frontend.includes("native-unistyles")) instructions += `\n${pc.yellow("NOTE:")} Unistyles requires a development build.\n cd apps/native and run ${runCmd} android or ${runCmd} ios\n`;
|
|
6215
|
+
let instructions = `${pc$1.yellow("NOTE:")} For Expo connectivity issues, update\n apps/native/${envFileName} with ${ipNote}:\n ${`${envVar}=${exampleUrl}`}\n`;
|
|
6216
|
+
if (isConvex) instructions += `\n${pc$1.yellow("IMPORTANT:")} When using local development with Convex and native apps,\n ensure you use your local IP address instead of localhost or 127.0.0.1\n for proper connectivity.\n`;
|
|
6217
|
+
if (frontend.includes("native-unistyles")) instructions += `\n${pc$1.yellow("NOTE:")} Unistyles requires a development build.\n cd apps/native and run ${runCmd} android or ${runCmd} ios\n`;
|
|
7075
6218
|
return instructions;
|
|
7076
6219
|
}
|
|
7077
6220
|
function getHuskyInstructions(runCmd) {
|
|
7078
|
-
return `${pc.bold("Git hooks with Husky:")}\n${pc.cyan("•")} Initialize hooks: ${`${runCmd} prepare`}\n`;
|
|
6221
|
+
return `${pc$1.bold("Git hooks with Husky:")}\n${pc$1.cyan("•")} Initialize hooks: ${`${runCmd} prepare`}\n`;
|
|
6222
|
+
}
|
|
6223
|
+
function getPlaywrightInstructions(runCmd, packageManager) {
|
|
6224
|
+
const browserInstall = packageManager === "bun" ? "bunx playwright install" : packageManager === "pnpm" ? "pnpm exec playwright install" : "npx playwright install";
|
|
6225
|
+
return `${pc$1.bold("End-to-end tests with Playwright:")}\n${pc$1.cyan("•")} Install browsers (if needed): ${browserInstall}\n${pc$1.cyan("•")} Run e2e tests: ${`${runCmd} test:e2e`}\n`;
|
|
7079
6226
|
}
|
|
7080
6227
|
function getLintingInstructions(runCmd) {
|
|
7081
|
-
return `${pc.bold("Linting and formatting:")}\n${pc.cyan("•")} Run checks: ${`${runCmd} check`}\n`;
|
|
6228
|
+
return `${pc$1.bold("Linting and formatting:")}\n${pc$1.cyan("•")} Run checks: ${`${runCmd} check`}\n`;
|
|
7082
6229
|
}
|
|
7083
6230
|
function getLefthookInstructions(packageManager) {
|
|
7084
6231
|
const cmd = packageManager === "npm" ? "npx" : packageManager;
|
|
7085
|
-
return `${pc.bold("Git hooks with Lefthook:")}\n${pc.cyan("•")} Install hooks: ${cmd} lefthook install\n`;
|
|
6232
|
+
return `${pc$1.bold("Git hooks with Lefthook:")}\n${pc$1.cyan("•")} Install hooks: ${cmd} lefthook install\n`;
|
|
7086
6233
|
}
|
|
7087
6234
|
function getVitePlusNativeHooksInstructions(runCmd) {
|
|
7088
|
-
return `${pc.bold("Vite+ native Git hooks:")}\n${pc.cyan("•")} Optional hook setup: ${`${runCmd} hooks:setup`}\n${pc.dim(" (runs vp config; hooks install into .vite-hooks and use vp staged)")}\n`;
|
|
6235
|
+
return `${pc$1.bold("Vite+ native Git hooks:")}\n${pc$1.cyan("•")} Optional hook setup: ${`${runCmd} hooks:setup`}\n${pc$1.dim(" (runs vp config; hooks install into .vite-hooks and use vp staged)")}\n`;
|
|
7089
6236
|
}
|
|
7090
6237
|
async function getDatabaseInstructions(database, orm, runCmd, _runtime, dbSetup, webDeploy, serverDeploy, backend) {
|
|
7091
6238
|
const instructions = [];
|
|
@@ -7098,56 +6245,63 @@ async function getDatabaseInstructions(database, orm, runCmd, _runtime, dbSetup,
|
|
|
7098
6245
|
}
|
|
7099
6246
|
}
|
|
7100
6247
|
if (isD1Alchemy) {
|
|
7101
|
-
if (orm === "drizzle") instructions.push(`${pc.cyan("•")} Generate migrations: ${`${runCmd} db:generate`}`);
|
|
6248
|
+
if (orm === "drizzle") instructions.push(`${pc$1.cyan("•")} Generate migrations: ${`${runCmd} db:generate`}`);
|
|
7102
6249
|
else if (orm === "prisma") {
|
|
7103
|
-
instructions.push(`${pc.cyan("•")} Generate Prisma client: ${`${runCmd} db:generate`}`);
|
|
7104
|
-
instructions.push(`${pc.cyan("•")} Apply migrations: ${`${runCmd} db:migrate`}`);
|
|
6250
|
+
instructions.push(`${pc$1.cyan("•")} Generate Prisma client: ${`${runCmd} db:generate`}`);
|
|
6251
|
+
instructions.push(`${pc$1.cyan("•")} Apply migrations: ${`${runCmd} db:migrate`}`);
|
|
7105
6252
|
}
|
|
7106
6253
|
}
|
|
7107
6254
|
if (dbSetup === "planetscale") {
|
|
7108
|
-
if (database === "mysql" && orm === "drizzle") instructions.push(`${pc.yellow("NOTE:")} Enable foreign key constraints in PlanetScale database settings`);
|
|
7109
|
-
if (database === "mysql" && orm === "prisma") instructions.push(`${pc.yellow("NOTE:")} How to handle Prisma migrations with PlanetScale:\n https://github.com/prisma/prisma/issues/7292`);
|
|
6255
|
+
if (database === "mysql" && orm === "drizzle") instructions.push(`${pc$1.yellow("NOTE:")} Enable foreign key constraints in PlanetScale database settings`);
|
|
6256
|
+
if (database === "mysql" && orm === "prisma") instructions.push(`${pc$1.yellow("NOTE:")} How to handle Prisma migrations with PlanetScale:\n https://github.com/prisma/prisma/issues/7292`);
|
|
7110
6257
|
}
|
|
7111
|
-
if (dbSetup === "turso" && orm === "prisma") instructions.push(`${pc.yellow("NOTE:")} Follow Turso's Prisma guide for migrations via the Turso CLI:\n https://docs.turso.tech/sdk/ts/orm/prisma`);
|
|
6258
|
+
if (dbSetup === "turso" && orm === "prisma") instructions.push(`${pc$1.yellow("NOTE:")} Follow Turso's Prisma guide for migrations via the Turso CLI:\n https://docs.turso.tech/sdk/ts/orm/prisma`);
|
|
7112
6259
|
if (orm === "prisma") {
|
|
7113
|
-
if (database === "mongodb" && dbSetup === "docker") instructions.push(`${pc.yellow("WARNING:")} Prisma + MongoDB + Docker combination\n may not work.`);
|
|
7114
|
-
if (dbSetup === "docker") instructions.push(`${pc.cyan("•")} Start docker container: ${`${runCmd} db:start`}`);
|
|
6260
|
+
if (database === "mongodb" && dbSetup === "docker") instructions.push(`${pc$1.yellow("WARNING:")} Prisma + MongoDB + Docker combination\n may not work.`);
|
|
6261
|
+
if (dbSetup === "docker") instructions.push(`${pc$1.cyan("•")} Start docker container: ${`${runCmd} db:start`}`);
|
|
7115
6262
|
if (!isD1Alchemy) {
|
|
7116
|
-
instructions.push(`${pc.cyan("•")} Generate Prisma Client: ${`${runCmd} db:generate`}`);
|
|
7117
|
-
instructions.push(`${pc.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
|
|
6263
|
+
instructions.push(`${pc$1.cyan("•")} Generate Prisma Client: ${`${runCmd} db:generate`}`);
|
|
6264
|
+
instructions.push(`${pc$1.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
|
|
7118
6265
|
}
|
|
7119
|
-
if (!isD1Alchemy) instructions.push(`${pc.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
|
|
6266
|
+
if (!isD1Alchemy) instructions.push(`${pc$1.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
|
|
7120
6267
|
} else if (orm === "drizzle") {
|
|
7121
|
-
if (dbSetup === "docker") instructions.push(`${pc.cyan("•")} Start docker container: ${`${runCmd} db:start`}`);
|
|
7122
|
-
if (!isD1Alchemy) instructions.push(`${pc.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
|
|
7123
|
-
if (!isD1Alchemy) instructions.push(`${pc.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
|
|
6268
|
+
if (dbSetup === "docker") instructions.push(`${pc$1.cyan("•")} Start docker container: ${`${runCmd} db:start`}`);
|
|
6269
|
+
if (!isD1Alchemy) instructions.push(`${pc$1.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
|
|
6270
|
+
if (!isD1Alchemy) instructions.push(`${pc$1.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
|
|
7124
6271
|
} else if (orm === "mongoose") {
|
|
7125
|
-
if (dbSetup === "docker") instructions.push(`${pc.cyan("•")} Start docker container: ${`${runCmd} db:start`}`);
|
|
7126
|
-
} else if (orm === "none") instructions.push(`${pc.yellow("NOTE:")} Manual database schema setup\n required.`);
|
|
7127
|
-
return instructions.length ? `${pc.bold("Database commands:")}\n${instructions.join("\n")}` : "";
|
|
6272
|
+
if (dbSetup === "docker") instructions.push(`${pc$1.cyan("•")} Start docker container: ${`${runCmd} db:start`}`);
|
|
6273
|
+
} else if (orm === "none") instructions.push(`${pc$1.yellow("NOTE:")} Manual database schema setup\n required.`);
|
|
6274
|
+
return instructions.length ? `${pc$1.bold("Database commands:")}\n${instructions.join("\n")}` : "";
|
|
7128
6275
|
}
|
|
7129
6276
|
function getTauriInstructions(runCmd, frontend) {
|
|
7130
6277
|
const staticBuildNote = getDesktopStaticBuildNote(frontend);
|
|
7131
|
-
return `\n${pc.bold("Desktop app with Tauri:")}\n${pc.cyan("•")} Start desktop app: ${`cd apps/web && ${runCmd} desktop:dev`}\n${pc.cyan("•")} Build desktop app: ${`cd apps/web && ${runCmd} desktop:build`}\n${pc.yellow("NOTE:")} Tauri requires Rust and platform-specific dependencies.\n See: https://v2.tauri.app/start/prerequisites/${staticBuildNote ? `\n${staticBuildNote}` : ""}`;
|
|
6278
|
+
return `\n${pc$1.bold("Desktop app with Tauri:")}\n${pc$1.cyan("•")} Start desktop app: ${`cd apps/web && ${runCmd} desktop:dev`}\n${pc$1.cyan("•")} Build desktop app: ${`cd apps/web && ${runCmd} desktop:build`}\n${pc$1.yellow("NOTE:")} Tauri requires Rust and platform-specific dependencies.\n See: https://v2.tauri.app/start/prerequisites/${staticBuildNote ? `\n${staticBuildNote}` : ""}`;
|
|
7132
6279
|
}
|
|
7133
6280
|
function getElectrobunInstructions(runCmd, frontend) {
|
|
7134
6281
|
const staticBuildNote = getDesktopStaticBuildNote(frontend);
|
|
7135
|
-
return `\n${pc.bold("Desktop app with Electrobun:")}\n${pc.cyan("•")} Start desktop app with HMR: ${`${runCmd} dev:desktop`}\n${pc.cyan("•")} Build stable desktop app (DMG/App): ${`${runCmd} build:desktop`}\n${pc.cyan("•")} Build canary desktop app: ${`${runCmd} build:desktop:canary`}\n${pc.yellow("NOTE:")} Electrobun wraps your web frontend in a desktop shell.\n See: https://framework.blackboard.sh/electrobun/${staticBuildNote ? `\n${staticBuildNote}` : ""}`;
|
|
6282
|
+
return `\n${pc$1.bold("Desktop app with Electrobun:")}\n${pc$1.cyan("•")} Start desktop app with HMR: ${`${runCmd} dev:desktop`}\n${pc$1.cyan("•")} Build stable desktop app (DMG/App): ${`${runCmd} build:desktop`}\n${pc$1.cyan("•")} Build canary desktop app: ${`${runCmd} build:desktop:canary`}\n${pc$1.yellow("NOTE:")} Electrobun wraps your web frontend in a desktop shell.\n See: https://framework.blackboard.sh/electrobun/${staticBuildNote ? `\n${staticBuildNote}` : ""}`;
|
|
7136
6283
|
}
|
|
7137
6284
|
function getPwaInstructions() {
|
|
7138
|
-
return `\n${pc.bold("PWA with React Router v7:")}\n${pc.yellow("NOTE:")} There is a known compatibility issue between VitePWA\n and React Router v7. See:\n https://github.com/vite-pwa/vite-plugin-pwa/issues/809`;
|
|
7139
|
-
}
|
|
7140
|
-
function getStarlightInstructions(runCmd) {
|
|
7141
|
-
return `\n${pc.bold("Documentation with Starlight:")}\n${pc.cyan("•")} Start docs site: ${`cd apps/docs && ${runCmd} dev`}\n${pc.cyan("•")} Build docs site: ${`cd apps/docs && ${runCmd} build`}`;
|
|
6285
|
+
return `\n${pc$1.bold("PWA with React Router v7:")}\n${pc$1.yellow("NOTE:")} There is a known compatibility issue between VitePWA\n and React Router v7. See:\n https://github.com/vite-pwa/vite-plugin-pwa/issues/809`;
|
|
7142
6286
|
}
|
|
7143
6287
|
function getNoOrmWarning() {
|
|
7144
|
-
return `\n${pc.yellow("WARNING:")} Database selected without an ORM. Features requiring\n database access (e.g., examples, auth) need manual setup.`;
|
|
6288
|
+
return `\n${pc$1.yellow("WARNING:")} Database selected without an ORM. Features requiring\n database access (e.g., examples, auth) need manual setup.`;
|
|
7145
6289
|
}
|
|
7146
6290
|
function getGetMonitorInstructions() {
|
|
7147
|
-
return `${pc.bold("GetMonitor observability:")}\n${pc.cyan("•")}
|
|
6291
|
+
return `${pc$1.bold("GetMonitor observability:")}\n${pc$1.cyan("•")} Keys are optional on first run — the app starts without them; capture stays idle until configured\n${pc$1.cyan("•")} When ready, set GETMONITOR_API_KEY (public gm_xxx) in the web and/or server .env\n${pc$1.cyan("•")} Ingestion host is fixed (http://ingest.getmonitor.io) — no apiHost env var\n${pc$1.cyan("•")} Optional: set GETMONITOR_AUTH_TOKEN for Next/Nuxt source-map upload on production builds\n${pc$1.cyan("•")} Browser/Node capture is automatic; React trees include GetMonitorErrorBoundary\n${pc$1.cyan("•")} Opt out next time with --disable-observability\n${pc$1.dim(" https://github.com/get-monitor/getmonitor-js")}`;
|
|
6292
|
+
}
|
|
6293
|
+
function getResendInstructions() {
|
|
6294
|
+
return `${pc$1.bold("Resend communication:")}\n${pc$1.cyan("•")} Keys are optional on first run — sendEmail throws only when called without RESEND_API_KEY\n${pc$1.cyan("•")} Set RESEND_API_KEY (and optional RESEND_FROM_EMAIL) in the server .env\n${pc$1.cyan("•")} Import sendEmail from packages/email (e.g. @project/email)\n${pc$1.cyan("•")} Replace onboarding@resend.dev with a verified domain sender for production\n${pc$1.dim(" https://resend.com/docs/send-with-nodejs")}`;
|
|
6295
|
+
}
|
|
6296
|
+
function getNotifiqueInstructions() {
|
|
6297
|
+
return `${pc$1.bold("Notifique communication:")}\n${pc$1.cyan("•")} Keys are optional on first run — helpers throw only when called without NOTIFIQUE_API_KEY\n${pc$1.cyan("•")} Set NOTIFIQUE_API_KEY (sk_live_… / sk_test_…) in the server .env\n${pc$1.cyan("•")} Import sendSms / sendWhatsAppText / sendEmail from packages/notifique\n${pc$1.cyan("•")} Auth is Bearer only — do not send x-workspace-id\n${pc$1.dim(" https://docs.notifique.dev/skill.md")}`;
|
|
6298
|
+
}
|
|
6299
|
+
function getAraraInstructions(backend) {
|
|
6300
|
+
const envPath = backend === "convex" ? "packages/backend/.env.local" : "the server .env";
|
|
6301
|
+
return `${pc$1.bold("AraraHQ communication:")}\n${pc$1.cyan("•")} Set ARARA_API_KEY in ${envPath}\n${pc$1.cyan("•")} Use @project/arara only from server code; the SDK does not run on Edge/Workers\n${pc$1.cyan("•")} Convex projects use the generated Node Action (api.arara.execute)\n${pc$1.dim(" https://docs.ararahq.com/sdks/node")}`;
|
|
7148
6302
|
}
|
|
7149
6303
|
function getBunWebNativeWarning() {
|
|
7150
|
-
return `\n${pc.yellow("WARNING:")} 'bun' might cause issues with web + native apps in a monorepo.\n Use 'pnpm' if problems arise.`;
|
|
6304
|
+
return `\n${pc$1.yellow("WARNING:")} 'bun' might cause issues with web + native apps in a monorepo.\n Use 'pnpm' if problems arise.`;
|
|
7151
6305
|
}
|
|
7152
6306
|
function getClerkQuickstartUrl(frontend) {
|
|
7153
6307
|
if (frontend.includes("next")) return "https://clerk.com/docs/nextjs/getting-started/quickstart";
|
|
@@ -7203,28 +6357,28 @@ function getClerkInstructionLines(frontend, backend, api) {
|
|
|
7203
6357
|
}
|
|
7204
6358
|
function getClerkInstructions(frontend, backend, api) {
|
|
7205
6359
|
return [
|
|
7206
|
-
`${pc.bold("Clerk Authentication Setup:")}`,
|
|
7207
|
-
`${pc.cyan("•")} Follow the guide: ${pc.underline(getClerkQuickstartUrl(frontend))}`,
|
|
7208
|
-
...getClerkInstructionLines(frontend, backend, api).map((line) => `${pc.cyan("•")} ${line}`)
|
|
6360
|
+
`${pc$1.bold("Clerk Authentication Setup:")}`,
|
|
6361
|
+
`${pc$1.cyan("•")} Follow the guide: ${pc$1.underline(getClerkQuickstartUrl(frontend))}`,
|
|
6362
|
+
...getClerkInstructionLines(frontend, backend, api).map((line) => `${pc$1.cyan("•")} ${line}`)
|
|
7209
6363
|
].join("\n");
|
|
7210
6364
|
}
|
|
7211
6365
|
function getBetterAuthConvexInstructions(hasWeb, webPort, packageManager, runCmd) {
|
|
7212
6366
|
const cmd = packageManager === "npm" ? "npx" : packageManager;
|
|
7213
|
-
return `${pc.bold("Better Auth + Convex Setup:")}\n${pc.cyan("•")} Configure the Convex deployment before setting env vars:\n${pc.white(` ${runCmd} dev:setup`)}\n${pc.cyan("•")} Set environment variables from ${pc.white("packages/backend")}:\n${pc.white(" cd packages/backend")}\n${pc.white(` ${cmd} convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)`)}\n` + (hasWeb ? `${pc.white(` ${cmd} convex env set SITE_URL http://localhost:${webPort}`)}\n` : "");
|
|
6367
|
+
return `${pc$1.bold("Better Auth + Convex Setup:")}\n${pc$1.cyan("•")} Configure the Convex deployment before setting env vars:\n${pc$1.white(` ${runCmd} dev:setup`)}\n${pc$1.cyan("•")} Set environment variables from ${pc$1.white("packages/backend")}:\n${pc$1.white(" cd packages/backend")}\n${pc$1.white(` ${cmd} convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)`)}\n` + (hasWeb ? `${pc$1.white(` ${cmd} convex env set SITE_URL http://localhost:${webPort}`)}\n` : "");
|
|
7214
6368
|
}
|
|
7215
6369
|
function getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy, backend) {
|
|
7216
6370
|
const instructions = [];
|
|
7217
6371
|
const isBackendSelf = backend === "self";
|
|
7218
6372
|
if (webDeploy === "cloudflare" && serverDeploy !== "cloudflare" && !isBackendSelf) {
|
|
7219
6373
|
const cfDeploy = serverDeploy === "vercel" ? "deploy:web" : "deploy";
|
|
7220
|
-
instructions.push(`${pc.bold("Deploy web with Cloudflare (Alchemy):")}\n${pc.cyan("•")} Dev: ${`${runCmd} dev`}\n${pc.cyan("•")} Deploy: ${`${runCmd} ${cfDeploy}`}\n${pc.cyan("•")} Destroy: ${`${runCmd} destroy`}`);
|
|
6374
|
+
instructions.push(`${pc$1.bold("Deploy web with Cloudflare (Alchemy):")}\n${pc$1.cyan("•")} Dev: ${`${runCmd} dev`}\n${pc$1.cyan("•")} Deploy: ${`${runCmd} ${cfDeploy}`}\n${pc$1.cyan("•")} Destroy: ${`${runCmd} destroy`}`);
|
|
7221
6375
|
} else if (serverDeploy === "cloudflare" && webDeploy !== "cloudflare" && !isBackendSelf) {
|
|
7222
6376
|
const cfDeploy = webDeploy === "vercel" ? "deploy:server" : "deploy";
|
|
7223
|
-
instructions.push(`${pc.bold("Deploy server with Cloudflare (Alchemy):")}\n${pc.cyan("•")} Dev: ${`${runCmd} dev`}\n${pc.cyan("•")} Deploy: ${`${runCmd} ${cfDeploy}`}\n${pc.cyan("•")} Destroy: ${`${runCmd} destroy`}`);
|
|
7224
|
-
} else if (webDeploy === "cloudflare" && (serverDeploy === "cloudflare" || isBackendSelf)) instructions.push(`${pc.bold("Deploy with Cloudflare (Alchemy):")}\n${pc.cyan("•")} Dev: ${`${runCmd} dev`}\n${pc.cyan("•")} Deploy: ${`${runCmd} deploy`}\n${pc.cyan("•")} Destroy: ${`${runCmd} destroy`}`);
|
|
6377
|
+
instructions.push(`${pc$1.bold("Deploy server with Cloudflare (Alchemy):")}\n${pc$1.cyan("•")} Dev: ${`${runCmd} dev`}\n${pc$1.cyan("•")} Deploy: ${`${runCmd} ${cfDeploy}`}\n${pc$1.cyan("•")} Destroy: ${`${runCmd} destroy`}`);
|
|
6378
|
+
} else if (webDeploy === "cloudflare" && (serverDeploy === "cloudflare" || isBackendSelf)) instructions.push(`${pc$1.bold("Deploy with Cloudflare (Alchemy):")}\n${pc$1.cyan("•")} Dev: ${`${runCmd} dev`}\n${pc$1.cyan("•")} Deploy: ${`${runCmd} deploy`}\n${pc$1.cyan("•")} Destroy: ${`${runCmd} destroy`}`);
|
|
7225
6379
|
if (webDeploy === "docker" || serverDeploy === "docker") {
|
|
7226
6380
|
const dockerTargets = webDeploy === "docker" && serverDeploy === "docker" ? "web + server" : webDeploy === "docker" ? "web" : "server";
|
|
7227
|
-
instructions.push(`${pc.bold(`Deploy ${dockerTargets} with Docker Compose:`)}\n${pc.cyan("•")} Start: ${`${runCmd} docker:up`}\n${pc.cyan("•")} Logs: ${`${runCmd} docker:logs`}\n${pc.cyan("•")} Stop: ${`${runCmd} docker:down`}\n${pc.cyan("•")} Config: docker-compose.yml`);
|
|
6381
|
+
instructions.push(`${pc$1.bold(`Deploy ${dockerTargets} with Docker Compose:`)}\n${pc$1.cyan("•")} Start: ${`${runCmd} docker:up`}\n${pc$1.cyan("•")} Logs: ${`${runCmd} docker:logs`}\n${pc$1.cyan("•")} Stop: ${`${runCmd} docker:down`}\n${pc$1.cyan("•")} Config: docker-compose.yml`);
|
|
7228
6382
|
}
|
|
7229
6383
|
if (webDeploy === "vercel" || serverDeploy === "vercel") {
|
|
7230
6384
|
const mixedWithCloudflare = webDeploy !== "none" && serverDeploy !== "none" && webDeploy !== serverDeploy;
|
|
@@ -7232,14 +6386,14 @@ function getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy, backend)
|
|
|
7232
6386
|
const vercelEnvScript = "env:production";
|
|
7233
6387
|
const vercelDeployScript = mixedWithCloudflare ? webDeploy === "vercel" ? "deploy:web:prod" : "deploy:server:prod" : "deploy:prod";
|
|
7234
6388
|
const vercelTargets = webDeploy === "vercel" && (serverDeploy === "vercel" || isBackendSelf) ? "web + server" : webDeploy === "vercel" ? "web" : "server";
|
|
7235
|
-
instructions.push(`${pc.bold(`Deploy ${vercelTargets} with Vercel Services:`)}\n${pc.cyan("•")} Link project: ${`${runCmd} ${vercelSetupScript}`}\n${pc.cyan("•")} Sync env (before first deploy): ${`${runCmd} ${vercelEnvScript}`}\n${pc.cyan("•")} Deploy: ${`${runCmd} ${vercelDeployScript}`}\n${pc.cyan("•")} Guide: https://www.kubojs.dev/docs/guides/vercel`);
|
|
6389
|
+
instructions.push(`${pc$1.bold(`Deploy ${vercelTargets} with Vercel Services:`)}\n${pc$1.cyan("•")} Link project: ${`${runCmd} ${vercelSetupScript}`}\n${pc$1.cyan("•")} Sync env (before first deploy): ${`${runCmd} ${vercelEnvScript}`}\n${pc$1.cyan("•")} Deploy: ${`${runCmd} ${vercelDeployScript}`}\n${pc$1.cyan("•")} Guide: https://www.kubojs.dev/docs/guides/vercel`);
|
|
7236
6390
|
}
|
|
7237
6391
|
if (webDeploy === "guaracloud" || serverDeploy === "guaracloud") {
|
|
7238
6392
|
const deployScript = webDeploy !== "none" && serverDeploy !== "none" && webDeploy !== serverDeploy || webDeploy === "guaracloud" && serverDeploy === "guaracloud" ? `deploy:${webDeploy === "guaracloud" ? "web" : "server"}` : "deploy";
|
|
7239
6393
|
const linkScript = `${deployScript}:link`;
|
|
7240
6394
|
const logsScript = `${deployScript}:logs`;
|
|
7241
6395
|
const guaraTargets = webDeploy === "guaracloud" && (serverDeploy === "guaracloud" || isBackendSelf) ? "web + server" : webDeploy === "guaracloud" ? "web" : "server";
|
|
7242
|
-
instructions.push(`${pc.bold(`Deploy ${guaraTargets} with Guara Cloud:`)}\n${pc.cyan("•")} Login: ${`${runCmd} deploy:login`}\n${pc.cyan("•")} Link app directory: ${`${runCmd} ${linkScript}`}\n${pc.cyan("•")} Deploy: ${`${runCmd} ${deployScript}`}\n${pc.cyan("•")} Logs: ${`${runCmd} ${logsScript}`}\n${pc.cyan("•")} Docs: https://guaracloud.com/docs/getting-started/introduction/`);
|
|
6396
|
+
instructions.push(`${pc$1.bold(`Deploy ${guaraTargets} with Guara Cloud:`)}\n${pc$1.cyan("•")} Login: ${`${runCmd} deploy:login`}\n${pc$1.cyan("•")} Link app directory: ${`${runCmd} ${linkScript}`}\n${pc$1.cyan("•")} Deploy: ${`${runCmd} ${deployScript}`}\n${pc$1.cyan("•")} Logs: ${`${runCmd} ${logsScript}`}\n${pc$1.cyan("•")} Docs: https://guaracloud.com/docs/getting-started/introduction/`);
|
|
7243
6397
|
}
|
|
7244
6398
|
return instructions.length ? `\n${instructions.join("\n")}` : "";
|
|
7245
6399
|
}
|
|
@@ -7275,6 +6429,7 @@ async function createProject(options, cliInput = {}) {
|
|
|
7275
6429
|
message: e.message,
|
|
7276
6430
|
cause: e
|
|
7277
6431
|
}))));
|
|
6432
|
+
yield* Result.await(generateRouteTreeIfNeeded(projectDir, options));
|
|
7278
6433
|
yield* Result.await(setPackageManagerVersion(projectDir, options.packageManager));
|
|
7279
6434
|
if (!isConvex && options.database !== "none") yield* Result.await(Result.tryPromise({
|
|
7280
6435
|
try: () => setupDatabase(options, cliInput),
|
|
@@ -7284,7 +6439,9 @@ async function createProject(options, cliInput = {}) {
|
|
|
7284
6439
|
cause: e
|
|
7285
6440
|
})
|
|
7286
6441
|
}));
|
|
7287
|
-
|
|
6442
|
+
const hasAddons = options.addons.some((addon) => addon !== "none");
|
|
6443
|
+
const hasTesting = options.testing.some((tool) => tool !== "none");
|
|
6444
|
+
if (hasAddons) yield* Result.await(Result.tryPromise({
|
|
7288
6445
|
try: () => setupAddons(options),
|
|
7289
6446
|
catch: (e) => new ProjectCreationError({
|
|
7290
6447
|
phase: "addons-setup",
|
|
@@ -7292,12 +6449,23 @@ async function createProject(options, cliInput = {}) {
|
|
|
7292
6449
|
cause: e
|
|
7293
6450
|
})
|
|
7294
6451
|
}));
|
|
7295
|
-
yield* Result.await(
|
|
7296
|
-
|
|
7297
|
-
|
|
7298
|
-
|
|
7299
|
-
|
|
6452
|
+
if (hasTesting) yield* Result.await(Result.tryPromise({
|
|
6453
|
+
try: () => setupTesting(projectDir, options.testing),
|
|
6454
|
+
catch: (e) => new ProjectCreationError({
|
|
6455
|
+
phase: "testing-setup",
|
|
6456
|
+
message: String(e),
|
|
6457
|
+
cause: e
|
|
6458
|
+
})
|
|
7300
6459
|
}));
|
|
6460
|
+
yield* Result.await(formatProject(projectDir, { addons: options.addons }));
|
|
6461
|
+
if (!isSilent()) log.success("Project template successfully scaffolded!");
|
|
6462
|
+
if (options.install) {
|
|
6463
|
+
yield* Result.await(installDependencies({
|
|
6464
|
+
projectDir,
|
|
6465
|
+
packageManager: options.packageManager
|
|
6466
|
+
}));
|
|
6467
|
+
if (options.testing.includes("playwright")) await installPlaywrightBrowsers(projectDir, options.packageManager);
|
|
6468
|
+
}
|
|
7301
6469
|
yield* Result.await(initializeGit(projectDir, options.git));
|
|
7302
6470
|
if (!isSilent()) await displayPostInstallInstructions({
|
|
7303
6471
|
...options,
|
|
@@ -7306,21 +6474,40 @@ async function createProject(options, cliInput = {}) {
|
|
|
7306
6474
|
return Result.ok(projectDir);
|
|
7307
6475
|
});
|
|
7308
6476
|
}
|
|
7309
|
-
|
|
7310
|
-
|
|
7311
|
-
|
|
6477
|
+
/**
|
|
6478
|
+
* Corepack/Turbo require `name@x.y.z`. Placeholders like `@latest` are invalid.
|
|
6479
|
+
*/
|
|
6480
|
+
function isValidPackageManagerField(value) {
|
|
6481
|
+
return /^(bun|npm|pnpm|yarn)@\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(value);
|
|
6482
|
+
}
|
|
6483
|
+
function normalizePackageManagerVersion(version) {
|
|
6484
|
+
const cleaned = version.replace(/^v/i, "").trim();
|
|
6485
|
+
if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(cleaned)) return null;
|
|
6486
|
+
return cleaned;
|
|
6487
|
+
}
|
|
6488
|
+
async function resolveInstalledPackageManagerVersion(packageManager) {
|
|
6489
|
+
if (packageManager === "bun" && typeof process.versions.bun === "string" && process.versions.bun.length > 0) return normalizePackageManagerVersion(process.versions.bun);
|
|
7312
6490
|
const versionResult = await Result.tryPromise({
|
|
7313
6491
|
try: async () => {
|
|
7314
6492
|
const { stdout } = await $({ cwd: os.tmpdir() })`${packageManager} -v`;
|
|
7315
|
-
return stdout
|
|
6493
|
+
return normalizePackageManagerVersion(stdout);
|
|
7316
6494
|
},
|
|
7317
6495
|
catch: () => null
|
|
7318
6496
|
});
|
|
6497
|
+
return versionResult.isOk() ? versionResult.value : null;
|
|
6498
|
+
}
|
|
6499
|
+
async function setPackageManagerVersion(projectDir, packageManager) {
|
|
6500
|
+
const pkgJsonPath = path.join(projectDir, "package.json");
|
|
6501
|
+
if (!await fs.pathExists(pkgJsonPath)) return Result.ok(void 0);
|
|
6502
|
+
const version = await resolveInstalledPackageManagerVersion(packageManager);
|
|
7319
6503
|
return Result.tryPromise({
|
|
7320
6504
|
try: async () => {
|
|
7321
6505
|
const pkgJson = await fs.readJson(pkgJsonPath);
|
|
7322
|
-
if (
|
|
7323
|
-
|
|
6506
|
+
if (version) {
|
|
6507
|
+
const packageManagerField = `${packageManager}@${version}`;
|
|
6508
|
+
if (isValidPackageManagerField(packageManagerField)) pkgJson.packageManager = packageManagerField;
|
|
6509
|
+
else delete pkgJson.packageManager;
|
|
6510
|
+
} else delete pkgJson.packageManager;
|
|
7324
6511
|
await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
|
7325
6512
|
},
|
|
7326
6513
|
catch: (e) => new ProjectCreationError({
|
|
@@ -7349,9 +6536,11 @@ function createEmptyResult(timeScaffolded, elapsedTimeMs, error) {
|
|
|
7349
6536
|
frontend: [],
|
|
7350
6537
|
addons: [],
|
|
7351
6538
|
examples: [],
|
|
6539
|
+
testing: [],
|
|
7352
6540
|
auth: "none",
|
|
7353
|
-
payments:
|
|
7354
|
-
observability:
|
|
6541
|
+
payments: [],
|
|
6542
|
+
observability: [],
|
|
6543
|
+
communication: "none",
|
|
7355
6544
|
git: false,
|
|
7356
6545
|
packageManager: "npm",
|
|
7357
6546
|
install: false,
|
|
@@ -7389,7 +6578,7 @@ async function createProjectHandler(input, options = {}) {
|
|
|
7389
6578
|
async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
|
|
7390
6579
|
return Result.gen(async function* () {
|
|
7391
6580
|
if (!isSilent() && input.renderTitle !== false) renderTitle();
|
|
7392
|
-
if (!isSilent()) intro(
|
|
6581
|
+
if (!isSilent()) intro(cliColors.bright("Creating a new kubojs project"));
|
|
7393
6582
|
if (!isSilent() && input.yolo) cliConsola.fatal("YOLO mode enabled - skipping checks. Things may break!");
|
|
7394
6583
|
let currentPathInput;
|
|
7395
6584
|
if (isSilent()) currentPathInput = yield* Result.await(resolveProjectNameForSilent(input));
|
|
@@ -7451,7 +6640,7 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
|
|
|
7451
6640
|
const templateName = input.template.toUpperCase();
|
|
7452
6641
|
const templateDescription = getTemplateDescription(input.template);
|
|
7453
6642
|
if (!isSilent()) {
|
|
7454
|
-
log.message(pc.bold(
|
|
6643
|
+
log.message(pc.bold(cliColors.bright(`Using template: ${pc.white(templateName)}`)));
|
|
7455
6644
|
log.message(pc.dim(` ${templateDescription}`));
|
|
7456
6645
|
}
|
|
7457
6646
|
const userOverrides = {};
|
|
@@ -7523,6 +6712,8 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
|
|
|
7523
6712
|
if (!input.yolo) {
|
|
7524
6713
|
const addonsValidationResult = validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth, config.backend, config.runtime);
|
|
7525
6714
|
if (addonsValidationResult.isErr()) return Result.err(new CLIError({ message: addonsValidationResult.error.message }));
|
|
6715
|
+
const testingValidationResult = validateTestingAgainstFrontends(config.testing, config.frontend);
|
|
6716
|
+
if (testingValidationResult.isErr()) return Result.err(new CLIError({ message: testingValidationResult.error.message }));
|
|
7526
6717
|
}
|
|
7527
6718
|
const reproducibleCommand = generateReproducibleCommand(config);
|
|
7528
6719
|
if (input.dryRun) {
|
|
@@ -7532,7 +6723,7 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
|
|
|
7532
6723
|
log.success(pc.green("Dry run validation passed. No files were written."));
|
|
7533
6724
|
log.message(pc.dim(`Target directory: ${finalResolvedPath}`));
|
|
7534
6725
|
log.message(pc.dim(`Run without --dry-run to create the project.`));
|
|
7535
|
-
outro(
|
|
6726
|
+
outro(cliColors.signal("Dry run complete."));
|
|
7536
6727
|
}
|
|
7537
6728
|
return Result.ok({
|
|
7538
6729
|
success: true,
|
|
@@ -7548,14 +6739,14 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
|
|
|
7548
6739
|
manualDb: cliInput.manualDb ?? input.manualDb,
|
|
7549
6740
|
dbSetupOptions: effectiveDbSetupOptions
|
|
7550
6741
|
}));
|
|
7551
|
-
if (!isSilent()) log.success(
|
|
6742
|
+
if (!isSilent()) log.success(cliColors.bright(`You can reproduce this setup with the following command:\n${reproducibleCommand}`));
|
|
7552
6743
|
await trackProjectCreation(config, input.disableAnalytics);
|
|
7553
6744
|
const historyResult = await addToHistory(config, reproducibleCommand);
|
|
7554
6745
|
if (historyResult.isErr() && !isSilent()) log.warn(pc.yellow(historyResult.error.message));
|
|
7555
6746
|
const elapsedTimeMs = Date.now() - startTime;
|
|
7556
6747
|
if (!isSilent()) {
|
|
7557
6748
|
const elapsedTimeInSeconds = (elapsedTimeMs / 1e3).toFixed(2);
|
|
7558
|
-
outro(
|
|
6749
|
+
outro(cliColors.signal(`Project created successfully in ${pc.bold(elapsedTimeInSeconds)} seconds!`));
|
|
7559
6750
|
}
|
|
7560
6751
|
return Result.ok({
|
|
7561
6752
|
success: true,
|
|
@@ -7655,12 +6846,14 @@ const SchemaNameSchema = z.enum([
|
|
|
7655
6846
|
"frontend",
|
|
7656
6847
|
"addons",
|
|
7657
6848
|
"examples",
|
|
6849
|
+
"testing",
|
|
7658
6850
|
"packageManager",
|
|
7659
6851
|
"databaseSetup",
|
|
7660
6852
|
"api",
|
|
7661
6853
|
"auth",
|
|
7662
6854
|
"payments",
|
|
7663
6855
|
"observability",
|
|
6856
|
+
"communication",
|
|
7664
6857
|
"webDeploy",
|
|
7665
6858
|
"serverDeploy",
|
|
7666
6859
|
"directoryConflict",
|
|
@@ -7670,8 +6863,8 @@ const SchemaNameSchema = z.enum([
|
|
|
7670
6863
|
"createInput",
|
|
7671
6864
|
"addInput",
|
|
7672
6865
|
"projectConfig",
|
|
7673
|
-
"
|
|
7674
|
-
"
|
|
6866
|
+
"kubojsConfig",
|
|
6867
|
+
"kubojsConfigFile",
|
|
7675
6868
|
"initResult"
|
|
7676
6869
|
]).default("all");
|
|
7677
6870
|
const t = initTRPC.meta().create();
|
|
@@ -7693,7 +6886,7 @@ function getSchemaResult(name) {
|
|
|
7693
6886
|
}
|
|
7694
6887
|
const router = t.router({
|
|
7695
6888
|
create: t.procedure.meta({
|
|
7696
|
-
description: "Create a new
|
|
6889
|
+
description: "Create a new kubojs project",
|
|
7697
6890
|
default: true,
|
|
7698
6891
|
negateBooleans: true
|
|
7699
6892
|
}).input(z.tuple([types_exports.ProjectNameSchema.optional(), z.object({
|
|
@@ -7707,9 +6900,12 @@ const router = t.router({
|
|
|
7707
6900
|
auth: types_exports.AuthSchema.optional(),
|
|
7708
6901
|
payments: types_exports.PaymentsSchema.optional(),
|
|
7709
6902
|
observability: types_exports.ObservabilitySchema.optional(),
|
|
6903
|
+
disableObservability: z.boolean().optional().describe("Disable all observability providers"),
|
|
6904
|
+
communication: types_exports.CommunicationSchema.optional(),
|
|
7710
6905
|
frontend: z.array(types_exports.FrontendSchema).optional(),
|
|
7711
6906
|
addons: z.array(types_exports.AddonsSchema).optional(),
|
|
7712
6907
|
examples: z.array(types_exports.ExamplesSchema).optional(),
|
|
6908
|
+
testing: z.array(types_exports.TestingSchema).optional(),
|
|
7713
6909
|
git: z.boolean().optional(),
|
|
7714
6910
|
packageManager: types_exports.PackageManagerSchema.optional(),
|
|
7715
6911
|
install: z.boolean().optional(),
|
|
@@ -7742,11 +6938,11 @@ const router = t.router({
|
|
|
7742
6938
|
return result;
|
|
7743
6939
|
}),
|
|
7744
6940
|
schema: t.procedure.meta({ description: "Show runtime CLI and input schemas as JSON" }).input(z.object({ name: SchemaNameSchema.describe("Schema name to inspect") })).query(({ input }) => getSchemaResult(input.name)),
|
|
7745
|
-
|
|
7746
|
-
docs: t.procedure.meta({ description: "Open I dont know documentation" }).mutation(() => openDocsCommand()),
|
|
6941
|
+
docs: t.procedure.meta({ description: "Open kubojs documentation" }).mutation(() => openDocsCommand()),
|
|
7747
6942
|
builder: t.procedure.meta({ description: "Open the web-based stack builder" }).mutation(() => openBuilderCommand()),
|
|
7748
|
-
add: t.procedure.meta({ description: "Add addons to an existing
|
|
6943
|
+
add: t.procedure.meta({ description: "Add addons to an existing kubojs project" }).input(z.object({
|
|
7749
6944
|
addons: z.array(types_exports.AddonsSchema).optional().describe("Addons to add"),
|
|
6945
|
+
testing: z.array(types_exports.TestingSchema).optional().describe("Testing tools to add"),
|
|
7750
6946
|
install: z.boolean().optional().default(false).describe("Install dependencies after adding"),
|
|
7751
6947
|
packageManager: types_exports.PackageManagerSchema.optional().describe("Package manager to use"),
|
|
7752
6948
|
projectDir: z.string().optional().describe("Project directory (defaults to current)")
|
|
@@ -7778,7 +6974,7 @@ function createBtsCli() {
|
|
|
7778
6974
|
});
|
|
7779
6975
|
}
|
|
7780
6976
|
/**
|
|
7781
|
-
* Programmatic API to create a new
|
|
6977
|
+
* Programmatic API to create a new kubojs project.
|
|
7782
6978
|
* Returns a Result type - no console output, no interactive prompts.
|
|
7783
6979
|
*
|
|
7784
6980
|
* @example
|
|
@@ -7829,9 +7025,6 @@ async function create(projectName, options) {
|
|
|
7829
7025
|
}
|
|
7830
7026
|
});
|
|
7831
7027
|
}
|
|
7832
|
-
async function sponsors() {
|
|
7833
|
-
return showSponsorsCommand();
|
|
7834
|
-
}
|
|
7835
7028
|
async function docs() {
|
|
7836
7029
|
return openDocsCommand();
|
|
7837
7030
|
}
|
|
@@ -7862,6 +7055,8 @@ async function builder() {
|
|
|
7862
7055
|
* ```
|
|
7863
7056
|
*/
|
|
7864
7057
|
async function createVirtual(options) {
|
|
7058
|
+
const rawPayments = options.payments;
|
|
7059
|
+
const rawObservability = options.observability;
|
|
7865
7060
|
const config = {
|
|
7866
7061
|
projectName: options.projectName || "my-project",
|
|
7867
7062
|
projectDir: "/virtual",
|
|
@@ -7875,9 +7070,11 @@ async function createVirtual(options) {
|
|
|
7875
7070
|
frontend: options.frontend || ["tanstack-router"],
|
|
7876
7071
|
addons: options.addons || [],
|
|
7877
7072
|
examples: options.examples || [],
|
|
7073
|
+
testing: options.testing || [],
|
|
7878
7074
|
auth: options.auth || "none",
|
|
7879
|
-
payments:
|
|
7880
|
-
observability: options.
|
|
7075
|
+
payments: rawPayments === "none" ? [] : Array.isArray(rawPayments) ? rawPayments : rawPayments ? [rawPayments] : [],
|
|
7076
|
+
observability: options.disableObservability ? [] : rawObservability === "none" ? [] : Array.isArray(rawObservability) ? rawObservability : rawObservability ? [rawObservability] : [],
|
|
7077
|
+
communication: options.communication || "none",
|
|
7881
7078
|
git: options.git ?? false,
|
|
7882
7079
|
packageManager: options.packageManager || "bun",
|
|
7883
7080
|
install: false,
|
|
@@ -7894,10 +7091,13 @@ async function createVirtual(options) {
|
|
|
7894
7091
|
"frontend",
|
|
7895
7092
|
"addons",
|
|
7896
7093
|
"examples",
|
|
7094
|
+
"testing",
|
|
7897
7095
|
"auth",
|
|
7898
7096
|
"dbSetup",
|
|
7899
7097
|
"payments",
|
|
7900
7098
|
"observability",
|
|
7099
|
+
"disableObservability",
|
|
7100
|
+
"communication",
|
|
7901
7101
|
"api",
|
|
7902
7102
|
"webDeploy",
|
|
7903
7103
|
"serverDeploy"
|
|
@@ -7913,7 +7113,7 @@ async function createVirtual(options) {
|
|
|
7913
7113
|
});
|
|
7914
7114
|
}
|
|
7915
7115
|
/**
|
|
7916
|
-
* Programmatic API to add addons to an existing
|
|
7116
|
+
* Programmatic API to add addons to an existing kubojs project.
|
|
7917
7117
|
*
|
|
7918
7118
|
* @example
|
|
7919
7119
|
* ```typescript
|
|
@@ -7933,4 +7133,4 @@ async function add(options = {}) {
|
|
|
7933
7133
|
return addHandler(options, { silent: true });
|
|
7934
7134
|
}
|
|
7935
7135
|
//#endregion
|
|
7936
|
-
export {
|
|
7136
|
+
export { UserCancelledError as C, ProjectCreationError as S, getLatestCLIVersion as _, TEMPLATE_COUNT as a, DatabaseSetupError as b, builder as c, createVirtual as d, docs as f, types_exports as g, router as h, SchemaNameSchema as i, create as l, getSchemaResult as m, GeneratorError$1 as n, VirtualFileSystem$1 as o, generate$1 as p, Result$1 as r, add as s, EMBEDDED_TEMPLATES$1 as t, createBtsCli as u, CLIError as v, ValidationError as w, DirectoryConflictError as x, CompatibilityError as y };
|