Pi Chinese Nightly #512
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Pi Chinese Nightly | |
| # pi(earendil-works/pi)汉化版【实时更新版】自动构建发布流水线(nightly) | |
| # 每小时检查上游 main 最新提交:有变化则同步 → 校验翻译词表 100% 匹配 → 应用汉化 → 构建 → 发布 v<上游版本>-cn-nightly-<sha12> 预览版 | |
| # tag 唯一化:每次上游新提交发布全新 tag(含上游 commit sha 前 12 位),发布后自动删除旧预览版,Releases 页面只保留最新 1 个预览条目 | |
| # 正式版见 pi-cn-release.yml(跟随上游正式 Release) | |
| on: | |
| schedule: | |
| - cron: "47 * * * *" | |
| workflow_dispatch: | |
| inputs: | |
| upstream_ref: | |
| description: "上游分支或 commit(留空为 main 最新提交)" | |
| required: false | |
| type: string | |
| release_tag: | |
| description: "本仓库发布 tag(留空为 v<上游版本>-cn-nightly-<sha12>)" | |
| required: false | |
| type: string | |
| concurrency: | |
| group: pi-cn-nightly | |
| cancel-in-progress: false | |
| permissions: | |
| contents: write | |
| env: | |
| UPSTREAM_REPO: earendil-works/pi | |
| jobs: | |
| resolve: | |
| name: Resolve upstream commit | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| outputs: | |
| should_build: ${{ steps.resolve.outputs.should_build }} | |
| upstream_ref: ${{ steps.resolve.outputs.upstream_ref }} | |
| upstream_sha: ${{ steps.resolve.outputs.upstream_sha }} | |
| upstream_version: ${{ steps.resolve.outputs.upstream_version }} | |
| release_tag: ${{ steps.resolve.outputs.release_tag }} | |
| steps: | |
| - id: resolve | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| REQUESTED_UPSTREAM: ${{ github.event.inputs.upstream_ref }} | |
| REQUESTED_RELEASE: ${{ github.event.inputs.release_tag }} | |
| run: | | |
| set -euo pipefail | |
| # 跟随上游 main 最新提交;dispatch 可指定任意分支/commit | |
| upstream="${REQUESTED_UPSTREAM:-main}" | |
| if [[ "$upstream" =~ ^[0-9a-f]{40}$ ]]; then | |
| sha="$upstream" | |
| else | |
| sha="$(gh api "repos/${UPSTREAM_REPO}/commits/${upstream}" --jq '.sha')" | |
| fi | |
| # 上游版本号(packages/coding-agent/package.json) | |
| version="$(gh api "repos/${UPSTREAM_REPO}/contents/packages/coding-agent/package.json?ref=${sha}" \ | |
| --jq '.content' | base64 -d | jq -r '.version')" | |
| [[ -n "$version" && "$version" != "null" ]] || { | |
| echo "无法从上游解析版本号" >&2 | |
| exit 1 | |
| } | |
| release="${REQUESTED_RELEASE:-v${version}-cn-nightly-${sha:0:12}}" | |
| # tag 字符校验:仅允许字母数字与 . _ -(sha 后缀均为合法字符;防 dispatch 注入非法 tag) | |
| [[ "$release" =~ ^[A-Za-z0-9._-]+$ ]] || { | |
| echo "release tag 含非法字符: ${release}" >&2 | |
| exit 1 | |
| } | |
| should_build=true | |
| if [[ "${{ github.event_name }}" == "schedule" ]] && | |
| gh api "repos/${GITHUB_REPOSITORY}/releases/tags/${release}" >/dev/null 2>&1; then | |
| # tag 内含上游 commit sha(12 位 hex),天然防重复构建:同名 tag 已存在即跳过 | |
| should_build=false | |
| fi | |
| echo "should_build=$should_build" >>"$GITHUB_OUTPUT" | |
| echo "upstream_ref=$upstream" >>"$GITHUB_OUTPUT" | |
| echo "upstream_sha=$sha" >>"$GITHUB_OUTPUT" | |
| echo "upstream_version=$version" >>"$GITHUB_OUTPUT" | |
| echo "release_tag=$release" >>"$GITHUB_OUTPUT" | |
| echo "上游: ${UPSTREAM_REPO}@${sha:0:12} (v${version});发布 tag: ${release};需要构建: $should_build" | |
| translation-gate: | |
| name: Require complete translation match | |
| needs: resolve | |
| if: needs.resolve.outputs.should_build == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Sync upstream main | |
| run: | | |
| set -euo pipefail | |
| git remote add upstream "https://github.com/${UPSTREAM_REPO}.git" | |
| git fetch upstream "${{ needs.resolve.outputs.upstream_ref }}" | |
| git config user.name "yuloop" | |
| git config user.email "yuloop@users.noreply.github.com" | |
| git config user.name "yuloop" | |
| git config user.email "yuloop@users.noreply.github.com" | |
| git config merge.ours.driver true | |
| printf '.github/workflows/build-binaries.yml merge=ours\n' >> .git/info/attributes | |
| printf 'packages/ai/src/providers/cloudflare-ai-gateway.ts merge=ours\n' >> .git/info/attributes | |
| git merge --allow-unrelated-histories --no-edit FETCH_HEAD 2>&1 || true | |
| # Resolve add/add conflicts: take upstream for source, ours for CN files | |
| if [ -n "$(git diff --name-only --diff-filter=U 2>/dev/null)" ]; then | |
| echo "Resolving conflicts: taking upstream for source code" | |
| git diff --name-only --diff-filter=U | while read -r f; do | |
| git checkout --theirs "$f" 2>/dev/null || true | |
| done | |
| # Restore our fork-specific files | |
| git checkout HEAD -- .github/workflows/ pi-i18n/ packages/ai/src/providers/cloudflare-ai-gateway.ts 2>/dev/null || true | |
| git add -A 2>/dev/null || true | |
| git commit --no-edit 2>&1 || echo "Merge commit complete with conflicts resolved" | |
| fi | |
| git log --oneline -3 | |
| - name: Verify every translation entry still matches uniquely | |
| run: | | |
| set -euo pipefail | |
| python3 pi-i18n/apply_translations.py verify \ | |
| --translations pi-i18n/translations --source . --strict --min-match-rate 1 | |
| build: | |
| name: Build translated pi (${{ matrix.platform }}) | |
| needs: [resolve, translation-gate] | |
| if: needs.resolve.outputs.should_build == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 60 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| platform: [linux-x64, windows-x64] | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: 1.3.14 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: "22" | |
| - name: Install system dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev fd-find ripgrep | |
| sudo ln -s "$(which fdfind)" /usr/local/bin/fd | |
| - name: Sync upstream main | |
| run: | | |
| set -euo pipefail | |
| git remote add upstream "https://github.com/${UPSTREAM_REPO}.git" | |
| git fetch upstream "${{ needs.resolve.outputs.upstream_ref }}" | |
| git config user.name "yuloop" | |
| git config user.email "yuloop@users.noreply.github.com" | |
| git config user.name "yuloop" | |
| git config user.email "yuloop@users.noreply.github.com" | |
| git config merge.ours.driver true | |
| printf '.github/workflows/build-binaries.yml merge=ours\n' >> .git/info/attributes | |
| printf 'packages/ai/src/providers/cloudflare-ai-gateway.ts merge=ours\n' >> .git/info/attributes | |
| git merge --allow-unrelated-histories --no-edit FETCH_HEAD 2>&1 || true | |
| if [ -n "$(git diff --name-only --diff-filter=U 2>/dev/null)" ]; then | |
| echo "Resolving add/add conflicts: taking upstream for source" | |
| git diff --name-only --diff-filter=U | while read -r f; do | |
| git checkout --theirs "$f" 2>/dev/null || true | |
| done | |
| git checkout HEAD -- .github/workflows/ pi-i18n/ packages/ai/src/providers/cloudflare-ai-gateway.ts 2>/dev/null || true | |
| git add -A 2>/dev/null || true | |
| git commit --no-edit || true | |
| fi | |
| - name: Verify translation gate (100% match required) | |
| run: | | |
| set -euo pipefail | |
| python3 pi-i18n/apply_translations.py verify \ | |
| --translations pi-i18n/translations --source . --strict --min-match-rate 1 | |
| - name: Apply translations | |
| run: | | |
| set -euo pipefail | |
| python3 pi-i18n/apply_translations.py apply \ | |
| --translations pi-i18n/translations --source . --strict --min-match-rate 1 | |
| - name: Hydrate model data | |
| run: | | |
| set -euo pipefail | |
| npm ci | |
| npm run hydrate:model-data | |
| - name: Build ${{ matrix.platform }} portable binary | |
| run: | | |
| set -euo pipefail | |
| ./scripts/build-binaries.sh \ | |
| --offline-model-data \ | |
| --platform "${{ matrix.platform }}" \ | |
| --out "${GITHUB_WORKSPACE}/dist-binaries" | |
| - name: Package binary artifact | |
| env: | |
| PLATFORM: ${{ matrix.platform }} | |
| VERSION: ${{ needs.resolve.outputs.upstream_version }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p dist | |
| if [[ "$PLATFORM" == windows-* ]]; then | |
| (cd dist-binaries/windows-x64 && zip -r "${GITHUB_WORKSPACE}/dist/pi-cn-${VERSION}-windows-x64.zip" .) | |
| else | |
| tar -czf "dist/pi-cn-${VERSION}-linux-x64.tar.gz" -C dist-binaries/linux-x64 . | |
| fi | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: pi-cn-${{ matrix.platform }} | |
| path: dist/* | |
| if-no-files-found: error | |
| build-source: | |
| name: Package translated source archive | |
| needs: [resolve, translation-gate] | |
| if: needs.resolve.outputs.should_build == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Sync upstream main and apply translations | |
| run: | | |
| set -euo pipefail | |
| git remote add upstream "https://github.com/${UPSTREAM_REPO}.git" | |
| git fetch upstream "${{ needs.resolve.outputs.upstream_ref }}" | |
| git config user.name "yuloop" | |
| git config user.email "yuloop@users.noreply.github.com" | |
| git config user.name "yuloop" | |
| git config user.email "yuloop@users.noreply.github.com" | |
| git config merge.ours.driver true | |
| printf '.github/workflows/build-binaries.yml merge=ours\n' >> .git/info/attributes | |
| printf 'packages/ai/src/providers/cloudflare-ai-gateway.ts merge=ours\n' >> .git/info/attributes | |
| git merge --allow-unrelated-histories --no-edit FETCH_HEAD 2>&1 || true | |
| if [ -n "$(git diff --name-only --diff-filter=U 2>/dev/null)" ]; then | |
| echo "Resolving add/add conflicts: taking upstream for source" | |
| git diff --name-only --diff-filter=U | while read -r f; do | |
| git checkout --theirs "$f" 2>/dev/null || true | |
| done | |
| git checkout HEAD -- .github/workflows/ pi-i18n/ packages/ai/src/providers/cloudflare-ai-gateway.ts 2>/dev/null || true | |
| git add -A 2>/dev/null || true | |
| git commit --no-edit || true | |
| fi | |
| python3 pi-i18n/apply_translations.py apply \ | |
| --translations pi-i18n/translations --source . --strict --min-match-rate 1 | |
| - name: Package source archive | |
| env: | |
| VERSION: ${{ needs.resolve.outputs.upstream_version }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p dist | |
| tar -czf "dist/pi-cn-${VERSION}-source.tar.gz" \ | |
| --exclude='.git' --exclude='node_modules' --exclude='dist' \ | |
| --transform "s,^,pi-cn-${VERSION}/," \ | |
| . | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: pi-cn-source | |
| path: dist/* | |
| if-no-files-found: error | |
| release: | |
| name: Publish verified nightly release | |
| needs: [resolve, translation-gate, build, build-source] | |
| if: needs.resolve.outputs.should_build == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| pattern: "pi-cn-*" | |
| path: dist | |
| merge-multiple: true | |
| - name: Create checksums and release notes | |
| env: | |
| RELEASE_TAG: ${{ needs.resolve.outputs.release_tag }} | |
| UPSTREAM_VERSION: ${{ needs.resolve.outputs.upstream_version }} | |
| UPSTREAM_SHA: ${{ needs.resolve.outputs.upstream_sha }} | |
| run: | | |
| set -euo pipefail | |
| ( | |
| cd dist | |
| find . -maxdepth 1 -type f ! -name SHA256SUMS -printf '%f\n' | sort | xargs sha256sum >SHA256SUMS | |
| ) | |
| cat >release_notes.md <<EOF | |
| ## Pi CLI/TUI 简体中文汉化版(实时更新版) | |
| - 上游最新提交:\`${UPSTREAM_SHA:0:12}\`(main 分支,对应版本 v${UPSTREAM_VERSION}) | |
| - 翻译门禁:词表 \`pi-i18n/translations\` 全部条目在源码中 100% 唯一匹配后才允许发布 | |
| - **实时更新版**:每小时跟随上游 \`main\` 最新提交构建,每次上游新提交发布新 tag,自动清理旧预览版(页面只保留最新 1 个预览条目) | |
| - 追求稳定请下载正式版(\`*-cn\`,见仓库 Releases 最新版) | |
| ### 下载 | |
| | 平台 | 文件 | | |
| |---|---| | |
| | Linux x64 | \`pi-cn-${UPSTREAM_VERSION}-linux-x64.tar.gz\` | | |
| | Windows x64 | \`pi-cn-${UPSTREAM_VERSION}-windows-x64.zip\` | | |
| | 汉化源码 | \`pi-cn-${UPSTREAM_VERSION}-source.tar.gz\` | | |
| 所有文件的 SHA-256 摘要见 \`SHA256SUMS\`。 | |
| ### 安装与更新 | |
| ### 一键安装(预览版) | |
| Windows PowerShell: | |
| \`\`\`powershell | |
| powershell -Command "irm https://raw.githubusercontent.com/yuloop/pi/main/pi-i18n/install-preview.ps1 | iex" | |
| \`\`\` | |
| Linux / WSL: | |
| \`\`\`bash | |
| curl -fsSL https://raw.githubusercontent.com/yuloop/pi/main/pi-i18n/install.sh | bash -s -- --preview | |
| \`\`\` | |
| 详见仓库 \`pi-i18n/README.md\`。 | |
| > 本项目是社区维护的非官方汉化构建,与 pi 官方团队无隶属关系。 | |
| EOF | |
| - uses: softprops/action-gh-release@v3 | |
| with: | |
| tag_name: ${{ needs.resolve.outputs.release_tag }} | |
| target_commitish: ${{ github.sha }} | |
| name: "Pi v${{ needs.resolve.outputs.upstream_version }} 汉化版(实时版)" | |
| body_path: release_notes.md | |
| files: dist/* | |
| overwrite_files: true | |
| fail_on_unmatched_files: true | |
| prerelease: true | |
| - name: Delete outdated preview releases | |
| env: | |
| GH_TOKEN: ${{ secrets.YU_TOKEN }} | |
| CURRENT_TAG: ${{ needs.resolve.outputs.release_tag }} | |
| run: | | |
| set -euo pipefail | |
| gh api "repos/${GITHUB_REPOSITORY}/releases?per_page=100" --paginate \ | |
| --jq '.[] | select(.prerelease == true and .draft == false and .tag_name != env.CURRENT_TAG and (.tag_name | contains("-cn-nightly"))) | .id' \ | |
| | while read -r id; do | |
| echo "deleting outdated preview release id=$id" | |
| gh api -X DELETE "repos/${GITHUB_REPOSITORY}/releases/$id" || echo "delete failed id=$id" | |
| done |