create-better-fullstack 1.3.16 → 1.4.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/dist/cli.mjs +1 -1
- package/dist/index.d.mts +3 -2
- package/dist/index.mjs +1 -1
- package/dist/{src-zCIIp5Gn.mjs → src-p54ND5wJ.mjs} +123 -42
- package/package.json +3 -3
package/dist/cli.mjs
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -252,6 +252,7 @@ declare const router: {
|
|
|
252
252
|
examples: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
253
253
|
none: "none";
|
|
254
254
|
ai: "ai";
|
|
255
|
+
"chat-sdk": "chat-sdk";
|
|
255
256
|
}>>>;
|
|
256
257
|
git: z.ZodOptional<z.ZodBoolean>;
|
|
257
258
|
packageManager: z.ZodOptional<z.ZodEnum<{
|
|
@@ -452,7 +453,7 @@ declare const router: {
|
|
|
452
453
|
runtime: "none" | "bun" | "node" | "workers";
|
|
453
454
|
frontend: ("none" | "tanstack-router" | "react-router" | "tanstack-start" | "next" | "nuxt" | "native-bare" | "native-uniwind" | "native-unistyles" | "svelte" | "solid" | "astro" | "qwik" | "angular" | "redwood" | "fresh")[];
|
|
454
455
|
addons: ("none" | "pwa" | "tauri" | "starlight" | "biome" | "lefthook" | "husky" | "ruler" | "mcp" | "skills" | "turborepo" | "fumadocs" | "ultracite" | "oxlint" | "opentui" | "wxt" | "msw" | "storybook")[];
|
|
455
|
-
examples: ("ai" | "none")[];
|
|
456
|
+
examples: ("ai" | "none" | "chat-sdk")[];
|
|
456
457
|
auth: "none" | "better-auth" | "clerk" | "nextauth" | "stack-auth" | "supabase-auth" | "auth0";
|
|
457
458
|
payments: "none" | "polar" | "stripe" | "lemon-squeezy" | "paddle" | "dodo";
|
|
458
459
|
git: boolean;
|
|
@@ -531,7 +532,7 @@ declare const router: {
|
|
|
531
532
|
runtime: "none" | "bun" | "node" | "workers";
|
|
532
533
|
frontend: ("none" | "tanstack-router" | "react-router" | "tanstack-start" | "next" | "nuxt" | "native-bare" | "native-uniwind" | "native-unistyles" | "svelte" | "solid" | "astro" | "qwik" | "angular" | "redwood" | "fresh")[];
|
|
533
534
|
addons: ("none" | "pwa" | "tauri" | "starlight" | "biome" | "lefthook" | "husky" | "ruler" | "mcp" | "skills" | "turborepo" | "fumadocs" | "ultracite" | "oxlint" | "opentui" | "wxt" | "msw" | "storybook")[];
|
|
534
|
-
examples: ("ai" | "none")[];
|
|
535
|
+
examples: ("ai" | "none" | "chat-sdk")[];
|
|
535
536
|
auth: "none" | "better-auth" | "clerk" | "nextauth" | "stack-auth" | "supabase-auth" | "auth0";
|
|
536
537
|
payments: "none" | "polar" | "stripe" | "lemon-squeezy" | "paddle" | "dodo";
|
|
537
538
|
git: boolean;
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as builder, c as createVirtual, d as history, f as router, i as add, l as docs, n as TEMPLATE_COUNT, o as create, p as sponsors, r as VirtualFileSystem, s as createBtsCli, t as EMBEDDED_TEMPLATES, u as generateVirtualProject } from "./src-
|
|
2
|
+
import { a as builder, c as createVirtual, d as history, f as router, i as add, l as docs, n as TEMPLATE_COUNT, o as create, p as sponsors, r as VirtualFileSystem, s as createBtsCli, t as EMBEDDED_TEMPLATES, u as generateVirtualProject } from "./src-p54ND5wJ.mjs";
|
|
3
3
|
|
|
4
4
|
export { EMBEDDED_TEMPLATES, TEMPLATE_COUNT, VirtualFileSystem, add, builder, create, createBtsCli, createVirtual, docs, generateVirtualProject, history, router, sponsors };
|
|
@@ -18,8 +18,8 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
18
18
|
import { ConfirmPrompt, GroupMultiSelectPrompt, MultiSelectPrompt, SelectPrompt, isCancel as isCancel$1 } from "@clack/core";
|
|
19
19
|
import * as JSONC from "jsonc-parser";
|
|
20
20
|
import { $, execa } from "execa";
|
|
21
|
-
import { format } from "oxfmt";
|
|
22
21
|
import os$1 from "node:os";
|
|
22
|
+
import { format } from "oxfmt";
|
|
23
23
|
|
|
24
24
|
//#region src/utils/get-package-manager.ts
|
|
25
25
|
const getUserPkgManager = () => {
|
|
@@ -542,6 +542,7 @@ async function historyHandler(input) {
|
|
|
542
542
|
*/
|
|
543
543
|
function formatUpdate(info) {
|
|
544
544
|
const colorFn = {
|
|
545
|
+
downgrade: pc.red,
|
|
545
546
|
major: pc.red,
|
|
546
547
|
minor: pc.yellow,
|
|
547
548
|
patch: pc.green,
|
|
@@ -597,6 +598,7 @@ async function interactiveUpdate(updates) {
|
|
|
597
598
|
console.log(formatUpdate(update));
|
|
598
599
|
if (update.ecosystem) console.log(pc.dim(` Ecosystem: ${update.ecosystem}`));
|
|
599
600
|
if (update.updateType === "major") console.log(pc.yellow(" Warning: Breaking changes possible. Check changelog."));
|
|
601
|
+
else if (update.updateType === "downgrade") console.log(pc.red(" Warning: npm latest is lower than current pinned version. Review carefully."));
|
|
600
602
|
const action = await select({
|
|
601
603
|
message: "What would you like to do?",
|
|
602
604
|
options: [
|
|
@@ -652,6 +654,7 @@ async function updateDepsHandler(options) {
|
|
|
652
654
|
console.log(generateCliReport(result));
|
|
653
655
|
if (check || result.outdated.length === 0) return;
|
|
654
656
|
let toApply = [];
|
|
657
|
+
const downgradeCount = result.outdated.filter((u) => u.updateType === "downgrade").length;
|
|
655
658
|
if (patch) {
|
|
656
659
|
toApply = result.outdated.filter((u) => u.updateType === "patch" || u.updateType === "minor");
|
|
657
660
|
if (toApply.length === 0) {
|
|
@@ -659,6 +662,7 @@ async function updateDepsHandler(options) {
|
|
|
659
662
|
return;
|
|
660
663
|
}
|
|
661
664
|
log.info(`Found ${toApply.length} patch/minor updates to apply automatically.`);
|
|
665
|
+
if (downgradeCount > 0) log.warn(`${downgradeCount} downgrade${downgradeCount === 1 ? "" : "s"} detected and excluded from --patch mode.`);
|
|
662
666
|
const shouldProceed = await confirm({ message: `Apply ${toApply.length} safe updates?` });
|
|
663
667
|
if (isCancel(shouldProceed) || !shouldProceed) {
|
|
664
668
|
log.info("Cancelled.");
|
|
@@ -672,7 +676,7 @@ async function updateDepsHandler(options) {
|
|
|
672
676
|
return;
|
|
673
677
|
}
|
|
674
678
|
} else {
|
|
675
|
-
const shouldProceed = await confirm({ message: `Apply all ${result.outdated.length} updates?` });
|
|
679
|
+
const shouldProceed = await confirm({ message: downgradeCount > 0 ? `Apply all ${result.outdated.length} updates (including ${downgradeCount} downgrade${downgradeCount === 1 ? "" : "s"})?` : `Apply all ${result.outdated.length} updates?` });
|
|
676
680
|
if (isCancel(shouldProceed) || !shouldProceed) {
|
|
677
681
|
log.info("Cancelled. Use --check to only view updates without prompting.");
|
|
678
682
|
return;
|
|
@@ -925,7 +929,7 @@ const WORKERS_COMPATIBLE_BACKENDS = [
|
|
|
925
929
|
];
|
|
926
930
|
function validateWorkersCompatibility(providedFlags, options, config) {
|
|
927
931
|
if (providedFlags.has("runtime") && options.runtime === "workers" && config.backend && !WORKERS_COMPATIBLE_BACKENDS.includes(config.backend)) incompatibilityError({
|
|
928
|
-
message: "Cloudflare Workers runtime
|
|
932
|
+
message: "In Better-Fullstack, Cloudflare Workers runtime is currently supported only with compatible backends (Hono, Nitro, or Fets).",
|
|
929
933
|
provided: {
|
|
930
934
|
runtime: "workers",
|
|
931
935
|
backend: config.backend
|
|
@@ -938,7 +942,7 @@ function validateWorkersCompatibility(providedFlags, options, config) {
|
|
|
938
942
|
]
|
|
939
943
|
});
|
|
940
944
|
if (providedFlags.has("backend") && config.backend && !WORKERS_COMPATIBLE_BACKENDS.includes(config.backend) && config.runtime === "workers") incompatibilityError({
|
|
941
|
-
message: `
|
|
945
|
+
message: `In Better-Fullstack, backend '${config.backend}' is currently not available with Cloudflare Workers runtime.`,
|
|
942
946
|
provided: {
|
|
943
947
|
backend: config.backend,
|
|
944
948
|
runtime: "workers"
|
|
@@ -946,7 +950,7 @@ function validateWorkersCompatibility(providedFlags, options, config) {
|
|
|
946
950
|
suggestions: ["Use --backend hono, --backend nitro, or --backend fets", "Choose a different runtime (node, bun)"]
|
|
947
951
|
});
|
|
948
952
|
if (providedFlags.has("runtime") && options.runtime === "workers" && config.database === "mongodb") incompatibilityError({
|
|
949
|
-
message: "Cloudflare Workers runtime is not
|
|
953
|
+
message: "In Better-Fullstack, Cloudflare Workers runtime is currently not available with MongoDB.",
|
|
950
954
|
provided: {
|
|
951
955
|
runtime: "workers",
|
|
952
956
|
database: "mongodb"
|
|
@@ -954,7 +958,7 @@ function validateWorkersCompatibility(providedFlags, options, config) {
|
|
|
954
958
|
suggestions: ["Use a different database (postgres, sqlite, mysql)", "Choose a different runtime (node, bun)"]
|
|
955
959
|
});
|
|
956
960
|
if (providedFlags.has("runtime") && options.runtime === "workers" && config.dbSetup === "docker") incompatibilityError({
|
|
957
|
-
message: "Cloudflare Workers runtime is not
|
|
961
|
+
message: "In Better-Fullstack, Cloudflare Workers runtime is currently not available with Docker database setup.",
|
|
958
962
|
provided: {
|
|
959
963
|
runtime: "workers",
|
|
960
964
|
"db-setup": "docker"
|
|
@@ -962,7 +966,7 @@ function validateWorkersCompatibility(providedFlags, options, config) {
|
|
|
962
966
|
suggestions: ["Use --db-setup d1 for SQLite", "Choose a different runtime (node, bun)"]
|
|
963
967
|
});
|
|
964
968
|
if (providedFlags.has("database") && config.database === "mongodb" && config.runtime === "workers") incompatibilityError({
|
|
965
|
-
message: "MongoDB is not
|
|
969
|
+
message: "In Better-Fullstack, MongoDB is currently not available with Cloudflare Workers runtime.",
|
|
966
970
|
provided: {
|
|
967
971
|
database: "mongodb",
|
|
968
972
|
runtime: "workers"
|
|
@@ -1058,6 +1062,9 @@ function isFrontendAllowedWithBackend(frontend, backend, auth) {
|
|
|
1058
1062
|
"solid"
|
|
1059
1063
|
].includes(frontend)) return false;
|
|
1060
1064
|
}
|
|
1065
|
+
if (auth === "clerk" && backend === "self") {
|
|
1066
|
+
if (!["next", "tanstack-start"].includes(frontend)) return false;
|
|
1067
|
+
}
|
|
1061
1068
|
if (auth === "nextauth") {
|
|
1062
1069
|
if (frontend !== "next") return false;
|
|
1063
1070
|
if (backend !== "self") return false;
|
|
@@ -1068,29 +1075,57 @@ function isFrontendAllowedWithBackend(frontend, backend, auth) {
|
|
|
1068
1075
|
}
|
|
1069
1076
|
return true;
|
|
1070
1077
|
}
|
|
1078
|
+
function validateClerkCompatibility(auth, backend, frontends = []) {
|
|
1079
|
+
if (auth !== "clerk") return;
|
|
1080
|
+
if (backend === "convex") {
|
|
1081
|
+
const incompatibleFrontends = frontends.filter((f) => [
|
|
1082
|
+
"nuxt",
|
|
1083
|
+
"svelte",
|
|
1084
|
+
"solid"
|
|
1085
|
+
].includes(f));
|
|
1086
|
+
if (incompatibleFrontends.length > 0) exitWithError(`In Better-Fullstack, Clerk + Convex is not compatible with the following frontends: ${incompatibleFrontends.join(", ")}. Please choose a different frontend or auth provider.`);
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
if (backend === "self") {
|
|
1090
|
+
if (frontends.some((f) => [
|
|
1091
|
+
"native-bare",
|
|
1092
|
+
"native-uniwind",
|
|
1093
|
+
"native-unistyles"
|
|
1094
|
+
].includes(f))) exitWithError("In Better-Fullstack, Clerk with the 'self' backend is currently supported only for web-only Next.js or TanStack Start projects (no native companion app). Please remove the native frontend or choose a different auth provider.");
|
|
1095
|
+
const hasNextJs = frontends.includes("next");
|
|
1096
|
+
const hasTanStackStart = frontends.includes("tanstack-start");
|
|
1097
|
+
if (!hasNextJs && !hasTanStackStart) {
|
|
1098
|
+
if (frontends.includes("astro")) exitWithError("In Better-Fullstack, Clerk is not yet supported for Astro fullstack projects. Please use '--frontend next' or '--frontend tanstack-start' with '--backend self', or choose a different auth provider.");
|
|
1099
|
+
if (frontends.includes("nuxt")) exitWithError("In Better-Fullstack, Clerk is not yet supported for Nuxt fullstack projects. Please use '--frontend next' or '--frontend tanstack-start' with '--backend self', or choose a different auth provider.");
|
|
1100
|
+
exitWithError("In Better-Fullstack, Clerk with the 'self' backend currently requires the Next.js or TanStack Start frontend. Please use '--frontend next' or '--frontend tanstack-start', or choose a different auth provider.");
|
|
1101
|
+
}
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
exitWithError("In Better-Fullstack, Clerk authentication is currently supported with the Convex backend, or with the 'self' backend when using Next.js or TanStack Start. Please choose a supported backend/frontend combination or a different auth provider.");
|
|
1105
|
+
}
|
|
1071
1106
|
function validateNextAuthCompatibility(auth, backend, frontends = []) {
|
|
1072
1107
|
if (auth !== "nextauth") return;
|
|
1073
1108
|
const hasNextJs = frontends.includes("next");
|
|
1074
|
-
if (backend !== "self") exitWithError("Auth.js (NextAuth) is
|
|
1075
|
-
if (!hasNextJs) exitWithError("Auth.js (NextAuth) requires Next.js frontend. Please use '--frontend next' or choose a different auth provider.");
|
|
1109
|
+
if (backend !== "self") exitWithError("In Better-Fullstack, Auth.js (NextAuth) is currently supported only with the 'self' backend (fullstack Next.js). Please use '--backend self' or choose a different auth provider.");
|
|
1110
|
+
if (!hasNextJs) exitWithError("In Better-Fullstack, Auth.js (NextAuth) currently requires the Next.js frontend. Please use '--frontend next' or choose a different auth provider.");
|
|
1076
1111
|
}
|
|
1077
1112
|
function validateStackAuthCompatibility(auth, backend, frontends = []) {
|
|
1078
1113
|
if (auth !== "stack-auth") return;
|
|
1079
1114
|
const hasNextJs = frontends.includes("next");
|
|
1080
|
-
if (backend !== "self") exitWithError("Stack Auth is
|
|
1081
|
-
if (!hasNextJs) exitWithError("Stack Auth requires Next.js frontend. Please use '--frontend next' or choose a different auth provider.");
|
|
1115
|
+
if (backend !== "self") exitWithError("In Better-Fullstack, Stack Auth is currently supported only with the 'self' backend (fullstack Next.js). Please use '--backend self' or choose a different auth provider.");
|
|
1116
|
+
if (!hasNextJs) exitWithError("In Better-Fullstack, Stack Auth currently requires the Next.js frontend. Please use '--frontend next' or choose a different auth provider.");
|
|
1082
1117
|
}
|
|
1083
1118
|
function validateSupabaseAuthCompatibility(auth, backend, frontends = []) {
|
|
1084
1119
|
if (auth !== "supabase-auth") return;
|
|
1085
1120
|
const hasNextJs = frontends.includes("next");
|
|
1086
|
-
if (backend !== "self") exitWithError("Supabase Auth is
|
|
1087
|
-
if (!hasNextJs) exitWithError("Supabase Auth requires Next.js frontend. Please use '--frontend next' or choose a different auth provider.");
|
|
1121
|
+
if (backend !== "self") exitWithError("In Better-Fullstack, Supabase Auth is currently supported only with the 'self' backend (fullstack Next.js). Please use '--backend self' or choose a different auth provider.");
|
|
1122
|
+
if (!hasNextJs) exitWithError("In Better-Fullstack, Supabase Auth currently requires the Next.js frontend. Please use '--frontend next' or choose a different auth provider.");
|
|
1088
1123
|
}
|
|
1089
1124
|
function validateAuth0Compatibility(auth, backend, frontends = []) {
|
|
1090
1125
|
if (auth !== "auth0") return;
|
|
1091
1126
|
const hasNextJs = frontends.includes("next");
|
|
1092
|
-
if (backend !== "self") exitWithError("Auth0 is
|
|
1093
|
-
if (!hasNextJs) exitWithError("Auth0 requires Next.js frontend. Please use '--frontend next' or choose a different auth provider.");
|
|
1127
|
+
if (backend !== "self") exitWithError("In Better-Fullstack, Auth0 is currently supported only with the 'self' backend (fullstack Next.js). Please use '--backend self' or choose a different auth provider.");
|
|
1128
|
+
if (!hasNextJs) exitWithError("In Better-Fullstack, Auth0 currently requires the Next.js frontend. Please use '--frontend next' or choose a different auth provider.");
|
|
1094
1129
|
}
|
|
1095
1130
|
function allowedApisForFrontends(frontends = [], astroIntegration) {
|
|
1096
1131
|
const includesNuxt = frontends.includes("nuxt");
|
|
@@ -1125,6 +1160,24 @@ function isExampleAIAllowed(backend, frontends = []) {
|
|
|
1125
1160
|
}
|
|
1126
1161
|
return true;
|
|
1127
1162
|
}
|
|
1163
|
+
function hasExampleChatSdkSelfFrontend(frontends = []) {
|
|
1164
|
+
return frontends.some((f) => [
|
|
1165
|
+
"next",
|
|
1166
|
+
"tanstack-start",
|
|
1167
|
+
"nuxt"
|
|
1168
|
+
].includes(f));
|
|
1169
|
+
}
|
|
1170
|
+
function isExampleChatSdkAllowed(backend, frontends = [], runtime) {
|
|
1171
|
+
if (!backend || backend === "none" || backend === "convex") return false;
|
|
1172
|
+
if (backend === "self") return hasExampleChatSdkSelfFrontend(frontends);
|
|
1173
|
+
if (backend === "hono") return runtime === "node";
|
|
1174
|
+
return false;
|
|
1175
|
+
}
|
|
1176
|
+
function requiresChatSdkVercelAI(backend, frontends = [], runtime) {
|
|
1177
|
+
if (backend === "self" && frontends.includes("nuxt")) return true;
|
|
1178
|
+
if (backend === "hono" && runtime === "node") return true;
|
|
1179
|
+
return false;
|
|
1180
|
+
}
|
|
1128
1181
|
function validateWebDeployRequiresWebFrontend(webDeploy, hasWebFrontendFlag) {
|
|
1129
1182
|
if (webDeploy && webDeploy !== "none" && !hasWebFrontendFlag) exitWithError("'--web-deploy' requires a web frontend. Please select a web frontend or set '--web-deploy none'.");
|
|
1130
1183
|
}
|
|
@@ -1164,7 +1217,7 @@ function validatePaymentsCompatibility(payments, auth, _backend, frontends = [])
|
|
|
1164
1217
|
if (web.length === 0 && frontends.length > 0) exitWithError("Polar payments requires a web frontend or no frontend. Please select a web frontend or choose a different payments provider.");
|
|
1165
1218
|
}
|
|
1166
1219
|
}
|
|
1167
|
-
function validateExamplesCompatibility(examples, backend, frontend) {
|
|
1220
|
+
function validateExamplesCompatibility(examples, backend, frontend, runtime, ai) {
|
|
1168
1221
|
const examplesArr = examples ?? [];
|
|
1169
1222
|
if (examplesArr.length === 0 || examplesArr.includes("none")) return;
|
|
1170
1223
|
if (examplesArr.includes("ai") && (frontend ?? []).includes("solid")) exitWithError("The 'ai' example is not compatible with the Solid frontend.");
|
|
@@ -1174,6 +1227,17 @@ function validateExamplesCompatibility(examples, backend, frontend) {
|
|
|
1174
1227
|
const includesSvelte = frontendArr.includes("svelte");
|
|
1175
1228
|
if (includesNuxt || includesSvelte) exitWithError("The 'ai' example with Convex backend only supports React-based frontends (Next.js, TanStack Router, TanStack Start, React Router). Svelte and Nuxt are not supported with Convex AI.");
|
|
1176
1229
|
}
|
|
1230
|
+
if (examplesArr.includes("chat-sdk")) {
|
|
1231
|
+
const frontendArr = frontend ?? [];
|
|
1232
|
+
if (!isExampleChatSdkAllowed(backend, frontendArr, runtime)) {
|
|
1233
|
+
if (backend === "none") exitWithError("The 'chat-sdk' example requires a backend.");
|
|
1234
|
+
if (backend === "convex") exitWithError("The 'chat-sdk' example is not supported with the Convex backend in v1. Use self backend (Next.js, TanStack Start, Nuxt) or Hono with Node runtime.");
|
|
1235
|
+
if (backend === "self") exitWithError("The 'chat-sdk' example with self backend only supports Next.js, TanStack Start, or Nuxt frontends in v1.");
|
|
1236
|
+
if (backend === "hono" && runtime !== "node") exitWithError("The 'chat-sdk' example with Hono requires '--runtime node' in v1 (Bun/Workers not supported yet).");
|
|
1237
|
+
exitWithError("The 'chat-sdk' example is only supported with self backend (Next.js, TanStack Start, Nuxt) or Hono with Node runtime in v1.");
|
|
1238
|
+
}
|
|
1239
|
+
if (requiresChatSdkVercelAI(backend, frontendArr, runtime) && ai && ai !== "vercel-ai") exitWithError("The 'chat-sdk' example requires '--ai vercel-ai' for the selected stack in v1 (Nuxt Discord and Hono GitHub profiles).");
|
|
1240
|
+
}
|
|
1177
1241
|
}
|
|
1178
1242
|
/**
|
|
1179
1243
|
* Validates that a UI library is compatible with the selected frontend(s)
|
|
@@ -2404,7 +2468,8 @@ const SKILL_SOURCES = {
|
|
|
2404
2468
|
"prisma/skills": { label: "Prisma" },
|
|
2405
2469
|
"elysiajs/skills": { label: "ElysiaJS" },
|
|
2406
2470
|
"waynesutton/convexskills": { label: "Convex" },
|
|
2407
|
-
"msmps/opentui-skill": { label: "OpenTUI Platform" }
|
|
2471
|
+
"msmps/opentui-skill": { label: "OpenTUI Platform" },
|
|
2472
|
+
"haydenbleasel/ultracite": { label: "Ultracite" }
|
|
2408
2473
|
};
|
|
2409
2474
|
const AVAILABLE_AGENTS = [
|
|
2410
2475
|
{
|
|
@@ -2536,6 +2601,7 @@ function getRecommendedSourceKeys(config) {
|
|
|
2536
2601
|
if (backend === "elysia") sources.push("elysiajs/skills");
|
|
2537
2602
|
if (backend === "convex") sources.push("waynesutton/convexskills");
|
|
2538
2603
|
if (addons.includes("opentui")) sources.push("msmps/opentui-skill");
|
|
2604
|
+
if (addons.includes("ultracite")) sources.push("haydenbleasel/ultracite");
|
|
2539
2605
|
return sources;
|
|
2540
2606
|
}
|
|
2541
2607
|
const CURATED_SKILLS_BY_SOURCE = {
|
|
@@ -2593,7 +2659,8 @@ const CURATED_SKILLS_BY_SOURCE = {
|
|
|
2593
2659
|
"convex-migrations",
|
|
2594
2660
|
"convex-security-check"
|
|
2595
2661
|
],
|
|
2596
|
-
"msmps/opentui-skill": () => ["opentui"]
|
|
2662
|
+
"msmps/opentui-skill": () => ["opentui"],
|
|
2663
|
+
"haydenbleasel/ultracite": () => ["ultracite"]
|
|
2597
2664
|
};
|
|
2598
2665
|
function getCuratedSkillNamesForSourceKey(sourceKey, config) {
|
|
2599
2666
|
return CURATED_SKILLS_BY_SOURCE[sourceKey](config);
|
|
@@ -3563,16 +3630,26 @@ async function getAuthChoice(auth, backend, frontend) {
|
|
|
3563
3630
|
if (isCancel$1(response$1)) return exitCancelled("Operation cancelled");
|
|
3564
3631
|
return response$1;
|
|
3565
3632
|
}
|
|
3566
|
-
const
|
|
3633
|
+
const hasNextJs = frontend?.includes("next");
|
|
3634
|
+
const hasTanStackStart = frontend?.includes("tanstack-start");
|
|
3635
|
+
const isSelfBackend = backend === "self";
|
|
3636
|
+
const supportsNextJsAuth = hasNextJs && isSelfBackend;
|
|
3637
|
+
const hasNativeFrontend$2 = frontend?.some((f) => [
|
|
3638
|
+
"native-bare",
|
|
3639
|
+
"native-uniwind",
|
|
3640
|
+
"native-unistyles"
|
|
3641
|
+
].includes(f));
|
|
3642
|
+
const supportsClerkSelf = isSelfBackend && !hasNativeFrontend$2 && Boolean(hasNextJs || hasTanStackStart);
|
|
3567
3643
|
const options = [{
|
|
3568
3644
|
value: "better-auth",
|
|
3569
3645
|
label: "Better-Auth",
|
|
3570
3646
|
hint: "comprehensive auth framework for TypeScript"
|
|
3571
|
-
}
|
|
3647
|
+
}];
|
|
3648
|
+
if (supportsClerkSelf) options.push({
|
|
3572
3649
|
value: "clerk",
|
|
3573
3650
|
label: "Clerk",
|
|
3574
3651
|
hint: "More than auth, Complete User Management"
|
|
3575
|
-
}
|
|
3652
|
+
});
|
|
3576
3653
|
if (supportsNextJsAuth) {
|
|
3577
3654
|
options.push({
|
|
3578
3655
|
value: "nextauth",
|
|
@@ -4076,7 +4153,7 @@ async function getEmailChoice(email, backend) {
|
|
|
4076
4153
|
|
|
4077
4154
|
//#endregion
|
|
4078
4155
|
//#region src/prompts/examples.ts
|
|
4079
|
-
async function getExamplesChoice(examples, frontends, backend) {
|
|
4156
|
+
async function getExamplesChoice(examples, frontends, backend, runtime) {
|
|
4080
4157
|
if (examples !== void 0) return examples;
|
|
4081
4158
|
if (backend === "none") return [];
|
|
4082
4159
|
let response = [];
|
|
@@ -4086,6 +4163,11 @@ async function getExamplesChoice(examples, frontends, backend) {
|
|
|
4086
4163
|
label: "AI Chat",
|
|
4087
4164
|
hint: "A simple AI chat interface using AI SDK"
|
|
4088
4165
|
});
|
|
4166
|
+
if (isExampleChatSdkAllowed(backend, frontends ?? [], runtime)) options.push({
|
|
4167
|
+
value: "chat-sdk",
|
|
4168
|
+
label: "Chat SDK Bots",
|
|
4169
|
+
hint: "Framework-specific Chat SDK bot example (Slack/Discord/GitHub depending on stack)"
|
|
4170
|
+
});
|
|
4089
4171
|
if (options.length === 0) return [];
|
|
4090
4172
|
response = await navigableMultiselect({
|
|
4091
4173
|
message: "Include examples",
|
|
@@ -5621,7 +5703,7 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
|
|
|
5621
5703
|
},
|
|
5622
5704
|
examples: ({ results }) => {
|
|
5623
5705
|
if (results.ecosystem !== "typescript") return Promise.resolve([]);
|
|
5624
|
-
return getExamplesChoice(flags.examples, results.frontend, results.backend);
|
|
5706
|
+
return getExamplesChoice(flags.examples, results.frontend, results.backend, results.runtime);
|
|
5625
5707
|
},
|
|
5626
5708
|
dbSetup: ({ results }) => {
|
|
5627
5709
|
if (results.ecosystem !== "typescript") return Promise.resolve("none");
|
|
@@ -5637,6 +5719,7 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
|
|
|
5637
5719
|
},
|
|
5638
5720
|
ai: ({ results }) => {
|
|
5639
5721
|
if (results.ecosystem !== "typescript") return Promise.resolve("none");
|
|
5722
|
+
if (flags.ai === void 0 && results.examples?.includes("chat-sdk") && requiresChatSdkVercelAI(results.backend, results.frontend, results.runtime)) return Promise.resolve("vercel-ai");
|
|
5640
5723
|
return getAIChoice(flags.ai);
|
|
5641
5724
|
},
|
|
5642
5725
|
validation: ({ results }) => {
|
|
@@ -6510,7 +6593,7 @@ function validateDatabaseOrmAuth(cfg, flags) {
|
|
|
6510
6593
|
suggestions: ["Use --orm mongoose or --orm prisma for MongoDB", "Choose a different database (postgres, sqlite, mysql)"]
|
|
6511
6594
|
});
|
|
6512
6595
|
if (has("database") && has("orm") && db === "mongodb" && orm && orm !== "mongoose" && orm !== "prisma" && orm !== "none") incompatibilityError({
|
|
6513
|
-
message: "MongoDB only
|
|
6596
|
+
message: "In Better-Fullstack, MongoDB is currently supported only with Mongoose or Prisma ORM.",
|
|
6514
6597
|
provided: {
|
|
6515
6598
|
database: "mongodb",
|
|
6516
6599
|
orm
|
|
@@ -6593,7 +6676,7 @@ function validateDatabaseSetup(config, providedFlags) {
|
|
|
6593
6676
|
runtime: "workers",
|
|
6594
6677
|
errorMessage: "Cloudflare D1 setup requires SQLite database and Cloudflare Workers runtime."
|
|
6595
6678
|
},
|
|
6596
|
-
docker: { errorMessage: "Docker setup is not
|
|
6679
|
+
docker: { errorMessage: "In Better-Fullstack, Docker setup is currently not available with SQLite database or Cloudflare Workers runtime." },
|
|
6597
6680
|
none: { errorMessage: "" }
|
|
6598
6681
|
};
|
|
6599
6682
|
if (dbSetup && dbSetup !== "none") {
|
|
@@ -6603,8 +6686,8 @@ function validateDatabaseSetup(config, providedFlags) {
|
|
|
6603
6686
|
} else if (validation.database && database !== validation.database) exitWithError(validation.errorMessage);
|
|
6604
6687
|
if (validation.runtime && runtime !== validation.runtime) exitWithError(validation.errorMessage);
|
|
6605
6688
|
if (dbSetup === "docker") {
|
|
6606
|
-
if (database === "sqlite") exitWithError("Docker setup is not
|
|
6607
|
-
if (runtime === "workers") exitWithError("Docker setup is not
|
|
6689
|
+
if (database === "sqlite") exitWithError("In Better-Fullstack, Docker setup is currently not available with SQLite database. SQLite is file-based and doesn't require Docker. Please use '--database postgres', '--database mysql', '--database mongodb', or choose a different setup.");
|
|
6690
|
+
if (runtime === "workers") exitWithError("In Better-Fullstack, Docker setup is currently not available with Cloudflare Workers runtime. 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.");
|
|
6608
6691
|
}
|
|
6609
6692
|
}
|
|
6610
6693
|
}
|
|
@@ -6723,15 +6806,6 @@ function validateAdonisJSConstraints(config, providedFlags) {
|
|
|
6723
6806
|
}
|
|
6724
6807
|
function validateBackendConstraints(config, providedFlags, options) {
|
|
6725
6808
|
const { backend } = config;
|
|
6726
|
-
if (config.auth === "clerk" && backend !== "convex") exitWithError("Clerk authentication is only supported with the Convex backend. Please use '--backend convex' or choose a different auth provider.");
|
|
6727
|
-
if (backend === "convex" && config.auth === "clerk" && config.frontend) {
|
|
6728
|
-
const incompatibleFrontends = config.frontend.filter((f) => [
|
|
6729
|
-
"nuxt",
|
|
6730
|
-
"svelte",
|
|
6731
|
-
"solid"
|
|
6732
|
-
].includes(f));
|
|
6733
|
-
if (incompatibleFrontends.length > 0) exitWithError(`Clerk authentication is not compatible with the following frontends: ${incompatibleFrontends.join(", ")}. Please choose a different frontend or auth provider.`);
|
|
6734
|
-
}
|
|
6735
6809
|
if (providedFlags.has("backend") && backend && backend !== "convex" && backend !== "none" && backend !== "self" && backend !== "encore") {
|
|
6736
6810
|
if (providedFlags.has("runtime") && options.runtime === "none") exitWithError("'--runtime none' is only supported with '--backend convex', '--backend none', '--backend self', or '--backend encore'. Please choose 'bun', 'node', or remove the --runtime flag.");
|
|
6737
6811
|
}
|
|
@@ -6770,8 +6844,9 @@ function validateFullConfig(config, providedFlags, options) {
|
|
|
6770
6844
|
validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth);
|
|
6771
6845
|
config.addons = [...new Set(config.addons)];
|
|
6772
6846
|
}
|
|
6773
|
-
validateExamplesCompatibility(config.examples ?? [], config.backend, config.frontend ?? []);
|
|
6847
|
+
validateExamplesCompatibility(config.examples ?? [], config.backend, config.frontend ?? [], config.runtime, config.ai);
|
|
6774
6848
|
validatePaymentsCompatibility(config.payments, config.auth, config.backend, config.frontend ?? []);
|
|
6849
|
+
validateClerkCompatibility(config.auth, config.backend, config.frontend ?? []);
|
|
6775
6850
|
validateNextAuthCompatibility(config.auth, config.backend, config.frontend ?? []);
|
|
6776
6851
|
validateStackAuthCompatibility(config.auth, config.backend, config.frontend ?? []);
|
|
6777
6852
|
validateSupabaseAuthCompatibility(config.auth, config.backend, config.frontend ?? []);
|
|
@@ -6786,12 +6861,13 @@ function validateConfigForProgrammaticUse(config) {
|
|
|
6786
6861
|
if (config.frontend && config.frontend.length > 0) ensureSingleWebAndNative(config.frontend);
|
|
6787
6862
|
validateApiFrontendCompatibility(config.api, config.frontend, config.astroIntegration);
|
|
6788
6863
|
validatePaymentsCompatibility(config.payments, config.auth, config.backend, config.frontend);
|
|
6864
|
+
validateClerkCompatibility(config.auth, config.backend, config.frontend ?? []);
|
|
6789
6865
|
validateNextAuthCompatibility(config.auth, config.backend, config.frontend ?? []);
|
|
6790
6866
|
validateStackAuthCompatibility(config.auth, config.backend, config.frontend ?? []);
|
|
6791
6867
|
validateSupabaseAuthCompatibility(config.auth, config.backend, config.frontend ?? []);
|
|
6792
6868
|
validateAuth0Compatibility(config.auth, config.backend, config.frontend ?? []);
|
|
6793
6869
|
if (config.addons && config.addons.length > 0) validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth);
|
|
6794
|
-
validateExamplesCompatibility(config.examples ?? [], config.backend, config.frontend ?? []);
|
|
6870
|
+
validateExamplesCompatibility(config.examples ?? [], config.backend, config.frontend ?? [], config.runtime, config.ai);
|
|
6795
6871
|
validateUILibraryFrontendCompatibility(config.uiLibrary, config.frontend ?? [], config.astroIntegration);
|
|
6796
6872
|
validateUILibraryCSSFrameworkCompatibility(config.uiLibrary, config.cssFramework);
|
|
6797
6873
|
validatePeerDependencies(config);
|
|
@@ -8113,7 +8189,7 @@ async function displayPostInstallInstructions(config) {
|
|
|
8113
8189
|
const nativeInstructions = (frontend?.includes("native-bare") || frontend?.includes("native-uniwind") || frontend?.includes("native-unistyles")) && backend !== "none" ? getNativeInstructions(isConvex, isBackendSelf, frontend || [], runCmd) : "";
|
|
8114
8190
|
const pwaInstructions = addons?.includes("pwa") && frontend?.includes("react-router") ? getPwaInstructions() : "";
|
|
8115
8191
|
const starlightInstructions = addons?.includes("starlight") ? getStarlightInstructions(runCmd) : "";
|
|
8116
|
-
const clerkInstructions =
|
|
8192
|
+
const clerkInstructions = config.auth === "clerk" ? getClerkInstructions(config.backend, config.frontend ?? []) : "";
|
|
8117
8193
|
const polarInstructions = config.payments === "polar" && config.auth === "better-auth" ? getPolarInstructions(backend) : "";
|
|
8118
8194
|
const alchemyDeployInstructions = getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy, backend);
|
|
8119
8195
|
const hasWeb = frontend?.some((f) => WEB_FRAMEWORKS.includes(f));
|
|
@@ -8244,8 +8320,13 @@ function getNoOrmWarning() {
|
|
|
8244
8320
|
function getBunWebNativeWarning() {
|
|
8245
8321
|
return `\n${pc.yellow("WARNING:")} 'bun' might cause issues with web + native apps in a monorepo.\n Use 'pnpm' if problems arise.`;
|
|
8246
8322
|
}
|
|
8247
|
-
function getClerkInstructions() {
|
|
8248
|
-
return `${pc.bold("Clerk Authentication Setup:")}\n${pc.cyan("•")} Follow the guide: ${pc.underline("https://docs.convex.dev/auth/clerk")}\n${pc.cyan("•")} Set CLERK_JWT_ISSUER_DOMAIN in Convex Dashboard\n${pc.cyan("•")} Set CLERK_PUBLISHABLE_KEY in apps/*/.env`;
|
|
8323
|
+
function getClerkInstructions(backend, frontend) {
|
|
8324
|
+
if (backend === "convex") return `${pc.bold("Clerk Authentication Setup:")}\n${pc.cyan("•")} Follow the guide: ${pc.underline("https://docs.convex.dev/auth/clerk")}\n${pc.cyan("•")} Set CLERK_JWT_ISSUER_DOMAIN in Convex Dashboard\n${pc.cyan("•")} Set CLERK_PUBLISHABLE_KEY in apps/*/.env`;
|
|
8325
|
+
if (backend === "self" && (frontend.includes("next") || frontend.includes("tanstack-start"))) {
|
|
8326
|
+
const publishableKeyVar = frontend.includes("next") ? "NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY" : "VITE_CLERK_PUBLISHABLE_KEY";
|
|
8327
|
+
return `${pc.bold("Clerk Authentication Setup:")}\n${pc.cyan("•")} Create an application in ${pc.underline("https://dashboard.clerk.com/")}\n${pc.cyan("•")} Set ${publishableKeyVar} in ${pc.white("apps/web/.env")}\n${pc.cyan("•")} Set CLERK_SECRET_KEY in ${pc.white("apps/web/.env")}\n${pc.cyan("•")} Clerk middleware and a protected dashboard route are already generated`;
|
|
8328
|
+
}
|
|
8329
|
+
return "";
|
|
8249
8330
|
}
|
|
8250
8331
|
function getBetterAuthConvexInstructions(hasWeb, webPort, packageManager) {
|
|
8251
8332
|
const cmd = packageManager === "npm" ? "npx" : packageManager;
|
|
@@ -8433,7 +8514,7 @@ async function setPackageManagerVersion(projectDir, packageManager) {
|
|
|
8433
8514
|
const pkgJsonPath = path.join(projectDir, "package.json");
|
|
8434
8515
|
if (!await fs.pathExists(pkgJsonPath)) return;
|
|
8435
8516
|
try {
|
|
8436
|
-
const { stdout } = await
|
|
8517
|
+
const { stdout } = await $({ cwd: os$1.tmpdir() })`${packageManager} -v`;
|
|
8437
8518
|
const version = stdout.trim();
|
|
8438
8519
|
const pkgJson = await fs.readJson(pkgJsonPath);
|
|
8439
8520
|
pkgJson.packageManager = `${packageManager}@${version}`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-better-fullstack",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
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",
|
|
@@ -76,8 +76,8 @@
|
|
|
76
76
|
"prepublishOnly": "npm run build"
|
|
77
77
|
},
|
|
78
78
|
"dependencies": {
|
|
79
|
-
"@better-fullstack/template-generator": "^1.
|
|
80
|
-
"@better-fullstack/types": "^1.
|
|
79
|
+
"@better-fullstack/template-generator": "^1.4.0",
|
|
80
|
+
"@better-fullstack/types": "^1.4.0",
|
|
81
81
|
"@clack/core": "^0.5.0",
|
|
82
82
|
"@clack/prompts": "^1.0.0-alpha.8",
|
|
83
83
|
"@orpc/server": "^1.13.0",
|