mirror of
https://github.com/mshick/add-pr-comment.git
synced 2026-01-02 14:59:43 +11:00
parent
5cc4621415
commit
f8c324a9fc
9 changed files with 502 additions and 90 deletions
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
|
|
@ -74,3 +74,13 @@ jobs:
|
||||||
**Hello**
|
**Hello**
|
||||||
🌏
|
🌏
|
||||||
!
|
!
|
||||||
|
|
||||||
|
- uses: ./
|
||||||
|
with:
|
||||||
|
message-id: text
|
||||||
|
find: |
|
||||||
|
Hello
|
||||||
|
🌏
|
||||||
|
replace: |
|
||||||
|
Goodnight
|
||||||
|
🌕
|
||||||
|
|
|
||||||
132
README.md
132
README.md
|
|
@ -1,7 +1,9 @@
|
||||||
# add-pr-comment
|
# add-pr-comment
|
||||||
|
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||||
|
|
||||||
[](#contributors-)
|
[](#contributors-)
|
||||||
|
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||||
|
|
||||||
A GitHub Action which adds a comment to a pull request's issue.
|
A GitHub Action which adds a comment to a pull request's issue.
|
||||||
|
|
@ -168,6 +170,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
|
||||||
|
|
|
||||||
|
|
@ -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,17 +96,16 @@ 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, 'debug').mockImplementation(() => {})
|
||||||
// vi.spyOn(core, 'info').mockImplementation(() => {})
|
// vi.spyOn(core, 'info').mockImplementation(() => {})
|
||||||
// vi.spyOn(core, 'warning').mockImplementation(() => {})
|
// vi.spyOn(core, 'warning').mockImplementation(() => {})
|
||||||
server.listen({ onUnhandledRequest: 'error' })
|
server.listen({ onUnhandledRequest: 'error' })
|
||||||
})
|
})
|
||||||
afterAll(() => server.close())
|
afterAll(() => server.close())
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
inputs = { ...defaultInputs }
|
inputs = { ...defaultInputs }
|
||||||
issueNumber = defaultIssueNumber
|
issueNumber = defaultIssueNumber
|
||||||
messagePayload = undefined
|
messagePayload = undefined
|
||||||
|
|
@ -127,14 +127,14 @@ describe('add-pr-comment action', () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as WebhookPayload
|
} as WebhookPayload
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
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 +142,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 +300,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 +318,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 +341,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 +362,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 +383,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 +404,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 +436,169 @@ 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 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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,8 @@ 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
|
||||||
|
message-pattern:
|
||||||
|
description: "A regular expression to find and replace using the evaluated message."
|
||||||
outputs:
|
outputs:
|
||||||
comment-created:
|
comment-created:
|
||||||
description: "Whether a comment was created."
|
description: "Whether a comment was created."
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
54
src/main.ts
54
src/main.ts
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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[],
|
||||||
|
replacement: 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), replacement[i] ?? replacement[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
|
||||||
12
src/types.ts
12
src/types.ts
|
|
@ -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'>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue