create-better-t-stack 3.36.5 → 3.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.mts +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +37 -46
- package/dist/index.mjs +1 -1
- package/dist/{src-DpdI3cqy.mjs → src-Cbu9a6bq.mjs} +1242 -466
- package/package.json +16 -16
- /package/dist/{chunk-BtN16TXe.mjs → rolldown-runtime-yhw22V8Z.mjs} +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as __reExport, t as __exportAll } from "./
|
|
2
|
+
import { n as __reExport, t as __exportAll } from "./rolldown-runtime-yhw22V8Z.mjs";
|
|
3
3
|
import { getAllJsonSchemas } from "@better-t-stack/types/json-schema";
|
|
4
4
|
import { initTRPC } from "@trpc/server";
|
|
5
5
|
import { Result, Result as Result$1, TaggedError } from "better-result";
|
|
6
6
|
import { createCli } from "trpc-cli";
|
|
7
7
|
import z from "zod";
|
|
8
|
-
import { cancel, confirm, intro, isCancel, log, outro, select, spinner, text } from "@clack/prompts";
|
|
8
|
+
import { box, cancel, confirm, intro, isCancel, limitOptions, log, outro, select, spinner, text } from "@clack/prompts";
|
|
9
9
|
import pc from "picocolors";
|
|
10
10
|
import path from "node:path";
|
|
11
11
|
import envPaths from "env-paths";
|
|
@@ -22,6 +22,137 @@ import { ConfirmPrompt, GroupMultiSelectPrompt, MultiSelectPrompt, SelectPrompt,
|
|
|
22
22
|
import { applyEdits, modify, parse } from "jsonc-parser";
|
|
23
23
|
import os from "node:os";
|
|
24
24
|
import { format } from "oxfmt";
|
|
25
|
+
//#region src/utils/display-config.ts
|
|
26
|
+
const VALUE_LABELS = {
|
|
27
|
+
none: "None",
|
|
28
|
+
"tanstack-router": "TanStack Router",
|
|
29
|
+
"react-router": "React Router",
|
|
30
|
+
"tanstack-start": "TanStack Start",
|
|
31
|
+
next: "Next.js",
|
|
32
|
+
nuxt: "Nuxt",
|
|
33
|
+
svelte: "SvelteKit",
|
|
34
|
+
solid: "SolidStart",
|
|
35
|
+
astro: "Astro",
|
|
36
|
+
"native-bare": "Expo (bare)",
|
|
37
|
+
"native-uniwind": "Expo + Uniwind",
|
|
38
|
+
"native-unistyles": "Expo + Unistyles",
|
|
39
|
+
hono: "Hono",
|
|
40
|
+
express: "Express",
|
|
41
|
+
fastify: "Fastify",
|
|
42
|
+
elysia: "Elysia",
|
|
43
|
+
convex: "Convex",
|
|
44
|
+
self: "Fullstack framework",
|
|
45
|
+
bun: "Bun",
|
|
46
|
+
node: "Node.js",
|
|
47
|
+
workers: "Cloudflare Workers",
|
|
48
|
+
trpc: "tRPC",
|
|
49
|
+
orpc: "oRPC",
|
|
50
|
+
sqlite: "SQLite",
|
|
51
|
+
postgres: "PostgreSQL",
|
|
52
|
+
mysql: "MySQL",
|
|
53
|
+
mongodb: "MongoDB",
|
|
54
|
+
drizzle: "Drizzle",
|
|
55
|
+
prisma: "Prisma",
|
|
56
|
+
mongoose: "Mongoose",
|
|
57
|
+
"better-auth": "Better Auth",
|
|
58
|
+
clerk: "Clerk",
|
|
59
|
+
polar: "Polar",
|
|
60
|
+
pwa: "PWA",
|
|
61
|
+
tauri: "Tauri",
|
|
62
|
+
electrobun: "Electrobun",
|
|
63
|
+
biome: "Biome",
|
|
64
|
+
oxlint: "Oxlint + Oxfmt",
|
|
65
|
+
ultracite: "Ultracite",
|
|
66
|
+
lefthook: "Lefthook",
|
|
67
|
+
husky: "Husky",
|
|
68
|
+
turborepo: "Turborepo",
|
|
69
|
+
nx: "Nx",
|
|
70
|
+
"vite-plus": "Vite+",
|
|
71
|
+
starlight: "Starlight",
|
|
72
|
+
fumadocs: "Fumadocs",
|
|
73
|
+
opentui: "OpenTUI",
|
|
74
|
+
wxt: "WXT",
|
|
75
|
+
skills: "Agent skills",
|
|
76
|
+
mcp: "MCP servers",
|
|
77
|
+
evlog: "evlog",
|
|
78
|
+
todo: "Todo app",
|
|
79
|
+
ai: "AI chat",
|
|
80
|
+
turso: "Turso",
|
|
81
|
+
neon: "Neon",
|
|
82
|
+
planetscale: "PlanetScale",
|
|
83
|
+
supabase: "Supabase",
|
|
84
|
+
"prisma-postgres": "Prisma Postgres",
|
|
85
|
+
"mongodb-atlas": "MongoDB Atlas",
|
|
86
|
+
d1: "Cloudflare D1",
|
|
87
|
+
docker: "Docker",
|
|
88
|
+
cloudflare: "Cloudflare",
|
|
89
|
+
vercel: "Vercel",
|
|
90
|
+
npm: "npm",
|
|
91
|
+
pnpm: "pnpm"
|
|
92
|
+
};
|
|
93
|
+
function formatConfigValue(value) {
|
|
94
|
+
if (typeof value === "boolean") return value ? "Yes" : "No";
|
|
95
|
+
if (Array.isArray(value)) return value.length > 0 ? value.map(formatConfigValue).join(", ") : "None";
|
|
96
|
+
const text = String(value);
|
|
97
|
+
return VALUE_LABELS[text] ?? text.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
98
|
+
}
|
|
99
|
+
function section(title, entries) {
|
|
100
|
+
const rows = entries.filter(([, value]) => value !== void 0).map(([label, value, format]) => ({
|
|
101
|
+
label,
|
|
102
|
+
value: format === "raw" ? String(value) : formatConfigValue(value)
|
|
103
|
+
}));
|
|
104
|
+
return rows.length > 0 ? {
|
|
105
|
+
title,
|
|
106
|
+
rows
|
|
107
|
+
} : void 0;
|
|
108
|
+
}
|
|
109
|
+
function getConfigSections(config) {
|
|
110
|
+
return [
|
|
111
|
+
section("Project", [[
|
|
112
|
+
"Name",
|
|
113
|
+
config.projectName,
|
|
114
|
+
"raw"
|
|
115
|
+
], [
|
|
116
|
+
"Directory",
|
|
117
|
+
config.relativePath,
|
|
118
|
+
"raw"
|
|
119
|
+
]]),
|
|
120
|
+
section("Application", [
|
|
121
|
+
["Frontend", config.frontend],
|
|
122
|
+
["Backend", config.backend],
|
|
123
|
+
["Runtime", config.runtime],
|
|
124
|
+
["API", config.api]
|
|
125
|
+
]),
|
|
126
|
+
section("Data", [
|
|
127
|
+
["Database", config.database],
|
|
128
|
+
["ORM", config.orm],
|
|
129
|
+
["Setup", config.dbSetup]
|
|
130
|
+
]),
|
|
131
|
+
section("Product", [
|
|
132
|
+
["Auth", config.auth],
|
|
133
|
+
["Payments", config.payments],
|
|
134
|
+
["Addons", config.addons],
|
|
135
|
+
["Examples", config.examples]
|
|
136
|
+
]),
|
|
137
|
+
section("Delivery", [
|
|
138
|
+
["Web deploy", config.webDeploy],
|
|
139
|
+
["Server deploy", config.serverDeploy],
|
|
140
|
+
["Package manager", config.packageManager],
|
|
141
|
+
["Git", config.git],
|
|
142
|
+
["Install deps", config.install]
|
|
143
|
+
])
|
|
144
|
+
].filter((value) => value !== void 0);
|
|
145
|
+
}
|
|
146
|
+
function displayConfig(config) {
|
|
147
|
+
const sections = getConfigSections(config);
|
|
148
|
+
if (sections.length === 0) return pc.yellow("No configuration selected.");
|
|
149
|
+
return sections.map(({ title, rows }) => {
|
|
150
|
+
const labelWidth = Math.max(...rows.map(({ label }) => label.length));
|
|
151
|
+
const renderedRows = rows.map(({ label, value }) => ` ${pc.dim(label.padEnd(labelWidth))} ${value}`).join("\n");
|
|
152
|
+
return `${pc.magenta(pc.bold(title))}\n${renderedRows}`;
|
|
153
|
+
}).join("\n\n");
|
|
154
|
+
}
|
|
155
|
+
//#endregion
|
|
25
156
|
//#region src/utils/get-package-manager.ts
|
|
26
157
|
const getUserPkgManager = () => {
|
|
27
158
|
const userAgent = process.env.npm_config_user_agent;
|
|
@@ -120,6 +251,9 @@ function isFirstPrompt() {
|
|
|
120
251
|
function didLastPromptShowUI() {
|
|
121
252
|
return getContext().navigation.lastPromptShownUI;
|
|
122
253
|
}
|
|
254
|
+
function getPromptProgress() {
|
|
255
|
+
return getContext().navigation.promptProgress;
|
|
256
|
+
}
|
|
123
257
|
function setIsFirstPrompt$1(value) {
|
|
124
258
|
const ctx = tryGetContext();
|
|
125
259
|
if (ctx) ctx.navigation.isFirstPrompt = value;
|
|
@@ -128,6 +262,10 @@ function setLastPromptShownUI(value) {
|
|
|
128
262
|
const ctx = tryGetContext();
|
|
129
263
|
if (ctx) ctx.navigation.lastPromptShownUI = value;
|
|
130
264
|
}
|
|
265
|
+
function setPromptProgress(value) {
|
|
266
|
+
const ctx = tryGetContext();
|
|
267
|
+
if (ctx) ctx.navigation.promptProgress = value;
|
|
268
|
+
}
|
|
131
269
|
async function runWithContextAsync(options, fn) {
|
|
132
270
|
const ctx = {
|
|
133
271
|
navigation: {
|
|
@@ -451,10 +589,10 @@ const renderTitle = () => {
|
|
|
451
589
|
//#region src/commands/history.ts
|
|
452
590
|
function formatStackSummary(entry) {
|
|
453
591
|
const parts = [];
|
|
454
|
-
if (entry.stack.frontend.length > 0 && !entry.stack.frontend.includes("none")) parts.push(entry.stack.frontend
|
|
455
|
-
if (entry.stack.backend && entry.stack.backend !== "none") parts.push(entry.stack.backend);
|
|
456
|
-
if (entry.stack.database && entry.stack.database !== "none") parts.push(entry.stack.database);
|
|
457
|
-
if (entry.stack.orm && entry.stack.orm !== "none") parts.push(entry.stack.orm);
|
|
592
|
+
if (entry.stack.frontend.length > 0 && !entry.stack.frontend.includes("none")) parts.push(formatConfigValue(entry.stack.frontend));
|
|
593
|
+
if (entry.stack.backend && entry.stack.backend !== "none") parts.push(formatConfigValue(entry.stack.backend));
|
|
594
|
+
if (entry.stack.database && entry.stack.database !== "none") parts.push(formatConfigValue(entry.stack.database));
|
|
595
|
+
if (entry.stack.orm && entry.stack.orm !== "none") parts.push(formatConfigValue(entry.stack.orm));
|
|
458
596
|
return parts.length > 0 ? parts.join(" + ") : "minimal";
|
|
459
597
|
}
|
|
460
598
|
function formatDate(isoString) {
|
|
@@ -466,6 +604,25 @@ function formatDate(isoString) {
|
|
|
466
604
|
minute: "2-digit"
|
|
467
605
|
});
|
|
468
606
|
}
|
|
607
|
+
function formatHistoryEntry(entry, index) {
|
|
608
|
+
const rows = [
|
|
609
|
+
{
|
|
610
|
+
label: "Created",
|
|
611
|
+
value: formatDate(entry.createdAt)
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
label: "Location",
|
|
615
|
+
value: entry.projectDir
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
label: "Stack",
|
|
619
|
+
value: formatStackSummary(entry)
|
|
620
|
+
}
|
|
621
|
+
];
|
|
622
|
+
const labelWidth = Math.max(...rows.map(({ label }) => label.length));
|
|
623
|
+
const details = rows.map(({ label, value }) => `${pc.dim(label.padEnd(labelWidth))} ${value}`).join("\n");
|
|
624
|
+
return `${pc.cyan(pc.bold(`${index + 1}. ${entry.projectName}`))}\n${details}\n${pc.dim("Recreate")}\n${pc.cyan(entry.reproducibleCommand)}`;
|
|
625
|
+
}
|
|
469
626
|
async function historyHandler(input) {
|
|
470
627
|
if (input.clear) {
|
|
471
628
|
const clearResult = await clearHistory();
|
|
@@ -482,28 +639,18 @@ async function historyHandler(input) {
|
|
|
482
639
|
return;
|
|
483
640
|
}
|
|
484
641
|
const entries = historyResult.value;
|
|
485
|
-
if (entries.length === 0) {
|
|
486
|
-
log.info(pc.dim("No projects in history yet."));
|
|
487
|
-
log.info(pc.dim("Create a project with: create-better-t-stack my-app"));
|
|
488
|
-
return;
|
|
489
|
-
}
|
|
490
642
|
if (input.json) {
|
|
491
643
|
console.log(JSON.stringify(entries, null, 2));
|
|
492
644
|
return;
|
|
493
645
|
}
|
|
494
646
|
renderTitle();
|
|
495
|
-
intro(pc.magenta(`Project
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
const stack = pc.dim(formatStackSummary(entry));
|
|
500
|
-
log.message(`${num} ${name}`);
|
|
501
|
-
log.message(` ${pc.dim("Created:")} ${formatDate(entry.createdAt)}`);
|
|
502
|
-
log.message(` ${pc.dim("Path:")} ${entry.projectDir}`);
|
|
503
|
-
log.message(` ${pc.dim("Stack:")} ${stack}`);
|
|
504
|
-
log.message(` ${pc.dim("Command:")} ${pc.dim(entry.reproducibleCommand)}`);
|
|
505
|
-
log.message("");
|
|
647
|
+
intro(pc.magenta(`Project history · ${entries.length}`));
|
|
648
|
+
if (entries.length === 0) {
|
|
649
|
+
outro(`${pc.dim("No saved projects yet · create one with")} ${pc.cyan("create-better-t-stack my-app")}`);
|
|
650
|
+
return;
|
|
506
651
|
}
|
|
652
|
+
log.message(entries.map(formatHistoryEntry).join("\n\n"));
|
|
653
|
+
outro(pc.dim("Run a command above to recreate that project"));
|
|
507
654
|
}
|
|
508
655
|
//#endregion
|
|
509
656
|
//#region src/utils/open-url.ts
|
|
@@ -575,27 +722,32 @@ async function fetchSponsorsQuietly({ url = SPONSORS_JSON_URL, timeoutMs = 1500
|
|
|
575
722
|
function displaySponsors(sponsors) {
|
|
576
723
|
const { total_sponsors } = sponsors.summary;
|
|
577
724
|
if (total_sponsors === 0) {
|
|
578
|
-
log.info("No sponsors found
|
|
579
|
-
outro(pc.
|
|
725
|
+
log.info("No sponsors found yet");
|
|
726
|
+
outro(`${pc.dim("Become the first sponsor ·")} ${pc.cyan(GITHUB_SPONSOR_URL)}`);
|
|
580
727
|
return;
|
|
581
728
|
}
|
|
582
729
|
displaySponsorsBox(sponsors);
|
|
583
|
-
if (total_sponsors - sponsors.specialSponsors.length > 0) log.message(pc.
|
|
584
|
-
outro(pc.
|
|
730
|
+
if (total_sponsors - sponsors.specialSponsors.length > 0) log.message(pc.dim(`+${total_sponsors - sponsors.specialSponsors.length} more sponsors`));
|
|
731
|
+
outro(`${pc.dim("Become a sponsor ·")} ${pc.cyan(GITHUB_SPONSOR_URL)}`);
|
|
585
732
|
}
|
|
586
733
|
function displaySponsorsBox(sponsors) {
|
|
587
734
|
if (sponsors.specialSponsors.length === 0) return;
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
output += ` ${pc.dim("GitHub:")} https://github.com/${sponsor.githubId}\n`;
|
|
594
|
-
const website = sponsor.websiteUrl ?? sponsor.githubUrl;
|
|
595
|
-
if (website) output += ` ${pc.dim("Website:")} ${website}\n`;
|
|
596
|
-
if (idx < sponsors.specialSponsors.length - 1) output += "\n";
|
|
735
|
+
box(formatSpecialSponsorsDetails(sponsors), pc.bold("Special sponsors"), {
|
|
736
|
+
contentPadding: 2,
|
|
737
|
+
formatBorder: pc.dim,
|
|
738
|
+
rounded: true,
|
|
739
|
+
width: "auto"
|
|
597
740
|
});
|
|
598
|
-
|
|
741
|
+
}
|
|
742
|
+
function formatSpecialSponsorsDetails(sponsors) {
|
|
743
|
+
return sponsors.specialSponsors.map((sponsor) => {
|
|
744
|
+
const displayName = sponsor.name ?? sponsor.githubId;
|
|
745
|
+
const tier = sponsor.tierName ? pc.dim(` · ${sponsor.tierName}`) : "";
|
|
746
|
+
const links = [];
|
|
747
|
+
if (sponsor.websiteUrl) links.push(`${pc.dim("Website")} ${pc.cyan(sponsor.websiteUrl)}`);
|
|
748
|
+
links.push(`${pc.dim("GitHub ")} ${pc.cyan(sponsor.githubUrl)}`);
|
|
749
|
+
return `${pc.bold(displayName)}${tier}\n${links.join("\n")}`;
|
|
750
|
+
}).join("\n\n");
|
|
599
751
|
}
|
|
600
752
|
function formatPostInstallSpecialSponsorsSection(sponsors) {
|
|
601
753
|
if (sponsors.specialSponsors.length === 0) return "";
|
|
@@ -658,7 +810,7 @@ async function fetchSponsorsData({ url = SPONSORS_JSON_URL, withSpinner = false,
|
|
|
658
810
|
cause: parseResult.error
|
|
659
811
|
}));
|
|
660
812
|
}
|
|
661
|
-
if (s) s.stop("Sponsors
|
|
813
|
+
if (s) s.stop("Sponsors loaded");
|
|
662
814
|
return Result.ok(parseResult.data);
|
|
663
815
|
} catch (error) {
|
|
664
816
|
const normalizedError = normalizeSponsorFetchError(error);
|
|
@@ -696,7 +848,7 @@ async function openExternalUrl(url, successMessage) {
|
|
|
696
848
|
}
|
|
697
849
|
async function showSponsorsCommand() {
|
|
698
850
|
renderTitle();
|
|
699
|
-
intro(pc.magenta("
|
|
851
|
+
intro(pc.magenta("Sponsors"));
|
|
700
852
|
const sponsorsResult = await fetchSponsors();
|
|
701
853
|
if (sponsorsResult.isErr()) {
|
|
702
854
|
displayError(sponsorsResult.error);
|
|
@@ -984,15 +1136,12 @@ function isGoBack(value) {
|
|
|
984
1136
|
}
|
|
985
1137
|
//#endregion
|
|
986
1138
|
//#region src/prompts/navigable.ts
|
|
987
|
-
/**
|
|
988
|
-
* Navigable prompt wrappers using @clack/core
|
|
989
|
-
* These prompts return GO_BACK_SYMBOL when 'b' is pressed (instead of canceling)
|
|
990
|
-
*/
|
|
991
1139
|
const unicode = process.platform !== "win32";
|
|
992
1140
|
const S_STEP_ACTIVE = unicode ? "◆" : "*";
|
|
993
1141
|
const S_STEP_CANCEL = unicode ? "■" : "x";
|
|
994
1142
|
const S_STEP_ERROR = unicode ? "▲" : "x";
|
|
995
1143
|
const S_STEP_SUBMIT = unicode ? "◇" : "o";
|
|
1144
|
+
const S_STEP_BACK = unicode ? "↶" : "<";
|
|
996
1145
|
const S_BAR = unicode ? "│" : "|";
|
|
997
1146
|
const S_BAR_END = unicode ? "└" : "—";
|
|
998
1147
|
const S_RADIO_ACTIVE = unicode ? "●" : ">";
|
|
@@ -1000,6 +1149,10 @@ const S_RADIO_INACTIVE = unicode ? "○" : " ";
|
|
|
1000
1149
|
const S_CHECKBOX_ACTIVE = unicode ? "◻" : "[•]";
|
|
1001
1150
|
const S_CHECKBOX_SELECTED = unicode ? "◼" : "[+]";
|
|
1002
1151
|
const S_CHECKBOX_INACTIVE = unicode ? "◻" : "[ ]";
|
|
1152
|
+
const promptsNavigatingBack = /* @__PURE__ */ new WeakSet();
|
|
1153
|
+
function keycap(label) {
|
|
1154
|
+
return pc.inverse(` ${label} `);
|
|
1155
|
+
}
|
|
1003
1156
|
function symbol(state) {
|
|
1004
1157
|
switch (state) {
|
|
1005
1158
|
case "initial":
|
|
@@ -1009,10 +1162,10 @@ function symbol(state) {
|
|
|
1009
1162
|
case "submit": return pc.green(S_STEP_SUBMIT);
|
|
1010
1163
|
}
|
|
1011
1164
|
}
|
|
1012
|
-
const KEYBOARD_HINT = pc.dim(`${
|
|
1013
|
-
const KEYBOARD_HINT_FIRST = pc.dim(`${
|
|
1014
|
-
const KEYBOARD_HINT_MULTI = pc.dim(`${
|
|
1015
|
-
const KEYBOARD_HINT_MULTI_FIRST = pc.dim(`${
|
|
1165
|
+
const KEYBOARD_HINT = pc.dim(`${keycap("↑↓")} move ${keycap("enter")} choose ${keycap("b")} back ${keycap("^c")} cancel`);
|
|
1166
|
+
const KEYBOARD_HINT_FIRST = pc.dim(`${keycap("↑↓")} move ${keycap("enter")} choose ${keycap("^c")} cancel`);
|
|
1167
|
+
const KEYBOARD_HINT_MULTI = pc.dim(`${keycap("↑↓")} move ${keycap("space")} toggle ${keycap("enter")} choose ${keycap("b")} back ${keycap("^c")} cancel`);
|
|
1168
|
+
const KEYBOARD_HINT_MULTI_FIRST = pc.dim(`${keycap("↑↓")} move ${keycap("space")} toggle ${keycap("enter")} choose ${keycap("^c")} cancel`);
|
|
1016
1169
|
const setIsFirstPrompt = setIsFirstPrompt$1;
|
|
1017
1170
|
function getHint() {
|
|
1018
1171
|
return isFirstPrompt() ? KEYBOARD_HINT_FIRST : KEYBOARD_HINT;
|
|
@@ -1020,6 +1173,19 @@ function getHint() {
|
|
|
1020
1173
|
function getMultiHint() {
|
|
1021
1174
|
return isFirstPrompt() ? KEYBOARD_HINT_MULTI_FIRST : KEYBOARD_HINT_MULTI;
|
|
1022
1175
|
}
|
|
1176
|
+
function activePromptTitle(message, state = "active") {
|
|
1177
|
+
const progress = getPromptProgress();
|
|
1178
|
+
const eyebrow = progress ? `${pc.magenta(pc.bold(progress.section.toUpperCase()))} ${pc.dim(`· ${progress.current}/${progress.total}`)}` : pc.dim("SETUP");
|
|
1179
|
+
return `${pc.gray(S_BAR)} ${eyebrow}\n${symbol(state)} ${pc.bold(message)}\n`;
|
|
1180
|
+
}
|
|
1181
|
+
function resolvedPrompt(message, value, state) {
|
|
1182
|
+
const promptMessage = state === "cancel" ? pc.strikethrough(pc.dim(message)) : pc.dim(message);
|
|
1183
|
+
return `${symbol(state)} ${promptMessage} ${pc.dim("›")} ${value}`;
|
|
1184
|
+
}
|
|
1185
|
+
function canceledPrompt(prompt, message, value) {
|
|
1186
|
+
if (promptsNavigatingBack.has(prompt)) return `${pc.cyan(S_STEP_BACK)} ${pc.dim(message)}`;
|
|
1187
|
+
return resolvedPrompt(message, value, "cancel");
|
|
1188
|
+
}
|
|
1023
1189
|
function normalizeValidationMessage(validationMessage) {
|
|
1024
1190
|
return validationMessage instanceof Error ? validationMessage.message : validationMessage;
|
|
1025
1191
|
}
|
|
@@ -1028,20 +1194,26 @@ async function runWithNavigation(prompt) {
|
|
|
1028
1194
|
prompt.on("key", (char) => {
|
|
1029
1195
|
if ((char === "b" || char === "B") && !isFirstPrompt()) {
|
|
1030
1196
|
goBack = true;
|
|
1197
|
+
promptsNavigatingBack.add(prompt);
|
|
1031
1198
|
prompt.state = "cancel";
|
|
1032
1199
|
}
|
|
1033
1200
|
});
|
|
1034
1201
|
setLastPromptShownUI(true);
|
|
1035
|
-
|
|
1036
|
-
|
|
1202
|
+
try {
|
|
1203
|
+
const result = await prompt.prompt();
|
|
1204
|
+
return goBack ? GO_BACK_SYMBOL : result;
|
|
1205
|
+
} finally {
|
|
1206
|
+
promptsNavigatingBack.delete(prompt);
|
|
1207
|
+
}
|
|
1037
1208
|
}
|
|
1038
1209
|
async function navigableSelect(opts) {
|
|
1039
1210
|
const opt = (option, state) => {
|
|
1211
|
+
if (!option) return pc.dim("none");
|
|
1040
1212
|
const label = option.label ?? String(option.value);
|
|
1041
1213
|
switch (state) {
|
|
1042
1214
|
case "disabled": return `${pc.gray(S_RADIO_INACTIVE)} ${pc.gray(label)}${option.hint ? ` ${pc.dim(`(${option.hint ?? "disabled"})`)}` : ""}`;
|
|
1043
1215
|
case "selected": return `${pc.dim(label)}`;
|
|
1044
|
-
case "active": return `${pc.
|
|
1216
|
+
case "active": return `${pc.cyan(S_RADIO_ACTIVE)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
1045
1217
|
case "cancelled": return `${pc.strikethrough(pc.dim(label))}`;
|
|
1046
1218
|
default: return `${pc.dim(S_RADIO_INACTIVE)} ${pc.dim(label)}`;
|
|
1047
1219
|
}
|
|
@@ -1049,15 +1221,25 @@ async function navigableSelect(opts) {
|
|
|
1049
1221
|
return runWithNavigation(new SelectPrompt({
|
|
1050
1222
|
options: opts.options,
|
|
1051
1223
|
initialValue: opts.initialValue,
|
|
1224
|
+
signal: opts.signal,
|
|
1225
|
+
input: opts.input,
|
|
1226
|
+
output: opts.output,
|
|
1052
1227
|
render() {
|
|
1053
|
-
const title = `${pc.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
1054
1228
|
switch (this.state) {
|
|
1055
|
-
case "submit": return
|
|
1056
|
-
case "cancel": return
|
|
1229
|
+
case "submit": return resolvedPrompt(opts.message, opt(this.options[this.cursor], "selected"), "submit");
|
|
1230
|
+
case "cancel": return canceledPrompt(this, opts.message, opt(this.options[this.cursor], "cancelled"));
|
|
1057
1231
|
default: {
|
|
1058
|
-
const optionsText =
|
|
1059
|
-
|
|
1060
|
-
|
|
1232
|
+
const optionsText = limitOptions({
|
|
1233
|
+
output: opts.output,
|
|
1234
|
+
options: this.options,
|
|
1235
|
+
cursor: this.cursor,
|
|
1236
|
+
maxItems: opts.maxItems,
|
|
1237
|
+
columnPadding: 3,
|
|
1238
|
+
rowPadding: 5,
|
|
1239
|
+
style: (option, active) => opt(option, option.disabled ? "disabled" : active ? "active" : "inactive")
|
|
1240
|
+
}).join(`\n${pc.cyan(S_BAR)} `);
|
|
1241
|
+
const hint = `${pc.gray(S_BAR_END)} ${getHint()}`;
|
|
1242
|
+
return `${activePromptTitle(opts.message)}${pc.cyan(S_BAR)} ${optionsText}\n${hint}\n`;
|
|
1061
1243
|
}
|
|
1062
1244
|
}
|
|
1063
1245
|
}
|
|
@@ -1078,13 +1260,16 @@ async function navigableMultiselect(opts) {
|
|
|
1078
1260
|
return runWithNavigation(new MultiSelectPrompt({
|
|
1079
1261
|
options: opts.options,
|
|
1080
1262
|
initialValues: opts.initialValues,
|
|
1263
|
+
cursorAt: opts.cursorAt,
|
|
1081
1264
|
required,
|
|
1265
|
+
signal: opts.signal,
|
|
1266
|
+
input: opts.input,
|
|
1267
|
+
output: opts.output,
|
|
1082
1268
|
validate(selected) {
|
|
1083
1269
|
if (required && (selected === void 0 || selected.length === 0)) return `Please select at least one option.\n${pc.reset(pc.dim(`Press ${pc.gray(pc.bgWhite(pc.inverse(" space ")))} to select, ${pc.gray(pc.bgWhite(pc.inverse(" enter ")))} to submit`))}`;
|
|
1084
1270
|
return normalizeValidationMessage(opts.validate?.(selected));
|
|
1085
1271
|
},
|
|
1086
1272
|
render() {
|
|
1087
|
-
const title = `${pc.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
1088
1273
|
const value = this.value ?? [];
|
|
1089
1274
|
const styleOption = (option, active) => {
|
|
1090
1275
|
if (option.disabled) return opt(option, "disabled");
|
|
@@ -1096,21 +1281,37 @@ async function navigableMultiselect(opts) {
|
|
|
1096
1281
|
switch (this.state) {
|
|
1097
1282
|
case "submit": {
|
|
1098
1283
|
const submitText = this.options.filter(({ value: optionValue }) => value.includes(optionValue)).map((option) => opt(option, "submitted")).join(pc.dim(", ")) || pc.dim("none");
|
|
1099
|
-
return
|
|
1284
|
+
return resolvedPrompt(opts.message, submitText, "submit");
|
|
1100
1285
|
}
|
|
1101
1286
|
case "cancel": {
|
|
1102
|
-
const label = this.options.filter(({ value: optionValue }) => value.includes(optionValue)).map((option) => opt(option, "cancelled")).join(pc.dim(", "));
|
|
1103
|
-
return
|
|
1287
|
+
const label = this.options.filter(({ value: optionValue }) => value.includes(optionValue)).map((option) => opt(option, "cancelled")).join(pc.dim(", ")) || pc.dim("none");
|
|
1288
|
+
return canceledPrompt(this, opts.message, label);
|
|
1104
1289
|
}
|
|
1105
1290
|
case "error": {
|
|
1106
1291
|
const footer = this.error.split("\n").map((ln, i) => i === 0 ? `${pc.yellow(S_BAR_END)} ${pc.yellow(ln)}` : ` ${ln}`).join("\n");
|
|
1107
|
-
const optionsText =
|
|
1108
|
-
|
|
1292
|
+
const optionsText = limitOptions({
|
|
1293
|
+
output: opts.output,
|
|
1294
|
+
options: this.options,
|
|
1295
|
+
cursor: this.cursor,
|
|
1296
|
+
maxItems: opts.maxItems,
|
|
1297
|
+
columnPadding: 3,
|
|
1298
|
+
rowPadding: footer.split("\n").length + 4,
|
|
1299
|
+
style: styleOption
|
|
1300
|
+
}).join(`\n${pc.yellow(S_BAR)} `);
|
|
1301
|
+
return `${activePromptTitle(opts.message, "error")}${pc.yellow(S_BAR)} ${optionsText}\n${footer}\n`;
|
|
1109
1302
|
}
|
|
1110
1303
|
default: {
|
|
1111
|
-
const optionsText =
|
|
1112
|
-
|
|
1113
|
-
|
|
1304
|
+
const optionsText = limitOptions({
|
|
1305
|
+
output: opts.output,
|
|
1306
|
+
options: this.options,
|
|
1307
|
+
cursor: this.cursor,
|
|
1308
|
+
maxItems: opts.maxItems,
|
|
1309
|
+
columnPadding: 3,
|
|
1310
|
+
rowPadding: 5,
|
|
1311
|
+
style: styleOption
|
|
1312
|
+
}).join(`\n${pc.cyan(S_BAR)} `);
|
|
1313
|
+
const hint = `${pc.gray(S_BAR_END)} ${getMultiHint()}`;
|
|
1314
|
+
return `${activePromptTitle(opts.message)}${pc.cyan(S_BAR)} ${optionsText}\n${hint}\n`;
|
|
1114
1315
|
}
|
|
1115
1316
|
}
|
|
1116
1317
|
}
|
|
@@ -1123,15 +1324,17 @@ async function navigableConfirm(opts) {
|
|
|
1123
1324
|
active,
|
|
1124
1325
|
inactive,
|
|
1125
1326
|
initialValue: opts.initialValue ?? true,
|
|
1327
|
+
signal: opts.signal,
|
|
1328
|
+
input: opts.input,
|
|
1329
|
+
output: opts.output,
|
|
1126
1330
|
render() {
|
|
1127
|
-
const title = `${pc.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
1128
1331
|
const value = this.value ? active : inactive;
|
|
1129
1332
|
switch (this.state) {
|
|
1130
|
-
case "submit": return
|
|
1131
|
-
case "cancel": return
|
|
1333
|
+
case "submit": return resolvedPrompt(opts.message, pc.dim(value), "submit");
|
|
1334
|
+
case "cancel": return canceledPrompt(this, opts.message, pc.strikethrough(pc.dim(value)));
|
|
1132
1335
|
default: {
|
|
1133
|
-
const hint =
|
|
1134
|
-
return `${
|
|
1336
|
+
const hint = `${pc.gray(S_BAR_END)} ${getHint()}`;
|
|
1337
|
+
return `${activePromptTitle(opts.message)}${pc.cyan(S_BAR)} ${this.value ? `${pc.cyan(S_RADIO_ACTIVE)} ${active}` : `${pc.dim(S_RADIO_INACTIVE)} ${pc.dim(active)}`} ${pc.dim("/")} ${!this.value ? `${pc.cyan(S_RADIO_ACTIVE)} ${inactive}` : `${pc.dim(S_RADIO_INACTIVE)} ${pc.dim(inactive)}`}\n${hint}\n`;
|
|
1135
1338
|
}
|
|
1136
1339
|
}
|
|
1137
1340
|
}
|
|
@@ -1161,52 +1364,59 @@ async function navigableGroupMultiselect(opts) {
|
|
|
1161
1364
|
return runWithNavigation(new GroupMultiSelectPrompt({
|
|
1162
1365
|
options: opts.options,
|
|
1163
1366
|
initialValues: opts.initialValues,
|
|
1367
|
+
cursorAt: opts.cursorAt,
|
|
1164
1368
|
required,
|
|
1165
1369
|
selectableGroups: true,
|
|
1370
|
+
signal: opts.signal,
|
|
1371
|
+
input: opts.input,
|
|
1372
|
+
output: opts.output,
|
|
1166
1373
|
validate(selected) {
|
|
1167
1374
|
if (required && (selected === void 0 || selected.length === 0)) return `Please select at least one option.\n${pc.reset(pc.dim(`Press ${pc.gray(pc.bgWhite(pc.inverse(" space ")))} to select, ${pc.gray(pc.bgWhite(pc.inverse(" enter ")))} to submit`))}`;
|
|
1168
1375
|
return normalizeValidationMessage(opts.validate?.(selected));
|
|
1169
1376
|
},
|
|
1170
1377
|
render() {
|
|
1171
|
-
const title = `${pc.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
1172
1378
|
const value = this.value ?? [];
|
|
1379
|
+
const styleOption = (option, active) => {
|
|
1380
|
+
const selected = value.includes(option.value) || option.group === true && this.isGroupSelected(`${option.value}`);
|
|
1381
|
+
if (!active && typeof option.group === "string" && this.options[this.cursor]?.value === option.group) return opt(option, selected ? "group-active-selected" : "group-active", this.options);
|
|
1382
|
+
if (active && selected) return opt(option, "active-selected", this.options);
|
|
1383
|
+
if (selected) return opt(option, "selected", this.options);
|
|
1384
|
+
return opt(option, active ? "active" : "inactive", this.options);
|
|
1385
|
+
};
|
|
1173
1386
|
switch (this.state) {
|
|
1174
1387
|
case "submit": {
|
|
1175
|
-
const
|
|
1176
|
-
|
|
1177
|
-
return `${title}${pc.gray(S_BAR)}${optionsText}`;
|
|
1388
|
+
const optionsText = this.options.filter(({ value: optionValue }) => value.includes(optionValue)).map((option) => opt(option, "submitted")).join(pc.dim(", ")) || pc.dim("none");
|
|
1389
|
+
return resolvedPrompt(opts.message, optionsText, "submit");
|
|
1178
1390
|
}
|
|
1179
1391
|
case "cancel": {
|
|
1180
|
-
const label = this.options.filter(({ value: optionValue }) => value.includes(optionValue)).map((option) => opt(option, "cancelled")).join(pc.dim(", "));
|
|
1181
|
-
return
|
|
1392
|
+
const label = this.options.filter(({ value: optionValue }) => value.includes(optionValue)).map((option) => opt(option, "cancelled")).join(pc.dim(", ")) || pc.dim("none");
|
|
1393
|
+
return canceledPrompt(this, opts.message, label);
|
|
1182
1394
|
}
|
|
1183
1395
|
case "error": {
|
|
1184
1396
|
const footer = this.error.split("\n").map((ln, i) => i === 0 ? `${pc.yellow(S_BAR_END)} ${pc.yellow(ln)}` : ` ${ln}`).join("\n");
|
|
1185
|
-
const optionsText =
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1397
|
+
const optionsText = limitOptions({
|
|
1398
|
+
output: opts.output,
|
|
1399
|
+
options: this.options,
|
|
1400
|
+
cursor: this.cursor,
|
|
1401
|
+
maxItems: opts.maxItems,
|
|
1402
|
+
columnPadding: 3,
|
|
1403
|
+
rowPadding: footer.split("\n").length + 4,
|
|
1404
|
+
style: styleOption
|
|
1192
1405
|
}).join(`\n${pc.yellow(S_BAR)} `);
|
|
1193
|
-
return `${
|
|
1406
|
+
return `${activePromptTitle(opts.message, "error")}${pc.yellow(S_BAR)} ${optionsText}\n${footer}\n`;
|
|
1194
1407
|
}
|
|
1195
1408
|
default: {
|
|
1196
|
-
const optionsText =
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
}
|
|
1207
|
-
const optionsPrefix = optionsText.startsWith("\n") ? "" : " ";
|
|
1208
|
-
const hint = `\n${pc.gray(S_BAR)} ${getMultiHint()}`;
|
|
1209
|
-
return `${title}${pc.cyan(S_BAR)}${optionsPrefix}${optionsText}\n${pc.cyan(S_BAR_END)}${hint}\n`;
|
|
1409
|
+
const optionsText = limitOptions({
|
|
1410
|
+
output: opts.output,
|
|
1411
|
+
options: this.options,
|
|
1412
|
+
cursor: this.cursor,
|
|
1413
|
+
maxItems: opts.maxItems,
|
|
1414
|
+
columnPadding: 3,
|
|
1415
|
+
rowPadding: 5,
|
|
1416
|
+
style: styleOption
|
|
1417
|
+
}).join(`\n${pc.cyan(S_BAR)} `);
|
|
1418
|
+
const hint = `${pc.gray(S_BAR_END)} ${getMultiHint()}`;
|
|
1419
|
+
return `${activePromptTitle(opts.message)}${pc.cyan(S_BAR)} ${optionsText}\n${hint}\n`;
|
|
1210
1420
|
}
|
|
1211
1421
|
}
|
|
1212
1422
|
}
|
|
@@ -1372,7 +1582,7 @@ async function getAddonsChoice(addons, frontends, auth, backend, runtime, previo
|
|
|
1372
1582
|
}
|
|
1373
1583
|
sortAndPruneGroupedOptions(groupedOptions);
|
|
1374
1584
|
const response = await navigableGroupMultiselect({
|
|
1375
|
-
message: "
|
|
1585
|
+
message: "Pick addons",
|
|
1376
1586
|
options: groupedOptions,
|
|
1377
1587
|
initialValues: (previousValue ?? DEFAULT_CONFIG.addons).filter((addonValue) => Object.values(groupedOptions).some((options) => options.some((opt) => opt.value === addonValue))),
|
|
1378
1588
|
required: false,
|
|
@@ -1491,15 +1701,37 @@ const evlogWebFrontends = [
|
|
|
1491
1701
|
"tanstack-start",
|
|
1492
1702
|
"astro"
|
|
1493
1703
|
];
|
|
1704
|
+
const NODE_DEV_FS_DRAIN_EXPRESSION = "process.env.NODE_ENV === \"production\" ? undefined : createFsDrain()";
|
|
1705
|
+
const SVELTE_DEV_FS_DRAIN_EXPRESSION = "dev ? createFsDrain() : undefined";
|
|
1706
|
+
const ASTRO_DEV_FS_DRAIN_EXPRESSION = "import.meta.env.DEV ? createFsDrain() : undefined";
|
|
1494
1707
|
function isEvlogBackend(backend) {
|
|
1495
1708
|
return evlogBackends.includes(backend);
|
|
1496
1709
|
}
|
|
1497
1710
|
function getEvlogWebFrontend(frontends) {
|
|
1498
1711
|
return frontends.find((frontend) => evlogWebFrontends.includes(frontend));
|
|
1499
1712
|
}
|
|
1713
|
+
function shouldWireEvlogServerFsDrain(config) {
|
|
1714
|
+
return isEvlogBackend(config.backend) && config.runtime !== "workers" && config.serverDeploy !== "cloudflare";
|
|
1715
|
+
}
|
|
1716
|
+
function shouldWireEvlogWebFsDrain(config) {
|
|
1717
|
+
return getEvlogWebFrontend(config.frontend) !== void 0 && config.webDeploy !== "cloudflare";
|
|
1718
|
+
}
|
|
1719
|
+
function supportsEvlogLocalLogs(config) {
|
|
1720
|
+
return shouldWireEvlogServerFsDrain(config) || shouldWireEvlogWebFsDrain(config);
|
|
1721
|
+
}
|
|
1500
1722
|
function shouldIdentifyWebAuth(config) {
|
|
1501
1723
|
return config.auth === "better-auth" && config.backend === "self";
|
|
1502
1724
|
}
|
|
1725
|
+
function getEvlogServerMiddlewareMarker(backend, fsDrain) {
|
|
1726
|
+
const options = fsDrain ? `{ drain: ${NODE_DEV_FS_DRAIN_EXPRESSION} }` : "";
|
|
1727
|
+
if (backend === "hono" || backend === "express") return `app.use(evlog(${options}));`;
|
|
1728
|
+
if (backend === "fastify") return `fastify.register(evlog${options ? `, ${options}` : ""});`;
|
|
1729
|
+
return `.use(evlog(${options}))`;
|
|
1730
|
+
}
|
|
1731
|
+
function findEvlogServerMiddlewareMarker(content, backend) {
|
|
1732
|
+
const fsDrainMarker = getEvlogServerMiddlewareMarker(backend, true);
|
|
1733
|
+
return content.includes(fsDrainMarker) ? fsDrainMarker : getEvlogServerMiddlewareMarker(backend, false);
|
|
1734
|
+
}
|
|
1503
1735
|
function prependMissingImports(content, imports) {
|
|
1504
1736
|
const missingImports = imports.filter((line) => !content.includes(line));
|
|
1505
1737
|
if (missingImports.length === 0) return content;
|
|
@@ -1561,46 +1793,72 @@ function addEvlogBetterAuthServerSetup(content, backend, authExpression) {
|
|
|
1561
1793
|
const identifySnippet = usesAuthFactory ? "" : `const identifyUser = createAuthMiddleware(${evlogAuthExpression}, ${authOptions});\n\n`;
|
|
1562
1794
|
const identifyUserSetup = usesAuthFactory ? `\n\tconst identifyUser = createAuthMiddleware(${evlogAuthExpression}, ${authOptions});` : "";
|
|
1563
1795
|
if (backend === "hono") {
|
|
1796
|
+
const evlogMarker = findEvlogServerMiddlewareMarker(nextContent, backend);
|
|
1564
1797
|
nextContent = insertBeforeOnce(nextContent, "const app = new Hono", identifySnippet, "createAuthMiddleware(");
|
|
1565
|
-
return insertAfterOnce(nextContent,
|
|
1798
|
+
return insertAfterOnce(nextContent, evlogMarker, `\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\")");
|
|
1566
1799
|
}
|
|
1567
1800
|
if (backend === "express") {
|
|
1801
|
+
const evlogMarker = findEvlogServerMiddlewareMarker(nextContent, backend);
|
|
1568
1802
|
nextContent = addNamedImport(nextContent, "evlog/express", ["useLogger"]);
|
|
1569
1803
|
nextContent = insertBeforeOnce(nextContent, "const app = express();", identifySnippet, "createAuthMiddleware(");
|
|
1570
|
-
return insertAfterOnce(nextContent,
|
|
1804
|
+
return insertAfterOnce(nextContent, evlogMarker, `\napp.use(async (req, _res, next) => {${identifyUserSetup}\n\tawait identifyUser(useLogger(), req.headers, req.path);\n\tnext();\n});`, "identifyUser(useLogger()");
|
|
1571
1805
|
}
|
|
1572
1806
|
if (backend === "fastify") {
|
|
1807
|
+
const evlogMarker = findEvlogServerMiddlewareMarker(nextContent, backend);
|
|
1573
1808
|
nextContent = addNamedImport(nextContent, "evlog/fastify", ["useLogger"]);
|
|
1574
1809
|
nextContent = insertBeforeOnce(nextContent, "const fastify = Fastify", identifySnippet, "createAuthMiddleware(");
|
|
1575
|
-
return insertAfterOnce(nextContent,
|
|
1810
|
+
return insertAfterOnce(nextContent, evlogMarker, `\nfastify.addHook("preHandler", async (request) => {${identifyUserSetup}\n\tawait identifyUser(useLogger(), request.headers, request.url);\n});`, "identifyUser(useLogger()");
|
|
1576
1811
|
}
|
|
1577
1812
|
const elysiaMarker = nextContent.includes("const app = new Elysia") ? "const app = new Elysia" : "new Elysia";
|
|
1578
1813
|
nextContent = insertBeforeOnce(nextContent, elysiaMarker, identifySnippet, "createAuthMiddleware(");
|
|
1579
|
-
|
|
1814
|
+
const evlogMarker = findEvlogServerMiddlewareMarker(nextContent, backend);
|
|
1815
|
+
return insertAfterOnce(nextContent, evlogMarker, `\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
1816
|
}
|
|
1581
|
-
function addEvlogServerSetup(content, backend, serviceName) {
|
|
1817
|
+
function addEvlogServerSetup(content, backend, serviceName, fsDrain) {
|
|
1582
1818
|
const initSnippet = `initLogger({\n\tenv: { service: "${serviceName}" },\n});\n\n`;
|
|
1819
|
+
const evlogMarker = getEvlogServerMiddlewareMarker(backend, fsDrain);
|
|
1820
|
+
const legacyEvlogMarker = getEvlogServerMiddlewareMarker(backend, false);
|
|
1583
1821
|
if (backend === "hono") {
|
|
1584
|
-
let nextContent = prependMissingImports(content, [
|
|
1822
|
+
let nextContent = prependMissingImports(content, [
|
|
1823
|
+
"import { initLogger } from \"evlog\";",
|
|
1824
|
+
"import { evlog, type EvlogVariables } from \"evlog/hono\";",
|
|
1825
|
+
...fsDrain ? ["import { createFsDrain } from \"evlog/fs\";"] : []
|
|
1826
|
+
]);
|
|
1585
1827
|
nextContent = insertBeforeOnce(nextContent, "const app = new Hono", initSnippet, "initLogger({");
|
|
1586
1828
|
nextContent = nextContent.replace("const app = new Hono();", "const app = new Hono<EvlogVariables>();");
|
|
1587
1829
|
nextContent = nextContent.replace("import { logger } from \"hono/logger\";\n", "").replace(/\napp\.use\(logger\(\)\);/, "");
|
|
1588
|
-
|
|
1830
|
+
if (fsDrain) nextContent = nextContent.replace(legacyEvlogMarker, evlogMarker);
|
|
1831
|
+
return insertAfterOnce(nextContent, "const app = new Hono<EvlogVariables>();", `\n\n${evlogMarker}`, evlogMarker);
|
|
1589
1832
|
}
|
|
1590
1833
|
if (backend === "express") {
|
|
1591
|
-
let nextContent = prependMissingImports(content, [
|
|
1834
|
+
let nextContent = prependMissingImports(content, [
|
|
1835
|
+
"import { initLogger } from \"evlog\";",
|
|
1836
|
+
"import { evlog } from \"evlog/express\";",
|
|
1837
|
+
...fsDrain ? ["import { createFsDrain } from \"evlog/fs\";"] : []
|
|
1838
|
+
]);
|
|
1592
1839
|
nextContent = insertBeforeOnce(nextContent, "const app = express();", initSnippet, "initLogger({");
|
|
1593
|
-
|
|
1840
|
+
if (fsDrain) nextContent = nextContent.replace(legacyEvlogMarker, evlogMarker);
|
|
1841
|
+
return insertAfterOnce(nextContent, "const app = express();", `\n\n${evlogMarker}`, evlogMarker);
|
|
1594
1842
|
}
|
|
1595
1843
|
if (backend === "fastify") {
|
|
1596
|
-
let nextContent = prependMissingImports(content, [
|
|
1844
|
+
let nextContent = prependMissingImports(content, [
|
|
1845
|
+
"import { initLogger } from \"evlog\";",
|
|
1846
|
+
"import { evlog } from \"evlog/fastify\";",
|
|
1847
|
+
...fsDrain ? ["import { createFsDrain } from \"evlog/fs\";"] : []
|
|
1848
|
+
]);
|
|
1597
1849
|
nextContent = insertBeforeOnce(nextContent, "const fastify = Fastify", initSnippet, "initLogger({");
|
|
1598
|
-
|
|
1850
|
+
if (fsDrain) nextContent = nextContent.replace(legacyEvlogMarker, evlogMarker);
|
|
1851
|
+
return insertBeforeOnce(nextContent, "fastify.register(fastifyCors", `${evlogMarker}\n`, evlogMarker);
|
|
1599
1852
|
}
|
|
1600
|
-
let nextContent = prependMissingImports(content, [
|
|
1853
|
+
let nextContent = prependMissingImports(content, [
|
|
1854
|
+
"import { initLogger } from \"evlog\";",
|
|
1855
|
+
"import { evlog } from \"evlog/elysia\";",
|
|
1856
|
+
...fsDrain ? ["import { createFsDrain } from \"evlog/fs\";"] : []
|
|
1857
|
+
]);
|
|
1601
1858
|
const elysiaMarker = nextContent.includes("const app = new Elysia") ? "const app = new Elysia" : "new Elysia";
|
|
1602
1859
|
nextContent = insertBeforeOnce(nextContent, elysiaMarker, initSnippet, "initLogger({");
|
|
1603
|
-
|
|
1860
|
+
if (fsDrain) nextContent = nextContent.replace(legacyEvlogMarker, evlogMarker);
|
|
1861
|
+
for (const marker of ["new Elysia({ adapter: node() })", "new Elysia()"]) nextContent = insertAfterOnce(nextContent, marker, `\n\t${evlogMarker}`, evlogMarker);
|
|
1604
1862
|
return nextContent;
|
|
1605
1863
|
}
|
|
1606
1864
|
function addNuxtEvlogSetup(content, serviceName) {
|
|
@@ -1617,14 +1875,20 @@ function addSvelteViteEvlogSetup(content, serviceName) {
|
|
|
1617
1875
|
if (nextContent.includes("evlog({")) return nextContent;
|
|
1618
1876
|
return nextContent.replace("plugins: [tailwindcss(), sveltekit()],", `plugins: [\n tailwindcss(),\n sveltekit(),\n evlog({ service: "${serviceName}" }),\n ],`);
|
|
1619
1877
|
}
|
|
1620
|
-
function
|
|
1621
|
-
|
|
1878
|
+
function getSvelteEvlogHooksCall(fsDrain) {
|
|
1879
|
+
return fsDrain ? `createEvlogHooks({ drain: ${SVELTE_DEV_FS_DRAIN_EXPRESSION} })` : "createEvlogHooks()";
|
|
1880
|
+
}
|
|
1881
|
+
function addSvelteHooksEvlogSetup(content, fsDrain) {
|
|
1882
|
+
let nextContent = prependMissingImports(content, ["import { createEvlogHooks } from \"evlog/sveltekit\";", ...fsDrain ? ["import { createFsDrain } from \"evlog/fs\";"] : []]);
|
|
1883
|
+
if (fsDrain) nextContent = addNamedImport(nextContent, "$app/environment", ["dev"]);
|
|
1884
|
+
const hooksCall = getSvelteEvlogHooksCall(fsDrain);
|
|
1885
|
+
if (fsDrain) nextContent = nextContent.replaceAll("createEvlogHooks()", hooksCall);
|
|
1622
1886
|
if (!nextContent.includes("export const handle") && !nextContent.includes("const authHandle")) {
|
|
1623
|
-
if (!nextContent.includes("createEvlogHooks(
|
|
1887
|
+
if (!nextContent.includes("createEvlogHooks(")) nextContent = `${nextContent.trimEnd()}\n\nexport const { handle, handleError } = ${hooksCall};\n`;
|
|
1624
1888
|
return nextContent;
|
|
1625
1889
|
}
|
|
1626
1890
|
nextContent = prependMissingImports(nextContent, ["import { sequence } from \"@sveltejs/kit/hooks\";"]);
|
|
1627
|
-
if (!nextContent.includes("const { handle: evlogHandle, handleError }")) nextContent = nextContent.replace(/((?:import .+\n)+)/, `$1\nconst { handle: evlogHandle, handleError } =
|
|
1891
|
+
if (!nextContent.includes("const { handle: evlogHandle, handleError }")) nextContent = nextContent.replace(/((?:import .+\n)+)/, `$1\nconst { handle: evlogHandle, handleError } = ${hooksCall};\n\n`);
|
|
1628
1892
|
nextContent = nextContent.replace(/export const handle(:\s*Handle)?\s*=\s*async/, (_match, typeAnnotation) => `const authHandle${typeAnnotation ?? ""} = async`);
|
|
1629
1893
|
if (!nextContent.includes("sequence(evlogHandle, authHandle)")) nextContent = `${nextContent.trimEnd()}\n\nexport const handle = sequence(evlogHandle as Handle, authHandle);\nexport { handleError };\n`;
|
|
1630
1894
|
return nextContent;
|
|
@@ -1643,10 +1907,14 @@ function addTanstackStartRootEvlogSetup(content) {
|
|
|
1643
1907
|
if (/server:\s*{/.test(nextContent)) return nextContent.replace(/server:\s*{\n/, `server: {\n middleware: [${middlewareEntry}],\n`);
|
|
1644
1908
|
return nextContent.replace("head: () => ({", `server: {\n middleware: [${middlewareEntry}],\n },\n\n head: () => ({`);
|
|
1645
1909
|
}
|
|
1646
|
-
function
|
|
1647
|
-
|
|
1648
|
-
|
|
1910
|
+
function getInitLoggerSnippet(serviceName, fsDrain, indent) {
|
|
1911
|
+
return `initLogger({\n${indent}env: { service: "${serviceName}" },${fsDrain ? `\n${indent}drain: ${ASTRO_DEV_FS_DRAIN_EXPRESSION},` : ""}\n});\n\n`;
|
|
1912
|
+
}
|
|
1913
|
+
function addAstroMiddlewareEvlogSetup(content, serviceName, fsDrain) {
|
|
1914
|
+
let nextContent = prependMissingImports(content, ["import { createRequestLogger, initLogger } from \"evlog\";", ...fsDrain ? ["import { createFsDrain } from \"evlog/fs\";"] : []]);
|
|
1915
|
+
const initSnippet = getInitLoggerSnippet(serviceName, fsDrain, " ");
|
|
1649
1916
|
nextContent = insertBeforeOnce(nextContent, "export const onRequest", initSnippet, "initLogger({");
|
|
1917
|
+
if (fsDrain && !nextContent.includes("drain:")) nextContent = nextContent.replace(/initLogger\(\{\n(\s+env: \{ service: "[^"]+" \},)/, `initLogger({\n$1\n drain: ${ASTRO_DEV_FS_DRAIN_EXPRESSION},`);
|
|
1650
1918
|
if (nextContent.includes("createRequestLogger({")) return nextContent;
|
|
1651
1919
|
const contextMarker = "export const onRequest = defineMiddleware(async (context, next) => {";
|
|
1652
1920
|
if (nextContent.includes(contextMarker)) {
|
|
@@ -1721,7 +1989,8 @@ function addSvelteBetterAuthEvlogSetup(content, config) {
|
|
|
1721
1989
|
const authExpression = getAuthExpression(config);
|
|
1722
1990
|
const authOptions = "{ exclude: [\"/api/auth/**\"], maskEmail: true }";
|
|
1723
1991
|
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`;
|
|
1724
|
-
|
|
1992
|
+
const evlogHandleDeclaration = nextContent.match(/const \{ handle: evlogHandle, handleError \} = createEvlogHooks\([\s\S]*?\);\n\n/)?.[0];
|
|
1993
|
+
if (evlogHandleDeclaration) nextContent = insertAfterOnce(nextContent, evlogHandleDeclaration, authHandleSnippet, "evlogAuthHandle");
|
|
1725
1994
|
return nextContent.replace("sequence(evlogHandle as Handle, authHandle)", "sequence(evlogHandle as Handle, evlogAuthHandle, authHandle)").replace("sequence(evlogHandle, authHandle)", "sequence(evlogHandle as Handle, evlogAuthHandle, authHandle)");
|
|
1726
1995
|
}
|
|
1727
1996
|
function addAstroBetterAuthEvlogSetup(content, config) {
|
|
@@ -1740,19 +2009,29 @@ function addAstroBetterAuthEvlogSetup(content, config) {
|
|
|
1740
2009
|
}
|
|
1741
2010
|
return nextContent;
|
|
1742
2011
|
}
|
|
1743
|
-
function getNextEvlogFile(serviceName) {
|
|
2012
|
+
function getNextEvlogFile(serviceName, fsDrain) {
|
|
1744
2013
|
return `import { createEvlog } from "evlog/next";
|
|
1745
2014
|
import { createInstrumentation } from "evlog/next/instrumentation/create";
|
|
2015
|
+
${fsDrain ? "import { createFsDrain } from \"evlog/fs\";\n" : ""}
|
|
1746
2016
|
|
|
1747
2017
|
export const { withEvlog, useLogger, log, createError } = createEvlog({
|
|
1748
2018
|
service: "${serviceName}",
|
|
1749
|
-
});
|
|
2019
|
+
${fsDrain ? ` drain: ${NODE_DEV_FS_DRAIN_EXPRESSION},\n` : ""}});
|
|
1750
2020
|
|
|
1751
2021
|
export const { register, onRequestError } = createInstrumentation({
|
|
1752
2022
|
service: "${serviceName}",
|
|
1753
2023
|
});
|
|
1754
2024
|
`;
|
|
1755
2025
|
}
|
|
2026
|
+
function getNitroEvlogDrainFile() {
|
|
2027
|
+
return `import { createFsDrain } from "evlog/fs";
|
|
2028
|
+
|
|
2029
|
+
export default defineNitroPlugin((nitroApp) => {
|
|
2030
|
+
if (!import.meta.dev) return;
|
|
2031
|
+
nitroApp.hooks.hook("evlog:drain", createFsDrain());
|
|
2032
|
+
});
|
|
2033
|
+
`;
|
|
2034
|
+
}
|
|
1756
2035
|
function getNextInstrumentationFile() {
|
|
1757
2036
|
return `import { defineNodeInstrumentation } from "evlog/next/instrumentation";
|
|
1758
2037
|
|
|
@@ -1867,13 +2146,12 @@ export default defineConfig({
|
|
|
1867
2146
|
});
|
|
1868
2147
|
`;
|
|
1869
2148
|
}
|
|
1870
|
-
function getAstroMiddlewareFile(serviceName) {
|
|
2149
|
+
function getAstroMiddlewareFile(serviceName, fsDrain) {
|
|
1871
2150
|
return `import { defineMiddleware } from "astro:middleware";
|
|
1872
2151
|
import { createRequestLogger, initLogger } from "evlog";
|
|
2152
|
+
${fsDrain ? "import { createFsDrain } from \"evlog/fs\";\n" : ""}
|
|
1873
2153
|
|
|
1874
|
-
|
|
1875
|
-
env: { service: "${serviceName}" },
|
|
1876
|
-
});
|
|
2154
|
+
${getInitLoggerSnippet(serviceName, fsDrain, " ").trimEnd()}
|
|
1877
2155
|
|
|
1878
2156
|
export const onRequest = defineMiddleware(async ({ request, locals }, next) => {
|
|
1879
2157
|
const url = new URL(request.url);
|
|
@@ -1910,8 +2188,9 @@ declare namespace App {
|
|
|
1910
2188
|
}
|
|
1911
2189
|
async function setupNextEvlog(config, serviceName) {
|
|
1912
2190
|
const webDir = path.join(config.projectDir, "apps/web");
|
|
2191
|
+
const fsDrain = shouldWireEvlogWebFsDrain(config);
|
|
1913
2192
|
const evlogPath = path.join(webDir, "src/lib/evlog.ts");
|
|
1914
|
-
if (!await fs.pathExists(evlogPath)) await writeFileIfChanged(evlogPath, getNextEvlogFile(serviceName));
|
|
2193
|
+
if (!await fs.pathExists(evlogPath)) await writeFileIfChanged(evlogPath, getNextEvlogFile(serviceName, fsDrain));
|
|
1915
2194
|
const identifyWebAuth = shouldIdentifyWebAuth(config);
|
|
1916
2195
|
if (identifyWebAuth) {
|
|
1917
2196
|
const evlogAuthPath = path.join(webDir, "src/lib/evlog-auth.ts");
|
|
@@ -1937,7 +2216,12 @@ async function setupNextEvlog(config, serviceName) {
|
|
|
1937
2216
|
}
|
|
1938
2217
|
async function setupNuxtEvlog(config, serviceName) {
|
|
1939
2218
|
const webDir = path.join(config.projectDir, "apps/web");
|
|
2219
|
+
const fsDrain = shouldWireEvlogWebFsDrain(config);
|
|
1940
2220
|
await updateFileIfExists(path.join(webDir, "nuxt.config.ts"), (content) => addNuxtEvlogSetup(content, serviceName));
|
|
2221
|
+
if (fsDrain) {
|
|
2222
|
+
const drainPath = path.join(webDir, "server/plugins/evlog-drain.ts");
|
|
2223
|
+
if (!await fs.pathExists(drainPath)) await writeFileIfChanged(drainPath, getNitroEvlogDrainFile());
|
|
2224
|
+
}
|
|
1941
2225
|
if (shouldIdentifyWebAuth(config)) {
|
|
1942
2226
|
const oldAuthPluginPath = path.join(webDir, "server/plugins/evlog-auth.ts");
|
|
1943
2227
|
if (await fs.pathExists(oldAuthPluginPath)) {
|
|
@@ -1950,12 +2234,15 @@ async function setupNuxtEvlog(config, serviceName) {
|
|
|
1950
2234
|
}
|
|
1951
2235
|
async function setupSvelteEvlog(config, serviceName) {
|
|
1952
2236
|
const webDir = path.join(config.projectDir, "apps/web");
|
|
2237
|
+
const fsDrain = shouldWireEvlogWebFsDrain(config);
|
|
1953
2238
|
await updateFileIfExists(path.join(webDir, "vite.config.ts"), (content) => addSvelteViteEvlogSetup(content, serviceName));
|
|
1954
2239
|
const hooksPath = path.join(webDir, "src/hooks.server.ts");
|
|
1955
|
-
if (await fs.pathExists(hooksPath)) await updateFileIfExists(hooksPath, addSvelteHooksEvlogSetup);
|
|
2240
|
+
if (await fs.pathExists(hooksPath)) await updateFileIfExists(hooksPath, (content) => addSvelteHooksEvlogSetup(content, fsDrain));
|
|
1956
2241
|
else await writeFileIfChanged(hooksPath, `import { createEvlogHooks } from "evlog/sveltekit";
|
|
2242
|
+
${fsDrain ? "import { createFsDrain } from \"evlog/fs\";\n" : ""}
|
|
2243
|
+
${fsDrain ? "import { dev } from \"$app/environment\";\n" : ""}
|
|
1957
2244
|
|
|
1958
|
-
export const { handle, handleError } =
|
|
2245
|
+
export const { handle, handleError } = ${getSvelteEvlogHooksCall(fsDrain)};
|
|
1959
2246
|
`);
|
|
1960
2247
|
await updateFileIfExists(path.join(webDir, "src/app.d.ts"), addSvelteLocalsType);
|
|
1961
2248
|
if (shouldIdentifyWebAuth(config)) await updateFileIfExists(path.join(webDir, "src/hooks.server.ts"), (content) => addSvelteBetterAuthEvlogSetup(content, config));
|
|
@@ -1963,9 +2250,14 @@ export const { handle, handleError } = createEvlogHooks();
|
|
|
1963
2250
|
}
|
|
1964
2251
|
async function setupTanstackStartEvlog(config, serviceName) {
|
|
1965
2252
|
const webDir = path.join(config.projectDir, "apps/web");
|
|
2253
|
+
const fsDrain = shouldWireEvlogWebFsDrain(config);
|
|
1966
2254
|
const nitroConfigPath = path.join(webDir, "nitro.config.ts");
|
|
1967
2255
|
if (!await fs.pathExists(nitroConfigPath)) await writeFileIfChanged(nitroConfigPath, getTanstackNitroConfigFile(serviceName));
|
|
1968
2256
|
await updateFileIfExists(path.join(webDir, "src/routes/__root.tsx"), addTanstackStartRootEvlogSetup);
|
|
2257
|
+
if (fsDrain) {
|
|
2258
|
+
const drainPath = path.join(webDir, "server/plugins/evlog-drain.ts");
|
|
2259
|
+
if (!await fs.pathExists(drainPath)) await writeFileIfChanged(drainPath, getNitroEvlogDrainFile());
|
|
2260
|
+
}
|
|
1969
2261
|
if (shouldIdentifyWebAuth(config)) {
|
|
1970
2262
|
const authPluginPath = path.join(webDir, "server/plugins/evlog-auth.ts");
|
|
1971
2263
|
if (!await fs.pathExists(authPluginPath)) await writeFileIfChanged(authPluginPath, getNitroEvlogAuthPluginFile(config));
|
|
@@ -1974,9 +2266,10 @@ async function setupTanstackStartEvlog(config, serviceName) {
|
|
|
1974
2266
|
}
|
|
1975
2267
|
async function setupAstroEvlog(config, serviceName) {
|
|
1976
2268
|
const webDir = path.join(config.projectDir, "apps/web");
|
|
2269
|
+
const fsDrain = shouldWireEvlogWebFsDrain(config);
|
|
1977
2270
|
const middlewarePath = path.join(webDir, "src/middleware.ts");
|
|
1978
|
-
if (!await fs.pathExists(middlewarePath)) await writeFileIfChanged(middlewarePath, getAstroMiddlewareFile(serviceName));
|
|
1979
|
-
else await updateFileIfExists(middlewarePath, (content) => addAstroMiddlewareEvlogSetup(content, serviceName));
|
|
2271
|
+
if (!await fs.pathExists(middlewarePath)) await writeFileIfChanged(middlewarePath, getAstroMiddlewareFile(serviceName, fsDrain));
|
|
2272
|
+
else await updateFileIfExists(middlewarePath, (content) => addAstroMiddlewareEvlogSetup(content, serviceName, fsDrain));
|
|
1980
2273
|
const envPath = path.join(webDir, "src/env.d.ts");
|
|
1981
2274
|
if (!await fs.pathExists(envPath)) await writeFileIfChanged(envPath, getAstroEnvFile());
|
|
1982
2275
|
else await updateFileIfExists(envPath, addAstroLocalsType);
|
|
@@ -1999,7 +2292,7 @@ async function setupEvlog(config) {
|
|
|
1999
2292
|
const serverIndexPath = path.join(config.projectDir, "apps/server/src/index.ts");
|
|
2000
2293
|
if (await fs.pathExists(serverIndexPath)) {
|
|
2001
2294
|
const content = await fs.readFile(serverIndexPath, "utf-8");
|
|
2002
|
-
let nextContent = addEvlogServerSetup(content, config.backend, `${config.projectName}-server
|
|
2295
|
+
let nextContent = addEvlogServerSetup(content, config.backend, `${config.projectName}-server`, shouldWireEvlogServerFsDrain(config));
|
|
2003
2296
|
if (config.auth === "better-auth") nextContent = addEvlogBetterAuthServerSetup(nextContent, config.backend, getAuthExpression(config));
|
|
2004
2297
|
if (config.examples.includes("ai")) nextContent = addBackendAiEvlogSetup(nextContent, config.backend);
|
|
2005
2298
|
if (nextContent !== content) await fs.writeFile(serverIndexPath, nextContent);
|
|
@@ -2037,45 +2330,56 @@ async function navigableGroup(prompts, opts) {
|
|
|
2037
2330
|
delete results[prevName];
|
|
2038
2331
|
currentIndex--;
|
|
2039
2332
|
};
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2333
|
+
try {
|
|
2334
|
+
while (currentIndex < promptNames.length) {
|
|
2335
|
+
const name = promptNames[currentIndex];
|
|
2336
|
+
const prompt = prompts[name];
|
|
2337
|
+
const section = opts?.sections?.find(({ prompts: sectionPrompts }) => sectionPrompts.includes(name));
|
|
2338
|
+
const sectionPromptIndex = section?.prompts.indexOf(name) ?? -1;
|
|
2339
|
+
setPromptProgress({
|
|
2340
|
+
current: currentIndex + 1,
|
|
2341
|
+
total: promptNames.length,
|
|
2342
|
+
section: section?.label ?? "Setup",
|
|
2343
|
+
sectionCurrent: sectionPromptIndex + 1,
|
|
2344
|
+
sectionTotal: section?.prompts.length ?? promptNames.length
|
|
2345
|
+
});
|
|
2346
|
+
setIsFirstPrompt$1(currentIndex === 0);
|
|
2347
|
+
setLastPromptShownUI(false);
|
|
2348
|
+
const presetResult = opts?.preselected?.[name];
|
|
2349
|
+
const result = presetResult !== void 0 ? presetResult : await prompt({
|
|
2350
|
+
results,
|
|
2351
|
+
previousAnswer: previousAnswers[name]
|
|
2352
|
+
});
|
|
2353
|
+
if (isGoBack(result)) {
|
|
2354
|
+
goingBack = true;
|
|
2355
|
+
if (currentIndex > 0) {
|
|
2356
|
+
stepBack();
|
|
2357
|
+
continue;
|
|
2358
|
+
}
|
|
2359
|
+
goingBack = false;
|
|
2055
2360
|
continue;
|
|
2056
2361
|
}
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
results
|
|
2063
|
-
opts.onCancel({ results });
|
|
2362
|
+
if (isCancel$1(result)) {
|
|
2363
|
+
if (typeof opts?.onCancel === "function") {
|
|
2364
|
+
results[name] = "canceled";
|
|
2365
|
+
opts.onCancel({ results });
|
|
2366
|
+
}
|
|
2367
|
+
return results;
|
|
2064
2368
|
}
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
stepBack();
|
|
2071
|
-
continue;
|
|
2369
|
+
if (goingBack && !didLastPromptShowUI()) {
|
|
2370
|
+
if (currentIndex > 0) {
|
|
2371
|
+
stepBack();
|
|
2372
|
+
continue;
|
|
2373
|
+
}
|
|
2072
2374
|
}
|
|
2375
|
+
goingBack = false;
|
|
2376
|
+
results[name] = result;
|
|
2377
|
+
currentIndex++;
|
|
2073
2378
|
}
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2379
|
+
} finally {
|
|
2380
|
+
setIsFirstPrompt$1(false);
|
|
2381
|
+
setPromptProgress(void 0);
|
|
2077
2382
|
}
|
|
2078
|
-
setIsFirstPrompt$1(false);
|
|
2079
2383
|
return results;
|
|
2080
2384
|
}
|
|
2081
2385
|
//#endregion
|
|
@@ -2210,6 +2514,10 @@ const TEMPLATES$2 = {
|
|
|
2210
2514
|
label: "Tanstack Start SPA: Fumadocs MDX (not RSC)",
|
|
2211
2515
|
hint: "SPA mode allows you to host the site statically, compatible with a CDN.",
|
|
2212
2516
|
value: "tanstack-start-spa"
|
|
2517
|
+
},
|
|
2518
|
+
astro: {
|
|
2519
|
+
label: "Astro: Fumadocs MDX",
|
|
2520
|
+
value: "astro"
|
|
2213
2521
|
}
|
|
2214
2522
|
};
|
|
2215
2523
|
const DEFAULT_TEMPLATE$2 = "next-mdx";
|
|
@@ -2223,7 +2531,7 @@ function getFumadocsLinter(addons) {
|
|
|
2223
2531
|
if (addons.includes("vite-plus")) return "oxlint";
|
|
2224
2532
|
}
|
|
2225
2533
|
function getFumadocsAddonContext(currentAddons, persistedAddons) {
|
|
2226
|
-
return Array.from(new Set([...persistedAddons ?? [], ...currentAddons]));
|
|
2534
|
+
return Array.from(/* @__PURE__ */ new Set([...persistedAddons ?? [], ...currentAddons]));
|
|
2227
2535
|
}
|
|
2228
2536
|
async function setupFumadocs(config) {
|
|
2229
2537
|
if (shouldSkipExternalCommands()) return Result.ok(void 0);
|
|
@@ -2425,6 +2733,11 @@ const MCP_AGENTS = [
|
|
|
2425
2733
|
label: "GitHub Copilot CLI",
|
|
2426
2734
|
scope: "both"
|
|
2427
2735
|
},
|
|
2736
|
+
{
|
|
2737
|
+
value: "grok-build",
|
|
2738
|
+
label: "Grok Build",
|
|
2739
|
+
scope: "both"
|
|
2740
|
+
},
|
|
2428
2741
|
{
|
|
2429
2742
|
value: "mcporter",
|
|
2430
2743
|
label: "MCPorter",
|
|
@@ -2435,6 +2748,11 @@ const MCP_AGENTS = [
|
|
|
2435
2748
|
label: "VS Code (GitHub Copilot)",
|
|
2436
2749
|
scope: "both"
|
|
2437
2750
|
},
|
|
2751
|
+
{
|
|
2752
|
+
value: "windsurf",
|
|
2753
|
+
label: "Windsurf",
|
|
2754
|
+
scope: "global"
|
|
2755
|
+
},
|
|
2438
2756
|
{
|
|
2439
2757
|
value: "zed",
|
|
2440
2758
|
label: "Zed",
|
|
@@ -2808,10 +3126,42 @@ const SKILL_SOURCES = {
|
|
|
2808
3126
|
"haydenbleasel/ultracite": { label: "Ultracite" },
|
|
2809
3127
|
"https://www.evlog.dev": { label: "evlog" }
|
|
2810
3128
|
};
|
|
2811
|
-
const
|
|
3129
|
+
const SKILLS_CLI_AGENT_OPTIONS = [
|
|
2812
3130
|
{
|
|
2813
|
-
value: "
|
|
2814
|
-
label: "
|
|
3131
|
+
value: "adal",
|
|
3132
|
+
label: "AdaL"
|
|
3133
|
+
},
|
|
3134
|
+
{
|
|
3135
|
+
value: "aider-desk",
|
|
3136
|
+
label: "AiderDesk"
|
|
3137
|
+
},
|
|
3138
|
+
{
|
|
3139
|
+
value: "amp",
|
|
3140
|
+
label: "Amp"
|
|
3141
|
+
},
|
|
3142
|
+
{
|
|
3143
|
+
value: "antigravity",
|
|
3144
|
+
label: "Antigravity"
|
|
3145
|
+
},
|
|
3146
|
+
{
|
|
3147
|
+
value: "antigravity-cli",
|
|
3148
|
+
label: "Antigravity CLI"
|
|
3149
|
+
},
|
|
3150
|
+
{
|
|
3151
|
+
value: "astrbot",
|
|
3152
|
+
label: "AstrBot"
|
|
3153
|
+
},
|
|
3154
|
+
{
|
|
3155
|
+
value: "augment",
|
|
3156
|
+
label: "Augment"
|
|
3157
|
+
},
|
|
3158
|
+
{
|
|
3159
|
+
value: "autohand-code",
|
|
3160
|
+
label: "Autohand Code CLI"
|
|
3161
|
+
},
|
|
3162
|
+
{
|
|
3163
|
+
value: "bob",
|
|
3164
|
+
label: "IBM Bob"
|
|
2815
3165
|
},
|
|
2816
3166
|
{
|
|
2817
3167
|
value: "claude-code",
|
|
@@ -2822,100 +3172,306 @@ const AVAILABLE_AGENTS = [
|
|
|
2822
3172
|
label: "Cline"
|
|
2823
3173
|
},
|
|
2824
3174
|
{
|
|
2825
|
-
value: "
|
|
2826
|
-
label: "
|
|
3175
|
+
value: "codearts-agent",
|
|
3176
|
+
label: "CodeArts Agent"
|
|
3177
|
+
},
|
|
3178
|
+
{
|
|
3179
|
+
value: "codebuddy",
|
|
3180
|
+
label: "CodeBuddy"
|
|
3181
|
+
},
|
|
3182
|
+
{
|
|
3183
|
+
value: "codemaker",
|
|
3184
|
+
label: "Codemaker"
|
|
3185
|
+
},
|
|
3186
|
+
{
|
|
3187
|
+
value: "codestudio",
|
|
3188
|
+
label: "Code Studio"
|
|
2827
3189
|
},
|
|
2828
3190
|
{
|
|
2829
3191
|
value: "codex",
|
|
2830
3192
|
label: "Codex"
|
|
2831
3193
|
},
|
|
2832
3194
|
{
|
|
2833
|
-
value: "
|
|
2834
|
-
label: "
|
|
3195
|
+
value: "command-code",
|
|
3196
|
+
label: "Command Code"
|
|
2835
3197
|
},
|
|
2836
3198
|
{
|
|
2837
|
-
value: "
|
|
2838
|
-
label: "
|
|
3199
|
+
value: "continue",
|
|
3200
|
+
label: "Continue"
|
|
3201
|
+
},
|
|
3202
|
+
{
|
|
3203
|
+
value: "cortex",
|
|
3204
|
+
label: "Cortex Code"
|
|
3205
|
+
},
|
|
3206
|
+
{
|
|
3207
|
+
value: "crush",
|
|
3208
|
+
label: "Crush"
|
|
3209
|
+
},
|
|
3210
|
+
{
|
|
3211
|
+
value: "cursor",
|
|
3212
|
+
label: "Cursor"
|
|
3213
|
+
},
|
|
3214
|
+
{
|
|
3215
|
+
value: "deepagents",
|
|
3216
|
+
label: "Deep Agents"
|
|
3217
|
+
},
|
|
3218
|
+
{
|
|
3219
|
+
value: "devin",
|
|
3220
|
+
label: "Devin for Terminal"
|
|
3221
|
+
},
|
|
3222
|
+
{
|
|
3223
|
+
value: "dexto",
|
|
3224
|
+
label: "Dexto"
|
|
3225
|
+
},
|
|
3226
|
+
{
|
|
3227
|
+
value: "droid",
|
|
3228
|
+
label: "Droid"
|
|
3229
|
+
},
|
|
3230
|
+
{
|
|
3231
|
+
value: "eve",
|
|
3232
|
+
label: "Eve"
|
|
3233
|
+
},
|
|
3234
|
+
{
|
|
3235
|
+
value: "firebender",
|
|
3236
|
+
label: "Firebender"
|
|
3237
|
+
},
|
|
3238
|
+
{
|
|
3239
|
+
value: "forgecode",
|
|
3240
|
+
label: "ForgeCode"
|
|
3241
|
+
},
|
|
3242
|
+
{
|
|
3243
|
+
value: "gemini-cli",
|
|
3244
|
+
label: "Gemini CLI"
|
|
3245
|
+
},
|
|
3246
|
+
{
|
|
3247
|
+
value: "github-copilot",
|
|
3248
|
+
label: "GitHub Copilot"
|
|
2839
3249
|
},
|
|
2840
3250
|
{
|
|
2841
3251
|
value: "goose",
|
|
2842
3252
|
label: "Goose"
|
|
2843
3253
|
},
|
|
2844
3254
|
{
|
|
2845
|
-
value: "
|
|
2846
|
-
label: "
|
|
3255
|
+
value: "grok",
|
|
3256
|
+
label: "Grok Build"
|
|
3257
|
+
},
|
|
3258
|
+
{
|
|
3259
|
+
value: "hermes-agent",
|
|
3260
|
+
label: "Hermes Agent"
|
|
3261
|
+
},
|
|
3262
|
+
{
|
|
3263
|
+
value: "iflow-cli",
|
|
3264
|
+
label: "iFlow CLI"
|
|
3265
|
+
},
|
|
3266
|
+
{
|
|
3267
|
+
value: "inference-sh",
|
|
3268
|
+
label: "inference.sh"
|
|
3269
|
+
},
|
|
3270
|
+
{
|
|
3271
|
+
value: "jazz",
|
|
3272
|
+
label: "Jazz"
|
|
3273
|
+
},
|
|
3274
|
+
{
|
|
3275
|
+
value: "junie",
|
|
3276
|
+
label: "Junie"
|
|
2847
3277
|
},
|
|
2848
3278
|
{
|
|
2849
3279
|
value: "kilo",
|
|
2850
3280
|
label: "Kilo Code"
|
|
2851
3281
|
},
|
|
2852
3282
|
{
|
|
2853
|
-
value: "
|
|
2854
|
-
label: "
|
|
3283
|
+
value: "kimchi",
|
|
3284
|
+
label: "Kimchi"
|
|
2855
3285
|
},
|
|
2856
3286
|
{
|
|
2857
|
-
value: "
|
|
2858
|
-
label: "
|
|
3287
|
+
value: "kimi-code-cli",
|
|
3288
|
+
label: "Kimi Code CLI"
|
|
2859
3289
|
},
|
|
2860
3290
|
{
|
|
2861
|
-
value: "
|
|
2862
|
-
label: "
|
|
3291
|
+
value: "kiro-cli",
|
|
3292
|
+
label: "Kiro CLI"
|
|
2863
3293
|
},
|
|
2864
3294
|
{
|
|
2865
|
-
value: "
|
|
2866
|
-
label: "
|
|
3295
|
+
value: "kode",
|
|
3296
|
+
label: "Kode"
|
|
2867
3297
|
},
|
|
2868
3298
|
{
|
|
2869
|
-
value: "
|
|
2870
|
-
label: "
|
|
3299
|
+
value: "lingma",
|
|
3300
|
+
label: "Lingma"
|
|
3301
|
+
},
|
|
3302
|
+
{
|
|
3303
|
+
value: "loaf",
|
|
3304
|
+
label: "Loaf"
|
|
3305
|
+
},
|
|
3306
|
+
{
|
|
3307
|
+
value: "mcpjam",
|
|
3308
|
+
label: "MCPJam"
|
|
3309
|
+
},
|
|
3310
|
+
{
|
|
3311
|
+
value: "mistral-vibe",
|
|
3312
|
+
label: "Mistral Vibe"
|
|
3313
|
+
},
|
|
3314
|
+
{
|
|
3315
|
+
value: "moxby",
|
|
3316
|
+
label: "Moxby"
|
|
3317
|
+
},
|
|
3318
|
+
{
|
|
3319
|
+
value: "mux",
|
|
3320
|
+
label: "Mux"
|
|
3321
|
+
},
|
|
3322
|
+
{
|
|
3323
|
+
value: "neovate",
|
|
3324
|
+
label: "Neovate"
|
|
3325
|
+
},
|
|
3326
|
+
{
|
|
3327
|
+
value: "ona",
|
|
3328
|
+
label: "Ona"
|
|
3329
|
+
},
|
|
3330
|
+
{
|
|
3331
|
+
value: "openclaw",
|
|
3332
|
+
label: "OpenClaw"
|
|
3333
|
+
},
|
|
3334
|
+
{
|
|
3335
|
+
value: "opencode",
|
|
3336
|
+
label: "OpenCode"
|
|
3337
|
+
},
|
|
3338
|
+
{
|
|
3339
|
+
value: "openhands",
|
|
3340
|
+
label: "OpenHands"
|
|
2871
3341
|
},
|
|
2872
3342
|
{
|
|
2873
3343
|
value: "pi",
|
|
2874
3344
|
label: "Pi"
|
|
2875
3345
|
},
|
|
3346
|
+
{
|
|
3347
|
+
value: "pochi",
|
|
3348
|
+
label: "Pochi"
|
|
3349
|
+
},
|
|
3350
|
+
{
|
|
3351
|
+
value: "promptscript",
|
|
3352
|
+
label: "PromptScript"
|
|
3353
|
+
},
|
|
2876
3354
|
{
|
|
2877
3355
|
value: "qoder",
|
|
2878
3356
|
label: "Qoder"
|
|
2879
3357
|
},
|
|
3358
|
+
{
|
|
3359
|
+
value: "qoder-cn",
|
|
3360
|
+
label: "Qoder CN"
|
|
3361
|
+
},
|
|
2880
3362
|
{
|
|
2881
3363
|
value: "qwen-code",
|
|
2882
3364
|
label: "Qwen Code"
|
|
2883
3365
|
},
|
|
2884
3366
|
{
|
|
2885
|
-
value: "
|
|
2886
|
-
label: "
|
|
3367
|
+
value: "reasonix",
|
|
3368
|
+
label: "Reasonix"
|
|
2887
3369
|
},
|
|
2888
3370
|
{
|
|
2889
|
-
value: "
|
|
2890
|
-
label: "
|
|
3371
|
+
value: "replit",
|
|
3372
|
+
label: "Replit"
|
|
2891
3373
|
},
|
|
2892
3374
|
{
|
|
2893
|
-
value: "
|
|
2894
|
-
label: "
|
|
3375
|
+
value: "roo",
|
|
3376
|
+
label: "Roo Code"
|
|
2895
3377
|
},
|
|
2896
3378
|
{
|
|
2897
|
-
value: "
|
|
2898
|
-
label: "
|
|
3379
|
+
value: "rovodev",
|
|
3380
|
+
label: "Rovo Dev"
|
|
2899
3381
|
},
|
|
2900
3382
|
{
|
|
2901
|
-
value: "
|
|
2902
|
-
label: "
|
|
3383
|
+
value: "tabnine-cli",
|
|
3384
|
+
label: "Tabnine CLI"
|
|
2903
3385
|
},
|
|
2904
3386
|
{
|
|
2905
|
-
value: "
|
|
2906
|
-
label: "
|
|
3387
|
+
value: "terramind",
|
|
3388
|
+
label: "Terramind"
|
|
2907
3389
|
},
|
|
2908
3390
|
{
|
|
2909
|
-
value: "
|
|
2910
|
-
label: "
|
|
3391
|
+
value: "tinycloud",
|
|
3392
|
+
label: "Tinycloud"
|
|
3393
|
+
},
|
|
3394
|
+
{
|
|
3395
|
+
value: "trae",
|
|
3396
|
+
label: "Trae"
|
|
3397
|
+
},
|
|
3398
|
+
{
|
|
3399
|
+
value: "trae-cn",
|
|
3400
|
+
label: "Trae CN"
|
|
3401
|
+
},
|
|
3402
|
+
{
|
|
3403
|
+
value: "universal",
|
|
3404
|
+
label: "Universal"
|
|
3405
|
+
},
|
|
3406
|
+
{
|
|
3407
|
+
value: "warp",
|
|
3408
|
+
label: "Warp"
|
|
3409
|
+
},
|
|
3410
|
+
{
|
|
3411
|
+
value: "windsurf",
|
|
3412
|
+
label: "Windsurf"
|
|
3413
|
+
},
|
|
3414
|
+
{
|
|
3415
|
+
value: "zcode",
|
|
3416
|
+
label: "ZCode"
|
|
3417
|
+
},
|
|
3418
|
+
{
|
|
3419
|
+
value: "zed",
|
|
3420
|
+
label: "Zed"
|
|
3421
|
+
},
|
|
3422
|
+
{
|
|
3423
|
+
value: "zencoder",
|
|
3424
|
+
label: "Zencoder"
|
|
3425
|
+
},
|
|
3426
|
+
{
|
|
3427
|
+
value: "zenflow",
|
|
3428
|
+
label: "Zenflow"
|
|
2911
3429
|
}
|
|
2912
3430
|
];
|
|
2913
|
-
const
|
|
2914
|
-
|
|
3431
|
+
const UNIVERSAL_SKILLS_AGENTS = [
|
|
3432
|
+
"amp",
|
|
3433
|
+
"antigravity",
|
|
3434
|
+
"antigravity-cli",
|
|
3435
|
+
"cline",
|
|
3436
|
+
"codex",
|
|
2915
3437
|
"cursor",
|
|
2916
|
-
"
|
|
2917
|
-
"
|
|
3438
|
+
"deepagents",
|
|
3439
|
+
"dexto",
|
|
3440
|
+
"firebender",
|
|
3441
|
+
"gemini-cli",
|
|
3442
|
+
"github-copilot",
|
|
3443
|
+
"kimi-code-cli",
|
|
3444
|
+
"loaf",
|
|
3445
|
+
"opencode",
|
|
3446
|
+
"promptscript",
|
|
3447
|
+
"warp",
|
|
3448
|
+
"zed"
|
|
2918
3449
|
];
|
|
3450
|
+
const PROMPT_HIDDEN_AGENTS = /* @__PURE__ */ new Set([
|
|
3451
|
+
...UNIVERSAL_SKILLS_AGENTS,
|
|
3452
|
+
"universal",
|
|
3453
|
+
"eve",
|
|
3454
|
+
"replit"
|
|
3455
|
+
]);
|
|
3456
|
+
const SKILLS_AGENT_PROMPT_OPTIONS = [
|
|
3457
|
+
{
|
|
3458
|
+
value: "universal",
|
|
3459
|
+
label: "Universal (.agents/skills)",
|
|
3460
|
+
hint: "17 agents including Amp, Antigravity, Cline, Codex, Cursor, Gemini CLI, Copilot, OpenCode, Warp, and Zed"
|
|
3461
|
+
},
|
|
3462
|
+
...SKILLS_CLI_AGENT_OPTIONS.filter(({ value }) => value === "claude-code"),
|
|
3463
|
+
...SKILLS_CLI_AGENT_OPTIONS.filter(({ value }) => value !== "claude-code" && !PROMPT_HIDDEN_AGENTS.has(value))
|
|
3464
|
+
];
|
|
3465
|
+
function expandSkillsAgentTargets(agents) {
|
|
3466
|
+
const expanded = agents.flatMap((agent) => {
|
|
3467
|
+
if (agent === "universal") return [...UNIVERSAL_SKILLS_AGENTS];
|
|
3468
|
+
if (agent === "clawdbot") return ["openclaw"];
|
|
3469
|
+
return [agent];
|
|
3470
|
+
});
|
|
3471
|
+
return Array.from(new Set(expanded));
|
|
3472
|
+
}
|
|
3473
|
+
const DEFAULT_SCOPE = "project";
|
|
3474
|
+
const DEFAULT_AGENTS$1 = ["universal", "claude-code"];
|
|
2919
3475
|
function hasReactBasedFrontend(frontend) {
|
|
2920
3476
|
return frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("tanstack-start") || frontend.includes("next");
|
|
2921
3477
|
}
|
|
@@ -3017,7 +3573,11 @@ const CURATED_SKILLS_BY_SOURCE = {
|
|
|
3017
3573
|
],
|
|
3018
3574
|
"msmps/opentui-skill": () => ["opentui"],
|
|
3019
3575
|
"haydenbleasel/ultracite": () => ["ultracite"],
|
|
3020
|
-
"https://www.evlog.dev": () => [
|
|
3576
|
+
"https://www.evlog.dev": (config) => [
|
|
3577
|
+
"review-logging-patterns",
|
|
3578
|
+
"build-audit-logs",
|
|
3579
|
+
...supportsEvlogLocalLogs(config) ? ["analyze-logs"] : []
|
|
3580
|
+
]
|
|
3021
3581
|
};
|
|
3022
3582
|
function getCuratedSkillNamesForSourceKey(sourceKey, config) {
|
|
3023
3583
|
return CURATED_SKILLS_BY_SOURCE[sourceKey](config);
|
|
@@ -3094,9 +3654,10 @@ async function setupSkills(config) {
|
|
|
3094
3654
|
if (configuredAgents !== void 0) return [...configuredAgents];
|
|
3095
3655
|
return navigableMultiselect({
|
|
3096
3656
|
message: "Select agents to install skills to",
|
|
3097
|
-
options:
|
|
3657
|
+
options: SKILLS_AGENT_PROMPT_OPTIONS,
|
|
3098
3658
|
required: false,
|
|
3099
|
-
initialValues: [...DEFAULT_AGENTS$1]
|
|
3659
|
+
initialValues: [...DEFAULT_AGENTS$1],
|
|
3660
|
+
maxItems: 10
|
|
3100
3661
|
});
|
|
3101
3662
|
}
|
|
3102
3663
|
});
|
|
@@ -3116,6 +3677,7 @@ async function setupSkills(config) {
|
|
|
3116
3677
|
installSpinner.start("Installing skills...");
|
|
3117
3678
|
const runner = getPackageRunnerPrefix(packageManager);
|
|
3118
3679
|
const globalFlags = scope === "global" ? ["-g"] : [];
|
|
3680
|
+
const agentTargets = expandSkillsAgentTargets(selectedAgents);
|
|
3119
3681
|
for (const [source, skills] of Object.entries(skillsBySource)) if ((await Result.tryPromise({
|
|
3120
3682
|
try: async () => {
|
|
3121
3683
|
const args = [
|
|
@@ -3127,7 +3689,7 @@ async function setupSkills(config) {
|
|
|
3127
3689
|
"--skill",
|
|
3128
3690
|
...skills,
|
|
3129
3691
|
"--agent",
|
|
3130
|
-
...
|
|
3692
|
+
...agentTargets,
|
|
3131
3693
|
"-y"
|
|
3132
3694
|
];
|
|
3133
3695
|
await $({
|
|
@@ -3439,7 +4001,7 @@ const HOOKS = {
|
|
|
3439
4001
|
claude: { label: "Claude Code" },
|
|
3440
4002
|
copilot: { label: "GitHub Copilot" }
|
|
3441
4003
|
};
|
|
3442
|
-
const ULTRACITE_VERSION = "7.9.
|
|
4004
|
+
const ULTRACITE_VERSION = "7.9.4";
|
|
3443
4005
|
const DEFAULT_LINTER = "biome";
|
|
3444
4006
|
const DEFAULT_EDITORS = ["vscode"];
|
|
3445
4007
|
const DEFAULT_AGENTS = ["universal"];
|
|
@@ -3846,7 +4408,7 @@ async function installDependencies({ projectDir, packageManager }) {
|
|
|
3846
4408
|
cause: e
|
|
3847
4409
|
})
|
|
3848
4410
|
});
|
|
3849
|
-
if (result.isOk()) s.stop("Dependencies installed
|
|
4411
|
+
if (result.isOk()) s.stop("Dependencies installed");
|
|
3850
4412
|
else s.stop(pc.red("Failed to install dependencies"));
|
|
3851
4413
|
return result;
|
|
3852
4414
|
}
|
|
@@ -3951,7 +4513,7 @@ async function addHandlerInternal(input) {
|
|
|
3951
4513
|
}));
|
|
3952
4514
|
if (!isSilent()) {
|
|
3953
4515
|
renderTitle();
|
|
3954
|
-
intro(pc.magenta("Add
|
|
4516
|
+
intro(pc.magenta("Add to your project"));
|
|
3955
4517
|
}
|
|
3956
4518
|
const existingConfig = await detectProjectConfig(projectDir);
|
|
3957
4519
|
if (!existingConfig) return Result.err(new CLIError({ message: `No Better-T-Stack project found in ${projectDir}. Make sure bts.jsonc exists.` }));
|
|
@@ -3960,7 +4522,10 @@ async function addHandlerInternal(input) {
|
|
|
3960
4522
|
if (input.addons && input.addons.length > 0) {
|
|
3961
4523
|
addonsToAdd = input.addons.filter((addon) => addon !== "none" && !existingConfig.addons.includes(addon));
|
|
3962
4524
|
if (addonsToAdd.length === 0) {
|
|
3963
|
-
if (!isSilent())
|
|
4525
|
+
if (!isSilent()) {
|
|
4526
|
+
log.warn(pc.yellow("Nothing to add — those addons are already installed"));
|
|
4527
|
+
outro(pc.dim("Project unchanged"));
|
|
4528
|
+
}
|
|
3964
4529
|
return Result.ok({
|
|
3965
4530
|
success: true,
|
|
3966
4531
|
addedAddons: [],
|
|
@@ -3982,10 +4547,7 @@ async function addHandlerInternal(input) {
|
|
|
3982
4547
|
if (promptResult.isErr()) return Result.err(promptResult.error);
|
|
3983
4548
|
const selectedAddons = promptResult.value;
|
|
3984
4549
|
if (selectedAddons.length === 0) {
|
|
3985
|
-
if (!isSilent())
|
|
3986
|
-
log.info(pc.dim("No addons selected."));
|
|
3987
|
-
outro(pc.magenta("Nothing to add."));
|
|
3988
|
-
}
|
|
4550
|
+
if (!isSilent()) outro(pc.dim("Nothing selected · project unchanged"));
|
|
3989
4551
|
return Result.ok({
|
|
3990
4552
|
success: true,
|
|
3991
4553
|
addedAddons: [],
|
|
@@ -3997,7 +4559,7 @@ async function addHandlerInternal(input) {
|
|
|
3997
4559
|
const updatedAddons = [...existingConfig.addons, ...addonsToAdd];
|
|
3998
4560
|
const addonsValidationResult = validateAddonsAgainstConfig(updatedAddons, existingConfig);
|
|
3999
4561
|
if (addonsValidationResult.isErr()) return Result.err(new CLIError({ message: addonsValidationResult.error.message }));
|
|
4000
|
-
if (!isSilent()) log.info(pc.
|
|
4562
|
+
if (!isSilent()) log.info(`${pc.dim("Adding")} ${pc.cyan(formatConfigValue(addonsToAdd))}`);
|
|
4001
4563
|
const mergedAddonOptions = mergeAddonOptions(existingConfig.addonOptions, input.addonOptions);
|
|
4002
4564
|
const config = {
|
|
4003
4565
|
projectName: existingConfig.projectName,
|
|
@@ -4025,7 +4587,7 @@ async function addHandlerInternal(input) {
|
|
|
4025
4587
|
...config,
|
|
4026
4588
|
addons: updatedAddons
|
|
4027
4589
|
};
|
|
4028
|
-
if (!isSilent()) log.info(pc.dim("
|
|
4590
|
+
if (!isSilent()) log.info(pc.dim("Preparing addon files…"));
|
|
4029
4591
|
const vfs = new VirtualFileSystem();
|
|
4030
4592
|
for (const pkgPath of ADD_PACKAGE_JSON_PATHS) {
|
|
4031
4593
|
const fullPath = path.join(projectDir, pkgPath);
|
|
@@ -4057,9 +4619,9 @@ async function addHandlerInternal(input) {
|
|
|
4057
4619
|
};
|
|
4058
4620
|
if (input.dryRun) {
|
|
4059
4621
|
if (!isSilent()) {
|
|
4060
|
-
log.success(pc.green("Dry run
|
|
4061
|
-
log.
|
|
4062
|
-
outro(pc.
|
|
4622
|
+
log.success(pc.green("Dry run passed · no files written"));
|
|
4623
|
+
log.message(pc.dim(`${vfs.getFileCount()} addon files planned`));
|
|
4624
|
+
outro(pc.dim("Project unchanged"));
|
|
4063
4625
|
}
|
|
4064
4626
|
return Result.ok({
|
|
4065
4627
|
success: true,
|
|
@@ -4090,17 +4652,17 @@ async function addHandlerInternal(input) {
|
|
|
4090
4652
|
addons: updatedAddons,
|
|
4091
4653
|
addonOptions: updatedConfig.addonOptions
|
|
4092
4654
|
});
|
|
4093
|
-
if (input.install) {
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
packageManager: config.packageManager
|
|
4098
|
-
});
|
|
4099
|
-
}
|
|
4655
|
+
if (input.install) await installDependencies({
|
|
4656
|
+
projectDir,
|
|
4657
|
+
packageManager: config.packageManager
|
|
4658
|
+
});
|
|
4100
4659
|
if (!isSilent()) {
|
|
4101
|
-
log.success(pc.green(`
|
|
4102
|
-
if (!input.install)
|
|
4103
|
-
|
|
4660
|
+
log.success(pc.green(`Added ${formatConfigValue(addonsToAdd)}`));
|
|
4661
|
+
if (!input.install) {
|
|
4662
|
+
const installCommand = config.packageManager === "npm" ? "npm install" : `${config.packageManager} install`;
|
|
4663
|
+
log.message(`${pc.dim("Next step")}\n${pc.cyan(installCommand)}`);
|
|
4664
|
+
}
|
|
4665
|
+
outro(pc.magenta("Project updated"));
|
|
4104
4666
|
}
|
|
4105
4667
|
return Result.ok({
|
|
4106
4668
|
success: true,
|
|
@@ -4133,7 +4695,7 @@ async function getApiChoice(Api, frontend, backend, previousValue) {
|
|
|
4133
4695
|
hint: "No API layer (e.g. for full-stack frameworks like Next.js with Route Handlers)"
|
|
4134
4696
|
});
|
|
4135
4697
|
const apiType = await navigableSelect({
|
|
4136
|
-
message: "
|
|
4698
|
+
message: "Choose an API layer",
|
|
4137
4699
|
options: apiOptions,
|
|
4138
4700
|
initialValue: preferValidInitial(apiOptions, previousValue, apiOptions[0].value)
|
|
4139
4701
|
});
|
|
@@ -4185,7 +4747,7 @@ async function getAuthChoice(auth, backend, frontend = [], previousValue) {
|
|
|
4185
4747
|
}
|
|
4186
4748
|
});
|
|
4187
4749
|
const response = await navigableSelect({
|
|
4188
|
-
message: "
|
|
4750
|
+
message: "Choose authentication",
|
|
4189
4751
|
options,
|
|
4190
4752
|
initialValue: preferValidInitial(options, previousValue, options.some((option) => option.value === DEFAULT_CONFIG.auth) ? DEFAULT_CONFIG.auth : "none")
|
|
4191
4753
|
});
|
|
@@ -4239,7 +4801,7 @@ async function getBackendFrameworkChoice(backendFramework, frontends, previousVa
|
|
|
4239
4801
|
hint: "No backend server"
|
|
4240
4802
|
});
|
|
4241
4803
|
const response = await navigableSelect({
|
|
4242
|
-
message: "
|
|
4804
|
+
message: "Choose a backend",
|
|
4243
4805
|
options: backendOptions,
|
|
4244
4806
|
initialValue: preferValidInitial(backendOptions, previousValue, hasFullstackFrontend ? "self" : DEFAULT_CONFIG.backend)
|
|
4245
4807
|
});
|
|
@@ -4279,7 +4841,7 @@ async function getDatabaseChoice(database, backend, runtime, previousValue) {
|
|
|
4279
4841
|
hint: "open-source NoSQL database that stores data in JSON-like documents called BSON"
|
|
4280
4842
|
});
|
|
4281
4843
|
const response = await navigableSelect({
|
|
4282
|
-
message: "
|
|
4844
|
+
message: "Choose a database",
|
|
4283
4845
|
options: databaseOptions,
|
|
4284
4846
|
initialValue: preferValidInitial(databaseOptions, previousValue, DEFAULT_CONFIG.database)
|
|
4285
4847
|
});
|
|
@@ -4378,7 +4940,7 @@ async function getDBSetupChoice(databaseType, dbSetup, _orm, backend, runtime, p
|
|
|
4378
4940
|
];
|
|
4379
4941
|
else return "none";
|
|
4380
4942
|
const response = await navigableSelect({
|
|
4381
|
-
message: `
|
|
4943
|
+
message: `Choose a ${databaseType} setup`,
|
|
4382
4944
|
options,
|
|
4383
4945
|
initialValue: preferValidInitial(options, previousValue, "none")
|
|
4384
4946
|
});
|
|
@@ -4404,7 +4966,7 @@ async function getExamplesChoice(examples, database, frontends, backend, api, pr
|
|
|
4404
4966
|
});
|
|
4405
4967
|
if (options.length === 0) return [];
|
|
4406
4968
|
response = await navigableMultiselect({
|
|
4407
|
-
message: "Include examples",
|
|
4969
|
+
message: "Include starter examples?",
|
|
4408
4970
|
options,
|
|
4409
4971
|
required: false,
|
|
4410
4972
|
initialValues: (previousValue ?? DEFAULT_CONFIG.examples)?.filter((ex) => options.some((o) => o.value === ex))
|
|
@@ -4431,7 +4993,7 @@ async function getFrontendChoice(frontendOptions, backend, auth, previousValue)
|
|
|
4431
4993
|
while (true) {
|
|
4432
4994
|
const wasFirstPrompt = isFirstPrompt();
|
|
4433
4995
|
const frontendTypes = await navigableMultiselect({
|
|
4434
|
-
message: "
|
|
4996
|
+
message: "What are you building?",
|
|
4435
4997
|
options: [{
|
|
4436
4998
|
value: "web",
|
|
4437
4999
|
label: "Web",
|
|
@@ -4493,7 +5055,7 @@ async function getFrontendChoice(frontendOptions, backend, auth, previousValue)
|
|
|
4493
5055
|
}
|
|
4494
5056
|
].filter((option) => isFrontendAllowedWithBackend(option.value, backend, auth));
|
|
4495
5057
|
const webFramework = await navigableSelect({
|
|
4496
|
-
message: "Choose web",
|
|
5058
|
+
message: "Choose a web framework",
|
|
4497
5059
|
options: webOptions,
|
|
4498
5060
|
initialValue: preferValidInitial(webOptions, previousWeb, DEFAULT_CONFIG.frontend[0])
|
|
4499
5061
|
});
|
|
@@ -4507,7 +5069,7 @@ async function getFrontendChoice(frontendOptions, backend, auth, previousValue)
|
|
|
4507
5069
|
}
|
|
4508
5070
|
if (frontendTypes.includes("native")) {
|
|
4509
5071
|
const nativeFramework = await navigableSelect({
|
|
4510
|
-
message: "Choose native",
|
|
5072
|
+
message: "Choose a native setup",
|
|
4511
5073
|
options: [
|
|
4512
5074
|
{
|
|
4513
5075
|
value: "native-bare",
|
|
@@ -4547,7 +5109,7 @@ async function getFrontendChoice(frontendOptions, backend, auth, previousValue)
|
|
|
4547
5109
|
async function getGitChoice(git, previousValue) {
|
|
4548
5110
|
if (git !== void 0) return git;
|
|
4549
5111
|
const response = await navigableConfirm({
|
|
4550
|
-
message: "Initialize
|
|
5112
|
+
message: "Initialize a Git repository?",
|
|
4551
5113
|
initialValue: previousValue ?? DEFAULT_CONFIG.git
|
|
4552
5114
|
});
|
|
4553
5115
|
if (isCancel$1(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
@@ -4799,7 +5361,7 @@ async function getORMChoice(orm, hasDatabase, database, backend, runtime, previo
|
|
|
4799
5361
|
}
|
|
4800
5362
|
const options = database === "mongodb" ? [ormOptions.prisma, ormOptions.mongoose] : [ormOptions.drizzle, ormOptions.prisma];
|
|
4801
5363
|
const response = await navigableSelect({
|
|
4802
|
-
message: "
|
|
5364
|
+
message: "Choose an ORM",
|
|
4803
5365
|
options,
|
|
4804
5366
|
initialValue: preferValidInitial(options, previousValue, database === "mongodb" ? "prisma" : runtime === "workers" ? "drizzle" : DEFAULT_CONFIG.orm)
|
|
4805
5367
|
});
|
|
@@ -4851,7 +5413,7 @@ async function getPaymentsChoice(payments, auth, backend, _frontends, previousVa
|
|
|
4851
5413
|
hint: "No payments integration"
|
|
4852
5414
|
}];
|
|
4853
5415
|
const response = await navigableSelect({
|
|
4854
|
-
message: "
|
|
5416
|
+
message: "Add payments?",
|
|
4855
5417
|
options,
|
|
4856
5418
|
initialValue: preferValidInitial(options, previousValue, DEFAULT_CONFIG.payments)
|
|
4857
5419
|
});
|
|
@@ -4878,7 +5440,7 @@ async function getRuntimeChoice(runtime, backend, previousValue) {
|
|
|
4878
5440
|
hint: "Edge runtime on Cloudflare's global network"
|
|
4879
5441
|
});
|
|
4880
5442
|
const response = await navigableSelect({
|
|
4881
|
-
message: "
|
|
5443
|
+
message: "Choose a runtime",
|
|
4882
5444
|
options: runtimeOptions,
|
|
4883
5445
|
initialValue: preferValidInitial(runtimeOptions, previousValue, DEFAULT_CONFIG.runtime)
|
|
4884
5446
|
});
|
|
@@ -4932,7 +5494,7 @@ async function getServerDeploymentChoice(deployment, runtime, backend, _webDeplo
|
|
|
4932
5494
|
};
|
|
4933
5495
|
});
|
|
4934
5496
|
const response = await navigableSelect({
|
|
4935
|
-
message: "
|
|
5497
|
+
message: "Choose server deployment",
|
|
4936
5498
|
options,
|
|
4937
5499
|
initialValue: preferValidInitial(options, previousValue, DEFAULT_CONFIG.serverDeploy)
|
|
4938
5500
|
});
|
|
@@ -4980,7 +5542,7 @@ async function getDeploymentChoice(deployment, _runtime, backend, frontend = [],
|
|
|
4980
5542
|
};
|
|
4981
5543
|
});
|
|
4982
5544
|
const response = await navigableSelect({
|
|
4983
|
-
message: "
|
|
5545
|
+
message: "Choose web deployment",
|
|
4984
5546
|
options,
|
|
4985
5547
|
initialValue: preferValidInitial(options, previousValue, DEFAULT_CONFIG.webDeploy)
|
|
4986
5548
|
});
|
|
@@ -4989,7 +5551,7 @@ async function getDeploymentChoice(deployment, _runtime, backend, frontend = [],
|
|
|
4989
5551
|
}
|
|
4990
5552
|
//#endregion
|
|
4991
5553
|
//#region src/prompts/config-prompts.ts
|
|
4992
|
-
async function gatherConfig(flags, projectName, projectDir, relativePath) {
|
|
5554
|
+
async function gatherConfig(flags, projectName, projectDir, relativePath, options = {}) {
|
|
4993
5555
|
if (isSilent()) return {
|
|
4994
5556
|
projectName,
|
|
4995
5557
|
projectDir,
|
|
@@ -5017,22 +5579,63 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
|
|
|
5017
5579
|
frontend: ({ previousAnswer }) => getFrontendChoice(flags.frontend, flags.backend, flags.auth, previousAnswer),
|
|
5018
5580
|
backend: ({ results, previousAnswer }) => getBackendFrameworkChoice(flags.backend, results.frontend, previousAnswer),
|
|
5019
5581
|
runtime: ({ results, previousAnswer }) => getRuntimeChoice(flags.runtime, results.backend, previousAnswer),
|
|
5582
|
+
api: ({ results, previousAnswer }) => getApiChoice(flags.api, results.frontend, results.backend, previousAnswer),
|
|
5020
5583
|
database: ({ results, previousAnswer }) => getDatabaseChoice(flags.database, results.backend, results.runtime, previousAnswer),
|
|
5021
5584
|
orm: ({ results, previousAnswer }) => getORMChoice(flags.orm, results.database !== "none", results.database, results.backend, results.runtime, previousAnswer),
|
|
5022
|
-
|
|
5585
|
+
dbSetup: ({ results, previousAnswer }) => getDBSetupChoice(results.database ?? "none", flags.dbSetup, results.orm, results.backend, results.runtime, previousAnswer),
|
|
5023
5586
|
auth: ({ results, previousAnswer }) => getAuthChoice(flags.auth, results.backend, results.frontend, previousAnswer),
|
|
5024
5587
|
payments: ({ results, previousAnswer }) => getPaymentsChoice(flags.payments, results.auth, results.backend, results.frontend, previousAnswer),
|
|
5025
5588
|
addons: ({ results, previousAnswer }) => getAddonsChoice(flags.addons, results.frontend, results.auth, results.backend, results.runtime, previousAnswer),
|
|
5026
5589
|
examples: ({ results, previousAnswer }) => getExamplesChoice(flags.examples, results.database, results.frontend, results.backend, results.api, previousAnswer),
|
|
5027
|
-
dbSetup: ({ results, previousAnswer }) => getDBSetupChoice(results.database ?? "none", flags.dbSetup, results.orm, results.backend, results.runtime, previousAnswer),
|
|
5028
5590
|
webDeploy: ({ results, previousAnswer }) => getDeploymentChoice(flags.webDeploy, results.runtime, results.backend, results.frontend, results.dbSetup, previousAnswer),
|
|
5029
5591
|
serverDeploy: ({ results, previousAnswer }) => getServerDeploymentChoice(flags.serverDeploy, results.runtime, results.backend, results.webDeploy, previousAnswer),
|
|
5030
5592
|
git: ({ previousAnswer }) => getGitChoice(flags.git, previousAnswer),
|
|
5031
5593
|
packageManager: ({ previousAnswer }) => getPackageManagerChoice(flags.packageManager, previousAnswer),
|
|
5032
5594
|
install: ({ previousAnswer }) => getinstallChoice(flags.install, previousAnswer)
|
|
5033
|
-
}, {
|
|
5034
|
-
|
|
5035
|
-
|
|
5595
|
+
}, {
|
|
5596
|
+
preselected: options.skipCompatibilityChecks ? flags : void 0,
|
|
5597
|
+
sections: [
|
|
5598
|
+
{
|
|
5599
|
+
label: "App",
|
|
5600
|
+
prompts: [
|
|
5601
|
+
"frontend",
|
|
5602
|
+
"backend",
|
|
5603
|
+
"runtime",
|
|
5604
|
+
"api"
|
|
5605
|
+
]
|
|
5606
|
+
},
|
|
5607
|
+
{
|
|
5608
|
+
label: "Data",
|
|
5609
|
+
prompts: [
|
|
5610
|
+
"database",
|
|
5611
|
+
"orm",
|
|
5612
|
+
"dbSetup"
|
|
5613
|
+
]
|
|
5614
|
+
},
|
|
5615
|
+
{
|
|
5616
|
+
label: "Product",
|
|
5617
|
+
prompts: [
|
|
5618
|
+
"auth",
|
|
5619
|
+
"payments",
|
|
5620
|
+
"addons",
|
|
5621
|
+
"examples"
|
|
5622
|
+
]
|
|
5623
|
+
},
|
|
5624
|
+
{
|
|
5625
|
+
label: "Ship",
|
|
5626
|
+
prompts: [
|
|
5627
|
+
"webDeploy",
|
|
5628
|
+
"serverDeploy",
|
|
5629
|
+
"git",
|
|
5630
|
+
"packageManager",
|
|
5631
|
+
"install"
|
|
5632
|
+
]
|
|
5633
|
+
}
|
|
5634
|
+
],
|
|
5635
|
+
onCancel: () => {
|
|
5636
|
+
throw new UserCancelledError({ message: "Operation cancelled" });
|
|
5637
|
+
}
|
|
5638
|
+
});
|
|
5036
5639
|
return {
|
|
5037
5640
|
projectName,
|
|
5038
5641
|
projectDir,
|
|
@@ -5081,13 +5684,22 @@ async function getProjectName(initialName) {
|
|
|
5081
5684
|
let projectPath = "";
|
|
5082
5685
|
let defaultName = DEFAULT_CONFIG.projectName;
|
|
5083
5686
|
let counter = 1;
|
|
5084
|
-
while (
|
|
5687
|
+
while (true) {
|
|
5688
|
+
const defaultPath = path.resolve(process.cwd(), defaultName);
|
|
5689
|
+
let stats;
|
|
5690
|
+
try {
|
|
5691
|
+
stats = await fs.lstat(defaultPath);
|
|
5692
|
+
} catch (error) {
|
|
5693
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") break;
|
|
5694
|
+
throw error;
|
|
5695
|
+
}
|
|
5696
|
+
if (stats.isDirectory() && (await fs.readdir(defaultPath)).length === 0) break;
|
|
5085
5697
|
defaultName = `${DEFAULT_CONFIG.projectName}-${counter}`;
|
|
5086
5698
|
counter++;
|
|
5087
5699
|
}
|
|
5088
5700
|
while (!isValid) {
|
|
5089
5701
|
const response = await text({
|
|
5090
|
-
message: "
|
|
5702
|
+
message: "Where should we create your project?",
|
|
5091
5703
|
placeholder: defaultName,
|
|
5092
5704
|
initialValue: initialName,
|
|
5093
5705
|
defaultValue: defaultName,
|
|
@@ -5116,9 +5728,7 @@ async function getProjectName(initialName) {
|
|
|
5116
5728
|
*/
|
|
5117
5729
|
function isTelemetryEnabled() {
|
|
5118
5730
|
const BTS_TELEMETRY_DISABLED = process.env.BTS_TELEMETRY_DISABLED;
|
|
5119
|
-
const BTS_TELEMETRY = "1";
|
|
5120
5731
|
if (BTS_TELEMETRY_DISABLED !== void 0) return BTS_TELEMETRY_DISABLED !== "1";
|
|
5121
|
-
if (BTS_TELEMETRY !== void 0) return BTS_TELEMETRY === "1";
|
|
5122
5732
|
return true;
|
|
5123
5733
|
}
|
|
5124
5734
|
//#endregion
|
|
@@ -5148,90 +5758,83 @@ async function trackProjectCreation(config, disableAnalytics = false) {
|
|
|
5148
5758
|
});
|
|
5149
5759
|
}
|
|
5150
5760
|
//#endregion
|
|
5151
|
-
//#region src/utils/
|
|
5152
|
-
function
|
|
5153
|
-
const
|
|
5154
|
-
|
|
5155
|
-
if (config.frontend !== void 0) {
|
|
5156
|
-
const frontend = Array.isArray(config.frontend) ? config.frontend : [config.frontend];
|
|
5157
|
-
const frontendText = frontend.length > 0 && frontend[0] !== void 0 ? frontend.join(", ") : "none";
|
|
5158
|
-
configDisplay.push(`${pc.blue("Frontend:")} ${frontendText}`);
|
|
5159
|
-
}
|
|
5160
|
-
if (config.backend !== void 0) configDisplay.push(`${pc.blue("Backend:")} ${String(config.backend)}`);
|
|
5161
|
-
if (config.runtime !== void 0) configDisplay.push(`${pc.blue("Runtime:")} ${String(config.runtime)}`);
|
|
5162
|
-
if (config.api !== void 0) configDisplay.push(`${pc.blue("API:")} ${String(config.api)}`);
|
|
5163
|
-
if (config.database !== void 0) configDisplay.push(`${pc.blue("Database:")} ${String(config.database)}`);
|
|
5164
|
-
if (config.orm !== void 0) configDisplay.push(`${pc.blue("ORM:")} ${String(config.orm)}`);
|
|
5165
|
-
if (config.auth !== void 0) configDisplay.push(`${pc.blue("Auth:")} ${String(config.auth)}`);
|
|
5166
|
-
if (config.payments !== void 0) configDisplay.push(`${pc.blue("Payments:")} ${String(config.payments)}`);
|
|
5167
|
-
if (config.addons !== void 0) {
|
|
5168
|
-
const addons = Array.isArray(config.addons) ? config.addons : [config.addons];
|
|
5169
|
-
const addonsText = addons.length > 0 && addons[0] !== void 0 ? addons.join(", ") : "none";
|
|
5170
|
-
configDisplay.push(`${pc.blue("Addons:")} ${addonsText}`);
|
|
5171
|
-
}
|
|
5172
|
-
if (config.examples !== void 0) {
|
|
5173
|
-
const examples = Array.isArray(config.examples) ? config.examples : [config.examples];
|
|
5174
|
-
const examplesText = examples.length > 0 && examples[0] !== void 0 ? examples.join(", ") : "none";
|
|
5175
|
-
configDisplay.push(`${pc.blue("Examples:")} ${examplesText}`);
|
|
5176
|
-
}
|
|
5177
|
-
if (config.git !== void 0) {
|
|
5178
|
-
const gitText = typeof config.git === "boolean" ? config.git ? "Yes" : "No" : String(config.git);
|
|
5179
|
-
configDisplay.push(`${pc.blue("Git Init:")} ${gitText}`);
|
|
5180
|
-
}
|
|
5181
|
-
if (config.packageManager !== void 0) configDisplay.push(`${pc.blue("Package Manager:")} ${String(config.packageManager)}`);
|
|
5182
|
-
if (config.install !== void 0) {
|
|
5183
|
-
const installText = typeof config.install === "boolean" ? config.install ? "Yes" : "No" : String(config.install);
|
|
5184
|
-
configDisplay.push(`${pc.blue("Install Dependencies:")} ${installText}`);
|
|
5185
|
-
}
|
|
5186
|
-
if (config.dbSetup !== void 0) configDisplay.push(`${pc.blue("Database Setup:")} ${String(config.dbSetup)}`);
|
|
5187
|
-
if (config.webDeploy !== void 0) configDisplay.push(`${pc.blue("Web Deployment:")} ${String(config.webDeploy)}`);
|
|
5188
|
-
if (config.serverDeploy !== void 0) configDisplay.push(`${pc.blue("Server Deployment:")} ${String(config.serverDeploy)}`);
|
|
5189
|
-
if (configDisplay.length === 0) return pc.yellow("No configuration selected.");
|
|
5190
|
-
return configDisplay.join("\n");
|
|
5761
|
+
//#region src/utils/cli-invocation.ts
|
|
5762
|
+
function getCliSubcommandCommand(subcommand, fallbackPackageManager, userAgent = process.env.npm_config_user_agent) {
|
|
5763
|
+
const normalizedUserAgent = userAgent?.toLowerCase();
|
|
5764
|
+
return getPackageExecutionCommand(normalizedUserAgent?.startsWith("bun") ? "bun" : normalizedUserAgent?.startsWith("pnpm") ? "pnpm" : normalizedUserAgent?.startsWith("npm") ? "npm" : fallbackPackageManager, `create-better-t-stack@latest ${subcommand}`);
|
|
5191
5765
|
}
|
|
5192
5766
|
//#endregion
|
|
5193
5767
|
//#region src/utils/project-directory.ts
|
|
5194
5768
|
async function handleDirectoryConflict(currentPathInput) {
|
|
5195
5769
|
while (true) {
|
|
5196
|
-
const
|
|
5197
|
-
if (
|
|
5770
|
+
const pathStateResult = await inspectProjectPath(path.resolve(process.cwd(), currentPathInput));
|
|
5771
|
+
if (pathStateResult.isErr()) throw pathStateResult.error;
|
|
5772
|
+
const pathState = pathStateResult.value;
|
|
5773
|
+
if (pathState === "missing" || pathState === "empty-directory") return {
|
|
5198
5774
|
finalPathInput: currentPathInput,
|
|
5199
5775
|
shouldClearDirectory: false
|
|
5200
5776
|
};
|
|
5201
|
-
if (isSilent()) throw new CLIError({ message: `
|
|
5202
|
-
log.warn(`
|
|
5777
|
+
if (isSilent()) throw new CLIError({ message: `Project path "${currentPathInput}" is unavailable. In silent mode, provide a different project path or an explicit directoryConflict strategy.` });
|
|
5778
|
+
if (pathState === "symbolic-link") log.warn(`Project path "${pc.yellow(currentPathInput)}" is a symbolic link.`);
|
|
5779
|
+
else if (pathState === "non-directory") log.warn(`Project path "${pc.yellow(currentPathInput)}" exists and is not a directory.`);
|
|
5780
|
+
else log.warn(`Directory "${pc.yellow(currentPathInput)}" already exists and is not empty.`);
|
|
5781
|
+
let incrementedPath;
|
|
5782
|
+
if (currentPathInput !== ".") {
|
|
5783
|
+
const incrementResult = await findAvailableIncrementedPath(currentPathInput);
|
|
5784
|
+
if (incrementResult.isErr()) throw incrementResult.error;
|
|
5785
|
+
incrementedPath = incrementResult.value;
|
|
5786
|
+
}
|
|
5787
|
+
const options = [];
|
|
5788
|
+
if (incrementedPath) options.push({
|
|
5789
|
+
value: "increment",
|
|
5790
|
+
label: `Create as "${incrementedPath}"`,
|
|
5791
|
+
hint: "Keep the existing path untouched"
|
|
5792
|
+
});
|
|
5793
|
+
options.push({
|
|
5794
|
+
value: "rename",
|
|
5795
|
+
label: "Choose another path",
|
|
5796
|
+
hint: "Enter a different project directory"
|
|
5797
|
+
});
|
|
5798
|
+
if (pathState === "non-empty-directory") options.push({
|
|
5799
|
+
value: "merge",
|
|
5800
|
+
label: "Merge into this directory",
|
|
5801
|
+
hint: "Keep unrelated files; replace conflicts"
|
|
5802
|
+
}, {
|
|
5803
|
+
value: "overwrite",
|
|
5804
|
+
label: "Delete and overwrite",
|
|
5805
|
+
hint: "Permanently remove existing contents"
|
|
5806
|
+
});
|
|
5807
|
+
options.push({
|
|
5808
|
+
value: "cancel",
|
|
5809
|
+
label: "Cancel",
|
|
5810
|
+
hint: "Leave everything unchanged"
|
|
5811
|
+
});
|
|
5203
5812
|
const action = await select({
|
|
5204
|
-
message: "
|
|
5205
|
-
options
|
|
5206
|
-
|
|
5207
|
-
value: "overwrite",
|
|
5208
|
-
label: "Overwrite",
|
|
5209
|
-
hint: "Empty the directory and create the project"
|
|
5210
|
-
},
|
|
5211
|
-
{
|
|
5212
|
-
value: "merge",
|
|
5213
|
-
label: "Merge",
|
|
5214
|
-
hint: "Create project files inside, potentially overwriting conflicts"
|
|
5215
|
-
},
|
|
5216
|
-
{
|
|
5217
|
-
value: "rename",
|
|
5218
|
-
label: "Choose a different name/path",
|
|
5219
|
-
hint: "Keep the existing directory and create a new one"
|
|
5220
|
-
},
|
|
5221
|
-
{
|
|
5222
|
-
value: "cancel",
|
|
5223
|
-
label: "Cancel",
|
|
5224
|
-
hint: "Abort the process"
|
|
5225
|
-
}
|
|
5226
|
-
],
|
|
5227
|
-
initialValue: "rename"
|
|
5813
|
+
message: "How should we continue?",
|
|
5814
|
+
options,
|
|
5815
|
+
initialValue: incrementedPath ? "increment" : "rename"
|
|
5228
5816
|
});
|
|
5229
5817
|
if (isCancel(action)) throw new UserCancelledError({ message: "Operation cancelled." });
|
|
5230
5818
|
switch (action) {
|
|
5231
|
-
case "
|
|
5232
|
-
finalPathInput:
|
|
5233
|
-
shouldClearDirectory:
|
|
5819
|
+
case "increment": return {
|
|
5820
|
+
finalPathInput: incrementedPath,
|
|
5821
|
+
shouldClearDirectory: false
|
|
5234
5822
|
};
|
|
5823
|
+
case "overwrite": {
|
|
5824
|
+
const confirmed = await confirm({
|
|
5825
|
+
message: `Permanently delete every file in "${currentPathInput}"?`,
|
|
5826
|
+
initialValue: false
|
|
5827
|
+
});
|
|
5828
|
+
if (isCancel(confirmed)) throw new UserCancelledError({ message: "Operation cancelled." });
|
|
5829
|
+
if (!confirmed) {
|
|
5830
|
+
log.info("Nothing was deleted. Choose another option.");
|
|
5831
|
+
continue;
|
|
5832
|
+
}
|
|
5833
|
+
return {
|
|
5834
|
+
finalPathInput: currentPathInput,
|
|
5835
|
+
shouldClearDirectory: true
|
|
5836
|
+
};
|
|
5837
|
+
}
|
|
5235
5838
|
case "merge":
|
|
5236
5839
|
log.info(`Proceeding into existing directory "${pc.yellow(currentPathInput)}". Files may be overwritten.`);
|
|
5237
5840
|
return {
|
|
@@ -5239,8 +5842,8 @@ async function handleDirectoryConflict(currentPathInput) {
|
|
|
5239
5842
|
shouldClearDirectory: false
|
|
5240
5843
|
};
|
|
5241
5844
|
case "rename":
|
|
5242
|
-
|
|
5243
|
-
|
|
5845
|
+
currentPathInput = await getProjectName(void 0);
|
|
5846
|
+
continue;
|
|
5244
5847
|
case "cancel": throw new UserCancelledError({ message: "Operation cancelled." });
|
|
5245
5848
|
}
|
|
5246
5849
|
}
|
|
@@ -5255,9 +5858,11 @@ async function setupProjectDirectory(finalPathInput, shouldClearDirectory) {
|
|
|
5255
5858
|
finalResolvedPath = path.resolve(process.cwd(), finalPathInput);
|
|
5256
5859
|
finalBaseName = path.basename(finalResolvedPath);
|
|
5257
5860
|
}
|
|
5861
|
+
const pathSafetyResult = await validateSafeProjectDirectoryPath(finalPathInput);
|
|
5862
|
+
if (pathSafetyResult.isErr()) throw pathSafetyResult.error;
|
|
5258
5863
|
if (shouldClearDirectory) {
|
|
5259
|
-
const s = spinner();
|
|
5260
|
-
s
|
|
5864
|
+
const s = isSilent() ? void 0 : spinner();
|
|
5865
|
+
s?.start(`Clearing directory "${finalResolvedPath}"...`);
|
|
5261
5866
|
const clearResult = await Result.tryPromise({
|
|
5262
5867
|
try: () => fs.emptyDir(finalResolvedPath),
|
|
5263
5868
|
catch: (error) => new CLIError({
|
|
@@ -5266,16 +5871,78 @@ async function setupProjectDirectory(finalPathInput, shouldClearDirectory) {
|
|
|
5266
5871
|
})
|
|
5267
5872
|
});
|
|
5268
5873
|
if (clearResult.isErr()) {
|
|
5269
|
-
s
|
|
5874
|
+
s?.stop(pc.red(`Failed to clear directory "${finalResolvedPath}".`));
|
|
5270
5875
|
throw clearResult.error;
|
|
5271
5876
|
}
|
|
5272
|
-
s
|
|
5877
|
+
s?.stop(`Directory "${finalResolvedPath}" cleared.`);
|
|
5273
5878
|
} else await fs.ensureDir(finalResolvedPath);
|
|
5274
5879
|
return {
|
|
5275
5880
|
finalResolvedPath,
|
|
5276
5881
|
finalBaseName
|
|
5277
5882
|
};
|
|
5278
5883
|
}
|
|
5884
|
+
async function validateSafeProjectDirectoryPath(finalPathInput) {
|
|
5885
|
+
return Result.tryPromise({
|
|
5886
|
+
try: async () => {
|
|
5887
|
+
const cwd = path.resolve(process.cwd());
|
|
5888
|
+
const targetPath = finalPathInput === "." ? cwd : path.resolve(cwd, finalPathInput);
|
|
5889
|
+
const relativeTarget = path.relative(cwd, targetPath);
|
|
5890
|
+
if (relativeTarget === ".." || relativeTarget.startsWith(`..${path.sep}`) || path.isAbsolute(relativeTarget)) throw new CLIError({ message: `Project path "${finalPathInput}" resolves outside the current working directory.` });
|
|
5891
|
+
const pathSegments = relativeTarget.split(path.sep).filter(Boolean);
|
|
5892
|
+
let nearestExistingPath = cwd;
|
|
5893
|
+
for (const segment of pathSegments) {
|
|
5894
|
+
const candidatePath = path.join(nearestExistingPath, segment);
|
|
5895
|
+
let stats;
|
|
5896
|
+
try {
|
|
5897
|
+
stats = await fs.lstat(candidatePath);
|
|
5898
|
+
} catch (error) {
|
|
5899
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") break;
|
|
5900
|
+
throw error;
|
|
5901
|
+
}
|
|
5902
|
+
if (stats.isSymbolicLink()) throw new CLIError({ message: `Project path "${finalPathInput}" passes through symbolic link "${path.relative(cwd, candidatePath)}". Choose a real directory within the current working directory.` });
|
|
5903
|
+
if (!stats.isDirectory()) throw new CLIError({ message: `Project path "${finalPathInput}" passes through "${path.relative(cwd, candidatePath)}", which is not a directory.` });
|
|
5904
|
+
nearestExistingPath = candidatePath;
|
|
5905
|
+
}
|
|
5906
|
+
const [realCwd, realExistingPath] = await Promise.all([fs.realpath(cwd), fs.realpath(nearestExistingPath)]);
|
|
5907
|
+
const relativeRealPath = path.relative(realCwd, realExistingPath);
|
|
5908
|
+
if (relativeRealPath === ".." || relativeRealPath.startsWith(`..${path.sep}`) || path.isAbsolute(relativeRealPath)) throw new CLIError({ message: `Project path "${finalPathInput}" resolves outside the current working directory.` });
|
|
5909
|
+
},
|
|
5910
|
+
catch: (error) => CLIError.is(error) ? error : new CLIError({
|
|
5911
|
+
message: `Unable to validate project path "${finalPathInput}".`,
|
|
5912
|
+
cause: error
|
|
5913
|
+
})
|
|
5914
|
+
});
|
|
5915
|
+
}
|
|
5916
|
+
async function inspectProjectPath(targetPath) {
|
|
5917
|
+
return Result.tryPromise({
|
|
5918
|
+
try: async () => {
|
|
5919
|
+
let stats;
|
|
5920
|
+
try {
|
|
5921
|
+
stats = await fs.lstat(targetPath);
|
|
5922
|
+
} catch (error) {
|
|
5923
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") return "missing";
|
|
5924
|
+
throw error;
|
|
5925
|
+
}
|
|
5926
|
+
if (stats.isSymbolicLink()) return "symbolic-link";
|
|
5927
|
+
if (!stats.isDirectory()) return "non-directory";
|
|
5928
|
+
return (await fs.readdir(targetPath)).length === 0 ? "empty-directory" : "non-empty-directory";
|
|
5929
|
+
},
|
|
5930
|
+
catch: (error) => new CLIError({
|
|
5931
|
+
message: `Unable to inspect project path "${targetPath}".`,
|
|
5932
|
+
cause: error
|
|
5933
|
+
})
|
|
5934
|
+
});
|
|
5935
|
+
}
|
|
5936
|
+
async function findAvailableIncrementedPath(currentPathInput) {
|
|
5937
|
+
let counter = 1;
|
|
5938
|
+
while (true) {
|
|
5939
|
+
const candidate = `${currentPathInput}-${counter}`;
|
|
5940
|
+
const candidateStateResult = await inspectProjectPath(path.resolve(process.cwd(), candidate));
|
|
5941
|
+
if (candidateStateResult.isErr()) return Result.err(candidateStateResult.error);
|
|
5942
|
+
if (candidateStateResult.value === "missing" || candidateStateResult.value === "empty-directory") return Result.ok(candidate);
|
|
5943
|
+
counter++;
|
|
5944
|
+
}
|
|
5945
|
+
}
|
|
5279
5946
|
//#endregion
|
|
5280
5947
|
//#region src/utils/project-name-validation.ts
|
|
5281
5948
|
function validateProjectName(name) {
|
|
@@ -5439,7 +6106,7 @@ function validateArrayOptions(options) {
|
|
|
5439
6106
|
}
|
|
5440
6107
|
//#endregion
|
|
5441
6108
|
//#region src/validation.ts
|
|
5442
|
-
const CORE_STACK_FLAGS = new Set([
|
|
6109
|
+
const CORE_STACK_FLAGS = /* @__PURE__ */ new Set([
|
|
5443
6110
|
"database",
|
|
5444
6111
|
"orm",
|
|
5445
6112
|
"backend",
|
|
@@ -5689,7 +6356,7 @@ async function initMongoDBAtlas(serverDir) {
|
|
|
5689
6356
|
await $({
|
|
5690
6357
|
cwd: serverDir,
|
|
5691
6358
|
stdio: "inherit"
|
|
5692
|
-
})`atlas
|
|
6359
|
+
})`atlas setup`;
|
|
5693
6360
|
cliLog.success("MongoDB Atlas deployment ready");
|
|
5694
6361
|
},
|
|
5695
6362
|
catch: (e) => new DatabaseSetupError({
|
|
@@ -5738,7 +6405,7 @@ ${pc.green("MongoDB Atlas Manual Setup Instructions:")}
|
|
|
5738
6405
|
${pc.blue("https://www.mongodb.com/docs/atlas/cli/stable/install-atlas-cli/")}
|
|
5739
6406
|
|
|
5740
6407
|
2. Run the following command and follow the prompts:
|
|
5741
|
-
${pc.blue("atlas
|
|
6408
|
+
${pc.blue("atlas setup")}
|
|
5742
6409
|
|
|
5743
6410
|
3. Get your connection string from the Atlas dashboard:
|
|
5744
6411
|
Format: ${pc.dim("mongodb+srv://USERNAME:PASSWORD@CLUSTER.mongodb.net/DATABASE_NAME")}
|
|
@@ -5839,10 +6506,6 @@ const NEON_REGIONS = [
|
|
|
5839
6506
|
label: "AWS Asia Pacific (Singapore)",
|
|
5840
6507
|
value: "aws-ap-southeast-1"
|
|
5841
6508
|
},
|
|
5842
|
-
{
|
|
5843
|
-
label: "AWS South America East 1 (São Paulo)",
|
|
5844
|
-
value: "aws-sa-east-1"
|
|
5845
|
-
},
|
|
5846
6509
|
{
|
|
5847
6510
|
label: "AWS Asia Pacific (Sydney)",
|
|
5848
6511
|
value: "aws-ap-southeast-2"
|
|
@@ -5852,13 +6515,12 @@ const NEON_REGIONS = [
|
|
|
5852
6515
|
value: "azure-eastus2"
|
|
5853
6516
|
}
|
|
5854
6517
|
];
|
|
5855
|
-
async function executeNeonCommand(
|
|
6518
|
+
async function executeNeonCommand(commandArgs, spinnerText) {
|
|
5856
6519
|
const s = createSpinner();
|
|
5857
|
-
const args = getPackageExecutionArgs(packageManager, commandArgsString);
|
|
5858
6520
|
if (spinnerText) s.start(spinnerText);
|
|
5859
6521
|
return Result.tryPromise({
|
|
5860
6522
|
try: async () => {
|
|
5861
|
-
const result = await $`${
|
|
6523
|
+
const result = await $`${commandArgs}`;
|
|
5862
6524
|
if (spinnerText) s.stop(pc.green(spinnerText.replace("...", "").replace("ing ", "ed ").trim()));
|
|
5863
6525
|
return result;
|
|
5864
6526
|
},
|
|
@@ -5872,8 +6534,22 @@ async function executeNeonCommand(packageManager, commandArgsString, spinnerText
|
|
|
5872
6534
|
}
|
|
5873
6535
|
});
|
|
5874
6536
|
}
|
|
6537
|
+
function getNeonProjectCreateArgs(packageManager, projectName, regionId) {
|
|
6538
|
+
return [
|
|
6539
|
+
...getPackageRunnerPrefix(packageManager),
|
|
6540
|
+
"neon@latest",
|
|
6541
|
+
"projects",
|
|
6542
|
+
"create",
|
|
6543
|
+
"--name",
|
|
6544
|
+
projectName,
|
|
6545
|
+
"--region-id",
|
|
6546
|
+
regionId,
|
|
6547
|
+
"--output",
|
|
6548
|
+
"json"
|
|
6549
|
+
];
|
|
6550
|
+
}
|
|
5875
6551
|
async function createNeonProject(projectName, regionId, packageManager) {
|
|
5876
|
-
const execResult = await executeNeonCommand(packageManager,
|
|
6552
|
+
const execResult = await executeNeonCommand(getNeonProjectCreateArgs(packageManager, projectName, regionId), `Creating Neon project "${projectName}"...`);
|
|
5877
6553
|
if (execResult.isErr()) return Result.err(execResult.error);
|
|
5878
6554
|
const parseResult = Result.try({
|
|
5879
6555
|
try: () => JSON.parse(execResult.value.stdout),
|
|
@@ -5917,7 +6593,7 @@ async function writeEnvFile$2(projectDir, backend, config) {
|
|
|
5917
6593
|
}
|
|
5918
6594
|
async function setupWithNeonDb(projectDir, packageManager, backend) {
|
|
5919
6595
|
const s = createSpinner();
|
|
5920
|
-
s.start("Creating Neon database using
|
|
6596
|
+
s.start("Creating Neon database using neon-new...");
|
|
5921
6597
|
const targetApp = backend === "self" ? "apps/web" : "apps/server";
|
|
5922
6598
|
const targetDir = path.join(projectDir, targetApp);
|
|
5923
6599
|
const ensureDirResult = await Result.tryPromise({
|
|
@@ -5932,17 +6608,17 @@ async function setupWithNeonDb(projectDir, packageManager, backend) {
|
|
|
5932
6608
|
s.stop(pc.red("Failed to create directory"));
|
|
5933
6609
|
return ensureDirResult;
|
|
5934
6610
|
}
|
|
5935
|
-
const packageArgs = getPackageExecutionArgs(packageManager, `
|
|
6611
|
+
const packageArgs = getPackageExecutionArgs(packageManager, `neon-new@latest --yes --ref "sbA3tIe"`);
|
|
5936
6612
|
return Result.tryPromise({
|
|
5937
6613
|
try: async () => {
|
|
5938
6614
|
await $({ cwd: targetDir })`${packageArgs}`;
|
|
5939
6615
|
s.stop(pc.green("Neon database created successfully!"));
|
|
5940
6616
|
},
|
|
5941
6617
|
catch: (e) => {
|
|
5942
|
-
s.stop(pc.red("Failed to create database with
|
|
6618
|
+
s.stop(pc.red("Failed to create database with neon-new"));
|
|
5943
6619
|
return new DatabaseSetupError({
|
|
5944
6620
|
provider: "neon",
|
|
5945
|
-
message: `Failed to create database with
|
|
6621
|
+
message: `Failed to create database with neon-new: ${e instanceof Error ? e.message : String(e)}`,
|
|
5946
6622
|
cause: e
|
|
5947
6623
|
});
|
|
5948
6624
|
}
|
|
@@ -6003,25 +6679,25 @@ async function setupNeonPostgres(config, cliInput) {
|
|
|
6003
6679
|
return Result.ok(void 0);
|
|
6004
6680
|
}
|
|
6005
6681
|
let setupMethod = cliInput?.dbSetupOptions?.neon?.method ?? config.dbSetupOptions?.neon?.method;
|
|
6006
|
-
if (!setupMethod) if (isSilent()) setupMethod = "
|
|
6682
|
+
if (!setupMethod) if (isSilent()) setupMethod = "neon-new";
|
|
6007
6683
|
else {
|
|
6008
6684
|
const promptedSetupMethod = await select({
|
|
6009
6685
|
message: "Choose your Neon setup method:",
|
|
6010
6686
|
options: [{
|
|
6011
|
-
label: "Quick setup with
|
|
6012
|
-
value: "
|
|
6687
|
+
label: "Quick setup with neon-new",
|
|
6688
|
+
value: "neon-new",
|
|
6013
6689
|
hint: "fastest, no auth required"
|
|
6014
6690
|
}, {
|
|
6015
|
-
label: "Custom setup with
|
|
6016
|
-
value: "
|
|
6691
|
+
label: "Custom setup with Neon CLI",
|
|
6692
|
+
value: "neon",
|
|
6017
6693
|
hint: "More control - choose project name and region"
|
|
6018
6694
|
}],
|
|
6019
|
-
initialValue: "
|
|
6695
|
+
initialValue: "neon-new"
|
|
6020
6696
|
});
|
|
6021
6697
|
if (isCancel(promptedSetupMethod)) return userCancelled("Operation cancelled");
|
|
6022
6698
|
setupMethod = promptedSetupMethod;
|
|
6023
6699
|
}
|
|
6024
|
-
if (setupMethod === "neondb") {
|
|
6700
|
+
if (setupMethod === "neon-new" || setupMethod === "neondb") {
|
|
6025
6701
|
const neonDbResult = await setupWithNeonDb(projectDir, packageManager, backend);
|
|
6026
6702
|
if (neonDbResult.isErr()) {
|
|
6027
6703
|
cliLog.error(pc.red(neonDbResult.error.message));
|
|
@@ -6941,7 +7617,7 @@ async function getDockerStatus(database) {
|
|
|
6941
7617
|
//#endregion
|
|
6942
7618
|
//#region src/helpers/core/post-installation.ts
|
|
6943
7619
|
function getDesktopStaticBuildNote(frontend) {
|
|
6944
|
-
const staticBuildFrontends = new Map([
|
|
7620
|
+
const staticBuildFrontends = /* @__PURE__ */ new Map([
|
|
6945
7621
|
["tanstack-start", "TanStack Start"],
|
|
6946
7622
|
["next", "Next.js"],
|
|
6947
7623
|
["nuxt", "Nuxt"],
|
|
@@ -6985,7 +7661,7 @@ async function displayPostInstallInstructions(config) {
|
|
|
6985
7661
|
const polarInstructions = config.payments === "polar" && config.auth === "better-auth" ? getPolarInstructions(backend, packageManager) : "";
|
|
6986
7662
|
const bunWebNativeWarning = packageManager === "bun" && hasNative && hasWeb ? getBunWebNativeWarning() : "";
|
|
6987
7663
|
const noOrmWarning = !isConvex && database !== "none" && orm === "none" ? getNoOrmWarning() : "";
|
|
6988
|
-
let output = `${pc.
|
|
7664
|
+
let output = `${pc.cyan("1.")} ${cdCmd}\n`;
|
|
6989
7665
|
let stepCounter = 2;
|
|
6990
7666
|
if (!depsInstalled) output += `${pc.cyan(`${stepCounter++}.`)} ${packageManager} install\n`;
|
|
6991
7667
|
if (database === "sqlite" && dbSetup !== "d1") output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} db:local\n${pc.dim(" (optional - starts local SQLite database)")}\n`;
|
|
@@ -7003,19 +7679,44 @@ async function displayPostInstallInstructions(config) {
|
|
|
7003
7679
|
}
|
|
7004
7680
|
const hasStandaloneBackend = backend !== "none";
|
|
7005
7681
|
if (hasWeb || hasStandaloneBackend || addons?.includes("starlight") || addons?.includes("fumadocs")) {
|
|
7006
|
-
|
|
7007
|
-
|
|
7008
|
-
|
|
7682
|
+
const localServices = [];
|
|
7683
|
+
let localDevelopmentNote = "";
|
|
7684
|
+
if (hasWeb) localServices.push({
|
|
7685
|
+
label: "Frontend",
|
|
7686
|
+
url: `http://localhost:${webPort}`
|
|
7687
|
+
});
|
|
7688
|
+
else if (!hasNative && !addons?.includes("starlight")) localDevelopmentNote = "Backend-only app — no frontend selected";
|
|
7009
7689
|
if (!isConvex && !isBackendSelf && hasStandaloneBackend) {
|
|
7010
|
-
|
|
7011
|
-
|
|
7690
|
+
localServices.push({
|
|
7691
|
+
label: "API",
|
|
7692
|
+
url: "http://localhost:3000"
|
|
7693
|
+
});
|
|
7694
|
+
if (api === "orpc") localServices.push({
|
|
7695
|
+
label: "API reference",
|
|
7696
|
+
url: "http://localhost:3000/api-reference"
|
|
7697
|
+
});
|
|
7012
7698
|
}
|
|
7013
7699
|
if (isBackendSelf && api === "orpc") {
|
|
7014
7700
|
const rpcPath = frontend?.includes("next") || frontend?.includes("tanstack-start") ? "/api/rpc" : "/rpc";
|
|
7015
|
-
|
|
7701
|
+
localServices.push({
|
|
7702
|
+
label: "API reference",
|
|
7703
|
+
url: `http://localhost:${webPort}${rpcPath}/api-reference`
|
|
7704
|
+
});
|
|
7705
|
+
}
|
|
7706
|
+
if (addons?.includes("starlight")) localServices.push({
|
|
7707
|
+
label: "Docs",
|
|
7708
|
+
url: "http://localhost:4321"
|
|
7709
|
+
});
|
|
7710
|
+
if (addons?.includes("fumadocs")) localServices.push({
|
|
7711
|
+
label: "Fumadocs",
|
|
7712
|
+
url: "http://localhost:4000"
|
|
7713
|
+
});
|
|
7714
|
+
output += `\n${pc.bold("Local development")}\n`;
|
|
7715
|
+
if (localDevelopmentNote) output += `${pc.dim(localDevelopmentNote)}\n`;
|
|
7716
|
+
if (localServices.length > 0) {
|
|
7717
|
+
const labelWidth = Math.max(...localServices.map(({ label }) => label.length));
|
|
7718
|
+
for (const { label, url } of localServices) output += `${pc.dim(label.padEnd(labelWidth))} ${pc.cyan(url)}\n`;
|
|
7016
7719
|
}
|
|
7017
|
-
if (addons?.includes("starlight")) output += `${pc.cyan("•")} Docs: http://localhost:4321\n`;
|
|
7018
|
-
if (addons?.includes("fumadocs")) output += `${pc.cyan("•")} Fumadocs: http://localhost:4000\n`;
|
|
7019
7720
|
}
|
|
7020
7721
|
if (nativeInstructions) output += `\n${nativeInstructions.trim()}\n`;
|
|
7021
7722
|
if (databaseInstructions) output += `\n${databaseInstructions.trim()}\n`;
|
|
@@ -7035,10 +7736,15 @@ async function displayPostInstallInstructions(config) {
|
|
|
7035
7736
|
if (bunWebNativeWarning) output += `\n${bunWebNativeWarning.trim()}\n`;
|
|
7036
7737
|
const sponsorsResult = await fetchSponsorsQuietly();
|
|
7037
7738
|
const specialSponsorsSection = sponsorsResult.isOk() ? formatPostInstallSpecialSponsorsSection(sponsorsResult.value) : "";
|
|
7038
|
-
|
|
7039
|
-
output
|
|
7040
|
-
|
|
7041
|
-
|
|
7739
|
+
log.message([], { spacing: 1 });
|
|
7740
|
+
box(output.trimEnd(), pc.bold("Next steps"), {
|
|
7741
|
+
contentPadding: 2,
|
|
7742
|
+
formatBorder: pc.dim,
|
|
7743
|
+
rounded: true,
|
|
7744
|
+
width: "auto"
|
|
7745
|
+
});
|
|
7746
|
+
if (specialSponsorsSection) cliLog.message(specialSponsorsSection);
|
|
7747
|
+
cliLog.message(`${pc.bold("Like Better T Stack?")} ${pc.dim("Star the project on GitHub")}\n${pc.cyan("https://github.com/AmanVarshney01/create-better-t-stack")}`);
|
|
7042
7748
|
}
|
|
7043
7749
|
function getNativeInstructions(isConvex, isBackendSelf, frontend, runCmd) {
|
|
7044
7750
|
const envVar = isConvex ? "EXPO_PUBLIC_CONVEX_URL" : "EXPO_PUBLIC_SERVER_URL";
|
|
@@ -7065,43 +7771,82 @@ function getVitePlusNativeHooksInstructions(runCmd) {
|
|
|
7065
7771
|
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`;
|
|
7066
7772
|
}
|
|
7067
7773
|
async function getDatabaseInstructions(database, orm, runCmd, _runtime, dbSetup, webDeploy, serverDeploy, backend) {
|
|
7068
|
-
const
|
|
7774
|
+
const notes = [];
|
|
7775
|
+
const commands = [];
|
|
7069
7776
|
const isD1Alchemy = dbSetup === "d1" && (serverDeploy === "cloudflare" || backend === "self" && webDeploy === "cloudflare");
|
|
7070
7777
|
if (dbSetup === "docker") {
|
|
7071
7778
|
const dockerStatus = await getDockerStatus(database);
|
|
7072
|
-
if (dockerStatus.message)
|
|
7073
|
-
instructions.push(dockerStatus.message);
|
|
7074
|
-
instructions.push("");
|
|
7075
|
-
}
|
|
7779
|
+
if (dockerStatus.message) notes.push(dockerStatus.message);
|
|
7076
7780
|
}
|
|
7077
7781
|
if (isD1Alchemy) {
|
|
7078
|
-
if (orm === "drizzle")
|
|
7782
|
+
if (orm === "drizzle") commands.push({
|
|
7783
|
+
label: "Generate migrations",
|
|
7784
|
+
command: `${runCmd} db:generate`
|
|
7785
|
+
});
|
|
7079
7786
|
else if (orm === "prisma") {
|
|
7080
|
-
|
|
7081
|
-
|
|
7787
|
+
commands.push({
|
|
7788
|
+
label: "Generate client",
|
|
7789
|
+
command: `${runCmd} db:generate`
|
|
7790
|
+
});
|
|
7791
|
+
commands.push({
|
|
7792
|
+
label: "Apply migrations",
|
|
7793
|
+
command: `${runCmd} db:migrate`
|
|
7794
|
+
});
|
|
7082
7795
|
}
|
|
7083
7796
|
}
|
|
7084
7797
|
if (dbSetup === "planetscale") {
|
|
7085
|
-
if (database === "mysql" && orm === "drizzle")
|
|
7086
|
-
if (database === "mysql" && orm === "prisma")
|
|
7798
|
+
if (database === "mysql" && orm === "drizzle") notes.push(`${pc.yellow("NOTE:")} Enable foreign key constraints in PlanetScale database settings`);
|
|
7799
|
+
if (database === "mysql" && orm === "prisma") notes.push(`${pc.yellow("NOTE:")} How to handle Prisma migrations with PlanetScale:\n https://github.com/prisma/prisma/issues/7292`);
|
|
7087
7800
|
}
|
|
7088
|
-
if (dbSetup === "turso" && orm === "prisma")
|
|
7801
|
+
if (dbSetup === "turso" && orm === "prisma") notes.push(`${pc.yellow("NOTE:")} Follow Turso's Prisma guide for migrations via the Turso CLI:\n https://docs.turso.tech/sdk/ts/orm/prisma`);
|
|
7089
7802
|
if (orm === "prisma") {
|
|
7090
|
-
if (database === "mongodb" && dbSetup === "docker")
|
|
7091
|
-
if (dbSetup === "docker")
|
|
7803
|
+
if (database === "mongodb" && dbSetup === "docker") notes.push(`${pc.yellow("WARNING:")} Prisma + MongoDB + Docker combination\n may not work.`);
|
|
7804
|
+
if (dbSetup === "docker") commands.push({
|
|
7805
|
+
label: "Start database",
|
|
7806
|
+
command: `${runCmd} db:start`
|
|
7807
|
+
});
|
|
7092
7808
|
if (!isD1Alchemy) {
|
|
7093
|
-
|
|
7094
|
-
|
|
7809
|
+
commands.push({
|
|
7810
|
+
label: "Generate client",
|
|
7811
|
+
command: `${runCmd} db:generate`
|
|
7812
|
+
});
|
|
7813
|
+
commands.push({
|
|
7814
|
+
label: "Apply schema",
|
|
7815
|
+
command: `${runCmd} db:push`
|
|
7816
|
+
});
|
|
7095
7817
|
}
|
|
7096
|
-
if (!isD1Alchemy)
|
|
7818
|
+
if (!isD1Alchemy) commands.push({
|
|
7819
|
+
label: "Open studio",
|
|
7820
|
+
command: `${runCmd} db:studio`
|
|
7821
|
+
});
|
|
7097
7822
|
} else if (orm === "drizzle") {
|
|
7098
|
-
if (dbSetup === "docker")
|
|
7099
|
-
|
|
7100
|
-
|
|
7823
|
+
if (dbSetup === "docker") commands.push({
|
|
7824
|
+
label: "Start database",
|
|
7825
|
+
command: `${runCmd} db:start`
|
|
7826
|
+
});
|
|
7827
|
+
if (!isD1Alchemy) commands.push({
|
|
7828
|
+
label: "Apply schema",
|
|
7829
|
+
command: `${runCmd} db:push`
|
|
7830
|
+
});
|
|
7831
|
+
if (!isD1Alchemy) commands.push({
|
|
7832
|
+
label: "Open studio",
|
|
7833
|
+
command: `${runCmd} db:studio`
|
|
7834
|
+
});
|
|
7101
7835
|
} else if (orm === "mongoose") {
|
|
7102
|
-
if (dbSetup === "docker")
|
|
7103
|
-
|
|
7104
|
-
|
|
7836
|
+
if (dbSetup === "docker") commands.push({
|
|
7837
|
+
label: "Start database",
|
|
7838
|
+
command: `${runCmd} db:start`
|
|
7839
|
+
});
|
|
7840
|
+
} else if (orm === "none") notes.push(`${pc.yellow("NOTE:")} Manual database schema setup required.`);
|
|
7841
|
+
if (notes.length === 0 && commands.length === 0) return "";
|
|
7842
|
+
const sections = [pc.bold("Database")];
|
|
7843
|
+
if (notes.length > 0) sections.push(notes.join("\n"));
|
|
7844
|
+
if (commands.length > 0) {
|
|
7845
|
+
const labelWidth = Math.max(...commands.map(({ label }) => label.length));
|
|
7846
|
+
const commandRows = commands.map(({ label, command }) => `${pc.dim(label.padEnd(labelWidth))} ${pc.cyan(command)}`);
|
|
7847
|
+
sections.push(commandRows.join("\n"));
|
|
7848
|
+
}
|
|
7849
|
+
return sections.join("\n");
|
|
7105
7850
|
}
|
|
7106
7851
|
function getTauriInstructions(runCmd, frontend) {
|
|
7107
7852
|
const staticBuildNote = getDesktopStaticBuildNote(frontend);
|
|
@@ -7268,7 +8013,7 @@ async function createProject(options, cliInput = {}) {
|
|
|
7268
8013
|
})
|
|
7269
8014
|
}));
|
|
7270
8015
|
yield* Result.await(formatProject(projectDir));
|
|
7271
|
-
if (!isSilent()) log.success("Project
|
|
8016
|
+
if (!isSilent()) log.success("Project scaffolded");
|
|
7272
8017
|
if (options.install) yield* Result.await(installDependencies({
|
|
7273
8018
|
projectDir,
|
|
7274
8019
|
packageManager: options.packageManager
|
|
@@ -7363,19 +8108,16 @@ async function createProjectHandler(input, options = {}) {
|
|
|
7363
8108
|
async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
|
|
7364
8109
|
return Result.gen(async function* () {
|
|
7365
8110
|
if (!isSilent() && input.renderTitle !== false) renderTitle();
|
|
7366
|
-
if (!isSilent()) intro(pc.magenta("
|
|
7367
|
-
if (!isSilent() && input.yolo)
|
|
8111
|
+
if (!isSilent()) intro(pc.magenta("Configure your new project"));
|
|
8112
|
+
if (!isSilent() && input.yolo) log.warn(pc.yellow("YOLO mode enabled — compatibility checks are disabled."));
|
|
7368
8113
|
let currentPathInput;
|
|
7369
8114
|
if (isSilent()) currentPathInput = yield* Result.await(resolveProjectNameForSilent(input));
|
|
7370
8115
|
else if (input.yes && input.projectName) currentPathInput = input.projectName;
|
|
7371
8116
|
else if (input.yes) {
|
|
7372
8117
|
const defaultConfig = getDefaultConfig();
|
|
7373
8118
|
let defaultName = defaultConfig.relativePath;
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
defaultName = `${defaultConfig.projectName}-${counter}`;
|
|
7377
|
-
counter++;
|
|
7378
|
-
}
|
|
8119
|
+
const defaultPathState = yield* Result.await(inspectProjectPath(path.resolve(process.cwd(), defaultName)));
|
|
8120
|
+
if (defaultPathState !== "missing" && defaultPathState !== "empty-directory") defaultName = yield* Result.await(findAvailableIncrementedPath(defaultConfig.projectName));
|
|
7379
8121
|
currentPathInput = defaultName;
|
|
7380
8122
|
} else currentPathInput = yield* Result.await(Result.tryPromise({
|
|
7381
8123
|
try: async () => getProjectName(input.projectName),
|
|
@@ -7394,6 +8136,7 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
|
|
|
7394
8136
|
finalPathInput = conflictResult.finalPathInput;
|
|
7395
8137
|
shouldClearDirectory = conflictResult.shouldClearDirectory;
|
|
7396
8138
|
yield* validateResolvedProjectPathInput(finalPathInput);
|
|
8139
|
+
yield* Result.await(validateSafeProjectDirectoryPath(finalPathInput));
|
|
7397
8140
|
let finalResolvedPath;
|
|
7398
8141
|
let finalBaseName;
|
|
7399
8142
|
if (input.dryRun) {
|
|
@@ -7424,10 +8167,7 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
|
|
|
7424
8167
|
if (templateConfig) {
|
|
7425
8168
|
const templateName = input.template.toUpperCase();
|
|
7426
8169
|
const templateDescription = getTemplateDescription(input.template);
|
|
7427
|
-
if (!isSilent()) {
|
|
7428
|
-
log.message(pc.bold(pc.cyan(`Using template: ${pc.white(templateName)}`)));
|
|
7429
|
-
log.message(pc.dim(` ${templateDescription}`));
|
|
7430
|
-
}
|
|
8170
|
+
if (!isSilent()) log.info(`${pc.dim("Template")} ${pc.bold(pc.cyan(templateName))}\n${pc.dim(templateDescription)}`);
|
|
7431
8171
|
const userOverrides = {};
|
|
7432
8172
|
for (const [key, value] of Object.entries(originalInput)) if (value !== void 0) userOverrides[key] = value;
|
|
7433
8173
|
cliInput = {
|
|
@@ -7458,10 +8198,7 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
|
|
|
7458
8198
|
message: validationResult.error.message,
|
|
7459
8199
|
cause: validationResult.error
|
|
7460
8200
|
}));
|
|
7461
|
-
if (!isSilent())
|
|
7462
|
-
log.info(pc.yellow("Using default/flag options (config prompts skipped):"));
|
|
7463
|
-
log.message(displayConfig(config));
|
|
7464
|
-
}
|
|
8201
|
+
if (!isSilent()) log.info(pc.dim("Quick setup selected — using defaults and provided flags."));
|
|
7465
8202
|
} else {
|
|
7466
8203
|
const flagConfigResult = processAndValidateFlags(cliInput, providedFlags, finalBaseName);
|
|
7467
8204
|
if (flagConfigResult.isErr()) return Result.err(new CLIError({
|
|
@@ -7470,13 +8207,10 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
|
|
|
7470
8207
|
}));
|
|
7471
8208
|
const flagConfig = flagConfigResult.value;
|
|
7472
8209
|
const { projectName: _projectNameFromFlags, ...otherFlags } = flagConfig;
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
log.message(displayConfig(otherFlags));
|
|
7476
|
-
log.message("");
|
|
7477
|
-
}
|
|
8210
|
+
const isTemplateSetup = input.template && input.template !== "none";
|
|
8211
|
+
if (!isSilent() && !isTemplateSetup && Object.keys(otherFlags).length > 0) log.info(pc.dim("Command-line options applied."));
|
|
7478
8212
|
config = yield* Result.await(Result.tryPromise({
|
|
7479
|
-
try: async () => gatherConfig(flagConfig, finalBaseName, finalResolvedPath, finalPathInput),
|
|
8213
|
+
try: async () => gatherConfig(flagConfig, finalBaseName, finalResolvedPath, finalPathInput, { skipCompatibilityChecks: cliInput.yolo }),
|
|
7480
8214
|
catch: (e) => {
|
|
7481
8215
|
if (e instanceof UserCancelledError) return e;
|
|
7482
8216
|
return new CLIError({
|
|
@@ -7498,12 +8232,16 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
|
|
|
7498
8232
|
const addonsValidationResult = validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth, config.backend, config.runtime);
|
|
7499
8233
|
if (addonsValidationResult.isErr()) return Result.err(new CLIError({ message: addonsValidationResult.error.message }));
|
|
7500
8234
|
}
|
|
8235
|
+
if (!isSilent()) {
|
|
8236
|
+
log.info(pc.magenta(pc.bold("Stack ready")));
|
|
8237
|
+
log.message(displayConfig(config));
|
|
8238
|
+
}
|
|
7501
8239
|
const reproducibleCommand = generateReproducibleCommand(config);
|
|
7502
8240
|
if (input.dryRun) {
|
|
7503
8241
|
const elapsedTimeMs = Date.now() - startTime;
|
|
7504
8242
|
if (!isSilent()) {
|
|
7505
8243
|
if (shouldClearDirectory) log.warn(pc.yellow(`Dry run: directory "${finalPathInput}" would be cleared due to overwrite strategy.`));
|
|
7506
|
-
log.success(pc.green("
|
|
8244
|
+
log.success(pc.green("Configuration ready. No files were written."));
|
|
7507
8245
|
log.message(pc.dim(`Target directory: ${finalResolvedPath}`));
|
|
7508
8246
|
log.message(pc.dim(`Run without --dry-run to create the project.`));
|
|
7509
8247
|
outro(pc.magenta("Dry run complete."));
|
|
@@ -7522,14 +8260,19 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
|
|
|
7522
8260
|
manualDb: cliInput.manualDb ?? input.manualDb,
|
|
7523
8261
|
dbSetupOptions: effectiveDbSetupOptions
|
|
7524
8262
|
}));
|
|
7525
|
-
if (!isSilent()) log.success(pc.blue(`You can reproduce this setup with the following command:\n${reproducibleCommand}`));
|
|
7526
8263
|
await trackProjectCreation(config, input.disableAnalytics);
|
|
7527
8264
|
const historyResult = await addToHistory(config, reproducibleCommand);
|
|
7528
|
-
if (historyResult.isErr() && !isSilent())
|
|
8265
|
+
if (historyResult.isErr() && !isSilent()) {
|
|
8266
|
+
log.warn(pc.yellow(historyResult.error.message));
|
|
8267
|
+
log.message(`${pc.dim("Recreate this stack")}\n${pc.cyan(reproducibleCommand)}`);
|
|
8268
|
+
} else if (!isSilent()) {
|
|
8269
|
+
const historyCommand = getCliSubcommandCommand("history", config.packageManager);
|
|
8270
|
+
log.message(`${pc.dim("Setup saved to history")}\n${pc.cyan(historyCommand)}`);
|
|
8271
|
+
}
|
|
7529
8272
|
const elapsedTimeMs = Date.now() - startTime;
|
|
7530
8273
|
if (!isSilent()) {
|
|
7531
|
-
const elapsedTimeInSeconds = (elapsedTimeMs / 1e3).toFixed(
|
|
7532
|
-
outro(pc.magenta(`Project
|
|
8274
|
+
const elapsedTimeInSeconds = (elapsedTimeMs / 1e3).toFixed(1);
|
|
8275
|
+
outro(pc.magenta(`Project ready in ${pc.bold(`${elapsedTimeInSeconds}s`)}`));
|
|
7533
8276
|
}
|
|
7534
8277
|
return Result.ok({
|
|
7535
8278
|
success: true,
|
|
@@ -7582,15 +8325,23 @@ async function handleDirectoryConflictResult(currentPathInput, strategy) {
|
|
|
7582
8325
|
});
|
|
7583
8326
|
}
|
|
7584
8327
|
async function handleDirectoryConflictProgrammatically(currentPathInput, strategy) {
|
|
7585
|
-
const
|
|
7586
|
-
if (
|
|
7587
|
-
|
|
7588
|
-
|
|
7589
|
-
});
|
|
7590
|
-
if (!((await fs.readdir(currentPath)).length > 0)) return Result.ok({
|
|
8328
|
+
const pathStateResult = await inspectProjectPath(path.resolve(process.cwd(), currentPathInput));
|
|
8329
|
+
if (pathStateResult.isErr()) return Result.err(pathStateResult.error);
|
|
8330
|
+
const pathState = pathStateResult.value;
|
|
8331
|
+
if (pathState === "missing" || pathState === "empty-directory") return Result.ok({
|
|
7591
8332
|
finalPathInput: currentPathInput,
|
|
7592
8333
|
shouldClearDirectory: false
|
|
7593
8334
|
});
|
|
8335
|
+
if (strategy === "increment") {
|
|
8336
|
+
const incrementResult = await findAvailableIncrementedPath(currentPathInput);
|
|
8337
|
+
if (incrementResult.isErr()) return Result.err(incrementResult.error);
|
|
8338
|
+
return Result.ok({
|
|
8339
|
+
finalPathInput: incrementResult.value,
|
|
8340
|
+
shouldClearDirectory: false
|
|
8341
|
+
});
|
|
8342
|
+
}
|
|
8343
|
+
if (pathState === "symbolic-link") return Result.err(new CLIError({ message: `Project path "${currentPathInput}" is a symbolic link. Choose a real directory or use directoryConflict: "increment".` }));
|
|
8344
|
+
if (pathState === "non-directory") return Result.err(new CLIError({ message: `Project path "${currentPathInput}" exists and is not a directory. Choose a different path or use directoryConflict: "increment".` }));
|
|
7594
8345
|
switch (strategy) {
|
|
7595
8346
|
case "overwrite": return Result.ok({
|
|
7596
8347
|
finalPathInput: currentPathInput,
|
|
@@ -7600,19 +8351,6 @@ async function handleDirectoryConflictProgrammatically(currentPathInput, strateg
|
|
|
7600
8351
|
finalPathInput: currentPathInput,
|
|
7601
8352
|
shouldClearDirectory: false
|
|
7602
8353
|
});
|
|
7603
|
-
case "increment": {
|
|
7604
|
-
let counter = 1;
|
|
7605
|
-
const baseName = currentPathInput;
|
|
7606
|
-
let finalPathInput = `${baseName}-${counter}`;
|
|
7607
|
-
while (await fs.pathExists(path.resolve(process.cwd(), finalPathInput)) && (await fs.readdir(path.resolve(process.cwd(), finalPathInput))).length > 0) {
|
|
7608
|
-
counter++;
|
|
7609
|
-
finalPathInput = `${baseName}-${counter}`;
|
|
7610
|
-
}
|
|
7611
|
-
return Result.ok({
|
|
7612
|
-
finalPathInput,
|
|
7613
|
-
shouldClearDirectory: false
|
|
7614
|
-
});
|
|
7615
|
-
}
|
|
7616
8354
|
case "error": return Result.err(new DirectoryConflictError({ directory: currentPathInput }));
|
|
7617
8355
|
default: return Result.err(new DirectoryConflictError({ directory: currentPathInput }));
|
|
7618
8356
|
}
|
|
@@ -7647,6 +8385,10 @@ const SchemaNameSchema = z.enum([
|
|
|
7647
8385
|
"betterTStackConfigFile",
|
|
7648
8386
|
"initResult"
|
|
7649
8387
|
]).default("all");
|
|
8388
|
+
const CreateVirtualInputSchema = types_exports.ProjectConfigSchema.omit({
|
|
8389
|
+
projectDir: true,
|
|
8390
|
+
relativePath: true
|
|
8391
|
+
}).partial().strict();
|
|
7650
8392
|
const t = initTRPC.meta().create();
|
|
7651
8393
|
function getCliSchemaJson() {
|
|
7652
8394
|
return createCli({
|
|
@@ -7702,11 +8444,11 @@ const router = t.router({
|
|
|
7702
8444
|
projectName,
|
|
7703
8445
|
...options
|
|
7704
8446
|
});
|
|
7705
|
-
if (options.verbose
|
|
8447
|
+
if (options.verbose) return result;
|
|
7706
8448
|
}),
|
|
7707
8449
|
createJson: t.procedure.meta({
|
|
7708
8450
|
description: "Create a project from a raw JSON payload (agent-friendly)",
|
|
7709
|
-
jsonInput:
|
|
8451
|
+
jsonInput: "always"
|
|
7710
8452
|
}).input(types_exports.CreateInputSchema).mutation(async ({ input }) => {
|
|
7711
8453
|
const result = await createProjectHandler(input, { silent: true });
|
|
7712
8454
|
if (!result) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
@@ -7721,13 +8463,14 @@ const router = t.router({
|
|
|
7721
8463
|
addons: z.array(types_exports.AddonsSchema).optional().describe("Addons to add"),
|
|
7722
8464
|
install: z.boolean().optional().default(false).describe("Install dependencies after adding"),
|
|
7723
8465
|
packageManager: types_exports.PackageManagerSchema.optional().describe("Package manager to use"),
|
|
7724
|
-
projectDir: z.string().optional().describe("Project directory (defaults to current)")
|
|
8466
|
+
projectDir: z.string().optional().describe("Project directory (defaults to current)"),
|
|
8467
|
+
dryRun: z.boolean().optional().default(false).describe("Preview addon changes without writing files")
|
|
7725
8468
|
})).mutation(async ({ input }) => {
|
|
7726
8469
|
await addHandler(input);
|
|
7727
8470
|
}),
|
|
7728
8471
|
addJson: t.procedure.meta({
|
|
7729
8472
|
description: "Add addons from a raw JSON payload (agent-friendly)",
|
|
7730
|
-
jsonInput:
|
|
8473
|
+
jsonInput: "always"
|
|
7731
8474
|
}).input(types_exports.AddInputSchema).mutation(async ({ input }) => {
|
|
7732
8475
|
const result = await addHandler(input, { silent: true });
|
|
7733
8476
|
if (!result) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
@@ -7749,6 +8492,12 @@ function createBtsCli() {
|
|
|
7749
8492
|
version: getLatestCLIVersion()
|
|
7750
8493
|
});
|
|
7751
8494
|
}
|
|
8495
|
+
function formatInputValidationError(label, error) {
|
|
8496
|
+
return `Invalid ${label} input: ${error.issues.map((issue) => {
|
|
8497
|
+
const field = issue.path.join(".");
|
|
8498
|
+
return field ? `${field}: ${issue.message}` : issue.message;
|
|
8499
|
+
}).join("; ")}`;
|
|
8500
|
+
}
|
|
7752
8501
|
/**
|
|
7753
8502
|
* Programmatic API to create a new Better-T-Stack project.
|
|
7754
8503
|
* Returns a Result type - no console output, no interactive prompts.
|
|
@@ -7775,13 +8524,21 @@ function createBtsCli() {
|
|
|
7775
8524
|
* ```
|
|
7776
8525
|
*/
|
|
7777
8526
|
async function create(projectName, options) {
|
|
7778
|
-
const
|
|
8527
|
+
const rawInput = options === void 0 || typeof options === "object" && options !== null ? {
|
|
7779
8528
|
...options,
|
|
7780
|
-
projectName
|
|
8529
|
+
projectName
|
|
8530
|
+
} : options;
|
|
8531
|
+
const parsedInput = types_exports.CreateInputSchema.safeParse(rawInput);
|
|
8532
|
+
if (!parsedInput.success) return Result.err(new CLIError({
|
|
8533
|
+
message: formatInputValidationError("create", parsedInput.error),
|
|
8534
|
+
cause: parsedInput.error
|
|
8535
|
+
}));
|
|
8536
|
+
const input = {
|
|
8537
|
+
...parsedInput.data,
|
|
7781
8538
|
renderTitle: false,
|
|
7782
8539
|
verbose: true,
|
|
7783
|
-
disableAnalytics:
|
|
7784
|
-
directoryConflict:
|
|
8540
|
+
disableAnalytics: parsedInput.data.disableAnalytics ?? true,
|
|
8541
|
+
directoryConflict: parsedInput.data.directoryConflict ?? "error"
|
|
7785
8542
|
};
|
|
7786
8543
|
return Result.tryPromise({
|
|
7787
8544
|
try: async () => {
|
|
@@ -7834,30 +8591,37 @@ async function builder() {
|
|
|
7834
8591
|
* ```
|
|
7835
8592
|
*/
|
|
7836
8593
|
async function createVirtual(options) {
|
|
8594
|
+
const parsedInput = CreateVirtualInputSchema.safeParse(options);
|
|
8595
|
+
if (!parsedInput.success) return Result.err(new GeneratorError({
|
|
8596
|
+
message: formatInputValidationError("virtual create", parsedInput.error),
|
|
8597
|
+
phase: "validation",
|
|
8598
|
+
cause: parsedInput.error
|
|
8599
|
+
}));
|
|
8600
|
+
const virtualOptions = parsedInput.data;
|
|
7837
8601
|
const config = {
|
|
7838
|
-
projectName:
|
|
8602
|
+
projectName: virtualOptions.projectName || "my-project",
|
|
7839
8603
|
projectDir: "/virtual",
|
|
7840
8604
|
relativePath: "./virtual",
|
|
7841
|
-
addonOptions:
|
|
7842
|
-
dbSetupOptions:
|
|
7843
|
-
database:
|
|
7844
|
-
orm:
|
|
7845
|
-
backend:
|
|
7846
|
-
runtime:
|
|
7847
|
-
frontend:
|
|
7848
|
-
addons:
|
|
7849
|
-
examples:
|
|
7850
|
-
auth:
|
|
7851
|
-
payments:
|
|
7852
|
-
git:
|
|
7853
|
-
packageManager:
|
|
8605
|
+
addonOptions: virtualOptions.addonOptions,
|
|
8606
|
+
dbSetupOptions: virtualOptions.dbSetupOptions,
|
|
8607
|
+
database: virtualOptions.database || "none",
|
|
8608
|
+
orm: virtualOptions.orm || "none",
|
|
8609
|
+
backend: virtualOptions.backend || "hono",
|
|
8610
|
+
runtime: virtualOptions.runtime || "bun",
|
|
8611
|
+
frontend: virtualOptions.frontend || ["tanstack-router"],
|
|
8612
|
+
addons: virtualOptions.addons || [],
|
|
8613
|
+
examples: virtualOptions.examples || [],
|
|
8614
|
+
auth: virtualOptions.auth || "none",
|
|
8615
|
+
payments: virtualOptions.payments || "none",
|
|
8616
|
+
git: virtualOptions.git ?? false,
|
|
8617
|
+
packageManager: virtualOptions.packageManager || "bun",
|
|
7854
8618
|
install: false,
|
|
7855
|
-
dbSetup:
|
|
7856
|
-
api:
|
|
7857
|
-
webDeploy:
|
|
7858
|
-
serverDeploy:
|
|
8619
|
+
dbSetup: virtualOptions.dbSetup || "none",
|
|
8620
|
+
api: virtualOptions.api || "trpc",
|
|
8621
|
+
webDeploy: virtualOptions.webDeploy || "none",
|
|
8622
|
+
serverDeploy: virtualOptions.serverDeploy || "none"
|
|
7859
8623
|
};
|
|
7860
|
-
const validationResult = validateConfigCompatibility(config, new Set([
|
|
8624
|
+
const validationResult = validateConfigCompatibility(config, /* @__PURE__ */ new Set([
|
|
7861
8625
|
"database",
|
|
7862
8626
|
"orm",
|
|
7863
8627
|
"backend",
|
|
@@ -7894,13 +8658,25 @@ async function createVirtual(options) {
|
|
|
7894
8658
|
* install: true,
|
|
7895
8659
|
* });
|
|
7896
8660
|
*
|
|
7897
|
-
* if (result
|
|
8661
|
+
* if (result.success) {
|
|
7898
8662
|
* console.log(`Added: ${result.addedAddons.join(", ")}`);
|
|
7899
8663
|
* }
|
|
7900
8664
|
* ```
|
|
7901
8665
|
*/
|
|
7902
8666
|
async function add(options = {}) {
|
|
7903
|
-
|
|
8667
|
+
const parsedInput = types_exports.AddInputSchema.safeParse(options);
|
|
8668
|
+
if (!parsedInput.success) return {
|
|
8669
|
+
success: false,
|
|
8670
|
+
addedAddons: [],
|
|
8671
|
+
projectDir: "",
|
|
8672
|
+
error: formatInputValidationError("add", parsedInput.error)
|
|
8673
|
+
};
|
|
8674
|
+
return await addHandler(parsedInput.data, { silent: true }) ?? {
|
|
8675
|
+
success: false,
|
|
8676
|
+
addedAddons: [],
|
|
8677
|
+
projectDir: parsedInput.data.projectDir ?? "",
|
|
8678
|
+
error: "Operation cancelled"
|
|
8679
|
+
};
|
|
7904
8680
|
}
|
|
7905
8681
|
//#endregion
|
|
7906
8682
|
export { ProjectCreationError as C, DirectoryConflictError as S, ValidationError as T, types_exports as _, TEMPLATE_COUNT as a, CompatibilityError as b, builder as c, createVirtual as d, docs as f, sponsors 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, getLatestCLIVersion as v, UserCancelledError as w, DatabaseSetupError as x, CLIError as y };
|