create-better-fullstack 1.1.9 → 1.1.11

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/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { o as createBtsCli } from "./src-B07FiCRd.mjs";
2
+ import { o as createBtsCli } from "./src-BkL9XJbn.mjs";
3
3
 
4
4
  //#region src/cli.ts
5
5
  createBtsCli().run();
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { a as create, c as docs, d as sponsors, i as builder, l as generateVirtualProject, n as TEMPLATE_COUNT, o as createBtsCli, r as VirtualFileSystem, s as createVirtual, t as EMBEDDED_TEMPLATES, u as router } from "./src-B07FiCRd.mjs";
2
+ import { a as create, c as docs, d as sponsors, i as builder, l as generateVirtualProject, n as TEMPLATE_COUNT, o as createBtsCli, r as VirtualFileSystem, s as createVirtual, t as EMBEDDED_TEMPLATES, u as router } from "./src-BkL9XJbn.mjs";
3
3
 
4
4
  export { EMBEDDED_TEMPLATES, TEMPLATE_COUNT, VirtualFileSystem, builder, create, createBtsCli, createVirtual, docs, generateVirtualProject, router, sponsors };
@@ -625,6 +625,67 @@ function handleError(error, fallbackMessage) {
625
625
  process.exit(1);
626
626
  }
627
627
 
628
+ //#endregion
629
+ //#region src/utils/error-formatter.ts
630
+ function getCategoryTitle(category) {
631
+ return {
632
+ incompatibility: "Incompatible Options",
633
+ "invalid-selection": "Invalid Selection",
634
+ "missing-requirement": "Missing Requirement",
635
+ constraint: "Constraint Violation"
636
+ }[category];
637
+ }
638
+ function displayStructuredError(error) {
639
+ if (isSilent()) throw new CLIError(error.message);
640
+ const lines = [];
641
+ lines.push(pc.bold(pc.red(getCategoryTitle(error.category))));
642
+ lines.push("");
643
+ lines.push(error.message);
644
+ if (error.provided && Object.keys(error.provided).length > 0) {
645
+ lines.push("");
646
+ lines.push(pc.dim("You provided:"));
647
+ for (const [key, value] of Object.entries(error.provided)) {
648
+ const displayValue = Array.isArray(value) ? value.join(", ") : value;
649
+ lines.push(` ${pc.cyan("--" + key)} ${displayValue}`);
650
+ }
651
+ }
652
+ if (error.suggestions.length > 0) {
653
+ lines.push("");
654
+ lines.push(pc.dim("Suggestions:"));
655
+ for (const suggestion of error.suggestions) lines.push(` ${pc.green("•")} ${suggestion}`);
656
+ }
657
+ consola.box({
658
+ title: pc.red("Error"),
659
+ message: lines.join("\n"),
660
+ style: { borderColor: "red" }
661
+ });
662
+ process.exit(1);
663
+ }
664
+ function incompatibilityError(opts) {
665
+ return displayStructuredError({
666
+ category: "incompatibility",
667
+ ...opts
668
+ });
669
+ }
670
+ function invalidSelectionError(opts) {
671
+ return displayStructuredError({
672
+ category: "invalid-selection",
673
+ ...opts
674
+ });
675
+ }
676
+ function missingRequirementError(opts) {
677
+ return displayStructuredError({
678
+ category: "missing-requirement",
679
+ ...opts
680
+ });
681
+ }
682
+ function constraintError(opts) {
683
+ return displayStructuredError({
684
+ category: "constraint",
685
+ ...opts
686
+ });
687
+ }
688
+
628
689
  //#endregion
629
690
  //#region src/utils/compatibility-rules.ts
