mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
add unistyles
This commit is contained in:
@@ -92,7 +92,9 @@ const hasWebFrontend = (frontend: string[]) =>
|
||||
].includes(f),
|
||||
);
|
||||
|
||||
const hasNativeFrontend = (frontend: string[]) => frontend.includes("native");
|
||||
const checkHasNativeFrontend = (frontend: string[]) =>
|
||||
frontend.includes("native-nativewind") ||
|
||||
frontend.includes("native-unistyles");
|
||||
|
||||
const hasPWACompatibleFrontend = (frontend: string[]) =>
|
||||
frontend.some((f) =>
|
||||
@@ -183,6 +185,7 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
||||
}
|
||||
|
||||
const isConvex = nextStack.backend === "convex";
|
||||
const isBackendNone = nextStack.backend === "none";
|
||||
|
||||
if (isConvex) {
|
||||
const convexOverrides: Partial<StackState> = {
|
||||
@@ -247,6 +250,40 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
||||
message: "Frontend defaulted to TanStack Router",
|
||||
});
|
||||
}
|
||||
} else if (isBackendNone) {
|
||||
const noneOverrides: Partial<StackState> = {
|
||||
auth: "false",
|
||||
database: "none",
|
||||
orm: "none",
|
||||
api: "none",
|
||||
runtime: "none",
|
||||
dbSetup: "none",
|
||||
examples: [],
|
||||
};
|
||||
|
||||
for (const [key, value] of Object.entries(noneOverrides)) {
|
||||
const catKey = key as keyof StackState;
|
||||
if (JSON.stringify(nextStack[catKey]) !== JSON.stringify(value)) {
|
||||
const displayName = getCategoryDisplayName(catKey);
|
||||
const valueDisplay = Array.isArray(value) ? "none" : value;
|
||||
const message = `${displayName} set to '${valueDisplay}'`;
|
||||
|
||||
notes[catKey].notes.push(
|
||||
`No backend selected: ${displayName} will be set to '${valueDisplay}'.`,
|
||||
);
|
||||
notes.backend.notes.push(
|
||||
`No backend requires ${displayName} to be '${valueDisplay}'.`,
|
||||
);
|
||||
notes[catKey].hasIssue = true;
|
||||
notes.backend.hasIssue = true;
|
||||
(nextStack[catKey] as string | string[]) = value;
|
||||
changed = true;
|
||||
changes.push({
|
||||
category: "backend-none",
|
||||
message,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (nextStack.runtime === "none") {
|
||||
notes.runtime.notes.push(
|
||||
@@ -562,7 +599,7 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
||||
|
||||
const incompatibleExamples: string[] = [];
|
||||
const isWeb = hasWebFrontend(nextStack.frontend);
|
||||
const isNativeOnly = hasNativeFrontend(nextStack.frontend) && !isWeb;
|
||||
const isNativeOnly = checkHasNativeFrontend(nextStack.frontend) && !isWeb;
|
||||
|
||||
if (isNativeOnly) {
|
||||
if (nextStack.examples.length > 0) {
|
||||
@@ -694,17 +731,19 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
||||
|
||||
const getCompatibilityRules = (stack: StackState) => {
|
||||
const isConvex = stack.backend === "convex";
|
||||
const isBackendNone = stack.backend === "none";
|
||||
const hasWebFrontendSelected = hasWebFrontend(stack.frontend);
|
||||
const hasNativeOnly =
|
||||
hasNativeFrontend(stack.frontend) && !hasWebFrontendSelected;
|
||||
const hasNativeFrontend = checkHasNativeFrontend(stack.frontend);
|
||||
const hasNativeOnly = hasNativeFrontend && !hasWebFrontendSelected;
|
||||
const hasSolid = stack.frontend.includes("solid");
|
||||
const hasNuxt = stack.frontend.includes("nuxt");
|
||||
const hasSvelte = stack.frontend.includes("svelte");
|
||||
|
||||
return {
|
||||
isConvex,
|
||||
isBackendNone,
|
||||
hasWebFrontend: hasWebFrontendSelected,
|
||||
hasNativeFrontend: hasNativeFrontend(stack.frontend),
|
||||
hasNativeFrontend,
|
||||
hasNativeOnly,
|
||||
hasPWACompatible: hasPWACompatibleFrontend(stack.frontend),
|
||||
hasTauriCompatible: hasTauriCompatibleFrontend(stack.frontend),
|
||||
@@ -853,7 +892,7 @@ const StackArchitect = () => {
|
||||
};
|
||||
|
||||
for (const category of CATEGORY_ORDER) {
|
||||
const options = TECH_OPTIONS[category] || [];
|
||||
const options = TECH_OPTIONS[category as keyof typeof TECH_OPTIONS] || [];
|
||||
const catKey = category as keyof StackState;
|
||||
|
||||
for (const tech of options) {
|
||||
@@ -879,7 +918,9 @@ const StackArchitect = () => {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
`Convex backend requires ${getCategoryDisplayName(catKey)} to be '${requiredValue}'.`,
|
||||
`Convex backend requires ${getCategoryDisplayName(
|
||||
catKey,
|
||||
)} to be '${requiredValue}'.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -897,12 +938,43 @@ const StackArchitect = () => {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
`${tech.name} is not compatible with Convex backend.`,
|
||||
`Convex backend is not compatible with ${tech.name}.`,
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rules.isBackendNone) {
|
||||
if (
|
||||
[
|
||||
"auth",
|
||||
"database",
|
||||
"orm",
|
||||
"api",
|
||||
"runtime",
|
||||
"dbSetup",
|
||||
"examples",
|
||||
].includes(catKey)
|
||||
) {
|
||||
if (
|
||||
(catKey === "auth" && techId === "true") ||
|
||||
((catKey === "database" ||
|
||||
catKey === "orm" ||
|
||||
catKey === "api" ||
|
||||
catKey === "runtime" ||
|
||||
catKey === "dbSetup") &&
|
||||
techId !== "none") ||
|
||||
(catKey === "examples" && techId !== "none")
|
||||
) {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
`Cannot be selected when 'No Backend' is chosen. Will be set to 'None' or disabled.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (catKey === "runtime" && techId === "none") {
|
||||
addRule(
|
||||
category,
|
||||
@@ -934,114 +1006,68 @@ const StackArchitect = () => {
|
||||
}
|
||||
|
||||
if (catKey === "orm") {
|
||||
if (stack.database === "none" && techId !== "none") {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"Select a database to enable ORM options.",
|
||||
);
|
||||
}
|
||||
if (
|
||||
stack.database === "mongodb" &&
|
||||
techId !== "prisma" &&
|
||||
techId !== "mongoose" &&
|
||||
techId !== "none"
|
||||
) {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"MongoDB requires the Prisma or Mongoose ORM.",
|
||||
);
|
||||
}
|
||||
if (
|
||||
stack.database !== "mongodb" &&
|
||||
stack.database !== "none" &&
|
||||
techId === "mongoose"
|
||||
) {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"Mongoose ORM is only compatible with MongoDB.",
|
||||
);
|
||||
}
|
||||
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"
|
||||
) {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"MongoDB Atlas setup requires Prisma or Mongoose ORM.",
|
||||
);
|
||||
}
|
||||
if (techId === "none") {
|
||||
if (stack.database === "mongodb") {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"MongoDB requires Prisma or Mongoose ORM.",
|
||||
);
|
||||
}
|
||||
if (stack.dbSetup === "turso") {
|
||||
addRule(category, techId, "Turso DB setup requires Drizzle ORM.");
|
||||
}
|
||||
if (stack.dbSetup === "prisma-postgres") {
|
||||
addRule(category, techId, "This DB setup requires Prisma ORM.");
|
||||
}
|
||||
if (stack.dbSetup === "mongodb-atlas") {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"This DB setup requires Prisma or Mongoose ORM.",
|
||||
);
|
||||
}
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"ORM 'None' is only available with the Convex backend.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (catKey === "dbSetup" && techId !== "none") {
|
||||
if (stack.database === "none") {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"Select a database before choosing a cloud setup.",
|
||||
);
|
||||
} 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.");
|
||||
}
|
||||
if (techId === "turso") {
|
||||
if (stack.database !== "sqlite") {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"Turso requires SQLite. It will be selected.",
|
||||
);
|
||||
}
|
||||
if (stack.orm !== "drizzle") {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"Turso requires Drizzle ORM. It will be selected.",
|
||||
);
|
||||
}
|
||||
} else if (techId === "prisma-postgres") {
|
||||
if (stack.database !== "postgres") {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"Prisma PostgreSQL setup requires PostgreSQL. It will be selected.",
|
||||
);
|
||||
}
|
||||
if (stack.orm !== "prisma") {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"Prisma PostgreSQL setup requires Prisma ORM. It will be selected.",
|
||||
);
|
||||
}
|
||||
} else if (techId === "mongodb-atlas") {
|
||||
if (stack.database !== "mongodb") {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"MongoDB Atlas setup requires MongoDB. It will be selected.",
|
||||
);
|
||||
}
|
||||
if (stack.orm !== "prisma" && stack.orm !== "mongoose") {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"MongoDB Atlas setup requires Prisma or Mongoose ORM. Prisma will be selected.",
|
||||
);
|
||||
}
|
||||
} else if (techId === "neon") {
|
||||
if (stack.database !== "postgres") {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"Neon requires PostgreSQL. It will be selected.",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1055,56 +1081,164 @@ const StackArchitect = () => {
|
||||
}
|
||||
|
||||
if (catKey === "addons") {
|
||||
if (techId === "pwa" && !rules.hasPWACompatible) {
|
||||
const incompatibleAddons: string[] = [];
|
||||
const isPWACompat = hasPWACompatibleFrontend(stack.frontend);
|
||||
const isTauriCompat = hasTauriCompatibleFrontend(stack.frontend);
|
||||
|
||||
if (!isPWACompat && stack.addons.includes("pwa")) {
|
||||
incompatibleAddons.push("pwa");
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"Requires TanStack Router, React Router or Solid frontend.",
|
||||
"PWA addon removed (requires compatible frontend)",
|
||||
);
|
||||
}
|
||||
if (techId === "tauri" && !rules.hasTauriCompatible) {
|
||||
if (!isTauriCompat && stack.addons.includes("tauri")) {
|
||||
incompatibleAddons.push("tauri");
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"Requires TanStack Router, React Router, Nuxt, Svelte or Solid frontend.",
|
||||
"Tauri addon removed (requires compatible frontend)",
|
||||
);
|
||||
}
|
||||
|
||||
const originalAddonsLength = stack.addons.length;
|
||||
if (incompatibleAddons.length > 0) {
|
||||
stack.addons = stack.addons.filter(
|
||||
(addon) => !incompatibleAddons.includes(addon),
|
||||
);
|
||||
if (stack.addons.length !== originalAddonsLength) {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"Addons filtered (requires compatible frontend)",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
stack.addons.includes("husky") &&
|
||||
!stack.addons.includes("biome")
|
||||
) {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"Husky addon is selected without Biome. Consider adding Biome for lint-staged integration.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (catKey === "examples") {
|
||||
if (rules.hasNativeOnly) {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"Examples are not supported with Native-only frontend.",
|
||||
);
|
||||
} else {
|
||||
if (!rules.hasWebFrontend) {
|
||||
const incompatibleExamples: string[] = [];
|
||||
const isWeb = hasWebFrontend(stack.frontend);
|
||||
const isNativeOnly = checkHasNativeFrontend(stack.frontend) && !isWeb;
|
||||
|
||||
if (isNativeOnly) {
|
||||
if (stack.examples.length > 0) {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"Requires a web frontend (TanStack Router, React Router, etc.).",
|
||||
"Examples removed (not supported with Native-only frontend)",
|
||||
);
|
||||
}
|
||||
if (techId === "todo" && stack.database === "none") {
|
||||
addRule(category, techId, "Todo example requires a database.");
|
||||
} else {
|
||||
if (!isWeb) {
|
||||
if (stack.examples.includes("todo")) {
|
||||
incompatibleExamples.push("todo");
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"Todo example removed (requires web frontend)",
|
||||
);
|
||||
}
|
||||
if (stack.examples.includes("ai")) {
|
||||
incompatibleExamples.push("ai");
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"AI example removed (requires web frontend)",
|
||||
);
|
||||
}
|
||||
}
|
||||
if (techId === "ai") {
|
||||
if (stack.backend === "elysia") {
|
||||
if (stack.database === "none" && stack.examples.includes("todo")) {
|
||||
incompatibleExamples.push("todo");
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"Todo example removed (requires a database)",
|
||||
);
|
||||
}
|
||||
if (stack.backend === "elysia" && stack.examples.includes("ai")) {
|
||||
incompatibleExamples.push("ai");
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"AI example removed (not compatible with Elysia)",
|
||||
);
|
||||
}
|
||||
if (rules.hasSolid && stack.examples.includes("ai")) {
|
||||
incompatibleExamples.push("ai");
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"AI example removed (not compatible with Solid)",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const uniqueIncompatibleExamples = [...new Set(incompatibleExamples)];
|
||||
if (uniqueIncompatibleExamples.length > 0) {
|
||||
if (!isWeb && !isNativeOnly) {
|
||||
if (
|
||||
uniqueIncompatibleExamples.includes("todo") ||
|
||||
uniqueIncompatibleExamples.includes("ai")
|
||||
) {
|
||||
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.",
|
||||
"Examples require a web frontend. Incompatible examples will be removed.",
|
||||
);
|
||||
}
|
||||
}
|
||||
if (
|
||||
stack.database === "none" &&
|
||||
uniqueIncompatibleExamples.includes("todo")
|
||||
) {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"Todo example requires a database. It will be removed.",
|
||||
);
|
||||
}
|
||||
if (
|
||||
stack.backend === "elysia" &&
|
||||
uniqueIncompatibleExamples.includes("ai")
|
||||
) {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"AI example is not compatible with Elysia. It will be removed.",
|
||||
);
|
||||
}
|
||||
if (rules.hasSolid && uniqueIncompatibleExamples.includes("ai")) {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"AI example is not compatible with Solid. It will be removed.",
|
||||
);
|
||||
}
|
||||
|
||||
const originalExamplesLength = stack.examples.length;
|
||||
stack.examples = stack.examples.filter(
|
||||
(ex) => !uniqueIncompatibleExamples.includes(ex),
|
||||
);
|
||||
if (stack.examples.length !== originalExamplesLength) {
|
||||
addRule(
|
||||
category,
|
||||
techId,
|
||||
"Examples filtered (incompatible examples removed)",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1256,6 +1390,8 @@ const StackArchitect = () => {
|
||||
nextArray = nextArray.filter((id) => id !== "none");
|
||||
if (webTypes.includes(techId)) {
|
||||
nextArray = nextArray.filter((id) => !webTypes.includes(id));
|
||||
} else if (techId.startsWith("native-")) {
|
||||
nextArray = nextArray.filter((id) => !id.startsWith("native-"));
|
||||
}
|
||||
nextArray.push(techId);
|
||||
}
|
||||
|
||||
@@ -75,13 +75,21 @@ export const TECH_OPTIONS = {
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
id: "native",
|
||||
name: "React Native",
|
||||
description: "Expo with NativeWind",
|
||||
id: "native-nativewind",
|
||||
name: "React Native + NativeWind",
|
||||
description: "Expo with NativeWind (Tailwind)",
|
||||
icon: "/icon/expo.svg",
|
||||
color: "from-purple-400 to-purple-600",
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
id: "native-unistyles",
|
||||
name: "React Native + Unistyles",
|
||||
description: "Expo with Unistyles",
|
||||
icon: "/icon/expo.svg",
|
||||
color: "from-pink-400 to-pink-600",
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
id: "none",
|
||||
name: "No Frontend",
|
||||
@@ -145,6 +153,13 @@ export const TECH_OPTIONS = {
|
||||
icon: "/icon/convex.svg",
|
||||
color: "from-pink-500 to-pink-700",
|
||||
},
|
||||
{
|
||||
id: "none",
|
||||
name: "No Backend",
|
||||
description: "Skip backend integration (frontend only)",
|
||||
icon: "⚙️",
|
||||
color: "from-gray-400 to-gray-600",
|
||||
},
|
||||
],
|
||||
database: [
|
||||
{
|
||||
@@ -447,7 +462,7 @@ export const PRESET_TEMPLATES = [
|
||||
description: "React Native with Expo and SQLite database",
|
||||
stack: {
|
||||
projectName: "my-better-t-app",
|
||||
frontend: ["native"],
|
||||
frontend: ["native-nativewind"],
|
||||
runtime: "bun",
|
||||
backend: "hono",
|
||||
database: "sqlite",
|
||||
@@ -489,7 +504,7 @@ export const PRESET_TEMPLATES = [
|
||||
description: "Complete setup with web, native, Turso, and addons",
|
||||
stack: {
|
||||
projectName: "my-better-t-app",
|
||||
frontend: ["tanstack-router", "native"],
|
||||
frontend: ["tanstack-router", "native-nativewind"],
|
||||
runtime: "bun",
|
||||
backend: "hono",
|
||||
database: "sqlite",
|
||||
|
||||
Reference in New Issue
Block a user