Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(formatters): add GitHub Actions formatter #2508

Merged
merged 13 commits into from
Jul 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/guides/2-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ Other options include:
[string] [choices: "utf8", "ascii", "utf-8", "utf16le", "ucs2", "ucs-2", "base64", "latin1"] [default: "utf8"]
-f, --format formatters to use for outputting results, more than one can be given joining them with
a comma
[string] [choices: "json", "stylish", "junit", "html", "text", "teamcity", "pretty"] [default: "stylish"]
[string] [choices: "json", "stylish", "junit", "html", "text", "teamcity", "pretty", "github-actions"] [default:
"stylish"]
-o, --output where to output results, can be a single file name, multiple "output.<format>" or
missing to print to stdout [string]
--stdin-filepath path to a file to pretend that stdin comes from [string]
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/services/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export enum OutputFormat {
TEXT = 'text',
TEAMCITY = 'teamcity',
PRETTY = 'pretty',
GITHUB_ACTIONS = 'github-actions',
}

export interface ILintConfig {
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/services/output.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as process from 'process';
import { IRuleResult } from '@stoplight/spectral-core';
import { promises as fs } from 'fs';
import { html, json, junit, stylish, teamcity, text, pretty } from '@stoplight/spectral-formatters';
import { html, json, junit, stylish, teamcity, text, pretty, githubActions } from '@stoplight/spectral-formatters';
import type { Formatter, FormatterOptions } from '@stoplight/spectral-formatters';
import type { OutputFormat } from './config';

Expand All @@ -13,6 +13,7 @@ const formatters: Record<OutputFormat, Formatter> = {
html,
text,
teamcity,
'github-actions': githubActions,
};

export function formatOutput(results: IRuleResult[], format: OutputFormat, formatOptions: FormatterOptions): string {
Expand Down
1 change: 1 addition & 0 deletions packages/formatters/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ console.error(output);
### Node.js only

- pretty
- github-actions
50 changes: 50 additions & 0 deletions packages/formatters/src/__tests__/github-actions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { DiagnosticSeverity } from '@stoplight/types';
import type { IRuleResult } from '@stoplight/spectral-core';
import { githubActions } from '../github-actions';

const cwd = process.cwd();
const results: IRuleResult[] = [
{
code: 'operation-description',
message: 'paths./pets.get.description is not truthy\nMessage can have\nmultiple lines',
path: ['paths', '/pets', 'get', 'description'],
severity: 1,
source: `${cwd}/__tests__/fixtures/petstore.oas2.yaml`,
range: {
start: {
line: 60,
character: 8,
},
end: {
line: 71,
character: 60,
},
},
},
{
code: 'operation-tags',
message: 'paths./pets.get.tags is not truthy',
path: ['paths', '/pets', 'get', 'tags'],
severity: 1,
source: `${cwd}/__tests__/fixtures/petstore.oas2.yaml`,
range: {
start: {
line: 60,
character: 8,
},
end: {
line: 71,
character: 60,
},
},
},
];

describe('GitHub Actions formatter', () => {
test('should be formatted correctly', () => {
expect(githubActions(results, { failSeverity: DiagnosticSeverity.Error }).split('\n')).toEqual([
'::warning title=operation-description,file=__tests__/fixtures/petstore.oas2.yaml,col=9,endColumn=61,line=61,endLine=72::paths./pets.get.description is not truthy%0AMessage can have%0Amultiple lines',
'::warning title=operation-tags,file=__tests__/fixtures/petstore.oas2.yaml,col=9,endColumn=61,line=61,endLine=72::paths./pets.get.tags is not truthy',
]);
});
});
47 changes: 47 additions & 0 deletions packages/formatters/src/github-actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { relative } from '@stoplight/path';
import { DiagnosticSeverity, Dictionary } from '@stoplight/types';
import { Formatter } from './types';

const OUTPUT_TYPES: Dictionary<string, DiagnosticSeverity> = {
[DiagnosticSeverity.Error]: 'error',
[DiagnosticSeverity.Warning]: 'warning',
[DiagnosticSeverity.Information]: 'notice',
[DiagnosticSeverity.Hint]: 'notice',
};

type OutputParams = {
title?: string;
file: string;
col?: number;
endColumn?: number;
line?: number;
endLine?: number;
};

export const githubActions: Formatter = results => {
return results
.map(result => {
// GitHub Actions requires relative path for annotations, determining from working directory here
const file = relative(process.cwd(), result.source ?? '');
const params: OutputParams = {
title: result.code.toString(),
file,
col: result.range.start.character + 1,
endColumn: result.range.end.character + 1,
line: result.range.start.line + 1,
endLine: result.range.end.line + 1,
};

const paramsString = Object.entries(params)
.map(p => p.join('='))
.join(',');

// As annotated messages must be one-line due to GitHub's limitation, replacing all LF to %0A here.
// see: https://github.com/actions/toolkit/issues/193
// FIXME: Use replaceAll instead after removing Node.js 14 support.
const message = result.message.replace(/\n/g, '%0A');

return `::${OUTPUT_TYPES[result.severity]} ${paramsString}::${message}`;
})
.join('\n');
};
1 change: 1 addition & 0 deletions packages/formatters/src/index.node.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { html, json, junit, text, stylish, teamcity } from './index';
export type { Formatter, FormatterOptions } from './index';
export { pretty } from './pretty';
export { githubActions } from './github-actions';
4 changes: 4 additions & 0 deletions packages/formatters/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ export type { Formatter, FormatterOptions } from './types';
export const pretty: Formatter = () => {
throw Error('pretty formatter is available only in Node.js');
};

export const githubActions: Formatter = () => {
throw Error('github-actions formatter is available only in Node.js');
};
2 changes: 1 addition & 1 deletion test-harness/scenarios/help-no-document.scenario
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Options:
--version Show version number [boolean]
--help Show help [boolean]
-e, --encoding text encoding to use [string] [choices: "utf8", "ascii", "utf-8", "utf16le", "ucs2", "ucs-2", "base64", "latin1"] [default: "utf8"]
-f, --format formatters to use for outputting results, more than one can be given joining them with a comma [string] [choices: "json", "stylish", "junit", "html", "text", "teamcity", "pretty"] [default: "stylish"]
-f, --format formatters to use for outputting results, more than one can be given joining them with a comma [string] [choices: "json", "stylish", "junit", "html", "text", "teamcity", "pretty", "github-actions"] [default: "stylish"]
-o, --output where to output results, can be a single file name, multiple "output.<format>" or missing to print to stdout [string]
--stdin-filepath path to a file to pretend that stdin comes from [string]
--resolver path to custom json-ref-resolver instance [string]
Expand Down
2 changes: 1 addition & 1 deletion test-harness/scenarios/strict-options.scenario
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Options:
--version Show version number [boolean]
--help Show help [boolean]
-e, --encoding text encoding to use [string] [choices: "utf8", "ascii", "utf-8", "utf16le", "ucs2", "ucs-2", "base64", "latin1"] [default: "utf8"]
-f, --format formatters to use for outputting results, more than one can be given joining them with a comma [string] [choices: "json", "stylish", "junit", "html", "text", "teamcity", "pretty"] [default: "stylish"]
-f, --format formatters to use for outputting results, more than one can be given joining them with a comma [string] [choices: "json", "stylish", "junit", "html", "text", "teamcity", "pretty", "github-actions"] [default: "stylish"]
-o, --output where to output results, can be a single file name, multiple "output.<format>" or missing to print to stdout [string]
--stdin-filepath path to a file to pretend that stdin comes from [string]
--resolver path to custom json-ref-resolver instance [string]
Expand Down