diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d5d9d5c..b78e40e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,930 +1,77 @@ -# 贡献指南 (Contributing Guide) +# Contributing -欢迎来到 **KVideo** 项目!我们非常感谢你愿意为这个项目做出贡献。无论是修复 Bug、添加新功能、改进文档,还是提出建议,你的每一份贡献都将让这个项目变得更好。 +## Baseline -为了确保协作顺畅、代码质量一致,请在提交贡献前仔细阅读本指南。 +Contributions are expected to preserve the post-audit behavior of this repository: -## 📋 目录 +- secure outbound request policy +- private-by-default relay endpoints +- explicit auth secret requirements +- Workers/OpenNext Cloudflare path +- Android TV-only wrapper scope +- Apple TV unsupported -- [行为准则](#行为准则) -- [快速开始](#快速开始) -- [开发环境设置](#开发环境设置) -- [代码规范](#代码规范) -- [Git 工作流程](#git-工作流程) -- [提交规范](#提交规范) -- [Pull Request 指南](#pull-request-指南) -- [设计系统规范](#设计系统规范) -- [测试要求](#测试要求) -- [常见问题](#常见问题) +Do not reintroduce permissive relay behavior, wildcard CORS, cookie forwarding, TLS verification bypasses, or public private-network fetches. -## 🤝 行为准则 +## Prerequisites -我们致力于构建一个开放、友好、包容的社区环境。请在参与项目时: +- Node.js 22+ +- npm 10+ +- Java 17 for Android builds +- Android SDK for Android TV validation +- Docker if you need to run the image/build checks locally -- ✅ 保持尊重和礼貌 -- ✅ 欢迎不同的观点和经验 -- ✅ 接受建设性的批评 -- ✅ 专注于对社区最有利的事情 -- ❌ 不要使用性别化的语言或图像 -- ❌ 不要进行人身攻击或政治攻击 -- ❌ 不要骚扰或歧视他人 - -详细的行为准则请参阅 [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)。 - -## 🚀 快速开始 - -### 我能贡献什么? - -以下是一些你可以做出贡献的方式: - -1. **🐛 报告 Bug**:发现了问题?请提交 Issue -2. **💡 提出新功能**:有好想法?在 Discussions 或 Issues 中分享 -3. **📝 改进文档**:发现文档不清晰或有错误?帮助我们改进 -4. **🎨 优化 UI/UX**:让界面更美观、更易用 -5. **⚡ 性能优化**:让应用运行得更快 -6. **🔧 修复 Bug**:解决现有的问题 -7. **✨ 添加功能**:实现新的特性 - -### 第一次贡献? - -如果这是你第一次为开源项目做贡献,我们推荐: - -1. 浏览 [GitHub Issues](https://github.com/KuekHaoYang/KVideo/issues) -2. 寻找标记为 `good first issue` 的问题 -3. 在 Issue 中评论,表明你想要解决这个问题 -4. 按照本指南进行开发和提交 - -## 🛠 开发环境设置 - -### 系统要求 - -确保你的开发环境满足以下要求: - -| 工具 | 最低版本 | 推荐版本 | 检查命令 | -|------|----------|----------|----------| -| **Node.js** | 20.0.0 | 22.x LTS | `node --version` | -| **npm** | 9.0.0 | 10.x | `npm --version` | -| **Git** | 2.30.0 | 最新版本 | `git --version` | - -### 详细设置步骤 - -#### 1. Fork 仓库 - -点击 GitHub 页面右上角的 "Fork" 按钮,将项目 Fork 到你的账号下。 - -#### 2. 克隆仓库 - -```bash -# 克隆你 Fork 的仓库 -git clone https://github.com/YOUR_USERNAME/KVideo.git -cd KVideo - -# 添加上游仓库 -git remote add upstream https://github.com/KuekHaoYang/KVideo.git -``` - -#### 3. 安装依赖 +Install dependencies: ```bash npm install ``` -#### 4. 启动开发服务器 +## Development Commands ```bash npm run dev -``` - -访问 `http://localhost:3000` 查看应用。 - -#### 5. 验证环境 - -确保以下命令都能正常运行: - -```bash -# 代码检查 npm run lint - -# 构建测试 +npm test +npm run test:e2e npm run build +npm run cf:build +docker compose config +docker build -t kvideo . +cd android-tv && ./gradlew --no-daemon lint test assembleDebug assembleRelease ``` -## 📏 代码规范 +## Required Checks Before a PR -### 核心规范 +At minimum, run the checks relevant to the code you changed. For broad or infrastructure-facing work, run the full matrix: -#### 1. 文件长度限制 ⚠️ +- `npm run lint` +- `npm test` +- `npm run build` +- `npm run cf:build` +- `npm audit --omit=dev` +- `docker compose config` +- `docker build -t kvideo .` +- `cd android-tv && ./gradlew --no-daemon lint test assembleDebug assembleRelease` -> [!CAUTION] -> **这是项目的硬性规则!所有项目文件必须保持在 150 行以内(除系统文件外)。** +If you touch user flows, add or update Playwright smoke coverage in [`playwright`](/Users/haoyangkuek/development/KVideo/playwright). -**检查命令:** +## Style Expectations -```bash -find . -type f -not -path "*/node_modules/*" -not -path "*/.next/*" -not -path "*/.git/*" -not -name "package-lock.json" -not -name "*.png" -not -name "*.md" | xargs wc -l | awk '$1 > 150 && $2 != "total" {print $2 " - " $1 "行"}' -``` +- Keep changes scoped and intentional. +- Prefer testable extraction over speculative abstraction. +- Do not add fake compatibility aliases for insecure legacy behavior. +- Do not depend on arbitrary file-length limits. CI-backed quality gates matter; line counts do not. +- Keep documentation accurate to the actual runtime behavior of the branch. -**如果命令有输出,说明有文件超过 150 行,必须重构!** +## Pull Requests -**重构策略:** +Each PR should include: -如果文件超过 150 行,请使用以下方法重构: +- what changed +- why it changed +- risk areas +- validation performed +- any deployment/env var changes -##### A. 提取组件 - -**问题:** 一个组件太长,包含太多 JSX - -**解决方案:** 将大组件拆分为多个小组件 - -```typescript -// ❌ 不好:一个 200 行的大组件 -export function VideoPlayer() { - // 150+ 行代码 - return ( -
- {/* 大量 JSX */} -
- ); -} - -// ✅ 好:拆分为多个小组件 -export function VideoPlayer() { - return ( -
- - - -
- ); -} - -// PlayerControls.tsx (单独文件) -export function PlayerControls() { /* ... */ } - -// ProgressBar.tsx (单独文件) -export function ProgressBar() { /* ... */ } - -// VolumeControl.tsx (单独文件) -export function VolumeControl() { /* ... */ } -``` - -##### B. 提取自定义 Hook - -**问题:** 组件包含大量状态逻辑 - -**解决方案:** 将逻辑提取到自定义 Hook - -```typescript -// ❌ 不好:组件内有大量状态逻辑 -export function SearchPage() { - const [query, setQuery] = useState(''); - const [results, setResults] = useState([]); - const [loading, setLoading] = useState(false); - // ... 大量逻辑 - - const handleSearch = async () => { - // ... 50+ 行逻辑 - }; - - return
{/* JSX */}
; -} - -// ✅ 好:提取到自定义 Hook -export function SearchPage() { - const { query, results, loading, handleSearch } = useSearch(); - return
{/* JSX */}
; -} - -// useSearch.ts (单独文件) -export function useSearch() { - // ... 所有状态逻辑 - return { query, results, loading, handleSearch }; -} -``` - -##### C. 提取工具函数 - -**问题:** 文件包含大量辅助函数 - -**解决方案:** 将工具函数移到 `lib/utils/` - -```typescript -// ❌ 不好:组件文件包含工具函数 -export function VideoCard() { - const formatDuration = (seconds: number) => { - // ... 格式化逻辑 - }; - - const formatDate = (date: Date) => { - // ... 格式化逻辑 - }; - - // ... 更多工具函数 - - return
{/* JSX */}
; -} - -// ✅ 好:提取到工具文件 -import { formatDuration, formatDate } from '@/lib/utils/format-utils'; - -export function VideoCard() { - return
{/* JSX */}
; -} - -// lib/utils/format-utils.ts -export function formatDuration(seconds: number) { /* ... */ } -export function formatDate(date: Date) { /* ... */ } -``` - -##### D. 模块化 - -**问题:** 单个文件处理多个相关功能 - -**解决方案:** 按功能拆分文件并使用桶文件(barrel exports) - -```typescript -// ❌ 不好:player-utils.ts 包含 200 行 -export function parseHLS() { /* ... */ } -export function handlePlayback() { /* ... */ } -export function manageQuality() { /* ... */ } -// ... 更多函数 - -// ✅ 好:拆分为多个文件 -// lib/utils/player/index.ts -export * from './hls-parser'; -export * from './playback-manager'; -export * from './quality-manager'; - -// lib/utils/player/hls-parser.ts -export function parseHLS() { /* ... */ } - -// lib/utils/player/playback-manager.ts -export function handlePlayback() { /* ... */ } - -// lib/utils/player/quality-manager.ts -export function manageQuality() { /* ... */ } -``` - -#### 2. TypeScript 规范 - -**类型安全** - -```typescript -// ❌ 避免使用 any -function processData(data: any) { - return data.value; -} - -// ✅ 使用具体类型 -interface VideoData { - id: string; - title: string; - url: string; -} - -function processData(data: VideoData) { - return data.title; -} - -// ✅ 或使用 unknown(需要类型检查) -function processData(data: unknown) { - if (typeof data === 'object' && data !== null && 'value' in data) { - return (data as { value: string }).value; - } - throw new Error('Invalid data'); -} -``` - -**函数返回类型** - -```typescript -// ❌ 缺少返回类型 -function calculateTotal(items) { - return items.reduce((sum, item) => sum + item.price, 0); -} - -// ✅ 明确返回类型 -function calculateTotal(items: Item[]): number { - return items.reduce((sum, item) => sum + item.price, 0); -} -``` - -**接口定义** - -```typescript -// ✅ 使用 interface 定义对象类型 -interface VideoCardProps { - video: Video; - onPlay: (id: string) => void; - className?: string; -} - -// ✅ 使用 type 定义联合类型 -type ThemeMode = 'light' | 'dark' | 'system'; -``` - -#### 3. React 组件规范 - -**函数组件** - -```typescript -// ✅ 标准函数组件结构 -interface ButtonProps { - variant?: 'primary' | 'secondary'; - children: React.ReactNode; - onClick?: () => void; -} - -export function Button({ variant = 'primary', children, onClick }: ButtonProps) { - return ( - - ); -} -``` - -**组件文件组织** - -```typescript -// 1. 导入 -import React from 'react'; -import { useState } from 'react'; -import { useRouter } from 'next/navigation'; - -// 2. 类型定义 -interface ComponentProps { - // ... -} - -// 3. 组件定义 -export function Component({ prop1, prop2 }: ComponentProps) { - // 4. Hooks - const [state, setState] = useState(); - const router = useRouter(); - - // 5. 事件处理函数 - const handleClick = () => { - // ... - }; - - // 6. 渲染 - return ( -
{/* JSX */}
- ); -} -``` - -**单一职责原则** - -```typescript -// ❌ 组件做太多事情 -export function VideoSection() { - // 获取数据 - // 处理搜索 - // 渲染列表 - // 处理分页 - // 处理过滤 -} - -// ✅ 拆分为专注的组件 -export function VideoSection() { - const videos = useVideos(); - return ( -
- - - - -
- ); -} -``` - -#### 4. 样式规范 - -**Tailwind CSS 优先** - -```typescript -// ✅ 使用 Tailwind 类名 -export function Card({ children }: { children: React.ReactNode }) { - return ( -
- {children} -
- ); -} -``` - -**遵循 Liquid Glass 设计系统** - -```typescript -// ✅ 正确使用圆角 -
{/* 容器:大圆角 */} -
{/* 小元素:完全圆形 */} - -// ❌ 不要使用其他圆角值 -
{/* 错误! */} -
{/* 错误! */} -``` - -**响应式设计** - -```typescript -// ✅ 移动优先的响应式设计 -
-``` - -#### 5. 命名规范 - -**文件命名** - -- 组件文件:`PascalCase.tsx`(例如:`VideoCard.tsx`) -- Hook 文件:`camelCase.ts`(例如:`useVideoPlayer.ts`) -- 工具文件:`kebab-case.ts`(例如:`format-utils.ts`) -- 类型文件:`kebab-case.ts`(例如:`video-types.ts`) - -**变量命名** - -```typescript -// ✅ 清晰的命名 -const videoList = [...]; -const isLoading = false; -const handleSubmit = () => {}; - -// ❌ 模糊的命名 -const data = [...]; -const flag = false; -const fn = () => {}; -``` - -**常量命名** - -```typescript -// ✅ 全大写 + 下划线 -const MAX_VIDEO_DURATION = 7200; -const API_BASE_URL = 'https://api.example.com'; -``` - -#### 6. 导入顺序 - -```typescript -// 1. React 和 Next.js -import React from 'react'; -import { useState } from 'react'; -import Link from 'next/link'; - -// 2. 第三方库 -import { create } from 'zustand'; - -// 3. 项目别名导入 -import { Button } from '@/components/ui/Button'; -import { formatDate } from '@/lib/utils/date-utils'; - -// 4. 相对路径导入 -import { LocalComponent } from './LocalComponent'; - -// 5. 类型导入 -import type { Video } from '@/lib/types/video'; -``` - -## 🔄 Git 工作流程 - -### 分支策略 - -**主分支** - -- `main`:稳定的生产分支,只接受 PR 合并 - -**功能分支命名** - -遵循以下命名规范: - -- `feat/功能名称`:新功能(例如:`feat/add-playlist`) -- `fix/问题描述`:错误修复(例如:`fix/search-crash`) -- `docs/文档修改`:文档更新(例如:`docs/update-readme`) -- `refactor/重构名称`:代码重构(例如:`refactor/player-controls`) -- `perf/优化内容`:性能优化(例如:`perf/image-loading`) -- `style/样式修改`:样式调整(例如:`style/button-spacing`) -- `test/测试内容`:测试相关(例如:`test/add-unit-tests`) -- `chore/其他修改`:构建或工具变动(例如:`chore/update-deps`) - -### 开发流程 - -#### 1. 同步上游仓库 - -在开始新工作前,先同步最新的代码: - -```bash -# 获取上游更新 -git fetch upstream - -# 切换到主分支 -git checkout main - -# 合并上游更新 -git merge upstream/main - -# 推送到你的 Fork -git push origin main -``` - -#### 2. 创建功能分支 - -```bash -# 从 main 创建新分支 -git checkout -b feat/your-feature-name - -# 确认当前分支 -git branch -``` - -#### 3. 进行开发 - -在开发过程中: - -- 频繁提交小的、原子性的改动 -- 编写清晰的提交信息 -- 定期运行 `npm run lint` 检查代码 - -#### 4. 提交前检查 - -**必须通过的检查:** - -```bash -# 1. 代码规范检查 -npm run lint - -# 2. 文件长度检查 -find . -type f -not -path "*/node_modules/*" -not -path "*/.next/*" -not -path "*/.git/*" -not -name "package-lock.json" -not -name "*.png" -not -name "*.md" | xargs wc -l | awk '$1 > 150 && $2 != "total" {print $2 " - " $1 "行"}' - -# 3. 构建测试 -npm run build -``` - -**如果任何检查失败,必须先修复!** - -#### 5. 推送分支 - -```bash -# 推送到你的 Fork -git push origin feat/your-feature-name -``` - -## 📝 提交规范 - -### Conventional Commits - -我们使用 [Conventional Commits](https://www.conventionalcommits.org/) 规范: - -``` -(): - - - -