@@ -10,6 +10,17 @@ inputs:
1010 description : GitHub user to Slack user mapping
1111 required : false
1212 default : " {}"
13+ display-pr-details :
14+ description : Whether to display associated PR details in the step summary
15+ required : false
16+ default : " false"
17+ export-file-changes :
18+ description : |
19+ Newline-separated list of filenames to check for changes in the PR.
20+ When provided, populates the `changes` output with the subset of these files
21+ that were modified in the PR.
22+ required : false
23+ default : " "
1324
1425outputs :
1526 number :
@@ -31,11 +42,16 @@ outputs:
3142 description : The head repository full name of the PR
3243 value : ${{ steps.associated-pr.outputs.head_repo }}
3344 labels :
34- description : Comma-separated list of PR labels
45+ description : JSON array of PR label names (use `fromJSON(...)` in expressions)
3546 value : ${{ steps.associated-pr.outputs.labels }}
3647 closed :
3748 description : Whether the PR is closed
3849 value : ${{ steps.associated-pr.outputs.closed }}
50+ changes :
51+ description : |
52+ JSON array of filenames from `export-file-changes` that were modified in the PR.
53+ Empty array when `export-file-changes` is not provided or no PR was found.
54+ value : ${{ steps.associated-pr.outputs.changes }}
3955
4056runs :
4157 using : " composite"
@@ -73,36 +89,44 @@ runs:
7389 env :
7490 GH_USER_TO_SLACK_USER : ${{ inputs.gh-user-to-slack-user }}
7591 INPUT_SHA : ${{ steps.get-sha.outputs.sha }}
92+ INPUT_EXPORT_FILE_CHANGES : ${{ inputs.export-file-changes }}
7693 with :
7794 script : | # js
7895 const commitSha = process.env.INPUT_SHA || context.sha;
79- let pr = context.payload.pull_request;
80-
81- if (!pr || pr.head?.sha !== commitSha) {
82- const maxRetries = 5;
83- const baseDelay = 2000;
84-
85- // Retry logic to handle timing issues with GitHub API
86- // Sometimes the commit-PR association isn't immediately available after merge
87- for (let attempt = 0; attempt < maxRetries; attempt++) {
88- if (attempt > 0) {
89- // Exponential backoff: 2s, 4s, 8s, 16s, 32s
90- const delay = baseDelay * Math.pow(2, attempt - 1);
91- core.info(`PR not found, retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries})...`);
92- await new Promise(resolve => setTimeout(resolve, delay));
93- }
94-
95- const response = await github.rest.repos.listPullRequestsAssociatedWithCommit({
96- commit_sha: commitSha,
97- owner: context.repo.owner,
98- repo: context.repo.repo,
99- });
100-
101- if (response.data && response.data.length > 0) {
102- pr = response.data[0];
103- core.info(`Found PR #${pr.number} on attempt ${attempt + 1}`);
104- break;
105- }
96+ const filesToTrack = (process.env.INPUT_EXPORT_FILE_CHANGES || '')
97+ .split('\n')
98+ .map(s => s.trim())
99+ .filter(Boolean);
100+ core.setOutput('changes', '[]');
101+ core.setOutput('labels', '[]');
102+
103+ let pr;
104+ const maxRetries = 5;
105+ const baseDelay = 2000;
106+
107+ // Always fetch from the API rather than reading context.payload.pull_request:
108+ // the webhook payload is frozen at event-fire time and does not reflect
109+ // label/metadata changes made afterwards.
110+ // Retry logic also handles timing issues where the commit-PR association
111+ // isn't immediately available after merge.
112+ for (let attempt = 0; attempt < maxRetries; attempt++) {
113+ if (attempt > 0) {
114+ // Exponential backoff: 2s, 4s, 8s, 16s, 32s
115+ const delay = baseDelay * Math.pow(2, attempt - 1);
116+ core.info(`PR not found, retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries})...`);
117+ await new Promise(resolve => setTimeout(resolve, delay));
118+ }
119+
120+ const response = await github.rest.repos.listPullRequestsAssociatedWithCommit({
121+ commit_sha: commitSha,
122+ owner: context.repo.owner,
123+ repo: context.repo.repo,
124+ });
125+
126+ if (response.data && response.data.length > 0) {
127+ pr = response.data[0];
128+ core.info(`Found PR #${pr.number} on attempt ${attempt + 1}`);
129+ break;
106130 }
107131 }
108132
@@ -124,11 +148,33 @@ runs:
124148 core.setOutput('title', pr.title);
125149 core.setOutput('head_ref', pr.head.ref);
126150 core.setOutput('head_repo', pr.head.repo?.full_name || '');
127- core.setOutput('labels', ( pr.labels || []).map(l => l.name).join(',' ));
151+ core.setOutput('labels', JSON.stringify(( pr.labels || []).map(l => l.name)));
128152 core.setOutput('closed', pr.closed_at ? 'true' : 'false');
129153
130154 const author = pr.merged_by?.login || pr.user.login;
131155 core.setOutput('author', author);
132156
133157 const userMapping = JSON.parse(process.env.GH_USER_TO_SLACK_USER || '{}');
134158 core.setOutput('slack-user', userMapping[author] || "");
159+
160+ if (filesToTrack.length > 0) {
161+ const files = await github.paginate(github.rest.pulls.listFiles, {
162+ owner: context.repo.owner,
163+ repo: context.repo.repo,
164+ pull_number: pr.number,
165+ });
166+ const changedFilenames = new Set(files.map(f => f.filename));
167+ const changes = filesToTrack.filter(f => changedFilenames.has(f));
168+ core.setOutput('changes', JSON.stringify(changes));
169+ }
170+
171+ - name : Display associated PR details
172+ if : steps.associated-pr.outputs.number != '' && inputs.display-pr-details == 'true'
173+ env :
174+ PR_NUMBER : ${{ steps.associated-pr.outputs.number }}
175+ PR_TITLE : ${{ steps.associated-pr.outputs.title }}
176+ shell : bash
177+ run : | # shell
178+ pr_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/pull/${PR_NUMBER}"
179+ echo "### Associated PR" >> "$GITHUB_STEP_SUMMARY"
180+ echo "[#${PR_NUMBER} - ${PR_TITLE}](${pr_url})" >> "$GITHUB_STEP_SUMMARY"
0 commit comments