mirror of
https://github.com/LHY0125/PathEditor.git
synced 2026-06-30 18:45:55 +08:00
Compare commits
6 Commits
v4.1.1
..
8ff02fd88b
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ff02fd88b | |||
| 39a95cc50d | |||
| 44fdc2eec6 | |||
| 6dc32dca93 | |||
| a2b66d087f | |||
| 8c1655d25c |
@@ -0,0 +1,52 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '**'
|
||||||
|
tags-ignore:
|
||||||
|
- '**'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
frontend:
|
||||||
|
name: 前端检查 (TypeScript + Lint + Test)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- run: npm ci
|
||||||
|
|
||||||
|
- name: TypeScript 类型检查
|
||||||
|
run: npx tsc --noEmit
|
||||||
|
|
||||||
|
- name: ESLint
|
||||||
|
run: npm run lint
|
||||||
|
|
||||||
|
- name: Vitest 测试
|
||||||
|
run: npm test
|
||||||
|
|
||||||
|
rust:
|
||||||
|
name: Rust 检查 (Check + Clippy + Test)
|
||||||
|
runs-on: windows-latest
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: src-tauri
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Cargo Check
|
||||||
|
run: cargo check
|
||||||
|
|
||||||
|
- name: Cargo Clippy
|
||||||
|
run: cargo clippy -- -D warnings
|
||||||
|
|
||||||
|
- name: Cargo Test
|
||||||
|
run: cargo test
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-release:
|
||||||
|
name: 构建 NSIS 安装包并发布
|
||||||
|
runs-on: windows-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- run: npm ci
|
||||||
|
|
||||||
|
- name: Tauri Build
|
||||||
|
run: npx tauri build
|
||||||
|
|
||||||
|
- name: 创建 Release 并上传安装包
|
||||||
|
run: |
|
||||||
|
$installer = Get-ChildItem -Path "src-tauri\target\release\bundle\nsis\*.exe" | Select-Object -First 1
|
||||||
|
gh release create $env:GITHUB_REF_NAME "$installer" --title "$env:GITHUB_REF_NAME" --generate-notes
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
# v4.2 CI/CD 流水线 — 实现计划
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** 为 PathEditor 添加 GitHub Actions CI/CD:push 自动检查 + tag 自动构建发布
|
||||||
|
|
||||||
|
**Architecture:** 两个 workflow 文件。前端 job 跑 ubuntu(快),Rust job 跑 windows(winreg 依赖)。tag 推送触发 NSIS 构建上传。
|
||||||
|
|
||||||
|
**Tech Stack:** GitHub Actions, Windows runner, MinGW (MSYS2), Tauri CLI
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: 创建 CI workflow
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `.github/workflows/ci.yml`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 创建目录并写入 ci.yml**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p .github/workflows
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# .github/workflows/ci.yml
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '**'
|
||||||
|
tags-ignore:
|
||||||
|
- '**'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
frontend:
|
||||||
|
name: 前端检查 (TypeScript + Lint + Test)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- run: npm ci
|
||||||
|
|
||||||
|
- name: TypeScript 类型检查
|
||||||
|
run: npx tsc --noEmit
|
||||||
|
|
||||||
|
- name: ESLint
|
||||||
|
run: npm run lint
|
||||||
|
|
||||||
|
- name: Vitest 测试
|
||||||
|
run: npm test
|
||||||
|
|
||||||
|
rust:
|
||||||
|
name: Rust 检查 (Check + Clippy + Test)
|
||||||
|
runs-on: windows-latest
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: src-tauri
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: 安装 GNU 工具链
|
||||||
|
run: |
|
||||||
|
rustup toolchain install stable-x86_64-pc-windows-gnu
|
||||||
|
rustup override set stable-x86_64-pc-windows-gnu
|
||||||
|
|
||||||
|
- name: 添加 MinGW 到 PATH
|
||||||
|
run: echo "C:\msys64\mingw64\bin" | Out-File -FilePath $env:GITHUB_PATH -Append
|
||||||
|
|
||||||
|
- name: Cargo Check
|
||||||
|
run: cargo check
|
||||||
|
|
||||||
|
- name: Cargo Clippy
|
||||||
|
run: cargo clippy -- -D warnings
|
||||||
|
|
||||||
|
- name: Cargo Test
|
||||||
|
run: cargo test
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 本地验证 YAML 语法**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 可以用 Python 验证 YAML 语法(可选)
|
||||||
|
python -c "import yaml; yaml.safe_load(open('.github/workflows/ci.yml'))" 2>/dev/null || echo "跳过(无需本地验证,push 后 GitHub 自行检查)"
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add .github/workflows/ci.yml
|
||||||
|
git commit -m "ci: 添加 CI workflow — push 自动检查 TypeScript + Rust
|
||||||
|
|
||||||
|
前端: tsc --noEmit + ESLint + Vitest (ubuntu)
|
||||||
|
Rust: cargo check + clippy + test (windows + GNU toolchain)
|
||||||
|
|
||||||
|
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: 创建 Release workflow
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `.github/workflows/release.yml`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 写入 release.yml**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# .github/workflows/release.yml
|
||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-release:
|
||||||
|
name: 构建 NSIS 安装包并发布
|
||||||
|
runs-on: windows-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- run: npm ci
|
||||||
|
|
||||||
|
- name: 安装 GNU 工具链
|
||||||
|
run: |
|
||||||
|
rustup toolchain install stable-x86_64-pc-windows-gnu
|
||||||
|
rustup override set stable-x86_64-pc-windows-gnu
|
||||||
|
|
||||||
|
- name: 添加 MinGW 到 PATH
|
||||||
|
run: echo "C:\msys64\mingw64\bin" | Out-File -FilePath $env:GITHUB_PATH -Append
|
||||||
|
|
||||||
|
- name: Tauri Build
|
||||||
|
run: npx tauri build
|
||||||
|
env:
|
||||||
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||||
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||||
|
|
||||||
|
- name: 上传安装包到 Release
|
||||||
|
run: |
|
||||||
|
$installer = Get-ChildItem -Path "src-tauri\target\release\bundle\nsis\*.exe" | Select-Object -First 1
|
||||||
|
gh release upload $env:GITHUB_REF_NAME "$installer" --clobber
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add .github/workflows/release.yml
|
||||||
|
git commit -m "ci: 添加 Release workflow — tag 推送自动构建 NSIS 安装包并发布
|
||||||
|
|
||||||
|
tag v* 触发 Tauri build,生成 NSIS 安装包后上传到 GitHub Release
|
||||||
|
|
||||||
|
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: 推送并验证
|
||||||
|
|
||||||
|
- [ ] **Step 1: 推送到 GitHub**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git push origin v4.2
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 查看 GitHub Actions**
|
||||||
|
|
||||||
|
打开 `https://github.com/LHY0125/PathEditor/actions`,确认 CI workflow 已触发并等待结果。
|
||||||
|
|
||||||
|
两个 job 应该都绿:
|
||||||
|
- `前端检查` — tsc + lint + vitest 通过
|
||||||
|
- `Rust 检查` — check + clippy + test 通过
|
||||||
|
|
||||||
|
- [ ] **Step 3: 验证 Release workflow(可选)**
|
||||||
|
|
||||||
|
推送一个测试 tag:
|
||||||
|
```bash
|
||||||
|
git tag -a v4.2.0-beta -m "测试 CI release"
|
||||||
|
git push origin v4.2.0-beta
|
||||||
|
```
|
||||||
|
|
||||||
|
确认 `https://github.com/LHY0125/PathEditor/releases` 出现构建产物。测试完成后删除 tag:
|
||||||
|
```bash
|
||||||
|
git push origin --delete v4.2.0-beta
|
||||||
|
git tag -d v4.2.0-beta
|
||||||
|
gh release delete v4.2.0-beta --yes
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **TAURI_SIGNING_PRIVATE_KEY**: 如果项目签名配置了 Tauri updater 密钥,需要在 GitHub Settings → Secrets 中添加这两个 secret。如果当前没有配置 updater 签名,`tauri build` 会跳过签名步骤正常构建,但 CI 那一步会报找不到环境变量的警告。可以先不加这两个 secret,构建如果失败再加。
|
||||||
|
|
||||||
|
2. **首次运行**: GitHub Actions 在第一次 push `.github/workflows/` 后才会出现,之前需要等待。
|
||||||
|
|
||||||
|
3. **MinGW 路径**: `C:\msys64\mingw64\bin` 是 `windows-latest` runner 的固定路径。
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# v4.2 CI/CD 流水线 — 设计文档
|
||||||
|
|
||||||
|
**日期**: 2026-05-27
|
||||||
|
**分支**: v4.2
|
||||||
|
**状态**: 已确认
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
为 PathEditor 添加 GitHub Actions CI/CD,实现 push 自动检查 + tag 自动构建发布。
|
||||||
|
|
||||||
|
## 触发策略
|
||||||
|
|
||||||
|
| 触发条件 | 做什么 |
|
||||||
|
|----------|--------|
|
||||||
|
| push 任意分支(不含 tag) | 前端类型检查 + lint + 测试,Rust check + clippy + test |
|
||||||
|
| 推送 tag `v*` | Tauri 构建 NSIS 安装包,上传到 GitHub Release |
|
||||||
|
|
||||||
|
## Workflow 1: CI
|
||||||
|
|
||||||
|
**文件**: `.github/workflows/ci.yml`
|
||||||
|
|
||||||
|
两个并行 job:
|
||||||
|
|
||||||
|
**frontend (ubuntu-latest)**:
|
||||||
|
- 用 ubuntu 而非 windows,更快且不依赖系统 API
|
||||||
|
- 步骤:checkout → setup-node → npm ci → tsc --noEmit → npm run lint → npm test
|
||||||
|
|
||||||
|
**rust (windows-latest)**:
|
||||||
|
- 必须用 windows(`winreg` crate 依赖 Windows API)
|
||||||
|
- 安装 GNU 工具链并 override,添加 MinGW bin 到 PATH
|
||||||
|
- 步骤:checkout → rustup toolchain install → override → PATH → cargo check → cargo clippy -- -D warnings → cargo test
|
||||||
|
|
||||||
|
## Workflow 2: Release
|
||||||
|
|
||||||
|
**文件**: `.github/workflows/release.yml`
|
||||||
|
|
||||||
|
单一 job `build-and-release` (windows-latest):
|
||||||
|
- checkout → setup-node → npm ci → rustup + MinGW → npx tauri build → gh release upload
|
||||||
|
|
||||||
|
构建产物:NSIS 安装包(`.exe`),上传到对应 tag 的 GitHub Release。
|
||||||
|
|
||||||
|
## MinGW 处理
|
||||||
|
|
||||||
|
- GitHub Actions `windows-latest` 自带 MSYS2,MinGW 位于 `C:\msys64\mingw64\bin`
|
||||||
|
- `cargo test` 运行时需要 `libmcfgthread-2.dll`,将此路径加入 `PATH` 即可
|
||||||
|
|
||||||
|
## 范围限制
|
||||||
|
|
||||||
|
- 不做跨平台构建(项目仅面向 Windows)
|
||||||
|
- 不做覆盖率门槛
|
||||||
|
- Release 不重复跑 CI(tag 推送说明已通过 push 检查)
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import js from '@eslint/js';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
import reactHooks from 'eslint-plugin-react-hooks';
|
||||||
|
import reactRefresh from 'eslint-plugin-react-refresh';
|
||||||
|
import globals from 'globals';
|
||||||
|
|
||||||
|
export default tseslint.config(
|
||||||
|
{ ignores: ['dist', 'src-tauri'] },
|
||||||
|
{
|
||||||
|
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||||
|
files: ['**/*.{ts,tsx}'],
|
||||||
|
languageOptions: {
|
||||||
|
globals: globals.browser,
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
'react-hooks': reactHooks,
|
||||||
|
'react-refresh': reactRefresh,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...reactHooks.configs.recommended.rules,
|
||||||
|
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -14,6 +14,8 @@ export function PathEditDialog({ open, title, initialValue, onConfirm, onCancel
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [value, setValue] = useState(initialValue);
|
const [value, setValue] = useState(initialValue);
|
||||||
|
|
||||||
|
// 对话框打开时重置输入值 — 此模式不会导致级联渲染
|
||||||
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||||
useEffect(() => { if (open) setValue(initialValue); }, [open, initialValue]);
|
useEffect(() => { if (open) setValue(initialValue); }, [open, initialValue]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ interface KeyboardActions {
|
|||||||
export function useKeyboard(actions: KeyboardActions) {
|
export function useKeyboard(actions: KeyboardActions) {
|
||||||
const isAdmin = useAppStore((s) => s.isAdmin);
|
const isAdmin = useAppStore((s) => s.isAdmin);
|
||||||
const actionsRef = useRef(actions);
|
const actionsRef = useRef(actions);
|
||||||
|
// eslint-disable-next-line react-hooks/refs -- React 官方推荐的 ref 同步模式,避免每次渲染重复注册事件监听器
|
||||||
actionsRef.current = actions;
|
actionsRef.current = actions;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -254,6 +254,7 @@ describe('savePaths', () => {
|
|||||||
it('isSaving 守卫:并发第二次调用直接返回', async () => {
|
it('isSaving 守卫:并发第二次调用直接返回', async () => {
|
||||||
let resolveAll: (v: unknown) => void;
|
let resolveAll: (v: unknown) => void;
|
||||||
const pending = new Promise((r) => { resolveAll = r; });
|
const pending = new Promise((r) => { resolveAll = r; });
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
mockedInvoke.mockReturnValue(pending as any);
|
mockedInvoke.mockReturnValue(pending as any);
|
||||||
|
|
||||||
// 第一次调用(不等它完成,停在 Promise.allSettled)
|
// 第一次调用(不等它完成,停在 Promise.allSettled)
|
||||||
|
|||||||
Reference in New Issue
Block a user