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,1139 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { f as types_exports, m as DEFAULT_CONFIG } from "./bts-config-Bg1Qea9Y.mjs";
|
|
3
|
+
import { a as CLIError, c as exitWithError, d as isFirstPrompt, f as isSilent, h as setLastPromptShownUI, m as setIsFirstPrompt$1, s as exitCancelled } from "./addons-setup-HSghQS7c.mjs";
|
|
4
|
+
import { log, spinner } from "@clack/prompts";
|
|
5
|
+
import pc from "picocolors";
|
|
6
|
+
import fs from "fs-extra";
|
|
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
|
+
import consola from "consola";
|
|
11
|
+
import { ConfirmPrompt, GroupMultiSelectPrompt, MultiSelectPrompt, SelectPrompt, isCancel as isCancel$1 } from "@clack/core";
|
|
12
|
+
import { $ } from "execa";
|
|
13
|
+
|
|
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
|
+
for (const addon of addons) {
|
|
284
|
+
if (addon === "none") continue;
|
|
285
|
+
const { isCompatible, reason } = validateAddonCompatibility$1(addon, frontends, auth, backend, runtime, ecosystem, rustFrontend, javaWebFramework, database);
|
|
286
|
+
if (!isCompatible) exitWithError(`Incompatible addon/frontend combination: ${reason}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function validatePaymentsCompatibility(payments, auth, _backend, frontends = []) {
|
|
290
|
+
if (!payments || payments === "none") return;
|
|
291
|
+
if (payments === "dodo" && frontends.includes("react-vite")) exitWithError("Dodo Payments are not yet supported for React + Vite projects.");
|
|
292
|
+
if (payments === "polar") {
|
|
293
|
+
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.");
|
|
294
|
+
const { web } = splitFrontends$1(frontends);
|
|
295
|
+
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.");
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function validateExamplesCompatibility(examples, backend, frontend, runtime, ai) {
|
|
299
|
+
const examplesArr = examples ?? [];
|
|
300
|
+
if (examplesArr.length === 0 || examplesArr.includes("none")) return;
|
|
301
|
+
if (examplesArr.includes("tanstack-showcase")) {
|
|
302
|
+
const showcaseFrontends = ["tanstack-router", "tanstack-start"];
|
|
303
|
+
if (!(frontend ?? []).some((f) => showcaseFrontends.includes(f))) exitWithError("The 'tanstack-showcase' example requires TanStack Router or TanStack Start frontend.");
|
|
304
|
+
}
|
|
305
|
+
if (examplesArr.includes("ai") && (frontend ?? []).includes("solid")) exitWithError("The 'ai' example is not compatible with the Solid frontend.");
|
|
306
|
+
if (examplesArr.includes("ai") && (frontend ?? []).includes("solid-start")) exitWithError("The 'ai' example is not compatible with the SolidStart frontend.");
|
|
307
|
+
if (examplesArr.includes("ai") && backend === "convex") {
|
|
308
|
+
const frontendArr = frontend ?? [];
|
|
309
|
+
const includesNuxt = frontendArr.includes("nuxt");
|
|
310
|
+
const includesSvelte = frontendArr.includes("svelte");
|
|
311
|
+
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.");
|
|
312
|
+
}
|
|
313
|
+
if (examplesArr.includes("chat-sdk")) {
|
|
314
|
+
const frontendArr = frontend ?? [];
|
|
315
|
+
if (frontendArr.includes("react-vite")) exitWithError("The 'chat-sdk' example is not yet supported for React + Vite projects.");
|
|
316
|
+
if (!isExampleChatSdkAllowed$1(backend, frontendArr, runtime)) {
|
|
317
|
+
if (backend === "none") exitWithError("The 'chat-sdk' example requires a backend.");
|
|
318
|
+
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.");
|
|
319
|
+
if (backend === "self") exitWithError("The 'chat-sdk' example with self backend only supports Next.js, TanStack Start, or Nuxt frontends in v1.");
|
|
320
|
+
if (backend === "hono" && runtime !== "node") exitWithError("The 'chat-sdk' example with Hono requires '--runtime node' in v1 (Bun/Workers not supported yet).");
|
|
321
|
+
exitWithError("The 'chat-sdk' example is only supported with self backend (Next.js, TanStack Start, Nuxt) or Hono with Node runtime in v1.");
|
|
322
|
+
}
|
|
323
|
+
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).");
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Validates that TanStack AI is only used with compatible frontends (React or Solid).
|
|
328
|
+
* Server-side @tanstack/ai core works anywhere, but client adapters only exist for React and Solid.
|
|
329
|
+
*/
|
|
330
|
+
function validateAIFrontendCompatibility(ai, frontends = []) {
|
|
331
|
+
const issue = getAIFrontendCompatibilityIssue(ai, frontends);
|
|
332
|
+
if (!issue) return;
|
|
333
|
+
exitWithError(issue.message);
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Validates that a UI library is compatible with the selected frontend(s)
|
|
337
|
+
*/
|
|
338
|
+
function validateUILibraryFrontendCompatibility(uiLibrary, frontends = [], astroIntegration) {
|
|
339
|
+
if (!uiLibrary || uiLibrary === "none") return;
|
|
340
|
+
const { web } = splitFrontends$1(frontends);
|
|
341
|
+
if (web.length === 0) return;
|
|
342
|
+
const compatible = getCompatibleUILibraries(frontends, astroIntegration);
|
|
343
|
+
if (!compatible.includes(uiLibrary)) {
|
|
344
|
+
const isAstroNonReact = web.includes("astro") && astroIntegration !== "react";
|
|
345
|
+
const supportsAstroReact = getCompatibleUILibraries(["astro"], "react").includes(uiLibrary);
|
|
346
|
+
if (isAstroNonReact && supportsAstroReact) {
|
|
347
|
+
incompatibilityError({
|
|
348
|
+
message: `UI library '${uiLibrary}' requires React.`,
|
|
349
|
+
provided: {
|
|
350
|
+
"ui-library": uiLibrary,
|
|
351
|
+
"astro-integration": astroIntegration || "none"
|
|
352
|
+
},
|
|
353
|
+
suggestions: ["Use --astro-integration react", "Choose a different UI library (daisyui, ark-ui)"]
|
|
354
|
+
});
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
incompatibilityError({
|
|
358
|
+
message: `UI library '${uiLibrary}' is not compatible with the selected frontend.`,
|
|
359
|
+
provided: {
|
|
360
|
+
"ui-library": uiLibrary,
|
|
361
|
+
frontend: frontends
|
|
362
|
+
},
|
|
363
|
+
suggestions: [`Supported choices for this stack: ${compatible.join(", ")}`, "Choose a different UI library"]
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Validates that a UI library is compatible with the selected CSS framework
|
|
369
|
+
*/
|
|
370
|
+
function validateUILibraryCSSFrameworkCompatibility(uiLibrary, cssFramework) {
|
|
371
|
+
if (!uiLibrary || uiLibrary === "none") return;
|
|
372
|
+
if (!cssFramework) return;
|
|
373
|
+
const supported = getCompatibleCSSFrameworks(uiLibrary);
|
|
374
|
+
if (!supported.includes(cssFramework)) exitWithError(`UI library '${uiLibrary}' is not compatible with '${cssFramework}' CSS framework. Supported CSS frameworks: ${supported.join(", ")}`);
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Gets list of UI libraries compatible with the selected frontend(s)
|
|
378
|
+
*/
|
|
379
|
+
function getCompatibleUILibraries$1(frontends = [], astroIntegration) {
|
|
380
|
+
return getCompatibleUILibraries(frontends, astroIntegration);
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Gets list of CSS frameworks compatible with the selected UI library
|
|
384
|
+
*/
|
|
385
|
+
function getCompatibleCSSFrameworks$1(uiLibrary) {
|
|
386
|
+
return getCompatibleCSSFrameworks(uiLibrary);
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Checks if a frontend has web styling (excludes native-only frontends)
|
|
390
|
+
*/
|
|
391
|
+
function hasWebStyling$1(frontends = []) {
|
|
392
|
+
return hasWebStyling(frontends);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
//#endregion
|
|
396
|
+
//#region src/utils/navigation.ts
|
|
397
|
+
const GO_BACK_SYMBOL = Symbol("clack:goBack");
|
|
398
|
+
function isGoBack(value) {
|
|
399
|
+
return value === GO_BACK_SYMBOL;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
//#endregion
|
|
403
|
+
//#region src/prompts/navigable.ts
|
|
404
|
+
/**
|
|
405
|
+
* Navigable prompt wrappers using @clack/core
|
|
406
|
+
* These prompts return GO_BACK_SYMBOL when 'b' is pressed (instead of canceling)
|
|
407
|
+
*/
|
|
408
|
+
const unicode = process.platform !== "win32";
|
|
409
|
+
const S_STEP_ACTIVE = unicode ? "◆" : "*";
|
|
410
|
+
const S_STEP_CANCEL = unicode ? "■" : "x";
|
|
411
|
+
const S_STEP_ERROR = unicode ? "▲" : "x";
|
|
412
|
+
const S_STEP_SUBMIT = unicode ? "◇" : "o";
|
|
413
|
+
const S_BAR = unicode ? "│" : "|";
|
|
414
|
+
const S_BAR_END = unicode ? "└" : "—";
|
|
415
|
+
const S_RADIO_ACTIVE = unicode ? "●" : ">";
|
|
416
|
+
const S_RADIO_INACTIVE = unicode ? "○" : " ";
|
|
417
|
+
const S_CHECKBOX_ACTIVE = unicode ? "◻" : "[•]";
|
|
418
|
+
const S_CHECKBOX_SELECTED = unicode ? "◼" : "[+]";
|
|
419
|
+
const S_CHECKBOX_INACTIVE = unicode ? "◻" : "[ ]";
|
|
420
|
+
function symbol(state) {
|
|
421
|
+
switch (state) {
|
|
422
|
+
case "initial":
|
|
423
|
+
case "active": return pc.cyan(S_STEP_ACTIVE);
|
|
424
|
+
case "cancel": return pc.red(S_STEP_CANCEL);
|
|
425
|
+
case "error": return pc.yellow(S_STEP_ERROR);
|
|
426
|
+
case "submit": return pc.green(S_STEP_SUBMIT);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
const KEYBOARD_HINT = pc.dim(`${pc.gray("↑/↓")} navigate • ${pc.gray("enter")} confirm • ${pc.gray("b")} back • ${pc.gray("ctrl+c")} cancel`);
|
|
430
|
+
const KEYBOARD_HINT_FIRST = pc.dim(`${pc.gray("↑/↓")} navigate • ${pc.gray("enter")} confirm • ${pc.gray("ctrl+c")} cancel`);
|
|
431
|
+
const KEYBOARD_HINT_MULTI = pc.dim(`${pc.gray("↑/↓")} navigate • ${pc.gray("space")} select • ${pc.gray("enter")} confirm • ${pc.gray("b")} back • ${pc.gray("ctrl+c")} cancel`);
|
|
432
|
+
const KEYBOARD_HINT_MULTI_FIRST = pc.dim(`${pc.gray("↑/↓")} navigate • ${pc.gray("space")} select • ${pc.gray("enter")} confirm • ${pc.gray("ctrl+c")} cancel`);
|
|
433
|
+
const setIsFirstPrompt = setIsFirstPrompt$1;
|
|
434
|
+
function getHint() {
|
|
435
|
+
return isFirstPrompt() ? KEYBOARD_HINT_FIRST : KEYBOARD_HINT;
|
|
436
|
+
}
|
|
437
|
+
function getMultiHint() {
|
|
438
|
+
return isFirstPrompt() ? KEYBOARD_HINT_MULTI_FIRST : KEYBOARD_HINT_MULTI;
|
|
439
|
+
}
|
|
440
|
+
async function runWithNavigation(prompt) {
|
|
441
|
+
let goBack = false;
|
|
442
|
+
prompt.on("key", (char) => {
|
|
443
|
+
if (char === "b" && !isFirstPrompt()) {
|
|
444
|
+
goBack = true;
|
|
445
|
+
prompt.state = "cancel";
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
setLastPromptShownUI(true);
|
|
449
|
+
const result = await prompt.prompt();
|
|
450
|
+
return goBack ? GO_BACK_SYMBOL : result;
|
|
451
|
+
}
|
|
452
|
+
async function navigableSelect(opts) {
|
|
453
|
+
const opt = (option, state) => {
|
|
454
|
+
const label = option.label ?? String(option.value);
|
|
455
|
+
switch (state) {
|
|
456
|
+
case "disabled": return `${pc.gray(S_RADIO_INACTIVE)} ${pc.gray(label)}${option.hint ? ` ${pc.dim(`(${option.hint ?? "disabled"})`)}` : ""}`;
|
|
457
|
+
case "selected": return `${pc.dim(label)}`;
|
|
458
|
+
case "active": return `${pc.green(S_RADIO_ACTIVE)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
459
|
+
case "cancelled": return `${pc.strikethrough(pc.dim(label))}`;
|
|
460
|
+
default: return `${pc.dim(S_RADIO_INACTIVE)} ${pc.dim(label)}`;
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
return runWithNavigation(new SelectPrompt({
|
|
464
|
+
options: opts.options,
|
|
465
|
+
initialValue: opts.initialValue,
|
|
466
|
+
render() {
|
|
467
|
+
const title = `${pc.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
468
|
+
switch (this.state) {
|
|
469
|
+
case "submit": return `${title}${pc.gray(S_BAR)} ${opt(this.options[this.cursor], "selected")}`;
|
|
470
|
+
case "cancel": return `${title}${pc.gray(S_BAR)} ${opt(this.options[this.cursor], "cancelled")}\n${pc.gray(S_BAR)}`;
|
|
471
|
+
default: {
|
|
472
|
+
const optionsText = this.options.map((option, i) => opt(option, option.disabled ? "disabled" : i === this.cursor ? "active" : "inactive")).join(`\n${pc.cyan(S_BAR)} `);
|
|
473
|
+
const hint = `\n${pc.gray(S_BAR)} ${getHint()}`;
|
|
474
|
+
return `${title}${pc.cyan(S_BAR)} ${optionsText}\n${pc.cyan(S_BAR_END)}${hint}\n`;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}));
|
|
479
|
+
}
|
|
480
|
+
async function navigableMultiselect(opts) {
|
|
481
|
+
const required = opts.required ?? true;
|
|
482
|
+
const opt = (option, state) => {
|
|
483
|
+
const label = option.label ?? String(option.value);
|
|
484
|
+
if (state === "disabled") return `${pc.gray(S_CHECKBOX_INACTIVE)} ${pc.strikethrough(pc.gray(label))}${option.hint ? ` ${pc.dim(`(${option.hint ?? "disabled"})`)}` : ""}`;
|
|
485
|
+
if (state === "active") return `${pc.cyan(S_CHECKBOX_ACTIVE)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
486
|
+
if (state === "selected") return `${pc.green(S_CHECKBOX_SELECTED)} ${pc.dim(label)}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
487
|
+
if (state === "cancelled") return `${pc.strikethrough(pc.dim(label))}`;
|
|
488
|
+
if (state === "active-selected") return `${pc.green(S_CHECKBOX_SELECTED)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
489
|
+
if (state === "submitted") return `${pc.dim(label)}`;
|
|
490
|
+
return `${pc.dim(S_CHECKBOX_INACTIVE)} ${pc.dim(label)}`;
|
|
491
|
+
};
|
|
492
|
+
return runWithNavigation(new MultiSelectPrompt({
|
|
493
|
+
options: opts.options,
|
|
494
|
+
initialValues: opts.initialValues,
|
|
495
|
+
required,
|
|
496
|
+
validate(selected) {
|
|
497
|
+
if (required && (selected === void 0 || selected.length === 0)) return `Please select at least one option.\n${pc.reset(pc.dim(`Press ${pc.gray(pc.bgWhite(pc.inverse(" space ")))} to select, ${pc.gray(pc.bgWhite(pc.inverse(" enter ")))} to submit`))}`;
|
|
498
|
+
},
|
|
499
|
+
render() {
|
|
500
|
+
const title = `${pc.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
501
|
+
const value = this.value ?? [];
|
|
502
|
+
const styleOption = (option, active) => {
|
|
503
|
+
if (option.disabled) return opt(option, "disabled");
|
|
504
|
+
const selected = value.includes(option.value);
|
|
505
|
+
if (active && selected) return opt(option, "active-selected");
|
|
506
|
+
if (selected) return opt(option, "selected");
|
|
507
|
+
return opt(option, active ? "active" : "inactive");
|
|
508
|
+
};
|
|
509
|
+
switch (this.state) {
|
|
510
|
+
case "submit": {
|
|
511
|
+
const submitText = this.options.filter(({ value: optionValue }) => value.includes(optionValue)).map((option) => opt(option, "submitted")).join(pc.dim(", ")) || pc.dim("none");
|
|
512
|
+
return `${title}${pc.gray(S_BAR)} ${submitText}`;
|
|
513
|
+
}
|
|
514
|
+
case "cancel": {
|
|
515
|
+
const label = this.options.filter(({ value: optionValue }) => value.includes(optionValue)).map((option) => opt(option, "cancelled")).join(pc.dim(", "));
|
|
516
|
+
return `${title}${pc.gray(S_BAR)} ${label}\n${pc.gray(S_BAR)}`;
|
|
517
|
+
}
|
|
518
|
+
case "error": {
|
|
519
|
+
const footer = this.error.split("\n").map((ln, i) => i === 0 ? `${pc.yellow(S_BAR_END)} ${pc.yellow(ln)}` : ` ${ln}`).join("\n");
|
|
520
|
+
const optionsText = this.options.map((option, i) => styleOption(option, i === this.cursor)).join(`\n${pc.yellow(S_BAR)} `);
|
|
521
|
+
return `${title}${pc.yellow(S_BAR)} ${optionsText}\n${footer}\n`;
|
|
522
|
+
}
|
|
523
|
+
default: {
|
|
524
|
+
const optionsText = this.options.map((option, i) => styleOption(option, i === this.cursor)).join(`\n${pc.cyan(S_BAR)} `);
|
|
525
|
+
const hint = `\n${pc.gray(S_BAR)} ${getMultiHint()}`;
|
|
526
|
+
return `${title}${pc.cyan(S_BAR)} ${optionsText}\n${pc.cyan(S_BAR_END)}${hint}\n`;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}));
|
|
531
|
+
}
|
|
532
|
+
async function navigableConfirm(opts) {
|
|
533
|
+
const active = opts.active ?? "Yes";
|
|
534
|
+
const inactive = opts.inactive ?? "No";
|
|
535
|
+
return runWithNavigation(new ConfirmPrompt({
|
|
536
|
+
active,
|
|
537
|
+
inactive,
|
|
538
|
+
initialValue: opts.initialValue ?? true,
|
|
539
|
+
render() {
|
|
540
|
+
const title = `${pc.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
541
|
+
const value = this.value ? active : inactive;
|
|
542
|
+
switch (this.state) {
|
|
543
|
+
case "submit": return `${title}${pc.gray(S_BAR)} ${pc.dim(value)}`;
|
|
544
|
+
case "cancel": return `${title}${pc.gray(S_BAR)} ${pc.strikethrough(pc.dim(value))}\n${pc.gray(S_BAR)}`;
|
|
545
|
+
default: {
|
|
546
|
+
const hint = `\n${pc.gray(S_BAR)} ${getHint()}`;
|
|
547
|
+
return `${title}${pc.cyan(S_BAR)} ${this.value ? `${pc.green(S_RADIO_ACTIVE)} ${active}` : `${pc.dim(S_RADIO_INACTIVE)} ${pc.dim(active)}`} ${pc.dim("/")} ${!this.value ? `${pc.green(S_RADIO_ACTIVE)} ${inactive}` : `${pc.dim(S_RADIO_INACTIVE)} ${pc.dim(inactive)}`}\n${pc.cyan(S_BAR_END)}${hint}\n`;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}));
|
|
552
|
+
}
|
|
553
|
+
async function navigableGroupMultiselect(opts) {
|
|
554
|
+
const required = opts.required ?? true;
|
|
555
|
+
const opt = (option, state, options = []) => {
|
|
556
|
+
const label = option.label ?? String(option.value);
|
|
557
|
+
const isItem = typeof option.group === "string";
|
|
558
|
+
const next = isItem && (options[options.indexOf(option) + 1] ?? { group: true });
|
|
559
|
+
const isLast = isItem && next && next.group === true;
|
|
560
|
+
const prefix = isItem ? `${isLast ? S_BAR_END : S_BAR} ` : "";
|
|
561
|
+
if (state === "active") return `${pc.dim(prefix)}${pc.cyan(S_CHECKBOX_ACTIVE)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
562
|
+
if (state === "group-active") return `${prefix}${pc.cyan(S_CHECKBOX_ACTIVE)} ${pc.dim(label)}`;
|
|
563
|
+
if (state === "group-active-selected") return `${prefix}${pc.green(S_CHECKBOX_SELECTED)} ${pc.dim(label)}`;
|
|
564
|
+
if (state === "selected") {
|
|
565
|
+
const selectedCheckbox = isItem ? pc.green(S_CHECKBOX_SELECTED) : "";
|
|
566
|
+
return `${pc.dim(prefix)}${selectedCheckbox} ${pc.dim(label)}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
567
|
+
}
|
|
568
|
+
if (state === "cancelled") return `${pc.strikethrough(pc.dim(label))}`;
|
|
569
|
+
if (state === "active-selected") return `${pc.dim(prefix)}${pc.green(S_CHECKBOX_SELECTED)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
570
|
+
if (state === "submitted") return `${pc.dim(label)}`;
|
|
571
|
+
const unselectedCheckbox = isItem ? pc.dim(S_CHECKBOX_INACTIVE) : "";
|
|
572
|
+
return `${pc.dim(prefix)}${unselectedCheckbox} ${pc.dim(label)}`;
|
|
573
|
+
};
|
|
574
|
+
return runWithNavigation(new GroupMultiSelectPrompt({
|
|
575
|
+
options: opts.options,
|
|
576
|
+
initialValues: opts.initialValues,
|
|
577
|
+
required,
|
|
578
|
+
selectableGroups: true,
|
|
579
|
+
validate(selected) {
|
|
580
|
+
if (required && (selected === void 0 || selected.length === 0)) return `Please select at least one option.\n${pc.reset(pc.dim(`Press ${pc.gray(pc.bgWhite(pc.inverse(" space ")))} to select, ${pc.gray(pc.bgWhite(pc.inverse(" enter ")))} to submit`))}`;
|
|
581
|
+
},
|
|
582
|
+
render() {
|
|
583
|
+
const title = `${pc.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
584
|
+
const value = this.value ?? [];
|
|
585
|
+
switch (this.state) {
|
|
586
|
+
case "submit": {
|
|
587
|
+
const selectedOptions = this.options.filter(({ value: optionValue }) => value.includes(optionValue)).map((option) => opt(option, "submitted"));
|
|
588
|
+
const optionsText = selectedOptions.length === 0 ? "" : ` ${selectedOptions.join(pc.dim(", "))}`;
|
|
589
|
+
return `${title}${pc.gray(S_BAR)}${optionsText}`;
|
|
590
|
+
}
|
|
591
|
+
case "cancel": {
|
|
592
|
+
const label = this.options.filter(({ value: optionValue }) => value.includes(optionValue)).map((option) => opt(option, "cancelled")).join(pc.dim(", "));
|
|
593
|
+
return `${title}${pc.gray(S_BAR)} ${label.trim() ? `${label}\n${pc.gray(S_BAR)}` : ""}`;
|
|
594
|
+
}
|
|
595
|
+
case "error": {
|
|
596
|
+
const footer = this.error.split("\n").map((ln, i) => i === 0 ? `${pc.yellow(S_BAR_END)} ${pc.yellow(ln)}` : ` ${ln}`).join("\n");
|
|
597
|
+
const optionsText = this.options.map((option, i, options) => {
|
|
598
|
+
const selected = value.includes(option.value) || option.group === true && this.isGroupSelected(`${option.value}`);
|
|
599
|
+
const active = i === this.cursor;
|
|
600
|
+
if (!active && typeof option.group === "string" && this.options[this.cursor].value === option.group) return opt(option, selected ? "group-active-selected" : "group-active", options);
|
|
601
|
+
if (active && selected) return opt(option, "active-selected", options);
|
|
602
|
+
if (selected) return opt(option, "selected", options);
|
|
603
|
+
return opt(option, active ? "active" : "inactive", options);
|
|
604
|
+
}).join(`\n${pc.yellow(S_BAR)} `);
|
|
605
|
+
return `${title}${pc.yellow(S_BAR)} ${optionsText}\n${footer}\n`;
|
|
606
|
+
}
|
|
607
|
+
default: {
|
|
608
|
+
const optionsText = this.options.map((option, i, options) => {
|
|
609
|
+
const selected = value.includes(option.value) || option.group === true && this.isGroupSelected(`${option.value}`);
|
|
610
|
+
const active = i === this.cursor;
|
|
611
|
+
const groupActive = !active && typeof option.group === "string" && this.options[this.cursor].value === option.group;
|
|
612
|
+
let optionText = "";
|
|
613
|
+
if (groupActive) optionText = opt(option, selected ? "group-active-selected" : "group-active", options);
|
|
614
|
+
else if (active && selected) optionText = opt(option, "active-selected", options);
|
|
615
|
+
else if (selected) optionText = opt(option, "selected", options);
|
|
616
|
+
else optionText = opt(option, active ? "active" : "inactive", options);
|
|
617
|
+
return `${i !== 0 && !optionText.startsWith("\n") ? " " : ""}${optionText}`;
|
|
618
|
+
}).join(`\n${pc.cyan(S_BAR)}`);
|
|
619
|
+
const optionsPrefix = optionsText.startsWith("\n") ? "" : " ";
|
|
620
|
+
const hint = `\n${pc.gray(S_BAR)} ${getMultiHint()}`;
|
|
621
|
+
return `${title}${pc.cyan(S_BAR)}${optionsPrefix}${optionsText}\n${pc.cyan(S_BAR_END)}${hint}\n`;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}));
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
//#endregion
|
|
629
|
+
//#region src/prompts/addons.ts
|
|
630
|
+
function getAddonDisplay(addon) {
|
|
631
|
+
let label;
|
|
632
|
+
let hint;
|
|
633
|
+
switch (addon) {
|
|
634
|
+
case "turborepo":
|
|
635
|
+
label = "Turborepo";
|
|
636
|
+
hint = "High-performance build system";
|
|
637
|
+
break;
|
|
638
|
+
case "pwa":
|
|
639
|
+
label = "PWA";
|
|
640
|
+
hint = "Make your app installable and work offline";
|
|
641
|
+
break;
|
|
642
|
+
case "tauri":
|
|
643
|
+
label = "Tauri";
|
|
644
|
+
hint = "Build native desktop apps from your web frontend";
|
|
645
|
+
break;
|
|
646
|
+
case "biome":
|
|
647
|
+
label = "Biome";
|
|
648
|
+
hint = "Format, lint, and more";
|
|
649
|
+
break;
|
|
650
|
+
case "oxlint":
|
|
651
|
+
label = "Oxlint";
|
|
652
|
+
hint = "Oxlint + Oxfmt (linting & formatting)";
|
|
653
|
+
break;
|
|
654
|
+
case "ultracite":
|
|
655
|
+
label = "Ultracite";
|
|
656
|
+
hint = "Zero-config Biome preset with AI integration";
|
|
657
|
+
break;
|
|
658
|
+
case "ruler":
|
|
659
|
+
label = "Ruler";
|
|
660
|
+
hint = "Centralize your AI rules";
|
|
661
|
+
break;
|
|
662
|
+
case "mcp":
|
|
663
|
+
label = "MCP";
|
|
664
|
+
hint = "Install MCP server recommendations for your stack";
|
|
665
|
+
break;
|
|
666
|
+
case "skills":
|
|
667
|
+
label = "Skills";
|
|
668
|
+
hint = "Install curated AI coding skills for your stack";
|
|
669
|
+
break;
|
|
670
|
+
case "lefthook":
|
|
671
|
+
label = "Lefthook";
|
|
672
|
+
hint = "Fast and powerful Git hooks manager";
|
|
673
|
+
break;
|
|
674
|
+
case "husky":
|
|
675
|
+
label = "Husky";
|
|
676
|
+
hint = "Modern native Git hooks made easy";
|
|
677
|
+
break;
|
|
678
|
+
case "starlight":
|
|
679
|
+
label = "Starlight";
|
|
680
|
+
hint = "Build stellar docs with astro";
|
|
681
|
+
break;
|
|
682
|
+
case "fumadocs":
|
|
683
|
+
label = "Fumadocs";
|
|
684
|
+
hint = "Build excellent documentation site";
|
|
685
|
+
break;
|
|
686
|
+
case "opentui":
|
|
687
|
+
label = "OpenTUI";
|
|
688
|
+
hint = "Build terminal user interfaces";
|
|
689
|
+
break;
|
|
690
|
+
case "wxt":
|
|
691
|
+
label = "WXT";
|
|
692
|
+
hint = "Build browser extensions";
|
|
693
|
+
break;
|
|
694
|
+
case "msw":
|
|
695
|
+
label = "MSW";
|
|
696
|
+
hint = "Mock Service Worker for API mocking";
|
|
697
|
+
break;
|
|
698
|
+
case "storybook":
|
|
699
|
+
label = "Storybook";
|
|
700
|
+
hint = "Component development and testing workshop";
|
|
701
|
+
break;
|
|
702
|
+
case "swr":
|
|
703
|
+
label = "SWR";
|
|
704
|
+
hint = "React Hooks for data fetching and caching";
|
|
705
|
+
break;
|
|
706
|
+
case "tanstack-query":
|
|
707
|
+
label = "TanStack Query";
|
|
708
|
+
hint = "Powerful async state management & data fetching";
|
|
709
|
+
break;
|
|
710
|
+
case "tanstack-table":
|
|
711
|
+
label = "TanStack Table";
|
|
712
|
+
hint = "Headless table with sorting, filtering & pagination";
|
|
713
|
+
break;
|
|
714
|
+
case "tanstack-virtual":
|
|
715
|
+
label = "TanStack Virtual";
|
|
716
|
+
hint = "Virtualize large lists & grids for 60fps performance";
|
|
717
|
+
break;
|
|
718
|
+
case "tanstack-db":
|
|
719
|
+
label = "TanStack DB";
|
|
720
|
+
hint = "Reactive client-first data store with sync backends (Beta)";
|
|
721
|
+
break;
|
|
722
|
+
case "tanstack-pacer":
|
|
723
|
+
label = "TanStack Pacer";
|
|
724
|
+
hint = "Debounce, throttle, rate-limit & queue utilities (Beta)";
|
|
725
|
+
break;
|
|
726
|
+
case "backend-utils":
|
|
727
|
+
label = "Backend Utils";
|
|
728
|
+
hint = "asyncHandler, ApiResponse & global error handler for your server";
|
|
729
|
+
break;
|
|
730
|
+
case "docker-compose":
|
|
731
|
+
label = "Docker Compose";
|
|
732
|
+
hint = "Containerize your app for deployment";
|
|
733
|
+
break;
|
|
734
|
+
default:
|
|
735
|
+
label = addon;
|
|
736
|
+
hint = `Add ${addon}`;
|
|
737
|
+
}
|
|
738
|
+
return {
|
|
739
|
+
label,
|
|
740
|
+
hint
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
const ADDON_GROUPS = {
|
|
744
|
+
Tooling: [
|
|
745
|
+
"turborepo",
|
|
746
|
+
"biome",
|
|
747
|
+
"oxlint",
|
|
748
|
+
"ultracite",
|
|
749
|
+
"husky",
|
|
750
|
+
"lefthook"
|
|
751
|
+
],
|
|
752
|
+
Documentation: ["starlight", "fumadocs"],
|
|
753
|
+
Extensions: [
|
|
754
|
+
"pwa",
|
|
755
|
+
"tauri",
|
|
756
|
+
"opentui",
|
|
757
|
+
"wxt",
|
|
758
|
+
"ruler",
|
|
759
|
+
"docker-compose"
|
|
760
|
+
],
|
|
761
|
+
Integrations: [
|
|
762
|
+
"msw",
|
|
763
|
+
"storybook",
|
|
764
|
+
"backend-utils"
|
|
765
|
+
],
|
|
766
|
+
"AI Agents": ["mcp", "skills"],
|
|
767
|
+
"Data Fetching": ["swr"],
|
|
768
|
+
TanStack: [
|
|
769
|
+
"tanstack-query",
|
|
770
|
+
"tanstack-table",
|
|
771
|
+
"tanstack-virtual",
|
|
772
|
+
"tanstack-db",
|
|
773
|
+
"tanstack-pacer"
|
|
774
|
+
]
|
|
775
|
+
};
|
|
776
|
+
function createGroupedAddonOptions() {
|
|
777
|
+
return Object.fromEntries(Object.keys(ADDON_GROUPS).map((group$1) => [group$1, []]));
|
|
778
|
+
}
|
|
779
|
+
function getAddonGroup(addon) {
|
|
780
|
+
return Object.entries(ADDON_GROUPS).find(([, addons]) => addons.includes(addon))?.[0];
|
|
781
|
+
}
|
|
782
|
+
function validateAddonCompatibilityForPrompt(addon, frontends, auth, backend, runtime) {
|
|
783
|
+
return validateAddonCompatibility$1(addon, frontends, auth, backend, runtime);
|
|
784
|
+
}
|
|
785
|
+
function getCompatibleAddonsForPrompt(allAddons, frontends, existingAddons = [], auth, backend, runtime) {
|
|
786
|
+
return getCompatibleAddons$1(allAddons, frontends, existingAddons, auth, backend, runtime);
|
|
787
|
+
}
|
|
788
|
+
async function getAddonsChoice(addons, frontends, auth, backend, runtime) {
|
|
789
|
+
if (addons !== void 0) return addons;
|
|
790
|
+
const allAddons = types_exports.AddonsSchema.options.filter((addon) => addon !== "none");
|
|
791
|
+
const groupedOptions = createGroupedAddonOptions();
|
|
792
|
+
const frontendsArray = frontends || [];
|
|
793
|
+
for (const addon of allAddons) {
|
|
794
|
+
const { isCompatible } = validateAddonCompatibilityForPrompt(addon, frontendsArray, auth, backend, runtime);
|
|
795
|
+
if (!isCompatible) continue;
|
|
796
|
+
const { label, hint } = getAddonDisplay(addon);
|
|
797
|
+
const option = {
|
|
798
|
+
value: addon,
|
|
799
|
+
label,
|
|
800
|
+
hint
|
|
801
|
+
};
|
|
802
|
+
const group$1 = getAddonGroup(addon);
|
|
803
|
+
if (group$1) groupedOptions[group$1].push(option);
|
|
804
|
+
}
|
|
805
|
+
Object.keys(groupedOptions).forEach((group$1) => {
|
|
806
|
+
if (groupedOptions[group$1].length === 0) delete groupedOptions[group$1];
|
|
807
|
+
else {
|
|
808
|
+
const groupOrder = ADDON_GROUPS[group$1] || [];
|
|
809
|
+
groupedOptions[group$1].sort((a, b) => {
|
|
810
|
+
return groupOrder.indexOf(a.value) - groupOrder.indexOf(b.value);
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
});
|
|
814
|
+
const response = await navigableGroupMultiselect({
|
|
815
|
+
message: "Select addons",
|
|
816
|
+
options: groupedOptions,
|
|
817
|
+
initialValues: DEFAULT_CONFIG.addons.filter((addonValue) => Object.values(groupedOptions).some((options) => options.some((opt) => opt.value === addonValue))),
|
|
818
|
+
required: false
|
|
819
|
+
});
|
|
820
|
+
if (isCancel$1(response)) return exitCancelled("Operation cancelled");
|
|
821
|
+
return response;
|
|
822
|
+
}
|
|
823
|
+
async function getAddonsToAdd(frontend, existingAddons = [], auth, backend, runtime) {
|
|
824
|
+
const groupedOptions = createGroupedAddonOptions();
|
|
825
|
+
const frontendArray = frontend || [];
|
|
826
|
+
const compatibleAddons = getCompatibleAddonsForPrompt(types_exports.AddonsSchema.options.filter((addon) => addon !== "none"), frontendArray, existingAddons, auth, backend, runtime);
|
|
827
|
+
for (const addon of compatibleAddons) {
|
|
828
|
+
const { label, hint } = getAddonDisplay(addon);
|
|
829
|
+
const option = {
|
|
830
|
+
value: addon,
|
|
831
|
+
label,
|
|
832
|
+
hint
|
|
833
|
+
};
|
|
834
|
+
const group$1 = getAddonGroup(addon);
|
|
835
|
+
if (group$1) groupedOptions[group$1].push(option);
|
|
836
|
+
}
|
|
837
|
+
Object.keys(groupedOptions).forEach((group$1) => {
|
|
838
|
+
if (groupedOptions[group$1].length === 0) delete groupedOptions[group$1];
|
|
839
|
+
else {
|
|
840
|
+
const groupOrder = ADDON_GROUPS[group$1] || [];
|
|
841
|
+
groupedOptions[group$1].sort((a, b) => {
|
|
842
|
+
return groupOrder.indexOf(a.value) - groupOrder.indexOf(b.value);
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
if (Object.keys(groupedOptions).length === 0) return [];
|
|
847
|
+
const response = await navigableGroupMultiselect({
|
|
848
|
+
message: "Select addons to add",
|
|
849
|
+
options: groupedOptions,
|
|
850
|
+
required: false
|
|
851
|
+
});
|
|
852
|
+
if (isCancel$1(response)) return exitCancelled("Operation cancelled");
|
|
853
|
+
return response;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
//#endregion
|
|
857
|
+
//#region src/utils/dependency-version-channel.ts
|
|
858
|
+
const VERSION_CACHE = /* @__PURE__ */ new Map();
|
|
859
|
+
const PRERELEASE_TAG_PRIORITY = [
|
|
860
|
+
"beta",
|
|
861
|
+
"next",
|
|
862
|
+
"rc",
|
|
863
|
+
"canary",
|
|
864
|
+
"alpha"
|
|
865
|
+
];
|
|
866
|
+
const REGISTRY_FETCH_TIMEOUT_MS = 1e4;
|
|
867
|
+
const REGISTRY_CONCURRENCY = 10;
|
|
868
|
+
function mapWithConcurrency(items, fn, concurrency) {
|
|
869
|
+
const results = Array.from({ length: items.length });
|
|
870
|
+
let index = 0;
|
|
871
|
+
async function worker() {
|
|
872
|
+
while (index < items.length) {
|
|
873
|
+
const i = index++;
|
|
874
|
+
results[i] = await fn(items[i], i);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
return Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker())).then(() => results);
|
|
878
|
+
}
|
|
879
|
+
function parseVersion(value) {
|
|
880
|
+
const normalized = value.replace(/^[^\d]*/, "");
|
|
881
|
+
const dashIdx = normalized.indexOf("-");
|
|
882
|
+
const base = dashIdx === -1 ? normalized : normalized.slice(0, dashIdx);
|
|
883
|
+
const prerelease = dashIdx === -1 ? "" : normalized.slice(dashIdx + 1);
|
|
884
|
+
const [major = "0", minor = "0", patch = "0"] = base.split(".");
|
|
885
|
+
return {
|
|
886
|
+
major: Number(major) || 0,
|
|
887
|
+
minor: Number(minor) || 0,
|
|
888
|
+
patch: Number(patch) || 0,
|
|
889
|
+
prerelease: prerelease ? prerelease.split(/[.-]/).map((part) => /^\d+$/.test(part) ? Number(part) : part) : []
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
function compareVersions(a, b) {
|
|
893
|
+
const left = parseVersion(a);
|
|
894
|
+
const right = parseVersion(b);
|
|
895
|
+
if (left.major !== right.major) return left.major - right.major;
|
|
896
|
+
if (left.minor !== right.minor) return left.minor - right.minor;
|
|
897
|
+
if (left.patch !== right.patch) return left.patch - right.patch;
|
|
898
|
+
if (left.prerelease.length === 0 && right.prerelease.length === 0) return 0;
|
|
899
|
+
if (left.prerelease.length === 0) return 1;
|
|
900
|
+
if (right.prerelease.length === 0) return -1;
|
|
901
|
+
const maxLength = Math.max(left.prerelease.length, right.prerelease.length);
|
|
902
|
+
for (let index = 0; index < maxLength; index++) {
|
|
903
|
+
const leftPart = left.prerelease[index];
|
|
904
|
+
const rightPart = right.prerelease[index];
|
|
905
|
+
if (leftPart === void 0) return -1;
|
|
906
|
+
if (rightPart === void 0) return 1;
|
|
907
|
+
if (leftPart === rightPart) continue;
|
|
908
|
+
if (typeof leftPart === "number" && typeof rightPart === "number") return leftPart - rightPart;
|
|
909
|
+
if (typeof leftPart === "number") return -1;
|
|
910
|
+
if (typeof rightPart === "number") return 1;
|
|
911
|
+
return leftPart.localeCompare(rightPart);
|
|
912
|
+
}
|
|
913
|
+
return 0;
|
|
914
|
+
}
|
|
915
|
+
function isPrerelease(version) {
|
|
916
|
+
return /-(alpha|beta|rc|next|canary)/i.test(version);
|
|
917
|
+
}
|
|
918
|
+
function getVersionPrefix(version) {
|
|
919
|
+
return version.match(/^[^\d]*/)?.[0] ?? "";
|
|
920
|
+
}
|
|
921
|
+
function applyVersionPrefix(currentVersion, resolvedVersion) {
|
|
922
|
+
return `${getVersionPrefix(currentVersion)}${resolvedVersion}`;
|
|
923
|
+
}
|
|
924
|
+
function isRegistrySemverSpec(version) {
|
|
925
|
+
return /^[~^]?\d/.test(version);
|
|
926
|
+
}
|
|
927
|
+
async function fetchPackageInfo(packageName) {
|
|
928
|
+
const cached = VERSION_CACHE.get(packageName);
|
|
929
|
+
if (cached) return cached;
|
|
930
|
+
const encodedName = encodeURIComponent(packageName).replace("%40", "@");
|
|
931
|
+
const response = await fetch(`https://registry.npmjs.org/${encodedName}`, {
|
|
932
|
+
headers: { Accept: "application/vnd.npm.install-v1+json" },
|
|
933
|
+
signal: AbortSignal.timeout(REGISTRY_FETCH_TIMEOUT_MS)
|
|
934
|
+
});
|
|
935
|
+
if (!response.ok) throw new Error(`Package ${packageName} not found (${response.status})`);
|
|
936
|
+
const raw = await response.json();
|
|
937
|
+
const data = {
|
|
938
|
+
"dist-tags": raw["dist-tags"],
|
|
939
|
+
versions: raw["versions"]
|
|
940
|
+
};
|
|
941
|
+
VERSION_CACHE.set(packageName, data);
|
|
942
|
+
return data;
|
|
943
|
+
}
|
|
944
|
+
function selectRegistryVersionForChannel(packageInfo, channel) {
|
|
945
|
+
const tags = packageInfo["dist-tags"] ?? {};
|
|
946
|
+
if (channel === "latest") return tags.latest ?? null;
|
|
947
|
+
for (const tag of PRERELEASE_TAG_PRIORITY) if (tags[tag]) return tags[tag];
|
|
948
|
+
const prereleases = Object.keys(packageInfo.versions ?? {}).filter(isPrerelease);
|
|
949
|
+
if (prereleases.length > 0) return prereleases.sort((left, right) => compareVersions(right, left))[0] ?? null;
|
|
950
|
+
return tags.latest ?? null;
|
|
951
|
+
}
|
|
952
|
+
async function resolveRegistryVersion(packageName, channel) {
|
|
953
|
+
const version = selectRegistryVersionForChannel(await fetchPackageInfo(packageName), channel);
|
|
954
|
+
if (!version) throw new Error(`No ${channel} version available for ${packageName}`);
|
|
955
|
+
return version;
|
|
956
|
+
}
|
|
957
|
+
async function collectPackageJsonPaths(projectDir) {
|
|
958
|
+
const results = [];
|
|
959
|
+
async function walk(currentDir) {
|
|
960
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
961
|
+
for (const entry of entries) {
|
|
962
|
+
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === ".turbo") continue;
|
|
963
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
964
|
+
if (entry.isDirectory()) {
|
|
965
|
+
await walk(fullPath);
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
968
|
+
if (entry.isFile() && entry.name === "package.json") results.push(fullPath);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
await walk(projectDir);
|
|
972
|
+
return results.sort();
|
|
973
|
+
}
|
|
974
|
+
async function applyDependencyVersionChannel(projectDir, channel) {
|
|
975
|
+
if (channel === "stable") return;
|
|
976
|
+
const packageJsonPaths = await collectPackageJsonPaths(projectDir);
|
|
977
|
+
if (packageJsonPaths.length === 0) return;
|
|
978
|
+
const packageNames = /* @__PURE__ */ new Set();
|
|
979
|
+
for (const packageJsonPath of packageJsonPaths) {
|
|
980
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
981
|
+
for (const [depName, depVersion] of Object.entries(packageJson.dependencies ?? {})) if (typeof depVersion === "string" && isRegistrySemverSpec(depVersion)) packageNames.add(depName);
|
|
982
|
+
for (const [depName, depVersion] of Object.entries(packageJson.devDependencies ?? {})) if (typeof depVersion === "string" && isRegistrySemverSpec(depVersion)) packageNames.add(depName);
|
|
983
|
+
}
|
|
984
|
+
if (packageNames.size === 0) return;
|
|
985
|
+
const resolvedVersions = /* @__PURE__ */ new Map();
|
|
986
|
+
await mapWithConcurrency([...packageNames], async (packageName) => {
|
|
987
|
+
try {
|
|
988
|
+
const resolvedVersion = await resolveRegistryVersion(packageName, channel);
|
|
989
|
+
resolvedVersions.set(packageName, resolvedVersion);
|
|
990
|
+
} catch (error) {
|
|
991
|
+
log.warn(`Failed to resolve ${channel} version for ${packageName}: ${error instanceof Error ? error.message : String(error)}`);
|
|
992
|
+
}
|
|
993
|
+
}, REGISTRY_CONCURRENCY);
|
|
994
|
+
if (resolvedVersions.size === 0) return;
|
|
995
|
+
for (const packageJsonPath of packageJsonPaths) {
|
|
996
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
997
|
+
let changed = false;
|
|
998
|
+
for (const sectionName of ["dependencies", "devDependencies"]) {
|
|
999
|
+
const section = packageJson[sectionName];
|
|
1000
|
+
if (!section) continue;
|
|
1001
|
+
for (const [packageName, currentVersion] of Object.entries(section)) {
|
|
1002
|
+
if (!isRegistrySemverSpec(currentVersion)) continue;
|
|
1003
|
+
const resolvedVersion = resolvedVersions.get(packageName);
|
|
1004
|
+
if (!resolvedVersion) continue;
|
|
1005
|
+
const nextVersion = applyVersionPrefix(currentVersion, resolvedVersion);
|
|
1006
|
+
if (nextVersion !== currentVersion) {
|
|
1007
|
+
section[packageName] = nextVersion;
|
|
1008
|
+
changed = true;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
if (changed) await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
//#endregion
|
|
1017
|
+
//#region src/helpers/core/install-dependencies.ts
|
|
1018
|
+
function getInstallEnvironment(packageManager) {
|
|
1019
|
+
if (packageManager === "yarn") return {
|
|
1020
|
+
YARN_ENABLE_HARDENED_MODE: "0",
|
|
1021
|
+
YARN_ENABLE_IMMUTABLE_INSTALLS: "false"
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
function getInstallArgs(packageManager) {
|
|
1025
|
+
if (packageManager === "pnpm") return ["install", "--dangerously-allow-all-builds"];
|
|
1026
|
+
return ["install"];
|
|
1027
|
+
}
|
|
1028
|
+
async function installDependencies({ projectDir, packageManager }) {
|
|
1029
|
+
const s = spinner();
|
|
1030
|
+
try {
|
|
1031
|
+
s.start(`Running ${packageManager} install...`);
|
|
1032
|
+
const installArgs = getInstallArgs(packageManager);
|
|
1033
|
+
await $({
|
|
1034
|
+
cwd: projectDir,
|
|
1035
|
+
env: {
|
|
1036
|
+
...process.env,
|
|
1037
|
+
...getInstallEnvironment(packageManager)
|
|
1038
|
+
},
|
|
1039
|
+
stderr: "inherit"
|
|
1040
|
+
})`${packageManager} ${installArgs}`;
|
|
1041
|
+
s.stop("Dependencies installed successfully");
|
|
1042
|
+
} catch (error) {
|
|
1043
|
+
s.stop(pc.red("Failed to install dependencies"));
|
|
1044
|
+
if (error instanceof Error) consola.error(pc.red(`Installation error: ${error.message}`));
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
async function runCargoBuild({ projectDir }) {
|
|
1048
|
+
const s = spinner();
|
|
1049
|
+
try {
|
|
1050
|
+
s.start("Running cargo build...");
|
|
1051
|
+
await $({
|
|
1052
|
+
cwd: projectDir,
|
|
1053
|
+
stderr: "inherit"
|
|
1054
|
+
})`cargo build`;
|
|
1055
|
+
s.stop("Cargo build completed");
|
|
1056
|
+
} catch (error) {
|
|
1057
|
+
s.stop(pc.red("Cargo build failed"));
|
|
1058
|
+
if (error instanceof Error) consola.error(pc.red(`Cargo build error: ${error.message}`));
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
async function runUvSync({ projectDir }) {
|
|
1062
|
+
const s = spinner();
|
|
1063
|
+
try {
|
|
1064
|
+
s.start("Running uv sync...");
|
|
1065
|
+
await $({
|
|
1066
|
+
cwd: projectDir,
|
|
1067
|
+
stderr: "inherit"
|
|
1068
|
+
})`uv sync`;
|
|
1069
|
+
s.stop("Python dependencies installed successfully");
|
|
1070
|
+
} catch (error) {
|
|
1071
|
+
s.stop(pc.red("uv sync failed"));
|
|
1072
|
+
if (error instanceof Error) consola.error(pc.red(`uv sync error: ${error.message}`));
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
async function runGoModTidy({ projectDir }) {
|
|
1076
|
+
const s = spinner();
|
|
1077
|
+
try {
|
|
1078
|
+
s.start("Running go mod tidy...");
|
|
1079
|
+
await $({
|
|
1080
|
+
cwd: projectDir,
|
|
1081
|
+
stderr: "inherit"
|
|
1082
|
+
})`go mod tidy`;
|
|
1083
|
+
s.stop("Go dependencies installed successfully");
|
|
1084
|
+
} catch (error) {
|
|
1085
|
+
s.stop(pc.red("go mod tidy failed"));
|
|
1086
|
+
if (error instanceof Error) consola.error(pc.red(`go mod tidy error: ${error.message}`));
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
async function runMavenTests({ projectDir }) {
|
|
1090
|
+
const s = spinner();
|
|
1091
|
+
const mvnw = process.platform === "win32" ? "mvnw.cmd" : "./mvnw";
|
|
1092
|
+
try {
|
|
1093
|
+
s.start("Running Maven tests...");
|
|
1094
|
+
await $({
|
|
1095
|
+
cwd: projectDir,
|
|
1096
|
+
stderr: "inherit"
|
|
1097
|
+
})`${mvnw} test`;
|
|
1098
|
+
s.stop("Maven tests completed");
|
|
1099
|
+
} catch (error) {
|
|
1100
|
+
s.stop(pc.red("Maven tests failed"));
|
|
1101
|
+
if (error instanceof Error) consola.error(pc.red(`Maven test error: ${error.message}`));
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
async function runGradleTests({ projectDir }) {
|
|
1105
|
+
const s = spinner();
|
|
1106
|
+
const gradlew = process.platform === "win32" ? "gradlew.bat" : "./gradlew";
|
|
1107
|
+
try {
|
|
1108
|
+
s.start("Running Gradle tests...");
|
|
1109
|
+
await $({
|
|
1110
|
+
cwd: projectDir,
|
|
1111
|
+
stderr: "inherit"
|
|
1112
|
+
})`${gradlew} test`;
|
|
1113
|
+
s.stop("Gradle tests completed");
|
|
1114
|
+
} catch (error) {
|
|
1115
|
+
s.stop(pc.red("Gradle tests failed"));
|
|
1116
|
+
if (error instanceof Error) consola.error(pc.red(`Gradle test error: ${error.message}`));
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
async function runMixCompile({ projectDir }) {
|
|
1120
|
+
const s = spinner();
|
|
1121
|
+
try {
|
|
1122
|
+
s.start("Running mix deps.get and mix compile...");
|
|
1123
|
+
await $({
|
|
1124
|
+
cwd: projectDir,
|
|
1125
|
+
stderr: "inherit"
|
|
1126
|
+
})`mix deps.get`;
|
|
1127
|
+
await $({
|
|
1128
|
+
cwd: projectDir,
|
|
1129
|
+
stderr: "inherit"
|
|
1130
|
+
})`mix compile`;
|
|
1131
|
+
s.stop("Elixir dependencies installed and project compiled");
|
|
1132
|
+
} catch (error) {
|
|
1133
|
+
s.stop(pc.red("mix compile failed"));
|
|
1134
|
+
if (error instanceof Error) consola.error(pc.red(`Mix error: ${error.message}`));
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
//#endregion
|
|
1139
|
+
export { validateAddonsAgainstFrontends as A, validateWorkersCompatibility as B, isExampleAIAllowed$1 as C, requiresChatSdkVercelAI as D, isWebFrontend$1 as E, validateServerDeployRequiresBackend as F, incompatibilityError as H, validateUILibraryCSSFrameworkCompatibility as I, validateUILibraryFrontendCompatibility as L, validateExamplesCompatibility as M, validatePaymentsCompatibility as N, splitFrontends$1 as O, validateSelfBackendCompatibility as P, validateWebDeployFrontendTemplates as R, hasWebStyling$1 as S, isFrontendAllowedWithBackend$1 as T, missingRequirementError as U, constraintError as V, renderTitle as W, isGoBack as _, runMavenTests as a, getCompatibleCSSFrameworks$1 as b, applyDependencyVersionChannel as c, isCancel$1 as d, navigableConfirm as f, GO_BACK_SYMBOL as g, setIsFirstPrompt as h, runGradleTests as i, validateApiFrontendCompatibility as j, validateAIFrontendCompatibility as k, 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, allowedApisForFrontends$1 as v, isExampleChatSdkAllowed$1 as w, getCompatibleUILibraries$1 as x, ensureSingleWebAndNative as y, validateWebDeployRequiresWebFrontend as z };
|