eslint-formatter-gitlab 5.1.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,22 +1,26 @@
1
1
  # ESLint Formatter for GitLab
2
2
 
3
+ [![gitlab pipeline](https://gitlab.com/remcohaszing/eslint-formatter-gitlab/badges/main/pipeline.svg)](https://gitlab.com/remcohaszing/eslint-formatter-gitlab/-/pipelines)
4
+ [![code coverage](https://gitlab.com/remcohaszing/eslint-formatter-gitlab/badges/main/coverage.svg)](https://gitlab.com/remcohaszing/eslint-formatter-gitlab/-/pipelines)
5
+ [![sponsors](https://img.shields.io/github/sponsors/remcohaszing)](https://github.com/sponsors/remcohaszing)
6
+ [![npm version](https://img.shields.io/npm/v/eslint-formatter-gitlab)](https://www.npmjs.com/package/eslint-formatter-gitlab)
7
+ [![npm downloads](https://img.shields.io/npm/dm/eslint-formatter-gitlab)](https://www.npmjs.com/package/eslint-formatter-gitlab)
8
+
9
+ <img alt="" height="256" src="https://gitlab.com/remcohaszing/eslint-formatter-gitlab/-/avatar">
10
+
3
11
  Show ESLint results directly in the
4
- [GitLab code quality](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality.html)
5
- results.
12
+ [GitLab code quality](https://docs.gitlab.com/ee/ci/testing/code_quality.html) results.
6
13
 
7
14
  ## Table of Contents
8
15
 
9
- - [Requirements](#requirements)
10
16
  - [Installation](#installation)
11
17
  - [Usage](#usage)
18
+ - [Programmatic usage](#programmatic-usage)
12
19
  - [Example](#example)
13
20
  - [Configuration](#configuration)
21
+ - [Compatibility](#compatibility)
14
22
  - [License](#license)
15
23
 
16
- ## Requirements
17
-
18
- This package requires at least Node.js 18 and ESLint 5.
19
-
20
24
  ## Installation
21
25
 
22
26
  Install `eslint` and `eslint-formatter-gitlab` using your package manager.
@@ -29,7 +33,7 @@ npm install --save-dev eslint eslint-formatter-gitlab
29
33
 
30
34
  Define a GitLab job to run `eslint`.
31
35
 
32
- _.gitlab-ci.yml_:
36
+ `.gitlab-ci.yml`:
33
37
 
34
38
  ```yaml
35
39
  eslint:
@@ -43,7 +47,21 @@ eslint:
43
47
  ```
44
48
 
45
49
  The formatter automatically detects a GitLab CI environment. It detects where to output the code
46
- quality report based on the GitLab configuration file.
50
+ quality report based on the GitLab configuration file. It also prints ESLint issues to the GitLab
51
+ job console with links.
52
+
53
+ ### Programmatic usage
54
+
55
+ The formatter can be used programmatically using ESLint.
56
+
57
+ ```js
58
+ import { ESLint } from 'eslint'
59
+
60
+ const eslint = new ESLint()
61
+ const formatter = await eslint.loadFormatter('gitlab')
62
+ const results = await eslint.lintFiles([])
63
+ const formatted = await formatter.format(results)
64
+ ```
47
65
 
48
66
  ## Example
49
67
 
@@ -55,8 +73,20 @@ An example of the results can be seen in
55
73
 
56
74
  ESLint formatters don’t take any configuration options. `eslint-formatter-gitlab` uses GitLab’s
57
75
  [predefined environment variables](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html)
58
- to configure the output. In addition, the environment variable `ESLINT_CODE_QUALITY_REPORT` is used
59
- to override the location to store the code quality report.
76
+ to configure the output. The following predefined environment variables are used:
77
+
78
+ - `CI_COMMIT_SHORT_SHA` to generate a link in the console output.
79
+ - `CI_CONFIG_PATH` to determine the GitLab CI configuration file to use. (Default: `.gitlab-ci.yml`)
80
+ - `CI_JOB_NAME` to determine which job configuration to read the code quality report path from.
81
+ - `CI_PROJECT_DIR` To determine relative paths. (Default: current working directory)
82
+ - `CI_PROJECT_URL` to generate a link in the console output.
83
+
84
+ In addition, the environment variable `ESLINT_CODE_QUALITY_REPORT` is used to override the location
85
+ to store the code quality report.
86
+
87
+ ## Compatibility
88
+
89
+ This package is compatible with Node.js 20 or greater and ESLint 9 or greater.
60
90
 
61
91
  ## License
62
92
 
@@ -1,20 +1,14 @@
1
- const { createHash } = require('node:crypto')
2
- const { existsSync, lstatSync, mkdirSync, readFileSync, writeFileSync } = require('node:fs')
3
- const { EOL } = require('node:os')
4
- const { dirname, join, relative, resolve } = require('node:path')
5
-
6
- const chalk = require('chalk')
7
- const yaml = require('yaml')
8
-
9
- const {
10
- CI_COMMIT_SHORT_SHA,
11
- CI_CONFIG_PATH = '.gitlab-ci.yml',
12
- CI_JOB_NAME,
13
- CI_PROJECT_DIR = process.cwd(),
14
- CI_PROJECT_URL,
15
- ESLINT_CODE_QUALITY_REPORT,
16
- GITLAB_CI
17
- } = process.env
1
+ /**
2
+ * @import { Issue } from 'codeclimate-types'
3
+ * @import { ESLint, Linter } from 'eslint'
4
+ */
5
+
6
+ import { createHash } from 'node:crypto'
7
+ import { mkdir, readFile, writeFile } from 'node:fs/promises'
8
+ import { dirname, join, relative, resolve } from 'node:path'
9
+ import { styleText } from 'node:util'
10
+
11
+ import yaml from 'yaml'
18
12
 
19
13
  /** @type {yaml.CollectionTag} */
20
14
  const reference = {
@@ -27,38 +21,45 @@ const reference = {
27
21
  }
28
22
 
29
23
  /**
30
- * @returns {string}
24
+ * @param {string} projectDir
25
+ * The GitLab project directory.
26
+ * @param {string | undefined} jobName
27
+ * The GitLab CI job name.
28
+ * @returns {Promise<string>}
31
29
  * The output path of the code quality artifact.
32
30
  */
33
- function getOutputPath() {
34
- const configPath = join(CI_PROJECT_DIR, CI_CONFIG_PATH)
31
+ async function getOutputPath(projectDir, jobName) {
32
+ const configPath = join(projectDir, process.env.CI_CONFIG_PATH ?? '.gitlab-ci.yml')
35
33
  // GitlabCI allows a custom configuration path which can be a URL or a path relative to another
36
34
  // project. In these cases CI_CONFIG_PATH is empty and we'll have to require the user provide
37
35
  // ESLINT_CODE_QUALITY_REPORT.
38
- if (!existsSync(configPath) || !lstatSync(configPath).isFile()) {
36
+ let configContents
37
+ try {
38
+ configContents = await readFile(configPath, 'utf8')
39
+ } catch {
39
40
  throw new Error(
40
41
  'Could not resolve .gitlab-ci.yml to automatically detect report artifact path.' +
41
42
  ' Please manually provide a path via the ESLINT_CODE_QUALITY_REPORT variable.'
42
43
  )
43
44
  }
44
- const doc = yaml.parseDocument(readFileSync(configPath, 'utf8'), {
45
+ const doc = yaml.parseDocument(configContents, {
45
46
  version: '1.1',
46
47
  customTags: [reference]
47
48
  })
48
- const path = [CI_JOB_NAME, 'artifacts', 'reports', 'codequality']
49
+ const path = [jobName, 'artifacts', 'reports', 'codequality']
49
50
  const location = doc.getIn(path)
50
51
  if (typeof location !== 'string' || !location) {
51
52
  throw new TypeError(
52
53
  `Expected ${path.join('.')} to be one exact path, got: ${JSON.stringify(location)}`
53
54
  )
54
55
  }
55
- return resolve(CI_PROJECT_DIR, location)
56
+ return resolve(projectDir, location)
56
57
  }
57
58
 
58
59
  /**
59
60
  * @param {string} filePath
60
61
  * The path to the linted file.
61
- * @param {import('eslint').Linter.LintMessage} message
62
+ * @param {Linter.LintMessage} message
62
63
  * The ESLint report message.
63
64
  * @param {Set<string>} hashes
64
65
  * Hashes already encountered. Used to avoid duplicate hashes
@@ -91,25 +92,27 @@ function createFingerprint(filePath, message, hashes) {
91
92
  }
92
93
 
93
94
  /**
94
- * @param {import('eslint').ESLint.LintResult[]} results
95
+ * @param {ESLint.LintResult[]} results
95
96
  * The ESLint report results.
96
- * @param {import('eslint').ESLint.LintResultData} data
97
+ * @param {ESLint.LintResultData} data
97
98
  * The ESLint report result data.
98
- * @returns {import('codeclimate-types').Issue[]}
99
+ * @param {string} projectDir
100
+ * The GitLab project directory.
101
+ * @returns {Issue[]}
99
102
  * The ESLint messages in the form of a GitLab code quality report.
100
103
  */
101
- function convert(results, data) {
102
- /** @type {import('codeclimate-types').Issue[]} */
104
+ function convert(results, data, projectDir) {
105
+ /** @type {Issue[]} */
103
106
  const messages = []
104
107
 
105
108
  /** @type {Set<string>} */
106
109
  const hashes = new Set()
107
110
 
108
111
  for (const result of results) {
109
- const relativePath = relative(CI_PROJECT_DIR, result.filePath)
112
+ const relativePath = relative(projectDir, result.filePath)
110
113
 
111
114
  for (const message of result.messages) {
112
- /** @type {import('codeclimate-types').Issue} */
115
+ /** @type {Issue} */
113
116
  const issue = {
114
117
  type: 'issue',
115
118
  categories: ['Style'],
@@ -175,23 +178,27 @@ function plural(count, text) {
175
178
  }
176
179
 
177
180
  /**
178
- * @param {import('eslint').ESLint.LintResult[]} results
181
+ * @param {ESLint.LintResult[]} results
179
182
  * The ESLint report results.
183
+ * @param {string} projectDir
184
+ * The GitLab project directory.
180
185
  * @returns {string}
181
186
  * The ESLint messages converted to a format suitable as output in GitLab CI job logs.
182
187
  */
183
- function gitlabConsoleFormatter(results) {
188
+ function gitlabConsoleFormatter(results, projectDir) {
184
189
  // Severity labels manually padded to have equal lengths and end with spaces
185
- const labelFatal = `${chalk.magenta('fatal')} `
186
- const labelError = `${chalk.red('error')} `
187
- const labelWarn = `${chalk.yellow('warn')} `
190
+ const labelFatal = `${styleText('magenta', 'fatal')} `
191
+ const labelError = `${styleText('red', 'error')} `
192
+ const labelWarn = `${styleText('yellow', 'warn')} `
188
193
 
189
194
  const lines = ['']
190
195
 
191
196
  /** @type {string | undefined} */
192
197
  let gitLabBaseURL
193
- if (CI_PROJECT_URL && CI_COMMIT_SHORT_SHA) {
194
- gitLabBaseURL = `${CI_PROJECT_URL}/-/blob/${CI_COMMIT_SHORT_SHA}/`
198
+ const projectUrl = process.env.CI_PROJECT_URL
199
+ const commitSha = process.env.CI_COMMIT_SHORT_SHA
200
+ if (projectUrl && commitSha) {
201
+ gitLabBaseURL = `${projectUrl}/-/blob/${commitSha}/`
195
202
  }
196
203
 
197
204
  let fatal = 0
@@ -214,7 +221,7 @@ function gitlabConsoleFormatter(results) {
214
221
 
215
222
  for (const result of results) {
216
223
  const { filePath, messages } = result
217
- const repoFilePath = relative(CI_PROJECT_DIR, filePath)
224
+ const repoFilePath = relative(projectDir, filePath)
218
225
 
219
226
  for (const message of messages) {
220
227
  let line = message.fatal ? labelFatal : message.severity === 1 ? labelWarn : labelError
@@ -227,7 +234,7 @@ function gitlabConsoleFormatter(results) {
227
234
  if (message.endLine != null && message.endLine !== message.line) {
228
235
  anchor += `-${message.endLine}`
229
236
  }
230
- line += chalk.blue(`${gitLabBaseURL}${repoFilePath}${anchor}`)
237
+ line += styleText('blue', `${gitLabBaseURL}${repoFilePath}${anchor}`)
231
238
  } else {
232
239
  line += `${filePath}:${message.line}:${message.column}`
233
240
  }
@@ -239,39 +246,36 @@ function gitlabConsoleFormatter(results) {
239
246
  const total = warnings + errors + fatal
240
247
  if (total > 0) {
241
248
  const details = `(${fatal} fatal, ${plural(errors, 'error')}, ${plural(warnings, 'warning')})`
242
- lines.push('', `${chalk.red('✖')} ${plural(total, 'problem')} ${details}`)
249
+ lines.push('', `${styleText('red', '✖')} ${plural(total, 'problem')} ${details}`)
243
250
  } else {
244
- lines.push(`${chalk.green('✔')} No problems found`)
251
+ lines.push(`${styleText('green', '✔')} No problems found`)
245
252
  }
246
253
 
247
254
  lines.push('')
248
- return lines.join(EOL)
255
+ return lines.join('\n')
249
256
  }
250
257
 
251
258
  /**
252
- * @param {import('eslint').ESLint.LintResult[]} results
259
+ * @param {ESLint.LintResult[]} results
253
260
  * The ESLint report results.
254
- * @param {import('eslint').ESLint.LintResultData} data
261
+ * @param {ESLint.LintResultData} data
255
262
  * The ESLint report result data.
256
- * @returns {string}
263
+ * @returns {Promise<string>}
257
264
  * The ESLint output to print to the console.
258
265
  */
259
- function eslintFormatterGitLab(results, data) {
260
- /* c8 ignore start */
261
- if (GITLAB_CI === 'true') {
262
- chalk.level = 1
263
- }
264
-
265
- /* c8 ignore stop */
266
- if (CI_JOB_NAME || ESLINT_CODE_QUALITY_REPORT) {
267
- const issues = convert(results, data)
268
- const outputPath = ESLINT_CODE_QUALITY_REPORT || getOutputPath()
266
+ async function eslintFormatterGitLab(results, data) {
267
+ let outputPath = process.env.ESLINT_CODE_QUALITY_REPORT
268
+ const projectDir = process.env.CI_PROJECT_DIR ?? data.cwd
269
+ const jobName = process.env.CI_JOB_NAME
270
+ if (jobName || outputPath) {
271
+ const issues = convert(results, data, projectDir)
272
+ outputPath ||= await getOutputPath(projectDir, jobName)
269
273
  const dir = dirname(outputPath)
270
- mkdirSync(dir, { recursive: true })
271
- writeFileSync(outputPath, JSON.stringify(issues, null, 2))
274
+ await mkdir(dir, { recursive: true })
275
+ await writeFile(outputPath, `${JSON.stringify(issues, null, 2)}\n`)
272
276
  }
273
277
 
274
- return gitlabConsoleFormatter(results)
278
+ return gitlabConsoleFormatter(results, projectDir)
275
279
  }
276
280
 
277
- module.exports = eslintFormatterGitLab
281
+ export default eslintFormatterGitLab
package/package.json CHANGED
@@ -1,20 +1,27 @@
1
1
  {
2
2
  "name": "eslint-formatter-gitlab",
3
- "version": "5.1.0",
3
+ "version": "6.0.0",
4
4
  "description": "Show ESLint results directly in the GitLab code quality results",
5
+ "type": "module",
5
6
  "author": "Remco Haszing <remcohaszing@gmail.com>",
6
7
  "license": "MIT",
7
8
  "homepage": "https://gitlab.com/remcohaszing/eslint-formatter-gitlab#readme",
8
9
  "repository": "gitlab:remcohaszing/eslint-formatter-gitlab",
9
10
  "funding": "https://github.com/sponsors/remcohaszing",
10
11
  "bugs": "https://gitlab.com/remcohaszing/eslint-formatter-gitlab/-/issues",
11
- "exports": "./index.js",
12
+ "main": "./lib/eslint-formatter-gitlab.js",
13
+ "types": "./types/eslint-formatter-gitlab.d.ts",
14
+ "exports": {
15
+ "types": "./types/eslint-formatter-gitlab.d.ts",
16
+ "default": "./lib/eslint-formatter-gitlab.js"
17
+ },
12
18
  "files": [
13
- "index.*"
19
+ "lib",
20
+ "types"
14
21
  ],
15
22
  "scripts": {
16
23
  "prepack": "tsc --build",
17
- "test": "c8 node --test --test-reporter @reporters/junit --test-reporter-destination=junit.xml --test-reporter spec --test-reporter-destination stdout"
24
+ "test": "c8 node --test --test-reporter junit --test-reporter-destination=junit.xml --test-reporter spec --test-reporter-destination stdout"
18
25
  },
19
26
  "keywords": [
20
27
  "eslint",
@@ -24,23 +31,19 @@
24
31
  "gitlab-ci"
25
32
  ],
26
33
  "dependencies": {
27
- "chalk": "^4.0.0",
28
34
  "yaml": "^2.0.0"
29
35
  },
30
36
  "peerDependencies": {
31
- "eslint": ">=5"
37
+ "eslint": ">=9"
32
38
  },
33
39
  "devDependencies": {
34
- "@reporters/junit": "^1.0.0",
35
- "@types/eslint": "^8.0.0",
36
- "@types/node": "^20.0.0",
37
- "c8": "^8.0.0",
40
+ "@remcohaszing/eslint": "^11.0.0",
41
+ "@types/node": "^22.0.0",
42
+ "c8": "^10.0.0",
38
43
  "codeclimate-types": "^0.3.0",
39
- "eslint": "^8.0.0",
40
- "eslint-config-remcohaszing": "^10.0.0",
41
44
  "prettier": "^3.0.0",
42
- "remark-cli": "^11.0.0",
43
- "remark-preset-remcohaszing": "^2.0.0",
45
+ "remark-cli": "^12.0.0",
46
+ "remark-preset-remcohaszing": "^3.0.0",
44
47
  "typescript": "^5.0.0"
45
48
  }
46
49
  }
@@ -0,0 +1,12 @@
1
+ export default eslintFormatterGitLab;
2
+ /**
3
+ * @param {ESLint.LintResult[]} results
4
+ * The ESLint report results.
5
+ * @param {ESLint.LintResultData} data
6
+ * The ESLint report result data.
7
+ * @returns {Promise<string>}
8
+ * The ESLint output to print to the console.
9
+ */
10
+ declare function eslintFormatterGitLab(results: ESLint.LintResult[], data: ESLint.LintResultData): Promise<string>;
11
+ import type { ESLint } from 'eslint';
12
+ //# sourceMappingURL=eslint-formatter-gitlab.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eslint-formatter-gitlab.d.ts","sourceRoot":"","sources":["../lib/eslint-formatter-gitlab.js"],"names":[],"mappings":";AAiQA;;;;;;;GAOG;AACH,gDAPW,iBAAiB,EAAE,QAEnB,qBAAqB,GAEnB,OAAO,CAAC,MAAM,CAAC,CAgB3B;4BApRkC,QAAQ"}
package/index.d.ts DELETED
@@ -1,11 +0,0 @@
1
- export = eslintFormatterGitLab;
2
- /**
3
- * @param {import('eslint').ESLint.LintResult[]} results
4
- * The ESLint report results.
5
- * @param {import('eslint').ESLint.LintResultData} data
6
- * The ESLint report result data.
7
- * @returns {string}
8
- * The ESLint output to print to the console.
9
- */
10
- declare function eslintFormatterGitLab(results: import('eslint').ESLint.LintResult[], data: import('eslint').ESLint.LintResultData): string;
11
- //# sourceMappingURL=index.d.ts.map
package/index.d.ts.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.js"],"names":[],"mappings":";AA0PA;;;;;;;GAOG;AACH,gDAPW,OAAO,QAAQ,EAAE,MAAM,CAAC,UAAU,EAAE,QAEpC,OAAO,QAAQ,EAAE,MAAM,CAAC,cAAc,GAEpC,MAAM,CAmBlB"}