create-better-fullstack 2.1.2 → 2.1.4

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.
@@ -1,9 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import { A as types_exports, c as isSilent, i as exitWithError, k as hasGraphPart } from "./errors-D9yiiGVq.mjs";
3
- import { C as validateWebDeployFrontendTemplates, D as incompatibilityError, E as constraintError, O as missingRequirementError, S as validateUILibraryFrontendCompatibility, T as validateWorkersCompatibility, _ as validateExamplesCompatibility, b as validateServerDeployRequiresBackend, g as validateApiFrontendCompatibility, h as validateAddonsAgainstFrontends, n as ensureSingleWebAndNative, p as validateAIFrontendCompatibility, u as isWebFrontend, v as validatePaymentsCompatibility, w as validateWebDeployRequiresWebFrontend, x as validateUILibraryCSSFrameworkCompatibility, y as validateSelfBackendCompatibility } from "./compatibility-rules-D7zYNVjC.mjs";
2
+ import { A as types_exports, c as isSilent, i as exitWithError, t as CLIError } from "./errors-Cyol8zbN.mjs";
4
3
  import pc from "picocolors";
5
4
  import z from "zod";
5
+ import fs from "fs-extra";
6
+ import path from "node:path";
7
+ import { allowedApisForFrontends, getAIFrontendCompatibilityIssue, getApiFrontendCompatibilityIssue, getCompatibleAddons, getCompatibleCSSFrameworks, getCompatibleUILibraries, getUnsupportedWebDeployFrontend, hasDockerComposeCompatibleFrontend, hasWebStyling, isBackendUtilsCompatibleBackend, isExampleAIAllowed, isExampleChatSdkAllowed, isFrontendAllowedWithBackend, isWebFrontend, requiresChatSdkVercelAIForSelection, splitFrontends, validateAddonCompatibility } from "@better-fullstack/types";
6
8
  import consola from "consola";
9
+ import { format } from "oxfmt";
7
10
 
8
11
  //#region src/create-command-input.ts
