├── .all-contributorsrc ├── .github ├── test │ ├── file-1.txt │ ├── file-2.txt │ └── image.png └── workflows │ └── ci.yml ├── .gitignore ├── .node-version ├── LICENSE ├── README.md ├── __tests__ ├── add-pr-comment.test.ts ├── message-part-1.txt ├── message-part-2.txt ├── message-windows.txt ├── sample-master-push.json └── sample-pulls-api-response.json ├── action.yml ├── dist ├── index.js ├── index.js.map └── sourcemap-register.js ├── lib ├── comments.js ├── config.js ├── files.js ├── issues.js ├── main.js ├── message.js ├── proxy.js ├── types.js └── util.js ├── package-lock.json ├── package.json ├── src ├── comments.ts ├── config.ts ├── files.ts ├── issues.ts ├── main.ts ├── message.ts ├── proxy.ts └── types.ts └── tsconfig.json /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "commitConvention": "angular", 8 | "contributors": [ 9 | { 10 | "login": "ReenigneArcher", 11 | "name": "ReenigneArcher", 12 | "avatar_url": "https://avatars.githubusercontent.com/u/42013603?v=4", 13 | "profile": "https://app.lizardbyte.dev", 14 | "contributions": [ 15 | "code" 16 | ] 17 | }, 18 | { 19 | "login": "aryella-lacerda", 20 | "name": "Aryella Lacerda", 21 | "avatar_url": "https://avatars.githubusercontent.com/u/28730324?v=4", 22 | "profile": "https://github.com/aryella-lacerda", 23 | "contributions": [ 24 | "code" 25 | ] 26 | }, 27 | { 28 | "login": "vincent-joignie-dd", 29 | "name": "vincent-joignie-dd", 30 | "avatar_url": "https://avatars.githubusercontent.com/u/103102299?v=4", 31 | "profile": "https://github.com/vincent-joignie-dd", 32 | "contributions": [ 33 | "code" 34 | ] 35 | }, 36 | { 37 | "login": "ahanoff", 38 | "name": "Akhan Zhakiyanov", 39 | "avatar_url": "https://avatars.githubusercontent.com/u/2371703?v=4", 40 | "profile": "https://ahanoff.dev", 41 | "contributions": [ 42 | "code" 43 | ] 44 | }, 45 | { 46 | "login": "ahatzz11", 47 | "name": "Alex Hatzenbuhler", 48 | "avatar_url": "https://avatars.githubusercontent.com/u/6256032?v=4", 49 | "profile": "https://github.com/ahatzz11", 50 | "contributions": [ 51 | "code" 52 | ] 53 | }, 54 | { 55 | "login": "twang817", 56 | "name": "Tommy Wang", 57 | "avatar_url": "https://avatars.githubusercontent.com/u/766820?v=4", 58 | "profile": "http://www.august8.net", 59 | "contributions": [ 60 | "code" 61 | ] 62 | } 63 | ], 64 | "contributorsPerLine": 7, 65 | "skipCi": true, 66 | "repoType": "github", 67 | "repoHost": "https://github.com", 68 | "projectName": "add-pr-comment", 69 | "projectOwner": "mshick" 70 | } 71 | -------------------------------------------------------------------------------- /.github/test/file-1.txt: -------------------------------------------------------------------------------- 1 | Hello -------------------------------------------------------------------------------- /.github/test/file-2.txt: -------------------------------------------------------------------------------- 1 | Goodbye -------------------------------------------------------------------------------- /.github/test/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mshick/add-pr-comment/dd126dd8c253650d181ad9538d8b4fa218fc31e8/.github/test/image.png -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - synchronize 8 | - reopened 9 | 10 | jobs: 11 | test: 12 | name: unit tests 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: checkout repo 16 | uses: actions/checkout@v3 17 | 18 | - name: setup node.js 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version-file: '.node-version' 22 | cache: 'npm' 23 | 24 | - name: install dependencies 25 | run: | 26 | npm ci 27 | 28 | - name: lint code 29 | run: | 30 | npm run lint 31 | 32 | - name: build action 33 | run: | 34 | npm run build 35 | 36 | - name: run tests 37 | run: | 38 | npm test 39 | 40 | dogfood: 41 | name: dogfood 42 | runs-on: ubuntu-latest 43 | permissions: 44 | pull-requests: write 45 | steps: 46 | - name: checkout repo 47 | uses: actions/checkout@v3 48 | 49 | - name: setup node.js 50 | uses: actions/setup-node@v3 51 | with: 52 | node-version-file: '.node-version' 53 | cache: 'npm' 54 | 55 | - name: install dependencies 56 | run: | 57 | npm ci 58 | 59 | - name: Build action 60 | run: | 61 | npm run build 62 | 63 | - uses: ./ 64 | with: 65 | preformatted: true 66 | message-id: path 67 | message-path: | 68 | .github/test/file-*.txt 69 | 70 | - uses: ./ 71 | with: 72 | message-id: text 73 | message: | 74 | **Hello** 75 | 🌏 76 | ! 77 | 78 | - uses: ./ 79 | with: 80 | message-id: text 81 | find: | 82 | Hello 83 | 🌏 84 | replace: | 85 | Goodnight 86 | 🌕 87 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Ignore test runner output 3 | __tests__/runner/* 4 | !__tests__/runner/.gitkeep 5 | 6 | node_modules/ 7 | 8 | # Editors 9 | .vscode 10 | .idea/** 11 | 12 | # Logs 13 | logs 14 | *.log 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | 19 | # Runtime data 20 | pids 21 | *.pid 22 | *.seed 23 | *.pid.lock 24 | 25 | # Directory for instrumented libs generated by jscoverage/JSCover 26 | lib-cov 27 | 28 | # Coverage directory used by tools like istanbul 29 | coverage 30 | 31 | # nyc test coverage 32 | .nyc_output 33 | 34 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 35 | .grunt 36 | 37 | # Bower dependency directory (https://bower.io/) 38 | bower_components 39 | 40 | # node-waf configuration 41 | .lock-wscript 42 | 43 | # Compiled binary addons (https://nodejs.org/api/addons.html) 44 | build/Release 45 | 46 | # Other Dependency directories 47 | jspm_packages/ 48 | 49 | # TypeScript v1 declaration files 50 | typings/ 51 | 52 | # Optional npm cache directory 53 | .npm 54 | 55 | # Optional eslint cache 56 | .eslintcache 57 | 58 | # Optional REPL history 59 | .node_repl_history 60 | 61 | # Output of 'npm pack' 62 | *.tgz 63 | 64 | # Yarn Integrity file 65 | .yarn-integrity 66 | 67 | # dotenv environment variables file 68 | .env 69 | 70 | # next.js build output 71 | .next 72 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | v20 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Michael Shick 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # add-pr-comment 2 | 3 | 4 | 5 | [![All Contributors](https://img.shields.io/badge/all_contributors-6-orange.svg?style=flat-square)](#contributors-) 6 | 7 | 8 | 9 | A GitHub Action which adds a comment to a pull request's issue. 10 | 11 | This actions also works on [issue](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#issues), 12 | [issue_comment](https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#issue_comment), 13 | [deployment_status](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#deployment_status), 14 | [push](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#push) 15 | and any other event where an issue can be found directly on the payload or via a commit sha. 16 | 17 | ## Features 18 | 19 | - Modify issues for PRs merged to main. 20 | - By default will post "sticky" comments. If on a subsequent run the message text changes the original comment will be updated. 21 | - Multiple sticky comments allowed by setting unique `message-id`s. 22 | - Optional message overrides based on job status. 23 | - Multiple posts to the same conversation optionally allowable. 24 | - Supports a proxy for fork-based PRs. [See below](#proxy-for-fork-based-prs). 25 | - Supports creating a message from a file path. 26 | 27 | ## Usage 28 | 29 | Note that write access needs to be granted for the pull-requests scope. 30 | 31 | ```yaml 32 | on: 33 | pull_request: 34 | 35 | jobs: 36 | test: 37 | runs-on: ubuntu-latest 38 | permissions: 39 | pull-requests: write 40 | steps: 41 | - uses: mshick/add-pr-comment@v2 42 | with: 43 | message: | 44 | **Hello** 45 | 🌏 46 | ! 47 | ``` 48 | 49 | You can even use it on PR Issues that are related to PRs that were merged into main, for example: 50 | 51 | ```yaml 52 | on: 53 | push: 54 | branches: 55 | - main 56 | 57 | jobs: 58 | test: 59 | runs-on: ubuntu-latest 60 | permissions: 61 | pull-requests: write 62 | steps: 63 | - uses: mshick/add-pr-comment@v2 64 | with: 65 | message: | 66 | **Hello MAIN** 67 | ``` 68 | 69 | ## Configuration options 70 | 71 | | Input | Location | Description | Required | Default | 72 | | ------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------------------------------- | 73 | | message | with | The message you'd like displayed, supports Markdown and all valid Unicode characters. | maybe | | 74 | | message-path | with | Path to a message you'd like displayed. Will be read and displayed just like a normal message. Supports multi-line input and globs. Multiple messages will be concatenated. | maybe | | 75 | | message-success | with | A message override, printed in case of success. | no | | 76 | | message-failure | with | A message override, printed in case of failure. | no | | 77 | | message-cancelled | with | A message override, printed in case of cancelled. | no | | 78 | | message-skipped | with | A message override, printed in case of skipped. | no | | 79 | | status | with | Required if you want to use message status overrides. | no | {{ job.status }} | 80 | | repo-owner | with | Owner of the repo. | no | {{ github.repository_owner }} | 81 | | repo-name | with | Name of the repo. | no | {{ github.event.repository.name }} | 82 | | repo-token | with | Valid GitHub token, either the temporary token GitHub provides or a personal access token. | no | {{ github.token }} | 83 | | message-id | with | Message id to use when searching existing comments. If found, updates the existing (sticky comment). | no | | 84 | | refresh-message-position | with | Should the sticky message be the last one in the PR's feed. | no | false | 85 | | allow-repeats | with | Boolean flag to allow identical messages to be posted each time this action is run. | no | false | 86 | | proxy-url | with | String for your proxy service URL if you'd like this to work with fork-based PRs. | no | | 87 | | issue | with | Optional issue number override. | no | | 88 | | update-only | with | Only update the comment if it already exists. | no | false | 89 | | GITHUB_TOKEN | env | Valid GitHub token, can alternatively be defined in the env. | no | | 90 | | preformatted | with | Treat message text as pre-formatted and place it in a codeblock | no | | 91 | | find | with | Patterns to find in an existing message and replace with either `replace` text or a resolved `message`. See [Find-and-Replace](#find-and-replace) for more detail. | no | | 92 | | replace | with | Strings to replace a found pattern with. Each new line is a new replacement, or if you only have one pattern, you can replace with a multiline string. | no | | 93 | 94 | ## Advanced Uses 95 | 96 | ### Proxy for Fork-based PRs 97 | 98 | GitHub limits `GITHUB_TOKEN` and other API access token permissions when creating a PR from a fork. This precludes adding comments when your PRs are coming from forks, which is the norm for open source projects. To work around this situation I've created a simple companion app you can deploy to Cloud Run or another host to proxy the create comment requests with a personal access token you provide. 99 | 100 | See this issue: https://github.community/t/github-actions-are-severely-limited-on-prs/18179/4 for more details. 101 | 102 | Check out the proxy service here: https://github.com/mshick/add-pr-comment-proxy 103 | 104 | **Example** 105 | 106 | ```yaml 107 | on: 108 | pull_request: 109 | 110 | jobs: 111 | pr: 112 | runs-on: ubuntu-latest 113 | permissions: 114 | pull-requests: write 115 | steps: 116 | - uses: mshick/add-pr-comment@v2 117 | with: 118 | message: | 119 | **Howdie!** 120 | proxy-url: https://add-pr-comment-proxy-94idvmwyie-uc.a.run.app 121 | ``` 122 | 123 | ### Status Message Overrides 124 | 125 | You can override your messages based on your job status. This can be helpful 126 | if you don't anticipate having the data required to create a helpful message in 127 | case of failure, but you still want a message to be sent to the PR comment. 128 | 129 | **Example** 130 | 131 | ```yaml 132 | on: 133 | pull_request: 134 | 135 | jobs: 136 | pr: 137 | runs-on: ubuntu-latest 138 | permissions: 139 | pull-requests: write 140 | steps: 141 | - uses: mshick/add-pr-comment@v2 142 | if: always() 143 | with: 144 | message: | 145 | **Howdie!** 146 | message-failure: | 147 | Uh oh! 148 | ``` 149 | 150 | ### Multiple Message Files 151 | 152 | Instead of directly setting the message you can also load a file with the text 153 | of your message using `message-path`. `message-path` supports loading multiple 154 | files and files on multiple lines, the contents of which will be concatenated. 155 | 156 | **Example** 157 | 158 | ```yaml 159 | on: 160 | pull_request: 161 | 162 | jobs: 163 | pr: 164 | runs-on: ubuntu-latest 165 | permissions: 166 | pull-requests: write 167 | steps: 168 | - uses: mshick/add-pr-comment@v2 169 | if: always() 170 | with: 171 | message-path: | 172 | message-part-*.txt 173 | ``` 174 | 175 | ### Find-and-Replace 176 | 177 | Patterns can be matched and replaced to update comments. This could be useful 178 | for some situations, for instance, updating a checklist comment. 179 | 180 | Find is a regular expression passed to the [RegExp() constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/RegExp). You can also 181 | include modifiers to override the default `gi`. 182 | 183 | **Example** 184 | 185 | Original message: 186 | 187 | ``` 188 | [ ] Hello 189 | [ ] World 190 | ``` 191 | 192 | Action: 193 | 194 | ```yaml 195 | on: 196 | pull_request: 197 | 198 | jobs: 199 | pr: 200 | runs-on: ubuntu-latest 201 | permissions: 202 | pull-requests: write 203 | steps: 204 | - uses: mshick/add-pr-comment@v2 205 | if: always() 206 | with: 207 | find: | 208 | \n\\[ \\] 209 | replace: | 210 | [X] 211 | ``` 212 | 213 | Final message: 214 | 215 | ``` 216 | [X] Hello 217 | [X] World 218 | ``` 219 | 220 | Multiple find and replaces can be used: 221 | 222 | **Example** 223 | 224 | Original message: 225 | 226 | ``` 227 | hello world! 228 | ``` 229 | 230 | Action: 231 | 232 | ```yaml 233 | on: 234 | pull_request: 235 | 236 | jobs: 237 | pr: 238 | runs-on: ubuntu-latest 239 | permissions: 240 | pull-requests: write 241 | steps: 242 | - uses: mshick/add-pr-comment@v2 243 | if: always() 244 | with: 245 | find: | 246 | hello 247 | world 248 | replace: | 249 | goodnight 250 | moon 251 | ``` 252 | 253 | Final message: 254 | 255 | ``` 256 | goodnight moon! 257 | ``` 258 | 259 | It defaults to your resolved message (either from `message` or `message-path`) to 260 | do a replacement: 261 | 262 | **Example** 263 | 264 | Original message: 265 | 266 | ``` 267 | hello 268 | 269 | << FILE_CONTENTS >> 270 | 271 | world 272 | ``` 273 | 274 | Action: 275 | 276 | ```yaml 277 | on: 278 | pull_request: 279 | 280 | jobs: 281 | pr: 282 | runs-on: ubuntu-latest 283 | permissions: 284 | pull-requests: write 285 | steps: 286 | - uses: mshick/add-pr-comment@v2 287 | if: always() 288 | with: 289 | message-path: | 290 | message.txt 291 | find: | 292 | << FILE_CONTENTS >> 293 | ``` 294 | 295 | Final message: 296 | 297 | ``` 298 | hello 299 | 300 | secret message from message.txt 301 | 302 | world 303 | ``` 304 | 305 | ### Bring your own issues 306 | 307 | You can set an issue id explicitly. Helpful for cases where you want to post 308 | to an issue but for some reason the event would not allow the id to be determined. 309 | 310 | **Example** 311 | 312 | > In this case `add-pr-comment` should have no problem finding the issue number 313 | > on its own, but for demonstration purposes. 314 | 315 | ```yaml 316 | on: 317 | deployment_status: 318 | 319 | jobs: 320 | pr: 321 | runs-on: ubuntu-latest 322 | permissions: 323 | pull-requests: write 324 | steps: 325 | - id: pr 326 | run: | 327 | issue=$(gh pr list --search "${{ github.sha }}" --state open --json number --jq ".[0].number") 328 | echo "issue=$issue" >>$GITHUB_OUTPUT 329 | env: 330 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 331 | 332 | - uses: mshick/add-pr-comment@v2 333 | with: 334 | issue: ${{ steps.pr.outputs.issue }} 335 | message: | 336 | **Howdie!** 337 | ``` 338 | 339 | ## Contributors ✨ 340 | 341 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 |
ReenigneArcher
ReenigneArcher

💻
Aryella Lacerda
Aryella Lacerda

💻
vincent-joignie-dd
vincent-joignie-dd

💻
Akhan Zhakiyanov
Akhan Zhakiyanov

💻
Alex Hatzenbuhler
Alex Hatzenbuhler

💻
Tommy Wang
Tommy Wang

💻
358 | 359 | 360 | 361 | 362 | 363 | 364 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 365 | -------------------------------------------------------------------------------- /__tests__/add-pr-comment.test.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core' 2 | import * as github from '@actions/github' 3 | import { WebhookPayload } from '@actions/github/lib/interfaces' 4 | import { rest } from 'msw' 5 | import { setupServer } from 'msw/node' 6 | import * as fs from 'node:fs/promises' 7 | import * as path from 'node:path' 8 | import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' 9 | import run from '../src/main' 10 | import apiResponse from './sample-pulls-api-response.json' 11 | 12 | const messagePath1Fixture = path.resolve(__dirname, './message-part-1.txt') 13 | const messagePath1FixturePayload = await fs.readFile(messagePath1Fixture, 'utf-8') 14 | const messagePath2Fixture = path.resolve(__dirname, './message-part-2.txt') 15 | 16 | const repoToken = '12345' 17 | const commitSha = 'abc123' 18 | const simpleMessage = 'hello world' 19 | 20 | type Inputs = { 21 | message: string | undefined 22 | 'message-path': string | undefined 23 | 'repo-owner': string 24 | 'repo-name': string 25 | 'repo-token': string 26 | 'message-id': string 27 | 'allow-repeats': string 28 | 'message-pattern'?: string 29 | 'message-success'?: string 30 | 'message-failure'?: string 31 | 'message-cancelled'?: string 32 | 'message-skipped'?: string 33 | 'update-only'?: string 34 | preformatted?: string 35 | status?: 'success' | 'failure' | 'cancelled' | 'skipped' 36 | } 37 | 38 | const defaultInputs: Inputs = { 39 | message: '', 40 | 'message-path': undefined, 41 | 'repo-owner': 'foo', 42 | 'repo-name': 'bar', 43 | 'repo-token': repoToken, 44 | 'message-id': 'add-pr-comment', 45 | 'allow-repeats': 'false', 46 | status: 'success', 47 | } 48 | 49 | const defaultIssueNumber = 1 50 | 51 | let inputs = defaultInputs 52 | let issueNumber = defaultIssueNumber 53 | let getCommitPullsResponse 54 | let getIssueCommentsResponse 55 | let postIssueCommentsResponse = { 56 | id: 42, 57 | } 58 | 59 | type MessagePayload = { 60 | comment_id?: number 61 | body: string 62 | } 63 | 64 | let messagePayload: MessagePayload | undefined 65 | 66 | vi.mock('@actions/core') 67 | 68 | const handlers = [ 69 | rest.post( 70 | `https://api.github.com/repos/:repoUser/:repoName/issues/:issueNumber/comments`, 71 | async (req, res, ctx) => { 72 | messagePayload = await req.json() 73 | return res(ctx.status(200), ctx.json(postIssueCommentsResponse)) 74 | }, 75 | ), 76 | rest.patch( 77 | `https://api.github.com/repos/:repoUser/:repoName/issues/comments/:commentId`, 78 | async (req, res, ctx) => { 79 | messagePayload = await req.json() 80 | return res(ctx.status(200), ctx.json(postIssueCommentsResponse)) 81 | }, 82 | ), 83 | rest.get( 84 | `https://api.github.com/repos/:repoUser/:repoName/issues/:issueNumber/comments`, 85 | (req, res, ctx) => { 86 | return res(ctx.status(200), ctx.json(getIssueCommentsResponse)) 87 | }, 88 | ), 89 | rest.get( 90 | `https://api.github.com/repos/:repoUser/:repoName/commits/:commitSha/pulls`, 91 | (req, res, ctx) => { 92 | return res(ctx.status(200), ctx.json(getCommitPullsResponse)) 93 | }, 94 | ), 95 | ] 96 | 97 | const server = setupServer(...handlers) 98 | 99 | beforeAll(() => { 100 | vi.spyOn(console, 'log').mockImplementation(() => {}) 101 | server.listen({ onUnhandledRequest: 'error' }) 102 | }) 103 | afterAll(() => server.close()) 104 | 105 | beforeEach(() => { 106 | inputs = { ...defaultInputs } 107 | issueNumber = defaultIssueNumber 108 | messagePayload = undefined 109 | 110 | vi.resetModules() 111 | 112 | github.context.sha = commitSha 113 | 114 | // https://developer.github.com/webhooks/event-payloads/#issues 115 | github.context.payload = { 116 | pull_request: { 117 | number: issueNumber, 118 | }, 119 | repository: { 120 | full_name: `${inputs['repo-owner']}/${inputs['repo-name']}`, 121 | name: 'bar', 122 | owner: { 123 | login: 'bar', 124 | }, 125 | }, 126 | } as WebhookPayload 127 | }) 128 | 129 | afterEach(() => { 130 | vi.clearAllMocks() 131 | server.resetHandlers() 132 | }) 133 | 134 | const getInput = (name: string, options?: core.InputOptions) => { 135 | const value = inputs[name] ?? '' 136 | 137 | if (options?.required && value === undefined) { 138 | throw new Error(`${name} is required`) 139 | } 140 | 141 | return value 142 | } 143 | 144 | function getMultilineInput(name, options) { 145 | const inputs = getInput(name, options) 146 | .split('\n') 147 | .filter((x) => x !== '') 148 | 149 | if (options && options.trimWhitespace === false) { 150 | return inputs 151 | } 152 | 153 | return inputs.map((input) => input.trim()) 154 | } 155 | 156 | function getBooleanInput(name, options) { 157 | const trueValue = ['true', 'True', 'TRUE'] 158 | const falseValue = ['false', 'False', 'FALSE'] 159 | const val = getInput(name, options) 160 | if (trueValue.includes(val)) return true 161 | if (falseValue.includes(val)) return false 162 | throw new TypeError( 163 | `Input does not meet YAML 1.2 "Core Schema" specification: ${name}\n` + 164 | `Support boolean input list: \`true | True | TRUE | false | False | FALSE\``, 165 | ) 166 | } 167 | 168 | vi.mocked(core.getInput).mockImplementation(getInput) 169 | vi.mocked(core.getMultilineInput).mockImplementation(getMultilineInput) 170 | vi.mocked(core.getBooleanInput).mockImplementation(getBooleanInput) 171 | 172 | describe('add-pr-comment action', () => { 173 | it('creates a comment with message text', async () => { 174 | inputs.message = simpleMessage 175 | inputs['allow-repeats'] = 'true' 176 | 177 | await expect(run()).resolves.not.toThrow() 178 | expect(core.setOutput).toHaveBeenCalledWith('comment-created', 'true') 179 | expect(core.setOutput).toHaveBeenCalledWith('comment-id', postIssueCommentsResponse.id) 180 | }) 181 | 182 | it('creates a comment with a message-path', async () => { 183 | inputs.message = undefined 184 | inputs['message-path'] = messagePath1Fixture 185 | inputs['allow-repeats'] = 'true' 186 | 187 | await expect(run()).resolves.not.toThrow() 188 | expect(`\n\n${messagePath1FixturePayload}`).toEqual( 189 | messagePayload?.body, 190 | ) 191 | expect(core.setOutput).toHaveBeenCalledWith('comment-created', 'true') 192 | expect(core.setOutput).toHaveBeenCalledWith('comment-id', postIssueCommentsResponse.id) 193 | }) 194 | 195 | it('creates a comment with multiple message-paths concatenated', async () => { 196 | inputs.message = undefined 197 | inputs['message-path'] = `${messagePath1Fixture}\n${messagePath2Fixture}` 198 | inputs['allow-repeats'] = 'true' 199 | 200 | await expect(run()).resolves.not.toThrow() 201 | expect( 202 | `\n\n${messagePath1FixturePayload}\n${messagePath1FixturePayload}`, 203 | ).toEqual(messagePayload?.body) 204 | expect(core.setOutput).toHaveBeenCalledWith('comment-created', 'true') 205 | expect(core.setOutput).toHaveBeenCalledWith('comment-id', postIssueCommentsResponse.id) 206 | }) 207 | 208 | it('supports globs in message paths', async () => { 209 | inputs.message = undefined 210 | inputs['message-path'] = `${path.resolve(__dirname)}/message-part-*.txt` 211 | inputs['allow-repeats'] = 'true' 212 | 213 | await expect(run()).resolves.not.toThrow() 214 | expect( 215 | `\n\n${messagePath1FixturePayload}\n${messagePath1FixturePayload}`, 216 | ).toEqual(messagePayload?.body) 217 | expect(core.setOutput).toHaveBeenCalledWith('comment-created', 'true') 218 | expect(core.setOutput).toHaveBeenCalledWith('comment-id', postIssueCommentsResponse.id) 219 | }) 220 | 221 | it('fails when both message and message-path are defined', async () => { 222 | inputs.message = 'foobar' 223 | inputs['message-path'] = messagePath1Fixture 224 | 225 | await expect(run()).resolves.not.toThrow() 226 | expect(core.setFailed).toHaveBeenCalledWith('must specify only one, message or message-path') 227 | }) 228 | 229 | it('creates a comment in an existing PR', async () => { 230 | inputs.message = simpleMessage 231 | inputs['allow-repeats'] = 'true' 232 | 233 | github.context.payload = { 234 | ...github.context.payload, 235 | pull_request: { 236 | number: 0, 237 | }, 238 | } as WebhookPayload 239 | 240 | issueNumber = apiResponse.result[0].number 241 | 242 | getCommitPullsResponse = apiResponse.result 243 | 244 | await expect(run()).resolves.not.toThrow() 245 | expect(core.setOutput).toHaveBeenCalledWith('comment-created', 'true') 246 | }) 247 | 248 | it('does not create a comment when updateOnly is true and no existing comment is found', async () => { 249 | inputs.message = simpleMessage 250 | inputs['allow-repeats'] = 'true' 251 | inputs['update-only'] = 'true' 252 | 253 | await expect(run()).resolves.not.toThrow() 254 | expect(core.setOutput).toHaveBeenCalledWith('comment-created', 'false') 255 | }) 256 | 257 | it('creates a comment in another repo', async () => { 258 | inputs.message = simpleMessage 259 | inputs['repo-owner'] = 'my-owner' 260 | inputs['repo-name'] = 'my-repo' 261 | inputs['allow-repeats'] = 'true' 262 | 263 | github.context.payload = { 264 | ...github.context.payload, 265 | pull_request: { 266 | number: 0, 267 | }, 268 | } as WebhookPayload 269 | 270 | issueNumber = apiResponse.result[0].number 271 | 272 | getCommitPullsResponse = apiResponse.result 273 | 274 | await expect(run()).resolves.not.toThrow() 275 | expect(core.setOutput).toHaveBeenCalledWith('comment-created', 'true') 276 | expect(core.setOutput).toHaveBeenCalledWith('comment-id', postIssueCommentsResponse.id) 277 | }) 278 | 279 | it('safely exits when no issue can be found [using GITHUB_TOKEN in env]', async () => { 280 | process.env['GITHUB_TOKEN'] = repoToken 281 | 282 | inputs.message = simpleMessage 283 | inputs['allow-repeats'] = 'true' 284 | 285 | github.context.payload = { 286 | ...github.context.payload, 287 | pull_request: { 288 | number: 0, 289 | }, 290 | } as WebhookPayload 291 | 292 | getCommitPullsResponse = [] 293 | 294 | await run() 295 | expect(core.setOutput).toHaveBeenCalledWith('comment-created', 'false') 296 | }) 297 | 298 | it('creates a message when the message id does not exist', async () => { 299 | inputs.message = simpleMessage 300 | 301 | inputs['message-id'] = 'custom-id' 302 | 303 | const replyBody = [ 304 | { 305 | body: `\n\n${simpleMessage}`, 306 | }, 307 | ] 308 | 309 | getIssueCommentsResponse = replyBody 310 | 311 | await run() 312 | 313 | expect(core.setOutput).toHaveBeenCalledWith('comment-created', 'true') 314 | }) 315 | 316 | it('identifies an existing message by id and updates it', async () => { 317 | inputs.message = simpleMessage 318 | 319 | const commentId = 123 320 | 321 | const replyBody = [ 322 | { 323 | id: commentId, 324 | body: `\n\n${simpleMessage}`, 325 | }, 326 | ] 327 | 328 | getIssueCommentsResponse = replyBody 329 | postIssueCommentsResponse = { 330 | id: commentId, 331 | } 332 | 333 | await run() 334 | 335 | expect(core.setOutput).toHaveBeenCalledWith('comment-updated', 'true') 336 | expect(core.setOutput).toHaveBeenCalledWith('comment-id', commentId) 337 | }) 338 | 339 | it('overrides the default message with a success message on success', async () => { 340 | inputs.message = simpleMessage 341 | 342 | inputs['message-success'] = '666' 343 | inputs.status = 'success' 344 | 345 | const commentId = 123 346 | 347 | getIssueCommentsResponse = [ 348 | { 349 | id: commentId, 350 | }, 351 | ] 352 | postIssueCommentsResponse = { 353 | id: commentId, 354 | } 355 | 356 | await run() 357 | expect(messagePayload?.body).toContain('666') 358 | }) 359 | 360 | it('overrides the default message with a failure message on failure', async () => { 361 | inputs.message = simpleMessage 362 | 363 | inputs['message-failure'] = '666' 364 | inputs.status = 'failure' 365 | 366 | const commentId = 123 367 | 368 | getIssueCommentsResponse = [ 369 | { 370 | id: commentId, 371 | }, 372 | ] 373 | postIssueCommentsResponse = { 374 | id: commentId, 375 | } 376 | 377 | await run() 378 | expect(messagePayload?.body).toContain('666') 379 | }) 380 | 381 | it('overrides the default message with a cancelled message on cancelled', async () => { 382 | inputs.message = simpleMessage 383 | 384 | inputs['message-cancelled'] = '666' 385 | inputs.status = 'cancelled' 386 | 387 | const commentId = 123 388 | 389 | getIssueCommentsResponse = [ 390 | { 391 | id: commentId, 392 | }, 393 | ] 394 | postIssueCommentsResponse = { 395 | id: commentId, 396 | } 397 | 398 | await run() 399 | expect(messagePayload?.body).toContain('666') 400 | }) 401 | 402 | it('overrides the default message with a skipped message on skipped', async () => { 403 | inputs.message = simpleMessage 404 | 405 | inputs['message-skipped'] = '666' 406 | inputs.status = 'skipped' 407 | 408 | const commentId = 123 409 | 410 | getIssueCommentsResponse = [ 411 | { 412 | id: commentId, 413 | }, 414 | ] 415 | postIssueCommentsResponse = { 416 | id: commentId, 417 | } 418 | 419 | await run() 420 | expect(messagePayload?.body).toContain('666') 421 | }) 422 | 423 | it('wraps a message in a codeblock if preformatted is true', async () => { 424 | inputs.message = undefined 425 | inputs['preformatted'] = 'true' 426 | inputs['message-path'] = messagePath1Fixture 427 | 428 | await expect(run()).resolves.not.toThrow() 429 | expect( 430 | `\n\n\`\`\`\n${messagePath1FixturePayload}\n\`\`\``, 431 | ).toEqual(messagePayload?.body) 432 | expect(core.setOutput).toHaveBeenCalledWith('comment-created', 'true') 433 | expect(core.setOutput).toHaveBeenCalledWith('comment-id', postIssueCommentsResponse.id) 434 | }) 435 | }) 436 | 437 | describe('find and replace', () => { 438 | it('can find and replace text in an existing comment', async () => { 439 | inputs['find'] = 'world' 440 | inputs['replace'] = 'mars' 441 | 442 | const commentId = 123 443 | 444 | const replyBody = [ 445 | { 446 | id: commentId, 447 | body: `\n\n${simpleMessage}`, 448 | }, 449 | ] 450 | 451 | getIssueCommentsResponse = replyBody 452 | postIssueCommentsResponse = { 453 | id: commentId, 454 | } 455 | 456 | await run() 457 | 458 | expect(`\n\nhello mars`).toEqual(messagePayload?.body) 459 | expect(core.setOutput).toHaveBeenCalledWith('comment-updated', 'true') 460 | expect(core.setOutput).toHaveBeenCalledWith('comment-id', commentId) 461 | }) 462 | 463 | it('can multiple find and replace text in an existing comment', async () => { 464 | inputs['find'] = 'hello\nworld' 465 | inputs['replace'] = 'goodbye\nmars' 466 | 467 | const body = `\n\nhello\nworld` 468 | 469 | const commentId = 123 470 | 471 | const replyBody = [ 472 | { 473 | id: commentId, 474 | body, 475 | }, 476 | ] 477 | 478 | getIssueCommentsResponse = replyBody 479 | postIssueCommentsResponse = { 480 | id: commentId, 481 | } 482 | 483 | await run() 484 | 485 | expect(`\n\ngoodbye\nmars`).toEqual(messagePayload?.body) 486 | expect(core.setOutput).toHaveBeenCalledWith('comment-updated', 'true') 487 | expect(core.setOutput).toHaveBeenCalledWith('comment-id', commentId) 488 | }) 489 | 490 | it('can multiple find and replace text using a message', async () => { 491 | inputs['find'] = 'hello\nworld' 492 | inputs['message'] = 'mars' 493 | 494 | const body = `\n\nhello\nworld` 495 | 496 | const commentId = 123 497 | 498 | const replyBody = [ 499 | { 500 | id: commentId, 501 | body, 502 | }, 503 | ] 504 | 505 | getIssueCommentsResponse = replyBody 506 | postIssueCommentsResponse = { 507 | id: commentId, 508 | } 509 | 510 | await run() 511 | 512 | expect(`\n\nmars\nmars`).toEqual(messagePayload?.body) 513 | expect(core.setOutput).toHaveBeenCalledWith('comment-updated', 'true') 514 | expect(core.setOutput).toHaveBeenCalledWith('comment-id', commentId) 515 | }) 516 | 517 | it('can multiple find and replace a single pattern with a multiline replacement', async () => { 518 | inputs['find'] = 'hello' 519 | inputs['message'] = 'h\ne\nl\nl\no' 520 | 521 | const body = `\n\nhello\nworld` 522 | 523 | const commentId = 123 524 | 525 | const replyBody = [ 526 | { 527 | id: commentId, 528 | body, 529 | }, 530 | ] 531 | 532 | getIssueCommentsResponse = replyBody 533 | postIssueCommentsResponse = { 534 | id: commentId, 535 | } 536 | 537 | await run() 538 | 539 | expect(`\n\nh\ne\nl\nl\no\nworld`).toEqual( 540 | messagePayload?.body, 541 | ) 542 | expect(core.setOutput).toHaveBeenCalledWith('comment-updated', 'true') 543 | expect(core.setOutput).toHaveBeenCalledWith('comment-id', commentId) 544 | }) 545 | 546 | it('can multiple find and replace text using a message-path', async () => { 547 | inputs['find'] = '<< FILE_CONTENTS >>' 548 | inputs['message-path'] = messagePath1Fixture 549 | 550 | const body = `\n\nhello\n<< FILE_CONTENTS >>\nworld` 551 | 552 | const commentId = 123 553 | 554 | const replyBody = [ 555 | { 556 | id: commentId, 557 | body, 558 | }, 559 | ] 560 | 561 | getIssueCommentsResponse = replyBody 562 | postIssueCommentsResponse = { 563 | id: commentId, 564 | } 565 | 566 | await run() 567 | 568 | expect( 569 | `\n\nhello\n${messagePath1FixturePayload}\nworld`, 570 | ).toEqual(messagePayload?.body) 571 | expect(core.setOutput).toHaveBeenCalledWith('comment-updated', 'true') 572 | expect(core.setOutput).toHaveBeenCalledWith('comment-id', commentId) 573 | }) 574 | 575 | it('can find and replace patterns and use alternative modifiers', async () => { 576 | inputs['find'] = '(o|l)/g' 577 | inputs['replace'] = 'YY' 578 | 579 | const body = `\n\nHELLO\nworld` 580 | 581 | const commentId = 123 582 | 583 | const replyBody = [ 584 | { 585 | id: commentId, 586 | body, 587 | }, 588 | ] 589 | 590 | getIssueCommentsResponse = replyBody 591 | postIssueCommentsResponse = { 592 | id: commentId, 593 | } 594 | 595 | await run() 596 | 597 | expect(`\n\nHELLO\nwYYrYYd`).toEqual(messagePayload?.body) 598 | expect(core.setOutput).toHaveBeenCalledWith('comment-updated', 'true') 599 | expect(core.setOutput).toHaveBeenCalledWith('comment-id', commentId) 600 | }) 601 | 602 | it('can check some boxes with find and replace', async () => { 603 | inputs['find'] = '\n\\[ \\]' 604 | inputs['replace'] = '[X]' 605 | 606 | const body = `\n\n[ ] Hello\n[ ] World` 607 | 608 | const commentId = 123 609 | 610 | const replyBody = [ 611 | { 612 | id: commentId, 613 | body, 614 | }, 615 | ] 616 | 617 | getIssueCommentsResponse = replyBody 618 | postIssueCommentsResponse = { 619 | id: commentId, 620 | } 621 | 622 | await run() 623 | 624 | expect(`\n\n[X] Hello\n[X] World`).toEqual( 625 | messagePayload?.body, 626 | ) 627 | expect(core.setOutput).toHaveBeenCalledWith('comment-updated', 'true') 628 | expect(core.setOutput).toHaveBeenCalledWith('comment-id', commentId) 629 | }) 630 | }) 631 | -------------------------------------------------------------------------------- /__tests__/message-part-1.txt: -------------------------------------------------------------------------------- 1 | ## [Preview link](https://antares-blog-staging-pr-${{ github.event.number }}.azurewebsites.net) 2 | 3 | - Your changes have been deployed to the preview site. The preview site will update as you add more commits to this branch. 4 | - The preview site shows any future-dated articles. Don't worry, if you are publishing a future-dated article, it will not show on the production site until the file's specified date. 5 | - The preview link is shareable, but will be deleted when the pull request is merged or closed. 6 | 7 | > *This is an automated message.* 8 | -------------------------------------------------------------------------------- /__tests__/message-part-2.txt: -------------------------------------------------------------------------------- 1 | ## [Preview link](https://antares-blog-staging-pr-${{ github.event.number }}.azurewebsites.net) 2 | 3 | - Your changes have been deployed to the preview site. The preview site will update as you add more commits to this branch. 4 | - The preview site shows any future-dated articles. Don't worry, if you are publishing a future-dated article, it will not show on the production site until the file's specified date. 5 | - The preview link is shareable, but will be deleted when the pull request is merged or closed. 6 | 7 | > *This is an automated message.* 8 | -------------------------------------------------------------------------------- /__tests__/message-windows.txt: -------------------------------------------------------------------------------- 1 | ## [Preview link](https://antares-blog-staging-pr-${{ github.event.number }}.azurewebsites.net) 2 | 3 | - Your changes have been deployed to the preview site. The preview site will update as you add more commits to this branch. 4 | - The preview site shows any future-dated articles. Don't worry, if you are publishing a future-dated article, it will not show on the production site until the file's specified date. 5 | - The preview link is shareable, but will be deleted when the pull request is merged or closed. 6 | 7 | > *This is an automated message.* 8 | -------------------------------------------------------------------------------- /__tests__/sample-master-push.json: -------------------------------------------------------------------------------- 1 | { 2 | "payload": { 3 | "after": "566e30ed2c7a061fa9e14117c14eb385a197e750", 4 | "base_ref": null, 5 | "before": "76a68e332929a5f129a6cdfc496c6d98f6f9de7e", 6 | "commits": [ 7 | { 8 | "author": { 9 | "email": "m@shick.us", 10 | "name": "Michael Shick", 11 | "username": "mshick" 12 | }, 13 | "committer": { 14 | "email": "m@shick.us", 15 | "name": "Michael Shick", 16 | "username": "mshick" 17 | }, 18 | "distinct": true, 19 | "id": "566e30ed2c7a061fa9e14117c14eb385a197e750", 20 | "message": "debug: adding context debugging", 21 | "timestamp": "2020-04-08T16:49:21-07:00", 22 | "tree_id": "055d2e982d249db10b8dd73fc1745ff97576ecc9", 23 | "url": "https://github.com/mshick/add-pr-comment/commit/566e30ed2c7a061fa9e14117c14eb385a197e750" 24 | } 25 | ], 26 | "compare": "https://github.com/mshick/add-pr-comment/compare/76a68e332929...566e30ed2c7a", 27 | "created": false, 28 | "deleted": false, 29 | "forced": false, 30 | "head_commit": { 31 | "author": { 32 | "email": "m@shick.us", 33 | "name": "Michael Shick", 34 | "username": "mshick" 35 | }, 36 | "committer": { 37 | "email": "m@shick.us", 38 | "name": "Michael Shick", 39 | "username": "mshick" 40 | }, 41 | "distinct": true, 42 | "id": "566e30ed2c7a061fa9e14117c14eb385a197e750", 43 | "message": "debug: adding context debugging", 44 | "timestamp": "2020-04-08T16:49:21-07:00", 45 | "tree_id": "055d2e982d249db10b8dd73fc1745ff97576ecc9", 46 | "url": "https://github.com/mshick/add-pr-comment/commit/566e30ed2c7a061fa9e14117c14eb385a197e750" 47 | }, 48 | "pusher": { 49 | "email": "m@shick.us", 50 | "name": "mshick" 51 | }, 52 | "ref": "refs/heads/master", 53 | "repository": { 54 | "archive_url": "https://api.github.com/repos/mshick/add-pr-comment/{archive_format}{/ref}", 55 | "archived": false, 56 | "assignees_url": "https://api.github.com/repos/mshick/add-pr-comment/assignees{/user}", 57 | "blobs_url": "https://api.github.com/repos/mshick/add-pr-comment/git/blobs{/sha}", 58 | "branches_url": "https://api.github.com/repos/mshick/add-pr-comment/branches{/branch}", 59 | "clone_url": "https://github.com/mshick/add-pr-comment.git", 60 | "collaborators_url": "https://api.github.com/repos/mshick/add-pr-comment/collaborators{/collaborator}", 61 | "comments_url": "https://api.github.com/repos/mshick/add-pr-comment/comments{/number}", 62 | "commits_url": "https://api.github.com/repos/mshick/add-pr-comment/commits{/sha}", 63 | "compare_url": "https://api.github.com/repos/mshick/add-pr-comment/compare/{base}...{head}", 64 | "contents_url": "https://api.github.com/repos/mshick/add-pr-comment/contents/{+path}", 65 | "contributors_url": "https://api.github.com/repos/mshick/add-pr-comment/contributors", 66 | "created_at": 1574460704, 67 | "default_branch": "master", 68 | "deployments_url": "https://api.github.com/repos/mshick/add-pr-comment/deployments", 69 | "description": "uses: mshick/add-pr-comment@v1", 70 | "disabled": false, 71 | "downloads_url": "https://api.github.com/repos/mshick/add-pr-comment/downloads", 72 | "events_url": "https://api.github.com/repos/mshick/add-pr-comment/events", 73 | "fork": false, 74 | "forks": 2, 75 | "forks_count": 2, 76 | "forks_url": "https://api.github.com/repos/mshick/add-pr-comment/forks", 77 | "full_name": "mshick/add-pr-comment", 78 | "git_commits_url": "https://api.github.com/repos/mshick/add-pr-comment/git/commits{/sha}", 79 | "git_refs_url": "https://api.github.com/repos/mshick/add-pr-comment/git/refs{/sha}", 80 | "git_tags_url": "https://api.github.com/repos/mshick/add-pr-comment/git/tags{/sha}", 81 | "git_url": "git://github.com/mshick/add-pr-comment.git", 82 | "has_downloads": true, 83 | "has_issues": true, 84 | "has_pages": false, 85 | "has_projects": true, 86 | "has_wiki": true, 87 | "homepage": "https://github.com/marketplace/actions/add-pr-comment", 88 | "hooks_url": "https://api.github.com/repos/mshick/add-pr-comment/hooks", 89 | "html_url": "https://github.com/mshick/add-pr-comment", 90 | "id": 223494945, 91 | "issue_comment_url": "https://api.github.com/repos/mshick/add-pr-comment/issues/comments{/number}", 92 | "issue_events_url": "https://api.github.com/repos/mshick/add-pr-comment/issues/events{/number}", 93 | "issues_url": "https://api.github.com/repos/mshick/add-pr-comment/issues{/number}", 94 | "keys_url": "https://api.github.com/repos/mshick/add-pr-comment/keys{/key_id}", 95 | "labels_url": "https://api.github.com/repos/mshick/add-pr-comment/labels{/name}", 96 | "language": "JavaScript", 97 | "languages_url": "https://api.github.com/repos/mshick/add-pr-comment/languages", 98 | "license": { 99 | "key": "mit", 100 | "name": "MIT License", 101 | "node_id": "MDc6TGljZW5zZTEz", 102 | "spdx_id": "MIT", 103 | "url": "https://api.github.com/licenses/mit" 104 | }, 105 | "master_branch": "master", 106 | "merges_url": "https://api.github.com/repos/mshick/add-pr-comment/merges", 107 | "milestones_url": "https://api.github.com/repos/mshick/add-pr-comment/milestones{/number}", 108 | "mirror_url": null, 109 | "name": "add-pr-comment", 110 | "node_id": "MDEwOlJlcG9zaXRvcnkyMjM0OTQ5NDU=", 111 | "notifications_url": "https://api.github.com/repos/mshick/add-pr-comment/notifications{?since,all,participating}", 112 | "open_issues": 1, 113 | "open_issues_count": 1, 114 | "owner": { 115 | "avatar_url": "https://avatars1.githubusercontent.com/u/277401?v=4", 116 | "email": "m@shick.us", 117 | "events_url": "https://api.github.com/users/mshick/events{/privacy}", 118 | "followers_url": "https://api.github.com/users/mshick/followers", 119 | "following_url": "https://api.github.com/users/mshick/following{/other_user}", 120 | "gists_url": "https://api.github.com/users/mshick/gists{/gist_id}", 121 | "gravatar_id": "", 122 | "html_url": "https://github.com/mshick", 123 | "id": 277401, 124 | "login": "mshick", 125 | "name": "mshick", 126 | "node_id": "MDQ6VXNlcjI3NzQwMQ==", 127 | "organizations_url": "https://api.github.com/users/mshick/orgs", 128 | "received_events_url": "https://api.github.com/users/mshick/received_events", 129 | "repos_url": "https://api.github.com/users/mshick/repos", 130 | "site_admin": false, 131 | "starred_url": "https://api.github.com/users/mshick/starred{/owner}{/repo}", 132 | "subscriptions_url": "https://api.github.com/users/mshick/subscriptions", 133 | "type": "User", 134 | "url": "https://api.github.com/users/mshick" 135 | }, 136 | "private": false, 137 | "pulls_url": "https://api.github.com/repos/mshick/add-pr-comment/pulls{/number}", 138 | "pushed_at": 1586389768, 139 | "releases_url": "https://api.github.com/repos/mshick/add-pr-comment/releases{/id}", 140 | "size": 299, 141 | "ssh_url": "git@github.com:mshick/add-pr-comment.git", 142 | "stargazers": 8, 143 | "stargazers_count": 8, 144 | "stargazers_url": "https://api.github.com/repos/mshick/add-pr-comment/stargazers", 145 | "statuses_url": "https://api.github.com/repos/mshick/add-pr-comment/statuses/{sha}", 146 | "subscribers_url": "https://api.github.com/repos/mshick/add-pr-comment/subscribers", 147 | "subscription_url": "https://api.github.com/repos/mshick/add-pr-comment/subscription", 148 | "svn_url": "https://github.com/mshick/add-pr-comment", 149 | "tags_url": "https://api.github.com/repos/mshick/add-pr-comment/tags", 150 | "teams_url": "https://api.github.com/repos/mshick/add-pr-comment/teams", 151 | "trees_url": "https://api.github.com/repos/mshick/add-pr-comment/git/trees{/sha}", 152 | "updated_at": "2020-04-08T23:47:36Z", 153 | "url": "https://github.com/mshick/add-pr-comment", 154 | "watchers": 8, 155 | "watchers_count": 8 156 | }, 157 | "sender": { 158 | "avatar_url": "https://avatars1.githubusercontent.com/u/277401?v=4", 159 | "events_url": "https://api.github.com/users/mshick/events{/privacy}", 160 | "followers_url": "https://api.github.com/users/mshick/followers", 161 | "following_url": "https://api.github.com/users/mshick/following{/other_user}", 162 | "gists_url": "https://api.github.com/users/mshick/gists{/gist_id}", 163 | "gravatar_id": "", 164 | "html_url": "https://github.com/mshick", 165 | "id": 277401, 166 | "login": "mshick", 167 | "node_id": "MDQ6VXNlcjI3NzQwMQ==", 168 | "organizations_url": "https://api.github.com/users/mshick/orgs", 169 | "received_events_url": "https://api.github.com/users/mshick/received_events", 170 | "repos_url": "https://api.github.com/users/mshick/repos", 171 | "site_admin": false, 172 | "starred_url": "https://api.github.com/users/mshick/starred{/owner}{/repo}", 173 | "subscriptions_url": "https://api.github.com/users/mshick/subscriptions", 174 | "type": "User", 175 | "url": "https://api.github.com/users/mshick" 176 | } 177 | }, 178 | "eventName": "push", 179 | "sha": "566e30ed2c7a061fa9e14117c14eb385a197e750", 180 | "ref": "refs/heads/master", 181 | "workflow": "test-local", 182 | "action": "self", 183 | "actor": "mshick" 184 | } 185 | -------------------------------------------------------------------------------- /__tests__/sample-pulls-api-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "statusCode": 200, 3 | "result": [ 4 | { 5 | "url": "https://api.github.com/repos/mshick/add-pr-comment/pulls/6", 6 | "id": 401139651, 7 | "node_id": "MDExOlB1bGxSZXF1ZXN0NDAxMTM5NjUx", 8 | "html_url": "https://github.com/mshick/add-pr-comment/pull/6", 9 | "diff_url": "https://github.com/mshick/add-pr-comment/pull/6.diff", 10 | "patch_url": "https://github.com/mshick/add-pr-comment/pull/6.patch", 11 | "issue_url": "https://api.github.com/repos/mshick/add-pr-comment/issues/6", 12 | "number": 6, 13 | "state": "closed", 14 | "locked": false, 15 | "title": "debug: trigger small change", 16 | "user": { 17 | "login": "mshick", 18 | "id": 277401, 19 | "node_id": "MDQ6VXNlcjI3NzQwMQ==", 20 | "avatar_url": "https://avatars1.githubusercontent.com/u/277401?v=4", 21 | "gravatar_id": "", 22 | "url": "https://api.github.com/users/mshick", 23 | "html_url": "https://github.com/mshick", 24 | "followers_url": "https://api.github.com/users/mshick/followers", 25 | "following_url": "https://api.github.com/users/mshick/following{/other_user}", 26 | "gists_url": "https://api.github.com/users/mshick/gists{/gist_id}", 27 | "starred_url": "https://api.github.com/users/mshick/starred{/owner}{/repo}", 28 | "subscriptions_url": "https://api.github.com/users/mshick/subscriptions", 29 | "organizations_url": "https://api.github.com/users/mshick/orgs", 30 | "repos_url": "https://api.github.com/users/mshick/repos", 31 | "events_url": "https://api.github.com/users/mshick/events{/privacy}", 32 | "received_events_url": "https://api.github.com/users/mshick/received_events", 33 | "type": "User", 34 | "site_admin": false 35 | }, 36 | "body": "", 37 | "created_at": "2020-04-09T00:07:01Z", 38 | "updated_at": "2020-04-09T00:08:32Z", 39 | "closed_at": "2020-04-09T00:08:32Z", 40 | "merged_at": "2020-04-09T00:08:32Z", 41 | "merge_commit_sha": "8dba58ef879174e996c956424d47f88a1a36e04a", 42 | "assignee": null, 43 | "assignees": [], 44 | "requested_reviewers": [], 45 | "requested_teams": [], 46 | "labels": [], 47 | "milestone": null, 48 | "draft": false, 49 | "commits_url": "https://api.github.com/repos/mshick/add-pr-comment/pulls/6/commits", 50 | "review_comments_url": "https://api.github.com/repos/mshick/add-pr-comment/pulls/6/comments", 51 | "review_comment_url": "https://api.github.com/repos/mshick/add-pr-comment/pulls/comments{/number}", 52 | "comments_url": "https://api.github.com/repos/mshick/add-pr-comment/issues/6/comments", 53 | "statuses_url": "https://api.github.com/repos/mshick/add-pr-comment/statuses/9bef15e74a60ecbb5197e7e975096fd7c6525794", 54 | "head": { 55 | "label": "mshick:feature/test2", 56 | "ref": "feature/test2", 57 | "sha": "9bef15e74a60ecbb5197e7e975096fd7c6525794", 58 | "user": { 59 | "login": "mshick", 60 | "id": 277401, 61 | "node_id": "MDQ6VXNlcjI3NzQwMQ==", 62 | "avatar_url": "https://avatars1.githubusercontent.com/u/277401?v=4", 63 | "gravatar_id": "", 64 | "url": "https://api.github.com/users/mshick", 65 | "html_url": "https://github.com/mshick", 66 | "followers_url": "https://api.github.com/users/mshick/followers", 67 | "following_url": "https://api.github.com/users/mshick/following{/other_user}", 68 | "gists_url": "https://api.github.com/users/mshick/gists{/gist_id}", 69 | "starred_url": "https://api.github.com/users/mshick/starred{/owner}{/repo}", 70 | "subscriptions_url": "https://api.github.com/users/mshick/subscriptions", 71 | "organizations_url": "https://api.github.com/users/mshick/orgs", 72 | "repos_url": "https://api.github.com/users/mshick/repos", 73 | "events_url": "https://api.github.com/users/mshick/events{/privacy}", 74 | "received_events_url": "https://api.github.com/users/mshick/received_events", 75 | "type": "User", 76 | "site_admin": false 77 | }, 78 | "repo": { 79 | "id": 223494945, 80 | "node_id": "MDEwOlJlcG9zaXRvcnkyMjM0OTQ5NDU=", 81 | "name": "add-pr-comment", 82 | "full_name": "mshick/add-pr-comment", 83 | "private": false, 84 | "owner": { 85 | "login": "mshick", 86 | "id": 277401, 87 | "node_id": "MDQ6VXNlcjI3NzQwMQ==", 88 | "avatar_url": "https://avatars1.githubusercontent.com/u/277401?v=4", 89 | "gravatar_id": "", 90 | "url": "https://api.github.com/users/mshick", 91 | "html_url": "https://github.com/mshick", 92 | "followers_url": "https://api.github.com/users/mshick/followers", 93 | "following_url": "https://api.github.com/users/mshick/following{/other_user}", 94 | "gists_url": "https://api.github.com/users/mshick/gists{/gist_id}", 95 | "starred_url": "https://api.github.com/users/mshick/starred{/owner}{/repo}", 96 | "subscriptions_url": "https://api.github.com/users/mshick/subscriptions", 97 | "organizations_url": "https://api.github.com/users/mshick/orgs", 98 | "repos_url": "https://api.github.com/users/mshick/repos", 99 | "events_url": "https://api.github.com/users/mshick/events{/privacy}", 100 | "received_events_url": "https://api.github.com/users/mshick/received_events", 101 | "type": "User", 102 | "site_admin": false 103 | }, 104 | "html_url": "https://github.com/mshick/add-pr-comment", 105 | "description": "uses: mshick/add-pr-comment@v1", 106 | "fork": false, 107 | "url": "https://api.github.com/repos/mshick/add-pr-comment", 108 | "forks_url": "https://api.github.com/repos/mshick/add-pr-comment/forks", 109 | "keys_url": "https://api.github.com/repos/mshick/add-pr-comment/keys{/key_id}", 110 | "collaborators_url": "https://api.github.com/repos/mshick/add-pr-comment/collaborators{/collaborator}", 111 | "teams_url": "https://api.github.com/repos/mshick/add-pr-comment/teams", 112 | "hooks_url": "https://api.github.com/repos/mshick/add-pr-comment/hooks", 113 | "issue_events_url": "https://api.github.com/repos/mshick/add-pr-comment/issues/events{/number}", 114 | "events_url": "https://api.github.com/repos/mshick/add-pr-comment/events", 115 | "assignees_url": "https://api.github.com/repos/mshick/add-pr-comment/assignees{/user}", 116 | "branches_url": "https://api.github.com/repos/mshick/add-pr-comment/branches{/branch}", 117 | "tags_url": "https://api.github.com/repos/mshick/add-pr-comment/tags", 118 | "blobs_url": "https://api.github.com/repos/mshick/add-pr-comment/git/blobs{/sha}", 119 | "git_tags_url": "https://api.github.com/repos/mshick/add-pr-comment/git/tags{/sha}", 120 | "git_refs_url": "https://api.github.com/repos/mshick/add-pr-comment/git/refs{/sha}", 121 | "trees_url": "https://api.github.com/repos/mshick/add-pr-comment/git/trees{/sha}", 122 | "statuses_url": "https://api.github.com/repos/mshick/add-pr-comment/statuses/{sha}", 123 | "languages_url": "https://api.github.com/repos/mshick/add-pr-comment/languages", 124 | "stargazers_url": "https://api.github.com/repos/mshick/add-pr-comment/stargazers", 125 | "contributors_url": "https://api.github.com/repos/mshick/add-pr-comment/contributors", 126 | "subscribers_url": "https://api.github.com/repos/mshick/add-pr-comment/subscribers", 127 | "subscription_url": "https://api.github.com/repos/mshick/add-pr-comment/subscription", 128 | "commits_url": "https://api.github.com/repos/mshick/add-pr-comment/commits{/sha}", 129 | "git_commits_url": "https://api.github.com/repos/mshick/add-pr-comment/git/commits{/sha}", 130 | "comments_url": "https://api.github.com/repos/mshick/add-pr-comment/comments{/number}", 131 | "issue_comment_url": "https://api.github.com/repos/mshick/add-pr-comment/issues/comments{/number}", 132 | "contents_url": "https://api.github.com/repos/mshick/add-pr-comment/contents/{+path}", 133 | "compare_url": "https://api.github.com/repos/mshick/add-pr-comment/compare/{base}...{head}", 134 | "merges_url": "https://api.github.com/repos/mshick/add-pr-comment/merges", 135 | "archive_url": "https://api.github.com/repos/mshick/add-pr-comment/{archive_format}{/ref}", 136 | "downloads_url": "https://api.github.com/repos/mshick/add-pr-comment/downloads", 137 | "issues_url": "https://api.github.com/repos/mshick/add-pr-comment/issues{/number}", 138 | "pulls_url": "https://api.github.com/repos/mshick/add-pr-comment/pulls{/number}", 139 | "milestones_url": "https://api.github.com/repos/mshick/add-pr-comment/milestones{/number}", 140 | "notifications_url": "https://api.github.com/repos/mshick/add-pr-comment/notifications{?since,all,participating}", 141 | "labels_url": "https://api.github.com/repos/mshick/add-pr-comment/labels{/name}", 142 | "releases_url": "https://api.github.com/repos/mshick/add-pr-comment/releases{/id}", 143 | "deployments_url": "https://api.github.com/repos/mshick/add-pr-comment/deployments", 144 | "created_at": "2019-11-22T22:11:44Z", 145 | "updated_at": "2020-04-09T00:08:35Z", 146 | "pushed_at": "2020-04-09T00:08:32Z", 147 | "git_url": "git://github.com/mshick/add-pr-comment.git", 148 | "ssh_url": "git@github.com:mshick/add-pr-comment.git", 149 | "clone_url": "https://github.com/mshick/add-pr-comment.git", 150 | "svn_url": "https://github.com/mshick/add-pr-comment", 151 | "homepage": "https://github.com/marketplace/actions/add-pr-comment", 152 | "size": 299, 153 | "stargazers_count": 8, 154 | "watchers_count": 8, 155 | "language": "JavaScript", 156 | "has_issues": true, 157 | "has_projects": true, 158 | "has_downloads": true, 159 | "has_wiki": true, 160 | "has_pages": false, 161 | "forks_count": 2, 162 | "mirror_url": null, 163 | "archived": false, 164 | "disabled": false, 165 | "open_issues_count": 1, 166 | "license": { 167 | "key": "mit", 168 | "name": "MIT License", 169 | "spdx_id": "MIT", 170 | "url": "https://api.github.com/licenses/mit", 171 | "node_id": "MDc6TGljZW5zZTEz" 172 | }, 173 | "forks": 2, 174 | "open_issues": 1, 175 | "watchers": 8, 176 | "default_branch": "master" 177 | } 178 | }, 179 | "base": { 180 | "label": "mshick:master", 181 | "ref": "master", 182 | "sha": "240085d7fd326249008880448fe368e9da6f45b8", 183 | "user": { 184 | "login": "mshick", 185 | "id": 277401, 186 | "node_id": "MDQ6VXNlcjI3NzQwMQ==", 187 | "avatar_url": "https://avatars1.githubusercontent.com/u/277401?v=4", 188 | "gravatar_id": "", 189 | "url": "https://api.github.com/users/mshick", 190 | "html_url": "https://github.com/mshick", 191 | "followers_url": "https://api.github.com/users/mshick/followers", 192 | "following_url": "https://api.github.com/users/mshick/following{/other_user}", 193 | "gists_url": "https://api.github.com/users/mshick/gists{/gist_id}", 194 | "starred_url": "https://api.github.com/users/mshick/starred{/owner}{/repo}", 195 | "subscriptions_url": "https://api.github.com/users/mshick/subscriptions", 196 | "organizations_url": "https://api.github.com/users/mshick/orgs", 197 | "repos_url": "https://api.github.com/users/mshick/repos", 198 | "events_url": "https://api.github.com/users/mshick/events{/privacy}", 199 | "received_events_url": "https://api.github.com/users/mshick/received_events", 200 | "type": "User", 201 | "site_admin": false 202 | }, 203 | "repo": { 204 | "id": 223494945, 205 | "node_id": "MDEwOlJlcG9zaXRvcnkyMjM0OTQ5NDU=", 206 | "name": "add-pr-comment", 207 | "full_name": "mshick/add-pr-comment", 208 | "private": false, 209 | "owner": { 210 | "login": "mshick", 211 | "id": 277401, 212 | "node_id": "MDQ6VXNlcjI3NzQwMQ==", 213 | "avatar_url": "https://avatars1.githubusercontent.com/u/277401?v=4", 214 | "gravatar_id": "", 215 | "url": "https://api.github.com/users/mshick", 216 | "html_url": "https://github.com/mshick", 217 | "followers_url": "https://api.github.com/users/mshick/followers", 218 | "following_url": "https://api.github.com/users/mshick/following{/other_user}", 219 | "gists_url": "https://api.github.com/users/mshick/gists{/gist_id}", 220 | "starred_url": "https://api.github.com/users/mshick/starred{/owner}{/repo}", 221 | "subscriptions_url": "https://api.github.com/users/mshick/subscriptions", 222 | "organizations_url": "https://api.github.com/users/mshick/orgs", 223 | "repos_url": "https://api.github.com/users/mshick/repos", 224 | "events_url": "https://api.github.com/users/mshick/events{/privacy}", 225 | "received_events_url": "https://api.github.com/users/mshick/received_events", 226 | "type": "User", 227 | "site_admin": false 228 | }, 229 | "html_url": "https://github.com/mshick/add-pr-comment", 230 | "description": "uses: mshick/add-pr-comment@v1", 231 | "fork": false, 232 | "url": "https://api.github.com/repos/mshick/add-pr-comment", 233 | "forks_url": "https://api.github.com/repos/mshick/add-pr-comment/forks", 234 | "keys_url": "https://api.github.com/repos/mshick/add-pr-comment/keys{/key_id}", 235 | "collaborators_url": "https://api.github.com/repos/mshick/add-pr-comment/collaborators{/collaborator}", 236 | "teams_url": "https://api.github.com/repos/mshick/add-pr-comment/teams", 237 | "hooks_url": "https://api.github.com/repos/mshick/add-pr-comment/hooks", 238 | "issue_events_url": "https://api.github.com/repos/mshick/add-pr-comment/issues/events{/number}", 239 | "events_url": "https://api.github.com/repos/mshick/add-pr-comment/events", 240 | "assignees_url": "https://api.github.com/repos/mshick/add-pr-comment/assignees{/user}", 241 | "branches_url": "https://api.github.com/repos/mshick/add-pr-comment/branches{/branch}", 242 | "tags_url": "https://api.github.com/repos/mshick/add-pr-comment/tags", 243 | "blobs_url": "https://api.github.com/repos/mshick/add-pr-comment/git/blobs{/sha}", 244 | "git_tags_url": "https://api.github.com/repos/mshick/add-pr-comment/git/tags{/sha}", 245 | "git_refs_url": "https://api.github.com/repos/mshick/add-pr-comment/git/refs{/sha}", 246 | "trees_url": "https://api.github.com/repos/mshick/add-pr-comment/git/trees{/sha}", 247 | "statuses_url": "https://api.github.com/repos/mshick/add-pr-comment/statuses/{sha}", 248 | "languages_url": "https://api.github.com/repos/mshick/add-pr-comment/languages", 249 | "stargazers_url": "https://api.github.com/repos/mshick/add-pr-comment/stargazers", 250 | "contributors_url": "https://api.github.com/repos/mshick/add-pr-comment/contributors", 251 | "subscribers_url": "https://api.github.com/repos/mshick/add-pr-comment/subscribers", 252 | "subscription_url": "https://api.github.com/repos/mshick/add-pr-comment/subscription", 253 | "commits_url": "https://api.github.com/repos/mshick/add-pr-comment/commits{/sha}", 254 | "git_commits_url": "https://api.github.com/repos/mshick/add-pr-comment/git/commits{/sha}", 255 | "comments_url": "https://api.github.com/repos/mshick/add-pr-comment/comments{/number}", 256 | "issue_comment_url": "https://api.github.com/repos/mshick/add-pr-comment/issues/comments{/number}", 257 | "contents_url": "https://api.github.com/repos/mshick/add-pr-comment/contents/{+path}", 258 | "compare_url": "https://api.github.com/repos/mshick/add-pr-comment/compare/{base}...{head}", 259 | "merges_url": "https://api.github.com/repos/mshick/add-pr-comment/merges", 260 | "archive_url": "https://api.github.com/repos/mshick/add-pr-comment/{archive_format}{/ref}", 261 | "downloads_url": "https://api.github.com/repos/mshick/add-pr-comment/downloads", 262 | "issues_url": "https://api.github.com/repos/mshick/add-pr-comment/issues{/number}", 263 | "pulls_url": "https://api.github.com/repos/mshick/add-pr-comment/pulls{/number}", 264 | "milestones_url": "https://api.github.com/repos/mshick/add-pr-comment/milestones{/number}", 265 | "notifications_url": "https://api.github.com/repos/mshick/add-pr-comment/notifications{?since,all,participating}", 266 | "labels_url": "https://api.github.com/repos/mshick/add-pr-comment/labels{/name}", 267 | "releases_url": "https://api.github.com/repos/mshick/add-pr-comment/releases{/id}", 268 | "deployments_url": "https://api.github.com/repos/mshick/add-pr-comment/deployments", 269 | "created_at": "2019-11-22T22:11:44Z", 270 | "updated_at": "2020-04-09T00:08:35Z", 271 | "pushed_at": "2020-04-09T00:08:32Z", 272 | "git_url": "git://github.com/mshick/add-pr-comment.git", 273 | "ssh_url": "git@github.com:mshick/add-pr-comment.git", 274 | "clone_url": "https://github.com/mshick/add-pr-comment.git", 275 | "svn_url": "https://github.com/mshick/add-pr-comment", 276 | "homepage": "https://github.com/marketplace/actions/add-pr-comment", 277 | "size": 299, 278 | "stargazers_count": 8, 279 | "watchers_count": 8, 280 | "language": "JavaScript", 281 | "has_issues": true, 282 | "has_projects": true, 283 | "has_downloads": true, 284 | "has_wiki": true, 285 | "has_pages": false, 286 | "forks_count": 2, 287 | "mirror_url": null, 288 | "archived": false, 289 | "disabled": false, 290 | "open_issues_count": 1, 291 | "license": { 292 | "key": "mit", 293 | "name": "MIT License", 294 | "spdx_id": "MIT", 295 | "url": "https://api.github.com/licenses/mit", 296 | "node_id": "MDc6TGljZW5zZTEz" 297 | }, 298 | "forks": 2, 299 | "open_issues": 1, 300 | "watchers": 8, 301 | "default_branch": "master" 302 | } 303 | }, 304 | "_links": { 305 | "self": { 306 | "href": "https://api.github.com/repos/mshick/add-pr-comment/pulls/6" 307 | }, 308 | "html": { 309 | "href": "https://github.com/mshick/add-pr-comment/pull/6" 310 | }, 311 | "issue": { 312 | "href": "https://api.github.com/repos/mshick/add-pr-comment/issues/6" 313 | }, 314 | "comments": { 315 | "href": "https://api.github.com/repos/mshick/add-pr-comment/issues/6/comments" 316 | }, 317 | "review_comments": { 318 | "href": "https://api.github.com/repos/mshick/add-pr-comment/pulls/6/comments" 319 | }, 320 | "review_comment": { 321 | "href": "https://api.github.com/repos/mshick/add-pr-comment/pulls/comments{/number}" 322 | }, 323 | "commits": { 324 | "href": "https://api.github.com/repos/mshick/add-pr-comment/pulls/6/commits" 325 | }, 326 | "statuses": { 327 | "href": "https://api.github.com/repos/mshick/add-pr-comment/statuses/9bef15e74a60ecbb5197e7e975096fd7c6525794" 328 | } 329 | }, 330 | "author_association": "OWNER" 331 | } 332 | ], 333 | "headers": { 334 | "server": "GitHub.com", 335 | "date": "Thu, 09 Apr 2020 00:09:21 GMT", 336 | "content-type": "application/json; charset=utf-8", 337 | "status": "200 OK", 338 | "cache-control": "public, max-age=60, s-maxage=60", 339 | "vary": "Accept, Accept-Encoding, Accept, X-Requested-With", 340 | "etag": "W/\"c8d0f2877b89a817343189c4c84e5afd\"", 341 | "x-github-media-type": "github.groot-preview; format=json", 342 | "access-control-expose-headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, Deprecation, Sunset", 343 | "access-control-allow-origin": "*", 344 | "strict-transport-security": "max-age=31536000; includeSubdomains; preload", 345 | "x-frame-options": "deny", 346 | "x-content-type-options": "nosniff", 347 | "x-xss-protection": "1; mode=block", 348 | "referrer-policy": "origin-when-cross-origin, strict-origin-when-cross-origin", 349 | "content-security-policy": "default-src 'none'", 350 | "x-ratelimit-limit": "60", 351 | "x-ratelimit-remaining": "50", 352 | "x-ratelimit-reset": "1586392794", 353 | "accept-ranges": "bytes", 354 | "transfer-encoding": "chunked", 355 | "connection": "close", 356 | "x-github-request-id": "0401:253A:371D25:5CAF8A:5E8E67B1" 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: "Add PR Comment" 2 | description: "Add a comment to a pull request" 3 | inputs: 4 | message: 5 | description: "The message to print." 6 | required: false 7 | message-path: 8 | description: "A path or list of paths to a file to print as a message instead of a string." 9 | required: false 10 | message-id: 11 | description: "An optional id to use for this message." 12 | default: "add-pr-comment" 13 | required: true 14 | refresh-message-position: 15 | description: "If a message with the same id, this option allow to refresh the position of the message to be the last one posted." 16 | default: "false" 17 | required: true 18 | repo-owner: 19 | description: "The repo owner." 20 | default: "${{ github.repository_owner }}" 21 | required: true 22 | repo-name: 23 | description: "The repo name." 24 | default: "${{ github.event.repository.name }}" 25 | required: true 26 | repo-token: 27 | description: "A GitHub token for API access. Defaults to {{ github.token }}." 28 | default: "${{ github.token }}" 29 | required: true 30 | allow-repeats: 31 | description: "Allow messages to be repeated." 32 | default: "false" 33 | required: true 34 | proxy-url: 35 | description: "Proxy URL for comment creation" 36 | required: false 37 | status: 38 | description: "A job status for status headers. Defaults to {{ job.status }}." 39 | default: "${{ job.status }}" 40 | required: true 41 | message-success: 42 | description: "Override the message when a run is successful." 43 | required: false 44 | message-failure: 45 | description: "Override the message when a run fails." 46 | required: false 47 | message-cancelled: 48 | description: "Override the message when a run is cancelled." 49 | required: false 50 | message-skipped: 51 | description: "Override the message when a run is skipped." 52 | required: false 53 | issue: 54 | description: "Override the message when a run is cancelled." 55 | required: false 56 | update-only: 57 | description: "Only update the comment if it already exists." 58 | required: false 59 | preformatted: 60 | description: "Treat message text (from a file or input) as pre-formatted and place it in a codeblock." 61 | required: false 62 | find: 63 | description: "A regular expression to find for replacement. Multiple lines become individual regular expressions." 64 | replace: 65 | description: "A replacement to use, overrides the message. Multple lines can replace same-indexed find patterns." 66 | outputs: 67 | comment-created: 68 | description: "Whether a comment was created." 69 | comment-updated: 70 | description: "Whether a comment was updated." 71 | comment-id: 72 | description: "If a comment was created or updated, the comment id." 73 | branding: 74 | icon: message-circle 75 | color: purple 76 | runs: 77 | using: "node20" 78 | main: "dist/index.js" 79 | -------------------------------------------------------------------------------- /dist/sourcemap-register.js: -------------------------------------------------------------------------------- 1 | (()=>{var e={650:e=>{var r=Object.prototype.toString;var n=typeof Buffer.alloc==="function"&&typeof Buffer.allocUnsafe==="function"&&typeof Buffer.from==="function";function isArrayBuffer(e){return r.call(e).slice(8,-1)==="ArrayBuffer"}function fromArrayBuffer(e,r,t){r>>>=0;var o=e.byteLength-r;if(o<0){throw new RangeError("'offset' is out of bounds")}if(t===undefined){t=o}else{t>>>=0;if(t>o){throw new RangeError("'length' is out of bounds")}}return n?Buffer.from(e.slice(r,r+t)):new Buffer(new Uint8Array(e.slice(r,r+t)))}function fromString(e,r){if(typeof r!=="string"||r===""){r="utf8"}if(!Buffer.isEncoding(r)){throw new TypeError('"encoding" must be a valid string encoding')}return n?Buffer.from(e,r):new Buffer(e,r)}function bufferFrom(e,r,t){if(typeof e==="number"){throw new TypeError('"value" argument must not be a number')}if(isArrayBuffer(e)){return fromArrayBuffer(e,r,t)}if(typeof e==="string"){return fromString(e,r)}return n?Buffer.from(e):new Buffer(e)}e.exports=bufferFrom},274:(e,r,n)=>{var t=n(339);var o=Object.prototype.hasOwnProperty;var i=typeof Map!=="undefined";function ArraySet(){this._array=[];this._set=i?new Map:Object.create(null)}ArraySet.fromArray=function ArraySet_fromArray(e,r){var n=new ArraySet;for(var t=0,o=e.length;t=0){return r}}else{var n=t.toSetString(e);if(o.call(this._set,n)){return this._set[n]}}throw new Error('"'+e+'" is not in the set.')};ArraySet.prototype.at=function ArraySet_at(e){if(e>=0&&e{var t=n(190);var o=5;var i=1<>1;return r?-n:n}r.encode=function base64VLQ_encode(e){var r="";var n;var i=toVLQSigned(e);do{n=i&a;i>>>=o;if(i>0){n|=u}r+=t.encode(n)}while(i>0);return r};r.decode=function base64VLQ_decode(e,r,n){var i=e.length;var s=0;var l=0;var c,p;do{if(r>=i){throw new Error("Expected more digits in base 64 VLQ value.")}p=t.decode(e.charCodeAt(r++));if(p===-1){throw new Error("Invalid base64 digit: "+e.charAt(r-1))}c=!!(p&u);p&=a;s=s+(p<{var n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".split("");r.encode=function(e){if(0<=e&&e{r.GREATEST_LOWER_BOUND=1;r.LEAST_UPPER_BOUND=2;function recursiveSearch(e,n,t,o,i,a){var u=Math.floor((n-e)/2)+e;var s=i(t,o[u],true);if(s===0){return u}else if(s>0){if(n-u>1){return recursiveSearch(u,n,t,o,i,a)}if(a==r.LEAST_UPPER_BOUND){return n1){return recursiveSearch(e,u,t,o,i,a)}if(a==r.LEAST_UPPER_BOUND){return u}else{return e<0?-1:e}}}r.search=function search(e,n,t,o){if(n.length===0){return-1}var i=recursiveSearch(-1,n.length,e,n,t,o||r.GREATEST_LOWER_BOUND);if(i<0){return-1}while(i-1>=0){if(t(n[i],n[i-1],true)!==0){break}--i}return i}},680:(e,r,n)=>{var t=n(339);function generatedPositionAfter(e,r){var n=e.generatedLine;var o=r.generatedLine;var i=e.generatedColumn;var a=r.generatedColumn;return o>n||o==n&&a>=i||t.compareByGeneratedPositionsInflated(e,r)<=0}function MappingList(){this._array=[];this._sorted=true;this._last={generatedLine:-1,generatedColumn:0}}MappingList.prototype.unsortedForEach=function MappingList_forEach(e,r){this._array.forEach(e,r)};MappingList.prototype.add=function MappingList_add(e){if(generatedPositionAfter(this._last,e)){this._last=e;this._array.push(e)}else{this._sorted=false;this._array.push(e)}};MappingList.prototype.toArray=function MappingList_toArray(){if(!this._sorted){this._array.sort(t.compareByGeneratedPositionsInflated);this._sorted=true}return this._array};r.H=MappingList},758:(e,r)=>{function swap(e,r,n){var t=e[r];e[r]=e[n];e[n]=t}function randomIntInRange(e,r){return Math.round(e+Math.random()*(r-e))}function doQuickSort(e,r,n,t){if(n{var t;var o=n(339);var i=n(345);var a=n(274).I;var u=n(449);var s=n(758).U;function SourceMapConsumer(e,r){var n=e;if(typeof e==="string"){n=o.parseSourceMapInput(e)}return n.sections!=null?new IndexedSourceMapConsumer(n,r):new BasicSourceMapConsumer(n,r)}SourceMapConsumer.fromSourceMap=function(e,r){return BasicSourceMapConsumer.fromSourceMap(e,r)};SourceMapConsumer.prototype._version=3;SourceMapConsumer.prototype.__generatedMappings=null;Object.defineProperty(SourceMapConsumer.prototype,"_generatedMappings",{configurable:true,enumerable:true,get:function(){if(!this.__generatedMappings){this._parseMappings(this._mappings,this.sourceRoot)}return this.__generatedMappings}});SourceMapConsumer.prototype.__originalMappings=null;Object.defineProperty(SourceMapConsumer.prototype,"_originalMappings",{configurable:true,enumerable:true,get:function(){if(!this.__originalMappings){this._parseMappings(this._mappings,this.sourceRoot)}return this.__originalMappings}});SourceMapConsumer.prototype._charIsMappingSeparator=function SourceMapConsumer_charIsMappingSeparator(e,r){var n=e.charAt(r);return n===";"||n===","};SourceMapConsumer.prototype._parseMappings=function SourceMapConsumer_parseMappings(e,r){throw new Error("Subclasses must implement _parseMappings")};SourceMapConsumer.GENERATED_ORDER=1;SourceMapConsumer.ORIGINAL_ORDER=2;SourceMapConsumer.GREATEST_LOWER_BOUND=1;SourceMapConsumer.LEAST_UPPER_BOUND=2;SourceMapConsumer.prototype.eachMapping=function SourceMapConsumer_eachMapping(e,r,n){var t=r||null;var i=n||SourceMapConsumer.GENERATED_ORDER;var a;switch(i){case SourceMapConsumer.GENERATED_ORDER:a=this._generatedMappings;break;case SourceMapConsumer.ORIGINAL_ORDER:a=this._originalMappings;break;default:throw new Error("Unknown order of iteration.")}var u=this.sourceRoot;a.map((function(e){var r=e.source===null?null:this._sources.at(e.source);r=o.computeSourceURL(u,r,this._sourceMapURL);return{source:r,generatedLine:e.generatedLine,generatedColumn:e.generatedColumn,originalLine:e.originalLine,originalColumn:e.originalColumn,name:e.name===null?null:this._names.at(e.name)}}),this).forEach(e,t)};SourceMapConsumer.prototype.allGeneratedPositionsFor=function SourceMapConsumer_allGeneratedPositionsFor(e){var r=o.getArg(e,"line");var n={source:o.getArg(e,"source"),originalLine:r,originalColumn:o.getArg(e,"column",0)};n.source=this._findSourceIndex(n.source);if(n.source<0){return[]}var t=[];var a=this._findMapping(n,this._originalMappings,"originalLine","originalColumn",o.compareByOriginalPositions,i.LEAST_UPPER_BOUND);if(a>=0){var u=this._originalMappings[a];if(e.column===undefined){var s=u.originalLine;while(u&&u.originalLine===s){t.push({line:o.getArg(u,"generatedLine",null),column:o.getArg(u,"generatedColumn",null),lastColumn:o.getArg(u,"lastGeneratedColumn",null)});u=this._originalMappings[++a]}}else{var l=u.originalColumn;while(u&&u.originalLine===r&&u.originalColumn==l){t.push({line:o.getArg(u,"generatedLine",null),column:o.getArg(u,"generatedColumn",null),lastColumn:o.getArg(u,"lastGeneratedColumn",null)});u=this._originalMappings[++a]}}}return t};r.SourceMapConsumer=SourceMapConsumer;function BasicSourceMapConsumer(e,r){var n=e;if(typeof e==="string"){n=o.parseSourceMapInput(e)}var t=o.getArg(n,"version");var i=o.getArg(n,"sources");var u=o.getArg(n,"names",[]);var s=o.getArg(n,"sourceRoot",null);var l=o.getArg(n,"sourcesContent",null);var c=o.getArg(n,"mappings");var p=o.getArg(n,"file",null);if(t!=this._version){throw new Error("Unsupported version: "+t)}if(s){s=o.normalize(s)}i=i.map(String).map(o.normalize).map((function(e){return s&&o.isAbsolute(s)&&o.isAbsolute(e)?o.relative(s,e):e}));this._names=a.fromArray(u.map(String),true);this._sources=a.fromArray(i,true);this._absoluteSources=this._sources.toArray().map((function(e){return o.computeSourceURL(s,e,r)}));this.sourceRoot=s;this.sourcesContent=l;this._mappings=c;this._sourceMapURL=r;this.file=p}BasicSourceMapConsumer.prototype=Object.create(SourceMapConsumer.prototype);BasicSourceMapConsumer.prototype.consumer=SourceMapConsumer;BasicSourceMapConsumer.prototype._findSourceIndex=function(e){var r=e;if(this.sourceRoot!=null){r=o.relative(this.sourceRoot,r)}if(this._sources.has(r)){return this._sources.indexOf(r)}var n;for(n=0;n1){v.source=l+_[1];l+=_[1];v.originalLine=i+_[2];i=v.originalLine;v.originalLine+=1;v.originalColumn=a+_[3];a=v.originalColumn;if(_.length>4){v.name=c+_[4];c+=_[4]}}m.push(v);if(typeof v.originalLine==="number"){d.push(v)}}}s(m,o.compareByGeneratedPositionsDeflated);this.__generatedMappings=m;s(d,o.compareByOriginalPositions);this.__originalMappings=d};BasicSourceMapConsumer.prototype._findMapping=function SourceMapConsumer_findMapping(e,r,n,t,o,a){if(e[n]<=0){throw new TypeError("Line must be greater than or equal to 1, got "+e[n])}if(e[t]<0){throw new TypeError("Column must be greater than or equal to 0, got "+e[t])}return i.search(e,r,o,a)};BasicSourceMapConsumer.prototype.computeColumnSpans=function SourceMapConsumer_computeColumnSpans(){for(var e=0;e=0){var t=this._generatedMappings[n];if(t.generatedLine===r.generatedLine){var i=o.getArg(t,"source",null);if(i!==null){i=this._sources.at(i);i=o.computeSourceURL(this.sourceRoot,i,this._sourceMapURL)}var a=o.getArg(t,"name",null);if(a!==null){a=this._names.at(a)}return{source:i,line:o.getArg(t,"originalLine",null),column:o.getArg(t,"originalColumn",null),name:a}}}return{source:null,line:null,column:null,name:null}};BasicSourceMapConsumer.prototype.hasContentsOfAllSources=function BasicSourceMapConsumer_hasContentsOfAllSources(){if(!this.sourcesContent){return false}return this.sourcesContent.length>=this._sources.size()&&!this.sourcesContent.some((function(e){return e==null}))};BasicSourceMapConsumer.prototype.sourceContentFor=function SourceMapConsumer_sourceContentFor(e,r){if(!this.sourcesContent){return null}var n=this._findSourceIndex(e);if(n>=0){return this.sourcesContent[n]}var t=e;if(this.sourceRoot!=null){t=o.relative(this.sourceRoot,t)}var i;if(this.sourceRoot!=null&&(i=o.urlParse(this.sourceRoot))){var a=t.replace(/^file:\/\//,"");if(i.scheme=="file"&&this._sources.has(a)){return this.sourcesContent[this._sources.indexOf(a)]}if((!i.path||i.path=="/")&&this._sources.has("/"+t)){return this.sourcesContent[this._sources.indexOf("/"+t)]}}if(r){return null}else{throw new Error('"'+t+'" is not in the SourceMap.')}};BasicSourceMapConsumer.prototype.generatedPositionFor=function SourceMapConsumer_generatedPositionFor(e){var r=o.getArg(e,"source");r=this._findSourceIndex(r);if(r<0){return{line:null,column:null,lastColumn:null}}var n={source:r,originalLine:o.getArg(e,"line"),originalColumn:o.getArg(e,"column")};var t=this._findMapping(n,this._originalMappings,"originalLine","originalColumn",o.compareByOriginalPositions,o.getArg(e,"bias",SourceMapConsumer.GREATEST_LOWER_BOUND));if(t>=0){var i=this._originalMappings[t];if(i.source===n.source){return{line:o.getArg(i,"generatedLine",null),column:o.getArg(i,"generatedColumn",null),lastColumn:o.getArg(i,"lastGeneratedColumn",null)}}}return{line:null,column:null,lastColumn:null}};t=BasicSourceMapConsumer;function IndexedSourceMapConsumer(e,r){var n=e;if(typeof e==="string"){n=o.parseSourceMapInput(e)}var t=o.getArg(n,"version");var i=o.getArg(n,"sections");if(t!=this._version){throw new Error("Unsupported version: "+t)}this._sources=new a;this._names=new a;var u={line:-1,column:0};this._sections=i.map((function(e){if(e.url){throw new Error("Support for url field in sections not implemented.")}var n=o.getArg(e,"offset");var t=o.getArg(n,"line");var i=o.getArg(n,"column");if(t{var t=n(449);var o=n(339);var i=n(274).I;var a=n(680).H;function SourceMapGenerator(e){if(!e){e={}}this._file=o.getArg(e,"file",null);this._sourceRoot=o.getArg(e,"sourceRoot",null);this._skipValidation=o.getArg(e,"skipValidation",false);this._sources=new i;this._names=new i;this._mappings=new a;this._sourcesContents=null}SourceMapGenerator.prototype._version=3;SourceMapGenerator.fromSourceMap=function SourceMapGenerator_fromSourceMap(e){var r=e.sourceRoot;var n=new SourceMapGenerator({file:e.file,sourceRoot:r});e.eachMapping((function(e){var t={generated:{line:e.generatedLine,column:e.generatedColumn}};if(e.source!=null){t.source=e.source;if(r!=null){t.source=o.relative(r,t.source)}t.original={line:e.originalLine,column:e.originalColumn};if(e.name!=null){t.name=e.name}}n.addMapping(t)}));e.sources.forEach((function(t){var i=t;if(r!==null){i=o.relative(r,t)}if(!n._sources.has(i)){n._sources.add(i)}var a=e.sourceContentFor(t);if(a!=null){n.setSourceContent(t,a)}}));return n};SourceMapGenerator.prototype.addMapping=function SourceMapGenerator_addMapping(e){var r=o.getArg(e,"generated");var n=o.getArg(e,"original",null);var t=o.getArg(e,"source",null);var i=o.getArg(e,"name",null);if(!this._skipValidation){this._validateMapping(r,n,t,i)}if(t!=null){t=String(t);if(!this._sources.has(t)){this._sources.add(t)}}if(i!=null){i=String(i);if(!this._names.has(i)){this._names.add(i)}}this._mappings.add({generatedLine:r.line,generatedColumn:r.column,originalLine:n!=null&&n.line,originalColumn:n!=null&&n.column,source:t,name:i})};SourceMapGenerator.prototype.setSourceContent=function SourceMapGenerator_setSourceContent(e,r){var n=e;if(this._sourceRoot!=null){n=o.relative(this._sourceRoot,n)}if(r!=null){if(!this._sourcesContents){this._sourcesContents=Object.create(null)}this._sourcesContents[o.toSetString(n)]=r}else if(this._sourcesContents){delete this._sourcesContents[o.toSetString(n)];if(Object.keys(this._sourcesContents).length===0){this._sourcesContents=null}}};SourceMapGenerator.prototype.applySourceMap=function SourceMapGenerator_applySourceMap(e,r,n){var t=r;if(r==null){if(e.file==null){throw new Error("SourceMapGenerator.prototype.applySourceMap requires either an explicit source file, "+'or the source map\'s "file" property. Both were omitted.')}t=e.file}var a=this._sourceRoot;if(a!=null){t=o.relative(a,t)}var u=new i;var s=new i;this._mappings.unsortedForEach((function(r){if(r.source===t&&r.originalLine!=null){var i=e.originalPositionFor({line:r.originalLine,column:r.originalColumn});if(i.source!=null){r.source=i.source;if(n!=null){r.source=o.join(n,r.source)}if(a!=null){r.source=o.relative(a,r.source)}r.originalLine=i.line;r.originalColumn=i.column;if(i.name!=null){r.name=i.name}}}var l=r.source;if(l!=null&&!u.has(l)){u.add(l)}var c=r.name;if(c!=null&&!s.has(c)){s.add(c)}}),this);this._sources=u;this._names=s;e.sources.forEach((function(r){var t=e.sourceContentFor(r);if(t!=null){if(n!=null){r=o.join(n,r)}if(a!=null){r=o.relative(a,r)}this.setSourceContent(r,t)}}),this)};SourceMapGenerator.prototype._validateMapping=function SourceMapGenerator_validateMapping(e,r,n,t){if(r&&typeof r.line!=="number"&&typeof r.column!=="number"){throw new Error("original.line and original.column are not numbers -- you probably meant to omit "+"the original mapping entirely and only map the generated position. If so, pass "+"null for the original mapping instead of an object with empty or null values.")}if(e&&"line"in e&&"column"in e&&e.line>0&&e.column>=0&&!r&&!n&&!t){return}else if(e&&"line"in e&&"column"in e&&r&&"line"in r&&"column"in r&&e.line>0&&e.column>=0&&r.line>0&&r.column>=0&&n){return}else{throw new Error("Invalid mapping: "+JSON.stringify({generated:e,source:n,original:r,name:t}))}};SourceMapGenerator.prototype._serializeMappings=function SourceMapGenerator_serializeMappings(){var e=0;var r=1;var n=0;var i=0;var a=0;var u=0;var s="";var l;var c;var p;var f;var g=this._mappings.toArray();for(var h=0,d=g.length;h0){if(!o.compareByGeneratedPositionsInflated(c,g[h-1])){continue}l+=","}}l+=t.encode(c.generatedColumn-e);e=c.generatedColumn;if(c.source!=null){f=this._sources.indexOf(c.source);l+=t.encode(f-u);u=f;l+=t.encode(c.originalLine-1-i);i=c.originalLine-1;l+=t.encode(c.originalColumn-n);n=c.originalColumn;if(c.name!=null){p=this._names.indexOf(c.name);l+=t.encode(p-a);a=p}}s+=l}return s};SourceMapGenerator.prototype._generateSourcesContent=function SourceMapGenerator_generateSourcesContent(e,r){return e.map((function(e){if(!this._sourcesContents){return null}if(r!=null){e=o.relative(r,e)}var n=o.toSetString(e);return Object.prototype.hasOwnProperty.call(this._sourcesContents,n)?this._sourcesContents[n]:null}),this)};SourceMapGenerator.prototype.toJSON=function SourceMapGenerator_toJSON(){var e={version:this._version,sources:this._sources.toArray(),names:this._names.toArray(),mappings:this._serializeMappings()};if(this._file!=null){e.file=this._file}if(this._sourceRoot!=null){e.sourceRoot=this._sourceRoot}if(this._sourcesContents){e.sourcesContent=this._generateSourcesContent(e.sources,e.sourceRoot)}return e};SourceMapGenerator.prototype.toString=function SourceMapGenerator_toString(){return JSON.stringify(this.toJSON())};r.h=SourceMapGenerator},351:(e,r,n)=>{var t;var o=n(591).h;var i=n(339);var a=/(\r?\n)/;var u=10;var s="$$$isSourceNode$$$";function SourceNode(e,r,n,t,o){this.children=[];this.sourceContents={};this.line=e==null?null:e;this.column=r==null?null:r;this.source=n==null?null:n;this.name=o==null?null:o;this[s]=true;if(t!=null)this.add(t)}SourceNode.fromStringWithSourceMap=function SourceNode_fromStringWithSourceMap(e,r,n){var t=new SourceNode;var o=e.split(a);var u=0;var shiftNextLine=function(){var e=getNextLine();var r=getNextLine()||"";return e+r;function getNextLine(){return u=0;r--){this.prepend(e[r])}}else if(e[s]||typeof e==="string"){this.children.unshift(e)}else{throw new TypeError("Expected a SourceNode, string, or an array of SourceNodes and strings. Got "+e)}return this};SourceNode.prototype.walk=function SourceNode_walk(e){var r;for(var n=0,t=this.children.length;n0){r=[];for(n=0;n{function getArg(e,r,n){if(r in e){return e[r]}else if(arguments.length===3){return n}else{throw new Error('"'+r+'" is a required argument.')}}r.getArg=getArg;var n=/^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.-]*)(?::(\d+))?(.*)$/;var t=/^data:.+\,.+$/;function urlParse(e){var r=e.match(n);if(!r){return null}return{scheme:r[1],auth:r[2],host:r[3],port:r[4],path:r[5]}}r.urlParse=urlParse;function urlGenerate(e){var r="";if(e.scheme){r+=e.scheme+":"}r+="//";if(e.auth){r+=e.auth+"@"}if(e.host){r+=e.host}if(e.port){r+=":"+e.port}if(e.path){r+=e.path}return r}r.urlGenerate=urlGenerate;function normalize(e){var n=e;var t=urlParse(e);if(t){if(!t.path){return e}n=t.path}var o=r.isAbsolute(n);var i=n.split(/\/+/);for(var a,u=0,s=i.length-1;s>=0;s--){a=i[s];if(a==="."){i.splice(s,1)}else if(a===".."){u++}else if(u>0){if(a===""){i.splice(s+1,u);u=0}else{i.splice(s,2);u--}}}n=i.join("/");if(n===""){n=o?"/":"."}if(t){t.path=n;return urlGenerate(t)}return n}r.normalize=normalize;function join(e,r){if(e===""){e="."}if(r===""){r="."}var n=urlParse(r);var o=urlParse(e);if(o){e=o.path||"/"}if(n&&!n.scheme){if(o){n.scheme=o.scheme}return urlGenerate(n)}if(n||r.match(t)){return r}if(o&&!o.host&&!o.path){o.host=r;return urlGenerate(o)}var i=r.charAt(0)==="/"?r:normalize(e.replace(/\/+$/,"")+"/"+r);if(o){o.path=i;return urlGenerate(o)}return i}r.join=join;r.isAbsolute=function(e){return e.charAt(0)==="/"||n.test(e)};function relative(e,r){if(e===""){e="."}e=e.replace(/\/$/,"");var n=0;while(r.indexOf(e+"/")!==0){var t=e.lastIndexOf("/");if(t<0){return r}e=e.slice(0,t);if(e.match(/^([^\/]+:\/)?\/*$/)){return r}++n}return Array(n+1).join("../")+r.substr(e.length+1)}r.relative=relative;var o=function(){var e=Object.create(null);return!("__proto__"in e)}();function identity(e){return e}function toSetString(e){if(isProtoString(e)){return"$"+e}return e}r.toSetString=o?identity:toSetString;function fromSetString(e){if(isProtoString(e)){return e.slice(1)}return e}r.fromSetString=o?identity:fromSetString;function isProtoString(e){if(!e){return false}var r=e.length;if(r<9){return false}if(e.charCodeAt(r-1)!==95||e.charCodeAt(r-2)!==95||e.charCodeAt(r-3)!==111||e.charCodeAt(r-4)!==116||e.charCodeAt(r-5)!==111||e.charCodeAt(r-6)!==114||e.charCodeAt(r-7)!==112||e.charCodeAt(r-8)!==95||e.charCodeAt(r-9)!==95){return false}for(var n=r-10;n>=0;n--){if(e.charCodeAt(n)!==36){return false}}return true}function compareByOriginalPositions(e,r,n){var t=strcmp(e.source,r.source);if(t!==0){return t}t=e.originalLine-r.originalLine;if(t!==0){return t}t=e.originalColumn-r.originalColumn;if(t!==0||n){return t}t=e.generatedColumn-r.generatedColumn;if(t!==0){return t}t=e.generatedLine-r.generatedLine;if(t!==0){return t}return strcmp(e.name,r.name)}r.compareByOriginalPositions=compareByOriginalPositions;function compareByGeneratedPositionsDeflated(e,r,n){var t=e.generatedLine-r.generatedLine;if(t!==0){return t}t=e.generatedColumn-r.generatedColumn;if(t!==0||n){return t}t=strcmp(e.source,r.source);if(t!==0){return t}t=e.originalLine-r.originalLine;if(t!==0){return t}t=e.originalColumn-r.originalColumn;if(t!==0){return t}return strcmp(e.name,r.name)}r.compareByGeneratedPositionsDeflated=compareByGeneratedPositionsDeflated;function strcmp(e,r){if(e===r){return 0}if(e===null){return 1}if(r===null){return-1}if(e>r){return 1}return-1}function compareByGeneratedPositionsInflated(e,r){var n=e.generatedLine-r.generatedLine;if(n!==0){return n}n=e.generatedColumn-r.generatedColumn;if(n!==0){return n}n=strcmp(e.source,r.source);if(n!==0){return n}n=e.originalLine-r.originalLine;if(n!==0){return n}n=e.originalColumn-r.originalColumn;if(n!==0){return n}return strcmp(e.name,r.name)}r.compareByGeneratedPositionsInflated=compareByGeneratedPositionsInflated;function parseSourceMapInput(e){return JSON.parse(e.replace(/^\)]}'[^\n]*\n/,""))}r.parseSourceMapInput=parseSourceMapInput;function computeSourceURL(e,r,n){r=r||"";if(e){if(e[e.length-1]!=="/"&&r[0]!=="/"){e+="/"}r=e+r}if(n){var t=urlParse(n);if(!t){throw new Error("sourceMapURL could not be parsed")}if(t.path){var o=t.path.lastIndexOf("/");if(o>=0){t.path=t.path.substring(0,o+1)}}r=join(urlGenerate(t),r)}return normalize(r)}r.computeSourceURL=computeSourceURL},997:(e,r,n)=>{n(591).h;r.SourceMapConsumer=n(952).SourceMapConsumer;n(351)},284:(e,r,n)=>{e=n.nmd(e);var t=n(997).SourceMapConsumer;var o=n(17);var i;try{i=n(147);if(!i.existsSync||!i.readFileSync){i=null}}catch(e){}var a=n(650);function dynamicRequire(e,r){return e.require(r)}var u=false;var s=false;var l=false;var c="auto";var p={};var f={};var g=/^data:application\/json[^,]+base64,/;var h=[];var d=[];function isInBrowser(){if(c==="browser")return true;if(c==="node")return false;return typeof window!=="undefined"&&typeof XMLHttpRequest==="function"&&!(window.require&&window.module&&window.process&&window.process.type==="renderer")}function hasGlobalProcessEventEmitter(){return typeof process==="object"&&process!==null&&typeof process.on==="function"}function globalProcessVersion(){if(typeof process==="object"&&process!==null){return process.version}else{return""}}function globalProcessStderr(){if(typeof process==="object"&&process!==null){return process.stderr}}function globalProcessExit(e){if(typeof process==="object"&&process!==null&&typeof process.exit==="function"){return process.exit(e)}}function handlerExec(e){return function(r){for(var n=0;n"}var n=this.getLineNumber();if(n!=null){r+=":"+n;var t=this.getColumnNumber();if(t){r+=":"+t}}}var o="";var i=this.getFunctionName();var a=true;var u=this.isConstructor();var s=!(this.isToplevel()||u);if(s){var l=this.getTypeName();if(l==="[object Object]"){l="null"}var c=this.getMethodName();if(i){if(l&&i.indexOf(l)!=0){o+=l+"."}o+=i;if(c&&i.indexOf("."+c)!=i.length-c.length-1){o+=" [as "+c+"]"}}else{o+=l+"."+(c||"")}}else if(u){o+="new "+(i||"")}else if(i){o+=i}else{o+=r;a=false}if(a){o+=" ("+r+")"}return o}function cloneCallSite(e){var r={};Object.getOwnPropertyNames(Object.getPrototypeOf(e)).forEach((function(n){r[n]=/^(?:is|get)/.test(n)?function(){return e[n].call(e)}:e[n]}));r.toString=CallSiteToString;return r}function wrapCallSite(e,r){if(r===undefined){r={nextPosition:null,curPosition:null}}if(e.isNative()){r.curPosition=null;return e}var n=e.getFileName()||e.getScriptNameOrSourceURL();if(n){var t=e.getLineNumber();var o=e.getColumnNumber()-1;var i=/^v(10\.1[6-9]|10\.[2-9][0-9]|10\.[0-9]{3,}|1[2-9]\d*|[2-9]\d|\d{3,}|11\.11)/;var a=i.test(globalProcessVersion())?0:62;if(t===1&&o>a&&!isInBrowser()&&!e.isEval()){o-=a}var u=mapSourcePosition({source:n,line:t,column:o});r.curPosition=u;e=cloneCallSite(e);var s=e.getFunctionName;e.getFunctionName=function(){if(r.nextPosition==null){return s()}return r.nextPosition.name||s()};e.getFileName=function(){return u.source};e.getLineNumber=function(){return u.line};e.getColumnNumber=function(){return u.column+1};e.getScriptNameOrSourceURL=function(){return u.source};return e}var l=e.isEval()&&e.getEvalOrigin();if(l){l=mapEvalOrigin(l);e=cloneCallSite(e);e.getEvalOrigin=function(){return l};return e}return e}function prepareStackTrace(e,r){if(l){p={};f={}}var n=e.name||"Error";var t=e.message||"";var o=n+": "+t;var i={nextPosition:null,curPosition:null};var a=[];for(var u=r.length-1;u>=0;u--){a.push("\n at "+wrapCallSite(r[u],i));i.nextPosition=i.curPosition}i.curPosition=i.nextPosition=null;return o+a.reverse().join("")}function getErrorSource(e){var r=/\n at [^(]+ \((.*):(\d+):(\d+)\)/.exec(e.stack);if(r){var n=r[1];var t=+r[2];var o=+r[3];var a=p[n];if(!a&&i&&i.existsSync(n)){try{a=i.readFileSync(n,"utf8")}catch(e){a=""}}if(a){var u=a.split(/(?:\r\n|\r|\n)/)[t-1];if(u){return n+":"+t+"\n"+u+"\n"+new Array(o).join(" ")+"^"}}}return null}function printErrorAndExit(e){var r=getErrorSource(e);var n=globalProcessStderr();if(n&&n._handle&&n._handle.setBlocking){n._handle.setBlocking(true)}if(r){console.error();console.error(r)}console.error(e.stack);globalProcessExit(1)}function shimEmitUncaughtException(){var e=process.emit;process.emit=function(r){if(r==="uncaughtException"){var n=arguments[1]&&arguments[1].stack;var t=this.listeners(r).length>0;if(n&&!t){return printErrorAndExit(arguments[1])}}return e.apply(this,arguments)}}var S=h.slice(0);var _=d.slice(0);r.wrapCallSite=wrapCallSite;r.getErrorSource=getErrorSource;r.mapSourcePosition=mapSourcePosition;r.retrieveSourceMap=v;r.install=function(r){r=r||{};if(r.environment){c=r.environment;if(["node","browser","auto"].indexOf(c)===-1){throw new Error("environment "+c+" was unknown. Available options are {auto, browser, node}")}}if(r.retrieveFile){if(r.overrideRetrieveFile){h.length=0}h.unshift(r.retrieveFile)}if(r.retrieveSourceMap){if(r.overrideRetrieveSourceMap){d.length=0}d.unshift(r.retrieveSourceMap)}if(r.hookRequire&&!isInBrowser()){var n=dynamicRequire(e,"module");var t=n.prototype._compile;if(!t.__sourceMapSupport){n.prototype._compile=function(e,r){p[r]=e;f[r]=undefined;return t.call(this,e,r)};n.prototype._compile.__sourceMapSupport=true}}if(!l){l="emptyCacheBetweenOperations"in r?r.emptyCacheBetweenOperations:false}if(!u){u=true;Error.prepareStackTrace=prepareStackTrace}if(!s){var o="handleUncaughtExceptions"in r?r.handleUncaughtExceptions:true;try{var i=dynamicRequire(e,"worker_threads");if(i.isMainThread===false){o=false}}catch(e){}if(o&&hasGlobalProcessEventEmitter()){s=true;shimEmitUncaughtException()}}};r.resetRetrieveHandlers=function(){h.length=0;d.length=0;h=S.slice(0);d=_.slice(0);v=handlerExec(d);m=handlerExec(h)}},147:e=>{"use strict";e.exports=require("fs")},17:e=>{"use strict";e.exports=require("path")}};var r={};function __webpack_require__(n){var t=r[n];if(t!==undefined){return t.exports}var o=r[n]={id:n,loaded:false,exports:{}};var i=true;try{e[n](o,o.exports,__webpack_require__);i=false}finally{if(i)delete r[n]}o.loaded=true;return o.exports}(()=>{__webpack_require__.nmd=e=>{e.paths=[];if(!e.children)e.children=[];return e}})();if(typeof __webpack_require__!=="undefined")__webpack_require__.ab=__dirname+"/";var n={};(()=>{__webpack_require__(284).install()})();module.exports=n})(); -------------------------------------------------------------------------------- /lib/comments.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.createComment = exports.deleteComment = exports.updateComment = exports.getExistingComment = void 0; 4 | async function getExistingComment(octokit, owner, repo, issueNumber, messageId) { 5 | const parameters = { 6 | owner, 7 | repo, 8 | issue_number: issueNumber, 9 | per_page: 100, 10 | }; 11 | let found; 12 | for await (const comments of octokit.paginate.iterator(octokit.rest.issues.listComments, parameters)) { 13 | found = comments.data.find(({ body }) => { 14 | var _a; 15 | return ((_a = body === null || body === void 0 ? void 0 : body.search(messageId)) !== null && _a !== void 0 ? _a : -1) > -1; 16 | }); 17 | if (found) { 18 | break; 19 | } 20 | } 21 | if (found) { 22 | const { id, body } = found; 23 | return { id, body }; 24 | } 25 | return; 26 | } 27 | exports.getExistingComment = getExistingComment; 28 | async function updateComment(octokit, owner, repo, existingCommentId, body) { 29 | const updatedComment = await octokit.rest.issues.updateComment({ 30 | comment_id: existingCommentId, 31 | owner, 32 | repo, 33 | body, 34 | }); 35 | return updatedComment.data; 36 | } 37 | exports.updateComment = updateComment; 38 | async function deleteComment(octokit, owner, repo, existingCommentId, body) { 39 | const deletedComment = await octokit.rest.issues.deleteComment({ 40 | comment_id: existingCommentId, 41 | owner, 42 | repo, 43 | body, 44 | }); 45 | return deletedComment.data; 46 | } 47 | exports.deleteComment = deleteComment; 48 | async function createComment(octokit, owner, repo, issueNumber, body) { 49 | const createdComment = await octokit.rest.issues.createComment({ 50 | issue_number: issueNumber, 51 | owner, 52 | repo, 53 | body, 54 | }); 55 | return createdComment.data; 56 | } 57 | exports.createComment = createComment; 58 | -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | var desc = Object.getOwnPropertyDescriptor(m, k); 5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 | desc = { enumerable: true, get: function() { return m[k]; } }; 7 | } 8 | Object.defineProperty(o, k2, desc); 9 | }) : (function(o, m, k, k2) { 10 | if (k2 === undefined) k2 = k; 11 | o[k2] = m[k]; 12 | })); 13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 14 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 15 | }) : function(o, v) { 16 | o["default"] = v; 17 | }); 18 | var __importStar = (this && this.__importStar) || function (mod) { 19 | if (mod && mod.__esModule) return mod; 20 | var result = {}; 21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 22 | __setModuleDefault(result, mod); 23 | return result; 24 | }; 25 | Object.defineProperty(exports, "__esModule", { value: true }); 26 | exports.getInputs = void 0; 27 | const core = __importStar(require("@actions/core")); 28 | const github = __importStar(require("@actions/github")); 29 | async function getInputs() { 30 | var _a, _b; 31 | const messageIdInput = core.getInput('message-id', { required: false }); 32 | const messageId = messageIdInput === '' ? 'add-pr-comment' : `add-pr-comment:${messageIdInput}`; 33 | const messageInput = core.getInput('message', { required: false }); 34 | const messagePath = core.getInput('message-path', { required: false }); 35 | const messageFind = core.getMultilineInput('find', { required: false }); 36 | const messageReplace = core.getMultilineInput('replace', { required: false }); 37 | const repoOwner = core.getInput('repo-owner', { required: true }); 38 | const repoName = core.getInput('repo-name', { required: true }); 39 | const repoToken = core.getInput('repo-token', { required: true }); 40 | const status = core.getInput('status', { required: true }); 41 | const issue = core.getInput('issue', { required: false }); 42 | const proxyUrl = core.getInput('proxy-url', { required: false }).replace(/\/$/, ''); 43 | const allowRepeats = core.getInput('allow-repeats', { required: true }) === 'true'; 44 | const refreshMessagePosition = core.getInput('refresh-message-position', { required: false }) === 'true'; 45 | const updateOnly = core.getInput('update-only', { required: false }) === 'true'; 46 | const preformatted = core.getInput('preformatted', { required: false }) === 'true'; 47 | if (messageInput && messagePath) { 48 | throw new Error('must specify only one, message or message-path'); 49 | } 50 | const messageSuccess = core.getInput(`message-success`); 51 | const messageFailure = core.getInput(`message-failure`); 52 | const messageCancelled = core.getInput(`message-cancelled`); 53 | const messageSkipped = core.getInput(`message-skipped`); 54 | const { payload } = github.context; 55 | return { 56 | allowRepeats, 57 | commitSha: github.context.sha, 58 | issue: issue ? Number(issue) : (_a = payload.issue) === null || _a === void 0 ? void 0 : _a.number, 59 | messageInput, 60 | messageId: ``, 61 | messageSuccess, 62 | messageFailure, 63 | messageCancelled, 64 | messageSkipped, 65 | messagePath, 66 | messageFind, 67 | messageReplace, 68 | preformatted, 69 | proxyUrl, 70 | pullRequestNumber: (_b = payload.pull_request) === null || _b === void 0 ? void 0 : _b.number, 71 | refreshMessagePosition, 72 | repoToken, 73 | status, 74 | owner: repoOwner || payload.repo.owner, 75 | repo: repoName || payload.repo.repo, 76 | updateOnly: updateOnly, 77 | }; 78 | } 79 | exports.getInputs = getInputs; 80 | -------------------------------------------------------------------------------- /lib/files.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | var desc = Object.getOwnPropertyDescriptor(m, k); 5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 | desc = { enumerable: true, get: function() { return m[k]; } }; 7 | } 8 | Object.defineProperty(o, k2, desc); 9 | }) : (function(o, m, k, k2) { 10 | if (k2 === undefined) k2 = k; 11 | o[k2] = m[k]; 12 | })); 13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 14 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 15 | }) : function(o, v) { 16 | o["default"] = v; 17 | }); 18 | var __importStar = (this && this.__importStar) || function (mod) { 19 | if (mod && mod.__esModule) return mod; 20 | var result = {}; 21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 22 | __setModuleDefault(result, mod); 23 | return result; 24 | }; 25 | var __importDefault = (this && this.__importDefault) || function (mod) { 26 | return (mod && mod.__esModule) ? mod : { "default": mod }; 27 | }; 28 | Object.defineProperty(exports, "__esModule", { value: true }); 29 | exports.findFiles = void 0; 30 | const core = __importStar(require("@actions/core")); 31 | const glob = __importStar(require("@actions/glob")); 32 | const promises_1 = __importDefault(require("node:fs/promises")); 33 | async function findFiles(searchPath) { 34 | const searchResults = []; 35 | const globber = await glob.create(searchPath, { 36 | followSymbolicLinks: true, 37 | implicitDescendants: true, 38 | omitBrokenSymbolicLinks: true, 39 | }); 40 | const rawSearchResults = await globber.glob(); 41 | for (const searchResult of rawSearchResults) { 42 | const fileStats = await promises_1.default.stat(searchResult); 43 | if (!fileStats.isDirectory()) { 44 | core.debug(`File: ${searchResult} was found using the provided searchPath`); 45 | searchResults.push(searchResult); 46 | } 47 | else { 48 | core.debug(`Removing ${searchResult} from rawSearchResults because it is a directory`); 49 | } 50 | } 51 | return searchResults; 52 | } 53 | exports.findFiles = findFiles; 54 | -------------------------------------------------------------------------------- /lib/issues.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.getIssueNumberFromCommitPullsList = void 0; 4 | async function getIssueNumberFromCommitPullsList(octokit, owner, repo, commitSha) { 5 | var _a; 6 | const commitPullsList = await octokit.rest.repos.listPullRequestsAssociatedWithCommit({ 7 | owner, 8 | repo, 9 | commit_sha: commitSha, 10 | }); 11 | return commitPullsList.data.length ? (_a = commitPullsList.data) === null || _a === void 0 ? void 0 : _a[0].number : null; 12 | } 13 | exports.getIssueNumberFromCommitPullsList = getIssueNumberFromCommitPullsList; 14 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | var desc = Object.getOwnPropertyDescriptor(m, k); 5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 | desc = { enumerable: true, get: function() { return m[k]; } }; 7 | } 8 | Object.defineProperty(o, k2, desc); 9 | }) : (function(o, m, k, k2) { 10 | if (k2 === undefined) k2 = k; 11 | o[k2] = m[k]; 12 | })); 13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 14 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 15 | }) : function(o, v) { 16 | o["default"] = v; 17 | }); 18 | var __importStar = (this && this.__importStar) || function (mod) { 19 | if (mod && mod.__esModule) return mod; 20 | var result = {}; 21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 22 | __setModuleDefault(result, mod); 23 | return result; 24 | }; 25 | Object.defineProperty(exports, "__esModule", { value: true }); 26 | const core = __importStar(require("@actions/core")); 27 | const github = __importStar(require("@actions/github")); 28 | const comments_1 = require("./comments"); 29 | const config_1 = require("./config"); 30 | const issues_1 = require("./issues"); 31 | const message_1 = require("./message"); 32 | const proxy_1 = require("./proxy"); 33 | const run = async () => { 34 | try { 35 | const { allowRepeats, messagePath, messageInput, messageId, refreshMessagePosition, repoToken, proxyUrl, issue, pullRequestNumber, commitSha, repo, owner, updateOnly, messageCancelled, messageFailure, messageSuccess, messageSkipped, preformatted, status, messageFind, messageReplace, } = await (0, config_1.getInputs)(); 36 | const octokit = github.getOctokit(repoToken); 37 | let message = await (0, message_1.getMessage)({ 38 | messagePath, 39 | messageInput, 40 | messageSkipped, 41 | messageCancelled, 42 | messageSuccess, 43 | messageFailure, 44 | preformatted, 45 | status, 46 | }); 47 | let issueNumber; 48 | if (issue) { 49 | issueNumber = issue; 50 | } 51 | else if (pullRequestNumber) { 52 | issueNumber = pullRequestNumber; 53 | } 54 | else { 55 | // If this is not a pull request, attempt to find a PR matching the sha 56 | issueNumber = await (0, issues_1.getIssueNumberFromCommitPullsList)(octokit, owner, repo, commitSha); 57 | } 58 | if (!issueNumber) { 59 | core.info('no issue number found, use a pull_request event, a pull event, or provide an issue input'); 60 | core.setOutput('comment-created', 'false'); 61 | return; 62 | } 63 | let existingComment; 64 | if (!allowRepeats) { 65 | core.debug('repeat comments are disallowed, checking for existing'); 66 | existingComment = await (0, comments_1.getExistingComment)(octokit, owner, repo, issueNumber, messageId); 67 | if (existingComment) { 68 | core.debug(`existing comment found with id: ${existingComment.id}`); 69 | } 70 | } 71 | // if no existing comment and updateOnly is true, exit 72 | if (!existingComment && updateOnly) { 73 | core.info('no existing comment found and update-only is true, exiting'); 74 | core.setOutput('comment-created', 'false'); 75 | return; 76 | } 77 | let comment; 78 | if ((messageFind === null || messageFind === void 0 ? void 0 : messageFind.length) && ((messageReplace === null || messageReplace === void 0 ? void 0 : messageReplace.length) || message) && (existingComment === null || existingComment === void 0 ? void 0 : existingComment.body)) { 79 | message = (0, message_1.findAndReplaceInMessage)(messageFind, (messageReplace === null || messageReplace === void 0 ? void 0 : messageReplace.length) ? messageReplace : [message], (0, message_1.removeMessageHeader)(existingComment.body)); 80 | } 81 | if (!message) { 82 | throw new Error('no message, check your message inputs'); 83 | } 84 | const body = (0, message_1.addMessageHeader)(messageId, message); 85 | if (proxyUrl) { 86 | comment = await (0, proxy_1.createCommentProxy)({ 87 | commentId: existingComment === null || existingComment === void 0 ? void 0 : existingComment.id, 88 | owner, 89 | repo, 90 | issueNumber, 91 | body, 92 | repoToken, 93 | proxyUrl, 94 | }); 95 | core.setOutput((existingComment === null || existingComment === void 0 ? void 0 : existingComment.id) ? 'comment-updated' : 'comment-created', 'true'); 96 | } 97 | else if (existingComment === null || existingComment === void 0 ? void 0 : existingComment.id) { 98 | if (refreshMessagePosition) { 99 | await (0, comments_1.deleteComment)(octokit, owner, repo, existingComment.id, body); 100 | comment = await (0, comments_1.createComment)(octokit, owner, repo, issueNumber, body); 101 | } 102 | else { 103 | comment = await (0, comments_1.updateComment)(octokit, owner, repo, existingComment.id, body); 104 | } 105 | core.setOutput('comment-updated', 'true'); 106 | } 107 | else { 108 | comment = await (0, comments_1.createComment)(octokit, owner, repo, issueNumber, body); 109 | core.setOutput('comment-created', 'true'); 110 | } 111 | if (comment) { 112 | core.setOutput('comment-id', comment.id); 113 | } 114 | else { 115 | core.setOutput('comment-created', 'false'); 116 | core.setOutput('comment-updated', 'false'); 117 | } 118 | } 119 | catch (err) { 120 | if (process.env.NODE_ENV === 'test') { 121 | // eslint-disable-next-line no-console 122 | console.log(err); 123 | } 124 | if (err instanceof Error) { 125 | core.setFailed(err.message); 126 | } 127 | } 128 | }; 129 | // Don't auto-execute in the test environment 130 | if (process.env['NODE_ENV'] !== 'test') { 131 | run(); 132 | } 133 | exports.default = run; 134 | -------------------------------------------------------------------------------- /lib/message.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.findAndReplaceInMessage = exports.removeMessageHeader = exports.addMessageHeader = exports.getMessageFromPath = exports.getMessage = void 0; 7 | const promises_1 = __importDefault(require("node:fs/promises")); 8 | const files_1 = require("./files"); 9 | async function getMessage({ messageInput, messagePath, messageCancelled, messageSkipped, messageFailure, messageSuccess, preformatted, status, }) { 10 | let message; 11 | if (status === 'success' && messageSuccess) { 12 | message = messageSuccess; 13 | } 14 | if (status === 'failure' && messageFailure) { 15 | message = messageFailure; 16 | } 17 | if (status === 'cancelled' && messageCancelled) { 18 | message = messageCancelled; 19 | } 20 | if (status === 'skipped' && messageSkipped) { 21 | message = messageSkipped; 22 | } 23 | if (!message) { 24 | if (messagePath) { 25 | message = await getMessageFromPath(messagePath); 26 | } 27 | else { 28 | message = messageInput; 29 | } 30 | } 31 | if (preformatted) { 32 | message = `\`\`\`\n${message}\n\`\`\``; 33 | } 34 | return message !== null && message !== void 0 ? message : ''; 35 | } 36 | exports.getMessage = getMessage; 37 | async function getMessageFromPath(searchPath) { 38 | let message = ''; 39 | const files = await (0, files_1.findFiles)(searchPath); 40 | for (const [index, path] of files.entries()) { 41 | if (index > 0) { 42 | message += '\n'; 43 | } 44 | message += await promises_1.default.readFile(path, { encoding: 'utf8' }); 45 | } 46 | return message; 47 | } 48 | exports.getMessageFromPath = getMessageFromPath; 49 | function addMessageHeader(messageId, message) { 50 | return `${messageId}\n\n${message}`; 51 | } 52 | exports.addMessageHeader = addMessageHeader; 53 | function removeMessageHeader(message) { 54 | return message.split('\n').slice(2).join('\n'); 55 | } 56 | exports.removeMessageHeader = removeMessageHeader; 57 | function splitFind(find) { 58 | const matches = find.match(/\/((i|g|m|s|u|y){1,6})$/); 59 | if (!matches) { 60 | return { 61 | regExp: find, 62 | modifiers: 'gi', 63 | }; 64 | } 65 | const [, modifiers] = matches; 66 | const regExp = find.replace(modifiers, '').slice(0, -1); 67 | return { 68 | regExp, 69 | modifiers, 70 | }; 71 | } 72 | function findAndReplaceInMessage(find, replace, original) { 73 | var _a; 74 | let message = original; 75 | for (const [i, f] of find.entries()) { 76 | const { regExp, modifiers } = splitFind(f); 77 | message = message.replace(new RegExp(regExp, modifiers), (_a = replace[i]) !== null && _a !== void 0 ? _a : replace.join('\n')); 78 | } 79 | return message; 80 | } 81 | exports.findAndReplaceInMessage = findAndReplaceInMessage; 82 | -------------------------------------------------------------------------------- /lib/proxy.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.createCommentProxy = void 0; 4 | const http_client_1 = require("@actions/http-client"); 5 | async function createCommentProxy(params) { 6 | const { repoToken, owner, repo, issueNumber, body, commentId, proxyUrl } = params; 7 | const http = new http_client_1.HttpClient('http-client-add-pr-comment'); 8 | const response = await http.postJson(`${proxyUrl}/repos/${owner}/${repo}/issues/${issueNumber}/comments`, { comment_id: commentId, body }, { 9 | ['temporary-github-token']: repoToken, 10 | }); 11 | return response.result; 12 | } 13 | exports.createCommentProxy = createCommentProxy; 14 | -------------------------------------------------------------------------------- /lib/types.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | var desc = Object.getOwnPropertyDescriptor(m, k); 5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 | desc = { enumerable: true, get: function() { return m[k]; } }; 7 | } 8 | Object.defineProperty(o, k2, desc); 9 | }) : (function(o, m, k, k2) { 10 | if (k2 === undefined) k2 = k; 11 | o[k2] = m[k]; 12 | })); 13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 14 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 15 | }) : function(o, v) { 16 | o["default"] = v; 17 | }); 18 | var __importStar = (this && this.__importStar) || function (mod) { 19 | if (mod && mod.__esModule) return mod; 20 | var result = {}; 21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 22 | __setModuleDefault(result, mod); 23 | return result; 24 | }; 25 | var __importDefault = (this && this.__importDefault) || function (mod) { 26 | return (mod && mod.__esModule) ? mod : { "default": mod }; 27 | }; 28 | Object.defineProperty(exports, "__esModule", { value: true }); 29 | exports.findFiles = exports.getMessageFromPaths = void 0; 30 | const core = __importStar(require("@actions/core")); 31 | const glob = __importStar(require("@actions/glob")); 32 | const promises_1 = __importDefault(require("node:fs/promises")); 33 | async function getMessageFromPaths(searchPath) { 34 | let message = ''; 35 | const files = await findFiles(searchPath); 36 | for (const [index, path] of files.entries()) { 37 | if (index > 0) { 38 | message += '\n'; 39 | } 40 | message += await promises_1.default.readFile(path, { encoding: 'utf8' }); 41 | } 42 | return message; 43 | } 44 | exports.getMessageFromPaths = getMessageFromPaths; 45 | function getDefaultGlobOptions() { 46 | return { 47 | followSymbolicLinks: true, 48 | implicitDescendants: true, 49 | omitBrokenSymbolicLinks: true, 50 | }; 51 | } 52 | async function findFiles(searchPath, globOptions) { 53 | const searchResults = []; 54 | const globber = await glob.create(searchPath, globOptions || getDefaultGlobOptions()); 55 | const rawSearchResults = await globber.glob(); 56 | for (const searchResult of rawSearchResults) { 57 | const fileStats = await promises_1.default.stat(searchResult); 58 | if (!fileStats.isDirectory()) { 59 | core.debug(`File: ${searchResult} was found using the provided searchPath`); 60 | searchResults.push(searchResult); 61 | } 62 | else { 63 | core.debug(`Removing ${searchResult} from rawSearchResults because it is a directory`); 64 | } 65 | } 66 | return searchResults; 67 | } 68 | exports.findFiles = findFiles; 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mshick/add-pr-comment", 3 | "version": "2.8.2", 4 | "description": "A GitHub Action which adds a comment to a Pull Request Issue.", 5 | "keywords": [ 6 | "GitHub", 7 | "Actions", 8 | "javascript", 9 | "pull request", 10 | "comments", 11 | "pr" 12 | ], 13 | "homepage": "https://github.com/mshick/add-pr-comment#readme", 14 | "bugs": { 15 | "url": "https://github.com/mshick/add-pr-comment/issues" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/mshick/add-pr-comment.git" 20 | }, 21 | "license": "MIT", 22 | "author": "Michael Shick ", 23 | "main": "lib/main.js", 24 | "scripts": { 25 | "build": "del-cli dist && tsc && ncc build --source-map", 26 | "clean": "rm -rf node_modules dist package-lock.json __tests__/runner/**/*", 27 | "lint": "eslint src/**/*.ts", 28 | "prepare": "npm run build && git add lib dist", 29 | "release": "npm run build && np --no-publish", 30 | "test": "vitest run", 31 | "watch": "vitest" 32 | }, 33 | "prettier": { 34 | "bracketSpacing": true, 35 | "jsxSingleQuote": true, 36 | "printWidth": 100, 37 | "semi": false, 38 | "singleQuote": true, 39 | "tabWidth": 2, 40 | "trailingComma": "all" 41 | }, 42 | "eslintConfig": { 43 | "env": { 44 | "node": true 45 | }, 46 | "settings": { 47 | "import/resolver": { 48 | "typescript": {} 49 | } 50 | }, 51 | "extends": [ 52 | "eslint:recommended", 53 | "plugin:prettier/recommended" 54 | ], 55 | "rules": { 56 | "capitalized-comments": "off", 57 | "no-console": "error", 58 | "no-unreachable": "error" 59 | }, 60 | "overrides": [ 61 | { 62 | "files": [ 63 | "**/*.ts" 64 | ], 65 | "parser": "@typescript-eslint/parser", 66 | "parserOptions": { 67 | "ecmaVersion": "latest", 68 | "sourceType": "module" 69 | }, 70 | "extends": [ 71 | "plugin:@typescript-eslint/recommended" 72 | ], 73 | "rules": { 74 | "@typescript-eslint/no-explicit-any": "off" 75 | } 76 | }, 77 | { 78 | "files": [ 79 | "**/*.test.ts" 80 | ], 81 | "parser": "@typescript-eslint/parser", 82 | "parserOptions": { 83 | "ecmaVersion": "latest", 84 | "sourceType": "module" 85 | }, 86 | "extends": [ 87 | "plugin:@typescript-eslint/recommended" 88 | ], 89 | "rules": { 90 | "@typescript-eslint/no-empty-function": "off", 91 | "@typescript-eslint/no-explicit-any": "off" 92 | } 93 | }, 94 | { 95 | "files": [ 96 | "*.json" 97 | ], 98 | "plugins": [ 99 | "json-format" 100 | ] 101 | }, 102 | { 103 | "files": [ 104 | "*.mdx", 105 | "*.md" 106 | ], 107 | "settings": { 108 | "mdx/code-blocks": false, 109 | "mdx/language-mapper": {} 110 | }, 111 | "extends": "plugin:mdx/recommended", 112 | "rules": { 113 | "indent": "off" 114 | } 115 | } 116 | ] 117 | }, 118 | "eslintIgnore": [ 119 | "tsconfig.json", 120 | "dist" 121 | ], 122 | "dependencies": { 123 | "@actions/artifact": "^1.1.1", 124 | "@actions/core": "^1.10.0", 125 | "@actions/github": "^5.1.1", 126 | "@actions/glob": "^0.4.0", 127 | "@actions/http-client": "^2.1.0" 128 | }, 129 | "devDependencies": { 130 | "@octokit/types": "^9.1.2", 131 | "@types/node": "^18.15.13", 132 | "@typescript-eslint/eslint-plugin": "^5.59.0", 133 | "@typescript-eslint/parser": "^5.59.0", 134 | "@vercel/ncc": "^0.36.1", 135 | "del-cli": "^5.0.0", 136 | "eslint": "^8.39.0", 137 | "eslint-config-prettier": "^8.8.0", 138 | "eslint-import-resolver-typescript": "^3.5.5", 139 | "eslint-plugin-import": "^2.27.5", 140 | "eslint-plugin-json-format": "^2.0.1", 141 | "eslint-plugin-mdx": "^2.0.5", 142 | "eslint-plugin-prettier": "^4.2.1", 143 | "msw": "^1.2.1", 144 | "nock": "^13.3.0", 145 | "np": "^7.7.0", 146 | "prettier": "^2.8.7", 147 | "typescript": "^5.0.4", 148 | "vitest": "^0.30.1" 149 | }, 150 | "engines": { 151 | "node": "^16.13.0 || ^18.0.0 || ^20.0.0" 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/comments.ts: -------------------------------------------------------------------------------- 1 | import { GitHub } from '@actions/github/lib/utils' 2 | import { 3 | CreateIssueCommentResponseData, 4 | ExistingIssueComment, 5 | ExistingIssueCommentResponseData, 6 | } from './types' 7 | 8 | export async function getExistingComment( 9 | octokit: InstanceType, 10 | owner: string, 11 | repo: string, 12 | issueNumber: number, 13 | messageId: string, 14 | ): Promise { 15 | const parameters = { 16 | owner, 17 | repo, 18 | issue_number: issueNumber, 19 | per_page: 100, 20 | } 21 | 22 | let found: ExistingIssueCommentResponseData | undefined 23 | 24 | for await (const comments of octokit.paginate.iterator( 25 | octokit.rest.issues.listComments, 26 | parameters, 27 | )) { 28 | found = comments.data.find(({ body }) => { 29 | return (body?.search(messageId) ?? -1) > -1 30 | }) 31 | 32 | if (found) { 33 | break 34 | } 35 | } 36 | 37 | if (found) { 38 | const { id, body } = found 39 | return { id, body } 40 | } 41 | 42 | return 43 | } 44 | 45 | export async function updateComment( 46 | octokit: InstanceType, 47 | owner: string, 48 | repo: string, 49 | existingCommentId: number, 50 | body: string, 51 | ): Promise { 52 | const updatedComment = await octokit.rest.issues.updateComment({ 53 | comment_id: existingCommentId, 54 | owner, 55 | repo, 56 | body, 57 | }) 58 | 59 | return updatedComment.data 60 | } 61 | 62 | export async function deleteComment( 63 | octokit: InstanceType, 64 | owner: string, 65 | repo: string, 66 | existingCommentId: number, 67 | body: string, 68 | ): Promise { 69 | const deletedComment = await octokit.rest.issues.deleteComment({ 70 | comment_id: existingCommentId, 71 | owner, 72 | repo, 73 | body, 74 | }) 75 | 76 | return deletedComment.data 77 | } 78 | 79 | export async function createComment( 80 | octokit: InstanceType, 81 | owner: string, 82 | repo: string, 83 | issueNumber: number, 84 | body: string, 85 | ): Promise { 86 | const createdComment = await octokit.rest.issues.createComment({ 87 | issue_number: issueNumber, 88 | owner, 89 | repo, 90 | body, 91 | }) 92 | 93 | return createdComment.data 94 | } 95 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core' 2 | import * as github from '@actions/github' 3 | import { Inputs } from './types' 4 | 5 | export async function getInputs(): Promise { 6 | const messageIdInput = core.getInput('message-id', { required: false }) 7 | const messageId = messageIdInput === '' ? 'add-pr-comment' : `add-pr-comment:${messageIdInput}` 8 | const messageInput = core.getInput('message', { required: false }) 9 | const messagePath = core.getInput('message-path', { required: false }) 10 | const messageFind = core.getMultilineInput('find', { required: false }) 11 | const messageReplace = core.getMultilineInput('replace', { required: false }) 12 | const repoOwner = core.getInput('repo-owner', { required: true }) 13 | const repoName = core.getInput('repo-name', { required: true }) 14 | const repoToken = core.getInput('repo-token', { required: true }) 15 | const status = core.getInput('status', { required: true }) 16 | const issue = core.getInput('issue', { required: false }) 17 | const proxyUrl = core.getInput('proxy-url', { required: false }).replace(/\/$/, '') 18 | const allowRepeats = core.getInput('allow-repeats', { required: true }) === 'true' 19 | const refreshMessagePosition = 20 | core.getInput('refresh-message-position', { required: false }) === 'true' 21 | const updateOnly = core.getInput('update-only', { required: false }) === 'true' 22 | const preformatted = core.getInput('preformatted', { required: false }) === 'true' 23 | 24 | if (messageInput && messagePath) { 25 | throw new Error('must specify only one, message or message-path') 26 | } 27 | 28 | const messageSuccess = core.getInput(`message-success`) 29 | const messageFailure = core.getInput(`message-failure`) 30 | const messageCancelled = core.getInput(`message-cancelled`) 31 | const messageSkipped = core.getInput(`message-skipped`) 32 | 33 | const { payload } = github.context 34 | 35 | return { 36 | allowRepeats, 37 | commitSha: github.context.sha, 38 | issue: issue ? Number(issue) : payload.issue?.number, 39 | messageInput, 40 | messageId: ``, 41 | messageSuccess, 42 | messageFailure, 43 | messageCancelled, 44 | messageSkipped, 45 | messagePath, 46 | messageFind, 47 | messageReplace, 48 | preformatted, 49 | proxyUrl, 50 | pullRequestNumber: payload.pull_request?.number, 51 | refreshMessagePosition, 52 | repoToken, 53 | status, 54 | owner: repoOwner || payload.repo.owner, 55 | repo: repoName || payload.repo.repo, 56 | updateOnly: updateOnly, 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/files.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core' 2 | import * as glob from '@actions/glob' 3 | import fs from 'node:fs/promises' 4 | 5 | export async function findFiles(searchPath: string): Promise { 6 | const searchResults: string[] = [] 7 | const globber = await glob.create(searchPath, { 8 | followSymbolicLinks: true, 9 | implicitDescendants: true, 10 | omitBrokenSymbolicLinks: true, 11 | }) 12 | const rawSearchResults: string[] = await globber.glob() 13 | 14 | for (const searchResult of rawSearchResults) { 15 | const fileStats = await fs.stat(searchResult) 16 | if (!fileStats.isDirectory()) { 17 | core.debug(`File: ${searchResult} was found using the provided searchPath`) 18 | searchResults.push(searchResult) 19 | } else { 20 | core.debug(`Removing ${searchResult} from rawSearchResults because it is a directory`) 21 | } 22 | } 23 | 24 | return searchResults 25 | } 26 | -------------------------------------------------------------------------------- /src/issues.ts: -------------------------------------------------------------------------------- 1 | import { GitHub } from '@actions/github/lib/utils' 2 | 3 | export async function getIssueNumberFromCommitPullsList( 4 | octokit: InstanceType, 5 | owner: string, 6 | repo: string, 7 | commitSha: string, 8 | ): Promise { 9 | const commitPullsList = await octokit.rest.repos.listPullRequestsAssociatedWithCommit({ 10 | owner, 11 | repo, 12 | commit_sha: commitSha, 13 | }) 14 | 15 | return commitPullsList.data.length ? commitPullsList.data?.[0].number : null 16 | } 17 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core' 2 | import * as github from '@actions/github' 3 | import { createComment, deleteComment, getExistingComment, updateComment } from './comments' 4 | import { getInputs } from './config' 5 | import { getIssueNumberFromCommitPullsList } from './issues' 6 | import { 7 | addMessageHeader, 8 | findAndReplaceInMessage, 9 | getMessage, 10 | removeMessageHeader, 11 | } from './message' 12 | import { createCommentProxy } from './proxy' 13 | import { CreateIssueCommentResponseData, ExistingIssueComment } from './types' 14 | 15 | const run = async (): Promise => { 16 | try { 17 | const { 18 | allowRepeats, 19 | messagePath, 20 | messageInput, 21 | messageId, 22 | refreshMessagePosition, 23 | repoToken, 24 | proxyUrl, 25 | issue, 26 | pullRequestNumber, 27 | commitSha, 28 | repo, 29 | owner, 30 | updateOnly, 31 | messageCancelled, 32 | messageFailure, 33 | messageSuccess, 34 | messageSkipped, 35 | preformatted, 36 | status, 37 | messageFind, 38 | messageReplace, 39 | } = await getInputs() 40 | 41 | const octokit = github.getOctokit(repoToken) 42 | 43 | let message = await getMessage({ 44 | messagePath, 45 | messageInput, 46 | messageSkipped, 47 | messageCancelled, 48 | messageSuccess, 49 | messageFailure, 50 | preformatted, 51 | status, 52 | }) 53 | 54 | let issueNumber 55 | 56 | if (issue) { 57 | issueNumber = issue 58 | } else if (pullRequestNumber) { 59 | issueNumber = pullRequestNumber 60 | } else { 61 | // If this is not a pull request, attempt to find a PR matching the sha 62 | issueNumber = await getIssueNumberFromCommitPullsList(octokit, owner, repo, commitSha) 63 | } 64 | 65 | if (!issueNumber) { 66 | core.info( 67 | 'no issue number found, use a pull_request event, a pull event, or provide an issue input', 68 | ) 69 | core.setOutput('comment-created', 'false') 70 | return 71 | } 72 | 73 | let existingComment: ExistingIssueComment | undefined 74 | 75 | if (!allowRepeats) { 76 | core.debug('repeat comments are disallowed, checking for existing') 77 | 78 | existingComment = await getExistingComment(octokit, owner, repo, issueNumber, messageId) 79 | 80 | if (existingComment) { 81 | core.debug(`existing comment found with id: ${existingComment.id}`) 82 | } 83 | } 84 | 85 | // if no existing comment and updateOnly is true, exit 86 | if (!existingComment && updateOnly) { 87 | core.info('no existing comment found and update-only is true, exiting') 88 | core.setOutput('comment-created', 'false') 89 | return 90 | } 91 | 92 | let comment: CreateIssueCommentResponseData | null | undefined 93 | 94 | if (messageFind?.length && (messageReplace?.length || message) && existingComment?.body) { 95 | message = findAndReplaceInMessage( 96 | messageFind, 97 | messageReplace?.length ? messageReplace : [message], 98 | removeMessageHeader(existingComment.body), 99 | ) 100 | } 101 | 102 | if (!message) { 103 | throw new Error('no message, check your message inputs') 104 | } 105 | 106 | const body = addMessageHeader(messageId, message) 107 | 108 | if (proxyUrl) { 109 | comment = await createCommentProxy({ 110 | commentId: existingComment?.id, 111 | owner, 112 | repo, 113 | issueNumber, 114 | body, 115 | repoToken, 116 | proxyUrl, 117 | }) 118 | core.setOutput(existingComment?.id ? 'comment-updated' : 'comment-created', 'true') 119 | } else if (existingComment?.id) { 120 | if (refreshMessagePosition) { 121 | await deleteComment(octokit, owner, repo, existingComment.id, body) 122 | comment = await createComment(octokit, owner, repo, issueNumber, body) 123 | } else { 124 | comment = await updateComment(octokit, owner, repo, existingComment.id, body) 125 | } 126 | core.setOutput('comment-updated', 'true') 127 | } else { 128 | comment = await createComment(octokit, owner, repo, issueNumber, body) 129 | core.setOutput('comment-created', 'true') 130 | } 131 | 132 | if (comment) { 133 | core.setOutput('comment-id', comment.id) 134 | } else { 135 | core.setOutput('comment-created', 'false') 136 | core.setOutput('comment-updated', 'false') 137 | } 138 | } catch (err) { 139 | if (process.env.NODE_ENV === 'test') { 140 | // eslint-disable-next-line no-console 141 | console.log(err) 142 | } 143 | 144 | if (err instanceof Error) { 145 | core.setFailed(err.message) 146 | } 147 | } 148 | } 149 | 150 | // Don't auto-execute in the test environment 151 | if (process.env['NODE_ENV'] !== 'test') { 152 | run() 153 | } 154 | 155 | export default run 156 | -------------------------------------------------------------------------------- /src/message.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs/promises' 2 | import { findFiles } from './files' 3 | import { Inputs } from './types' 4 | 5 | export async function getMessage({ 6 | messageInput, 7 | messagePath, 8 | messageCancelled, 9 | messageSkipped, 10 | messageFailure, 11 | messageSuccess, 12 | preformatted, 13 | status, 14 | }: Pick< 15 | Inputs, 16 | | 'messageInput' 17 | | 'messageCancelled' 18 | | 'messageSuccess' 19 | | 'messageFailure' 20 | | 'messageSkipped' 21 | | 'messagePath' 22 | | 'preformatted' 23 | | 'status' 24 | >): Promise { 25 | let message 26 | 27 | if (status === 'success' && messageSuccess) { 28 | message = messageSuccess 29 | } 30 | 31 | if (status === 'failure' && messageFailure) { 32 | message = messageFailure 33 | } 34 | 35 | if (status === 'cancelled' && messageCancelled) { 36 | message = messageCancelled 37 | } 38 | 39 | if (status === 'skipped' && messageSkipped) { 40 | message = messageSkipped 41 | } 42 | 43 | if (!message) { 44 | if (messagePath) { 45 | message = await getMessageFromPath(messagePath) 46 | } else { 47 | message = messageInput 48 | } 49 | } 50 | 51 | if (preformatted) { 52 | message = `\`\`\`\n${message}\n\`\`\`` 53 | } 54 | 55 | return message ?? '' 56 | } 57 | 58 | export async function getMessageFromPath(searchPath: string) { 59 | let message = '' 60 | 61 | const files = await findFiles(searchPath) 62 | 63 | for (const [index, path] of files.entries()) { 64 | if (index > 0) { 65 | message += '\n' 66 | } 67 | 68 | message += await fs.readFile(path, { encoding: 'utf8' }) 69 | } 70 | 71 | return message 72 | } 73 | 74 | export function addMessageHeader(messageId: string, message: string) { 75 | return `${messageId}\n\n${message}` 76 | } 77 | 78 | export function removeMessageHeader(message: string) { 79 | return message.split('\n').slice(2).join('\n') 80 | } 81 | 82 | function splitFind(find: string) { 83 | const matches = find.match(/\/((i|g|m|s|u|y){1,6})$/) 84 | 85 | if (!matches) { 86 | return { 87 | regExp: find, 88 | modifiers: 'gi', 89 | } 90 | } 91 | 92 | const [, modifiers] = matches 93 | const regExp = find.replace(modifiers, '').slice(0, -1) 94 | 95 | return { 96 | regExp, 97 | modifiers, 98 | } 99 | } 100 | 101 | export function findAndReplaceInMessage( 102 | find: string[], 103 | replace: string[], 104 | original: string, 105 | ): string { 106 | let message = original 107 | 108 | for (const [i, f] of find.entries()) { 109 | const { regExp, modifiers } = splitFind(f) 110 | message = message.replace(new RegExp(regExp, modifiers), replace[i] ?? replace.join('\n')) 111 | } 112 | 113 | return message 114 | } 115 | -------------------------------------------------------------------------------- /src/proxy.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@actions/http-client' 2 | import { Endpoints } from '@octokit/types' 3 | 4 | type CreateIssueCommentResponseData = 5 | Endpoints['POST /repos/{owner}/{repo}/issues/{issue_number}/comments']['response']['data'] 6 | 7 | export interface CreateCommentProxyParams { 8 | repoToken: string 9 | commentId?: number 10 | body: string 11 | owner: string 12 | repo: string 13 | issueNumber: number 14 | proxyUrl: string 15 | } 16 | 17 | export async function createCommentProxy( 18 | params: CreateCommentProxyParams, 19 | ): Promise { 20 | const { repoToken, owner, repo, issueNumber, body, commentId, proxyUrl } = params 21 | 22 | const http = new HttpClient('http-client-add-pr-comment') 23 | 24 | const response = await http.postJson( 25 | `${proxyUrl}/repos/${owner}/${repo}/issues/${issueNumber}/comments`, 26 | { comment_id: commentId, body }, 27 | { 28 | ['temporary-github-token']: repoToken, 29 | }, 30 | ) 31 | 32 | return response.result 33 | } 34 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { Endpoints } from '@octokit/types' 2 | 3 | export interface Inputs { 4 | allowRepeats: boolean 5 | attachPath?: string[] 6 | commitSha: string 7 | issue?: number 8 | messageInput?: string 9 | messageId: string 10 | messagePath?: string 11 | messageFind?: string[] 12 | messageReplace?: string[] 13 | messageSuccess?: string 14 | messageFailure?: string 15 | messageCancelled?: string 16 | messageSkipped?: string 17 | preformatted: boolean 18 | proxyUrl?: string 19 | pullRequestNumber?: number 20 | refreshMessagePosition: boolean 21 | repo: string 22 | repoToken: string 23 | status: string 24 | owner: string 25 | updateOnly: boolean 26 | } 27 | 28 | export type CreateIssueCommentResponseData = 29 | Endpoints['POST /repos/{owner}/{repo}/issues/{issue_number}/comments']['response']['data'] 30 | 31 | export type ExistingIssueCommentResponseData = 32 | Endpoints['GET /repos/{owner}/{repo}/issues/{issue_number}/comments']['response']['data'][0] 33 | 34 | export type ExistingIssueComment = Pick 35 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2018", 5 | "lib": ["ES2020"], 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "skipLibCheck": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "noImplicitAny": true, 12 | "removeComments": false, 13 | "preserveConstEnums": true, 14 | "resolveJsonModule": true, 15 | "rootDir": "src", 16 | "outDir": "./lib" 17 | }, 18 | "exclude": ["node_modules", "**/*.test.ts"] 19 | } 20 | --------------------------------------------------------------------------------