Compare commits

...

23 commits
v2.5.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
Michael Shick
a624db19fc
2.6.1 2023-05-05 16:09:45 -04:00
Michael Shick
475aa8ac2b
new build 2023-05-05 16:09:23 -04:00
Michael Shick
a8a22ad616
fix message fallback behavior 2023-05-05 16:09:03 -04:00
Michael Shick
9367dbbf15
2.6.0 2023-05-05 16:05:32 -04:00
Michael Shick
445bbc6324
new build 2023-05-05 16:05:09 -04:00
Michael Shick
a251f051d3
Preformatted messages (#97)
* refactor message creation
2023-05-05 16:02:36 -04:00
23 changed files with 1090 additions and 341 deletions

View file

@ -50,6 +50,15 @@
"contributions": [
"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,

View file

@ -62,6 +62,7 @@ jobs:
- uses: ./
with:
preformatted: true
message-id: path
message-path: |
.github/test/file-*.txt
@ -72,4 +73,14 @@ jobs:
message: |
**Hello**
🌏
!
!
- uses: ./
with:
message-id: text
find: |
Hello
🌏
replace: |
Goodnight
🌕

View file

@ -1 +1 @@
v16
v20

View file

@ -1,6 +1,6 @@
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
of this software and associated documentation files (the "Software"), to deal

137
README.md
View file

@ -2,7 +2,7 @@
<!-- 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 -->
@ -87,6 +87,9 @@ jobs:
| issue | with | Optional issue number override. | no | |
| 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 | |
| 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
@ -169,6 +172,135 @@ jobs:
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
@ -202,7 +334,7 @@ jobs:
issue: ${{ steps.pr.outputs.issue }}
message: |
**Howdie!**
````
```
## Contributors ✨
@ -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://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="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>
</tbody>
</table>

View file

@ -25,11 +25,13 @@ type Inputs = {
'repo-token': string
'message-id': string
'allow-repeats': string
'message-pattern'?: string
'message-success'?: string
'message-failure'?: string
'message-cancelled'?: string
'message-skipped'?: string
'update-only'?: string
preformatted?: string
status?: 'success' | 'failure' | 'cancelled' | 'skipped'
}
@ -41,6 +43,7 @@ const defaultInputs: Inputs = {
'repo-token': repoToken,
'message-id': 'add-pr-comment',
'allow-repeats': 'false',
status: 'success',
}
const defaultIssueNumber = 1
@ -93,55 +96,80 @@ const handlers = [
const server = setupServer(...handlers)
beforeAll(() => {
vi.spyOn(console, 'log').mockImplementation(() => {})
server.listen({ onUnhandledRequest: 'error' })
})
afterAll(() => server.close())
beforeEach(() => {
inputs = { ...defaultInputs }
issueNumber = defaultIssueNumber
messagePayload = undefined
vi.resetModules()
github.context.sha = commitSha
// https://developer.github.com/webhooks/event-payloads/#issues
github.context.payload = {
pull_request: {
number: issueNumber,
},
repository: {
full_name: `${inputs['repo-owner']}/${inputs['repo-name']}`,
name: 'bar',
owner: {
login: 'bar',
},
},
} as WebhookPayload
})
afterEach(() => {
vi.clearAllMocks()
server.resetHandlers()
})
const getInput = (name: string, options?: core.InputOptions) => {
const value = inputs[name] ?? ''
if (options?.required && value === undefined) {
throw new Error(`${name} is required`)
}
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', () => {
beforeAll(() => {
vi.spyOn(console, 'log').mockImplementation(() => {})
vi.spyOn(core, 'debug').mockImplementation(() => {})
vi.spyOn(core, 'info').mockImplementation(() => {})
vi.spyOn(core, 'warning').mockImplementation(() => {})
server.listen({ onUnhandledRequest: 'error' })
})
afterAll(() => server.close())
beforeEach(() => {
inputs = { ...defaultInputs }
issueNumber = defaultIssueNumber
messagePayload = undefined
vi.resetModules()
github.context.sha = commitSha
// https://developer.github.com/webhooks/event-payloads/#issues
github.context.payload = {
pull_request: {
number: issueNumber,
},
repository: {
full_name: `${inputs['repo-owner']}/${inputs['repo-name']}`,
name: 'bar',
owner: {
login: 'bar',
},
},
} as WebhookPayload
})
afterEach(() => {
vi.clearAllMocks()
server.resetHandlers()
})
vi.mocked(core.getInput).mockImplementation((name: string, options?: core.InputOptions) => {
const value = inputs[name] ?? ''
if (options?.required && value === undefined) {
throw new Error(`${name} is required`)
}
return value
})
it('creates a comment with message text', async () => {
inputs.message = simpleMessage
inputs['allow-repeats'] = 'true'
@ -269,7 +297,7 @@ describe('add-pr-comment action', () => {
it('creates a message when the message id does not exist', async () => {
inputs.message = simpleMessage
inputs['allow-repeats'] = 'false'
inputs['message-id'] = 'custom-id'
const replyBody = [
@ -287,7 +315,6 @@ describe('add-pr-comment action', () => {
it('identifies an existing message by id and updates it', async () => {
inputs.message = simpleMessage
inputs['allow-repeats'] = 'false'
const commentId = 123
@ -311,7 +338,7 @@ describe('add-pr-comment action', () => {
it('overrides the default message with a success message on success', async () => {
inputs.message = simpleMessage
inputs['allow-repeats'] = 'false'
inputs['message-success'] = '666'
inputs.status = 'success'
@ -332,7 +359,7 @@ describe('add-pr-comment action', () => {
it('overrides the default message with a failure message on failure', async () => {
inputs.message = simpleMessage
inputs['allow-repeats'] = 'false'
inputs['message-failure'] = '666'
inputs.status = 'failure'
@ -353,7 +380,7 @@ describe('add-pr-comment action', () => {
it('overrides the default message with a cancelled message on cancelled', async () => {
inputs.message = simpleMessage
inputs['allow-repeats'] = 'false'
inputs['message-cancelled'] = '666'
inputs.status = 'cancelled'
@ -374,7 +401,7 @@ describe('add-pr-comment action', () => {
it('overrides the default message with a skipped message on skipped', async () => {
inputs.message = simpleMessage
inputs['allow-repeats'] = 'false'
inputs['message-skipped'] = '666'
inputs.status = 'skipped'
@ -392,4 +419,212 @@ describe('add-pr-comment action', () => {
await run()
expect(messagePayload?.body).toContain('666')
})
it('wraps a message in a codeblock if preformatted is true', async () => {
inputs.message = undefined
inputs['preformatted'] = 'true'
inputs['message-path'] = messagePath1Fixture
await expect(run()).resolves.not.toThrow()
expect(
`<!-- add-pr-comment:add-pr-comment -->\n\n\`\`\`\n${messagePath1FixturePayload}\n\`\`\``,
).toEqual(messagePayload?.body)
expect(core.setOutput).toHaveBeenCalledWith('comment-created', 'true')
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

@ -56,6 +56,13 @@ inputs:
update-only:
description: "Only update the comment if it already exists."
required: false
preformatted:
description: "Treat message text (from a file or input) as pre-formatted and place it in a codeblock."
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:
comment-created:
description: "Whether a comment was created."
@ -67,5 +74,5 @@ branding:
icon: message-circle
color: purple
runs:
using: "node16"
using: "node20"
main: "dist/index.js"

315
dist/index.js vendored
View file

@ -7,8 +7,8 @@ require('./sourcemap-register.js');/******/ (() => { // webpackBootstrap
"use strict";
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.createComment = exports.deleteComment = exports.updateComment = exports.getExistingCommentId = void 0;
async function getExistingCommentId(octokit, owner, repo, issueNumber, messageId) {
exports.createComment = exports.deleteComment = exports.updateComment = exports.getExistingComment = void 0;
async function getExistingComment(octokit, owner, repo, issueNumber, messageId) {
const parameters = {
owner,
repo,
@ -25,9 +25,13 @@ async function getExistingCommentId(octokit, owner, repo, issueNumber, messageId
break;
}
}
return found === null || found === void 0 ? void 0 : found.id;
if (found) {
const { id, body } = found;
return { id, body };
}
return;
}
exports.getExistingCommentId = getExistingCommentId;
exports.getExistingComment = getExistingComment;
async function updateComment(octokit, owner, repo, existingCommentId, body) {
const updatedComment = await octokit.rest.issues.updateComment({
comment_id: existingCommentId,
@ -94,13 +98,14 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.getInputs = void 0;
const core = __importStar(__nccwpck_require__(2186));
const github = __importStar(__nccwpck_require__(5438));
const util_1 = __nccwpck_require__(4024);
async function getInputs() {
var _a, _b;
const messageIdInput = core.getInput('message-id', { required: false });
const messageId = messageIdInput === '' ? 'add-pr-comment' : `add-pr-comment:${messageIdInput}`;
const messageInput = core.getInput('message', { 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 repoName = core.getInput('repo-name', { required: true });
const repoToken = core.getInput('repo-token', { required: true });
@ -110,42 +115,29 @@ async function getInputs() {
const allowRepeats = core.getInput('allow-repeats', { required: true }) === 'true';
const refreshMessagePosition = core.getInput('refresh-message-position', { required: false }) === 'true';
const updateOnly = core.getInput('update-only', { required: false }) === 'true';
const preformatted = core.getInput('preformatted', { required: false }) === 'true';
if (messageInput && messagePath) {
throw new Error('must specify only one, message or message-path');
}
let message;
if (messagePath) {
message = await (0, util_1.getMessageFromPaths)(messagePath);
}
else {
message = messageInput;
}
const messageSuccess = core.getInput(`message-success`);
const messageFailure = core.getInput(`message-failure`);
const messageCancelled = core.getInput(`message-cancelled`);
const messageSkipped = core.getInput(`message-skipped`);
if (status === 'success' && messageSuccess) {
message = messageSuccess;
}
if (status === 'failure' && messageFailure) {
message = messageFailure;
}
if (status === 'cancelled' && messageCancelled) {
message = messageCancelled;
}
if (status === 'skipped' && messageSkipped) {
message = messageSkipped;
}
if (!message) {
throw new Error('no message, check your message inputs');
}
const { payload } = github.context;
return {
allowRepeats,
commitSha: github.context.sha,
issue: issue ? Number(issue) : (_a = payload.issue) === null || _a === void 0 ? void 0 : _a.number,
message,
messageInput,
messageId: `<!-- ${messageId} -->`,
messageSuccess,
messageFailure,
messageCancelled,
messageSkipped,
messagePath,
messageFind,
messageReplace,
preformatted,
proxyUrl,
pullRequestNumber: (_b = payload.pull_request) === null || _b === void 0 ? void 0 : _b.number,
refreshMessagePosition,
@ -159,6 +151,67 @@ async function getInputs() {
exports.getInputs = getInputs;
/***/ }),
/***/ 1743:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.findFiles = void 0;
const core = __importStar(__nccwpck_require__(2186));
const glob = __importStar(__nccwpck_require__(8090));
const promises_1 = __importDefault(__nccwpck_require__(3977));
async function findFiles(searchPath) {
const searchResults = [];
const globber = await glob.create(searchPath, {
followSymbolicLinks: true,
implicitDescendants: true,
omitBrokenSymbolicLinks: true,
});
const rawSearchResults = await globber.glob();
for (const searchResult of rawSearchResults) {
const fileStats = await promises_1.default.stat(searchResult);
if (!fileStats.isDirectory()) {
core.debug(`File: ${searchResult} was found using the provided searchPath`);
searchResults.push(searchResult);
}
else {
core.debug(`Removing ${searchResult} from rawSearchResults because it is a directory`);
}
}
return searchResults;
}
exports.findFiles = findFiles;
/***/ }),
/***/ 6962:
@ -216,11 +269,22 @@ const github = __importStar(__nccwpck_require__(5438));
const comments_1 = __nccwpck_require__(1910);
const config_1 = __nccwpck_require__(88);
const issues_1 = __nccwpck_require__(6962);
const message_1 = __nccwpck_require__(3307);
const proxy_1 = __nccwpck_require__(8689);
const run = async () => {
try {
const { allowRepeats, message, messageId, refreshMessagePosition, repoToken, proxyUrl, issue, pullRequestNumber, commitSha, repo, owner, updateOnly, } = 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);
let message = await (0, message_1.getMessage)({
messagePath,
messageInput,
messageSkipped,
messageCancelled,
messageSuccess,
messageFailure,
preformatted,
status,
});
let issueNumber;
if (issue) {
issueNumber = issue;
@ -237,25 +301,31 @@ const run = async () => {
core.setOutput('comment-created', 'false');
return;
}
let existingCommentId;
let existingComment;
if (!allowRepeats) {
core.debug('repeat comments are disallowed, checking for existing');
existingCommentId = await (0, comments_1.getExistingCommentId)(octokit, owner, repo, issueNumber, messageId);
if (existingCommentId) {
core.debug(`existing comment found with id: ${existingCommentId}`);
existingComment = await (0, comments_1.getExistingComment)(octokit, owner, repo, issueNumber, messageId);
if (existingComment) {
core.debug(`existing comment found with id: ${existingComment.id}`);
}
}
// 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.setOutput('comment-created', 'false');
return;
}
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) {
comment = await (0, proxy_1.createCommentProxy)({
commentId: existingCommentId,
commentId: existingComment === null || existingComment === void 0 ? void 0 : existingComment.id,
owner,
repo,
issueNumber,
@ -263,15 +333,15 @@ const run = async () => {
repoToken,
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) {
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);
}
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');
}
@ -304,6 +374,95 @@ if (process.env['NODE_ENV'] !== 'test') {
exports["default"] = run;
/***/ }),
/***/ 3307:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.findAndReplaceInMessage = exports.removeMessageHeader = exports.addMessageHeader = exports.getMessageFromPath = exports.getMessage = void 0;
const promises_1 = __importDefault(__nccwpck_require__(3977));
const files_1 = __nccwpck_require__(1743);
async function getMessage({ messageInput, messagePath, messageCancelled, messageSkipped, messageFailure, messageSuccess, preformatted, status, }) {
let message;
if (status === 'success' && messageSuccess) {
message = messageSuccess;
}
if (status === 'failure' && messageFailure) {
message = messageFailure;
}
if (status === 'cancelled' && messageCancelled) {
message = messageCancelled;
}
if (status === 'skipped' && messageSkipped) {
message = messageSkipped;
}
if (!message) {
if (messagePath) {
message = await getMessageFromPath(messagePath);
}
else {
message = messageInput;
}
}
if (preformatted) {
message = `\`\`\`\n${message}\n\`\`\``;
}
return message !== null && message !== void 0 ? message : '';
}
exports.getMessage = getMessage;
async function getMessageFromPath(searchPath) {
let message = '';
const files = await (0, files_1.findFiles)(searchPath);
for (const [index, path] of files.entries()) {
if (index > 0) {
message += '\n';
}
message += await promises_1.default.readFile(path, { encoding: 'utf8' });
}
return message;
}
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;
/***/ }),
/***/ 8689:
@ -325,82 +484,6 @@ async function createCommentProxy(params) {
exports.createCommentProxy = createCommentProxy;
/***/ }),
/***/ 4024:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.findFiles = exports.getMessageFromPaths = void 0;
const core = __importStar(__nccwpck_require__(2186));
const glob = __importStar(__nccwpck_require__(8090));
const promises_1 = __importDefault(__nccwpck_require__(3977));
async function getMessageFromPaths(searchPath) {
let message = '';
const files = await findFiles(searchPath);
for (const [index, path] of files.entries()) {
if (index > 0) {
message += '\n';
}
message += await promises_1.default.readFile(path, { encoding: 'utf8' });
}
return message;
}
exports.getMessageFromPaths = getMessageFromPaths;
function getDefaultGlobOptions() {
return {
followSymbolicLinks: true,
implicitDescendants: true,
omitBrokenSymbolicLinks: true,
};
}
async function findFiles(searchPath, globOptions) {
const searchResults = [];
const globber = await glob.create(searchPath, globOptions || getDefaultGlobOptions());
const rawSearchResults = await globber.glob();
for (const searchResult of rawSearchResults) {
const fileStats = await promises_1.default.stat(searchResult);
if (!fileStats.isDirectory()) {
core.debug(`File: ${searchResult} was found using the provided searchPath`);
searchResults.push(searchResult);
}
else {
core.debug(`Removing ${searchResult} from rawSearchResults because it is a directory`);
}
}
return searchResults;
}
exports.findFiles = findFiles;
/***/ }),
/***/ 7351:

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

View file

@ -26,13 +26,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
exports.getInputs = void 0;
const core = __importStar(require("@actions/core"));
const github = __importStar(require("@actions/github"));
const util_1 = require("./util");
async function getInputs() {
var _a, _b;
const messageIdInput = core.getInput('message-id', { required: false });
const messageId = messageIdInput === '' ? 'add-pr-comment' : `add-pr-comment:${messageIdInput}`;
const messageInput = core.getInput('message', { 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 repoName = core.getInput('repo-name', { required: true });
const repoToken = core.getInput('repo-token', { required: true });
@ -42,42 +43,29 @@ async function getInputs() {
const allowRepeats = core.getInput('allow-repeats', { required: true }) === 'true';
const refreshMessagePosition = core.getInput('refresh-message-position', { required: false }) === 'true';
const updateOnly = core.getInput('update-only', { required: false }) === 'true';
const preformatted = core.getInput('preformatted', { required: false }) === 'true';
if (messageInput && messagePath) {
throw new Error('must specify only one, message or message-path');
}
let message;
if (messagePath) {
message = await (0, util_1.getMessageFromPaths)(messagePath);
}
else {
message = messageInput;
}
const messageSuccess = core.getInput(`message-success`);
const messageFailure = core.getInput(`message-failure`);
const messageCancelled = core.getInput(`message-cancelled`);
const messageSkipped = core.getInput(`message-skipped`);
if (status === 'success' && messageSuccess) {
message = messageSuccess;
}
if (status === 'failure' && messageFailure) {
message = messageFailure;
}
if (status === 'cancelled' && messageCancelled) {
message = messageCancelled;
}
if (status === 'skipped' && messageSkipped) {
message = messageSkipped;
}
if (!message) {
throw new Error('no message, check your message inputs');
}
const { payload } = github.context;
return {
allowRepeats,
commitSha: github.context.sha,
issue: issue ? Number(issue) : (_a = payload.issue) === null || _a === void 0 ? void 0 : _a.number,
message,
messageInput,
messageId: `<!-- ${messageId} -->`,
messageSuccess,
messageFailure,
messageCancelled,
messageSkipped,
messagePath,
messageFind,
messageReplace,
preformatted,
proxyUrl,
pullRequestNumber: (_b = payload.pull_request) === null || _b === void 0 ? void 0 : _b.number,
refreshMessagePosition,

53
lib/files.js Normal file
View file

@ -0,0 +1,53 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.findFiles = void 0;
const core = __importStar(require("@actions/core"));
const glob = __importStar(require("@actions/glob"));
const promises_1 = __importDefault(require("node:fs/promises"));
async function findFiles(searchPath) {
const searchResults = [];
const globber = await glob.create(searchPath, {
followSymbolicLinks: true,
implicitDescendants: true,
omitBrokenSymbolicLinks: true,
});
const rawSearchResults = await globber.glob();
for (const searchResult of rawSearchResults) {
const fileStats = await promises_1.default.stat(searchResult);
if (!fileStats.isDirectory()) {
core.debug(`File: ${searchResult} was found using the provided searchPath`);
searchResults.push(searchResult);
}
else {
core.debug(`Removing ${searchResult} from rawSearchResults because it is a directory`);
}
}
return searchResults;
}
exports.findFiles = findFiles;

View file

@ -28,11 +28,22 @@ const github = __importStar(require("@actions/github"));
const comments_1 = require("./comments");
const config_1 = require("./config");
const issues_1 = require("./issues");
const message_1 = require("./message");
const proxy_1 = require("./proxy");
const run = async () => {
try {
const { allowRepeats, message, messageId, refreshMessagePosition, repoToken, proxyUrl, issue, pullRequestNumber, commitSha, repo, owner, updateOnly, } = 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);
let message = await (0, message_1.getMessage)({
messagePath,
messageInput,
messageSkipped,
messageCancelled,
messageSuccess,
messageFailure,
preformatted,
status,
});
let issueNumber;
if (issue) {
issueNumber = issue;
@ -49,25 +60,31 @@ const run = async () => {
core.setOutput('comment-created', 'false');
return;
}
let existingCommentId;
let existingComment;
if (!allowRepeats) {
core.debug('repeat comments are disallowed, checking for existing');
existingCommentId = await (0, comments_1.getExistingCommentId)(octokit, owner, repo, issueNumber, messageId);
if (existingCommentId) {
core.debug(`existing comment found with id: ${existingCommentId}`);
existingComment = await (0, comments_1.getExistingComment)(octokit, owner, repo, issueNumber, messageId);
if (existingComment) {
core.debug(`existing comment found with id: ${existingComment.id}`);
}
}
// 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.setOutput('comment-created', 'false');
return;
}
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) {
comment = await (0, proxy_1.createCommentProxy)({
commentId: existingCommentId,
commentId: existingComment === null || existingComment === void 0 ? void 0 : existingComment.id,
owner,
repo,
issueNumber,
@ -75,15 +92,15 @@ const run = async () => {
repoToken,
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) {
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);
}
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');
}

81
lib/message.js Normal file
View file

@ -0,0 +1,81 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.findAndReplaceInMessage = exports.removeMessageHeader = exports.addMessageHeader = exports.getMessageFromPath = exports.getMessage = void 0;
const promises_1 = __importDefault(require("node:fs/promises"));
const files_1 = require("./files");
async function getMessage({ messageInput, messagePath, messageCancelled, messageSkipped, messageFailure, messageSuccess, preformatted, status, }) {
let message;
if (status === 'success' && messageSuccess) {
message = messageSuccess;
}
if (status === 'failure' && messageFailure) {
message = messageFailure;
}
if (status === 'cancelled' && messageCancelled) {
message = messageCancelled;
}
if (status === 'skipped' && messageSkipped) {
message = messageSkipped;
}
if (!message) {
if (messagePath) {
message = await getMessageFromPath(messagePath);
}
else {
message = messageInput;
}
}
if (preformatted) {
message = `\`\`\`\n${message}\n\`\`\``;
}
return message !== null && message !== void 0 ? message : '';
}
exports.getMessage = getMessage;
async function getMessageFromPath(searchPath) {
let message = '';
const files = await (0, files_1.findFiles)(searchPath);
for (const [index, path] of files.entries()) {
if (index > 0) {
message += '\n';
}
message += await promises_1.default.readFile(path, { encoding: 'utf8' });
}
return message;
}
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
lib/types.js Normal file
View file

@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

32
package-lock.json generated
View file

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

View file

@ -1,6 +1,6 @@
{
"name": "@mshick/add-pr-comment",
"version": "2.5.1",
"version": "2.8.2",
"description": "A GitHub Action which adds a comment to a Pull Request Issue.",
"keywords": [
"GitHub",
@ -25,9 +25,9 @@
"build": "del-cli dist && tsc && ncc build --source-map",
"clean": "rm -rf node_modules dist package-lock.json __tests__/runner/**/*",
"lint": "eslint src/**/*.ts",
"prepare": "npm run build && git add lib dist",
"release": "npm run build && np --no-publish",
"test": "vitest run",
"version": "npm run build && git add lib dist",
"watch": "vitest"
},
"prettier": {
@ -148,6 +148,6 @@
"vitest": "^0.30.1"
},
"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 { Endpoints } from '@octokit/types'
import {
CreateIssueCommentResponseData,
ExistingIssueComment,
ExistingIssueCommentResponseData,
} from './types'
export type CreateIssueCommentResponseData =
Endpoints['POST /repos/{owner}/{repo}/issues/{issue_number}/comments']['response']['data']
export async function getExistingCommentId(
export async function getExistingComment(
octokit: InstanceType<typeof GitHub>,
owner: string,
repo: string,
issueNumber: number,
messageId: string,
): Promise<number | undefined> {
): Promise<ExistingIssueComment | undefined> {
const parameters = {
owner,
repo,
@ -18,7 +19,7 @@ export async function getExistingCommentId(
per_page: 100,
}
let found
let found: ExistingIssueCommentResponseData | undefined
for await (const comments of octokit.paginate.iterator(
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(

View file

@ -1,33 +1,14 @@
import * as core from '@actions/core'
import * as github from '@actions/github'
import { getMessageFromPaths } from './util'
interface Inputs {
allowRepeats: boolean
attachPath?: string[]
commitSha: string
issue?: number
message?: string
messageId: string
messagePath?: string
messageSuccess?: string
messageFailure?: string
messageCancelled?: string
proxyUrl?: string
pullRequestNumber?: number
refreshMessagePosition: boolean
repo: string
repoToken: string
status?: string
owner: string
updateOnly: boolean
}
import { Inputs } from './types'
export async function getInputs(): Promise<Inputs> {
const messageIdInput = core.getInput('message-id', { required: false })
const messageId = messageIdInput === '' ? 'add-pr-comment' : `add-pr-comment:${messageIdInput}`
const messageInput = core.getInput('message', { 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 repoName = core.getInput('repo-name', { required: true })
const repoToken = core.getInput('repo-token', { required: true })
@ -38,52 +19,33 @@ export async function getInputs(): Promise<Inputs> {
const refreshMessagePosition =
core.getInput('refresh-message-position', { required: false }) === 'true'
const updateOnly = core.getInput('update-only', { required: false }) === 'true'
const preformatted = core.getInput('preformatted', { required: false }) === 'true'
if (messageInput && messagePath) {
throw new Error('must specify only one, message or message-path')
}
let message
if (messagePath) {
message = await getMessageFromPaths(messagePath)
} else {
message = messageInput
}
const messageSuccess = core.getInput(`message-success`)
const messageFailure = core.getInput(`message-failure`)
const messageCancelled = core.getInput(`message-cancelled`)
const messageSkipped = core.getInput(`message-skipped`)
if (status === 'success' && messageSuccess) {
message = messageSuccess
}
if (status === 'failure' && messageFailure) {
message = messageFailure
}
if (status === 'cancelled' && messageCancelled) {
message = messageCancelled
}
if (status === 'skipped' && messageSkipped) {
message = messageSkipped
}
if (!message) {
throw new Error('no message, check your message inputs')
}
const { payload } = github.context
return {
allowRepeats,
commitSha: github.context.sha,
issue: issue ? Number(issue) : payload.issue?.number,
message,
messageInput,
messageId: `<!-- ${messageId} -->`,
messageSuccess,
messageFailure,
messageCancelled,
messageSkipped,
messagePath,
messageFind,
messageReplace,
preformatted,
proxyUrl,
pullRequestNumber: payload.pull_request?.number,
refreshMessagePosition,

View file

@ -2,36 +2,13 @@ import * as core from '@actions/core'
import * as glob from '@actions/glob'
import fs from 'node:fs/promises'
export async function getMessageFromPaths(searchPath: string) {
let message = ''
const files = await findFiles(searchPath)
for (const [index, path] of files.entries()) {
if (index > 0) {
message += '\n'
}
message += await fs.readFile(path, { encoding: 'utf8' })
}
return message
}
function getDefaultGlobOptions(): glob.GlobOptions {
return {
export async function findFiles(searchPath: string): Promise<string[]> {
const searchResults: string[] = []
const globber = await glob.create(searchPath, {
followSymbolicLinks: true,
implicitDescendants: true,
omitBrokenSymbolicLinks: true,
}
}
export async function findFiles(
searchPath: string,
globOptions?: glob.GlobOptions,
): Promise<string[]> {
const searchResults: string[] = []
const globber = await glob.create(searchPath, globOptions || getDefaultGlobOptions())
})
const rawSearchResults: string[] = await globber.glob()
for (const searchResult of rawSearchResults) {

View file

@ -1,21 +1,23 @@
import * as core from '@actions/core'
import * as github from '@actions/github'
import {
CreateIssueCommentResponseData,
createComment,
deleteComment,
getExistingCommentId,
updateComment,
} from './comments'
import { createComment, deleteComment, getExistingComment, updateComment } from './comments'
import { getInputs } from './config'
import { getIssueNumberFromCommitPullsList } from './issues'
import {
addMessageHeader,
findAndReplaceInMessage,
getMessage,
removeMessageHeader,
} from './message'
import { createCommentProxy } from './proxy'
import { CreateIssueCommentResponseData, ExistingIssueComment } from './types'
const run = async (): Promise<void> => {
try {
const {
allowRepeats,
message,
messagePath,
messageInput,
messageId,
refreshMessagePosition,
repoToken,
@ -26,10 +28,29 @@ const run = async (): Promise<void> => {
repo,
owner,
updateOnly,
messageCancelled,
messageFailure,
messageSuccess,
messageSkipped,
preformatted,
status,
messageFind,
messageReplace,
} = await getInputs()
const octokit = github.getOctokit(repoToken)
let message = await getMessage({
messagePath,
messageInput,
messageSkipped,
messageCancelled,
messageSuccess,
messageFailure,
preformatted,
status,
})
let issueNumber
if (issue) {
@ -49,20 +70,20 @@ const run = async (): Promise<void> => {
return
}
let existingCommentId
let existingComment: ExistingIssueComment | undefined
if (!allowRepeats) {
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) {
core.debug(`existing comment found with id: ${existingCommentId}`)
if (existingComment) {
core.debug(`existing comment found with id: ${existingComment.id}`)
}
}
// 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.setOutput('comment-created', 'false')
return
@ -70,11 +91,23 @@ const run = async (): Promise<void> => {
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) {
comment = await createCommentProxy({
commentId: existingCommentId,
commentId: existingComment?.id,
owner,
repo,
issueNumber,
@ -82,13 +115,13 @@ const run = async (): Promise<void> => {
repoToken,
proxyUrl,
})
core.setOutput(existingCommentId ? 'comment-updated' : 'comment-created', 'true')
} else if (existingCommentId) {
core.setOutput(existingComment?.id ? 'comment-updated' : 'comment-created', 'true')
} else if (existingComment?.id) {
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)
} else {
comment = await updateComment(octokit, owner, repo, existingCommentId, body)
comment = await updateComment(octokit, owner, repo, existingComment.id, body)
}
core.setOutput('comment-updated', 'true')
} else {

114
src/message.ts Normal file
View file

@ -0,0 +1,114 @@
import fs from 'node:fs/promises'
import { findFiles } from './files'
import { Inputs } from './types'
export async function getMessage({
messageInput,
messagePath,
messageCancelled,
messageSkipped,
messageFailure,
messageSuccess,
preformatted,
status,
}: Pick<
Inputs,
| 'messageInput'
| 'messageCancelled'
| 'messageSuccess'
| 'messageFailure'
| 'messageSkipped'
| 'messagePath'
| 'preformatted'
| 'status'
>): Promise<string> {
let message
if (status === 'success' && messageSuccess) {
message = messageSuccess
}
if (status === 'failure' && messageFailure) {
message = messageFailure
}
if (status === 'cancelled' && messageCancelled) {
message = messageCancelled
}
if (status === 'skipped' && messageSkipped) {
message = messageSkipped
}
if (!message) {
if (messagePath) {
message = await getMessageFromPath(messagePath)
} else {
message = messageInput
}
}
if (preformatted) {
message = `\`\`\`\n${message}\n\`\`\``
}
return message ?? ''
}
export async function getMessageFromPath(searchPath: string) {
let message = ''
const files = await findFiles(searchPath)
for (const [index, path] of files.entries()) {
if (index > 0) {
message += '\n'
}
message += await fs.readFile(path, { encoding: 'utf8' })
}
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
}

34
src/types.ts Normal file
View file

@ -0,0 +1,34 @@
import { Endpoints } from '@octokit/types'
export interface Inputs {
allowRepeats: boolean
attachPath?: string[]
commitSha: string
issue?: number
messageInput?: string
messageId: string
messagePath?: string
messageFind?: string[]
messageReplace?: string[]
messageSuccess?: string
messageFailure?: string
messageCancelled?: string
messageSkipped?: string
preformatted: boolean
proxyUrl?: string
pullRequestNumber?: number
refreshMessagePosition: boolean
repo: string
repoToken: string
status: string
owner: string
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'>