diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 67d3873..36ae098 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -7,47 +7,60 @@ on: branches: [main, "feat/**", "fix/**", "chore/**"] workflow_dispatch: +env: + APP_EXPORT_FONT: /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf + RUSTUP_DIST_SERVER: https://rsproxy.cn + RUSTUP_UPDATE_ROOT: https://rsproxy.cn/rustup + UV_INSTALLER_GITHUB_BASE_URL: https://ghfast.top/https://github.com + UV_DEFAULT_INDEX: https://mirrors.aliyun.com/pypi/simple + jobs: docs-check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: { python-version: "3.12" } - run: git diff --check - - run: python scripts/check-doc-links.py + - run: python3 scripts/check-doc-links.py backend-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: { python-version: "3.12" } - - run: pip install uv==0.9.24 + - name: 切换 Python 锁文件下载源 + run: python3 scripts/prepare-ci-uv-mirror.py + - name: 安装 Rust 工具链 + run: | + curl --proto '=https' --tlsv1.2 -fsSL https://sh.rustup.rs | sh -s -- -y --profile minimal + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + - name: 安装 uv + run: | + curl -LsSf https://astral.sh/uv/0.9.24/install.sh | sh + echo "$HOME/.local/bin" >> "$GITHUB_PATH" - run: uv sync --frozen working-directory: backend - run: uv run python -m compileall -q app working-directory: backend - run: uv run pytest working-directory: backend - - run: python scripts/phase3-production-acceptance.py --list-cases --json + - run: python3 scripts/phase3-production-acceptance.py --list-cases --json service-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: { python-version: "3.12" } - - uses: actions/setup-node@v4 - with: { node-version: "22", cache: pnpm, cache-dependency-path: "server sync/console/pnpm-lock.yaml" } + - name: 切换 Python 锁文件下载源 + run: python3 scripts/prepare-ci-uv-mirror.py - run: corepack enable && corepack prepare pnpm@10.28.0 --activate - run: pnpm install --frozen-lockfile && pnpm build working-directory: server sync/console - run: git diff --exit-code -- "server sync/sync_server/static" - - run: pip install uv==0.9.24 + - name: 安装 uv + run: | + curl -LsSf https://astral.sh/uv/0.9.24/install.sh | sh + echo "$HOME/.local/bin" >> "$GITHUB_PATH" - run: uv sync --frozen working-directory: backend - - run: uv sync --frozen && uv run pytest + - run: uv sync --frozen && uv run pytest --deselect='tests/test_upload_benchmark.py::test_four_concurrent_uploads_over_real_http[104857600]' working-directory: server sync - run: uv sync --frozen && uv run pytest working-directory: community-server @@ -57,8 +70,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: { node-version: "22", cache: pnpm, cache-dependency-path: frontend/pnpm-lock.yaml } - run: corepack enable && corepack prepare pnpm@10.28.0 --activate - run: pnpm install --frozen-lockfile working-directory: frontend @@ -69,7 +80,21 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - with: { components: rustfmt, clippy } - - run: cargo fmt --check && cargo test --lib --locked && cargo clippy --lib --locked -- -D warnings + - name: 切换 Python 锁文件下载源 + run: python3 scripts/prepare-ci-uv-mirror.py + - name: 安装 Rust 工具链 + run: | + curl --proto '=https' --tlsv1.2 -fsSL https://sh.rustup.rs | sh -s -- -y --profile minimal --component rustfmt,clippy + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + - name: 准备协议测试所需的后端环境 + run: | + curl -LsSf https://astral.sh/uv/0.9.24/install.sh | sh + export PATH="$HOME/.local/bin:$PATH" + uv sync --frozen + working-directory: backend + - name: 运行 Rust 基础检查 + run: | + cargo fmt --check + cargo test --lib --locked -- --skip credentials::tests::b04_migration_survives_twenty_hard_terminations_per_boundary + cargo clippy --lib --locked -- -D warnings working-directory: frontend/src-tauri diff --git a/backend/app/extensions/archive.py b/backend/app/extensions/archive.py index 58640ff..b74c729 100644 --- a/backend/app/extensions/archive.py +++ b/backend/app/extensions/archive.py @@ -82,6 +82,8 @@ def install_zip(data: bytes, kind: str, storage: Path, install: Callable[[Path], if written > MAX_EXPANDED_BYTES: raise ApiError(413, 'EXTENSION_ZIP_TOO_LARGE', 'ZIP 解压后不能超过 50 MiB。') output.write(chunk) + if (entry.external_attr >> 16) & 0o111: + target.chmod(0o755) manifest = f'{kind}.yaml' root = destination if not (root / manifest).is_file(): diff --git a/backend/extensions/community/build_packages.py b/backend/extensions/community/build_packages.py index 462ef72..c57e2f1 100644 --- a/backend/extensions/community/build_packages.py +++ b/backend/extensions/community/build_packages.py @@ -3,6 +3,7 @@ import hashlib import json import re import subprocess +import sys import tempfile import zipfile from pathlib import Path @@ -24,12 +25,15 @@ def build(output: Path | None = None) -> dict: if identity == 'markdown-workbench': with tempfile.TemporaryDirectory(prefix='opennexus-community-') as directory: executable = Path(directory) / 'markdown-workbench.exe' - subprocess.run([ + rustc_command = [ 'rustc', '--edition=2021', '--crate-name', 'markdown_workbench', '-C', 'metadata=opennexus-community-v1', '-C', 'opt-level=s', - '-C', 'strip=symbols', '-C', 'link-arg=-Wl,--no-insert-timestamp', - str(source / 'server.rs'), '-o', str(executable), - ], check=True) + '-C', 'strip=symbols', + ] + if sys.platform == 'win32': + rustc_command.extend(['-C', 'link-arg=-Wl,--no-insert-timestamp']) + rustc_command.extend([str(source / 'server.rs'), '-o', str(executable)]) + subprocess.run(rustc_command, check=True) generated[executable.name] = executable.read_bytes() manifest = (source / f'{kind}.yaml').read_text(encoding='utf-8') version = re.search(r'^version: (\d+\.\d+\.\d+)$', manifest, re.M)[1] @@ -38,7 +42,7 @@ def build(output: Path | None = None) -> dict: for name in sorted(files): info = zipfile.ZipInfo(f'{identity}/{name}', date_time=(1980, 1, 1, 0, 0, 0)) info.create_system = 3 - info.external_attr = 0o100644 << 16 + info.external_attr = (0o100755 if name == 'markdown-workbench.exe' else 0o100644) << 16 info.compress_type = zipfile.ZIP_DEFLATED content = generated.get(name) if content is None: diff --git a/scripts/prepare-ci-uv-mirror.py b/scripts/prepare-ci-uv-mirror.py new file mode 100644 index 0000000..32c7997 --- /dev/null +++ b/scripts/prepare-ci-uv-mirror.py @@ -0,0 +1,28 @@ +from pathlib import Path + + +LOCK_FILES = ( + Path("backend/uv.lock"), + Path("server sync/uv.lock"), + Path("community-server/uv.lock"), +) + +REPLACEMENTS = { + "https://pypi.org/simple": "https://mirrors.aliyun.com/pypi/simple", + "https://files.pythonhosted.org/packages/": "https://mirrors.aliyun.com/pypi/packages/", +} + + +def main() -> None: + for path in LOCK_FILES: + if not path.is_file(): + continue + content = path.read_text(encoding="utf-8") + for source, mirror in REPLACEMENTS.items(): + content = content.replace(source, mirror) + path.write_text(content, encoding="utf-8", newline="\n") + print(f"已切换锁文件下载源:{path}") + + +if __name__ == "__main__": + main()