create-better-fullstack 2.0.1 → 2.0.3

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.
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env node
2
+ import { g as getDefaultConfig, n as readBtsConfig, r as updateBtsConfig } from "./bts-config-Bg1Qea9Y.mjs";
3
+ import { W as renderTitle, c as applyDependencyVersionChannel, t as installDependencies, u as getAddonsToAdd } from "./install-dependencies-D0Z1dZEx.mjs";
4
+ import { a as CLIError, f as isSilent, o as UserCancelledError, p as runWithContextAsync, t as setupAddons } from "./addons-setup-HSghQS7c.mjs";
5
+ import { intro, log, outro } from "@clack/prompts";
6
+ import pc from "picocolors";
7
+ import fs from "fs-extra";
8
+ import path from "node:path";
9
+ import { EMBEDDED_TEMPLATES, VirtualFileSystem, processAddonTemplates, processAddonsDeps } from "@better-fullstack/template-generator";
10
+ import { writeTreeToFilesystem } from "@better-fullstack/template-generator/fs-writer";
11
+
12
+ //#region src/helpers/core/add-handler.ts
13
+ async function addHandler(input, options = {}) {
14
+ const { silent = false } = options;
15
+ return runWithContextAsync({ silent }, async () => {
16
+ try {
17
+ return await addHandlerInternal(input);
18
+ } catch (error) {
19
+ if (error instanceof UserCancelledError) {
20
+ if (isSilent()) return {
21
+ success: false,
22
+ addedAddons: [],
23
+ projectDir: "",
24
+ error: error.message
25
+ };
26
+ return;
27
+ }
28
+ if (error instanceof CLIError) {
29
+ if (isSilent()) return {
30
+ success: false,
31
+ addedAddons: [],
32
+ projectDir: "",
33
+ error: error.message
34
+ };
35
+ throw error;
36
+ }
37
+ if (isSilent()) return {
38
+ success: false,
39
+ addedAddons: [],
40
+ projectDir: "",
41
+ error: error instanceof Error ? error.message : String(error)
42
+ };
43
+ throw error;
44
+ }
45
+ });
46
+ }
47
+ async function addHandlerInternal(input) {
48
+ const projectDir = path.resolve(input.projectDir || process.cwd());
49
+ if (!isSilent()) {
50
+ renderTitle();
51
+ intro(pc.magenta("Add addons to your Better Fullstack project"));
52
+ }
53
+ const btsConfig = await readBtsConfig(projectDir);
54
+ if (!btsConfig) throw new CLIError(`No Better Fullstack project found in ${projectDir}. Make sure bts.jsonc exists.`);
55
+ const projectName = path.basename(projectDir);
56
+ if (!isSilent()) log.info(pc.dim(`Detected project: ${projectName}`));
57
+ const existingAddons = btsConfig.addons || [];
58
+ let addonsToAdd = [];
59
+ if (input.addons && input.addons.length > 0) addonsToAdd = input.addons.filter((addon) => addon !== "none" && !existingAddons.includes(addon));
60
+ else addonsToAdd = (await getAddonsToAdd(btsConfig.frontend || [], existingAddons, btsConfig.auth)).filter((addon) => addon !== "none");
61
+ if (addonsToAdd.length === 0) {
62
+ if (!isSilent()) {
63
+ log.info(pc.dim("No new addons selected."));
64
+ outro(pc.magenta("Nothing to add."));
65
+ }
66
+ return {
67
+ success: true,
68
+ addedAddons: [],
69
+ projectDir
70
+ };
71
+ }
72
+ if (!isSilent()) log.info(pc.cyan(`Adding addons: ${addonsToAdd.join(", ")}`));
73
+ const baseConfig = getDefaultConfig();
74
+ const config = {
75
+ ...baseConfig,
76
+ ...btsConfig,
77
+ projectName,
78
+ projectDir,
79
+ relativePath: ".",
80
+ packageManager: input.packageManager || btsConfig.packageManager || baseConfig.packageManager,
81
+ addons: addonsToAdd,
82
+ frontend: btsConfig.frontend || baseConfig.frontend,
83
+ examples: btsConfig.examples || [],
84
+ rustLibraries: btsConfig.rustLibraries || [],
85
+ pythonAi: btsConfig.pythonAi || [],
86
+ aiDocs: btsConfig.aiDocs || []
87
+ };
88
+ const vfs = new VirtualFileSystem();
89
+ const packageJsonPaths = await collectPackageJsonPaths(projectDir);
90
+ for (const pkgPath of packageJsonPaths) {
91
+ const fullPath = path.join(projectDir, pkgPath);
92
+ const content = await fs.readFile(fullPath, "utf-8");
93
+ vfs.writeFile(pkgPath, content);
94
+ }
95
+ await processAddonTemplates(vfs, EMBEDDED_TEMPLATES, config);
96
+ processAddonsDeps(vfs, config);
97
+ await writeTreeToFilesystem({
98
+ root: vfs.toTree(projectName),
99
+ fileCount: vfs.getFileCount(),
100
+ directoryCount: vfs.getDirectoryCount(),
101
+ config
102
+ }, projectDir);
103
+ const setupWarnings = await setupAddons(config);
104
+ await applyDependencyVersionChannel(projectDir, config.versionChannel);
105
+ const configUpdates = { addons: [...new Set([...existingAddons, ...addonsToAdd])] };
106
+ if (input.webDeploy !== void 0) configUpdates.webDeploy = input.webDeploy;
107
+ if (input.serverDeploy !== void 0) configUpdates.serverDeploy = input.serverDeploy;
108
+ await updateBtsConfig(projectDir, configUpdates);
109
+ if (input.install) await installDependencies({
110
+ projectDir,
111
+ packageManager: config.packageManager
112
+ });
113
+ if (!isSilent()) {
114
+ log.success(pc.green(`Successfully added: ${addonsToAdd.join(", ")}`));
115
+ for (const warning of setupWarnings) log.warn(pc.yellow(warning));
116
+ if (!input.install) {
117
+ const installCmd = config.packageManager === "npm" ? "npm install" : `${config.packageManager} install`;
118
+ log.info(pc.yellow(`Run '${installCmd}' to install new dependencies.`));
119
+ }
120
+ outro(pc.magenta("Addons added successfully!"));
121
+ }
122
+ return {
123
+ success: true,
124
+ addedAddons: addonsToAdd,
125
+ projectDir,
126
+ setupWarnings: setupWarnings.length > 0 ? setupWarnings : void 0
127
+ };
128
+ }
129
+ async function collectPackageJsonPaths(projectDir) {
130
+ const results = [];
131
+ async function walk(currentDir) {
132
+ const entries = await fs.readdir(currentDir, { withFileTypes: true });
133
+ for (const entry of entries) {
134
+ if (entry.name === "node_modules" || entry.name === ".git" || entry.name === ".turbo") continue;
135
+ const fullPath = path.join(currentDir, entry.name);
136
+ if (entry.isDirectory()) {
137
+ await walk(fullPath);
138
+ continue;
139
+ }
140
+ if (entry.isFile() && entry.name === "package.json") results.push(path.relative(projectDir, fullPath).replaceAll(path.sep, "/"));
141
+ }
142
+ }
143
+ await walk(projectDir);
144
+ if (!results.includes("package.json") && await fs.pathExists(path.join(projectDir, "package.json"))) results.push("package.json");
145
+ return results;
146
+ }
147
+
148
+ //#endregion
149
+ export { addHandler };
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import "./bts-config-Bg1Qea9Y.mjs";
3
+ import { t as setupAddons } from "./addons-setup-HSghQS7c.mjs";
4
+
5
+ export { setupAddons };
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { m as dependencyVersionMap, t as readBtsConfig } from "./bts-config-CSvxsFML.mjs";
2
+ import { n as readBtsConfig } from "./bts-config-Bg1Qea9Y.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";
@@ -16,8 +16,7 @@ function defaultContext() {
16
16
  isFirstPrompt: false,
17
17
  lastPromptShownUI: false
18
18
  },
