Skip to content

Commit bc0c765

Browse files
committed
feat: Add 'results' output
1 parent e656d33 commit bc0c765

File tree

10 files changed

+81
-65
lines changed

10 files changed

+81
-65
lines changed

.github/actions/fix/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,9 @@ Attempts to fix issues with Copilot.
1717
#### `token`
1818

1919
**Required** Personal access token (PAT) with fine-grained permissions 'issues: write' and 'pull_requests: write'.
20+
21+
### Outputs
22+
23+
#### `fixings`
24+
25+
List of pull requests filed (and their associated issues), as stringified JSON. For example: `'[{"issue":{"id":1,"nodeId":"SXNzdWU6MQ==","url":"https://github.com/github/docs/issues/123","title":"Accessibility issue: 1"},"pullRequest":{"url":"https://github.com/github/docs/pulls/124"}}]'`

.github/actions/fix/action.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ inputs:
1212
description: "Personal access token (PAT) with fine-grained permissions 'issues: write' and 'pull_requests: write'"
1313
required: true
1414

15+
outputs:
16+
fixings:
17+
description: "List of pull requests filed (and their associated issues), as stringified JSON"
18+
1519
runs:
1620
using: "node24"
1721
main: "bootstrap.js"

.github/actions/fix/src/Issue.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
1-
import { IssueInput } from "./types.d.js";
1+
import { Issue as IssueInput } from "./types.d.js";
22