630
691
  function isWebFrontend(value) {
@@ -638,8 +699,16 @@ function splitFrontends(values = []) {
638
699
  }
639
700
  function ensureSingleWebAndNative(frontends) {
640
701
  const { web, native } = splitFrontends(frontends);
641
- if (web.length > 1) exitWithError("Cannot select multiple web frameworks. Choose only one of: tanstack-router, tanstack-start, react-router, next, nuxt, svelte, solid, astro, qwik, angular, redwood, fresh");
642
- if (native.length > 1) exitWithError("Cannot select multiple native frameworks. Choose only one of: native-bare, native-uniwind, native-unistyles");
702
+ if (web.length > 1) invalidSelectionError({
703
+ message: "Only one web framework can be selected per project.",
704
+ provided: { frontend: web },
705
+ suggestions: ["Keep one web framework and remove the others", "Use separate projects for multiple web frameworks"]
706
+ });
707
+ if (native.length > 1) invalidSelectionError({
708
+ message: "Only one native framework can be selected per project.",
709
+ provided: { frontend: native },
710
+ suggestions: ["Keep one native framework and remove the others", "Choose: native-bare, native-uniwind, or native-unistyles"]
711
+ });
643
712
  }
644
713
  const FULLSTACK_FRONTENDS$1 = [
645
714
  "next",
@@ -663,11 +732,51 @@ const WORKERS_COMPATIBLE_BACKENDS = [
663
732
  "fets"
664
733
  ];
665
734
  function validateWorkersCompatibility(providedFlags, options, config) {
666
- if (providedFlags.has("runtime") && options.runtime === "workers" && config.backend && !WORKERS_COMPATIBLE_BACKENDS.includes(config.backend)) exitWithError(`Cloudflare Workers runtime (--runtime workers) is only supported with Hono, Nitro, or feTS backend. Current backend: ${config.backend}. Please use '--backend hono', '--backend nitro', or '--backend fets', or choose a different runtime.`);
667
- if (providedFlags.has("backend") && config.backend && !WORKERS_COMPATIBLE_BACKENDS.includes(config.backend) && config.runtime === "workers") exitWithError(`Backend '${config.backend}' is not compatible with Cloudflare Workers runtime. Cloudflare Workers runtime is only supported with Hono, Nitro, or feTS backend. Please use '--backend hono', '--backend nitro', or '--backend fets', or choose a different runtime.`);
668
- if (providedFlags.has("runtime") && options.runtime === "workers" && config.database === "mongodb") exitWithError("Cloudflare Workers runtime (--runtime workers) is not compatible with MongoDB database. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle or Prisma ORM. Please use a different database or runtime.");
669
- if (providedFlags.has("runtime") && options.runtime === "workers" && config.dbSetup === "docker") exitWithError("Cloudflare Workers runtime (--runtime workers) is not compatible with Docker setup. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use '--db-setup d1' for SQLite or choose a different runtime.");
670
- if (providedFlags.has("database") && config.database === "mongodb" && config.runtime === "workers") exitWithError("MongoDB database is not compatible with Cloudflare Workers runtime. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle or Prisma ORM. Please use a different database or runtime.");
735
+ if (providedFlags.has("runtime") && options.runtime === "workers" && config.backend && !WORKERS_COMPATIBLE_BACKENDS.includes(config.backend)) incompatibilityError({
736
+ message: "Cloudflare Workers runtime requires a compatible backend.",
737
+ provided: {
738
+ runtime: "workers",
739
+ backend: config.backend
740
+ },
741
+ suggestions: [
742
+ "Use --backend hono",
743
+ "Use --backend nitro",
744
+ "Use --backend fets",
745
+ "Choose a different runtime (node, bun)"
746
+ ]
747
+ });
748
+ if (providedFlags.has("backend") && config.backend && !WORKERS_COMPATIBLE_BACKENDS.includes(config.backend) && config.runtime === "workers") incompatibilityError({
749
+ message: `Backend '${config.backend}' is not compatible with Workers runtime.`,
750
+ provided: {
751
+ backend: config.backend,
752
+ runtime: "workers"
753
+ },
754
+ suggestions: ["Use --backend hono, --backend nitro, or --backend fets", "Choose a different runtime (node, bun)"]
755
+ });
756
+ if (providedFlags.has("runtime") && options.runtime === "workers" && config.database === "mongodb") incompatibilityError({
757
+ message: "Cloudflare Workers runtime is not compatible with MongoDB.",
758
+ provided: {
759
+ runtime: "workers",
760
+ database: "mongodb"
761
+ },
762
+ suggestions: ["Use a different database (postgres, sqlite, mysql)", "Choose a different runtime (node, bun)"]
763
+ });
764
+ if (providedFlags.has("runtime") && options.runtime === "workers" && config.dbSetup === "docker") incompatibilityError({
765
+ message: "Cloudflare Workers runtime is not compatible with Docker setup.",
766
+ provided: {
767
+ runtime: "workers",
768
+ "db-setup": "docker"
769
+ },
770
+ suggestions: ["Use --db-setup d1 for SQLite", "Choose a different runtime (node, bun)"]
771
+ });
772
+ if (providedFlags.has("database") && config.database === "mongodb" && config.runtime === "workers") incompatibilityError({
773
+ message: "MongoDB is not compatible with Cloudflare Workers runtime.",
774
+ provided: {
775
+ database: "mongodb",
776
+ runtime: "workers"
777
+ },
778
+ suggestions: ["Use a different database (postgres, sqlite, mysql)", "Choose a different runtime (node, bun)"]
779
+ });
671
780
  }
672
781
  function validateApiFrontendCompatibility(api, frontends = [], astroIntegration) {
673
782
  const includesNuxt = frontends.includes("nuxt");
@@ -678,12 +787,66 @@ function validateApiFrontendCompatibility(api, frontends = [], astroIntegration)
678
787
  const includesAngular = frontends.includes("angular");
679
788
  const includesRedwood = frontends.includes("redwood");
680
789
  const includesFresh = frontends.includes("fresh");
681
- if ((includesNuxt || includesSvelte || includesSolid) && (api === "trpc" || api === "ts-rest" || api === "garph")) exitWithError(`${api === "trpc" ? "tRPC" : api === "ts-rest" ? "ts-rest" : "garph"} API is not supported with '${includesNuxt ? "nuxt" : includesSvelte ? "svelte" : "solid"}' frontend. Please use --api orpc or --api none or remove '${includesNuxt ? "nuxt" : includesSvelte ? "svelte" : "solid"}' from --frontend.`);
682
- if (includesQwik && api && api !== "none") exitWithError(`Qwik has its own built-in server capabilities and doesn't support external API layers (tRPC/oRPC). Please use --api none with Qwik.`);
683
- if (includesAngular && api && api !== "none") exitWithError(`Angular has its own built-in HttpClient and doesn't support external API layers (tRPC/oRPC/ts-rest/garph). Please use --api none with Angular.`);
684
- if (includesRedwood && api && api !== "none") exitWithError(`RedwoodJS has its own built-in GraphQL API and doesn't support external API layers (tRPC/oRPC/ts-rest/garph). Please use --api none with RedwoodJS.`);
685
- if (includesFresh && api && api !== "none") exitWithError(`Fresh has its own built-in server capabilities (Deno-native) and doesn't support external API layers (tRPC/oRPC/ts-rest/garph). Please use --api none with Fresh.`);
686
- if (includesAstro && astroIntegration && astroIntegration !== "react" && (api === "trpc" || api === "ts-rest" || api === "garph")) exitWithError(`${api === "trpc" ? "tRPC" : api === "ts-rest" ? "ts-rest" : "garph"} API requires React integration with Astro. Please use --api orpc or --api none, or use --astro-integration react.`);
790
+ if ((includesNuxt || includesSvelte || includesSolid) && (api === "trpc" || api === "ts-rest" || api === "garph")) {
791
+ const apiName = api === "trpc" ? "tRPC" : api === "ts-rest" ? "ts-rest" : "garph";
792
+ const incompatibleFrontend = includesNuxt ? "nuxt" : includesSvelte ? "svelte" : "solid";
793
+ incompatibilityError({
794
+ message: `${apiName} API requires React-based frontends.`,
795
+ provided: {
796
+ api,
797
+ frontend: incompatibleFrontend
798
+ },
799
+ suggestions: [
800
+ "Use --api orpc (works with all frontends)",
801
+ "Use --api none",
802
+ "Choose next, react-router, or tanstack-start"
803
+ ]
804
+ });
805
+ }
806
+ if (includesQwik && api && api !== "none") incompatibilityError({
807
+ message: "Qwik has built-in server capabilities and doesn't support external APIs.",
808
+ provided: {
809
+ api,
810
+ frontend: "qwik"
811
+ },
812
+ suggestions: ["Use --api none with Qwik"]
813
+ });
814
+ if (includesAngular && api && api !== "none") incompatibilityError({
815
+ message: "Angular has built-in HttpClient and doesn't support external APIs.",
816
+ provided: {
817
+ api,
818
+ frontend: "angular"
819
+ },
820
+ suggestions: ["Use --api none with Angular"]
821
+ });
822
+ if (includesRedwood && api && api !== "none") incompatibilityError({
823
+ message: "RedwoodJS has built-in GraphQL API and doesn't support external APIs.",
824
+ provided: {
825
+ api,
826
+ frontend: "redwood"
827
+ },
828
+ suggestions: ["Use --api none with RedwoodJS"]
829
+ });
830
+ if (includesFresh && api && api !== "none") incompatibilityError({
831
+ message: "Fresh has built-in server capabilities and doesn't support external APIs.",
832
+ provided: {
833
+ api,
834
+ frontend: "fresh"
835
+ },
836
+ suggestions: ["Use --api none with Fresh"]
837
+ });
838
+ if (includesAstro && astroIntegration && astroIntegration !== "react" && (api === "trpc" || api === "ts-rest" || api === "garph")) incompatibilityError({
839
+ message: `${api === "trpc" ? "tRPC" : api === "ts-rest" ? "ts-rest" : "garph"} API requires React integration with Astro.`,
840
+ provided: {
841
+ api,
842
+ "astro-integration": astroIntegration
843
+ },
844
+ suggestions: [
845
+ "Use --api orpc (works with all Astro integrations)",
846
+ "Use --api none",
847
+ "Use --astro-integration react"
848
+ ]
849
+ });
687
850
  }
688
851
  function isFrontendAllowedWithBackend(frontend, backend, auth) {
689
852
  if (backend === "convex" && frontend === "solid") return false;
@@ -817,10 +980,27 @@ function validateUILibraryFrontendCompatibility(uiLibrary, frontends = [], astro
817
980
  "next"
818
981
  ].includes(f))) return;
819
982
  }