19
- silent: false,
20
- verbose: false
19
+ silent: false
21
20
  };
22
21
  }
23
22
  function getContext() {
@@ -51,11 +50,7 @@ async function runWithContextAsync(options, fn) {
51
50
  isFirstPrompt: false,
52
51
  lastPromptShownUI: false
53
52
  },
54
- silent: options.silent ?? false,
55
- verbose: options.verbose ?? false,
56
- projectDir: options.projectDir,
57
- projectName: options.projectName,
58
- packageManager: options.packageManager
53
+ silent: options.silent ?? false
59
54
  };
60
55
  return cliStorage.run(ctx, fn);
61
56
  }
@@ -91,10 +86,33 @@ function handleError(error, fallbackMessage) {
91
86
  process.exit(1);
92
87
  }
93
88
 
89
+ //#endregion
90
+ //#region src/utils/prompt-environment.ts
91
+ function hasOwnProperty(value, property) {
92
+ return Object.prototype.hasOwnProperty.call(value, property);
93
+ }
94
+ function resolveCiValue(environment) {
95
+ if (environment && hasOwnProperty(environment, "ci")) return environment.ci;
96
+ return process.env.CI;
97
+ }
98
+ function isCiEnvironment(value) {
99
+ if (!value) return false;
100
+ const normalizedValue = value.trim().toLowerCase();
101
+ return normalizedValue !== "" && normalizedValue !== "0" && normalizedValue !== "false";
102
+ }
103
+ function canPromptInteractively(environment = {}) {
104
+ const silent = environment.silent ?? isSilent();
105
+ const stdinIsTTY = environment.stdinIsTTY ?? process.stdin.isTTY === true;
106
+ const stdoutIsTTY = environment.stdoutIsTTY ?? process.stdout.isTTY === true;
107
+ const ci = resolveCiValue(environment);
108
+ return !silent && stdinIsTTY && stdoutIsTTY && !isCiEnvironment(ci);
109
+ }
110
+
94
111
  //#endregion
