create-better-fullstack 1.6.2 → 1.7.0

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Better Fullstack
2
2
 
3
- Scaffold production-ready fullstack apps in seconds. Pick your stack from 424 options — the CLI wires everything together.
3
+ Scaffold production-ready fullstack apps in seconds. Pick your stack from 425 options — the CLI wires everything together.
4
4
 
5
5
  ## Quick Start
6
6
 
@@ -31,7 +31,7 @@ Configure your stack visually — pick every option from a UI, preview your choi
31
31
 
32
32
  ## Features
33
33
 
34
- - **424 options** — frontend, backend, database, auth, payments, AI, DevOps, and more
34
+ - **425 options** — frontend, backend, database, auth, payments, AI, DevOps, and more
35
35
  - **5 ecosystems** — TypeScript, Rust, Python, Go, Java
36
36
  - **Visual builder** — configure your stack in the browser
37
37
  - **Wired for you** — every picked integration is preconfigured and working out of the box
package/dist/index.d.mts CHANGED
@@ -148,6 +148,7 @@ declare const router: {
148
148
  langchain: "langchain";
149
149
  llamaindex: "llamaindex";
150
150
  "tanstack-ai": "tanstack-ai";
151
+ "ai-cli": "ai-cli";
151
152
  }>>;
152
153
  realtime: z.ZodOptional<z.ZodEnum<{
153
154
  none: "none";
@@ -273,6 +274,7 @@ declare const router: {
273
274
  "tanstack-virtual": "tanstack-virtual";
274
275
  "tanstack-db": "tanstack-db";
275
276
  "tanstack-pacer": "tanstack-pacer";
277
+ "docker-compose": "docker-compose";
276
278
  }>>>;
277
279
  examples: z.ZodOptional<z.ZodArray<z.ZodEnum<{
278
280
  none: "none";
@@ -638,7 +640,7 @@ declare const router: {
638
640
  backend: "none" | "hono" | "express" | "fastify" | "elysia" | "fets" | "nestjs" | "adonisjs" | "nitro" | "encore" | "convex" | "self";
639
641
  runtime: "none" | "bun" | "node" | "workers";
640
642
  frontend: ("none" | "tanstack-router" | "react-router" | "react-vite" | "tanstack-start" | "next" | "nuxt" | "native-bare" | "native-uniwind" | "native-unistyles" | "svelte" | "solid" | "solid-start" | "astro" | "qwik" | "angular" | "redwood" | "fresh")[];
641
- addons: ("none" | "pwa" | "tauri" | "starlight" | "biome" | "lefthook" | "husky" | "ruler" | "mcp" | "skills" | "turborepo" | "fumadocs" | "ultracite" | "oxlint" | "opentui" | "wxt" | "msw" | "storybook" | "tanstack-query" | "tanstack-table" | "tanstack-virtual" | "tanstack-db" | "tanstack-pacer")[];
643
+ addons: ("none" | "pwa" | "tauri" | "starlight" | "biome" | "lefthook" | "husky" | "ruler" | "mcp" | "skills" | "turborepo" | "fumadocs" | "ultracite" | "oxlint" | "opentui" | "wxt" | "msw" | "storybook" | "tanstack-query" | "tanstack-table" | "tanstack-virtual" | "tanstack-db" | "tanstack-pacer" | "docker-compose")[];
642
644
  examples: ("ai" | "none" | "chat-sdk" | "tanstack-showcase")[];
643
645
  auth: "none" | "better-auth" | "go-better-auth" | "clerk" | "nextauth" | "stack-auth" | "supabase-auth" | "auth0";
644
646
  payments: "none" | "polar" | "stripe" | "lemon-squeezy" | "paddle" | "dodo";
@@ -650,7 +652,7 @@ declare const router: {
650
652
  api: "none" | "trpc" | "orpc" | "ts-rest" | "garph" | "graphql-yoga";
651
653
  webDeploy: "none" | "docker" | "cloudflare" | "fly" | "railway" | "sst" | "vercel";
652
654
  serverDeploy: "none" | "docker" | "cloudflare" | "fly" | "railway" | "sst" | "vercel";
653
- ai: "none" | "langgraph" | "langchain" | "llamaindex" | "vercel-ai" | "mastra" | "voltagent" | "openai-agents" | "google-adk" | "modelfusion" | "tanstack-ai";
655
+ ai: "none" | "langgraph" | "langchain" | "llamaindex" | "vercel-ai" | "mastra" | "voltagent" | "openai-agents" | "google-adk" | "modelfusion" | "tanstack-ai" | "ai-cli";
654
656
  effect: "effect" | "none" | "effect-full";
655
657
  stateManagement: "none" | "zustand" | "jotai" | "nanostores" | "redux-toolkit" | "mobx" | "xstate" | "valtio" | "tanstack-store" | "legend-state";
656
658
  forms: "none" | "tanstack-form" | "react-hook-form" | "formik" | "final-form" | "conform" | "modular-forms";
@@ -734,7 +736,7 @@ declare const router: {
734
736
  backend: "none" | "hono" | "express" | "fastify" | "elysia" | "fets" | "nestjs" | "adonisjs" | "nitro" | "encore" | "convex" | "self";
735
737
  runtime: "none" | "bun" | "node" | "workers";
736
738
  frontend: ("none" | "tanstack-router" | "react-router" | "react-vite" | "tanstack-start" | "next" | "nuxt" | "native-bare" | "native-uniwind" | "native-unistyles" | "svelte" | "solid" | "solid-start" | "astro" | "qwik" | "angular" | "redwood" | "fresh")[];
737
- addons: ("none" | "pwa" | "tauri" | "starlight" | "biome" | "lefthook" | "husky" | "ruler" | "mcp" | "skills" | "turborepo" | "fumadocs" | "ultracite" | "oxlint" | "opentui" | "wxt" | "msw" | "storybook" | "tanstack-query" | "tanstack-table" | "tanstack-virtual" | "tanstack-db" | "tanstack-pacer")[];
739
+ addons: ("none" | "pwa" | "tauri" | "starlight" | "biome" | "lefthook" | "husky" | "ruler" | "mcp" | "skills" | "turborepo" | "fumadocs" | "ultracite" | "oxlint" | "opentui" | "wxt" | "msw" | "storybook" | "tanstack-query" | "tanstack-table" | "tanstack-virtual" | "tanstack-db" | "tanstack-pacer" | "docker-compose")[];
738
740
  examples: ("ai" | "none" | "chat-sdk" | "tanstack-showcase")[];
739
741
  auth: "none" | "better-auth" | "go-better-auth" | "clerk" | "nextauth" | "stack-auth" | "supabase-auth" | "auth0";
740
742
  payments: "none" | "polar" | "stripe" | "lemon-squeezy" | "paddle" | "dodo";
@@ -746,7 +748,7 @@ declare const router: {
746
748
  api: "none" | "trpc" | "orpc" | "ts-rest" | "garph" | "graphql-yoga";
747
749
  webDeploy: "none" | "docker" | "cloudflare" | "fly" | "railway" | "sst" | "vercel";
748
750
  serverDeploy: "none" | "docker" | "cloudflare" | "fly" | "railway" | "sst" | "vercel";
749
- ai: "none" | "langgraph" | "langchain" | "llamaindex" | "vercel-ai" | "mastra" | "voltagent" | "openai-agents" | "google-adk" | "modelfusion" | "tanstack-ai";
751
+ ai: "none" | "langgraph" | "langchain" | "llamaindex" | "vercel-ai" | "mastra" | "voltagent" | "openai-agents" | "google-adk" | "modelfusion" | "tanstack-ai" | "ai-cli";
750
752
  effect: "effect" | "none" | "effect-full";
751
753
  stateManagement: "none" | "zustand" | "jotai" | "nanostores" | "redux-toolkit" | "mobx" | "xstate" | "valtio" | "tanstack-store" | "legend-state";
752
754
  forms: "none" | "tanstack-form" | "react-hook-form" | "formik" | "final-form" | "conform" | "modular-forms";
@@ -843,7 +845,7 @@ declare const router: {
843
845
  backend: "none" | "hono" | "express" | "fastify" | "elysia" | "fets" | "nestjs" | "adonisjs" | "nitro" | "encore" | "convex" | "self";
844
846
  runtime: "none" | "bun" | "node" | "workers";
845
847
  frontend: ("none" | "tanstack-router" | "react-router" | "react-vite" | "tanstack-start" | "next" | "nuxt" | "native-bare" | "native-uniwind" | "native-unistyles" | "svelte" | "solid" | "solid-start" | "astro" | "qwik" | "angular" | "redwood" | "fresh")[];
846
- addons: ("none" | "pwa" | "tauri" | "starlight" | "biome" | "lefthook" | "husky" | "ruler" | "mcp" | "skills" | "turborepo" | "fumadocs" | "ultracite" | "oxlint" | "opentui" | "wxt" | "msw" | "storybook" | "tanstack-query" | "tanstack-table" | "tanstack-virtual" | "tanstack-db" | "tanstack-pacer")[];
848
+ addons: ("none" | "pwa" | "tauri" | "starlight" | "biome" | "lefthook" | "husky" | "ruler" | "mcp" | "skills" | "turborepo" | "fumadocs" | "ultracite" | "oxlint" | "opentui" | "wxt" | "msw" | "storybook" | "tanstack-query" | "tanstack-table" | "tanstack-virtual" | "tanstack-db" | "tanstack-pacer" | "docker-compose")[];
847
849
  examples: ("ai" | "none" | "chat-sdk" | "tanstack-showcase")[];
848
850
  auth: "none" | "better-auth" | "go-better-auth" | "clerk" | "nextauth" | "stack-auth" | "supabase-auth" | "auth0";
849
851
  payments: "none" | "polar" | "stripe" | "lemon-squeezy" | "paddle" | "dodo";
@@ -855,7 +857,7 @@ declare const router: {
855
857
  api: "none" | "trpc" | "orpc" | "ts-rest" | "garph" | "graphql-yoga";
856
858
  webDeploy: "none" | "docker" | "cloudflare" | "fly" | "railway" | "sst" | "vercel";
857
859
  serverDeploy: "none" | "docker" | "cloudflare" | "fly" | "railway" | "sst" | "vercel";
858
- ai: "none" | "langgraph" | "langchain" | "llamaindex" | "vercel-ai" | "mastra" | "voltagent" | "openai-agents" | "google-adk" | "modelfusion" | "tanstack-ai";
860
+ ai: "none" | "langgraph" | "langchain" | "llamaindex" | "vercel-ai" | "mastra" | "voltagent" | "openai-agents" | "google-adk" | "modelfusion" | "tanstack-ai" | "ai-cli";
859
861
  effect: "effect" | "none" | "effect-full";
860
862
  stateManagement: "none" | "zustand" | "jotai" | "nanostores" | "redux-toolkit" | "mobx" | "xstate" | "valtio" | "tanstack-store" | "legend-state";
861
863
  forms: "none" | "tanstack-form" | "react-hook-form" | "formik" | "final-form" | "conform" | "modular-forms";
@@ -939,7 +941,7 @@ declare const router: {
939
941
  backend: "none" | "hono" | "express" | "fastify" | "elysia" | "fets" | "nestjs" | "adonisjs" | "nitro" | "encore" | "convex" | "self";
940
942
  runtime: "none" | "bun" | "node" | "workers";
941
943
  frontend: ("none" | "tanstack-router" | "react-router" | "react-vite" | "tanstack-start" | "next" | "nuxt" | "native-bare" | "native-uniwind" | "native-unistyles" | "svelte" | "solid" | "solid-start" | "astro" | "qwik" | "angular" | "redwood" | "fresh")[];
942
- addons: ("none" | "pwa" | "tauri" | "starlight" | "biome" | "lefthook" | "husky" | "ruler" | "mcp" | "skills" | "turborepo" | "fumadocs" | "ultracite" | "oxlint" | "opentui" | "wxt" | "msw" | "storybook" | "tanstack-query" | "tanstack-table" | "tanstack-virtual" | "tanstack-db" | "tanstack-pacer")[];
944
+ addons: ("none" | "pwa" | "tauri" | "starlight" | "biome" | "lefthook" | "husky" | "ruler" | "mcp" | "skills" | "turborepo" | "fumadocs" | "ultracite" | "oxlint" | "opentui" | "wxt" | "msw" | "storybook" | "tanstack-query" | "tanstack-table" | "tanstack-virtual" | "tanstack-db" | "tanstack-pacer" | "docker-compose")[];
943
945
  examples: ("ai" | "none" | "chat-sdk" | "tanstack-showcase")[];
944
946
  auth: "none" | "better-auth" | "go-better-auth" | "clerk" | "nextauth" | "stack-auth" | "supabase-auth" | "auth0";
945
947
  payments: "none" | "polar" | "stripe" | "lemon-squeezy" | "paddle" | "dodo";
@@ -951,7 +953,7 @@ declare const router: {
951
953
  api: "none" | "trpc" | "orpc" | "ts-rest" | "garph" | "graphql-yoga";
952
954
  webDeploy: "none" | "docker" | "cloudflare" | "fly" | "railway" | "sst" | "vercel";
953
955
  serverDeploy: "none" | "docker" | "cloudflare" | "fly" | "railway" | "sst" | "vercel";
954
- ai: "none" | "langgraph" | "langchain" | "llamaindex" | "vercel-ai" | "mastra" | "voltagent" | "openai-agents" | "google-adk" | "modelfusion" | "tanstack-ai";
956
+ ai: "none" | "langgraph" | "langchain" | "llamaindex" | "vercel-ai" | "mastra" | "voltagent" | "openai-agents" | "google-adk" | "modelfusion" | "tanstack-ai" | "ai-cli";
955
957
  effect: "effect" | "none" | "effect-full";
956
958
  stateManagement: "none" | "zustand" | "jotai" | "nanostores" | "redux-toolkit" | "mobx" | "xstate" | "valtio" | "tanstack-store" | "legend-state";
957
959
  forms: "none" | "tanstack-form" | "react-hook-form" | "formik" | "final-form" | "conform" | "modular-forms";
@@ -1065,6 +1067,7 @@ declare const router: {
1065
1067
  "tanstack-virtual": "tanstack-virtual";
1066
1068
  "tanstack-db": "tanstack-db";
1067
1069
  "tanstack-pacer": "tanstack-pacer";
1070
+ "docker-compose": "docker-compose";
1068
1071
  }>>>;
1069
1072
  webDeploy: z.ZodOptional<z.ZodEnum<{
1070
1073
  none: "none";
package/dist/index.mjs CHANGED
@@ -10,7 +10,7 @@ import z from "zod";
10
10
  import envPaths from "env-paths";
11
11
  import fs from "fs-extra";
12
12
  import path from "node:path";
13
- import { allowedApisForFrontends, getCompatibleAddons, getCompatibleCSSFrameworks, getCompatibleUILibraries, getLocalWebDevPort, hasWebStyling, isExampleAIAllowed, isExampleChatSdkAllowed, isFrontendAllowedWithBackend, isWebFrontend, requiresChatSdkVercelAIForSelection, splitFrontends, validateAddonCompatibility } from "@better-fullstack/types";
13
+ import { allowedApisForFrontends, getAIFrontendCompatibilityIssue, getApiFrontendCompatibilityIssue, getCompatibleAddons, getCompatibleCSSFrameworks, getCompatibleUILibraries, getLocalWebDevPort, hasDockerComposeCompatibleFrontend, hasWebStyling, isExampleAIAllowed, isExampleChatSdkAllowed, isFrontendAllowedWithBackend, isWebFrontend, requiresChatSdkVercelAIForSelection, splitFrontends, validateAddonCompatibility } from "@better-fullstack/types";
14
14
  import { ECOSYSTEM_GROUPS, EMBEDDED_TEMPLATES, EMBEDDED_TEMPLATES as EMBEDDED_TEMPLATES$1, TEMPLATE_COUNT, VirtualFileSystem, VirtualFileSystem as VirtualFileSystem$1, checkAllVersions, generateCliReport, generateVirtualProject, generateVirtualProject as generateVirtualProject$1, listEcosystems, processAddonTemplates, processAddonsDeps, validatePreflightConfig } from "@better-fullstack/template-generator";
15
15
  import gradient from "gradient-string";
16
16
  import path$1 from "path";
@@ -18,6 +18,7 @@ import { writeTreeToFilesystem } from "@better-fullstack/template-generator/fs-w
18
18
  import consola, { consola as consola$1 } from "consola";
19
19
  import { ConfirmPrompt, GroupMultiSelectPrompt, MultiSelectPrompt, SelectPrompt, isCancel as isCancel$1 } from "@clack/core";
20
20
  import { $, execa } from "execa";
21
+ import { cliInputToProjectConfigPartial } from "@better-fullstack/types/stack-translation";
21
22
  import os$1 from "node:os";
22
23
  import { format } from "oxfmt";
23
24
 
@@ -602,74 +603,12 @@ function validateWorkersCompatibility(providedFlags, options, config) {
602
603
  });
603
604
  }
604
605
  function validateApiFrontendCompatibility(api, frontends = [], astroIntegration) {
605
- const includesNuxt = frontends.includes("nuxt");
606
- const includesSvelte = frontends.includes("svelte");
607
- const includesSolid = frontends.includes("solid");
608
- const includesAstro = frontends.includes("astro");
609
- const includesQwik = frontends.includes("qwik");
610
- const includesAngular = frontends.includes("angular");
611
- const includesRedwood = frontends.includes("redwood");
612
- const includesFresh = frontends.includes("fresh");
613
- const includesSolidStart = frontends.includes("solid-start");
614
- if ((includesNuxt || includesSvelte || includesSolid || includesSolidStart) && (api === "trpc" || api === "ts-rest" || api === "garph")) {
615
- const apiName = api === "trpc" ? "tRPC" : api === "ts-rest" ? "ts-rest" : "garph";
616
- const incompatibleFrontend = includesNuxt ? "nuxt" : includesSvelte ? "svelte" : includesSolid ? "solid" : "solid-start";
617
- incompatibilityError({
618
- message: `${apiName} API requires React-based frontends.`,
619
- provided: {
620
- api,
621
- frontend: incompatibleFrontend
622
- },
623
- suggestions: [
624
- "Use --api orpc (works with all frontends)",
625
- "Use --api none",
626
- "Choose next, react-router, react-vite, or tanstack-start"
627
- ]
628
- });
629
- }
630
- if (includesQwik && api && api !== "none") incompatibilityError({
631
- message: "Qwik has built-in server capabilities and doesn't support external APIs.",
632
- provided: {
633
- api,
634
- frontend: "qwik"
635
- },
636
- suggestions: ["Use --api none with Qwik"]
637
- });
638
- if (includesAngular && api && api !== "none") incompatibilityError({
639
- message: "Angular has built-in HttpClient and doesn't support external APIs.",
640
- provided: {
641
- api,
642
- frontend: "angular"
643
- },
644
- suggestions: ["Use --api none with Angular"]
645
- });
646
- if (includesRedwood && api && api !== "none") incompatibilityError({
647
- message: "RedwoodJS has built-in GraphQL API and doesn't support external APIs.",
648
- provided: {
649
- api,
650
- frontend: "redwood"
651
- },
652
- suggestions: ["Use --api none with RedwoodJS"]
653
- });
654
- if (includesFresh && api && api !== "none") incompatibilityError({
655
- message: "Fresh has built-in server capabilities and doesn't support external APIs.",
656
- provided: {
657
- api,
658
- frontend: "fresh"
659
- },
660
- suggestions: ["Use --api none with Fresh"]
661
- });
662
- if (includesAstro && astroIntegration && astroIntegration !== "react" && (api === "trpc" || api === "ts-rest" || api === "garph")) incompatibilityError({
663
- message: `${api === "trpc" ? "tRPC" : api === "ts-rest" ? "ts-rest" : "garph"} API requires React integration with Astro.`,
664
- provided: {
665
- api,
666
- "astro-integration": astroIntegration
667
- },
668
- suggestions: [
669
- "Use --api orpc (works with all Astro integrations)",
670
- "Use --api none",
671
- "Use --astro-integration react"
672
- ]
606
+ const issue = getApiFrontendCompatibilityIssue(api, frontends, astroIntegration);
607
+ if (!issue) return;
608
+ incompatibilityError({
609
+ message: issue.message,
610
+ provided: issue.provided ?? {},
611
+ suggestions: issue.suggestions ?? []
673
612
  });
674
613
  }
675
614
  function isFrontendAllowedWithBackend$1(frontend, backend, auth) {
@@ -693,16 +632,51 @@ function validateWebDeployRequiresWebFrontend(webDeploy, hasWebFrontendFlag) {
693
632
  function validateServerDeployRequiresBackend(serverDeploy, backend) {
694
633
  if (serverDeploy && serverDeploy !== "none" && (!backend || backend === "none")) exitWithError("'--server-deploy' requires a backend. Please select a backend or set '--server-deploy none'.");
695
634
  }
696
- function validateAddonCompatibility$1(addon, frontend, _auth) {
697
- return validateAddonCompatibility(addon, frontend, _auth);
635
+ function validateAddonCompatibility$1(addon, frontend, _auth, backend, runtime, ecosystem, rustFrontend, javaWebFramework, database) {
636
+ const baseCompatibility = validateAddonCompatibility(addon, frontend, _auth);
637
+ if (!baseCompatibility.isCompatible) return baseCompatibility;
638
+ if (addon === "docker-compose") {
639
+ if (backend === "convex") return {
640
+ isCompatible: false,
641
+ reason: "docker-compose is not compatible with Convex backend (managed service)"
642
+ };
643
+ if (runtime === "workers") return {
644
+ isCompatible: false,
645
+ reason: "docker-compose is not compatible with Cloudflare Workers runtime"
646
+ };
647
+ if (ecosystem === "typescript" && !hasDockerComposeCompatibleFrontend(frontend)) return {
648
+ isCompatible: false,
649
+ reason: "Docker Compose currently supports Next.js, TanStack Router, React Router, React Vite, Solid, or Astro"
650
+ };
651
+ if (ecosystem === "typescript" && backend === "self" && !frontend.includes("next")) return {
652
+ isCompatible: false,
653
+ reason: "Docker Compose self-backend support currently requires Next.js"
654
+ };
655
+ if (ecosystem === "rust" && rustFrontend && rustFrontend !== "none") return {
656
+ isCompatible: false,
657
+ reason: "Docker Compose for Rust currently supports server-only projects"
658
+ };
659
+ if (ecosystem === "java" && javaWebFramework && javaWebFramework !== "spring-boot") return {
660
+ isCompatible: false,
661
+ reason: "Docker Compose for Java currently requires Spring Boot"
662
+ };
663
+ if (ecosystem === "python" && database && database !== "none" && database !== "sqlite" && database !== "postgres") return {
664
+ isCompatible: false,
665
+ reason: "Docker Compose for Python ORM projects currently supports SQLite defaults or Postgres"
666
+ };
667
+ }
668
+ return { isCompatible: true };
698
669
  }
699
- function getCompatibleAddons$1(allAddons, frontend, existingAddons = [], auth) {
700
- return getCompatibleAddons(allAddons, frontend, existingAddons, auth);
670
+ function getCompatibleAddons$1(allAddons, frontend, existingAddons = [], auth, backend, runtime) {
671
+ return getCompatibleAddons(allAddons, frontend, existingAddons, auth).filter((addon) => {
672
+ const { isCompatible } = validateAddonCompatibility$1(addon, frontend, auth, backend, runtime);
673
+ return isCompatible;
674
+ });
701
675
  }
702
- function validateAddonsAgainstFrontends(addons = [], frontends = [], auth) {
676
+ function validateAddonsAgainstFrontends(addons = [], frontends = [], auth, backend, runtime, ecosystem, rustFrontend, javaWebFramework, database) {
703
677
  for (const addon of addons) {
704
678
  if (addon === "none") continue;
705
- const { isCompatible, reason } = validateAddonCompatibility$1(addon, frontends, auth);
679
+ const { isCompatible, reason } = validateAddonCompatibility$1(addon, frontends, auth, backend, runtime, ecosystem, rustFrontend, javaWebFramework, database);
706
680
  if (!isCompatible) exitWithError(`Incompatible addon/frontend combination: ${reason}`);
707
681
  }
708
682
  }
@@ -748,18 +722,9 @@ function validateExamplesCompatibility(examples, backend, frontend, runtime, ai)
748
722
  * Server-side @tanstack/ai core works anywhere, but client adapters only exist for React and Solid.
749
723
  */
750
724
  function validateAIFrontendCompatibility(ai, frontends = []) {
751
- if (!ai || ai !== "tanstack-ai") return;
752
- const compatibleFrontends = [
753
- "tanstack-router",
754
- "react-router",
755
- "react-vite",
756
- "tanstack-start",
757
- "next",
758
- "redwood",
759
- "solid",
760
- "solid-start"
761
- ];
762
- if (!frontends.some((f) => compatibleFrontends.includes(f))) exitWithError("TanStack AI requires React or Solid frontend (no Vue/Svelte/Angular adapter yet). Please use a React-based frontend (Next.js, TanStack Router, React Router, etc.) or Solid.");
725
+ const issue = getAIFrontendCompatibilityIssue(ai, frontends);
726
+ if (!issue) return;
727
+ exitWithError(issue.message);
763
728
  }
764
729
  /**
765
730
  * Validates that a UI library is compatible with the selected frontend(s)
@@ -1147,6 +1112,10 @@ function getAddonDisplay(addon) {
1147
1112
  label = "TanStack Pacer";
1148
1113
  hint = "Debounce, throttle, rate-limit & queue utilities (Beta)";
1149
1114
  break;
1115
+ case "docker-compose":
1116
+ label = "Docker Compose";
1117
+ hint = "Containerize your app for deployment";
1118
+ break;
1150
1119
  default:
1151
1120
  label = addon;
1152
1121
  hint = `Add ${addon}`;
@@ -1171,7 +1140,8 @@ const ADDON_GROUPS = {
1171
1140
  "tauri",
1172
1141
  "opentui",
1173
1142
  "wxt",
1174
- "ruler"
1143
+ "ruler",
1144
+ "docker-compose"
1175
1145
  ],
1176
1146
  Integrations: ["msw", "storybook"],
1177
1147
  "AI Agents": ["mcp", "skills"],
@@ -1183,7 +1153,13 @@ const ADDON_GROUPS = {
1183
1153
  "tanstack-pacer"
1184
1154
  ]
1185
1155
  };
1186
- async function getAddonsChoice(addons, frontends, auth) {
1156
+ function validateAddonCompatibilityForPrompt(addon, frontends, auth, backend, runtime) {
1157
+ return validateAddonCompatibility$1(addon, frontends, auth, backend, runtime);
1158
+ }
1159
+ function getCompatibleAddonsForPrompt(allAddons, frontends, existingAddons = [], auth, backend, runtime) {
1160
+ return getCompatibleAddons$1(allAddons, frontends, existingAddons, auth, backend, runtime);
1161
+ }
1162
+ async function getAddonsChoice(addons, frontends, auth, backend, runtime) {
1187
1163
  if (addons !== void 0) return addons;
1188
1164
  const allAddons = types_exports.AddonsSchema.options.filter((addon) => addon !== "none");
1189
1165
  const groupedOptions = {
@@ -1196,7 +1172,7 @@ async function getAddonsChoice(addons, frontends, auth) {
1196
1172
  };
1197
1173
  const frontendsArray = frontends || [];
1198
1174
  for (const addon of allAddons) {
1199
- const { isCompatible } = validateAddonCompatibility$1(addon, frontendsArray, auth);
1175
+ const { isCompatible } = validateAddonCompatibilityForPrompt(addon, frontendsArray, auth, backend, runtime);
1200
1176
  if (!isCompatible) continue;
1201
1177
  const { label, hint } = getAddonDisplay(addon);
1202
1178
  const option = {
@@ -1229,7 +1205,7 @@ async function getAddonsChoice(addons, frontends, auth) {
1229
1205
  if (isCancel$1(response)) return exitCancelled("Operation cancelled");
1230
1206
  return response;
1231
1207
  }
1232
- async function getAddonsToAdd(frontend, existingAddons = [], auth) {
1208
+ async function getAddonsToAdd(frontend, existingAddons = [], auth, backend, runtime) {
1233
1209
  const groupedOptions = {
1234
1210
  Tooling: [],
1235
1211
  Documentation: [],
@@ -1239,7 +1215,7 @@ async function getAddonsToAdd(frontend, existingAddons = [], auth) {
1239
1215
  TanStack: []
1240
1216
  };
1241
1217
  const frontendArray = frontend || [];
1242
- const compatibleAddons = getCompatibleAddons$1(types_exports.AddonsSchema.options.filter((addon) => addon !== "none"), frontendArray, existingAddons, auth);
1218
+ const compatibleAddons = getCompatibleAddonsForPrompt(types_exports.AddonsSchema.options.filter((addon) => addon !== "none"), frontendArray, existingAddons, auth, backend, runtime);
1243
1219
  for (const addon of compatibleAddons) {
1244
1220
  const { label, hint } = getAddonDisplay(addon);
1245
1221
  const option = {
@@ -1436,16 +1412,20 @@ async function applyDependencyVersionChannel(projectDir, channel) {
1436
1412
  //#endregion
1437
1413
  //#region src/helpers/core/install-dependencies.ts
1438
1414
  function getInstallEnvironment(packageManager) {
1439
- if (packageManager !== "yarn") return;
1440
- return {
1415
+ if (packageManager === "yarn") return {
1441
1416
  YARN_ENABLE_HARDENED_MODE: "0",
1442
1417
  YARN_ENABLE_IMMUTABLE_INSTALLS: "false"
1443
1418
  };
1444
1419
  }
1420
+ function getInstallArgs(packageManager) {
1421
+ if (packageManager === "pnpm") return ["install", "--dangerously-allow-all-builds"];
1422
+ return ["install"];
1423
+ }
1445
1424
  async function installDependencies({ projectDir, packageManager }) {
1446
1425
  const s = spinner();
1447
1426
  try {
1448
1427
  s.start(`Running ${packageManager} install...`);
1428
+ const installArgs = getInstallArgs(packageManager);
1449
1429
  await $({
1450
1430
  cwd: projectDir,
1451
1431
  env: {
@@ -1453,7 +1433,7 @@ async function installDependencies({ projectDir, packageManager }) {
1453
1433
  ...getInstallEnvironment(packageManager)
1454
1434
  },
1455
1435
  stderr: "inherit"
1456
- })`${packageManager} install`;
1436
+ })`${packageManager} ${installArgs}`;
1457
1437
  s.stop("Dependencies installed successfully");
1458
1438
  } catch (error) {
1459
1439
  s.stop(pc.red("Failed to install dependencies"));
@@ -1752,6 +1732,11 @@ const AI_PROMPT_OPTIONS = [
1752
1732
  label: "TanStack AI",
1753
1733
  hint: "Unified LLM interface for AI-powered apps (Alpha)"
1754
1734
  },
1735
+ {
1736
+ value: "ai-cli",
1737
+ label: "AI CLI",
1738
+ hint: "Agent-native terminal CLI for generating text, images, and video"
1739
+ },
1755
1740
  {
1756
1741
  value: "none",
1757
1742
  label: "None",
@@ -5623,7 +5608,7 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
5623
5608
  return getRuntimeChoice(flags.runtime, results.backend);
5624
5609
  },
5625
5610
  database: ({ results }) => {
5626
- if (results.ecosystem !== "typescript") return Promise.resolve("none");
5611
+ if (results.ecosystem !== "typescript") return Promise.resolve(results.ecosystem === "python" ? flags.database ?? "none" : "none");
5627
5612
  return getDatabaseChoice(flags.database, results.backend, results.runtime);
5628
5613
  },
5629
5614
  orm: ({ results }) => {
@@ -5652,8 +5637,11 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
5652
5637
  return getEffectChoice(flags.effect);
5653
5638
  },
5654
5639
  addons: ({ results }) => {
5655
- if (results.ecosystem !== "typescript") return Promise.resolve([]);
5656
- return getAddonsChoice(flags.addons, results.frontend, results.auth);
5640
+ if (results.ecosystem !== "typescript") {
5641
+ const nonTypeScriptAddons = (flags.addons ?? []).filter((addon) => addon === "docker-compose");
5642
+ return Promise.resolve(nonTypeScriptAddons);
5643
+ }
5644
+ return getAddonsChoice(flags.addons, results.frontend, results.auth, results.backend, results.runtime);
5657
5645
  },
5658
5646
  examples: ({ results }) => {
5659
5647
  if (results.ecosystem !== "typescript") return Promise.resolve([]);
@@ -6463,100 +6451,14 @@ function getTemplateDescription(template) {
6463
6451
 
6464
6452
  //#endregion
6465
6453
  //#region src/utils/config-processing.ts
6466
- function processArrayOption(options) {
6467
- if (!options || options.length === 0) return [];
6468
- if (options.includes("none")) return [];
6469
- return options.filter((item) => item !== "none");
6470
- }
6471
6454
  function deriveProjectName(projectName, projectDirectory) {
6472
6455
  if (projectName) return projectName;
6473
6456
  if (projectDirectory) return path.basename(path.resolve(process.cwd(), projectDirectory));
6474
6457
  return "";
6475
6458
  }
6476
6459
  function processFlags(options, projectName) {
6477
- const config = {};
6478
- if (options.ecosystem) config.ecosystem = options.ecosystem;
6479
- if (options.api) config.api = options.api;
6480
- if (options.backend) config.backend = options.backend;
6481
- if (options.database) config.database = options.database;
6482
- if (options.orm) config.orm = options.orm;
6483
- if (options.auth !== void 0) config.auth = options.auth;
6484
- if (options.payments !== void 0) config.payments = options.payments;
6485
- if (options.email !== void 0) config.email = options.email;
6486
- if (options.effect !== void 0) config.effect = options.effect;
6487
- if (options.stateManagement !== void 0) config.stateManagement = options.stateManagement;
6488
- if (options.validation !== void 0) config.validation = options.validation;
6489
- if (options.realtime !== void 0) config.realtime = options.realtime;
6490
- if (options.jobQueue !== void 0) config.jobQueue = options.jobQueue;
6491
- if (options.animation !== void 0) config.animation = options.animation;
6492
- if (options.ai !== void 0) config.ai = options.ai;
6493
- if (options.forms !== void 0) config.forms = options.forms;
6494
- if (options.testing !== void 0) config.testing = options.testing;
6495
- if (options.logging !== void 0) config.logging = options.logging;
6496
- if (options.observability !== void 0) config.observability = options.observability;
6497
- if (options.cms !== void 0) config.cms = options.cms;
6498
- if (options.caching !== void 0) config.caching = options.caching;
6499
- if (options.i18n !== void 0) config.i18n = options.i18n;
6500
- if (options.search !== void 0) config.search = options.search;
6501
- if (options.fileStorage !== void 0) config.fileStorage = options.fileStorage;
6502
- if (options.analytics !== void 0) config.analytics = options.analytics;
6503
- if (options.featureFlags !== void 0) config.featureFlags = options.featureFlags;
6504
- if (options.fileUpload !== void 0) config.fileUpload = options.fileUpload;
6505
- if (options.git !== void 0) config.git = options.git;
6506
- if (options.install !== void 0) config.install = options.install;
6507
- if (options.runtime) config.runtime = options.runtime;
6508
- if (options.dbSetup) config.dbSetup = options.dbSetup;
6509
- if (options.packageManager) config.packageManager = options.packageManager;
6510
- if (options.versionChannel) config.versionChannel = options.versionChannel;
6511
- if (options.webDeploy) config.webDeploy = options.webDeploy;
6512
- if (options.serverDeploy) config.serverDeploy = options.serverDeploy;
6513
6460
  const derivedName = deriveProjectName(projectName, options.projectDirectory);
6514
- if (derivedName) config.projectName = projectName || derivedName;
6515
- if (options.frontend && options.frontend.length > 0) config.frontend = processArrayOption(options.frontend);
6516
- if (options.astroIntegration) config.astroIntegration = options.astroIntegration;
6517
- if (options.cssFramework) config.cssFramework = options.cssFramework;
6518
- if (options.uiLibrary) config.uiLibrary = options.uiLibrary;
6519
- if (options.shadcnBase) config.shadcnBase = options.shadcnBase;
6520
- if (options.shadcnStyle) config.shadcnStyle = options.shadcnStyle;
6521
- if (options.shadcnIconLibrary) config.shadcnIconLibrary = options.shadcnIconLibrary;
6522
- if (options.shadcnColorTheme) config.shadcnColorTheme = options.shadcnColorTheme;
6523
- if (options.shadcnBaseColor) config.shadcnBaseColor = options.shadcnBaseColor;
6524
- if (options.shadcnFont) config.shadcnFont = options.shadcnFont;
6525
- if (options.shadcnRadius) config.shadcnRadius = options.shadcnRadius;
6526
- if (options.addons && options.addons.length > 0) config.addons = processArrayOption(options.addons);
6527
- if (options.examples && options.examples.length > 0) config.examples = processArrayOption(options.examples);
6528
- if (options.aiDocs !== void 0) config.aiDocs = processArrayOption(options.aiDocs);
6529
- if (options.rustWebFramework !== void 0) config.rustWebFramework = options.rustWebFramework;
6530
- if (options.rustFrontend !== void 0) config.rustFrontend = options.rustFrontend;
6531
- if (options.rustOrm !== void 0) config.rustOrm = options.rustOrm;
6532
- if (options.rustApi !== void 0) config.rustApi = options.rustApi;
6533
- if (options.rustCli !== void 0) config.rustCli = options.rustCli;
6534
- if (options.rustLibraries !== void 0) config.rustLibraries = processArrayOption(options.rustLibraries);
6535
- if (options.rustLogging !== void 0) config.rustLogging = options.rustLogging;
6536
- if (options.rustErrorHandling !== void 0) config.rustErrorHandling = options.rustErrorHandling;
6537
- if (options.rustCaching !== void 0) config.rustCaching = options.rustCaching;
6538
- if (options.rustAuth !== void 0) config.rustAuth = options.rustAuth;
6539
- if (options.pythonWebFramework !== void 0) config.pythonWebFramework = options.pythonWebFramework;
6540
- if (options.pythonOrm !== void 0) config.pythonOrm = options.pythonOrm;
6541
- if (options.pythonValidation !== void 0) config.pythonValidation = options.pythonValidation;
6542
- if (options.pythonAi !== void 0) config.pythonAi = processArrayOption(options.pythonAi);
6543
- if (options.pythonAuth !== void 0) config.pythonAuth = options.pythonAuth;
6544
- if (options.pythonTaskQueue !== void 0) config.pythonTaskQueue = options.pythonTaskQueue;
6545
- if (options.pythonGraphql !== void 0) config.pythonGraphql = options.pythonGraphql;
6546
- if (options.pythonQuality !== void 0) config.pythonQuality = options.pythonQuality;
6547
- if (options.goWebFramework !== void 0) config.goWebFramework = options.goWebFramework;
6548
- if (options.goOrm !== void 0) config.goOrm = options.goOrm;
6549
- if (options.goApi !== void 0) config.goApi = options.goApi;
6550
- if (options.goCli !== void 0) config.goCli = options.goCli;
6551
- if (options.goLogging !== void 0) config.goLogging = options.goLogging;
6552
- if (options.goAuth !== void 0) config.goAuth = options.goAuth;
6553
- if (options.javaWebFramework !== void 0) config.javaWebFramework = options.javaWebFramework;
6554
- if (options.javaBuildTool !== void 0) config.javaBuildTool = options.javaBuildTool;
6555
- if (options.javaOrm !== void 0) config.javaOrm = options.javaOrm;
6556
- if (options.javaAuth !== void 0) config.javaAuth = options.javaAuth;
6557
- if (options.javaLibraries !== void 0) config.javaLibraries = processArrayOption(options.javaLibraries);
6558
- if (options.javaTestingLibraries !== void 0) config.javaTestingLibraries = processArrayOption(options.javaTestingLibraries);
6559
- return config;
6461
+ return cliInputToProjectConfigPartial(options, projectName || derivedName);
6560
6462
  }
6561
6463
  function getProvidedFlags(options) {
6562
6464
  return new Set(Object.keys(options).filter((key) => options[key] !== void 0));
@@ -7095,7 +6997,7 @@ function validateFullConfig(config, providedFlags, options) {
7095
6997
  suggestions: ["Use --server-deploy fly or --server-deploy railway for NestJS/AdonisJS", "Switch to a serverless-compatible backend like Hono or Express"]
7096
6998
  });
7097
6999
  if (config.addons && config.addons.length > 0) {
7098
- validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth);
7000
+ validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth, config.backend, config.runtime, config.ecosystem, config.rustFrontend, config.javaWebFramework, config.database);
7099
7001
  config.addons = [...new Set(config.addons)];
7100
7002
  }
7101
7003
  validateExamplesCompatibility(config.examples ?? [], config.backend, config.frontend ?? [], config.runtime, config.ai);
@@ -7114,7 +7016,7 @@ function validateConfigForProgrammaticUse(config) {
7114
7016
  validateApiFrontendCompatibility(config.api, config.frontend, config.astroIntegration);
7115
7017
  validateJavaConstraints(config);
7116
7018
  validatePaymentsCompatibility(config.payments, config.auth, config.backend, config.frontend);
7117
- if (config.addons && config.addons.length > 0) validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth);
7019
+ if (config.addons && config.addons.length > 0) validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth, config.backend, config.runtime, config.ecosystem, config.rustFrontend, config.javaWebFramework, config.database);
7118
7020
  validateExamplesCompatibility(config.examples ?? [], config.backend, config.frontend ?? [], config.runtime, config.ai);
7119
7021
  validateAIFrontendCompatibility(config.ai, config.frontend ?? []);
7120
7022
  validateUILibraryFrontendCompatibility(config.uiLibrary, config.frontend ?? [], config.astroIntegration);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-better-fullstack",
3
- "version": "1.6.2",
4
- "description": "Scaffold production-ready fullstack apps in seconds. Pick your stack from 424 options — the CLI wires everything together.",
3
+ "version": "1.7.0",
4
+ "description": "Scaffold production-ready fullstack apps in seconds. Pick your stack from 425 options — the CLI wires everything together.",
5
5
  "keywords": [
6
6
  "algolia",
7
7
  "angular",
@@ -127,11 +127,11 @@
127
127
  "prepublishOnly": "npm run build"
128
128
  },
129
129
  "dependencies": {
130
- "@better-fullstack/template-generator": "^1.6.2",
131
- "@better-fullstack/types": "^1.6.2",
130
+ "@better-fullstack/template-generator": "^1.7.0",
131
+ "@better-fullstack/types": "^1.7.0",
132
132
  "@clack/core": "^0.5.0",
133
- "@clack/prompts": "^1.2.0",
134
- "@orpc/server": "^1.14.0",
133
+ "@clack/prompts": "^1.3.0",
134
+ "@orpc/server": "^1.14.1",
135
135
  "consola": "^3.4.2",
136
136
  "env-paths": "^4.0.0",
137
137
  "execa": "^9.6.1",
@@ -145,8 +145,8 @@
145
145
  "@modelcontextprotocol/sdk": "^1.29.0",
146
146
  "trpc-cli": "^0.12.1",
147
147
  "ts-morph": "^27.0.2",
148
- "yaml": "^2.8.3",
149
- "zod": "^4.3.6"
148
+ "yaml": "^2.8.4",
149
+ "zod": "4.3.6"
150
150
  },
151
151
  "devDependencies": {
152
152
  "@types/bun": "^1.3.13",