diff --git a/.husky/_/husky.sh b/.husky/_/husky.sh new file mode 100644 index 0000000..756185a --- /dev/null +++ b/.husky/_/husky.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env sh +if [ -z "$husky_skip_init" ]; then + debug () { + if [ "$HUSKY_DEBUG" = "1" ]; then + echo "husky (debug) - $1" + fi + } + + readonly hook_name="$(basename -- "$0")" + debug "starting $hook_name..." + + if [ "$HUSKY" = "0" ]; then + debug "HUSKY env variable is set to 0, skipping hook" + exit 0 + fi + + if [ -f ~/.huskyrc ]; then + debug "sourcing ~/.huskyrc" + . ~/.huskyrc + fi + + readonly husky_skip_init=1 + export husky_skip_init + sh -e "$0" "$@" + exitCode="$?" + + if [ $exitCode != 0 ]; then + echo "husky - $hook_name hook exited with code $exitCode (error)" + fi + + if [ $exitCode = 127 ]; then + echo "husky - command not found in PATH=$PATH" + fi + + exit $exitCode +fi \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..267e359 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +cd frontend && npm run lint && npm run format:check \ No newline at end of file diff --git a/frontend/.prettierignore b/frontend/.prettierignore new file mode 100644 index 0000000..f218f90 --- /dev/null +++ b/frontend/.prettierignore @@ -0,0 +1,26 @@ +# 依赖目录 +node_modules/ + +# 构建输出 +dist/ +build/ + +# 日志文件 +*.log + +# 环境变量文件 +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# 包管理器文件 +package-lock.json +yarn.lock +pnpm-lock.yaml + +# 其他 +.DS_Store +*.min.js +*.min.css \ No newline at end of file diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index 3e98068..2425cba 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.js @@ -1,45 +1,51 @@ -import js from '@eslint/js' -import globals from 'globals' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' -import tseslint from 'typescript-eslint' +import js from "@eslint/js"; +import globals from "globals"; +import reactHooks from "eslint-plugin-react-hooks"; +import reactRefresh from "eslint-plugin-react-refresh"; +import tseslint from "typescript-eslint"; +import eslintConfigPrettier from "eslint-config-prettier"; +import eslintPluginPrettier from "eslint-plugin-prettier"; export default tseslint.config( - { ignores: ['dist'] }, + { ignores: ["dist"] }, { - extends: [js.configs.recommended, ...tseslint.configs.recommended], - files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + ...tseslint.configs.recommended, + eslintConfigPrettier, + ], + files: ["**/*.{ts,tsx}"], languageOptions: { ecmaVersion: 2020, globals: globals.browser, }, plugins: { - 'react-hooks': reactHooks, - 'react-refresh': reactRefresh, + "react-hooks": reactHooks, + "react-refresh": reactRefresh, + prettier: eslintPluginPrettier, }, rules: { ...reactHooks.configs.recommended.rules, - 'react-refresh/only-export-components': [ - 'warn', + "react-refresh/only-export-components": [ + "warn", { allowConstantExport: true }, ], - '@typescript-eslint/no-unused-vars': [ - 'off', + "@typescript-eslint/no-unused-vars": [ + "off", { - argsIgnorePattern: '^_', - varsIgnorePattern: '^_', + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", ignoreRestSiblings: true, }, ], - '@typescript-eslint/no-explicit-any': [ - 'off', + "@typescript-eslint/no-explicit-any": [ + "off", { ignoreRestArgs: true, }, ], - '@typescript-eslint/ban-ts-comment': [ - 'off' - ] + "@typescript-eslint/ban-ts-comment": ["off"], + "prettier/prettier": "error", }, }, -) +); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 32ecdba..96d9917 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -31,9 +31,13 @@ "@vitejs/plugin-react": "^4.3.4", "daisyui": "^5.0.35", "eslint": "^9.22.0", + "eslint-config-prettier": "^10.1.5", + "eslint-plugin-prettier": "^5.4.1", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.19", "globals": "^16.0.0", + "husky": "^9.1.7", + "prettier": "^3.5.3", "typescript": "~5.7.2", "typescript-eslint": "^8.26.1", "vite": "^6.3.1" @@ -1111,6 +1115,19 @@ "node": ">= 8" } }, + "node_modules/@pkgr/core": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz", + "integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, "node_modules/@react-hook/debounce": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@react-hook/debounce/-/debounce-3.0.0.tgz", @@ -2551,9 +2568,9 @@ } }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2866,6 +2883,53 @@ } } }, + "node_modules/eslint-config-prettier": { + "version": "10.1.5", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz", + "integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.1.tgz", + "integrity": "sha512-9dF+KuU/Ilkq27A8idRP7N2DH8iUR6qXcjF3FR2wETY21PZdBrIjwCau8oboyGj9b7etWmTGEeM8e7oOed6ZWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.11.7" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, "node_modules/eslint-plugin-react-hooks": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", @@ -3098,6 +3162,13 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -3556,6 +3627,22 @@ "node": ">= 0.8" } }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/i18next": { "version": "25.1.1", "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.1.1.tgz", @@ -5141,6 +5228,35 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/property-information": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", @@ -5796,6 +5912,22 @@ "node": ">=8" } }, + "node_modules/synckit": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz", + "integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.4" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, "node_modules/tailwindcss": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.5.tgz", @@ -6318,6 +6450,20 @@ "dev": true, "license": "ISC" }, + "node_modules/yaml": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 6d47b19..7a5e87f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -7,7 +7,12 @@ "dev": "vite", "build": "tsc -b && vite build", "lint": "eslint .", - "preview": "vite preview" + "lint:fix": "eslint . --fix", + "format": "prettier --write .", + "format:check": "prettier --check .", + "lint:format": "npm run lint:fix && npm run format", + "preview": "vite preview", + "prepare": "husky" }, "dependencies": { "@marsidev/react-turnstile": "^1.1.0", @@ -33,9 +38,13 @@ "@vitejs/plugin-react": "^4.3.4", "daisyui": "^5.0.35", "eslint": "^9.22.0", + "eslint-config-prettier": "^10.1.5", + "eslint-plugin-prettier": "^5.4.1", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.19", "globals": "^16.0.0", + "husky": "^9.1.7", + "prettier": "^3.5.3", "typescript": "~5.7.2", "typescript-eslint": "^8.26.1", "vite": "^6.3.1" diff --git a/frontend/prettierrc.yml b/frontend/prettierrc.yml new file mode 100644 index 0000000..2a77ec8 --- /dev/null +++ b/frontend/prettierrc.yml @@ -0,0 +1,12 @@ +printWidth: 120 +tabWidth: 2 +useTabs: false +semi: false +singleQuote: false +quoteProps: "as-needed" +jsxSingleQuote: true +trailingComma: "none" +bracketSameLine: false +arrowParens: "avoid" +endOfLine: "lf" +bracketSpacing: false