Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,24 @@ src/env.d.ts
*.min.css

# ============================================================
# 版本控制
# 版本控制 & CI/CD
# ============================================================
.git
.github

# ============================================================
# 点开头的配置文件 (除 .prettierrc.mjs 外)
# ============================================================
.clinerules
.gitignore
.husky
.prettierignore

# ============================================================
# 环境变量文件
# ============================================================
.env
.env.*

# ============================================================
# 操作系统
Expand Down
26 changes: 25 additions & 1 deletion .prettierrc.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,37 @@ export default {
},
},
{
files: ['*.json', '*.yml'],
files: ['*.json', '*.yml', '*.yaml'],
options: {
tabWidth: 2,
},
},
],

// 基础配置
tabWidth: 4,
useTabs: false,
endOfLine: 'lf',

// 引号与分号
singleQuote: true,
jsxSingleQuote: false,
semi: true,

// 尾逗号与括号
trailingComma: 'es5',
bracketSpacing: true,
bracketSameLine: false,

// 代码宽度
printWidth: 100,

// 箭头函数参数
arrowParens: 'always',

// HTML 空格敏感度
htmlWhitespaceSensitivity: 'css',

// 内嵌语言格式化
embeddedLanguageFormatting: 'auto',
};
5 changes: 1 addition & 4 deletions astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ import tailwindcss from '@tailwindcss/vite';
import { extractDomains } from './src/api/utils/url';

// 配置远程图片允许的域名
const imageDomains = extractDomains(
process.env.GHOST_URL,
process.env.IMAGE_HOST_URL,
);
const imageDomains = extractDomains(process.env.GHOST_URL, process.env.IMAGE_HOST_URL);

