lint-staged 12.0.2 → 12.1.2

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
@@ -42,6 +42,10 @@ See [Releases](https://github.com/okonet/lint-staged/releases).
42
42
 
43
43
  ### Migration
44
44
 
45
+ #### v12
46
+
47
+ - Since `v12.0.0` _lint-staged_ is a pure ESM module, so make sure your Node.js version is at least `12.20.0`, `14.13.1`, or `16.0.0`. Read more about ESM modules from the official [Node.js Documentation site here](https://nodejs.org/api/esm.html#introduction).
48
+
45
49
  #### v10
46
50
 
47
51
  - From `v10.0.0` onwards any new modifications to originally staged files will be automatically added to the commit.
@@ -102,12 +106,15 @@ Starting with v3.1 you can now use different ways of configuring lint-staged:
102
106
  - `.lintstagedrc.json`
103
107
  - `.lintstagedrc.yaml`
104
108
  - `.lintstagedrc.yml`
105
- - `lint-staged.config.js`, `.lintstagedrc.js`, or `.lintstagedrc.cjs` file in JS format
109
+ - `.lintstagedrc.mjs` or `lint-staged.config.mjs` file in ESM format
110
+ - the default export value should be a configuration: `export default { ... }`
111
+ - `.lintstagedrc.cjs` or `lint-staged.config.cjs` file in CommonJS format
112
+ - the exports value should be a configuration: `module.exports = { ... }`
113
+ - `lint-staged.config.js` or `.lintstagedrc.js` in either ESM or CommonJS format, depending on
114
+ whether your project's _package.json_ contains the `"type": "module"` option or not.
106
115
  - Pass a configuration file using the `--config` or `-c` flag
107
116
 
108
- See [cosmiconfig](https://github.com/davidtheclark/cosmiconfig) for more details on what formats are supported.
109
-
110
- Configuration should be an object where each value is a command to run and its key is a glob pattern to use for this command. This package uses [micromatch](https://github.com/micromatch/micromatch) for glob patterns.
117
+ Configuration should be an object where each value is a command to run and its key is a glob pattern to use for this command. This package uses [micromatch](https://github.com/micromatch/micromatch) for glob patterns. JavaScript files can also export advanced configuration as a function. See [Using JS configuration files](#using-js-configuration-files) for more info.
111
118
 
112
119
  #### `package.json` example:
113
120
 
@@ -193,7 +200,7 @@ For example:
193
200
 
194
201
  going to execute `eslint` and if it exits with `0` code, it will execute `prettier --write` on all staged `*.js` files.
195
202
 
196
- ## Using JS configuration file
203
+ ## Using JS configuration files
197
204
 
198
205
  Writing the configuration file in JavaScript is the most powerful way to configure lint-staged (`lint-staged.config.js`, [similar](https://github.com/okonet/lint-staged/README.md#configuration), or passed via `--config`). From the configuration file, you can export either a single function or an object.
199
206
 
@@ -216,9 +223,9 @@ The function can also be async:
216
223
 
217
224
  ```js
218
225
  // lint-staged.config.js
219
- const micromatch = require('micromatch')
226
+ import micromatch from 'micromatch'
220
227
 
221
- module.exports = (allStagedFiles) => {
228
+ export default (allStagedFiles) => {
222
229
  const shFiles = micromatch(allStagedFiles, ['**/src/**/*.sh'])
223
230
  if (shFiles.length) {
224
231
  return `printf '%s\n' "Script files aren't allowed in src directory" >&2`
@@ -238,7 +245,7 @@ module.exports = (allStagedFiles) => {
238
245
 
239
246
  ```js
240
247
  // .lintstagedrc.js
241
- module.exports = {
248
+ export default {
242
249
  '**/*.js?(x)': (filenames) => filenames.map((filename) => `prettier --write '${filename}'`),
243
250
  }
244
251
  ```
@@ -252,7 +259,7 @@ module.exports = {
252
259
 
253
260
  ```js
254
261
  // lint-staged.config.js
255
- module.exports = {
262
+ export default {
256
263
  '**/*.ts?(x)': () => 'tsc -p tsconfig.json --noEmit',
257
264
  }
258
265
  ```
@@ -266,7 +273,7 @@ module.exports = {
266
273
 
267
274
  ```js
268
275
  // .lintstagedrc.js
269
- module.exports = {
276
+ export default {
270
277
  '**/*.js?(x)': (filenames) =>
271
278
  filenames.length > 10 ? 'eslint .' : `eslint ${filenames.join(' ')}`,
272
279
  }
@@ -283,9 +290,9 @@ It's better to use the [function-based configuration (seen above)](https://githu
283
290
 
284
291
  ```js
285
292
  // lint-staged.config.js
286
- const micromatch = require('micromatch')
293
+ import micromatch from 'micromatch'
287
294
 
288
- module.exports = {
295
+ export default {
289
296
  '*': (allFiles) => {
290
297
  const codeFiles = micromatch(allFiles, ['**/*.js', '**/*.ts'])
291
298
  const docFiles = micromatch(allFiles, ['**/*.md'])
@@ -305,9 +312,9 @@ If for some reason you want to ignore files from the glob match, you can use `mi
305
312
 
306
313
  ```js
307
314
  // lint-staged.config.js
308
- const micromatch = require('micromatch')
315
+ import micromatch from 'micromatch'
309
316
 
310
- module.exports = {
317
+ export default {
311
318
  '*.js': (files) => {
312
319
  // from `files` filter those _NOT_ matching `*test.js`
313
320
  const match = micromatch.not(files, '*test.js')
@@ -326,9 +333,9 @@ Please note that for most cases, globs can achieve the same effect. For the abov
326
333
  <summary>Click to expand</summary>
327
334
 
328
335
  ```js
329
- const path = require('path')
336
+ import path from 'path'
330
337
 
331
- module.exports = {
338
+ export default {
332
339
  '*.ts': (absolutePaths) => {
333
340
  const cwd = process.cwd()
334
341
  const relativePaths = absolutePaths.map((file) => path.relative(cwd, file))
@@ -542,7 +549,7 @@ See more on [this blog post](https://medium.com/@tomchentw/imagemin-lint-staged-
542
549
  Yes!
543
550
 
544
551
  ```js
545
- const lintStaged = require('lint-staged')
552
+ import lintStaged from 'lint-staged'
546
553
 
547
554
  try {
548
555
  const success = await lintStaged()
@@ -681,13 +688,13 @@ Based on the discussion from [this issue](https://github.com/eslint/eslint/issue
681
688
  So you can setup a `.lintstagedrc.js` config file to do this:
682
689
 
683
690
  ```js
684
- const { CLIEngine } = require('eslint')
691
+ import { CLIEngine } from 'eslint'
685
692
 
686
- const cli = new CLIEngine({})
687
-
688
- module.exports = {
689
- '*.js': (files) =>
690
- 'eslint --max-warnings=0 ' + files.filter((file) => !cli.isPathIgnored(file)).join(' '),
693
+ export default {
694
+ '*.js': (files) => {
695
+ const cli = new CLIEngine({})
696
+ return 'eslint --max-warnings=0 ' + files.filter((file) => !cli.isPathIgnored(file)).join(' ')
697
+ },
691
698
  }
692
699
  ```
693
700
 
@@ -703,7 +710,7 @@ In versions of ESLint > 7, [isPathIgnored](https://eslint.org/docs/developer-gui
703
710
  Since [10.5.3](https://github.com/okonet/lint-staged/releases), any errors due to a bad ESLint config will come through to the console.
704
711
 
705
712
  ```js
706
- const { ESLint } = require('eslint')
713
+ import { ESLint } from 'eslint'
707
714
 
708
715
  const removeIgnoredFiles = async (files) => {
709
716
  const eslint = new ESLint()
@@ -716,7 +723,7 @@ const removeIgnoredFiles = async (files) => {
716
723
  return filteredFiles.join(' ')
717
724
  }
718
725
 
719
- module.exports = {
726
+ export default {
720
727
  '**/*.{ts,tsx,js,jsx}': async (files) => {
721
728
  const filesToLint = await removeIgnoredFiles(files)
722
729
  return [`eslint --max-warnings=0 ${filesToLint}`]
package/lib/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { cosmiconfig } from 'cosmiconfig'
2
1
  import debug from 'debug'
3
2
  import inspect from 'object-inspect'
4
3
 
4
+ import { loadConfig } from './loadConfig.js'
5
5
  import { PREVENTED_EMPTY_COMMIT, GIT_ERROR, RESTORE_STASH_EXAMPLE } from './messages.js'
6
6
  import { printTaskOutput } from './printTaskOutput.js'
7
7
  import { runAll } from './runAll.js'
@@ -16,32 +16,6 @@ import { validateOptions } from './validateOptions.js'
16
16
 
17
17
  const debugLog = debug('lint-staged')
18
18
 
19
- const resolveConfig = (configPath) => {
20
- try {
21
- return require.resolve(configPath)
22
- } catch {
23
- return configPath
24
- }
25
- }
26
-
27
- const loadConfig = (configPath) => {
28
- const explorer = cosmiconfig('lint-staged', {
29
- searchPlaces: [
30
- 'package.json',
31
- '.lintstagedrc',
32
- '.lintstagedrc.json',
33
- '.lintstagedrc.yaml',
34
- '.lintstagedrc.yml',
35
- '.lintstagedrc.js',
36
- '.lintstagedrc.cjs',
37
- 'lint-staged.config.js',
38
- 'lint-staged.config.cjs',
39
- ],
40
- })
41
-
42
- return configPath ? explorer.load(resolveConfig(configPath)) : explorer.search()
43
- }
44
-
45
19
  /**
46
20
  * @typedef {(...any) => void} LogFunction
47
21
  * @typedef {{ error: LogFunction, log: LogFunction, warn: LogFunction }} Logger
@@ -84,22 +58,14 @@ const lintStaged = async (
84
58
  ) => {
85
59
  await validateOptions({ shell }, logger)
86
60
 
87
- debugLog('Loading config using `cosmiconfig`')
61
+ const inputConfig = configObject || (await loadConfig(configPath, logger))
88
62
 
89
- const resolved = configObject
90
- ? { config: configObject, filepath: '(input)' }
91
- : await loadConfig(configPath)
92
-
93
- if (resolved == null) {
63
+ if (!inputConfig) {
94
64
  logger.error(`${ConfigNotFoundError.message}.`)
95
65
  throw ConfigNotFoundError
96
66
  }
97
67
 
98
- debugLog('Successfully loaded config from `%s`:\n%O', resolved.filepath, resolved.config)
99
-
100
- // resolved.config is the parsed configuration object
101
- // resolved.filepath is the path to the config file that was found
102
- const config = validateConfig(resolved.config, logger)
68
+ const config = validateConfig(inputConfig, logger)
103
69
 
104
70
  if (debug) {
105
71
  // Log using logger to be able to test through `consolemock`.
@@ -0,0 +1,86 @@
1
+ import { pathToFileURL } from 'url'
2
+
3
+ import debug from 'debug'
4
+ import { lilconfig } from 'lilconfig'
5
+ import YAML from 'yaml'
6
+
7
+ const debugLog = debug('lint-staged:loadConfig')
8
+
9
+ /**
10
+ * The list of files `lint-staged` will read configuration
11
+ * from, in the declared order.
12
+ */
13
+ const searchPlaces = [
14
+ 'package.json',
15
+ '.lintstagedrc',
16
+ '.lintstagedrc.json',
17
+ '.lintstagedrc.yaml',
18
+ '.lintstagedrc.yml',
19
+ '.lintstagedrc.mjs',
20
+ '.lintstagedrc.js',
21
+ '.lintstagedrc.cjs',
22
+ 'lint-staged.config.mjs',
23
+ 'lint-staged.config.js',
24
+ 'lint-staged.config.cjs',
25
+ ]
26
+
27
+ /** exported for tests */
28
+ export const dynamicImport = (path) => import(pathToFileURL(path)).then((module) => module.default)
29
+
30
+ const jsonParse = (path, content) => JSON.parse(content)
31
+
32
+ const yamlParse = (path, content) => YAML.parse(content)
33
+
34
+ /**
35
+ * `lilconfig` doesn't support yaml files by default,
36
+ * so we add custom loaders for those. Files without
37
+ * an extensions are assumed to be yaml — this
38
+ * assumption is in `cosmiconfig` as well.
39
+ */
40
+ const loaders = {
41
+ '.js': dynamicImport,
42
+ '.json': jsonParse,
43
+ '.mjs': dynamicImport,
44
+ '.cjs': dynamicImport,
45
+ '.yaml': yamlParse,
46
+ '.yml': yamlParse,
47
+ noExt: yamlParse,
48
+ }
49
+
50
+ const resolveConfig = (configPath) => {
51
+ try {
52
+ return require.resolve(configPath)
53
+ } catch {
54
+ return configPath
55
+ }
56
+ }
57
+
58
+ /**
59
+ * @param {string} [configPath]
60
+ * @param {Logger} [logger]
61
+ */
62
+ export const loadConfig = async (configPath, logger) => {
63
+ try {
64
+ if (configPath) {
65
+ debugLog('Loading configuration from `%s`...', configPath)
66
+ } else {
67
+ debugLog('Searching for configuration...')
68
+ }
69
+
70
+ const explorer = lilconfig('lint-staged', { searchPlaces, loaders })
71
+
72
+ const result = await (configPath ? explorer.load(resolveConfig(configPath)) : explorer.search())
73
+ if (!result) return null
74
+
75
+ // config is a promise when using the `dynamicImport` loader
76
+ const config = await result.config
77
+
78
+ debugLog('Successfully loaded config from `%s`:\n%O', result.filepath, config)
79
+
80
+ return config
81
+ } catch (error) {
82
+ debugLog('Failed to load configuration from `%s`', configPath)
83
+ logger.error(error)
84
+ return null
85
+ }
86
+ }
package/lib/symbols.js CHANGED
@@ -1,6 +1,10 @@
1
1
  export const ApplyEmptyCommitError = Symbol('ApplyEmptyCommitError')
2
2
 
3
- export const ConfigNotFoundError = new Error('Config could not be found')
3
+ export const ConfigNotFoundError = new Error('Configuration could not be found')
4
+
5
+ export const ConfigFormatError = new Error('Configuration should be an object or a function')
6
+
7
+ export const ConfigEmptyError = new Error('Configuration should not be empty')
4
8
 
5
9
  export const GetBackupStashError = Symbol('GetBackupStashError')
6
10
 
@@ -1,6 +1,7 @@
1
1
  import debug from 'debug'
2
2
 
3
3
  import { configurationError } from './messages.js'
4
+ import { ConfigEmptyError, ConfigFormatError } from './symbols.js'
4
5
  import { validateBraces } from './validateBraces.js'
5
6
 
6
7
  const debugLog = debug('lint-staged:validateConfig')
@@ -27,7 +28,7 @@ export const validateConfig = (config, logger) => {
27
28
  debugLog('Validating config')
28
29
 
29
30
  if (!config || (typeof config !== 'object' && typeof config !== 'function')) {
30
- throw new Error('Configuration should be an object or a function!')
31
+ throw ConfigFormatError
31
32
  }
32
33
 
33
34
  /**
@@ -42,7 +43,7 @@ export const validateConfig = (config, logger) => {
42
43
  }
43
44
 
44
45
  if (Object.entries(config).length === 0) {
45
- throw new Error('Configuration should not be empty!')
46
+ throw ConfigEmptyError
46
47
  }
47
48
 
48
49
  const errors = []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lint-staged",
3
- "version": "12.0.2",
3
+ "version": "12.1.2",
4
4
  "description": "Lint files staged by git",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/okonet/lint-staged",
@@ -24,30 +24,31 @@
24
24
  "lib"
25
25
  ],
26
26
  "scripts": {
27
- "cz": "git-cz",
28
27
  "lint": "eslint .",
29
28
  "test": "jest --coverage",
30
29
  "test:watch": "jest --watch"
31
30
  },
32
31
  "dependencies": {
33
- "cli-truncate": "3.1.0",
32
+ "cli-truncate": "^3.1.0",
34
33
  "colorette": "^2.0.16",
35
34
  "commander": "^8.3.0",
36
- "cosmiconfig": "^7.0.1",
37
35
  "debug": "^4.3.2",
36
+ "enquirer": "^2.3.6",
38
37
  "execa": "^5.1.1",
38
+ "lilconfig": "2.0.4",
39
39
  "listr2": "^3.13.3",
40
40
  "micromatch": "^4.0.4",
41
41
  "normalize-path": "^3.0.0",
42
- "object-inspect": "1.11.0",
43
- "string-argv": "0.3.1",
44
- "supports-color": "9.0.2"
42
+ "object-inspect": "^1.11.0",
43
+ "string-argv": "^0.3.1",
44
+ "supports-color": "^9.0.2",
45
+ "yaml": "^1.10.2"
45
46
  },
46
47
  "devDependencies": {
47
48
  "@babel/core": "^7.16.0",
48
- "@babel/eslint-parser": "7.16.3",
49
+ "@babel/eslint-parser": "^7.16.3",
49
50
  "@babel/preset-env": "^7.16.0",
50
- "babel-jest": "27.3.1",
51
+ "babel-jest": "^27.3.1",
51
52
  "consolemock": "^1.1.0",
52
53
  "eslint": "^8.2.0",
53
54
  "eslint-config-prettier": "^8.3.0",
@@ -57,15 +58,9 @@
57
58
  "fs-extra": "^10.0.0",
58
59
  "husky": "^7.0.4",
59
60
  "jest": "^27.3.1",
60
- "jest-mock-console": "1.2.3",
61
61
  "jest-snapshot-serializer-ansi": "^1.0.0",
62
62
  "prettier": "^2.4.1"
63
63
  },
64
- "config": {
65
- "commitizen": {
66
- "path": "./node_modules/cz-conventional-changelog"
67
- }
68
- },
69
64
  "keywords": [
70
65
  "lint",
71
66
  "git",