Skip to content

rarajes2 triggered release comment bot #46

rarajes2 triggered release comment bot

rarajes2 triggered release comment bot #46

name: PR Release Comment Bot
run-name: ${{ github.actor }} triggered release comment bot
on:
workflow_dispatch:
inputs:
test_version:
description: 'Test version number (e.g., 3.4.0)'
required: false
default: '3.4.0'
test_pr_number:
description: 'Test PR number (e.g., 2) - leave empty to auto-detect'
required: false
default: ''
workflow_run:
workflows: ["Deploy CD"]
types:
- completed
branches:
- next
jobs:
comment-on-prs:
name: Comment on Released PRs
runs-on: ubuntu-latest
# Only run if manually triggered or Deploy CD succeeded
if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
steps:
- name: Checkout Repository
uses: actions/checkout@v3
with:
fetch-depth: 0 # Fetch all history to find commits
- name: Get Version and PR from Tag
id: tag-info
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
VERSION="${{ github.event.inputs.test_version }}"
PR_NUMBER="${{ github.event.inputs.test_pr_number }}"
echo "πŸ§ͺ TEST MODE: version=${VERSION}, pr=${PR_NUMBER}"
else
git fetch --tags
VERSION=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [ -z "$VERSION" ]; then
echo "❌ No tags found"
exit 1
fi
TAG_MESSAGE=$(git tag -l --format='%(contents:subject)' "$VERSION")
PR_NUMBER=$(echo "$TAG_MESSAGE" | grep -oE '#[0-9]+' | head -1 | tr -d '#')
echo "πŸ“¦ Tag: ${VERSION}, PR: #${PR_NUMBER}"
fi
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "version_number=${VERSION#v}" >> $GITHUB_OUTPUT
echo "pr_number=${PR_NUMBER}" >> $GITHUB_OUTPUT
- name: Get Packages from PR
id: get-prs
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const path = require('path');
const prNumber = '${{ steps.tag-info.outputs.pr_number }}';
if (!prNumber) {
console.log('❌ No PR number found from tag, skipping');
core.setOutput('pr_numbers', JSON.stringify([]));
core.setOutput('packages', JSON.stringify([]));
core.setOutput('package_versions', JSON.stringify({}));
core.setOutput('primary_package', '');
core.setOutput('primary_package_version', '');
core.setOutput('commit_count', '0');
return { prs: [], packages: [] };
}
console.log(`βœ… Using PR #${prNumber} from tag`);
const { packages, packageVersions, primaryPackage, primaryPackageVersion } =
await getPackagesForPr(prNumber);
core.setOutput('pr_numbers', JSON.stringify([prNumber]));
core.setOutput('packages', JSON.stringify(packages));
core.setOutput('package_versions', JSON.stringify(packageVersions));
core.setOutput('primary_package', primaryPackage);
core.setOutput('primary_package_version', primaryPackageVersion);
core.setOutput('commit_count', '1');
return { prs: [prNumber], packages: packages };
async function getPackagesForPr(prNumber) {
const packageSet = new Set();
try {
const files = await github.paginate(github.rest.pulls.listFiles, {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: parseInt(prNumber, 10),
per_page: 100
});
for (const file of files) {
const filename = file.filename;
if (!filename.startsWith('packages/')) {
continue;
}
const parts = filename.split('/');
if (parts[1] === '@webex' && parts.length >= 3) {
packageSet.add(`@webex/${parts[2]}`);
} else if (parts.length >= 2) {
packageSet.add(parts[1]);
}
}
} catch (error) {
console.log(`Error listing PR files: ${error.message}`);
}
if (packageSet.size === 0) {
packageSet.add('webex');
}
const packages = Array.from(packageSet);
const packageVersions = {};
for (const packageName of packages) {
const packageJsonPath = packageName.startsWith('@webex/')
? path.join(process.env.GITHUB_WORKSPACE, 'packages', '@webex', packageName.split('/')[1], 'package.json')
: path.join(process.env.GITHUB_WORKSPACE, 'packages', packageName, 'package.json');
try {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
if (packageJson.version) {
packageVersions[packageName] = packageJson.version;
}
} catch (error) {
console.log(`Error reading ${packageJsonPath}: ${error.message}`);
}
}
const primaryPackage = packages[0] || '';
const primaryPackageVersion = primaryPackage ? (packageVersions[primaryPackage] || '') : '';
return { packages, packageVersions, primaryPackage, primaryPackageVersion };
}
- name: Post Release Comment on PR
uses: actions/github-script@v7
with:
script: |
const version = '${{ steps.tag-info.outputs.version }}';
const versionNumber = '${{ steps.tag-info.outputs.version_number }}';
const stableVersion = versionNumber.replace(/-next\..*$/, '');
const prNumbersRaw = '${{ steps.get-prs.outputs.pr_numbers }}';
const packagesRaw = '${{ steps.get-prs.outputs.packages }}';
const packageVersionsRaw = '${{ steps.get-prs.outputs.package_versions }}';
const primaryPackage = '${{ steps.get-prs.outputs.primary_package }}';
const primaryPackageVersion = '${{ steps.get-prs.outputs.primary_package_version }}';
const prNumbers = prNumbersRaw ? JSON.parse(prNumbersRaw) : [];
const packages = packagesRaw ? JSON.parse(packagesRaw) : [];
const packageVersions = packageVersionsRaw ? JSON.parse(packageVersionsRaw) : {};
const prNumber = prNumbers[0];
console.log(`Posting comment to PR #${prNumber}`);
if (prNumbers.length === 0) {
console.log('No PRs found to comment on');
return;
}
// Build the changelog URL
// If 'webex' package is in the list, use it. Otherwise use the first detected package.
const hasWebexPackage = packages.includes('webex');
const changelogPackage = hasWebexPackage ? 'webex' : packages[0];
const changelogUrl = new URL('https://web-sdk.webex.com/changelog/');
changelogUrl.searchParams.set('stable_version', stableVersion);
changelogUrl.searchParams.set('package', changelogPackage);
changelogUrl.searchParams.set('version', versionNumber);
console.log(`πŸ“¦ Changelog URL package: ${changelogPackage}`);
console.log(`πŸ“¦ Changelog URL version: ${versionNumber}`);
console.log(`πŸ”— Full URL: ${changelogUrl.toString()}`);
// Build the package list markdown
const packageList = packages.slice(0, 5).map(p => `\`${p}\``).join(', ');
const morePackages = packages.length > 5 ? ` and ${packages.length - 5} more` : '';
// Create the comment body
const commentBody = [
'## πŸŽ‰ Your changes are now available!',
'',
`**Released in:** [\`${version}\`](https://github.com/${context.repo.owner}/${context.repo.repo}/releases/tag/${version})`,
`**Packages updated:** ${packageList}${morePackages}`,
'',
`πŸ“– **[View full changelog β†’](${changelogUrl})**`,
'',
`Thank you for your contribution!`,
'',
'---',
`<sub>πŸ€– This is an automated message. For questions, please refer to the [documentation](https://github.com/${context.repo.owner}/${context.repo.repo}#readme).</sub>`
].join('\n');
try {
console.log(`Commenting on PR #${prNumber}`);
// Check if PR exists and is merged
let pr;
try {
pr = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: parseInt(prNumber)
});
} catch (error) {
console.log(`PR #${prNumber} not found or error: ${error.message}`);
return;
}
if (!pr.data.merged_at) {
console.log(`PR #${prNumber} is not merged, skipping`);
return;
}
// Check if we've already commented (to avoid duplicates)
const existingComments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: parseInt(prNumber)
});
const botCommentExists = existingComments.data.some(comment =>
comment.user.type === 'Bot' &&
comment.body.includes('Your changes are now available') &&
comment.body.includes(version)
);
if (botCommentExists) {
console.log(`Already commented on PR #${prNumber} for version ${version}`);
return;
}
// Post the comment
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: parseInt(prNumber),
body: commentBody
});
console.log(`βœ… Successfully commented on PR #${prNumber}`);
} catch (error) {
console.error(`Failed to comment on PR #${prNumber}:`, error.message);
}
console.log('✨ Comment posted successfully!');