Skip to content

Commit

Permalink
fix: truncate command title to stdout width
Browse files Browse the repository at this point in the history
This makes sure the task title is as long as possible to fit on a single line of the console output, applying both to regular and functional tasks.
  • Loading branch information
iiroj committed May 15, 2020
1 parent 1f5968e commit 6a99a17
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 23 deletions.
35 changes: 27 additions & 8 deletions lib/makeCmdTasks.js
@@ -1,9 +1,27 @@
'use strict'

const cliTruncate = require('cli-truncate')
const debug = require('debug')('lint-staged:make-cmd-tasks')

const resolveTaskFn = require('./resolveTaskFn')
const { createError } = require('./validateConfig')

const debug = require('debug')('lint-staged:make-cmd-tasks')
const STDOUT_COLUMNS_DEFAULT = 80

const listrPrefixLength = {
update: ` X `.length,
verbose: `[STARTED] `.length,
}

/**
* Get length of title based on the number of available columns prefix length
* @param {string} renderer The name of the Listr renderer
* @returns {number}
*/
const getTitleLength = (renderer, columns = process.stdout.columns) => {
const prefixLength = listrPrefixLength[renderer] || 0
return (columns || STDOUT_COLUMNS_DEFAULT) - prefixLength
}

/**
* Creates and returns an array of listr tasks which map to the given commands.
Expand All @@ -12,10 +30,11 @@ const debug = require('debug')('lint-staged:make-cmd-tasks')
* @param {Array<string|Function>|string|Function} options.commands
* @param {Array<string>} options.files
* @param {string} options.gitDir
* @param {string} options.renderer
* @param {Boolean} shell
* @param {Boolean} verbose
*/
module.exports = async function makeCmdTasks({ commands, files, gitDir, shell, verbose }) {
const makeCmdTasks = async ({ commands, files, gitDir, renderer, shell, verbose }) => {
debug('Creating listr tasks for commands %o', commands)
const commandArray = Array.isArray(commands) ? commands : [commands]
const cmdTasks = []
Expand All @@ -28,25 +47,23 @@ module.exports = async function makeCmdTasks({ commands, files, gitDir, shell, v
const resolvedArray = Array.isArray(resolved) ? resolved : [resolved] // Wrap non-array command as array

for (const command of resolvedArray) {
let title = isFn ? '[Function]' : command

if (isFn) {
// If the function linter didn't return string | string[] it won't work
// Do the validation here instead of `validateConfig` to skip evaluating the function multiple times
if (typeof command !== 'string') {
throw new Error(
createError(
title,
'[Function]',
'Function task should return a string or an array of strings',
resolved
)
)
}

const [startOfFn] = command.split(' ')
title += ` ${startOfFn} ...` // Append function name, like `[Function] eslint ...`
}

// Truncate title to single line based on renderer
const title = cliTruncate(command, getTitleLength(renderer))

cmdTasks.push({
title,
command,
Expand All @@ -57,3 +74,5 @@ module.exports = async function makeCmdTasks({ commands, files, gitDir, shell, v

return cmdTasks
}

module.exports = makeCmdTasks
1 change: 1 addition & 0 deletions lib/runAll.js
Expand Up @@ -136,6 +136,7 @@ const runAll = async (
commands: task.commands,
files: task.fileList,
gitDir,
renderer: listrOptions.renderer,
shell,
verbose,
})
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -33,6 +33,7 @@
},
"dependencies": {
"chalk": "^4.0.0",
"cli-truncate": "2.1.0",
"commander": "^5.1.0",
"cosmiconfig": "^6.0.0",
"debug": "^4.1.1",
Expand Down
8 changes: 4 additions & 4 deletions test/integration.test.js
Expand Up @@ -751,8 +751,8 @@ describe('lint-staged', () => {
LOG [SUCCESS] Preparing...
LOG [STARTED] Running tasks...
LOG [STARTED] Running tasks for *.js
LOG [STARTED] [Function] git ...
LOG [SUCCESS] [Function] git ...
LOG [STARTED] git stash drop
LOG [SUCCESS] git stash drop
LOG [SUCCESS] Running tasks for *.js
LOG [SUCCESS] Running tasks...
LOG [STARTED] Applying modifications...
Expand Down Expand Up @@ -980,8 +980,8 @@ describe('lint-staged', () => {
LOG [SUCCESS] Hiding unstaged changes to partially staged files...
LOG [STARTED] Running tasks...
LOG [STARTED] Running tasks for *.js
LOG [STARTED] [Function] prettier ...
LOG [SUCCESS] [Function] prettier ...
LOG [STARTED] prettier --write /private/var/folders/vc/jjz0nxd55lqbv8q4j9sqs19w0000…
LOG [SUCCESS] prettier --write /private/var/folders/vc/jjz0nxd55lqbv8q4j9sqs19w0000…
LOG [SUCCESS] Running tasks for *.js
LOG [SUCCESS] Running tasks...
LOG [STARTED] Applying modifications...
Expand Down
33 changes: 23 additions & 10 deletions test/makeCmdTasks.spec.js
Expand Up @@ -61,7 +61,7 @@ describe('makeCmdTasks', () => {
it('should work with function task returning a string', async () => {
const res = await makeCmdTasks({ commands: () => 'test', gitDir, files: ['test.js'] })
expect(res.length).toBe(1)
expect(res[0].title).toEqual('[Function] test ...')
expect(res[0].title).toEqual('test')
})

it('should work with function task returning array of string', async () => {
Expand All @@ -71,8 +71,8 @@ describe('makeCmdTasks', () => {
files: ['test.js'],
})
expect(res.length).toBe(2)
expect(res[0].title).toEqual('[Function] test ...')
expect(res[1].title).toEqual('[Function] test2 ...')
expect(res[0].title).toEqual('test')
expect(res[1].title).toEqual('test2')
})

it('should work with function task accepting arguments', async () => {
Expand All @@ -82,8 +82,8 @@ describe('makeCmdTasks', () => {
files: ['test.js', 'test2.js'],
})
expect(res.length).toBe(2)
expect(res[0].title).toEqual('[Function] test ...')
expect(res[1].title).toEqual('[Function] test ...')
expect(res[0].title).toEqual('test test.js')
expect(res[1].title).toEqual('test test2.js')
})

it('should work with array of mixed string and function tasks', async () => {
Expand All @@ -93,17 +93,17 @@ describe('makeCmdTasks', () => {
files: ['test.js', 'test2.js', 'test3.js'],
})
expect(res.length).toBe(5)
expect(res[0].title).toEqual('[Function] test ...')
expect(res[0].title).toEqual('test')
expect(res[1].title).toEqual('test2')
expect(res[2].title).toEqual('[Function] test ...')
expect(res[3].title).toEqual('[Function] test ...')
expect(res[4].title).toEqual('[Function] test ...')
expect(res[2].title).toEqual('test test.js')
expect(res[3].title).toEqual('test test2.js')
expect(res[4].title).toEqual('test test3.js')
})

it('should work with async function tasks', async () => {
const res = await makeCmdTasks({ commands: async () => 'test', gitDir, files: ['test.js'] })
expect(res.length).toBe(1)
expect(res[0].title).toEqual('[Function] test ...')
expect(res[0].title).toEqual('test')
})

it("should throw when function task doesn't return string | string[]", async () => {
Expand All @@ -120,4 +120,17 @@ describe('makeCmdTasks', () => {
Please refer to https://github.com/okonet/lint-staged#configuration for more information..."
`)
})

it('should truncate task title', async () => {
const longString = new Array(1000)
.fill()
.map((_, index) => index)
.join('')

const res = await makeCmdTasks({ commands: () => longString, gitDir, files: ['test.js'] })
expect(res.length).toBe(1)
expect(res[0].title).toMatchInlineSnapshot(
`"0123456789101112131415161718192021222324252627282930313233343536373839404142434…"`
)
})
})
2 changes: 1 addition & 1 deletion yarn.lock
Expand Up @@ -1845,7 +1845,7 @@ cli-cursor@^3.1.0:
dependencies:
restore-cursor "^3.1.0"

cli-truncate@^2.1.0:
cli-truncate@2.1.0, cli-truncate@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7"
integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==
Expand Down

0 comments on commit 6a99a17

Please sign in to comment.