create-better-fullstack 2.1.5 → 2.1.7

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.
Files changed (29) hide show
  1. package/dist/{add-handler-BNSL6HdM.mjs → add-handler-ztNjzoMN.mjs} +52 -6
  2. package/dist/addons-setup-DnLjAzTw.mjs +7 -0
  3. package/dist/{addons-setup-CyrP1IV-.mjs → addons-setup-LaAj43NP.mjs} +10 -24
  4. package/dist/analytics-DVltG11u.mjs +170 -0
  5. package/dist/{errors-Cyol8zbN.mjs → bts-config-DQVWvPDs.mjs} +72 -149
  6. package/dist/cli.mjs +2 -2
  7. package/dist/{templates-CnTOtKjm.mjs → config-processing-D9-F2Us9.mjs} +57 -2
  8. package/dist/{doctor-DBoq7bZ9.mjs → doctor-a4ca3SMd.mjs} +3 -2
  9. package/dist/errors-ns_o2OKg.mjs +86 -0
  10. package/dist/{file-formatter-B3dsev2l.mjs → file-formatter-gvmrpd-g.mjs} +78 -6
  11. package/dist/gen-CCClL7Ve.mjs +274 -0
  12. package/dist/{generated-checks-C8hn9w2i.mjs → generated-checks-BV9jol5h.mjs} +1 -1
  13. package/dist/index.d.mts +162 -64
  14. package/dist/index.mjs +17 -8
  15. package/dist/{install-dependencies-CgNh-aOy.mjs → install-dependencies-RoUyaE8o.mjs} +80 -21
  16. package/dist/mcp-492OkjcS.mjs +11 -0
  17. package/dist/mcp-entry.mjs +205 -30
  18. package/dist/prompt-environment-BR0Kkw2W.mjs +26 -0
  19. package/dist/registry-DSf2CEaU.mjs +408 -0
  20. package/dist/{run-BYse4yJy.mjs → run-D9I7NdES.mjs} +2081 -464
  21. package/dist/run-buK4aZS8.mjs +15 -0
  22. package/dist/scaffold-manifest-DGRyepdb.mjs +4 -0
  23. package/dist/scaffold-manifest-Dyi0voqE.mjs +135 -0
  24. package/dist/update-DJ8CI5KW.mjs +401 -0
  25. package/package.json +3 -3
  26. package/dist/addons-setup-DyMm42a5.mjs +0 -5
  27. package/dist/mcp-CsW3i66c.mjs +0 -6
  28. package/dist/run-_cf_sFwM.mjs +0 -10
  29. /package/dist/{update-deps-D5OG0KmJ.mjs → update-deps-aD-iQw4U.mjs} +0 -0
@@ -1,11 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import { A as types_exports, C as getEffectiveStack, D as getGraphSummary, _ as writeBtsConfig, f as buildBtsConfigForPersistence, m as readBtsConfig, p as previewBtsConfigUpdate, v as getLatestCLIVersion, x as getDefaultConfig } from "./errors-Cyol8zbN.mjs";
3
- import { r as validateConfigForProgrammaticUse, t as formatCode, v as CreateCommandOptionsSchema } from "./file-formatter-B3dsev2l.mjs";
4
- import { n as getTemplateDescription, r as generateReproducibleCommand, t as getTemplateConfig } from "./templates-CnTOtKjm.mjs";
2
+ import { b as types_exports, d as getGraphSummary, m as getLatestCLIVersion, n as previewBtsConfigUpdate, o as writeBtsConfig, r as readBtsConfig, s as getEffectiveStack, t as buildBtsConfigForPersistence, v as getDefaultConfig } from "./bts-config-DQVWvPDs.mjs";
3
+ import "./errors-ns_o2OKg.mjs";
4
+ import "./prompt-environment-BR0Kkw2W.mjs";
5
+ import { a as trackProjectCreation, i as trackEvent } from "./analytics-DVltG11u.mjs";
6
+ import { r as validateConfigForProgrammaticUse, t as formatCode, v as CreateCommandOptionsSchema } from "./file-formatter-gvmrpd-g.mjs";
7
+ import { a as getTemplateConfig, o as getTemplateDescription, s as generateReproducibleCommand, t as applyEffectBackendDefaults } from "./config-processing-D9-F2Us9.mjs";
8
+ import { o as refreshScaffoldManifestFiles } from "./scaffold-manifest-Dyi0voqE.mjs";
5
9
  import z from "zod";
6
10
  import fs from "fs-extra";
7
11
  import path from "node:path";
