create-better-fullstack 1.1.8 → 1.1.10

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-CiDXRNuh.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-CiDXRNuh.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;
@@ -802,13 +965,42 @@ function validateExamplesCompatibility(examples, backend, database, frontend, ap
802
965
  /**
803
966
  * Validates that a UI library is compatible with the selected frontend(s)
804
967
  */
805
- function validateUILibraryFrontendCompatibility(uiLibrary, frontends = []) {
968
+ function validateUILibraryFrontendCompatibility(uiLibrary, frontends = [], astroIntegration) {
806
969
  if (!uiLibrary || uiLibrary === "none") return;
807
970
  const { web } = splitFrontends(frontends);
808
971
  if (web.length === 0) return;
809
972
  const compatibility = UI_LIBRARY_COMPATIBILITY[uiLibrary];
810
973
  const webFrontend = web[0];
811
- if (!compatibility.frontends.includes(webFrontend)) exitWithError(`UI library '${uiLibrary}' is not compatible with '${webFrontend}' frontend. Supported frontends: ${compatibility.frontends.join(", ")}`);
974
+ if (webFrontend === "astro") {
975
+ if (astroIntegration === "react") {
976
+ if (compatibility.frontends.some((f) => [
977
+ "tanstack-router",
978
+ "react-router",
979
+ "tanstack-start",
980
+ "next"
981
+ ].includes(f))) return;
982
+ }
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
+ }
994
+ return;
995
+ }
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
+ });
812
1004
  }
813
1005
  /**
814
1006
  * Validates that a UI library is compatible with the selected CSS framework
@@ -822,12 +1014,24 @@ function validateUILibraryCSSFrameworkCompatibility(uiLibrary, cssFramework) {
822
1014
  /**
823
1015
  * Gets list of UI libraries compatible with the selected frontend(s)
824
1016
  */
