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
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# 随仓库固定的第三方许可全文按 SHA-256 校验,Windows 检出不能转换换行。
tools/licenses/** -text
22 changes: 14 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
name: CI

# PR 只读 Rust 缓存;main 推送负责写入,供后续 PR 和发行复用。
on:
pull_request:
branches: [main]
push:
branches: [main]
workflow_dispatch:

permissions:
Expand All @@ -16,6 +19,7 @@ jobs:
changes:
name: Changed paths
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
frontend: ${{ steps.filter.outputs.frontend }}
rust: ${{ steps.filter.outputs.rust }}
Expand All @@ -36,16 +40,18 @@ jobs:
needs: changes
if: needs.changes.outputs.rust == 'true'
runs-on: windows-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v7.0.1
- uses: dtolnay/rust-toolchain@stable
- name: 安装 rust-toolchain.toml 固定的工具链
run: rustup toolchain install
- uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
with:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: fmt
run: cargo fmt --all -- --check
- name: clippy
run: cargo clippy --workspace --all-targets -- -D warnings
run: cargo clippy --locked --workspace --all-targets -- -D warnings
- name: test
env:
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUNNER: powershell -NoProfile -ExecutionPolicy Bypass -File ${{ github.workspace }}/tools/run-windows-test.ps1
Expand All @@ -56,11 +62,10 @@ jobs:
needs: changes
if: needs.changes.outputs.frontend == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7.0.1
- uses: pnpm/action-setup@v6.1.0
with:
version: 9
- uses: actions/setup-node@v7.1.0
with:
node-version: 24
Expand All @@ -80,14 +85,13 @@ jobs:
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 15
steps:
- uses: actions/checkout@v7.0.1
- uses: actions/setup-python@v7.1.0
with:
python-version: "3.12"
- uses: pnpm/action-setup@v6.1.0
with:
version: 9
- uses: actions/setup-node@v7.1.0
with:
node-version: 24
Expand All @@ -103,6 +107,7 @@ jobs:
needs: changes
if: needs.changes.outputs.ci == 'true'
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v7.0.1
- name: 路径判断与汇总检查回归
Expand All @@ -115,6 +120,7 @@ jobs:
if: always()
needs: [changes, frontend, rust, native, ci]
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v7.0.1
- name: 确认所需检查均已通过
Expand Down
50 changes: 31 additions & 19 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ on:
tags: ['v*']
workflow_dispatch:

# 只有 publish 需要写权限;构建 job 运行依赖脚本,保持只读。
permissions:
contents: write
contents: read

concurrency:
group: release-${{ github.ref }}
Expand All @@ -18,45 +19,48 @@ jobs:
windows:
name: Windows x64 便携包与更新安装包
runs-on: windows-latest
timeout-minutes: 90
# 私钥只进需要的步骤,不暴露给依赖安装、构建脚本和测试。
env:
MPIDER_UPDATER_PUBLIC_KEY: ${{ vars.MPIDER_UPDATER_PUBLIC_KEY }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
steps:
- uses: actions/checkout@v7.0.1
- uses: pnpm/action-setup@v6.1.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
version: 9
- uses: actions/setup-node@v7.1.0
persist-credentials: false
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 24
cache: pnpm
- uses: actions/setup-python@v7.1.0
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with:
python-version: '3.12'
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
targets: x86_64-pc-windows-msvc
- uses: Swatinem/rust-cache@v2
- name: 安装 rust-toolchain.toml 固定的工具链
run: rustup toolchain install
- uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
with:
key: release-windows-x64
workspaces: . -> target
cache-on-failure: true
# 标签之间不共享缓存;main 上的手动预演写入,标签只读取。
save-if: ${{ github.ref == 'refs/heads/main' }}

- name: 核对当前项目版本、标签与更新签名
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
run: node tools/release-updater.mjs prepare

- name: 安装验证依赖
run: |
$PSNativeCommandUseErrorActionPreference = $true
pnpm install --frozen-lockfile
python -m pip install -r native/requirements.txt
- name: 发行许可预检
- name: 发行依赖与许可预检
shell: pwsh
run: |
$PSNativeCommandUseErrorActionPreference = $true
python tools/collect-third-party-licenses.py --output (Join-Path $env:RUNNER_TEMP 'mpider-license-preflight') --cache dist-windows/.downloads
python tools/package-windows-release.py --prefetch --output dist-windows
- name: 类型、格式与 Rust 静态检查
run: |
$PSNativeCommandUseErrorActionPreference = $true
Expand Down Expand Up @@ -110,6 +114,9 @@ jobs:

- name: 构建带运行环境的 NSIS 与 MSI 安装包
shell: pwsh
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: |
$PSNativeCommandUseErrorActionPreference = $true
# 与便携包共用 Windows x64 runner 的原生 target/release,复用已编译依赖。
Expand All @@ -128,7 +135,7 @@ jobs:
$sums | Set-Content release-assets/SHA256SUMS -Encoding ascii

- name: 留存完整发行包与验收记录
uses: actions/upload-artifact@v7.0.2
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: mpider-windows-x64
if-no-files-found: error
Expand All @@ -137,7 +144,7 @@ jobs:

- name: 失败时留存已生成文件以便排查
if: failure()
uses: actions/upload-artifact@v7.0.2
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: mpider-windows-partial
if-no-files-found: warn
Expand All @@ -154,12 +161,17 @@ jobs:
if: github.ref_type == 'tag'
needs: windows
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
steps:
- uses: actions/checkout@v7.0.1
- uses: actions/setup-node@v7.1.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 24
- uses: actions/download-artifact@v8.0.2
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: mpider-windows-x64
path: release-assets
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

## 开发环境

- Rust stable、Node.js 24+、pnpm 9+;只使用 pnpm,保留 `pnpm-lock.yaml`。
- Rust(版本由 `rust-toolchain.toml` 固定,rustup 自动安装)、Node.js 24+、pnpm(版本由 `package.json` 的 `packageManager` 固定);只使用 pnpm,保留 `pnpm-lock.yaml`。
- 原生桥离线测试使用 Python 3.12,依赖固定在 `native/requirements.txt`。
- Windows 桌面构建还需要 MSVC C++ Build Tools、Windows SDK 与 WebView2,见[独立运行](docs/windows-standalone.md)。
- 当前原生采集只验证微信 4.1.15.56 x64。开发构建或跨平台测试通过不代表该平台可采集。
Expand Down Expand Up @@ -35,7 +35,7 @@ pnpm test

按改动范围补充有意义的回归。说明实际运行的平台、命令与结果,区分离线测试、桌面启动、真实请求和长期观察;没有验证的部分明确写出。

日常 CI 仅由面向 main 的 PR 或手动操作触发,合并后的普通 main 推送不重复运行。PR 按路径选择任务:
日常 CI 由面向 main 的 PR、main 推送或手动操作触发。main 推送按相同路径规则运行并写入 Rust 缓存,PR 只读取缓存。PR 按路径选择任务:

| 改动范围 | 执行的检查 |
| --- | --- |
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ Tauri / React 提供界面,Rust 管理 SQLite、调度、请求策略和本地

## 开发与贡献

开发需要 Node.js **24+**、**pnpm**、Rust stable;Windows 构建还需 Visual Studio C++ Build Tools 与 Windows SDK。改动在独立 Git worktree 中进行:
开发需要 Node.js **24+**、**pnpm**、Rust(版本见 `rust-toolchain.toml`);Windows 构建还需 Visual Studio C++ Build Tools 与 Windows SDK。改动在独立 Git worktree 中进行:

```sh
pnpm install --frozen-lockfile
Expand All @@ -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 在 PR 中按改动路径选择前端、Windows Rust、原生桥或 CI 配置检查,合并到 main 后不重复执行;纯公开资料只运行轻量路径判断与汇总,编入界面的更新记录、FAQ、实验室与请求策略文档仅触发前端检查。共享契约改动联动验证,手动触发可运行全部检查,详细规则见[贡献指南](CONTRIBUTING.md)。客户端发行仅面向 Windows x64,标签必须与项目版本一致,完成构建、离线测试、便携包解压自检与签名清单生成后创建 Release 草稿。
Windows 测试 runner、Python 离线测试和完整客户端构建见[Windows 构建说明](docs/windows-standalone.md)。日常 CI 在 PR 中按改动路径选择前端、Windows Rust、原生桥或 CI 配置检查,合并到 main 后按相同规则运行并刷新 Rust 缓存;纯公开资料只运行轻量路径判断与汇总,编入界面的更新记录、FAQ、实验室与请求策略文档仅触发前端检查。共享契约改动联动验证,手动触发可运行全部检查,详细规则见[贡献指南](CONTRIBUTING.md)。客户端发行仅面向 Windows x64,标签必须与项目版本一致,完成构建、离线测试、便携包解压自检与签名清单生成后创建 Release 草稿。

[贡献指南](CONTRIBUTING.md) · [实现原理](docs/how-it-works.md) · [更新记录](CHANGELOG.md) · [第三方声明](THIRD-PARTY-NOTICES.md)

Expand Down
2 changes: 1 addition & 1 deletion THIRD-PARTY-NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ SOFTWARE.
| cffi | 2.0.0 | `runtime/session-deps/cffi-2.0.0.dist-info/licenses/`(LICENSE、AUTHORS) | [PyPI](https://pypi.org/project/cffi/2.0.0/) |
| pycparser | 2.23 | `runtime/session-deps/pycparser-2.23.dist-info/LICENSE` | [PyPI](https://pypi.org/project/pycparser/2.23/) |

应用未修改这些运行环境组件。Frida 的 wxWindows 例外文本引用的 LGPL 全文从 [GNU 官方许可档案](https://www.gnu.org/licenses/old-licenses/lgpl-2.0.txt) 获取并固定摘要。微信与 Microsoft WebView2 不在便携包中再分发。
应用未修改这些运行环境组件。Frida 的 wxWindows 例外文本引用的 LGPL 全文取自 [GNU 官方许可档案](https://www.gnu.org/licenses/old-licenses/lgpl-2.0.txt),原样保存在 `tools/licenses/Frida-COPYING.LIB` 并固定摘要,组装时不再联网获取。微信与 Microsoft WebView2 不在便携包中再分发。

## Rust、界面与字体依赖

Expand Down
2 changes: 1 addition & 1 deletion docs/app-updates.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ pnpm tauri signer generate -w ~/.tauri/mpider-updater.key

1. 核对发布提交与标签;本次项目版本为 `1.0.2`,标签应为 `v1.0.2`。保留历史 Release 与标签,不覆盖仍在服务用户的版本。
2. 按项目主线/开源发布流程准备发布提交。需要升级版本时,同时修改三个版本文件及锁文件;本功能不会自行修改项目版本。
3. 将匹配当前项目版本的 `v*` 标签推送到 GitHub。工作流先核对版本与签名配置,再为 Windows x64 构建;缺少签名配置时标签发布立即失败。
3. 打标签前在 main 手动运行 Release(`gh workflow run release.yml --ref main`)预演完整构建。预演只留存 artifact,不创建 Release,并写入后续标签可读取的 Rust 缓存。通过后将匹配当前项目版本的 `v*` 标签推送到 GitHub。工作流先核对版本与签名配置,再为 Windows x64 构建;缺少签名配置时标签发布立即失败。
4. Windows 构建成功后整理附件并生成 `latest.json`,创建 Release **预发布草稿**;保留完整便携 ZIP、自检与摘要,同时提供携带相同 Python 运行环境和完整许可证的 NSIS/MSI。Windows 更新默认使用 NSIS `-setup.exe`;MSI 仍提供手动安装。工作流不构建或上传 macOS 产物。
5. 检查产物、签名、Windows 清单并补充发布说明,再公开草稿。验收通过后发布为正式版本(取消预发布标记),客户端才会通过 latest 地址检测到该版本;保持预发布标记时不会被稳定更新通道选中。缺包、缺签名或缺平台时不生成可发布清单。

Expand Down
6 changes: 3 additions & 3 deletions docs/windows-standalone.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ v1.0.2 包含独立监测、按公众号任务、Markdown/HTML 双格式导出

## 从源码构建

构建环境:Windows x64、Visual Studio 2022 Build Tools(C++ 桌面开发及 Windows SDK)、Rust stable MSVC、Node.js 24+、pnpm 9+;组装完整发行包还需构建机 Python 3.9+。包管理器只使用 pnpm。
构建环境:Windows x64、Visual Studio 2022 Build Tools(C++ 桌面开发及 Windows SDK)、Rust MSVC(版本见 `rust-toolchain.toml`)、Node.js 24+、pnpm(版本见 `package.json`);组装完整发行包还需构建机 Python 3.9+。包管理器只使用 pnpm。

在独立 Git worktree 中执行:

Expand All @@ -50,7 +50,7 @@ powershell -NoProfile -ExecutionPolicy Bypass -File tools\build-windows.ps1 -Rel

## 发行包与验收边界

`tools/windows-runtime.lock.json` 固定 Python 和 Windows wheels 的下载地址、版本与 SHA-256;构建时校验后原样保留 Python `LICENSE.txt`、wheel 的 `dist-info` 完整许可证,并补充 Frida wxWindows 许可引用的 GNU LGPL v2 全文。下载缓存只在忽略目录 `dist-windows/.downloads`。
`tools/windows-runtime.lock.json` 固定 Python 和 Windows wheels 的下载地址、版本与 SHA-256;构建时校验后原样保留 Python `LICENSE.txt`、wheel 的 `dist-info` 完整许可证,并补充 Frida wxWindows 许可引用的 GNU LGPL v2 全文(随仓库保存在 `tools/licenses/`,同样校验 SHA-256)。下载缓存只在忽略目录 `dist-windows/.downloads`。

构建机还会从 Cargo/pnpm 依赖复制完整许可与字体 OFL,补回上游遗漏的许可文件;MPL 组件和部分缺声明组件的原版源码一并附带。详见包内 `THIRD-PARTY-NOTICES.md` 与 `licenses/dependencies/index.json`,该过程只在构建阶段依赖 Cargo/pnpm。

Expand All @@ -62,7 +62,7 @@ python tools/verify-windows-release.py --directory dist-windows\MPider-v1.0.2-wi

该检查只核对文件完整性、x64 PE、实际 Python/依赖版本以及虚构材料 AES-GCM 和当前用户 DPAPI 往返,不启动微信、不读取真实会话、不发送采集请求。**离线检查成功不代表真实采集或长期稳定性通过。**页面交互验收需在交互桌面另行进行,真实数据不得进入公开包。

`.github/workflows/release.yml` 在 Windows 执行类型、Rust、Python、Node 离线检查后构建,并重新解压 ZIP 验证。手动触发只留存 Actions artifact;`v*` 标签必须与应用版本一致,只创建预发布草稿,核验后再公开。当前流程不发布 macOS 安装包,避免将旧代理工作流支持误认为新原生工作台支持。
`.github/workflows/release.yml` 先下载并校验全部锁定依赖,再在 Windows 执行类型、Rust、Python、Node 离线检查后构建,并重新解压 ZIP 验证。手动触发只留存 Actions artifact;`v*` 标签必须与应用版本一致,只创建预发布草稿,核验后再公开。当前流程不发布 macOS 安装包,避免将旧代理工作流支持误认为新原生工作台支持。

## 数据与异常

Expand Down
4 changes: 3 additions & 1 deletion native/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# 原生桥与会话协议的已验证依赖;不安装 frida-tools。
# 原生桥与会话协议的已验证依赖,与 tools/windows-runtime.lock.json 保持一致;不安装 frida-tools。
frida==17.23.3
cryptography==50.0.2
cffi==2.0.0
pycparser==2.23
19 changes: 19 additions & 0 deletions native/test_runtime_lock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import hashlib
import json
from pathlib import Path
import unittest

ROOT = Path(__file__).resolve().parent.parent


class RuntimeLockTests(unittest.TestCase):
def test_vendored_licenses_match_locked_digests(self):
# 在 Windows 检出上提前发现换行被转换,避免等到长构建后的组装阶段才失败。
lock = json.loads((ROOT / "tools/windows-runtime.lock.json").read_text(encoding="utf-8"))
for item in lock["licenses"]:
data = (ROOT / item["path"]).read_bytes()
self.assertEqual(hashlib.sha256(data).hexdigest(), item["sha256"], item["filename"])


if __name__ == "__main__":
unittest.main()
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"private": true,
"version": "1.0.2",
"type": "module",
"packageManager": "pnpm@10.29.2",
"description": "mpider 桌面外壳的前端 + Tauri CLI(前端源码在 frontend/,Tauri 壳在 src-tauri/)",
"engines": {
"node": ">=24"
Expand Down
6 changes: 6 additions & 0 deletions rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# CI、发行与本地开发共用同一版本;升级时改这里并单独验证 Clippy。
[toolchain]
channel = "1.99.1"
profile = "minimal"
components = ["rustfmt", "clippy"]
targets = ["x86_64-pc-windows-msvc"]
Loading
Loading