8
- import { AISchema, APISchema, AddonsSchema, AiDocsSchema, AnalyticsSchema, AnimationSchema, AstroIntegrationSchema, AuthSchema, BackendSchema, CATEGORY_ORDER, CMSSchema, CSSFrameworkSchema, CachingSchema, DatabaseSchema, DatabaseSetupSchema, DotnetApiSchema, DotnetAuthSchema, DotnetCachingSchema, DotnetDeploySchema, DotnetJobQueueSchema, DotnetObservabilitySchema, DotnetOrmSchema, DotnetRealtimeSchema, DotnetTestingSchema, DotnetValidationSchema, DotnetWebFrameworkSchema, EcosystemSchema, EffectSchema, ElixirApiSchema, ElixirAuthSchema, ElixirCachingSchema, ElixirDeploySchema, ElixirEmailSchema, ElixirHttpSchema, ElixirJobsSchema, ElixirJsonSchema, ElixirLibrariesSchema, ElixirObservabilitySchema, ElixirOrmSchema, ElixirQualitySchema, ElixirRealtimeSchema, ElixirTestingSchema, ElixirValidationSchema, ElixirWebFrameworkSchema, EmailSchema, ExamplesSchema, FeatureFlagsSchema, FileStorageSchema, FileUploadSchema, FormsSchema, FrontendSchema, GoApiSchema, GoAuthSchema, GoCachingSchema, GoCliSchema, GoConfigSchema, GoLoggingSchema, GoMessageQueueSchema, GoObservabilitySchema, GoOrmSchema, GoRealtimeSchema, GoTestingSchema, GoWebFrameworkSchema, I18nSchema, JavaApiSchema, JavaAuthSchema, JavaBuildToolSchema, JavaLibrariesSchema, JavaLoggingSchema, JavaOrmSchema, JavaTestingLibrariesSchema, JavaWebFrameworkSchema, JobQueueSchema, LoggingSchema, MobileDeepLinkingSchema, MobileNavigationSchema, MobileOTASchema, MobilePushSchema, MobileStorageSchema, MobileTestingSchema, MobileUISchema, OPTION_CATEGORY_METADATA, ORMSchema, ObservabilitySchema, PackageManagerSchema, PaymentsSchema, PythonAiSchema, PythonApiSchema, PythonAuthSchema, PythonCachingSchema, PythonCliSchema, PythonGraphqlSchema, PythonObservabilitySchema, PythonOrmSchema, PythonQualitySchema, PythonRealtimeSchema, PythonTaskQueueSchema, PythonTestingSchema, PythonValidationSchema, PythonWebFrameworkSchema, RateLimitSchema, RealtimeSchema, RuntimeSchema, RustApiSchema, RustAuthSchema, RustCachingSchema, RustCliSchema, RustErrorHandlingSchema, RustFrontendSchema, RustLibrariesSchema, RustLoggingSchema, RustMessageQueueSchema, RustObservabilitySchema, RustOrmSchema, RustRealtimeSchema, RustTemplatingSchema, RustWebFrameworkSchema, SearchSchema, ServerDeploySchema, ShadcnBaseColorSchema, ShadcnBaseSchema, ShadcnColorThemeSchema, ShadcnFontSchema, ShadcnIconLibrarySchema, ShadcnRadiusSchema, ShadcnStyleSchema, StateManagementSchema, TEMPLATE_VALUES, TestingSchema, UILibrarySchema, ValidationSchema, VectorDbSchema, VersionChannelSchema, WebDeploySchema, analyzeStackCompatibility, evaluateCompatibility, formatStackPartSpec, getCategoryOrderForEcosystem, legacyProjectConfigToStackParts, parseStackPartSpecs, stackPartsToLegacyProjectConfigPartial } from "@better-fullstack/types";
12
+ import { AISchema, APISchema, AddonsSchema, AiDocsSchema, AnalyticsSchema, AnimationSchema, AstroIntegrationSchema, AuthSchema, BackendSchema, CATEGORY_ORDER, CMSSchema, CSSFrameworkSchema, CachingSchema, DatabaseSchema, DatabaseSetupSchema, DotnetApiSchema, DotnetAuthSchema, DotnetCachingSchema, DotnetDeploySchema, DotnetJobQueueSchema, DotnetObservabilitySchema, DotnetOrmSchema, DotnetRealtimeSchema, DotnetTestingSchema, DotnetValidationSchema, DotnetWebFrameworkSchema, EcosystemSchema, EffectSchema, ElixirApiSchema, ElixirAuthSchema, ElixirCachingSchema, ElixirDeploySchema, ElixirEmailSchema, ElixirHttpSchema, ElixirJobsSchema, ElixirJsonSchema, ElixirLibrariesSchema, ElixirObservabilitySchema, ElixirOrmSchema, ElixirQualitySchema, ElixirRealtimeSchema, ElixirTestingSchema, ElixirValidationSchema, ElixirWebFrameworkSchema, EmailSchema, ExamplesSchema, FeatureFlagsSchema, FileStorageSchema, FileUploadSchema, FormsSchema, FrontendSchema, GoApiSchema, GoAuthSchema, GoCachingSchema, GoCliSchema, GoConfigSchema, GoLoggingSchema, GoMessageQueueSchema, GoObservabilitySchema, GoOrmSchema, GoRealtimeSchema, GoTestingSchema, GoWebFrameworkSchema, I18nSchema, JavaApiSchema, JavaAuthSchema, JavaBuildToolSchema, JavaLanguageSchema, JavaLibrariesSchema, JavaLoggingSchema, JavaOrmSchema, JavaTestingLibrariesSchema, JavaWebFrameworkSchema, JobQueueSchema, LoggingSchema, MobileDeepLinkingSchema, MobileNavigationSchema, MobileOTASchema, MobilePushSchema, MobileStorageSchema, MobileTestingSchema, MobileUISchema, OPTION_CATEGORY_METADATA, ORMSchema, ObservabilitySchema, PackageManagerSchema, PaymentsSchema, PythonAiSchema, PythonApiSchema, PythonAuthSchema, PythonCachingSchema, PythonCliSchema, PythonGraphqlSchema, PythonObservabilitySchema, PythonOrmSchema, PythonQualitySchema, PythonRealtimeSchema, PythonTaskQueueSchema, PythonTestingSchema, PythonValidationSchema, PythonWebFrameworkSchema, RateLimitSchema, RealtimeSchema, RuntimeSchema, RustApiSchema, RustAuthSchema, RustCachingSchema, RustCliSchema, RustErrorHandlingSchema, RustFrontendSchema, RustLibrariesSchema, RustLoggingSchema, RustMessageQueueSchema, RustObservabilitySchema, RustOrmSchema, RustRealtimeSchema, RustTemplatingSchema, RustWebFrameworkSchema, SearchSchema, ServerDeploySchema, ShadcnBaseColorSchema, ShadcnBaseSchema, ShadcnColorThemeSchema, ShadcnFontSchema, ShadcnIconLibrarySchema, ShadcnRadiusSchema, ShadcnStyleSchema, StateManagementSchema, TEMPLATE_VALUES, TestingSchema, UILibrarySchema, ValidationSchema, VectorDbSchema, VersionChannelSchema, WebDeploySchema, analyzeStackCompatibility, evaluateCompatibility, formatStackPartSpec, getCategoryOrderForEcosystem, legacyProjectConfigToStackParts, parseStackPartSpecs, stackPartsToLegacyProjectConfigPartial } from "@better-fullstack/types";
9
13
  import { tmpdir } from "node:os";
