From 5b1727acfe08efc4e610905652248238b3bef37d Mon Sep 17 00:00:00 2001 From: castillios Date: Tue, 15 Sep 2026 16:45:04 -0700 Subject: [PATCH 01/10] add default permissions to pr trigger workflow --- .github/workflows/pull-request-trigger.yml | 4 ++++ .github/workflows/wr-pull-request-trigger.yml | 3 +++ 2 files changed, 7 insertions(+) diff --git a/.github/workflows/pull-request-trigger.yml b/.github/workflows/pull-request-trigger.yml index faf26acb48..d2e35d008a 100644 --- a/.github/workflows/pull-request-trigger.yml +++ b/.github/workflows/pull-request-trigger.yml @@ -5,6 +5,10 @@ on: branches: - 'gh-pages' +permissions: + contents: read + issues: write + jobs: Check-For-Linked-Issue: runs-on: ubuntu-latest diff --git a/.github/workflows/wr-pull-request-trigger.yml b/.github/workflows/wr-pull-request-trigger.yml index c9afa97614..e7e5f4ab97 100644 --- a/.github/workflows/wr-pull-request-trigger.yml +++ b/.github/workflows/wr-pull-request-trigger.yml @@ -4,6 +4,9 @@ on: workflows: ["Pull Request Trigger"] types: [completed] +permissions: + contents: read + jobs: Hello-World: runs-on: ubuntu-latest From 812c9b9f905812ae4f8e67fe2d9d8857baec99d4 Mon Sep 17 00:00:00 2001 From: castillios Date: Wed, 30 Sep 2026 17:39:56 -0700 Subject: [PATCH 02/10] refactor PR trigger on issue # by splitting logic - splits the logic in check-linked-issue.js between checking for a linked issue and posting a comment if it is needed - utilize artifacts to pass comment data into its corresponding workflow run - pr trigger purely handles checking for linked issues - wr-pull-request-trigger now handles comment logic --- .github/workflows/pull-request-trigger.yml | 25 ++- .github/workflows/wr-pull-request-trigger.yml | 25 ++- .../trigger-pr/check-linked-issue.js | 178 +++++++++++++++--- 3 files changed, 200 insertions(+), 28 deletions(-) diff --git a/.github/workflows/pull-request-trigger.yml b/.github/workflows/pull-request-trigger.yml index d2e35d008a..1f76ef0b2e 100644 --- a/.github/workflows/pull-request-trigger.yml +++ b/.github/workflows/pull-request-trigger.yml @@ -13,6 +13,10 @@ jobs: Check-For-Linked-Issue: runs-on: ubuntu-latest if: ${{ github.event.action == 'opened' || github.event.action == 'edited' }} + env: + ARTIFACT_DIR: './pull-request-trigger/artifacts' + ARTIFACT_FILE: 'pr-comment.json' + SCRIPT_PATH: './github-actions/trigger-pr/check-linked-issue.js' steps: - name: Checkout repository uses: actions/checkout@v7 @@ -22,5 +26,22 @@ jobs: uses: actions/github-script@v9 with: script: | - const script = require('./github-actions/trigger-pr/check-linked-issue.js') - script({g: github, c: context}) + const { checkForLinkedIssue } = require('${{ env.SCRIPT_PATH }}'); + return await checkForLinkedIssue({ github, context, core }); + # Do not upload artifact if there is no PR comment to be posted + - name: Save PR comment + id: save-pr-comment + if: fromJson(steps.check-for-keyword.outputs.result).prComment != '' + run: | + mkdir -p '${{ env.ARTIFACT_DIR }}' + echo ${{ steps.check-for-keyword.outputs.result }} > '${{ env.ARTIFACT_DIR }}/${{ env.ARTIFACT_FILE }}' + + - name: Upload PR comment artifact + id: upload-pr-comment-artifact + uses: actions/upload-artifact@v7 + if: fromJson(steps.check-for-keyword.outputs.result).prComment != '' + with: + name: pr-comment + path: '${{ env.ARTIFACT_DIR }}/${{ env.ARTIFACT_FILE }}' + retention-days: 1 + diff --git a/.github/workflows/wr-pull-request-trigger.yml b/.github/workflows/wr-pull-request-trigger.yml index e7e5f4ab97..b96d01acde 100644 --- a/.github/workflows/wr-pull-request-trigger.yml +++ b/.github/workflows/wr-pull-request-trigger.yml @@ -6,9 +6,30 @@ on: permissions: contents: read + issues: write jobs: - Hello-World: + post-pr-comment: runs-on: ubuntu-latest + env: + ARTIFACT_NAME: 'pr-comment' + SCRIPT_PATH: './github-actions/trigger-pr/check-linked-issue.js' steps: - - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." \ No newline at end of file + - uses: actions/checkout@v7 + - name: Download PR comment artifact + id: download-pr-comment-artifact + uses: actions/github-script@v9 + with: + script: | + const { downloadPRCommentArtifact } = require('${{ env.SCRIPT_PATH }}'); + await downloadPRCommentArtifact({ github, context }, '${{ env.ARTIFACT_NAME }}'); + - name: Extract artifact + id: extract-artifact + run: unzip '${{ env.ARTIFACT_NAME }}.zip' + - name: Post comment to PR + id: post-comment-to-pr + uses: actions/github-script@v9 + with: + script: | + const { postPRComment } = require('${{ env.SCRIPT_PATH }}'); + await postPRComment({ github, context, core }, '${{ env.ARTIFACT_NAME }}.json'); diff --git a/github-actions/trigger-pr/check-linked-issue.js b/github-actions/trigger-pr/check-linked-issue.js index 066edd5b67..9d7ed266e0 100644 --- a/github-actions/trigger-pr/check-linked-issue.js +++ b/github-actions/trigger-pr/check-linked-issue.js @@ -1,54 +1,184 @@ // Import modules +const fs = require('fs'); const postIssueComment = require('../utils/post-issue-comment'); // Global variables -var github; -var context; +// var github; +// var context; -async function main({ g, c }) { - github = g; - context = c; +/** ************************************** + ** HELPER FUNCTIONS + *************************************** */ - // Retrieve body of context.payload and search for GitHub keywords followed by - // '#' + number. Exclude any matches that are in a comment within the PR body +/** + * Returns resulting Object from the GitHub API downloadArtifact call. + * This function is a wrapper for the downloadArtifact method in the + * octokit/rest.js client. + * https://octokit.github.io/rest.js/v18#actions-download-artifact + * @param {Object} packages.github - The octokit/rest.js client + * @param {Object} packages.context - The context of the workflow run + * @param {Object} artifact - The artifact + * @returns {Object} The resulting Object + */ +async function _downloadArtifact({github, context}, artifact) { + const { owner, repo } = context.repo; + const { id: artifact_id } = artifact; + return await github.rest.actions.downloadArtifact({ + owner, + repo, + artifact_id, + archive_format: 'zip', + }); +} + +/** + * Returns the resulting Object from the GitHub API listWorkflowRunArtifacts + * call. + * This function is a wrapper for the listWorkflowRunArtifacts method in the + * octokit/rest.js client. + * https://octokit.github.io/rest.js/v18#actions-list-workflow-run-artifacts + * @param {Object} packages.github - The octokit/rest.js client + * @param {Object} packages.context - The context of the workflow run + * @returns {Object} The resulting Object + */ +async function _listWorkflowRunArtifacts({ github, context }) { + const { owner, repo } = context.repo; + const { id: run_id } = context.payload.workflow_run; + return await github.rest.actions.listWorkflowRunArtifacts({ + owner, + repo, + run_id, + }); +} + +/** + * Returns PR data containing the body, PR number, and owner + * by retrieving data from context.payload + * @param {Object} packages.context - The context of the workflow run + * @param {Object} packages.core - The @actions/core package + * @returns {Object} An object containing the pull request body, number and owner. + */ +function _retrievePRBody({ context, core }) { const prBody = context.payload.pull_request.body; const prNumber = context.payload.pull_request.number; const prOwner = context.payload.pull_request.user.login; + + core.info(`Found PR #${prNumber} authored by ${prOwner}`); + + + return { body: prBody, number: prNumber, owner: prOwner }; +} + + +/** + * Returns the resulting Object from the GitHub API get issue call. + * This function servers as a wrapper for the get issue method in the + * octokit.rest.js client. + * https://octokit.github.io/rest.js/v20#issues-get + * @param {Object} packages.github - The octokit/rest.js client + * @param {Object} packages.context - The context of the workflow run + * @param {string} issueNum - The issue number to be looked up + * @returns + */ +async function _checkIssueExists({ github, context }, issueNum) { + return await github.rest.issues.get({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNum, + }); +} + +/** ************************************** + ** MAIN FUNCTIONS + *************************************** */ + + /** + * Returns a PR comment if issue is not linked or linked issue could not be found. + * Otherwise, returns empty PR comment, indicating that the issue has been properly linked. + * @param {Object} packages.github - The octokit/rest.js client + * @param {Object} packages.context - The context of the workflow run + * @param {Object} packages.core - The @actions/core package + * @returns + */ +async function checkForLinkedIssue({ github, context, core }) { + const pr = _retrievePRBody({ context, core }); + + // Search for GitHub keywords followed by + // '#' + number. Exclude any matches that are in a comment within the PR body. const regex = /(?!)/gi; - const match = prBody.match(regex); - - let prComment; + const match = pr.body.match(regex); + + let prComment = ""; if (!match) { console.log('PR does not have a properly linked issue. Posting comment...'); - prComment = `@${prOwner}, this Pull Request is not linked to a valid issue. Above, on the first line of your PR, please link the number of the issue that you worked on using the format of 'Fixes #' + issue number, for example: **_Fixes #9876_**\n\nNote: Do **_not_** use the number of this PR.`; - } - - else { + prComment = `@${pr.owner}, this Pull Request is not linked to a valid issue. Above, on the first line of your PR, please link the number of the issue that you worked on using the format of 'Fixes #' + issue number, for example: **_Fixes #9876_**\n\nNote: Do **_not_** use the number of this PR.`; + } else { console.log(match[0]); let [ keyword, linkNumber ] = match[0].replaceAll('#','').split(' '); + console.log(`Found a keyword: \'${keyword}\'. Checking for legitimate linked issue...`); // Check if the linked issue exists in repo - // https://octokit.github.io/rest.js/v20#issues-get try { - await github.rest.issues.get({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: linkNumber, - }); + await _checkIssueExists({ github, context }, linkNumber); console.log(`Found an issue: \'#${linkNumber}\' in repo. Reference is a legitimate linked issue.`); } catch (error) { console.log(`Couldn\'t find issue: \'#${linkNumber}\' in repo. Posting comment...`); - prComment = `@${prOwner}, the issue number referenced above as "**${keyword} #${linkNumber}**" is not found. Please replace with a valid issue number.`; + prComment = `@${pr.owner}, the issue number referenced above as "**${keyword} #${linkNumber}**" is not found. Please replace with a valid issue number.`; } } - // If the prComment was given text, then post the comment to the PR - if (prComment) { + return JSON.stringify({ prNumber: pr.number, prComment}); +} + +/** + * Returns the file path of the downloaded artifact. + * The function downloads the artifact and stores it in a pre-determined + * location. + * @param {Object} packages.github - The octokit/rest.js client + * @param {Object} packages.context - The context of the workflow run + * @param {Object} packages.core - The @actions/core package + * @param {string} artifactName - The name of the artifact + * @returns {string} The file path of the downloaded artifact + */ +async function downloadPRCommentArtifact({ github, context }, artifactName) { + const artifacts = _listWorkflowRunArtifacts({ github, context }); + const match = artifacts.data.artifacts.filter( + ({ name }) => name === artifactName + )[0]; + + const download = await _downloadArtifact({github, context }, match); + + const filepath = `${process.env.GITHUB_WORKSPACE}/${artifactName}.zip`; + fs.writeFileSync(filepath, Buffer.from(download.data)); + return filepath; +} + +/** + * Calls postIssueComment to post a comment to the PR on GitHub + * This function us a wrapper for the postIssueComment method + * and will only be called if prComment contains text. + * @param {Object} packages.github - The octokit/rest.js client + * @param {Object} packages.context - The context of the workflow run + * @param {string} filepath - The path of the file + */ + +async function postPRComment({ github, context, cor }, filepath) { + const data = JSON.parse(fs.readFileSync(filepath, 'utf8')); + const { prNumber, prComment } = data; + + if (prComment){ + core.info(`Posting PR comment...`) postIssueComment(prNumber, prComment, github, context); + core.info(`Posted comment:`) + core.info(JSON.stringify(prComment)); } } -module.exports = main; +module.exports = { + downloadPRCommentArtifact, + checkForLinkedIssue, + postPRComment, +}; From 3a3da9ce522523f3808dd0b45836ec072643dc14 Mon Sep 17 00:00:00 2001 From: castillios Date: Wed, 30 Sep 2026 17:59:10 -0700 Subject: [PATCH 03/10] remove artifact upload flags on empty comment --- .github/workflows/pull-request-trigger.yml | 2 -- github-actions/trigger-pr/check-linked-issue.js | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull-request-trigger.yml b/.github/workflows/pull-request-trigger.yml index 1f76ef0b2e..73b96b07e5 100644 --- a/.github/workflows/pull-request-trigger.yml +++ b/.github/workflows/pull-request-trigger.yml @@ -31,7 +31,6 @@ jobs: # Do not upload artifact if there is no PR comment to be posted - name: Save PR comment id: save-pr-comment - if: fromJson(steps.check-for-keyword.outputs.result).prComment != '' run: | mkdir -p '${{ env.ARTIFACT_DIR }}' echo ${{ steps.check-for-keyword.outputs.result }} > '${{ env.ARTIFACT_DIR }}/${{ env.ARTIFACT_FILE }}' @@ -39,7 +38,6 @@ jobs: - name: Upload PR comment artifact id: upload-pr-comment-artifact uses: actions/upload-artifact@v7 - if: fromJson(steps.check-for-keyword.outputs.result).prComment != '' with: name: pr-comment path: '${{ env.ARTIFACT_DIR }}/${{ env.ARTIFACT_FILE }}' diff --git a/github-actions/trigger-pr/check-linked-issue.js b/github-actions/trigger-pr/check-linked-issue.js index 9d7ed266e0..5ff56d2feb 100644 --- a/github-actions/trigger-pr/check-linked-issue.js +++ b/github-actions/trigger-pr/check-linked-issue.js @@ -174,6 +174,8 @@ async function postPRComment({ github, context, cor }, filepath) { postIssueComment(prNumber, prComment, github, context); core.info(`Posted comment:`) core.info(JSON.stringify(prComment)); + } else { + core.info(`No comment posted. Issue is properly linked.`) } } From 830112c0c2a89b8a4bc7985726a398cd44d4c33b Mon Sep 17 00:00:00 2001 From: castillios Date: Wed, 30 Sep 2026 18:05:24 -0700 Subject: [PATCH 04/10] replace console.log with core.info --- .github/workflows/pull-request-trigger.yml | 1 - github-actions/trigger-pr/check-linked-issue.js | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pull-request-trigger.yml b/.github/workflows/pull-request-trigger.yml index 73b96b07e5..682a0ce2f9 100644 --- a/.github/workflows/pull-request-trigger.yml +++ b/.github/workflows/pull-request-trigger.yml @@ -28,7 +28,6 @@ jobs: script: | const { checkForLinkedIssue } = require('${{ env.SCRIPT_PATH }}'); return await checkForLinkedIssue({ github, context, core }); - # Do not upload artifact if there is no PR comment to be posted - name: Save PR comment id: save-pr-comment run: | diff --git a/github-actions/trigger-pr/check-linked-issue.js b/github-actions/trigger-pr/check-linked-issue.js index 5ff56d2feb..c4a569a096 100644 --- a/github-actions/trigger-pr/check-linked-issue.js +++ b/github-actions/trigger-pr/check-linked-issue.js @@ -111,21 +111,21 @@ async function checkForLinkedIssue({ github, context, core }) { let prComment = ""; if (!match) { - console.log('PR does not have a properly linked issue. Posting comment...'); + core.info('PR does not have a properly linked issue. Posting comment...'); prComment = `@${pr.owner}, this Pull Request is not linked to a valid issue. Above, on the first line of your PR, please link the number of the issue that you worked on using the format of 'Fixes #' + issue number, for example: **_Fixes #9876_**\n\nNote: Do **_not_** use the number of this PR.`; } else { - console.log(match[0]); + core.info(match[0]); let [ keyword, linkNumber ] = match[0].replaceAll('#','').split(' '); - console.log(`Found a keyword: \'${keyword}\'. Checking for legitimate linked issue...`); + core.info(`Found a keyword: \'${keyword}\'. Checking for legitimate linked issue...`); // Check if the linked issue exists in repo try { await _checkIssueExists({ github, context }, linkNumber); - console.log(`Found an issue: \'#${linkNumber}\' in repo. Reference is a legitimate linked issue.`); + core.info(`Found an issue: \'#${linkNumber}\' in repo. Reference is a legitimate linked issue.`); } catch (error) { - console.log(`Couldn\'t find issue: \'#${linkNumber}\' in repo. Posting comment...`); + core.info(`Couldn\'t find issue: \'#${linkNumber}\' in repo. Posting comment...`); prComment = `@${pr.owner}, the issue number referenced above as "**${keyword} #${linkNumber}**" is not found. Please replace with a valid issue number.`; } } From 9cd924148efe6aef37c7ed569f5e31ab609ed4ad Mon Sep 17 00:00:00 2001 From: castillios Date: Wed, 30 Sep 2026 18:10:20 -0700 Subject: [PATCH 05/10] add missing await for artifact download --- github-actions/trigger-pr/check-linked-issue.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/github-actions/trigger-pr/check-linked-issue.js b/github-actions/trigger-pr/check-linked-issue.js index c4a569a096..2f13499407 100644 --- a/github-actions/trigger-pr/check-linked-issue.js +++ b/github-actions/trigger-pr/check-linked-issue.js @@ -144,7 +144,7 @@ async function checkForLinkedIssue({ github, context, core }) { * @returns {string} The file path of the downloaded artifact */ async function downloadPRCommentArtifact({ github, context }, artifactName) { - const artifacts = _listWorkflowRunArtifacts({ github, context }); + const artifacts = await _listWorkflowRunArtifacts({ github, context }); const match = artifacts.data.artifacts.filter( ({ name }) => name === artifactName )[0]; From 2ee294adfcbd3f210b9b264f60abd23afcb0dcce Mon Sep 17 00:00:00 2001 From: castillios Date: Wed, 30 Sep 2026 18:16:38 -0700 Subject: [PATCH 06/10] fix core not defined error --- github-actions/trigger-pr/check-linked-issue.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/github-actions/trigger-pr/check-linked-issue.js b/github-actions/trigger-pr/check-linked-issue.js index 2f13499407..f4302c5f11 100644 --- a/github-actions/trigger-pr/check-linked-issue.js +++ b/github-actions/trigger-pr/check-linked-issue.js @@ -165,7 +165,7 @@ async function downloadPRCommentArtifact({ github, context }, artifactName) { * @param {string} filepath - The path of the file */ -async function postPRComment({ github, context, cor }, filepath) { +async function postPRComment({ github, context, core }, filepath) { const data = JSON.parse(fs.readFileSync(filepath, 'utf8')); const { prNumber, prComment } = data; From 77d39996e708a36f51edfd7fafdce9ceceff0488 Mon Sep 17 00:00:00 2001 From: castillios Date: Wed, 30 Sep 2026 18:29:43 -0700 Subject: [PATCH 07/10] add pull requests write permission --- .github/workflows/wr-pull-request-trigger.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/wr-pull-request-trigger.yml b/.github/workflows/wr-pull-request-trigger.yml index b96d01acde..8b1d10fabb 100644 --- a/.github/workflows/wr-pull-request-trigger.yml +++ b/.github/workflows/wr-pull-request-trigger.yml @@ -7,6 +7,7 @@ on: permissions: contents: read issues: write + pull-requests: write jobs: post-pr-comment: From 50a218d7f6be4b24ceaa14e6ead5e66b7f1abc43 Mon Sep 17 00:00:00 2001 From: castillios Date: Wed, 30 Sep 2026 19:39:23 -0700 Subject: [PATCH 08/10] remove unused globals --- github-actions/trigger-pr/check-linked-issue.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/github-actions/trigger-pr/check-linked-issue.js b/github-actions/trigger-pr/check-linked-issue.js index f4302c5f11..5218065fb0 100644 --- a/github-actions/trigger-pr/check-linked-issue.js +++ b/github-actions/trigger-pr/check-linked-issue.js @@ -2,10 +2,6 @@ const fs = require('fs'); const postIssueComment = require('../utils/post-issue-comment'); -// Global variables -// var github; -// var context; - /** ************************************** ** HELPER FUNCTIONS *************************************** */ From 478579447b3dbf6e9dd34423ad64a2c61d2cb49b Mon Sep 17 00:00:00 2001 From: castillios Date: Wed, 30 Sep 2026 19:44:42 -0700 Subject: [PATCH 09/10] remove issues write permission from pr trigger --- .github/workflows/pull-request-trigger.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/pull-request-trigger.yml b/.github/workflows/pull-request-trigger.yml index 682a0ce2f9..5973caa6a5 100644 --- a/.github/workflows/pull-request-trigger.yml +++ b/.github/workflows/pull-request-trigger.yml @@ -7,7 +7,6 @@ on: permissions: contents: read - issues: write jobs: Check-For-Linked-Issue: From 0905c0d2a0473453681e72285a2ef5c501046556 Mon Sep 17 00:00:00 2001 From: castillios Date: Wed, 30 Sep 2026 20:33:36 -0700 Subject: [PATCH 10/10] add PR number in post comment logs --- github-actions/trigger-pr/check-linked-issue.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/github-actions/trigger-pr/check-linked-issue.js b/github-actions/trigger-pr/check-linked-issue.js index 5218065fb0..578c5b550c 100644 --- a/github-actions/trigger-pr/check-linked-issue.js +++ b/github-actions/trigger-pr/check-linked-issue.js @@ -168,7 +168,7 @@ async function postPRComment({ github, context, core }, filepath) { if (prComment){ core.info(`Posting PR comment...`) postIssueComment(prNumber, prComment, github, context); - core.info(`Posted comment:`) + core.info(`Posted comment to PR #${prNumber}:`) core.info(JSON.stringify(prComment)); } else { core.info(`No comment posted. Issue is properly linked.`)