Skip to content

Commit

Permalink
feat: add --verbose to show output even when tasks succeed
Browse files Browse the repository at this point in the history
This commit also moves most of the logging logic from runAll.js to index.js
  • Loading branch information
iiroj committed Apr 25, 2020
1 parent d69c65b commit 85de3a3
Show file tree
Hide file tree
Showing 15 changed files with 230 additions and 125 deletions.
41 changes: 25 additions & 16 deletions README.md
Expand Up @@ -72,6 +72,7 @@ Options:
-r, --relative pass relative filepaths to tasks (default: false)
-x, --shell skip parsing of tasks for better shell support (default:
false)
-v, --verbose always show task output (default: false)
-h, --help output usage information
```
Expand All @@ -88,6 +89,7 @@ Options:
- **`--quiet`**: Supress all CLI output, except from tasks.
- **`--relative`**: Pass filepaths relative to `process.cwd()` (where `lint-staged` runs) to tasks. Default is `false`.
- **`--shell`**: By default linter commands will be parsed for speed and security. This has the side-effect that regular shell scripts might not work as expected. You can skip parsing of commands with this option.
- **`--verbose`**: Show task output even when tasks succeed. By default only failed output is shown.
## Configuration
Expand Down Expand Up @@ -199,7 +201,7 @@ type TaskFn = (filenames: string[]) => string | string[] | Promise<string | stri
```js
// .lintstagedrc.js
module.exports = {
'**/*.js?(x)': filenames => filenames.map(filename => `prettier --write '${filename}'`)
'**/*.js?(x)': (filenames) => filenames.map((filename) => `prettier --write '${filename}'`)
}
```
Expand All @@ -217,7 +219,8 @@ module.exports = {
```js
// .lintstagedrc.js
module.exports = {
'**/*.js?(x)': filenames => (filenames.length > 10 ? 'eslint .' : `eslint ${filenames.join(' ')}`)
'**/*.js?(x)': (filenames) =>
filenames.length > 10 ? 'eslint .' : `eslint ${filenames.join(' ')}`
}
```
Expand All @@ -228,7 +231,7 @@ module.exports = {
const micromatch = require('micromatch')
module.exports = {
'*': allFiles => {
'*': (allFiles) => {
const match = micromatch(allFiles, ['*.js', '*.ts'])
return `eslint ${match.join(' ')}`
}
Expand All @@ -244,7 +247,7 @@ If for some reason you want to ignore files from the glob match, you can use `mi
const micromatch = require('micromatch')
module.exports = {
'*.js': files => {
'*.js': (files) => {
// from `files` filter those _NOT_ matching `*test.js`
const match = micromatch.not(files, '*test.js')
return `eslint ${match.join(' ')}`
Expand All @@ -260,9 +263,9 @@ Please note that for most cases, globs can achieve the same effect. For the abov
const path = require('path')
module.exports = {
'*.ts': absolutePaths => {
'*.ts': (absolutePaths) => {
const cwd = process.cwd()
const relativePaths = absolutePaths.map(file => path.relative(cwd, file))
const relativePaths = absolutePaths.map((file) => path.relative(cwd, file))
return `ng lint myProjectName --files ${relativePaths.join(' ')}`
}
}
Expand Down Expand Up @@ -422,27 +425,33 @@ Parameters to `lintStaged` are equivalent to their CLI counterparts:
```js
const success = await lintStaged({
allowEmpty: false,
concurrent: true,
configPath: './path/to/configuration/file',
debug: false,
maxArgLength: null,
relative: false,
shell: false,
quiet: false,
debug: false
relative: false,
shell: false
stash: true,
verbose: false
})
```
You can also pass config directly with `config` option:
```js
const success = await lintStaged({
config: {
'*.js': 'eslint --fix'
},
allowEmpty: false,
concurrent: true,
config: { '*.js': 'eslint --fix' },
debug: false,
maxArgLength: null,
quiet: false,
relative: false,
shell: false,
quiet: false,
debug: false
stash: true,
verbose: false
})
```
Expand Down Expand Up @@ -518,7 +527,7 @@ const { CLIEngine } = require('eslint')
const cli = new CLIEngine({})
module.exports = {
'*.js': files =>
'eslint --max-warnings=0 ' + files.filter(file => !cli.isPathIgnored(file)).join(' ')
'*.js': (files) =>
'eslint --max-warnings=0 ' + files.filter((file) => !cli.isPathIgnored(file)).join(' ')
}
```
4 changes: 3 additions & 1 deletion bin/lint-staged.js
Expand Up @@ -40,6 +40,7 @@ cmdline
.option('-q, --quiet', 'disable lint-staged’s own console output', false)
.option('-r, --relative', 'pass relative filepaths to tasks', false)
.option('-x, --shell', 'skip parsing of tasks for better shell support', false)
.option('-v, --verbose', 'always show task output', false)
.parse(process.argv)

if (cmdline.debug) {
Expand Down Expand Up @@ -75,7 +76,8 @@ const options = {
stash: !!cmdline.stash, // commander inverts `no-<x>` flags to `!x`
quiet: !!cmdline.quiet,
relative: !!cmdline.relative,
shell: !!cmdline.shell
shell: !!cmdline.shell,
verbose: !!cmdline.verbose
}

debug('Options parsed from command-line:', options)
Expand Down
38 changes: 32 additions & 6 deletions lib/index.js
Expand Up @@ -5,7 +5,10 @@ const { cosmiconfig } = require('cosmiconfig')
const debugLog = require('debug')('lint-staged')
const stringifyObject = require('stringify-object')

const { PREVENTED_EMPTY_COMMIT, GIT_ERROR, RESTORE_STASH_EXAMPLE } = require('./messages')
const printTaskOutput = require('./printTaskOutput')
const runAll = require('./runAll')
const { ApplyEmptyCommitError, GetBackupStashError, GitError } = require('./symbols')
const validateConfig = require('./validateConfig')

const errConfigNotFound = new Error('Config could not be found')
Expand Down Expand Up @@ -51,6 +54,7 @@ function loadConfig(configPath) {
* @param {boolean} [options.relative] - Pass relative filepaths to tasks
* @param {boolean} [options.shell] - Skip parsing of tasks for better shell support
* @param {boolean} [options.stash] - Enable the backup stash, and revert in case of errors
* @param {boolean} [options.verbose] - Always show task output
* @param {Logger} [logger]
*
* @returns {Promise<boolean>} Promise of whether the linting passed or failed
Expand All @@ -66,7 +70,8 @@ module.exports = async function lintStaged(
quiet = false,
relative = false,
shell = false,
stash = true
stash = true,
verbose = false
} = {},
logger = console
) {
Expand Down Expand Up @@ -97,16 +102,37 @@ module.exports = async function lintStaged(
delete process.env.GIT_LITERAL_PATHSPECS

try {
await runAll(
{ allowEmpty, concurrent, config, debug, maxArgLength, stash, quiet, relative, shell },
const ctx = await runAll(
{
allowEmpty,
concurrent,
config,
debug,
maxArgLength,
quiet,
relative,
shell,
stash,
verbose
},
logger
)
debugLog('tasks were executed successfully!')
debugLog('Tasks were executed successfully!')
printTaskOutput(ctx, logger)
return true
} catch (runAllError) {
if (runAllError && runAllError.ctx && runAllError.ctx.output) {
logger.error(...runAllError.ctx.output)
const { ctx } = runAllError
if (ctx.errors.has(ApplyEmptyCommitError)) {
logger.warn(PREVENTED_EMPTY_COMMIT)
} else if (ctx.errors.has(GitError) && !ctx.errors.has(GetBackupStashError)) {
logger.error(GIT_ERROR)
if (ctx.shouldBackup) {
// No sense to show this if the backup stash itself is missing.
logger.error(RESTORE_STASH_EXAMPLE)
}
}

printTaskOutput(ctx, logger)
return false
}
} catch (lintStagedError) {
Expand Down
5 changes: 3 additions & 2 deletions lib/makeCmdTasks.js
Expand Up @@ -13,8 +13,9 @@ const debug = require('debug')('lint-staged:make-cmd-tasks')
* @param {Array<string>} options.files
* @param {string} options.gitDir
* @param {Boolean} shell
* @param {Boolean} verbose
*/
module.exports = async function makeCmdTasks({ commands, files, gitDir, shell }) {
module.exports = async function makeCmdTasks({ commands, files, gitDir, shell, verbose }) {
debug('Creating listr tasks for commands %o', commands)
const commandArray = Array.isArray(commands) ? commands : [commands]
const cmdTasks = []
Expand Down Expand Up @@ -49,7 +50,7 @@ module.exports = async function makeCmdTasks({ commands, files, gitDir, shell })
cmdTasks.push({
title,
command,
task: resolveTaskFn({ command, files, gitDir, isFn, shell })
task: resolveTaskFn({ command, files, gitDir, isFn, shell, verbose })
})
}
}
Expand Down
21 changes: 15 additions & 6 deletions lib/messages.js
@@ -1,16 +1,22 @@
'use strict'

const chalk = require('chalk')
const symbols = require('log-symbols')
const { error, info, warning } = require('log-symbols')

const NO_STAGED_FILES = `${symbols.info} No staged files found.`
const NOT_GIT_REPO = chalk.redBright(`${error} Current directory is not a git directory!`)

const FAILED_GET_STAGED_FILES = chalk.redBright(`${error} Failed to get staged files!`)

const NO_STAGED_FILES = `${info} No staged files found.`

const NO_TASKS = `${info} No staged files match any configured task.`

const skippingBackup = (hasInitialCommit) => {
const reason = hasInitialCommit ? '`--no-stash` was used' : 'there’s no initial commit yet'
return `${symbols.warning} ${chalk.yellow(`Skipping backup because ${reason}.\n`)}`
return `${warning} ${chalk.yellow(`Skipping backup because ${reason}.\n`)}`
}

const DEPRECATED_GIT_ADD = `${symbols.warning} ${chalk.yellow(
const DEPRECATED_GIT_ADD = `${warning} ${chalk.yellow(
`Some of your tasks use \`git add\` command. Please remove it from the config since all modifications made by tasks will be automatically added to the git commit index.`
)}
`
Expand All @@ -19,10 +25,10 @@ const TASK_ERROR = 'Skipped because of errors from tasks.'

const SKIPPED_GIT_ERROR = 'Skipped because of previous git error.'

const GIT_ERROR = `\n ${symbols.error} ${chalk.red(`lint-staged failed due to a git error.`)}`
const GIT_ERROR = `\n ${error} ${chalk.red(`lint-staged failed due to a git error.`)}`

const PREVENTED_EMPTY_COMMIT = `
${symbols.warning} ${chalk.yellow(`lint-staged prevented an empty git commit.
${warning} ${chalk.yellow(`lint-staged prevented an empty git commit.
Use the --allow-empty option to continue, or check your task configuration`)}
`

Expand All @@ -34,7 +40,10 @@ const RESTORE_STASH_EXAMPLE = ` Any lost modifications can be restored from a g
`

module.exports = {
NOT_GIT_REPO,
FAILED_GET_STAGED_FILES,
NO_STAGED_FILES,
NO_TASKS,
skippingBackup,
DEPRECATED_GIT_ADD,
TASK_ERROR,
Expand Down
16 changes: 16 additions & 0 deletions lib/printTaskOutput.js
@@ -0,0 +1,16 @@
'use strict'

/**
* Handle logging of listr `ctx.output` to the specified `logger`
* @param {Object} ctx - The listr initial state
* @param {Object} logger - The logger
*/
const printTaskOutput = (ctx = {}, logger) => {
if (!Array.isArray(ctx.output)) return
const log = ctx.errors && ctx.errors.size > 0 ? logger.error : logger.log
for (const line of ctx.output) {
log(line)
}
}

module.exports = printTaskOutput
55 changes: 43 additions & 12 deletions lib/resolveTaskFn.js
Expand Up @@ -4,15 +4,17 @@ const { redBright, dim } = require('chalk')
const execa = require('execa')
const debug = require('debug')('lint-staged:task')
const { parseArgsStringToArgv } = require('string-argv')
const { error } = require('log-symbols')
const { error, info } = require('log-symbols')

const { getInitialState } = require('./state')
const { TaskError } = require('./symbols')

const getTag = ({ code, killed, signal }) => signal || (killed && 'KILLED') || code || 'FAILED'

/**
* Create a failure message dependding on process result.
* Handle task console output.
*
* @param {string} linter
* @param {string} command
* @param {Object} result
* @param {string} result.stdout
* @param {string} result.stderr
Expand All @@ -22,23 +24,39 @@ const { TaskError } = require('./symbols')
* @param {Object} ctx
* @returns {Error}
*/
const makeErr = (command, result, ctx) => {
ctx.errors.add(TaskError)
const { code, killed, signal, stderr, stdout } = result

const tag = signal || (killed && 'KILLED') || code || 'FAILED'

const handleOutput = (command, result, ctx, isError = false) => {
const { stderr, stdout } = result
const hasOutput = !!stderr || !!stdout

if (hasOutput) {
const errorTitle = redBright(`${error} ${command}:`)
const output = ['', errorTitle].concat(stderr ? [stderr] : []).concat(stdout ? [stdout] : [])
const outputTitle = isError ? redBright(`${error} ${command}:`) : `${info} ${command}:`
const output = ['', outputTitle].concat(stderr ? [stderr] : []).concat(stdout ? [stdout] : [])
ctx.output.push(output.join('\n'))
} else {
// Show generic error when task had no output
const tag = getTag(result)
const message = redBright(`\n${error} ${command} failed without output (${tag}).`)
ctx.output.push(message)
}
}

/**
* Create a error output dependding on process result.
*
* @param {string} command
* @param {Object} result
* @param {string} result.stdout
* @param {string} result.stderr
* @param {boolean} result.failed
* @param {boolean} result.killed
* @param {string} result.signal
* @param {Object} ctx
* @returns {Error}
*/
const makeErr = (command, result, ctx) => {
ctx.errors.add(TaskError)
handleOutput(command, result, ctx, true)
const tag = getTag(result)
return new Error(`${redBright(command)} ${dim(`[${tag}]`)}`)
}

Expand All @@ -52,9 +70,18 @@ const makeErr = (command, result, ctx) => {
* @param {Array<string>} options.files — Filepaths to run the linter task against
* @param {Boolean} [options.relative] — Whether the filepaths should be relative
* @param {Boolean} [options.shell] — Whether to skip parsing linter task for better shell support
* @param {Boolean} [options.verbose] — Always show task verbose
* @returns {function(): Promise<Array<string>>}
*/
module.exports = function resolveTaskFn({ command, files, gitDir, isFn, relative, shell = false }) {
module.exports = function resolveTaskFn({
command,
files,
gitDir,
isFn,
relative,
shell = false,
verbose = false
}) {
const [cmd, ...args] = parseArgsStringToArgv(command)
debug('cmd:', cmd)
debug('args:', args)
Expand All @@ -77,5 +104,9 @@ module.exports = function resolveTaskFn({ command, files, gitDir, isFn, relative
if (result.failed || result.killed || result.signal != null) {
throw makeErr(command, result, ctx)
}

if (verbose) {
handleOutput(command, result, ctx)
}
}
}

0 comments on commit 85de3a3

Please sign in to comment.