From d9d7cb261c3e12b64feda9bc217e76c6ac10e7f9 Mon Sep 17 00:00:00 2001 From: LiviaMedeiros Date: Fri, 2 Jan 2026 02:53:22 +0800 Subject: [PATCH 1/3] tools: add tool that fills `pr-url` as code review suggestion --- .github/workflows/suggest-fill-prurl.yml | 29 +++++++++ tools/actions/suggest-fill-prurl.mjs | 76 ++++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 .github/workflows/suggest-fill-prurl.yml create mode 100755 tools/actions/suggest-fill-prurl.mjs diff --git a/.github/workflows/suggest-fill-prurl.yml b/.github/workflows/suggest-fill-prurl.yml new file mode 100644 index 00000000000000..f2df50382f04ed --- /dev/null +++ b/.github/workflows/suggest-fill-prurl.yml @@ -0,0 +1,29 @@ +name: Suggest PR URL + +on: + pull_request: + types: [opened] + paths: + - doc/api/**/*.md + +permissions: + pull-requests: write + +jobs: + suggest-fill-prurl: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + with: + persist-credentials: false + sparse-checkout: /tools/actions/suggest-fill-prurl.mjs + sparse-checkout-cone-mode: false + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + with: + node-version: latest + - run: ./tools/actions/suggest-fill-prurl.mjs + env: + GH_TOKEN: ${{ secrets.GH_USER_TOKEN }} + PR_NUMBER: ${{ github.event.number }} + REPO: ${{ github.repository }} + SHA: ${{ github.sha }} diff --git a/tools/actions/suggest-fill-prurl.mjs b/tools/actions/suggest-fill-prurl.mjs new file mode 100755 index 00000000000000..4fd30736233f97 --- /dev/null +++ b/tools/actions/suggest-fill-prurl.mjs @@ -0,0 +1,76 @@ +#!/usr/bin/env node + +// Replaces: +// pr-url: https://github.com/nodejs/node/pull/FILLME +// With: +// pr-url: https://github.com/nodejs/node/pull/ACTUAL_PR_NUMBER +// And posts it as ```suggestion``` on pull request on GitHub + +import { env } from 'node:process'; + +const { GH_TOKEN, PR_NUMBER, REPO, SHA } = env; +if (!GH_TOKEN || !PR_NUMBER || !REPO || !SHA) { + throw new Error('Missing required environment variables'); +} + +const PLACEHOLDER = 'FILLME'; +const placeholderReg = new RegExp(`^\\+.*${RegExp.escape(`https://github.com/${REPO}/pull/${PLACEHOLDER}`)}`); + +const headers = new Headers({ + 'Accept': 'application/vnd.github+json', + 'Authorization': `Bearer ${GH_TOKEN}`, + 'User-Agent': 'nodejs-bot', + 'X-GitHub-Api-Version': '2022-11-28', +}); + +// https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#list-pull-requests-files +const res = await fetch( + new URL(`repos/${REPO}/pulls/${PR_NUMBER}/files`, 'https://api.github.com'), + { headers }, +); +if (!res.ok) { + throw new Error(`Failed to fetch PR files, status=${res.status}`); +} + +const files = await res.json(); + +const comments = files.flatMap(({ status, filename, patch }) => { + if (!patch || !['added', 'modified'].includes(status)) { + return []; + } + + return patch.split('\n').map((line, position) => { + if (!placeholderReg.test(line)) { + return false; + } + const suggestion = line + .slice(1) + .replace(`https://github.com/${REPO}/pull/${PLACEHOLDER}`, `https://github.com/${REPO}/pull/${PR_NUMBER}`); + return { + path: filename, + position, + body: `Replace ${PLACEHOLDER} with PR number ${PR_NUMBER}\n` + + '```suggestion\n' + + `${suggestion}\n` + + '```\n', + }; + }).filter(Boolean); +}); + +if (comments.length) { + const payload = { + comments, + commit_id: SHA, + event: 'COMMENT', + }; + + // https://docs.github.com/en/rest/pulls/reviews?apiVersion=2022-11-28#create-a-review-for-a-pull-request + await fetch( + new URL(`repos/${REPO}/pulls/${PR_NUMBER}/reviews`, 'https://api.github.com'), + { + method: 'POST', + headers, + body: JSON.stringify(payload), + }, + ); +} From 8057a4af5f04d8e0c030997f4eb14ab11519fbf3 Mon Sep 17 00:00:00 2001 From: Livia Medeiros Date: Fri, 2 Jan 2026 17:57:41 +0800 Subject: [PATCH 2/3] squash: Update .github/workflows/suggest-fill-prurl.yml Co-authored-by: Antoine du Hamel --- .github/workflows/suggest-fill-prurl.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/suggest-fill-prurl.yml b/.github/workflows/suggest-fill-prurl.yml index f2df50382f04ed..e960de991a3b35 100644 --- a/.github/workflows/suggest-fill-prurl.yml +++ b/.github/workflows/suggest-fill-prurl.yml @@ -7,6 +7,7 @@ on: - doc/api/**/*.md permissions: + contents: read pull-requests: write jobs: From 4f8f49360c7eae05f68c797b62739c9443cbdd6e Mon Sep 17 00:00:00 2001 From: LiviaMedeiros Date: Fri, 2 Jan 2026 17:59:42 +0800 Subject: [PATCH 3/3] squash: adjust env variables --- .github/workflows/suggest-fill-prurl.yml | 2 -- tools/actions/suggest-fill-prurl.mjs | 23 ++++++++++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/.github/workflows/suggest-fill-prurl.yml b/.github/workflows/suggest-fill-prurl.yml index e960de991a3b35..1bd99e9ee8e163 100644 --- a/.github/workflows/suggest-fill-prurl.yml +++ b/.github/workflows/suggest-fill-prurl.yml @@ -26,5 +26,3 @@ jobs: env: GH_TOKEN: ${{ secrets.GH_USER_TOKEN }} PR_NUMBER: ${{ github.event.number }} - REPO: ${{ github.repository }} - SHA: ${{ github.sha }} diff --git a/tools/actions/suggest-fill-prurl.mjs b/tools/actions/suggest-fill-prurl.mjs index 4fd30736233f97..c47c64b19512ad 100755 --- a/tools/actions/suggest-fill-prurl.mjs +++ b/tools/actions/suggest-fill-prurl.mjs @@ -8,13 +8,19 @@ import { env } from 'node:process'; -const { GH_TOKEN, PR_NUMBER, REPO, SHA } = env; -if (!GH_TOKEN || !PR_NUMBER || !REPO || !SHA) { +const { + GITHUB_API_URL = 'https://api.github.com', + GITHUB_REPOSITORY = 'nodejs/node', + GITHUB_SHA, + GH_TOKEN, + PR_NUMBER, +} = env; +if (!GITHUB_SHA || !GH_TOKEN || !PR_NUMBER) { throw new Error('Missing required environment variables'); } const PLACEHOLDER = 'FILLME'; -const placeholderReg = new RegExp(`^\\+.*${RegExp.escape(`https://github.com/${REPO}/pull/${PLACEHOLDER}`)}`); +const placeholderReg = new RegExp(`^\\+.*${RegExp.escape(`https://github.com/${GITHUB_REPOSITORY}/pull/${PLACEHOLDER}`)}`); const headers = new Headers({ 'Accept': 'application/vnd.github+json', @@ -25,7 +31,7 @@ const headers = new Headers({ // https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#list-pull-requests-files const res = await fetch( - new URL(`repos/${REPO}/pulls/${PR_NUMBER}/files`, 'https://api.github.com'), + new URL(`repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/files`, GITHUB_API_URL), { headers }, ); if (!res.ok) { @@ -45,7 +51,10 @@ const comments = files.flatMap(({ status, filename, patch }) => { } const suggestion = line .slice(1) - .replace(`https://github.com/${REPO}/pull/${PLACEHOLDER}`, `https://github.com/${REPO}/pull/${PR_NUMBER}`); + .replace( + `https://github.com/${GITHUB_REPOSITORY}/pull/${PLACEHOLDER}`, + `https://github.com/${GITHUB_REPOSITORY}/pull/${PR_NUMBER}`, + ); return { path: filename, position, @@ -60,13 +69,13 @@ const comments = files.flatMap(({ status, filename, patch }) => { if (comments.length) { const payload = { comments, - commit_id: SHA, + commit_id: GITHUB_SHA, event: 'COMMENT', }; // https://docs.github.com/en/rest/pulls/reviews?apiVersion=2022-11-28#create-a-review-for-a-pull-request await fetch( - new URL(`repos/${REPO}/pulls/${PR_NUMBER}/reviews`, 'https://api.github.com'), + new URL(`repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/reviews`, GITHUB_API_URL), { method: 'POST', headers,