Skip to content

Commit

Permalink
fix(renderer): force process.stdout to be a TTY to support spinners
Browse files Browse the repository at this point in the history
  • Loading branch information
iiroj committed Jun 9, 2022
1 parent cc044e7 commit 56a2bd3
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 12 deletions.
27 changes: 15 additions & 12 deletions bin/lint-staged.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@ import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'

import { isColorSupported } from 'colorette'
import { Option, program } from 'commander'
import debug from 'debug'

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

// Force colors for packages that depend on https://www.npmjs.com/package/supports-color
if (isColorSupported) {
// Force `process.stdout` to be a TTY to support spinners
const ttyHandle = await forceTty()
if (ttyHandle) {
process.env.FORCE_COLOR = '1'
}

Expand Down Expand Up @@ -113,6 +112,8 @@ if (options.configPath === '-') {
try {
options.config = fs.readFileSync(process.stdin.fd, 'utf8').toString().trim()
} catch {
// Lazy import so that `colorette` is not loaded before `forceTty`
const { CONFIG_STDIN_ERROR } = await import('../lib/messages.js')
console.error(CONFIG_STDIN_ERROR)
process.exit(1)
}
Expand All @@ -124,10 +125,12 @@ if (options.configPath === '-') {
}
}

lintStaged(options)
.then((passed) => {
process.exitCode = passed ? 0 : 1
})
.catch(() => {
process.exitCode = 1
})
try {
const { default: lintStaged } = await import('../lib/index.js')
const passed = await lintStaged(options)
process.exitCode = passed ? 0 : 1
} catch {
process.exitCode = 1
} finally {
await ttyHandle?.close()
}
49 changes: 49 additions & 0 deletions lib/forceTty.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { constants } from 'node:fs'
import fs from 'node:fs/promises'
import tty from 'node:tty'

const getHandle = async () => {
if (process.platform === 'win32') {
return await fs.open('conout$', constants.O_WRONLY | constants.O_EXCL, 0o666)
} else {
return await fs.open('/dev/tty', constants.O_WRONLY + constants.O_NOCTTY)
}
}

/**
* Try to override the `process.stdout` with a TTY `tty.WriteStream`.
* When invoking lint-staged through Git hooks, the shell might not
* be a TTY, but we still want to enable spinners in a "non-hacky" way.
*
* @returns {Promise<fs.FileHandle | null>} the `FileHandle` pointing to the TTY, or `null` when failed.
*/
export const forceTty = async () => {
/** No need to do anything if stdout is already a TTY. */
if (process.stdout.isTTY) return null

try {
const handle = await getHandle()

/**
* Probably not realistic that the TTY handle is not a TTY,
* but close the handle just in case.
*/
if (tty.isatty(handle.fd)) {
await handle.close()
return null
}

const stdout = new tty.WriteStream(handle.fd)

/** Override `process.stdout` */
Object.defineProperty(process, 'stdout', {
configurable: true,
enumerable: true,
get: () => stdout,
})

return handle
} catch {
return null
}
}

0 comments on commit 56a2bd3

Please sign in to comment.