10
14
  import { EMBEDDED_TEMPLATES, generateVirtualProject } from "@better-fullstack/template-generator";
11
15
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -43,7 +47,8 @@ const NON_STACK_UPDATE_CREATE_KEYS = new Set([
43
47
  "directoryConflict",
44
48
  "renderTitle",
45
49
  "disableAnalytics",
46
- "manualDb"
50
+ "manualDb",
51
+ "workspaceShape"
47
52
  ]);
48
53
  const SUPPORTED_STACK_UPDATE_KEYS = Object.keys(CreateCommandOptionsSchema.shape).filter((key) => !NON_STACK_UPDATE_CREATE_KEYS.has(key)).sort();
49
54
  const SUPPORTED_STACK_UPDATE_KEY_SET = new Set(SUPPORTED_STACK_UPDATE_KEYS);
@@ -51,8 +56,17 @@ const IGNORED_REQUEST_KEYS = new Set([
51
56
  "projectDir",
52
57
  "projectName",
53
58
  "install",
54
- "git"
59
+ "git",
60
+ "acknowledgeArchitectureChange"
55
61
  ]);
62
+ const RISKY_ARCHITECTURE_KEYS = [
63
+ "database",
64
+ "orm",
65
+ "auth",
66
+ "api",
67
+ "backend",
68
+ "runtime"
69
+ ];
56
70
  const PACKAGE_JSON_SECTIONS = [
57
71
  "dependencies",
58
72
  "devDependencies",
@@ -193,6 +207,18 @@ function getUpdatedSpecForChangedPart(part, parts, proposedConfig, changedKeys)
193
207
  }, parts);
194
208
  }
195
209
  }
210
+ function pruneScopedSpecsWithoutOwners(specs) {
211
+ const primaryRoles = /* @__PURE__ */ new Set();
212
+ for (const spec of specs) {
213
+ const rolePath = spec.split(":")[0];
214
+ if (rolePath && !rolePath.includes(".")) primaryRoles.add(rolePath);
215
+ }
216
+ return specs.filter((spec) => {
217
+ const rolePath = spec.split(":")[0];
218
+ const ownerRole = rolePath?.split(".")[0];
219
+ return !rolePath?.includes(".") || ownerRole !== void 0 && primaryRoles.has(ownerRole);
220
+ });
221
+ }
196
222
  function mergeDerivedStackPartsWithExistingGraph(currentConfig, proposedConfig) {
197
223
  const currentStackParts = currentConfig.stackParts?.length ? currentConfig.stackParts : (0, types_exports.legacyProjectConfigToStackParts)(currentConfig);
198
224
  const derivedStackParts = (0, types_exports.legacyProjectConfigToStackParts)(proposedConfig);
@@ -210,21 +236,82 @@ function mergeDerivedStackPartsWithExistingGraph(currentConfig, proposedConfig)
210
236
  return spec ? [spec] : [];
211
237
  });
212
238
  const preservedSpecs = new Set(preservedParts.map((part) => (0, types_exports.formatStackPartSpec)(part, currentStackParts)));
213
- const coveredParts = (0, types_exports.parseStackPartSpecs)([...new Set([...preservedSpecs, ...updatedSpecs])], "selected");
239
+ const coveredSpecs = pruneScopedSpecsWithoutOwners([...new Set([...preservedSpecs, ...updatedSpecs])]);
240
+ const coveredParts = (0, types_exports.parseStackPartSpecs)(coveredSpecs, "selected");
214
241
  const preservedProjectedKeys = /* @__PURE__ */ new Set();
215
242
  for (const part of coveredParts) for (const key of getProjectedConfigKeys(part, coveredParts)) preservedProjectedKeys.add(key);
