( prNumber: string, body: string, rootDir: string, marker: string = COMMENT_MARKER, )
| 1715 | const SNAPSHOT_COMMENT_MARKER = '<!-- bumpy-snapshot -->'; |
| 1716 | |
| 1717 | async function postOrUpdatePrComment( |
| 1718 | prNumber: string, |
| 1719 | body: string, |
| 1720 | rootDir: string, |
| 1721 | marker: string = COMMENT_MARKER, |
| 1722 | ): Promise<void> { |
| 1723 | const validPr = validatePrNumber(prNumber); |
| 1724 | const markedBody = `${marker}\n${body}`; |
| 1725 | |
| 1726 | try { |
| 1727 | // Find existing bumpy comment using gh with jq. The marker keeps each kind of comment |
| 1728 | // (release plan vs snapshot) independent, so they don't overwrite each other. |
| 1729 | const jqFilter = `.comments[] | select(.body | startswith("${marker}")) | .url | capture("issuecomment-(?<id>[0-9]+)$") | .id`; |
| 1730 | const existingComment = tryRunArgs(['gh', 'pr', 'view', validPr, '--json', 'comments', '--jq', jqFilter], { |
| 1731 | cwd: rootDir, |
| 1732 | }); |
| 1733 | |
| 1734 | // Take the first result if multiple |
| 1735 | const commentId = existingComment?.split('\n')[0]?.trim(); |
| 1736 | |
| 1737 | if (commentId) { |
| 1738 | await runArgsAsync( |
| 1739 | ['gh', 'api', `repos/{owner}/{repo}/issues/comments/${commentId}`, '-X', 'PATCH', '-F', 'body=@-'], |
| 1740 | { cwd: rootDir, input: markedBody }, |
| 1741 | ); |
| 1742 | log.dim(' Updated PR comment'); |
| 1743 | } else { |
| 1744 | await runArgsAsync(['gh', 'pr', 'comment', validPr, '--body-file', '-'], { |
| 1745 | cwd: rootDir, |
| 1746 | input: markedBody, |
| 1747 | }); |
| 1748 | log.dim(' Posted PR comment'); |
| 1749 | } |
| 1750 | } catch (err) { |
| 1751 | log.warn(` Failed to comment on PR: ${err instanceof Error ? err.message : err}`); |
| 1752 | // Most common cause: the workflow is on `pull_request` and this is a fork PR, |
| 1753 | // so GITHUB_TOKEN is read-only. Surface that explicitly — otherwise contributors |
| 1754 | // see a red check with no comment and no clue why. |
| 1755 | if (process.env.GITHUB_EVENT_NAME === 'pull_request' && isForkPr()) { |
| 1756 | log.warn( |
| 1757 | ' This PR is from a fork. Fork PRs running on `pull_request` get a read-only token\n' + |
| 1758 | ' and cannot post comments. Switch the workflow to `pull_request_target` —\n' + |
| 1759 | ' see https://bumpy.varlock.dev/docs/github-actions', |
| 1760 | ); |
| 1761 | } |
| 1762 | } |
| 1763 | } |
| 1764 | |
| 1765 | function detectPrBranch(rootDir: string): string | null { |
| 1766 | // GitHub Actions sets GITHUB_HEAD_REF for pull_request events |
no test coverage detected
searching dependent graphs…