├── .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 | [](#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 |
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 |
--------------------------------------------------------------------------------