create-better-fullstack 2.0.3 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{add-handler-BQN6pwGJ.mjs → add-handler-9F-AsGM-.mjs} +11 -9
- package/dist/addons-setup-C8eaCaH5.mjs +5 -0
- package/dist/{addons-setup-HSghQS7c.mjs → addons-setup-C_xrNtkL.mjs} +3 -83
- package/dist/cli.mjs +2 -2
- package/dist/compatibility-rules-D7zYNVjC.mjs +372 -0
- package/dist/config-validation-C4glouQh.mjs +1512 -0
- package/dist/doctor-BFSSbS-U.mjs +280 -0
- package/dist/{bts-config-Bg1Qea9Y.mjs → errors-D9yiiGVq.mjs} +215 -72
- package/dist/generated-checks-DUvVXWId.mjs +91 -0
- package/dist/index.d.mts +85 -29
- package/dist/index.mjs +11 -6
- package/dist/{install-dependencies-D0Z1dZEx.mjs → install-dependencies-DDGF-zDG.mjs} +102 -397
- package/dist/mcp-BXLhb7wW.mjs +7 -0
- package/dist/mcp-entry.mjs +1802 -296
- package/dist/render-title-zvyKC1ej.mjs +34 -0
- package/dist/run-BmRKR2wG.mjs +11 -0
- package/dist/{run-BhcXBjxh.mjs → run-DRzP53v3.mjs} +534 -1717
- package/package.json +5 -4
- package/dist/addons-setup-CV5uZyhH.mjs +0 -5
- package/dist/mcp-58r70ZcL.mjs +0 -5
- package/dist/run-DCxVLUMS.mjs +0 -7
- /package/dist/{update-deps-CLebIM70.mjs → update-deps-D5OG0KmJ.mjs} +0 -0
|
@@ -1,399 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { A as types_exports, d as setLastPromptShownUI, r as exitCancelled, s as isFirstPrompt, u as setIsFirstPrompt$1, y as DEFAULT_CONFIG } from "./errors-D9yiiGVq.mjs";
|
|
3
|
+
import { m as validateAddonCompatibility, r as getCompatibleAddons } from "./compatibility-rules-D7zYNVjC.mjs";
|
|
4
4
|
import { log, spinner } from "@clack/prompts";
|
|
5
5
|
import pc from "picocolors";
|
|
6
6
|
import fs from "fs-extra";
|
|
7
7
|
import path from "node:path";
|
|
8
|
-
import { allowedApisForFrontends, getAIFrontendCompatibilityIssue, getApiFrontendCompatibilityIssue, getCompatibleAddons, getCompatibleCSSFrameworks, getCompatibleUILibraries, getUnsupportedWebDeployFrontend, hasDockerComposeCompatibleFrontend, hasWebStyling, isBackendUtilsCompatibleBackend, isExampleAIAllowed, isExampleChatSdkAllowed, isFrontendAllowedWithBackend, isWebFrontend, requiresChatSdkVercelAIForSelection, splitFrontends, validateAddonCompatibility } from "@better-fullstack/types";
|
|
9
|
-
import gradient from "gradient-string";
|
|
10
8
|
import consola from "consola";
|
|
11
9
|
import { ConfirmPrompt, GroupMultiSelectPrompt, MultiSelectPrompt, SelectPrompt, isCancel as isCancel$1 } from "@clack/core";
|
|
12
10
|
import { $ } from "execa";
|
|
13
11
|
|
|
14
|
-
//#region src/utils/render-title.ts
|
|
15
|
-
const TITLE_TEXT = `
|
|
16
|
-
██████╗ ███████╗████████╗████████╗███████╗██████╗
|
|
17
|
-
██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗
|
|
18
|
-
██████╔╝█████╗ ██║ ██║ █████╗ ██████╔╝
|
|
19
|
-
██╔══██╗██╔══╝ ██║ ██║ ██╔══╝ ██╔══██╗
|
|
20
|
-
██████╔╝███████╗ ██║ ██║ ███████╗██║ ██║
|
|
21
|
-
╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
|
|
22
|
-
|
|
23
|
-
███████╗██╗ ██╗██╗ ██╗ ███████╗████████╗ █████╗ ██████╗██╗ ██╗
|
|
24
|
-
██╔════╝██║ ██║██║ ██║ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
|
|
25
|
-
█████╗ ██║ ██║██║ ██║ ███████╗ ██║ ███████║██║ █████╔╝
|
|
26
|
-
██╔══╝ ██║ ██║██║ ██║ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗
|
|
27
|
-
██║ ╚██████╔╝███████╗███████╗███████║ ██║ ██║ ██║╚██████╗██║ ██╗
|
|
28
|
-
╚═╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
|
|
29
|
-
`;
|
|
30
|
-
const monochromeTheme = {
|
|
31
|
-
white: "#FFFFFF",
|
|
32
|
-
lightGray: "#E5E5E5",
|
|
33
|
-
gray: "#A3A3A3",
|
|
34
|
-
darkGray: "#737373"
|
|
35
|
-
};
|
|
36
|
-
const renderTitle = () => {
|
|
37
|
-
const terminalWidth = process.stdout.columns || 80;
|
|
38
|
-
const titleLines = TITLE_TEXT.split("\n");
|
|
39
|
-
if (terminalWidth < Math.max(...titleLines.map((line) => line.length))) console.log(gradient(Object.values(monochromeTheme)).multiline(`Better Fullstack`));
|
|
40
|
-
else console.log(gradient(Object.values(monochromeTheme)).multiline(TITLE_TEXT));
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
//#endregion
|
|
44
|
-
//#region src/utils/error-formatter.ts
|
|
45
|
-
function getCategoryTitle(category) {
|
|
46
|
-
return {
|
|
47
|
-
incompatibility: "Incompatible Options",
|
|
48
|
-
"invalid-selection": "Invalid Selection",
|
|
49
|
-
"missing-requirement": "Missing Requirement",
|
|
50
|
-
constraint: "Constraint Violation"
|
|
51
|
-
}[category];
|
|
52
|
-
}
|
|
53
|
-
function displayStructuredError(error) {
|
|
54
|
-
if (isSilent()) throw new CLIError(error.message);
|
|
55
|
-
const lines = [];
|
|
56
|
-
lines.push(pc.bold(pc.red(getCategoryTitle(error.category))));
|
|
57
|
-
lines.push("");
|
|
58
|
-
lines.push(error.message);
|
|
59
|
-
if (error.provided && Object.keys(error.provided).length > 0) {
|
|
60
|
-
lines.push("");
|
|
61
|
-
lines.push(pc.dim("You provided:"));
|
|
62
|
-
for (const [key, value] of Object.entries(error.provided)) {
|
|
63
|
-
const displayValue = Array.isArray(value) ? value.join(", ") : value;
|
|
64
|
-
lines.push(` ${pc.cyan("--" + key)} ${displayValue}`);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
if (error.suggestions.length > 0) {
|
|
68
|
-
lines.push("");
|
|
69
|
-
lines.push(pc.dim("Suggestions:"));
|
|
70
|
-
for (const suggestion of error.suggestions) lines.push(` ${pc.green("•")} ${suggestion}`);
|
|
71
|
-
}
|
|
72
|
-
consola.box({
|
|
73
|
-
title: pc.red("Error"),
|
|
74
|
-
message: lines.join("\n"),
|
|
75
|
-
style: { borderColor: "red" }
|
|
76
|
-
});
|
|
77
|
-
process.exit(1);
|
|
78
|
-
}
|
|
79
|
-
function incompatibilityError(opts) {
|
|
80
|
-
return displayStructuredError({
|
|
81
|
-
category: "incompatibility",
|
|
82
|
-
...opts
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
function invalidSelectionError(opts) {
|
|
86
|
-
return displayStructuredError({
|
|
87
|
-
category: "invalid-selection",
|
|
88
|
-
...opts
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
function missingRequirementError(opts) {
|
|
92
|
-
return displayStructuredError({
|
|
93
|
-
category: "missing-requirement",
|
|
94
|
-
...opts
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
function constraintError(opts) {
|
|
98
|
-
return displayStructuredError({
|
|
99
|
-
category: "constraint",
|
|
100
|
-
...opts
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
//#endregion
|
|
105
|
-
//#region src/utils/compatibility-rules.ts
|
|
106
|
-
function isWebFrontend$1(value) {
|
|
107
|
-
return isWebFrontend(value);
|
|
108
|
-
}
|
|
109
|
-
function splitFrontends$1(values = []) {
|
|
110
|
-
return splitFrontends(values);
|
|
111
|
-
}
|
|
112
|
-
function ensureSingleWebAndNative(frontends) {
|
|
113
|
-
const { web, native } = splitFrontends$1(frontends);
|
|
114
|
-
if (web.length > 1) invalidSelectionError({
|
|
115
|
-
message: "Only one web framework can be selected per project.",
|
|
116
|
-
provided: { frontend: web },
|
|
117
|
-
suggestions: ["Keep one web framework and remove the others", "Use separate projects for multiple web frameworks"]
|
|
118
|
-
});
|
|
119
|
-
if (native.length > 1) invalidSelectionError({
|
|
120
|
-
message: "Only one native framework can be selected per project.",
|
|
121
|
-
provided: { frontend: native },
|
|
122
|
-
suggestions: ["Keep one native framework and remove the others", "Choose: native-bare, native-uniwind, or native-unistyles"]
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
const FULLSTACK_FRONTENDS = [
|
|
126
|
-
"next",
|
|
127
|
-
"vinext",
|
|
128
|
-
"tanstack-start",
|
|
129
|
-
"astro",
|
|
130
|
-
"nuxt",
|
|
131
|
-
"svelte",
|
|
132
|
-
"solid-start"
|
|
133
|
-
];
|
|
134
|
-
function validateSelfBackendCompatibility(providedFlags, options, config) {
|
|
135
|
-
const backend = config.backend || options.backend;
|
|
136
|
-
const frontends = config.frontend || options.frontend || [];
|
|
137
|
-
if (backend === "self") {
|
|
138
|
-
const { web, native } = splitFrontends$1(frontends);
|
|
139
|
-
if (!(web.length === 1 && FULLSTACK_FRONTENDS.includes(web[0]))) exitWithError("Backend 'self' (fullstack) only supports Next.js, Vinext, TanStack Start, Astro, Nuxt, SvelteKit, or SolidStart frontends. Please use --frontend next, --frontend vinext, --frontend tanstack-start, --frontend astro, --frontend nuxt, --frontend svelte, or --frontend solid-start.");
|
|
140
|
-
if (native.length > 1) exitWithError("Cannot select multiple native frameworks. Choose only one of: native-bare, native-uniwind, native-unistyles");
|
|
141
|
-
}
|
|
142
|
-
const hasFullstackFrontend = frontends.some((f) => FULLSTACK_FRONTENDS.includes(f));
|
|
143
|
-
if (providedFlags.has("backend") && !hasFullstackFrontend && backend === "self") exitWithError("Backend 'self' (fullstack) only supports Next.js, Vinext, TanStack Start, Astro, Nuxt, SvelteKit, or SolidStart frontends. Please use --frontend next, --frontend vinext, --frontend tanstack-start, --frontend astro, --frontend nuxt, --frontend svelte, --frontend solid-start, or choose a different backend.");
|
|
144
|
-
}
|
|
145
|
-
const WORKERS_COMPATIBLE_BACKENDS = [
|
|
146
|
-
"hono",
|
|
147
|
-
"nitro",
|
|
148
|
-
"fets"
|
|
149
|
-
];
|
|
150
|
-
function validateWorkersCompatibility(providedFlags, options, config) {
|
|
151
|
-
if (providedFlags.has("runtime") && options.runtime === "workers" && config.backend && !WORKERS_COMPATIBLE_BACKENDS.includes(config.backend)) incompatibilityError({
|
|
152
|
-
message: "In Better-Fullstack, Cloudflare Workers runtime is currently supported only with compatible backends (Hono, Nitro, or Fets).",
|
|
153
|
-
provided: {
|
|
154
|
-
runtime: "workers",
|
|
155
|
-
backend: config.backend
|
|
156
|
-
},
|
|
157
|
-
suggestions: [
|
|
158
|
-
"Use --backend hono",
|
|
159
|
-
"Use --backend nitro",
|
|
160
|
-
"Use --backend fets",
|
|
161
|
-
"Choose a different runtime (node, bun)"
|
|
162
|
-
]
|
|
163
|
-
});
|
|
164
|
-
if (providedFlags.has("backend") && config.backend && !WORKERS_COMPATIBLE_BACKENDS.includes(config.backend) && config.runtime === "workers") incompatibilityError({
|
|
165
|
-
message: `In Better-Fullstack, backend '${config.backend}' is currently not available with Cloudflare Workers runtime.`,
|
|
166
|
-
provided: {
|
|
167
|
-
backend: config.backend,
|
|
168
|
-
runtime: "workers"
|
|
169
|
-
},
|
|
170
|
-
suggestions: ["Use --backend hono, --backend nitro, or --backend fets", "Choose a different runtime (node, bun)"]
|
|
171
|
-
});
|
|
172
|
-
if (providedFlags.has("runtime") && options.runtime === "workers" && config.database === "mongodb") incompatibilityError({
|
|
173
|
-
message: "In Better-Fullstack, Cloudflare Workers runtime is currently not available with MongoDB.",
|
|
174
|
-
provided: {
|
|
175
|
-
runtime: "workers",
|
|
176
|
-
database: "mongodb"
|
|
177
|
-
},
|
|
178
|
-
suggestions: ["Use a different database (postgres, sqlite, mysql)", "Choose a different runtime (node, bun)"]
|
|
179
|
-
});
|
|
180
|
-
if (providedFlags.has("runtime") && options.runtime === "workers" && config.dbSetup === "docker") incompatibilityError({
|
|
181
|
-
message: "In Better-Fullstack, Cloudflare Workers runtime is currently not available with Docker database setup.",
|
|
182
|
-
provided: {
|
|
183
|
-
runtime: "workers",
|
|
184
|
-
"db-setup": "docker"
|
|
185
|
-
},
|
|
186
|
-
suggestions: ["Use --db-setup d1 for SQLite", "Choose a different runtime (node, bun)"]
|
|
187
|
-
});
|
|
188
|
-
if (providedFlags.has("database") && config.database === "mongodb" && config.runtime === "workers") incompatibilityError({
|
|
189
|
-
message: "In Better-Fullstack, MongoDB is currently not available with Cloudflare Workers runtime.",
|
|
190
|
-
provided: {
|
|
191
|
-
database: "mongodb",
|
|
192
|
-
runtime: "workers"
|
|
193
|
-
},
|
|
194
|
-
suggestions: ["Use a different database (postgres, sqlite, mysql)", "Choose a different runtime (node, bun)"]
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
function validateApiFrontendCompatibility(api, frontends = [], astroIntegration) {
|
|
198
|
-
const issue = getApiFrontendCompatibilityIssue(api, frontends, astroIntegration);
|
|
199
|
-
if (!issue) return;
|
|
200
|
-
incompatibilityError({
|
|
201
|
-
message: issue.message,
|
|
202
|
-
provided: issue.provided ?? {},
|
|
203
|
-
suggestions: issue.suggestions ?? []
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
function isFrontendAllowedWithBackend$1(frontend, backend, auth) {
|
|
207
|
-
return isFrontendAllowedWithBackend(frontend, backend, auth);
|
|
208
|
-
}
|
|
209
|
-
function allowedApisForFrontends$1(frontends = [], astroIntegration) {
|
|
210
|
-
return allowedApisForFrontends(frontends, astroIntegration);
|
|
211
|
-
}
|
|
212
|
-
function isExampleAIAllowed$1(backend, frontends = []) {
|
|
213
|
-
return isExampleAIAllowed(backend, frontends);
|
|
214
|
-
}
|
|
215
|
-
function isExampleChatSdkAllowed$1(backend, frontends = [], runtime) {
|
|
216
|
-
return isExampleChatSdkAllowed(backend, frontends, runtime);
|
|
217
|
-
}
|
|
218
|
-
function requiresChatSdkVercelAI(backend, frontends = [], runtime) {
|
|
219
|
-
return requiresChatSdkVercelAIForSelection(backend, frontends, runtime);
|
|
220
|
-
}
|
|
221
|
-
function validateWebDeployRequiresWebFrontend(webDeploy, hasWebFrontendFlag) {
|
|
222
|
-
if (webDeploy && webDeploy !== "none" && !hasWebFrontendFlag) exitWithError("'--web-deploy' requires a web frontend. Please select a web frontend or set '--web-deploy none'.");
|
|
223
|
-
}
|
|
224
|
-
function validateWebDeployFrontendTemplates(webDeploy, frontends = []) {
|
|
225
|
-
const blocked = getUnsupportedWebDeployFrontend(webDeploy, frontends);
|
|
226
|
-
if (blocked) exitWithError(`${webDeploy === "render" ? "Render" : "Netlify"} deployment is not yet wired up for the '${blocked}' frontend. Choose a different web deploy target or frontend.`);
|
|
227
|
-
}
|
|
228
|
-
function validateServerDeployRequiresBackend(serverDeploy, backend, hasGraphBackend = false) {
|
|
229
|
-
if (serverDeploy && serverDeploy !== "none" && !hasGraphBackend && (!backend || backend === "none")) exitWithError("'--server-deploy' requires a backend. Please select a backend or set '--server-deploy none'.");
|
|
230
|
-
}
|
|
231
|
-
function validateAddonCompatibility$1(addon, frontend, _auth, backend, runtime, ecosystem, rustFrontend, javaWebFramework, database) {
|
|
232
|
-
const baseCompatibility = validateAddonCompatibility(addon, frontend, _auth);
|
|
233
|
-
if (!baseCompatibility.isCompatible) return baseCompatibility;
|
|
234
|
-
if (addon === "backend-utils") {
|
|
235
|
-
if (ecosystem !== void 0 && ecosystem !== "typescript") return {
|
|
236
|
-
isCompatible: false,
|
|
237
|
-
reason: "Backend Utils requires a TypeScript server stack"
|
|
238
|
-
};
|
|
239
|
-
if (backend !== void 0 && !isBackendUtilsCompatibleBackend(backend)) return {
|
|
240
|
-
isCompatible: false,
|
|
241
|
-
reason: "Backend Utils requires a Hono, Express, Fastify, Elysia, feTS, or NestJS backend"
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
if (addon === "docker-compose") {
|
|
245
|
-
if (backend === "convex") return {
|
|
246
|
-
isCompatible: false,
|
|
247
|
-
reason: "docker-compose is not compatible with Convex backend (managed service)"
|
|
248
|
-
};
|
|
249
|
-
if (runtime === "workers") return {
|
|
250
|
-
isCompatible: false,
|
|
251
|
-
reason: "docker-compose is not compatible with Cloudflare Workers runtime"
|
|
252
|
-
};
|
|
253
|
-
if (ecosystem === "typescript" && !hasDockerComposeCompatibleFrontend(frontend)) return {
|
|
254
|
-
isCompatible: false,
|
|
255
|
-
reason: "Docker Compose currently supports Next.js, Vinext, TanStack Router, React Router, React Vite, Solid, or Astro"
|
|
256
|
-
};
|
|
257
|
-
if (ecosystem === "typescript" && backend === "self" && !frontend.includes("next") && !frontend.includes("vinext")) return {
|
|
258
|
-
isCompatible: false,
|
|
259
|
-
reason: "Docker Compose self-backend support currently requires Next.js or Vinext"
|
|
260
|
-
};
|
|
261
|
-
if (ecosystem === "rust" && rustFrontend && rustFrontend !== "none") return {
|
|
262
|
-
isCompatible: false,
|
|
263
|
-
reason: "Docker Compose for Rust currently supports server-only projects"
|
|
264
|
-
};
|
|
265
|
-
if (ecosystem === "java" && javaWebFramework && javaWebFramework !== "spring-boot") return {
|
|
266
|
-
isCompatible: false,
|
|
267
|
-
reason: "Docker Compose for Java currently requires Spring Boot"
|
|
268
|
-
};
|
|
269
|
-
if (ecosystem === "python" && database && database !== "none" && database !== "sqlite" && database !== "postgres") return {
|
|
270
|
-
isCompatible: false,
|
|
271
|
-
reason: "Docker Compose for Python ORM projects currently supports SQLite defaults or Postgres"
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
return { isCompatible: true };
|
|
275
|
-
}
|
|
276
|
-
function getCompatibleAddons$1(allAddons, frontend, existingAddons = [], auth, backend, runtime) {
|
|
277
|
-
return getCompatibleAddons(allAddons, frontend, existingAddons, auth).filter((addon) => {
|
|
278
|
-
const { isCompatible } = validateAddonCompatibility$1(addon, frontend, auth, backend, runtime);
|
|
279
|
-
return isCompatible;
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
function validateAddonsAgainstFrontends(addons = [], frontends = [], auth, backend, runtime, ecosystem, rustFrontend, javaWebFramework, database) {
|
|
283
|
-
if (addons.includes("nx") && addons.includes("turborepo")) exitWithError("Nx and Turborepo are alternative workspace runners. Choose one addon.");
|
|
284
|
-
for (const addon of addons) {
|
|
285
|
-
if (addon === "none") continue;
|
|
286
|
-
const { isCompatible, reason } = validateAddonCompatibility$1(addon, frontends, auth, backend, runtime, ecosystem, rustFrontend, javaWebFramework, database);
|
|
287
|
-
if (!isCompatible) exitWithError(`Incompatible addon/frontend combination: ${reason}`);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
function validatePaymentsCompatibility(payments, auth, _backend, frontends = []) {
|
|
291
|
-
if (!payments || payments === "none") return;
|
|
292
|
-
if (payments === "dodo" && frontends.includes("react-vite")) exitWithError("Dodo Payments are not yet supported for React + Vite projects.");
|
|
293
|
-
if (payments === "polar") {
|
|
294
|
-
if (!auth || auth === "none" || auth !== "better-auth" && auth !== "better-auth-organizations") exitWithError("Polar payments requires Better Auth. Please use '--auth better-auth' or choose a different payments provider.");
|
|
295
|
-
const { web } = splitFrontends$1(frontends);
|
|
296
|
-
if (web.length === 0 && frontends.length > 0) exitWithError("Polar payments requires a web frontend or no frontend. Please select a web frontend or choose a different payments provider.");
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
function validateExamplesCompatibility(examples, backend, frontend, runtime, ai) {
|
|
300
|
-
const examplesArr = examples ?? [];
|
|
301
|
-
if (examplesArr.length === 0 || examplesArr.includes("none")) return;
|
|
302
|
-
if (examplesArr.includes("tanstack-showcase")) {
|
|
303
|
-
const showcaseFrontends = ["tanstack-router", "tanstack-start"];
|
|
304
|
-
if (!(frontend ?? []).some((f) => showcaseFrontends.includes(f))) exitWithError("The 'tanstack-showcase' example requires TanStack Router or TanStack Start frontend.");
|
|
305
|
-
}
|
|
306
|
-
if (examplesArr.includes("ai") && (frontend ?? []).includes("solid")) exitWithError("The 'ai' example is not compatible with the Solid frontend.");
|
|
307
|
-
if (examplesArr.includes("ai") && (frontend ?? []).includes("solid-start")) exitWithError("The 'ai' example is not compatible with the SolidStart frontend.");
|
|
308
|
-
if (examplesArr.includes("ai") && backend === "convex") {
|
|
309
|
-
const frontendArr = frontend ?? [];
|
|
310
|
-
const includesNuxt = frontendArr.includes("nuxt");
|
|
311
|
-
const includesSvelte = frontendArr.includes("svelte");
|
|
312
|
-
if (includesNuxt || includesSvelte) exitWithError("The 'ai' example with Convex backend only supports React-based frontends (Next.js, TanStack Router, TanStack Start, React Router, React + Vite). Svelte and Nuxt are not supported with Convex AI.");
|
|
313
|
-
}
|
|
314
|
-
if (examplesArr.includes("chat-sdk")) {
|
|
315
|
-
const frontendArr = frontend ?? [];
|
|
316
|
-
if (frontendArr.includes("react-vite")) exitWithError("The 'chat-sdk' example is not yet supported for React + Vite projects.");
|
|
317
|
-
if (!isExampleChatSdkAllowed$1(backend, frontendArr, runtime)) {
|
|
318
|
-
if (backend === "none") exitWithError("The 'chat-sdk' example requires a backend.");
|
|
319
|
-
if (backend === "convex") exitWithError("The 'chat-sdk' example is not supported with the Convex backend in v1. Use self backend (Next.js, TanStack Start, Nuxt) or Hono with Node runtime.");
|
|
320
|
-
if (backend === "self") exitWithError("The 'chat-sdk' example with self backend only supports Next.js, TanStack Start, or Nuxt frontends in v1.");
|
|
321
|
-
if (backend === "hono" && runtime !== "node") exitWithError("The 'chat-sdk' example with Hono requires '--runtime node' in v1 (Bun/Workers not supported yet).");
|
|
322
|
-
exitWithError("The 'chat-sdk' example is only supported with self backend (Next.js, TanStack Start, Nuxt) or Hono with Node runtime in v1.");
|
|
323
|
-
}
|
|
324
|
-
if (requiresChatSdkVercelAI(backend, frontendArr, runtime) && ai && ai !== "vercel-ai") exitWithError("The 'chat-sdk' example requires '--ai vercel-ai' for the selected stack in v1 (Nuxt Discord and Hono GitHub profiles).");
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
/**
|
|
328
|
-
* Validates that TanStack AI is only used with compatible frontends (React or Solid).
|
|
329
|
-
* Server-side @tanstack/ai core works anywhere, but client adapters only exist for React and Solid.
|
|
330
|
-
*/
|
|
331
|
-
function validateAIFrontendCompatibility(ai, frontends = []) {
|
|
332
|
-
const issue = getAIFrontendCompatibilityIssue(ai, frontends);
|
|
333
|
-
if (!issue) return;
|
|
334
|
-
exitWithError(issue.message);
|
|
335
|
-
}
|
|
336
|
-
/**
|
|
337
|
-
* Validates that a UI library is compatible with the selected frontend(s)
|
|
338
|
-
*/
|
|
339
|
-
function validateUILibraryFrontendCompatibility(uiLibrary, frontends = [], astroIntegration) {
|
|
340
|
-
if (!uiLibrary || uiLibrary === "none") return;
|
|
341
|
-
const { web } = splitFrontends$1(frontends);
|
|
342
|
-
if (web.length === 0) return;
|
|
343
|
-
const compatible = getCompatibleUILibraries(frontends, astroIntegration);
|
|
344
|
-
if (!compatible.includes(uiLibrary)) {
|
|
345
|
-
const isAstroNonReact = web.includes("astro") && astroIntegration !== "react";
|
|
346
|
-
const supportsAstroReact = getCompatibleUILibraries(["astro"], "react").includes(uiLibrary);
|
|
347
|
-
if (isAstroNonReact && supportsAstroReact) {
|
|
348
|
-
incompatibilityError({
|
|
349
|
-
message: `UI library '${uiLibrary}' requires React.`,
|
|
350
|
-
provided: {
|
|
351
|
-
"ui-library": uiLibrary,
|
|
352
|
-
"astro-integration": astroIntegration || "none"
|
|
353
|
-
},
|
|
354
|
-
suggestions: ["Use --astro-integration react", "Choose a different UI library (daisyui, ark-ui)"]
|
|
355
|
-
});
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
incompatibilityError({
|
|
359
|
-
message: `UI library '${uiLibrary}' is not compatible with the selected frontend.`,
|
|
360
|
-
provided: {
|
|
361
|
-
"ui-library": uiLibrary,
|
|
362
|
-
frontend: frontends
|
|
363
|
-
},
|
|
364
|
-
suggestions: [`Supported choices for this stack: ${compatible.join(", ")}`, "Choose a different UI library"]
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
/**
|
|
369
|
-
* Validates that a UI library is compatible with the selected CSS framework
|
|
370
|
-
*/
|
|
371
|
-
function validateUILibraryCSSFrameworkCompatibility(uiLibrary, cssFramework) {
|
|
372
|
-
if (!uiLibrary || uiLibrary === "none") return;
|
|
373
|
-
if (!cssFramework) return;
|
|
374
|
-
const supported = getCompatibleCSSFrameworks(uiLibrary);
|
|
375
|
-
if (!supported.includes(cssFramework)) exitWithError(`UI library '${uiLibrary}' is not compatible with '${cssFramework}' CSS framework. Supported CSS frameworks: ${supported.join(", ")}`);
|
|
376
|
-
}
|
|
377
|
-
/**
|
|
378
|
-
* Gets list of UI libraries compatible with the selected frontend(s)
|
|
379
|
-
*/
|
|
380
|
-
function getCompatibleUILibraries$1(frontends = [], astroIntegration) {
|
|
381
|
-
return getCompatibleUILibraries(frontends, astroIntegration);
|
|
382
|
-
}
|
|
383
|
-
/**
|
|
384
|
-
* Gets list of CSS frameworks compatible with the selected UI library
|
|
385
|
-
*/
|
|
386
|
-
function getCompatibleCSSFrameworks$1(uiLibrary) {
|
|
387
|
-
return getCompatibleCSSFrameworks(uiLibrary);
|
|
388
|
-
}
|
|
389
|
-
/**
|
|
390
|
-
* Checks if a frontend has web styling (excludes native-only frontends)
|
|
391
|
-
*/
|
|
392
|
-
function hasWebStyling$1(frontends = []) {
|
|
393
|
-
return hasWebStyling(frontends);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
//#endregion
|
|
397
12
|
//#region src/utils/navigation.ts
|
|
398
13
|
const GO_BACK_SYMBOL = Symbol("clack:goBack");
|
|
399
14
|
function isGoBack(value) {
|
|
@@ -732,10 +347,18 @@ function getAddonDisplay(addon) {
|
|
|
732
347
|
label = "Backend Utils";
|
|
733
348
|
hint = "asyncHandler, ApiResponse & global error handler for your server";
|
|
734
349
|
break;
|
|
350
|
+
case "devcontainer":
|
|
351
|
+
label = "DevContainer";
|
|
352
|
+
hint = "VS Code container config with stack-aware ports and extensions";
|
|
353
|
+
break;
|
|
735
354
|
case "docker-compose":
|
|
736
355
|
label = "Docker Compose";
|
|
737
356
|
hint = "Containerize your app for deployment";
|
|
738
357
|
break;
|
|
358
|
+
case "github-actions":
|
|
359
|
+
label = "GitHub Actions";
|
|
360
|
+
hint = "Ship a CI workflow (install, lint, type-check, build)";
|
|
361
|
+
break;
|
|
739
362
|
default:
|
|
740
363
|
label = addon;
|
|
741
364
|
hint = `Add ${addon}`;
|
|
@@ -749,6 +372,7 @@ const ADDON_GROUPS = {
|
|
|
749
372
|
Tooling: [
|
|
750
373
|
"turborepo",
|
|
751
374
|
"nx",
|
|
375
|
+
"github-actions",
|
|
752
376
|
"biome",
|
|
753
377
|
"oxlint",
|
|
754
378
|
"ultracite",
|
|
@@ -762,6 +386,7 @@ const ADDON_GROUPS = {
|
|
|
762
386
|
"opentui",
|
|
763
387
|
"wxt",
|
|
764
388
|
"ruler",
|
|
389
|
+
"devcontainer",
|
|
765
390
|
"docker-compose"
|
|
766
391
|
],
|
|
767
392
|
Integrations: [
|
|
@@ -786,10 +411,10 @@ function getAddonGroup(addon) {
|
|
|
786
411
|
return Object.entries(ADDON_GROUPS).find(([, addons]) => addons.includes(addon))?.[0];
|
|
787
412
|
}
|
|
788
413
|
function validateAddonCompatibilityForPrompt(addon, frontends, auth, backend, runtime) {
|
|
789
|
-
return validateAddonCompatibility
|
|
414
|
+
return validateAddonCompatibility(addon, frontends, auth, backend, runtime);
|
|
790
415
|
}
|
|
791
416
|
function getCompatibleAddonsForPrompt(allAddons, frontends, existingAddons = [], auth, backend, runtime) {
|
|
792
|
-
return getCompatibleAddons
|
|
417
|
+
return getCompatibleAddons(allAddons, frontends, existingAddons, auth, backend, runtime);
|
|
793
418
|
}
|
|
794
419
|
async function getAddonsChoice(addons, frontends, auth, backend, runtime) {
|
|
795
420
|
if (addons !== void 0) return addons;
|
|
@@ -1021,6 +646,9 @@ async function applyDependencyVersionChannel(projectDir, channel) {
|
|
|
1021
646
|
|
|
1022
647
|
//#endregion
|
|
1023
648
|
//#region src/helpers/core/install-dependencies.ts
|
|
649
|
+
function toErrorMessage(error) {
|
|
650
|
+
return error instanceof Error ? error.message : String(error);
|
|
651
|
+
}
|
|
1024
652
|
function getInstallEnvironment(packageManager) {
|
|
1025
653
|
if (packageManager === "yarn") return {
|
|
1026
654
|
YARN_ENABLE_HARDENED_MODE: "0",
|
|
@@ -1033,6 +661,7 @@ function getInstallArgs(packageManager) {
|
|
|
1033
661
|
}
|
|
1034
662
|
async function installDependencies({ projectDir, packageManager }) {
|
|
1035
663
|
const s = spinner();
|
|
664
|
+
const step = "Install dependencies";
|
|
1036
665
|
try {
|
|
1037
666
|
s.start(`Running ${packageManager} install...`);
|
|
1038
667
|
const installArgs = getInstallArgs(packageManager);
|
|
@@ -1045,13 +674,24 @@ async function installDependencies({ projectDir, packageManager }) {
|
|
|
1045
674
|
stderr: "inherit"
|
|
1046
675
|
})`${packageManager} ${installArgs}`;
|
|
1047
676
|
s.stop("Dependencies installed successfully");
|
|
677
|
+
return {
|
|
678
|
+
step,
|
|
679
|
+
success: true
|
|
680
|
+
};
|
|
1048
681
|
} catch (error) {
|
|
1049
682
|
s.stop(pc.red("Failed to install dependencies"));
|
|
1050
|
-
|
|
683
|
+
const errorMessage = toErrorMessage(error);
|
|
684
|
+
consola.error(pc.red(`Installation error: ${errorMessage}`));
|
|
685
|
+
return {
|
|
686
|
+
step,
|
|
687
|
+
success: false,
|
|
688
|
+
errorMessage
|
|
689
|
+
};
|
|
1051
690
|
}
|
|
1052
691
|
}
|
|
1053
692
|
async function runCargoBuild({ projectDir }) {
|
|
1054
693
|
const s = spinner();
|
|
694
|
+
const step = "Cargo build";
|
|
1055
695
|
try {
|
|
1056
696
|
s.start("Running cargo build...");
|
|
1057
697
|
await $({
|
|
@@ -1059,13 +699,24 @@ async function runCargoBuild({ projectDir }) {
|
|
|
1059
699
|
stderr: "inherit"
|
|
1060
700
|
})`cargo build`;
|
|
1061
701
|
s.stop("Cargo build completed");
|
|
702
|
+
return {
|
|
703
|
+
step,
|
|
704
|
+
success: true
|
|
705
|
+
};
|
|
1062
706
|
} catch (error) {
|
|
1063
707
|
s.stop(pc.red("Cargo build failed"));
|
|
1064
|
-
|
|
708
|
+
const errorMessage = toErrorMessage(error);
|
|
709
|
+
consola.error(pc.red(`Cargo build error: ${errorMessage}`));
|
|
710
|
+
return {
|
|
711
|
+
step,
|
|
712
|
+
success: false,
|
|
713
|
+
errorMessage
|
|
714
|
+
};
|
|
1065
715
|
}
|
|
1066
716
|
}
|
|
1067
717
|
async function runUvSync({ projectDir }) {
|
|
1068
718
|
const s = spinner();
|
|
719
|
+
const step = "uv sync (Python dependencies)";
|
|
1069
720
|
try {
|
|
1070
721
|
s.start("Running uv sync...");
|
|
1071
722
|
await $({
|
|
@@ -1073,13 +724,24 @@ async function runUvSync({ projectDir }) {
|
|
|
1073
724
|
stderr: "inherit"
|
|
1074
725
|
})`uv sync`;
|
|
1075
726
|
s.stop("Python dependencies installed successfully");
|
|
727
|
+
return {
|
|
728
|
+
step,
|
|
729
|
+
success: true
|
|
730
|
+
};
|
|
1076
731
|
} catch (error) {
|
|
1077
732
|
s.stop(pc.red("uv sync failed"));
|
|
1078
|
-
|
|
733
|
+
const errorMessage = toErrorMessage(error);
|
|
734
|
+
consola.error(pc.red(`uv sync error: ${errorMessage}`));
|
|
735
|
+
return {
|
|
736
|
+
step,
|
|
737
|
+
success: false,
|
|
738
|
+
errorMessage
|
|
739
|
+
};
|
|
1079
740
|
}
|
|
1080
741
|
}
|
|
1081
742
|
async function runGoModTidy({ projectDir }) {
|
|
1082
743
|
const s = spinner();
|
|
744
|
+
const step = "go mod tidy";
|
|
1083
745
|
try {
|
|
1084
746
|
s.start("Running go mod tidy...");
|
|
1085
747
|
await $({
|
|
@@ -1087,14 +749,25 @@ async function runGoModTidy({ projectDir }) {
|
|
|
1087
749
|
stderr: "inherit"
|
|
1088
750
|
})`go mod tidy`;
|
|
1089
751
|
s.stop("Go dependencies installed successfully");
|
|
752
|
+
return {
|
|
753
|
+
step,
|
|
754
|
+
success: true
|
|
755
|
+
};
|
|
1090
756
|
} catch (error) {
|
|
1091
757
|
s.stop(pc.red("go mod tidy failed"));
|
|
1092
|
-
|
|
758
|
+
const errorMessage = toErrorMessage(error);
|
|
759
|
+
consola.error(pc.red(`go mod tidy error: ${errorMessage}`));
|
|
760
|
+
return {
|
|
761
|
+
step,
|
|
762
|
+
success: false,
|
|
763
|
+
errorMessage
|
|
764
|
+
};
|
|
1093
765
|
}
|
|
1094
766
|
}
|
|
1095
767
|
async function runMavenTests({ projectDir }) {
|
|
1096
768
|
const s = spinner();
|
|
1097
769
|
const mvnw = process.platform === "win32" ? "mvnw.cmd" : "./mvnw";
|
|
770
|
+
const step = "Maven tests";
|
|
1098
771
|
try {
|
|
1099
772
|
s.start("Running Maven tests...");
|
|
1100
773
|
await $({
|
|
@@ -1102,14 +775,25 @@ async function runMavenTests({ projectDir }) {
|
|
|
1102
775
|
stderr: "inherit"
|
|
1103
776
|
})`${mvnw} test`;
|
|
1104
777
|
s.stop("Maven tests completed");
|
|
778
|
+
return {
|
|
779
|
+
step,
|
|
780
|
+
success: true
|
|
781
|
+
};
|
|
1105
782
|
} catch (error) {
|
|
1106
783
|
s.stop(pc.red("Maven tests failed"));
|
|
1107
|
-
|
|
784
|
+
const errorMessage = toErrorMessage(error);
|
|
785
|
+
consola.error(pc.red(`Maven test error: ${errorMessage}`));
|
|
786
|
+
return {
|
|
787
|
+
step,
|
|
788
|
+
success: false,
|
|
789
|
+
errorMessage
|
|
790
|
+
};
|
|
1108
791
|
}
|
|
1109
792
|
}
|
|
1110
793
|
async function runGradleTests({ projectDir }) {
|
|
1111
794
|
const s = spinner();
|
|
1112
795
|
const gradlew = process.platform === "win32" ? "gradlew.bat" : "./gradlew";
|
|
796
|
+
const step = "Gradle tests";
|
|
1113
797
|
try {
|
|
1114
798
|
s.start("Running Gradle tests...");
|
|
1115
799
|
await $({
|
|
@@ -1117,13 +801,24 @@ async function runGradleTests({ projectDir }) {
|
|
|
1117
801
|
stderr: "inherit"
|
|
1118
802
|
})`${gradlew} test`;
|
|
1119
803
|
s.stop("Gradle tests completed");
|
|
804
|
+
return {
|
|
805
|
+
step,
|
|
806
|
+
success: true
|
|
807
|
+
};
|
|
1120
808
|
} catch (error) {
|
|
1121
809
|
s.stop(pc.red("Gradle tests failed"));
|
|
1122
|
-
|
|
810
|
+
const errorMessage = toErrorMessage(error);
|
|
811
|
+
consola.error(pc.red(`Gradle test error: ${errorMessage}`));
|
|
812
|
+
return {
|
|
813
|
+
step,
|
|
814
|
+
success: false,
|
|
815
|
+
errorMessage
|
|
816
|
+
};
|
|
1123
817
|
}
|
|
1124
818
|
}
|
|
1125
819
|
async function runMixCompile({ projectDir }) {
|
|
1126
820
|
const s = spinner();
|
|
821
|
+
const step = "mix deps.get / compile";
|
|
1127
822
|
try {
|
|
1128
823
|
s.start("Running mix deps.get and mix compile...");
|
|
1129
824
|
await $({
|
|
@@ -1135,11 +830,21 @@ async function runMixCompile({ projectDir }) {
|
|
|
1135
830
|
stderr: "inherit"
|
|
1136
831
|
})`mix compile`;
|
|
1137
832
|
s.stop("Elixir dependencies installed and project compiled");
|
|
833
|
+
return {
|
|
834
|
+
step,
|
|
835
|
+
success: true
|
|
836
|
+
};
|
|
1138
837
|
} catch (error) {
|
|
1139
838
|
s.stop(pc.red("mix compile failed"));
|
|
1140
|
-
|
|
839
|
+
const errorMessage = toErrorMessage(error);
|
|
840
|
+
consola.error(pc.red(`Mix error: ${errorMessage}`));
|
|
841
|
+
return {
|
|
842
|
+
step,
|
|
843
|
+
success: false,
|
|
844
|
+
errorMessage
|
|
845
|
+
};
|
|
1141
846
|
}
|
|
1142
847
|
}
|
|
1143
848
|
|
|
1144
849
|
//#endregion
|
|
1145
|
-
export {
|
|
850
|
+
export { isGoBack as _, runMavenTests as a, applyDependencyVersionChannel as c, isCancel$1 as d, navigableConfirm as f, GO_BACK_SYMBOL as g, setIsFirstPrompt as h, runGradleTests as i, getAddonsChoice as l, navigableSelect as m, runCargoBuild as n, runMixCompile as o, navigableMultiselect as p, runGoModTidy as r, runUvSync as s, installDependencies as t, getAddonsToAdd as u };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "./errors-D9yiiGVq.mjs";
|
|
3
|
+
import "./config-validation-C4glouQh.mjs";
|
|
4
|
+
import "./compatibility-rules-D7zYNVjC.mjs";
|
|
5
|
+
import { i as startMcpServer, n as MCP_STACK_UPDATE_SCHEMA, r as getMcpGraphPreview, t as MCP_PLAN_CREATE_SCHEMA } from "./mcp-entry.mjs";
|
|
6
|
+
|
|
7
|
+
export { startMcpServer };
|