From 5ebc0c4c7e006ba4c1f586bb61f134ad4f65e015 Mon Sep 17 00:00:00 2001 From: BeetMan Date: Fri, 30 Oct 2026 09:54:04 -0700 Subject: [PATCH] ci: one-click multi-platform release pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Split build.yml (checks + all-platform builds, runs on every PR and can be called with a ref) from release.yml (prepare, build, publish) - release.yml workflow_dispatch takes a version: validates it, syncs all version fields, commits to main, tags, builds, and publishes the release; pushing a vX.Y.Z tag still works and skips the version commit - Release notes come from the "## X.Y.Z" section of docs/更新日志-X.Y.md (new `release-notes` command in scripts/project.mjs); optional draft mode; reruns update the existing release - New targets: Windows ARM64, Linux ARM64, and .rpm for Linux - Docs describe the new release steps and artifacts Co-Authored-By: Claude Opus 5.5 --- .github/workflows/build.yml | 219 ++++++++++++ .github/workflows/release.yml | 324 +++++++----------- README.md | 16 +- ...23\345\214\205\350\257\264\346\230\216.md" | 3 +- ...64\346\212\244\350\257\264\346\230\216.md" | 38 +- scripts/project.mjs | 17 + 6 files changed, 403 insertions(+), 214 deletions(-) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..d5baa60 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,219 @@ +name: Build + +# 全平台构建:PR 上直接运行用于验证,发版时由 release.yml 以指定标签调用。 +# 只负责检查、构建和上传产物,不创建 Release。 + +on: + pull_request: + workflow_dispatch: + workflow_call: + inputs: + ref: + description: 要构建的 Git 引用(标签或提交) + type: string + required: true + outputs: + version: + description: 构建所用的产品版本号 + value: ${{ jobs.check.outputs.version }} + +permissions: + contents: read + +jobs: + check: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + ref: ${{ inputs.ref }} + + - name: Set up Node.js + uses: actions/setup-node@v5 + with: + node-version: 22 + cache: npm + cache-dependency-path: package-lock.json + + - name: Read version + id: version + run: | + version="$(tr -d '[:space:]' < VERSION)" + echo "version=$version" >> "$GITHUB_OUTPUT" + + - name: Install root dependencies + run: npm ci + + - name: Install Playwright browser + run: npx playwright install --with-deps chromium + + - name: Run project checks + run: npm run check + + tauri: + needs: check + name: tauri (${{ matrix.name }}) + strategy: + fail-fast: false + matrix: + include: + - name: windows-x64 + os: windows-latest + args: --bundles nsis + assets: | + nsis/*.exe=Windows-x64-Setup.exe + - name: windows-arm64 + os: windows-latest + target: aarch64-pc-windows-msvc + args: --target aarch64-pc-windows-msvc --bundles nsis + assets: | + nsis/*.exe=Windows-arm64-Setup.exe + - name: macos-universal + os: macos-latest + target: universal-apple-darwin + rust-targets: aarch64-apple-darwin,x86_64-apple-darwin + args: --target universal-apple-darwin --bundles app,dmg + assets: | + dmg/*.dmg=macOS-universal.dmg + - name: linux-x64 + os: ubuntu-22.04 + args: --bundles deb,rpm,appimage + assets: | + deb/*.deb=Linux-amd64.deb + rpm/*.rpm=Linux-x86_64.rpm + appimage/*.AppImage=Linux-x86_64.AppImage + - name: linux-arm64 + os: ubuntu-22.04-arm + args: --bundles deb,rpm,appimage + assets: | + deb/*.deb=Linux-arm64.deb + rpm/*.rpm=Linux-aarch64.rpm + appimage/*.AppImage=Linux-aarch64.AppImage + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + ref: ${{ inputs.ref }} + + - name: Install Linux system dependencies + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf + + - name: Set up Node.js + uses: actions/setup-node@v5 + with: + node-version: 22 + cache: npm + cache-dependency-path: tauri/package-lock.json + + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.rust-targets || matrix.target }} + + - name: Cache Rust build + uses: Swatinem/rust-cache@v2 + with: + workspaces: tauri/src-tauri -> target + key: ${{ matrix.name }} + + - name: Install Tauri dependencies + working-directory: tauri + run: npm ci + + - name: Build Tauri bundles + working-directory: tauri + run: npm run build -- ${{ matrix.args }} + + - name: Collect release files + shell: bash + env: + VERSION: ${{ needs.check.outputs.version }} + TARGET: ${{ matrix.target }} + ASSETS: ${{ matrix.assets }} + run: | + set -euo pipefail + shopt -s nullglob + bundle="tauri/src-tauri/target/${TARGET:+$TARGET/}release/bundle" + mkdir -p release-assets + + while IFS='=' read -r pattern name; do + [[ -z "$pattern" ]] && continue + # shellcheck disable=SC2206 # pattern 是需要展开的通配符 + files=("$bundle"/$pattern) + if [[ ${#files[@]} -ne 1 ]]; then + echo "Expected exactly one file for $bundle/$pattern, found ${#files[@]}" >&2 + exit 1 + fi + cp "${files[0]}" "release-assets/Teleprompter-Tauri-$VERSION-$name" + done <<< "$ASSETS" + ls -l release-assets + + - name: Upload build artifacts + uses: actions/upload-artifact@v5 + with: + name: tauri-${{ matrix.name }} + path: release-assets/ + if-no-files-found: error + + # Electron 版仅作为 Windows x64 备用版本继续发布。 + electron: + needs: check + runs-on: windows-latest + + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + ref: ${{ inputs.ref }} + + - name: Set up Node.js + uses: actions/setup-node@v5 + with: + node-version: 22 + cache: npm + cache-dependency-path: windows-electron/package-lock.json + + - name: Install Electron dependencies + working-directory: windows-electron + run: npm ci + + # Build the unpacked directory once, then package it twice. This avoids + # repeating the Electron application packaging work for each target. + - name: Build Electron application directory + working-directory: windows-electron + run: npx electron-builder --win dir --x64 --config.win.signAndEditExecutable=false + + - name: Build Electron portable package + working-directory: windows-electron + run: npx electron-builder --win portable --x64 --prepackaged dist/win-unpacked --config.win.signAndEditExecutable=false --config.directories.output=dist/portable + + - name: Build Electron installer + working-directory: windows-electron + run: npx electron-builder --win nsis --x64 --prepackaged dist/win-unpacked --config.win.signAndEditExecutable=false --config.directories.output=dist/nsis + + - name: Collect release files + shell: bash + env: + VERSION: ${{ needs.check.outputs.version }} + run: | + set -euo pipefail + mkdir -p release-assets + cp windows-electron/dist/portable/*.exe "release-assets/Teleprompter-Electron-$VERSION-Windows-x64-Portable.exe" + cp windows-electron/dist/nsis/*.exe "release-assets/Teleprompter-Electron-$VERSION-Windows-x64-Setup.exe" + ls -l release-assets + + - name: Upload build artifacts + uses: actions/upload-artifact@v5 + with: + name: electron-windows-x64 + path: release-assets/ + if-no-files-found: error diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5aea5ed..c4106d3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,209 +1,130 @@ -name: Build And Release +name: Release + +# 两种发版方式: +# 1. 推荐:在 Actions 页面手动运行本工作流并填写版本号。工作流会同步版本号、提交、 +# 打标签,然后构建全部平台并发布 Release。 +# 2. 本地已用 `npm run sync:version` 提交版本号时,直接推送 vX.Y.Z 标签。 +# 两种方式都要求 docs/更新日志-X.Y.md 中已有 "## X.Y.Z" 小节,作为 Release 说明。 on: push: tags: - "v*.*.*" - # PR 上完整构建三个平台以便验证;创建 Release 的步骤只在推送标签时执行。 - pull_request: workflow_dispatch: + inputs: + version: + description: 新版本号(x.y.z,例如 6.6.0) + type: string + required: true + draft: + description: 创建为草稿 Release(检查无误后在 GitHub 上手动发布) + type: boolean + default: false + +concurrency: + group: release + cancel-in-progress: false permissions: - contents: write + contents: read jobs: - check: + prepare: runs-on: ubuntu-latest + permissions: + contents: write outputs: version: ${{ steps.version.outputs.version }} + tag: ${{ steps.version.outputs.tag }} steps: - name: Checkout uses: actions/checkout@v5 - - - name: Set up Node.js - uses: actions/setup-node@v5 with: - node-version: 22 - cache: npm - cache-dependency-path: package-lock.json - - - name: Check version - id: version - shell: bash - run: | - version="$(tr -d '[:space:]' < VERSION)" - if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "VERSION must use semantic versioning: $version" >&2 - exit 1 - fi - if [[ "$GITHUB_REF" == refs/tags/* && "v$version" != "$GITHUB_REF_NAME" ]]; then - echo "Tag $GITHUB_REF_NAME does not match VERSION $version" >&2 - exit 1 - fi - echo "version=$version" >> "$GITHUB_OUTPUT" - - - name: Install root dependencies - run: npm ci - - - name: Install Playwright browser - run: npx playwright install --with-deps chromium - - - name: Run project checks - run: npm run check - - tauri: - needs: check - strategy: - fail-fast: false - matrix: - include: - - platform: windows - os: windows-latest - args: --bundles nsis - - platform: macos - os: macos-latest - rust-targets: aarch64-apple-darwin,x86_64-apple-darwin - args: --target universal-apple-darwin --bundles app,dmg - - platform: linux - os: ubuntu-22.04 - args: --bundles deb,appimage - runs-on: ${{ matrix.os }} - - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Install Linux system dependencies - if: matrix.platform == 'linux' - run: | - sudo apt-get update - sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf + # 手动发版从默认分支最新提交开始;推送标签时使用标签本身 + ref: ${{ github.event_name == 'workflow_dispatch' && github.event.repository.default_branch || github.ref }} + fetch-depth: 0 - name: Set up Node.js uses: actions/setup-node@v5 with: node-version: 22 - cache: npm - cache-dependency-path: tauri/package-lock.json - - - name: Set up Rust - uses: dtolnay/rust-toolchain@stable - with: - targets: ${{ matrix.rust-targets }} - - - name: Cache Rust build - uses: Swatinem/rust-cache@v2 - with: - workspaces: tauri/src-tauri -> target - - name: Install Tauri dependencies - working-directory: tauri - run: npm ci - - - name: Build Tauri bundles - working-directory: tauri - run: npm run build -- ${{ matrix.args }} - - - name: Collect release files - shell: bash + - name: Resolve version + id: version env: - VERSION: ${{ needs.check.outputs.version }} - PLATFORM: ${{ matrix.platform }} + INPUT_VERSION: ${{ inputs.version }} run: | set -euo pipefail - shopt -s nullglob - mkdir -p release-assets - bundle=tauri/src-tauri/target/release/bundle - prefix="Teleprompter-Tauri-$VERSION" - - copy_one() { - local files=($1) - if [[ ${#files[@]} -ne 1 ]]; then - echo "Expected exactly one file for $1, found ${#files[@]}" >&2 + current="$(tr -d '[:space:]' < VERSION)" + + if [[ "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]]; then + version="${INPUT_VERSION#v}" + version="$(echo "$version" | tr -d '[:space:]')" + if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::版本号必须是 x.y.z 格式:$INPUT_VERSION" exit 1 fi - cp "${files[0]}" "release-assets/$2" - } - - case "$PLATFORM" in - windows) - copy_one "$bundle/nsis/*.exe" "$prefix-Windows-x64-Setup.exe" - ;; - macos) - copy_one "tauri/src-tauri/target/universal-apple-darwin/release/bundle/dmg/*.dmg" "$prefix-macOS-universal.dmg" - ;; - linux) - copy_one "$bundle/deb/*.deb" "$prefix-Linux-amd64.deb" - copy_one "$bundle/appimage/*.AppImage" "$prefix-Linux-x86_64.AppImage" - ;; - esac - ls -l release-assets - - - name: Upload build artifacts - uses: actions/upload-artifact@v5 - with: - name: tauri-${{ matrix.platform }} - path: release-assets/ - if-no-files-found: error - - # Electron 版仅作为 Windows 备用版本继续发布。 - electron: - needs: check - runs-on: windows-latest - - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Set up Node.js - uses: actions/setup-node@v5 - with: - node-version: 22 - cache: npm - cache-dependency-path: windows-electron/package-lock.json - - - name: Install Electron dependencies - working-directory: windows-electron - run: npm ci - - # Build the unpacked directory once, then package it twice. This avoids - # repeating the Electron application packaging work for each target. - - name: Build Electron application directory - working-directory: windows-electron - run: npx electron-builder --win dir --x64 --config.win.signAndEditExecutable=false + if git rev-parse -q --verify "refs/tags/v$version" > /dev/null; then + echo "::error::标签 v$version 已存在。" + exit 1 + fi + # 允许与当前 VERSION 相同(已在本地同步过版本号但还没打标签),不允许回退 + if [[ "$(printf '%s\n%s\n' "$current" "$version" | sort -V | tail -1)" != "$version" ]]; then + echo "::error::新版本 $version 低于当前版本 $current。" + exit 1 + fi + else + version="${GITHUB_REF_NAME#v}" + if [[ "$version" != "$current" ]]; then + echo "::error::标签 $GITHUB_REF_NAME 与 VERSION 文件中的 $current 不一致。" + exit 1 + fi + fi - - name: Build Electron portable package - working-directory: windows-electron - run: npx electron-builder --win portable --x64 --prepackaged dist/win-unpacked --config.win.signAndEditExecutable=false --config.directories.output=dist/portable + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "tag=v$version" >> "$GITHUB_OUTPUT" - - name: Build Electron installer - working-directory: windows-electron - run: npx electron-builder --win nsis --x64 --prepackaged dist/win-unpacked --config.win.signAndEditExecutable=false --config.directories.output=dist/nsis + - name: Check release notes + run: node scripts/project.mjs release-notes "${{ steps.version.outputs.version }}" - - name: Collect release files - shell: bash + - name: Commit version and push tag + if: github.event_name == 'workflow_dispatch' env: - VERSION: ${{ needs.check.outputs.version }} + VERSION: ${{ steps.version.outputs.version }} + BRANCH: ${{ github.event.repository.default_branch }} run: | set -euo pipefail - mkdir -p release-assets - cp windows-electron/dist/portable/*.exe "release-assets/Teleprompter-Electron-$VERSION-Windows-x64-Portable.exe" - cp windows-electron/dist/nsis/*.exe "release-assets/Teleprompter-Electron-$VERSION-Windows-x64-Setup.exe" - ls -l release-assets + node scripts/project.mjs sync-version "$VERSION" + node scripts/project.mjs check-version - - name: Upload build artifacts - uses: actions/upload-artifact@v5 - with: - name: electron-windows - path: release-assets/ - if-no-files-found: error - - release: - needs: [check, tauri, electron] + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + if ! git diff --quiet; then + git commit -am "release: prepare version $VERSION" + fi + git tag -a "v$VERSION" -m "Release v$VERSION" + # 分支和标签原子推送;用 GITHUB_TOKEN 推送的标签不会再次触发本工作流 + git push --atomic origin "HEAD:refs/heads/$BRANCH" "refs/tags/v$VERSION" + + build: + needs: prepare + uses: ./.github/workflows/build.yml + with: + ref: ${{ needs.prepare.outputs.tag }} + + publish: + needs: [prepare, build] runs-on: ubuntu-latest + permissions: + contents: write steps: + - name: Checkout + uses: actions/checkout@v5 + with: + ref: ${{ needs.prepare.outputs.tag }} + - name: Download build artifacts uses: actions/download-artifact@v5 with: @@ -216,30 +137,47 @@ jobs: sha256sum -- * | sort -k2 > SHA256SUMS.txt cat SHA256SUMS.txt - - name: Upload combined artifact - uses: actions/upload-artifact@v5 - with: - name: teleprompter-${{ needs.check.outputs.version }} - path: release-assets/ - if-no-files-found: error + - name: Write release notes + env: + VERSION: ${{ needs.prepare.outputs.version }} + run: | + set -euo pipefail + { + node scripts/project.mjs release-notes "$VERSION" + cat < 当前安装包尚未配置代码签名。Windows 可能显示 SmartScreen 警告;macOS 首次打开如提示"已损坏"或"无法验证开发者",请将应用拖入"应用程序"后在终端执行 `xattr -cr /Applications/提词器.app`,或在"系统设置 → 隐私与安全性"中选择"仍要打开"。 - files: | - release-assets/* + ## 下载 + + | 平台 | 安装包 | + | --- | --- | + | Windows(x64) | \`Teleprompter-Tauri-$VERSION-Windows-x64-Setup.exe\` | + | Windows(ARM64) | \`Teleprompter-Tauri-$VERSION-Windows-arm64-Setup.exe\` | + | macOS 10.15+(Apple 芯片与 Intel 通用) | \`Teleprompter-Tauri-$VERSION-macOS-universal.dmg\` | + | Linux(x86_64) | \`.AppImage\`、\`.deb\`(Debian / Ubuntu)、\`.rpm\`(Fedora / openSUSE) | + | Linux(ARM64) | \`.AppImage\`、\`.deb\`、\`.rpm\` | + + Electron Windows x64 便携版和安装版继续作为备用版本提供。\`SHA256SUMS.txt\` 为全部文件的校验清单。 + + > 当前安装包尚未配置代码签名。Windows 可能显示 SmartScreen 警告;macOS 首次打开如提示"已损坏"或"无法验证开发者",请将应用拖入"应用程序"后在终端执行 \`xattr -cr /Applications/提词器.app\`,或在"系统设置 → 隐私与安全性"中选择"仍要打开"。 + EOF + } > release-notes.md + cat release-notes.md + + - name: Publish GitHub Release + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ needs.prepare.outputs.tag }} + DRAFT: ${{ inputs.draft && 'true' || 'false' }} + run: | + set -euo pipefail + if gh release view "$TAG" > /dev/null 2>&1; then + # 重跑时更新已有 Release,覆盖同名文件 + gh release edit "$TAG" --title "提词器 $TAG" --notes-file release-notes.md + gh release upload "$TAG" release-assets/* --clobber + else + args=(--title "提词器 $TAG" --notes-file release-notes.md --verify-tag) + if [[ "$DRAFT" == "true" ]]; then + args+=(--draft) + fi + gh release create "$TAG" "${args[@]}" release-assets/* + fi diff --git a/README.md b/README.md index 842d8f6..8ffff2d 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,9 @@ | 平台 | 安装包 | | --- | --- | -| Windows 10 / 11(x64) | Tauri 安装版 `.exe`,或 Electron 便携版 / 安装版 | +| Windows 10 / 11(x64 / ARM64) | Tauri 安装版 `.exe`;x64 另有 Electron 便携版 / 安装版 | | macOS 10.15+(Apple 芯片与 Intel 通用) | Tauri `.dmg` | -| Linux(x86_64) | Tauri `.AppImage` 或 `.deb` | +| Linux(x86_64 / ARM64) | Tauri `.AppImage`、`.deb` 或 `.rpm` | 最新安装包可在 [GitHub Releases](https://github.com/BeetMan/teleprompter/releases/latest) 获取。 @@ -113,16 +113,14 @@ npm run check npm run sync:version -- 6.5.0 ``` -## GitHub Actions 发布 +## GitHub Actions 发版 -发布流程由 `.github/workflows/release.yml` 管理。提交版本号同步改动后,推送符合 `vX.Y.Z` 格式的标签即可触发自动构建: +1. 在 `docs/更新日志-X.Y.md` 中写好 `## X.Y.Z` 小节并推送到 `main`。 +2. 在 GitHub 打开 **Actions → Release → Run workflow**,填写版本号。 -```bash -git tag -a v6.5.0 -m "Release v6.5.0" -git push origin v6.5.0 -``` +工作流会同步版本号、提交并打标签,然后并行构建 Tauri 的 Windows(x64 / ARM64)、macOS 通用、Linux(x86_64 / ARM64,`.AppImage` / `.deb` / `.rpm`)安装包和 Electron Windows 备用版本,生成 `SHA256SUMS.txt`,最后用更新日志创建 GitHub Release。也可以在本地同步版本号后直接推送 `vX.Y.Z` 标签触发。详细说明见 [项目维护说明](./docs/项目维护说明.md#自动发布)。 -Actions 会先在 Linux 上执行项目检查,然后并行构建 Tauri 的 Windows 安装版、macOS 通用 `.dmg`、Linux `.deb` 和 `.AppImage`,以及 Electron Windows 便携版和安装版,最后生成 `SHA256SUMS.txt` 并创建或更新对应的 GitHub Release。当前工作流默认生成未签名安装包;后续接入证书后,可在工作流中增加签名步骤。 +每个 PR 都会运行 `.github/workflows/build.yml` 完成检查和全平台构建。 ## 项目结构 diff --git "a/docs/\346\211\223\345\214\205\350\257\264\346\230\216.md" "b/docs/\346\211\223\345\214\205\350\257\264\346\230\216.md" index 267de81..47519b7 100644 --- "a/docs/\346\211\223\345\214\205\350\257\264\346\230\216.md" +++ "b/docs/\346\211\223\345\214\205\350\257\264\346\230\216.md" @@ -34,7 +34,8 @@ npm run build | Windows | `npm run build -- --bundles nsis` | `tauri/src-tauri/target/release/bundle/nsis/` | | macOS(当前芯片) | `npm run build -- --bundles app,dmg` | `tauri/src-tauri/target/release/bundle/dmg/` | | macOS(通用二进制) | `npm run build -- --target universal-apple-darwin --bundles app,dmg` | `tauri/src-tauri/target/universal-apple-darwin/release/bundle/dmg/` | -| Linux | `npm run build -- --bundles deb,appimage` | `tauri/src-tauri/target/release/bundle/deb/`、`appimage/` | +| Windows ARM64(在 x64 上交叉编译) | `npm run build -- --target aarch64-pc-windows-msvc --bundles nsis` | `tauri/src-tauri/target/aarch64-pc-windows-msvc/release/bundle/nsis/` | +| Linux | `npm run build -- --bundles deb,rpm,appimage` | `tauri/src-tauri/target/release/bundle/deb/`、`rpm/`、`appimage/` | 构建 macOS 通用二进制前需要安装两个目标: diff --git "a/docs/\351\241\271\347\233\256\347\273\264\346\212\244\350\257\264\346\230\216.md" "b/docs/\351\241\271\347\233\256\347\273\264\346\212\244\350\257\264\346\230\216.md" index 1cded25..337d0fb 100644 --- "a/docs/\351\241\271\347\233\256\347\273\264\346\212\244\350\257\264\346\230\216.md" +++ "b/docs/\351\241\271\347\233\256\347\273\264\346\212\244\350\257\264\346\230\216.md" @@ -74,19 +74,35 @@ node scripts/project.mjs check-parity ## 自动发布 -工作流位于 `.github/workflows/release.yml`。推送符合 `vX.Y.Z` 格式的标签后,GitHub Actions 会: +### 发版步骤 -1. 在 Linux 上执行 `npm run check`(版本、前端一致性和 Playwright 测试) -2. 并行构建 Tauri 安装包:Windows `.exe`、macOS 通用 `.dmg`、Linux `.deb` 和 `.AppImage` -3. 构建 Electron Windows 便携版和安装版 -4. 汇总全部文件并生成 `SHA256SUMS.txt` -5. 创建对应的 GitHub Release 并上传安装包 +1. 在 `docs/更新日志-X.Y.md` 中写好 `## X.Y.Z` 小节(新的次版本号就新建文件,格式参考已有日志),提交并推送到 `main`。这一节会直接作为 GitHub Release 的说明,缺失时发版会在第一步失败。 +2. 打开 GitHub 仓库的 **Actions → Release → Run workflow**,填写版本号(例如 `6.6.0`)。勾选"草稿"会先创建草稿 Release,检查无误后再在 GitHub 上手动发布。 +3. 工作流自动完成: + - 校验版本号格式、不低于当前版本、标签不存在,且更新日志中有对应小节 + - 同步全部版本号位置,提交 `release: prepare version X.Y.Z` 到 `main` 并打 `vX.Y.Z` 标签 + - 在 Linux 上执行 `npm run check` + - 并行构建全部安装包(见下表) + - 生成 `SHA256SUMS.txt`,以更新日志小节加下载说明作为正文,创建 GitHub Release -示例: +也可以在本地运行 `npm run sync:version -- X.Y.Z`、提交后推送 `vX.Y.Z` 标签,工作流会跳过版本提交,直接构建并发布。 -```bash -git tag -a v6.5.0 -m "Release v6.5.0" -git push origin v6.5.0 -``` +重跑失败的发版工作流时,已存在的 Release 会被更新,同名文件会被覆盖。 + +### 构建产物 + +| 平台 | 文件 | +| --- | --- | +| Windows x64 | `Teleprompter-Tauri-X.Y.Z-Windows-x64-Setup.exe` | +| Windows ARM64 | `Teleprompter-Tauri-X.Y.Z-Windows-arm64-Setup.exe` | +| macOS(通用) | `Teleprompter-Tauri-X.Y.Z-macOS-universal.dmg` | +| Linux x86_64 | `Teleprompter-Tauri-X.Y.Z-Linux-x86_64.AppImage`、`-Linux-amd64.deb`、`-Linux-x86_64.rpm` | +| Linux ARM64 | `Teleprompter-Tauri-X.Y.Z-Linux-aarch64.AppImage`、`-Linux-arm64.deb`、`-Linux-aarch64.rpm` | +| Windows x64(Electron 备用) | `Teleprompter-Electron-X.Y.Z-Windows-x64-Portable.exe`、`-Setup.exe` | + +### 工作流文件 + +- `.github/workflows/build.yml`:检查和全平台构建,只上传构建产物。每个 PR 都会运行,也可以在 Actions 页面手动运行。 +- `.github/workflows/release.yml`:发版。准备版本和标签,调用 `build.yml` 构建,再发布 Release。 当前工作流默认生成未签名安装包(macOS 为 ad-hoc 签名,未公证)。接入 Windows 代码签名证书或 Apple Developer ID 后,应将证书配置放入 GitHub Actions Secrets,并在构建步骤中增加签名流程,不要把证书文件或密码提交到仓库。 diff --git a/scripts/project.mjs b/scripts/project.mjs index f7685f1..e2ffe75 100644 --- a/scripts/project.mjs +++ b/scripts/project.mjs @@ -6,6 +6,7 @@ // node scripts/project.mjs check-version // node scripts/project.mjs check-parity // node scripts/project.mjs check (同步前端 + 检查版本 + 检查一致性 + Playwright 测试) +// node scripts/project.mjs release-notes 6.5.0 (输出 docs/更新日志-6.5.md 中 ## 6.5.0 一节,缺失时报错) import { createHash } from "node:crypto"; import { spawnSync } from "node:child_process"; @@ -149,12 +150,28 @@ function check() { console.log("\nProject check passed."); } +// 发版说明取自 docs/更新日志-X.Y.md 中对应的 "## X.Y.Z" 小节,直到下一个同级标题 +function releaseNotes(version) { + if (!semver.test(version ?? "")) throw new Error("Usage: node scripts/project.mjs release-notes x.y.z"); + const path = `docs/更新日志-${shortVersion(version)}.md`; + if (!existsSync(abs(path))) throw new Error(`Missing changelog file: ${path}`); + const lines = read(path).split(/\r?\n/); + const start = lines.findIndex((line) => line.trim() === `## ${version}`); + if (start === -1) throw new Error(`${path} has no "## ${version}" section.`); + const rest = lines.slice(start + 1); + const end = rest.findIndex((line) => /^##\s/.test(line)); + const notes = (end === -1 ? rest : rest.slice(0, end)).join("\n").trim(); + if (!notes) throw new Error(`${path} "## ${version}" section is empty.`); + console.log(notes); +} + const commands = { "sync-version": syncVersion, "sync-frontend": syncFrontend, "check-version": checkVersion, "check-parity": checkParity, check, + "release-notes": releaseNotes, }; const [command, ...args] = process.argv.slice(2);