Skip to content

Commit

Permalink
fix: fix reading config from stdin, introduced in v14.0.0 (#1317)
Browse files Browse the repository at this point in the history
  • Loading branch information
iiroj committed Aug 21, 2023
1 parent 9da8777 commit fc3bfea
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 12 deletions.
13 changes: 5 additions & 8 deletions bin/lint-staged.js
Expand Up @@ -8,6 +8,7 @@ import debug from 'debug'

import lintStaged from '../lib/index.js'
import { CONFIG_STDIN_ERROR } from '../lib/messages.js'
import { readStdin } from '../lib/readStdin.js'

// Force colors for packages that depend on https://www.npmjs.com/package/supports-color
if (supportsColor) {
Expand Down Expand Up @@ -110,17 +111,13 @@ debugLog('Options parsed from command-line:', options)
if (options.configPath === '-') {
delete options.configPath
try {
options.config = await fs.readFile(process.stdin.fd, 'utf8').toString().trim()
} catch {
debugLog('Reading config from stdin')
options.config = JSON.parse(await readStdin())
} catch (error) {
debugLog(CONFIG_STDIN_ERROR, error)
console.error(CONFIG_STDIN_ERROR)
process.exit(1)
}

try {
options.config = JSON.parse(options.config)
} catch {
// Let config parsing complain if it's not JSON
}
}

try {
Expand Down
2 changes: 1 addition & 1 deletion lib/messages.js
Expand Up @@ -71,4 +71,4 @@ export const RESTORE_STASH_EXAMPLE = ` Any lost modifications can be restored f
> git stash apply --index stash@{0}
`

export const CONFIG_STDIN_ERROR = 'Error: Could not read config from stdin.'
export const CONFIG_STDIN_ERROR = chalk.redBright(`${error} Failed to read config from stdin.`)
19 changes: 19 additions & 0 deletions lib/readStdin.js
@@ -0,0 +1,19 @@
import { createInterface } from 'node:readline'

/**
* Returns a promise resolving to the first line written to stdin after invoking.
* @warn will never resolve if called after writing to stdin
*
* @returns {Promise<string>}
*/
export const readStdin = () => {
const readline = createInterface({ input: process.stdin })

return new Promise((resolve) => {
readline.prompt()
readline.on('line', (line) => {
readline.close()
resolve(line)
})
})
}
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -59,6 +59,7 @@
"husky": "8.0.3",
"jest": "29.6.2",
"jest-snapshot-serializer-ansi": "2.1.0",
"mock-stdin": "1.0.0",
"prettier": "3.0.1"
},
"keywords": [
Expand Down
6 changes: 3 additions & 3 deletions test/e2e/__utils__/getLintStagedExecutor.js
Expand Up @@ -2,13 +2,13 @@ import { resolve } from 'node:path'

import { execaCommand } from 'execa'

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

/**
* @param {string} cwd
* @return {Function}
*/
export const getLintStagedExecutor =
(cwd) =>
async (params = '') =>
await execaCommand(`${lintStagedBin} --cwd=${cwd} ${params}`)
(params = '', options) =>
execaCommand(`${lintStagedBin} --cwd=${cwd} ${params}`, options)
55 changes: 55 additions & 0 deletions test/e2e/stdin-config.test.js
@@ -0,0 +1,55 @@
import '../integration/__mocks__/resolveConfig.js'

import { jest } from '@jest/globals'

import { withGitIntegration } from '../integration/__utils__/withGitIntegration.js'
import * as fileFixtures from '../integration/__fixtures__/files.js'
import * as configFixtures from '../integration/__fixtures__/configs.js'

import { getLintStagedExecutor } from './__utils__/getLintStagedExecutor.js'

jest.setTimeout(20000)
jest.retryTimes(2)

describe('lint-staged', () => {
test(
'reads config from stdin',
withGitIntegration(async ({ cwd, execGit, readFile, writeFile }) => {
const lintStaged = getLintStagedExecutor(cwd)

// Stage ugly file
await writeFile('test file.js', fileFixtures.uglyJS)
await execGit(['add', 'test file.js'])

// Run lint-staged with config from stdin
await lintStaged('-c -', {
input: JSON.stringify(configFixtures.prettierWrite),
})

// Nothing was wrong so file was prettified
expect(await readFile('test file.js')).toEqual(fileFixtures.prettyJS)
})
)

test(
'fails when stdin config is not valid',
withGitIntegration(async ({ cwd, execGit, readFile, writeFile }) => {
const lintStaged = getLintStagedExecutor(cwd)

// Stage ugly file
await writeFile('test file.js', fileFixtures.uglyJS)
await execGit(['add', 'test file.js'])

// Break JSON by removing } from the end
const brokenJSONConfig = JSON.stringify(configFixtures.prettierWrite).replace('"}', '"')

// Run lint-staged with broken config from stdin
await expect(lintStaged('-c -', { input: brokenJSONConfig })).rejects.toThrowError(
'Failed to read config from stdin'
)

// File was not edited
expect(await readFile('test file.js')).toEqual(fileFixtures.uglyJS)
})
)
})
16 changes: 16 additions & 0 deletions test/unit/readStdin.spec.js
@@ -0,0 +1,16 @@
import { stdin } from 'mock-stdin'

import { readStdin } from '../../lib/readStdin.js'

const mockStdin = stdin()

describe('readStdin', () => {
it('should return stdin', async () => {
const stdinPromise = readStdin()

mockStdin.send('Hello, world!')
mockStdin.end()

expect(await stdinPromise).toEqual('Hello, world!')
})
})

0 comments on commit fc3bfea

Please sign in to comment.