feat(release): add guarded prerelease and stable pipeline (#54)

* feat(release): add guarded prerelease and stable pipeline

* feat(release): add guided dws-release entry

(cherry picked from commit f7fa7b78f325f3574f0487862fc3e65bba5cdc96)
This commit is contained in:
修雨
2026-07-16 10:26:18 +08:00
committed by 修雨
parent e615bd433c
commit 22d19863fb
32 changed files with 4327 additions and 591 deletions
+6
View File
@@ -0,0 +1,6 @@
paths:
.github/workflows/release.yml:
ignore:
# GitHub Actions added concurrency.queue in 2026. actionlint v1.7.12's
# bundled workflow schema has not caught up with the platform syntax.
- 'unexpected key "queue" for "concurrency" section'
+123 -29
View File
@@ -63,10 +63,17 @@ jobs:
# package timeout aligned with the macOS race job so the Linux runner's
# five-minute default does not expire while that gate is still making
# progress.
run: go test -v -race -count=1 -timeout=10m ./cmd/... ./internal/...
run: |
go test -v -race -count=1 -timeout=10m \
./cmd/... \
./internal/... \
./pkg/... \
./test/unit/...
go test -v -count=1 -timeout=5m \
./test/cli/...
- name: Test release scripts
run: go test -v -count=1 -timeout=5m ./test/scripts
- name: Test engineering scripts
run: go test -v -count=1 -timeout=5m ./test/scripts/...
test-darwin:
name: Test (macOS auth/keychain)
@@ -408,41 +415,25 @@ jobs:
- name: Run edition contract tests
run: go test -v -count=1 ./pkg/editiontest/...
ci-gate:
name: CI Gate
needs:
- lint
- test
- test-darwin
- test-windows
- coverage
- coverage-darwin
- coverage-windows
- policy
- interface-integrity
- cli-smoke
- mock-mcp-smoke
- edition-tests
code-check:
name: Code Check
needs: [lint, test, test-darwin, test-windows, coverage, policy, edition-tests]
if: ${{ always() }}
runs-on: ubuntu-latest
timeout-minutes: 5
permissions: {}
steps:
- name: Verify required checks
- name: Verify code checks
env:
LINT_RESULT: ${{ needs.lint.result }}
TEST_RESULT: ${{ needs.test.result }}
TEST_DARWIN_RESULT: ${{ needs.test-darwin.result }}
TEST_WINDOWS_RESULT: ${{ needs.test-windows.result }}
COVERAGE_RESULT: ${{ needs.coverage.result }}
COVERAGE_DARWIN_RESULT: ${{ needs.coverage-darwin.result }}
COVERAGE_WINDOWS_RESULT: ${{ needs.coverage-windows.result }}
POLICY_RESULT: ${{ needs.policy.result }}
INTERFACE_INTEGRITY_RESULT: ${{ needs.interface-integrity.result }}
CLI_SMOKE_RESULT: ${{ needs.cli-smoke.result }}
MOCK_MCP_SMOKE_RESULT: ${{ needs.mock-mcp-smoke.result }}
EDITION_TESTS_RESULT: ${{ needs.edition-tests.result }}
run: |
set -eu
failed=0
for check in \
"Lint:$LINT_RESULT" \
@@ -450,12 +441,7 @@ jobs:
"Test (macOS auth/keychain):$TEST_DARWIN_RESULT" \
"Test (Windows):$TEST_WINDOWS_RESULT" \
"Coverage:$COVERAGE_RESULT" \
"Coverage (macOS):$COVERAGE_DARWIN_RESULT" \
"Coverage (Windows):$COVERAGE_WINDOWS_RESULT" \
"Policy Check:$POLICY_RESULT" \
"Interface Integrity:$INTERFACE_INTEGRITY_RESULT" \
"CLI Smoke:$CLI_SMOKE_RESULT" \
"Mock MCP Smoke:$MOCK_MCP_SMOKE_RESULT" \
"Edition Contract Tests:$EDITION_TESTS_RESULT"
do
name="${check%%:*}"
@@ -467,6 +453,114 @@ jobs:
done
test "$failed" -eq 0
command-compatibility:
name: Command Compatibility
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
actions: read
contents: read
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Fetch official release tags
run: |
git fetch --force --no-tags \
https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli.git \
'+refs/tags/v*:refs/tags/v*'
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Check command compatibility
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
PUSH_BEFORE_SHA: ${{ github.event.before }}
run: |
set -eu
base_ref="$PUSH_BEFORE_SHA"
if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then
base_ref="$PR_BASE_SHA"
fi
stable_ref="$(git tag --merged HEAD --list 'v*' --sort=-version:refname | awk '/^v[0-9]+\.[0-9]+\.[0-9]+$/ { print; exit }')"
test -n "$base_ref"
test -n "$stable_ref"
stable_commit="$(git rev-parse "$stable_ref^{commit}")"
./scripts/release/verify-delivered-stable.sh "$stable_ref" "$stable_commit"
./scripts/policy/check-command-compatibility.sh \
--base-ref "$base_ref" \
--stable-ref "$stable_ref"
cli-smoke:
name: CLI Smoke
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Run CLI smoke tests
run: go test -count=1 -timeout=5m ./test/smoke/...
mock-mcp-smoke:
name: Mock MCP Smoke
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Run Mock MCP smoke tests
run: go test -count=1 -timeout=5m ./test/mock_mcp/...
ci-gate:
name: CI Gate
needs: [code-check, command-compatibility, cli-smoke, mock-mcp-smoke]
if: ${{ always() }}
runs-on: ubuntu-latest
timeout-minutes: 5
permissions: {}
steps:
- name: Verify required checks
env:
CODE_CHECK_RESULT: ${{ needs.code-check.result }}
COMMAND_COMPATIBILITY_RESULT: ${{ needs.command-compatibility.result }}
CLI_SMOKE_RESULT: ${{ needs.cli-smoke.result }}
MOCK_MCP_SMOKE_RESULT: ${{ needs.mock-mcp-smoke.result }}
run: |
set -eu
failed=0
for check in \
"Code Check:$CODE_CHECK_RESULT" \
"Command Compatibility:$COMMAND_COMPATIBILITY_RESULT" \
"CLI Smoke:$CLI_SMOKE_RESULT" \
"Mock MCP Smoke:$MOCK_MCP_SMOKE_RESULT"
do
name="${check%%:*}"
result="${check#*:}"
printf '%s: %s\n' "$name" "$result"
if [ "$result" != "success" ]; then
failed=1
fi
done
test "$failed" -eq 0
notify-downstream:
name: Notify Wukong Overlay
needs: [ci-gate]
+7 -17
View File
@@ -1,4 +1,5 @@
# 把本仓库代码自动镜像到 Gitee,供国内用户访问(raw 脚本入口 + tags)。
# 把本仓库 main 代码自动镜像到 Gitee,供国内用户访问 raw 脚本入口。
# Release tag 与附件只由 release.yml 的受控 publication queue 发布。
# 用 HTTPS + 令牌直接 git push(无需 SSH key),复用已配置的 secret:
# GITEE_TOKEN —— Gitee 私人令牌(勾 projects)
# GITEE_USER —— 令牌所属 Gitee 用户名(用于 https 推送鉴权)
@@ -10,8 +11,6 @@ on:
push:
branches:
- main
tags:
- 'v*'
schedule:
- cron: '0 18 * * *'
workflow_dispatch:
@@ -23,13 +22,14 @@ concurrency:
jobs:
mirror:
runs-on: ubuntu-latest
if: ${{ github.ref_name == github.event.repository.default_branch && github.repository_owner == 'DingTalk-Real-AI' }}
# GitHub Actions 不允许在 job-level if 直接引用 secrets,故先用 env 暴露再在 step 守卫。
env:
GITEE_TOKEN: ${{ secrets.GITEE_TOKEN }}
GITEE_USER: ${{ secrets.GITEE_USER }}
GITEE_REPO: ${{ secrets.GITEE_REPO }}
steps:
- name: Checkout (full history + tags)
- name: Checkout main history
if: env.GITEE_TOKEN != ''
uses: actions/checkout@v4
with:
@@ -41,14 +41,7 @@ jobs:
set -eu
REMOTE="https://${GITEE_USER}:${GITEE_TOKEN}@gitee.com/${GITEE_REPO}.git"
if [ "${GITHUB_REF_TYPE:-}" = "tag" ]; then
VERSION="$GITHUB_REF_NAME" ./scripts/release/sync-gitee-tag.sh
echo "✅ 已镜像 tag ${GITHUB_REF_NAME} 到 Gitee ${GITEE_REPO}"
exit 0
fi
# 取到 main 与所有 tag(落到 origin/* 与本地 tags,避免推当前分支引用冲突)
git fetch --force --tags origin 'refs/heads/main:refs/remotes/origin/main'
git fetch --force origin 'refs/heads/main:refs/remotes/origin/main'
# Gitee 专属分支:在 origin/main 之上叠加一个 README 本地化 commit。
# GitHub 那份 README 不变;只有推往 Gitee 的副本被改写。
@@ -80,9 +73,6 @@ jobs:
git add README.md README_zh.md 2>/dev/null || true
git commit -m "docs(gitee): localize install commands + coverage badge for Gitee mirror" || true
# 镜像对齐(force:Gitee 始终跟随 GitHub + Gitee 专属 README 本地化)
# main 镜像对齐;release tag 由 release.yml 单独校验后创建,禁止在这里 force。
git push --force "$REMOTE" 'gitee-main:refs/heads/main'
# Release tags are immutable. Push only missing tags and fail closed
# on a conflicting existing ref instead of trying to move it.
timeout --signal=TERM 180s git push --tags "$REMOTE"
echo "✅ 已镜像 main(+Gitee README 本地化) + tags 到 Gitee ${GITEE_REPO}"
echo "✅ 已镜像 main(含 Gitee README 本地化)到 Gitee ${GITEE_REPO}"
-71
View File
@@ -1,71 +0,0 @@
name: Publish npm release
on:
workflow_dispatch:
inputs:
version:
description: "Release tag to publish to npm (e.g. v1.0.48)"
required: true
type: string
permissions:
contents: read
jobs:
publish-npm:
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.version }}" \
--repo "${{ github.repository }}" \
--dir dist \
--pattern 'dws-*' \
--pattern 'checksums.txt' \
--clobber
ls -la dist
- name: Stage npm package
run: |
set -eu
version="${{ inputs.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.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.version, '-') }}
working-directory: dist/npm/dingtalk-workspace-cli
run: npm publish --access public --tag beta
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
File diff suppressed because it is too large Load Diff
@@ -1,55 +0,0 @@
name: Sync release to Gitee
# Manually mirror a published GitHub release's assets to the matching Gitee
# release. Use this to repair a release whose Gitee mirror is incomplete (e.g.
# the Release job timed out mid-upload). It runs ONLY the idempotent Gitee sync
# step — it does not run GoReleaser and does not touch the GitHub release, so
# there is no release outage. The sync script skips assets already on Gitee, so
# this only uploads what is missing.
on:
workflow_dispatch:
inputs:
version:
description: "Release tag to mirror to Gitee (e.g. v1.0.42)"
required: true
type: string
permissions:
contents: read
jobs:
sync-gitee:
runs-on: ubuntu-latest
# Each step has its own ceiling. Their 115-minute sum leaves five minutes
# for runner scheduling/teardown inside this 120-minute job deadline.
timeout-minutes: 120
steps:
- name: Check out repository
uses: actions/checkout@v4
timeout-minutes: 5
- name: Download GitHub release assets
timeout-minutes: 10
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -eu
mkdir -p dist
gh release download "${{ inputs.version }}" \
--repo "${{ github.repository }}" \
--dir dist \
--pattern 'dws-*' \
--pattern 'checksums.txt' \
--clobber
ls -la dist
- name: Mirror release to Gitee (China)
# Idempotent: uploads only assets not already present on the Gitee release.
timeout-minutes: 100
run: ./scripts/release/sync-to-gitee.sh
env:
VERSION: ${{ inputs.version }}
GITEE_TOKEN: ${{ secrets.GITEE_TOKEN }}
GITEE_USER: ${{ secrets.GITEE_USER }}
GITEE_REPO: ${{ secrets.GITEE_REPO }}