create-better-fullstack 1.6.0 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -8
- package/dist/addons-setup-CBK1Htlc.mjs +5 -0
- package/dist/{addons-setup--oB72n29.mjs → addons-setup-DQa6TRrx.mjs} +174 -104
- package/dist/{bts-config-snHxP_EH.mjs → bts-config-B_rZ4_sj.mjs} +21 -73
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +99 -9
- package/dist/index.mjs +2611 -1198
- package/dist/{mcp-Bx_Esljk.mjs → mcp-CuEEG8e5.mjs} +1 -1
- package/dist/mcp-entry.mjs +88 -21
- package/dist/virtual.d.mts +1 -1
- package/package.json +24 -9
- package/dist/addons-setup-By7Kqjss.mjs +0 -5
package/README.md
CHANGED
|
@@ -1,23 +1,28 @@
|
|
|
1
1
|
# Better Fullstack
|
|
2
2
|
|
|
3
|
-
Scaffold production-ready fullstack apps in seconds. Pick your stack from
|
|
3
|
+
Scaffold production-ready fullstack apps in seconds. Pick your stack from 424 options — the CLI wires everything together.
|
|
4
4
|
|
|
5
5
|
## Quick Start
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
# Using
|
|
9
|
-
|
|
8
|
+
# Using npm
|
|
9
|
+
npm create better-fullstack@latest
|
|
10
|
+
|
|
11
|
+
# Using npx
|
|
12
|
+
npx create-better-fullstack@latest
|
|
10
13
|
|
|
11
14
|
# Using pnpm
|
|
12
15
|
pnpm create better-fullstack@latest
|
|
13
16
|
|
|
14
|
-
# Using
|
|
15
|
-
|
|
17
|
+
# Using bun
|
|
18
|
+
bun create better-fullstack@latest
|
|
16
19
|
|
|
17
20
|
# Using yarn
|
|
18
21
|
yarn create better-fullstack@latest
|
|
19
22
|
```
|
|
20
23
|
|
|
24
|
+
Bun is required only when the generated project selects Bun as its runtime or package manager. Node.js with npm is enough for Node-based projects.
|
|
25
|
+
|
|
21
26
|
## Web Builder
|
|
22
27
|
|
|
23
28
|
Configure your stack visually — pick every option from a UI, preview your choices, and get a ready-to-run command.
|
|
@@ -26,8 +31,8 @@ Configure your stack visually — pick every option from a UI, preview your choi
|
|
|
26
31
|
|
|
27
32
|
## Features
|
|
28
33
|
|
|
29
|
-
- **
|
|
30
|
-
- **
|
|
34
|
+
- **424 options** — frontend, backend, database, auth, payments, AI, DevOps, and more
|
|
35
|
+
- **5 ecosystems** — TypeScript, Rust, Python, Go, Java
|
|
31
36
|
- **Visual builder** — configure your stack in the browser
|
|
32
37
|
- **Wired for you** — every picked integration is preconfigured and working out of the box
|
|
33
38
|
|
|
@@ -37,7 +42,7 @@ Configure your stack visually — pick every option from a UI, preview your choi
|
|
|
37
42
|
--yes # Accept all defaults
|
|
38
43
|
--yolo # Scaffold a random stack — good for exploring
|
|
39
44
|
--template <name> # Use a preset (t3, mern, pern, uniwind)
|
|
40
|
-
--ecosystem <lang> # Start in rust, python, or
|
|
45
|
+
--ecosystem <lang> # Start in typescript, rust, python, go, or java mode
|
|
41
46
|
--version-channel # Dependency channel: stable, latest, beta
|
|
42
47
|
--no-git # Skip git initialization
|
|
43
48
|
--no-install # Skip dependency installation
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { s as dependencyVersionMap, t as readBtsConfig } from "./bts-config-
|
|
2
|
+
import { s as dependencyVersionMap, t as readBtsConfig } from "./bts-config-B_rZ4_sj.mjs";
|
|
3
3
|
import { autocompleteMultiselect, cancel, group, isCancel, log, multiselect, select, spinner } from "@clack/prompts";
|
|
4
4
|
import pc from "picocolors";
|
|
5
5
|
import fs from "fs-extra";
|
|
@@ -178,6 +178,48 @@ function getPackageRunnerPrefix(packageManager) {
|
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
//#endregion
|
|
182
|
+
//#region src/utils/prompt-environment.ts
|
|
183
|
+
function hasOwnProperty(value, property) {
|
|
184
|
+
return Object.prototype.hasOwnProperty.call(value, property);
|
|
185
|
+
}
|
|
186
|
+
function resolveCiValue(environment) {
|
|
187
|
+
if (environment && hasOwnProperty(environment, "ci")) return environment.ci;
|
|
188
|
+
return process.env.CI;
|
|
189
|
+
}
|
|
190
|
+
function isCiEnvironment(value) {
|
|
191
|
+
if (!value) return false;
|
|
192
|
+
const normalizedValue = value.trim().toLowerCase();
|
|
193
|
+
return normalizedValue !== "" && normalizedValue !== "0" && normalizedValue !== "false";
|
|
194
|
+
}
|
|
195
|
+
function canPromptInteractively(environment = {}) {
|
|
196
|
+
const silent = environment.silent ?? isSilent();
|
|
197
|
+
const stdinIsTTY = environment.stdinIsTTY ?? process.stdin.isTTY === true;
|
|
198
|
+
const stdoutIsTTY = environment.stdoutIsTTY ?? process.stdout.isTTY === true;
|
|
199
|
+
const ci = resolveCiValue(environment);
|
|
200
|
+
return !silent && stdinIsTTY && stdoutIsTTY && !isCiEnvironment(ci);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
//#endregion
|
|
204
|
+
//#region src/helpers/addons/interactive-selection.ts
|
|
205
|
+
function shouldPromptForAddonSelection(environment = {}) {
|
|
206
|
+
return canPromptInteractively(environment);
|
|
207
|
+
}
|
|
208
|
+
async function selectAddonOptionOrDefault({ addonName, message, options, defaultValue }) {
|
|
209
|
+
if (!shouldPromptForAddonSelection()) {
|
|
210
|
+
const fallback = options.find((option) => option.value === defaultValue);
|
|
211
|
+
if (!isSilent() && fallback) log.info(`Using default ${addonName} template: ${fallback.label}`);
|
|
212
|
+
return defaultValue;
|
|
213
|
+
}
|
|
214
|
+
const selection = await select({
|
|
215
|
+
message,
|
|
216
|
+
options,
|
|
217
|
+
initialValue: defaultValue
|
|
218
|
+
});
|
|
219
|
+
if (isCancel(selection)) return exitCancelled("Operation cancelled");
|
|
220
|
+
return selection;
|
|
221
|
+
}
|
|
222
|
+
|
|
181
223
|
//#endregion
|
|
182
224
|
//#region src/helpers/addons/fumadocs-setup.ts
|
|
183
225
|
const TEMPLATES$2 = {
|
|
@@ -211,17 +253,16 @@ async function setupFumadocs(config) {
|
|
|
211
253
|
const { packageManager, projectDir } = config;
|
|
212
254
|
try {
|
|
213
255
|
log.info("Setting up Fumadocs...");
|
|
214
|
-
const
|
|
256
|
+
const args = getPackageExecutionArgs(packageManager, `create-fumadocs-app@latest fumadocs --template ${TEMPLATES$2[await selectAddonOptionOrDefault({
|
|
257
|
+
addonName: "Fumadocs",
|
|
215
258
|
message: "Choose a template",
|
|
216
|
-
options: Object.entries(TEMPLATES$2).map(([key, template
|
|
259
|
+
options: Object.entries(TEMPLATES$2).map(([key, template]) => ({
|
|
217
260
|
value: key,
|
|
218
|
-
label: template
|
|
219
|
-
hint: template
|
|
261
|
+
label: template.label,
|
|
262
|
+
hint: template.hint
|
|
220
263
|
})),
|
|
221
|
-
|
|
222
|
-
});
|
|
223
|
-
if (isCancel(template)) return exitCancelled("Operation cancelled");
|
|
224
|
-
const args = getPackageExecutionArgs(packageManager, `create-fumadocs-app@latest fumadocs --template ${TEMPLATES$2[template].value} --src --pm ${packageManager} --no-git`);
|
|
264
|
+
defaultValue: "next-mdx"
|
|
265
|
+
})].value} --src --pm ${packageManager} --no-git`);
|
|
225
266
|
const appsDir = path.join(projectDir, "apps");
|
|
226
267
|
await fs.ensureDir(appsDir);
|
|
227
268
|
const s = spinner();
|
|
@@ -445,51 +486,63 @@ function filterAgentsForScope(scope) {
|
|
|
445
486
|
return MCP_AGENTS.filter((a) => a.scope === "both" || a.scope === scope);
|
|
446
487
|
}
|
|
447
488
|
async function setupMcp(config) {
|
|
448
|
-
if (shouldSkipExternalCommands()) return;
|
|
449
489
|
const { packageManager, projectDir } = config;
|
|
450
490
|
log.info("Setting up MCP servers...");
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
options: [{
|
|
454
|
-
value: "project",
|
|
455
|
-
label: "Project",
|
|
456
|
-
hint: "Writes to project config files (recommended for teams)"
|
|
457
|
-
}, {
|
|
458
|
-
value: "global",
|
|
459
|
-
label: "Global",
|
|
460
|
-
hint: "Writes to user-level config files (personal machine)"
|
|
461
|
-
}],
|
|
462
|
-
initialValue: "project"
|
|
463
|
-
});
|
|
464
|
-
if (isCancel(scope)) return;
|
|
491
|
+
const skipExternalCommands = shouldSkipExternalCommands();
|
|
492
|
+
const canPrompt = canPromptInteractively() && !skipExternalCommands;
|
|
465
493
|
const recommendedServers = getRecommendedMcpServers(config);
|
|
466
494
|
if (recommendedServers.length === 0) return;
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
495
|
+
let scope = "project";
|
|
496
|
+
let selectedServerKeys = recommendedServers.map((server) => server.key);
|
|
497
|
+
if (canPrompt) {
|
|
498
|
+
const promptedScope = await select({
|
|
499
|
+
message: "Where should MCP servers be installed?",
|
|
500
|
+
options: [{
|
|
501
|
+
value: "project",
|
|
502
|
+
label: "Project",
|
|
503
|
+
hint: "Writes to project config files (recommended for teams)"
|
|
504
|
+
}, {
|
|
505
|
+
value: "global",
|
|
506
|
+
label: "Global",
|
|
507
|
+
hint: "Writes to user-level config files (personal machine)"
|
|
508
|
+
}],
|
|
509
|
+
initialValue: "project"
|
|
510
|
+
});
|
|
511
|
+
if (isCancel(promptedScope)) return;
|
|
512
|
+
scope = promptedScope;
|
|
513
|
+
const promptedServerKeys = await multiselect({
|
|
514
|
+
message: "Select MCP servers to install",
|
|
515
|
+
options: recommendedServers.map((server) => ({
|
|
516
|
+
value: server.key,
|
|
517
|
+
label: server.label,
|
|
518
|
+
hint: server.target
|
|
519
|
+
})),
|
|
520
|
+
required: false,
|
|
521
|
+
initialValues: selectedServerKeys
|
|
522
|
+
});
|
|
523
|
+
if (isCancel(promptedServerKeys) || promptedServerKeys.length === 0) return;
|
|
524
|
+
selectedServerKeys = [...promptedServerKeys];
|
|
525
|
+
}
|
|
478
526
|
const agentOptions = filterAgentsForScope(scope).map((a) => ({
|
|
479
527
|
value: a.value,
|
|
480
528
|
label: a.label
|
|
481
529
|
}));
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
530
|
+
const defaultAgents = uniqueValues$1([
|
|
531
|
+
"cursor",
|
|
532
|
+
"claude-code",
|
|
533
|
+
"vscode"
|
|
534
|
+
].filter((agent) => agentOptions.some((option) => option.value === agent)));
|
|
535
|
+
let selectedAgents = defaultAgents;
|
|
536
|
+
if (canPrompt) {
|
|
537
|
+
const promptedAgents = await multiselect({
|
|
538
|
+
message: "Select agents to install MCP servers to",
|
|
539
|
+
options: agentOptions,
|
|
540
|
+
required: false,
|
|
541
|
+
initialValues: defaultAgents
|
|
542
|
+
});
|
|
543
|
+
if (isCancel(promptedAgents) || promptedAgents.length === 0) return;
|
|
544
|
+
selectedAgents = [...promptedAgents];
|
|
545
|
+
}
|
|
493
546
|
const serversByKey = new Map(recommendedServers.map((server) => [server.key, server]));
|
|
494
547
|
const selectedServers = [];
|
|
495
548
|
for (const key of selectedServerKeys) {
|
|
@@ -497,6 +550,7 @@ async function setupMcp(config) {
|
|
|
497
550
|
if (server) selectedServers.push(server);
|
|
498
551
|
}
|
|
499
552
|
if (selectedServers.length === 0) return;
|
|
553
|
+
if (skipExternalCommands) return;
|
|
500
554
|
const installSpinner = spinner();
|
|
501
555
|
installSpinner.start("Installing MCP servers...");
|
|
502
556
|
const runner = getPackageRunnerPrefix(packageManager);
|
|
@@ -572,45 +626,50 @@ async function setupRuler(config) {
|
|
|
572
626
|
log.error(pc.red("Ruler template directory not found. Please ensure ruler addon is properly installed."));
|
|
573
627
|
return;
|
|
574
628
|
}
|
|
575
|
-
const
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
629
|
+
const EDITORS$1 = {
|
|
630
|
+
agentsmd: { label: "Agents.md" },
|
|
631
|
+
aider: { label: "Aider" },
|
|
632
|
+
amazonqcli: { label: "Amazon Q CLI" },
|
|
633
|
+
amp: { label: "AMP" },
|
|
634
|
+
antigravity: { label: "Antigravity" },
|
|
635
|
+
augmentcode: { label: "AugmentCode" },
|
|
636
|
+
claude: { label: "Claude Code" },
|
|
637
|
+
cline: { label: "Cline" },
|
|
638
|
+
codex: { label: "OpenAI Codex CLI" },
|
|
639
|
+
copilot: { label: "GitHub Copilot" },
|
|
640
|
+
crush: { label: "Crush" },
|
|
641
|
+
cursor: { label: "Cursor" },
|
|
642
|
+
firebase: { label: "Firebase Studio" },
|
|
643
|
+
firebender: { label: "Firebender" },
|
|
644
|
+
"gemini-cli": { label: "Gemini CLI" },
|
|
645
|
+
goose: { label: "Goose" },
|
|
646
|
+
jules: { label: "Jules" },
|
|
647
|
+
junie: { label: "Junie" },
|
|
648
|
+
kilocode: { label: "Kilo Code" },
|
|
649
|
+
kiro: { label: "Kiro" },
|
|
650
|
+
mistral: { label: "Mistral" },
|
|
651
|
+
opencode: { label: "OpenCode" },
|
|
652
|
+
openhands: { label: "Open Hands" },
|
|
653
|
+
qwen: { label: "Qwen" },
|
|
654
|
+
roo: { label: "RooCode" },
|
|
655
|
+
trae: { label: "Trae AI" },
|
|
656
|
+
warp: { label: "Warp" },
|
|
657
|
+
windsurf: { label: "Windsurf" },
|
|
658
|
+
zed: { label: "Zed" }
|
|
659
|
+
};
|
|
660
|
+
let selectedEditors = [];
|
|
661
|
+
if (canPromptInteractively()) {
|
|
662
|
+
const prompted = await autocompleteMultiselect({
|
|
663
|
+
message: "Select AI assistants for Ruler",
|
|
664
|
+
options: Object.entries(EDITORS$1).map(([key, v]) => ({
|
|
665
|
+
value: key,
|
|
666
|
+
label: v.label
|
|
667
|
+
})),
|
|
668
|
+
required: false
|
|
669
|
+
});
|
|
670
|
+
if (isCancel(prompted)) return exitCancelled("Operation cancelled");
|
|
671
|
+
selectedEditors = [...prompted];
|
|
672
|
+
} else log.info("Skipping AI assistant selection (non-interactive mode).");
|
|
614
673
|
if (selectedEditors.length === 0) {
|
|
615
674
|
log.info("No AI assistants selected. To apply rules later, run:");
|
|
616
675
|
log.info(pc.cyan(`${getPackageExecutionCommand(packageManager, "@intellectronica/ruler@latest apply --local-only")}`));
|
|
@@ -886,6 +945,10 @@ async function setupSkills(config) {
|
|
|
886
945
|
}));
|
|
887
946
|
});
|
|
888
947
|
if (skillOptions.length === 0) return;
|
|
948
|
+
if (!canPromptInteractively()) {
|
|
949
|
+
log.info("Skipping skills installation (non-interactive mode). Run the skills CLI manually to install.");
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
889
952
|
const scope = await select({
|
|
890
953
|
message: "Where should skills be installed?",
|
|
891
954
|
options: [{
|
|
@@ -1017,17 +1080,16 @@ async function setupTui(config) {
|
|
|
1017
1080
|
const { packageManager, projectDir } = config;
|
|
1018
1081
|
try {
|
|
1019
1082
|
log.info("Setting up OpenTUI...");
|
|
1020
|
-
const
|
|
1083
|
+
const args = getPackageExecutionArgs(packageManager, `create-tui@latest --template ${await selectAddonOptionOrDefault({
|
|
1084
|
+
addonName: "OpenTUI",
|
|
1021
1085
|
message: "Choose a template",
|
|
1022
|
-
options: Object.entries(TEMPLATES$1).map(([key, template
|
|
1086
|
+
options: Object.entries(TEMPLATES$1).map(([key, template]) => ({
|
|
1023
1087
|
value: key,
|
|
1024
|
-
label: template
|
|
1025
|
-
hint: template
|
|
1088
|
+
label: template.label,
|
|
1089
|
+
hint: template.hint
|
|
1026
1090
|
})),
|
|
1027
|
-
|
|
1028
|
-
});
|
|
1029
|
-
if (isCancel(template)) return exitCancelled("Operation cancelled");
|
|
1030
|
-
const args = getPackageExecutionArgs(packageManager, `create-tui@latest --template ${template} --no-git --no-install tui`);
|
|
1091
|
+
defaultValue: "core"
|
|
1092
|
+
})} --no-git --no-install tui`);
|
|
1031
1093
|
const appsDir = path.join(projectDir, "apps");
|
|
1032
1094
|
await fs.ensureDir(appsDir);
|
|
1033
1095
|
const s = spinner();
|
|
@@ -1123,7 +1185,9 @@ async function setupUltracite(config, gitHooks) {
|
|
|
1123
1185
|
const { packageManager, projectDir, frontend } = config;
|
|
1124
1186
|
try {
|
|
1125
1187
|
log.info("Setting up Ultracite...");
|
|
1126
|
-
const
|
|
1188
|
+
const skipExternalCommands = shouldSkipExternalCommands();
|
|
1189
|
+
const canPrompt = canPromptInteractively() && !skipExternalCommands;
|
|
1190
|
+
const result = canPrompt ? await group({
|
|
1127
1191
|
linter: () => select({
|
|
1128
1192
|
message: "Choose linter/formatter",
|
|
1129
1193
|
options: Object.entries(LINTERS).map(([key, linter$1]) => ({
|
|
@@ -1158,7 +1222,12 @@ async function setupUltracite(config, gitHooks) {
|
|
|
1158
1222
|
})
|
|
1159
1223
|
}, { onCancel: () => {
|
|
1160
1224
|
exitCancelled("Operation cancelled");
|
|
1161
|
-
} })
|
|
1225
|
+
} }) : {
|
|
1226
|
+
linter: "biome",
|
|
1227
|
+
editors: [],
|
|
1228
|
+
agents: [],
|
|
1229
|
+
hooks: []
|
|
1230
|
+
};
|
|
1162
1231
|
const linter = result.linter;
|
|
1163
1232
|
const editors = result.editors;
|
|
1164
1233
|
const agents = result.agents;
|
|
@@ -1171,6 +1240,7 @@ async function setupUltracite(config, gitHooks) {
|
|
|
1171
1240
|
"--linter",
|
|
1172
1241
|
linter
|
|
1173
1242
|
];
|
|
1243
|
+
if (!canPrompt) ultraciteArgs.push("--quiet");
|
|
1174
1244
|
if (frameworks.length > 0) ultraciteArgs.push("--frameworks", ...frameworks);
|
|
1175
1245
|
if (editors.length > 0) ultraciteArgs.push("--editors", ...editors);
|
|
1176
1246
|
if (agents.length > 0) ultraciteArgs.push("--agents", ...agents);
|
|
@@ -1180,6 +1250,7 @@ async function setupUltracite(config, gitHooks) {
|
|
|
1180
1250
|
if (gitHooks.includes("husky")) integrations.push("lint-staged");
|
|
1181
1251
|
ultraciteArgs.push("--integrations", ...integrations);
|
|
1182
1252
|
}
|
|
1253
|
+
if (skipExternalCommands) return;
|
|
1183
1254
|
const args = getPackageExecutionArgs(packageManager, `ultracite@latest ${ultraciteArgs.join(" ")} --skip-install`);
|
|
1184
1255
|
const s = spinner();
|
|
1185
1256
|
s.start("Running Ultracite init command...");
|
|
@@ -1222,17 +1293,16 @@ async function setupWxt(config) {
|
|
|
1222
1293
|
const { packageManager, projectDir } = config;
|
|
1223
1294
|
try {
|
|
1224
1295
|
log.info("Setting up WXT...");
|
|
1225
|
-
const
|
|
1296
|
+
const args = getPackageExecutionArgs(packageManager, `wxt@latest init extension --template ${await selectAddonOptionOrDefault({
|
|
1297
|
+
addonName: "WXT",
|
|
1226
1298
|
message: "Choose a template",
|
|
1227
|
-
options: Object.entries(TEMPLATES).map(([key, template
|
|
1299
|
+
options: Object.entries(TEMPLATES).map(([key, template]) => ({
|
|
1228
1300
|
value: key,
|
|
1229
|
-
label: template
|
|
1230
|
-
hint: template
|
|
1301
|
+
label: template.label,
|
|
1302
|
+
hint: template.hint
|
|
1231
1303
|
})),
|
|
1232
|
-
|
|
1233
|
-
});
|
|
1234
|
-
if (isCancel(template)) return exitCancelled("Operation cancelled");
|
|
1235
|
-
const args = getPackageExecutionArgs(packageManager, `wxt@latest init extension --template ${template} --pm ${packageManager}`);
|
|
1304
|
+
defaultValue: "react"
|
|
1305
|
+
})} --pm ${packageManager}`);
|
|
1236
1306
|
const appsDir = path.join(projectDir, "apps");
|
|
1237
1307
|
await fs.ensureDir(appsDir);
|
|
1238
1308
|
const s = spinner();
|
|
@@ -1346,4 +1416,4 @@ async function setupLefthook(projectDir) {
|
|
|
1346
1416
|
}
|
|
1347
1417
|
|
|
1348
1418
|
//#endregion
|
|
1349
|
-
export {
|
|
1419
|
+
export { setIsFirstPrompt as _, canPromptInteractively as a, CLIError as c, exitWithError as d, handleError as f, runWithContextAsync as g, isSilent as h, setupLefthook as i, UserCancelledError as l, isFirstPrompt as m, setupBiome as n, getPackageExecutionArgs as o, didLastPromptShowUI as p, setupHusky as r, addPackageDependency as s, setupAddons as t, exitCancelled as u, setLastPromptShownUI as v };
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import fs from "fs-extra";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { createCliDefaultProjectConfigBase } from "@better-fullstack/types";
|
|
5
6
|
import { dependencyVersionMap } from "@better-fullstack/template-generator";
|
|
6
7
|
import * as JSONC from "jsonc-parser";
|
|
7
8
|
|
|
@@ -10,6 +11,7 @@ const getUserPkgManager = () => {
|
|
|
10
11
|
const userAgent = process.env.npm_config_user_agent;
|
|
11
12
|
if (userAgent?.startsWith("pnpm")) return "pnpm";
|
|
12
13
|
if (userAgent?.startsWith("bun")) return "bun";
|
|
14
|
+
if (userAgent?.startsWith("yarn")) return "yarn";
|
|
13
15
|
return "npm";
|
|
14
16
|
};
|
|
15
17
|
|
|
@@ -18,79 +20,7 @@ const getUserPkgManager = () => {
|
|
|
18
20
|
const __filename = fileURLToPath(import.meta.url);
|
|
19
21
|
const distPath = path.dirname(__filename);
|
|
20
22
|
const PKG_ROOT = path.join(distPath, "../");
|
|
21
|
-
const DEFAULT_CONFIG_BASE =
|
|
22
|
-
projectName: "my-app",
|
|
23
|
-
relativePath: "my-app",
|
|
24
|
-
ecosystem: "typescript",
|
|
25
|
-
frontend: ["tanstack-router"],
|
|
26
|
-
database: "sqlite",
|
|
27
|
-
orm: "drizzle",
|
|
28
|
-
auth: "better-auth",
|
|
29
|
-
payments: "none",
|
|
30
|
-
email: "none",
|
|
31
|
-
fileUpload: "none",
|
|
32
|
-
effect: "none",
|
|
33
|
-
stateManagement: "none",
|
|
34
|
-
validation: "zod",
|
|
35
|
-
forms: "react-hook-form",
|
|
36
|
-
testing: "vitest",
|
|
37
|
-
ai: "none",
|
|
38
|
-
realtime: "none",
|
|
39
|
-
jobQueue: "none",
|
|
40
|
-
caching: "none",
|
|
41
|
-
i18n: "none",
|
|
42
|
-
search: "none",
|
|
43
|
-
fileStorage: "none",
|
|
44
|
-
animation: "none",
|
|
45
|
-
logging: "none",
|
|
46
|
-
observability: "none",
|
|
47
|
-
featureFlags: "none",
|
|
48
|
-
analytics: "none",
|
|
49
|
-
cms: "none",
|
|
50
|
-
addons: ["turborepo"],
|
|
51
|
-
examples: [],
|
|
52
|
-
git: true,
|
|
53
|
-
install: true,
|
|
54
|
-
versionChannel: "stable",
|
|
55
|
-
dbSetup: "none",
|
|
56
|
-
backend: "hono",
|
|
57
|
-
runtime: "bun",
|
|
58
|
-
api: "trpc",
|
|
59
|
-
webDeploy: "none",
|
|
60
|
-
serverDeploy: "none",
|
|
61
|
-
cssFramework: "tailwind",
|
|
62
|
-
uiLibrary: "shadcn-ui",
|
|
63
|
-
shadcnBase: "radix",
|
|
64
|
-
shadcnStyle: "nova",
|
|
65
|
-
shadcnIconLibrary: "lucide",
|
|
66
|
-
shadcnColorTheme: "neutral",
|
|
67
|
-
shadcnBaseColor: "neutral",
|
|
68
|
-
shadcnFont: "inter",
|
|
69
|
-
shadcnRadius: "default",
|
|
70
|
-
rustWebFramework: "none",
|
|
71
|
-
rustFrontend: "none",
|
|
72
|
-
rustOrm: "none",
|
|
73
|
-
rustApi: "none",
|
|
74
|
-
rustCli: "none",
|
|
75
|
-
rustLibraries: [],
|
|
76
|
-
rustLogging: "tracing",
|
|
77
|
-
rustErrorHandling: "anyhow-thiserror",
|
|
78
|
-
rustCaching: "none",
|
|
79
|
-
pythonWebFramework: "fastapi",
|
|
80
|
-
pythonOrm: "sqlalchemy",
|
|
81
|
-
pythonValidation: "pydantic",
|
|
82
|
-
pythonAi: [],
|
|
83
|
-
pythonAuth: "none",
|
|
84
|
-
pythonTaskQueue: "none",
|
|
85
|
-
pythonGraphql: "none",
|
|
86
|
-
pythonQuality: "ruff",
|
|
87
|
-
goWebFramework: "gin",
|
|
88
|
-
goOrm: "gorm",
|
|
89
|
-
goApi: "none",
|
|
90
|
-
goCli: "none",
|
|
91
|
-
goLogging: "zap",
|
|
92
|
-
aiDocs: ["claude-md"]
|
|
93
|
-
};
|
|
23
|
+
const DEFAULT_CONFIG_BASE = createCliDefaultProjectConfigBase(getUserPkgManager());
|
|
94
24
|
function getDefaultConfig() {
|
|
95
25
|
return {
|
|
96
26
|
...DEFAULT_CONFIG_BASE,
|
|
@@ -101,6 +31,8 @@ function getDefaultConfig() {
|
|
|
101
31
|
examples: [...DEFAULT_CONFIG_BASE.examples],
|
|
102
32
|
rustLibraries: [...DEFAULT_CONFIG_BASE.rustLibraries],
|
|
103
33
|
pythonAi: [...DEFAULT_CONFIG_BASE.pythonAi],
|
|
34
|
+
javaLibraries: [...DEFAULT_CONFIG_BASE.javaLibraries],
|
|
35
|
+
javaTestingLibraries: [...DEFAULT_CONFIG_BASE.javaTestingLibraries],
|
|
104
36
|
aiDocs: [...DEFAULT_CONFIG_BASE.aiDocs]
|
|
105
37
|
};
|
|
106
38
|
}
|
|
@@ -191,6 +123,7 @@ async function writeBtsConfig(projectConfig) {
|
|
|
191
123
|
rustLogging: projectConfig.rustLogging,
|
|
192
124
|
rustErrorHandling: projectConfig.rustErrorHandling,
|
|
193
125
|
rustCaching: projectConfig.rustCaching,
|
|
126
|
+
rustAuth: projectConfig.rustAuth,
|
|
194
127
|
pythonWebFramework: projectConfig.pythonWebFramework,
|
|
195
128
|
pythonOrm: projectConfig.pythonOrm,
|
|
196
129
|
pythonValidation: projectConfig.pythonValidation,
|
|
@@ -204,6 +137,13 @@ async function writeBtsConfig(projectConfig) {
|
|
|
204
137
|
goApi: projectConfig.goApi,
|
|
205
138
|
goCli: projectConfig.goCli,
|
|
206
139
|
goLogging: projectConfig.goLogging,
|
|
140
|
+
goAuth: projectConfig.goAuth,
|
|
141
|
+
javaWebFramework: projectConfig.javaWebFramework,
|
|
142
|
+
javaBuildTool: projectConfig.javaBuildTool,
|
|
143
|
+
javaOrm: projectConfig.javaOrm,
|
|
144
|
+
javaAuth: projectConfig.javaAuth,
|
|
145
|
+
javaLibraries: projectConfig.javaLibraries,
|
|
146
|
+
javaTestingLibraries: projectConfig.javaTestingLibraries,
|
|
207
147
|
aiDocs: projectConfig.aiDocs
|
|
208
148
|
};
|
|
209
149
|
const baseContent = {
|
|
@@ -257,6 +197,7 @@ async function writeBtsConfig(projectConfig) {
|
|
|
257
197
|
rustLogging: btsConfig.rustLogging,
|
|
258
198
|
rustErrorHandling: btsConfig.rustErrorHandling,
|
|
259
199
|
rustCaching: btsConfig.rustCaching,
|
|
200
|
+
rustAuth: btsConfig.rustAuth,
|
|
260
201
|
pythonWebFramework: btsConfig.pythonWebFramework,
|
|
261
202
|
pythonOrm: btsConfig.pythonOrm,
|
|
262
203
|
pythonValidation: btsConfig.pythonValidation,
|
|
@@ -269,6 +210,13 @@ async function writeBtsConfig(projectConfig) {
|
|
|
269
210
|
goApi: btsConfig.goApi,
|
|
270
211
|
goCli: btsConfig.goCli,
|
|
271
212
|
goLogging: btsConfig.goLogging,
|
|
213
|
+
goAuth: btsConfig.goAuth,
|
|
214
|
+
javaWebFramework: btsConfig.javaWebFramework,
|
|
215
|
+
javaBuildTool: btsConfig.javaBuildTool,
|
|
216
|
+
javaOrm: btsConfig.javaOrm,
|
|
217
|
+
javaAuth: btsConfig.javaAuth,
|
|
218
|
+
javaLibraries: btsConfig.javaLibraries,
|
|
219
|
+
javaTestingLibraries: btsConfig.javaTestingLibraries,
|
|
272
220
|
aiDocs: btsConfig.aiDocs
|
|
273
221
|
};
|
|
274
222
|
let configContent = JSON.stringify(baseContent);
|
package/dist/cli.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
//#region src/cli.ts
|
|
3
|
-
if (process.argv[2] === "mcp" && process.argv.length === 3) import("./mcp-
|
|
3
|
+
if (process.argv[2] === "mcp" && process.argv.length === 3) import("./mcp-CuEEG8e5.mjs").then((m) => m.startMcpServer());
|
|
4
4
|
else import("./index.mjs").then((m) => m.createBtsCli().run());
|
|
5
5
|
|
|
6
6
|
//#endregion
|