216
- const nextSpecs = [...new Set([...preservedSpecs, ...updatedSpecs])];
243
+ const nextSpecs = [...coveredSpecs];
217
244
  for (const part of derivedStackParts) {
218
245
  if (part.source === "provided") continue;
219
246
  const spec = (0, types_exports.formatStackPartSpec)(part, derivedStackParts);
220
247
  if (nextSpecs.includes(spec)) continue;
221
248
  if (![...getProjectedConfigKeys(part, derivedStackParts)].some((key) => preservedProjectedKeys.has(key))) nextSpecs.push(spec);
222
249
  }
223
- return (0, types_exports.parseStackPartSpecs)([...new Set(nextSpecs)], "selected");
250
+ return (0, types_exports.parseStackPartSpecs)(pruneScopedSpecsWithoutOwners([...new Set(nextSpecs)]), "selected");
224
251
  }
225
252
  function asString(value, fallback = "none") {
226
253
  return typeof value === "string" ? value : fallback;
227
254
  }
255
+ function computeArchitectureChanges(currentConfig, proposedConfig) {
256
+ const changes = [];
257
+ for (const key of RISKY_ARCHITECTURE_KEYS) {
258
+ const from = asString(currentConfig[key]);
259
+ const to = asString(proposedConfig[key]);
260
+ if (from !== "none" && from !== to) changes.push({
261
+ key,
262
+ from,
263
+ to
264
+ });
265
+ }
266
+ return changes;
267
+ }
268
+ function buildMigrationSteps(changes) {
269
+ const steps = [];
270
+ for (const { key, from, to } of changes) {
271
+ const label = `${key} (${from} -> ${to})`;
272
+ switch (key) {
273
+ case "database":
274
+ steps.push(`${label}: Back up all existing data from the ${from} database before making changes.`, `${label}: Provision a ${to} database and update DATABASE_URL in .env and .env.example.`, `${label}: Regenerate the schema for ${to} and create + run an initial migration.`, `${label}: Export rows from ${from} and import them into ${to} (data is NOT migrated automatically).`);
275
+ break;
276
+ case "orm":
277
+ steps.push(`${label}: Re-author the database schema/models using ${to} conventions.`, `${label}: Regenerate the ${to} client and create an initial ${to} migration.`, `${label}: Port existing ${from} queries and migration history to ${to}, then remove ${from} artifacts.`);
278
+ break;
279
+ case "auth":
280
+ steps.push(`${label}: Migrate existing user/account records into the ${to} schema.`, `${label}: Invalidate current sessions and update auth secrets/env vars for ${to}.`, `${label}: Update sign-in/sign-up flows and protected routes to use ${to}.`);
281
+ break;
282
+ case "api":
283
+ steps.push(`${label}: Port server routers/handlers from ${from} to ${to}.`, `${label}: Update client call sites and generated types to the ${to} client.`);
284
+ break;
285
+ case "backend":
286
+ steps.push(`${label}: Port the server entrypoint, routes, and middleware from ${from} to ${to}.`, `${label}: Reconcile runtime and deploy configuration for the ${to} server.`);
287
+ break;
288
+ case "runtime":
289
+ steps.push(`${label}: Update the runtime toolchain, scripts, and deploy target for ${to}.`, `${label}: Verify runtime-specific APIs and environment bindings behave correctly on ${to}.`);
290
+ break;
291
+ default: steps.push(`${label}: Review and migrate affected code manually.`);
292
+ }
293
+ }
294
+ return steps;
295
+ }
296
+ async function writeMigrationChecklist(projectDir, plan) {
297
+ if (plan.architectureChanges.length === 0 || plan.migrationSteps.length === 0) return;
298
+ const migrationPath = path.join(projectDir, "MIGRATION.md");
299
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
300
+ const swaps = plan.architectureChanges.map((change) => `\`${change.key}\`: \`${change.from}\` -> \`${change.to}\``).join(", ");
301
+ const section = [
302
+ `## Architecture change - ${timestamp}`,
303
+ "",
304
+ `Swapped: ${swaps}`,
305
+ "",
306
+ "Data and schema are NOT migrated automatically. Complete these steps manually:",
307
+ "",
308
+ ...plan.migrationSteps.map((step) => `- [ ] ${step}`)
309
+ ].join("\n");
310
+ if (await fs.pathExists(migrationPath)) {
311
+ const existing = (await fs.readFile(migrationPath, "utf-8")).trimEnd();
312
+ await fs.writeFile(migrationPath, `${existing}\n\n${section}\n`, "utf-8");
313
+ } else await fs.writeFile(migrationPath, `# Migration checklist\n\n${section}\n`, "utf-8");
314
+ }
228
315
  function asStringArray(value) {
229
316
  return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
230
317
  }
@@ -304,6 +391,13 @@ function getDefaultNativeFrontendForRequestedUpdate(requestedChanges) {
304
391
  if (requestedChanges.mobileUI === "uniwind" || requestedChanges.mobileUI === "unistyles") return;
305
392
  return hasRequestedNonNoneValue(requestedChanges, "mobileNavigation") || hasRequestedNonNoneValue(requestedChanges, "mobileUI") || hasRequestedNonNoneValue(requestedChanges, "mobileStorage") || hasRequestedNonNoneValue(requestedChanges, "mobileTesting") || hasRequestedNonNoneValue(requestedChanges, "mobilePush") || hasRequestedNonNoneValue(requestedChanges, "mobileOTA") || hasRequestedNonNoneValue(requestedChanges, "mobileDeepLinking") ? "native-bare" : void 0;
306
393
  }
