From 9991a2955dd3cc80809ce9a1bec310a183aeef39 Mon Sep 17 00:00:00 2001 From: Natsuki Ikeguchi Date: Thu, 13 Jul 2023 16:12:26 +0900 Subject: [PATCH 01/10] feat(formatters): add GitHub Actions formatter --- packages/cli/src/services/config.ts | 1 + packages/cli/src/services/output.ts | 3 +- .../src/__tests__/github-actions.test.ts | 50 +++++++++++++++++++ packages/formatters/src/github-actions.ts | 39 +++++++++++++++ packages/formatters/src/index.node.ts | 2 +- packages/formatters/src/index.ts | 1 + 6 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 packages/formatters/src/__tests__/github-actions.test.ts create mode 100644 packages/formatters/src/github-actions.ts diff --git a/packages/cli/src/services/config.ts b/packages/cli/src/services/config.ts index 50024e510..909a831f0 100644 --- a/packages/cli/src/services/config.ts +++ b/packages/cli/src/services/config.ts @@ -11,6 +11,7 @@ export enum OutputFormat { TEXT = 'text', TEAMCITY = 'teamcity', PRETTY = 'pretty', + GITHUB_ACTIONS = 'github-actions', } export interface ILintConfig { diff --git a/packages/cli/src/services/output.ts b/packages/cli/src/services/output.ts index a7bb0f0ea..bfc2e3ed6 100644 --- a/packages/cli/src/services/output.ts +++ b/packages/cli/src/services/output.ts @@ -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'; @@ -13,6 +13,7 @@ const formatters: Record = { html, text, teamcity, + 'github-actions': githubActions, }; export function formatOutput(results: IRuleResult[], format: OutputFormat, formatOptions: FormatterOptions): string { diff --git a/packages/formatters/src/__tests__/github-actions.test.ts b/packages/formatters/src/__tests__/github-actions.test.ts new file mode 100644 index 000000000..3283212d1 --- /dev/null +++ b/packages/formatters/src/__tests__/github-actions.test.ts @@ -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', + 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=8,endColumn=60,line=60,endLine=71::paths./pets.get.description is not truthy', + '::warning title=operation-tags,file=__tests__/fixtures/petstore.oas2.yaml,col=8,endColumn=60,line=60,endLine=71::paths./pets.get.tags is not truthy', + ]); + }); +}); diff --git a/packages/formatters/src/github-actions.ts b/packages/formatters/src/github-actions.ts new file mode 100644 index 000000000..bf2ea3d0e --- /dev/null +++ b/packages/formatters/src/github-actions.ts @@ -0,0 +1,39 @@ +import { DiagnosticSeverity, Dictionary } from '@stoplight/types'; +import { Formatter } from './types'; + +const OUTPUT_TYPES: Dictionary = { + [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 => { + const params: OutputParams = { + title: result.code.toString(), + file: require('path').relative(process.cwd(), result.source ?? ''), + col: result.range.start.character, + endColumn: result.range.end.character, + line: result.range.start.line, + endLine: result.range.end.line, + }; + + const paramsString = Object.entries(params) + .map(p => p.join('=')) + .join(','); + + return `::${OUTPUT_TYPES[result.severity]} ${paramsString}::${result.message}`; + }) + .join('\n'); +}; diff --git a/packages/formatters/src/index.node.ts b/packages/formatters/src/index.node.ts index bbb40b065..b064f82e9 100644 --- a/packages/formatters/src/index.node.ts +++ b/packages/formatters/src/index.node.ts @@ -1,3 +1,3 @@ -export { html, json, junit, text, stylish, teamcity } from './index'; +export { githubActions, html, json, junit, text, stylish, teamcity } from './index'; export type { Formatter, FormatterOptions } from './index'; export { pretty } from './pretty'; diff --git a/packages/formatters/src/index.ts b/packages/formatters/src/index.ts index 4c3b90e34..93ed15a71 100644 --- a/packages/formatters/src/index.ts +++ b/packages/formatters/src/index.ts @@ -4,6 +4,7 @@ export * from './junit'; export * from './html'; export * from './text'; export * from './teamcity'; +export * from './github-actions'; import type { Formatter } from './types'; export type { Formatter, FormatterOptions } from './types'; From 42b79be6cacddf0e4e573335db22b90c3f89c534 Mon Sep 17 00:00:00 2001 From: Natsuki Ikeguchi Date: Thu, 13 Jul 2023 16:28:01 +0900 Subject: [PATCH 02/10] docs(cli): update formatter list in docs --- docs/guides/2-cli.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/guides/2-cli.md b/docs/guides/2-cli.md index f38944c80..7bfd2e5ab 100644 --- a/docs/guides/2-cli.md +++ b/docs/guides/2-cli.md @@ -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." or missing to print to stdout [string] --stdin-filepath path to a file to pretend that stdin comes from [string] From 3295bd4885ce4dc5aa9f1db4fe504bf4d44473e4 Mon Sep 17 00:00:00 2001 From: Natsuki Ikeguchi Date: Thu, 13 Jul 2023 16:36:03 +0900 Subject: [PATCH 03/10] test(cli): update test scenarios --- test-harness/scenarios/help-no-document.scenario | 2 +- test-harness/scenarios/strict-options.scenario | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test-harness/scenarios/help-no-document.scenario b/test-harness/scenarios/help-no-document.scenario index 8e686198b..038861d46 100644 --- a/test-harness/scenarios/help-no-document.scenario +++ b/test-harness/scenarios/help-no-document.scenario @@ -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." 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] diff --git a/test-harness/scenarios/strict-options.scenario b/test-harness/scenarios/strict-options.scenario index 8b1cb3708..4fa1203e0 100644 --- a/test-harness/scenarios/strict-options.scenario +++ b/test-harness/scenarios/strict-options.scenario @@ -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." 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] From 275b50dc1060bf6e1a6766d8c278b3882dc29758 Mon Sep 17 00:00:00 2001 From: Natsuki Ikeguchi Date: Thu, 13 Jul 2023 17:12:23 +0900 Subject: [PATCH 04/10] fix(formatters): fix ESLint errors --- packages/formatters/src/github-actions.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/formatters/src/github-actions.ts b/packages/formatters/src/github-actions.ts index bf2ea3d0e..ec0f664bb 100644 --- a/packages/formatters/src/github-actions.ts +++ b/packages/formatters/src/github-actions.ts @@ -18,11 +18,13 @@ type OutputParams = { }; export const githubActions: Formatter = results => { + const path = require('path') as { relative: (from: string, to: string) => string }; + return results .map(result => { const params: OutputParams = { title: result.code.toString(), - file: require('path').relative(process.cwd(), result.source ?? ''), + file: path.relative(process.cwd(), result.source ?? ''), col: result.range.start.character, endColumn: result.range.end.character, line: result.range.start.line, From a18d5917afa10aa5f95110384cd6b413d8168c93 Mon Sep 17 00:00:00 2001 From: Natsuki Ikeguchi Date: Sun, 16 Jul 2023 22:55:51 +0900 Subject: [PATCH 05/10] refactor(formatters): use @stoplight/path instead of node:path --- packages/formatters/src/github-actions.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/formatters/src/github-actions.ts b/packages/formatters/src/github-actions.ts index ec0f664bb..f1ffaddc2 100644 --- a/packages/formatters/src/github-actions.ts +++ b/packages/formatters/src/github-actions.ts @@ -1,3 +1,4 @@ +import { relative } from '@stoplight/path'; import { DiagnosticSeverity, Dictionary } from '@stoplight/types'; import { Formatter } from './types'; @@ -18,13 +19,13 @@ type OutputParams = { }; export const githubActions: Formatter = results => { - const path = require('path') as { relative: (from: string, to: string) => string }; - 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: path.relative(process.cwd(), result.source ?? ''), + file, col: result.range.start.character, endColumn: result.range.end.character, line: result.range.start.line, From d79e4b986353bb0092644ec6eb10b5c471e21f51 Mon Sep 17 00:00:00 2001 From: Natsuki Ikeguchi Date: Sun, 16 Jul 2023 22:58:02 +0900 Subject: [PATCH 06/10] fix(formatters): export github-actions formatter for Node.js only --- packages/formatters/src/index.node.ts | 3 ++- packages/formatters/src/index.ts | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/formatters/src/index.node.ts b/packages/formatters/src/index.node.ts index b064f82e9..d9e9b3ce2 100644 --- a/packages/formatters/src/index.node.ts +++ b/packages/formatters/src/index.node.ts @@ -1,3 +1,4 @@ -export { githubActions, html, json, junit, text, stylish, teamcity } from './index'; +export { html, json, junit, text, stylish, teamcity } from './index'; export type { Formatter, FormatterOptions } from './index'; export { pretty } from './pretty'; +export { githubActions } from './github-actions'; diff --git a/packages/formatters/src/index.ts b/packages/formatters/src/index.ts index 93ed15a71..8caf77087 100644 --- a/packages/formatters/src/index.ts +++ b/packages/formatters/src/index.ts @@ -4,10 +4,13 @@ export * from './junit'; export * from './html'; export * from './text'; export * from './teamcity'; -export * from './github-actions'; import type { Formatter } from './types'; 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'); +}; From 00b5dfc8f04d4b0c398baf8856d8debeb93d6c47 Mon Sep 17 00:00:00 2001 From: Natsuki Ikeguchi Date: Sun, 16 Jul 2023 22:58:22 +0900 Subject: [PATCH 07/10] docs(formatters): update README.md --- packages/formatters/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/formatters/README.md b/packages/formatters/README.md index cc2dfa53c..ebade72f6 100644 --- a/packages/formatters/README.md +++ b/packages/formatters/README.md @@ -32,3 +32,4 @@ console.error(output); ### Node.js only - pretty +- github-actions From b8298760dcc74c8684a7823731a2448a44f9f34d Mon Sep 17 00:00:00 2001 From: Natsuki Ikeguchi Date: Tue, 25 Jul 2023 23:56:38 +0900 Subject: [PATCH 08/10] fix(formatters): escape newlines with %0A --- packages/formatters/src/github-actions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/formatters/src/github-actions.ts b/packages/formatters/src/github-actions.ts index f1ffaddc2..85b2f587a 100644 --- a/packages/formatters/src/github-actions.ts +++ b/packages/formatters/src/github-actions.ts @@ -36,7 +36,7 @@ export const githubActions: Formatter = results => { .map(p => p.join('=')) .join(','); - return `::${OUTPUT_TYPES[result.severity]} ${paramsString}::${result.message}`; + return `::${OUTPUT_TYPES[result.severity]} ${paramsString}::${result.message.replaceAll('\n', '%0A')}`; }) .join('\n'); }; From 0b0c6351e9b4ce5b02b789491d4af0efbee5d325 Mon Sep 17 00:00:00 2001 From: Natsuki Ikeguchi Date: Tue, 25 Jul 2023 23:58:46 +0900 Subject: [PATCH 09/10] fix(formatters): add 1 to column numbers and line numbers # Conflicts: # packages/formatters/src/github-actions.ts --- packages/formatters/src/__tests__/github-actions.test.ts | 4 ++-- packages/formatters/src/github-actions.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/formatters/src/__tests__/github-actions.test.ts b/packages/formatters/src/__tests__/github-actions.test.ts index 3283212d1..16d5db68b 100644 --- a/packages/formatters/src/__tests__/github-actions.test.ts +++ b/packages/formatters/src/__tests__/github-actions.test.ts @@ -43,8 +43,8 @@ const results: IRuleResult[] = [ 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=8,endColumn=60,line=60,endLine=71::paths./pets.get.description is not truthy', - '::warning title=operation-tags,file=__tests__/fixtures/petstore.oas2.yaml,col=8,endColumn=60,line=60,endLine=71::paths./pets.get.tags is not truthy', + '::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', + '::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', ]); }); }); diff --git a/packages/formatters/src/github-actions.ts b/packages/formatters/src/github-actions.ts index 85b2f587a..49914a60a 100644 --- a/packages/formatters/src/github-actions.ts +++ b/packages/formatters/src/github-actions.ts @@ -26,10 +26,10 @@ export const githubActions: Formatter = results => { const params: OutputParams = { title: result.code.toString(), file, - col: result.range.start.character, - endColumn: result.range.end.character, - line: result.range.start.line, - endLine: result.range.end.line, + 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) From f0bc3fbd7bef3ecd7e3df969cbe3ec9fc2f40f4c Mon Sep 17 00:00:00 2001 From: Natsuki Ikeguchi Date: Wed, 26 Jul 2023 00:06:46 +0900 Subject: [PATCH 10/10] fix(formatters): use replace and g flag instead of replaceAll --- packages/formatters/src/__tests__/github-actions.test.ts | 4 ++-- packages/formatters/src/github-actions.ts | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/formatters/src/__tests__/github-actions.test.ts b/packages/formatters/src/__tests__/github-actions.test.ts index 16d5db68b..c81d76fec 100644 --- a/packages/formatters/src/__tests__/github-actions.test.ts +++ b/packages/formatters/src/__tests__/github-actions.test.ts @@ -6,7 +6,7 @@ const cwd = process.cwd(); const results: IRuleResult[] = [ { code: 'operation-description', - message: 'paths./pets.get.description is not truthy', + 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`, @@ -43,7 +43,7 @@ const results: IRuleResult[] = [ 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', + '::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', ]); }); diff --git a/packages/formatters/src/github-actions.ts b/packages/formatters/src/github-actions.ts index 49914a60a..f09b43adf 100644 --- a/packages/formatters/src/github-actions.ts +++ b/packages/formatters/src/github-actions.ts @@ -36,7 +36,12 @@ export const githubActions: Formatter = results => { .map(p => p.join('=')) .join(','); - return `::${OUTPUT_TYPES[result.severity]} ${paramsString}::${result.message.replaceAll('\n', '%0A')}`; + // 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'); };