825
- function getCompatibleUILibraries(frontends = []) {
1017
+ function getCompatibleUILibraries(frontends = [], astroIntegration) {
826
1018
  const { web } = splitFrontends(frontends);
827
1019
  if (web.length === 0) return ["none"];
828
1020
  const webFrontend = web[0];
829
1021
  return Object.keys(UI_LIBRARY_COMPATIBILITY).filter((lib) => {
830
- return UI_LIBRARY_COMPATIBILITY[lib].frontends.includes(webFrontend);
1022
+ if (lib === "none") return true;
1023
+ const compatibility = UI_LIBRARY_COMPATIBILITY[lib];
1024
+ if (webFrontend === "astro") {
1025
+ if (astroIntegration === "react") return compatibility.frontends.some((f) => [
1026
+ "tanstack-router",
1027
+ "react-router",
1028
+ "tanstack-start",
1029
+ "next",
1030
+ "astro"
1031
+ ].includes(f));
1032
+ return compatibility.frontends.includes("astro");
1033
+ }
1034
+ return compatibility.frontends.includes(webFrontend);
831
1035
  });
832
1036
  }
833
1037
  /**
@@ -2895,10 +3099,10 @@ const UI_LIBRARY_OPTIONS = {
2895
3099
  hint: "No UI component library"
2896
3100
  }
2897
3101
  };
2898
- async function getUILibraryChoice(uiLibrary, frontends) {
3102
+ async function getUILibraryChoice(uiLibrary, frontends, astroIntegration) {
2899
3103
  const { web } = splitFrontends(frontends);
2900
3104
  if (web.length === 0) return "none";
2901
- const compatibleLibraries = getCompatibleUILibraries(frontends);
3105
+ const compatibleLibraries = getCompatibleUILibraries(frontends, astroIntegration);
2902
3106
  if (uiLibrary !== void 0) return compatibleLibraries.includes(uiLibrary) ? uiLibrary : compatibleLibraries[0];
2903
3107
  const options = compatibleLibraries.map((lib) => ({
2904
3108
  value: lib,
@@ -3019,7 +3223,7 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
3019
3223
  },
3020
3224
  uiLibrary: ({ results }) => {
3021
3225
  if (results.ecosystem === "rust") return Promise.resolve("none");
3022
- if (hasWebStyling(results.frontend)) return getUILibraryChoice(flags.uiLibrary, results.frontend);
3226
+ if (hasWebStyling(results.frontend)) return getUILibraryChoice(flags.uiLibrary, results.frontend, results.astroIntegration);
3023
3227
  return Promise.resolve("none");
3024
3228
  },
3025
3229
  cssFramework: ({ results }) => {
@@ -3650,21 +3854,244 @@ function validateArrayOptions(options) {
3650
3854
  validateNoneExclusivity(options.examples, "examples");
3651
3855
  }
3652
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
+
3653
4008
  //#endregion
3654
4009
  //#region src/utils/config-validation.ts
3655
4010
  function validateDatabaseOrmAuth(cfg, flags) {
3656
4011
  const db = cfg.database;
3657
4012
  const orm = cfg.orm;
3658
4013
  const has = (k) => flags ? flags.has(k) : true;
3659
- if (has("orm") && has("database") && orm === "mongoose" && db !== "mongodb") exitWithError("Mongoose ORM requires MongoDB database. Please use '--database mongodb' or choose a different ORM.");
3660
- 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.");
3661
- 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.");
3662
- 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.");
3663
- 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.");
3664
- 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.");
3665
- 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.");
3666
- 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'.");
3667
- 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
+ });
3668
4095
  }
3669
4096
  function validateDatabaseSetup(config, providedFlags) {
3670
4097
  const { dbSetup, database, runtime } = config;
@@ -3715,12 +4142,54 @@ function validateConvexConstraints(config, providedFlags) {
3715
4142
  const { backend } = config;
3716
4143
  if (backend !== "convex") return;
3717
4144
  const has = (k) => providedFlags.has(k);
3718
- if (has("runtime") && config.runtime !== "none") exitWithError("Convex backend requires '--runtime none'. Please remove the --runtime flag or set it to 'none'.");
3719
- if (has("database") && config.database !== "none") exitWithError("Convex backend requires '--database none'. Please remove the --database flag or set it to 'none'.");
3720
- if (has("orm") && config.orm !== "none") exitWithError("Convex backend requires '--orm none'. Please remove the --orm flag or set it to 'none'.");
3721
- if (has("api") && config.api !== "none") exitWithError("Convex backend requires '--api none'. Please remove the --api flag or set it to 'none'.");
3722
- if (has("dbSetup") && config.dbSetup !== "none") exitWithError("Convex backend requires '--db-setup none'. Please remove the --db-setup flag or set it to 'none'.");
3723
- 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
+ });
3724
4193
  if (has("auth") && config.auth === "better-auth") {
3725
4194
  const supportedFrontends = [
3726
4195
  "tanstack-router",
@@ -3730,7 +4199,20 @@ function validateConvexConstraints(config, providedFlags) {
3730
4199
  "native-uniwind",
3731
4200
  "native-unistyles"
3732
4201
  ];
3733
- 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
+ });
3734
4216
  }
3735
4217
  }
3736
4218
  function validateBackendNoneConstraints(config, providedFlags) {
@@ -3825,8 +4307,9 @@ function validateFullConfig(config, providedFlags, options) {
3825
4307
  validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? [], config.api);
3826
4308
  validatePaymentsCompatibility(config.payments, config.auth, config.backend, config.frontend ?? []);
3827
4309
  validateNextAuthCompatibility(config.auth, config.backend, config.frontend ?? []);
3828
- validateUILibraryFrontendCompatibility(config.uiLibrary, config.frontend ?? []);
4310
+ validateUILibraryFrontendCompatibility(config.uiLibrary, config.frontend ?? [], config.astroIntegration);
3829
4311
  validateUILibraryCSSFrameworkCompatibility(config.uiLibrary, config.cssFramework);
4312
+ validatePeerDependencies(config);
3830
4313
  }
3831
4314
  function validateConfigForProgrammaticUse(config) {
3832
4315
  try {
@@ -3837,8 +4320,9 @@ function validateConfigForProgrammaticUse(config) {
3837
4320
  validateNextAuthCompatibility(config.auth, config.backend, config.frontend ?? []);
3838
4321
  if (config.addons && config.addons.length > 0) validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth);
3839
4322
  validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? [], config.api);
3840
- validateUILibraryFrontendCompatibility(config.uiLibrary, config.frontend ?? []);
4323
+ validateUILibraryFrontendCompatibility(config.uiLibrary, config.frontend ?? [], config.astroIntegration);
3841
4324
  validateUILibraryCSSFrameworkCompatibility(config.uiLibrary, config.cssFramework);
4325
+ validatePeerDependencies(config);
3842
4326
  } catch (error) {
3843
4327
  if (error instanceof Error) throw error;
3844
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.8",
3
+ "version": "1.1.10",
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,16 +64,17 @@
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 run build && bun test",
68
- "test:watch": "bun run build && bun test --watch",
69
- "test:coverage": "bun run build && bun test --coverage",
70
- "test:ci": "bun run build && CI=1 bun test --bail=5",
71
- "test:astro-combos": "bun run build && bun run scripts/test-astro-combinations.ts",
67
+ "test": "bun test",
68
+ "test:watch": "bun test --watch",
69
+ "test:coverage": "bun test --coverage",
70
+ "test:ci": "CI=1 bun test --bail=5",
71
+ "test:e2e": "E2E=1 bun test test/e2e/e2e.e2e.ts",
72
+ "test:astro-combos": "bun run scripts/test-astro-combinations.ts",
72
73
  "prepublishOnly": "npm run build"
73
74
  },
74
75
  "dependencies": {
75
- "@better-fullstack/template-generator": "^1.1.8",
76
- "@better-fullstack/types": "^1.1.8",
76
+ "@better-fullstack/template-generator": "^1.1.10",
77
+ "@better-fullstack/types": "^1.1.10",
77
78
  "@clack/core": "^0.5.0",
78
79
  "@clack/prompts": "^1.0.0-alpha.8",
79
80
  "@orpc/server": "^1.13.0",