820
- if (!compatibility.frontends.includes("astro")) exitWithError(`UI library '${uiLibrary}' requires React. Astro is configured with '${astroIntegration || "none"}' integration. Please use --astro-integration react or choose a different UI library (e.g., daisyui, ark-ui).`);
983
+ if (!compatibility.frontends.includes("astro")) {
984
+ const integrationName = astroIntegration || "none";
985
+ incompatibilityError({
986
+ message: `UI library '${uiLibrary}' requires React.`,
987
+ provided: {
988
+ "ui-library": uiLibrary,
989
+ "astro-integration": integrationName
990
+ },
991
+ suggestions: ["Use --astro-integration react", "Choose a different UI library (daisyui, ark-ui)"]
992
+ });
993
+ }
821
994
  return;
822
995
  }
823
- if (!compatibility.frontends.includes(webFrontend)) exitWithError(`UI library '${uiLibrary}' is not compatible with '${webFrontend}' frontend. Supported frontends: ${compatibility.frontends.join(", ")}`);
996
+ if (!compatibility.frontends.includes(webFrontend)) incompatibilityError({
997
+ message: `UI library '${uiLibrary}' is not compatible with '${webFrontend}'.`,
998
+ provided: {
999
+ "ui-library": uiLibrary,
1000
+ frontend: webFrontend
1001
+ },
1002
+ suggestions: [`Supported frontends: ${compatibility.frontends.join(", ")}`, "Choose a different UI library"]
1003
+ });
824
1004
  }
