mirror of
https://github.com/mshick/add-pr-comment.git
synced 2026-01-08 00:20:09 +11:00
Compare commits
55 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd126dd8c2 | ||
|
|
30602c8f56 | ||
|
|
f24a409f6b | ||
|
|
b8f338c590 | ||
|
|
74e66d7778 | ||
|
|
8fedd701c5 | ||
|
|
7c0890544f | ||
|
|
12282e9c93 | ||
|
|
918f138773 | ||
|
|
84c8c4f13e | ||
|
|
445c144052 | ||
|
|
ff82b38f95 | ||
|
|
25e7c93662 | ||
|
|
09331f990d | ||
|
|
06b07c2e70 | ||
|
|
f8c324a9fc | ||
|
|
5cc4621415 | ||
|
|
a624db19fc | ||
|
|
475aa8ac2b | ||
|
|
a8a22ad616 | ||
|
|
9367dbbf15 | ||
|
|
445bbc6324 | ||
|
|
a251f051d3 | ||
|
|
ef723874d4 | ||
|
|
f593e19b25 | ||
|
|
3db21c2292 | ||
|
|
7f44ca3b15 | ||
|
|
d0b6b97ab0 | ||
|
|
73ffb32342 | ||
|
|
4a541a260f | ||
|
|
a0c6c0cbf4 | ||
|
|
75ab356ce1 | ||
|
|
bafc97d9ce | ||
|
|
442e4f9c93 | ||
|
|
3a15c7386b | ||
|
|
f0a003891a | ||
|
|
1dff58b1a3 | ||
|
|
387ece43e3 | ||
|
|
84295c55d2 | ||
|
|
99718eaf16 | ||
|
|
a02677ce9e | ||
|
|
655ef16eab | ||
|
|
484efd8915 | ||
|
|
1ea82c5459 | ||
|
|
378906227c | ||
|
|
71ddf4a572 | ||
|
|
0baab3bf91 | ||
|
|
a5ba281881 | ||
|
|
0e8dc756bc | ||
|
|
1605572889 | ||
|
|
7ca8398d28 | ||
|
|
9c1e156588 | ||
|
|
7ca909c028 | ||
|
|
747b5a722c | ||
|
|
9412131e1e |
31 changed files with 7939 additions and 10936 deletions
70
.all-contributorsrc
Normal file
70
.all-contributorsrc
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"README.md"
|
||||||
|
],
|
||||||
|
"imageSize": 100,
|
||||||
|
"commit": false,
|
||||||
|
"commitConvention": "angular",
|
||||||
|
"contributors": [
|
||||||
|
{
|
||||||
|
"login": "ReenigneArcher",
|
||||||
|
"name": "ReenigneArcher",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/42013603?v=4",
|
||||||
|
"profile": "https://app.lizardbyte.dev",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "aryella-lacerda",
|
||||||
|
"name": "Aryella Lacerda",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/28730324?v=4",
|
||||||
|
"profile": "https://github.com/aryella-lacerda",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "vincent-joignie-dd",
|
||||||
|
"name": "vincent-joignie-dd",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/103102299?v=4",
|
||||||
|
"profile": "https://github.com/vincent-joignie-dd",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "ahanoff",
|
||||||
|
"name": "Akhan Zhakiyanov",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/2371703?v=4",
|
||||||
|
"profile": "https://ahanoff.dev",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "ahatzz11",
|
||||||
|
"name": "Alex Hatzenbuhler",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/6256032?v=4",
|
||||||
|
"profile": "https://github.com/ahatzz11",
|
||||||
|
"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,
|
||||||
|
"skipCi": true,
|
||||||
|
"repoType": "github",
|
||||||
|
"repoHost": "https://github.com",
|
||||||
|
"projectName": "add-pr-comment",
|
||||||
|
"projectOwner": "mshick"
|
||||||
|
}
|
||||||
1
.github/test/file-1.txt
vendored
Normal file
1
.github/test/file-1.txt
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Hello
|
||||||
1
.github/test/file-2.txt
vendored
Normal file
1
.github/test/file-2.txt
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Goodbye
|
||||||
BIN
.github/test/image.png
vendored
Normal file
BIN
.github/test/image.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
24
.github/workflows/ci.yml
vendored
24
.github/workflows/ci.yml
vendored
|
|
@ -2,6 +2,10 @@ name: ci
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- synchronize
|
||||||
|
- reopened
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
|
|
@ -58,7 +62,25 @@ jobs:
|
||||||
|
|
||||||
- uses: ./
|
- uses: ./
|
||||||
with:
|
with:
|
||||||
|
preformatted: true
|
||||||
|
message-id: path
|
||||||
|
message-path: |
|
||||||
|
.github/test/file-*.txt
|
||||||
|
|
||||||
|
- uses: ./
|
||||||
|
with:
|
||||||
|
message-id: text
|
||||||
message: |
|
message: |
|
||||||
**Hello ${{ github.run_number }}**
|
**Hello**
|
||||||
🌏
|
🌏
|
||||||
!
|
!
|
||||||
|
|
||||||
|
- uses: ./
|
||||||
|
with:
|
||||||
|
message-id: text
|
||||||
|
find: |
|
||||||
|
Hello
|
||||||
|
🌏
|
||||||
|
replace: |
|
||||||
|
Goodnight
|
||||||
|
🌕
|
||||||
|
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -7,6 +7,7 @@ node_modules/
|
||||||
|
|
||||||
# Editors
|
# Editors
|
||||||
.vscode
|
.vscode
|
||||||
|
.idea/**
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
v16
|
v20
|
||||||
|
|
|
||||||
2
LICENSE
2
LICENSE
|
|
@ -1,6 +1,6 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2019 Michael Shick
|
Copyright (c) 2024 Michael Shick
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
|
||||||
198
README.md
198
README.md
|
|
@ -1,5 +1,11 @@
|
||||||
# add-pr-comment
|
# add-pr-comment
|
||||||
|
|
||||||
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||||
|
|
||||||
|
[](#contributors-)
|
||||||
|
|
||||||
|
<!-- 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.
|
||||||
|
|
||||||
This actions also works on [issue](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#issues),
|
This actions also works on [issue](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#issues),
|
||||||
|
|
@ -63,21 +69,27 @@ jobs:
|
||||||
## Configuration options
|
## Configuration options
|
||||||
|
|
||||||
| Input | Location | Description | Required | Default |
|
| Input | Location | Description | Required | Default |
|
||||||
| ------------------------ | -------- | ---------------------------------------------------------------------------------------------------- | -------- | ------------------ |
|
| ------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------------------------------- |
|
||||||
| message | with | The message you'd like displayed, supports Markdown and all valid Unicode characters. | maybe | |
|
| message | with | The message you'd like displayed, supports Markdown and all valid Unicode characters. | maybe | |
|
||||||
| message-path | with | Path to a message you'd like displayed. Will be read and displayed just like a normal message. | maybe | |
|
| message-path | with | Path to a message you'd like displayed. Will be read and displayed just like a normal message. Supports multi-line input and globs. Multiple messages will be concatenated. | maybe | |
|
||||||
| message-success | with | A message override, printed in case of success. | no | |
|
| message-success | with | A message override, printed in case of success. | no | |
|
||||||
| message-failure | with | A message override, printed in case of failure. | no | |
|
| message-failure | with | A message override, printed in case of failure. | no | |
|
||||||
| message-cancelled | with | A message override, printed in case of cancelled. | no | |
|
| message-cancelled | with | A message override, printed in case of cancelled. | no | |
|
||||||
| message-skipped | with | A message override, printed in case of skipped. | no | |
|
| message-skipped | with | A message override, printed in case of skipped. | no | |
|
||||||
| status | with | Required if you want to use message status overrides. | no | {{ job.status }} |
|
| status | with | Required if you want to use message status overrides. | no | {{ job.status }} |
|
||||||
|
| repo-owner | with | Owner of the repo. | no | {{ github.repository_owner }} |
|
||||||
|
| repo-name | with | Name of the repo. | no | {{ github.event.repository.name }} |
|
||||||
| repo-token | with | Valid GitHub token, either the temporary token GitHub provides or a personal access token. | no | {{ github.token }} |
|
| repo-token | with | Valid GitHub token, either the temporary token GitHub provides or a personal access token. | no | {{ github.token }} |
|
||||||
| message-id | with | Message id to use when searching existing comments. If found, updates the existing (sticky comment). | no | |
|
| message-id | with | Message id to use when searching existing comments. If found, updates the existing (sticky comment). | no | |
|
||||||
| refresh-message-position | with | Should the sticky message be the last one in the PR's feed. | no | false |
|
| refresh-message-position | with | Should the sticky message be the last one in the PR's feed. | no | false |
|
||||||
| allow-repeats | with | Boolean flag to allow identical messages to be posted each time this action is run. | no | false |
|
| allow-repeats | with | Boolean flag to allow identical messages to be posted each time this action is run. | no | false |
|
||||||
| proxy-url | with | String for your proxy service URL if you'd like this to work with fork-based PRs. | no | |
|
| proxy-url | with | String for your proxy service URL if you'd like this to work with fork-based PRs. | no | |
|
||||||
| issue | with | Optional issue number override. | no | |
|
| 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 | |
|
| 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
|
## Advanced Uses
|
||||||
|
|
||||||
|
|
@ -135,6 +147,161 @@ jobs:
|
||||||
Uh oh!
|
Uh oh!
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Multiple Message Files
|
||||||
|
|
||||||
|
Instead of directly setting the message you can also load a file with the text
|
||||||
|
of your message using `message-path`. `message-path` supports loading multiple
|
||||||
|
files and files on multiple lines, the contents of which will be concatenated.
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```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-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
|
||||||
|
|
@ -168,3 +335,30 @@ jobs:
|
||||||
message: |
|
message: |
|
||||||
**Howdie!**
|
**Howdie!**
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Contributors ✨
|
||||||
|
|
||||||
|
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
||||||
|
|
||||||
|
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
|
<!-- markdownlint-disable -->
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://app.lizardbyte.dev"><img src="https://avatars.githubusercontent.com/u/42013603?v=4?s=100" width="100px;" alt="ReenigneArcher"/><br /><sub><b>ReenigneArcher</b></sub></a><br /><a href="https://github.com/mshick/add-pr-comment/commits?author=ReenigneArcher" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/aryella-lacerda"><img src="https://avatars.githubusercontent.com/u/28730324?v=4?s=100" width="100px;" alt="Aryella Lacerda"/><br /><sub><b>Aryella Lacerda</b></sub></a><br /><a href="https://github.com/mshick/add-pr-comment/commits?author=aryella-lacerda" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/vincent-joignie-dd"><img src="https://avatars.githubusercontent.com/u/103102299?v=4?s=100" width="100px;" alt="vincent-joignie-dd"/><br /><sub><b>vincent-joignie-dd</b></sub></a><br /><a href="https://github.com/mshick/add-pr-comment/commits?author=vincent-joignie-dd" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://ahanoff.dev"><img src="https://avatars.githubusercontent.com/u/2371703?v=4?s=100" width="100px;" alt="Akhan Zhakiyanov"/><br /><sub><b>Akhan Zhakiyanov</b></sub></a><br /><a href="https://github.com/mshick/add-pr-comment/commits?author=ahanoff" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://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>
|
||||||
|
|
||||||
|
<!-- markdownlint-restore -->
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
|
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||||
|
|
||||||
|
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,16 @@ import * as github from '@actions/github'
|
||||||
import { WebhookPayload } from '@actions/github/lib/interfaces'
|
import { WebhookPayload } from '@actions/github/lib/interfaces'
|
||||||
import { rest } from 'msw'
|
import { rest } from 'msw'
|
||||||
import { setupServer } from 'msw/node'
|
import { setupServer } from 'msw/node'
|
||||||
|
import * as fs from 'node:fs/promises'
|
||||||
import * as path from 'node:path'
|
import * as path from 'node:path'
|
||||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
|
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
import run from '../src/main'
|
import run from '../src/main'
|
||||||
import apiResponse from './sample-pulls-api-response.json'
|
import apiResponse from './sample-pulls-api-response.json'
|
||||||
|
|
||||||
const repoFullName = 'foo/bar'
|
const messagePath1Fixture = path.resolve(__dirname, './message-part-1.txt')
|
||||||
|
const messagePath1FixturePayload = await fs.readFile(messagePath1Fixture, 'utf-8')
|
||||||
|
const messagePath2Fixture = path.resolve(__dirname, './message-part-2.txt')
|
||||||
|
|
||||||
const repoToken = '12345'
|
const repoToken = '12345'
|
||||||
const commitSha = 'abc123'
|
const commitSha = 'abc123'
|
||||||
const simpleMessage = 'hello world'
|
const simpleMessage = 'hello world'
|
||||||
|
|
@ -16,25 +20,36 @@ const simpleMessage = 'hello world'
|
||||||
type Inputs = {
|
type Inputs = {
|
||||||
message: string | undefined
|
message: string | undefined
|
||||||
'message-path': string | undefined
|
'message-path': string | undefined
|
||||||
|
'repo-owner': string
|
||||||
|
'repo-name': string
|
||||||
'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
|
||||||
'message-skipped'?: string
|
'message-skipped'?: string
|
||||||
|
'update-only'?: string
|
||||||
|
preformatted?: string
|
||||||
status?: 'success' | 'failure' | 'cancelled' | 'skipped'
|
status?: 'success' | 'failure' | 'cancelled' | 'skipped'
|
||||||
}
|
}
|
||||||
|
|
||||||
const inputs: Inputs = {
|
const defaultInputs: Inputs = {
|
||||||
message: '',
|
message: '',
|
||||||
'message-path': undefined,
|
'message-path': undefined,
|
||||||
'repo-token': '',
|
'repo-owner': 'foo',
|
||||||
|
'repo-name': 'bar',
|
||||||
|
'repo-token': repoToken,
|
||||||
'message-id': 'add-pr-comment',
|
'message-id': 'add-pr-comment',
|
||||||
'allow-repeats': 'false',
|
'allow-repeats': 'false',
|
||||||
|
status: 'success',
|
||||||
}
|
}
|
||||||
|
|
||||||
let issueNumber = 1
|
const defaultIssueNumber = 1
|
||||||
|
|
||||||
|
let inputs = defaultInputs
|
||||||
|
let issueNumber = defaultIssueNumber
|
||||||
let getCommitPullsResponse
|
let getCommitPullsResponse
|
||||||
let getIssueCommentsResponse
|
let getIssueCommentsResponse
|
||||||
let postIssueCommentsResponse = {
|
let postIssueCommentsResponse = {
|
||||||
|
|
@ -50,29 +65,29 @@ let messagePayload: MessagePayload | undefined
|
||||||
|
|
||||||
vi.mock('@actions/core')
|
vi.mock('@actions/core')
|
||||||
|
|
||||||
export const handlers = [
|
const handlers = [
|
||||||
rest.post(
|
rest.post(
|
||||||
`https://api.github.com/repos/${repoFullName}/issues/:issueNumber/comments`,
|
`https://api.github.com/repos/:repoUser/:repoName/issues/:issueNumber/comments`,
|
||||||
async (req, res, ctx) => {
|
async (req, res, ctx) => {
|
||||||
messagePayload = await req.json<MessagePayload>()
|
messagePayload = await req.json<MessagePayload>()
|
||||||
return res(ctx.status(200), ctx.json(postIssueCommentsResponse))
|
return res(ctx.status(200), ctx.json(postIssueCommentsResponse))
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
rest.patch(
|
rest.patch(
|
||||||
`https://api.github.com/repos/${repoFullName}/issues/comments/:commentId`,
|
`https://api.github.com/repos/:repoUser/:repoName/issues/comments/:commentId`,
|
||||||
async (req, res, ctx) => {
|
async (req, res, ctx) => {
|
||||||
messagePayload = await req.json<MessagePayload>()
|
messagePayload = await req.json<MessagePayload>()
|
||||||
return res(ctx.status(200), ctx.json(postIssueCommentsResponse))
|
return res(ctx.status(200), ctx.json(postIssueCommentsResponse))
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
rest.get(
|
rest.get(
|
||||||
`https://api.github.com/repos/${repoFullName}/issues/:issueNumber/comments`,
|
`https://api.github.com/repos/:repoUser/:repoName/issues/:issueNumber/comments`,
|
||||||
(req, res, ctx) => {
|
(req, res, ctx) => {
|
||||||
return res(ctx.status(200), ctx.json(getIssueCommentsResponse))
|
return res(ctx.status(200), ctx.json(getIssueCommentsResponse))
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
rest.get(
|
rest.get(
|
||||||
`https://api.github.com/repos/${repoFullName}/commits/:commitSha/pulls`,
|
`https://api.github.com/repos/:repoUser/:repoName/commits/:commitSha/pulls`,
|
||||||
(req, res, ctx) => {
|
(req, res, ctx) => {
|
||||||
return res(ctx.status(200), ctx.json(getCommitPullsResponse))
|
return res(ctx.status(200), ctx.json(getCommitPullsResponse))
|
||||||
},
|
},
|
||||||
|
|
@ -81,12 +96,17 @@ export const handlers = [
|
||||||
|
|
||||||
const server = setupServer(...handlers)
|
const server = setupServer(...handlers)
|
||||||
|
|
||||||
describe('add-pr-comment action', () => {
|
beforeAll(() => {
|
||||||
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
|
vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||||
|
server.listen({ onUnhandledRequest: 'error' })
|
||||||
|
})
|
||||||
afterAll(() => server.close())
|
afterAll(() => server.close())
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
issueNumber = 1
|
inputs = { ...defaultInputs }
|
||||||
|
issueNumber = defaultIssueNumber
|
||||||
|
messagePayload = undefined
|
||||||
|
|
||||||
vi.resetModules()
|
vi.resetModules()
|
||||||
|
|
||||||
github.context.sha = commitSha
|
github.context.sha = commitSha
|
||||||
|
|
@ -97,7 +117,7 @@ describe('add-pr-comment action', () => {
|
||||||
number: issueNumber,
|
number: issueNumber,
|
||||||
},
|
},
|
||||||
repository: {
|
repository: {
|
||||||
full_name: repoFullName,
|
full_name: `${inputs['repo-owner']}/${inputs['repo-name']}`,
|
||||||
name: 'bar',
|
name: 'bar',
|
||||||
owner: {
|
owner: {
|
||||||
login: 'bar',
|
login: 'bar',
|
||||||
|
|
@ -111,7 +131,7 @@ describe('add-pr-comment action', () => {
|
||||||
server.resetHandlers()
|
server.resetHandlers()
|
||||||
})
|
})
|
||||||
|
|
||||||
vi.mocked(core.getInput).mockImplementation((name: string, options?: core.InputOptions) => {
|
const getInput = (name: string, options?: core.InputOptions) => {
|
||||||
const value = inputs[name] ?? ''
|
const value = inputs[name] ?? ''
|
||||||
|
|
||||||
if (options?.required && value === undefined) {
|
if (options?.required && value === undefined) {
|
||||||
|
|
@ -119,11 +139,39 @@ 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['repo-token'] = repoToken
|
|
||||||
inputs['allow-repeats'] = 'true'
|
inputs['allow-repeats'] = 'true'
|
||||||
|
|
||||||
await expect(run()).resolves.not.toThrow()
|
await expect(run()).resolves.not.toThrow()
|
||||||
|
|
@ -133,30 +181,53 @@ describe('add-pr-comment action', () => {
|
||||||
|
|
||||||
it('creates a comment with a message-path', async () => {
|
it('creates a comment with a message-path', async () => {
|
||||||
inputs.message = undefined
|
inputs.message = undefined
|
||||||
inputs['message-path'] = path.resolve(__dirname, './message.txt')
|
inputs['message-path'] = messagePath1Fixture
|
||||||
inputs['repo-token'] = repoToken
|
|
||||||
inputs['allow-repeats'] = 'true'
|
inputs['allow-repeats'] = 'true'
|
||||||
|
|
||||||
await expect(run()).resolves.not.toThrow()
|
await expect(run()).resolves.not.toThrow()
|
||||||
|
expect(`<!-- add-pr-comment:add-pr-comment -->\n\n${messagePath1FixturePayload}`).toEqual(
|
||||||
|
messagePayload?.body,
|
||||||
|
)
|
||||||
|
expect(core.setOutput).toHaveBeenCalledWith('comment-created', 'true')
|
||||||
|
expect(core.setOutput).toHaveBeenCalledWith('comment-id', postIssueCommentsResponse.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates a comment with multiple message-paths concatenated', async () => {
|
||||||
|
inputs.message = undefined
|
||||||
|
inputs['message-path'] = `${messagePath1Fixture}\n${messagePath2Fixture}`
|
||||||
|
inputs['allow-repeats'] = 'true'
|
||||||
|
|
||||||
|
await expect(run()).resolves.not.toThrow()
|
||||||
|
expect(
|
||||||
|
`<!-- add-pr-comment:add-pr-comment -->\n\n${messagePath1FixturePayload}\n${messagePath1FixturePayload}`,
|
||||||
|
).toEqual(messagePayload?.body)
|
||||||
|
expect(core.setOutput).toHaveBeenCalledWith('comment-created', 'true')
|
||||||
|
expect(core.setOutput).toHaveBeenCalledWith('comment-id', postIssueCommentsResponse.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('supports globs in message paths', async () => {
|
||||||
|
inputs.message = undefined
|
||||||
|
inputs['message-path'] = `${path.resolve(__dirname)}/message-part-*.txt`
|
||||||
|
inputs['allow-repeats'] = 'true'
|
||||||
|
|
||||||
|
await expect(run()).resolves.not.toThrow()
|
||||||
|
expect(
|
||||||
|
`<!-- add-pr-comment:add-pr-comment -->\n\n${messagePath1FixturePayload}\n${messagePath1FixturePayload}`,
|
||||||
|
).toEqual(messagePayload?.body)
|
||||||
expect(core.setOutput).toHaveBeenCalledWith('comment-created', 'true')
|
expect(core.setOutput).toHaveBeenCalledWith('comment-created', 'true')
|
||||||
expect(core.setOutput).toHaveBeenCalledWith('comment-id', postIssueCommentsResponse.id)
|
expect(core.setOutput).toHaveBeenCalledWith('comment-id', postIssueCommentsResponse.id)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('fails when both message and message-path are defined', async () => {
|
it('fails when both message and message-path are defined', async () => {
|
||||||
inputs.message = 'foobar'
|
inputs.message = 'foobar'
|
||||||
inputs['message-path'] = path.resolve(__dirname, './message.txt')
|
inputs['message-path'] = messagePath1Fixture
|
||||||
inputs['repo-token'] = repoToken
|
|
||||||
|
|
||||||
await expect(run()).resolves.not.toThrow()
|
await expect(run()).resolves.not.toThrow()
|
||||||
expect(core.setFailed).toHaveBeenCalledWith('must specify only one, message or message-path')
|
expect(core.setFailed).toHaveBeenCalledWith('must specify only one, message or message-path')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('creates a comment in an existing PR', async () => {
|
it('creates a comment in an existing PR', async () => {
|
||||||
process.env['GITHUB_TOKEN'] = repoToken
|
|
||||||
|
|
||||||
inputs.message = simpleMessage
|
inputs.message = simpleMessage
|
||||||
inputs['message-path'] = undefined
|
|
||||||
inputs['repo-token'] = repoToken
|
|
||||||
inputs['allow-repeats'] = 'true'
|
inputs['allow-repeats'] = 'true'
|
||||||
|
|
||||||
github.context.payload = {
|
github.context.payload = {
|
||||||
|
|
@ -174,11 +245,41 @@ describe('add-pr-comment action', () => {
|
||||||
expect(core.setOutput).toHaveBeenCalledWith('comment-created', 'true')
|
expect(core.setOutput).toHaveBeenCalledWith('comment-created', 'true')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('does not create a comment when updateOnly is true and no existing comment is found', async () => {
|
||||||
|
inputs.message = simpleMessage
|
||||||
|
inputs['allow-repeats'] = 'true'
|
||||||
|
inputs['update-only'] = 'true'
|
||||||
|
|
||||||
|
await expect(run()).resolves.not.toThrow()
|
||||||
|
expect(core.setOutput).toHaveBeenCalledWith('comment-created', 'false')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates a comment in another repo', async () => {
|
||||||
|
inputs.message = simpleMessage
|
||||||
|
inputs['repo-owner'] = 'my-owner'
|
||||||
|
inputs['repo-name'] = 'my-repo'
|
||||||
|
inputs['allow-repeats'] = 'true'
|
||||||
|
|
||||||
|
github.context.payload = {
|
||||||
|
...github.context.payload,
|
||||||
|
pull_request: {
|
||||||
|
number: 0,
|
||||||
|
},
|
||||||
|
} as WebhookPayload
|
||||||
|
|
||||||
|
issueNumber = apiResponse.result[0].number
|
||||||
|
|
||||||
|
getCommitPullsResponse = apiResponse.result
|
||||||
|
|
||||||
|
await expect(run()).resolves.not.toThrow()
|
||||||
|
expect(core.setOutput).toHaveBeenCalledWith('comment-created', 'true')
|
||||||
|
expect(core.setOutput).toHaveBeenCalledWith('comment-id', postIssueCommentsResponse.id)
|
||||||
|
})
|
||||||
|
|
||||||
it('safely exits when no issue can be found [using GITHUB_TOKEN in env]', async () => {
|
it('safely exits when no issue can be found [using GITHUB_TOKEN in env]', async () => {
|
||||||
process.env['GITHUB_TOKEN'] = repoToken
|
process.env['GITHUB_TOKEN'] = repoToken
|
||||||
|
|
||||||
inputs.message = simpleMessage
|
inputs.message = simpleMessage
|
||||||
inputs['message-path'] = undefined
|
|
||||||
inputs['allow-repeats'] = 'true'
|
inputs['allow-repeats'] = 'true'
|
||||||
|
|
||||||
github.context.payload = {
|
github.context.payload = {
|
||||||
|
|
@ -196,9 +297,7 @@ describe('add-pr-comment action', () => {
|
||||||
|
|
||||||
it('creates a message when the message id does not exist', async () => {
|
it('creates a message when the message id does not exist', async () => {
|
||||||
inputs.message = simpleMessage
|
inputs.message = simpleMessage
|
||||||
inputs['message-path'] = undefined
|
|
||||||
inputs['repo-token'] = repoToken
|
|
||||||
inputs['allow-repeats'] = 'false'
|
|
||||||
inputs['message-id'] = 'custom-id'
|
inputs['message-id'] = 'custom-id'
|
||||||
|
|
||||||
const replyBody = [
|
const replyBody = [
|
||||||
|
|
@ -216,9 +315,6 @@ describe('add-pr-comment action', () => {
|
||||||
|
|
||||||
it('identifies an existing message by id and updates it', async () => {
|
it('identifies an existing message by id and updates it', async () => {
|
||||||
inputs.message = simpleMessage
|
inputs.message = simpleMessage
|
||||||
inputs['message-path'] = undefined
|
|
||||||
inputs['repo-token'] = repoToken
|
|
||||||
inputs['allow-repeats'] = 'false'
|
|
||||||
|
|
||||||
const commentId = 123
|
const commentId = 123
|
||||||
|
|
||||||
|
|
@ -242,9 +338,7 @@ describe('add-pr-comment action', () => {
|
||||||
|
|
||||||
it('overrides the default message with a success message on success', async () => {
|
it('overrides the default message with a success message on success', async () => {
|
||||||
inputs.message = simpleMessage
|
inputs.message = simpleMessage
|
||||||
inputs['message-path'] = undefined
|
|
||||||
inputs['repo-token'] = repoToken
|
|
||||||
inputs['allow-repeats'] = 'false'
|
|
||||||
inputs['message-success'] = '666'
|
inputs['message-success'] = '666'
|
||||||
inputs.status = 'success'
|
inputs.status = 'success'
|
||||||
|
|
||||||
|
|
@ -265,9 +359,7 @@ describe('add-pr-comment action', () => {
|
||||||
|
|
||||||
it('overrides the default message with a failure message on failure', async () => {
|
it('overrides the default message with a failure message on failure', async () => {
|
||||||
inputs.message = simpleMessage
|
inputs.message = simpleMessage
|
||||||
inputs['message-path'] = undefined
|
|
||||||
inputs['repo-token'] = repoToken
|
|
||||||
inputs['allow-repeats'] = 'false'
|
|
||||||
inputs['message-failure'] = '666'
|
inputs['message-failure'] = '666'
|
||||||
inputs.status = 'failure'
|
inputs.status = 'failure'
|
||||||
|
|
||||||
|
|
@ -288,9 +380,7 @@ describe('add-pr-comment action', () => {
|
||||||
|
|
||||||
it('overrides the default message with a cancelled message on cancelled', async () => {
|
it('overrides the default message with a cancelled message on cancelled', async () => {
|
||||||
inputs.message = simpleMessage
|
inputs.message = simpleMessage
|
||||||
inputs['message-path'] = undefined
|
|
||||||
inputs['repo-token'] = repoToken
|
|
||||||
inputs['allow-repeats'] = 'false'
|
|
||||||
inputs['message-cancelled'] = '666'
|
inputs['message-cancelled'] = '666'
|
||||||
inputs.status = 'cancelled'
|
inputs.status = 'cancelled'
|
||||||
|
|
||||||
|
|
@ -311,9 +401,7 @@ describe('add-pr-comment action', () => {
|
||||||
|
|
||||||
it('overrides the default message with a skipped message on skipped', async () => {
|
it('overrides the default message with a skipped message on skipped', async () => {
|
||||||
inputs.message = simpleMessage
|
inputs.message = simpleMessage
|
||||||
inputs['message-path'] = undefined
|
|
||||||
inputs['repo-token'] = repoToken
|
|
||||||
inputs['allow-repeats'] = 'false'
|
|
||||||
inputs['message-skipped'] = '666'
|
inputs['message-skipped'] = '666'
|
||||||
inputs.status = 'skipped'
|
inputs.status = 'skipped'
|
||||||
|
|
||||||
|
|
@ -331,4 +419,212 @@ describe('add-pr-comment action', () => {
|
||||||
await run()
|
await run()
|
||||||
expect(messagePayload?.body).toContain('666')
|
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)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
7
__tests__/message-part-2.txt
Normal file
7
__tests__/message-part-2.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
## [Preview link](https://antares-blog-staging-pr-${{ github.event.number }}.azurewebsites.net)
|
||||||
|
|
||||||
|
- Your changes have been deployed to the preview site. The preview site will update as you add more commits to this branch.
|
||||||
|
- The preview site shows any future-dated articles. Don't worry, if you are publishing a future-dated article, it will not show on the production site until the file's specified date.
|
||||||
|
- The preview link is shareable, but will be deleted when the pull request is merged or closed.
|
||||||
|
|
||||||
|
> *This is an automated message.*
|
||||||
32
action.yml
32
action.yml
|
|
@ -5,31 +5,39 @@ inputs:
|
||||||
description: "The message to print."
|
description: "The message to print."
|
||||||
required: false
|
required: false
|
||||||
message-path:
|
message-path:
|
||||||
description: "A path to a file to print as a message instead of a string."
|
description: "A path or list of paths to a file to print as a message instead of a string."
|
||||||
required: false
|
required: false
|
||||||
message-id:
|
message-id:
|
||||||
description: "An optional id to use for this message."
|
description: "An optional id to use for this message."
|
||||||
default: "add-pr-comment"
|
default: "add-pr-comment"
|
||||||
required: false
|
required: true
|
||||||
refresh-message-position:
|
refresh-message-position:
|
||||||
description: "If a message with the same id, this option allow to refresh the position of the message to be the last one posted."
|
description: "If a message with the same id, this option allow to refresh the position of the message to be the last one posted."
|
||||||
default: "false"
|
default: "false"
|
||||||
required: false
|
required: true
|
||||||
|
repo-owner:
|
||||||
|
description: "The repo owner."
|
||||||
|
default: "${{ github.repository_owner }}"
|
||||||
|
required: true
|
||||||
|
repo-name:
|
||||||
|
description: "The repo name."
|
||||||
|
default: "${{ github.event.repository.name }}"
|
||||||
|
required: true
|
||||||
repo-token:
|
repo-token:
|
||||||
description: "A GitHub token for API access. Defaults to {{ github.token }}."
|
description: "A GitHub token for API access. Defaults to {{ github.token }}."
|
||||||
default: "${{ github.token }}"
|
default: "${{ github.token }}"
|
||||||
required: false
|
required: true
|
||||||
allow-repeats:
|
allow-repeats:
|
||||||
description: "Allow messages to be repeated."
|
description: "Allow messages to be repeated."
|
||||||
default: "false"
|
default: "false"
|
||||||
required: false
|
required: true
|
||||||
proxy-url:
|
proxy-url:
|
||||||
description: "Proxy URL for comment creation"
|
description: "Proxy URL for comment creation"
|
||||||
required: false
|
required: false
|
||||||
status:
|
status:
|
||||||
description: "A job status for status headers. Defaults to {{ job.status }}."
|
description: "A job status for status headers. Defaults to {{ job.status }}."
|
||||||
default: "${{ job.status }}"
|
default: "${{ job.status }}"
|
||||||
required: false
|
required: true
|
||||||
message-success:
|
message-success:
|
||||||
description: "Override the message when a run is successful."
|
description: "Override the message when a run is successful."
|
||||||
required: false
|
required: false
|
||||||
|
|
@ -45,6 +53,16 @@ inputs:
|
||||||
issue:
|
issue:
|
||||||
description: "Override the message when a run is cancelled."
|
description: "Override the message when a run is cancelled."
|
||||||
required: false
|
required: false
|
||||||
|
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:
|
outputs:
|
||||||
comment-created:
|
comment-created:
|
||||||
description: "Whether a comment was created."
|
description: "Whether a comment was created."
|
||||||
|
|
@ -56,5 +74,5 @@ branding:
|
||||||
icon: message-circle
|
icon: message-circle
|
||||||
color: purple
|
color: purple
|
||||||
runs:
|
runs:
|
||||||
using: "node16"
|
using: "node20"
|
||||||
main: "dist/index.js"
|
main: "dist/index.js"
|
||||||
|
|
|
||||||
2826
dist/index.js
vendored
2826
dist/index.js
vendored
File diff suppressed because it is too large
Load diff
2
dist/index.js.map
vendored
2
dist/index.js.map
vendored
File diff suppressed because one or more lines are too long
|
|
@ -1,7 +1,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.createComment = exports.deleteComment = exports.updateComment = exports.getExistingCommentId = void 0;
|
exports.createComment = exports.deleteComment = exports.updateComment = exports.getExistingComment = void 0;
|
||||||
async function getExistingCommentId(octokit, owner, repo, issueNumber, messageId) {
|
async function getExistingComment(octokit, owner, repo, issueNumber, messageId) {
|
||||||
const parameters = {
|
const parameters = {
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
|
|
@ -18,9 +18,13 @@ async function getExistingCommentId(octokit, owner, repo, issueNumber, messageId
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return found === null || found === void 0 ? void 0 : found.id;
|
if (found) {
|
||||||
|
const { id, body } = found;
|
||||||
|
return { id, body };
|
||||||
}
|
}
|
||||||
exports.getExistingCommentId = getExistingCommentId;
|
return;
|
||||||
|
}
|
||||||
|
exports.getExistingComment = getExistingComment;
|
||||||
async function updateComment(octokit, owner, repo, existingCommentId, body) {
|
async function updateComment(octokit, owner, repo, existingCommentId, body) {
|
||||||
const updatedComment = await octokit.rest.issues.updateComment({
|
const updatedComment = await octokit.rest.issues.updateComment({
|
||||||
comment_id: existingCommentId,
|
comment_id: existingCommentId,
|
||||||
|
|
|
||||||
|
|
@ -22,74 +22,58 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
||||||
__setModuleDefault(result, mod);
|
__setModuleDefault(result, mod);
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
||||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
||||||
};
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.getInputs = void 0;
|
exports.getInputs = void 0;
|
||||||
const core = __importStar(require("@actions/core"));
|
const core = __importStar(require("@actions/core"));
|
||||||
const github = __importStar(require("@actions/github"));
|
const github = __importStar(require("@actions/github"));
|
||||||
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
||||||
async function getInputs() {
|
async function getInputs() {
|
||||||
var _a, _b, _c;
|
var _a, _b;
|
||||||
const messageIdInput = core.getInput('message-id', { required: false });
|
const messageIdInput = core.getInput('message-id', { required: false });
|
||||||
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 repoName = core.getInput('repo-name', { required: true });
|
||||||
const repoToken = core.getInput('repo-token', { required: true });
|
const repoToken = core.getInput('repo-token', { required: true });
|
||||||
const status = core.getInput('status', { required: true });
|
const status = core.getInput('status', { required: true });
|
||||||
const issue = core.getInput('issue', { required: false });
|
const issue = core.getInput('issue', { required: false });
|
||||||
const proxyUrl = core.getInput('proxy-url', { required: false }).replace(/\/$/, '');
|
const proxyUrl = core.getInput('proxy-url', { required: false }).replace(/\/$/, '');
|
||||||
const allowRepeats = core.getInput('allow-repeats', { required: true }) === 'true';
|
const allowRepeats = core.getInput('allow-repeats', { required: true }) === 'true';
|
||||||
const refreshMessagePosition = core.getInput('refresh-message-position', { required: false }) === '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) {
|
if (messageInput && messagePath) {
|
||||||
throw new Error('must specify only one, message or message-path');
|
throw new Error('must specify only one, message or message-path');
|
||||||
}
|
}
|
||||||
let message;
|
|
||||||
if (messagePath) {
|
|
||||||
message = await promises_1.default.readFile(messagePath, { encoding: 'utf8' });
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
message = messageInput;
|
|
||||||
}
|
|
||||||
const messageSuccess = core.getInput(`message-success`);
|
const messageSuccess = core.getInput(`message-success`);
|
||||||
const messageFailure = core.getInput(`message-failure`);
|
const messageFailure = core.getInput(`message-failure`);
|
||||||
const messageCancelled = core.getInput(`message-cancelled`);
|
const messageCancelled = core.getInput(`message-cancelled`);
|
||||||
const messageSkipped = core.getInput(`message-skipped`);
|
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;
|
const { payload } = github.context;
|
||||||
const repoFullName = (_a = payload.repository) === null || _a === void 0 ? void 0 : _a.full_name;
|
|
||||||
if (!repoFullName) {
|
|
||||||
throw new Error('unable to determine repository from request type');
|
|
||||||
}
|
|
||||||
const [owner, repo] = repoFullName.split('/');
|
|
||||||
return {
|
return {
|
||||||
refreshMessagePosition,
|
|
||||||
allowRepeats,
|
allowRepeats,
|
||||||
message,
|
commitSha: github.context.sha,
|
||||||
|
issue: issue ? Number(issue) : (_a = payload.issue) === null || _a === void 0 ? void 0 : _a.number,
|
||||||
|
messageInput,
|
||||||
messageId: `<!-- ${messageId} -->`,
|
messageId: `<!-- ${messageId} -->`,
|
||||||
|
messageSuccess,
|
||||||
|
messageFailure,
|
||||||
|
messageCancelled,
|
||||||
|
messageSkipped,
|
||||||
|
messagePath,
|
||||||
|
messageFind,
|
||||||
|
messageReplace,
|
||||||
|
preformatted,
|
||||||
proxyUrl,
|
proxyUrl,
|
||||||
|
pullRequestNumber: (_b = payload.pull_request) === null || _b === void 0 ? void 0 : _b.number,
|
||||||
|
refreshMessagePosition,
|
||||||
repoToken,
|
repoToken,
|
||||||
status,
|
status,
|
||||||
issue: issue ? Number(issue) : (_b = payload.issue) === null || _b === void 0 ? void 0 : _b.number,
|
owner: repoOwner || payload.repo.owner,
|
||||||
pullRequestNumber: (_c = payload.pull_request) === null || _c === void 0 ? void 0 : _c.number,
|
repo: repoName || payload.repo.repo,
|
||||||
commitSha: github.context.sha,
|
updateOnly: updateOnly,
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
exports.getInputs = getInputs;
|
exports.getInputs = getInputs;
|
||||||
|
|
|
||||||
53
lib/files.js
Normal file
53
lib/files.js
Normal 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;
|
||||||
49
lib/main.js
49
lib/main.js
|
|
@ -28,11 +28,22 @@ const github = __importStar(require("@actions/github"));
|
||||||
const comments_1 = require("./comments");
|
const comments_1 = require("./comments");
|
||||||
const config_1 = require("./config");
|
const config_1 = require("./config");
|
||||||
const issues_1 = require("./issues");
|
const issues_1 = require("./issues");
|
||||||
|
const message_1 = require("./message");
|
||||||
const proxy_1 = require("./proxy");
|
const proxy_1 = require("./proxy");
|
||||||
const run = async () => {
|
const run = async () => {
|
||||||
try {
|
try {
|
||||||
const { allowRepeats, message, messageId, refreshMessagePosition, repoToken, proxyUrl, issue, pullRequestNumber, commitSha, repo, owner, } = await (0, config_1.getInputs)();
|
const { allowRepeats, messagePath, messageInput, messageId, refreshMessagePosition, repoToken, proxyUrl, issue, pullRequestNumber, commitSha, repo, owner, updateOnly, messageCancelled, messageFailure, messageSuccess, messageSkipped, preformatted, status, messageFind, messageReplace, } = await (0, config_1.getInputs)();
|
||||||
const octokit = github.getOctokit(repoToken);
|
const octokit = github.getOctokit(repoToken);
|
||||||
|
let message = await (0, message_1.getMessage)({
|
||||||
|
messagePath,
|
||||||
|
messageInput,
|
||||||
|
messageSkipped,
|
||||||
|
messageCancelled,
|
||||||
|
messageSuccess,
|
||||||
|
messageFailure,
|
||||||
|
preformatted,
|
||||||
|
status,
|
||||||
|
});
|
||||||
let issueNumber;
|
let issueNumber;
|
||||||
if (issue) {
|
if (issue) {
|
||||||
issueNumber = issue;
|
issueNumber = issue;
|
||||||
|
|
@ -49,19 +60,31 @@ const run = async () => {
|
||||||
core.setOutput('comment-created', 'false');
|
core.setOutput('comment-created', 'false');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let existingCommentId;
|
let existingComment;
|
||||||
if (!allowRepeats) {
|
if (!allowRepeats) {
|
||||||
core.debug('repeat comments are disallowed, checking for existing');
|
core.debug('repeat comments are disallowed, checking for existing');
|
||||||
existingCommentId = await (0, comments_1.getExistingCommentId)(octokit, owner, repo, issueNumber, messageId);
|
existingComment = await (0, comments_1.getExistingComment)(octokit, owner, repo, issueNumber, messageId);
|
||||||
if (existingCommentId) {
|
if (existingComment) {
|
||||||
core.debug(`existing comment found with id: ${existingCommentId}`);
|
core.debug(`existing comment found with id: ${existingComment.id}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// if no existing comment and updateOnly is true, exit
|
||||||
|
if (!existingComment && updateOnly) {
|
||||||
|
core.info('no existing comment found and update-only is true, exiting');
|
||||||
|
core.setOutput('comment-created', 'false');
|
||||||
|
return;
|
||||||
|
}
|
||||||
let comment;
|
let comment;
|
||||||
const body = `${messageId}\n\n${message}`;
|
if ((messageFind === null || messageFind === void 0 ? void 0 : messageFind.length) && ((messageReplace === null || messageReplace === void 0 ? void 0 : messageReplace.length) || message) && (existingComment === null || existingComment === void 0 ? void 0 : existingComment.body)) {
|
||||||
|
message = (0, message_1.findAndReplaceInMessage)(messageFind, (messageReplace === null || messageReplace === void 0 ? void 0 : messageReplace.length) ? messageReplace : [message], (0, message_1.removeMessageHeader)(existingComment.body));
|
||||||
|
}
|
||||||
|
if (!message) {
|
||||||
|
throw new Error('no message, check your message inputs');
|
||||||
|
}
|
||||||
|
const body = (0, message_1.addMessageHeader)(messageId, message);
|
||||||
if (proxyUrl) {
|
if (proxyUrl) {
|
||||||
comment = await (0, proxy_1.createCommentProxy)({
|
comment = await (0, proxy_1.createCommentProxy)({
|
||||||
commentId: existingCommentId,
|
commentId: existingComment === null || existingComment === void 0 ? void 0 : existingComment.id,
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
issueNumber,
|
issueNumber,
|
||||||
|
|
@ -69,15 +92,15 @@ const run = async () => {
|
||||||
repoToken,
|
repoToken,
|
||||||
proxyUrl,
|
proxyUrl,
|
||||||
});
|
});
|
||||||
core.setOutput(existingCommentId ? 'comment-updated' : 'comment-created', 'true');
|
core.setOutput((existingComment === null || existingComment === void 0 ? void 0 : existingComment.id) ? 'comment-updated' : 'comment-created', 'true');
|
||||||
}
|
}
|
||||||
else if (existingCommentId) {
|
else if (existingComment === null || existingComment === void 0 ? void 0 : existingComment.id) {
|
||||||
if (refreshMessagePosition) {
|
if (refreshMessagePosition) {
|
||||||
await (0, comments_1.deleteComment)(octokit, owner, repo, existingCommentId, body);
|
await (0, comments_1.deleteComment)(octokit, owner, repo, existingComment.id, body);
|
||||||
comment = await (0, comments_1.createComment)(octokit, owner, repo, issueNumber, body);
|
comment = await (0, comments_1.createComment)(octokit, owner, repo, issueNumber, body);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
comment = await (0, comments_1.updateComment)(octokit, owner, repo, existingCommentId, body);
|
comment = await (0, comments_1.updateComment)(octokit, owner, repo, existingComment.id, body);
|
||||||
}
|
}
|
||||||
core.setOutput('comment-updated', 'true');
|
core.setOutput('comment-updated', 'true');
|
||||||
}
|
}
|
||||||
|
|
@ -94,6 +117,10 @@ const run = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
|
if (process.env.NODE_ENV === 'test') {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
core.setFailed(err.message);
|
core.setFailed(err.message);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
81
lib/message.js
Normal file
81
lib/message.js
Normal 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
2
lib/types.js
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
68
lib/util.js
Normal file
68
lib/util.js
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
"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(require("@actions/core"));
|
||||||
|
const glob = __importStar(require("@actions/glob"));
|
||||||
|
const promises_1 = __importDefault(require("node:fs/promises"));
|
||||||
|
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;
|
||||||
11877
package-lock.json
generated
11877
package-lock.json
generated
File diff suppressed because it is too large
Load diff
56
package.json
56
package.json
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@mshick/add-pr-comment",
|
"name": "@mshick/add-pr-comment",
|
||||||
"version": "2.2.0",
|
"version": "2.8.2",
|
||||||
"description": "A GitHub Action which adds a comment to a Pull Request Issue.",
|
"description": "A GitHub Action which adds a comment to a Pull Request Issue.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"GitHub",
|
"GitHub",
|
||||||
|
|
@ -25,7 +25,8 @@
|
||||||
"build": "del-cli dist && tsc && ncc build --source-map",
|
"build": "del-cli dist && tsc && ncc build --source-map",
|
||||||
"clean": "rm -rf node_modules dist package-lock.json __tests__/runner/**/*",
|
"clean": "rm -rf node_modules dist package-lock.json __tests__/runner/**/*",
|
||||||
"lint": "eslint src/**/*.ts",
|
"lint": "eslint src/**/*.ts",
|
||||||
"release": "np --no-publish",
|
"prepare": "npm run build && git add lib dist",
|
||||||
|
"release": "npm run build && np --no-publish",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"watch": "vitest"
|
"watch": "vitest"
|
||||||
},
|
},
|
||||||
|
|
@ -73,6 +74,23 @@
|
||||||
"@typescript-eslint/no-explicit-any": "off"
|
"@typescript-eslint/no-explicit-any": "off"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"**/*.test.ts"
|
||||||
|
],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": "latest",
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"plugin:@typescript-eslint/recommended"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/no-empty-function": "off",
|
||||||
|
"@typescript-eslint/no-explicit-any": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"files": [
|
"files": [
|
||||||
"*.json"
|
"*.json"
|
||||||
|
|
@ -102,32 +120,34 @@
|
||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@actions/artifact": "^1.1.1",
|
||||||
"@actions/core": "^1.10.0",
|
"@actions/core": "^1.10.0",
|
||||||
"@actions/github": "^5.1.1",
|
"@actions/github": "^5.1.1",
|
||||||
"@actions/http-client": "^2.0.1"
|
"@actions/glob": "^0.4.0",
|
||||||
|
"@actions/http-client": "^2.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@octokit/types": "^8.0.0",
|
"@octokit/types": "^9.1.2",
|
||||||
"@types/node": "^18.11.9",
|
"@types/node": "^18.15.13",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.42.0",
|
"@typescript-eslint/eslint-plugin": "^5.59.0",
|
||||||
"@typescript-eslint/parser": "^5.42.0",
|
"@typescript-eslint/parser": "^5.59.0",
|
||||||
"@vercel/ncc": "^0.34.0",
|
"@vercel/ncc": "^0.36.1",
|
||||||
"del-cli": "^5.0.0",
|
"del-cli": "^5.0.0",
|
||||||
"eslint": "^8.26.0",
|
"eslint": "^8.39.0",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.8.0",
|
||||||
"eslint-import-resolver-typescript": "^3.5.2",
|
"eslint-import-resolver-typescript": "^3.5.5",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-plugin-import": "^2.27.5",
|
||||||
"eslint-plugin-json-format": "^2.0.1",
|
"eslint-plugin-json-format": "^2.0.1",
|
||||||
"eslint-plugin-mdx": "^2.0.5",
|
"eslint-plugin-mdx": "^2.0.5",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
"msw": "^0.47.4",
|
"msw": "^1.2.1",
|
||||||
"nock": "^13.2.9",
|
"nock": "^13.3.0",
|
||||||
"np": "^7.7.0",
|
"np": "^7.7.0",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.8.7",
|
||||||
"typescript": "^4.8.4",
|
"typescript": "^5.0.4",
|
||||||
"vitest": "^0.24.5"
|
"vitest": "^0.30.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^14.15.0 || ^16.13.0 || ^18.0.0"
|
"node": "^16.13.0 || ^18.0.0 || ^20.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -1,31 +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 fs from 'node:fs/promises'
|
import { Inputs } from './types'
|
||||||
|
|
||||||
interface Inputs {
|
|
||||||
refreshMessagePosition: boolean
|
|
||||||
allowRepeats: boolean
|
|
||||||
message?: string
|
|
||||||
messageId: string
|
|
||||||
messagePath?: string
|
|
||||||
messageSuccess?: string
|
|
||||||
messageFailure?: string
|
|
||||||
messageCancelled?: string
|
|
||||||
proxyUrl?: string
|
|
||||||
repoToken: string
|
|
||||||
status?: string
|
|
||||||
issue?: number
|
|
||||||
commitSha: string
|
|
||||||
pullRequestNumber?: number
|
|
||||||
repo: string
|
|
||||||
owner: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getInputs(): Promise<Inputs> {
|
export async function getInputs(): Promise<Inputs> {
|
||||||
const messageIdInput = core.getInput('message-id', { required: false })
|
const messageIdInput = core.getInput('message-id', { required: false })
|
||||||
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 repoName = core.getInput('repo-name', { required: true })
|
||||||
const repoToken = core.getInput('repo-token', { required: true })
|
const repoToken = core.getInput('repo-token', { required: true })
|
||||||
const status = core.getInput('status', { required: true })
|
const status = core.getInput('status', { required: true })
|
||||||
const issue = core.getInput('issue', { required: false })
|
const issue = core.getInput('issue', { required: false })
|
||||||
|
|
@ -33,66 +18,41 @@ export async function getInputs(): Promise<Inputs> {
|
||||||
const allowRepeats = core.getInput('allow-repeats', { required: true }) === 'true'
|
const allowRepeats = core.getInput('allow-repeats', { required: true }) === 'true'
|
||||||
const refreshMessagePosition =
|
const refreshMessagePosition =
|
||||||
core.getInput('refresh-message-position', { required: false }) === 'true'
|
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) {
|
if (messageInput && messagePath) {
|
||||||
throw new Error('must specify only one, message or message-path')
|
throw new Error('must specify only one, message or message-path')
|
||||||
}
|
}
|
||||||
|
|
||||||
let message
|
|
||||||
|
|
||||||
if (messagePath) {
|
|
||||||
message = await fs.readFile(messagePath, { encoding: 'utf8' })
|
|
||||||
} else {
|
|
||||||
message = messageInput
|
|
||||||
}
|
|
||||||
|
|
||||||
const messageSuccess = core.getInput(`message-success`)
|
const messageSuccess = core.getInput(`message-success`)
|
||||||
const messageFailure = core.getInput(`message-failure`)
|
const messageFailure = core.getInput(`message-failure`)
|
||||||
const messageCancelled = core.getInput(`message-cancelled`)
|
const messageCancelled = core.getInput(`message-cancelled`)
|
||||||
const messageSkipped = core.getInput(`message-skipped`)
|
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
|
const { payload } = github.context
|
||||||
|
|
||||||
const repoFullName = payload.repository?.full_name
|
|
||||||
|
|
||||||
if (!repoFullName) {
|
|
||||||
throw new Error('unable to determine repository from request type')
|
|
||||||
}
|
|
||||||
|
|
||||||
const [owner, repo] = repoFullName.split('/')
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
refreshMessagePosition,
|
|
||||||
allowRepeats,
|
allowRepeats,
|
||||||
message,
|
commitSha: github.context.sha,
|
||||||
|
issue: issue ? Number(issue) : payload.issue?.number,
|
||||||
|
messageInput,
|
||||||
messageId: `<!-- ${messageId} -->`,
|
messageId: `<!-- ${messageId} -->`,
|
||||||
|
messageSuccess,
|
||||||
|
messageFailure,
|
||||||
|
messageCancelled,
|
||||||
|
messageSkipped,
|
||||||
|
messagePath,
|
||||||
|
messageFind,
|
||||||
|
messageReplace,
|
||||||
|
preformatted,
|
||||||
proxyUrl,
|
proxyUrl,
|
||||||
|
pullRequestNumber: payload.pull_request?.number,
|
||||||
|
refreshMessagePosition,
|
||||||
repoToken,
|
repoToken,
|
||||||
status,
|
status,
|
||||||
issue: issue ? Number(issue) : payload.issue?.number,
|
owner: repoOwner || payload.repo.owner,
|
||||||
pullRequestNumber: payload.pull_request?.number,
|
repo: repoName || payload.repo.repo,
|
||||||
commitSha: github.context.sha,
|
updateOnly: updateOnly,
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
25
src/files.ts
Normal file
25
src/files.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import * as core from '@actions/core'
|
||||||
|
import * as glob from '@actions/glob'
|
||||||
|
import fs from 'node:fs/promises'
|
||||||
|
|
||||||
|
export async function findFiles(searchPath: string): Promise<string[]> {
|
||||||
|
const searchResults: string[] = []
|
||||||
|
const globber = await glob.create(searchPath, {
|
||||||
|
followSymbolicLinks: true,
|
||||||
|
implicitDescendants: true,
|
||||||
|
omitBrokenSymbolicLinks: true,
|
||||||
|
})
|
||||||
|
const rawSearchResults: string[] = await globber.glob()
|
||||||
|
|
||||||
|
for (const searchResult of rawSearchResults) {
|
||||||
|
const fileStats = await fs.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
|
||||||
|
}
|
||||||
82
src/main.ts
82
src/main.ts
|
|
@ -1,21 +1,23 @@
|
||||||
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'
|
||||||
createComment,
|
|
||||||
CreateIssueCommentResponseData,
|
|
||||||
getExistingCommentId,
|
|
||||||
updateComment,
|
|
||||||
deleteComment,
|
|
||||||
} from './comments'
|
|
||||||
import { getInputs } from './config'
|
import { getInputs } from './config'
|
||||||
import { getIssueNumberFromCommitPullsList } from './issues'
|
import { getIssueNumberFromCommitPullsList } from './issues'
|
||||||
|
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 {
|
||||||
const {
|
const {
|
||||||
allowRepeats,
|
allowRepeats,
|
||||||
message,
|
messagePath,
|
||||||
|
messageInput,
|
||||||
messageId,
|
messageId,
|
||||||
refreshMessagePosition,
|
refreshMessagePosition,
|
||||||
repoToken,
|
repoToken,
|
||||||
|
|
@ -25,10 +27,30 @@ const run = async (): Promise<void> => {
|
||||||
commitSha,
|
commitSha,
|
||||||
repo,
|
repo,
|
||||||
owner,
|
owner,
|
||||||
|
updateOnly,
|
||||||
|
messageCancelled,
|
||||||
|
messageFailure,
|
||||||
|
messageSuccess,
|
||||||
|
messageSkipped,
|
||||||
|
preformatted,
|
||||||
|
status,
|
||||||
|
messageFind,
|
||||||
|
messageReplace,
|
||||||
} = await getInputs()
|
} = await getInputs()
|
||||||
|
|
||||||
const octokit = github.getOctokit(repoToken)
|
const octokit = github.getOctokit(repoToken)
|
||||||
|
|
||||||
|
let message = await getMessage({
|
||||||
|
messagePath,
|
||||||
|
messageInput,
|
||||||
|
messageSkipped,
|
||||||
|
messageCancelled,
|
||||||
|
messageSuccess,
|
||||||
|
messageFailure,
|
||||||
|
preformatted,
|
||||||
|
status,
|
||||||
|
})
|
||||||
|
|
||||||
let issueNumber
|
let issueNumber
|
||||||
|
|
||||||
if (issue) {
|
if (issue) {
|
||||||
|
|
@ -48,25 +70,44 @@ 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 (!existingComment && updateOnly) {
|
||||||
|
core.info('no existing comment found and update-only is true, exiting')
|
||||||
|
core.setOutput('comment-created', 'false')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
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,
|
||||||
|
|
@ -74,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 {
|
||||||
|
|
@ -95,6 +136,11 @@ const run = async (): Promise<void> => {
|
||||||
core.setOutput('comment-updated', 'false')
|
core.setOutput('comment-updated', 'false')
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (process.env.NODE_ENV === 'test') {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
core.setFailed(err.message)
|
core.setFailed(err.message)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
114
src/message.ts
Normal file
114
src/message.ts
Normal 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
34
src/types.ts
Normal 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'>
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
"removeComments": false,
|
"removeComments": false,
|
||||||
"preserveConstEnums": true,
|
"preserveConstEnums": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"rootDir": "./src",
|
"rootDir": "src",
|
||||||
"outDir": "./lib"
|
"outDir": "./lib"
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules", "**/*.test.ts"]
|
"exclude": ["node_modules", "**/*.test.ts"]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue