From d6c5ccbc1611504d04eb03ba84c14b27f51d2668 Mon Sep 17 00:00:00 2001 From: "lixuefei.1313" Date: Tue, 24 Feb 2026 20:01:36 +0800 Subject: [PATCH 1/2] feat: optimiz workflows for release --- .github/workflows/develop-synced-dispatch.yml | 59 +++++++ .github/workflows/post-release.yml | 158 ++++++++++++++++++ .github/workflows/release.yml | 51 ++---- .github/workflows/sync-main-to-develop.yml | 32 ++++ 4 files changed, 264 insertions(+), 36 deletions(-) create mode 100644 .github/workflows/develop-synced-dispatch.yml create mode 100644 .github/workflows/post-release.yml create mode 100644 .github/workflows/sync-main-to-develop.yml diff --git a/.github/workflows/develop-synced-dispatch.yml b/.github/workflows/develop-synced-dispatch.yml new file mode 100644 index 000000000..3a568b182 --- /dev/null +++ b/.github/workflows/develop-synced-dispatch.yml @@ -0,0 +1,59 @@ +name: Dispatch develop-synced after release + +on: + push: + branches: + - develop + +jobs: + dispatch_develop_synced: + if: contains(github.event.head_commit.message, 'prelease version') + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Checkout develop + uses: actions/checkout@v4 + with: + ref: develop + + - name: Extract version from commit message + id: meta + env: + COMMIT_MESSAGE: ${{ github.event.head_commit.message }} + run: | + set -euo pipefail + echo "Commit message: ${COMMIT_MESSAGE}" + VERSION_WITH_V="$(echo "${COMMIT_MESSAGE}" | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' || true)" + if [ -n "${VERSION_WITH_V}" ]; then + VERSION="${VERSION_WITH_V#v}" + else + VERSION="$(echo "${COMMIT_MESSAGE}" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1 || true)" + fi + if [ -z "${VERSION}" ]; then + echo "Failed to parse version from commit message" >&2 + exit 1 + fi + echo "Parsed version: ${VERSION}" + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + + - name: Send develop-synced repository_dispatch + uses: actions/github-script@v7 + env: + VERSION: ${{ steps.meta.outputs.version }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const version = process.env.VERSION; + if (!version) { + core.setFailed('VERSION env is not set'); + return; + } + core.info(`Sending repository_dispatch develop-synced for version ${version}`); + await github.rest.repos.createDispatchEvent({ + owner: context.repo.owner, + repo: context.repo.repo, + event_type: 'develop-synced', + client_payload: { version } + }); diff --git a/.github/workflows/post-release.yml b/.github/workflows/post-release.yml new file mode 100644 index 000000000..4360f73e6 --- /dev/null +++ b/.github/workflows/post-release.yml @@ -0,0 +1,158 @@ +name: Post release after develop synced + +on: + repository_dispatch: + types: [develop-synced] + +jobs: + post_release: + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Read version from repository_dispatch payload + id: meta + env: + PAYLOAD_VERSION: ${{ github.event.client_payload.version }} + run: | + set -euo pipefail + if [ -z "${PAYLOAD_VERSION}" ]; then + echo "No version in client_payload, skip post-release." + echo "skip=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + echo "Using version from payload: ${PAYLOAD_VERSION}" + echo "version=${PAYLOAD_VERSION}" >> "$GITHUB_OUTPUT" + echo "skip=false" >> "$GITHUB_OUTPUT" + + - name: Checkout main + if: steps.meta.outputs.skip != 'true' + uses: actions/checkout@v4 + with: + ref: main + fetch-depth: 0 + persist-credentials: false + + - name: Rebind origin to PAT remote + if: steps.meta.outputs.skip != 'true' + env: + GH_PAT: ${{ secrets.CREATE_TAG_RELEASE_TOKEN }} + run: | + set -euo pipefail + if [ -z "${GH_PAT}" ]; then + echo "CREATE_TAG_RELEASE_TOKEN is not configured." + exit 1 + fi + git remote set-url origin "https://x-access-token:${GH_PAT}@github.com/${GITHUB_REPOSITORY}.git" + + - name: Fetch tags + if: steps.meta.outputs.skip != 'true' + run: | + set -euo pipefail + git fetch --tags --force + + - name: Check existing tag and release + id: exist + if: steps.meta.outputs.skip != 'true' + env: + VERSION: ${{ steps.meta.outputs.version }} + GH_TOKEN: ${{ secrets.CREATE_TAG_RELEASE_TOKEN }} + run: | + set -euo pipefail + TAG="v${VERSION}" + if git rev-parse "refs/tags/${TAG}" >/dev/null 2>&1; then + echo "Tag ${TAG} already exists, skip post-release." + echo "skip=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + if gh release view "${TAG}" >/dev/null 2>&1; then + echo "Release ${TAG} already exists, skip post-release." + echo "skip=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + echo "skip=false" >> "$GITHUB_OUTPUT" + + - name: Fetch develop changelog + if: steps.meta.outputs.skip != 'true' && steps.exist.outputs.skip != 'true' + run: | + set -euo pipefail + git fetch origin develop:refs/remotes/origin/develop --depth=1 + git show origin/develop:docs/assets/changelog/en/release.md > release-develop.md + + - name: Extract release body from develop changelog + id: body + if: steps.meta.outputs.skip != 'true' && steps.exist.outputs.skip != 'true' + env: + VERSION: ${{ steps.meta.outputs.version }} + run: | + set -euo pipefail + if [ ! -f "release-develop.md" ]; then + echo "develop changelog file not found, skip post-release." + echo "has_body=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + if node <<'NODE' + const fs = require('fs'); + const version = process.env.VERSION; + const content = fs.readFileSync('release-develop.md', 'utf8'); + function escapeRegExp(str) { + return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + } + const headerPattern = new RegExp('^#\\s*v?' + escapeRegExp(version) + '\\b', 'm'); + const match = headerPattern.exec(content); + if (!match) { + console.log('No changelog block for version', version, 'found in develop.'); + process.exit(1); + } + const startIndex = match.index; + const rest = content.slice(startIndex); + const nextHeaderPattern = /^#\s*v?\d+\.\d+\.\d+[^\n]*$/gm; + let nextIndex = rest.length; + let m; + if ((m = nextHeaderPattern.exec(rest)) !== null && m.index !== 0) { + nextIndex = m.index; + } + const block = rest.slice(0, nextIndex).trimEnd() + '\n'; + fs.writeFileSync('release-body.md', block, 'utf8'); + NODE + then + echo "has_body=true" >> "$GITHUB_OUTPUT" + else + echo "has_body=false" >> "$GITHUB_OUTPUT" + fi + + - name: Verify GitHub identity + if: steps.meta.outputs.skip != 'true' && steps.exist.outputs.skip != 'true' && steps.body.outputs.has_body == 'true' + env: + GH_TOKEN: ${{ secrets.CREATE_TAG_RELEASE_TOKEN }} + run: | + set -euo pipefail + gh api user -q '.login' + + - name: Create tag + if: steps.meta.outputs.skip != 'true' && steps.exist.outputs.skip != 'true' && steps.body.outputs.has_body == 'true' + env: + VERSION: ${{ steps.meta.outputs.version }} + run: | + set -euo pipefail + TAG="v${VERSION}" + git fetch origin main:refs/remotes/origin/main --depth=1 + MAIN_SHA="$(git rev-parse origin/main)" + echo "Creating tag ${TAG} at ${MAIN_SHA}" + git tag "${TAG}" "${MAIN_SHA}" + git push origin "${TAG}" + + - name: Create GitHub Release + if: steps.meta.outputs.skip != 'true' && steps.exist.outputs.skip != 'true' && steps.body.outputs.has_body == 'true' + env: + VERSION: ${{ steps.meta.outputs.version }} + GH_TOKEN: ${{ secrets.CREATE_TAG_RELEASE_TOKEN }} + run: | + set -euo pipefail + TAG="v${VERSION}" + echo "Creating GitHub Release ${TAG}" + gh release create "${TAG}" \ + --target "main" \ + --title "${TAG}" \ + --notes-file "release-body.md" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c4d5fa285..9599fbc66 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,7 +7,7 @@ on: jobs: build: - runs-on: macOS-latest # 如果用了electron,记得改成 macOS-latest + runs-on: macOS-latest permissions: contents: write pull-requests: write @@ -15,11 +15,13 @@ jobs: strategy: matrix: node-version: [18.x] - # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - - uses: actions/checkout@v3 - - run: | + - name: Checkout + uses: actions/checkout@v3 + + - name: Configure git user + run: | git config user.name ${{ github.actor }} git config user.email ${{ github.actor }}@users.noreply.github.com @@ -30,7 +32,6 @@ jobs: cache: 'npm' cache-dependency-path: './common/config/rush/pnpm-lock.yaml' - # Install rush - name: Install rush run: node common/scripts/install-run-rush.js install --bypass-policy @@ -40,9 +41,9 @@ jobs: with: path: packages/vtable semver_string: ${{ github.ref_name }} - semver_pattern: '^release/(.*)$' # ^v?(.*)$ by default + semver_pattern: '^release/(.*)$' - - name: update nextBump of version policies + - name: Update nextBump of version policies uses: xile611/set-next-bump-of-rush@main with: release_version: ${{ steps.semver_parser.outputs.full }} @@ -53,33 +54,24 @@ jobs: - name: Build packages env: - NODE_OPTIONS: "--max_old_space_size=4096" - NO_EMIT_ON_ERROR: "true" + NODE_OPTIONS: '--max_old_space_size=4096' + NO_EMIT_ON_ERROR: 'true' run: | - # 设置环境变量确保错误信息完整输出 export NODE_OPTIONS="--max_old_space_size=4096" export NO_EMIT_ON_ERROR="true" - # 运行构建,并将输出保存到文件 node common/scripts/install-run-rush.js build --only tag:package 2>&1 | tee build.log || { echo "=== Build failed, showing last 1000 lines of build.log ===" tail -n 1000 build.log echo "=== Full error details ===" - # 尝试从日志中提取错误信息 grep -A 50 "TypeScript Compilation Errors" build.log || true grep -A 50 "Build Error" build.log || true exit 1 } - # - name: Run Bugserver - # working-directory: ./packages/vtable - # env: - # BUG_SERVER_TOKEN: ${{ secrets.BUG_SERVER_TOKEN }} - # run: node ../../common/scripts/install-run-rushx.js ci - - name: Publish to npm env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - NPM_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: node common/scripts/install-run-rush.js publish --publish --include-all - name: Update shrinkwrap @@ -91,7 +83,7 @@ jobs: with: path: packages/vtable - - name: Commit & Push changes + - name: Commit and push changes uses: actions-js/push@master with: github_token: ${{ secrets.GITHUB_TOKEN }} @@ -104,19 +96,6 @@ jobs: with: version: ${{ steps.package-version.outputs.current_version }} - - name: Create Release for Tag - id: release_tag - uses: ncipollo/release-action@v1.12.0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag: v${{ steps.package-version.outputs.current_version }} - commit: main - prerelease: false - body: | - ${{ steps.changelog.outputs.markdown }} - draft: true # - - name: Create Pull Request uses: dustinirving/create-pr@v1.0.2 with: @@ -124,7 +103,7 @@ jobs: title: '[Auto release] release ${{ steps.package-version.outputs.current_version }}' base: main head: ${{ github.ref_name }} - labels: release # default labels, the action will throw error if not specified - reviewers: fangsmile,Rui-Sun # default reviewers, the action will throw error if not specified + labels: release + reviewers: fangsmile,Rui-Sun body: | ${{ steps.changelog.outputs.markdown }} diff --git a/.github/workflows/sync-main-to-develop.yml b/.github/workflows/sync-main-to-develop.yml new file mode 100644 index 000000000..067c77eb1 --- /dev/null +++ b/.github/workflows/sync-main-to-develop.yml @@ -0,0 +1,32 @@ +name: Sync main to develop after release + +on: + push: + branches: + - main + +jobs: + sync_main_to_develop: + if: contains(github.event.head_commit.message, 'prelease version') + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + - name: Checkout main + uses: actions/checkout@v4 + with: + ref: main + + - name: Create PR from main to develop + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + gh pr create \ + -B develop \ + -H main \ + -t "Sync main -> develop" \ + -b "Sync after release ${GITHUB_SHA}" \ + || echo 'PR may already exist' From 79d20fc692b2f748407bad6007bba3efdc95b694 Mon Sep 17 00:00:00 2001 From: "lixuefei.1313" Date: Thu, 5 Mar 2026 17:51:20 +0800 Subject: [PATCH 2/2] feat: optimiz workflows for post-release --- .github/workflows/post-release.yml | 117 ++++++++++++++++++++--------- 1 file changed, 83 insertions(+), 34 deletions(-) diff --git a/.github/workflows/post-release.yml b/.github/workflows/post-release.yml index 4360f73e6..c64de5754 100644 --- a/.github/workflows/post-release.yml +++ b/.github/workflows/post-release.yml @@ -7,6 +7,7 @@ on: jobs: post_release: runs-on: ubuntu-latest + permissions: contents: write @@ -34,17 +35,14 @@ jobs: fetch-depth: 0 persist-credentials: false - - name: Rebind origin to PAT remote + - name: Configure git remote to use PAT if: steps.meta.outputs.skip != 'true' env: GH_PAT: ${{ secrets.CREATE_TAG_RELEASE_TOKEN }} run: | set -euo pipefail - if [ -z "${GH_PAT}" ]; then - echo "CREATE_TAG_RELEASE_TOKEN is not configured." - exit 1 - fi git remote set-url origin "https://x-access-token:${GH_PAT}@github.com/${GITHUB_REPOSITORY}.git" + git remote -v - name: Fetch tags if: steps.meta.outputs.skip != 'true' @@ -61,68 +59,102 @@ jobs: run: | set -euo pipefail TAG="v${VERSION}" + if git rev-parse "refs/tags/${TAG}" >/dev/null 2>&1; then echo "Tag ${TAG} already exists, skip post-release." echo "skip=true" >> "$GITHUB_OUTPUT" exit 0 fi + if gh release view "${TAG}" >/dev/null 2>&1; then echo "Release ${TAG} already exists, skip post-release." echo "skip=true" >> "$GITHUB_OUTPUT" exit 0 fi + echo "skip=false" >> "$GITHUB_OUTPUT" - - name: Fetch develop changelog + - name: Ensure main changelog exists if: steps.meta.outputs.skip != 'true' && steps.exist.outputs.skip != 'true' run: | set -euo pipefail - git fetch origin develop:refs/remotes/origin/develop --depth=1 - git show origin/develop:docs/assets/changelog/en/release.md > release-develop.md + if [ ! -f "docs/assets/changelog/en/release.md" ]; then + echo "Error: docs/assets/changelog/en/release.md not found in main." + exit 1 + fi - - name: Extract release body from develop changelog + - name: Extract release body from main changelog id: body if: steps.meta.outputs.skip != 'true' && steps.exist.outputs.skip != 'true' env: VERSION: ${{ steps.meta.outputs.version }} run: | set -euo pipefail - if [ ! -f "release-develop.md" ]; then - echo "develop changelog file not found, skip post-release." - echo "has_body=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - if node <<'NODE' + node <<'NODE' const fs = require('fs'); + const version = process.env.VERSION; - const content = fs.readFileSync('release-develop.md', 'utf8'); + const changelogPath = 'docs/assets/changelog/en/release.md'; + const content = fs.readFileSync(changelogPath, 'utf8'); + function escapeRegExp(str) { return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } + const headerPattern = new RegExp('^#\\s*v?' + escapeRegExp(version) + '\\b', 'm'); const match = headerPattern.exec(content); + if (!match) { - console.log('No changelog block for version', version, 'found in develop.'); + console.error('No changelog block for version', version, 'found in main changelog.'); process.exit(1); } + const startIndex = match.index; const rest = content.slice(startIndex); - const nextHeaderPattern = /^#\s*v?\d+\.\d+\.\d+[^\n]*$/gm; + + // Find the next release header after the current one. + const nextHeaderPattern = /^#\s*v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?[^\n]*$/gm; let nextIndex = rest.length; let m; - if ((m = nextHeaderPattern.exec(rest)) !== null && m.index !== 0) { - nextIndex = m.index; + while ((m = nextHeaderPattern.exec(rest)) !== null) { + if (m.index > 0) { + nextIndex = m.index; + break; + } } + const block = rest.slice(0, nextIndex).trimEnd() + '\n'; + if (!block.trim()) { + console.error('Extracted changelog block is empty for version', version); + process.exit(1); + } fs.writeFileSync('release-body.md', block, 'utf8'); NODE - then - echo "has_body=true" >> "$GITHUB_OUTPUT" - else - echo "has_body=false" >> "$GITHUB_OUTPUT" + echo "has_body=true" >> "$GITHUB_OUTPUT" + + - name: Validate extracted release body + if: steps.meta.outputs.skip != 'true' && steps.exist.outputs.skip != 'true' && steps.body.outputs.has_body == 'true' + env: + VERSION: ${{ steps.meta.outputs.version }} + run: | + set -euo pipefail + if [ ! -s "release-body.md" ]; then + echo "Error: release-body.md is missing or empty." + exit 1 + fi + + if ! grep -Eq "^#\\s*v?${VERSION}\\b" release-body.md; then + echo "Error: release-body.md does not start with expected version header v${VERSION}." + exit 1 + fi + + HEADER_COUNT="$(grep -Ec '^#\s*v?[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.]+)?\b' release-body.md || true)" + if [ "${HEADER_COUNT}" -ne 1 ]; then + echo "Error: extracted release body contains ${HEADER_COUNT} release headers, expected exactly 1." + exit 1 fi - - name: Verify GitHub identity + - name: Verify gh identity if: steps.meta.outputs.skip != 'true' && steps.exist.outputs.skip != 'true' && steps.body.outputs.has_body == 'true' env: GH_TOKEN: ${{ secrets.CREATE_TAG_RELEASE_TOKEN }} @@ -130,20 +162,29 @@ jobs: set -euo pipefail gh api user -q '.login' - - name: Create tag + - name: Diagnose PAT repository permission if: steps.meta.outputs.skip != 'true' && steps.exist.outputs.skip != 'true' && steps.body.outputs.has_body == 'true' env: - VERSION: ${{ steps.meta.outputs.version }} + GH_TOKEN: ${{ secrets.CREATE_TAG_RELEASE_TOKEN }} run: | set -euo pipefail - TAG="v${VERSION}" - git fetch origin main:refs/remotes/origin/main --depth=1 - MAIN_SHA="$(git rev-parse origin/main)" - echo "Creating tag ${TAG} at ${MAIN_SHA}" - git tag "${TAG}" "${MAIN_SHA}" - git push origin "${TAG}" + LOGIN=$(gh api user -q '.login') + echo "PAT user: $LOGIN" + OWNER=${GITHUB_REPOSITORY%%/*} + REPO=${GITHUB_REPOSITORY#*/} + RESP=$(gh api "/repos/$OWNER/$REPO/collaborators/$LOGIN/permission" 2>&1 || true) + echo "$RESP" + if echo "$RESP" | grep -q '404'; then + echo "Not a collaborator (404), repository access may be restricted or PAT not authorized to org" + elif echo "$RESP" | grep -q '"permission":"write"'; then + echo "permission ok: write" + elif echo "$RESP" | grep -q '"permission":"admin"'; then + echo "permission ok: admin" + else + echo "permission insufficient: $RESP" + fi - - name: Create GitHub Release + - name: Create tag and GitHub Release if: steps.meta.outputs.skip != 'true' && steps.exist.outputs.skip != 'true' && steps.body.outputs.has_body == 'true' env: VERSION: ${{ steps.meta.outputs.version }} @@ -151,6 +192,14 @@ jobs: run: | set -euo pipefail TAG="v${VERSION}" + + git fetch origin main:refs/remotes/origin/main --depth=1 + MAIN_SHA="$(git rev-parse origin/main)" + + echo "Creating tag ${TAG} at ${MAIN_SHA}" + git tag "${TAG}" "${MAIN_SHA}" + git push origin "${TAG}" + echo "Creating GitHub Release ${TAG}" gh release create "${TAG}" \ --target "main" \