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