diff --git a/docs/assets/annotations.png b/docs/assets/annotations.png new file mode 100644 index 0000000000..f210022090 Binary files /dev/null and b/docs/assets/annotations.png differ diff --git a/docs/problem-matchers.md b/docs/problem-matchers.md index 520d26bae5..e496b299bb 100644 --- a/docs/problem-matchers.md +++ b/docs/problem-matchers.md @@ -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. -- 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 run (separate from the job annotations, these annotations aren’t created by users) diff --git a/packages/core/README.md b/packages/core/README.md index deffaa5d87..0df868880d 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -92,6 +92,8 @@ try { // Do stuff core.info('Output to the actions build log') + + core.notice('This is a message that will also emit an annotation') } catch (err) { core.error(`Error ${err}, action may still succeed though`); @@ -115,6 +117,53 @@ 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 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. diff --git a/packages/core/__tests__/core.test.ts b/packages/core/__tests__/core.test.ts index 651da543fd..bae51c468a 100644 --- a/packages/core/__tests__/core.test.ts +++ b/packages/core/__tests__/core.test.ts @@ -2,7 +2,7 @@ import * as fs from 'fs' import * as os from 'os' import * as path from 'path' import * as core from '../src/core' -import { toCommandProperties } from '../src/utils' +import {toCommandProperties} from '../src/utils' /* eslint-disable @typescript-eslint/unbound-method */ @@ -142,17 +142,17 @@ describe('@actions/core', () => { }) it('getInput gets required input', () => { - expect(core.getInput('my input', { required: true })).toBe('val') + expect(core.getInput('my input', {required: true})).toBe('val') }) it('getInput throws on missing required input', () => { - expect(() => core.getInput('missing', { required: true })).toThrow( + expect(() => core.getInput('missing', {required: true})).toThrow( 'Input required and not supplied: missing' ) }) it('getInput does not throw on missing non-required input', () => { - expect(core.getInput('missing', { required: false })).toBe('') + expect(core.getInput('missing', {required: false})).toBe('') }) it('getInput is case insensitive', () => { @@ -183,13 +183,13 @@ describe('@actions/core', () => { it('getInput trims whitespace when option is explicitly true', () => { expect( - core.getInput('with trailing whitespace', { trimWhitespace: true }) + core.getInput('with trailing whitespace', {trimWhitespace: true}) ).toBe('some val') }) it('getInput does not trim whitespace when option is false', () => { expect( - core.getInput('with trailing whitespace', { trimWhitespace: false }) + core.getInput('with trailing whitespace', {trimWhitespace: false}) ).toBe(' some val ') }) @@ -198,7 +198,7 @@ describe('@actions/core', () => { }) it('getInput gets required input', () => { - expect(core.getBooleanInput('boolean input', { required: true })).toBe(true) + expect(core.getBooleanInput('boolean input', {required: true})).toBe(true) }) it('getBooleanInput handles boolean input', () => { @@ -213,7 +213,7 @@ describe('@actions/core', () => { it('getBooleanInput handles wrong boolean input', () => { expect(() => core.getBooleanInput('wrong boolean input')).toThrow( 'Input does not meet YAML 1.2 "Core Schema" specification: wrong boolean input\n' + - `Support boolean input list: \`true | True | TRUE | false | False | FALSE\`` + `Support boolean input list: \`true | True | TRUE | false | False | FALSE\`` ) }) @@ -272,8 +272,16 @@ describe('@actions/core', () => { 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,end_line=5,col=1,end_column=2::Error: ${message}${os.EOL}`]) + core.error(new Error(message), { + title: 'A title', + startColumn: 1, + endColumn: 2, + startLine: 5, + endLine: 5 + }) + assertWriteCalls([ + `::error title=A title,line=5,end_line=5,col=1,end_column=2::Error: ${message}${os.EOL}` + ]) }) it('warning sets the correct message', () => { @@ -294,12 +302,26 @@ describe('@actions/core', () => { 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,end_line=5,col=1,end_column=2::Error: ${message}${os.EOL}`]) + core.warning(new Error(message), { + title: 'A title', + startColumn: 1, + endColumn: 2, + startLine: 5, + endLine: 5 + }) + assertWriteCalls([ + `::warning title=A title,line=5,end_line=5,col=1,end_column=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 }) + 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.end_column).toBe(2)