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.
- package/dist/{add-handler-BNSL6HdM.mjs → add-handler-ztNjzoMN.mjs} +52 -6
- package/dist/addons-setup-DnLjAzTw.mjs +7 -0
- package/dist/{addons-setup-CyrP1IV-.mjs → addons-setup-LaAj43NP.mjs} +10 -24
- package/dist/analytics-DVltG11u.mjs +170 -0
- package/dist/{errors-Cyol8zbN.mjs → bts-config-DQVWvPDs.mjs} +72 -149
- package/dist/cli.mjs +2 -2
- package/dist/{templates-CnTOtKjm.mjs → config-processing-D9-F2Us9.mjs} +57 -2
- package/dist/{doctor-DBoq7bZ9.mjs → doctor-a4ca3SMd.mjs} +3 -2
- package/dist/errors-ns_o2OKg.mjs +86 -0
- package/dist/{file-formatter-B3dsev2l.mjs → file-formatter-gvmrpd-g.mjs} +78 -6
- package/dist/gen-CCClL7Ve.mjs +274 -0
- package/dist/{generated-checks-C8hn9w2i.mjs → generated-checks-BV9jol5h.mjs} +1 -1
- package/dist/index.d.mts +162 -64
- package/dist/index.mjs +17 -8
- package/dist/{install-dependencies-CgNh-aOy.mjs → install-dependencies-RoUyaE8o.mjs} +80 -21
- package/dist/mcp-492OkjcS.mjs +11 -0
- package/dist/mcp-entry.mjs +205 -30
- package/dist/prompt-environment-BR0Kkw2W.mjs +26 -0
- package/dist/registry-DSf2CEaU.mjs +408 -0
- package/dist/{run-BYse4yJy.mjs → run-D9I7NdES.mjs} +2081 -464
- package/dist/run-buK4aZS8.mjs +15 -0
- package/dist/scaffold-manifest-DGRyepdb.mjs +4 -0
- package/dist/scaffold-manifest-Dyi0voqE.mjs +135 -0
- package/dist/update-DJ8CI5KW.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-aD-iQw4U.mjs} +0 -0
package/dist/mcp-entry.mjs
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import
|
|
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
|
|
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 = [...
|
|
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
|
|
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
|
-
|
|
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
|
|
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)
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
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-
|
|
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
|
|
2497
|
-
|
|
2498
|
-
|
|
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 {
|
|
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 };
|