add seperate rows for native and web frontends

This commit is contained in:
Aman Varshney
2025-05-13 11:27:41 +05:30
parent b38a33115a
commit 837d46c675
3 changed files with 174 additions and 84 deletions

View File

@@ -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[]) || []

View File

@@ -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();

View File

@@ -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",