mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
add seperate rows for native and web frontends
This commit is contained in:
@@ -63,7 +63,8 @@ const validateProjectName = (name: string): string | undefined => {
|
||||
};
|
||||
|
||||
const CATEGORY_ORDER: Array<keyof typeof TECH_OPTIONS> = [
|
||||
"frontend",
|
||||
"webFrontend",
|
||||
"nativeFrontend",
|
||||
"backend",
|
||||
"runtime",
|
||||
"api",
|
||||
@@ -78,8 +79,8 @@ const CATEGORY_ORDER: Array<keyof typeof TECH_OPTIONS> = [
|
||||
"install",
|
||||
];
|
||||
|
||||
const hasWebFrontend = (frontend: string[]) =>
|
||||
frontend.some((f) =>
|
||||
const hasWebFrontend = (webFrontend: string[]) =>
|
||||
webFrontend.some((f) =>
|
||||
[
|
||||
"tanstack-router",
|
||||
"react-router",
|
||||
@@ -91,17 +92,17 @@ const hasWebFrontend = (frontend: string[]) =>
|
||||
].includes(f),
|
||||
);
|
||||
|
||||
const checkHasNativeFrontend = (frontend: string[]) =>
|
||||
frontend.includes("native-nativewind") ||
|
||||
frontend.includes("native-unistyles");
|
||||
const checkHasNativeFrontend = (nativeFrontend: string[]) =>
|
||||
nativeFrontend.includes("native-nativewind") ||
|
||||
nativeFrontend.includes("native-unistyles");
|
||||
|
||||
const hasPWACompatibleFrontend = (frontend: string[]) =>
|
||||
frontend.some((f) =>
|
||||
const hasPWACompatibleFrontend = (webFrontend: string[]) =>
|
||||
webFrontend.some((f) =>
|
||||
["tanstack-router", "react-router", "solid"].includes(f),
|
||||
);
|
||||
|
||||
const hasTauriCompatibleFrontend = (frontend: string[]) =>
|
||||
frontend.some((f) =>
|
||||
const hasTauriCompatibleFrontend = (webFrontend: string[]) =>
|
||||
webFrontend.some((f) =>
|
||||
[
|
||||
"tanstack-router",
|
||||
"react-router",
|
||||
@@ -114,7 +115,8 @@ const hasTauriCompatibleFrontend = (frontend: string[]) =>
|
||||
|
||||
const getBadgeColors = (category: string): string => {
|
||||
switch (category) {
|
||||
case "frontend":
|
||||
case "webFrontend":
|
||||
case "nativeFrontend":
|
||||
return "border-blue-300 bg-blue-100 text-blue-800 dark:border-blue-700/30 dark:bg-blue-900/30 dark:text-blue-300";
|
||||
case "runtime":
|
||||
return "border-amber-300 bg-amber-100 text-amber-800 dark:border-amber-700/30 dark:bg-amber-900/30 dark:text-amber-300";
|
||||
@@ -229,33 +231,39 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
||||
}
|
||||
}
|
||||
const incompatibleConvexFrontends = ["nuxt", "solid"];
|
||||
const originalFrontendLength = nextStack.frontend.length;
|
||||
nextStack.frontend = nextStack.frontend.filter(
|
||||
const originalWebFrontendLength = nextStack.webFrontend.length;
|
||||
nextStack.webFrontend = nextStack.webFrontend.filter(
|
||||
(f) => !incompatibleConvexFrontends.includes(f),
|
||||
);
|
||||
if (nextStack.frontend.length !== originalFrontendLength) {
|
||||
if (nextStack.webFrontend.length !== originalWebFrontendLength) {
|
||||
changed = true;
|
||||
notes.frontend.notes.push(
|
||||
notes.webFrontend.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.webFrontend.hasIssue = true;
|
||||
notes.backend.hasIssue = true;
|
||||
changes.push({
|
||||
category: "convex",
|
||||
message: "Removed incompatible frontends (Nuxt, Solid)",
|
||||
message: "Removed incompatible web frontends (Nuxt, Solid)",
|
||||
});
|
||||
}
|
||||
if (nextStack.frontend.length === 0) {
|
||||
nextStack.frontend = ["tanstack-router"];
|
||||
if (
|
||||
nextStack.webFrontend.length === 0 ||
|
||||
nextStack.webFrontend[0] === "none"
|
||||
) {
|
||||
nextStack.webFrontend = ["tanstack-router"];
|
||||
changed = true;
|
||||
changes.push({
|
||||
category: "convex",
|
||||
message: "Frontend defaulted to TanStack Router",
|
||||
message: "Web Frontend defaulted to TanStack Router",
|
||||
});
|
||||
}
|
||||
if (nextStack.nativeFrontend[0] === "none") {
|
||||
} else {
|
||||
}
|
||||
} else if (isBackendNone) {
|
||||
const noneOverrides: Partial<StackState> = {
|
||||
auth: "false",
|
||||
@@ -506,19 +514,19 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
||||
}
|
||||
}
|
||||
|
||||
const isNuxt = nextStack.frontend.includes("nuxt");
|
||||
const isSvelte = nextStack.frontend.includes("svelte");
|
||||
const isSolid = nextStack.frontend.includes("solid");
|
||||
const isNuxt = nextStack.webFrontend.includes("nuxt");
|
||||
const isSvelte = nextStack.webFrontend.includes("svelte");
|
||||
const isSolid = nextStack.webFrontend.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.`,
|
||||
);
|
||||
notes.frontend.notes.push(
|
||||
notes.webFrontend.notes.push(
|
||||
`Selected ${frontendName}: API will be set to oRPC.`,
|
||||
);
|
||||
notes.api.hasIssue = true;
|
||||
notes.frontend.hasIssue = true;
|
||||
notes.webFrontend.hasIssue = true;
|
||||
nextStack.api = "orpc";
|
||||
changed = true;
|
||||
changes.push({
|
||||
@@ -528,37 +536,37 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
||||
}
|
||||
|
||||
const incompatibleAddons: string[] = [];
|
||||
const isPWACompat = hasPWACompatibleFrontend(nextStack.frontend);
|
||||
const isTauriCompat = hasTauriCompatibleFrontend(nextStack.frontend);
|
||||
const isPWACompat = hasPWACompatibleFrontend(nextStack.webFrontend);
|
||||
const isTauriCompat = hasTauriCompatibleFrontend(nextStack.webFrontend);
|
||||
|
||||
if (!isPWACompat && nextStack.addons.includes("pwa")) {
|
||||
incompatibleAddons.push("pwa");
|
||||
notes.frontend.notes.push(
|
||||
notes.webFrontend.notes.push(
|
||||
"PWA addon requires TanStack/React Router or Solid. Addon will be removed.",
|
||||
);
|
||||
notes.addons.notes.push(
|
||||
"PWA requires TanStack/React Router/Solid. It will be removed.",
|
||||
);
|
||||
notes.frontend.hasIssue = true;
|
||||
notes.webFrontend.hasIssue = true;
|
||||
notes.addons.hasIssue = true;
|
||||
changes.push({
|
||||
category: "addons",
|
||||
message: "PWA addon removed (requires compatible frontend)",
|
||||
message: "PWA addon removed (requires compatible web frontend)",
|
||||
});
|
||||
}
|
||||
if (!isTauriCompat && nextStack.addons.includes("tauri")) {
|
||||
incompatibleAddons.push("tauri");
|
||||
notes.frontend.notes.push(
|
||||
notes.webFrontend.notes.push(
|
||||
"Tauri addon requires TanStack/React Router, Nuxt, Svelte, Solid, or Next.js. Addon will be removed.",
|
||||
);
|
||||
notes.addons.notes.push(
|
||||
"Tauri requires TanStack/React Router/Nuxt/Svelte/Solid/Next.js. It will be removed.",
|
||||
);
|
||||
notes.frontend.hasIssue = true;
|
||||
notes.webFrontend.hasIssue = true;
|
||||
notes.addons.hasIssue = true;
|
||||
changes.push({
|
||||
category: "addons",
|
||||
message: "Tauri addon removed (requires compatible frontend)",
|
||||
message: "Tauri addon removed (requires compatible web frontend)",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -580,18 +588,19 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
||||
}
|
||||
|
||||
const incompatibleExamples: string[] = [];
|
||||
const isWeb = hasWebFrontend(nextStack.frontend);
|
||||
const isNativeOnly = checkHasNativeFrontend(nextStack.frontend) && !isWeb;
|
||||
const isWeb = hasWebFrontend(nextStack.webFrontend);
|
||||
const hasNative = checkHasNativeFrontend(nextStack.nativeFrontend);
|
||||
const isNativeOnly = hasNative && !isWeb;
|
||||
|
||||
if (isNativeOnly) {
|
||||
if (nextStack.examples.length > 0) {
|
||||
notes.frontend.notes.push(
|
||||
notes.webFrontend.notes.push(
|
||||
"Examples are not supported with Native-only frontend. Examples will be removed.",
|
||||
);
|
||||
notes.examples.notes.push(
|
||||
"Examples require a web frontend. They will be removed.",
|
||||
);
|
||||
notes.frontend.hasIssue = true;
|
||||
notes.webFrontend.hasIssue = true;
|
||||
notes.examples.hasIssue = true;
|
||||
incompatibleExamples.push(...nextStack.examples);
|
||||
changes.push({
|
||||
@@ -653,13 +662,13 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
||||
uniqueIncompatibleExamples.includes("todo") ||
|
||||
uniqueIncompatibleExamples.includes("ai")
|
||||
) {
|
||||
notes.frontend.notes.push(
|
||||
notes.webFrontend.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.webFrontend.hasIssue = true;
|
||||
notes.examples.hasIssue = true;
|
||||
}
|
||||
}
|
||||
@@ -690,13 +699,13 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
||||
notes.examples.hasIssue = true;
|
||||
}
|
||||
if (isSolid && uniqueIncompatibleExamples.includes("ai")) {
|
||||
notes.frontend.notes.push(
|
||||
notes.webFrontend.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.webFrontend.hasIssue = true;
|
||||
notes.examples.hasIssue = true;
|
||||
}
|
||||
|
||||
@@ -720,12 +729,12 @@ 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 hasNativeFrontend = checkHasNativeFrontend(stack.frontend);
|
||||
const hasWebFrontendSelected = hasWebFrontend(stack.webFrontend);
|
||||
const hasNativeFrontend = checkHasNativeFrontend(stack.nativeFrontend);
|
||||
const hasNativeOnly = hasNativeFrontend && !hasWebFrontendSelected;
|
||||
const hasSolid = stack.frontend.includes("solid");
|
||||
const hasNuxt = stack.frontend.includes("nuxt");
|
||||
const hasSvelte = stack.frontend.includes("svelte");
|
||||
const hasSolid = stack.webFrontend.includes("solid");
|
||||
const hasNuxt = stack.webFrontend.includes("nuxt");
|
||||
const hasSvelte = stack.webFrontend.includes("svelte");
|
||||
|
||||
return {
|
||||
isConvex,
|
||||
@@ -733,8 +742,8 @@ const getCompatibilityRules = (stack: StackState) => {
|
||||
hasWebFrontend: hasWebFrontendSelected,
|
||||
hasNativeFrontend,
|
||||
hasNativeOnly,
|
||||
hasPWACompatible: hasPWACompatibleFrontend(stack.frontend),
|
||||
hasTauriCompatible: hasTauriCompatibleFrontend(stack.frontend),
|
||||
hasPWACompatible: hasPWACompatibleFrontend(stack.webFrontend),
|
||||
hasTauriCompatible: hasTauriCompatibleFrontend(stack.webFrontend),
|
||||
hasNuxtOrSvelteOrSolid: hasNuxt || hasSvelte || hasSolid,
|
||||
hasSolid,
|
||||
hasNuxt,
|
||||
@@ -764,11 +773,34 @@ const generateCommand = (stackState: StackState): string => {
|
||||
value: StackState[K],
|
||||
) => isStackDefault(stackState, key, value);
|
||||
|
||||
if (!checkDefault("frontend", stackState.frontend)) {
|
||||
if (stackState.frontend.length === 0 || stackState.frontend[0] === "none") {
|
||||
if (!checkDefault("webFrontend", stackState.webFrontend)) {
|
||||
if (
|
||||
stackState.webFrontend.length === 0 ||
|
||||
stackState.webFrontend[0] === "none"
|
||||
) {
|
||||
flags.push("--frontend none");
|
||||
} else {
|
||||
flags.push(`--frontend ${stackState.frontend.join(" ")}`);
|
||||
flags.push(`--frontend ${stackState.webFrontend.join(" ")}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!checkDefault("nativeFrontend", stackState.nativeFrontend)) {
|
||||
if (
|
||||
stackState.nativeFrontend.length > 0 &&
|
||||
stackState.nativeFrontend[0] !== "none"
|
||||
) {
|
||||
if (checkDefault("webFrontend", stackState.webFrontend)) {
|
||||
flags.push(`--frontend ${stackState.nativeFrontend.join(" ")}`);
|
||||
} else {
|
||||
const existingFrontendIndex = flags.findIndex((f) =>
|
||||
f.startsWith("--frontend "),
|
||||
);
|
||||
if (existingFrontendIndex !== -1) {
|
||||
flags[existingFrontendIndex] += ` ${stackState.nativeFrontend.join(
|
||||
" ",
|
||||
)}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -876,12 +908,15 @@ const StackBuilder = () => {
|
||||
|
||||
const catKey = category as keyof StackState;
|
||||
|
||||
if (["frontend", "addons", "examples"].includes(catKey)) {
|
||||
if (
|
||||
["webFrontend", "nativeFrontend", "addons", "examples"].includes(catKey)
|
||||
) {
|
||||
const currentValues: string[] = [];
|
||||
randomStack[catKey as "frontend" | "addons" | "examples"] =
|
||||
currentValues;
|
||||
randomStack[
|
||||
catKey as "webFrontend" | "nativeFrontend" | "addons" | "examples"
|
||||
] = currentValues;
|
||||
|
||||
if (catKey === "frontend") {
|
||||
if (catKey === "webFrontend" || catKey === "nativeFrontend") {
|
||||
const randomIndex = Math.floor(Math.random() * options.length);
|
||||
const selectedOption = options[randomIndex].id;
|
||||
currentValues.push(selectedOption);
|
||||
@@ -986,7 +1021,7 @@ const StackBuilder = () => {
|
||||
);
|
||||
}
|
||||
} else if (
|
||||
catKey === "frontend" &&
|
||||
catKey === "webFrontend" &&
|
||||
(techId === "nuxt" || techId === "solid")
|
||||
) {
|
||||
addRule(
|
||||
@@ -1274,9 +1309,15 @@ const StackBuilder = () => {
|
||||
if (!options) continue;
|
||||
|
||||
if (Array.isArray(selectedValue)) {
|
||||
if (selectedValue.length === 0 || selectedValue[0] === "none") continue;
|
||||
if (
|
||||
selectedValue.length === 0 ||
|
||||
(selectedValue.length === 1 && selectedValue[0] === "none")
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const id of selectedValue) {
|
||||
if (id === "none") continue;
|
||||
const tech = options.find((opt) => opt.id === id);
|
||||
if (tech) {
|
||||
badges.push(
|
||||
@@ -1376,7 +1417,8 @@ const StackBuilder = () => {
|
||||
const currentValue = currentStack[catKey];
|
||||
|
||||
if (
|
||||
catKey === "frontend" ||
|
||||
catKey === "webFrontend" ||
|
||||
catKey === "nativeFrontend" ||
|
||||
catKey === "addons" ||
|
||||
catKey === "examples"
|
||||
) {
|
||||
@@ -1386,7 +1428,7 @@ const StackBuilder = () => {
|
||||
let nextArray = [...currentArray];
|
||||
const isSelected = currentArray.includes(techId);
|
||||
|
||||
if (catKey === "frontend") {
|
||||
if (catKey === "webFrontend") {
|
||||
const webTypes = [
|
||||
"tanstack-router",
|
||||
"react-router",
|
||||
@@ -1405,19 +1447,15 @@ const StackBuilder = () => {
|
||||
nextArray = ["none"];
|
||||
}
|
||||
} else {
|
||||
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);
|
||||
nextArray = [techId];
|
||||
}
|
||||
if (nextArray.length > 1) {
|
||||
nextArray = nextArray.filter((id) => id !== "none");
|
||||
}
|
||||
if (nextArray.length === 0) {
|
||||
} else if (catKey === "nativeFrontend") {
|
||||
if (techId === "none") {
|
||||
nextArray = ["none"];
|
||||
} else if (isSelected) {
|
||||
nextArray = ["none"];
|
||||
} else {
|
||||
nextArray = [techId];
|
||||
}
|
||||
} else {
|
||||
if (isSelected) {
|
||||
@@ -1425,6 +1463,16 @@ const StackBuilder = () => {
|
||||
} else {
|
||||
nextArray.push(techId);
|
||||
}
|
||||
if (nextArray.length > 1) {
|
||||
nextArray = nextArray.filter((id) => id !== "none");
|
||||
}
|
||||
if (
|
||||
nextArray.length === 0 &&
|
||||
(catKey === "addons" || catKey === "examples")
|
||||
) {
|
||||
} else if (nextArray.length === 0) {
|
||||
nextArray = ["none"];
|
||||
}
|
||||
}
|
||||
|
||||
const uniqueNext = [...new Set(nextArray)].sort();
|
||||
@@ -1730,7 +1778,8 @@ const StackBuilder = () => {
|
||||
if (
|
||||
category === "addons" ||
|
||||
category === "examples" ||
|
||||
category === "frontend"
|
||||
category === "webFrontend" ||
|
||||
category === "nativeFrontend"
|
||||
) {
|
||||
isSelected = (
|
||||
(currentValue as string[]) || []
|
||||
|
||||
@@ -23,7 +23,7 @@ export const TECH_OPTIONS = {
|
||||
color: "from-gray-400 to-gray-600",
|
||||
},
|
||||
],
|
||||
frontend: [
|
||||
webFrontend: [
|
||||
{
|
||||
id: "tanstack-router",
|
||||
name: "TanStack Router",
|
||||
@@ -81,6 +81,16 @@ export const TECH_OPTIONS = {
|
||||
color: "from-blue-600 to-blue-800",
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
id: "none",
|
||||
name: "No Web Frontend",
|
||||
description: "No web-based frontend",
|
||||
icon: "⚙️",
|
||||
color: "from-gray-400 to-gray-600",
|
||||
default: false,
|
||||
},
|
||||
],
|
||||
nativeFrontend: [
|
||||
{
|
||||
id: "native-nativewind",
|
||||
name: "React Native + NativeWind",
|
||||
@@ -99,9 +109,9 @@ export const TECH_OPTIONS = {
|
||||
},
|
||||
{
|
||||
id: "none",
|
||||
name: "No Frontend",
|
||||
description: "API-only backend",
|
||||
icon: "⚙️",
|
||||
name: "No Native Frontend",
|
||||
description: "No native mobile frontend",
|
||||
icon: "📱",
|
||||
color: "from-gray-400 to-gray-600",
|
||||
default: false,
|
||||
},
|
||||
@@ -427,7 +437,8 @@ export const PRESET_TEMPLATES = [
|
||||
description: "Standard web app with TanStack Router, Bun, Hono and SQLite",
|
||||
stack: {
|
||||
projectName: "my-better-t-app",
|
||||
frontend: ["tanstack-router"],
|
||||
webFrontend: ["tanstack-router"],
|
||||
nativeFrontend: ["none"],
|
||||
runtime: "bun",
|
||||
backend: "hono",
|
||||
database: "sqlite",
|
||||
@@ -448,7 +459,8 @@ export const PRESET_TEMPLATES = [
|
||||
description: "Reactive full-stack app with Convex and TanStack Router",
|
||||
stack: {
|
||||
projectName: "my-better-t-app",
|
||||
frontend: ["tanstack-router"],
|
||||
webFrontend: ["tanstack-router"],
|
||||
nativeFrontend: ["none"],
|
||||
backend: "convex",
|
||||
runtime: "none",
|
||||
database: "none",
|
||||
@@ -469,7 +481,8 @@ export const PRESET_TEMPLATES = [
|
||||
description: "React Native with Expo and SQLite database",
|
||||
stack: {
|
||||
projectName: "my-better-t-app",
|
||||
frontend: ["native-nativewind"],
|
||||
webFrontend: ["none"],
|
||||
nativeFrontend: ["native-nativewind"],
|
||||
runtime: "bun",
|
||||
backend: "hono",
|
||||
database: "sqlite",
|
||||
@@ -490,7 +503,8 @@ export const PRESET_TEMPLATES = [
|
||||
description: "Backend API with Hono and PostgreSQL",
|
||||
stack: {
|
||||
projectName: "my-better-t-app",
|
||||
frontend: ["none"],
|
||||
webFrontend: ["none"],
|
||||
nativeFrontend: ["none"],
|
||||
runtime: "bun",
|
||||
backend: "hono",
|
||||
database: "postgres",
|
||||
@@ -511,7 +525,8 @@ export const PRESET_TEMPLATES = [
|
||||
description: "Complete setup with web, native, Turso, and addons",
|
||||
stack: {
|
||||
projectName: "my-better-t-app",
|
||||
frontend: ["tanstack-router", "native-nativewind"],
|
||||
webFrontend: ["tanstack-router"],
|
||||
nativeFrontend: ["native-nativewind"],
|
||||
runtime: "bun",
|
||||
backend: "hono",
|
||||
database: "sqlite",
|
||||
@@ -530,7 +545,8 @@ export const PRESET_TEMPLATES = [
|
||||
|
||||
export type StackState = {
|
||||
projectName: string;
|
||||
frontend: string[];
|
||||
webFrontend: string[];
|
||||
nativeFrontend: string[];
|
||||
runtime: string;
|
||||
backend: string;
|
||||
database: string;
|
||||
@@ -547,7 +563,8 @@ export type StackState = {
|
||||
|
||||
export const DEFAULT_STACK: StackState = {
|
||||
projectName: "my-better-t-app",
|
||||
frontend: ["tanstack-router"],
|
||||
webFrontend: ["tanstack-router"],
|
||||
nativeFrontend: ["none"],
|
||||
runtime: "bun",
|
||||
backend: "hono",
|
||||
database: "sqlite",
|
||||
@@ -585,6 +602,24 @@ export const isStackDefault = <K extends keyof StackState>(
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key === "webFrontend" && stack.webFrontend) {
|
||||
const currentWeb = (stack.webFrontend as string[]).filter(
|
||||
(f) => !f.startsWith("native-") && f !== "none",
|
||||
);
|
||||
const currentNative = (stack.webFrontend as string[]).filter((f) =>
|
||||
f.startsWith("native-"),
|
||||
);
|
||||
|
||||
if (key === "webFrontend") {
|
||||
const defaultWeb = (DEFAULT_STACK.webFrontend as string[]).sort();
|
||||
const valueWeb = (value as string[]).sort();
|
||||
return (
|
||||
defaultWeb.length === valueWeb.length &&
|
||||
defaultWeb.every((item, index) => item === valueWeb[index])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(defaultValue) && Array.isArray(value)) {
|
||||
const sortedDefault = [...defaultValue].sort();
|
||||
const sortedValue = [...value].sort();
|
||||
|
||||
@@ -12,7 +12,12 @@ const getValidIds = (category: keyof typeof TECH_OPTIONS): string[] => {
|
||||
|
||||
export const stackParsers = {
|
||||
projectName: parseAsString.withDefault(DEFAULT_STACK.projectName),
|
||||
frontend: parseAsArrayOf(parseAsString).withDefault(DEFAULT_STACK.frontend),
|
||||
webFrontend: parseAsArrayOf(parseAsString).withDefault(
|
||||
DEFAULT_STACK.webFrontend,
|
||||
),
|
||||
nativeFrontend: parseAsArrayOf(parseAsString).withDefault(
|
||||
DEFAULT_STACK.nativeFrontend,
|
||||
),
|
||||
runtime: parseAsStringEnum<StackState["runtime"]>(
|
||||
getValidIds("runtime"),
|
||||
).withDefault(DEFAULT_STACK.runtime),
|
||||
@@ -50,7 +55,8 @@ export const stackParsers = {
|
||||
|
||||
export const stackUrlKeys: UrlKeys<typeof stackParsers> = {
|
||||
projectName: "name",
|
||||
frontend: "fe",
|
||||
webFrontend: "fe-w",
|
||||
nativeFrontend: "fe-n",
|
||||
runtime: "rt",
|
||||
backend: "be",
|
||||
api: "api",
|
||||
|
||||
Reference in New Issue
Block a user