825
1005
  /**
826
1006
  * Validates that a UI library is compatible with the selected CSS framework
@@ -3674,21 +3854,244 @@ function validateArrayOptions(options) {
3674
3854
  validateNoneExclusivity(options.examples, "examples");
3675
3855
  }
3676
3856
 
3857
+ //#endregion
3858
+ //#region src/utils/peer-dependency-conflicts.ts
3859
+ /**
3860
+ * Known peer dependency conflicts.
3861
+ *
3862
+ * Add new conflicts here as they are discovered.
3863
+ * The validator will check these against the user's selected options.
3864
+ */
3865
+ const PEER_DEPENDENCY_CONFLICTS = [
3866
+ {
3867
+ id: "redux-toolkit-react19",
3868
+ description: "redux-toolkit with React 19 may have peer dependency warnings",
3869
+ packages: ["@reduxjs/toolkit", "react-redux"],
3870
+ severity: "warning",
3871
+ resolution: "Consider using zustand or jotai for React 19 projects, or ensure react-redux v9+",
3872
+ triggeredBy: [{
3873
+ optionKey: "stateManagement",
3874
+ values: ["redux-toolkit"]
3875
+ }],
3876
+ conflictsWithOptions: [{
3877
+ optionKey: "frontend",
3878
+ values: ["next"]
3879
+ }]
3880
+ },
3881
+ {
3882
+ id: "effect-schema-zod-overlap",
3883
+ description: "@effect/schema and zod both provide validation - may cause confusion",
3884
+ packages: ["@effect/schema", "zod"],
3885
+ severity: "warning",
3886
+ resolution: "Use --validation effect-schema with Effect, or --effect none with Zod",
3887
+ triggeredBy: [{
3888
+ optionKey: "effect",
3889
+ values: ["effect-full"]
3890
+ }],
3891
+ conflictsWithOptions: [{
3892
+ optionKey: "validation",
3893
+ values: ["zod"]
3894
+ }]
3895
+ },
3896
+ {
3897
+ id: "prisma-d1-version",
3898
+ description: "Prisma with D1 requires Prisma 5.x+ and specific adapter setup",
3899
+ packages: ["prisma", "@prisma/adapter-d1"],
3900
+ severity: "warning",
3901
+ resolution: "Consider Drizzle ORM for simpler D1 integration",
3902
+ triggeredBy: [{
3903
+ optionKey: "orm",
3904
+ values: ["prisma"]
3905
+ }],
3906
+ conflictsWithOptions: [{
3907
+ optionKey: "dbSetup",
3908
+ values: ["d1"]
3909
+ }]
3910
+ },
3911
+ {
3912
+ id: "biome-linting-overlap",
3913
+ description: "Biome includes linting/formatting - other linting addons are redundant",
3914
+ packages: [
3915
+ "@biomejs/biome",
3916
+ "eslint",
3917
+ "prettier"
3918
+ ],
3919
+ severity: "warning",
3920
+ resolution: "Choose either Biome (all-in-one) or other linting tools, not both",
3921
+ triggeredBy: [{
3922
+ optionKey: "addons",
3923
+ values: ["biome"]
3924
+ }],
3925
+ conflictsWithOptions: [{
3926
+ optionKey: "addons",
3927
+ values: ["ultracite", "oxlint"]
3928
+ }]
3929
+ },
3930
+ {
3931
+ id: "framer-motion-react19",
3932
+ description: "framer-motion versions <12 may have React 19 issues",
3933
+ packages: ["framer-motion"],
3934
+ severity: "warning",
3935
+ resolution: "The CLI uses motion (framer-motion v12+) which supports React 19",
3936
+ triggeredBy: [{
3937
+ optionKey: "animation",
3938
+ values: ["framer-motion"]
3939
+ }],
3940
+ conflictsWithOptions: [{
3941
+ optionKey: "frontend",
3942
+ values: ["next"]
3943
+ }]
3944
+ }
3945
+ ];
3946
+
3947
+ //#endregion
3948
+ //#region src/utils/peer-dependency-validator.ts
3949
+ /**
3950
+ * Peer dependency conflict validator.
3951
+ *
3952
+ * Checks project configuration against known conflicts and provides
3953
+ * warnings or errors before project creation begins.
3954
+ */
3955
+ /**
3956
+ * Checks if a config value matches any of the specified values.
3957
+ */
3958
+ function matchesValue(configValue, values) {
3959
+ if (configValue === void 0 || configValue === null) return false;
3960
+ if (Array.isArray(configValue)) return values.some((v) => configValue.includes(v));
3961
+ return values.includes(configValue);
3962
+ }
3963
+ /**
3964
+ * Checks the configuration against known peer dependency conflicts.
3965
+ */
3966
+ function checkPeerDependencyConflicts(config) {
3967
+ const errors = [];
3968
+ const warnings = [];
3969
+ for (const conflict of PEER_DEPENDENCY_CONFLICTS) {
3970
+ if (!conflict.triggeredBy.some((trigger) => {
3971
+ const configValue = config[trigger.optionKey];
3972
+ return matchesValue(configValue, trigger.values);
3973
+ })) continue;
3974
+ if (conflict.conflictsWithOptions.some((opt) => {
3975
+ const configValue = config[opt.optionKey];
3976
+ return matchesValue(configValue, opt.values);
3977
+ })) if (conflict.severity === "error") errors.push(conflict);
3978
+ else warnings.push(conflict);
3979
+ }
3980
+ return {
3981
+ errors,
3982
+ warnings
3983
+ };
3984
+ }
3985
+ /**
3986
+ * Prints a warning message for a dependency conflict.
3987
+ */
3988
+ function warnDependencyConflict(message, resolution) {
3989
+ if (isSilent()) return;
3990
+ consola.warn(pc.yellow(`Peer Dependency Warning: ${message}`));
3991
+ consola.log(pc.dim(` → ${resolution}`));
3992
+ }
3993
+ /**
3994
+ * Handles conflict results by printing warnings and exiting on errors.
3995
+ */
3996
+ function handleConflictResult(result) {
3997
+ for (const warning of result.warnings) warnDependencyConflict(warning.description, warning.resolution);
3998
+ if (result.errors.length > 0) exitWithError(`Peer Dependency Conflict:\n${result.errors.map((e) => `${e.description}\n → ${e.resolution}`).join("\n\n")}`);
3999
+ }
4000
+ /**
4001
+ * Validates peer dependencies for the given configuration.
4002
+ * Prints warnings and exits on errors.
4003
+ */
4004
+ function validatePeerDependencies(config) {
4005
+ handleConflictResult(checkPeerDependencyConflicts(config));
4006
+ }
4007
+
3677
4008
  //#endregion
