JudyZhuHz triggered release comment #13
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: PR Release Comment | |
| run-name: ${{ github.actor }} triggered release comment | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| test_version: | |
| description: 'Test version number (e.g., 3.4.0)' | |
| required: true | |
| test_pr_number: | |
| description: 'Test PR number (e.g., 2) - required for manual testing' | |
| required: true | |
| workflow_run: | |
| workflows: ["Deploy CD"] | |
| types: | |
| - completed | |
| jobs: | |
| comment-on-prs: | |
| name: Comment on Released PRs | |
| runs-on: ubuntu-latest | |
| if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} | |
| steps: | |
| - name: Checkout Repository | |
| uses: actions/checkout@v3 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.sha }} | |
| - name: Get Version and Tag Info | |
| id: tag-info | |
| run: | | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| VERSION="${{ github.event.inputs.test_version }}" | |
| DEPLOY_SHA="${{ github.sha }}" | |
| PR_NUMBER="${{ github.event.inputs.test_pr_number }}" | |
| else | |
| DEPLOY_SHA="${{ github.event.workflow_run.head_sha }}" | |
| PR_NUMBER="" | |
| git fetch --tags | |
| NEAREST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") | |
| VERSION="" | |
| if [ -n "$NEAREST_TAG" ]; then | |
| TAG_SHA=$(git rev-list -n 1 "$NEAREST_TAG" 2>/dev/null || echo "") | |
| if [ "$TAG_SHA" = "$DEPLOY_SHA" ]; then | |
| VERSION="$NEAREST_TAG" | |
| fi | |
| fi | |
| fi | |
| STABLE_VERSION="" | |
| TAG_FOR_STABLE="${VERSION:-$NEAREST_TAG}" | |
| if [ -n "$TAG_FOR_STABLE" ]; then | |
| STABLE_VERSION=$(echo "${TAG_FOR_STABLE#v}" | sed 's/-next\..*//' | sed 's/-[a-z]*\..*//') | |
| fi | |
| CHANGELOG_FILE="" | |
| if [ -n "$STABLE_VERSION" ]; then | |
| FILE_NAME="v${STABLE_VERSION//./_}" | |
| CHANGELOG_FILE="/tmp/changelog_${FILE_NAME}.json" | |
| git fetch origin documentation 2>/dev/null | |
| if ! git show "origin/documentation:docs/changelog/logs/${FILE_NAME}.json" > "$CHANGELOG_FILE" 2>/dev/null; then | |
| CHANGELOG_FILE="" | |
| fi | |
| fi | |
| echo "version=${VERSION}" >> $GITHUB_OUTPUT | |
| echo "stable_version=${STABLE_VERSION}" >> $GITHUB_OUTPUT | |
| echo "deploy_sha=${DEPLOY_SHA}" >> $GITHUB_OUTPUT | |
| echo "pr_number=${PR_NUMBER}" >> $GITHUB_OUTPUT | |
| echo "changelog_file=${CHANGELOG_FILE}" >> $GITHUB_OUTPUT | |
| - name: Get Packages and Versions from Changelog | |
| id: get-prs | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const deploySha = '${{ steps.tag-info.outputs.deploy_sha }}'; | |
| const stableVersion = '${{ steps.tag-info.outputs.stable_version }}'; | |
| const manualPrNumber = '${{ steps.tag-info.outputs.pr_number }}'; | |
| const owner = 'webex'; | |
| const repo = 'webex-js-sdk'; | |
| const emptyResult = (prNum, fetchSuccess) => { | |
| core.setOutput('pr_numbers', JSON.stringify(prNum ? [prNum] : [])); | |
| core.setOutput('package_versions', JSON.stringify({})); | |
| core.setOutput('primary_package', ''); | |
| core.setOutput('fetch_success', String(fetchSuccess)); | |
| }; | |
| // --- Step 1: Discover PR number --- | |
| let prNumber = manualPrNumber; | |
| if (!prNumber && deploySha) { | |
| try { | |
| const prs = await github.rest.repos.listPullRequestsAssociatedWithCommit({ | |
| owner, repo, commit_sha: deploySha | |
| }); | |
| const mergedPR = prs.data.find(pr => pr.merged_at); | |
| if (mergedPR) { | |
| prNumber = String(mergedPR.number); | |
| } | |
| } catch (error) { | |
| console.log(`Failed to discover PR from commit: ${error.message}`); | |
| } | |
| } | |
| if (!prNumber) { | |
| emptyResult(null, true); | |
| return; | |
| } | |
| console.log(`PR #${prNumber}, deploy SHA: ${deploySha}`); | |
| // --- Step 2: Read changelog from local file (fetched via git in shell step) --- | |
| const changelogFile = '${{ steps.tag-info.outputs.changelog_file }}'; | |
| let changelog = {}; | |
| let fetchSuccess = false; | |
| if (!changelogFile) { | |
| emptyResult(prNumber, false); | |
| return; | |
| } | |
| try { | |
| const fs = require('fs'); | |
| changelog = JSON.parse(fs.readFileSync(changelogFile, 'utf8')); | |
| fetchSuccess = true; | |
| } catch (error) { | |
| console.log(`Failed to read changelog: ${error.message}`); | |
| emptyResult(prNumber, false); | |
| return; | |
| } | |
| // --- Step 3: Match deploy SHA against changelog entries --- | |
| const packageVersions = {}; | |
| for (const [pkgName, versions] of Object.entries(changelog)) { | |
| for (const [ver, data] of Object.entries(versions)) { | |
| const commits = data.commits || {}; | |
| if (deploySha in commits) { | |
| packageVersions[pkgName] = ver; | |
| for (const [alongPkg, alongVer] of Object.entries(data.alongWith || {})) { | |
| if (!packageVersions[alongPkg]) { | |
| packageVersions[alongPkg] = alongVer; | |
| } | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| const directlyChanged = Object.keys(packageVersions).filter(p => p !== 'webex' && p !== 'webex-node'); | |
| const primaryPackage = directlyChanged[0] || Object.keys(packageVersions)[0] || ''; | |
| core.setOutput('pr_numbers', JSON.stringify([prNumber])); | |
| core.setOutput('package_versions', JSON.stringify(packageVersions)); | |
| core.setOutput('primary_package', primaryPackage); | |
| core.setOutput('fetch_success', 'true'); | |
| - name: Post Release Comment on PR | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const version = '${{ steps.tag-info.outputs.version }}'; | |
| const stableVersion = '${{ steps.tag-info.outputs.stable_version }}'; | |
| const prNumbersRaw = '${{ steps.get-prs.outputs.pr_numbers }}'; | |
| const packageVersionsRaw = '${{ steps.get-prs.outputs.package_versions }}'; | |
| const primaryPackage = '${{ steps.get-prs.outputs.primary_package }}'; | |
| const fetchSuccess = '${{ steps.get-prs.outputs.fetch_success }}' === 'true'; | |
| const prNumbers = prNumbersRaw ? JSON.parse(prNumbersRaw) : []; | |
| const packageVersions = packageVersionsRaw ? JSON.parse(packageVersionsRaw) : {}; | |
| const owner = 'webex'; | |
| const repo = 'webex-js-sdk'; | |
| const prNumber = prNumbers[0]; | |
| if (!prNumber) return; | |
| const repoUrl = `https://github.com/${owner}/${repo}`; | |
| const packageEntries = Object.entries(packageVersions); | |
| const hasPackages = packageEntries.length > 0; | |
| let commentBody; | |
| if (hasPackages) { | |
| const changelogUrl = new URL('https://web-sdk.webex.com/changelog/'); | |
| if (stableVersion) { | |
| changelogUrl.searchParams.set('stable_version', stableVersion); | |
| } | |
| const changelogPackage = packageVersions['webex'] ? 'webex' : primaryPackage; | |
| changelogUrl.searchParams.set('package', changelogPackage); | |
| changelogUrl.searchParams.set('version', packageVersions[changelogPackage]); | |
| const releaseLine = version | |
| ? `| **Released in:** [\`${version}\`](${repoUrl}/releases/tag/${version}) |` | |
| : ''; | |
| const rows = packageEntries | |
| .sort(([a], [b]) => { | |
| if (a === 'webex') return -1; | |
| if (b === 'webex') return 1; | |
| if (a === primaryPackage) return -1; | |
| if (b === primaryPackage) return 1; | |
| return a.localeCompare(b); | |
| }) | |
| .map(([pkg, ver]) => `| \`${pkg}\` | \`${ver}\` |`) | |
| .join('\n'); | |
| const packagesTable = [ | |
| '', | |
| '| Packages Updated | Version |', | |
| '|---------|---------|', | |
| rows, | |
| '' | |
| ].join('\n'); | |
| commentBody = [ | |
| '| :tada: Your changes are now available! |', | |
| '|---|', | |
| releaseLine, | |
| `| :book: **[View full changelog →](${changelogUrl})** |`, | |
| packagesTable, | |
| 'Thank you for your contribution!', | |
| '', | |
| `_:robot: This is an automated message. For questions, please refer to the [documentation](${repoUrl}#readme)._` | |
| ].filter(Boolean).join('\n'); | |
| } else if (fetchSuccess) { | |
| commentBody = [ | |
| '| :white_check_mark: Your changes have been merged! |', | |
| '|---|', | |
| '| No packages affected. |', | |
| '', | |
| 'Thank you for your contribution!', | |
| '', | |
| `_:robot: This is an automated message. For questions, please refer to the [documentation](${repoUrl}#readme)._` | |
| ].join('\n'); | |
| } else { | |
| commentBody = [ | |
| '| :white_check_mark: Your changes have been merged! |', | |
| '|---|', | |
| '', | |
| 'Thank you for your contribution!', | |
| '', | |
| `_:robot: This is an automated message. For questions, please refer to the [documentation](${repoUrl}#readme)._` | |
| ].join('\n'); | |
| } | |
| try { | |
| let pr; | |
| try { | |
| pr = await github.rest.pulls.get({ | |
| owner, repo, | |
| pull_number: parseInt(prNumber) | |
| }); | |
| } catch (error) { | |
| return; | |
| } | |
| if (!pr.data.merged_at) return; | |
| const existingComments = await github.rest.issues.listComments({ | |
| owner, repo, | |
| issue_number: parseInt(prNumber), | |
| per_page: 100 | |
| }); | |
| const detailedComment = existingComments.data.find(comment => | |
| comment.user.type === 'Bot' && | |
| comment.body.includes('Your changes are now available') | |
| ); | |
| const mergedComment = existingComments.data.find(comment => | |
| comment.user.type === 'Bot' && | |
| comment.body.includes('Your changes have been merged') | |
| ); | |
| if (detailedComment) return; | |
| if (!hasPackages && mergedComment) return; | |
| if (mergedComment && hasPackages) { | |
| await github.rest.issues.updateComment({ | |
| owner, repo, | |
| comment_id: mergedComment.id, | |
| body: commentBody | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner, repo, | |
| issue_number: parseInt(prNumber), | |
| body: commentBody | |
| }); | |
| } | |
| } catch (error) { | |
| core.warning(`Failed to comment on PR #${prNumber}: ${error.message}`); | |
| } | |