Repository: okonet/lint-staged
Pull request #653 (2019-07-04 22:40:47)

feat: make node-api accessible
We have a use-case where we need to call `lint-staged` from inside another node script (there are other pre-commit steps that need to run). Currently we're calling out to `lint-staged` via `execa` but it's slower than calling it in-process via an exposed node API.

codecov[bot]: # [Codecov](https://codecov.io/gh/okonet/lint-staged/pull/653?src=pr&el=h1) Report
> Merging [#653](https://codecov.io/gh/okonet/lint-staged/pull/653?src=pr&el=desc) into [master](https://codecov.io/gh/okonet/lint-staged/commit/28da59a975da367b3ca9710e5387049c29fb1467?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/okonet/lint-staged/pull/653/graphs/tree.svg?width=650&token=LSoEgsjk4O&height=150&src=pr)](https://codecov.io/gh/okonet/lint-staged/pull/653?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##           master     #653   +/-   ##
=======================================
  Coverage   98.67%   98.67%           
=======================================
  Files          11       11           
  Lines         303      303           
  Branches       56       55    -1     
=======================================
  Hits          299      299           
  Misses          4        4
```


| [Impacted Files](https://codecov.io/gh/okonet/lint-staged/pull/653?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/index.js](https://codecov.io/gh/okonet/lint-staged/pull/653/diff?src=pr&el=tree#diff-c3JjL2luZGV4Lmpz) | `97.77% <100%> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/okonet/lint-staged/pull/653?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/okonet/lint-staged/pull/653?src=pr&el=footer). Last update [28da59a...ca9d7e2](https://codecov.io/gh/okonet/lint-staged/pull/653?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

cameronhunter: > It would be nice to have something in the README about node support, and even better would be TypeScript definitions for the `main` entrypoint!

The JSDoc comments on the main entrypoint function will provide TypeScript definitions 🎉 

I was hesitant to add the node API to the docs just yet (let it bed in for a bit), but I can add something if you'd like?
iiroj: > The JSDoc comments on the main entrypoint function will provide TypeScript definitions 🎉
> 
> I was hesitant to add the node API to the docs just yet (let it bed in for a bit), but I can add something if you'd like?

Maybe adding something in the FAQ section is enough, since this is definitely advanced usage.
```mdx
### Can I use `lint-staged` via node?
...
const lintStaged = require('lint-staged')
...
try { ... } catch () {...}
```
cameronhunter: Added section to the FAQ
iiroj: Ping @okonet 

Patch:
diff --git a/README.md b/README.md
index 198ada6..6bbf994 100644
--- a/README.md
+++ b/README.md
@@ -362,6 +362,33 @@ See more on [this blog post](https://medium.com/@tomchentw/imagemin-lint-staged-
 
 ## Frequently Asked Questions
 
+### Can I use `lint-staged` via node?
+
+Yes!
+
+```js
+const lintStaged = require('lint-staged')
+
+try {
+  const success = await lintStaged()
+  console.log(success ? 'Linting was successful!' : 'Linting failed!')
+} catch(e) {
+  // Failed to load configuration
+  console.error(e)
+}
+```
+
+Parameters to `lintStaged` are equivalent to their CLI counterparts:
+
+```js
+const success = await lintStaged({
+  configPath: './path/to/configuration/file',
+  shell: false,
+  quiet: false,
+  debug: false
+})
+```
+
 ### Using with JetBrains IDEs _(WebStorm, PyCharm, IntelliJ IDEA, RubyMine, etc.)_
 
 _**Update**_: The latest version of JetBrains IDEs now support running hooks as you would expect.
diff --git a/index.js b/bin/lint-staged
similarity index 56%
rename from index.js
rename to bin/lint-staged
index 69d8c12..4127993 100755
--- a/index.js
+++ b/bin/lint-staged
@@ -2,7 +2,14 @@
 
 'use strict'
 
-const pkg = require('./package.json')
+// Force colors for packages that depend on https://www.npmjs.com/package/supports-color
+// but do this only in TTY mode
+if (process.stdout.isTTY) {
+  // istanbul ignore next
+  process.env.FORCE_COLOR = '1'
+}
+
+const pkg = require('../package.json')
 require('please-upgrade-node')(
   Object.assign({}, pkg, {
     engines: {
@@ -13,6 +20,7 @@ require('please-upgrade-node')(
 
 const cmdline = require('commander')
 const debugLib = require('debug')
+const lintStaged = require('../src')
 
 const debug = debugLib('lint-staged:bin')
 
@@ -30,4 +38,15 @@ if (cmdline.debug) {
 
 debug('Running `lint-staged@%s`', pkg.version)
 
-require('./src')(console, cmdline.config, !!cmdline.shell, !!cmdline.quiet, !!cmdline.debug)
+lintStaged({
+  configPath: cmdline.config,
+  shell: !!cmdline.shell,
+  quiet: !!cmdline.quiet,
+  debug: !!cmdline.debug
+})
+  .then(passed => {
+    process.exitCode = passed ? 0 : 1
+  })
+  .catch(() => {
+    process.exitCode = 1
+  })
diff --git a/package.json b/package.json
index b534fd0..780507a 100644
--- a/package.json
+++ b/package.json
@@ -10,8 +10,9 @@
     "Suhas Karanth <sudo.suhas@gmail.com>",
     "Iiro Jäppinen <iiro@jappinen.fi> (https://iiro.fi)"
   ],
-  "bin": "index.js",
-  "files": ["index.js", "src"],
+  "main": "./src/index.js",
+  "bin": "./bin/lint-staged",
+  "files": ["src", "bin"],
   "scripts": {
     "cz": "git-cz",
     "lint:base": "eslint --rule \"prettier/prettier: 2\"",
@@ -23,7 +24,7 @@
   },
   "husky": {
     "hooks": {
-      "pre-commit": "node index.js"
+      "pre-commit": "./bin/lint-staged"
     }
   },
   "dependencies": {
diff --git a/src/index.js b/src/index.js
index d4d7aa1..f8afd8a 100644
--- a/src/index.js
+++ b/src/index.js
@@ -7,14 +7,7 @@ const printErrors = require('./printErrors')
 const runAll = require('./runAll')
 const validateConfig = require('./validateConfig')
 
-const debug = require('debug')('lint-staged')
-
-// Force colors for packages that depend on https://www.npmjs.com/package/supports-color
-// but do this only in TTY mode
-if (process.stdout.isTTY) {
-  // istanbul ignore next
-  process.env.FORCE_COLOR = '1'
-}
+const debugLog = require('debug')('lint-staged')
 
 const errConfigNotFound = new Error('Config could not be found')
 
@@ -43,53 +36,55 @@ function loadConfig(configPath) {
 }
 
 /**
- * Root lint-staged function that is called from .bin
- * @param {Function} logger
- * @param {String} configPath
- * @param {Boolean} shellMode Use execa’s shell mode to execute linter commands
- * @param {Boolean} quietMode Use Listr’s silent renderer
- * @param {Boolean} debugMode Enable debug mode
+ * @typedef {(...any) => void} LogFunction
+ * @typedef {{ error: LogFunction, log: LogFunction, warn: LogFunction }} Logger
+ *
+ * Root lint-staged function that is called from `bin/lint-staged`.
+ *
+ * @param {object} options
+ * @param {string} [options.configPath] - Path to configuration file
+ * @param {boolean} [options.shell] - Use execa’s shell mode to execute linter commands
+ * @param {boolean} [options.quiet] - Use Listr’s silent renderer
+ * @param {boolean} [options.debug] - Enable debug mode
+ * @param {Logger} [logger]
+ *
+ * @returns {Promise<boolean>} Promise of whether the linting passed or failed
  */
 module.exports = function lintStaged(
-  logger = console,
-  configPath,
-  shellMode = false,
-  quietMode = false,
-  debugMode = false
+  { configPath, shell = false, quiet = false, debug = false } = {},
+  logger = console
 ) {
-  debug('Loading config using `cosmiconfig`')
+  debugLog('Loading config using `cosmiconfig`')
 
   return loadConfig(configPath)
     .then(result => {
       if (result == null) throw errConfigNotFound
 
-      debug('Successfully loaded config from `%s`:\n%O', result.filepath, result.config)
+      debugLog('Successfully loaded config from `%s`:\n%O', result.filepath, result.config)
       // result.config is the parsed configuration object
       // result.filepath is the path to the config file that was found
       const config = validateConfig(result.config)
-      if (debugMode) {
+      if (debug) {
         // Log using logger to be able to test through `consolemock`.
         logger.log('Running lint-staged with the following config:')
         logger.log(stringifyObject(config, { indent: '  ' }))
       } else {
         // We might not be in debug mode but `DEBUG=lint-staged*` could have
         // been set.
-        debug('Normalized config:\n%O', config)
+        debugLog('Normalized config:\n%O', config)
       }
 
-      return runAll(config, shellMode, quietMode, debugMode)
+      return runAll(config, shell, quiet, debug, logger)
         .then(() => {
-          debug('linters were executed successfully!')
-          // No errors, exiting with 0
+          debugLog('linters were executed successfully!')
+          return Promise.resolve(true)
         })
         .catch(error => {
-          // Errors detected, printing and exiting with non-zero
-          process.exitCode = 1
-          printErrors(error)
+          printErrors(error, logger)
+          return Promise.resolve(false)
         })
     })
     .catch(err => {
-      process.exitCode = 1
       if (err === errConfigNotFound) {
         logger.error(`${err.message}.`)
       } else {
@@ -106,5 +101,7 @@ module.exports = function lintStaged(
         Please make sure you have created it correctly.
         See https://github.com/okonet/lint-staged#configuration.
       `)
+
+      return Promise.reject(err)
     })
 }
diff --git a/src/makeCmdTasks.js b/src/makeCmdTasks.js
index 8ac8d18..d07ec66 100644
--- a/src/makeCmdTasks.js
+++ b/src/makeCmdTasks.js
@@ -10,7 +10,6 @@ const debug = require('debug')('lint-staged:make-cmd-tasks')
  * @param {Array<string|Function>|string|Function} commands
  * @param {Boolean} shell
  * @param {Array<string>} pathsToLint
- * @param {Object} [options]
  */
 module.exports = async function makeCmdTasks(commands, shell, gitDir, pathsToLint) {
   debug('Creating listr tasks for commands %o', commands)
diff --git a/src/printErrors.js b/src/printErrors.js
index adf5c01..51bb25b 100644
--- a/src/printErrors.js
+++ b/src/printErrors.js
@@ -4,12 +4,12 @@
 // Work-around for duplicated error logs, see #142
 const errMsg = err => (err.privateMsg != null ? err.privateMsg : err.message)
 
-module.exports = function printErrors(errorInstance) {
+module.exports = function printErrors(errorInstance, logger) {
   if (Array.isArray(errorInstance.errors)) {
     errorInstance.errors.forEach(lintError => {
-      console.error(errMsg(lintError))
+      logger.error(errMsg(lintError))
     })
   } else {
-    console.error(errMsg(errorInstance))
+    logger.error(errMsg(errorInstance))
   }
 }
diff --git a/src/resolveTaskFn.js b/src/resolveTaskFn.js
index b6b52d9..6b04102 100644
--- a/src/resolveTaskFn.js
+++ b/src/resolveTaskFn.js
@@ -80,8 +80,8 @@ function makeErr(linter, result, context = {}) {
  * @param {string} options.gitDir
  * @param {Boolean} options.isFn
  * @param {string} options.linter
- * @param {Boolean} options.shellMode
  * @param {Array<string>} options.pathsToLint
+ * @param {Boolean} [options.shell]
  * @returns {function(): Promise<Array<string>>}
  */
 module.exports = function resolveTaskFn({ gitDir, isFn, linter, pathsToLint, shell = false }) {
diff --git a/src/runAll.js b/src/runAll.js
index 13ec747..62f23b6 100644
--- a/src/runAll.js
+++ b/src/runAll.js
@@ -1,5 +1,7 @@
 'use strict'
 
+/** @typedef {import('./index').Logger} Logger */
+
 const chalk = require('chalk')
 const dedent = require('dedent')
 const Listr = require('listr')
@@ -23,17 +25,20 @@ const MAX_ARG_LENGTH =
 
 /**
  * Executes all tasks and either resolves or rejects the promise
+ *
  * @param config {Object}
- * @param {Boolean} shellMode Use execa’s shell mode to execute linter commands
- * @param {Boolean} quietMode Use Listr’s silent renderer
- * @param {Boolean} debugMode Enable debug mode
+ * @param {Boolean} [shellMode] Use execa’s shell mode to execute linter commands
+ * @param {Boolean} [quietMode] Use Listr’s silent renderer
+ * @param {Boolean} [debugMode] Enable debug mode
+ * @param {Logger} logger
  * @returns {Promise}
  */
 module.exports = async function runAll(
   config,
   shellMode = false,
   quietMode = false,
-  debugMode = false
+  debugMode = false,
+  logger = console
 ) {
   debug('Running all linter scripts')
 
@@ -55,7 +60,7 @@ module.exports = async function runAll(
 
   const argLength = files.join(' ').length
   if (argLength > MAX_ARG_LENGTH) {
-    console.warn(
+    logger.warn(
       dedent`${symbols.warning}  ${chalk.yellow(
         `lint-staged generated an argument string of ${argLength} characters, and commands might not run correctly on your platform.
 It is recommended to use functions as linters and split your command based on the number of staged files. For more info, please visit:
@@ -91,7 +96,7 @@ https://github.com/okonet/lint-staged#using-js-functions-to-customize-linter-com
   // If all of the configured "linters" should be skipped
   // avoid executing any lint-staged logic
   if (tasks.every(task => task.skip())) {
-    console.log('No staged files match any of provided globs.')
+    logger.log('No staged files match any of provided globs.')
     return 'No tasks to run.'
   }
 


Test patch:
diff --git a/test/__snapshots__/index.spec.js.snap b/test/__snapshots__/index.spec.js.snap
index 975ff6b..8a4dcb8 100644
--- a/test/__snapshots__/index.spec.js.snap
+++ b/test/__snapshots__/index.spec.js.snap
@@ -1,11 +1,20 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`lintStaged should exit with code 1 on linter errors 1`] = `
+"
+ERROR 
+
+
+× node -e \\"process.exit(1)\\" found some errors. Please fix them and try committing again."
+`;
+
 exports[`lintStaged should load an npm config package when specified 1`] = `
 "
 LOG Running lint-staged with the following config:
 LOG {
   '*': 'mytask'
-}"
+}
+ERROR Unable to get staged files!"
 `;
 
 exports[`lintStaged should load config file when specified 1`] = `
@@ -13,17 +22,22 @@ exports[`lintStaged should load config file when specified 1`] = `
 LOG Running lint-staged with the following config:
 LOG {
   '*': 'mytask'
-}"
+}
+ERROR Unable to get staged files!"
 `;
 
-exports[`lintStaged should not output config in normal mode 1`] = `""`;
+exports[`lintStaged should not output config in normal mode 1`] = `
+"
+ERROR Unable to get staged files!"
+`;
 
 exports[`lintStaged should output config in debug mode 1`] = `
 "
 LOG Running lint-staged with the following config:
 LOG {
   '*': 'mytask'
-}"
+}
+ERROR Unable to get staged files!"
 `;
 
 exports[`lintStaged should parse function linter from js config 1`] = `
@@ -32,10 +46,11 @@ LOG Running lint-staged with the following config:
 LOG {
   '*.css': filenames => \`echo \${filenames.join(' ')}\`,
   '*.js': filenames => filenames.map(filename => \`echo \${filename}\`)
-}"
+}
+ERROR Unable to get staged files!"
 `;
 
-exports[`lintStaged should print helpful error message when config file is not found 1`] = `
+exports[`lintStaged should print helpful error message when config file is not found 2`] = `
 "
 ERROR Config could not be found.
 ERROR 
@@ -53,7 +68,22 @@ ERROR Please make sure you have created it correctly.
 See https://github.com/okonet/lint-staged#configuration.
 `;
 
-exports[`lintStaged should throw when invalid config is provided 1`] = `
+exports[`lintStaged should throw when invalid config is provided 2`] = `
+"
+ERROR Could not parse lint-staged config.
+
+Error: Configuration should not be empty!
+ERROR 
+ERROR Please make sure you have created it correctly.
+See https://github.com/okonet/lint-staged#configuration."
+`;
+
+exports[`lintStaged should use cosmiconfig if no params are passed 1`] = `
+"
+ERROR Unable to get staged files!"
+`;
+
+exports[`lintStaged should use use the console if no logger is passed 1`] = `
 "
 ERROR Could not parse lint-staged config.
 
diff --git a/test/__snapshots__/runAll.spec.js.snap b/test/__snapshots__/runAll.spec.js.snap
index 9364d36..b52b75f 100644
--- a/test/__snapshots__/runAll.spec.js.snap
+++ b/test/__snapshots__/runAll.spec.js.snap
@@ -111,3 +111,36 @@ LOG {
   context: {hasStash: true, hasErrors: true}
 }"
 `;
+
+exports[`runAll should use an injected logger 1`] = `
+"
+LOG No staged files match any of provided globs."
+`;
+
+exports[`runAll should warn if the argument length is longer than what the platform can handle 1`] = `
+"
+WARN ‼  lint-staged generated an argument string of 999999 characters, and commands might not run correctly on your platform.
+It is recommended to use functions as linters and split your command based on the number of staged files. For more info, please visit:
+https://github.com/okonet/lint-staged#using-js-functions-to-customize-linter-commands
+LOG Stashing changes... [started]
+LOG Stashing changes... [skipped]
+LOG → No partially staged files found...
+LOG Running linters... [started]
+LOG Running tasks for *.js [started]
+LOG echo \\"sample\\" [started]
+LOG echo \\"sample\\" [failed]
+LOG → 
+LOG Running tasks for *.js [failed]
+LOG → 
+LOG Running linters... [failed]
+LOG {
+  name: 'ListrError',
+  errors: [
+    {
+      privateMsg: '\\\\n\\\\n\\\\n× echo \\"sample\\" found some errors. Please fix them and try committing again.\\\\n\\\\nLinter finished with error',
+      context: {hasErrors: true}
+    }
+  ],
+  context: {hasErrors: true}
+}"
+`;
diff --git a/test/index.spec.js b/test/index.spec.js
index 0cebe25..6d2e3c2 100644
--- a/test/index.spec.js
+++ b/test/index.spec.js
@@ -4,10 +4,6 @@ import path from 'path'
 
 jest.unmock('execa')
 
-// silence console from Jest output
-console.log = jest.fn(() => {})
-console.error = jest.fn(() => {})
-
 // eslint-disable-next-line import/first
 import getStagedFiles from '../src/getStagedFiles'
 // eslint-disable-next-line import/first
@@ -28,6 +24,16 @@ const mockCosmiconfigWith = result => {
 
 jest.mock('../src/gitWorkflow')
 
+async function withMockedConsole(mockConsole, fn) {
+  const previousConsole = console
+  try {
+    console = mockConsole
+    await fn()
+  } finally {
+    console = previousConsole
+  }
+}
+
 // TODO: Never run tests in the project's WC because this might change source files git status
 
 describe('lintStaged', () => {
@@ -37,13 +43,35 @@ describe('lintStaged', () => {
     logger.clearHistory()
   })
 
+  it('should use cosmiconfig if no params are passed', async () => {
+    expect.assertions(1)
+    const config = {
+      '*': 'mytask'
+    }
+    mockCosmiconfigWith({ config })
+    await lintStaged(undefined, logger)
+    expect(logger.printHistory()).toMatchSnapshot()
+  })
+
+  it('should use use the console if no logger is passed', async () => {
+    expect.assertions(1)
+    mockCosmiconfigWith({ config: {} })
+
+    const mockedConsole = makeConsoleMock()
+    try {
+      await withMockedConsole(mockedConsole, () => lintStaged())
+    } catch (ignore) {
+      expect(mockedConsole.printHistory()).toMatchSnapshot()
+    }
+  })
+
   it('should output config in debug mode', async () => {
     expect.assertions(1)
     const config = {
       '*': 'mytask'
     }
     mockCosmiconfigWith({ config })
-    await lintStaged(logger, undefined, false, false, true)
+    await lintStaged({ debug: true, quiet: true }, logger)
     expect(logger.printHistory()).toMatchSnapshot()
   })
 
@@ -53,25 +81,28 @@ describe('lintStaged', () => {
       '*': 'mytask'
     }
     mockCosmiconfigWith({ config })
-    await lintStaged(logger)
+    await lintStaged({ quiet: true }, logger)
     expect(logger.printHistory()).toMatchSnapshot()
   })
 
   it('should throw when invalid config is provided', async () => {
     const config = {}
     mockCosmiconfigWith({ config })
-    await lintStaged(logger)
+    await expect(lintStaged({ quiet: true }, logger)).rejects.toMatchInlineSnapshot(
+      `[Error: Configuration should not be empty!]`
+    )
     expect(logger.printHistory()).toMatchSnapshot()
   })
 
   it('should load config file when specified', async () => {
     expect.assertions(1)
     await lintStaged(
-      logger,
-      path.join(__dirname, '__mocks__', 'my-config.json'),
-      false,
-      false,
-      true
+      {
+        configPath: path.join(__dirname, '__mocks__', 'my-config.json'),
+        debug: true,
+        quiet: true
+      },
+      logger
     )
     expect(logger.printHistory()).toMatchSnapshot()
   })
@@ -79,11 +110,12 @@ describe('lintStaged', () => {
   it('should parse function linter from js config', async () => {
     expect.assertions(1)
     await lintStaged(
-      logger,
-      path.join(__dirname, '__mocks__', 'advanced-config.js'),
-      false,
-      false,
-      true
+      {
+        configPath: path.join(__dirname, '__mocks__', 'advanced-config.js'),
+        debug: true,
+        quiet: true
+      },
+      logger
     )
     expect(logger.printHistory()).toMatchSnapshot()
   })
@@ -91,16 +123,17 @@ describe('lintStaged', () => {
   it('should load an npm config package when specified', async () => {
     expect.assertions(1)
     jest.mock('my-lint-staged-config')
-    await lintStaged(logger, 'my-lint-staged-config', false, false, true)
+    await lintStaged({ configPath: 'my-lint-staged-config', quiet: true, debug: true }, logger)
     expect(logger.printHistory()).toMatchSnapshot()
   })
 
   it('should print helpful error message when config file is not found', async () => {
     expect.assertions(2)
     mockCosmiconfigWith(null)
-    await lintStaged(logger)
+    await expect(lintStaged({ quiet: true }, logger)).rejects.toMatchInlineSnapshot(
+      `[Error: Config could not be found]`
+    )
     expect(logger.printHistory()).toMatchSnapshot()
-    expect(process.exitCode).toEqual(1)
   })
 
   it('should print helpful error message when explicit config file is not found', async () => {
@@ -115,9 +148,11 @@ describe('lintStaged', () => {
       )
     )
 
-    await lintStaged(logger, nonExistentConfig)
+    await expect(
+      lintStaged({ configPath: nonExistentConfig, quiet: true }, logger)
+    ).rejects.toThrowError()
+
     expect(logger.printHistory()).toMatchSnapshot()
-    expect(process.exitCode).toEqual(1)
   })
 
   it('should exit with code 1 on linter errors', async () => {
@@ -126,8 +161,8 @@ describe('lintStaged', () => {
     }
     mockCosmiconfigWith({ config })
     getStagedFiles.mockImplementationOnce(async () => ['sample.java'])
-    await lintStaged(logger, undefined)
-    expect(console.error).toHaveBeenCalledWith(expect.stringContaining('node -e "process.exit(1)"'))
-    expect(process.exitCode).toEqual(1)
+    const passed = await lintStaged({ quiet: true }, logger)
+    expect(logger.printHistory()).toMatchSnapshot()
+    expect(passed).toBe(false)
   })
 })
diff --git a/test/index2.spec.js b/test/index2.spec.js
index eacd7e2..1cff733 100644
--- a/test/index2.spec.js
+++ b/test/index2.spec.js
@@ -18,11 +18,8 @@ describe('lintStaged', () => {
   it('should pass quiet flag to Listr', async () => {
     expect.assertions(1)
     await lintStaged(
-      console,
-      path.join(__dirname, '__mocks__', 'my-config.json'),
-      false,
-      true,
-      false
+      { configPath: path.join(__dirname, '__mocks__', 'my-config.json'), quiet: true },
+      console
     )
     expect(Listr.mock.calls[0][1]).toEqual({ dateFormat: false, renderer: 'silent' })
   })
@@ -30,11 +27,11 @@ describe('lintStaged', () => {
   it('should pass debug flag to Listr', async () => {
     expect.assertions(1)
     await lintStaged(
-      console,
-      path.join(__dirname, '__mocks__', 'my-config.json'),
-      false,
-      false,
-      true
+      {
+        configPath: path.join(__dirname, '__mocks__', 'my-config.json'),
+        debug: true
+      },
+      console
     )
     expect(Listr.mock.calls[0][1]).toEqual({ dateFormat: false, renderer: 'verbose' })
   })
diff --git a/test/printErrors.spec.js b/test/printErrors.spec.js
index cb1ddf1..5c05b42 100644
--- a/test/printErrors.spec.js
+++ b/test/printErrors.spec.js
@@ -3,24 +3,16 @@ import makeConsoleMock from 'consolemock'
 import printErrors from '../src/printErrors'
 
 describe('printErrors', () => {
-  const originalConsole = console
-
-  beforeAll(() => {
-    console = makeConsoleMock()
-  })
+  const logger = makeConsoleMock()
 
   beforeEach(() => {
-    console.clearHistory()
-  })
-
-  afterAll(() => {
-    global.console = originalConsole
+    logger.clearHistory()
   })
 
   it('should print plain errors', () => {
     const err = new Error('We have a problem')
-    printErrors(err)
-    expect(console.printHistory()).toMatchSnapshot()
+    printErrors(err, logger)
+    expect(logger.printHistory()).toMatchSnapshot()
   })
 
   it('should print Listr nested errors', async () => {
@@ -59,8 +51,8 @@ describe('printErrors', () => {
     try {
       await list.run()
     } catch (err) {
-      printErrors(err)
-      expect(console.printHistory()).toMatchSnapshot()
+      printErrors(err, logger)
+      expect(logger.printHistory()).toMatchSnapshot()
     }
   })
 })
diff --git a/test/runAll.spec.js b/test/runAll.spec.js
index 3250d1a..d52eaf7 100644
--- a/test/runAll.spec.js
+++ b/test/runAll.spec.js
@@ -49,6 +49,13 @@ describe('runAll', () => {
     expect(console.printHistory()).toMatchSnapshot()
   })
 
+  it('should use an injected logger', async () => {
+    expect.assertions(1)
+    const logger = makeConsoleMock()
+    await runAll({ '*.js': ['echo "sample"'] }, undefined, true, undefined, logger)
+    expect(logger.printHistory()).toMatchSnapshot()
+  })
+
   it('should not skip tasks if there are files', async () => {
     expect.assertions(1)
     getStagedFiles.mockImplementationOnce(async () => ['sample.js'])
@@ -103,6 +110,18 @@ describe('runAll', () => {
     expect(gitStashPop).toHaveBeenCalledTimes(1)
   })
 
+  it('should warn if the argument length is longer than what the platform can handle', async () => {
+    hasPartiallyStagedFiles.mockImplementationOnce(() => Promise.resolve(false))
+    getStagedFiles.mockImplementationOnce(async () => new Array(100000).fill('sample.js'))
+
+    try {
+      await runAll({ '*.js': () => 'echo "sample"' })
+    } catch (err) {
+      console.log(err)
+    }
+    expect(console.printHistory()).toMatchSnapshot()
+  })
+
   it('should skip linters and stash update but perform working copy restore if terminated', async () => {
     expect.assertions(4)
     hasPartiallyStagedFiles.mockImplementationOnce(() => Promise.resolve(true))