3678
4009
  //#region src/utils/config-validation.ts
3679
4010
  function validateDatabaseOrmAuth(cfg, flags) {
3680
4011
  const db = cfg.database;
3681
4012
  const orm = cfg.orm;
3682
4013
  const has = (k) => flags ? flags.has(k) : true;
3683
- if (has("orm") && has("database") && orm === "mongoose" && db !== "mongodb") exitWithError("Mongoose ORM requires MongoDB database. Please use '--database mongodb' or choose a different ORM.");
3684
- if (has("orm") && has("database") && orm === "drizzle" && db === "mongodb") exitWithError("Drizzle ORM does not support MongoDB. Please use '--orm mongoose' or '--orm prisma' or choose a different database.");
3685
- if (has("orm") && has("database") && orm === "typeorm" && db === "mongodb") exitWithError("TypeORM does not support MongoDB in Better Fullstack. Please use '--orm mongoose' or '--orm prisma' or choose a different database.");
3686
- if (has("orm") && has("database") && orm === "kysely" && db === "mongodb") exitWithError("Kysely does not support MongoDB. Please use '--orm mongoose' or '--orm prisma' or choose a different database.");
3687
- if (has("orm") && has("database") && orm === "mikroorm" && db === "mongodb") exitWithError("MikroORM does not support MongoDB in Better Fullstack. Please use '--orm mongoose' or '--orm prisma' or choose a different database.");
3688
- if (has("orm") && has("database") && orm === "sequelize" && db === "mongodb") exitWithError("Sequelize does not support MongoDB. Please use '--orm mongoose' or '--orm prisma' or choose a different database.");
3689
- if (has("database") && has("orm") && db === "mongodb" && orm && orm !== "mongoose" && orm !== "prisma" && orm !== "none") exitWithError("MongoDB database requires Mongoose or Prisma ORM. Please use '--orm mongoose' or '--orm prisma' or choose a different database.");
3690
- if (has("database") && has("orm") && db && db !== "none" && orm === "none") exitWithError("Database selection requires an ORM. Please choose '--orm drizzle', '--orm prisma', '--orm typeorm', '--orm kysely', '--orm mikroorm', '--orm sequelize', or '--orm mongoose'.");
3691
- if (has("orm") && has("database") && orm && orm !== "none" && db === "none") exitWithError("ORM selection requires a database. Please choose a database or set '--orm none'.");
4014
+ if (has("orm") && has("database") && orm === "mongoose" && db !== "mongodb") incompatibilityError({
4015
+ message: "Mongoose ORM requires MongoDB database.",
4016
+ provided: {
4017
+ orm: "mongoose",
4018
+ database: db || "none"
4019
+ },
4020
+ suggestions: ["Use --database mongodb", "Choose a different ORM (drizzle, prisma)"]
4021
+ });
4022
+ if (has("orm") && has("database") && orm === "drizzle" && db === "mongodb") incompatibilityError({
4023
+ message: "Drizzle ORM does not support MongoDB.",
4024
+ provided: {
4025
+ orm: "drizzle",
4026
+ database: "mongodb"
4027
+ },
4028
+ suggestions: ["Use --orm mongoose or --orm prisma for MongoDB", "Choose a different database (postgres, sqlite, mysql)"]
4029
+ });
4030
+ if (has("orm") && has("database") && orm === "typeorm" && db === "mongodb") incompatibilityError({
4031
+ message: "TypeORM does not support MongoDB in Better Fullstack.",
4032
+ provided: {
4033
+ orm: "typeorm",
4034
+ database: "mongodb"
4035
+ },
4036
+ suggestions: ["Use --orm mongoose or --orm prisma for MongoDB", "Choose a different database (postgres, sqlite, mysql)"]
4037
+ });
4038
+ if (has("orm") && has("database") && orm === "kysely" && db === "mongodb") incompatibilityError({
4039
+ message: "Kysely does not support MongoDB.",
4040
+ provided: {
4041
+ orm: "kysely",
4042
+ database: "mongodb"
4043
+ },
4044
+ suggestions: ["Use --orm mongoose or --orm prisma for MongoDB", "Choose a different database (postgres, sqlite, mysql)"]
4045
+ });
4046
+ if (has("orm") && has("database") && orm === "mikroorm" && db === "mongodb") incompatibilityError({
4047
+ message: "MikroORM does not support MongoDB in Better Fullstack.",
4048
+ provided: {
4049
+ orm: "mikroorm",
4050
+ database: "mongodb"
4051
+ },
4052
+ suggestions: ["Use --orm mongoose or --orm prisma for MongoDB", "Choose a different database (postgres, sqlite, mysql)"]
4053
+ });
4054
+ if (has("orm") && has("database") && orm === "sequelize" && db === "mongodb") incompatibilityError({
4055
+ message: "Sequelize does not support MongoDB.",
4056
+ provided: {
4057
+ orm: "sequelize",
4058
+ database: "mongodb"
4059
+ },
4060
+ suggestions: ["Use --orm mongoose or --orm prisma for MongoDB", "Choose a different database (postgres, sqlite, mysql)"]
4061
+ });
4062
+ if (has("database") && has("orm") && db === "mongodb" && orm && orm !== "mongoose" && orm !== "prisma" && orm !== "none") incompatibilityError({
4063
+ message: "MongoDB only works with Mongoose or Prisma ORM.",
4064
+ provided: {
4065
+ database: "mongodb",
4066
+ orm
4067
+ },
4068
+ suggestions: ["Use --orm mongoose", "Use --orm prisma"]
4069
+ });
4070
+ if (has("database") && has("orm") && db && db !== "none" && orm === "none") missingRequirementError({
4071
+ message: "Database selection requires an ORM.",
4072
+ provided: {
4073
+ database: db,
4074
+ orm: "none"
4075
+ },
4076
+ suggestions: [
4077
+ "Use --orm drizzle (recommended)",
4078
+ "Use --orm prisma",
4079
+ "Use --orm mongoose (MongoDB only)"
4080
+ ]
4081
+ });
4082
+ if (has("orm") && has("database") && orm && orm !== "none" && db === "none") missingRequirementError({
4083
+ message: "ORM selection requires a database.",
4084
+ provided: {
4085
+ orm,
4086
+ database: "none"
4087
+ },
4088
+ suggestions: [
4089
+ "Use --database postgres",
4090
+ "Use --database sqlite",
4091
+ "Use --database mysql",
4092
+ "Set --orm none"
4093
+ ]
4094
+ });
3692
4095
  }
