create-better-fullstack 2.1.5 → 2.1.6
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/dist/{add-handler-BNSL6HdM.mjs → add-handler-CuPmSJRM.mjs} +13 -5
- package/dist/{addons-setup-CyrP1IV-.mjs → addons-setup-C_lxdJqU.mjs} +8 -1
- package/dist/addons-setup-DEPfsn6z.mjs +6 -0
- package/dist/{errors-Cyol8zbN.mjs → bts-config-BceXPcpI.mjs} +3 -84
- package/dist/cli.mjs +2 -2
- package/dist/{templates-CnTOtKjm.mjs → config-processing-B_1wTe3g.mjs} +57 -2
- package/dist/{doctor-DBoq7bZ9.mjs → doctor-DucDyWfl.mjs} +3 -2
- package/dist/errors-ns_o2OKg.mjs +86 -0
- package/dist/{file-formatter-B3dsev2l.mjs → file-formatter-XU6ti05V.mjs} +66 -6
- package/dist/gen-DWx3Xu_K.mjs +274 -0
- package/dist/{generated-checks-C8hn9w2i.mjs → generated-checks-Dt4Xqp1x.mjs} +1 -1
- package/dist/index.d.mts +171 -79
- package/dist/index.mjs +15 -8
- package/dist/{install-dependencies-CgNh-aOy.mjs → install-dependencies-DHoYa3P-.mjs} +75 -21
- package/dist/mcp-D9O5zgAA.mjs +8 -0
- package/dist/mcp-entry.mjs +153 -16
- package/dist/registry-CxeEOPot.mjs +394 -0
- package/dist/run-3AkXloH1.mjs +13 -0
- package/dist/{run-BYse4yJy.mjs → run-D80ZtSO8.mjs} +361 -88
- package/dist/scaffold-manifest-GV1fbhpD.mjs +123 -0
- package/dist/update-C9_x2yBF.mjs +401 -0
- package/package.json +3 -3
- package/dist/addons-setup-DyMm42a5.mjs +0 -5
- package/dist/mcp-CsW3i66c.mjs +0 -6
- package/dist/run-_cf_sFwM.mjs +0 -10
- /package/dist/{update-deps-D5OG0KmJ.mjs → update-deps-DLZAuT3V.mjs} +0 -0
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./
|
|
3
|
-
import { a as createBtsCli, c as history, d as telemetry, i as create, l as router, n as builder, o as docs, r as check, s as doctor, t as add, u as sponsors } from "./run-
|
|
2
|
+
import "./bts-config-BceXPcpI.mjs";
|
|
3
|
+
import { a as createBtsCli, c as history, d as telemetry, f as update, i as create, l as router, n as builder, o as docs, r as check, s as doctor, t as add, u as sponsors } from "./run-D80ZtSO8.mjs";
|
|
4
4
|
import "./render-title-zvyKC1ej.mjs";
|
|
5
|
-
import "./
|
|
6
|
-
import "./
|
|
7
|
-
import "./
|
|
8
|
-
import "./
|
|
5
|
+
import "./errors-ns_o2OKg.mjs";
|
|
6
|
+
import "./addons-setup-C_lxdJqU.mjs";
|
|
7
|
+
import "./file-formatter-XU6ti05V.mjs";
|
|
8
|
+
import "./install-dependencies-DHoYa3P-.mjs";
|
|
9
|
+
import "./generated-checks-Dt4Xqp1x.mjs";
|
|
10
|
+
import { t as applyEffectBackendDefaults } from "./config-processing-B_1wTe3g.mjs";
|
|
11
|
+
import "./scaffold-manifest-GV1fbhpD.mjs";
|
|
9
12
|
import { EMBEDDED_TEMPLATES, TEMPLATE_COUNT, VirtualFileSystem, generateVirtualProject } from "@better-fullstack/template-generator";
|
|
10
13
|
|
|
11
14
|
//#region src/index.ts
|
|
@@ -36,6 +39,7 @@ async function createVirtual(options) {
|
|
|
36
39
|
const ecosystem = options.ecosystem || "typescript";
|
|
37
40
|
const isReactNative = ecosystem === "react-native";
|
|
38
41
|
const frontend = options.frontend || (isReactNative ? ["native-bare"] : ["tanstack-router"]);
|
|
42
|
+
const backend = options.backend || (isReactNative ? "none" : "hono");
|
|
39
43
|
const hasNativeFrontend = frontend.some((item) => item === "native-bare" || item === "native-uniwind" || item === "native-unistyles");
|
|
40
44
|
const config = {
|
|
41
45
|
ecosystem,
|
|
@@ -44,7 +48,7 @@ async function createVirtual(options) {
|
|
|
44
48
|
relativePath: "./virtual",
|
|
45
49
|
database: options.database || "none",
|
|
46
50
|
orm: options.orm || "none",
|
|
47
|
-
backend
|
|
51
|
+
backend,
|
|
48
52
|
runtime: options.runtime || (isReactNative ? "none" : "bun"),
|
|
49
53
|
frontend,
|
|
50
54
|
addons: options.addons || [],
|
|
@@ -56,6 +60,7 @@ async function createVirtual(options) {
|
|
|
56
60
|
effect: options.effect || "none",
|
|
57
61
|
git: options.git ?? false,
|
|
58
62
|
packageManager: options.packageManager || "bun",
|
|
63
|
+
workspaceShape: options.workspaceShape || "monorepo",
|
|
59
64
|
versionChannel: options.versionChannel || "stable",
|
|
60
65
|
install: false,
|
|
61
66
|
dbSetup: options.dbSetup || "none",
|
|
@@ -139,6 +144,7 @@ async function createVirtual(options) {
|
|
|
139
144
|
goConfig: options.goConfig || "none",
|
|
140
145
|
goObservability: options.goObservability || "none",
|
|
141
146
|
javaWebFramework: options.javaWebFramework || (options.ecosystem === "java" ? "spring-boot" : "none"),
|
|
147
|
+
javaLanguage: options.javaLanguage || "java",
|
|
142
148
|
javaBuildTool: options.javaBuildTool || (options.ecosystem === "java" ? "maven" : "none"),
|
|
143
149
|
javaOrm: options.javaOrm || "none",
|
|
144
150
|
javaAuth: options.javaAuth || "none",
|
|
@@ -176,6 +182,7 @@ async function createVirtual(options) {
|
|
|
176
182
|
aiDocs: options.aiDocs || ["claude-md", "agents-md"]
|
|
177
183
|
};
|
|
178
184
|
if (options.stackParts) config.stackParts = options.stackParts;
|
|
185
|
+
applyEffectBackendDefaults(config, new Set(Object.keys(options)));
|
|
179
186
|
const { generateVirtualProject: generate, EMBEDDED_TEMPLATES: EMBEDDED_TEMPLATES$2 } = await import("@better-fullstack/template-generator");
|
|
180
187
|
const result = await generate({
|
|
181
188
|
config,
|
|
@@ -198,4 +205,4 @@ async function createVirtual(options) {
|
|
|
198
205
|
}
|
|
199
206
|
|
|
200
207
|
//#endregion
|
|
201
|
-
export { EMBEDDED_TEMPLATES, TEMPLATE_COUNT, VirtualFileSystem, add, builder, check, create, createBtsCli, createVirtual, docs, doctor, generateVirtualProject, history, router, sponsors, telemetry };
|
|
208
|
+
export { EMBEDDED_TEMPLATES, TEMPLATE_COUNT, VirtualFileSystem, add, builder, check, create, createBtsCli, createVirtual, docs, doctor, generateVirtualProject, history, router, sponsors, telemetry, update };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { c as DEFAULT_CONFIG, y as types_exports } from "./bts-config-BceXPcpI.mjs";
|
|
3
|
+
import { d as setLastPromptShownUI, r as exitCancelled, s as isFirstPrompt, u as setIsFirstPrompt$1 } from "./errors-ns_o2OKg.mjs";
|
|
4
|
+
import { g as validateAddonCompatibility, o as getCompatibleAddons } from "./file-formatter-XU6ti05V.mjs";
|
|
4
5
|
import { log, spinner } from "@clack/prompts";
|
|
5
6
|
import pc from "picocolors";
|
|
6
7
|
import fs from "fs-extra";
|
|
@@ -496,6 +497,16 @@ const PRERELEASE_TAG_PRIORITY = [
|
|
|
496
497
|
];
|
|
497
498
|
const REGISTRY_FETCH_TIMEOUT_MS = 1e4;
|
|
498
499
|
const REGISTRY_CONCURRENCY = 10;
|
|
500
|
+
const SYNCHRONIZED_VERSION_FAMILIES = [{
|
|
501
|
+
name: "oRPC",
|
|
502
|
+
packages: [
|
|
503
|
+
"@orpc/server",
|
|
504
|
+
"@orpc/client",
|
|
505
|
+
"@orpc/openapi",
|
|
506
|
+
"@orpc/zod",
|
|
507
|
+
"@orpc/tanstack-query"
|
|
508
|
+
]
|
|
509
|
+
}];
|
|
499
510
|
function mapWithConcurrency(items, fn, concurrency) {
|
|
500
511
|
const results = Array.from({ length: items.length });
|
|
501
512
|
let index = 0;
|
|
@@ -507,6 +518,19 @@ function mapWithConcurrency(items, fn, concurrency) {
|
|
|
507
518
|
}
|
|
508
519
|
return Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker())).then(() => results);
|
|
509
520
|
}
|
|
521
|
+
function getVersionSections(packageJson) {
|
|
522
|
+
const sections = [];
|
|
523
|
+
for (const sectionName of ["dependencies", "devDependencies"]) {
|
|
524
|
+
const section = packageJson[sectionName];
|
|
525
|
+
if (section && typeof section === "object" && !Array.isArray(section)) sections.push(section);
|
|
526
|
+
}
|
|
527
|
+
const workspaces = packageJson.workspaces;
|
|
528
|
+
if (workspaces && typeof workspaces === "object" && !Array.isArray(workspaces)) {
|
|
529
|
+
const catalog = workspaces.catalog;
|
|
530
|
+
if (catalog && typeof catalog === "object" && !Array.isArray(catalog)) sections.push(catalog);
|
|
531
|
+
}
|
|
532
|
+
return sections;
|
|
533
|
+
}
|
|
510
534
|
function parseVersion(value) {
|
|
511
535
|
const normalized = value.replace(/^[^\d]*/, "");
|
|
512
536
|
const dashIdx = normalized.indexOf("-");
|
|
@@ -580,10 +604,40 @@ function selectRegistryVersionForChannel(packageInfo, channel) {
|
|
|
580
604
|
if (prereleases.length > 0) return prereleases.sort((left, right) => compareVersions(right, left))[0] ?? null;
|
|
581
605
|
return tags.latest ?? null;
|
|
582
606
|
}
|
|
583
|
-
|
|
584
|
-
const
|
|
585
|
-
if (
|
|
586
|
-
|
|
607
|
+
function getVersionsForChannel(packageInfo, channel) {
|
|
608
|
+
const versions = Object.keys(packageInfo.versions ?? {});
|
|
609
|
+
if (channel === "latest") return versions.filter((version) => !isPrerelease(version));
|
|
610
|
+
const prereleases = versions.filter(isPrerelease);
|
|
611
|
+
return prereleases.length > 0 ? prereleases : versions.filter((version) => !isPrerelease(version));
|
|
612
|
+
}
|
|
613
|
+
function resolveSharedFamilyVersion(packageInfos, channel) {
|
|
614
|
+
if (packageInfos.length === 0) return null;
|
|
615
|
+
const firstInfo = packageInfos[0];
|
|
616
|
+
if (!firstInfo) return null;
|
|
617
|
+
let commonVersions = new Set(getVersionsForChannel(firstInfo, channel));
|
|
618
|
+
const remainingInfos = packageInfos.slice(1);
|
|
619
|
+
for (const packageInfo of remainingInfos) {
|
|
620
|
+
const availableVersions = new Set(getVersionsForChannel(packageInfo, channel));
|
|
621
|
+
commonVersions = new Set([...commonVersions].filter((version) => availableVersions.has(version)));
|
|
622
|
+
}
|
|
623
|
+
return [...commonVersions].sort((left, right) => compareVersions(right, left))[0] ?? null;
|
|
624
|
+
}
|
|
625
|
+
function applySynchronizedFamilyVersions(resolvedVersions, packageInfos, channel) {
|
|
626
|
+
for (const family of SYNCHRONIZED_VERSION_FAMILIES) {
|
|
627
|
+
const selectedPackages = family.packages.filter((packageName) => resolvedVersions.has(packageName));
|
|
628
|
+
if (selectedPackages.length < 2) continue;
|
|
629
|
+
const selectedPackageInfos = selectedPackages.flatMap((packageName) => {
|
|
630
|
+
const packageInfo = packageInfos.get(packageName);
|
|
631
|
+
return packageInfo ? [packageInfo] : [];
|
|
632
|
+
});
|
|
633
|
+
if (selectedPackageInfos.length !== selectedPackages.length) continue;
|
|
634
|
+
const sharedVersion = resolveSharedFamilyVersion(selectedPackageInfos, channel);
|
|
635
|
+
if (!sharedVersion) {
|
|
636
|
+
log.warn(`Failed to resolve shared ${channel} version for ${family.name} packages`);
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
for (const packageName of selectedPackages) resolvedVersions.set(packageName, sharedVersion);
|
|
640
|
+
}
|
|
587
641
|
}
|
|
588
642
|
async function collectPackageJsonPaths(projectDir) {
|
|
589
643
|
const results = [];
|
|
@@ -609,35 +663,35 @@ async function applyDependencyVersionChannel(projectDir, channel) {
|
|
|
609
663
|
const packageNames = /* @__PURE__ */ new Set();
|
|
610
664
|
for (const packageJsonPath of packageJsonPaths) {
|
|
611
665
|
const packageJson = await fs.readJson(packageJsonPath);
|
|
612
|
-
for (const [depName, depVersion] of Object.entries(
|
|
613
|
-
for (const [depName, depVersion] of Object.entries(packageJson.devDependencies ?? {})) if (typeof depVersion === "string" && isRegistrySemverSpec(depVersion)) packageNames.add(depName);
|
|
666
|
+
for (const section of getVersionSections(packageJson)) for (const [depName, depVersion] of Object.entries(section)) if (typeof depVersion === "string" && isRegistrySemverSpec(depVersion)) packageNames.add(depName);
|
|
614
667
|
}
|
|
615
668
|
if (packageNames.size === 0) return;
|
|
616
669
|
const resolvedVersions = /* @__PURE__ */ new Map();
|
|
670
|
+
const packageInfos = /* @__PURE__ */ new Map();
|
|
617
671
|
await mapWithConcurrency([...packageNames], async (packageName) => {
|
|
618
672
|
try {
|
|
619
|
-
const
|
|
673
|
+
const packageInfo = await fetchPackageInfo(packageName);
|
|
674
|
+
const resolvedVersion = selectRegistryVersionForChannel(packageInfo, channel);
|
|
675
|
+
if (!resolvedVersion) throw new Error(`No ${channel} version available for ${packageName}`);
|
|
676
|
+
packageInfos.set(packageName, packageInfo);
|
|
620
677
|
resolvedVersions.set(packageName, resolvedVersion);
|
|
621
678
|
} catch (error) {
|
|
622
679
|
log.warn(`Failed to resolve ${channel} version for ${packageName}: ${error instanceof Error ? error.message : String(error)}`);
|
|
623
680
|
}
|
|
624
681
|
}, REGISTRY_CONCURRENCY);
|
|
625
682
|
if (resolvedVersions.size === 0) return;
|
|
683
|
+
applySynchronizedFamilyVersions(resolvedVersions, packageInfos, channel);
|
|
626
684
|
for (const packageJsonPath of packageJsonPaths) {
|
|
627
685
|
const packageJson = await fs.readJson(packageJsonPath);
|
|
628
686
|
let changed = false;
|
|
629
|
-
for (const
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
if (nextVersion !== currentVersion) {
|
|
638
|
-
section[packageName] = nextVersion;
|
|
639
|
-
changed = true;
|
|
640
|
-
}
|
|
687
|
+
for (const section of getVersionSections(packageJson)) for (const [packageName, currentVersion] of Object.entries(section)) {
|
|
688
|
+
if (!isRegistrySemverSpec(currentVersion)) continue;
|
|
689
|
+
const resolvedVersion = resolvedVersions.get(packageName);
|
|
690
|
+
if (!resolvedVersion) continue;
|
|
691
|
+
const nextVersion = applyVersionPrefix(currentVersion, resolvedVersion);
|
|
692
|
+
if (nextVersion !== currentVersion) {
|
|
693
|
+
section[packageName] = nextVersion;
|
|
694
|
+
changed = true;
|
|
641
695
|
}
|
|
642
696
|
}
|
|
643
697
|
if (changed) await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "./bts-config-BceXPcpI.mjs";
|
|
3
|
+
import "./errors-ns_o2OKg.mjs";
|
|
4
|
+
import "./file-formatter-XU6ti05V.mjs";
|
|
5
|
+
import "./config-processing-B_1wTe3g.mjs";
|
|
6
|
+
import { a as startMcpServer, i as recommendStackFromBrief, n as MCP_STACK_UPDATE_SCHEMA, r as getMcpGraphPreview, t as MCP_PLAN_CREATE_SCHEMA } from "./mcp-entry.mjs";
|
|
7
|
+
|
|
8
|
+
export { recommendStackFromBrief, startMcpServer };
|
package/dist/mcp-entry.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
2
|
+
import { f as getEffectiveStack, g as getGraphSummary, n as previewBtsConfigUpdate, o as writeBtsConfig, r as readBtsConfig, s as getLatestCLIVersion, t as buildBtsConfigForPersistence, u as getDefaultConfig, y as types_exports } from "./bts-config-BceXPcpI.mjs";
|
|
3
|
+
import "./errors-ns_o2OKg.mjs";
|
|
4
|
+
import { r as validateConfigForProgrammaticUse, t as formatCode, v as CreateCommandOptionsSchema } from "./file-formatter-XU6ti05V.mjs";
|
|
5
|
+
import { a as getTemplateConfig, o as getTemplateDescription, s as generateReproducibleCommand, t as applyEffectBackendDefaults } from "./config-processing-B_1wTe3g.mjs";
|
|
5
6
|
import z from "zod";
|
|
6
7
|
import fs from "fs-extra";
|
|
7
8
|
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";
|
|
9
|
+
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
10
|
import { tmpdir } from "node:os";
|
|
10
11
|
import { EMBEDDED_TEMPLATES, generateVirtualProject } from "@better-fullstack/template-generator";
|
|
11
12
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -43,7 +44,8 @@ const NON_STACK_UPDATE_CREATE_KEYS = new Set([
|
|
|
43
44
|
"directoryConflict",
|
|
44
45
|
"renderTitle",
|
|
45
46
|
"disableAnalytics",
|
|
46
|
-
"manualDb"
|
|
47
|
+
"manualDb",
|
|
48
|
+
"workspaceShape"
|
|
47
49
|
]);
|
|
48
50
|
const SUPPORTED_STACK_UPDATE_KEYS = Object.keys(CreateCommandOptionsSchema.shape).filter((key) => !NON_STACK_UPDATE_CREATE_KEYS.has(key)).sort();
|
|
49
51
|
const SUPPORTED_STACK_UPDATE_KEY_SET = new Set(SUPPORTED_STACK_UPDATE_KEYS);
|
|
@@ -51,8 +53,17 @@ const IGNORED_REQUEST_KEYS = new Set([
|
|
|
51
53
|
"projectDir",
|
|
52
54
|
"projectName",
|
|
53
55
|
"install",
|
|
54
|
-
"git"
|
|
56
|
+
"git",
|
|
57
|
+
"acknowledgeArchitectureChange"
|
|
55
58
|
]);
|
|
59
|
+
const RISKY_ARCHITECTURE_KEYS = [
|
|
60
|
+
"database",
|
|
61
|
+
"orm",
|
|
62
|
+
"auth",
|
|
63
|
+
"api",
|
|
64
|
+
"backend",
|
|
65
|
+
"runtime"
|
|
66
|
+
];
|
|
56
67
|
const PACKAGE_JSON_SECTIONS = [
|
|
57
68
|
"dependencies",
|
|
58
69
|
"devDependencies",
|
|
@@ -193,6 +204,18 @@ function getUpdatedSpecForChangedPart(part, parts, proposedConfig, changedKeys)
|
|
|
193
204
|
}, parts);
|
|
194
205
|
}
|
|
195
206
|
}
|
|
207
|
+
function pruneScopedSpecsWithoutOwners(specs) {
|
|
208
|
+
const primaryRoles = /* @__PURE__ */ new Set();
|
|
209
|
+
for (const spec of specs) {
|
|
210
|
+
const rolePath = spec.split(":")[0];
|
|
211
|
+
if (rolePath && !rolePath.includes(".")) primaryRoles.add(rolePath);
|
|
212
|
+
}
|
|
213
|
+
return specs.filter((spec) => {
|
|
214
|
+
const rolePath = spec.split(":")[0];
|
|
215
|
+
const ownerRole = rolePath?.split(".")[0];
|
|
216
|
+
return !rolePath?.includes(".") || ownerRole !== void 0 && primaryRoles.has(ownerRole);
|
|
217
|
+
});
|
|
218
|
+
}
|
|
196
219
|
function mergeDerivedStackPartsWithExistingGraph(currentConfig, proposedConfig) {
|
|
197
220
|
const currentStackParts = currentConfig.stackParts?.length ? currentConfig.stackParts : (0, types_exports.legacyProjectConfigToStackParts)(currentConfig);
|
|
198
221
|
const derivedStackParts = (0, types_exports.legacyProjectConfigToStackParts)(proposedConfig);
|
|
@@ -210,21 +233,82 @@ function mergeDerivedStackPartsWithExistingGraph(currentConfig, proposedConfig)
|
|
|
210
233
|
return spec ? [spec] : [];
|
|
211
234
|
});
|
|
212
235
|
const preservedSpecs = new Set(preservedParts.map((part) => (0, types_exports.formatStackPartSpec)(part, currentStackParts)));
|
|
213
|
-
const
|
|
236
|
+
const coveredSpecs = pruneScopedSpecsWithoutOwners([...new Set([...preservedSpecs, ...updatedSpecs])]);
|
|
237
|
+
const coveredParts = (0, types_exports.parseStackPartSpecs)(coveredSpecs, "selected");
|
|
214
238
|
const preservedProjectedKeys = /* @__PURE__ */ new Set();
|
|
215
239
|
for (const part of coveredParts) for (const key of getProjectedConfigKeys(part, coveredParts)) preservedProjectedKeys.add(key);
|
|
216
|
-
const nextSpecs = [...
|
|
240
|
+
const nextSpecs = [...coveredSpecs];
|
|
217
241
|
for (const part of derivedStackParts) {
|
|
218
242
|
if (part.source === "provided") continue;
|
|
219
243
|
const spec = (0, types_exports.formatStackPartSpec)(part, derivedStackParts);
|
|
220
244
|
if (nextSpecs.includes(spec)) continue;
|
|
221
245
|
if (![...getProjectedConfigKeys(part, derivedStackParts)].some((key) => preservedProjectedKeys.has(key))) nextSpecs.push(spec);
|
|
222
246
|
}
|
|
223
|
-
return (0, types_exports.parseStackPartSpecs)([...new Set(nextSpecs)], "selected");
|
|
247
|
+
return (0, types_exports.parseStackPartSpecs)(pruneScopedSpecsWithoutOwners([...new Set(nextSpecs)]), "selected");
|
|
224
248
|
}
|
|
225
249
|
function asString(value, fallback = "none") {
|
|
226
250
|
return typeof value === "string" ? value : fallback;
|
|
227
251
|
}
|
|
252
|
+
function computeArchitectureChanges(currentConfig, proposedConfig) {
|
|
253
|
+
const changes = [];
|
|
254
|
+
for (const key of RISKY_ARCHITECTURE_KEYS) {
|
|
255
|
+
const from = asString(currentConfig[key]);
|
|
256
|
+
const to = asString(proposedConfig[key]);
|
|
257
|
+
if (from !== "none" && from !== to) changes.push({
|
|
258
|
+
key,
|
|
259
|
+
from,
|
|
260
|
+
to
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
return changes;
|
|
264
|
+
}
|
|
265
|
+
function buildMigrationSteps(changes) {
|
|
266
|
+
const steps = [];
|
|
267
|
+
for (const { key, from, to } of changes) {
|
|
268
|
+
const label = `${key} (${from} -> ${to})`;
|
|
269
|
+
switch (key) {
|
|
270
|
+
case "database":
|
|
271
|
+
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).`);
|
|
272
|
+
break;
|
|
273
|
+
case "orm":
|
|
274
|
+
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.`);
|
|
275
|
+
break;
|
|
276
|
+
case "auth":
|
|
277
|
+
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}.`);
|
|
278
|
+
break;
|
|
279
|
+
case "api":
|
|
280
|
+
steps.push(`${label}: Port server routers/handlers from ${from} to ${to}.`, `${label}: Update client call sites and generated types to the ${to} client.`);
|
|
281
|
+
break;
|
|
282
|
+
case "backend":
|
|
283
|
+
steps.push(`${label}: Port the server entrypoint, routes, and middleware from ${from} to ${to}.`, `${label}: Reconcile runtime and deploy configuration for the ${to} server.`);
|
|
284
|
+
break;
|
|
285
|
+
case "runtime":
|
|
286
|
+
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}.`);
|
|
287
|
+
break;
|
|
288
|
+
default: steps.push(`${label}: Review and migrate affected code manually.`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return steps;
|
|
292
|
+
}
|
|
293
|
+
async function writeMigrationChecklist(projectDir, plan) {
|
|
294
|
+
if (plan.architectureChanges.length === 0 || plan.migrationSteps.length === 0) return;
|
|
295
|
+
const migrationPath = path.join(projectDir, "MIGRATION.md");
|
|
296
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
297
|
+
const swaps = plan.architectureChanges.map((change) => `\`${change.key}\`: \`${change.from}\` -> \`${change.to}\``).join(", ");
|
|
298
|
+
const section = [
|
|
299
|
+
`## Architecture change - ${timestamp}`,
|
|
300
|
+
"",
|
|
301
|
+
`Swapped: ${swaps}`,
|
|
302
|
+
"",
|
|
303
|
+
"Data and schema are NOT migrated automatically. Complete these steps manually:",
|
|
304
|
+
"",
|
|
305
|
+
...plan.migrationSteps.map((step) => `- [ ] ${step}`)
|
|
306
|
+
].join("\n");
|
|
307
|
+
if (await fs.pathExists(migrationPath)) {
|
|
308
|
+
const existing = (await fs.readFile(migrationPath, "utf-8")).trimEnd();
|
|
309
|
+
await fs.writeFile(migrationPath, `${existing}\n\n${section}\n`, "utf-8");
|
|
310
|
+
} else await fs.writeFile(migrationPath, `# Migration checklist\n\n${section}\n`, "utf-8");
|
|
311
|
+
}
|
|
228
312
|
function asStringArray(value) {
|
|
229
313
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
230
314
|
}
|
|
@@ -304,6 +388,13 @@ function getDefaultNativeFrontendForRequestedUpdate(requestedChanges) {
|
|
|
304
388
|
if (requestedChanges.mobileUI === "uniwind" || requestedChanges.mobileUI === "unistyles") return;
|
|
305
389
|
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
390
|
}
|
|
391
|
+
function hasSelectedTypeScriptBackendPart(config) {
|
|
392
|
+
return config.stackParts?.some((part) => part.source !== "provided" && !part.ownerPartId && part.role === "backend" && part.ecosystem === "typescript" && part.toolId !== "none") ?? false;
|
|
393
|
+
}
|
|
394
|
+
function getCompatibilityEcosystem(config) {
|
|
395
|
+
if (config.ecosystem === "react-native" && hasSelectedTypeScriptBackendPart(config)) return "typescript";
|
|
396
|
+
return config.ecosystem;
|
|
397
|
+
}
|
|
307
398
|
function buildCompatibilityInputFromConfig(config) {
|
|
308
399
|
const frontend = asStringArray(config.frontend);
|
|
309
400
|
const addons = asStringArray(config.addons);
|
|
@@ -319,7 +410,7 @@ function buildCompatibilityInputFromConfig(config) {
|
|
|
319
410
|
else if (addon !== "none") appPlatforms.push(addon);
|
|
320
411
|
}
|
|
321
412
|
return {
|
|
322
|
-
ecosystem: config
|
|
413
|
+
ecosystem: getCompatibilityEcosystem(config),
|
|
323
414
|
projectName: config.projectName ?? null,
|
|
324
415
|
webFrontend,
|
|
325
416
|
nativeFrontend,
|
|
@@ -372,6 +463,7 @@ function buildCompatibilityInputFromConfig(config) {
|
|
|
372
463
|
documentation,
|
|
373
464
|
appPlatforms,
|
|
374
465
|
packageManager: asString(config.packageManager, "bun"),
|
|
466
|
+
workspaceShape: asString(config.workspaceShape, "monorepo"),
|
|
375
467
|
versionChannel: asString(config.versionChannel, "stable"),
|
|
376
468
|
examples: asStringArray(config.examples),
|
|
377
469
|
aiSdk: asString(config.ai),
|
|
@@ -422,6 +514,7 @@ function buildCompatibilityInputFromConfig(config) {
|
|
|
422
514
|
goCaching: asString(config.goCaching),
|
|
423
515
|
goConfig: asString(config.goConfig),
|
|
424
516
|
goObservability: asString(config.goObservability),
|
|
517
|
+
javaLanguage: asString(config.javaLanguage, "java"),
|
|
425
518
|
javaWebFramework: asString(config.javaWebFramework),
|
|
426
519
|
javaBuildTool: asString(config.javaBuildTool),
|
|
427
520
|
javaOrm: asString(config.javaOrm),
|
|
@@ -599,6 +692,16 @@ function applyKnownDependencyExpansions(config, requestedChanges) {
|
|
|
599
692
|
adjustments.push("examples: AI SDK set to 'vercel-ai' (Chat SDK profile requires Vercel AI)");
|
|
600
693
|
}
|
|
601
694
|
}
|
|
695
|
+
if (next.backend === "effect") {
|
|
696
|
+
if (next.effect !== "effect-full" && !requestedKeys.has("effect")) {
|
|
697
|
+
next.effect = "effect-full";
|
|
698
|
+
adjustments.push("effect: Effect services set to 'effect-full' (Effect backend requires Effect Platform + SQL)");
|
|
699
|
+
}
|
|
700
|
+
if (next.validation !== "effect-schema" && !requestedKeys.has("validation")) {
|
|
701
|
+
next.validation = "effect-schema";
|
|
702
|
+
adjustments.push("validation: Validation set to 'effect-schema' (Effect backend requires Effect Schema)");
|
|
703
|
+
}
|
|
704
|
+
}
|
|
602
705
|
return {
|
|
603
706
|
config: next,
|
|
604
707
|
adjustments
|
|
@@ -1037,6 +1140,8 @@ async function planStackUpdate(projectDirInput, input) {
|
|
|
1037
1140
|
proposedConfig: normalizedProposedConfig
|
|
1038
1141
|
});
|
|
1039
1142
|
const graphPreview = getGraphPreview(persistedProposedConfig);
|
|
1143
|
+
const architectureChanges = computeArchitectureChanges(currentConfig, proposedConfig);
|
|
1144
|
+
const migrationSteps = buildMigrationSteps(architectureChanges);
|
|
1040
1145
|
return {
|
|
1041
1146
|
success: true,
|
|
1042
1147
|
projectDir,
|
|
@@ -1049,6 +1154,9 @@ async function planStackUpdate(projectDirInput, input) {
|
|
|
1049
1154
|
scriptChanges,
|
|
1050
1155
|
envChanges,
|
|
1051
1156
|
manualReviewBlockers,
|
|
1157
|
+
architectureChanges,
|
|
1158
|
+
migrationSteps,
|
|
1159
|
+
requiresArchitectureAck: architectureChanges.length > 0,
|
|
1052
1160
|
operations,
|
|
1053
1161
|
installCommand: getInstallCommand$1(normalizedProposedConfig),
|
|
1054
1162
|
compatibilityAdjustments,
|
|
@@ -1063,6 +1171,16 @@ async function applyStackUpdate(projectDirInput, input) {
|
|
|
1063
1171
|
projectDir: plan.projectDir,
|
|
1064
1172
|
error: `Manual review required before applying stack update: ${plan.manualReviewBlockers.join("; ")}`
|
|
1065
1173
|
};
|
|
1174
|
+
const acknowledgeArchitectureChange = input.acknowledgeArchitectureChange === true;
|
|
1175
|
+
if (plan.requiresArchitectureAck && !acknowledgeArchitectureChange) {
|
|
1176
|
+
const swaps = plan.architectureChanges.map((change) => `${change.key}: ${change.from} -> ${change.to}`).join("; ");
|
|
1177
|
+
const checklist = plan.migrationSteps.map((step) => ` - ${step}`).join("\n");
|
|
1178
|
+
return {
|
|
1179
|
+
success: false,
|
|
1180
|
+
projectDir: plan.projectDir,
|
|
1181
|
+
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}`
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1066
1184
|
const projectName = await inferProjectName(plan.projectDir);
|
|
1067
1185
|
const proposedConfig = configFromBtsConfig(plan.proposedConfig, plan.projectDir, projectName);
|
|
1068
1186
|
const proposedTree = await generateTree(proposedConfig);
|
|
@@ -1078,6 +1196,7 @@ async function applyStackUpdate(projectDirInput, input) {
|
|
|
1078
1196
|
version: plan.proposedConfig.version,
|
|
1079
1197
|
createdAt: plan.proposedConfig.createdAt
|
|
1080
1198
|
});
|
|
1199
|
+
await writeMigrationChecklist(plan.projectDir, plan);
|
|
1081
1200
|
return plan;
|
|
1082
1201
|
}
|
|
1083
1202
|
|
|
@@ -1326,6 +1445,7 @@ const MCP_COMPATIBILITY_DEFAULTS = {
|
|
|
1326
1445
|
mobilePush: "none",
|
|
1327
1446
|
mobileOTA: "none",
|
|
1328
1447
|
packageManager: "bun",
|
|
1448
|
+
workspaceShape: "monorepo",
|
|
1329
1449
|
versionChannel: "stable",
|
|
1330
1450
|
examples: [],
|
|
1331
1451
|
aiSdk: "none",
|
|
@@ -1376,6 +1496,7 @@ const MCP_COMPATIBILITY_DEFAULTS = {
|
|
|
1376
1496
|
goCaching: "none",
|
|
1377
1497
|
goConfig: "none",
|
|
1378
1498
|
goObservability: "none",
|
|
1499
|
+
javaLanguage: "java",
|
|
1379
1500
|
javaWebFramework: "spring-boot",
|
|
1380
1501
|
javaBuildTool: "maven",
|
|
1381
1502
|
javaOrm: "none",
|
|
@@ -1465,6 +1586,7 @@ function buildProjectConfig(input, overrides) {
|
|
|
1465
1586
|
const stackParts = parseStackPartSpecs(input.part.filter((part) => typeof part === "string"), "selected");
|
|
1466
1587
|
Object.assign(config, stackPartsToLegacyProjectConfigPartial(stackParts), { stackParts });
|
|
1467
1588
|
}
|
|
1589
|
+
applyEffectBackendDefaults(config, new Set(Object.keys(input)));
|
|
1468
1590
|
return config;
|
|
1469
1591
|
}
|
|
1470
1592
|
function sanitizePath(input) {
|
|
@@ -1487,7 +1609,7 @@ function buildCompatibilityInput(input) {
|
|
|
1487
1609
|
...documentation,
|
|
1488
1610
|
"none"
|
|
1489
1611
|
].includes(a));
|
|
1490
|
-
|
|
1612
|
+
const result = {
|
|
1491
1613
|
ecosystem,
|
|
1492
1614
|
projectName: input.projectName ?? null,
|
|
1493
1615
|
webFrontend,
|
|
@@ -1500,6 +1622,11 @@ function buildCompatibilityInput(input) {
|
|
|
1500
1622
|
appPlatforms,
|
|
1501
1623
|
aiSdk: input.ai ?? defaults.aiSdk
|
|
1502
1624
|
};
|
|
1625
|
+
if (result.backend === "effect") {
|
|
1626
|
+
if (input.effect === void 0) result.backendLibraries = "effect-full";
|
|
1627
|
+
if (input.validation === void 0) result.validation = "effect-schema";
|
|
1628
|
+
}
|
|
1629
|
+
return result;
|
|
1503
1630
|
}
|
|
1504
1631
|
function summarizeTree(tree) {
|
|
1505
1632
|
const paths = [];
|
|
@@ -1534,6 +1661,7 @@ const COMPATIBILITY_RULES_MD = `# Better-Fullstack Compatibility Rules
|
|
|
1534
1661
|
|
|
1535
1662
|
## Backend Constraints
|
|
1536
1663
|
- **Convex**: Forces runtime=none, database=none, orm=none, api=none, dbSetup=none, serverDeploy=none. Removes incompatible frontends (Solid, SolidStart, Astro).
|
|
1664
|
+
- **Effect backend**: Requires effect=effect-full and validation=effect-schema. Other compatible frontend/backend-adjacent tools can still be selected.
|
|
1537
1665
|
- **No backend (none)**: Clears auth, payments, database, orm, api, serverDeploy, search, fileStorage.
|
|
1538
1666
|
- **Fullstack (backend='self')**: Sets runtime=none, serverDeploy=none. Only works with: next, vinext, tanstack-start, astro, nuxt, svelte, solid-start.
|
|
1539
1667
|
|
|
@@ -1734,6 +1862,13 @@ const stackUpdateOutputSchema = {
|
|
|
1734
1862
|
scriptChanges: z.record(z.string(), z.array(z.string())).optional(),
|
|
1735
1863
|
envChanges: z.record(z.string(), z.array(z.string())).optional(),
|
|
1736
1864
|
manualReviewBlockers: z.array(z.string()).optional(),
|
|
1865
|
+
architectureChanges: z.array(z.object({
|
|
1866
|
+
key: z.string(),
|
|
1867
|
+
from: z.string(),
|
|
1868
|
+
to: z.string()
|
|
1869
|
+
})).optional(),
|
|
1870
|
+
migrationSteps: z.array(z.string()).optional(),
|
|
1871
|
+
requiresArchitectureAck: z.boolean().optional(),
|
|
1737
1872
|
compatibilityAdjustments: z.array(z.string()).optional(),
|
|
1738
1873
|
compatibilityWarnings: z.array(z.string()).optional(),
|
|
1739
1874
|
installCommand: z.string().optional(),
|
|
@@ -1989,6 +2124,7 @@ const crossEcosystemInputSchema = {
|
|
|
1989
2124
|
goCaching: GoCachingSchema.optional().describe("Go caching library"),
|
|
1990
2125
|
goConfig: GoConfigSchema.optional().describe("Go config management"),
|
|
1991
2126
|
goObservability: GoObservabilitySchema.optional().describe("Go observability"),
|
|
2127
|
+
javaLanguage: JavaLanguageSchema.optional().describe("JVM language (java, kotlin)"),
|
|
1992
2128
|
javaWebFramework: JavaWebFrameworkSchema.optional().describe("Java web framework"),
|
|
1993
2129
|
javaBuildTool: JavaBuildToolSchema.optional().describe("Java build tool"),
|
|
1994
2130
|
javaOrm: JavaOrmSchema.optional().describe("Java ORM"),
|
|
@@ -2071,7 +2207,7 @@ const MCP_PLAN_CREATE_SCHEMA = {
|
|
|
2071
2207
|
...mobileInputSchema,
|
|
2072
2208
|
fileUpload: FileUploadSchema.optional().describe("File upload"),
|
|
2073
2209
|
...deploymentInputSchema,
|
|
2074
|
-
effect: EffectSchema.optional().describe("Effect
|
|
2210
|
+
effect: EffectSchema.optional().describe("Effect services (effect, effect-full)"),
|
|
2075
2211
|
analytics: AnalyticsSchema.optional().describe("Privacy-focused analytics provider"),
|
|
2076
2212
|
astroIntegration: AstroIntegrationSchema.optional().describe("Astro UI framework integration (react, vue, svelte, solid)"),
|
|
2077
2213
|
aiDocs: z.array(AiDocsSchema).optional().describe("AI documentation files (claude-md, agents-md, cursorrules)"),
|
|
@@ -2080,7 +2216,8 @@ const MCP_PLAN_CREATE_SCHEMA = {
|
|
|
2080
2216
|
};
|
|
2081
2217
|
const MCP_STACK_UPDATE_SCHEMA = {
|
|
2082
2218
|
...MCP_PLAN_CREATE_SCHEMA,
|
|
2083
|
-
projectDir: z.string().describe("Absolute path to the existing Better-Fullstack project")
|
|
2219
|
+
projectDir: z.string().describe("Absolute path to the existing Better-Fullstack project"),
|
|
2220
|
+
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
2221
|
};
|
|
2085
2222
|
async function startMcpServer() {
|
|
2086
2223
|
const server = new McpServer({
|
|
@@ -2393,7 +2530,7 @@ async function startMcpServer() {
|
|
|
2393
2530
|
const graphPreview = getMcpGraphPreview(config);
|
|
2394
2531
|
let addonWarnings = [];
|
|
2395
2532
|
if (config.addons.length > 0 && config.addons[0] !== "none") {
|
|
2396
|
-
const { setupAddons } = await import("./addons-setup-
|
|
2533
|
+
const { setupAddons } = await import("./addons-setup-DEPfsn6z.mjs");
|
|
2397
2534
|
addonWarnings = await setupAddons(config);
|
|
2398
2535
|
}
|
|
2399
2536
|
const installCmd = getInstallCommand(input.ecosystem ?? "typescript", projectName, input.packageManager, input.javaBuildTool, input.javaWebFramework);
|
|
@@ -2455,7 +2592,7 @@ async function startMcpServer() {
|
|
|
2455
2592
|
...safePlan,
|
|
2456
2593
|
...plan.compatibilityAdjustments.length > 0 ? { compatibilityAdjustments: plan.compatibilityAdjustments } : {},
|
|
2457
2594
|
...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}`
|
|
2595
|
+
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
2596
|
};
|
|
2460
2597
|
return {
|
|
2461
2598
|
content: [{
|
|
@@ -2718,4 +2855,4 @@ async function startMcpServer() {
|
|
|
2718
2855
|
startMcpServer();
|
|
2719
2856
|
|
|
2720
2857
|
//#endregion
|
|
2721
|
-
export {
|
|
2858
|
+
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 };
|