// https://astro.build/config
export default defineConfig({
Expand Down
5 changes: 1 addition & 4 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,7 @@ export function extractLocaleFromTags(tags: PostTag[]): Locale | null;
export function extractI18nKey(tags: PostTag[]): string | null;

// 多语言文章过滤
export function filterPostsByLocale<T>(
posts: T[],
currentLocale: Locale,
): LocalizedPost<T>[];
export function filterPostsByLocale<T>(posts: T[], currentLocale: Locale): LocalizedPost<T>[];
```

#### 多语言文章过滤逻辑
Expand Down
7 changes: 1 addition & 6 deletions docs/cline/TASK_ADD_PAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,7 @@ const { lang, post } = Astro.props;
import rss from '@astrojs/rss';
import type { APIContext } from 'astro';
import { getPosts } from '@api/ghost/posts';
import {
LOCALES,
type Locale,
filterPostsByLocale,
buildPostPath,
} from '@lib/i18n';
import { LOCALES, type Locale, filterPostsByLocale, buildPostPath } from '@lib/i18n';

export async function getStaticPaths() {
return LOCALES.map((lang) => ({
Expand Down
13 changes: 3 additions & 10 deletions docs/cline/templates/react-component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,7 @@ interface ComponentNameProps {
// 组件实现
// ============================================================

export default function ComponentName({
title,
children,
className,
onClick,
}: ComponentNameProps) {
export default function ComponentName({ title, children, className, onClick }: ComponentNameProps) {
// 状态管理
const [isActive, setIsActive] = useState(false);

Expand All @@ -57,7 +52,7 @@ export default function ComponentName({
// 条件样式
isActive && 'border-primary bg-primary/10',
// 外部传入的类名
className,
className
)}
onClick={handleClick}
role="button"
Expand All @@ -78,8 +73,6 @@ interface ComponentNameItemProps {
label: string;
}

ComponentName.Item = function ComponentNameItem({
label,
}: ComponentNameItemProps) {
ComponentName.Item = function ComponentNameItem({ label }: ComponentNameItemProps) {
return <span className="text-muted-foreground text-sm">{label}</span>;
};
212 changes: 212 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import js from '@eslint/js';
import typescript from '@typescript-eslint/eslint-plugin';
import typescriptParser from '@typescript-eslint/parser';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import jsxA11y from 'eslint-plugin-jsx-a11y';
import astro from 'eslint-plugin-astro';
import globals from 'globals';

/** @type {import('eslint').Linter.Config[]} */
export default [
// ============================================================
// 全局忽略
// ============================================================
{
ignores: [
'dist/**',
'build/**',
'output/**',
'.astro/**',
'node_modules/**',
'coverage/**',
'public/**',
'*.d.ts',
'*.min.js',
'*.min.css',
// 配置文件(由 Prettier 处理)
'.prettierrc.mjs',
// Astro 布局文件(复杂模板导致解析问题)
'src/layouts/base/BaseLayout.astro',
],
},

// ============================================================
// 基础 JavaScript 规则
// ============================================================
js.configs.recommended,

// ============================================================
// 全局变量配置
// ============================================================
{
languageOptions: {
globals: {
...globals.browser,
...globals.node,
...globals.es2021,
// React JSX runtime
React: 'readonly',
// Google Analytics
dataLayer: 'readonly',
gtag: 'readonly',
},
},
},

// ============================================================
// TypeScript 配置
// ============================================================
{
files: ['**/*.ts', '**/*.tsx'],
languageOptions: {
parser: typescriptParser,
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
},
plugins: {
'@typescript-eslint': typescript,
},
rules: {
// TypeScript 推荐规则
...typescript.configs.recommended.rules,

// 自定义 TypeScript 规则
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
},
],
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/consistent-type-imports': [
'error',
{
prefer: 'type-imports',
fixStyle: 'inline-type-imports',
},
],
'@typescript-eslint/no-import-type-side-effects': 'error',

// 禁用基础规则,使用 TypeScript 版本
'no-unused-vars': 'off',
},
},

// ============================================================
// React 配置
// ============================================================
{
files: ['**/*.tsx', '**/*.jsx'],
plugins: {
react: react,
'react-hooks': reactHooks,
'jsx-a11y': jsxA11y,
},
languageOptions: {
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
settings: {
react: {
version: 'detect',
},
},
rules: {
// React 推荐规则
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules,
...reactHooks.configs.recommended.rules,

// 可访问性规则
...jsxA11y.configs.recommended.rules,

// 自定义 React 规则
'react/prop-types': 'off', // TypeScript 已处理
'react/react-in-jsx-scope': 'off', // React 17+ 不需要
'react/display-name': 'off',
'react/no-unknown-property': ['error', { ignore: ['css'] }], // Emotion CSS prop

// Hooks 规则
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
// 允许在 effect 中设置初始状态(客户端初始化场景常见)
...(reactHooks.configs.recommended.rules['react-hooks/set-state-in-effect'] && {
'react-hooks/set-state-in-effect': 'off',
}),
// 允许修改 window.location 等浏览器全局对象
...(reactHooks.configs.recommended.rules['react-hooks/immutability'] && {
'react-hooks/immutability': 'off',
}),

// 可访问性规则调整
'jsx-a11y/anchor-is-valid': 'warn',
'jsx-a11y/anchor-has-content': 'warn', // 组件化场景下内容通过 props 传入
'jsx-a11y/click-events-have-key-events': 'warn',
'jsx-a11y/no-static-element-interactions': 'warn',
},
},

// ============================================================
// Astro 配置
// ============================================================
...astro.configs.recommended,
{
files: ['**/*.astro'],
rules: {
// Astro 特定规则调整
'astro/no-unused-css-selector': 'off', // 允许动态类名
},
},

// ============================================================
// 通用规则覆盖
// ============================================================
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.astro'],
rules: {
// 代码质量
'no-console': ['warn', { allow: ['warn', 'error'] }],
'no-debugger': 'warn',
'no-alert': 'warn',
'prefer-const': 'error',
'no-var': 'error',
eqeqeq: ['error', 'always', { null: 'ignore' }],

// 代码风格 (与 Prettier 配合)
'no-multiple-empty-lines': ['error', { max: 1, maxEOF: 0 }],
'no-trailing-spaces': 'error',
},
},

// ============================================================
// 测试文件配置
// ============================================================
{
files: ['**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts', '**/*.spec.tsx'],
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'no-console': 'off',
},
},

// ============================================================
// 配置文件 (允许 CommonJS)
// ============================================================
{
files: ['*.config.js', '*.config.mjs', '*.config.cjs', 'postcss.config.cjs'],
rules: {
'@typescript-eslint/no-require-imports': 'off',
},
},
];
13 changes: 13 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@
"preview": "astro preview",
"astro": "astro",
"prepare": "husky",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write .",
"format:check": "prettier --check .",
"format:staged": "lint-staged",
"check": "pnpm lint && pnpm format:check && astro check",
"test": "vitest",
"test:ui": "vitest --ui",
"test:run": "vitest run",
Expand All @@ -25,6 +28,7 @@
},
"lint-staged": {
"*.{astro,tsx,ts,jsx,js}": [
"eslint --fix",
"prettier --write"
],
"*.{json,md,yml,yaml,css,scss,html}": [
Expand Down Expand Up @@ -67,15 +71,24 @@
"typescript": "^5.9.3"
},
"devDependencies": {
"@eslint/js": "^9.39.2",
"@types/dompurify": "^3.2.0",
"@types/node": "^25.0.1",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@typescript-eslint/eslint-plugin": "^8.53.1",
"@typescript-eslint/parser": "^8.53.1",
"@vitest/coverage-v8": "^4.0.15",
"@vitest/ui": "^4.0.15",
"autoprefixer": "^10.4.22",
"axios-mock-adapter": "^2.1.0",
"cssnano": "^7.1.2",
"eslint": "^9.39.2",
"eslint-plugin-astro": "^1.5.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1",
"globals": "^17.1.0",
"husky": "^9.1.7",
"lint-staged": "^16.2.7",
"prettier": "^3.7.4",
Expand Down
Loading