9
12
  const CreateCommandOptionsSchema = z.object({
@@ -157,488 +160,368 @@ const CreateCommandOptionsSchema = z.object({
157
160
  const CreateCommandInputSchema = z.tuple([types_exports.ProjectNameSchema.optional(), CreateCommandOptionsSchema]);
158
161
 
159
162
  //#endregion
160
- //#region src/utils/generate-reproducible-command.ts
161
- function getBaseCommand(packageManager) {
162
- switch (packageManager) {
163
- case "bun": return "bun create better-fullstack@latest";
164
- case "pnpm": return "pnpm create better-fullstack@latest";
165
- case "yarn": return "yarn create better-fullstack@latest";
166
- case "npm":
167
- default: return "npx create-better-fullstack@latest";
163
+ //#region src/utils/error-formatter.ts
164
+ function getCategoryTitle(category) {
165
+ return {
166
+ incompatibility: "Incompatible Options",
167
+ "invalid-selection": "Invalid Selection",
168
+ "missing-requirement": "Missing Requirement",
169
+ constraint: "Constraint Violation"
170
+ }[category];
171
+ }
172
+ function displayStructuredError(error) {
173
+ if (isSilent()) throw new CLIError(error.message);
174
+ const lines = [];
175
+ lines.push(pc.bold(pc.red(getCategoryTitle(error.category))));
176
+ lines.push("");
177
+ lines.push(error.message);
178
+ if (error.provided && Object.keys(error.provided).length > 0) {
179
+ lines.push("");
180
+ lines.push(pc.dim("You provided:"));
181
+ for (const [key, value] of Object.entries(error.provided)) {
182
+ const displayValue = Array.isArray(value) ? value.join(", ") : value;
183
+ lines.push(` ${pc.cyan("--" + key)} ${displayValue}`);
184
+ }
185
+ }
186
+ if (error.suggestions.length > 0) {
187
+ lines.push("");
188
+ lines.push(pc.dim("Suggestions:"));
189
+ for (const suggestion of error.suggestions) lines.push(` ${pc.green("•")} ${suggestion}`);
168
190
  }
191
+ consola.box({
192
+ title: pc.red("Error"),
193
+ message: lines.join("\n"),
194
+ style: { borderColor: "red" }
195
+ });
196
+ process.exit(1);
169
197
  }
170
- function formatArrayFlag(flag, values) {
171
- const normalizedValues = values.filter((value) => value !== "none");
172
- if (normalizedValues.length === 0) return `--${flag} none`;
173
- return `--${flag} ${normalizedValues.join(" ")}`;
198
+ function incompatibilityError(opts) {
199
+ return displayStructuredError({
200
+ category: "incompatibility",
201
+ ...opts
202
+ });
174
203
  }
175
- function appendCommonFlags(flags, config) {
176
- if (config.aiDocs && config.aiDocs.length > 0) flags.push(formatArrayFlag("ai-docs", config.aiDocs));
177
- else flags.push("--ai-docs none");
178
- flags.push(config.git ? "--git" : "--no-git");
179
- flags.push(`--package-manager ${config.packageManager}`);
180
- if (config.versionChannel !== "stable") flags.push(`--version-channel ${config.versionChannel}`);
181
- flags.push(config.install ? "--install" : "--no-install");
204
+ function invalidSelectionError(opts) {
205
+ return displayStructuredError({
206
+ category: "invalid-selection",
207
+ ...opts
208
+ });
182
209
  }
183
- function hasGraphPrimaryPart(config, role, ecosystem) {
184
- return config.stackParts?.some((part) => part.source !== "provided" && part.role === role && !part.ownerPartId && (!ecosystem || part.ecosystem === ecosystem));
210
+ function missingRequirementError(opts) {
211
+ return displayStructuredError({
212
+ category: "missing-requirement",
213
+ ...opts
214
+ });
185
215
  }
186
- function hasOwnedGraphPart(config, ownerRole, role, ecosystem, toolId) {
187
- const owner = config.stackParts?.find((part) => part.source !== "provided" && part.role === ownerRole && !part.ownerPartId && (!ecosystem || part.ecosystem === ecosystem));
188
- if (!owner) return false;
189
- return Boolean(config.stackParts?.some((part) => part.source !== "provided" && part.role === role && part.ownerPartId === owner.id && (!ecosystem || part.ecosystem === ecosystem) && (!toolId || part.toolId === toolId)));
216
+ function constraintError(opts) {
217
+ return displayStructuredError({
218
+ category: "constraint",
219
+ ...opts
220
+ });
190
221
  }
191
- function hasGraphAddonPart(config, addon) {
192
- const binding = (0, types_exports.getAddonStackPartBinding)(addon);
193
- if (!binding) return false;
194
- if (!binding.ownerRole) return Boolean(config.stackParts?.some((part) => part.source !== "provided" && part.role === binding.role && part.ecosystem === binding.ecosystem && part.toolId === addon && !part.ownerPartId));
195
- const owner = config.stackParts?.find((part) => part.source !== "provided" && part.role === binding.ownerRole && !part.ownerPartId && part.ecosystem === binding.ecosystem);
196
- if (!owner) return false;
197
- return Boolean(config.stackParts?.some((part) => part.source !== "provided" && part.role === binding.role && part.ecosystem === binding.ecosystem && part.toolId === addon && part.ownerPartId === owner.id));
222
+
223
+ //#endregion
224
+ //#region src/utils/compatibility-rules.ts
225
+ function isWebFrontend$1(value) {
226
+ return isWebFrontend(value);
198
227
  }
199
- function hasGraphExamplePart(config, example) {
200
- return Boolean(config.stackParts?.some((part) => part.source !== "provided" && part.role === "examples" && part.ecosystem === "universal" && part.toolId === example && !part.ownerPartId));
228
+ function splitFrontends$1(values = []) {
229
+ return splitFrontends(values);
201
230
  }
202
- function hasGraphArrayParts(config, values, hasPart) {
203
- const normalizedValues = values.filter((value) => value !== "none");
204
- return normalizedValues.length > 0 && normalizedValues.every((value) => hasPart(config, value));
231
+ function ensureSingleWebAndNative(frontends) {
232
+ const { web, native } = splitFrontends$1(frontends);
233
+ if (web.length > 1) invalidSelectionError({
234
+ message: "Only one web framework can be selected per project.",
235
+ provided: { frontend: web },
236
+ suggestions: ["Keep one web framework and remove the others", "Use separate projects for multiple web frameworks"]
237
+ });
238
+ if (native.length > 1) invalidSelectionError({
239
+ message: "Only one native framework can be selected per project.",
240
+ provided: { frontend: native },
241
+ suggestions: ["Keep one native framework and remove the others", "Choose: native-bare, native-uniwind, or native-unistyles"]
242
+ });
205
243
  }
206
- function appendChangedStringFlag(flags, flag, value, defaultValue) {
207
- if (value !== defaultValue) flags.push(`--${flag} ${value}`);
244
+ const FULLSTACK_FRONTENDS = [
245
+ "next",
246
+ "vinext",
247
+ "tanstack-start",
248
+ "astro",
249
+ "nuxt",
250
+ "svelte",
251
+ "solid-start"
252
+ ];
253
+ function validateSelfBackendCompatibility(providedFlags, options, config) {
254
+ const backend = config.backend || options.backend;
255
+ const frontends = config.frontend || options.frontend || [];
256
+ if (backend === "self") {
257
+ const { web, native } = splitFrontends$1(frontends);
258
+ 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.");
259
+ if (native.length > 1) exitWithError("Cannot select multiple native frameworks. Choose only one of: native-bare, native-uniwind, native-unistyles");
260
+ }
261
+ const hasFullstackFrontend = frontends.some((f) => FULLSTACK_FRONTENDS.includes(f));
262
+ 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.");
208
263
  }
209
- function appendChangedGraphStringFlag(flags, config, role, ecosystem, flag, value, defaultValue) {
210
- if (hasGraphPart(config, role, ecosystem)) return;
211
- appendChangedStringFlag(flags, flag, value, defaultValue);
264
+ const WORKERS_COMPATIBLE_BACKENDS = [
265
+ "hono",
266
+ "nitro",
267
+ "fets"
268
+ ];
269
+ function validateWorkersCompatibility(providedFlags, options, config) {
270
+ if (providedFlags.has("runtime") && options.runtime === "workers" && config.backend && !WORKERS_COMPATIBLE_BACKENDS.includes(config.backend)) incompatibilityError({
271
+ message: "In Better-Fullstack, Cloudflare Workers runtime is currently supported only with compatible backends (Hono, Nitro, or Fets).",
272
+ provided: {
273
+ runtime: "workers",
274
+ backend: config.backend
275
+ },
276
+ suggestions: [
277
+ "Use --backend hono",
278
+ "Use --backend nitro",
279
+ "Use --backend fets",
280
+ "Choose a different runtime (node, bun)"
281
+ ]
282
+ });
283
+ if (providedFlags.has("backend") && config.backend && !WORKERS_COMPATIBLE_BACKENDS.includes(config.backend) && config.runtime === "workers") incompatibilityError({
284
+ message: `In Better-Fullstack, backend '${config.backend}' is currently not available with Cloudflare Workers runtime.`,
285
+ provided: {
286
+ backend: config.backend,
287
+ runtime: "workers"
288
+ },
289
+ suggestions: ["Use --backend hono, --backend nitro, or --backend fets", "Choose a different runtime (node, bun)"]
290
+ });
291
+ if (providedFlags.has("runtime") && options.runtime === "workers" && config.database === "mongodb") incompatibilityError({
292
+ message: "In Better-Fullstack, Cloudflare Workers runtime is currently not available with MongoDB.",
293
+ provided: {
294
+ runtime: "workers",
295
+ database: "mongodb"
296
+ },
297
+ suggestions: ["Use a different database (postgres, sqlite, mysql)", "Choose a different runtime (node, bun)"]
298
+ });
299
+ if (providedFlags.has("runtime") && options.runtime === "workers" && config.dbSetup === "docker") incompatibilityError({
300
+ message: "In Better-Fullstack, Cloudflare Workers runtime is currently not available with Docker database setup.",
301
+ provided: {
302
+ runtime: "workers",
303
+ "db-setup": "docker"
304
+ },
305
+ suggestions: ["Use --db-setup d1 for SQLite", "Choose a different runtime (node, bun)"]
306
+ });
307
+ if (providedFlags.has("database") && config.database === "mongodb" && config.runtime === "workers") incompatibilityError({
308
+ message: "In Better-Fullstack, MongoDB is currently not available with Cloudflare Workers runtime.",
309
+ provided: {
310
+ database: "mongodb",
311
+ runtime: "workers"
312
+ },
313
+ suggestions: ["Use a different database (postgres, sqlite, mysql)", "Choose a different runtime (node, bun)"]
314
+ });
212
315
  }
213
- function appendChangedOwnedGraphStringFlag(flags, config, ownerRole, role, ecosystem, flag, value, defaultValue) {
214
- if (hasOwnedGraphPart(config, ownerRole, role, ecosystem, value)) return;
215
- appendChangedStringFlag(flags, flag, value, defaultValue);
316
+ function validateApiFrontendCompatibility(api, frontends = [], astroIntegration) {
317
+ const issue = getApiFrontendCompatibilityIssue(api, frontends, astroIntegration);
318
+ if (!issue) return;
319
+ incompatibilityError({
320
+ message: issue.message,
321
+ provided: issue.provided ?? {},
322
+ suggestions: issue.suggestions ?? []
323
+ });
216
324
  }
217
- function hasOwnedGraphArrayParts(config, ownerRole, role, ecosystem, values) {
218
- const normalizedValues = values.filter((value) => value !== "none");
219
- if (normalizedValues.length === 0) return false;
220
- return normalizedValues.every((value) => hasOwnedGraphPart(config, ownerRole, role, ecosystem, value));
325
+ function isFrontendAllowedWithBackend$1(frontend, backend, auth) {
326
+ return isFrontendAllowedWithBackend(frontend, backend, auth);
221
327
  }
222
- function appendChangedOwnedGraphArrayFlag(flags, config, ownerRole, role, ecosystem, flag, values, defaultValues) {
223
- if (hasOwnedGraphArrayParts(config, ownerRole, role, ecosystem, values)) return;
224
- appendChangedArrayFlag(flags, flag, values, defaultValues);
328
+ function allowedApisForFrontends$1(frontends = [], astroIntegration) {
329
+ return allowedApisForFrontends(frontends, astroIntegration);
225
330
  }
226
- function appendChangedArrayFlag(flags, flag, values, defaultValues) {
227
- if (values.length !== defaultValues.length || values.some((value, index) => value !== defaultValues[index])) flags.push(formatArrayFlag(flag, values));
331
+ function isExampleAIAllowed$1(backend, frontends = []) {
332
+ return isExampleAIAllowed(backend, frontends);
228
333
  }
229
- function appendAstroIntegrationFlag(flags, config) {
230
- if (config.frontend.includes("astro") && config.astroIntegration !== "none") flags.push(`--astro-integration ${config.astroIntegration}`);
334
+ function isExampleChatSdkAllowed$1(backend, frontends = [], runtime) {
335
+ return isExampleChatSdkAllowed(backend, frontends, runtime);
231
336
  }
232
- function appendGraphExtraFlags(flags, config) {
233
- if (!hasGraphArrayParts(config, config.addons, hasGraphAddonPart)) appendChangedArrayFlag(flags, "addons", config.addons, ["turborepo"]);
234
- if (!hasGraphArrayParts(config, config.examples, hasGraphExamplePart)) appendChangedArrayFlag(flags, "examples", config.examples, []);
235
- if (hasGraphPrimaryPart(config, "database")) appendChangedOwnedGraphStringFlag(flags, config, "database", "dbSetup", "universal", "db-setup", config.dbSetup, "none");
236
- else appendChangedStringFlag(flags, "db-setup", config.dbSetup, "none");
237
- if (hasGraphPrimaryPart(config, "frontend", "typescript")) {
238
- appendAstroIntegrationFlag(flags, config);
239
- appendChangedOwnedGraphStringFlag(flags, config, "frontend", "deploy", "typescript", "web-deploy", config.webDeploy, "none");
240
- appendChangedGraphStringFlag(flags, config, "css", "typescript", "css-framework", config.cssFramework, "tailwind");
241
- appendChangedGraphStringFlag(flags, config, "ui", "typescript", "ui-library", config.uiLibrary, "shadcn-ui");
242
- if (config.uiLibrary === "shadcn-ui") {
243
- appendChangedStringFlag(flags, "shadcn-base", config.shadcnBase ?? "radix", "radix");
244
- appendChangedStringFlag(flags, "shadcn-style", config.shadcnStyle ?? "nova", "nova");
245
- appendChangedStringFlag(flags, "shadcn-icon-library", config.shadcnIconLibrary ?? "lucide", "lucide");
246
- appendChangedStringFlag(flags, "shadcn-color-theme", config.shadcnColorTheme ?? "neutral", "neutral");
247
- appendChangedStringFlag(flags, "shadcn-base-color", config.shadcnBaseColor ?? "neutral", "neutral");
248
- appendChangedStringFlag(flags, "shadcn-font", config.shadcnFont ?? "inter", "inter");
249
- appendChangedStringFlag(flags, "shadcn-radius", config.shadcnRadius ?? "default", "default");
250
- }
251
- appendChangedGraphStringFlag(flags, config, "stateManagement", "typescript", "state-management", config.stateManagement, "none");
252
- appendChangedGraphStringFlag(flags, config, "forms", "typescript", "forms", config.forms, "react-hook-form");
253
- appendChangedStringFlag(flags, "validation", config.validation, "zod");
254
- appendChangedStringFlag(flags, "testing", config.testing, "vitest");
255
- appendChangedGraphStringFlag(flags, config, "animation", "typescript", "animation", config.animation, "none");
256
- appendChangedGraphStringFlag(flags, config, "fileUpload", "typescript", "file-upload", config.fileUpload, "none");
257
- appendChangedGraphStringFlag(flags, config, "i18n", "typescript", "i18n", config.i18n, "none");
258
- appendChangedGraphStringFlag(flags, config, "analytics", "typescript", "analytics", config.analytics, "none");
259
- }
260
- if (hasGraphPrimaryPart(config, "frontend", "typescript") || hasGraphPrimaryPart(config, "backend", "typescript")) {
261
- if (hasGraphPrimaryPart(config, "backend", "typescript")) {
262
- appendChangedOwnedGraphStringFlag(flags, config, "backend", "runtime", "typescript", "runtime", config.runtime, "bun");
263
- appendChangedOwnedGraphStringFlag(flags, config, "backend", "deploy", "typescript", "server-deploy", config.serverDeploy, "none");
264
- } else appendChangedStringFlag(flags, "server-deploy", config.serverDeploy, "none");
265
- appendChangedGraphStringFlag(flags, config, "payments", "typescript", "payments", config.payments, "none");
266
- appendChangedGraphStringFlag(flags, config, "email", "typescript", "email", config.email, "none");
267
- appendChangedStringFlag(flags, "effect", config.effect, "none");
268
- appendChangedGraphStringFlag(flags, config, "ai", "typescript", "ai", config.ai, "none");
269
- appendChangedGraphStringFlag(flags, config, "realtime", "typescript", "realtime", config.realtime, "none");
270
- appendChangedGraphStringFlag(flags, config, "jobQueue", "typescript", "job-queue", config.jobQueue, "none");
271
- appendChangedGraphStringFlag(flags, config, "logging", "typescript", "logging", config.logging, "none");
272
- appendChangedGraphStringFlag(flags, config, "observability", "typescript", "observability", config.observability, "none");
273
- appendChangedGraphStringFlag(flags, config, "featureFlags", "typescript", "feature-flags", config.featureFlags, "none");
274
- appendChangedGraphStringFlag(flags, config, "caching", "typescript", "caching", config.caching, "none");
275
- appendChangedGraphStringFlag(flags, config, "rateLimit", "typescript", "rate-limit", config.rateLimit, "none");
276
- appendChangedGraphStringFlag(flags, config, "cms", "typescript", "cms", config.cms, "none");
277
- appendChangedGraphStringFlag(flags, config, "search", "typescript", "search", config.search, "none");
278
- appendChangedGraphStringFlag(flags, config, "vectorDb", "typescript", "vectorDb", config.vectorDb, "none");
279
- appendChangedGraphStringFlag(flags, config, "fileStorage", "typescript", "file-storage", config.fileStorage, "none");
337
+ function requiresChatSdkVercelAI(backend, frontends = [], runtime) {
338
+ return requiresChatSdkVercelAIForSelection(backend, frontends, runtime);
339
+ }
340
+ function validateWebDeployRequiresWebFrontend(webDeploy, hasWebFrontendFlag) {
341
+ if (webDeploy && webDeploy !== "none" && !hasWebFrontendFlag) exitWithError("'--web-deploy' requires a web frontend. Please select a web frontend or set '--web-deploy none'.");
342
+ }
343
+ function validateWebDeployFrontendTemplates(webDeploy, frontends = []) {
344
+ const blocked = getUnsupportedWebDeployFrontend(webDeploy, frontends);
345
+ 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.`);
346
+ }
347
+ function validateServerDeployRequiresBackend(serverDeploy, backend, hasGraphBackend = false) {
348
+ if (serverDeploy && serverDeploy !== "none" && !hasGraphBackend && (!backend || backend === "none")) exitWithError("'--server-deploy' requires a backend. Please select a backend or set '--server-deploy none'.");
349
+ }
350
+ function validateAddonCompatibility$1(addon, frontend, _auth, backend, runtime, ecosystem, rustFrontend, javaWebFramework, database) {
351
+ const baseCompatibility = validateAddonCompatibility(addon, frontend, _auth);
352
+ if (!baseCompatibility.isCompatible) return baseCompatibility;
353
+ if (addon === "backend-utils") {
354
+ if (ecosystem !== void 0 && ecosystem !== "typescript") return {
355
+ isCompatible: false,
356
+ reason: "Backend Utils requires a TypeScript server stack"
357
+ };
358
+ if (backend !== void 0 && !isBackendUtilsCompatibleBackend(backend)) return {
359
+ isCompatible: false,
360
+ reason: "Backend Utils requires a Hono, Express, Fastify, Elysia, feTS, or NestJS backend"
361
+ };
280
362
  }
281
- if (hasGraphPrimaryPart(config, "mobile")) {
282
- appendChangedOwnedGraphStringFlag(flags, config, "mobile", "navigation", "react-native", "mobile-navigation", config.mobileNavigation, "expo-router");
283
- appendChangedOwnedGraphStringFlag(flags, config, "mobile", "ui", "react-native", "mobile-ui", config.mobileUI, "none");
284
- appendChangedOwnedGraphStringFlag(flags, config, "mobile", "storage", "react-native", "mobile-storage", config.mobileStorage, "none");
285
- appendChangedOwnedGraphStringFlag(flags, config, "mobile", "testing", "react-native", "mobile-testing", config.mobileTesting, "none");
286
- appendChangedStringFlag(flags, "mobile-push", config.mobilePush, "none");
287
- appendChangedStringFlag(flags, "mobile-ota", config.mobileOTA, "none");
288
- appendChangedStringFlag(flags, "mobile-deep-linking", config.mobileDeepLinking, "none");
363
+ if (addon === "docker-compose" || addon === "devcontainer") {
364
+ const label = addon === "devcontainer" ? "DevContainer" : "docker-compose";
365
+ const title = addon === "devcontainer" ? "DevContainer" : "Docker Compose";
366
+ if (backend === "convex") return {
367
+ isCompatible: false,
368
+ reason: `${label} is not compatible with Convex backend (managed service)`
369
+ };
370
+ if (runtime === "workers") return {
371
+ isCompatible: false,
372
+ reason: `${label} is not compatible with Cloudflare Workers runtime`
373
+ };
374
+ if (ecosystem !== void 0 && ![
375
+ "typescript",
376
+ "python",
377
+ "go",
378
+ "rust",
379
+ "java"
380
+ ].includes(ecosystem)) return {
381
+ isCompatible: false,
382
+ reason: `${title} currently supports TypeScript, Python, Go, Rust, or Java projects`
383
+ };
384
+ if (ecosystem === "typescript" && !hasDockerComposeCompatibleFrontend(frontend)) return {
385
+ isCompatible: false,
386
+ reason: `${title} currently supports Next.js, Vinext, TanStack Router, React Router, React Vite, Solid, or Astro`
387
+ };
388
+ if (ecosystem === "typescript" && backend === "self" && !frontend.includes("next") && !frontend.includes("vinext")) return {
389
+ isCompatible: false,
390
+ reason: `${title} self-backend support currently requires Next.js or Vinext`
391
+ };
392
+ if (ecosystem === "rust" && rustFrontend && rustFrontend !== "none") return {
393
+ isCompatible: false,
394
+ reason: `${title} for Rust currently supports server-only projects`
395
+ };
396
+ if (ecosystem === "java" && javaWebFramework && javaWebFramework !== "spring-boot") return {
397
+ isCompatible: false,
398
+ reason: `${title} for Java currently requires Spring Boot`
399
+ };
400
+ if (ecosystem === "python" && database && database !== "none" && database !== "sqlite" && database !== "postgres") return {
401
+ isCompatible: false,
402
+ reason: `${title} for Python ORM projects currently supports SQLite defaults or Postgres`
403
+ };
289
404
  }
290
- if (hasGraphPrimaryPart(config, "frontend", "rust")) appendChangedStringFlag(flags, "rust-frontend", config.rustFrontend, "none");
291
- if (hasGraphPrimaryPart(config, "backend", "rust")) {
292
- appendChangedOwnedGraphStringFlag(flags, config, "backend", "cli", "rust", "rust-cli", config.rustCli, "none");
293
- appendChangedOwnedGraphArrayFlag(flags, config, "backend", "libraries", "rust", "rust-libraries", config.rustLibraries, []);
294
- appendChangedOwnedGraphStringFlag(flags, config, "backend", "logging", "rust", "rust-logging", config.rustLogging, "tracing");
295
- appendChangedOwnedGraphStringFlag(flags, config, "backend", "errorHandling", "rust", "rust-error-handling", config.rustErrorHandling, "anyhow-thiserror");
296
- appendChangedOwnedGraphStringFlag(flags, config, "backend", "caching", "rust", "rust-caching", config.rustCaching, "none");
405
+ return { isCompatible: true };
406
+ }
407
+ function getCompatibleAddons$1(allAddons, frontend, existingAddons = [], auth, backend, runtime) {
408
+ return getCompatibleAddons(allAddons, frontend, existingAddons, auth).filter((addon) => {
409
+ const { isCompatible } = validateAddonCompatibility$1(addon, frontend, auth, backend, runtime);
410
+ return isCompatible;
411
+ });
412
+ }
413
+ function validateAddonsAgainstFrontends(addons = [], frontends = [], auth, backend, runtime, ecosystem, rustFrontend, javaWebFramework, database) {
414
+ if (addons.includes("nx") && addons.includes("turborepo")) exitWithError("Nx and Turborepo are alternative workspace runners. Choose one addon.");
415
+ for (const addon of addons) {
416
+ if (addon === "none") continue;
417
+ const { isCompatible, reason } = validateAddonCompatibility$1(addon, frontends, auth, backend, runtime, ecosystem, rustFrontend, javaWebFramework, database);
418
+ if (!isCompatible) exitWithError(`Incompatible addon/frontend combination: ${reason}`);
297
419
  }
298
- if (hasGraphPrimaryPart(config, "backend", "python")) {
299
- appendChangedOwnedGraphStringFlag(flags, config, "backend", "validation", "python", "python-validation", config.pythonValidation, "none");
300
- appendChangedOwnedGraphArrayFlag(flags, config, "backend", "ai", "python", "python-ai", config.pythonAi, []);
301
- appendChangedOwnedGraphStringFlag(flags, config, "backend", "jobQueue", "python", "python-task-queue", config.pythonTaskQueue, "none");
302
- appendChangedOwnedGraphStringFlag(flags, config, "backend", "api", "python", "python-graphql", config.pythonGraphql, "none");
303
- appendChangedOwnedGraphStringFlag(flags, config, "backend", "codeQuality", "python", "python-quality", config.pythonQuality, "none");
420
+ }
421
+ function validatePaymentsCompatibility(payments, auth, _backend, frontends = []) {
422
+ if (!payments || payments === "none") return;
423
+ if (payments === "dodo" && frontends.includes("react-vite")) exitWithError("Dodo Payments are not yet supported for React + Vite projects.");
424
+ if (payments === "polar") {
425
+ 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.");
426
+ const { web } = splitFrontends$1(frontends);
427
+ 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.");
304
428
  }
305
- if (hasGraphPrimaryPart(config, "backend", "go")) {
306
- appendChangedOwnedGraphStringFlag(flags, config, "backend", "cli", "go", "go-cli", config.goCli, "none");
307
- appendChangedOwnedGraphStringFlag(flags, config, "backend", "logging", "go", "go-logging", config.goLogging, "none");
429
+ }
430
+ function validateExamplesCompatibility(examples, backend, frontend, runtime, ai) {
431
+ const examplesArr = examples ?? [];
432
+ if (examplesArr.length === 0 || examplesArr.includes("none")) return;
433
+ if (examplesArr.includes("tanstack-showcase")) {
434
+ const showcaseFrontends = ["tanstack-router", "tanstack-start"];
435
+ if (!(frontend ?? []).some((f) => showcaseFrontends.includes(f))) exitWithError("The 'tanstack-showcase' example requires TanStack Router or TanStack Start frontend.");
308
436
  }
309
- if (hasGraphPrimaryPart(config, "backend", "java")) {
310
- appendChangedOwnedGraphStringFlag(flags, config, "backend", "buildTool", "java", "java-build-tool", config.javaBuildTool, "maven");
311
- appendChangedOwnedGraphArrayFlag(flags, config, "backend", "libraries", "java", "java-libraries", config.javaLibraries, []);
312
- appendChangedOwnedGraphArrayFlag(flags, config, "backend", "testing", "java", "java-testing-libraries", config.javaTestingLibraries, ["junit5"]);
437
+ if (examplesArr.includes("ai") && (frontend ?? []).includes("solid")) exitWithError("The 'ai' example is not compatible with the Solid frontend.");
438
+ if (examplesArr.includes("ai") && (frontend ?? []).includes("solid-start")) exitWithError("The 'ai' example is not compatible with the SolidStart frontend.");
439
+ if (examplesArr.includes("ai") && backend === "convex") {
440
+ const frontendArr = frontend ?? [];
441
+ const includesNuxt = frontendArr.includes("nuxt");
442
+ const includesSvelte = frontendArr.includes("svelte");
443
+ 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
444
  }
314
- if (hasGraphPrimaryPart(config, "backend", "elixir")) {
315
- appendChangedGraphStringFlag(flags, config, "realtime", "elixir", "elixir-realtime", config.elixirRealtime, "channels");
316
- appendChangedGraphStringFlag(flags, config, "jobQueue", "elixir", "elixir-jobs", config.elixirJobs, "none");
317
- appendChangedGraphStringFlag(flags, config, "validation", "elixir", "elixir-validation", config.elixirValidation, "ecto-changesets");
318
- appendChangedOwnedGraphStringFlag(flags, config, "backend", "httpClient", "elixir", "elixir-http", config.elixirHttp, "req");
319
- appendChangedStringFlag(flags, "elixir-json", config.elixirJson, "jason");
320
- appendChangedGraphStringFlag(flags, config, "email", "elixir", "elixir-email", config.elixirEmail, "none");
321
- appendChangedGraphStringFlag(flags, config, "caching", "elixir", "elixir-caching", config.elixirCaching, "none");
322
- appendChangedGraphStringFlag(flags, config, "observability", "elixir", "elixir-observability", config.elixirObservability, "telemetry");
323
- appendChangedGraphStringFlag(flags, config, "testing", "elixir", "elixir-testing", config.elixirTesting, "ex_unit");
324
- appendChangedOwnedGraphStringFlag(flags, config, "backend", "codeQuality", "elixir", "elixir-quality", config.elixirQuality, "credo");
325
- appendChangedGraphStringFlag(flags, config, "deploy", "elixir", "elixir-deploy", config.elixirDeploy, "none");
445
+ if (examplesArr.includes("chat-sdk")) {
446
+ const frontendArr = frontend ?? [];
447
+ if (frontendArr.includes("react-vite")) exitWithError("The 'chat-sdk' example is not yet supported for React + Vite projects.");
448
+ if (!isExampleChatSdkAllowed$1(backend, frontendArr, runtime)) {
449
+ if (backend === "none") exitWithError("The 'chat-sdk' example requires a backend.");
450
+ 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.");
451
+ if (backend === "self") exitWithError("The 'chat-sdk' example with self backend only supports Next.js, TanStack Start, or Nuxt frontends in v1.");
452
+ if (backend === "hono" && runtime !== "node") exitWithError("The 'chat-sdk' example with Hono requires '--runtime node' in v1 (Bun/Workers not supported yet).");
453
+ exitWithError("The 'chat-sdk' example is only supported with self backend (Next.js, TanStack Start, Nuxt) or Hono with Node runtime in v1.");
454
+ }
455
+ 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).");
326
456
  }
327
457
  }
328
- function appendSharedNonTypeScriptFlags(flags, config) {
329
- flags.push(`--email ${config.email}`);
330
- flags.push(`--observability ${config.observability}`);
331
- flags.push(`--caching ${config.caching}`);
332
- flags.push(`--search ${config.search}`);
333
- flags.push(formatArrayFlag("addons", config.addons));
334
- flags.push(formatArrayFlag("examples", config.examples));
335
- flags.push(`--db-setup ${config.dbSetup}`);
336
- flags.push(`--web-deploy ${config.webDeploy}`);
337
- flags.push(`--server-deploy ${config.serverDeploy}`);
458
+ /**
459
+ * Validates that TanStack AI is only used with compatible frontends (React or Solid).
460
+ * Server-side @tanstack/ai core works anywhere, but client adapters only exist for React and Solid.
461
+ */
462
+ function validateAIFrontendCompatibility(ai, frontends = []) {
463
+ const issue = getAIFrontendCompatibilityIssue(ai, frontends);
464
+ if (!issue) return;
465
+ exitWithError(issue.message);
338
466
  }
339
- function getTypeScriptFlags(config) {
340
- const flags = [];
341
- if (config.frontend && config.frontend.length > 0) flags.push(`--frontend ${config.frontend.join(" ")}`);
342
- else flags.push("--frontend none");
343
- appendAstroIntegrationFlag(flags, config);
344
- flags.push(`--backend ${config.backend}`);
345
- flags.push(`--runtime ${config.runtime}`);
346
- flags.push(`--database ${config.database}`);
347
- flags.push(`--orm ${config.orm}`);
348
- flags.push(`--api ${config.api}`);
349
- flags.push(`--auth ${config.auth}`);
350
- flags.push(`--payments ${config.payments}`);
351
- flags.push(`--email ${config.email}`);
352
- flags.push(`--file-upload ${config.fileUpload}`);
353
- flags.push(`--effect ${config.effect}`);
354
- flags.push(`--css-framework ${config.cssFramework}`);
355
- flags.push(`--ui-library ${config.uiLibrary}`);
356
- if (config.uiLibrary === "shadcn-ui") {
357
- flags.push(`--shadcn-base ${config.shadcnBase}`);
358
- flags.push(`--shadcn-style ${config.shadcnStyle}`);
359
- flags.push(`--shadcn-icon-library ${config.shadcnIconLibrary}`);
360
- flags.push(`--shadcn-color-theme ${config.shadcnColorTheme}`);
361
- flags.push(`--shadcn-base-color ${config.shadcnBaseColor}`);
362
- flags.push(`--shadcn-font ${config.shadcnFont}`);
363
- flags.push(`--shadcn-radius ${config.shadcnRadius}`);
467
+ /**
468
+ * Validates that a UI library is compatible with the selected frontend(s)
469
+ */
470
+ function validateUILibraryFrontendCompatibility(uiLibrary, frontends = [], astroIntegration) {
471
+ if (!uiLibrary || uiLibrary === "none") return;
472
+ const { web } = splitFrontends$1(frontends);
473
+ if (web.length === 0) return;
474
+ const compatible = getCompatibleUILibraries(frontends, astroIntegration);
475
+ if (!compatible.includes(uiLibrary)) {
476
+ const isAstroNonReact = web.includes("astro") && astroIntegration !== "react";
477
+ const supportsAstroReact = getCompatibleUILibraries(["astro"], "react").includes(uiLibrary);
478
+ if (isAstroNonReact && supportsAstroReact) {
479
+ incompatibilityError({
480
+ message: `UI library '${uiLibrary}' requires React.`,
481
+ provided: {
482
+ "ui-library": uiLibrary,
483
+ "astro-integration": astroIntegration || "none"
484
+ },
485
+ suggestions: ["Use --astro-integration react", "Choose a different UI library (daisyui, ark-ui)"]
486
+ });
487
+ return;
488
+ }
489
+ incompatibilityError({
490
+ message: `UI library '${uiLibrary}' is not compatible with the selected frontend.`,
491
+ provided: {
492
+ "ui-library": uiLibrary,
493
+ frontend: frontends
494
+ },
495
+ suggestions: [`Supported choices for this stack: ${compatible.join(", ")}`, "Choose a different UI library"]
496
+ });
364
497
  }
365
- flags.push(`--ai ${config.ai}`);
366
- flags.push(`--state-management ${config.stateManagement}`);
367
- flags.push(`--forms ${config.forms}`);
368
- flags.push(`--validation ${config.validation}`);
369
- flags.push(`--testing ${config.testing}`);
370
- flags.push(`--animation ${config.animation}`);
371
- flags.push(`--realtime ${config.realtime}`);
372
- flags.push(`--job-queue ${config.jobQueue}`);
373
- flags.push(`--logging ${config.logging}`);
374
- flags.push(`--observability ${config.observability}`);
375
- flags.push(`--feature-flags ${config.featureFlags}`);
376
- flags.push(`--caching ${config.caching}`);
377
- flags.push(`--rate-limit ${config.rateLimit}`);
378
- flags.push(`--i18n ${config.i18n}`);
379
- flags.push(`--cms ${config.cms}`);
380
- flags.push(`--search ${config.search}`);
381
- flags.push(`--vector-db ${config.vectorDb}`);
382
- flags.push(`--file-storage ${config.fileStorage}`);
383
- flags.push(`--mobile-navigation ${config.mobileNavigation}`);
384
- flags.push(`--mobile-ui ${config.mobileUI}`);
385
- flags.push(`--mobile-storage ${config.mobileStorage}`);
386
- flags.push(`--mobile-testing ${config.mobileTesting}`);
387
- flags.push(`--mobile-push ${config.mobilePush}`);
388
- flags.push(`--mobile-ota ${config.mobileOTA}`);
389
- flags.push(`--mobile-deep-linking ${config.mobileDeepLinking}`);
390
- if (config.addons && config.addons.length > 0) flags.push(`--addons ${config.addons.join(" ")}`);
391
- else flags.push("--addons none");
392
- if (config.examples && config.examples.length > 0) flags.push(`--examples ${config.examples.join(" ")}`);
393
- else flags.push("--examples none");
394
- flags.push(`--db-setup ${config.dbSetup}`);
395
- flags.push(`--web-deploy ${config.webDeploy}`);
396
- flags.push(`--server-deploy ${config.serverDeploy}`);
397
- appendCommonFlags(flags, config);
398
- return flags;
399
- }
400
- function getReactNativeFlags(config) {
401
- const flags = ["--ecosystem react-native"];
402
- if (config.frontend && config.frontend.length > 0) flags.push(`--frontend ${config.frontend.join(" ")}`);
403
- else flags.push("--frontend native-bare");
404
- flags.push(`--auth ${config.auth}`);
405
- flags.push(`--mobile-navigation ${config.mobileNavigation}`);
406
- flags.push(`--mobile-ui ${config.mobileUI}`);
407
- flags.push(`--mobile-storage ${config.mobileStorage}`);
408
- flags.push(`--mobile-testing ${config.mobileTesting}`);
409
- flags.push(`--mobile-push ${config.mobilePush}`);
410
- flags.push(`--mobile-ota ${config.mobileOTA}`);
411
- flags.push(`--mobile-deep-linking ${config.mobileDeepLinking}`);
412
- appendCommonFlags(flags, config);
413
- return flags;
414
- }
415
- function getRustFlags(config) {
416
- const flags = ["--ecosystem rust"];
417
- flags.push(`--rust-web-framework ${config.rustWebFramework}`);
418
- flags.push(`--rust-frontend ${config.rustFrontend}`);
419
- flags.push(`--rust-orm ${config.rustOrm}`);
420
- flags.push(`--rust-api ${config.rustApi}`);
421
- flags.push(`--rust-cli ${config.rustCli}`);
422
- flags.push(formatArrayFlag("rust-libraries", config.rustLibraries));
423
- flags.push(`--rust-logging ${config.rustLogging}`);
424
- flags.push(`--rust-error-handling ${config.rustErrorHandling}`);
425
- flags.push(`--rust-caching ${config.rustCaching}`);
426
- flags.push(`--rust-auth ${config.rustAuth}`);
427
- flags.push(`--rust-realtime ${config.rustRealtime}`);
428
- flags.push(`--rust-message-queue ${config.rustMessageQueue}`);
429
- flags.push(`--rust-observability ${config.rustObservability}`);
430
- flags.push(`--rust-templating ${config.rustTemplating}`);
431
- appendSharedNonTypeScriptFlags(flags, config);
432
- appendCommonFlags(flags, config);
433
- return flags;
434
498
  }
435
- function getPythonFlags(config) {
436
- const flags = ["--ecosystem python"];
437
- flags.push(`--python-web-framework ${config.pythonWebFramework}`);
438
- flags.push(`--python-orm ${config.pythonOrm}`);
439
- flags.push(`--python-validation ${config.pythonValidation}`);
440
- flags.push(formatArrayFlag("python-ai", config.pythonAi));
441
- flags.push(`--python-auth ${config.pythonAuth}`);
442
- flags.push(`--python-api ${config.pythonApi}`);
443
- flags.push(`--python-task-queue ${config.pythonTaskQueue}`);
444
- flags.push(`--python-graphql ${config.pythonGraphql}`);
445
- flags.push(`--python-quality ${config.pythonQuality}`);
446
- flags.push(formatArrayFlag("python-testing", config.pythonTesting));
447
- flags.push(`--python-caching ${config.pythonCaching}`);
448
- flags.push(`--python-realtime ${config.pythonRealtime}`);
449
- flags.push(`--python-observability ${config.pythonObservability}`);
450
- flags.push(formatArrayFlag("python-cli", config.pythonCli));
451
- appendSharedNonTypeScriptFlags(flags, config);
452
- appendCommonFlags(flags, config);
453
- return flags;
454
- }
455
- function getGoFlags(config) {
456
- const flags = ["--ecosystem go"];
457
- flags.push(`--go-web-framework ${config.goWebFramework}`);
458
- flags.push(`--go-orm ${config.goOrm}`);
459
- flags.push(`--go-api ${config.goApi}`);
460
- flags.push(`--go-cli ${config.goCli}`);
461
- flags.push(`--go-logging ${config.goLogging}`);
462
- flags.push(`--go-auth ${config.goAuth}`);
463
- flags.push(formatArrayFlag("go-testing", config.goTesting));
464
- flags.push(`--go-realtime ${config.goRealtime}`);
465
- flags.push(`--go-message-queue ${config.goMessageQueue}`);
466
- flags.push(`--go-caching ${config.goCaching}`);
467
- flags.push(`--go-config ${config.goConfig}`);
468
- flags.push(`--go-observability ${config.goObservability}`);
469
- flags.push(`--auth ${config.auth}`);
470
- appendSharedNonTypeScriptFlags(flags, config);
471
- appendCommonFlags(flags, config);
472
- return flags;
473
- }
474
- function getJavaFlags(config) {
475
- const flags = ["--ecosystem java"];
476
- flags.push(`--java-web-framework ${config.javaWebFramework}`);
477
- flags.push(`--java-build-tool ${config.javaBuildTool}`);
478
- flags.push(`--java-orm ${config.javaOrm}`);
479
- flags.push(`--java-auth ${config.javaAuth}`);
480
- flags.push(`--java-api ${config.javaApi}`);
481
- flags.push(`--java-logging ${config.javaLogging}`);
482
- flags.push(formatArrayFlag("java-libraries", config.javaLibraries));
483
- flags.push(formatArrayFlag("java-testing-libraries", config.javaTestingLibraries));
484
- appendSharedNonTypeScriptFlags(flags, config);
485
- appendCommonFlags(flags, config);
486
- return flags;
487
- }
488
- function getDotnetFlags(config) {
489
- const flags = ["--ecosystem dotnet"];
490
- flags.push(`--dotnet-web-framework ${config.dotnetWebFramework}`);
491
- flags.push(`--dotnet-orm ${config.dotnetOrm}`);
492
- flags.push(`--dotnet-auth ${config.dotnetAuth}`);
493
- flags.push(`--dotnet-api ${config.dotnetApi}`);
494
- flags.push(formatArrayFlag("dotnet-testing", config.dotnetTesting));
495
- flags.push(`--dotnet-job-queue ${config.dotnetJobQueue}`);
496
- flags.push(`--dotnet-realtime ${config.dotnetRealtime}`);
497
- flags.push(formatArrayFlag("dotnet-observability", config.dotnetObservability));
498
- flags.push(`--dotnet-validation ${config.dotnetValidation}`);
499
- flags.push(`--dotnet-caching ${config.dotnetCaching}`);
500
- flags.push(`--dotnet-deploy ${config.dotnetDeploy}`);
501
- appendCommonFlags(flags, config);
502
- return flags;
503
- }
504
- function getElixirFlags(config) {
505
- const flags = ["--ecosystem elixir"];
506
- flags.push(`--elixir-web-framework ${config.elixirWebFramework}`);
507
- flags.push(`--elixir-orm ${config.elixirOrm}`);
508
- flags.push(`--elixir-auth ${config.elixirAuth}`);
509
- flags.push(`--elixir-api ${config.elixirApi}`);
510
- flags.push(`--elixir-realtime ${config.elixirRealtime}`);
511
- flags.push(`--elixir-jobs ${config.elixirJobs}`);
512
- flags.push(`--elixir-validation ${config.elixirValidation}`);
513
- flags.push(`--elixir-http ${config.elixirHttp}`);
514
- flags.push(`--elixir-json ${config.elixirJson}`);
515
- flags.push(`--elixir-email ${config.elixirEmail}`);
516
- flags.push(`--elixir-caching ${config.elixirCaching}`);
517
- flags.push(`--elixir-observability ${config.elixirObservability}`);
518
- flags.push(`--elixir-testing ${config.elixirTesting}`);
519
- flags.push(`--elixir-quality ${config.elixirQuality}`);
520
- flags.push(`--elixir-deploy ${config.elixirDeploy}`);
521
- flags.push(formatArrayFlag("elixir-libraries", config.elixirLibraries));
522
- appendCommonFlags(flags, config);
523
- return flags;
499
+ /**
500
+ * Validates that a UI library is compatible with the selected CSS framework
501
+ */
502
+ function validateUILibraryCSSFrameworkCompatibility(uiLibrary, cssFramework) {
503
+ if (!uiLibrary || uiLibrary === "none") return;
504
+ if (!cssFramework) return;
505
+ const supported = getCompatibleCSSFrameworks(uiLibrary);
506
+ if (!supported.includes(cssFramework)) exitWithError(`UI library '${uiLibrary}' is not compatible with '${cssFramework}' CSS framework. Supported CSS frameworks: ${supported.join(", ")}`);
524
507
  }
525
- function generateReproducibleCommand(config) {
526
- let flags;
527
- if (config.stackParts && config.stackParts.length > 0) {
528
- flags = config.stackParts.filter((part) => part.source !== "provided").map((part) => `--part ${(0, types_exports.formatStackPartSpec)(part, config.stackParts ?? [])}`);
529
- appendGraphExtraFlags(flags, config);
530
- appendCommonFlags(flags, config);
531
- return `${getBaseCommand(config.packageManager)}${config.relativePath ? ` ${config.relativePath}` : ""} ${flags.join(" ")}`;
532
- }
533
- switch (config.ecosystem) {
534
- case "react-native":
535
- flags = getReactNativeFlags(config);
536
- break;
537
- case "rust":
538
- flags = getRustFlags(config);
539
- break;
540
- case "python":
541
- flags = getPythonFlags(config);
542
- break;
543
- case "go":
544
- flags = getGoFlags(config);
545
- break;
546
- case "java":
547
- flags = getJavaFlags(config);
548
- break;
549
- case "dotnet":
550
- flags = getDotnetFlags(config);
551
- break;
552
- case "elixir":
553
- flags = getElixirFlags(config);
554
- break;
555
- case "typescript":
556
- default:
557
- flags = getTypeScriptFlags(config);
558
- break;
559
- }
560
- return `${getBaseCommand(config.packageManager)}${config.relativePath ? ` ${config.relativePath}` : ""} ${flags.join(" ")}`;
508
+ /**
509
+ * Gets list of UI libraries compatible with the selected frontend(s)
510
+ */
511
+ function getCompatibleUILibraries$1(frontends = [], astroIntegration) {
512
+ return getCompatibleUILibraries(frontends, astroIntegration);
561
513
  }
562
-
563
- //#endregion
564
- //#region src/utils/templates.ts
565
- const TEMPLATE_PRESETS = {
566
- mern: {
567
- database: "mongodb",
568
- orm: "mongoose",
569
- backend: "express",
570
- runtime: "node",
571
- frontend: ["react-router"],
572
- api: "orpc",
573
- auth: "better-auth",
574
- payments: "none",
575
- addons: ["turborepo"],
576
- examples: ["none"],
577
- dbSetup: "mongodb-atlas",
578
- webDeploy: "none",
579
- serverDeploy: "none"
580
- },
581
- pern: {
582
- database: "postgres",
583
- orm: "drizzle",
584
- backend: "express",
585
- runtime: "node",
586
- frontend: ["tanstack-router"],
587
- api: "trpc",
588
- auth: "better-auth",
589
- payments: "none",
590
- addons: ["turborepo"],
591
- examples: ["none"],
592
- dbSetup: "none",
593
- webDeploy: "none",
594
- serverDeploy: "none"
595
- },
596
- t3: {
597
- database: "postgres",
598
- orm: "prisma",
599
- backend: "self",
600
- runtime: "none",
601
- frontend: ["next"],
602
- api: "trpc",
603
- auth: "better-auth",
604
- payments: "none",
605
- addons: ["biome", "turborepo"],
606
- examples: ["none"],
607
- dbSetup: "none",
608
- webDeploy: "none",
609
- serverDeploy: "none"
610
- },
611
- uniwind: {
612
- database: "none",
613
- orm: "none",
614
- backend: "none",
615
- runtime: "none",
616
- frontend: ["native-uniwind"],
617
- api: "none",
618
- auth: "none",
619
- payments: "none",
620
- addons: ["none"],
621
- examples: ["none"],
622
- dbSetup: "none",
623
- webDeploy: "none",
624
- serverDeploy: "none"
625
- },
626
- none: null
627
- };
628
- function getTemplateConfig(template) {
629
- if (template === "none" || !template) return null;
630
- const config = TEMPLATE_PRESETS[template];
631
- if (!config) throw new Error(`Unknown template: ${template}`);
632
- return config;
514
+ /**
515
+ * Gets list of CSS frameworks compatible with the selected UI library
516
+ */
517
+ function getCompatibleCSSFrameworks$1(uiLibrary) {
518
+ return getCompatibleCSSFrameworks(uiLibrary);
633
519
  }
634
- function getTemplateDescription(template) {
635
- return {
636
- mern: "MongoDB + Express + React + Node.js - Classic MERN stack",
637
- pern: "PostgreSQL + Express + React + Node.js - Popular PERN stack",
638
- t3: "T3 Stack - Next.js + tRPC + Prisma + PostgreSQL + Better Auth",
639
- uniwind: "Expo + Uniwind native app with no backend services",
640
- none: "No template - Full customization"
641
- }[template] || "";
520
+ /**
521
+ * Checks if a frontend has web styling (excludes native-only frontends)
522
+ */
523
+ function hasWebStyling$1(frontends = []) {
524
+ return hasWebStyling(frontends);
642
525
  }
643
526
 
644
527
  //#endregion
@@ -1110,7 +993,7 @@ function validateFrontendConstraints(config, providedFlags) {
1110
993
  ensureSingleWebAndNative(frontend);
1111
994
  if (providedFlags.has("api") && providedFlags.has("frontend") && config.api) validateApiFrontendCompatibility(config.api, frontend, config.astroIntegration);
1112
995
  }
1113
- const hasWebFrontendFlag = (frontend ?? []).some((f) => isWebFrontend(f));
996
+ const hasWebFrontendFlag = (frontend ?? []).some((f) => isWebFrontend$1(f));
1114
997
  validateWebDeployRequiresWebFrontend(config.webDeploy, hasWebFrontendFlag);
1115
998
  validateWebDeployFrontendTemplates(config.webDeploy, frontend);
1116
999
  }
@@ -1509,4 +1392,37 @@ function validateConfigForProgrammaticUse(config) {
1509
1392
  }
1510
1393
 
1511
1394
  //#endregion
1512
- export { generateReproducibleCommand as a, getTemplateDescription as i, validateFullConfig as n, CreateCommandInputSchema as o, getTemplateConfig as r, CreateCommandOptionsSchema as s, validateConfigForProgrammaticUse as t };
1395
+ //#region src/utils/file-formatter.ts
1396
+ const formatOptions = {
1397
+ experimentalSortPackageJson: true,
1398
+ experimentalSortImports: { order: "asc" }
1399
+ };
1400
+ async function formatCode(filePath, content) {
1401
+ try {
1402
+ const result = await format(path.basename(filePath), content, formatOptions);
1403
+ if (result.errors && result.errors.length > 0) return null;
1404
+ return result.code;
1405
+ } catch {
1406
+ return null;
1407
+ }
1408
+ }
1409
+ async function formatProject(projectDir) {
1410
+ async function formatDirectory(dir) {
1411
+ const denoConfigPath = path.join(dir, "deno.json");
1412
+ if (await fs.pathExists(denoConfigPath)) return;
1413
+ const entries = await fs.readdir(dir, { withFileTypes: true });
1414
+ await Promise.all(entries.map(async (entry) => {
1415
+ const fullPath = path.join(dir, entry.name);
1416
+ if (entry.isDirectory()) await formatDirectory(fullPath);
1417
+ else if (entry.isFile()) try {
1418
+ const content = await fs.readFile(fullPath, "utf-8");
1419
+ const formatted = await formatCode(fullPath, content);
1420
+ if (formatted && formatted !== content) await fs.writeFile(fullPath, formatted, "utf-8");
1421
+ } catch {}
1422
+ }));
1423
+ }
1424
+ await formatDirectory(projectDir);
1425
+ }
1426
+
1427
+ //#endregion
1428
+ export { CreateCommandInputSchema as _, allowedApisForFrontends$1 as a, getCompatibleUILibraries$1 as c, isExampleChatSdkAllowed$1 as d, isFrontendAllowedWithBackend$1 as f, validateAddonCompatibility$1 as g, splitFrontends$1 as h, validateFullConfig as i, hasWebStyling$1 as l, requiresChatSdkVercelAI as m, formatProject as n, getCompatibleAddons$1 as o, isWebFrontend$1 as p, validateConfigForProgrammaticUse as r, getCompatibleCSSFrameworks$1 as s, formatCode as t, isExampleAIAllowed$1 as u, CreateCommandOptionsSchema as v };