mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
add solid in stack builder
This commit is contained in:
1
apps/web/public/icon/solid.svg
Normal file
1
apps/web/public/icon/solid.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 256 239" width="256" height="239" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><defs><linearGradient x1="-5.859%" y1="38.27%" x2="91.406%" y2="60.924%" id="a"><stop stop-color="#76B3E1" offset="10%"/><stop stop-color="#DCF2FD" offset="30%"/><stop stop-color="#76B3E1" offset="100%"/></linearGradient><linearGradient x1="56.996%" y1="38.44%" x2="37.941%" y2="68.375%" id="b"><stop stop-color="#76B3E1" offset="0%"/><stop stop-color="#4377BB" offset="50%"/><stop stop-color="#1F3B77" offset="100%"/></linearGradient><linearGradient x1="10.709%" y1="34.532%" x2="104.337%" y2="70.454%" id="c"><stop stop-color="#315AA9" offset="0%"/><stop stop-color="#518AC8" offset="50%"/><stop stop-color="#315AA9" offset="100%"/></linearGradient><linearGradient x1="61.993%" y1="29.58%" x2="17.762%" y2="105.119%" id="d"><stop stop-color="#4377BB" offset="0%"/><stop stop-color="#1A336B" offset="50%"/><stop stop-color="#1A336B" offset="100%"/></linearGradient></defs><path d="M256 50.473S170.667-12.32 104.654 2.17l-4.83 1.61c-9.66 3.22-17.71 8.05-22.541 14.49l-3.22 4.83-24.151 41.862 41.862 8.05c17.71 11.271 40.251 16.101 61.182 11.271l74.063 14.49L256 50.474Z" fill="#76B3E1"/><path d="M256 50.473S170.667-12.32 104.654 2.17l-4.83 1.61c-9.66 3.22-17.71 8.05-22.541 14.49l-3.22 4.83-24.151 41.862 41.862 8.05c17.71 11.271 40.251 16.101 61.182 11.271l74.063 14.49L256 50.474Z" fill="url(#a)" opacity=".3"/><path d="m77.283 50.473-6.44 1.61c-27.371 8.05-35.422 33.811-20.931 56.352 16.1 20.931 49.912 32.201 77.283 24.151l99.824-33.811S141.686 35.982 77.283 50.473Z" fill="#518AC8"/><path d="m77.283 50.473-6.44 1.61c-27.371 8.05-35.422 33.811-20.931 56.352 16.1 20.931 49.912 32.201 77.283 24.151l99.824-33.811S141.686 35.982 77.283 50.473Z" fill="url(#b)" opacity=".3"/><path d="M209.308 122.926c-18.44-23.037-49.007-32.59-77.283-24.151l-99.824 32.201L0 187.328l180.327 30.591 32.201-57.962c6.44-11.27 4.83-24.15-3.22-37.031Z" fill="url(#c)"/><path d="M177.107 179.278c-18.44-23.037-49.008-32.59-77.283-24.151L0 187.328s85.333 64.403 151.346 48.302l4.83-1.61c27.371-8.05 37.032-33.811 20.93-54.742Z" fill="url(#d)"/></svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -13,6 +13,7 @@ import {
|
||||
PRESET_TEMPLATES,
|
||||
type StackState,
|
||||
TECH_OPTIONS,
|
||||
isStackDefault,
|
||||
} from "@/lib/constant";
|
||||
import { stackParsers, stackQueryStatesOptions } from "@/lib/stack-url-state";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -87,17 +88,20 @@ const hasWebFrontend = (frontend: string[]) =>
|
||||
"next",
|
||||
"nuxt",
|
||||
"svelte",
|
||||
"solid",
|
||||
].includes(f),
|
||||
);
|
||||
|
||||
const hasNativeFrontend = (frontend: string[]) => frontend.includes("native");
|
||||
|
||||
const hasPWACompatibleFrontend = (frontend: string[]) =>
|
||||
frontend.some((f) => ["tanstack-router", "react-router"].includes(f));
|
||||
frontend.some((f) =>
|
||||
["tanstack-router", "react-router", "solid"].includes(f),
|
||||
);
|
||||
|
||||
const hasTauriCompatibleFrontend = (frontend: string[]) =>
|
||||
frontend.some((f) =>
|
||||
["tanstack-router", "react-router", "nuxt", "svelte"].includes(f),
|
||||
["tanstack-router", "react-router", "nuxt", "svelte", "solid"].includes(f),
|
||||
);
|
||||
|
||||
const getBadgeColors = (category: string): string => {
|
||||
@@ -215,6 +219,34 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
||||
});
|
||||
}
|
||||
}
|
||||
const incompatibleConvexFrontends = ["nuxt", "solid"];
|
||||
const originalFrontendLength = nextStack.frontend.length;
|
||||
nextStack.frontend = nextStack.frontend.filter(
|
||||
(f) => !incompatibleConvexFrontends.includes(f),
|
||||
);
|
||||
if (nextStack.frontend.length !== originalFrontendLength) {
|
||||
changed = true;
|
||||
notes.frontend.notes.push(
|
||||
"Nuxt and Solid are not compatible with Convex backend and have been removed.",
|
||||
);
|
||||
notes.backend.notes.push(
|
||||
"Convex backend is not compatible with Nuxt or Solid.",
|
||||
);
|
||||
notes.frontend.hasIssue = true;
|
||||
notes.backend.hasIssue = true;
|
||||
changes.push({
|
||||
category: "convex",
|
||||
message: "Removed incompatible frontends (Nuxt, Solid)",
|
||||
});
|
||||
}
|
||||
if (nextStack.frontend.length === 0) {
|
||||
nextStack.frontend = ["tanstack-router"];
|
||||
changed = true;
|
||||
changes.push({
|
||||
category: "convex",
|
||||
message: "Frontend defaulted to TanStack Router",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (nextStack.runtime === "none") {
|
||||
notes.runtime.notes.push(
|
||||
@@ -310,18 +342,18 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
||||
} else {
|
||||
if (nextStack.orm === "mongoose") {
|
||||
notes.database.notes.push(
|
||||
"Relational databases are not compatible with Mongoose ORM",
|
||||
"Relational databases are not compatible with Mongoose ORM. Defaulting to Drizzle.",
|
||||
);
|
||||
notes.orm.notes.push(
|
||||
"Relational databases are not compatible with Mongoose ORM",
|
||||
"Mongoose ORM only works with MongoDB. Defaulting to Drizzle.",
|
||||
);
|
||||
notes.database.hasIssue = true;
|
||||
notes.orm.hasIssue = true;
|
||||
nextStack.orm = "prisma";
|
||||
nextStack.orm = "drizzle";
|
||||
changed = true;
|
||||
changes.push({
|
||||
category: "database",
|
||||
message: "ORM set to 'Prisma' (Mongoose only works with MongoDB)",
|
||||
message: "ORM set to 'Drizzle' (Mongoose only works with MongoDB)",
|
||||
});
|
||||
}
|
||||
if (nextStack.dbSetup === "mongodb-atlas") {
|
||||
@@ -457,8 +489,9 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
||||
|
||||
const isNuxt = nextStack.frontend.includes("nuxt");
|
||||
const isSvelte = nextStack.frontend.includes("svelte");
|
||||
if ((isNuxt || isSvelte) && nextStack.api === "trpc") {
|
||||
const frontendName = isNuxt ? "Nuxt" : "Svelte";
|
||||
const isSolid = nextStack.frontend.includes("solid");
|
||||
if ((isNuxt || isSvelte || isSolid) && nextStack.api === "trpc") {
|
||||
const frontendName = isNuxt ? "Nuxt" : isSvelte ? "Svelte" : "Solid";
|
||||
notes.api.notes.push(
|
||||
`${frontendName} requires oRPC. It will be selected automatically.`,
|
||||
);
|
||||
@@ -482,25 +515,25 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
||||
if (!isPWACompat && nextStack.addons.includes("pwa")) {
|
||||
incompatibleAddons.push("pwa");
|
||||
notes.frontend.notes.push(
|
||||
"PWA addon requires TanStack or React Router. Addon will be removed.",
|
||||
"PWA addon requires TanStack/React Router or Solid. Addon will be removed.",
|
||||
);
|
||||
notes.addons.notes.push(
|
||||
"PWA requires TanStack/React Router. It will be removed.",
|
||||
"PWA requires TanStack/React Router/Solid. It will be removed.",
|
||||
);
|
||||
notes.frontend.hasIssue = true;
|
||||
notes.addons.hasIssue = true;
|
||||
changes.push({
|
||||
category: "addons",
|
||||
message: "PWA addon removed (requires TanStack or React Router)",
|
||||
message: "PWA addon removed (requires compatible frontend)",
|
||||
});
|
||||
}
|
||||
if (!isTauriCompat && nextStack.addons.includes("tauri")) {
|
||||
incompatibleAddons.push("tauri");
|
||||
notes.frontend.notes.push(
|
||||
"Tauri addon requires TanStack Router, React Router, Nuxt or Svelte. Addon will be removed.",
|
||||
"Tauri addon requires TanStack/React Router, Nuxt, Svelte or Solid. Addon will be removed.",
|
||||
);
|
||||
notes.addons.notes.push(
|
||||
"Tauri requires TanStack/React Router/Nuxt/Svelte. It will be removed.",
|
||||
"Tauri requires TanStack/React Router/Nuxt/Svelte/Solid. It will be removed.",
|
||||
);
|
||||
notes.frontend.hasIssue = true;
|
||||
notes.addons.hasIssue = true;
|
||||
@@ -529,8 +562,7 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
||||
|
||||
const incompatibleExamples: string[] = [];
|
||||
const isWeb = hasWebFrontend(nextStack.frontend);
|
||||
const isNativeOnly =
|
||||
hasNativeFrontend(nextStack.frontend) && !isWeb && !isConvex;
|
||||
const isNativeOnly = hasNativeFrontend(nextStack.frontend) && !isWeb;
|
||||
|
||||
if (isNativeOnly) {
|
||||
if (nextStack.examples.length > 0) {
|
||||
@@ -538,7 +570,7 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
||||
"Examples are not supported with Native-only frontend. Examples will be removed.",
|
||||
);
|
||||
notes.examples.notes.push(
|
||||
"Examples require a web frontend or Convex backend. They will be removed.",
|
||||
"Examples require a web frontend. They will be removed.",
|
||||
);
|
||||
notes.frontend.hasIssue = true;
|
||||
notes.examples.hasIssue = true;
|
||||
@@ -582,23 +614,31 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
||||
message: "AI example removed (not compatible with Elysia)",
|
||||
});
|
||||
}
|
||||
if (isSolid && nextStack.examples.includes("ai")) {
|
||||
incompatibleExamples.push("ai");
|
||||
changes.push({
|
||||
category: "examples",
|
||||
message: "AI example removed (not compatible with Solid)",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const uniqueIncompatibleExamples = [...new Set(incompatibleExamples)];
|
||||
if (uniqueIncompatibleExamples.length > 0) {
|
||||
if (
|
||||
!isWeb &&
|
||||
(uniqueIncompatibleExamples.includes("todo") ||
|
||||
uniqueIncompatibleExamples.includes("ai"))
|
||||
) {
|
||||
notes.frontend.notes.push(
|
||||
"Examples require a web frontend. Incompatible examples will be removed.",
|
||||
);
|
||||
notes.examples.notes.push(
|
||||
"Requires a web frontend. Incompatible examples will be removed.",
|
||||
);
|
||||
notes.frontend.hasIssue = true;
|
||||
notes.examples.hasIssue = true;
|
||||
if (!isWeb && !isNativeOnly) {
|
||||
if (
|
||||
uniqueIncompatibleExamples.includes("todo") ||
|
||||
uniqueIncompatibleExamples.includes("ai")
|
||||
) {
|
||||
notes.frontend.notes.push(
|
||||
"Examples require a web frontend. Incompatible examples will be removed.",
|
||||
);
|
||||
notes.examples.notes.push(
|
||||
"Requires a web frontend. Incompatible examples will be removed.",
|
||||
);
|
||||
notes.frontend.hasIssue = true;
|
||||
notes.examples.hasIssue = true;
|
||||
}
|
||||
}
|
||||
if (
|
||||
nextStack.database === "none" &&
|
||||
@@ -626,6 +666,16 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
||||
notes.backend.hasIssue = true;
|
||||
notes.examples.hasIssue = true;
|
||||
}
|
||||
if (isSolid && uniqueIncompatibleExamples.includes("ai")) {
|
||||
notes.frontend.notes.push(
|
||||
"AI example is not compatible with Solid. It will be removed.",
|
||||
);
|
||||
notes.examples.notes.push(
|
||||
"AI example is not compatible with Solid. It will be removed.",
|
||||
);
|
||||
notes.frontend.hasIssue = true;
|
||||
notes.examples.hasIssue = true;
|
||||
}
|
||||
|
||||
const originalExamplesLength = nextStack.examples.length;
|
||||
nextStack.examples = nextStack.examples.filter(
|
||||
@@ -647,6 +697,9 @@ const getCompatibilityRules = (stack: StackState) => {
|
||||
const hasWebFrontendSelected = hasWebFrontend(stack.frontend);
|
||||
const hasNativeOnly =
|
||||
hasNativeFrontend(stack.frontend) && !hasWebFrontendSelected;
|
||||
const hasSolid = stack.frontend.includes("solid");
|
||||
const hasNuxt = stack.frontend.includes("nuxt");
|
||||
const hasSvelte = stack.frontend.includes("svelte");
|
||||
|
||||
return {
|
||||
isConvex,
|
||||
@@ -655,8 +708,10 @@ const getCompatibilityRules = (stack: StackState) => {
|
||||
hasNativeOnly,
|
||||
hasPWACompatible: hasPWACompatibleFrontend(stack.frontend),
|
||||
hasTauriCompatible: hasTauriCompatibleFrontend(stack.frontend),
|
||||
hasNuxtOrSvelte:
|
||||
stack.frontend.includes("nuxt") || stack.frontend.includes("svelte"),
|
||||
hasNuxtOrSvelteOrSolid: hasNuxt || hasSvelte || hasSolid,
|
||||
hasSolid,
|
||||
hasNuxt,
|
||||
hasSvelte,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -675,42 +730,14 @@ const generateCommand = (stackState: StackState): string => {
|
||||
}
|
||||
|
||||
const projectName = stackState.projectName || "my-better-t-app";
|
||||
const flags: string[] = ["--yes"];
|
||||
const flags: string[] = [];
|
||||
|
||||
const isDefault = <K extends keyof StackState>(
|
||||
const checkDefault = <K extends keyof StackState>(
|
||||
key: K,
|
||||
value: StackState[K],
|
||||
) => {
|
||||
const defaultValue = DEFAULT_STACK[key];
|
||||
) => isStackDefault(stackState, key, value);
|
||||
|
||||
if (stackState.backend === "convex") {
|
||||
if (key === "runtime" && value === "none") return true;
|
||||
if (key === "database" && value === "none") return true;
|
||||
if (key === "orm" && value === "none") return true;
|
||||
if (key === "api" && value === "none") return true;
|
||||
if (key === "auth" && value === "false") return true;
|
||||
if (key === "dbSetup" && value === "none") return true;
|
||||
if (
|
||||
key === "examples" &&
|
||||
Array.isArray(value) &&
|
||||
value.length === 1 &&
|
||||
value[0] === "todo"
|
||||
)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Array.isArray(defaultValue) && Array.isArray(value)) {
|
||||
const sortedDefault = [...defaultValue].sort();
|
||||
const sortedValue = [...value].sort();
|
||||
return (
|
||||
sortedDefault.length === sortedValue.length &&
|
||||
sortedDefault.every((item, index) => item === sortedValue[index])
|
||||
);
|
||||
}
|
||||
return defaultValue === value;
|
||||
};
|
||||
|
||||
if (!isDefault("frontend", stackState.frontend)) {
|
||||
if (!checkDefault("frontend", stackState.frontend)) {
|
||||
if (stackState.frontend.length === 0 || stackState.frontend[0] === "none") {
|
||||
flags.push("--frontend none");
|
||||
} else {
|
||||
@@ -718,51 +745,51 @@ const generateCommand = (stackState: StackState): string => {
|
||||
}
|
||||
}
|
||||
|
||||
if (!isDefault("backend", stackState.backend)) {
|
||||
if (!checkDefault("backend", stackState.backend)) {
|
||||
flags.push(`--backend ${stackState.backend}`);
|
||||
}
|
||||
|
||||
if (stackState.backend !== "convex") {
|
||||
if (!isDefault("runtime", stackState.runtime)) {
|
||||
if (!checkDefault("runtime", stackState.runtime)) {
|
||||
flags.push(`--runtime ${stackState.runtime}`);
|
||||
}
|
||||
if (!isDefault("api", stackState.api)) {
|
||||
if (!checkDefault("api", stackState.api)) {
|
||||
flags.push(`--api ${stackState.api}`);
|
||||
}
|
||||
if (!isDefault("database", stackState.database)) {
|
||||
if (!checkDefault("database", stackState.database)) {
|
||||
flags.push(`--database ${stackState.database}`);
|
||||
}
|
||||
if (!isDefault("orm", stackState.orm)) {
|
||||
if (!checkDefault("orm", stackState.orm)) {
|
||||
flags.push(`--orm ${stackState.orm}`);
|
||||
}
|
||||
if (!isDefault("auth", stackState.auth)) {
|
||||
if (!checkDefault("auth", stackState.auth)) {
|
||||
if (stackState.auth === "false" && DEFAULT_STACK.auth === "true") {
|
||||
flags.push("--no-auth");
|
||||
}
|
||||
}
|
||||
if (!isDefault("dbSetup", stackState.dbSetup)) {
|
||||
if (!checkDefault("dbSetup", stackState.dbSetup)) {
|
||||
flags.push(`--db-setup ${stackState.dbSetup}`);
|
||||
}
|
||||
} else {
|
||||
if (stackState.auth === "false" && DEFAULT_STACK.auth === "true") {
|
||||
if (DEFAULT_STACK.auth === "true") {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isDefault("packageManager", stackState.packageManager)) {
|
||||
if (!checkDefault("packageManager", stackState.packageManager)) {
|
||||
flags.push(`--package-manager ${stackState.packageManager}`);
|
||||
}
|
||||
|
||||
if (!isDefault("git", stackState.git)) {
|
||||
if (stackState.git === "false") flags.push("--no-git");
|
||||
if (!checkDefault("git", stackState.git)) {
|
||||
if (stackState.git === "false" && DEFAULT_STACK.git === "true") {
|
||||
flags.push("--no-git");
|
||||
}
|
||||
}
|
||||
|
||||
if (!isDefault("install", stackState.install)) {
|
||||
if (stackState.install === "false") flags.push("--no-install");
|
||||
if (!checkDefault("install", stackState.install)) {
|
||||
if (stackState.install === "false" && DEFAULT_STACK.install === "true") {
|
||||
flags.push("--no-install");
|
||||
}
|
||||
}
|
||||
|
||||
if (!isDefault("addons", stackState.addons)) {
|
||||
if (!checkDefault("addons", stackState.addons)) {
|
||||
if (stackState.addons.length > 0) {
|
||||
flags.push(`--addons ${stackState.addons.join(" ")}`);
|
||||
} else {
|
||||
@@ -772,7 +799,7 @@ const generateCommand = (stackState: StackState): string => {
|
||||
}
|
||||
}
|
||||
|
||||
if (!isDefault("examples", stackState.examples)) {
|
||||
if (!checkDefault("examples", stackState.examples)) {
|
||||
if (stackState.examples.length > 0) {
|
||||
flags.push(`--examples ${stackState.examples.join(" ")}`);
|
||||
} else {
|
||||
@@ -782,10 +809,6 @@ const generateCommand = (stackState: StackState): string => {
|
||||
}
|
||||
}
|
||||
|
||||
if (flags.length === 1 && flags[0] === "--yes") {
|
||||
flags.pop();
|
||||
}
|
||||
|
||||
return `${base} ${projectName}${
|
||||
flags.length > 0 ? ` ${flags.join(" ")}` : ""
|
||||
}`;
|
||||
@@ -842,7 +865,7 @@ const StackArchitect = () => {
|
||||
catKey,
|
||||
)
|
||||
) {
|
||||
const convexDefaults: Record<string, string> = {
|
||||
const convexDefaults: Record<string, string | string[]> = {
|
||||
runtime: "none",
|
||||
database: "none",
|
||||
orm: "none",
|
||||
@@ -860,7 +883,6 @@ const StackArchitect = () => {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (catKey === "examples" && techId !== "todo") {
|
||||
addRule(
|
||||
category,
|
||||
@@ -868,6 +890,16 @@ const StackArchitect = () => {
|
||||
"Convex backend only supports the 'Todo' example.",
|
||||
);
|
||||
}
|
||||
if (
|
||||
catKey === "frontend" &&
|
||||
(techId === "nuxt" || techId === "solid")
|
||||
) {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
`${tech.name} is not compatible with Convex backend.`,
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -887,11 +919,12 @@ const StackArchitect = () => {
|
||||
"API 'None' is only available with the Convex backend.",
|
||||
);
|
||||
}
|
||||
|
||||
if (techId === "trpc" && rules.hasNuxtOrSvelte) {
|
||||
const frontendName = stack.frontend.includes("nuxt")
|
||||
if (techId === "trpc" && rules.hasNuxtOrSvelteOrSolid) {
|
||||
const frontendName = rules.hasNuxt
|
||||
? "Nuxt"
|
||||
: "Svelte";
|
||||
: rules.hasSvelte
|
||||
? "Svelte"
|
||||
: "Solid";
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
@@ -908,7 +941,6 @@ const StackArchitect = () => {
|
||||
"Select a database to enable ORM options.",
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
stack.database === "mongodb" &&
|
||||
techId !== "prisma" &&
|
||||
@@ -921,36 +953,31 @@ const StackArchitect = () => {
|
||||
"MongoDB requires the Prisma or Mongoose ORM.",
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
stack.dbSetup === "turso" &&
|
||||
techId !== "drizzle" &&
|
||||
techId !== "none"
|
||||
stack.database !== "mongodb" &&
|
||||
stack.database !== "none" &&
|
||||
techId === "mongoose"
|
||||
) {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"Turso DB setup requires the Drizzle ORM.",
|
||||
"Mongoose ORM is only compatible with MongoDB.",
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
stack.dbSetup === "prisma-postgres" &&
|
||||
techId !== "prisma" &&
|
||||
techId !== "none"
|
||||
) {
|
||||
if (stack.dbSetup === "turso" && techId !== "drizzle") {
|
||||
addRule(category, techId, "Turso DB setup requires Drizzle ORM.");
|
||||
}
|
||||
if (stack.dbSetup === "prisma-postgres" && techId !== "prisma") {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"Prisma PostgreSQL setup requires Prisma ORM.",
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
stack.dbSetup === "mongodb-atlas" &&
|
||||
techId !== "prisma" &&
|
||||
techId !== "mongoose" &&
|
||||
techId !== "none"
|
||||
techId !== "mongoose"
|
||||
) {
|
||||
addRule(
|
||||
category,
|
||||
@@ -958,7 +985,6 @@ const StackArchitect = () => {
|
||||
"MongoDB Atlas setup requires Prisma or Mongoose ORM.",
|
||||
);
|
||||
}
|
||||
|
||||
if (techId === "none") {
|
||||
if (stack.database === "mongodb") {
|
||||
addRule(
|
||||
@@ -973,14 +999,13 @@ const StackArchitect = () => {
|
||||
if (stack.dbSetup === "prisma-postgres") {
|
||||
addRule(category, techId, "This DB setup requires Prisma ORM.");
|
||||
}
|
||||
}
|
||||
|
||||
if (techId === "mongoose" && stack.database !== "mongodb") {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"Mongoose ORM is not compatible with relational databases.",
|
||||
);
|
||||
if (stack.dbSetup === "mongodb-atlas") {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"This DB setup requires Prisma or Mongoose ORM.",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -991,36 +1016,32 @@ const StackArchitect = () => {
|
||||
techId,
|
||||
"Select a database before choosing a cloud setup.",
|
||||
);
|
||||
}
|
||||
|
||||
if (techId === "turso") {
|
||||
if (stack.database !== "sqlite" && stack.database !== "none") {
|
||||
addRule(category, techId, "Turso requires SQLite database.");
|
||||
}
|
||||
if (stack.orm !== "drizzle" && stack.orm !== "none") {
|
||||
addRule(category, techId, "Turso requires Drizzle ORM.");
|
||||
}
|
||||
} else if (techId === "prisma-postgres") {
|
||||
if (stack.database !== "postgres" && stack.database !== "none") {
|
||||
addRule(category, techId, "Requires PostgreSQL database.");
|
||||
}
|
||||
if (stack.orm !== "prisma" && stack.orm !== "none") {
|
||||
addRule(category, techId, "Requires Prisma ORM.");
|
||||
}
|
||||
} else if (techId === "mongodb-atlas") {
|
||||
if (stack.database !== "mongodb" && stack.database !== "none") {
|
||||
addRule(category, techId, "Requires MongoDB database.");
|
||||
}
|
||||
if (
|
||||
stack.orm !== "prisma" &&
|
||||
stack.orm !== "mongoose" &&
|
||||
stack.orm !== "none"
|
||||
) {
|
||||
addRule(category, techId, "Requires Prisma or Mongoose ORM.");
|
||||
}
|
||||
} else if (techId === "neon") {
|
||||
if (stack.database !== "postgres" && stack.database !== "none") {
|
||||
addRule(category, techId, "Requires PostgreSQL database.");
|
||||
} else {
|
||||
if (techId === "turso") {
|
||||
if (stack.database !== "sqlite") {
|
||||
addRule(category, techId, "Turso requires SQLite database.");
|
||||
}
|
||||
if (stack.orm !== "drizzle") {
|
||||
addRule(category, techId, "Turso requires Drizzle ORM.");
|
||||
}
|
||||
} else if (techId === "prisma-postgres") {
|
||||
if (stack.database !== "postgres") {
|
||||
addRule(category, techId, "Requires PostgreSQL database.");
|
||||
}
|
||||
if (stack.orm !== "prisma") {
|
||||
addRule(category, techId, "Requires Prisma ORM.");
|
||||
}
|
||||
} else if (techId === "mongodb-atlas") {
|
||||
if (stack.database !== "mongodb") {
|
||||
addRule(category, techId, "Requires MongoDB database.");
|
||||
}
|
||||
if (stack.orm !== "prisma" && stack.orm !== "mongoose") {
|
||||
addRule(category, techId, "Requires Prisma or Mongoose ORM.");
|
||||
}
|
||||
} else if (techId === "neon") {
|
||||
if (stack.database !== "postgres") {
|
||||
addRule(category, techId, "Requires PostgreSQL database.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1038,15 +1059,14 @@ const StackArchitect = () => {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"Requires TanStack Router or React Router frontend.",
|
||||
"Requires TanStack Router, React Router or Solid frontend.",
|
||||
);
|
||||
}
|
||||
|
||||
if (techId === "tauri" && !rules.hasTauriCompatible) {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"Requires TanStack Router, React Router, Nuxt or Svelte frontend.",
|
||||
"Requires TanStack Router, React Router, Nuxt, Svelte or Solid frontend.",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1059,27 +1079,31 @@ const StackArchitect = () => {
|
||||
"Examples are not supported with Native-only frontend.",
|
||||
);
|
||||
} else {
|
||||
if (
|
||||
(techId === "todo" || techId === "ai") &&
|
||||
!rules.hasWebFrontend
|
||||
) {
|
||||
if (!rules.hasWebFrontend) {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"Requires a web frontend (TanStack Router, React Router, etc.).",
|
||||
);
|
||||
}
|
||||
|
||||
if (techId === "todo" && stack.database === "none") {
|
||||
addRule(category, techId, "Todo example requires a database.");
|
||||
}
|
||||
|
||||
if (techId === "ai" && stack.backend === "elysia") {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"AI example is not compatible with Elysia backend.",
|
||||
);
|
||||
if (techId === "ai") {
|
||||
if (stack.backend === "elysia") {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"AI example is not compatible with Elysia backend.",
|
||||
);
|
||||
}
|
||||
if (rules.hasSolid) {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"AI example is not compatible with Solid frontend.",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1174,6 +1198,8 @@ const StackArchitect = () => {
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
|
||||
useEffect(() => {
|
||||
if (compatibilityAnalysis.adjustedStack) {
|
||||
if (compatibilityAnalysis.changes.length > 0) {
|
||||
}
|
||||
setLastChanges(compatibilityAnalysis.changes);
|
||||
setStack(compatibilityAnalysis.adjustedStack);
|
||||
}
|
||||
@@ -1202,7 +1228,9 @@ const StackArchitect = () => {
|
||||
catKey === "addons" ||
|
||||
catKey === "examples"
|
||||
) {
|
||||
const currentArray = [...(currentValue as string[])];
|
||||
const currentArray = Array.isArray(currentValue)
|
||||
? [...currentValue]
|
||||
: [];
|
||||
let nextArray = [...currentArray];
|
||||
const isSelected = currentArray.includes(techId);
|
||||
|
||||
@@ -1214,15 +1242,15 @@ const StackArchitect = () => {
|
||||
"next",
|
||||
"nuxt",
|
||||
"svelte",
|
||||
"solid",
|
||||
];
|
||||
if (techId === "none") {
|
||||
nextArray = ["none"];
|
||||
} else if (isSelected) {
|
||||
if (currentArray.length > 1 || currentArray.includes("none")) {
|
||||
if (currentArray.length > 1) {
|
||||
nextArray = nextArray.filter((id) => id !== techId);
|
||||
if (nextArray.length === 0 && !currentArray.includes("none")) {
|
||||
nextArray = ["none"];
|
||||
}
|
||||
} else {
|
||||
nextArray = ["none"];
|
||||
}
|
||||
} else {
|
||||
nextArray = nextArray.filter((id) => id !== "none");
|
||||
@@ -1583,30 +1611,7 @@ const StackArchitect = () => {
|
||||
TECH_OPTIONS[categoryKey as keyof typeof TECH_OPTIONS] || [];
|
||||
const categoryDisplayName = getCategoryDisplayName(categoryKey);
|
||||
|
||||
const filteredOptions = categoryOptions.filter((tech) => {
|
||||
if (
|
||||
rules.isConvex &&
|
||||
tech.id === "none" &&
|
||||
["runtime", "database", "orm", "api", "dbSetup"].includes(
|
||||
categoryKey,
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
rules.isConvex &&
|
||||
categoryKey === "auth" &&
|
||||
tech.id === "false"
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
rules.isConvex &&
|
||||
categoryKey === "examples" &&
|
||||
tech.id !== "todo"
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const filteredOptions = categoryOptions.filter(() => {
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -1649,6 +1654,7 @@ const StackArchitect = () => {
|
||||
{filteredOptions.map((tech) => {
|
||||
let isSelected = false;
|
||||
const category = categoryKey as keyof StackState;
|
||||
const currentValue = stack[category];
|
||||
|
||||
if (
|
||||
category === "addons" ||
|
||||
@@ -1656,10 +1662,10 @@ const StackArchitect = () => {
|
||||
category === "frontend"
|
||||
) {
|
||||
isSelected = (
|
||||
(stack[category] as string[]) || []
|
||||
(currentValue as string[]) || []
|
||||
).includes(tech.id);
|
||||
} else {
|
||||
isSelected = stack[category] === tech.id;
|
||||
isSelected = currentValue === tech.id;
|
||||
}
|
||||
|
||||
const disabledReason = disabledReasons.get(
|
||||
|
||||
@@ -66,6 +66,14 @@ export const TECH_OPTIONS = {
|
||||
color: "from-orange-500 to-orange-700",
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
id: "solid",
|
||||
name: "Solid",
|
||||
description: "Simple and performant reactivity for building UIs",
|
||||
icon: "/icon/solid.svg",
|
||||
color: "from-blue-600 to-blue-800",
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
id: "native",
|
||||
name: "React Native",
|
||||
@@ -417,7 +425,7 @@ export const PRESET_TEMPLATES = [
|
||||
name: "Convex + React",
|
||||
description: "Reactive full-stack app with Convex and TanStack Router",
|
||||
stack: {
|
||||
projectName: "my-convex-app",
|
||||
projectName: "my-better-t-app",
|
||||
frontend: ["tanstack-router"],
|
||||
backend: "convex",
|
||||
runtime: "none",
|
||||
@@ -438,7 +446,7 @@ export const PRESET_TEMPLATES = [
|
||||
name: "Mobile App",
|
||||
description: "React Native with Expo and SQLite database",
|
||||
stack: {
|
||||
projectName: "my-native-app",
|
||||
projectName: "my-better-t-app",
|
||||
frontend: ["native"],
|
||||
runtime: "bun",
|
||||
backend: "hono",
|
||||
@@ -459,7 +467,7 @@ export const PRESET_TEMPLATES = [
|
||||
name: "API Only",
|
||||
description: "Backend API with Hono and PostgreSQL",
|
||||
stack: {
|
||||
projectName: "my-api",
|
||||
projectName: "my-better-t-app",
|
||||
frontend: ["none"],
|
||||
runtime: "bun",
|
||||
backend: "hono",
|
||||
@@ -480,7 +488,7 @@ export const PRESET_TEMPLATES = [
|
||||
name: "Full Featured",
|
||||
description: "Complete setup with web, native, Turso, and addons",
|
||||
stack: {
|
||||
projectName: "my-full-app",
|
||||
projectName: "my-better-t-app",
|
||||
frontend: ["tanstack-router", "native"],
|
||||
runtime: "bun",
|
||||
backend: "hono",
|
||||
@@ -531,3 +539,37 @@ export const DEFAULT_STACK: StackState = {
|
||||
install: "true",
|
||||
api: "trpc",
|
||||
};
|
||||
|
||||
export const isStackDefault = <K extends keyof StackState>(
|
||||
stack: StackState,
|
||||
key: K,
|
||||
value: StackState[K],
|
||||
): boolean => {
|
||||
const defaultValue = DEFAULT_STACK[key];
|
||||
|
||||
if (stack.backend === "convex") {
|
||||
if (key === "runtime" && value === "none") return true;
|
||||
if (key === "database" && value === "none") return true;
|
||||
if (key === "orm" && value === "none") return true;
|
||||
if (key === "api" && value === "none") return true;
|
||||
if (key === "auth" && value === "false") return true;
|
||||
if (key === "dbSetup" && value === "none") return true;
|
||||
if (
|
||||
key === "examples" &&
|
||||
Array.isArray(value) &&
|
||||
value.length === 1 &&
|
||||
value[0] === "todo"
|
||||
)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Array.isArray(defaultValue) && Array.isArray(value)) {
|
||||
const sortedDefault = [...defaultValue].sort();
|
||||
const sortedValue = [...value].sort();
|
||||
return (
|
||||
sortedDefault.length === sortedValue.length &&
|
||||
sortedDefault.every((item, index) => item === sortedValue[index])
|
||||
);
|
||||
}
|
||||
return defaultValue === value;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user