3-
interface IIssue extends IssueInput{
4-
owner: string;
5-
repository: string;
6-
issueNumber: number;
7-
}
8-
9-
export class Issue implements IIssue {
3+
export class Issue implements IssueInput {
104
/**
115
* Extracts owner, repository, and issue number from a GitHub issue URL.
126
* @param issueUrl A GitHub issue URL (e.g. `https://github.com/owner/repo/issues/42`).

.github/actions/fix/src/fixIssue.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export async function fixIssue(octokit: Octokit, { owner, repository, issueNumbe
5454
return;
5555
}
5656
// Assign issue to Copilot
57-
await octokit.graphql<{
57+
const response = await octokit.graphql<{
5858
replaceActorsForAssignable: {
5959
assignable: {
6060
id: string;
@@ -83,4 +83,9 @@ export async function fixIssue(octokit: Octokit, { owner, repository, issueNumbe
8383
}`,
8484
{ issueId, assigneeId: suggestedActorsResponse?.repository?.suggestedActors?.nodes[0]?.id }
8585
);
86+
return {
87+
nodeId: response.replaceActorsForAssignable.assignable.id,
88+
url: response.replaceActorsForAssignable.assignable.url,
89+
title: response.replaceActorsForAssignable.assignable.title,
90+
}
8691
}

.github/actions/fix/src/index.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { IssueInput } from "./types.d.js";
1+
import type { Issue as IssueInput, Fixing } from "./types.d.js";
22
import process from "node:process";
33
import core from "@actions/core";
44
import { Octokit } from "@octokit/core";
@@ -40,17 +40,27 @@ export default async function () {
4040
},
4141
},
4242
});
43-
for (const issueInput of issues) {
43+
const fixings: Fixing[] = issues.map((issue) => ({ issue })) as Fixing[];
44+
45+
for (const fixing of fixings) {
4446
try {
45-
const issue = new Issue(issueInput);
46-
await fixIssue(octokit, issue);
47+
const issue = new Issue(fixing.issue);
48+
const response = await fixIssue(octokit, issue);
49+
if (response) {
50+
fixing.pullRequest = response;
51+
}
4752
core.info(
4853
`Assigned ${issue.owner}/${issue.repository}#${issue.issueNumber} to Copilot!`
4954
);
5055
} catch (error) {
51-
core.setFailed(`Failed to assign ${issueInput.url} to Copilot: ${error}`);
56+
core.setFailed(
57+
`Failed to assign ${fixing.issue.url} to Copilot: ${error}`
58+
);
5259
process.exit(1);
5360
}
5461
}
62+
63+
core.setOutput("fixings", JSON.stringify(fixings));
64+
core.debug(`Output: 'fixings: ${JSON.stringify(fixings)}'`);
5565
core.info("Finished 'fix' action");
5666
}

.github/actions/fix/src/types.d.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
1-
export type IssueInput = {
1+
export type Issue = {
22
url: string;
33
nodeId?: string;
4-
};
4+
};
5+
6+
export type PullRequest = {
7+
url: string;
8+
nodeId?: string;
9+
};
10+
11+
export type Fixing = {
12+
issue: Issue;
13+
pullRequest: PullRequest;
14+
};

.github/workflows/test.yml

Lines changed: 4 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ jobs:
6161
id: cache_key
6262
shell: bash
6363
run: |
64-
echo "cache_key=$(printf 'cached_filings-%s-%s.json' "${{ matrix.site }}" "${{ github.ref_name }}" | tr -cs 'A-Za-z0-9._-' '_')" >> $GITHUB_OUTPUT
64+
echo "cache_key=$(printf 'cached_results-%s-%s.json' "${{ matrix.site }}" "${{ github.ref_name }}" | tr -cs 'A-Za-z0-9._-' '_')" >> $GITHUB_OUTPUT
6565
6666
- name: Scan site (${{ matrix.site }})
6767
uses: ./
@@ -78,54 +78,12 @@ jobs:
7878
token: ${{ secrets.GH_TOKEN }}
7979
cache_key: ${{ steps.cache_key.outputs.cache_key }}
8080

81-
- name: Retrieve cached filings
81+
- name: Retrieve cached results
8282
uses: ./.github/actions/gh-cache/restore
8383
with:
8484
path: ${{ steps.cache_key.outputs.cache_key }}
8585
token: ${{ secrets.GITHUB_TOKEN }}
8686

87-
- name: Add PR URLs to filings
88-
uses: actions/github-script@v8
89-
with:
90-
github-token: ${{ secrets.GITHUB_TOKEN }}
91-
script: |
92-
const fs = require('fs');
93-
if (!process.env.CACHE_PATH || !fs.existsSync(process.env.CACHE_PATH)) {
94-
core.info("Skipping 'Add PR URLs to filings' (no cached filings).");
95-
return;
96-
}
97-
const filings = JSON.parse(fs.readFileSync(process.env.CACHE_PATH, 'utf-8'));
98-
for (const filing of filings) {
99-
if (!filing?.issue.url) {
100-
continue;
101-
}
102-
const { owner, repo, issueNumber } = /https:\/\/github\.com\/(?<owner>[^/]+)\/(?<repo>[^/]+)\/issues\/(?<issueNumber>\d+)/.exec(filing.issue.url).groups;
103-
const query = `query($owner: String!, $repo: String!, $issueNumber: Int!) {
104-
repository(owner: $owner, name: $repo) {
105-
issue(number: $issueNumber) {
106-
timelineItems(first: 100, itemTypes: [CONNECTED_EVENT, CROSS_REFERENCED_EVENT]) {
107-
nodes {
108-
... on CrossReferencedEvent { source { ... on PullRequest { url } } }
109-
... on ConnectedEvent { subject { ... on PullRequest { url } } }
110-
}
111-
}
112-
}
113-
}
114-
}`;
115-
const variables = { owner, repo, issueNumber: parseInt(issueNumber, 10) }
116-
const result = await github.graphql(query, variables)
117-
const timelineNodes = result?.repository?.issue?.timelineItems?.nodes || [];
118-
const pullRequestNode = timelineNodes.find(n => n?.source?.url || n?.subject?.url);
119-
if (pullRequestNode) {
120-
filing.pullRequest = { url: pullRequestNode.source?.url || pullRequestNode.subject?.url };
121-
} else {
122-
core.info(`No pull request found for issue: ${filing.issue.url}`);
123-
}
124-
}
125-
fs.writeFileSync(process.env.CACHE_PATH, JSON.stringify(filings));
126-
env:
127-
CACHE_PATH: ${{ steps.cache_key.outputs.cache_key }}
128-
12987
- name: Validate scan results (${{ matrix.site }})
13088
run: |
13189
npm ci
@@ -140,7 +98,7 @@ jobs:
14098
run: |
14199
set -euo pipefail
142100
if [[ ! -f "${{ steps.cache_key.outputs.cache_key }}" ]]; then
143-
echo "Skipping 'Clean up issues and pull requests' (no cached filings)."
101+
echo "Skipping 'Clean up issues and pull requests' (no cached results)."
144102
exit 0
145103
fi
146104
jq -r '
@@ -162,7 +120,7 @@ jobs:
162120
env:
163121
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
164122

165-
- name: Clean up cached filings
123+
- name: Clean up cached results
166124
if: ${{ always() }}
167125
uses: ./.github/actions/gh-cache/delete
168126
with:

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ Trigger the workflow manually or automatically based on your configuration. The
9696
| `urls` | Yes | Newline-delimited list of URLs to scan | `https://primer.style`<br>`https://primer.style/octicons` |
9797
| `repository` | Yes | Repository (with owner) for issues and PRs | `primer/primer-docs` |
9898
| `token` | Yes | PAT with write permissions (see above) | `${{ secrets.GH_TOKEN }}` |
99-
| `cache_key` | Yes | Key for caching findings across runs<br>Allowed: `A-Za-z0-9._/-` | `cached_filings-main-primer.style.json` |
99+
| `cache_key` | Yes | Key for caching results across runs<br>Allowed: `A-Za-z0-9._/-` | `cached_results-primer.style-main.json` |
100100
| `login_url` | No | If scanned pages require authentication, the URL of the login page | `https://github.com/login` |
101101
| `username` | No | If scanned pages require authentication, the username to use for login | `some-user` |
102102
| `password` | No | If scanned pages require authentication, the password to use for login | `correct-horse-battery-staple` |

action.yml

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ inputs:
1313
description: "Personal access token (PAT) with fine-grained permissions 'contents: write', 'issues: write', and 'pull_requests: write'"
1414
required: true
1515
cache_key:
16-
description: "Key for caching findings across runs"
16+
description: "Key for caching results across runs"
1717
required: true
1818
login_url:
1919
description: "If scanned pages require authentication, the URL of the login page"
@@ -32,6 +32,10 @@ inputs:
3232
required: false
3333
default: "false"
3434

35+
outputs:
36+
results:
37+
description: "List of issues and pull requests filed (and their associated finding(s)), as stringified JSON"
38+
3539
runs:
3640
using: "composite"
3741
steps:
@@ -89,11 +93,35 @@ runs:
8993
issues: ${{ steps.get_issues_from_filings.outputs.issues }}
9094
repository: ${{ inputs.repository }}
9195
token: ${{ inputs.token }}
96+
- name: Set results output
97+
id: results
98+
uses: actions/github-script@v8
99+
with:
100+
script: |
101+
const filings = JSON.parse(process.env.FILINGS || '[]');
102+
const fixings = JSON.parse(process.env.FIXINGS || '[]');
103+
const fixingsByIssueUrl = fixings.reduce((acc, fixing) => {
104+
if (fixing.issue && fixing.issue.url) {
105+
acc[fixing.issue.url] = fixing;
106+
}
107+
return acc;
108+
}, {});
109+
const results = filings;
110+
for (const result of results) {
111+
if (result.issue && result.issue.url && fixingsByIssueUrl[result.issue.url]) {
112+
result.pullRequest = fixingsByIssueUrl[result.issue.url].pullRequest;
113+
}
114+
}
115+
core.setOutput('results', JSON.stringify(results));
116+
core.debug(`Results: ${JSON.stringify(results)}`);
117+
env:
118+
FILINGS: ${{ steps.file.outputs.filings }}
119+
FIXINGS: ${{ steps.fix.outputs.fixings }}
92120
- name: Save cached results
93121
uses: ./.github/actions/gh-cache/cache
94122
with:
95123
key: ${{ inputs.cache_key }}
96-
value: ${{ steps.file.outputs.filings }}
124+
value: ${{ steps.results.outputs.results }}
97125
token: ${{ inputs.token }}
98126

99127
branding:

tests/types.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export type Issue = {
1919

2020
export type PullRequest = {
2121
url: string;
22+
nodeId: string;
2223
};
2324

2425
export type Result = {

0 commit comments

Comments
 (0)