Repository: okonet/lint-staged (js)

Issue:
Output as plain text when running lint-staged through husky
### Description

When using lint-staged through a pre-commit hook with Husky, the output is shown as plain text as such:

```
git commit
```

![image](https://user-images.githubusercontent.com/1554116/63443660-7c588e80-c435-11e9-80cf-5fb3397beb41.png)

When I run lint-staged on the command line, the output is fine:

```
node .\node_modules\lint-staged\bin\lint-staged -r
```

![image](https://user-images.githubusercontent.com/1554116/63443993-0ef92d80-c436-11e9-85a1-c3cac2f3dc37.png)

I'm unsure what's causing this as I've tried it with both Powershell and Git Bash. Is it because of Husky or perhaps Git? I would like to have pretty output when committing.

### Steps to reproduce

My config `lint-staged.config.js`:

```
module.exports = {
  'src/**/*.ts': files => {
    const convertedFiles = files.map(file => `--files ${file}`).join(' ');
    return `ng lint smart ${convertedFiles}`
  },
  'src/**/*.scss': 'stylelint'
};
``` 

My Husky config in `packages.json`:

```
"husky": {
  "hooks": {
    "pre-commit": "lint-staged -r"
  }
}
```

### Debug Logs

<!--
Run `lint-staged` in debug mode using the command line flag `--debug`. You can
either modify your pre-commit script/command to include the flag or invoke
`lint-staged` manually(`./node_modules/.bin/lint-staged --debug`). Read more
about debug mode here -
https://github.com/okonet/lint-staged#command-line-flags.

NOTE: This requires `lint-staged` version >= `6.0.0`.
-->
<details>

<summary>expand to view</summary>

```
PS C:\Users\wbeeftink\Projects\project> node .\node_modules\lint-staged\bin\lint-staged -rd
  lint-staged:bin Running `lint-staged@9.2.1` +0ms 
  lint-staged Loading config using `cosmiconfig` +0ms   lint-staged Successfully loaded config from `C:\Users\wbeeftink\Projects\project\lint-staged.config.js`: 
  lint-staged { 'src/**/*.ts': [Function: src/**/*.ts],
  lint-staged   'src/**/*.scss': 'stylelint' } +14ms  lint-staged:cfg Validating config +0msRunning lint-staged with the following config:{  'src/**/*.ts': files => {    const convertedFiles = files.map(file => `--files ${file}`).join(' ');    return `ng lint smart ${convertedFiles}`  },  'src/**/*.scss': 'stylelint'}
  lint-staged:run Running all linter scripts +0ms  lint-staged:git Running git command [ 'rev-parse', '--show-toplevel' ] +0ms   lint-staged:run Resolved git directory to be `C:\Users\wbeeftink\Projects\project` +99ms   lint-staged:git Running git command [ 'diff', '--staged', '--diff-filter=ACM', '--name-only' ] +91ms 
  lint-staged:run Loaded list of staged files in git: 
  lint-staged:run [ 'build.cake',
  lint-staged:run   'lint-staged.config.js',
  lint-staged:run   'package.json',
  lint-staged:run   'src/app/test1.scss',
  lint-staged:run   'src/app/test1.ts',
  lint-staged:run   'src/app/test2.scss',
  lint-staged:run   'src/app/test2.ts',
  lint-staged:run   'yarn.lock' ] +109ms
  lint-staged:gen-tasks Generating linter tasks +0ms
  lint-staged:gen-tasks Generated task:
  lint-staged:gen-tasks { pattern: 'src/**/*.ts',
  lint-staged:gen-tasks   commands: [Function: src/**/*.ts],
  lint-staged:gen-tasks   fileList: [ 'src\\app\\test1.ts', 'src\\app\\test2.ts' ] } +5ms     
  lint-staged:gen-tasks Generated task:
  lint-staged:gen-tasks { pattern: 'src/**/*.scss',
  lint-staged:gen-tasks   commands: 'stylelint',
  lint-staged:gen-tasks   fileList: [ 'src\\app\\test1.scss', 'src\\app\\test2.scss' ] } +1ms 
Stashing changes... [started]
  lint-staged:git Running git command [ 'status', '--porcelain' ] +126ms 
Stashing changes... [skipped] 
→ No partially staged files found...
Running tasks... [started]
Running tasks for src/**/*.ts [started]
Running tasks for src/**/*.scss [started]
  lint-staged:make-cmd-tasks Creating listr tasks for commands [Function: src/**/*.ts] +0ms 
  lint-staged:make-cmd-tasks Creating listr tasks for commands 'stylelint' +17ms
ng lint smart --files src\app\test1.ts --files src\app\test2.ts [started]
stylelint [started]
  lint-staged:task cmd: ng +0ms
  lint-staged:task args: [ 'lint',
  'smart',
  '--files',
  'src\\app\\test1.ts',
  '--files',  'src\\app\\test2.ts' ] +0ms  lint-staged:task execaOptions: { preferLocal: true, reject: false, shell: false } +1ms 
  lint-staged:task cmd: stylelint +28ms 
  lint-staged:task args: [ 'src\\app\\test1.scss', 'src\\app\\test2.scss' ] +3ms
  lint-staged:task execaOptions: { preferLocal: true, reject: false, shell: false } +0ms 
stylelint [failed] 
→
Running tasks for src/**/*.scss [failed] 
→ 
ng lint smart --files src\app\test1.ts --files src\app\test2.ts [failed] 
→  
Running tasks for src/**/*.ts [failed]
→ 
Running tasks... [failed]



✖ stylelint found some errors. Please fix them and try committing again.
 
src/app/test1.scss
3:21  ✖  Unexpected named color "red"   color-named

src/app/test2.scss
1:1  ✖  Unexpected empty source   no-empty-source



✖ ng lint smart --files src\app\test1.ts --files src\app\test2.ts found some errors. Please fix them and try committing again.    
Linting "smart"...

ERROR: C:/Users/wbeeftink/Projects/project/src/app/test1.ts:1:12 - Type declaration of 'any' loses type-safety. Consider replacing it with a more precise type.
ERROR: C:/Users/wbeeftink/Projects/project/src/app/test2.ts:1:3 - missing whitespace

Option "files" was already specified with value ["src\\app\\test1.ts"]. The new value ["src\\app\\test1.ts","src\\app\\test2.ts"] 
will override it.
Lint errors found in the listed files.
```

</details>

### Environment

- **OS:** Windows 10
- **Node.js:** v10.15.3
- **`lint-staged`:** v9.2.1

Pull request description:
Force update rendering for forced colors and ensure quiet rendering when requested
I was kind of on the fence for whether forcing animation/update should be determined by `FORCE_COLOR` or a new command line flag or environment variable.

I went with `FORCE_COLOR` because I think 99.9% of the time if you want to force colors, then you also want animation/update. If you're forcing colors, then you're using a modern terminal, which should support animation/update. Let me know if that makes sense to you or if you think differently!

Lastly, while I was making changes here I also noticed that the fallback renderer can be non-silent even when the main renderer is silent. So if you passed the quiet option and the fallback renderer was chosen, then it wouldn't be quiet. I've fixed that as well.

Closes #693

Patch:
diff --git a/lib/getRenderer.js b/lib/getRenderer.js
index 9160686..9841195 100644
--- a/lib/getRenderer.js
+++ b/lib/getRenderer.js
@@ -1,7 +1,28 @@
-export const getRenderer = ({ debug, quiet }, env = process.env) => {
+const getMainRendererOptions = ({ debug, quiet }, env) => {
   if (quiet) return { renderer: 'silent' }
   // Better support for dumb terminals: https://en.wikipedia.org/wiki/Computer_terminal#Dumb_terminals
   const isDumbTerminal = env.TERM === 'dumb'
   if (debug || isDumbTerminal || env.NODE_ENV === 'test') return { renderer: 'verbose' }
   return { renderer: 'update', rendererOptions: { dateFormat: false } }
 }
+
+const getFallbackRenderer = ({ renderer }, { FORCE_COLOR }) => {
+  if (renderer === 'silent') {
+    return 'silent'
+  }
+
+  // If colors are being forced, then also force non-fallback rendering
+  if (Number(FORCE_COLOR) > 0) {
+    return renderer
+  }
+
+  return 'verbose'
+}
+
+export const getRenderer = (options, env = process.env) => {
+  const mainRendererOptions = getMainRendererOptions(options, env)
+  return {
+    ...mainRendererOptions,
+    nonTTYRenderer: getFallbackRenderer(mainRendererOptions, env),
+  }
+}
diff --git a/lib/runAll.js b/lib/runAll.js
index db2d1c8..c82e1cb 100644
--- a/lib/runAll.js
+++ b/lib/runAll.js
@@ -143,7 +143,6 @@ export const runAll = async (
   const listrOptions = {
     ctx,
     exitOnError: false,
-    nonTTYRenderer: 'verbose',
     registerSignalListeners: false,
     ...getRenderer({ debug, quiet }),
   }


Test patch:
diff --git a/test/getRenderer.spec.js b/test/getRenderer.spec.js
index 27f19c1..dc9c572 100644
--- a/test/getRenderer.spec.js
+++ b/test/getRenderer.spec.js
@@ -1,28 +1,61 @@
 import { getRenderer } from '../lib/getRenderer'
 
 describe('getRenderer', () => {
-  it('should return silent renderer when quiet', () => {
-    expect(getRenderer({ quiet: true }, {})).toEqual({ renderer: 'silent' })
+  it('should return silent renderers when quiet', () => {
+    expect(getRenderer({ quiet: true }, {})).toEqual({
+      renderer: 'silent',
+      nonTTYRenderer: 'silent',
+    })
   })
 
-  it('should return verbose renderer when NODE_ENV=test', () => {
-    expect(getRenderer({}, { NODE_ENV: 'test' })).toEqual({ renderer: 'verbose' })
+  it('should return verbose renderers when NODE_ENV=test', () => {
+    expect(getRenderer({}, { NODE_ENV: 'test' })).toEqual({
+      renderer: 'verbose',
+      nonTTYRenderer: 'verbose',
+    })
   })
 
-  it('should return test renderer when TERM=dumb', () => {
-    expect(getRenderer({}, { TERM: 'dumb' })).toEqual({ renderer: 'verbose' })
+  it('should return test renderers when TERM=dumb', () => {
+    expect(getRenderer({}, { TERM: 'dumb' })).toEqual({
+      renderer: 'verbose',
+      nonTTYRenderer: 'verbose',
+    })
   })
 
-  it('should return verbose renderer when debug', () => {
-    expect(getRenderer({ debug: true }, {})).toEqual({ renderer: 'verbose' })
+  it('should return verbose renderers when debug', () => {
+    expect(getRenderer({ debug: true }, {})).toEqual({
+      renderer: 'verbose',
+      nonTTYRenderer: 'verbose',
+    })
   })
 
-  it('should return update renderer by default', () => {
+  it('should return update main renderer and verbose fallback renderer by default', () => {
     expect(getRenderer({}, {})).toEqual({
       renderer: 'update',
       rendererOptions: {
         dateFormat: false,
       },
+      nonTTYRenderer: 'verbose',
+    })
+  })
+
+  it('should return update main renderer and verbose fallback renderer when colors are not forced', () => {
+    expect(getRenderer({}, { FORCE_COLOR: '0' })).toEqual({
+      renderer: 'update',
+      rendererOptions: {
+        dateFormat: false,
+      },
+      nonTTYRenderer: 'verbose',
+    })
+  })
+
+  it('should return update renderers when colors are forced', () => {
+    expect(getRenderer({}, { FORCE_COLOR: '1' })).toEqual({
+      renderer: 'update',
+      rendererOptions: {
+        dateFormat: false,
+      },
+      nonTTYRenderer: 'update',
     })
   })
 })
diff --git a/test/index2.spec.js b/test/index2.spec.js
index 41de135..94cb471 100644
--- a/test/index2.spec.js
+++ b/test/index2.spec.js
@@ -43,7 +43,7 @@ describe('lintStaged', () => {
           "shouldBackup": true,
         },
         "exitOnError": false,
-        "nonTTYRenderer": "verbose",
+        "nonTTYRenderer": "silent",
         "registerSignalListeners": false,
         "renderer": "silent",
       }