name: Output API Diff on: issue_comment: types: [created] permissions: {} concurrency: group: api-diff-${{ github.event.issue.number }} cancel-in-progress: true jobs: api-diff: name: Output API Diff if: >- github.event.issue.pull_request && contains(github.event.comment.body, '/api-diff') && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) runs-on: ubuntu-latest permissions: contents: read pull-requests: write steps: - name: Check maintainer permission uses: actions/github-script@v7 with: script: | const { data: permLevel } = await github.rest.repos.getCollaboratorPermissionLevel({ owner: context.repo.owner, repo: context.repo.repo, username: context.payload.comment.user.login, }); const allowed = ['admin', 'maintain', 'write']; if (!allowed.includes(permLevel.permission)) { core.setFailed(`User @${context.payload.comment.user.login} does not have write access.`); } - name: Add reaction to acknowledge command uses: actions/github-script@v7 with: script: | await github.rest.reactions.createForIssueComment({ owner: context.repo.owner, repo: context.repo.repo, comment_id: context.payload.comment.id, content: 'eyes', }); - name: Get PR branch info id: pr uses: actions/github-script@v7 with: script: | const { data: pr } = await github.rest.pulls.get({ owner: context.repo.owner, repo: context.repo.repo, pull_number: context.issue.number, }); if (pr.head.repo.full_name !== `${context.repo.owner}/${context.repo.repo}`) { core.setFailed('Cannot run /api-diff on fork PRs — would execute untrusted code.'); return; } core.setOutput('ref', pr.head.ref); core.setOutput('sha', pr.head.sha); - name: Checkout PR branch uses: actions/checkout@v4 with: ref: ${{ steps.pr.outputs.sha }} submodules: recursive - name: Setup .NET uses: actions/setup-dotnet@v4 with: global-json-file: global.json - name: Run OutputApiDiff run: dotnet run --project ./nukebuild/_build.csproj -- OutputApiDiff - name: Post API diff as PR comment if: always() && steps.pr.outcome == 'success' uses: actions/github-script@v7 with: script: | const fs = require('fs'); const path = require('path'); const diffDir = path.join(process.env.GITHUB_WORKSPACE, 'artifacts', 'api-diff', 'markdown'); const mergedPath = path.join(diffDir, '_diff.md'); let body; if (fs.existsSync(mergedPath)) { let diff = fs.readFileSync(mergedPath, 'utf8').trim(); if (!diff || diff.toLowerCase().includes('no changes')) { body = '### API Diff\n\n✅ No public API changes detected in this PR.'; } else { const MAX_COMMENT_LENGTH = 60000; // GitHub comment limit is 65536 const header = '### API Diff\n\n'; const footer = '\n\n---\n_Generated by `/api-diff` command._'; const budget = MAX_COMMENT_LENGTH - header.length - footer.length; if (diff.length > budget) { diff = diff.substring(0, budget) + '\n\n> ⚠️ Output truncated. See the [full workflow run](' + `${process.env.GITHUB_SERVER_URL}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}` + ') for complete diff.'; } body = header + diff + footer; } } else { body = '### API Diff\n\n⚠️ No diff output was produced. Check the [workflow run](' + `${process.env.GITHUB_SERVER_URL}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}` + ') for details.'; } // Collapse into
if large if (body.length > 2000) { const inner = body; body = '
\n📋 API Diff (click to expand)\n\n' + inner + '\n\n
'; } // Update existing bot comment or create a new one const marker = ''; body = marker + '\n' + body; const { data: comments } = await github.rest.issues.listComments({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, per_page: 100, }); const existing = comments.find(c => c.body?.includes(marker)); if (existing) { await github.rest.issues.updateComment({ owner: context.repo.owner, repo: context.repo.repo, comment_id: existing.id, body, }); } else { await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, body, }); } - name: Add success reaction if: success() uses: actions/github-script@v7 with: script: | await github.rest.reactions.createForIssueComment({ owner: context.repo.owner, repo: context.repo.repo, comment_id: context.payload.comment.id, content: 'rocket', }); - name: Report failure if: failure() uses: actions/github-script@v7 with: script: | await github.rest.reactions.createForIssueComment({ owner: context.repo.owner, repo: context.repo.repo, comment_id: context.payload.comment.id, content: '-1', }); await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, body: `❌ \`/api-diff\` failed. [See logs](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}).`, });