3693
4096
  function validateDatabaseSetup(config, providedFlags) {
3694
4097
  const { dbSetup, database, runtime } = config;
@@ -3739,12 +4142,54 @@ function validateConvexConstraints(config, providedFlags) {
3739
4142
  const { backend } = config;
3740
4143
  if (backend !== "convex") return;
3741
4144
  const has = (k) => providedFlags.has(k);
3742
- if (has("runtime") && config.runtime !== "none") exitWithError("Convex backend requires '--runtime none'. Please remove the --runtime flag or set it to 'none'.");
3743
- if (has("database") && config.database !== "none") exitWithError("Convex backend requires '--database none'. Please remove the --database flag or set it to 'none'.");
3744
- if (has("orm") && config.orm !== "none") exitWithError("Convex backend requires '--orm none'. Please remove the --orm flag or set it to 'none'.");
3745
- if (has("api") && config.api !== "none") exitWithError("Convex backend requires '--api none'. Please remove the --api flag or set it to 'none'.");
3746
- if (has("dbSetup") && config.dbSetup !== "none") exitWithError("Convex backend requires '--db-setup none'. Please remove the --db-setup flag or set it to 'none'.");
3747
- if (has("serverDeploy") && config.serverDeploy !== "none") exitWithError("Convex backend requires '--server-deploy none'. Please remove the --server-deploy flag or set it to 'none'.");
4145
+ if (has("runtime") && config.runtime !== "none") constraintError({
4146
+ message: "Convex backend manages its own runtime.",
4147
+ provided: {
4148
+ backend: "convex",
4149
+ runtime: config.runtime || ""
4150
+ },
4151
+ suggestions: ["Remove --runtime flag", "Set --runtime none"]
4152
+ });
4153
+ if (has("database") && config.database !== "none") constraintError({
4154
+ message: "Convex backend has its own built-in database.",
4155
+ provided: {
4156
+ backend: "convex",
4157
+ database: config.database || ""
4158
+ },
4159
+ suggestions: ["Remove --database flag", "Set --database none"]
4160
+ });
4161
+ if (has("orm") && config.orm !== "none") constraintError({
4162
+ message: "Convex backend has its own data layer (no ORM needed).",
4163
+ provided: {
4164
+ backend: "convex",
4165
+ orm: config.orm || ""
4166
+ },
4167
+ suggestions: ["Remove --orm flag", "Set --orm none"]
4168
+ });
4169
+ if (has("api") && config.api !== "none") constraintError({
4170
+ message: "Convex backend has its own built-in API layer.",
4171
+ provided: {
4172
+ backend: "convex",
4173
+ api: config.api || ""
4174
+ },
4175
+ suggestions: ["Remove --api flag", "Set --api none"]
4176
+ });
4177
+ if (has("dbSetup") && config.dbSetup !== "none") constraintError({
4178
+ message: "Convex backend manages its own database infrastructure.",
4179
+ provided: {
4180
+ backend: "convex",
4181
+ "db-setup": config.dbSetup || ""
4182
+ },
4183
+ suggestions: ["Remove --db-setup flag", "Set --db-setup none"]
4184
+ });
4185
+ if (has("serverDeploy") && config.serverDeploy !== "none") constraintError({
4186
+ message: "Convex backend has its own deployment platform.",
4187
+ provided: {
4188
+ backend: "convex",
4189
+ "server-deploy": config.serverDeploy || ""
4190
+ },
4191
+ suggestions: ["Remove --server-deploy flag", "Set --server-deploy none"]
4192
+ });
3748
4193
  if (has("auth") && config.auth === "better-auth") {
3749
4194
  const supportedFrontends = [
3750
4195
  "tanstack-router",
@@ -3754,7 +4199,20 @@ function validateConvexConstraints(config, providedFlags) {
3754
4199
  "native-uniwind",
3755
4200
  "native-unistyles"
3756
4201
  ];
3757
- if (!config.frontend?.some((f) => supportedFrontends.includes(f))) exitWithError("Better-Auth with Convex backend requires a supported frontend (TanStack Router, TanStack Start, Next.js, or Native).");
4202
+ if (!config.frontend?.some((f) => supportedFrontends.includes(f))) incompatibilityError({
4203
+ message: "Better-Auth with Convex requires specific frontends.",
4204
+ provided: {
4205
+ backend: "convex",
4206
+ auth: "better-auth",
4207
+ frontend: config.frontend || []
4208
+ },
4209
+ suggestions: [
4210
+ "Use --frontend tanstack-router",
4211
+ "Use --frontend tanstack-start",
4212
+ "Use --frontend next",
4213
+ "Use a native frontend"
4214
+ ]
4215
+ });
3758
4216
  }
3759
4217
  }
3760
4218
  function validateBackendNoneConstraints(config, providedFlags) {
@@ -3851,6 +4309,7 @@ function validateFullConfig(config, providedFlags, options) {
3851
4309
  validateNextAuthCompatibility(config.auth, config.backend, config.frontend ?? []);
3852
4310
  validateUILibraryFrontendCompatibility(config.uiLibrary, config.frontend ?? [], config.astroIntegration);
3853
4311
  validateUILibraryCSSFrameworkCompatibility(config.uiLibrary, config.cssFramework);
4312
+ validatePeerDependencies(config);
3854
4313
  }
3855
4314
  function validateConfigForProgrammaticUse(config) {
3856
4315
  try {
@@ -3863,6 +4322,7 @@ function validateConfigForProgrammaticUse(config) {
3863
4322
  validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? [], config.api);
3864
4323
  validateUILibraryFrontendCompatibility(config.uiLibrary, config.frontend ?? [], config.astroIntegration);
3865
4324
  validateUILibraryCSSFrameworkCompatibility(config.uiLibrary, config.cssFramework);
4325
+ validatePeerDependencies(config);
3866
4326
  } catch (error) {
3867
4327
  if (error instanceof Error) throw error;
3868
4328
  throw new Error(String(error));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-fullstack",
3
- "version": "1.1.9",
3
+ "version": "1.1.11",
4
4
  "description": "A CLI-first toolkit for building Full Stack applications. Skip the configuration. Ship the code.",
5
5
  "keywords": [
6
6
  "better-auth",
@@ -64,17 +64,20 @@
64
64
  "dev": "tsdown --watch",
65
65
  "lint": "oxlint . && tsc --noEmit && bun test test/cli-builder-sync.test.ts",
66
66
  "check-types": "tsc --noEmit",
67
- "test": "bun test",
67
+ "test": "bun test ./test/*.test.ts",
68
68
  "test:watch": "bun test --watch",
69
69
  "test:coverage": "bun test --coverage",
70
70
  "test:ci": "CI=1 bun test --bail=5",
71
71
  "test:e2e": "E2E=1 bun test test/e2e/e2e.e2e.ts",
72
72
  "test:astro-combos": "bun run scripts/test-astro-combinations.ts",
73
+ "test:matrix": "MATRIX_MODE=batched bun test ./test/matrix/matrix-test.test.ts",
74
+ "test:matrix:fast": "MATRIX_MODE=sample MATRIX_SAMPLE=0.1 bun test ./test/matrix/matrix-test.test.ts",
75
+ "test:matrix:full": "MATRIX_MODE=full bun test ./test/matrix/matrix-test.test.ts",
73
76
  "prepublishOnly": "npm run build"
74
77
  },
75
78
  "dependencies": {
76
- "@better-fullstack/template-generator": "^1.1.9",
77
- "@better-fullstack/types": "^1.1.9",
79
+ "@better-fullstack/template-generator": "^1.1.11",
80
+ "@better-fullstack/types": "^1.1.11",
78
81
  "@clack/core": "^0.5.0",
79
82
  "@clack/prompts": "^1.0.0-alpha.8",
80
83
  "@orpc/server": "^1.13.0",