create-better-fullstack 1.8.1 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -43,13 +43,27 @@ Configure your stack visually — pick every option from a UI, preview your choi
43
43
  --yolo # Scaffold a random stack — good for exploring
44
44
  --template <name> # Use a preset (t3, mern, pern, uniwind)
45
45
  --ecosystem <lang> # Start in typescript, react-native, rust, python, go, java, or elixir mode
46
+ --part <binding> # Add a multi-ecosystem stack part, e.g. frontend:typescript:next
46
47
  --version-channel # Dependency channel: stable, latest, beta
47
48
  --no-git # Skip git initialization
48
49
  --no-install # Skip dependency installation
50
+ --verify # Run generated project checks without starting dev servers
49
51
  --package-manager # Package manager (bun, pnpm, npm, yarn)
50
52
  --verbose # Show detailed output
51
53
  ```
52
54
 
55
+ ## Multi-Ecosystem Example
56
+
57
+ Use repeated `--part` flags to bind each generated app or capability to an ecosystem:
58
+
59
+ ```bash
60
+ bun create better-fullstack@latest my-mixed-app \
61
+ --part frontend:typescript:next \
62
+ --part backend:go:gin \
63
+ --part backend.orm:go:gorm \
64
+ --part database:universal:postgres
65
+ ```
66
+
53
67
  ## Links
54
68
 
55
69
  - [Website](https://better-fullstack.dev)
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import "./bts-config-CSvxsFML.mjs";
3
+ import { i as setupLefthook, n as setupBiome, r as setupHusky, t as setupAddons } from "./addons-setup-DqVFXnDv.mjs";
4
+
5
+ export { setupAddons };
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { s as dependencyVersionMap, t as readBtsConfig } from "./bts-config-YcroedMK.mjs";
2
+ import { m as dependencyVersionMap, t as readBtsConfig } from "./bts-config-CSvxsFML.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";
@@ -0,0 +1,565 @@
1
+ #!/usr/bin/env node
2
+ import { t as __reExport } from "./chunk-CCII7kTE.mjs";
3
+ import fs from "fs-extra";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { createCliDefaultProjectConfigBase } from "@better-fullstack/types";
7
+ import { dependencyVersionMap } from "@better-fullstack/template-generator";
8
+ import * as JSONC from "jsonc-parser";
9
+
10
+ //#region src/utils/get-package-manager.ts
11
+ const getUserPkgManager = () => {
12
+ const userAgent = process.env.npm_config_user_agent;
13
+ if (userAgent?.startsWith("pnpm")) return "pnpm";
14
+ if (userAgent?.startsWith("bun")) return "bun";
15
+ if (userAgent?.startsWith("yarn")) return "yarn";
16
+ return "npm";
17
+ };
18
+
19
+ //#endregion
20
+ //#region src/constants.ts
21
+ const __filename = fileURLToPath(import.meta.url);
22
+ const distPath = path.dirname(__filename);
23
+ const PKG_ROOT = path.join(distPath, "../");
24
+ const DEFAULT_CONFIG_BASE = createCliDefaultProjectConfigBase(getUserPkgManager());
25
+ function getDefaultConfig() {
26
+ return {
27
+ ...DEFAULT_CONFIG_BASE,
28
+ projectDir: path.resolve(process.cwd(), DEFAULT_CONFIG_BASE.projectName),
29
+ packageManager: getUserPkgManager(),
30
+ frontend: [...DEFAULT_CONFIG_BASE.frontend],
31
+ addons: [...DEFAULT_CONFIG_BASE.addons],
32
+ examples: [...DEFAULT_CONFIG_BASE.examples],
33
+ rustLibraries: [...DEFAULT_CONFIG_BASE.rustLibraries],
34
+ pythonAi: [...DEFAULT_CONFIG_BASE.pythonAi],
35
+ javaLibraries: [...DEFAULT_CONFIG_BASE.javaLibraries],
36
+ javaTestingLibraries: [...DEFAULT_CONFIG_BASE.javaTestingLibraries],
37
+ aiDocs: [...DEFAULT_CONFIG_BASE.aiDocs]
38
+ };
39
+ }
40
+ const DEFAULT_CONFIG = getDefaultConfig();
41
+ /**
42
+ * Default UI library for each frontend framework
43
+ * Falls back based on what's compatible
44
+ */
45
+ const DEFAULT_UI_LIBRARY_BY_FRONTEND = {
46
+ "tanstack-router": "shadcn-ui",
47
+ "react-router": "shadcn-ui",
48
+ "react-vite": "shadcn-ui",
49
+ "tanstack-start": "shadcn-ui",
50
+ next: "shadcn-ui",
51
+ vinext: "shadcn-ui",
52
+ nuxt: "daisyui",
53
+ svelte: "daisyui",
54
+ solid: "daisyui",
55
+ "solid-start": "daisyui",
56
+ astro: "daisyui",
57
+ qwik: "daisyui",
58
+ angular: "daisyui",
59
+ redwood: "daisyui",
60
+ fresh: "daisyui",
61
+ "native-bare": "none",
62
+ "native-uniwind": "none",
63
+ "native-unistyles": "none",
64
+ none: "none"
65
+ };
66
+
67
+ //#endregion
68
+ //#region src/utils/get-latest-cli-version.ts
69
+ const getLatestCLIVersion = () => {
70
+ const packageJsonPath = path.join(PKG_ROOT, "package.json");
71
+ return fs.readJSONSync(packageJsonPath).version ?? "1.0.0";
72
+ };
73
+
74
+ //#endregion
75
+ //#region src/types.ts
76
+ var types_exports = {};
77
+ import * as import__better_fullstack_types from "@better-fullstack/types";
78
+ __reExport(types_exports, import__better_fullstack_types);
79
+
80
+ //#endregion
81
+ //#region src/utils/graph-summary.ts
82
+ const FRONTEND_LABELS = {
83
+ next: "Next.js web app",
84
+ astro: "Astro web app",
85
+ "react-vite": "React + Vite app",
86
+ "react-router": "React Router app",
87
+ "tanstack-router": "TanStack Router app",
88
+ "tanstack-start": "TanStack Start app",
89
+ nuxt: "Nuxt app",
90
+ svelte: "SvelteKit app",
91
+ solid: "Solid app",
92
+ "solid-start": "SolidStart app",
93
+ qwik: "Qwik app",
94
+ angular: "Angular app",
95
+ redwood: "RedwoodJS app",
96
+ fresh: "Fresh app"
97
+ };
98
+ const BACKEND_LABELS = {
99
+ "rust:axum": "Rust Axum API",
100
+ "rust:actix-web": "Rust Actix Web API",
101
+ "rust:rocket": "Rust Rocket API",
102
+ "rust:warp": "Rust Warp API",
103
+ "rust:poem": "Rust Poem API",
104
+ "rust:salvo": "Rust Salvo API",
105
+ "python:fastapi": "Python FastAPI API",
106
+ "python:django": "Python Django API",
107
+ "python:flask": "Python Flask API",
108
+ "python:litestar": "Python Litestar API",
109
+ "go:gin": "Go Gin API",
110
+ "go:echo": "Go Echo API",
111
+ "go:fiber": "Go Fiber API",
112
+ "go:chi": "Go Chi API",
113
+ "java:spring-boot": "Java Spring Boot API",
114
+ "java:quarkus": "Java Quarkus API",
115
+ "elixir:phoenix": "Elixir Phoenix API",
116
+ "elixir:phoenix-live-view": "Elixir Phoenix LiveView API"
117
+ };
118
+ const TOOL_LABELS = {
119
+ postgres: "Postgres",
120
+ sqlite: "SQLite",
121
+ mysql: "MySQL",
122
+ mongodb: "MongoDB",
123
+ redis: "Redis",
124
+ edgedb: "EdgeDB",
125
+ gorm: "GORM",
126
+ sqlc: "sqlc",
127
+ ent: "Ent",
128
+ "ecto-sql": "Ecto SQL",
129
+ ecto: "Ecto",
130
+ sqlx: "SQLx",
131
+ "sea-orm": "SeaORM",
132
+ sqlalchemy: "SQLAlchemy",
133
+ sqlmodel: "SQLModel",
134
+ "django-orm": "Django ORM",
135
+ "spring-data-jpa": "Spring Data JPA"
136
+ };
137
+ function getSelectedGraphParts(config) {
138
+ return (config.stackParts ?? []).filter((part) => part.source !== "provided");
139
+ }
140
+ function getGraphPart(config, role, ecosystem) {
141
+ return getSelectedGraphParts(config).find((part) => part.role === role && (!ecosystem || part.ecosystem === ecosystem));
142
+ }
143
+ function hasGraphPart(config, role, ecosystem) {
144
+ return Boolean(getGraphPart(config, role, ecosystem));
145
+ }
146
+ function getPrimaryGraphPart(config, role, ecosystem) {
147
+ return getSelectedGraphParts(config).find((part) => part.role === role && !part.ownerPartId && (!ecosystem || part.ecosystem === ecosystem));
148
+ }
149
+ function getGraphBackendUrl(config) {
150
+ switch (getPrimaryGraphPart(config, "backend")?.ecosystem) {
151
+ case "elixir": return "http://localhost:4000";
152
+ case "rust": return "http://localhost:3000";
153
+ case "python": return "http://localhost:8000";
154
+ case "go":
155
+ case "java": return "http://localhost:8080";
156
+ default: return null;
157
+ }
158
+ }
159
+ function getEffectiveStack(config) {
160
+ const effectiveStack = {};
161
+ const parts = getSelectedGraphParts(config);
162
+ for (const part of parts) {
163
+ const key = part.ownerPartId ? `backend.${part.role}` : part.role;
164
+ effectiveStack[key] = `${part.ecosystem}:${part.toolId}`;
165
+ }
166
+ return effectiveStack;
167
+ }
168
+ function labelFor(part) {
169
+ if (!part) return null;
170
+ if (part.role === "frontend" && part.ecosystem === "typescript") return FRONTEND_LABELS[part.toolId] ?? `${part.toolId} web app`;
171
+ if (part.role === "backend") return BACKEND_LABELS[`${part.ecosystem}:${part.toolId}`] ?? `${part.ecosystem} ${part.toolId} API`;
172
+ return TOOL_LABELS[part.toolId] ?? part.toolId;
173
+ }
174
+ function getGraphSummary(config) {
175
+ const selectedParts = getSelectedGraphParts(config);
176
+ if (selectedParts.length === 0) return null;
177
+ const frontend = getPrimaryGraphPart(config, "frontend");
178
+ const backend = getPrimaryGraphPart(config, "backend");
179
+ const database = getPrimaryGraphPart(config, "database");
180
+ const orm = selectedParts.find((part) => part.role === "orm");
181
+ const segments = [labelFor(frontend), labelFor(backend)].filter(Boolean);
182
+ const databaseLabel = backend?.ecosystem === "java" && orm?.toolId === "spring-data-jpa" ? "H2 dev database" : labelFor(database);
183
+ const dataLabel = [labelFor(orm), databaseLabel].filter(Boolean).join("/");
184
+ if (dataLabel) segments.push(dataLabel);
185
+ return segments.length > 0 ? segments.join(" + ") : selectedParts.map((part) => (0, types_exports.formatStackPartSpec)(part, selectedParts)).join(" + ");
186
+ }
187
+ function getGraphBackendDeployInstructions(config) {
188
+ const backend = getPrimaryGraphPart(config, "backend");
189
+ if (!backend || config.serverDeploy === "none") return "";
190
+ const targetPath = backend.targetPath ?? "apps/server";
191
+ const backendLabel = labelFor(backend) ?? `${backend.ecosystem}:${backend.toolId}`;
192
+ switch (config.serverDeploy) {
193
+ case "railway": return [
194
+ "Server deployment with Railway:",
195
+ `* Backend: ${backendLabel}`,
196
+ `* Config: ${targetPath}/railway.toml`,
197
+ `* Deploy: cd ${targetPath} && railway up`
198
+ ].join("\n");
199
+ case "docker": return [
200
+ "Server deployment with Docker:",
201
+ `* Backend: ${backendLabel}`,
202
+ `* Build: cd ${targetPath} && docker build -t better-fullstack-server .`
203
+ ].join("\n");
204
+ case "fly": return [
205
+ "Server deployment with Fly:",
206
+ `* Backend: ${backendLabel}`,
207
+ `* Deploy: cd ${targetPath} && fly launch`
208
+ ].join("\n");
209
+ case "vercel": return [
210
+ "Server deployment with Vercel:",
211
+ `* Backend: ${backendLabel}`,
212
+ `* Deploy from: ${targetPath}`
213
+ ].join("\n");
214
+ case "cloudflare":
215
+ case "sst": return [
216
+ `Server deployment with ${config.serverDeploy}:`,
217
+ `* Backend: ${backendLabel}`,
218
+ `* Review generated files in ${targetPath}`
219
+ ].join("\n");
220
+ default: return "";
221
+ }
222
+ }
223
+
224
+ //#endregion
225
+ //#region src/utils/bts-config.ts
226
+ const BTS_CONFIG_FILE = "bts.jsonc";
227
+ function normalizeGraphConfigForPersistence(projectConfig, stackParts) {
228
+ if (!stackParts) return projectConfig;
229
+ const legacyConfig = (0, types_exports.stackPartsToLegacyProjectConfigPartial)(stackParts);
230
+ const selectedEcosystems = new Set(stackParts.filter((part) => part.source !== "provided").map((part) => part.ecosystem));
231
+ const normalized = {
232
+ ...projectConfig,
233
+ ...legacyConfig
234
+ };
235
+ if (!selectedEcosystems.has("rust")) {
236
+ normalized.rustWebFramework = "none";
237
+ normalized.rustFrontend = "none";
238
+ normalized.rustOrm = "none";
239
+ normalized.rustApi = "none";
240
+ normalized.rustCli = "none";
241
+ normalized.rustLibraries = [];
242
+ normalized.rustLogging = "none";
243
+ normalized.rustErrorHandling = "none";
244
+ normalized.rustCaching = "none";
245
+ normalized.rustAuth = "none";
246
+ }
247
+ if (!selectedEcosystems.has("python")) {
248
+ normalized.pythonWebFramework = "none";
249
+ normalized.pythonOrm = "none";
250
+ normalized.pythonValidation = "none";
251
+ normalized.pythonAi = [];
252
+ normalized.pythonAuth = "none";
253
+ normalized.pythonApi = "none";
254
+ normalized.pythonTaskQueue = "none";
255
+ normalized.pythonGraphql = "none";
256
+ normalized.pythonQuality = "none";
257
+ }
258
+ if (!selectedEcosystems.has("go")) {
259
+ normalized.goWebFramework = "none";
260
+ normalized.goOrm = "none";
261
+ normalized.goApi = "none";
262
+ normalized.goCli = "none";
263
+ normalized.goLogging = "none";
264
+ normalized.goAuth = "none";
265
+ }
266
+ if (!selectedEcosystems.has("java")) {
267
+ normalized.javaWebFramework = "none";
268
+ normalized.javaBuildTool = "none";
269
+ normalized.javaOrm = "none";
270
+ normalized.javaAuth = "none";
271
+ normalized.javaLibraries = [];
272
+ normalized.javaTestingLibraries = [];
273
+ }
274
+ if (!selectedEcosystems.has("elixir")) {
275
+ normalized.elixirWebFramework = "none";
276
+ normalized.elixirOrm = "none";
277
+ normalized.elixirAuth = "none";
278
+ normalized.elixirApi = "none";
279
+ normalized.elixirRealtime = "none";
280
+ normalized.elixirJobs = "none";
281
+ normalized.elixirValidation = "none";
282
+ normalized.elixirHttp = "none";
283
+ normalized.elixirJson = "none";
284
+ normalized.elixirEmail = "none";
285
+ normalized.elixirCaching = "none";
286
+ normalized.elixirObservability = "none";
287
+ normalized.elixirTesting = "none";
288
+ normalized.elixirQuality = "none";
289
+ normalized.elixirDeploy = "none";
290
+ }
291
+ return normalized;
292
+ }
293
+ async function writeBtsConfig(projectConfig) {
294
+ 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(),
301
+ ...graphSummary ? {
302
+ graphSummary,
303
+ effectiveStack
304
+ } : {},
305
+ ecosystem: persistedConfig.ecosystem,
306
+ database: persistedConfig.database,
307
+ orm: persistedConfig.orm,
308
+ backend: persistedConfig.backend,
309
+ runtime: persistedConfig.runtime,
310
+ frontend: persistedConfig.frontend,
311
+ addons: persistedConfig.addons,
312
+ examples: persistedConfig.examples,
313
+ auth: persistedConfig.auth,
314
+ payments: persistedConfig.payments,
315
+ email: persistedConfig.email,
316
+ fileUpload: persistedConfig.fileUpload,
317
+ effect: persistedConfig.effect,
318
+ ai: persistedConfig.ai,
319
+ stateManagement: persistedConfig.stateManagement,
320
+ validation: persistedConfig.validation,
321
+ forms: persistedConfig.forms,
322
+ testing: persistedConfig.testing,
323
+ packageManager: persistedConfig.packageManager,
324
+ versionChannel: persistedConfig.versionChannel,
325
+ dbSetup: persistedConfig.dbSetup,
326
+ api: persistedConfig.api,
327
+ webDeploy: persistedConfig.webDeploy,
328
+ serverDeploy: persistedConfig.serverDeploy,
329
+ cssFramework: persistedConfig.cssFramework,
330
+ uiLibrary: persistedConfig.uiLibrary,
331
+ realtime: persistedConfig.realtime,
332
+ jobQueue: persistedConfig.jobQueue,
333
+ animation: persistedConfig.animation,
334
+ logging: persistedConfig.logging,
335
+ observability: persistedConfig.observability,
336
+ featureFlags: persistedConfig.featureFlags,
337
+ analytics: persistedConfig.analytics,
338
+ mobileNavigation: persistedConfig.mobileNavigation,
339
+ mobileUI: persistedConfig.mobileUI,
340
+ mobileStorage: persistedConfig.mobileStorage,
341
+ mobileTesting: persistedConfig.mobileTesting,
342
+ mobilePush: persistedConfig.mobilePush,
343
+ mobileOTA: persistedConfig.mobileOTA,
344
+ mobileDeepLinking: persistedConfig.mobileDeepLinking,
345
+ cms: persistedConfig.cms,
346
+ caching: persistedConfig.caching,
347
+ i18n: persistedConfig.i18n,
348
+ search: persistedConfig.search,
349
+ fileStorage: persistedConfig.fileStorage,
350
+ rustWebFramework: persistedConfig.rustWebFramework,
351
+ rustFrontend: persistedConfig.rustFrontend,
352
+ rustOrm: persistedConfig.rustOrm,
353
+ rustApi: persistedConfig.rustApi,
354
+ rustCli: persistedConfig.rustCli,
355
+ rustLibraries: persistedConfig.rustLibraries,
356
+ rustLogging: persistedConfig.rustLogging,
357
+ rustErrorHandling: persistedConfig.rustErrorHandling,
358
+ rustCaching: persistedConfig.rustCaching,
359
+ rustAuth: persistedConfig.rustAuth,
360
+ pythonWebFramework: persistedConfig.pythonWebFramework,
361
+ pythonOrm: persistedConfig.pythonOrm,
362
+ pythonValidation: persistedConfig.pythonValidation,
363
+ pythonAi: persistedConfig.pythonAi,
364
+ pythonAuth: persistedConfig.pythonAuth,
365
+ pythonApi: persistedConfig.pythonApi,
366
+ pythonTaskQueue: persistedConfig.pythonTaskQueue,
367
+ pythonGraphql: persistedConfig.pythonGraphql,
368
+ pythonQuality: persistedConfig.pythonQuality,
369
+ goWebFramework: persistedConfig.goWebFramework,
370
+ goOrm: persistedConfig.goOrm,
371
+ goApi: persistedConfig.goApi,
372
+ goCli: persistedConfig.goCli,
373
+ goLogging: persistedConfig.goLogging,
374
+ goAuth: persistedConfig.goAuth,
375
+ javaWebFramework: persistedConfig.javaWebFramework,
376
+ javaBuildTool: persistedConfig.javaBuildTool,
377
+ javaOrm: persistedConfig.javaOrm,
378
+ javaAuth: persistedConfig.javaAuth,
379
+ javaLibraries: persistedConfig.javaLibraries,
380
+ javaTestingLibraries: persistedConfig.javaTestingLibraries,
381
+ elixirWebFramework: persistedConfig.elixirWebFramework,
382
+ elixirOrm: persistedConfig.elixirOrm,
383
+ elixirAuth: persistedConfig.elixirAuth,
384
+ elixirApi: persistedConfig.elixirApi,
385
+ elixirRealtime: persistedConfig.elixirRealtime,
386
+ elixirJobs: persistedConfig.elixirJobs,
387
+ elixirValidation: persistedConfig.elixirValidation,
388
+ elixirHttp: persistedConfig.elixirHttp,
389
+ elixirJson: persistedConfig.elixirJson,
390
+ elixirEmail: persistedConfig.elixirEmail,
391
+ elixirCaching: persistedConfig.elixirCaching,
392
+ elixirObservability: persistedConfig.elixirObservability,
393
+ elixirTesting: persistedConfig.elixirTesting,
394
+ elixirQuality: persistedConfig.elixirQuality,
395
+ elixirDeploy: persistedConfig.elixirDeploy,
396
+ aiDocs: persistedConfig.aiDocs,
397
+ stackParts
398
+ };
399
+ const baseContent = {
400
+ $schema: "https://better-fullstack-web.vercel.app/schema.json",
401
+ version: btsConfig.version,
402
+ createdAt: btsConfig.createdAt,
403
+ ...btsConfig.graphSummary ? {
404
+ graphSummary: btsConfig.graphSummary,
405
+ effectiveStack: btsConfig.effectiveStack
406
+ } : {},
407
+ ecosystem: btsConfig.ecosystem,
408
+ database: btsConfig.database,
409
+ orm: btsConfig.orm,
410
+ backend: btsConfig.backend,
411
+ runtime: btsConfig.runtime,
412
+ frontend: btsConfig.frontend,
413
+ addons: btsConfig.addons,
414
+ examples: btsConfig.examples,
415
+ auth: btsConfig.auth,
416
+ payments: btsConfig.payments,
417
+ email: btsConfig.email,
418
+ fileUpload: btsConfig.fileUpload,
419
+ effect: btsConfig.effect,
420
+ ai: btsConfig.ai,
421
+ stateManagement: btsConfig.stateManagement,
422
+ validation: btsConfig.validation,
423
+ forms: btsConfig.forms,
424
+ testing: btsConfig.testing,
425
+ packageManager: btsConfig.packageManager,
426
+ versionChannel: btsConfig.versionChannel,
427
+ dbSetup: btsConfig.dbSetup,
428
+ api: btsConfig.api,
429
+ webDeploy: btsConfig.webDeploy,
430
+ serverDeploy: btsConfig.serverDeploy,
431
+ cssFramework: btsConfig.cssFramework,
432
+ uiLibrary: btsConfig.uiLibrary,
433
+ realtime: btsConfig.realtime,
434
+ jobQueue: btsConfig.jobQueue,
435
+ animation: btsConfig.animation,
436
+ logging: btsConfig.logging,
437
+ observability: btsConfig.observability,
438
+ featureFlags: btsConfig.featureFlags,
439
+ analytics: btsConfig.analytics,
440
+ mobileNavigation: btsConfig.mobileNavigation,
441
+ mobileUI: btsConfig.mobileUI,
442
+ mobileStorage: btsConfig.mobileStorage,
443
+ mobileTesting: btsConfig.mobileTesting,
444
+ mobilePush: btsConfig.mobilePush,
445
+ mobileOTA: btsConfig.mobileOTA,
446
+ mobileDeepLinking: btsConfig.mobileDeepLinking,
447
+ cms: btsConfig.cms,
448
+ caching: btsConfig.caching,
449
+ i18n: btsConfig.i18n,
450
+ search: btsConfig.search,
451
+ fileStorage: btsConfig.fileStorage,
452
+ rustWebFramework: btsConfig.rustWebFramework,
453
+ rustFrontend: btsConfig.rustFrontend,
454
+ rustOrm: btsConfig.rustOrm,
455
+ rustApi: btsConfig.rustApi,
456
+ rustCli: btsConfig.rustCli,
457
+ rustLibraries: btsConfig.rustLibraries,
458
+ rustLogging: btsConfig.rustLogging,
459
+ rustErrorHandling: btsConfig.rustErrorHandling,
460
+ rustCaching: btsConfig.rustCaching,
461
+ rustAuth: btsConfig.rustAuth,
462
+ pythonWebFramework: btsConfig.pythonWebFramework,
463
+ pythonOrm: btsConfig.pythonOrm,
464
+ pythonValidation: btsConfig.pythonValidation,
465
+ pythonAi: btsConfig.pythonAi,
466
+ pythonAuth: btsConfig.pythonAuth,
467
+ pythonApi: btsConfig.pythonApi ?? "none",
468
+ pythonTaskQueue: btsConfig.pythonTaskQueue,
469
+ pythonGraphql: btsConfig.pythonGraphql,
470
+ pythonQuality: btsConfig.pythonQuality,
471
+ goWebFramework: btsConfig.goWebFramework,
472
+ goOrm: btsConfig.goOrm,
473
+ goApi: btsConfig.goApi,
474
+ goCli: btsConfig.goCli,
475
+ goLogging: btsConfig.goLogging,
476
+ goAuth: btsConfig.goAuth,
477
+ javaWebFramework: btsConfig.javaWebFramework,
478
+ javaBuildTool: btsConfig.javaBuildTool,
479
+ javaOrm: btsConfig.javaOrm,
480
+ javaAuth: btsConfig.javaAuth,
481
+ javaLibraries: btsConfig.javaLibraries,
482
+ javaTestingLibraries: btsConfig.javaTestingLibraries,
483
+ elixirWebFramework: btsConfig.elixirWebFramework,
484
+ elixirOrm: btsConfig.elixirOrm,
485
+ elixirAuth: btsConfig.elixirAuth,
486
+ elixirApi: btsConfig.elixirApi,
487
+ elixirRealtime: btsConfig.elixirRealtime,
488
+ elixirJobs: btsConfig.elixirJobs,
489
+ elixirValidation: btsConfig.elixirValidation,
490
+ elixirHttp: btsConfig.elixirHttp,
491
+ elixirJson: btsConfig.elixirJson,
492
+ elixirEmail: btsConfig.elixirEmail,
493
+ elixirCaching: btsConfig.elixirCaching,
494
+ elixirObservability: btsConfig.elixirObservability,
495
+ elixirTesting: btsConfig.elixirTesting,
496
+ elixirQuality: btsConfig.elixirQuality,
497
+ elixirDeploy: btsConfig.elixirDeploy,
498
+ aiDocs: btsConfig.aiDocs,
499
+ stackParts: btsConfig.stackParts
500
+ };
501
+ let configContent = JSON.stringify(baseContent);
502
+ const formatResult = JSONC.format(configContent, void 0, {
503
+ tabSize: 2,
504
+ insertSpaces: true,
505
+ eol: "\n"
506
+ });
507
+ configContent = JSONC.applyEdits(configContent, formatResult);
508
+ const finalContent = `// Better Fullstack configuration file
509
+ // 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" : ""}
511
+
512
+ ${configContent}`;
513
+ const configPath = path.join(projectConfig.projectDir, BTS_CONFIG_FILE);
514
+ await fs.writeFile(configPath, finalContent, "utf-8");
515
+ }
516
+ async function readBtsConfig(projectDir) {
517
+ try {
518
+ const configPath = path.join(projectDir, BTS_CONFIG_FILE);
519
+ if (!await fs.pathExists(configPath)) return null;
520
+ const configContent = await fs.readFile(configPath, "utf-8");
521
+ const errors = [];
522
+ const config = JSONC.parse(configContent, errors, {
523
+ allowTrailingComma: true,
524
+ disallowComments: false
525
+ });
526
+ if (errors.length > 0) {
527
+ console.warn("Warning: Found errors parsing bts.jsonc:", errors);
528
+ return null;
529
+ }
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
+ };
543
+ } catch {
544
+ return null;
545
+ }
546
+ }
547
+ async function updateBtsConfig(projectDir, updates) {
548
+ try {
549
+ const configPath = path.join(projectDir, BTS_CONFIG_FILE);
550
+ if (!await fs.pathExists(configPath)) return;
551
+ let modifiedContent = await fs.readFile(configPath, "utf-8");
552
+ for (const [key, value] of Object.entries(updates)) {
553
+ const editResult = JSONC.modify(modifiedContent, [key], value, { formattingOptions: {
554
+ tabSize: 2,
555
+ insertSpaces: true,
556
+ eol: "\n"
557
+ } });
558
+ modifiedContent = JSONC.applyEdits(modifiedContent, editResult);
559
+ }
560
+ await fs.writeFile(configPath, modifiedContent, "utf-8");
561
+ } catch {}
562
+ }
563
+
564
+ //#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 };
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-DoPutOIG.mjs").then((m) => m.startMcpServer());
3
+ if (process.argv[2] === "mcp" && process.argv.length === 3) import("./mcp-DfnYbMju.mjs").then((m) => m.startMcpServer());
4
4
  else import("./index.mjs").then((m) => m.createBtsCli().run());
5
5
 
6
6
  //#endregion