create-better-fullstack 1.6.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { s as dependencyVersionMap, t as readBtsConfig } from "./bts-config-snHxP_EH.mjs";
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 template = await select({
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$1]) => ({
259
+ options: Object.entries(TEMPLATES$2).map(([key, template]) => ({
217
260
  value: key,
218
- label: template$1.label,
219
- hint: template$1.hint
261
+ label: template.label,
262
+ hint: template.hint
220
263
  })),
221
- initialValue: "next-mdx"
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 scope = await select({
452
- message: "Where should MCP servers be installed?",
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
- const selectedServerKeys = await multiselect({
468
- message: "Select MCP servers to install",
469
- options: recommendedServers.map((server) => ({
470
- value: server.key,
471
- label: server.label,
472
- hint: server.target
473
- })),
474
- required: false,
475
- initialValues: recommendedServers.map((server) => server.key)
476
- });
477
- if (isCancel(selectedServerKeys) || selectedServerKeys.length === 0) return;
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 selectedAgents = await multiselect({
483
- message: "Select agents to install MCP servers to",
484
- options: agentOptions,
485
- required: false,
486
- initialValues: uniqueValues$1([
487
- "cursor",
488
- "claude-code",
489
- "vscode"
490
- ].filter((agent) => agentOptions.some((option) => option.value === agent)))
491
- });
492
- if (isCancel(selectedAgents) || selectedAgents.length === 0) return;
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);
@@ -1017,17 +1071,16 @@ async function setupTui(config) {
1017
1071
  const { packageManager, projectDir } = config;
1018
1072
  try {
1019
1073
  log.info("Setting up OpenTUI...");
1020
- const template = await select({
1074
+ const args = getPackageExecutionArgs(packageManager, `create-tui@latest --template ${await selectAddonOptionOrDefault({
1075
+ addonName: "OpenTUI",
1021
1076
  message: "Choose a template",
1022
- options: Object.entries(TEMPLATES$1).map(([key, template$1]) => ({
1077
+ options: Object.entries(TEMPLATES$1).map(([key, template]) => ({
1023
1078
  value: key,
1024
- label: template$1.label,
1025
- hint: template$1.hint
1079
+ label: template.label,
1080
+ hint: template.hint
1026
1081
  })),
1027
- initialValue: "core"
1028
- });
1029
- if (isCancel(template)) return exitCancelled("Operation cancelled");
1030
- const args = getPackageExecutionArgs(packageManager, `create-tui@latest --template ${template} --no-git --no-install tui`);
1082
+ defaultValue: "core"
1083
+ })} --no-git --no-install tui`);
1031
1084
  const appsDir = path.join(projectDir, "apps");
1032
1085
  await fs.ensureDir(appsDir);
1033
1086
  const s = spinner();
@@ -1123,7 +1176,9 @@ async function setupUltracite(config, gitHooks) {
1123
1176
  const { packageManager, projectDir, frontend } = config;
1124
1177
  try {
1125
1178
  log.info("Setting up Ultracite...");
1126
- const result = await group({
1179
+ const skipExternalCommands = shouldSkipExternalCommands();
1180
+ const canPrompt = canPromptInteractively() && !skipExternalCommands;
1181
+ const result = canPrompt ? await group({
1127
1182
  linter: () => select({
1128
1183
  message: "Choose linter/formatter",
1129
1184
  options: Object.entries(LINTERS).map(([key, linter$1]) => ({
@@ -1158,7 +1213,12 @@ async function setupUltracite(config, gitHooks) {
1158
1213
  })
1159
1214
  }, { onCancel: () => {
1160
1215
  exitCancelled("Operation cancelled");
1161
- } });
1216
+ } }) : {
1217
+ linter: "biome",
1218
+ editors: [],
1219
+ agents: [],
1220
+ hooks: []
1221
+ };
1162
1222
  const linter = result.linter;
1163
1223
  const editors = result.editors;
1164
1224
  const agents = result.agents;
@@ -1171,6 +1231,7 @@ async function setupUltracite(config, gitHooks) {
1171
1231
  "--linter",
1172
1232
  linter
1173
1233
  ];
1234
+ if (!canPrompt) ultraciteArgs.push("--quiet");
1174
1235
  if (frameworks.length > 0) ultraciteArgs.push("--frameworks", ...frameworks);
1175
1236
  if (editors.length > 0) ultraciteArgs.push("--editors", ...editors);
1176
1237
  if (agents.length > 0) ultraciteArgs.push("--agents", ...agents);
@@ -1180,6 +1241,7 @@ async function setupUltracite(config, gitHooks) {
1180
1241
  if (gitHooks.includes("husky")) integrations.push("lint-staged");
1181
1242
  ultraciteArgs.push("--integrations", ...integrations);
1182
1243
  }
1244
+ if (skipExternalCommands) return;
1183
1245
  const args = getPackageExecutionArgs(packageManager, `ultracite@latest ${ultraciteArgs.join(" ")} --skip-install`);
1184
1246
  const s = spinner();
1185
1247
  s.start("Running Ultracite init command...");
@@ -1222,17 +1284,16 @@ async function setupWxt(config) {
1222
1284
  const { packageManager, projectDir } = config;
1223
1285
  try {
1224
1286
  log.info("Setting up WXT...");
1225
- const template = await select({
1287
+ const args = getPackageExecutionArgs(packageManager, `wxt@latest init extension --template ${await selectAddonOptionOrDefault({
1288
+ addonName: "WXT",
1226
1289
  message: "Choose a template",
1227
- options: Object.entries(TEMPLATES).map(([key, template$1]) => ({
1290
+ options: Object.entries(TEMPLATES).map(([key, template]) => ({
1228
1291
  value: key,
1229
- label: template$1.label,
1230
- hint: template$1.hint
1292
+ label: template.label,
1293
+ hint: template.hint
1231
1294
  })),
1232
- initialValue: "react"
1233
- });
1234
- if (isCancel(template)) return exitCancelled("Operation cancelled");
1235
- const args = getPackageExecutionArgs(packageManager, `wxt@latest init extension --template ${template} --pm ${packageManager}`);
1295
+ defaultValue: "react"
1296
+ })} --pm ${packageManager}`);
1236
1297
  const appsDir = path.join(projectDir, "apps");
1237
1298
  await fs.ensureDir(appsDir);
1238
1299
  const s = spinner();
@@ -1346,4 +1407,4 @@ async function setupLefthook(projectDir) {
1346
1407
  }
1347
1408
 
1348
1409
  //#endregion
1349
- export { setLastPromptShownUI as _, getPackageExecutionArgs as a, UserCancelledError as c, handleError as d, didLastPromptShowUI as f, setIsFirstPrompt as g, runWithContextAsync as h, setupLefthook as i, exitCancelled as l, isSilent as m, setupBiome as n, addPackageDependency as o, isFirstPrompt as p, setupHusky as r, CLIError as s, setupAddons as t, exitWithError as u };
1410
+ 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 };
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import "./bts-config-B_rZ4_sj.mjs";
3
+ import { i as setupLefthook, n as setupBiome, r as setupHusky, t as setupAddons } from "./addons-setup-Cgup_RHm.mjs";
4
+
5
+ export { setupAddons };
@@ -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-Bx_Esljk.mjs").then((m) => m.startMcpServer());
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