From 68eb1804d8d2ff24c02f0fe44f0ab4ede938e925 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Tue, 9 Dec 2025 11:45:04 +0100 Subject: [PATCH 1/2] Add PR description check --- .github/workflows/validate_pr.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/workflows/validate_pr.yml b/.github/workflows/validate_pr.yml index 7f6e578810..c36b8796be 100644 --- a/.github/workflows/validate_pr.yml +++ b/.github/workflows/validate_pr.yml @@ -23,3 +23,27 @@ jobs: } console.log('✅ PR title format is valid'); + + - name: Ensure PR Has a Nitro Companion + uses: actions/github-script@v7 + with: + script: | + const prBody = context.payload.pull_request.body; + + if (!prBody) { + core.setFailed("The PR description is empty. Please ensure it contains the required link."); + return; + } + + // The required regex pattern: + // 1. Matches the literal string "pulled in by https://github.com/OffchainLabs/nitro/pull/" + // 2. Requires one or more digits (\d+) for the pull request number (xxxx) + // 3. The 'i' flag makes the entire match case-insensitive (e.g., "Pulled In By" is valid) + const requiredRegex = /pulled in by https:\/\/github\.com\/OffchainLabs\/nitro\/pull\/\d+/i; + + if (!requiredRegex.test(prBody)) { + const errorMessage = "PR description validation failed. The description must contain a line matching the case-insensitive pattern: 'pulled in by https://github.com/OffchainLabs/nitro/pull/xxxx', where 'xxxx' is a number."; + core.setFailed(errorMessage); + } else { + core.info("✅ PR description contains the required link."); + } From 08d8dda3d9d80c108d0425d678d5ce9045322ccb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Tue, 9 Dec 2025 12:05:13 +0100 Subject: [PATCH 2/2] Check nitro PR --- .github/workflows/validate_pr.yml | 61 +++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/.github/workflows/validate_pr.yml b/.github/workflows/validate_pr.yml index c36b8796be..d8148cfc97 100644 --- a/.github/workflows/validate_pr.yml +++ b/.github/workflows/validate_pr.yml @@ -29,6 +29,13 @@ jobs: with: script: | const prBody = context.payload.pull_request.body; + const currentPrNumber = context.payload.pull_request.number; + + // -------------------------------------- + // --- 1. Check the Current PR's Body --- + // -------------------------------------- + + core.info(`Validating current PR description.`); if (!prBody) { core.setFailed("The PR description is empty. Please ensure it contains the required link."); @@ -39,11 +46,57 @@ jobs: // 1. Matches the literal string "pulled in by https://github.com/OffchainLabs/nitro/pull/" // 2. Requires one or more digits (\d+) for the pull request number (xxxx) // 3. The 'i' flag makes the entire match case-insensitive (e.g., "Pulled In By" is valid) - const requiredRegex = /pulled in by https:\/\/github\.com\/OffchainLabs\/nitro\/pull\/\d+/i; + const requiredRegex = /pulled in by https:\/\/github\.com\/OffchainLabs\/nitro\/pull\/(\d+)/i; + const match = prBody.match(requiredRegex); - if (!requiredRegex.test(prBody)) { - const errorMessage = "PR description validation failed. The description must contain a line matching the case-insensitive pattern: 'pulled in by https://github.com/OffchainLabs/nitro/pull/xxxx', where 'xxxx' is a number."; - core.setFailed(errorMessage); + if (!match) { + core.setFailed("PR description validation failed. The description must contain a line matching the case-insensitive pattern: 'pulled in by https://github.com/OffchainLabs/nitro/pull/xxxx', where 'xxxx' is a number."); + return; } else { core.info("✅ PR description contains the required link."); } + + const nitroPrNumber = match[1]; + core.info(`✅ Current PR contains 'pulled in by' link.`); + core.info(`Found referenced Nitro PR number: #${nitroPrNumber}`); + + // --------------------------------------------------- + // --- 2. Fetch the Referenced PR's Body --- + // --------------------------------------------------- + + try { + // Fetch the referenced PR details from the OffchainLabs/nitro repository. + const referencedPr = await github.rest.pulls.get({ + owner: 'OffchainLabs', + repo: 'nitro', + pull_number: nitroPrNumber, + }); + + const referencedPrBody = referencedPr.data.body; + + if (!referencedPrBody || referencedPrBody.trim().length === 0) { + core.setFailed(`Referenced Nitro PR #${nitroPrNumber} description is empty. The referenced PR must have a description.`); + return; + } + + // ----------------------------------------- + // --- 3. Check the Referenced PR's Body --- + // ----------------------------------------- + + // The inverse link must reference the current PR's number (yyy is currentPrNumber) + // Pattern: "pulls in https://github.com/OffchainLabs/go-ethereum/pull/yyy" + const inversePatternString = `pulls in https:\/\/github\.com\/OffchainLabs\/go-ethereum\/pull\/${currentPrNumber}`; + const inverseRequiredRegex = new RegExp(inversePatternString, 'i'); + + if (!inverseRequiredRegex.test(referencedPrBody)) { + core.setFailed(`Inverse link validation failed on Nitro PR #${nitroPrNumber}. It must contain the case-insensitive link: 'pulls in https://github.com/OffchainLabs/go-ethereum/pull/${currentPrNumber}'`); + return; + } + + core.info(`✅ Referenced Nitro PR #${nitroPrNumber} contains the inverse link to go-ethereum PR #${currentPrNumber}.`); + core.info("✅ All PR description cross-validations passed successfully."); + + } catch (error) { + // Handle cases like "PR not found" or API errors + core.setFailed(`Could not fetch or validate referenced PR #${nitroPrNumber} in OffchainLabs/nitro. API Error: ${error.message}`); + }