394
+ function hasSelectedTypeScriptBackendPart(config) {
395
+ return config.stackParts?.some((part) => part.source !== "provided" && !part.ownerPartId && part.role === "backend" && part.ecosystem === "typescript" && part.toolId !== "none") ?? false;
396
+ }
397
+ function getCompatibilityEcosystem(config) {
398
+ if (config.ecosystem === "react-native" && hasSelectedTypeScriptBackendPart(config)) return "typescript";
399
+ return config.ecosystem;
400
+ }
307
401
  function buildCompatibilityInputFromConfig(config) {
308
402
  const frontend = asStringArray(config.frontend);
309
403
  const addons = asStringArray(config.addons);
@@ -319,7 +413,7 @@ function buildCompatibilityInputFromConfig(config) {
319
413
  else if (addon !== "none") appPlatforms.push(addon);
320
414
  }
321
415
  return {
322
- ecosystem: config.ecosystem,
416
+ ecosystem: getCompatibilityEcosystem(config),
323
417
  projectName: config.projectName ?? null,
324
418
  webFrontend,
325
419
  nativeFrontend,
@@ -372,6 +466,7 @@ function buildCompatibilityInputFromConfig(config) {
372
466
  documentation,
373
467
  appPlatforms,
374
468
  packageManager: asString(config.packageManager, "bun"),
469
+ workspaceShape: asString(config.workspaceShape, "monorepo"),
375
470
  versionChannel: asString(config.versionChannel, "stable"),
376
471
  examples: asStringArray(config.examples),
377
472
  aiSdk: asString(config.ai),
@@ -422,6 +517,7 @@ function buildCompatibilityInputFromConfig(config) {
422
517
  goCaching: asString(config.goCaching),
423
518
  goConfig: asString(config.goConfig),
424
519
  goObservability: asString(config.goObservability),
520
+ javaLanguage: asString(config.javaLanguage, "java"),
425
521
  javaWebFramework: asString(config.javaWebFramework),
426
522
  javaBuildTool: asString(config.javaBuildTool),
427
523
  javaOrm: asString(config.javaOrm),
@@ -599,6 +695,16 @@ function applyKnownDependencyExpansions(config, requestedChanges) {
599
695
  adjustments.push("examples: AI SDK set to 'vercel-ai' (Chat SDK profile requires Vercel AI)");
600
696
  }
601
697
  }
698
+ if (next.backend === "effect") {
699
+ if (next.effect !== "effect-full" && !requestedKeys.has("effect")) {
700
+ next.effect = "effect-full";
701
+ adjustments.push("effect: Effect services set to 'effect-full' (Effect backend requires Effect Platform + SQL)");
702
+ }
703
+ if (next.validation !== "effect-schema" && !requestedKeys.has("validation")) {
704
+ next.validation = "effect-schema";
705
+ adjustments.push("validation: Validation set to 'effect-schema' (Effect backend requires Effect Schema)");
706
+ }
707
+ }
602
708
  return {
603
709
  config: next,
604
710
  adjustments
@@ -1037,6 +1143,8 @@ async function planStackUpdate(projectDirInput, input) {
1037
1143
  proposedConfig: normalizedProposedConfig
1038
1144
  });
1039
1145
  const graphPreview = getGraphPreview(persistedProposedConfig);
1146
+ const architectureChanges = computeArchitectureChanges(currentConfig, proposedConfig);
1147
+ const migrationSteps = buildMigrationSteps(architectureChanges);
1040
1148
  return {
1041
1149
  success: true,
1042
1150
  projectDir,
@@ -1049,6 +1157,9 @@ async function planStackUpdate(projectDirInput, input) {
1049
1157
  scriptChanges,
1050
1158
  envChanges,
1051
1159
  manualReviewBlockers,
1160
+ architectureChanges,
1161
+ migrationSteps,
1162
+ requiresArchitectureAck: architectureChanges.length > 0,
1052
1163
  operations,
1053
1164
  installCommand: getInstallCommand$1(normalizedProposedConfig),
1054
1165
  compatibilityAdjustments,
@@ -1063,6 +1174,16 @@ async function applyStackUpdate(projectDirInput, input) {
1063
1174
  projectDir: plan.projectDir,
1064
1175
  error: `Manual review required before applying stack update: ${plan.manualReviewBlockers.join("; ")}`
1065
1176
  };
1177
+ const acknowledgeArchitectureChange = input.acknowledgeArchitectureChange === true;
1178
+ if (plan.requiresArchitectureAck && !acknowledgeArchitectureChange) {
1179
+ const swaps = plan.architectureChanges.map((change) => `${change.key}: ${change.from} -> ${change.to}`).join("; ");
1180
+ const checklist = plan.migrationSteps.map((step) => ` - ${step}`).join("\n");
1181
+ return {
1182
+ success: false,
1183
+ projectDir: plan.projectDir,
1184
+ error: `This architecture change requires acknowledgment before it can be applied. It replaces existing architecture-defining choices (${swaps}); data and schema are NOT migrated automatically. Re-run with acknowledgeArchitectureChange: true (MCP) or --acknowledge-architecture-change (CLI) after reviewing the migration checklist:\n${checklist}`
1185
+ };
1186
+ }
1066
1187
  const projectName = await inferProjectName(plan.projectDir);
1067
1188
  const proposedConfig = configFromBtsConfig(plan.proposedConfig, plan.projectDir, projectName);
1068
1189
  const proposedTree = await generateTree(proposedConfig);
@@ -1078,6 +1199,8 @@ async function applyStackUpdate(projectDirInput, input) {
1078
1199
  version: plan.proposedConfig.version,
1079
1200
  createdAt: plan.proposedConfig.createdAt
1080
1201
  });
1202
+ await refreshScaffoldManifestFiles(plan.projectDir, plan.operations.map((operation) => operation.path));
1203
+ await writeMigrationChecklist(plan.projectDir, plan);
1081
1204
  return plan;
1082
1205
  }
1083
1206
 
@@ -1326,6 +1449,7 @@ const MCP_COMPATIBILITY_DEFAULTS = {
1326
1449
  mobilePush: "none",
1327
1450
  mobileOTA: "none",
1328
1451
  packageManager: "bun",
1452
+ workspaceShape: "monorepo",
1329
1453
  versionChannel: "stable",
1330
1454
  examples: [],
1331
1455
  aiSdk: "none",
@@ -1376,6 +1500,7 @@ const MCP_COMPATIBILITY_DEFAULTS = {
1376
1500
  goCaching: "none",
1377
1501
  goConfig: "none",
1378
1502
  goObservability: "none",
1503
+ javaLanguage: "java",
1379
1504
  javaWebFramework: "spring-boot",
1380
1505
  javaBuildTool: "maven",
1381
1506
  javaOrm: "none",
@@ -1465,6 +1590,7 @@ function buildProjectConfig(input, overrides) {
1465
1590
  const stackParts = parseStackPartSpecs(input.part.filter((part) => typeof part === "string"), "selected");
1466
1591
  Object.assign(config, stackPartsToLegacyProjectConfigPartial(stackParts), { stackParts });
1467
1592
  }
1593
+ applyEffectBackendDefaults(config, new Set(Object.keys(input)));
1468
1594
  return config;
1469
1595
  }
1470
1596
  function sanitizePath(input) {
@@ -1487,7 +1613,7 @@ function buildCompatibilityInput(input) {
1487
1613
  ...documentation,
1488
1614
  "none"
1489
1615
  ].includes(a));
1490
- return {
1616
+ const result = {
1491
1617
  ecosystem,
1492
1618
  projectName: input.projectName ?? null,
1493
1619
  webFrontend,
@@ -1500,6 +1626,11 @@ function buildCompatibilityInput(input) {
1500
1626
  appPlatforms,
1501
1627
  aiSdk: input.ai ?? defaults.aiSdk
1502
1628
  };
1629
+ if (result.backend === "effect") {
1630
+ if (input.effect === void 0) result.backendLibraries = "effect-full";
1631
+ if (input.validation === void 0) result.validation = "effect-schema";
1632
+ }
1633
+ return result;
1503
1634
  }
1504
1635
  function summarizeTree(tree) {
1505
1636
  const paths = [];
@@ -1534,6 +1665,7 @@ const COMPATIBILITY_RULES_MD = `# Better-Fullstack Compatibility Rules
1534
1665
 
1535
1666
  ## Backend Constraints
1536
1667
  - **Convex**: Forces runtime=none, database=none, orm=none, api=none, dbSetup=none, serverDeploy=none. Removes incompatible frontends (Solid, SolidStart, Astro).
1668
+ - **Effect backend**: Requires effect=effect-full and validation=effect-schema. Other compatible frontend/backend-adjacent tools can still be selected.
1537
1669
  - **No backend (none)**: Clears auth, payments, database, orm, api, serverDeploy, search, fileStorage.
1538
1670
  - **Fullstack (backend='self')**: Sets runtime=none, serverDeploy=none. Only works with: next, vinext, tanstack-start, astro, nuxt, svelte, solid-start.
1539
1671
 
@@ -1734,6 +1866,13 @@ const stackUpdateOutputSchema = {
1734
1866
  scriptChanges: z.record(z.string(), z.array(z.string())).optional(),
1735
1867
  envChanges: z.record(z.string(), z.array(z.string())).optional(),
1736
1868
  manualReviewBlockers: z.array(z.string()).optional(),
1869
+ architectureChanges: z.array(z.object({
1870
+ key: z.string(),
1871
+ from: z.string(),
1872
+ to: z.string()
1873
+ })).optional(),
1874
+ migrationSteps: z.array(z.string()).optional(),
1875
+ requiresArchitectureAck: z.boolean().optional(),
1737
1876
  compatibilityAdjustments: z.array(z.string()).optional(),
1738
1877
  compatibilityWarnings: z.array(z.string()).optional(),
1739
1878
  installCommand: z.string().optional(),
@@ -1989,6 +2128,7 @@ const crossEcosystemInputSchema = {
1989
2128
  goCaching: GoCachingSchema.optional().describe("Go caching library"),
1990
2129
  goConfig: GoConfigSchema.optional().describe("Go config management"),
1991
2130
  goObservability: GoObservabilitySchema.optional().describe("Go observability"),
2131
+ javaLanguage: JavaLanguageSchema.optional().describe("JVM language (java, kotlin)"),
1992
2132
  javaWebFramework: JavaWebFrameworkSchema.optional().describe("Java web framework"),
1993
2133
  javaBuildTool: JavaBuildToolSchema.optional().describe("Java build tool"),
1994
2134
  javaOrm: JavaOrmSchema.optional().describe("Java ORM"),
@@ -2071,7 +2211,7 @@ const MCP_PLAN_CREATE_SCHEMA = {
2071
2211
  ...mobileInputSchema,
2072
2212
  fileUpload: FileUploadSchema.optional().describe("File upload"),
2073
2213
  ...deploymentInputSchema,
2074
- effect: EffectSchema.optional().describe("Effect ecosystem (effect, effect-full)"),
2214
+ effect: EffectSchema.optional().describe("Effect services (effect, effect-full)"),
2075
2215
  analytics: AnalyticsSchema.optional().describe("Privacy-focused analytics provider"),
2076
2216
  astroIntegration: AstroIntegrationSchema.optional().describe("Astro UI framework integration (react, vue, svelte, solid)"),
2077
2217
  aiDocs: z.array(AiDocsSchema).optional().describe("AI documentation files (claude-md, agents-md, cursorrules)"),
@@ -2080,7 +2220,8 @@ const MCP_PLAN_CREATE_SCHEMA = {
2080
2220
  };
2081
2221
  const MCP_STACK_UPDATE_SCHEMA = {
2082
2222
  ...MCP_PLAN_CREATE_SCHEMA,
2083
- projectDir: z.string().describe("Absolute path to the existing Better-Fullstack project")
2223
+ projectDir: z.string().describe("Absolute path to the existing Better-Fullstack project"),
2224
+ acknowledgeArchitectureChange: z.boolean().optional().describe("Acknowledge that this update replaces an existing database/orm/auth/api/backend/runtime choice. Required to apply architecture-changing updates; data and schema are NOT migrated automatically.")
2084
2225
  };
2085
2226
  async function startMcpServer() {
2086
2227
  const server = new McpServer({
@@ -2365,6 +2506,7 @@ async function startMcpServer() {
2365
2506
  openWorldHint: false
2366
2507
  }
2367
2508
  }, async (input) => {
2509
+ const startTime = Date.now();
2368
2510
  try {
2369
2511
  const { generateVirtualProject: generateVirtualProject$1, EMBEDDED_TEMPLATES: EMBEDDED_TEMPLATES$1 } = await import("@better-fullstack/template-generator");
2370
2512
  const { writeTreeToFilesystem: writeTreeToFilesystem$1 } = await import("@better-fullstack/template-generator/fs-writer");
@@ -2378,25 +2520,41 @@ async function startMcpServer() {
2378
2520
  config,
2379
2521
  templates: EMBEDDED_TEMPLATES$1
2380
2522
  });
2381
- if (!result.success || !result.tree) return {
2382
- content: [{
2383
- type: "text",
2384
- text: JSON.stringify({
2385
- success: false,
2386
- error: result.error ?? "Generation failed"
2387
- })
2388
- }],
2389
- isError: true
2390
- };
2523
+ if (!result.success || !result.tree) {
2524
+ await trackProjectCreation(config, false, {
2525
+ source: "mcp",
2526
+ success: false,
2527
+ errorName: "GenerationFailed",
2528
+ durationMs: Date.now() - startTime
2529
+ });
2530
+ return {
2531
+ content: [{
2532
+ type: "text",
2533
+ text: JSON.stringify({
2534
+ success: false,
2535
+ error: result.error ?? "Generation failed"
2536
+ })
2537
+ }],
2538
+ isError: true
2539
+ };
2540
+ }
2391
2541
  await writeTreeToFilesystem$1(result.tree, projectDir);
2392
2542
  await writeBtsConfig(config);
2393
2543
  const graphPreview = getMcpGraphPreview(config);
2394
2544
  let addonWarnings = [];
2395
2545
  if (config.addons.length > 0 && config.addons[0] !== "none") {
2396
- const { setupAddons } = await import("./addons-setup-DyMm42a5.mjs");
2546
+ const { setupAddons } = await import("./addons-setup-DnLjAzTw.mjs");
2397
2547
  addonWarnings = await setupAddons(config);
2398
2548
  }
2549
+ const { recordScaffoldManifest } = await import("./scaffold-manifest-DGRyepdb.mjs");
2550
+ await recordScaffoldManifest(projectDir);
2399
2551
  const installCmd = getInstallCommand(input.ecosystem ?? "typescript", projectName, input.packageManager, input.javaBuildTool, input.javaWebFramework);
2552
+ await trackProjectCreation(config, false, {
2553
+ source: "mcp",
2554
+ success: true,
2555
+ fileCount: result.tree.fileCount,
2556
+ durationMs: Date.now() - startTime
2557
+ });
2400
2558
  const payload = {
2401
2559
  success: true,
2402
2560
  projectDirectory: projectDir,
@@ -2413,6 +2571,12 @@ async function startMcpServer() {
2413
2571
  structuredContent: payload
2414
2572
  };
2415
2573
  } catch (error) {
2574
+ await trackEvent("project_created", {}, {
2575
+ source: "mcp",
2576
+ success: false,
2577
+ errorName: error instanceof Error ? error.name : "UnknownError",
2578
+ durationMs: Date.now() - startTime
2579
+ });
2416
2580
  return {
2417
2581
  content: [{
2418
2582
  type: "text",
@@ -2455,7 +2619,7 @@ async function startMcpServer() {
2455
2619
  ...safePlan,
2456
2620
  ...plan.compatibilityAdjustments.length > 0 ? { compatibilityAdjustments: plan.compatibilityAdjustments } : {},
2457
2621
  ...compatibilityWarnings ? { compatibilityWarnings } : {},
2458
- message: plan.manualReviewBlockers.length > 0 ? "Plan created, but manual review is required before applying." : `Plan created. If approved, call bfs_apply_stack_update, then run: ${plan.installCommand}`
2622
+ message: plan.manualReviewBlockers.length > 0 ? "Plan created, but manual review is required before applying." : plan.requiresArchitectureAck ? `Plan created. This is an architecture change (${plan.architectureChanges.map((change) => `${change.key}: ${change.from} -> ${change.to}`).join("; ")}); data and schema are NOT migrated automatically. Review migrationSteps, then call bfs_apply_stack_update with acknowledgeArchitectureChange: true, then run: ${plan.installCommand}` : `Plan created. If approved, call bfs_apply_stack_update, then run: ${plan.installCommand}`
2459
2623
  };
2460
2624
  return {
2461
2625
  content: [{
@@ -2492,10 +2656,15 @@ async function startMcpServer() {
2492
2656
  openWorldHint: false
2493
2657
  }
2494
2658
  }, async (input) => {
2659
+ const startTime = Date.now();
2660
+ const { projectDir: _projectDir, projectName: _projectName, ...requestedChanges } = input;
2495
2661
  try {
2496
- const safePath = sanitizePath(input.projectDir);
2497
- const { projectDir: _projectDir, projectName: _projectName, ...requestedChanges } = input;
2498
- const result = await applyStackUpdate(safePath, requestedChanges);
2662
+ const result = await applyStackUpdate(sanitizePath(input.projectDir), requestedChanges);
2663
+ await trackEvent("stack_updated", requestedChanges, {
2664
+ source: "mcp",
2665
+ success: result.success,
2666
+ durationMs: Date.now() - startTime
2667
+ });
2499
2668
  if (!result.success) return {
2500
2669
  content: [{
2501
2670
  type: "text",
@@ -2520,6 +2689,12 @@ async function startMcpServer() {
2520
2689
  structuredContent: payload
2521
2690
  };
2522
2691
  } catch (error) {
2692
+ await trackEvent("stack_updated", requestedChanges, {
2693
+ source: "mcp",
2694
+ success: false,
2695
+ errorName: error instanceof Error ? error.name : "UnknownError",
2696
+ durationMs: Date.now() - startTime
2697
+ });
2523
2698
  const payload = {
2524
2699
  success: false,
2525
2700
  projectDir: input.projectDir,
@@ -2648,7 +2823,7 @@ async function startMcpServer() {
2648
2823
  projectDir: safePath,
2649
2824
  install: false,
2650
2825
  packageManager: input.packageManager
2651
- });
2826
+ }, { telemetrySource: "mcp" });
2652
2827
  if (result?.success) {
2653
2828
  const existingConfig = await readBtsConfig(safePath);
2654
2829
  const graphPreview = existingConfig ? getMcpGraphPreview(existingConfig) : void 0;
@@ -2718,4 +2893,4 @@ async function startMcpServer() {
2718
2893
  startMcpServer();
2719
2894
 
2720
2895
  //#endregion
2721
- export { applyStackUpdate as a, startMcpServer as i, MCP_STACK_UPDATE_SCHEMA as n, planStackUpdate as o, getMcpGraphPreview as r, MCP_PLAN_CREATE_SCHEMA as t };
2896
+ export { startMcpServer as a, formatGeneratedTree as c, treeToFileMap as d, recommendStackFromBrief as i, generateTree as l, MCP_STACK_UPDATE_SCHEMA as n, applyStackUpdate as o, getMcpGraphPreview as r, configFromBtsConfig as s, MCP_PLAN_CREATE_SCHEMA as t, planStackUpdate as u };
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ import { c as isSilent } from "./errors-ns_o2OKg.mjs";
3
+
4
+ //#region src/utils/prompt-environment.ts
5
+ function hasOwnProperty(value, property) {
6
+ return Object.prototype.hasOwnProperty.call(value, property);
7
+ }
8
+ function resolveCiValue(environment) {
9
+ if (environment && hasOwnProperty(environment, "ci")) return environment.ci;
10
+ return process.env.CI;
11
+ }
12
+ function isCiEnvironment(value) {
13
+ if (!value) return false;
14
+ const normalizedValue = value.trim().toLowerCase();
15
+ return normalizedValue !== "" && normalizedValue !== "0" && normalizedValue !== "false";
16
+ }
17
+ function canPromptInteractively(environment = {}) {
18
+ const silent = environment.silent ?? isSilent();
19
+ const stdinIsTTY = environment.stdinIsTTY ?? process.stdin.isTTY === true;
20
+ const stdoutIsTTY = environment.stdoutIsTTY ?? process.stdout.isTTY === true;
21
+ const ci = resolveCiValue(environment);
22
+ return !silent && stdinIsTTY && stdoutIsTTY && !isCiEnvironment(ci);
23
+ }
24
+
25
+ //#endregion
26
+ export { canPromptInteractively as t };