fix: 补齐插件终端与权限档位用例的 mock 参数类型,修 typecheck:node #17
Workflow file for this run
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: Build desktop releases | |
| on: | |
| push: | |
| tags: | |
| - 'v*.*.*' | |
| workflow_dispatch: | |
| inputs: | |
| release_tag: | |
| description: 'Existing version tag to publish (leave empty for a build-only run)' | |
| required: false | |
| type: string | |
| permissions: | |
| contents: write | |
| concurrency: | |
| group: release-${{ inputs.release_tag || github.ref_name }} | |
| cancel-in-progress: false | |
| jobs: | |
| prepare: | |
| name: Prepare release | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| outputs: | |
| tag: ${{ steps.target.outputs.tag }} | |
| source_sha: ${{ steps.target.outputs.source_sha }} | |
| publish: ${{ steps.target.outputs.publish }} | |
| steps: | |
| - name: Check out source | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ inputs.release_tag || github.ref }} | |
| - name: Resolve and validate release target | |
| id: target | |
| env: | |
| TAG_NAME: ${{ inputs.release_tag || (startsWith(github.ref, 'refs/tags/') && github.ref_name) || '' }} | |
| run: | | |
| node --input-type=module <<'NODE' | |
| import { appendFileSync, readFileSync, writeFileSync } from 'node:fs' | |
| import { execFileSync } from 'node:child_process' | |
| import { join } from 'node:path' | |
| const tag = process.env.TAG_NAME | |
| const sha = execFileSync('git', ['rev-parse', 'HEAD'], { encoding: 'utf8' }).trim() | |
| if (tag) { | |
| const { version } = JSON.parse(readFileSync('package.json', 'utf8')) | |
| if (!/^v\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(tag) || tag !== `v${version}`) { | |
| throw new Error(`Release tag ${tag} does not match package version ${version}`) | |
| } | |
| const tagSha = execFileSync('git', ['rev-parse', `refs/tags/${tag}^{commit}`], { encoding: 'utf8' }).trim() | |
| if (tagSha !== sha) throw new Error('The checked-out commit does not match the release tag') | |
| const lines = readFileSync('CHANGELOG.md', 'utf8').split(/\r?\n/) | |
| const start = lines.indexOf(`## ${tag}`) | |
| if (start < 0) throw new Error(`No changelog entry found for ${tag}`) | |
| const rest = lines.slice(start + 1) | |
| const end = rest.findIndex(line => line.startsWith('## ')) | |
| const notes = rest.slice(0, end < 0 ? undefined : end).join('\n').trim() | |
| if (!notes) throw new Error(`Empty changelog entry for ${tag}`) | |
| writeFileSync(join(process.env.RUNNER_TEMP, 'release-notes.md'), `${notes}\n`) | |
| } | |
| appendFileSync(process.env.GITHUB_OUTPUT, `tag=${tag}\nsource_sha=${sha}\npublish=${Boolean(tag)}\n`) | |
| NODE | |
| - name: Prepare draft Release | |
| if: steps.target.outputs.publish == 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| TAG_NAME: ${{ steps.target.outputs.tag }} | |
| run: | | |
| if gh release view "$TAG_NAME" >/dev/null 2>&1; then | |
| gh release edit "$TAG_NAME" --title "$TAG_NAME" --notes-file "$RUNNER_TEMP/release-notes.md" | |
| else | |
| gh release create "$TAG_NAME" --verify-tag --draft \ | |
| --title "$TAG_NAME" --notes-file "$RUNNER_TEMP/release-notes.md" | |
| fi | |
| build: | |
| name: Build ${{ matrix.name }} | |
| needs: prepare | |
| runs-on: ${{ matrix.runner }} | |
| # 需求:30 分钟在 macOS 上不够。Build application 一步包含 electron-builder 的 | |
| # dmg+zip 双目标构建、签名与公证(公证每份产物都要等 Apple 的队列,一个 job 两份), | |
| # v2.2.4 首次启用公证后两个 macOS job 都在 30:20 被 timeout 掐死,publish 因此被跳过、 | |
| # Release 永远停在草稿。Windows/Ubuntu 实测 6 分钟内完成,上调只放宽上限,不拖慢它们。 | |
| timeout-minutes: 60 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - name: Windows | |
| id: windows | |
| platform: windows | |
| runner: windows-latest | |
| arch: x64 | |
| artifact_path: dist/*.exe | |
| - name: Ubuntu | |
| id: linux | |
| platform: linux | |
| runner: ubuntu-latest | |
| arch: x64 | |
| artifact_path: dist/*.AppImage | |
| - name: macOS Apple Silicon | |
| id: macos-arm64 | |
| platform: macos | |
| runner: macos-15 | |
| arch: arm64 | |
| artifact_path: | | |
| dist/*-arm64-mac.dmg | |
| dist/*-arm64-mac.zip | |
| - name: macOS Intel | |
| id: macos-x64 | |
| platform: macos | |
| runner: macos-15-intel | |
| arch: x64 | |
| artifact_path: | | |
| dist/*-x64-mac.dmg | |
| dist/*-x64-mac.zip | |
| steps: | |
| - name: Check out source | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ needs.prepare.outputs.source_sha }} | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| # Keep CI and packaging on a runtime that provides node:sqlite. | |
| node-version: 24.16.0 | |
| cache: npm | |
| - name: Install dependencies | |
| run: npm ci --no-audit --no-fund | |
| - name: Check macOS signing credentials | |
| # 需求:在打包前把缺失的 secret 一次性点名。 | |
| # 原先 Windows 与 macOS 都故意不签名,因为当时两个平台都没有证书;现在 macOS 有 | |
| # Developer ID 了,不签名的包既过不了 Gatekeeper,也过不了 Squirrel.Mac 的 | |
| # designated requirement 校验(更新包下载成功但装不上,见 src/main/update/update-service.ts)。 | |
| # 而"签名成功、公证被静默跳过"是最贵的失败:CI 全绿,用户拿到的包依旧报警。 | |
| # 不满足会怎样:构建日志里只有一行 "skipped macOS notarization",没人会注意到。 | |
| # 拆除条件:改用 App Store Connect API Key 公证时,把下面的名字换成 APPLE_API_KEY 那三个。 | |
| if: matrix.platform == 'macos' | |
| env: | |
| CSC_LINK: ${{ secrets.CSC_LINK }} | |
| CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} | |
| APPLE_ID: ${{ secrets.APPLE_ID }} | |
| APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} | |
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| run: | | |
| missing='' | |
| for name in CSC_LINK CSC_KEY_PASSWORD APPLE_ID APPLE_APP_SPECIFIC_PASSWORD APPLE_TEAM_ID; do | |
| if [ -z "${!name}" ]; then missing="$missing $name"; fi | |
| done | |
| if [ -n "$missing" ]; then | |
| echo "::error::Missing repository secrets:$missing" | |
| exit 1 | |
| fi | |
| echo "macOS signing credentials present." | |
| - name: Build application | |
| run: npm run dist:ci -- --${{ matrix.arch }} | |
| env: | |
| # ★ 这一行必须对 macOS 打开。关掉它时 findIdentity() 在没有 CSC_NAME 的情况下直接 | |
| # 返回 null,macPackager 随即 return false —— 签名和公证会一起静默跳过, | |
| # 连 CSC_LINK 都不会被读。Windows / Linux 仍然保持未签名(本流程的既有决定)。 | |
| CSC_IDENTITY_AUTO_DISCOVERY: ${{ matrix.platform == 'macos' && 'true' || 'false' }} | |
| # 只在 macOS 行注入证书。CSC_LINK 不能设成全局:Windows 上 getCscLink() 会退回到 | |
| # CSC_LINK(platformPackager),拿 mac 的 p12 去签 exe 必然失败。 | |
| CSC_LINK: ${{ matrix.platform == 'macos' && secrets.CSC_LINK || '' }} | |
| CSC_KEY_PASSWORD: ${{ matrix.platform == 'macos' && secrets.CSC_KEY_PASSWORD || '' }} | |
| APPLE_ID: ${{ matrix.platform == 'macos' && secrets.APPLE_ID || '' }} | |
| APPLE_APP_SPECIFIC_PASSWORD: ${{ matrix.platform == 'macos' && secrets.APPLE_APP_SPECIFIC_PASSWORD || '' }} | |
| APPLE_TEAM_ID: ${{ matrix.platform == 'macos' && secrets.APPLE_TEAM_ID || '' }} | |
| - name: Upload platform assets to Release | |
| if: needs.prepare.outputs.publish == 'true' | |
| shell: bash | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| TAG_NAME: ${{ needs.prepare.outputs.tag }} | |
| BUILD_PLATFORM: ${{ matrix.platform }} | |
| BUILD_ARCH: ${{ matrix.arch }} | |
| run: | | |
| node --input-type=module <<'NODE' | |
| import { execFileSync } from 'node:child_process' | |
| import { ARTIFACTS, collectArtifacts } from './scripts/upload-client-release.mjs' | |
| const { TAG_NAME: tag, BUILD_PLATFORM: platform, BUILD_ARCH: architecture } = process.env | |
| const { found } = await collectArtifacts('dist', false, tag.slice(1)) | |
| const expected = ARTIFACTS.filter(item => item.platform === platform && item.architecture === architecture) | |
| const assets = found.filter(item => item.platform === platform && item.architecture === architecture) | |
| if (expected.length === 0 || assets.length !== expected.length) { | |
| throw new Error(`Missing release assets for ${platform}/${architecture}`) | |
| } | |
| execFileSync('gh', ['release', 'upload', tag, ...assets.map(item => item.path), '--clobber'], { stdio: 'inherit' }) | |
| NODE | |
| - name: Upload build-only artifacts | |
| if: needs.prepare.outputs.publish != 'true' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: nextcowork-${{ matrix.id }} | |
| path: ${{ matrix.artifact_path }} | |
| if-no-files-found: error | |
| retention-days: 1 | |
| publish: | |
| name: Publish GitHub Release | |
| if: needs.prepare.outputs.publish == 'true' | |
| needs: [prepare, build] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| TAG_NAME: ${{ needs.prepare.outputs.tag }} | |
| steps: | |
| - name: Check out release tag | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ needs.prepare.outputs.source_sha }} | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24.16.0 | |
| - name: Download platform artifacts | |
| run: gh release download "$TAG_NAME" --dir release-assets --pattern '*.exe' --pattern '*.AppImage' --pattern '*-mac.dmg' --pattern '*-mac.zip' | |
| - name: Verify client artifacts before publishing | |
| run: node scripts/upload-client-release.mjs --dir release-assets --version "${TAG_NAME#v}" --require-all --dry-run | |
| - name: Publish complete Release | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| awk -v tag="$TAG_NAME" ' | |
| $0 == "## " tag { found = 1; next } | |
| found && /^## / { exit } | |
| found { print } | |
| ' CHANGELOG.md > "$RUNNER_TEMP/release-notes.md" | |
| if ! grep -q '[^[:space:]]' "$RUNNER_TEMP/release-notes.md"; then | |
| echo "No changelog entry found for $TAG_NAME" >&2 | |
| exit 1 | |
| fi | |
| gh release edit "$TAG_NAME" --draft=false --title "$TAG_NAME" --notes-file "$RUNNER_TEMP/release-notes.md" | |
| - name: Upload client artifacts to NextCoWork API | |
| env: | |
| CLIENT_UPLOAD_TOKEN: ${{ secrets.CLIENT_UPLOAD_TOKEN }} | |
| CLIENT_UPDATE_BASE_URL: https://nextco.work | |
| # Notes used to be a bare GitHub Release URL: the API read them from a plain HTTP header, | |
| # which cannot carry multiline Chinese text. The upload script now base64-encodes the full | |
| # changelog section via X-Client-Release-Notes-B64, so the client receives the notes itself | |
| # (the electron-updater feed only forwards fields present in latest*.yml — see the feed | |
| # endpoint, which now emits releaseNotes/mandatory/minimumSupportedVersion/graceUntil). | |
| # release-notes.md is cut from CHANGELOG.md by the "Publish complete Release" step above. | |
| run: node scripts/upload-client-release.mjs --dir release-assets --version "${TAG_NAME#v}" --base-url "$CLIENT_UPDATE_BASE_URL" --notes-file "$RUNNER_TEMP/release-notes.md" --require-all --ignore-duplicates |