Skip to content

Commit fc3bfea

Browse files
authoredAug 21, 2023
fix: fix reading config from stdin, introduced in v14.0.0 (#1317)
1 parent 9da8777 commit fc3bfea

8 files changed

+107
-12
lines changed
 

‎bin/lint-staged.js

+5-8
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import debug from 'debug'
88

99
import lintStaged from '../lib/index.js'
1010
import { CONFIG_STDIN_ERROR } from '../lib/messages.js'
11+
import { readStdin } from '../lib/readStdin.js'
1112

1213
// Force colors for packages that depend on https://www.npmjs.com/package/supports-color
1314
if (supportsColor) {
@@ -110,17 +111,13 @@ debugLog('Options parsed from command-line:', options)
110111
if (options.configPath === '-') {
111112
delete options.configPath
112113
try {
113-
options.config = await fs.readFile(process.stdin.fd, 'utf8').toString().trim()
114-
} catch {
114+
debugLog('Reading config from stdin')
115+
options.config = JSON.parse(await readStdin())
116+
} catch (error) {
117+
debugLog(CONFIG_STDIN_ERROR, error)
115118
console.error(CONFIG_STDIN_ERROR)
116119
process.exit(1)
117120
}
118-
119-
try {
120-
options.config = JSON.parse(options.config)
121-
} catch {
122-
// Let config parsing complain if it's not JSON
123-
}
124121
}
125122

126123
try {

‎lib/messages.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,4 @@ export const RESTORE_STASH_EXAMPLE = ` Any lost modifications can be restored f
7171
> git stash apply --index stash@{0}
7272
`
7373

74-
export const CONFIG_STDIN_ERROR = 'Error: Could not read config from stdin.'
74+
export const CONFIG_STDIN_ERROR = chalk.redBright(`${error} Failed to read config from stdin.`)

‎lib/readStdin.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { createInterface } from 'node:readline'
2+
3+
/**
4+
* Returns a promise resolving to the first line written to stdin after invoking.
5+
* @warn will never resolve if called after writing to stdin
6+
*
7+
* @returns {Promise<string>}
8+
*/
9+
export const readStdin = () => {
10+
const readline = createInterface({ input: process.stdin })
11+
12+
return new Promise((resolve) => {
13+
readline.prompt()
14+
readline.on('line', (line) => {
15+
readline.close()
16+
resolve(line)
17+
})
18+
})
19+
}

‎package-lock.json

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"husky": "8.0.3",
6060
"jest": "29.6.2",
6161
"jest-snapshot-serializer-ansi": "2.1.0",
62+
"mock-stdin": "1.0.0",
6263
"prettier": "3.0.1"
6364
},
6465
"keywords": [

‎test/e2e/__utils__/getLintStagedExecutor.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import { resolve } from 'node:path'
22

33
import { execaCommand } from 'execa'
44

5-
let lintStagedBin = resolve(__dirname, '../../../bin/lint-staged.js')
5+
const lintStagedBin = resolve(__dirname, '../../../bin/lint-staged.js')
66

77
/**
88
* @param {string} cwd
99
* @return {Function}
1010
*/
1111
export const getLintStagedExecutor =
1212
(cwd) =>
13-
async (params = '') =>
14-
await execaCommand(`${lintStagedBin} --cwd=${cwd} ${params}`)
13+
(params = '', options) =>
14+
execaCommand(`${lintStagedBin} --cwd=${cwd} ${params}`, options)

‎test/e2e/stdin-config.test.js

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import '../integration/__mocks__/resolveConfig.js'
2+
3+
import { jest } from '@jest/globals'
4+
5+
import { withGitIntegration } from '../integration/__utils__/withGitIntegration.js'
6+
import * as fileFixtures from '../integration/__fixtures__/files.js'
7+
import * as configFixtures from '../integration/__fixtures__/configs.js'
8+
9+
import { getLintStagedExecutor } from './__utils__/getLintStagedExecutor.js'
10+
11+
jest.setTimeout(20000)
12+
jest.retryTimes(2)
13+
14+
describe('lint-staged', () => {
15+
test(
16+
'reads config from stdin',
17+
withGitIntegration(async ({ cwd, execGit, readFile, writeFile }) => {
18+
const lintStaged = getLintStagedExecutor(cwd)
19+
20+
// Stage ugly file
21+
await writeFile('test file.js', fileFixtures.uglyJS)
22+
await execGit(['add', 'test file.js'])
23+
24+
// Run lint-staged with config from stdin
25+
await lintStaged('-c -', {
26+
input: JSON.stringify(configFixtures.prettierWrite),
27+
})
28+
29+
// Nothing was wrong so file was prettified
30+
expect(await readFile('test file.js')).toEqual(fileFixtures.prettyJS)
31+
})
32+
)
33+
34+
test(
35+
'fails when stdin config is not valid',
36+
withGitIntegration(async ({ cwd, execGit, readFile, writeFile }) => {
37+
const lintStaged = getLintStagedExecutor(cwd)
38+
39+
// Stage ugly file
40+
await writeFile('test file.js', fileFixtures.uglyJS)
41+
await execGit(['add', 'test file.js'])
42+
43+
// Break JSON by removing } from the end
44+
const brokenJSONConfig = JSON.stringify(configFixtures.prettierWrite).replace('"}', '"')
45+
46+
// Run lint-staged with broken config from stdin
47+
await expect(lintStaged('-c -', { input: brokenJSONConfig })).rejects.toThrowError(
48+
'Failed to read config from stdin'
49+
)
50+
51+
// File was not edited
52+
expect(await readFile('test file.js')).toEqual(fileFixtures.uglyJS)
53+
})
54+
)
55+
})

‎test/unit/readStdin.spec.js

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { stdin } from 'mock-stdin'
2+
3+
import { readStdin } from '../../lib/readStdin.js'
4+
5+
const mockStdin = stdin()
6+
7+
describe('readStdin', () => {
8+
it('should return stdin', async () => {
9+
const stdinPromise = readStdin()
10+
11+
mockStdin.send('Hello, world!')
12+
mockStdin.end()
13+
14+
expect(await stdinPromise).toEqual('Hello, world!')
15+
})
16+
})

0 commit comments

Comments
 (0)
Please sign in to comment.