95
112
  //#region src/utils/add-package-deps.ts
96
113
  const addPackageDependency = async (opts) => {
97
114
  const { dependencies = [], devDependencies = [], customDependencies = {}, customDevDependencies = {}, projectDir } = opts;
115
+ const { dependencyVersionMap } = await import("@better-fullstack/template-generator");
98
116
  const pkgJsonPath = path.join(projectDir, "package.json");
99
117
  const pkgJson = await fs.readJson(pkgJsonPath);
100
118
  if (!pkgJson.dependencies) pkgJson.dependencies = {};
@@ -178,28 +196,6 @@ function getPackageRunnerPrefix(packageManager) {
178
196
  }
179
197
  }
180
198
 
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
199
  //#endregion
204
200
  //#region src/helpers/addons/interactive-selection.ts
205
201
  function shouldPromptForAddonSelection(environment = {}) {
@@ -456,7 +452,7 @@ function getRecommendedMcpServers(config) {
456
452
  name: "supabase",
457
453
  target: "https://mcp.supabase.com/mcp"
458
454
  });
459
- if (config.auth === "better-auth") servers.push({
455
+ if (config.auth === "better-auth" || config.auth === "better-auth-organizations") servers.push({
460
456
  key: "better-auth",
461
457
  label: "Better Auth",
462
458
  name: "better-auth",
@@ -851,7 +847,7 @@ function getRecommendedSourceKeys(config) {
851
847
  if (frontend.includes("nuxt")) sources.push("nuxt/ui");
852
848
  if (frontend.includes("native-uniwind")) sources.push("heroui-inc/heroui");
853
849
  if (hasNativeFrontend(frontend)) sources.push("expo/skills");
854
- if (auth === "better-auth") sources.push("better-auth/skills");
850
+ if (auth === "better-auth" || auth === "better-auth-organizations") sources.push("better-auth/skills");
855
851
  if (dbSetup === "neon") sources.push("neondatabase/agent-skills");
856
852
  if (dbSetup === "supabase") sources.push("supabase/agent-skills");
857
853
  if (dbSetup === "planetscale") sources.push("planetscale/database-skills");
@@ -1416,4 +1412,4 @@ async function setupLefthook(projectDir) {
1416
1412
  }
1417
1413
 
1418
1414
  //#endregion
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 };
1415
+ export { CLIError as a, exitWithError as c, isFirstPrompt as d, isSilent as f, setLastPromptShownUI as h, canPromptInteractively as i, handleError as l, setIsFirstPrompt as m, getPackageExecutionArgs as n, UserCancelledError as o, runWithContextAsync as p, addPackageDependency as r, exitCancelled as s, setupAddons as t, didLastPromptShowUI as u };
@@ -4,7 +4,6 @@ import fs from "fs-extra";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { createCliDefaultProjectConfigBase } from "@better-fullstack/types";
7
- import { dependencyVersionMap } from "@better-fullstack/template-generator";
8
7
  import * as JSONC from "jsonc-parser";
9
8
 
10
9
  //#region src/utils/get-package-manager.ts
@@ -159,8 +158,10 @@ function getGraphBackendUrl(config) {
159
158
  function getEffectiveStack(config) {
160
159
  const effectiveStack = {};
161
160
  const parts = getSelectedGraphParts(config);
161
+ const partsById = new Map(parts.map((part) => [part.id, part]));
162
162
  for (const part of parts) {
163
- const key = part.ownerPartId ? `backend.${part.role}` : part.role;
163
+ const owner = part.ownerPartId ? partsById.get(part.ownerPartId) : void 0;
164
+ const key = owner ? `${owner.role}.${part.role}` : part.role;
164
165
  effectiveStack[key] = `${part.ecosystem}:${part.toolId}`;
165
166
  }
166
167
  return effectiveStack;
@@ -211,6 +212,12 @@ function getGraphBackendDeployInstructions(config) {
211
212
  `* Backend: ${backendLabel}`,
212
213
  `* Deploy from: ${targetPath}`
213
214
  ].join("\n");
215
+ case "netlify": return [
216
+ "Server deployment with Netlify Functions:",
217
+ `* Backend: ${backendLabel}`,
218
+ `* Config: ${targetPath}/netlify.toml`,
219
+ `* Functions: ${targetPath}/netlify/functions`
220
+ ].join("\n");
214
221
  case "cloudflare":
215
222
  case "sst": return [
216
223
  `Server deployment with ${config.serverDeploy}:`,
@@ -243,6 +250,10 @@ function normalizeGraphConfigForPersistence(projectConfig, stackParts) {
243
250
  normalized.rustErrorHandling = "none";
244
251
  normalized.rustCaching = "none";
245
252
  normalized.rustAuth = "none";
253
+ normalized.rustRealtime = "none";
254
+ normalized.rustMessageQueue = "none";
255
+ normalized.rustObservability = "none";
256
+ normalized.rustTemplating = "none";
246
257
  }
247
258
  if (!selectedEcosystems.has("python")) {
248
259
  normalized.pythonWebFramework = "none";
@@ -254,6 +265,11 @@ function normalizeGraphConfigForPersistence(projectConfig, stackParts) {
254
265
  normalized.pythonTaskQueue = "none";
255
266
  normalized.pythonGraphql = "none";
256
267
  normalized.pythonQuality = "none";
268
+ normalized.pythonTesting = [];
269
+ normalized.pythonCaching = "none";
270
+ normalized.pythonRealtime = "none";
271
+ normalized.pythonObservability = "none";
272
+ normalized.pythonCli = [];
257
273
  }
258
274
  if (!selectedEcosystems.has("go")) {
259
275
  normalized.goWebFramework = "none";
@@ -262,15 +278,36 @@ function normalizeGraphConfigForPersistence(projectConfig, stackParts) {
262
278
  normalized.goCli = "none";
263
279
  normalized.goLogging = "none";
264
280
  normalized.goAuth = "none";
281
+ normalized.goTesting = [];
282
+ normalized.goRealtime = "none";
283
+ normalized.goMessageQueue = "none";
284
+ normalized.goCaching = "none";
285
+ normalized.goConfig = "none";
286
+ normalized.goObservability = "none";
265
287
  }
266
288
  if (!selectedEcosystems.has("java")) {
267
289
  normalized.javaWebFramework = "none";
268
290
  normalized.javaBuildTool = "none";
269
291
  normalized.javaOrm = "none";
270
292
  normalized.javaAuth = "none";
293
+ normalized.javaApi = "none";
294
+ normalized.javaLogging = "none";
271
295
  normalized.javaLibraries = [];
272
296
  normalized.javaTestingLibraries = [];
273
297
  }
298
+ if (!selectedEcosystems.has("dotnet")) {
299
+ normalized.dotnetWebFramework = "none";
300
+ normalized.dotnetOrm = "none";
301
+ normalized.dotnetAuth = "none";
302
+ normalized.dotnetApi = "none";
303
+ normalized.dotnetTesting = [];
304
+ normalized.dotnetJobQueue = "none";
305
+ normalized.dotnetRealtime = "none";
306
+ normalized.dotnetObservability = [];
307
+ normalized.dotnetValidation = "none";
308
+ normalized.dotnetCaching = "none";
309
+ normalized.dotnetDeploy = "none";
310
+ }
274
311
  if (!selectedEcosystems.has("elixir")) {
275
312
  normalized.elixirWebFramework = "none";
276
313
  normalized.elixirOrm = "none";
@@ -287,17 +324,79 @@ function normalizeGraphConfigForPersistence(projectConfig, stackParts) {
287
324
  normalized.elixirTesting = "none";
288
325
  normalized.elixirQuality = "none";
289
326
  normalized.elixirDeploy = "none";
327
+ normalized.elixirLibraries = [];
328
+ }
329
+ if (!selectedEcosystems.has("react-native")) {
330
+ normalized.mobileNavigation = "none";
331
+ normalized.mobileUI = "none";
332
+ normalized.mobileStorage = "none";
333
+ normalized.mobileTesting = "none";
334
+ normalized.mobilePush = "none";
335
+ normalized.mobileOTA = "none";
336
+ normalized.mobileDeepLinking = "none";
290
337
  }
291
338
  return normalized;
292
339
  }
293
- async function writeBtsConfig(projectConfig) {
340
+ function findSelectedPrimaryPart(stackParts, role, ecosystem) {
341
+ return stackParts.find((part) => part.source !== "provided" && part.role === role && !part.ownerPartId && (!ecosystem || part.ecosystem === ecosystem));
342
+ }
343
+ function isAddonGraphPart(part) {
344
+ const binding = (0, types_exports.getAddonStackPartBinding)(part.toolId);
345
+ return binding !== void 0 && part.role === binding.role && part.ecosystem === binding.ecosystem;
346
+ }
347
+ function hasMatchingStackPart(stackParts, part) {
348
+ return stackParts.some((candidate) => candidate.source !== "provided" && candidate.role === part.role && candidate.ecosystem === part.ecosystem && candidate.toolId === part.toolId && candidate.ownerPartId === part.ownerPartId);
349
+ }
350
+ function syncAddonStackParts(stackParts, addons) {
351
+ const addonSet = new Set(addons.filter((addon) => addon !== "none"));
352
+ const frontend = findSelectedPrimaryPart(stackParts, "frontend", "typescript");
353
+ const next = stackParts.filter((part) => part.source === "provided" || !isAddonGraphPart(part) || addonSet.has(part.toolId));
354
+ for (const addon of addonSet) {
355
+ const binding = (0, types_exports.getAddonStackPartBinding)(addon);
356
+ if (!binding) continue;
357
+ const ownerPartId = binding.ownerRole === "frontend" ? frontend?.id : void 0;
358
+ if (binding.ownerRole === "frontend" && !ownerPartId) continue;
359
+ const part = (0, types_exports.createStackPart)({
360
+ role: binding.role,
361
+ ecosystem: binding.ecosystem,
362
+ toolId: addon,
363
+ ownerPartId,
364
+ source: "selected"
365
+ });
366
+ if (!hasMatchingStackPart(next, part)) next.push(part);
367
+ }
368
+ return next;
369
+ }
370
+ function syncOwnedDeployStackPart(stackParts, ownerRole, deployValue) {
371
+ if (deployValue === void 0) return [...stackParts];
372
+ const owner = findSelectedPrimaryPart(stackParts, ownerRole, "typescript");
373
+ const next = stackParts.filter((part) => part.source === "provided" || part.role !== "deploy" || part.ecosystem !== "typescript" || part.ownerPartId !== owner?.id);
374
+ if (!owner || deployValue === "none") return next;
375
+ next.push((0, types_exports.createStackPart)({
376
+ role: "deploy",
377
+ ecosystem: "typescript",
378
+ toolId: deployValue,
379
+ ownerPartId: owner.id,
380
+ source: "selected"
381
+ }));
382
+ return next;
383
+ }
384
+ function syncUpdatedStackParts(stackParts, updates) {
385
+ if (!stackParts) return void 0;
386
+ let next = [...stackParts];
387
+ if (updates.addons) next = syncAddonStackParts(next, updates.addons);
388
+ next = syncOwnedDeployStackPart(next, "frontend", updates.webDeploy);
389
+ next = syncOwnedDeployStackPart(next, "backend", updates.serverDeploy);
390
+ return next;
391
+ }
392
+ function buildBtsConfigForPersistence(projectConfig, metadata = {}) {
294
393
  const stackParts = projectConfig.stackParts ?? (0, types_exports.legacyProjectConfigToStackParts)(projectConfig);
295
- const persistedConfig = normalizeGraphConfigForPersistence(projectConfig, projectConfig.stackParts);
296
- const graphSummary = projectConfig.stackParts ? getGraphSummary({ stackParts }) : null;
297
- const effectiveStack = projectConfig.stackParts ? getEffectiveStack({ stackParts }) : void 0;
298
- const btsConfig = {
299
- version: getLatestCLIVersion(),
300
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
394
+ const persistedConfig = normalizeGraphConfigForPersistence(projectConfig, stackParts);
395
+ const graphSummary = stackParts.length > 0 ? getGraphSummary({ stackParts }) : null;
396
+ const effectiveStack = stackParts.length > 0 ? getEffectiveStack({ stackParts }) : void 0;
397
+ return {
398
+ version: metadata.version ?? getLatestCLIVersion(),
399
+ createdAt: metadata.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
301
400
  ...graphSummary ? {
302
401
  graphSummary,
303
402
  effectiveStack
@@ -344,6 +443,7 @@ async function writeBtsConfig(projectConfig) {
344
443
  mobileDeepLinking: persistedConfig.mobileDeepLinking,
345
444
  cms: persistedConfig.cms,
346
445
  caching: persistedConfig.caching,
446
+ rateLimit: persistedConfig.rateLimit,
347
447
  i18n: persistedConfig.i18n,
348
448
  search: persistedConfig.search,
349
449
  fileStorage: persistedConfig.fileStorage,
@@ -357,6 +457,10 @@ async function writeBtsConfig(projectConfig) {
357
457
  rustErrorHandling: persistedConfig.rustErrorHandling,
358
458
  rustCaching: persistedConfig.rustCaching,
359
459
  rustAuth: persistedConfig.rustAuth,
460
+ rustRealtime: persistedConfig.rustRealtime,
461
+ rustMessageQueue: persistedConfig.rustMessageQueue,
462
+ rustObservability: persistedConfig.rustObservability,
463
+ rustTemplating: persistedConfig.rustTemplating,
360
464
  pythonWebFramework: persistedConfig.pythonWebFramework,
361
465
  pythonOrm: persistedConfig.pythonOrm,
362
466
  pythonValidation: persistedConfig.pythonValidation,
@@ -366,18 +470,42 @@ async function writeBtsConfig(projectConfig) {
366
470
  pythonTaskQueue: persistedConfig.pythonTaskQueue,
367
471
  pythonGraphql: persistedConfig.pythonGraphql,
368
472
  pythonQuality: persistedConfig.pythonQuality,
473
+ pythonTesting: persistedConfig.pythonTesting,
474
+ pythonCaching: persistedConfig.pythonCaching,
475
+ pythonRealtime: persistedConfig.pythonRealtime,
476
+ pythonObservability: persistedConfig.pythonObservability,
477
+ pythonCli: persistedConfig.pythonCli,
369
478
  goWebFramework: persistedConfig.goWebFramework,
370
479
  goOrm: persistedConfig.goOrm,
371
480
  goApi: persistedConfig.goApi,
372
481
  goCli: persistedConfig.goCli,
373
482
  goLogging: persistedConfig.goLogging,
374
483
  goAuth: persistedConfig.goAuth,
484
+ goTesting: persistedConfig.goTesting,
485
+ goRealtime: persistedConfig.goRealtime,
486
+ goMessageQueue: persistedConfig.goMessageQueue,
487
+ goCaching: persistedConfig.goCaching,
488
+ goConfig: persistedConfig.goConfig,
489
+ goObservability: persistedConfig.goObservability,
375
490
  javaWebFramework: persistedConfig.javaWebFramework,
376
491
  javaBuildTool: persistedConfig.javaBuildTool,
377
492
  javaOrm: persistedConfig.javaOrm,
378
493
  javaAuth: persistedConfig.javaAuth,
494
+ javaApi: persistedConfig.javaApi,
495
+ javaLogging: persistedConfig.javaLogging,
379
496
  javaLibraries: persistedConfig.javaLibraries,
380
497
  javaTestingLibraries: persistedConfig.javaTestingLibraries,
498
+ dotnetWebFramework: persistedConfig.dotnetWebFramework,
499
+ dotnetOrm: persistedConfig.dotnetOrm,
500
+ dotnetAuth: persistedConfig.dotnetAuth,
501
+ dotnetApi: persistedConfig.dotnetApi,
502
+ dotnetTesting: persistedConfig.dotnetTesting,
503
+ dotnetJobQueue: persistedConfig.dotnetJobQueue,
504
+ dotnetRealtime: persistedConfig.dotnetRealtime,
505
+ dotnetObservability: persistedConfig.dotnetObservability,
506
+ dotnetValidation: persistedConfig.dotnetValidation,
507
+ dotnetCaching: persistedConfig.dotnetCaching,
508
+ dotnetDeploy: persistedConfig.dotnetDeploy,
381
509
  elixirWebFramework: persistedConfig.elixirWebFramework,
382
510
  elixirOrm: persistedConfig.elixirOrm,
383
511
  elixirAuth: persistedConfig.elixirAuth,
@@ -393,9 +521,24 @@ async function writeBtsConfig(projectConfig) {
393
521
  elixirTesting: persistedConfig.elixirTesting,
394
522
  elixirQuality: persistedConfig.elixirQuality,
395
523
  elixirDeploy: persistedConfig.elixirDeploy,
524
+ elixirLibraries: persistedConfig.elixirLibraries,
396
525
  aiDocs: persistedConfig.aiDocs,
397
526
  stackParts
398
527
  };
528
+ }
529
+ function previewBtsConfigUpdate(currentConfig, updates) {
530
+ const updatedStackParts = syncUpdatedStackParts(currentConfig.stackParts, updates);
531
+ return buildBtsConfigForPersistence({
532
+ ...currentConfig,
533
+ ...updates,
534
+ ...updatedStackParts ? { stackParts: updatedStackParts } : {}
535
+ }, {
536
+ version: currentConfig.version,
537
+ createdAt: currentConfig.createdAt
538
+ });
539
+ }
540
+ async function writeBtsConfig(projectConfig) {
541
+ const btsConfig = buildBtsConfigForPersistence(projectConfig);
399
542
  const baseContent = {
400
543
  $schema: "https://better-fullstack-web.vercel.app/schema.json",
401
544
  version: btsConfig.version,
@@ -446,6 +589,7 @@ async function writeBtsConfig(projectConfig) {
446
589
  mobileDeepLinking: btsConfig.mobileDeepLinking,
447
590
  cms: btsConfig.cms,
448
591
  caching: btsConfig.caching,
592
+ rateLimit: btsConfig.rateLimit,
449
593
  i18n: btsConfig.i18n,
450
594
  search: btsConfig.search,
451
595
  fileStorage: btsConfig.fileStorage,
@@ -459,6 +603,10 @@ async function writeBtsConfig(projectConfig) {
459
603
  rustErrorHandling: btsConfig.rustErrorHandling,
460
604
  rustCaching: btsConfig.rustCaching,
461
605
  rustAuth: btsConfig.rustAuth,
606
+ rustRealtime: btsConfig.rustRealtime,
607
+ rustMessageQueue: btsConfig.rustMessageQueue,
608
+ rustObservability: btsConfig.rustObservability,
609
+ rustTemplating: btsConfig.rustTemplating,
462
610
  pythonWebFramework: btsConfig.pythonWebFramework,
463
611
  pythonOrm: btsConfig.pythonOrm,
464
612
  pythonValidation: btsConfig.pythonValidation,
@@ -468,18 +616,42 @@ async function writeBtsConfig(projectConfig) {
468
616
  pythonTaskQueue: btsConfig.pythonTaskQueue,
469
617
  pythonGraphql: btsConfig.pythonGraphql,
470
618
  pythonQuality: btsConfig.pythonQuality,
619
+ pythonTesting: btsConfig.pythonTesting,
620
+ pythonCaching: btsConfig.pythonCaching,
621
+ pythonRealtime: btsConfig.pythonRealtime,
622
+ pythonObservability: btsConfig.pythonObservability,
623
+ pythonCli: btsConfig.pythonCli,
471
624
  goWebFramework: btsConfig.goWebFramework,
472
625
  goOrm: btsConfig.goOrm,
473
626
  goApi: btsConfig.goApi,
474
627
  goCli: btsConfig.goCli,
475
628
  goLogging: btsConfig.goLogging,
476
629
  goAuth: btsConfig.goAuth,
630
+ goTesting: btsConfig.goTesting,
631
+ goRealtime: btsConfig.goRealtime,
632
+ goMessageQueue: btsConfig.goMessageQueue,
633
+ goCaching: btsConfig.goCaching,
634
+ goConfig: btsConfig.goConfig,
635
+ goObservability: btsConfig.goObservability,
477
636
  javaWebFramework: btsConfig.javaWebFramework,
478
637
  javaBuildTool: btsConfig.javaBuildTool,
479
638
  javaOrm: btsConfig.javaOrm,
480
639
  javaAuth: btsConfig.javaAuth,
640
+ javaApi: btsConfig.javaApi,
641
+ javaLogging: btsConfig.javaLogging,
481
642
  javaLibraries: btsConfig.javaLibraries,
482
643
  javaTestingLibraries: btsConfig.javaTestingLibraries,
644
+ dotnetWebFramework: btsConfig.dotnetWebFramework,
645
+ dotnetOrm: btsConfig.dotnetOrm,
646
+ dotnetAuth: btsConfig.dotnetAuth,
647
+ dotnetApi: btsConfig.dotnetApi,
648
+ dotnetTesting: btsConfig.dotnetTesting,
649
+ dotnetJobQueue: btsConfig.dotnetJobQueue,
650
+ dotnetRealtime: btsConfig.dotnetRealtime,
651
+ dotnetObservability: btsConfig.dotnetObservability,
652
+ dotnetValidation: btsConfig.dotnetValidation,
653
+ dotnetCaching: btsConfig.dotnetCaching,
654
+ dotnetDeploy: btsConfig.dotnetDeploy,
483
655
  elixirWebFramework: btsConfig.elixirWebFramework,
484
656
  elixirOrm: btsConfig.elixirOrm,
485
657
  elixirAuth: btsConfig.elixirAuth,
@@ -495,6 +667,7 @@ async function writeBtsConfig(projectConfig) {
495
667
  elixirTesting: btsConfig.elixirTesting,
496
668
  elixirQuality: btsConfig.elixirQuality,
497
669
  elixirDeploy: btsConfig.elixirDeploy,
670
+ elixirLibraries: btsConfig.elixirLibraries,
498
671
  aiDocs: btsConfig.aiDocs,
499
672
  stackParts: btsConfig.stackParts
500
673
  };
@@ -507,7 +680,7 @@ async function writeBtsConfig(projectConfig) {
507
680
  configContent = JSONC.applyEdits(configContent, formatResult);
508
681
  const finalContent = `// Better Fullstack configuration file
509
682
  // safe to delete
510
- ${graphSummary ? "// For multi-ecosystem projects, graphSummary/effectiveStack and stackParts are the source of truth.\n// Legacy fields such as backend/orm may stay as compatibility fallbacks.\n" : ""}
683
+ ${btsConfig.stackParts?.length ? "// stackParts is the source of truth; graphSummary/effectiveStack summarize it for humans and tools.\n// Top-level option fields are a derived compatibility cache for older integrations.\n" : ""}
511
684
 
512
685
  ${configContent}`;
513
686
  const configPath = path.join(projectConfig.projectDir, BTS_CONFIG_FILE);
@@ -527,19 +700,10 @@ async function readBtsConfig(projectDir) {
527
700
  console.warn("Warning: Found errors parsing bts.jsonc:", errors);
528
701
  return null;
529
702
  }
530
- if (config.stackParts && config.stackParts.length > 0) {
531
- const diagnostics = (0, types_exports.compareLegacyConfigToStackParts)(config, config.stackParts);
532
- if (diagnostics.length > 0) console.warn(`Warning: bts.jsonc legacy fields differ from stackParts; using stackParts for ${diagnostics.map((diagnostic) => diagnostic.path).filter(Boolean).join(", ")}.`);
533
- return {
534
- ...config,
535
- ...(0, types_exports.stackPartsToLegacyProjectConfigPartial)(config.stackParts),
536
- stackParts: config.stackParts
537
- };
538
- }
539
- return {
540
- ...config,
541
- stackParts: (0, types_exports.legacyProjectConfigToStackParts)(config)
542
- };
703
+ return buildBtsConfigForPersistence(config, {
704
+ version: config.version,
705
+ createdAt: config.createdAt
706
+ });
543
707
  } catch {
544
708
  return null;
545
709
  }
@@ -548,8 +712,16 @@ async function updateBtsConfig(projectDir, updates) {
548
712
  try {
549
713
  const configPath = path.join(projectDir, BTS_CONFIG_FILE);
550
714
  if (!await fs.pathExists(configPath)) return;
551
- let modifiedContent = await fs.readFile(configPath, "utf-8");
552
- for (const [key, value] of Object.entries(updates)) {
715
+ const configContent = await fs.readFile(configPath, "utf-8");
716
+ const errors = [];
717
+ const currentConfig = JSONC.parse(configContent, errors, {
718
+ allowTrailingComma: true,
719
+ disallowComments: false
720
+ });
721
+ let modifiedContent = configContent;
722
+ const nextConfig = errors.length === 0 ? previewBtsConfigUpdate(currentConfig, updates) : void 0;
723
+ const persistedUpdates = nextConfig ? Object.fromEntries(Object.entries(nextConfig).filter(([key]) => key !== "version" && key !== "createdAt")) : updates;
724
+ for (const [key, value] of Object.entries(persistedUpdates)) {
553
725
  const editResult = JSONC.modify(modifiedContent, [key], value, { formattingOptions: {
554
726
  tabSize: 2,
555
727
  insertSpaces: true,
@@ -562,4 +734,4 @@ async function updateBtsConfig(projectDir, updates) {
562
734
  }
563
735
 
564
736
  //#endregion
565
- export { getGraphBackendUrl as a, getPrimaryGraphPart as c, getLatestCLIVersion as d, DEFAULT_CONFIG as f, getUserPkgManager as g, getDefaultConfig as h, getGraphBackendDeployInstructions as i, hasGraphPart as l, dependencyVersionMap as m, updateBtsConfig as n, getGraphPart as o, DEFAULT_UI_LIBRARY_BY_FRONTEND as p, writeBtsConfig as r, getGraphSummary as s, readBtsConfig as t, types_exports as u };
737
+ export { getUserPkgManager as _, getEffectiveStack as a, getGraphPart as c, hasGraphPart as d, types_exports as f, getDefaultConfig as g, DEFAULT_UI_LIBRARY_BY_FRONTEND as h, writeBtsConfig as i, getGraphSummary as l, DEFAULT_CONFIG as m, readBtsConfig as n, getGraphBackendDeployInstructions as o, getLatestCLIVersion as p, updateBtsConfig as r, getGraphBackendUrl as s, previewBtsConfigUpdate as t, getPrimaryGraphPart as u };
package/dist/cli.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  //#region src/cli.ts
3
- if (process.argv[2] === "mcp" && process.argv.length === 3) import("./mcp-DfnYbMju.mjs").then((m) => m.startMcpServer());
4
- else import("./index.mjs").then((m) => m.createBtsCli().run());
3
+ if (process.argv[2] === "mcp" && process.argv.length === 3) import("./mcp-58r70ZcL.mjs").then((m) => m.startMcpServer());
4
+ else import("./run-DCxVLUMS.mjs").then((m) => m.createBtsCli().run());
5
5
 
6
6
  //#endregion
7
7
  export { };