diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 30b4747..da2b5df 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,8 +3,27 @@ name: CI on: push: branches: [main] + # 在触发前过滤不影响当前构建的文件;与 tools/ci-changes.py 保持一致。 + paths: + - '**' + - '!*.md' + - '!LICENSE' + - '!.gitignore' + - '!docs/**' + - '!.github/ISSUE_TEMPLATE/**' + - '!.github/release-notes.md' + - '!.github/PULL_REQUEST_TEMPLATE.md' + - '!postman/mocks/**' + - '!postman/simulations/**' + - '!src-tauri/icons-v2/android/**' + - '!src-tauri/icons-v2/ios/**' + - 'CHANGELOG.md' + - 'docs/faq.md' + - 'docs/laboratory.md' + - 'docs/request-policy.md' pull_request: branches: [main] + # 保留必需检查结果,避免纯文档 PR 因缺少状态而无法合并。 permissions: contents: read diff --git a/README.md b/README.md index 531a545..5d1607c 100644 --- a/README.md +++ b/README.md @@ -187,7 +187,7 @@ cargo test --locked -p mpider-core -p article-md -p mpider-tauri pnpm tauri dev ``` -Windows 测试 runner、Python 离线测试和完整客户端构建见[Windows 构建说明](docs/windows-standalone.md)。CI 按改动路径检查前端与 Windows Rust,纯文档或截图修改跳过构建;编入界面的 FAQ、实验室与请求策略文档仍触发检查。客户端发行仅面向 Windows x64,标签必须与项目版本一致,完成构建、离线测试、便携包解压自检与签名清单生成后创建 Release 草稿。 +Windows 测试 runner、Python 离线测试和完整客户端构建见[Windows 构建说明](docs/windows-standalone.md)。CI 按改动路径检查前端与 Windows Rust:main 上仅修改公开资料、协作模板、`.gitignore`、本地 Postman 模拟或未使用的 Android/iOS 图标时不触发 CI,PR 保留路径判断以满足必需检查、跳过构建;编入界面的更新记录、FAQ、实验室与请求策略文档仍触发检查。客户端发行仅面向 Windows x64,标签必须与项目版本一致,完成构建、离线测试、便携包解压自检与签名清单生成后创建 Release 草稿。 [贡献指南](CONTRIBUTING.md) · [实现原理](docs/how-it-works.md) · [更新记录](CHANGELOG.md) · [第三方声明](THIRD-PARTY-NOTICES.md) diff --git a/tools/ci-changes.py b/tools/ci-changes.py index 3ae40eb..bc0dc41 100644 --- a/tools/ci-changes.py +++ b/tools/ci-changes.py @@ -1,4 +1,4 @@ -"""仅让影响应用或构建的改动运行完整 CI;纯公开资料改动跳过构建。""" +"""仅让影响应用或构建的改动运行完整 CI;与 workflow 的 push 路径过滤同步。""" import json import os @@ -6,22 +6,37 @@ import subprocess EMBEDDED_GUIDES = { + "CHANGELOG.md", "docs/faq.md", "docs/laboratory.md", "docs/request-policy.md", } +NON_BUILD_FILES = { + ".gitignore", + ".github/release-notes.md", + ".github/PULL_REQUEST_TEMPLATE.md", +} + +NON_BUILD_PREFIXES = ( + "docs/", + ".github/ISSUE_TEMPLATE/", + # 当前 CI 不启动这些本地模拟,也不构建移动端。 + "postman/mocks/", + "postman/simulations/", + "src-tauri/icons-v2/android/", + "src-tauri/icons-v2/ios/", +) + def requires_build(paths): for path in paths: if path in EMBEDDED_GUIDES: return True - if path.startswith(("docs/", ".github/ISSUE_TEMPLATE/")): + if path in NON_BUILD_FILES or path.startswith(NON_BUILD_PREFIXES): continue if "/" not in path and (path.endswith(".md") or path == "LICENSE"): continue - if path in {".github/release-notes.md", ".github/pull_request_template.md"}: - continue return True return False diff --git a/tools/test_ci_changes.py b/tools/test_ci_changes.py index 1d7c77b..5bbd311 100644 --- a/tools/test_ci_changes.py +++ b/tools/test_ci_changes.py @@ -1,5 +1,6 @@ import importlib.util from pathlib import Path +import re import unittest spec = importlib.util.spec_from_file_location("ci_changes", Path(__file__).with_name("ci-changes.py")) @@ -19,10 +20,40 @@ def test_embedded_guides_are_application_inputs(self): with self.subTest(path=path): self.assertTrue(ci_changes.requires_build([path])) + def test_markdown_imported_by_frontend_requires_build(self): + root = Path(__file__).resolve().parent.parent + imports = [] + for source in (root / "frontend/src").rglob("*.tsx"): + for relative in re.findall(r'from\s+["\x27]([^"\x27]+\.md)\?raw["\x27]', source.read_text()): + path = (source.parent / relative).resolve().relative_to(root).as_posix() + imports.append(path) + with self.subTest(path=path): + self.assertTrue(ci_changes.requires_build([path])) + self.assertIn("CHANGELOG.md", imports) + + def test_repository_metadata_local_simulations_and_mobile_icons_skip_build(self): + paths = [ + ".gitignore", ".github/PULL_REQUEST_TEMPLATE.md", + ".github/ISSUE_TEMPLATE/bug_report.yml", + "postman/mocks/updater-release/config.yaml", + "postman/mocks/updater-release/default.js", + "postman/simulations/updater-download.sim.yaml", + "src-tauri/icons-v2/android/mipmap-hdpi/ic_launcher.png", + "src-tauri/icons-v2/ios/AppIcon-20x20@1x.png", + ] + for path in paths: + with self.subTest(path=path): + self.assertFalse(ci_changes.requires_build([path])) + self.assertTrue(ci_changes.requires_build([path, "frontend/src/App.tsx"])) + self.assertFalse(ci_changes.requires_build(paths)) + def test_code_build_config_and_mixed_changes_run(self): for path in ["frontend/src/App.tsx", "src-tauri/src/lib.rs", "native/bridge.py", "Cargo.lock", "pnpm-lock.yaml", "tools/package-windows-release.py", - ".github/workflows/ci.yml", ".github/workflows/release.yml"]: + ".github/workflows/ci.yml", ".github/workflows/release.yml", + ".gitattributes", "config/advertisements.json", + "src-tauri/icons-v2/icon.ico", "src-tauri/fixtures/updater/package.bin", + "new-build-config.json"]: with self.subTest(path=path): self.assertTrue(ci_changes.requires_build(["README.md", path]))