From 1f87f4676eae3caf2d445874512c8579a80377d6 Mon Sep 17 00:00:00 2001 From: zetaloop Date: Fri, 20 Feb 2026 12:11:00 +0800 Subject: [PATCH] setup: Biome linter/formatter + Zustand + TanStack Query + RHF + Zod --- app/globals.css | 2 +- app/layout.tsx | 22 +++--- app/page.tsx | 4 +- biome.json | 58 +++++++++++++++ lib/utils.ts | 2 +- next-env.d.ts | 2 +- next.config.ts | 6 +- package.json | 17 ++++- pnpm-lock.yaml | 179 +++++++++++++++++++++++++++++++++++++++++++++ postcss.config.mjs | 4 +- 10 files changed, 270 insertions(+), 26 deletions(-) create mode 100644 biome.json diff --git a/app/globals.css b/app/globals.css index 382ca14..d767ad6 100644 --- a/app/globals.css +++ b/app/globals.css @@ -123,4 +123,4 @@ body { @apply bg-background text-foreground; } -} \ No newline at end of file +} diff --git a/app/layout.tsx b/app/layout.tsx index f7fa87e..41c0ab4 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,34 +1,30 @@ -import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; -import "./globals.css"; +import type { Metadata } from "next" +import { Geist, Geist_Mono } from "next/font/google" +import "./globals.css" const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"], -}); +}) const geistMono = Geist_Mono({ variable: "--font-geist-mono", subsets: ["latin"], -}); +}) export const metadata: Metadata = { title: "Create Next App", description: "Generated by create next app", -}; +} export default function RootLayout({ children, }: Readonly<{ - children: React.ReactNode; + children: React.ReactNode }>) { return ( - - {children} - + {children} - ); + ) } diff --git a/app/page.tsx b/app/page.tsx index 295f8fd..fcc63c4 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,4 +1,4 @@ -import Image from "next/image"; +import Image from "next/image" export default function Home() { return ( @@ -61,5 +61,5 @@ export default function Home() { - ); + ) } diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..91f7e0d --- /dev/null +++ b/biome.json @@ -0,0 +1,58 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.4.3/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "ignoreUnknown": true, + "includes": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "**/*.mjs", "**/*.json", "**/*.css"] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 100 + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "noUnusedImports": "warn", + "useExhaustiveDependencies": "warn" + }, + "style": { + "noNonNullAssertion": "warn", + "useImportType": "error" + }, + "suspicious": { + "noExplicitAny": "warn" + } + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double", + "semicolons": "asNeeded" + } + }, + "css": { + "parser": { + "cssModules": true, + "tailwindDirectives": true + }, + "formatter": { + "enabled": true + }, + "linter": { + "enabled": true + } + }, + "json": { + "formatter": { + "enabled": true + } + } +} diff --git a/lib/utils.ts b/lib/utils.ts index bd0c391..d084cca 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,4 +1,4 @@ -import { clsx, type ClassValue } from "clsx" +import { type ClassValue, clsx } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { diff --git a/next-env.d.ts b/next-env.d.ts index 9edff1c..7a70f65 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/types/routes.d.ts"; +import "./.next/types/routes.d.ts" // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/next.config.ts b/next.config.ts index e9ffa30..3b84f68 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,7 @@ -import type { NextConfig } from "next"; +import type { NextConfig } from "next" const nextConfig: NextConfig = { /* config options here */ -}; +} -export default nextConfig; +export default nextConfig diff --git a/package.json b/package.json index 7e38f0e..bc6f91d 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,18 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev", + "dev": "next dev --turbopack", "build": "next build", - "start": "next start" + "start": "next start", + "lint": "biome check .", + "lint:fix": "biome check --write .", + "format": "biome format --write .", + "check": "biome ci .", + "typecheck": "tsc --noEmit" }, "dependencies": { + "@hookform/resolvers": "^5.2.2", + "@tanstack/react-query": "^5.90.21", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.575.0", @@ -15,9 +22,13 @@ "radix-ui": "^1.4.3", "react": "19.2.3", "react-dom": "19.2.3", - "tailwind-merge": "^3.5.0" + "react-hook-form": "^7.71.1", + "tailwind-merge": "^3.5.0", + "zod": "^4.3.6", + "zustand": "^5.0.11" }, "devDependencies": { + "@biomejs/biome": "^2.4.3", "@tailwindcss/postcss": "^4", "@types/node": "^20", "@types/react": "^19", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3076255..7defc3a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,12 @@ importers: .: dependencies: + '@hookform/resolvers': + specifier: ^5.2.2 + version: 5.2.2(react-hook-form@7.71.1(react@19.2.3)) + '@tanstack/react-query': + specifier: ^5.90.21 + version: 5.90.21(react@19.2.3) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -29,10 +35,22 @@ importers: react-dom: specifier: 19.2.3 version: 19.2.3(react@19.2.3) + react-hook-form: + specifier: ^7.71.1 + version: 7.71.1(react@19.2.3) tailwind-merge: specifier: ^3.5.0 version: 3.5.0 + zod: + specifier: ^4.3.6 + version: 4.3.6 + zustand: + specifier: ^5.0.11 + version: 5.0.11(@types/react@19.2.14)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) devDependencies: + '@biomejs/biome': + specifier: ^2.4.3 + version: 2.4.3 '@tailwindcss/postcss': specifier: ^4 version: 4.2.0 @@ -197,6 +215,63 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@biomejs/biome@2.4.3': + resolution: {integrity: sha512-cBrjf6PNF6yfL8+kcNl85AjiK2YHNsbU0EvDOwiZjBPbMbQ5QcgVGFpjD0O52p8nec5O8NYw7PKw3xUR7fPAkQ==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@2.4.3': + resolution: {integrity: sha512-eOafSFlI/CF4id2tlwq9CVHgeEqvTL5SrhWff6ZORp6S3NL65zdsR3ugybItkgF8Pf4D9GSgtbB6sE3UNgOM9w==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@2.4.3': + resolution: {integrity: sha512-V2+av4ilbWcBMNufTtMMXVW00nPwyIjI5qf7n9wSvUaZ+tt0EvMGk46g9sAFDJBEDOzSyoRXiSP6pCvKTOEbPA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@2.4.3': + resolution: {integrity: sha512-QuFzvsGo8BA4Xm7jGX5idkw6BqFblcCPySMTvq0AhGYnhUej5VJIDJbmTKfHqwjHepZiC4fA+T5i6wmiZolZNw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@biomejs/cli-linux-arm64@2.4.3': + resolution: {integrity: sha512-0m+O0x9FgK99FAwDK+fiDtjs2wnqq7bvfj17KJVeCkTwT/liI+Q9njJG7lwXK0iSJVXeFNRIxukpVI3SifMYAA==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@biomejs/cli-linux-x64-musl@2.4.3': + resolution: {integrity: sha512-qEc0OCpj/uytruQ4wLM0yWNJLZy0Up8H1Er5MW3SrstqM6J2d4XqdNA86xzCy8MQCHpoVZ3lFye3GBlIL4/ljw==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@biomejs/cli-linux-x64@2.4.3': + resolution: {integrity: sha512-NVqh0saIU0u5OfOp/0jFdlKRE59+XyMvWmtx0f6Nm/2OpdxBl04coRIftBbY9d1gfu+23JVv4CItAqPYrjYh5w==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@biomejs/cli-win32-arm64@2.4.3': + resolution: {integrity: sha512-gRO96vrIARilv/Cp2ZnmNNL5LSZg3RO75GPp13hsLO3N4YVpE7saaMDp2bcyV48y2N2Pbit1brkGVGta0yd6VQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@2.4.3': + resolution: {integrity: sha512-vSm/vOJe06pf14aGHfHl3Ar91Nlx4YYmohElDJ+17UbRwe99n987S/MhAlQOkONqf1utJor04ChkCPmKb8SWdw==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + '@dotenvx/dotenvx@1.52.0': resolution: {integrity: sha512-CaQcc8JvtzQhUSm9877b6V4Tb7HCotkcyud9X2YwdqtQKwgljkMRwU96fVYKnzN3V0Hj74oP7Es+vZ0mS+Aa1w==} hasBin: true @@ -231,6 +306,11 @@ packages: peerDependencies: hono: ^4 + '@hookform/resolvers@5.2.2': + resolution: {integrity: sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==} + peerDependencies: + react-hook-form: ^7.55.0 + '@img/colour@1.0.0': resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} engines: {node: '>=18'} @@ -1234,6 +1314,9 @@ packages: resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} + '@standard-schema/utils@0.3.0': + resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} @@ -1329,6 +1412,14 @@ packages: '@tailwindcss/postcss@4.2.0': resolution: {integrity: sha512-u6YBacGpOm/ixPfKqfgrJEjMfrYmPD7gEFRoygS/hnQaRtV0VCBdpkx5Ouw9pnaLRwwlgGCuJw8xLpaR0hOrQg==} + '@tanstack/query-core@5.90.20': + resolution: {integrity: sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==} + + '@tanstack/react-query@5.90.21': + resolution: {integrity: sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==} + peerDependencies: + react: ^18 || ^19 + '@ts-morph/common@0.27.0': resolution: {integrity: sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==} @@ -2334,6 +2425,12 @@ packages: peerDependencies: react: ^19.2.3 + react-hook-form@7.71.1: + resolution: {integrity: sha512-9SUJKCGKo8HUSsCO+y0CtqkqI5nNuaDqTxyqPsZPqIwudpj4rCrAz/jZV+jn57bx5gtZKOh3neQu94DXMc+w5w==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 + react-remove-scroll-bar@2.3.8: resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} engines: {node: '>=10'} @@ -2724,6 +2821,27 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + + zustand@5.0.11: + resolution: {integrity: sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + snapshots: '@alloc/quick-lru@5.2.0': {} @@ -2921,6 +3039,41 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@biomejs/biome@2.4.3': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 2.4.3 + '@biomejs/cli-darwin-x64': 2.4.3 + '@biomejs/cli-linux-arm64': 2.4.3 + '@biomejs/cli-linux-arm64-musl': 2.4.3 + '@biomejs/cli-linux-x64': 2.4.3 + '@biomejs/cli-linux-x64-musl': 2.4.3 + '@biomejs/cli-win32-arm64': 2.4.3 + '@biomejs/cli-win32-x64': 2.4.3 + + '@biomejs/cli-darwin-arm64@2.4.3': + optional: true + + '@biomejs/cli-darwin-x64@2.4.3': + optional: true + + '@biomejs/cli-linux-arm64-musl@2.4.3': + optional: true + + '@biomejs/cli-linux-arm64@2.4.3': + optional: true + + '@biomejs/cli-linux-x64-musl@2.4.3': + optional: true + + '@biomejs/cli-linux-x64@2.4.3': + optional: true + + '@biomejs/cli-win32-arm64@2.4.3': + optional: true + + '@biomejs/cli-win32-x64@2.4.3': + optional: true + '@dotenvx/dotenvx@1.52.0': dependencies: commander: 11.1.0 @@ -2963,6 +3116,11 @@ snapshots: dependencies: hono: 4.12.0 + '@hookform/resolvers@5.2.2(react-hook-form@7.71.1(react@19.2.3))': + dependencies: + '@standard-schema/utils': 0.3.0 + react-hook-form: 7.71.1(react@19.2.3) + '@img/colour@1.0.0': optional: true @@ -3944,6 +4102,8 @@ snapshots: '@sindresorhus/merge-streams@4.0.0': {} + '@standard-schema/utils@0.3.0': {} + '@swc/helpers@0.5.15': dependencies: tslib: 2.8.1 @@ -4017,6 +4177,13 @@ snapshots: postcss: 8.5.6 tailwindcss: 4.2.0 + '@tanstack/query-core@5.90.20': {} + + '@tanstack/react-query@5.90.21(react@19.2.3)': + dependencies: + '@tanstack/query-core': 5.90.20 + react: 19.2.3 + '@ts-morph/common@0.27.0': dependencies: fast-glob: 3.3.3 @@ -4957,6 +5124,10 @@ snapshots: react: 19.2.3 scheduler: 0.27.0 + react-hook-form@7.71.1(react@19.2.3): + dependencies: + react: 19.2.3 + react-remove-scroll-bar@2.3.8(@types/react@19.2.14)(react@19.2.3): dependencies: react: 19.2.3 @@ -5376,3 +5547,11 @@ snapshots: zod: 3.25.76 zod@3.25.76: {} + + zod@4.3.6: {} + + zustand@5.0.11(@types/react@19.2.14)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)): + optionalDependencies: + '@types/react': 19.2.14 + react: 19.2.3 + use-sync-external-store: 1.6.0(react@19.2.3) diff --git a/postcss.config.mjs b/postcss.config.mjs index 61e3684..2f8795a 100644 --- a/postcss.config.mjs +++ b/postcss.config.mjs @@ -2,6 +2,6 @@ const config = { plugins: { "@tailwindcss/postcss": {}, }, -}; +} -export default config; +export default config