mirror of https://github.com/actions/toolkit
Add notice annotation and support more annotation fields (#855)
* Add support for notice annotation and additional properties
* Add additional tests
* Update readme
* Change casing for endLine and endColumn
* Update utils.ts
* Update README.md
* Rename files to have internal- nomenclature
* Revert "Rename files to have internal- nomenclature"
This reverts commit 7911689f29
.
* Update utils.ts
pull/851/head
parent
4564768940
commit
f0b00fd201
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
|
@ -6,7 +6,7 @@ Problem Matchers are a way to scan the output of actions for a specified regex p
|
||||||
|
|
||||||
Currently, GitHub Actions limit the annotation count in a workflow run.
|
Currently, GitHub Actions limit the annotation count in a workflow run.
|
||||||
|
|
||||||
- 10 warning annotations and 10 error annotations per step
|
- 10 warning annotations, 10 error annotations, and 10 notice annotations per step
|
||||||
- 50 annotations per job (sum of annotations from all the steps)
|
- 50 annotations per job (sum of annotations from all the steps)
|
||||||
- 50 annotations per run (separate from the job annotations, these annotations aren’t created by users)
|
- 50 annotations per run (separate from the job annotations, these annotations aren’t created by users)
|
||||||
|
|
||||||
|
|
|
@ -92,6 +92,8 @@ try {
|
||||||
|
|
||||||
// Do stuff
|
// Do stuff
|
||||||
core.info('Output to the actions build log')
|
core.info('Output to the actions build log')
|
||||||
|
|
||||||
|
core.notice('This is a message that will also emit an annotation')
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
core.error(`Error ${err}, action may still succeed though`);
|
core.error(`Error ${err}, action may still succeed though`);
|
||||||
|
@ -115,6 +117,54 @@ const result = await core.group('Do something async', async () => {
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Annotations
|
||||||
|
|
||||||
|
This library has 3 methods that will produce [annotations](https://docs.github.com/en/rest/reference/checks#create-a-check-run).
|
||||||
|
```js
|
||||||
|
core.error('This is a bad error. This will also fail the build.')
|
||||||
|
|
||||||
|
core.warning('Something went wrong, but it\'s not bad enough to fail the build.')
|
||||||
|
|
||||||
|
core.notice('Something happened that you might want to know about.')
|
||||||
|
```
|
||||||
|
|
||||||
|
These will surface to the UI in the Actions page and on Pull Requests. They look something like this:
|
||||||
|
|
||||||
|
![Annotations Image](../../docs/assets/annotations.png)
|
||||||
|
|
||||||
|
These annotations can also be attached to particular lines and columns of your source files to show exactly where a problem is occuring.
|
||||||
|
|
||||||
|
These options are:
|
||||||
|
```typescript
|
||||||
|
export interface AnnotationProperties {
|
||||||
|
/**
|
||||||
|
* A title for the annotation.
|
||||||
|
*/
|
||||||
|
title?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The start line for the annotation.
|
||||||
|
*/
|
||||||
|
startLine?: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The end line for the annotation. Defaults to `startLine` when `startLine` is provided.
|
||||||
|
*/
|
||||||
|
endLine?: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The start column for the annotation. Cannot be sent when `startLine` and `endLine` are different values.
|
||||||
|
*/
|
||||||
|
startColumn?: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The start column for the annotation. Cannot be sent when `startLine` and `endLine` are different values.
|
||||||
|
* Defaults to `startColumn` when `startColumn` is provided.
|
||||||
|
*/
|
||||||
|
endColumn?: number
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
#### Styling output
|
#### Styling output
|
||||||
|
|
||||||
Colored output is supported in the Action logs via standard [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code). 3/4 bit, 8 bit and 24 bit colors are all supported.
|
Colored output is supported in the Action logs via standard [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code). 3/4 bit, 8 bit and 24 bit colors are all supported.
|
||||||
|
|
|
@ -2,6 +2,7 @@ import * as fs from 'fs'
|
||||||
import * as os from 'os'
|
import * as os from 'os'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import * as core from '../src/core'
|
import * as core from '../src/core'
|
||||||
|
import {toCommandProperties} from '../src/utils'
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/unbound-method */
|
/* eslint-disable @typescript-eslint/unbound-method */
|
||||||
|
|
||||||
|
@ -269,6 +270,20 @@ describe('@actions/core', () => {
|
||||||
assertWriteCalls([`::error::Error: ${message}${os.EOL}`])
|
assertWriteCalls([`::error::Error: ${message}${os.EOL}`])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('error handles parameters correctly', () => {
|
||||||
|
const message = 'this is my error message'
|
||||||
|
core.error(new Error(message), {
|
||||||
|
title: 'A title',
|
||||||
|
startColumn: 1,
|
||||||
|
endColumn: 2,
|
||||||
|
startLine: 5,
|
||||||
|
endLine: 5
|
||||||
|
})
|
||||||
|
assertWriteCalls([
|
||||||
|
`::error title=A title,line=5,endLine=5,col=1,endColumn=2::Error: ${message}${os.EOL}`
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
it('warning sets the correct message', () => {
|
it('warning sets the correct message', () => {
|
||||||
core.warning('Warning')
|
core.warning('Warning')
|
||||||
assertWriteCalls([`::warning::Warning${os.EOL}`])
|
assertWriteCalls([`::warning::Warning${os.EOL}`])
|
||||||
|
@ -285,6 +300,38 @@ describe('@actions/core', () => {
|
||||||
assertWriteCalls([`::warning::Error: ${message}${os.EOL}`])
|
assertWriteCalls([`::warning::Error: ${message}${os.EOL}`])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('warning handles parameters correctly', () => {
|
||||||
|
const message = 'this is my error message'
|
||||||
|
core.warning(new Error(message), {
|
||||||
|
title: 'A title',
|
||||||
|
startColumn: 1,
|
||||||
|
endColumn: 2,
|
||||||
|
startLine: 5,
|
||||||
|
endLine: 5
|
||||||
|
})
|
||||||
|
assertWriteCalls([
|
||||||
|
`::warning title=A title,line=5,endLine=5,col=1,endColumn=2::Error: ${message}${os.EOL}`
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('annotations map field names correctly', () => {
|
||||||
|
const commandProperties = toCommandProperties({
|
||||||
|
title: 'A title',
|
||||||
|
startColumn: 1,
|
||||||
|
endColumn: 2,
|
||||||
|
startLine: 5,
|
||||||
|
endLine: 5
|
||||||
|
})
|
||||||
|
expect(commandProperties.title).toBe('A title')
|
||||||
|
expect(commandProperties.col).toBe(1)
|
||||||
|
expect(commandProperties.endColumn).toBe(2)
|
||||||
|
expect(commandProperties.line).toBe(5)
|
||||||
|
expect(commandProperties.endLine).toBe(5)
|
||||||
|
|
||||||
|
expect(commandProperties.startColumn).toBeUndefined()
|
||||||
|
expect(commandProperties.startLine).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
it('startGroup starts a new group', () => {
|
it('startGroup starts a new group', () => {
|
||||||
core.startGroup('my-group')
|
core.startGroup('my-group')
|
||||||
assertWriteCalls([`::group::my-group${os.EOL}`])
|
assertWriteCalls([`::group::my-group${os.EOL}`])
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {toCommandValue} from './utils'
|
||||||
// We use any as a valid input type
|
// We use any as a valid input type
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
interface CommandProperties {
|
export interface CommandProperties {
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {issue, issueCommand} from './command'
|
import {issue, issueCommand} from './command'
|
||||||
import {issueCommand as issueFileCommand} from './file-command'
|
import {issueCommand as issueFileCommand} from './file-command'
|
||||||
import {toCommandValue} from './utils'
|
import {toCommandProperties, toCommandValue} from './utils'
|
||||||
|
|
||||||
import * as os from 'os'
|
import * as os from 'os'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
|
@ -31,6 +31,38 @@ export enum ExitCode {
|
||||||
Failure = 1
|
Failure = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional properties that can be sent with annotatation commands (notice, error, and warning)
|
||||||
|
* See: https://docs.github.com/en/rest/reference/checks#create-a-check-run for more information about annotations.
|
||||||
|
*/
|
||||||
|
export interface AnnotationProperties {
|
||||||
|
/**
|
||||||
|
* A title for the annotation.
|
||||||
|
*/
|
||||||
|
title?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The start line for the annotation.
|
||||||
|
*/
|
||||||
|
startLine?: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The end line for the annotation. Defaults to `startLine` when `startLine` is provided.
|
||||||
|
*/
|
||||||
|
endLine?: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The start column for the annotation. Cannot be sent when `startLine` and `endLine` are different values.
|
||||||
|
*/
|
||||||
|
startColumn?: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The start column for the annotation. Cannot be sent when `startLine` and `endLine` are different values.
|
||||||
|
* Defaults to `startColumn` when `startColumn` is provided.
|
||||||
|
*/
|
||||||
|
endColumn?: number
|
||||||
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
//-----------------------------------------------------------------------
|
||||||
// Variables
|
// Variables
|
||||||
//-----------------------------------------------------------------------
|
//-----------------------------------------------------------------------
|
||||||
|
@ -199,17 +231,49 @@ export function debug(message: string): void {
|
||||||
/**
|
/**
|
||||||
* Adds an error issue
|
* Adds an error issue
|
||||||
* @param message error issue message. Errors will be converted to string via toString()
|
* @param message error issue message. Errors will be converted to string via toString()
|
||||||
|
* @param properties optional properties to add to the annotation.
|
||||||
*/
|
*/
|
||||||
export function error(message: string | Error): void {
|
export function error(
|
||||||
issue('error', message instanceof Error ? message.toString() : message)
|
message: string | Error,
|
||||||
|
properties: AnnotationProperties = {}
|
||||||
|
): void {
|
||||||
|
issueCommand(
|
||||||
|
'error',
|
||||||
|
toCommandProperties(properties),
|
||||||
|
message instanceof Error ? message.toString() : message
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds an warning issue
|
* Adds a warning issue
|
||||||
* @param message warning issue message. Errors will be converted to string via toString()
|
* @param message warning issue message. Errors will be converted to string via toString()
|
||||||
|
* @param properties optional properties to add to the annotation.
|
||||||
*/
|
*/
|
||||||
export function warning(message: string | Error): void {
|
export function warning(
|
||||||
issue('warning', message instanceof Error ? message.toString() : message)
|
message: string | Error,
|
||||||
|
properties: AnnotationProperties = {}
|
||||||
|
): void {
|
||||||
|
issueCommand(
|
||||||
|
'warning',
|
||||||
|
toCommandProperties(properties),
|
||||||
|
message instanceof Error ? message.toString() : message
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a notice issue
|
||||||
|
* @param message notice issue message. Errors will be converted to string via toString()
|
||||||
|
* @param properties optional properties to add to the annotation.
|
||||||
|
*/
|
||||||
|
export function notice(
|
||||||
|
message: string | Error,
|
||||||
|
properties: AnnotationProperties = {}
|
||||||
|
): void {
|
||||||
|
issueCommand(
|
||||||
|
'notice',
|
||||||
|
toCommandProperties(properties),
|
||||||
|
message instanceof Error ? message.toString() : message
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// We use any as a valid input type
|
// We use any as a valid input type
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
import {AnnotationProperties} from './core'
|
||||||
|
import {CommandProperties} from './command'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sanitizes an input into a string so it can be passed into issueCommand safely
|
* Sanitizes an input into a string so it can be passed into issueCommand safely
|
||||||
* @param input input to sanitize into a string
|
* @param input input to sanitize into a string
|
||||||
|
@ -13,3 +16,25 @@ export function toCommandValue(input: any): string {
|
||||||
}
|
}
|
||||||
return JSON.stringify(input)
|
return JSON.stringify(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param annotationProperties
|
||||||
|
* @returns The command properties to send with the actual annotation command
|
||||||
|
* See IssueCommandProperties: https://github.com/actions/runner/blob/main/src/Runner.Worker/ActionCommandManager.cs#L646
|
||||||
|
*/
|
||||||
|
export function toCommandProperties(
|
||||||
|
annotationProperties: AnnotationProperties
|
||||||
|
): CommandProperties {
|
||||||
|
if (!Object.keys(annotationProperties).length) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: annotationProperties.title,
|
||||||
|
line: annotationProperties.startLine,
|
||||||
|
endLine: annotationProperties.endLine,
|
||||||
|
col: annotationProperties.startColumn,
|
||||||
|
endColumn: annotationProperties.endColumn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue