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