Files
dingtalk-workspace-cli/.github/workflows/release.yml
T
修雨 73e010a992 fix: make Gitee release sync resilient (#622)
* fix: make Gitee release sync resilient

* fix: address Gitee sync review feedback

* fix: bound the full Gitee sync path
2026-07-15 15:28:04 +08:00

343 lines
14 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: Release
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
repair_npm_version:
description: "Only publish an existing release to npm, e.g. v1.0.48"
required: false
type: string
permissions:
contents: write
jobs:
release:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.repair_npm_version == '' }}
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check Homebrew PR automation token
if: ${{ github.repository_owner == 'DingTalk-Real-AI' }}
# Organization policy intentionally prevents the broad, built-in
# GITHUB_TOKEN from creating PRs. Keep Formula automation on a
# repository-scoped token instead of weakening that policy.
env:
HOMEBREW_PR_TOKEN: ${{ secrets.HOMEBREW_PR_TOKEN }}
run: |
if [ -z "${HOMEBREW_PR_TOKEN:-}" ]; then
echo "HOMEBREW_PR_TOKEN is required to open Formula PRs from official releases" >&2
exit 1
fi
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Install archive tooling
run: sudo apt-get update && sudo apt-get install -y zip unzip
- name: Multi Profile E2E
run: bash scripts/dev/test-multi-profile-e2e.sh
- name: Install rcodesign (sign darwin binaries from Linux)
run: |
set -euo pipefail
RCS_VERSION="0.29.0"
RCS_ARCHIVE_SHA256="dbe85cedd8ee4217b64e9a0e4c2aef92ab8bcaaa41f20bde99781ff02e600002"
curl -fsSL -o /tmp/rcodesign.tar.gz \
"https://github.com/indygreg/apple-platform-rs/releases/download/apple-codesign%2F${RCS_VERSION}/apple-codesign-${RCS_VERSION}-x86_64-unknown-linux-musl.tar.gz"
printf '%s %s\n' "$RCS_ARCHIVE_SHA256" /tmp/rcodesign.tar.gz \
| sha256sum --check --strict -
mkdir -p /tmp/rcodesign
tar -xzf /tmp/rcodesign.tar.gz -C /tmp/rcodesign --strip-components=1
sudo install -m 0755 /tmp/rcodesign/rcodesign /usr/local/bin/rcodesign
rcodesign --version
- name: Prepare Apple Developer ID certificate
env:
APPLE_CERTIFICATE_P12_BASE64: ${{ secrets.APPLE_CERTIFICATE_P12_BASE64 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
run: |
set -euo pipefail
if [ -z "${APPLE_CERTIFICATE_P12_BASE64:-}" ] || [ -z "${APPLE_CERTIFICATE_PASSWORD:-}" ]; then
if [ "$GITHUB_REPOSITORY_OWNER" = "DingTalk-Real-AI" ]; then
echo "APPLE_CERTIFICATE_P12_BASE64 and APPLE_CERTIFICATE_PASSWORD are required for official releases" >&2
exit 1
fi
echo "Developer ID secrets are unavailable; fork release will use ad-hoc signing."
exit 0
fi
umask 077
certificate_path="$RUNNER_TEMP/dws-developer-id.p12"
password_path="$RUNNER_TEMP/dws-developer-id-password"
printf '%s' "$APPLE_CERTIFICATE_P12_BASE64" | base64 --decode > "$certificate_path"
printf '%s' "$APPLE_CERTIFICATE_PASSWORD" > "$password_path"
# Fail before packaging if the secret is corrupt or the password is wrong.
# The exported P12 may use legacy PKCS#12 ciphers; OpenSSL 3 requires
# -legacy to validate those containers even though rcodesign can read them.
openssl pkcs12 -legacy -in "$certificate_path" -passin "file:$password_path" -noout
echo "DWS_APPLE_CERTIFICATE_P12=$certificate_path" >> "$GITHUB_ENV"
echo "DWS_APPLE_CERTIFICATE_PASSWORD_FILE=$password_path" >> "$GITHUB_ENV"
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
version: "~> v2"
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Post-release packaging
run: ./scripts/release/post-goreleaser.sh
env:
DWS_PACKAGE_VERSION: ${{ github.ref_name }}
DWS_REQUIRE_DEVELOPER_ID_SIGNING: ${{ github.repository_owner == 'DingTalk-Real-AI' }}
- name: Remove Apple Developer ID certificate
if: ${{ always() }}
run: |
rm -f "$RUNNER_TEMP/dws-developer-id.p12"
rm -f "$RUNNER_TEMP/dws-developer-id-password"
# GoReleaser uploads the original archives to a Draft before
# post-goreleaser.sh replaces the Darwin binaries. Re-upload every changed
# file, verify the Draft digests, and keep it private for Apple validation.
- name: Upload finalized signed assets to release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DWS_PUBLISH_RELEASE: "false"
run: ./scripts/release/finalize-github-release.sh
- name: Preserve finalized distribution files
uses: actions/upload-artifact@v4
with:
name: finalized-release-dist
path: dist/
if-no-files-found: error
retention-days: 1
verify-darwin-signatures:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.repair_npm_version == '' }}
needs: release
runs-on: macos-latest
timeout-minutes: 10
steps:
- name: Download finalized Darwin assets from Draft release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
mkdir -p dist
gh release download "$GITHUB_REF_NAME" \
--repo "$GITHUB_REPOSITORY" \
--dir dist \
--pattern 'dws-darwin-amd64.tar.gz' \
--pattern 'dws-darwin-arm64.tar.gz' \
--clobber
- name: Verify finalized Darwin signatures with Apple codesign
run: |
set -euo pipefail
for arch in amd64 arm64; do
archive="dist/dws-darwin-${arch}.tar.gz"
stage="$RUNNER_TEMP/verify-darwin-${arch}"
mkdir -p "$stage"
tar -xzf "$archive" -C "$stage"
test -f "$stage/dws"
codesign --verify --strict --verbose=4 "$stage/dws"
codesign -dvvv "$stage/dws"
done
publish-release:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.repair_npm_version == '' }}
needs:
- release
- verify-darwin-signatures
runs-on: ubuntu-latest
# The optional Gitee fallback has its own bounded retry budget and may be
# enabled during a cross-border incident. Normal releases skip that step.
timeout-minutes: 120
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Restore finalized distribution files
uses: actions/download-artifact@v4
with:
name: finalized-release-dist
path: dist
- name: Publish verified Draft release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release edit "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --draft=false
- name: Open stable Homebrew formula PR
# Beta builds must never replace the stable Homebrew formula. Formula
# updates use the normal PR path instead of writing main from a tag job.
if: ${{ github.repository_owner == 'DingTalk-Real-AI' && !contains(github.ref_name, '-') }}
run: ./scripts/release/publish-homebrew-formula.sh
env:
DWS_TAP_REPO_URL: https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli.git
DWS_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_PR_TOKEN }}
DWS_TAP_PR_REPOSITORY: ${{ github.repository }}
DWS_TAP_PR_BRANCH: "automation/homebrew-${{ github.ref_name }}"
DWS_TAP_PR_TITLE: "chore: update Homebrew formula for ${{ github.ref_name }}"
DWS_TAP_COMMIT_MESSAGE: "chore: update formula for ${{ github.ref_name }}"
- name: Open beta Homebrew formula PR
# Keep beta in a separately named, keg-only Formula so it cannot replace the
# stable dws link for ordinary Homebrew users.
if: ${{ github.repository_owner == 'DingTalk-Real-AI' && contains(github.ref_name, '-') }}
run: ./scripts/release/publish-homebrew-formula.sh
env:
DWS_FORMULA_SOURCE: dist/homebrew/dingtalk-workspace-cli-beta.rb
DWS_TAP_FORMULA_PATH: Formula/dingtalk-workspace-cli-beta.rb
DWS_TAP_REPO_URL: https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli.git
DWS_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_PR_TOKEN }}
DWS_TAP_PR_REPOSITORY: ${{ github.repository }}
DWS_TAP_PR_BRANCH: "automation/homebrew-beta-${{ github.ref_name }}"
DWS_TAP_PR_TITLE: "chore: update Homebrew beta formula for ${{ github.ref_name }}"
DWS_TAP_COMMIT_MESSAGE: "chore: update beta formula for ${{ github.ref_name }}"
- name: Sync release to China OSS mirror
# 自动同步到国内镜像,供 install.sh 的 DWS_RELEASE_BASE 开关消费。
# 脚本自带门控:未配置 OSS_* secret 时优雅跳过,不影响海外发布。
run: ./scripts/release/sync-to-oss.sh
env:
VERSION: ${{ github.ref_name }}
OSS_ACCESS_KEY_ID: ${{ secrets.OSS_ACCESS_KEY_ID }}
OSS_ACCESS_KEY_SECRET: ${{ secrets.OSS_ACCESS_KEY_SECRET }}
OSS_ENDPOINT: ${{ secrets.OSS_ENDPOINT }}
OSS_BUCKET: ${{ secrets.OSS_BUCKET }}
OSS_PREFIX: ${{ secrets.OSS_PREFIX }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
registry-url: "https://registry.npmjs.org"
- name: Publish stable to npm
# 只有官方仓库发 npm;fork(dev 预览)没有 NPM_TOKEN,跳过以免红叉。
# 必须在 Gitee mirror 前发布:Gitee 附件上传偶发长时间挂住,不能阻塞 npm/latest。
if: ${{ github.repository_owner == 'DingTalk-Real-AI' && !contains(github.ref_name, '-') }}
working-directory: dist/npm/dingtalk-workspace-cli
run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Publish prerelease to npm beta
# 预发布版本不能更新 npm latest,避免普通 npm 安装链路拿到 beta。
if: ${{ github.repository_owner == 'DingTalk-Real-AI' && contains(github.ref_name, '-') }}
working-directory: dist/npm/dingtalk-workspace-cli
run: npm publish --access public --tag beta
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
mirror-gitee-release:
# Keep the optional cross-border fallback out of publish-release so all
# pre-sync work has an independently provable budget.
if: ${{ vars.ENABLE_GITEE_UPLOAD_FALLBACK == 'true' }}
needs:
- release
- publish-release
runs-on: ubuntu-latest
timeout-minutes: 120
steps:
- name: Check out repository
uses: actions/checkout@v4
timeout-minutes: 5
- name: Restore finalized distribution files
uses: actions/download-artifact@v4
timeout-minutes: 10
with:
name: finalized-release-dist
path: dist
- name: Mirror release to Gitee (China)
timeout-minutes: 100
run: ./scripts/release/sync-to-gitee.sh
env:
VERSION: ${{ github.ref_name }}
GITEE_TOKEN: ${{ secrets.GITEE_TOKEN }}
GITEE_USER: ${{ secrets.GITEE_USER }}
GITEE_REPO: ${{ secrets.GITEE_REPO }}
repair-npm:
if: ${{ github.event_name == 'workflow_dispatch' && inputs.repair_npm_version != '' }}
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Download GitHub release assets
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -eu
mkdir -p dist
gh release download "${{ inputs.repair_npm_version }}" \
--repo "${{ github.repository }}" \
--dir dist \
--pattern 'dws-*' \
--pattern 'checksums.txt' \
--clobber
ls -la dist
- name: Stage npm package
run: |
set -eu
version="${{ inputs.repair_npm_version }}"
semver="${version#v}"
pkg_root="dist/npm/dingtalk-workspace-cli"
rm -rf "$pkg_root"
mkdir -p "$pkg_root/assets" "$pkg_root/bin"
cp build/npm/install.js "$pkg_root/install.js"
cp build/npm/bin/dws.js "$pkg_root/bin/dws.js"
cp build/npm/README.md "$pkg_root/README.md"
sed "s|__VERSION__|${semver}|g" build/npm/package.json.tmpl > "$pkg_root/package.json"
cp dist/dws-* "$pkg_root/assets/"
cp dist/checksums.txt "$pkg_root/assets/"
test -f "$pkg_root/assets/dws-skills.zip"
cat "$pkg_root/package.json"
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
registry-url: "https://registry.npmjs.org"
- name: Publish stable to npm
if: ${{ github.repository_owner == 'DingTalk-Real-AI' && !contains(inputs.repair_npm_version, '-') }}
working-directory: dist/npm/dingtalk-workspace-cli
run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Publish prerelease to npm beta
if: ${{ github.repository_owner == 'DingTalk-Real-AI' && contains(inputs.repair_npm_version, '-') }}
working-directory: dist/npm/dingtalk-workspace-cli
run: npm publish --access public --tag beta
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}