From 1d459b23ff43a47773c67c0888cd2f88a710a212 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Sun, 12 Oct 2025 12:17:23 -0600 Subject: [PATCH 1/7] feat: add automated PR comments on release MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds automation to comment on PRs when they are included in a release. The workflow parses CHANGELOGs to extract PR numbers and posts a comment with version info and links to the relevant CHANGELOG files. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .github/workflows/release.yml | 9 ++ scripts/comment-on-release.ts | 181 ++++++++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 scripts/comment-on-release.ts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 10caf9e6..0a8485d8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,6 +31,7 @@ jobs: - name: Run Tests run: pnpm run test:ci - name: Run Changesets (version or publish) + id: changesets uses: changesets/action@v1.5.3 with: version: pnpm run changeset:version @@ -40,3 +41,11 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Comment on PRs about release + if: steps.changesets.outputs.published == 'true' + env: + PUBLISHED_PACKAGES: ${{ steps.changesets.outputs.publishedPackages }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + sleep 0.01 + npx tsx scripts/comment-on-release.ts diff --git a/scripts/comment-on-release.ts b/scripts/comment-on-release.ts new file mode 100644 index 00000000..6879f798 --- /dev/null +++ b/scripts/comment-on-release.ts @@ -0,0 +1,181 @@ +#!/usr/bin/env node + +import { readFileSync } from 'node:fs' +import { resolve } from 'node:path' + +interface PublishedPackage { + name: string + version: string +} + +interface PRInfo { + number: number + packages: Array<{ name: string; version: string }> +} + +/** + * Parse CHANGELOG.md to extract PR numbers from the latest version entry + */ +function extractPRsFromChangelog( + changelogPath: string, + version: string, +): Array { + try { + const content = readFileSync(changelogPath, 'utf-8') + const lines = content.split('\n') + + let inTargetVersion = false + let foundVersion = false + const prNumbers = new Set() + + for (let i = 0; i < lines.length; i++) { + const line = lines[i] + + // Check for version header (e.g., "## 0.21.0") + if (line.startsWith('## ')) { + const versionMatch = line.match(/^## (\d+\.\d+\.\d+)/) + if (versionMatch) { + if (versionMatch[1] === version) { + inTargetVersion = true + foundVersion = true + } else if (inTargetVersion) { + // We've moved to the next version, stop processing + break + } + } + } + + // Extract PR numbers from links like [#302](https://github.com/TanStack/config/pull/302) + if (inTargetVersion) { + const prMatches = line.matchAll( + /\[#(\d+)\]\(https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+\)/g, + ) + for (const match of prMatches) { + prNumbers.add(parseInt(match[1], 10)) + } + } + } + + if (!foundVersion) { + console.warn( + `Warning: Could not find version ${version} in ${changelogPath}`, + ) + } + + return Array.from(prNumbers) + } catch (error) { + console.error(`Error reading changelog at ${changelogPath}:`, error) + return [] + } +} + +/** + * Group PRs by their numbers and collect all packages they contributed to + */ +function groupPRsByNumber( + publishedPackages: Array, +): Map { + const prMap = new Map() + + for (const pkg of publishedPackages) { + const changelogPath = resolve( + process.cwd(), + 'packages', + pkg.name.replace('@tanstack/', ''), + 'CHANGELOG.md', + ) + + const prNumbers = extractPRsFromChangelog(changelogPath, pkg.version) + + for (const prNumber of prNumbers) { + if (!prMap.has(prNumber)) { + prMap.set(prNumber, { number: prNumber, packages: [] }) + } + prMap.get(prNumber)!.packages.push({ + name: pkg.name, + version: pkg.version, + }) + } + } + + return prMap +} + +/** + * Post a comment on a GitHub PR using gh CLI + */ +async function commentOnPR(pr: PRInfo): Promise { + const { number, packages } = pr + + // Build the comment body + let comment = `🎉 This PR has been released!\n\n` + + for (const pkg of packages) { + const changelogUrl = `https://github.com/TanStack/config/blob/main/packages/${pkg.name.replace('@tanstack/', '')}/CHANGELOG.md` + comment += `- **${pkg.name}@${pkg.version}** - [CHANGELOG](${changelogUrl})\n` + } + + comment += `\nThank you for your contribution!` + + try { + // Use gh CLI to post the comment + const { execSync } = await import('node:child_process') + execSync(`gh pr comment ${number} --body ${JSON.stringify(comment)}`, { + stdio: 'inherit', + }) + console.log(`✓ Commented on PR #${number}`) + } catch (error) { + console.error(`✗ Failed to comment on PR #${number}:`, error) + } +} + +/** + * Main function + */ +async function main() { + // Read published packages from environment variable (set by GitHub Actions) + const publishedPackagesJson = process.env.PUBLISHED_PACKAGES + if (!publishedPackagesJson) { + console.log('No packages were published. Skipping PR comments.') + return + } + + let publishedPackages: Array + try { + publishedPackages = JSON.parse(publishedPackagesJson) + } catch (error) { + console.error('Failed to parse PUBLISHED_PACKAGES:', error) + process.exit(1) + } + + if (publishedPackages.length === 0) { + console.log('No packages were published. Skipping PR comments.') + return + } + + console.log( + `Processing ${publishedPackages.length} published package(s)...`, + ) + + // Group PRs by number + const prMap = groupPRsByNumber(publishedPackages) + + if (prMap.size === 0) { + console.log('No PRs found in CHANGELOGs. Nothing to comment on.') + return + } + + console.log(`Found ${prMap.size} PR(s) to comment on...`) + + // Comment on each PR + for (const pr of prMap.values()) { + await commentOnPR(pr) + } + + console.log('✓ Done!') +} + +main().catch((error) => { + console.error('Fatal error:', error) + process.exit(1) +}) From 05d4ab56a2a74db9bbb2431db2309d23dfd30b27 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 12 Oct 2025 18:42:53 +0000 Subject: [PATCH 2/7] ci: apply automated fixes --- scripts/comment-on-release.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/comment-on-release.ts b/scripts/comment-on-release.ts index 6879f798..5a7db6a1 100644 --- a/scripts/comment-on-release.ts +++ b/scripts/comment-on-release.ts @@ -153,9 +153,7 @@ async function main() { return } - console.log( - `Processing ${publishedPackages.length} published package(s)...`, - ) + console.log(`Processing ${publishedPackages.length} published package(s)...`) // Group PRs by number const prMap = groupPRsByNumber(publishedPackages) From e26fdfdc7ba830c7d4cde36ac20d0a03470f08dd Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Sun, 12 Oct 2025 13:09:02 -0600 Subject: [PATCH 3/7] refactor: make PR commenting reusable across TanStack repos MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Converts the PR commenting feature into a reusable composite action that any TanStack repo can use. Changes: - Move script to .github/comment-on-release/ directory - Create composite action at .github/comment-on-release/action.yml - Update release workflow to use the composite action - Other TanStack repos can now use: tanstack/config/.github/comment-on-release@main 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .github/comment-on-release/action.yml | 15 +++++++++++++++ .../comment-on-release}/comment-on-release.ts | 0 .github/workflows/release.yml | 9 +++------ 3 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 .github/comment-on-release/action.yml rename {scripts => .github/comment-on-release}/comment-on-release.ts (100%) diff --git a/.github/comment-on-release/action.yml b/.github/comment-on-release/action.yml new file mode 100644 index 00000000..57556952 --- /dev/null +++ b/.github/comment-on-release/action.yml @@ -0,0 +1,15 @@ +name: Comment on PRs about release +description: Automatically comments on PRs when they are included in a release +inputs: + published-packages: + description: 'JSON string of published packages from changesets/action' + required: true +runs: + using: composite + steps: + - name: Comment on PRs + shell: bash + env: + PUBLISHED_PACKAGES: ${{ inputs.published-packages }} + run: | + npx tsx ${{ github.action_path }}/comment-on-release.ts diff --git a/scripts/comment-on-release.ts b/.github/comment-on-release/comment-on-release.ts similarity index 100% rename from scripts/comment-on-release.ts rename to .github/comment-on-release/comment-on-release.ts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0a8485d8..df9d9017 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -43,9 +43,6 @@ jobs: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Comment on PRs about release if: steps.changesets.outputs.published == 'true' - env: - PUBLISHED_PACKAGES: ${{ steps.changesets.outputs.publishedPackages }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - sleep 0.01 - npx tsx scripts/comment-on-release.ts + uses: ./.github/comment-on-release + with: + published-packages: ${{ steps.changesets.outputs.publishedPackages }} From 5299423ba19034c3b34ada3a04633e7dc62699f7 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Sun, 12 Oct 2025 13:13:48 -0600 Subject: [PATCH 4/7] docs: add README for comment-on-release action MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Explains how other TanStack repos can adopt this reusable action. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .github/comment-on-release/README.md | 79 ++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 .github/comment-on-release/README.md diff --git a/.github/comment-on-release/README.md b/.github/comment-on-release/README.md new file mode 100644 index 00000000..72baebdd --- /dev/null +++ b/.github/comment-on-release/README.md @@ -0,0 +1,79 @@ +# Comment on Release Action + +A reusable GitHub Action that automatically comments on PRs when they are included in a release. + +## What It Does + +When packages are published via Changesets: +1. Parses each published package's CHANGELOG to find PR numbers in the latest version +2. Groups PRs by number (handling cases where one PR affects multiple packages) +3. Posts a comment on each PR with release info and CHANGELOG links + +## Example Comment + +``` +🎉 This PR has been released! + +- **@tanstack/query@5.0.0** - [CHANGELOG](https://github.com/TanStack/query/blob/main/packages/query-core/CHANGELOG.md) +- **@tanstack/react-query@5.0.0** - [CHANGELOG](https://github.com/TanStack/query/blob/main/packages/react-query/CHANGELOG.md) + +Thank you for your contribution! +``` + +## Usage + +Add this step to your `.github/workflows/release.yml` file after the `changesets/action` step: + +```yaml +- name: Run Changesets (version or publish) + id: changesets + uses: changesets/action@v1.5.3 + with: + version: pnpm run changeset:version + publish: pnpm run changeset:publish + commit: "ci: Version Packages" + title: "ci: Version Packages" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + +- name: Comment on PRs about release + if: steps.changesets.outputs.published == 'true' + uses: tanstack/config/.github/comment-on-release@main + with: + published-packages: ${{ steps.changesets.outputs.publishedPackages }} +``` + +## Requirements + +- Must be using [Changesets](https://github.com/changesets/changesets) for releases +- CHANGELOGs must include PR links in the format: `[#123](https://github.com/org/repo/pull/123)` +- Requires `pull-requests: write` permission in the workflow +- The `gh` CLI must be available (automatically available in GitHub Actions) + +## Inputs + +| Input | Required | Description | +|-------|----------|-------------| +| `published-packages` | Yes | JSON string of published packages from `changesets/action` outputs | + +## How It Works + +The action: +1. Receives the list of published packages from the Changesets action +2. For each package, reads its CHANGELOG at `packages/{package-name}/CHANGELOG.md` +3. Extracts PR numbers from the latest version section using regex +4. Groups all PRs and tracks which packages they contributed to +5. Posts a single comment per PR listing all packages it was released in +6. Uses the `gh` CLI to post comments via the GitHub API + +## Troubleshooting + +**No comments are posted:** +- Verify your CHANGELOGs have PR links in the correct format +- Check that `steps.changesets.outputs.published` is `true` +- Ensure the workflow has `pull-requests: write` permission + +**Script fails to find CHANGELOGs:** +- The script expects packages at `packages/{package-name}/CHANGELOG.md` +- Package name should match after removing the scope (e.g., `@tanstack/query` → `query`) From 8a59ad759fd753c958b6918022b7cf195c8a6a4e Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Sun, 12 Oct 2025 13:26:04 -0600 Subject: [PATCH 5/7] format --- .github/comment-on-release/README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/comment-on-release/README.md b/.github/comment-on-release/README.md index 72baebdd..72394322 100644 --- a/.github/comment-on-release/README.md +++ b/.github/comment-on-release/README.md @@ -5,6 +5,7 @@ A reusable GitHub Action that automatically comments on PRs when they are includ ## What It Does When packages are published via Changesets: + 1. Parses each published package's CHANGELOG to find PR numbers in the latest version 2. Groups PRs by number (handling cases where one PR affects multiple packages) 3. Posts a comment on each PR with release info and CHANGELOG links @@ -31,8 +32,8 @@ Add this step to your `.github/workflows/release.yml` file after the `changesets with: version: pnpm run changeset:version publish: pnpm run changeset:publish - commit: "ci: Version Packages" - title: "ci: Version Packages" + commit: 'ci: Version Packages' + title: 'ci: Version Packages' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} @@ -53,13 +54,14 @@ Add this step to your `.github/workflows/release.yml` file after the `changesets ## Inputs -| Input | Required | Description | -|-------|----------|-------------| -| `published-packages` | Yes | JSON string of published packages from `changesets/action` outputs | +| Input | Required | Description | +| -------------------- | -------- | ------------------------------------------------------------------ | +| `published-packages` | Yes | JSON string of published packages from `changesets/action` outputs | ## How It Works The action: + 1. Receives the list of published packages from the Changesets action 2. For each package, reads its CHANGELOG at `packages/{package-name}/CHANGELOG.md` 3. Extracts PR numbers from the latest version section using regex @@ -70,10 +72,12 @@ The action: ## Troubleshooting **No comments are posted:** + - Verify your CHANGELOGs have PR links in the correct format - Check that `steps.changesets.outputs.published` is `true` - Ensure the workflow has `pull-requests: write` permission **Script fails to find CHANGELOGs:** + - The script expects packages at `packages/{package-name}/CHANGELOG.md` - Package name should match after removing the scope (e.g., `@tanstack/query` → `query`) From 9d78db6ea18346d668e0fead6e89805a04b487ef Mon Sep 17 00:00:00 2001 From: Lachlan Collins <1667261+lachlancollins@users.noreply.github.com> Date: Tue, 14 Oct 2025 19:08:44 +1100 Subject: [PATCH 6/7] Slight tweaks --- .github/comment-on-release/action.yml | 3 +-- .github/comment-on-release/comment-on-release.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/comment-on-release/action.yml b/.github/comment-on-release/action.yml index 57556952..6b73e8da 100644 --- a/.github/comment-on-release/action.yml +++ b/.github/comment-on-release/action.yml @@ -11,5 +11,4 @@ runs: shell: bash env: PUBLISHED_PACKAGES: ${{ inputs.published-packages }} - run: | - npx tsx ${{ github.action_path }}/comment-on-release.ts + run: node {{ github.action_path }}/comment-on-release.ts diff --git a/.github/comment-on-release/comment-on-release.ts b/.github/comment-on-release/comment-on-release.ts index 5a7db6a1..994efa0c 100644 --- a/.github/comment-on-release/comment-on-release.ts +++ b/.github/comment-on-release/comment-on-release.ts @@ -2,6 +2,7 @@ import { readFileSync } from 'node:fs' import { resolve } from 'node:path' +import { execSync } from 'node:child_process' interface PublishedPackage { name: string @@ -119,7 +120,6 @@ async function commentOnPR(pr: PRInfo): Promise { try { // Use gh CLI to post the comment - const { execSync } = await import('node:child_process') execSync(`gh pr comment ${number} --body ${JSON.stringify(comment)}`, { stdio: 'inherit', }) From b085b94a23779be1ced71ee5e0124365bdfb1a1a Mon Sep 17 00:00:00 2001 From: Lachlan Collins <1667261+lachlancollins@users.noreply.github.com> Date: Tue, 14 Oct 2025 19:23:07 +1100 Subject: [PATCH 7/7] Tweak output comment, fix URL --- .github/comment-on-release/README.md | 6 ++--- .github/comment-on-release/action.yml | 1 + .../comment-on-release/comment-on-release.ts | 27 +++++++++++-------- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/.github/comment-on-release/README.md b/.github/comment-on-release/README.md index 72394322..28427970 100644 --- a/.github/comment-on-release/README.md +++ b/.github/comment-on-release/README.md @@ -15,8 +15,8 @@ When packages are published via Changesets: ``` 🎉 This PR has been released! -- **@tanstack/query@5.0.0** - [CHANGELOG](https://github.com/TanStack/query/blob/main/packages/query-core/CHANGELOG.md) -- **@tanstack/react-query@5.0.0** - [CHANGELOG](https://github.com/TanStack/query/blob/main/packages/react-query/CHANGELOG.md) +- [@tanstack/query-core@5.0.0](https://github.com/TanStack/query/blob/main/packages/query-core/CHANGELOG.md#500) +- [@tanstack/react-query@5.0.0](https://github.com/TanStack/query/blob/main/packages/react-query/CHANGELOG.md#500) Thank you for your contribution! ``` @@ -80,4 +80,4 @@ The action: **Script fails to find CHANGELOGs:** - The script expects packages at `packages/{package-name}/CHANGELOG.md` -- Package name should match after removing the scope (e.g., `@tanstack/query` → `query`) +- Package name should match after removing the scope (e.g., `@tanstack/query-core` → `query-core`) diff --git a/.github/comment-on-release/action.yml b/.github/comment-on-release/action.yml index 6b73e8da..0ea680b4 100644 --- a/.github/comment-on-release/action.yml +++ b/.github/comment-on-release/action.yml @@ -11,4 +11,5 @@ runs: shell: bash env: PUBLISHED_PACKAGES: ${{ inputs.published-packages }} + REPOSITORY: ${{ github.repository }} run: node {{ github.action_path }}/comment-on-release.ts diff --git a/.github/comment-on-release/comment-on-release.ts b/.github/comment-on-release/comment-on-release.ts index 994efa0c..8a7d088a 100644 --- a/.github/comment-on-release/comment-on-release.ts +++ b/.github/comment-on-release/comment-on-release.ts @@ -11,7 +11,7 @@ interface PublishedPackage { interface PRInfo { number: number - packages: Array<{ name: string; version: string }> + packages: Array<{ name: string; pkgPath: string; version: string }> } /** @@ -79,12 +79,8 @@ function groupPRsByNumber( const prMap = new Map() for (const pkg of publishedPackages) { - const changelogPath = resolve( - process.cwd(), - 'packages', - pkg.name.replace('@tanstack/', ''), - 'CHANGELOG.md', - ) + const pkgPath = `packages/${pkg.name.replace('@tanstack/', '')}` + const changelogPath = resolve(process.cwd(), pkgPath, 'CHANGELOG.md') const prNumbers = extractPRsFromChangelog(changelogPath, pkg.version) @@ -94,6 +90,7 @@ function groupPRsByNumber( } prMap.get(prNumber)!.packages.push({ name: pkg.name, + pkgPath: pkgPath, version: pkg.version, }) } @@ -105,15 +102,16 @@ function groupPRsByNumber( /** * Post a comment on a GitHub PR using gh CLI */ -async function commentOnPR(pr: PRInfo): Promise { +async function commentOnPR(pr: PRInfo, repository: string): Promise { const { number, packages } = pr // Build the comment body let comment = `🎉 This PR has been released!\n\n` for (const pkg of packages) { - const changelogUrl = `https://github.com/TanStack/config/blob/main/packages/${pkg.name.replace('@tanstack/', '')}/CHANGELOG.md` - comment += `- **${pkg.name}@${pkg.version}** - [CHANGELOG](${changelogUrl})\n` + // Link to the package's changelog and version anchor + const changelogUrl = `https://github.com/${repository}/blob/main/${pkg.pkgPath}/CHANGELOG.md#${pkg.version.replaceAll('.', '')}` + comment += `- [${pkg.name}@${pkg.version}](${changelogUrl})\n` } comment += `\nThank you for your contribution!` @@ -135,11 +133,18 @@ async function commentOnPR(pr: PRInfo): Promise { async function main() { // Read published packages from environment variable (set by GitHub Actions) const publishedPackagesJson = process.env.PUBLISHED_PACKAGES + const repository = process.env.REPOSITORY + if (!publishedPackagesJson) { console.log('No packages were published. Skipping PR comments.') return } + if (!repository) { + console.log('Repository is missing. Skipping PR comments.') + return + } + let publishedPackages: Array try { publishedPackages = JSON.parse(publishedPackagesJson) @@ -167,7 +172,7 @@ async function main() { // Comment on each PR for (const pr of prMap.values()) { - await commentOnPR(pr) + await commentOnPR(pr, repository) } console.log('✓ Done!')