security: bump vulnerable dependencies to patched versions #109
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Needs Rebase | |
| on: | |
| pull_request_target: | |
| types: [opened, synchronize, reopened] | |
| push: | |
| branches: [main] | |
| permissions: | |
| pull-requests: write | |
| issues: write | |
| contents: read | |
| jobs: | |
| needs-rebase: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check for merge conflicts and label PR | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const LABEL = 'needs-rebase'; | |
| const COMMENT_MARKER = '<!-- needs-rebase-bot -->'; | |
| // Poll until GitHub has computed the mergeable status (it's async). | |
| // maxAttempts=1 skips retries for bulk push scans to avoid per-PR sleeps. | |
| // Uses exponential backoff: 5s, 10s, 20s, 30s, 30s, ... (capped at 30s). | |
| async function getMergeableStatus(prNumber, maxAttempts = 10) { | |
| for (let attempt = 0; attempt < maxAttempts; attempt++) { | |
| const { data: pr } = await github.rest.pulls.get({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: prNumber, | |
| }); | |
| if (pr.mergeable !== null) return { mergeable: pr.mergeable, author: pr.user.login }; | |
| if (attempt + 1 < maxAttempts) { | |
| const delay = Math.min(5000 * Math.pow(2, attempt), 30000); | |
| core.info(`PR #${prNumber}: mergeable not computed yet, retrying in ${delay / 1000}s (attempt ${attempt + 1}/${maxAttempts})`); | |
| await new Promise(r => setTimeout(r, delay)); | |
| } | |
| } | |
| if (maxAttempts > 1) { | |
| core.warning(`PR #${prNumber}: could not determine mergeable status after ${maxAttempts} attempts — skipping`); | |
| } else { | |
| core.info(`PR #${prNumber}: mergeable not yet computed by GitHub — skipping (will recheck on next PR event)`); | |
| } | |
| return { mergeable: null, author: null }; | |
| } | |
| async function findBotComment(prNumber) { | |
| const comments = await github.paginate(github.rest.issues.listComments, { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| }); | |
| return comments.find(c => c.body.includes(COMMENT_MARKER)); | |
| } | |
| async function hasLabel(prNumber) { | |
| const { data: labels } = await github.rest.issues.listLabelsOnIssue({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| }); | |
| return labels.some(l => l.name === LABEL); | |
| } | |
| async function handlePR(prNumber, knownAuthor, maxAttempts = 6) { | |
| const { mergeable, author } = await getMergeableStatus(prNumber, maxAttempts); | |
| const prAuthor = knownAuthor || author; | |
| if (mergeable === null) return; // unknown — leave the PR alone | |
| const alreadyLabeled = await hasLabel(prNumber); | |
| const existingComment = await findBotComment(prNumber); | |
| if (!mergeable) { | |
| // PR has conflicts — add label + comment if not already done | |
| if (!alreadyLabeled) { | |
| await github.rest.issues.addLabels({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| labels: [LABEL], | |
| }); | |
| core.info(`PR #${prNumber}: added '${LABEL}' label`); | |
| } | |
| if (!existingComment) { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| body: [ | |
| COMMENT_MARKER, | |
| `Hi @${prAuthor}! :wave: This PR has merge conflicts with \`main\` and needs to be rebased before it can be merged.`, | |
| ``, | |
| `**To rebase your branch:**`, | |
| `\`\`\`bash`, | |
| `git fetch upstream`, | |
| `git rebase upstream/main`, | |
| `# resolve any conflicts, then:`, | |
| `git add .`, | |
| `git rebase --continue`, | |
| `git push --force-with-lease`, | |
| `\`\`\``, | |
| ``, | |
| `> **Tip:** \`--force-with-lease\` is safer than \`--force\` — it will fail if someone else has pushed to your branch since your last fetch.`, | |
| ``, | |
| `For more details, see the [Rebase with upstream](https://krkn-chaos.dev/docs/contribution-guidelines/git-pointers/#rebase-with-upstream) guide.`, | |
| ``, | |
| `The \`${LABEL}\` label and this comment will be removed automatically once conflicts are resolved.`, | |
| ].join('\n'), | |
| }); | |
| core.info(`PR #${prNumber}: posted needs-rebase comment`); | |
| } | |
| } else { | |
| // PR is clean — remove label and comment | |
| if (alreadyLabeled) { | |
| await github.rest.issues.removeLabel({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| name: LABEL, | |
| }); | |
| core.info(`PR #${prNumber}: removed '${LABEL}' label`); | |
| } | |
| if (existingComment) { | |
| await github.rest.issues.deleteComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existingComment.id, | |
| }); | |
| core.info(`PR #${prNumber}: removed needs-rebase comment`); | |
| } | |
| } | |
| } | |
| if (context.eventName === 'pull_request_target') { | |
| // A PR was opened or updated — check just this PR | |
| const prNumber = context.payload.pull_request.number; | |
| const author = context.payload.pull_request.user.login; | |
| core.info(`Triggered by PR #${prNumber} (${context.payload.action})`); | |
| try { | |
| await handlePR(prNumber, author); | |
| } catch (e) { | |
| core.warning(`PR #${prNumber}: unexpected error — ${e.message}`); | |
| } | |
| } else if (context.eventName === 'push') { | |
| // main was pushed to — re-check all open PRs targeting main. | |
| // Use a single read attempt per PR (no retry sleep): mergeable is | |
| // likely null for all PRs right after a push and will be recomputed | |
| // by GitHub asynchronously. PRs with null status are skipped here | |
| // and will be picked up on the next pull_request_target event. | |
| // PRs are processed in concurrent batches to reduce elapsed time. | |
| core.info('Triggered by push to main — checking all open PRs'); | |
| const prs = await github.paginate(github.rest.pulls.list, { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| state: 'open', | |
| base: 'main', | |
| }); | |
| core.info(`Found ${prs.length} open PRs targeting main`); | |
| const CONCURRENCY = 5; | |
| for (let i = 0; i < prs.length; i += CONCURRENCY) { | |
| const batch = prs.slice(i, i + CONCURRENCY); | |
| await Promise.all(batch.map(pr => | |
| handlePR(pr.number, pr.user.login, 3).catch(e => | |
| core.warning(`PR #${pr.number}: unexpected error — ${e.message}`) | |
| ) | |
| )); | |
| } | |
| } |