Preformatted messages (#97)

* refactor message creation
This commit is contained in:
Michael Shick 2023-05-05 16:02:36 -04:00 committed by GitHub
parent ef723874d4
commit a251f051d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 154 additions and 85 deletions

View file

@ -62,6 +62,7 @@ jobs:
- uses: ./ - uses: ./
with: with:
preformatted: true
message-id: path message-id: path
message-path: | message-path: |
.github/test/file-*.txt .github/test/file-*.txt

View file

@ -87,6 +87,7 @@ jobs:
| 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 | | 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 | |
## Advanced Uses ## Advanced Uses
@ -169,7 +170,6 @@ jobs:
message-part-*.txt message-part-*.txt
``` ```
### 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
@ -202,7 +202,7 @@ jobs:
issue: ${{ steps.pr.outputs.issue }} issue: ${{ steps.pr.outputs.issue }}
message: | message: |
**Howdie!** **Howdie!**
```` ```
## Contributors ✨ ## Contributors ✨

View file

@ -30,6 +30,7 @@ type Inputs = {
'message-cancelled'?: string 'message-cancelled'?: string
'message-skipped'?: string 'message-skipped'?: string
'update-only'?: string 'update-only'?: string
preformatted?: string
status?: 'success' | 'failure' | 'cancelled' | 'skipped' status?: 'success' | 'failure' | 'cancelled' | 'skipped'
} }
@ -41,6 +42,7 @@ const defaultInputs: Inputs = {
'repo-token': repoToken, 'repo-token': repoToken,
'message-id': 'add-pr-comment', 'message-id': 'add-pr-comment',
'allow-repeats': 'false', 'allow-repeats': 'false',
status: 'success',
} }
const defaultIssueNumber = 1 const defaultIssueNumber = 1
@ -95,10 +97,10 @@ const server = setupServer(...handlers)
describe('add-pr-comment action', () => { describe('add-pr-comment action', () => {
beforeAll(() => { beforeAll(() => {
vi.spyOn(console, 'log').mockImplementation(() => {}) // vi.spyOn(console, 'log').mockImplementation(() => {})
vi.spyOn(core, 'debug').mockImplementation(() => {}) // vi.spyOn(core, 'debug').mockImplementation(() => {})
vi.spyOn(core, 'info').mockImplementation(() => {}) // vi.spyOn(core, 'info').mockImplementation(() => {})
vi.spyOn(core, 'warning').mockImplementation(() => {}) // vi.spyOn(core, 'warning').mockImplementation(() => {})
server.listen({ onUnhandledRequest: 'error' }) server.listen({ onUnhandledRequest: 'error' })
}) })
afterAll(() => server.close()) afterAll(() => server.close())
@ -392,4 +394,17 @@ 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)
})
}) })

View file

@ -56,6 +56,9 @@ inputs:
update-only: update-only:
description: "Only update the comment if it already exists." description: "Only update the comment if it already exists."
required: false required: false
preformatted:
description: "Treat message text (from a file or input) as pre-formatted and place it in a codeblock."
required: false
outputs: outputs:
comment-created: comment-created:
description: "Whether a comment was created." description: "Whether a comment was created."

View file

@ -1,27 +1,6 @@
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 { getMessageFromPaths } from './util' import { Inputs } from './types'
interface Inputs {
allowRepeats: boolean
attachPath?: string[]
commitSha: string
issue?: number
message?: string
messageId: string
messagePath?: string
messageSuccess?: string
messageFailure?: string
messageCancelled?: string
proxyUrl?: string
pullRequestNumber?: number
refreshMessagePosition: boolean
repo: string
repoToken: string
status?: string
owner: string
updateOnly: boolean
}
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 })
@ -38,52 +17,31 @@ export async function getInputs(): Promise<Inputs> {
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 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 getMessageFromPaths(messagePath)
} 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
return { return {
allowRepeats, allowRepeats,
commitSha: github.context.sha, commitSha: github.context.sha,
issue: issue ? Number(issue) : payload.issue?.number, issue: issue ? Number(issue) : payload.issue?.number,
message, messageInput,
messageId: `<!-- ${messageId} -->`, messageId: `<!-- ${messageId} -->`,
messageSuccess,
messageFailure,
messageCancelled,
messageSkipped,
messagePath,
preformatted,
proxyUrl, proxyUrl,
pullRequestNumber: payload.pull_request?.number, pullRequestNumber: payload.pull_request?.number,
refreshMessagePosition, refreshMessagePosition,

View file

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

View file

@ -9,13 +9,15 @@ import {
} from './comments' } from './comments'
import { getInputs } from './config' import { getInputs } from './config'
import { getIssueNumberFromCommitPullsList } from './issues' import { getIssueNumberFromCommitPullsList } from './issues'
import { getMessage } from './message'
import { createCommentProxy } from './proxy' import { createCommentProxy } from './proxy'
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,
@ -26,10 +28,27 @@ const run = async (): Promise<void> => {
repo, repo,
owner, owner,
updateOnly, updateOnly,
messageCancelled,
messageFailure,
messageSuccess,
messageSkipped,
preformatted,
status,
} = await getInputs() } = await getInputs()
const octokit = github.getOctokit(repoToken) const octokit = github.getOctokit(repoToken)
const message = await getMessage({
messagePath,
messageInput,
messageSkipped,
messageCancelled,
messageSuccess,
messageFailure,
preformatted,
status,
})
let issueNumber let issueNumber
if (issue) { if (issue) {

74
src/message.ts Normal file
View file

@ -0,0 +1,74 @@
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'
>) {
let message
if (status === 'success') {
if (messageSuccess) {
message = messageSuccess
} else if (messagePath) {
message = await getMessageFromPath(messagePath)
} else {
message = messageInput
}
}
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')
}
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
}

22
src/types.ts Normal file
View file

@ -0,0 +1,22 @@
export interface Inputs {
allowRepeats: boolean
attachPath?: string[]
commitSha: string
issue?: number
messageInput?: string
messageId: string
messagePath?: 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
}