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