From 3d36b994c51d5f8b28bd18ee7594de97c4d01e1f Mon Sep 17 00:00:00 2001 From: xiwangly2 Date: Sat, 26 Sep 2026 15:32:26 +0800 Subject: [PATCH] ci: add pull request validation workflow --- .github/workflows/build.yml | 24 ++-- .github/workflows/ci.yml | 106 ++++++++++++++++++ .../utils/__tests__/chunkUpload.fault.test.ts | 17 ++- .../chunkUpload.preservation.test.ts | 7 ++ client/src/utils/chunkUpload.ts | 2 +- ...04\345\273\272\350\257\264\346\230\216.md" | 39 ++++--- ...00\346\237\245\350\257\264\346\230\216.md" | 45 +++++--- 7 files changed, 192 insertions(+), 48 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4f3433e8..803f5a74 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -80,12 +80,16 @@ jobs: with: node-version: '22.16.0' cache: 'npm' + cache-dependency-path: | + package-lock.json + client/package-lock.json + server/package-lock.json - name: Install dependencies run: | - npm install - cd client && npm install - cd ../server && npm install + npm ci + npm ci --prefix client + npm ci --prefix server - name: Build Linux ${{ matrix.arch }} package run: ${{ matrix.package_command }} env: @@ -111,15 +115,15 @@ jobs: with: node-version: '22.16.0' cache: 'npm' + cache-dependency-path: | + package-lock.json + client/package-lock.json + server/package-lock.json - name: Install dependencies run: | - npm install - cd client - npm install - cd .. - cd server - npm install - cd .. + npm ci + npm ci --prefix client + npm ci --prefix server - name: Build package run: npm run package:windows env: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..cb1bfb9a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,106 @@ +name: CI + +on: + pull_request: + branches: + - main + types: + - opened + - synchronize + - reopened + - ready_for_review + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + CI: true + NODE_VERSION: '22.16.0' + +jobs: + server: + name: Server test and build + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: npm + cache-dependency-path: server/package-lock.json + + - name: Install server dependencies + run: npm ci + working-directory: server + + - name: Run server tests + run: npm test + working-directory: server + + - name: Build server + run: npm run build + working-directory: server + + client: + name: Client test and build + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: npm + cache-dependency-path: client/package-lock.json + + - name: Install client dependencies + run: npm ci + working-directory: client + + - name: Run client tests + run: npm test + working-directory: client + + - name: Build client + run: npm run build + working-directory: client + + project-scripts: + name: Project scripts + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: npm + cache-dependency-path: package-lock.json + + - name: Install root dependencies + run: npm ci + + - name: Check packaging scripts syntax + run: | + node --check scripts/package.js + node --check scripts/resolve-build-version.js + + - name: Resolve build version + run: node scripts/resolve-build-version.js diff --git a/client/src/utils/__tests__/chunkUpload.fault.test.ts b/client/src/utils/__tests__/chunkUpload.fault.test.ts index e109bf43..167fc375 100644 --- a/client/src/utils/__tests__/chunkUpload.fault.test.ts +++ b/client/src/utils/__tests__/chunkUpload.fault.test.ts @@ -88,6 +88,18 @@ async function waitFor(condition: () => boolean, timeoutMs = 5000): Promise, chunkSize: number): Promise { try { + ;(globalThis.fetch as any).mockImplementation(async (url: string, options?: any) => { + if (options?.body) { + try { + const body = JSON.parse(options.body) + if (body.uploadId !== undefined && body.totalChunks !== undefined && !body.chunkIndex && body.targetPath) { + return { ok: true, json: async () => ({ success: true }) } + } + } catch { /* 非 JSON body */ } + } + return { ok: true, json: async () => ({ uploadedChunks: [] }) } + }) + // 多轮完成,处理批次间的延迟和新 XHR 创建 for (let round = 0; round < 5; round++) { for (const xhr of xhrInstances) { @@ -98,11 +110,6 @@ async function cleanupUpload(uploadPromise: Promise, chunkSize: number): P } await new Promise(r => setTimeout(r, 300)) } - // Mock merge - ;(globalThis.fetch as any).mockResolvedValue({ - ok: true, - json: async () => ({ success: true }), - }) await Promise.race([ uploadPromise, new Promise((_, rej) => setTimeout(() => rej(new Error('清理超时')), 8000)), diff --git a/client/src/utils/__tests__/chunkUpload.preservation.test.ts b/client/src/utils/__tests__/chunkUpload.preservation.test.ts index a8f5db73..184f6af4 100644 --- a/client/src/utils/__tests__/chunkUpload.preservation.test.ts +++ b/client/src/utils/__tests__/chunkUpload.preservation.test.ts @@ -419,11 +419,14 @@ describe('保持行为属性测试 — 非进度计算行为不变', () => { it('属性4: abort 信号正确中止所有 XHR 请求', async () => { const FILE_SIZE = 200 * MB const controller = new AbortController() + const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) const file = createMockFile(FILE_SIZE) const uploader = new ChunkUploader({ file, targetPath: '/test', + maxRetries: 1, signal: controller.signal, }) @@ -450,6 +453,10 @@ describe('保持行为属性测试 — 非进度计算行为不变', () => { // 验证 XHR 实例的 abort 被调用 const abortedCount = xhrBeforeAbort.filter(xhr => xhr._aborted).length expect(abortedCount).toBe(CONCURRENT_UPLOADS) + + await uploadPromise + consoleWarnSpy.mockRestore() + consoleErrorSpy.mockRestore() }) // ============================================================ diff --git a/client/src/utils/chunkUpload.ts b/client/src/utils/chunkUpload.ts index 3be009a0..8a8639c7 100644 --- a/client/src/utils/chunkUpload.ts +++ b/client/src/utils/chunkUpload.ts @@ -6,7 +6,7 @@ export interface ChunkUploadOptions { file: File targetPath: string - chunkSize?: number // 默认50MB + chunkSize?: number // 默认20MB maxRetries?: number // 单个分片最大重试次数 onProgress?: (progress: number) => void onChunkProgress?: (chunkIndex: number, totalChunks: number, chunkProgress: number) => void diff --git "a/docs/GitHub-Actions-\345\244\232\346\236\266\346\236\204\346\236\204\345\273\272\350\257\264\346\230\216.md" "b/docs/GitHub-Actions-\345\244\232\346\236\266\346\236\204\346\236\204\345\273\272\350\257\264\346\230\216.md" index 2010da7e..bd10a856 100644 --- "a/docs/GitHub-Actions-\345\244\232\346\236\266\346\236\204\346\236\204\345\273\272\350\257\264\346\230\216.md" +++ "b/docs/GitHub-Actions-\345\244\232\346\236\266\346\236\204\346\236\204\345\273\272\350\257\264\346\230\216.md" @@ -2,11 +2,28 @@ ## 概述 -项目现已支持通过GitHub Actions自动构建多架构Docker镜像,包括AMD64和ARM64平台。 +项目通过 GitHub Actions 分离常规 CI 与发布构建: + +- `ci.yml`:PR / `main` 分支常规检查,运行服务端测试与构建、前端测试与构建、根级脚本语法检查。 +- `build.yml`:发布构建,支持 Linux / Windows 应用包和 Docker 镜像。 ## 可用的构建选项 -### 1. 主构建工作流 (build.yml) +### 1. 常规 CI 工作流 (ci.yml) + +#### 自动触发: +- PR 打开、重新打开、推送新提交或从 Draft 标记为可审阅 +- 推送到 `main` 分支 + +#### 手动触发: +- 支持 `workflow_dispatch` + +#### 检查内容: +- 服务端:`npm ci`、`npm test`、`npm run build` +- 前端:`npm ci`、`npm test`、`npm run build` +- 根级脚本:`npm ci`、`node --check scripts/package.js`、`node --check scripts/resolve-build-version.js` + +### 2. 发布构建工作流 (build.yml) #### 手动触发选项: - ✅ **构建Linux版本** - 构建Linux应用包 @@ -23,22 +40,15 @@ > 导致同一版本启动两次构建、并发推送同一 Docker 标签互相冲突。 > 详见 [Release重复触发CI修复说明.md](./Release重复触发CI修复说明.md)。 -### 2. 专用多架构工作流 (docker-multiarch.yml) - -提供更详细的多架构构建控制: -- 可选择构建平台组合 -- 支持自定义镜像标签 -- 包含架构验证测试 - ## 使用方法 ### 方法一:GitHub网页操作 1. 进入GitHub仓库页面 2. 点击 **Actions** 标签 -3. 选择 **Build Package** 工作流 +3. 选择 **Build Package** 工作流进行发布构建,或选择 **CI** 手动运行常规检查 4. 点击 **Run workflow** -5. 选择需要的构建选项: +5. 如果运行 **Build Package**,选择需要的构建选项: - ☑️ 构建ARM64 Docker镜像 - ☑️ 构建Docker镜像(多架构) 6. 点击 **Run workflow** 开始构建 @@ -64,8 +74,8 @@ gh workflow run build.yml -f build_docker=true # 触发所有构建 gh workflow run build.yml -f build_linux=true -f build_windows=true -f build_docker=true -f build_docker_arm=true -# 使用专用多架构工作流 -gh workflow run docker-multiarch.yml -f tag=latest -f platforms="linux/amd64,linux/arm64" -f push_to_registry=true +# 手动触发常规 CI +gh workflow run ci.yml ``` ## 构建产物 @@ -138,7 +148,7 @@ docker run --platform linux/amd64 --rm xiaozhu674/gameservermanager:latest uname 1. **开发阶段**:使用ARM64专用构建进行快速测试 2. **发布阶段**:使用多架构构建确保兼容性 3. **标签管理**:为不同版本使用语义化版本标签 -4. **缓存优化**:GitHub Actions会自动缓存构建层以加速后续构建 +4. **缓存优化**:GitHub Actions 会缓存 npm 下载缓存和 Docker 构建层以加速后续构建 ## 更新日志 @@ -146,3 +156,4 @@ docker run --platform linux/amd64 --rm xiaozhu674/gameservermanager:latest uname - **v1.1**: 支持多架构并行构建 - **v1.2**: 添加构建验证和测试步骤 - **v1.3**: 移除 release 事件触发器,修复发布 Release 时重复启动两次构建的问题 +- **v1.4**: 新增 PR/main 常规 CI,并将发布构建依赖安装改为 `npm ci` diff --git "a/docs/PR\350\207\252\345\212\250\350\257\255\346\263\225\346\243\200\346\237\245\350\257\264\346\230\216.md" "b/docs/PR\350\207\252\345\212\250\350\257\255\346\263\225\346\243\200\346\237\245\350\257\264\346\230\216.md" index 32178312..fd7c1746 100644 --- "a/docs/PR\350\207\252\345\212\250\350\257\255\346\263\225\346\243\200\346\237\245\350\257\264\346\230\216.md" +++ "b/docs/PR\350\207\252\345\212\250\350\257\255\346\263\225\346\243\200\346\237\245\350\257\264\346\230\216.md" @@ -1,30 +1,39 @@ -# PR 自动语法检查说明 +# PR 自动检查说明 ## 说明 -`pr-auto-check.yml` 现在只负责对 PR 进行代码语法检查,不再执行以下操作: +`.github/workflows/ci.yml` 负责 PR 和 `main` 分支的常规持续集成检查。该流程只做只读校验,不评论 PR、不自动合并,也不修改目标分支。 -- 自动切换 PR 的目标分支 -- 自动合并 PR -- 自动创建或调整 `feature` 分支 +发布构建仍由 `.github/workflows/build.yml` 负责,避免把 PR 校验和 Release 打包推送混在一起。 -## 检查范围 +## 触发时机 -当前流程会在 PR 打开、重新打开、推送新提交或标记为可审阅时触发,并执行以下内容: +- PR 打开、重新打开、推送新提交或从 Draft 标记为可审阅时触发。 +- 推送到 `main` 分支时触发。 +- 维护者也可以通过 `workflow_dispatch` 手动触发。 -1. 读取 PR 变更文件列表,并在评论中提示当前状态 -2. 检出 PR 对应提交 -3. 判断是否仅包含文档变更 -4. 对 `server` 和 `client` 分别执行 `npx tsc --noEmit` -5. 如果检查失败,在 PR 中回复中文错误信息 +同一 PR 或同一分支的旧运行会被自动取消,减少重复排队。 -## 结果说明 +## 检查范围 -- 如果 PR 仅修改文档或文本文件,会跳过代码语法检查,并在 PR 中给出提示 -- 如果 `server` 或 `client` 的 TypeScript 检查失败,工作流会返回对应的错误输出 -- 如果检查通过,工作流不会再自动合并代码,也不会修改 PR 的目标分支 +当前 CI 包含三个 job: + +1. `Server test and build` + - `cd server && npm ci` + - `cd server && npm test` + - `cd server && npm run build` +2. `Client test and build` + - `cd client && npm ci` + - `cd client && npm test` + - `cd client && npm run build` +3. `Project scripts` + - `npm ci` + - `node --check scripts/package.js` + - `node --check scripts/resolve-build-version.js` + - `node scripts/resolve-build-version.js` ## 维护建议 -- 如果后续希望增加其他静态检查,可以在这里继续补充流程说明 -- 如果需要恢复自动合并,请单独设计新的工作流,不建议和语法检查混在一起 +- CI 中优先使用 `npm ci`,确保 lockfile 和实际安装一致。 +- 新增必须通过的检查前,先确认当前 `upstream/main` 基线能稳定通过。 +- Release 打包、Docker 推送和上传 GitHub Release 资产应继续放在 `build.yml`,并只在标签推送或手动触发时执行。