Compare commits

...

17 commits
v2.6.1 ... main

Author SHA1 Message Date
dependabot[bot]
dd126dd8c2
Bump postcss from 8.4.23 to 8.4.33 (#115)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.23 to 8.4.33.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.23...8.4.33)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-01 14:00:08 -05:00
dependabot[bot]
30602c8f56
Bump vite from 4.3.3 to 4.3.9 (#102)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.3.3 to 4.3.9.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.3.9/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-01 13:50:41 -05:00
dependabot[bot]
f24a409f6b
Bump word-wrap from 1.2.3 to 1.2.4 (#105)
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-01 13:50:28 -05:00
Michael Shick
b8f338c590
2.8.2 2024-02-01 13:43:55 -05:00
Robbie Ostrow
74e66d7778
Bump runtime to node20 from node16 (#114)
https://github.blog/changelog/2023-09-22-github-actions-transitioning-from-node-16-to-node-20/
2024-02-01 13:42:24 -05:00
Michael Shick
8fedd701c5
bumping package to node 20 2024-02-01 13:40:56 -05:00
Michael Shick
7c0890544f
2.8.1 2023-05-12 14:10:09 -04:00
Michael Shick
12282e9c93
Update action.yml to add find and replace 2023-05-12 14:09:24 -04:00
Michael Shick
918f138773
2.8.0 2023-05-07 09:04:31 -04:00
Michael Shick
84c8c4f13e
new build 2023-05-07 09:04:04 -04:00
Michael Shick
445c144052
clean up 2023-05-07 09:03:54 -04:00
Michael Shick
ff82b38f95
update replace behavior 2023-05-07 09:03:15 -04:00
Michael Shick
25e7c93662
2.7.0 2023-05-07 08:52:43 -04:00
Michael Shick
09331f990d
new build 2023-05-07 08:52:22 -04:00
Michael Shick
06b07c2e70
try to get release building 2023-05-07 08:51:49 -04:00
Michael Shick
f8c324a9fc
find-and-replace functionality (#100)
* find-and-replace functionality
2023-05-07 08:50:52 -04:00
allcontributors[bot]
5cc4621415
docs: add twang817 as a contributor for code (#98)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2023-05-05 16:12:15 -04:00
20 changed files with 696 additions and 158 deletions

View file

@ -50,6 +50,15 @@
"contributions": [ "contributions": [
"code" "code"
] ]
},
{
"login": "twang817",
"name": "Tommy Wang",
"avatar_url": "https://avatars.githubusercontent.com/u/766820?v=4",
"profile": "http://www.august8.net",
"contributions": [
"code"
]
} }
], ],
"contributorsPerLine": 7, "contributorsPerLine": 7,

View file

@ -74,3 +74,13 @@ jobs:
**Hello** **Hello**
🌏 🌏
! !
- uses: ./
with:
message-id: text
find: |
Hello
🌏
replace: |
Goodnight
🌕

View file

@ -1 +1 @@
v16 v20

View file

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2019 Michael Shick Copyright (c) 2024 Michael Shick
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

135
README.md
View file

@ -2,7 +2,7 @@
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section --> <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors-) [![All Contributors](https://img.shields.io/badge/all_contributors-6-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END --> <!-- ALL-CONTRIBUTORS-BADGE:END -->
@ -88,6 +88,8 @@ jobs:
| update-only | with | Only update the comment if it already exists. | no | false | | update-only | with | Only update the comment if it already exists. | no | false |
| GITHUB_TOKEN | env | Valid GitHub token, can alternatively be defined in the env. | no | | | GITHUB_TOKEN | env | Valid GitHub token, can alternatively be defined in the env. | no | |
| preformatted | with | Treat message text as pre-formatted and place it in a codeblock | no | | | preformatted | with | Treat message text as pre-formatted and place it in a codeblock | no | |
| 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 | |
| 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 | |
## Advanced Uses ## Advanced Uses
@ -170,6 +172,136 @@ jobs:
message-part-*.txt message-part-*.txt
``` ```
### Find-and-Replace
Patterns can be matched and replaced to update comments. This could be useful
for some situations, for instance, updating a checklist comment.
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
include modifiers to override the default `gi`.
**Example**
Original message:
```
[ ] Hello
[ ] World
```
Action:
```yaml
on:
pull_request:
jobs:
pr:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: mshick/add-pr-comment@v2
if: always()
with:
find: |
\n\\[ \\]
replace: |
[X]
```
Final message:
```
[X] Hello
[X] World
```
Multiple find and replaces can be used:
**Example**
Original message:
```
hello world!
```
Action:
```yaml
on:
pull_request:
jobs:
pr:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: mshick/add-pr-comment@v2
if: always()
with:
find: |
hello
world
replace: |
goodnight
moon
```
Final message:
```
goodnight moon!
```
It defaults to your resolved message (either from `message` or `message-path`) to
do a replacement:
**Example**
Original message:
```
hello
<< FILE_CONTENTS >>
world
```
Action:
```yaml
on:
pull_request:
jobs:
pr:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: mshick/add-pr-comment@v2
if: always()
with:
message-path: |
message.txt
find: |
<< FILE_CONTENTS >>
```
Final message:
```
hello
secret message from message.txt
world
```
### Bring your own issues ### Bring your own issues
You can set an issue id explicitly. Helpful for cases where you want to post You can set an issue id explicitly. Helpful for cases where you want to post
@ -219,6 +351,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center" valign="top" width="14.28%"><a href="https://github.com/vincent-joignie-dd"><img src="https://avatars.githubusercontent.com/u/103102299?v=4?s=100" width="100px;" alt="vincent-joignie-dd"/><br /><sub><b>vincent-joignie-dd</b></sub></a><br /><a href="https://github.com/mshick/add-pr-comment/commits?author=vincent-joignie-dd" title="Code">💻</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/vincent-joignie-dd"><img src="https://avatars.githubusercontent.com/u/103102299?v=4?s=100" width="100px;" alt="vincent-joignie-dd"/><br /><sub><b>vincent-joignie-dd</b></sub></a><br /><a href="https://github.com/mshick/add-pr-comment/commits?author=vincent-joignie-dd" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://ahanoff.dev"><img src="https://avatars.githubusercontent.com/u/2371703?v=4?s=100" width="100px;" alt="Akhan Zhakiyanov"/><br /><sub><b>Akhan Zhakiyanov</b></sub></a><br /><a href="https://github.com/mshick/add-pr-comment/commits?author=ahanoff" title="Code">💻</a></td> <td align="center" valign="top" width="14.28%"><a href="https://ahanoff.dev"><img src="https://avatars.githubusercontent.com/u/2371703?v=4?s=100" width="100px;" alt="Akhan Zhakiyanov"/><br /><sub><b>Akhan Zhakiyanov</b></sub></a><br /><a href="https://github.com/mshick/add-pr-comment/commits?author=ahanoff" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ahatzz11"><img src="https://avatars.githubusercontent.com/u/6256032?v=4?s=100" width="100px;" alt="Alex Hatzenbuhler"/><br /><sub><b>Alex Hatzenbuhler</b></sub></a><br /><a href="https://github.com/mshick/add-pr-comment/commits?author=ahatzz11" title="Code">💻</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/ahatzz11"><img src="https://avatars.githubusercontent.com/u/6256032?v=4?s=100" width="100px;" alt="Alex Hatzenbuhler"/><br /><sub><b>Alex Hatzenbuhler</b></sub></a><br /><a href="https://github.com/mshick/add-pr-comment/commits?author=ahatzz11" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.august8.net"><img src="https://avatars.githubusercontent.com/u/766820?v=4?s=100" width="100px;" alt="Tommy Wang"/><br /><sub><b>Tommy Wang</b></sub></a><br /><a href="https://github.com/mshick/add-pr-comment/commits?author=twang817" title="Code">💻</a></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View file

@ -25,6 +25,7 @@ type Inputs = {
'repo-token': string 'repo-token': string
'message-id': string 'message-id': string
'allow-repeats': string 'allow-repeats': string
'message-pattern'?: string
'message-success'?: string 'message-success'?: string
'message-failure'?: string 'message-failure'?: string
'message-cancelled'?: string 'message-cancelled'?: string
@ -95,12 +96,8 @@ const handlers = [
const server = setupServer(...handlers) const server = setupServer(...handlers)
describe('add-pr-comment action', () => {
beforeAll(() => { beforeAll(() => {
// vi.spyOn(console, 'log').mockImplementation(() => {}) vi.spyOn(console, 'log').mockImplementation(() => {})
// vi.spyOn(core, 'debug').mockImplementation(() => {})
// vi.spyOn(core, 'info').mockImplementation(() => {})
// vi.spyOn(core, 'warning').mockImplementation(() => {})
server.listen({ onUnhandledRequest: 'error' }) server.listen({ onUnhandledRequest: 'error' })
}) })
afterAll(() => server.close()) afterAll(() => server.close())
@ -134,7 +131,7 @@ describe('add-pr-comment action', () => {
server.resetHandlers() server.resetHandlers()
}) })
vi.mocked(core.getInput).mockImplementation((name: string, options?: core.InputOptions) => { const getInput = (name: string, options?: core.InputOptions) => {
const value = inputs[name] ?? '' const value = inputs[name] ?? ''
if (options?.required && value === undefined) { if (options?.required && value === undefined) {
@ -142,8 +139,37 @@ describe('add-pr-comment action', () => {
} }
return value return value
}) }
function getMultilineInput(name, options) {
const inputs = getInput(name, options)
.split('\n')
.filter((x) => x !== '')
if (options && options.trimWhitespace === false) {
return inputs
}
return inputs.map((input) => input.trim())
}
function getBooleanInput(name, options) {
const trueValue = ['true', 'True', 'TRUE']
const falseValue = ['false', 'False', 'FALSE']
const val = getInput(name, options)
if (trueValue.includes(val)) return true
if (falseValue.includes(val)) return false
throw new TypeError(
`Input does not meet YAML 1.2 "Core Schema" specification: ${name}\n` +
`Support boolean input list: \`true | True | TRUE | false | False | FALSE\``,
)
}
vi.mocked(core.getInput).mockImplementation(getInput)
vi.mocked(core.getMultilineInput).mockImplementation(getMultilineInput)
vi.mocked(core.getBooleanInput).mockImplementation(getBooleanInput)
describe('add-pr-comment action', () => {
it('creates a comment with message text', async () => { it('creates a comment with message text', async () => {
inputs.message = simpleMessage inputs.message = simpleMessage
inputs['allow-repeats'] = 'true' inputs['allow-repeats'] = 'true'
@ -271,7 +297,7 @@ describe('add-pr-comment action', () => {
it('creates a message when the message id does not exist', async () => { it('creates a message when the message id does not exist', async () => {
inputs.message = simpleMessage inputs.message = simpleMessage
inputs['allow-repeats'] = 'false'
inputs['message-id'] = 'custom-id' inputs['message-id'] = 'custom-id'
const replyBody = [ const replyBody = [
@ -289,7 +315,6 @@ describe('add-pr-comment action', () => {
it('identifies an existing message by id and updates it', async () => { it('identifies an existing message by id and updates it', async () => {
inputs.message = simpleMessage inputs.message = simpleMessage
inputs['allow-repeats'] = 'false'
const commentId = 123 const commentId = 123
@ -313,7 +338,7 @@ describe('add-pr-comment action', () => {
it('overrides the default message with a success message on success', async () => { it('overrides the default message with a success message on success', async () => {
inputs.message = simpleMessage inputs.message = simpleMessage
inputs['allow-repeats'] = 'false'
inputs['message-success'] = '666' inputs['message-success'] = '666'
inputs.status = 'success' inputs.status = 'success'
@ -334,7 +359,7 @@ describe('add-pr-comment action', () => {
it('overrides the default message with a failure message on failure', async () => { it('overrides the default message with a failure message on failure', async () => {
inputs.message = simpleMessage inputs.message = simpleMessage
inputs['allow-repeats'] = 'false'
inputs['message-failure'] = '666' inputs['message-failure'] = '666'
inputs.status = 'failure' inputs.status = 'failure'
@ -355,7 +380,7 @@ describe('add-pr-comment action', () => {
it('overrides the default message with a cancelled message on cancelled', async () => { it('overrides the default message with a cancelled message on cancelled', async () => {
inputs.message = simpleMessage inputs.message = simpleMessage
inputs['allow-repeats'] = 'false'
inputs['message-cancelled'] = '666' inputs['message-cancelled'] = '666'
inputs.status = 'cancelled' inputs.status = 'cancelled'
@ -376,7 +401,7 @@ describe('add-pr-comment action', () => {
it('overrides the default message with a skipped message on skipped', async () => { it('overrides the default message with a skipped message on skipped', async () => {
inputs.message = simpleMessage inputs.message = simpleMessage
inputs['allow-repeats'] = 'false'
inputs['message-skipped'] = '666' inputs['message-skipped'] = '666'
inputs.status = 'skipped' inputs.status = 'skipped'
@ -408,3 +433,198 @@ describe('add-pr-comment action', () => {
expect(core.setOutput).toHaveBeenCalledWith('comment-id', postIssueCommentsResponse.id) expect(core.setOutput).toHaveBeenCalledWith('comment-id', postIssueCommentsResponse.id)
}) })
}) })
describe('find and replace', () => {
it('can find and replace text in an existing comment', async () => {
inputs['find'] = 'world'
inputs['replace'] = 'mars'
const commentId = 123
const replyBody = [
{
id: commentId,
body: `<!-- add-pr-comment:${inputs['message-id']} -->\n\n${simpleMessage}`,
},
]
getIssueCommentsResponse = replyBody
postIssueCommentsResponse = {
id: commentId,
}
await run()
expect(`<!-- add-pr-comment:add-pr-comment -->\n\nhello mars`).toEqual(messagePayload?.body)
expect(core.setOutput).toHaveBeenCalledWith('comment-updated', 'true')
expect(core.setOutput).toHaveBeenCalledWith('comment-id', commentId)
})
it('can multiple find and replace text in an existing comment', async () => {
inputs['find'] = 'hello\nworld'
inputs['replace'] = 'goodbye\nmars'
const body = `<!-- add-pr-comment:${inputs['message-id']} -->\n\nhello\nworld`
const commentId = 123
const replyBody = [
{
id: commentId,
body,
},
]
getIssueCommentsResponse = replyBody
postIssueCommentsResponse = {
id: commentId,
}
await run()
expect(`<!-- add-pr-comment:add-pr-comment -->\n\ngoodbye\nmars`).toEqual(messagePayload?.body)
expect(core.setOutput).toHaveBeenCalledWith('comment-updated', 'true')
expect(core.setOutput).toHaveBeenCalledWith('comment-id', commentId)
})
it('can multiple find and replace text using a message', async () => {
inputs['find'] = 'hello\nworld'
inputs['message'] = 'mars'
const body = `<!-- add-pr-comment:${inputs['message-id']} -->\n\nhello\nworld`
const commentId = 123
const replyBody = [
{
id: commentId,
body,
},
]
getIssueCommentsResponse = replyBody
postIssueCommentsResponse = {
id: commentId,
}
await run()
expect(`<!-- add-pr-comment:add-pr-comment -->\n\nmars\nmars`).toEqual(messagePayload?.body)
expect(core.setOutput).toHaveBeenCalledWith('comment-updated', 'true')
expect(core.setOutput).toHaveBeenCalledWith('comment-id', commentId)
})
it('can multiple find and replace a single pattern with a multiline replacement', async () => {
inputs['find'] = 'hello'
inputs['message'] = 'h\ne\nl\nl\no'
const body = `<!-- add-pr-comment:${inputs['message-id']} -->\n\nhello\nworld`
const commentId = 123
const replyBody = [
{
id: commentId,
body,
},
]
getIssueCommentsResponse = replyBody
postIssueCommentsResponse = {
id: commentId,
}
await run()
expect(`<!-- add-pr-comment:add-pr-comment -->\n\nh\ne\nl\nl\no\nworld`).toEqual(
messagePayload?.body,
)
expect(core.setOutput).toHaveBeenCalledWith('comment-updated', 'true')
expect(core.setOutput).toHaveBeenCalledWith('comment-id', commentId)
})
it('can multiple find and replace text using a message-path', async () => {
inputs['find'] = '<< FILE_CONTENTS >>'
inputs['message-path'] = messagePath1Fixture
const body = `<!-- add-pr-comment:${inputs['message-id']} -->\n\nhello\n<< FILE_CONTENTS >>\nworld`
const commentId = 123
const replyBody = [
{
id: commentId,
body,
},
]
getIssueCommentsResponse = replyBody
postIssueCommentsResponse = {
id: commentId,
}
await run()
expect(
`<!-- add-pr-comment:add-pr-comment -->\n\nhello\n${messagePath1FixturePayload}\nworld`,
).toEqual(messagePayload?.body)
expect(core.setOutput).toHaveBeenCalledWith('comment-updated', 'true')
expect(core.setOutput).toHaveBeenCalledWith('comment-id', commentId)
})
it('can find and replace patterns and use alternative modifiers', async () => {
inputs['find'] = '(o|l)/g'
inputs['replace'] = 'YY'
const body = `<!-- add-pr-comment:${inputs['message-id']} -->\n\nHELLO\nworld`
const commentId = 123
const replyBody = [
{
id: commentId,
body,
},
]
getIssueCommentsResponse = replyBody
postIssueCommentsResponse = {
id: commentId,
}
await run()
expect(`<!-- add-pr-comment:add-pr-comment -->\n\nHELLO\nwYYrYYd`).toEqual(messagePayload?.body)
expect(core.setOutput).toHaveBeenCalledWith('comment-updated', 'true')
expect(core.setOutput).toHaveBeenCalledWith('comment-id', commentId)
})
it('can check some boxes with find and replace', async () => {
inputs['find'] = '\n\\[ \\]'
inputs['replace'] = '[X]'
const body = `<!-- add-pr-comment:${inputs['message-id']} -->\n\n[ ] Hello\n[ ] World`
const commentId = 123
const replyBody = [
{
id: commentId,
body,
},
]
getIssueCommentsResponse = replyBody
postIssueCommentsResponse = {
id: commentId,
}
await run()
expect(`<!-- add-pr-comment:add-pr-comment -->\n\n[X] Hello\n[X] World`).toEqual(
messagePayload?.body,
)
expect(core.setOutput).toHaveBeenCalledWith('comment-updated', 'true')
expect(core.setOutput).toHaveBeenCalledWith('comment-id', commentId)
})
})

View file

@ -59,6 +59,10 @@ inputs:
preformatted: preformatted:
description: "Treat message text (from a file or input) as pre-formatted and place it in a codeblock." description: "Treat message text (from a file or input) as pre-formatted and place it in a codeblock."
required: false required: false
find:
description: "A regular expression to find for replacement. Multiple lines become individual regular expressions."
replace:
description: "A replacement to use, overrides the message. Multple lines can replace same-indexed find patterns."
outputs: outputs:
comment-created: comment-created:
description: "Whether a comment was created." description: "Whether a comment was created."
@ -70,5 +74,5 @@ branding:
icon: message-circle icon: message-circle
color: purple color: purple
runs: runs:
using: "node16" using: "node20"
main: "dist/index.js" main: "dist/index.js"

88
dist/index.js vendored
View file

@ -7,8 +7,8 @@ require('./sourcemap-register.js');/******/ (() => { // webpackBootstrap
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.createComment = exports.deleteComment = exports.updateComment = exports.getExistingCommentId = void 0; exports.createComment = exports.deleteComment = exports.updateComment = exports.getExistingComment = void 0;
async function getExistingCommentId(octokit, owner, repo, issueNumber, messageId) { async function getExistingComment(octokit, owner, repo, issueNumber, messageId) {
const parameters = { const parameters = {
owner, owner,
repo, repo,
@ -25,9 +25,13 @@ async function getExistingCommentId(octokit, owner, repo, issueNumber, messageId
break; break;
} }
} }
return found === null || found === void 0 ? void 0 : found.id; if (found) {
const { id, body } = found;
return { id, body };
} }
exports.getExistingCommentId = getExistingCommentId; return;
}
exports.getExistingComment = getExistingComment;
async function updateComment(octokit, owner, repo, existingCommentId, body) { async function updateComment(octokit, owner, repo, existingCommentId, body) {
const updatedComment = await octokit.rest.issues.updateComment({ const updatedComment = await octokit.rest.issues.updateComment({
comment_id: existingCommentId, comment_id: existingCommentId,
@ -100,6 +104,8 @@ async function getInputs() {
const messageId = messageIdInput === '' ? 'add-pr-comment' : `add-pr-comment:${messageIdInput}`; const messageId = messageIdInput === '' ? 'add-pr-comment' : `add-pr-comment:${messageIdInput}`;
const messageInput = core.getInput('message', { required: false }); const messageInput = core.getInput('message', { required: false });
const messagePath = core.getInput('message-path', { required: false }); const messagePath = core.getInput('message-path', { required: false });
const messageFind = core.getMultilineInput('find', { required: false });
const messageReplace = core.getMultilineInput('replace', { required: false });
const repoOwner = core.getInput('repo-owner', { required: true }); const repoOwner = core.getInput('repo-owner', { required: true });
const repoName = core.getInput('repo-name', { required: true }); const repoName = core.getInput('repo-name', { required: true });
const repoToken = core.getInput('repo-token', { required: true }); const repoToken = core.getInput('repo-token', { required: true });
@ -129,6 +135,8 @@ async function getInputs() {
messageCancelled, messageCancelled,
messageSkipped, messageSkipped,
messagePath, messagePath,
messageFind,
messageReplace,
preformatted, preformatted,
proxyUrl, proxyUrl,
pullRequestNumber: (_b = payload.pull_request) === null || _b === void 0 ? void 0 : _b.number, pullRequestNumber: (_b = payload.pull_request) === null || _b === void 0 ? void 0 : _b.number,
@ -265,9 +273,9 @@ const message_1 = __nccwpck_require__(3307);
const proxy_1 = __nccwpck_require__(8689); const proxy_1 = __nccwpck_require__(8689);
const run = async () => { const run = async () => {
try { try {
const { allowRepeats, messagePath, messageInput, messageId, refreshMessagePosition, repoToken, proxyUrl, issue, pullRequestNumber, commitSha, repo, owner, updateOnly, messageCancelled, messageFailure, messageSuccess, messageSkipped, preformatted, status, } = await (0, config_1.getInputs)(); 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)();
const octokit = github.getOctokit(repoToken); const octokit = github.getOctokit(repoToken);
const message = await (0, message_1.getMessage)({ let message = await (0, message_1.getMessage)({
messagePath, messagePath,
messageInput, messageInput,
messageSkipped, messageSkipped,
@ -293,25 +301,31 @@ const run = async () => {
core.setOutput('comment-created', 'false'); core.setOutput('comment-created', 'false');
return; return;
} }
let existingCommentId; let existingComment;
if (!allowRepeats) { if (!allowRepeats) {
core.debug('repeat comments are disallowed, checking for existing'); core.debug('repeat comments are disallowed, checking for existing');
existingCommentId = await (0, comments_1.getExistingCommentId)(octokit, owner, repo, issueNumber, messageId); existingComment = await (0, comments_1.getExistingComment)(octokit, owner, repo, issueNumber, messageId);
if (existingCommentId) { if (existingComment) {
core.debug(`existing comment found with id: ${existingCommentId}`); core.debug(`existing comment found with id: ${existingComment.id}`);
} }
} }
// if no existing comment and updateOnly is true, exit // if no existing comment and updateOnly is true, exit
if (!existingCommentId && updateOnly) { if (!existingComment && updateOnly) {
core.info('no existing comment found and update-only is true, exiting'); core.info('no existing comment found and update-only is true, exiting');
core.setOutput('comment-created', 'false'); core.setOutput('comment-created', 'false');
return; return;
} }
let comment; let comment;
const body = `${messageId}\n\n${message}`; 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)) {
message = (0, message_1.findAndReplaceInMessage)(messageFind, (messageReplace === null || messageReplace === void 0 ? void 0 : messageReplace.length) ? messageReplace : [message], (0, message_1.removeMessageHeader)(existingComment.body));
}
if (!message) {
throw new Error('no message, check your message inputs');
}
const body = (0, message_1.addMessageHeader)(messageId, message);
if (proxyUrl) { if (proxyUrl) {
comment = await (0, proxy_1.createCommentProxy)({ comment = await (0, proxy_1.createCommentProxy)({
commentId: existingCommentId, commentId: existingComment === null || existingComment === void 0 ? void 0 : existingComment.id,
owner, owner,
repo, repo,
issueNumber, issueNumber,
@ -319,15 +333,15 @@ const run = async () => {
repoToken, repoToken,
proxyUrl, proxyUrl,
}); });
core.setOutput(existingCommentId ? 'comment-updated' : 'comment-created', 'true'); core.setOutput((existingComment === null || existingComment === void 0 ? void 0 : existingComment.id) ? 'comment-updated' : 'comment-created', 'true');
} }
else if (existingCommentId) { else if (existingComment === null || existingComment === void 0 ? void 0 : existingComment.id) {
if (refreshMessagePosition) { if (refreshMessagePosition) {
await (0, comments_1.deleteComment)(octokit, owner, repo, existingCommentId, body); await (0, comments_1.deleteComment)(octokit, owner, repo, existingComment.id, body);
comment = await (0, comments_1.createComment)(octokit, owner, repo, issueNumber, body); comment = await (0, comments_1.createComment)(octokit, owner, repo, issueNumber, body);
} }
else { else {
comment = await (0, comments_1.updateComment)(octokit, owner, repo, existingCommentId, body); comment = await (0, comments_1.updateComment)(octokit, owner, repo, existingComment.id, body);
} }
core.setOutput('comment-updated', 'true'); core.setOutput('comment-updated', 'true');
} }
@ -371,7 +385,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod }; return (mod && mod.__esModule) ? mod : { "default": mod };
}; };
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.getMessageFromPath = exports.getMessage = void 0; exports.findAndReplaceInMessage = exports.removeMessageHeader = exports.addMessageHeader = exports.getMessageFromPath = exports.getMessage = void 0;
const promises_1 = __importDefault(__nccwpck_require__(3977)); const promises_1 = __importDefault(__nccwpck_require__(3977));
const files_1 = __nccwpck_require__(1743); const files_1 = __nccwpck_require__(1743);
async function getMessage({ messageInput, messagePath, messageCancelled, messageSkipped, messageFailure, messageSuccess, preformatted, status, }) { async function getMessage({ messageInput, messagePath, messageCancelled, messageSkipped, messageFailure, messageSuccess, preformatted, status, }) {
@ -396,13 +410,10 @@ async function getMessage({ messageInput, messagePath, messageCancelled, message
message = messageInput; message = messageInput;
} }
} }
if (!message) {
throw new Error('no message, check your message inputs');
}
if (preformatted) { if (preformatted) {
message = `\`\`\`\n${message}\n\`\`\``; message = `\`\`\`\n${message}\n\`\`\``;
} }
return message; return message !== null && message !== void 0 ? message : '';
} }
exports.getMessage = getMessage; exports.getMessage = getMessage;
async function getMessageFromPath(searchPath) { async function getMessageFromPath(searchPath) {
@ -417,6 +428,39 @@ async function getMessageFromPath(searchPath) {
return message; return message;
} }
exports.getMessageFromPath = getMessageFromPath; exports.getMessageFromPath = getMessageFromPath;
function addMessageHeader(messageId, message) {
return `${messageId}\n\n${message}`;
}
exports.addMessageHeader = addMessageHeader;
function removeMessageHeader(message) {
return message.split('\n').slice(2).join('\n');
}
exports.removeMessageHeader = removeMessageHeader;
function splitFind(find) {
const matches = find.match(/\/((i|g|m|s|u|y){1,6})$/);
if (!matches) {
return {
regExp: find,
modifiers: 'gi',
};
}
const [, modifiers] = matches;
const regExp = find.replace(modifiers, '').slice(0, -1);
return {
regExp,
modifiers,
};
}
function findAndReplaceInMessage(find, replace, original) {
var _a;
let message = original;
for (const [i, f] of find.entries()) {
const { regExp, modifiers } = splitFind(f);
message = message.replace(new RegExp(regExp, modifiers), (_a = replace[i]) !== null && _a !== void 0 ? _a : replace.join('\n'));
}
return message;
}
exports.findAndReplaceInMessage = findAndReplaceInMessage;
/***/ }), /***/ }),

2
dist/index.js.map vendored

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,7 @@
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.createComment = exports.deleteComment = exports.updateComment = exports.getExistingCommentId = void 0; exports.createComment = exports.deleteComment = exports.updateComment = exports.getExistingComment = void 0;
async function getExistingCommentId(octokit, owner, repo, issueNumber, messageId) { async function getExistingComment(octokit, owner, repo, issueNumber, messageId) {
const parameters = { const parameters = {
owner, owner,
repo, repo,
@ -18,9 +18,13 @@ async function getExistingCommentId(octokit, owner, repo, issueNumber, messageId
break; break;
} }
} }
return found === null || found === void 0 ? void 0 : found.id; if (found) {
const { id, body } = found;
return { id, body };
} }
exports.getExistingCommentId = getExistingCommentId; return;
}
exports.getExistingComment = getExistingComment;
async function updateComment(octokit, owner, repo, existingCommentId, body) { async function updateComment(octokit, owner, repo, existingCommentId, body) {
const updatedComment = await octokit.rest.issues.updateComment({ const updatedComment = await octokit.rest.issues.updateComment({
comment_id: existingCommentId, comment_id: existingCommentId,

View file

@ -32,6 +32,8 @@ async function getInputs() {
const messageId = messageIdInput === '' ? 'add-pr-comment' : `add-pr-comment:${messageIdInput}`; const messageId = messageIdInput === '' ? 'add-pr-comment' : `add-pr-comment:${messageIdInput}`;
const messageInput = core.getInput('message', { required: false }); const messageInput = core.getInput('message', { required: false });
const messagePath = core.getInput('message-path', { required: false }); const messagePath = core.getInput('message-path', { required: false });
const messageFind = core.getMultilineInput('find', { required: false });
const messageReplace = core.getMultilineInput('replace', { required: false });
const repoOwner = core.getInput('repo-owner', { required: true }); const repoOwner = core.getInput('repo-owner', { required: true });
const repoName = core.getInput('repo-name', { required: true }); const repoName = core.getInput('repo-name', { required: true });
const repoToken = core.getInput('repo-token', { required: true }); const repoToken = core.getInput('repo-token', { required: true });
@ -61,6 +63,8 @@ async function getInputs() {
messageCancelled, messageCancelled,
messageSkipped, messageSkipped,
messagePath, messagePath,
messageFind,
messageReplace,
preformatted, preformatted,
proxyUrl, proxyUrl,
pullRequestNumber: (_b = payload.pull_request) === null || _b === void 0 ? void 0 : _b.number, pullRequestNumber: (_b = payload.pull_request) === null || _b === void 0 ? void 0 : _b.number,

View file

@ -32,9 +32,9 @@ const message_1 = require("./message");
const proxy_1 = require("./proxy"); const proxy_1 = require("./proxy");
const run = async () => { const run = async () => {
try { try {
const { allowRepeats, messagePath, messageInput, messageId, refreshMessagePosition, repoToken, proxyUrl, issue, pullRequestNumber, commitSha, repo, owner, updateOnly, messageCancelled, messageFailure, messageSuccess, messageSkipped, preformatted, status, } = await (0, config_1.getInputs)(); 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)();
const octokit = github.getOctokit(repoToken); const octokit = github.getOctokit(repoToken);
const message = await (0, message_1.getMessage)({ let message = await (0, message_1.getMessage)({
messagePath, messagePath,
messageInput, messageInput,
messageSkipped, messageSkipped,
@ -60,25 +60,31 @@ const run = async () => {
core.setOutput('comment-created', 'false'); core.setOutput('comment-created', 'false');
return; return;
} }
let existingCommentId; let existingComment;
if (!allowRepeats) { if (!allowRepeats) {
core.debug('repeat comments are disallowed, checking for existing'); core.debug('repeat comments are disallowed, checking for existing');
existingCommentId = await (0, comments_1.getExistingCommentId)(octokit, owner, repo, issueNumber, messageId); existingComment = await (0, comments_1.getExistingComment)(octokit, owner, repo, issueNumber, messageId);
if (existingCommentId) { if (existingComment) {
core.debug(`existing comment found with id: ${existingCommentId}`); core.debug(`existing comment found with id: ${existingComment.id}`);
} }
} }
// if no existing comment and updateOnly is true, exit // if no existing comment and updateOnly is true, exit
if (!existingCommentId && updateOnly) { if (!existingComment && updateOnly) {
core.info('no existing comment found and update-only is true, exiting'); core.info('no existing comment found and update-only is true, exiting');
core.setOutput('comment-created', 'false'); core.setOutput('comment-created', 'false');
return; return;
} }
let comment; let comment;
const body = `${messageId}\n\n${message}`; 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)) {
message = (0, message_1.findAndReplaceInMessage)(messageFind, (messageReplace === null || messageReplace === void 0 ? void 0 : messageReplace.length) ? messageReplace : [message], (0, message_1.removeMessageHeader)(existingComment.body));
}
if (!message) {
throw new Error('no message, check your message inputs');
}
const body = (0, message_1.addMessageHeader)(messageId, message);
if (proxyUrl) { if (proxyUrl) {
comment = await (0, proxy_1.createCommentProxy)({ comment = await (0, proxy_1.createCommentProxy)({
commentId: existingCommentId, commentId: existingComment === null || existingComment === void 0 ? void 0 : existingComment.id,
owner, owner,
repo, repo,
issueNumber, issueNumber,
@ -86,15 +92,15 @@ const run = async () => {
repoToken, repoToken,
proxyUrl, proxyUrl,
}); });
core.setOutput(existingCommentId ? 'comment-updated' : 'comment-created', 'true'); core.setOutput((existingComment === null || existingComment === void 0 ? void 0 : existingComment.id) ? 'comment-updated' : 'comment-created', 'true');
} }
else if (existingCommentId) { else if (existingComment === null || existingComment === void 0 ? void 0 : existingComment.id) {
if (refreshMessagePosition) { if (refreshMessagePosition) {
await (0, comments_1.deleteComment)(octokit, owner, repo, existingCommentId, body); await (0, comments_1.deleteComment)(octokit, owner, repo, existingComment.id, body);
comment = await (0, comments_1.createComment)(octokit, owner, repo, issueNumber, body); comment = await (0, comments_1.createComment)(octokit, owner, repo, issueNumber, body);
} }
else { else {
comment = await (0, comments_1.updateComment)(octokit, owner, repo, existingCommentId, body); comment = await (0, comments_1.updateComment)(octokit, owner, repo, existingComment.id, body);
} }
core.setOutput('comment-updated', 'true'); core.setOutput('comment-updated', 'true');
} }

View file

@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod }; return (mod && mod.__esModule) ? mod : { "default": mod };
}; };
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.getMessageFromPath = exports.getMessage = void 0; exports.findAndReplaceInMessage = exports.removeMessageHeader = exports.addMessageHeader = exports.getMessageFromPath = exports.getMessage = void 0;
const promises_1 = __importDefault(require("node:fs/promises")); const promises_1 = __importDefault(require("node:fs/promises"));
const files_1 = require("./files"); const files_1 = require("./files");
async function getMessage({ messageInput, messagePath, messageCancelled, messageSkipped, messageFailure, messageSuccess, preformatted, status, }) { async function getMessage({ messageInput, messagePath, messageCancelled, messageSkipped, messageFailure, messageSuccess, preformatted, status, }) {
@ -28,13 +28,10 @@ async function getMessage({ messageInput, messagePath, messageCancelled, message
message = messageInput; message = messageInput;
} }
} }
if (!message) {
throw new Error('no message, check your message inputs');
}
if (preformatted) { if (preformatted) {
message = `\`\`\`\n${message}\n\`\`\``; message = `\`\`\`\n${message}\n\`\`\``;
} }
return message; return message !== null && message !== void 0 ? message : '';
} }
exports.getMessage = getMessage; exports.getMessage = getMessage;
async function getMessageFromPath(searchPath) { async function getMessageFromPath(searchPath) {
@ -49,3 +46,36 @@ async function getMessageFromPath(searchPath) {
return message; return message;
} }
exports.getMessageFromPath = getMessageFromPath; exports.getMessageFromPath = getMessageFromPath;
function addMessageHeader(messageId, message) {
return `${messageId}\n\n${message}`;
}
exports.addMessageHeader = addMessageHeader;
function removeMessageHeader(message) {
return message.split('\n').slice(2).join('\n');
}
exports.removeMessageHeader = removeMessageHeader;
function splitFind(find) {
const matches = find.match(/\/((i|g|m|s|u|y){1,6})$/);
if (!matches) {
return {
regExp: find,
modifiers: 'gi',
};
}
const [, modifiers] = matches;
const regExp = find.replace(modifiers, '').slice(0, -1);
return {
regExp,
modifiers,
};
}
function findAndReplaceInMessage(find, replace, original) {
var _a;
let message = original;
for (const [i, f] of find.entries()) {
const { regExp, modifiers } = splitFind(f);
message = message.replace(new RegExp(regExp, modifiers), (_a = replace[i]) !== null && _a !== void 0 ? _a : replace.join('\n'));
}
return message;
}
exports.findAndReplaceInMessage = findAndReplaceInMessage;

32
package-lock.json generated
View file

@ -1,12 +1,12 @@
{ {
"name": "@mshick/add-pr-comment", "name": "@mshick/add-pr-comment",
"version": "2.6.1", "version": "2.8.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@mshick/add-pr-comment", "name": "@mshick/add-pr-comment",
"version": "2.6.1", "version": "2.8.2",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/artifact": "^1.1.1", "@actions/artifact": "^1.1.1",
@ -37,7 +37,7 @@
"vitest": "^0.30.1" "vitest": "^0.30.1"
}, },
"engines": { "engines": {
"node": "^14.15.0 || ^16.13.0 || ^18.0.0" "node": "^16.13.0 || ^18.0.0 || ^20.0.0"
} }
}, },
"node_modules/@actions/artifact": { "node_modules/@actions/artifact": {
@ -7647,9 +7647,9 @@
"dev": true "dev": true
}, },
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.6", "version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -9049,9 +9049,9 @@
} }
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.4.23", "version": "8.4.33",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.23.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz",
"integrity": "sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==", "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -9068,7 +9068,7 @@
} }
], ],
"dependencies": { "dependencies": {
"nanoid": "^3.3.6", "nanoid": "^3.3.7",
"picocolors": "^1.0.0", "picocolors": "^1.0.0",
"source-map-js": "^1.0.2" "source-map-js": "^1.0.2"
}, },
@ -10911,9 +10911,9 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "4.3.3", "version": "4.3.9",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.3.3.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz",
"integrity": "sha512-MwFlLBO4udZXd+VBcezo3u8mC77YQk+ik+fbc0GZWGgzfbPP+8Kf0fldhARqvSYmtIWoAJ5BXPClUbMTlqFxrA==", "integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"esbuild": "^0.17.5", "esbuild": "^0.17.5",
@ -11184,9 +11184,9 @@
} }
}, },
"node_modules/word-wrap": { "node_modules/word-wrap": {
"version": "1.2.3", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"

View file

@ -1,6 +1,6 @@
{ {
"name": "@mshick/add-pr-comment", "name": "@mshick/add-pr-comment",
"version": "2.6.1", "version": "2.8.2",
"description": "A GitHub Action which adds a comment to a Pull Request Issue.", "description": "A GitHub Action which adds a comment to a Pull Request Issue.",
"keywords": [ "keywords": [
"GitHub", "GitHub",
@ -25,9 +25,9 @@
"build": "del-cli dist && tsc && ncc build --source-map", "build": "del-cli dist && tsc && ncc build --source-map",
"clean": "rm -rf node_modules dist package-lock.json __tests__/runner/**/*", "clean": "rm -rf node_modules dist package-lock.json __tests__/runner/**/*",
"lint": "eslint src/**/*.ts", "lint": "eslint src/**/*.ts",
"prepare": "npm run build && git add lib dist",
"release": "npm run build && np --no-publish", "release": "npm run build && np --no-publish",
"test": "vitest run", "test": "vitest run",
"version": "npm run build && git add lib dist",
"watch": "vitest" "watch": "vitest"
}, },
"prettier": { "prettier": {
@ -148,6 +148,6 @@
"vitest": "^0.30.1" "vitest": "^0.30.1"
}, },
"engines": { "engines": {
"node": "^14.15.0 || ^16.13.0 || ^18.0.0" "node": "^16.13.0 || ^18.0.0 || ^20.0.0"
} }
} }

View file

@ -1,16 +1,17 @@
import { GitHub } from '@actions/github/lib/utils' import { GitHub } from '@actions/github/lib/utils'
import { Endpoints } from '@octokit/types' import {
CreateIssueCommentResponseData,
ExistingIssueComment,
ExistingIssueCommentResponseData,
} from './types'
export type CreateIssueCommentResponseData = export async function getExistingComment(
Endpoints['POST /repos/{owner}/{repo}/issues/{issue_number}/comments']['response']['data']
export async function getExistingCommentId(
octokit: InstanceType<typeof GitHub>, octokit: InstanceType<typeof GitHub>,
owner: string, owner: string,
repo: string, repo: string,
issueNumber: number, issueNumber: number,
messageId: string, messageId: string,
): Promise<number | undefined> { ): Promise<ExistingIssueComment | undefined> {
const parameters = { const parameters = {
owner, owner,
repo, repo,
@ -18,7 +19,7 @@ export async function getExistingCommentId(
per_page: 100, per_page: 100,
} }
let found let found: ExistingIssueCommentResponseData | undefined
for await (const comments of octokit.paginate.iterator( for await (const comments of octokit.paginate.iterator(
octokit.rest.issues.listComments, octokit.rest.issues.listComments,
@ -33,7 +34,12 @@ export async function getExistingCommentId(
} }
} }
return found?.id if (found) {
const { id, body } = found
return { id, body }
}
return
} }
export async function updateComment( export async function updateComment(

View file

@ -7,6 +7,8 @@ export async function getInputs(): Promise<Inputs> {
const messageId = messageIdInput === '' ? 'add-pr-comment' : `add-pr-comment:${messageIdInput}` const messageId = messageIdInput === '' ? 'add-pr-comment' : `add-pr-comment:${messageIdInput}`
const messageInput = core.getInput('message', { required: false }) const messageInput = core.getInput('message', { required: false })
const messagePath = core.getInput('message-path', { required: false }) const messagePath = core.getInput('message-path', { required: false })
const messageFind = core.getMultilineInput('find', { required: false })
const messageReplace = core.getMultilineInput('replace', { required: false })
const repoOwner = core.getInput('repo-owner', { required: true }) const repoOwner = core.getInput('repo-owner', { required: true })
const repoName = core.getInput('repo-name', { required: true }) const repoName = core.getInput('repo-name', { required: true })
const repoToken = core.getInput('repo-token', { required: true }) const repoToken = core.getInput('repo-token', { required: true })
@ -41,6 +43,8 @@ export async function getInputs(): Promise<Inputs> {
messageCancelled, messageCancelled,
messageSkipped, messageSkipped,
messagePath, messagePath,
messageFind,
messageReplace,
preformatted, preformatted,
proxyUrl, proxyUrl,
pullRequestNumber: payload.pull_request?.number, pullRequestNumber: payload.pull_request?.number,

View file

@ -1,16 +1,16 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import * as github from '@actions/github' import * as github from '@actions/github'
import { import { createComment, deleteComment, getExistingComment, updateComment } from './comments'
CreateIssueCommentResponseData,
createComment,
deleteComment,
getExistingCommentId,
updateComment,
} from './comments'
import { getInputs } from './config' import { getInputs } from './config'
import { getIssueNumberFromCommitPullsList } from './issues' import { getIssueNumberFromCommitPullsList } from './issues'
import { getMessage } from './message' import {
addMessageHeader,
findAndReplaceInMessage,
getMessage,
removeMessageHeader,
} from './message'
import { createCommentProxy } from './proxy' import { createCommentProxy } from './proxy'
import { CreateIssueCommentResponseData, ExistingIssueComment } from './types'
const run = async (): Promise<void> => { const run = async (): Promise<void> => {
try { try {
@ -34,11 +34,13 @@ const run = async (): Promise<void> => {
messageSkipped, messageSkipped,
preformatted, preformatted,
status, status,
messageFind,
messageReplace,
} = await getInputs() } = await getInputs()
const octokit = github.getOctokit(repoToken) const octokit = github.getOctokit(repoToken)
const message = await getMessage({ let message = await getMessage({
messagePath, messagePath,
messageInput, messageInput,
messageSkipped, messageSkipped,
@ -68,20 +70,20 @@ const run = async (): Promise<void> => {
return return
} }
let existingCommentId let existingComment: ExistingIssueComment | undefined
if (!allowRepeats) { if (!allowRepeats) {
core.debug('repeat comments are disallowed, checking for existing') core.debug('repeat comments are disallowed, checking for existing')
existingCommentId = await getExistingCommentId(octokit, owner, repo, issueNumber, messageId) existingComment = await getExistingComment(octokit, owner, repo, issueNumber, messageId)
if (existingCommentId) { if (existingComment) {
core.debug(`existing comment found with id: ${existingCommentId}`) core.debug(`existing comment found with id: ${existingComment.id}`)
} }
} }
// if no existing comment and updateOnly is true, exit // if no existing comment and updateOnly is true, exit
if (!existingCommentId && updateOnly) { if (!existingComment && updateOnly) {
core.info('no existing comment found and update-only is true, exiting') core.info('no existing comment found and update-only is true, exiting')
core.setOutput('comment-created', 'false') core.setOutput('comment-created', 'false')
return return
@ -89,11 +91,23 @@ const run = async (): Promise<void> => {
let comment: CreateIssueCommentResponseData | null | undefined let comment: CreateIssueCommentResponseData | null | undefined
const body = `${messageId}\n\n${message}` if (messageFind?.length && (messageReplace?.length || message) && existingComment?.body) {
message = findAndReplaceInMessage(
messageFind,
messageReplace?.length ? messageReplace : [message],
removeMessageHeader(existingComment.body),
)
}
if (!message) {
throw new Error('no message, check your message inputs')
}
const body = addMessageHeader(messageId, message)
if (proxyUrl) { if (proxyUrl) {
comment = await createCommentProxy({ comment = await createCommentProxy({
commentId: existingCommentId, commentId: existingComment?.id,
owner, owner,
repo, repo,
issueNumber, issueNumber,
@ -101,13 +115,13 @@ const run = async (): Promise<void> => {
repoToken, repoToken,
proxyUrl, proxyUrl,
}) })
core.setOutput(existingCommentId ? 'comment-updated' : 'comment-created', 'true') core.setOutput(existingComment?.id ? 'comment-updated' : 'comment-created', 'true')
} else if (existingCommentId) { } else if (existingComment?.id) {
if (refreshMessagePosition) { if (refreshMessagePosition) {
await deleteComment(octokit, owner, repo, existingCommentId, body) await deleteComment(octokit, owner, repo, existingComment.id, body)
comment = await createComment(octokit, owner, repo, issueNumber, body) comment = await createComment(octokit, owner, repo, issueNumber, body)
} else { } else {
comment = await updateComment(octokit, owner, repo, existingCommentId, body) comment = await updateComment(octokit, owner, repo, existingComment.id, body)
} }
core.setOutput('comment-updated', 'true') core.setOutput('comment-updated', 'true')
} else { } else {

View file

@ -21,7 +21,7 @@ export async function getMessage({
| 'messagePath' | 'messagePath'
| 'preformatted' | 'preformatted'
| 'status' | 'status'
>) { >): Promise<string> {
let message let message
if (status === 'success' && messageSuccess) { if (status === 'success' && messageSuccess) {
@ -48,15 +48,11 @@ export async function getMessage({
} }
} }
if (!message) {
throw new Error('no message, check your message inputs')
}
if (preformatted) { if (preformatted) {
message = `\`\`\`\n${message}\n\`\`\`` message = `\`\`\`\n${message}\n\`\`\``
} }
return message return message ?? ''
} }
export async function getMessageFromPath(searchPath: string) { export async function getMessageFromPath(searchPath: string) {
@ -74,3 +70,45 @@ export async function getMessageFromPath(searchPath: string) {
return message return message
} }
export function addMessageHeader(messageId: string, message: string) {
return `${messageId}\n\n${message}`
}
export function removeMessageHeader(message: string) {
return message.split('\n').slice(2).join('\n')
}
function splitFind(find: string) {
const matches = find.match(/\/((i|g|m|s|u|y){1,6})$/)
if (!matches) {
return {
regExp: find,
modifiers: 'gi',
}
}
const [, modifiers] = matches
const regExp = find.replace(modifiers, '').slice(0, -1)
return {
regExp,
modifiers,
}
}
export function findAndReplaceInMessage(
find: string[],
replace: string[],
original: string,
): string {
let message = original
for (const [i, f] of find.entries()) {
const { regExp, modifiers } = splitFind(f)
message = message.replace(new RegExp(regExp, modifiers), replace[i] ?? replace.join('\n'))
}
return message
}

View file

@ -1,3 +1,5 @@
import { Endpoints } from '@octokit/types'
export interface Inputs { export interface Inputs {
allowRepeats: boolean allowRepeats: boolean
attachPath?: string[] attachPath?: string[]
@ -6,6 +8,8 @@ export interface Inputs {
messageInput?: string messageInput?: string
messageId: string messageId: string
messagePath?: string messagePath?: string
messageFind?: string[]
messageReplace?: string[]
messageSuccess?: string messageSuccess?: string
messageFailure?: string messageFailure?: string
messageCancelled?: string messageCancelled?: string
@ -20,3 +24,11 @@ export interface Inputs {
owner: string owner: string
updateOnly: boolean updateOnly: boolean
} }
export type CreateIssueCommentResponseData =
Endpoints['POST /repos/{owner}/{repo}/issues/{issue_number}/comments']['response']['data']
export type ExistingIssueCommentResponseData =
Endpoints['GET /repos/{owner}/{repo}/issues/{issue_number}/comments']['response']['data'][0]
export type ExistingIssueComment = Pick<ExistingIssueCommentResponseData, 'id' | 'body'>