Skip to content

Commit 62b5b83

Browse files
committedJan 23, 2022
feat: add --cwd option for overriding task directory
1 parent 5542100 commit 62b5b83

File tree

6 files changed

+130
-50
lines changed

6 files changed

+130
-50
lines changed
 

‎README.md

+12-14
Original file line numberDiff line numberDiff line change
@@ -81,38 +81,36 @@ See [Releases](https://github.com/okonet/lint-staged/releases).
8181

8282
## Command line flags
8383

84-
```bash
84+
```
8585
❯ npx lint-staged --help
8686
Usage: lint-staged [options]
8787
8888
Options:
8989
-V, --version output the version number
90-
--allow-empty allow empty commits when tasks revert all staged changes
91-
(default: false)
90+
--allow-empty allow empty commits when tasks revert all staged changes (default: false)
91+
-p, --concurrent <number|boolean> the number of tasks to run concurrently, or false for serial (default: true)
9292
-c, --config [path] path to configuration file, or - to read from stdin
93+
--cwd [path] run all tasks in specific directory, instead of the current
9394
-d, --debug print additional debug information (default: false)
94-
--no-stash disable the backup stash, and do not revert in case of
95-
errors
96-
-p, --concurrent <parallel tasks> the number of tasks to run concurrently, or false to run
97-
tasks serially (default: true)
95+
--no-stash disable the backup stash, and do not revert in case of errors
9896
-q, --quiet disable lint-staged’s own console output (default: false)
9997
-r, --relative pass relative filepaths to tasks (default: false)
100-
-x, --shell [path] skip parsing of tasks for better shell support (default:
101-
false)
102-
-v, --verbose show task output even when tasks succeed; by default only
103-
failed output is shown (default: false)
98+
-x, --shell [path] skip parsing of tasks for better shell support (default: false)
99+
-v, --verbose show task output even when tasks succeed; by default only failed output is shown
100+
(default: false)
104101
-h, --help display help for command
105102
```
106103

107104
- **`--allow-empty`**: By default, when linter tasks undo all staged changes, lint-staged will exit with an error and abort the commit. Use this flag to allow creating empty git commits.
108-
- **`--config [path]`**: Manually specify a path to a config file or npm package name. Note: when used, lint-staged won't perform the config file search and will print an error if the specified file cannot be found. If '-' is provided as the filename then the config will be read from stdin, allowing piping in the config like `cat my-config.json | npx lint-staged --config -`.
109-
- **`--debug`**: Run in debug mode. When set, it does the following:
105+
- **`--concurrent [number|boolean]`**: Controls the concurrency of tasks being run by lint-staged. **NOTE**: This does NOT affect the concurrency of subtasks (they will always be run sequentially). Possible values are:
110106
- uses [debug](https://github.com/visionmedia/debug) internally to log additional information about staged files, commands being executed, location of binaries, etc. Debug logs, which are automatically enabled by passing the flag, can also be enabled by setting the environment variable `$DEBUG` to `lint-staged*`.
111107
- uses [`verbose` renderer](https://github.com/SamVerschueren/listr-verbose-renderer) for `listr`; this causes serial, uncoloured output to the terminal, instead of the default (beautified, dynamic) output.
112-
- **`--concurrent [number | (true/false)]`**: Controls the concurrency of tasks being run by lint-staged. **NOTE**: This does NOT affect the concurrency of subtasks (they will always be run sequentially). Possible values are:
113108
- `false`: Run all tasks serially
114109
- `true` (default) : _Infinite_ concurrency. Runs as many tasks in parallel as possible.
115110
- `{number}`: Run the specified number of tasks in parallel, where `1` is equivalent to `false`.
111+
- **`--config [path]`**: Manually specify a path to a config file or npm package name. Note: when used, lint-staged won't perform the config file search and will print an error if the specified file cannot be found. If '-' is provided as the filename then the config will be read from stdin, allowing piping in the config like `cat my-config.json | npx lint-staged --config -`.
112+
- **`--cwd [path]`**: By default tasks run in the current working directory. Use the `--cwd some/directory` to override this. The path can be absolute or relative to the current working directory.
113+
- **`--debug`**: Run in debug mode. When set, it does the following:
116114
- **`--no-stash`**: By default a backup stash will be created before running the tasks, and all task modifications will be reverted in case of an error. This option will disable creating the stash, and instead leave all modifications in the index when aborting the commit.
117115
- **`--quiet`**: Supress all CLI output, except from tasks.
118116
- **`--relative`**: Pass filepaths relative to `process.cwd()` (where `lint-staged` runs) to tasks. Default is `false`.

‎bin/lint-staged.js

+8-6
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,15 @@ const version = packageJson.version
2626
cmdline
2727
.version(version)
2828
.option('--allow-empty', 'allow empty commits when tasks revert all staged changes', false)
29-
.option('-c, --config [path]', 'path to configuration file, or - to read from stdin')
30-
.option('-d, --debug', 'print additional debug information', false)
31-
.option('--no-stash', 'disable the backup stash, and do not revert in case of errors', false)
3229
.option(
33-
'-p, --concurrent <parallel tasks>',
34-
'the number of tasks to run concurrently, or false to run tasks serially',
30+
'-p, --concurrent <number|boolean>',
31+
'the number of tasks to run concurrently, or false for serial',
3532
true
3633
)
34+
.option('-c, --config [path]', 'path to configuration file, or - to read from stdin')
35+
.option('--cwd [path]', 'run all tasks in specific directory, instead of the current')
36+
.option('-d, --debug', 'print additional debug information', false)
37+
.option('--no-stash', 'disable the backup stash, and do not revert in case of errors', false)
3738
.option('-q, --quiet', 'disable lint-staged’s own console output', false)
3839
.option('-r, --relative', 'pass relative filepaths to tasks', false)
3940
.option('-x, --shell [path]', 'skip parsing of tasks for better shell support', false)
@@ -75,12 +76,13 @@ const options = {
7576
allowEmpty: !!cmdlineOptions.allowEmpty,
7677
concurrent: JSON.parse(cmdlineOptions.concurrent),
7778
configPath: cmdlineOptions.config,
79+
cwd: cmdlineOptions.cwd,
7880
debug: !!cmdlineOptions.debug,
7981
maxArgLength: getMaxArgLength() / 2,
80-
stash: !!cmdlineOptions.stash, // commander inverts `no-<x>` flags to `!x`
8182
quiet: !!cmdlineOptions.quiet,
8283
relative: !!cmdlineOptions.relative,
8384
shell: cmdlineOptions.shell /* Either a boolean or a string pointing to the shell */,
85+
stash: !!cmdlineOptions.stash, // commander inverts `no-<x>` flags to `!x`
8486
verbose: !!cmdlineOptions.verbose,
8587
}
8688

‎lib/index.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const lintStaged = async (
3737
concurrent = true,
3838
config: configObject,
3939
configPath,
40-
cwd = process.cwd(),
40+
cwd,
4141
debug = false,
4242
maxArgLength,
4343
quiet = false,
@@ -48,7 +48,7 @@ const lintStaged = async (
4848
} = {},
4949
logger = console
5050
) => {
51-
await validateOptions({ shell }, logger)
51+
await validateOptions({ cwd, shell }, logger)
5252

5353
// Unset GIT_LITERAL_PATHSPECS to not mess with path interpretation
5454
debugLog('Unset GIT_LITERAL_PATHSPECS (was `%s`)', process.env.GIT_LITERAL_PATHSPECS)

‎lib/runAll.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export const runAll = async (
6666
concurrent = true,
6767
configObject,
6868
configPath,
69-
cwd = process.cwd(),
69+
cwd,
7070
debug = false,
7171
maxArgLength,
7272
quiet = false,
@@ -77,7 +77,11 @@ export const runAll = async (
7777
},
7878
logger = console
7979
) => {
80-
debugLog('Running all linter scripts')
80+
debugLog('Running all linter scripts...')
81+
82+
// Resolve relative CWD option
83+
cwd = cwd ? path.resolve(cwd) : process.cwd()
84+
debugLog('Using working directory `%s`', cwd)
8185

8286
const ctx = getInitialState({ quiet })
8387

‎lib/validateOptions.js

+13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { constants, promises as fs } from 'fs'
2+
import path from 'path'
23

34
import debug from 'debug'
45

@@ -10,13 +11,25 @@ const debugLog = debug('lint-staged:validateOptions')
1011
/**
1112
* Validate lint-staged options, either from the Node.js API or the command line flags.
1213
* @param {*} options
14+
* @param {boolean|string} [options.cwd] - Current working directory
1315
* @param {boolean|string} [options.shell] - Skip parsing of tasks for better shell support
1416
*
1517
* @throws {InvalidOptionsError}
1618
*/
1719
export const validateOptions = async (options = {}, logger) => {
1820
debugLog('Validating options...')
1921

22+
/** Ensure the passed cwd option exists; it might also be relative */
23+
if (typeof options.cwd === 'string') {
24+
try {
25+
const resolved = path.resolve(options.cwd)
26+
await fs.access(resolved, constants.F_OK)
27+
} catch (error) {
28+
logger.error(invalidOption('cwd', options.cwd, error.message))
29+
throw InvalidOptionsError
30+
}
31+
}
32+
2033
/** Ensure the passed shell option is executable */
2134
if (typeof options.shell === 'string') {
2235
try {

‎test/validateOptions.spec.js

+89-26
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { constants, promises as fs } from 'fs'
2+
import path from 'path'
23

34
import makeConsoleMock from 'consolemock'
45

@@ -7,8 +8,6 @@ import { InvalidOptionsError } from '../lib/symbols'
78

89
describe('validateOptions', () => {
910
const mockAccess = jest.spyOn(fs, 'access')
10-
mockAccess.mockImplementation(async () => {})
11-
1211
beforeEach(() => {
1312
mockAccess.mockClear()
1413
})
@@ -18,49 +17,113 @@ describe('validateOptions', () => {
1817

1918
const logger = makeConsoleMock()
2019

20+
mockAccess.mockImplementationOnce(async () => {})
21+
2122
await expect(validateOptions({}, logger)).resolves.toBeUndefined()
2223
await expect(validateOptions(undefined, logger)).resolves.toBeUndefined()
2324

2425
expect(logger.history()).toHaveLength(0)
2526
})
2627

27-
it('should resolve with valid string-valued shell option', async () => {
28-
expect.assertions(4)
28+
describe('cwd', () => {
29+
it('should resolve with valid absolute cwd option', async () => {
30+
expect.assertions(4)
2931

30-
const logger = makeConsoleMock()
32+
const logger = makeConsoleMock()
3133

32-
await expect(validateOptions({ shell: '/bin/sh' }, logger)).resolves.toBeUndefined()
34+
await expect(validateOptions({ cwd: process.cwd() }, logger)).resolves.toBeUndefined()
3335

34-
expect(mockAccess).toHaveBeenCalledTimes(1)
35-
expect(mockAccess).toHaveBeenCalledWith('/bin/sh', constants.X_OK)
36+
expect(mockAccess).toHaveBeenCalledTimes(1)
37+
expect(mockAccess).toHaveBeenCalledWith(process.cwd(), constants.F_OK)
3638

37-
expect(logger.history()).toHaveLength(0)
39+
expect(logger.history()).toHaveLength(0)
40+
})
41+
42+
it('should resolve with valid relative cwd option', async () => {
43+
expect.assertions(4)
44+
45+
const logger = makeConsoleMock()
46+
47+
await expect(validateOptions({ cwd: 'test' }, logger)).resolves.toBeUndefined()
48+
49+
expect(mockAccess).toHaveBeenCalledTimes(1)
50+
expect(mockAccess).toHaveBeenCalledWith(path.join(process.cwd(), 'test'), constants.F_OK)
51+
52+
expect(logger.history()).toHaveLength(0)
53+
})
54+
55+
it('should reject with invalid cwd option', async () => {
56+
expect.assertions(4)
57+
58+
const logger = makeConsoleMock()
59+
60+
await expect(validateOptions({ cwd: 'non_existent' }, logger)).rejects.toThrowError(
61+
InvalidOptionsError
62+
)
63+
64+
expect(mockAccess).toHaveBeenCalledTimes(1)
65+
expect(mockAccess).toHaveBeenCalledWith(
66+
path.join(process.cwd(), 'non_existent'),
67+
constants.F_OK
68+
)
69+
70+
expect(logger.printHistory()).toMatchInlineSnapshot(`
71+
"
72+
ERROR ✖ Validation Error:
73+
74+
Invalid value for option 'cwd': non_existent
75+
76+
ENOENT: no such file or directory, access '${path
77+
.join(process.cwd(), 'non_existent')
78+
// Windows test fix: D:\something -> D:\\something
79+
.replace(/\\/g, '\\\\')}'
80+
81+
See https://github.com/okonet/lint-staged#command-line-flags"
82+
`)
83+
})
3884
})
3985

40-
it('should reject with invalid string-valued shell option', async () => {
41-
expect.assertions(5)
86+
describe('shell', () => {
87+
it('should resolve with valid string-valued shell option', async () => {
88+
expect.assertions(4)
4289

43-
const logger = makeConsoleMock()
90+
const logger = makeConsoleMock()
91+
92+
mockAccess.mockImplementationOnce(async () => {})
93+
94+
await expect(validateOptions({ shell: '/bin/sh' }, logger)).resolves.toBeUndefined()
95+
96+
expect(mockAccess).toHaveBeenCalledTimes(1)
97+
expect(mockAccess).toHaveBeenCalledWith('/bin/sh', constants.X_OK)
98+
99+
expect(logger.history()).toHaveLength(0)
100+
})
101+
102+
it('should reject with invalid string-valued shell option', async () => {
103+
expect.assertions(5)
104+
105+
const logger = makeConsoleMock()
44106

45-
mockAccess.mockImplementationOnce(() => Promise.reject(new Error('Failed')))
107+
mockAccess.mockImplementationOnce(() => Promise.reject(new Error('Failed')))
46108

47-
await expect(validateOptions({ shell: '/bin/sh' }, logger)).rejects.toThrowError(
48-
InvalidOptionsError
49-
)
109+
await expect(validateOptions({ shell: '/bin/sh' }, logger)).rejects.toThrowError(
110+
InvalidOptionsError
111+
)
50112

51-
expect(mockAccess).toHaveBeenCalledTimes(1)
52-
expect(mockAccess).toHaveBeenCalledWith('/bin/sh', constants.X_OK)
113+
expect(mockAccess).toHaveBeenCalledTimes(1)
114+
expect(mockAccess).toHaveBeenCalledWith('/bin/sh', constants.X_OK)
53115

54-
expect(logger.history()).toHaveLength(1)
55-
expect(logger.printHistory()).toMatchInlineSnapshot(`
56-
"
57-
ERROR ✖ Validation Error:
116+
expect(logger.history()).toHaveLength(1)
117+
expect(logger.printHistory()).toMatchInlineSnapshot(`
118+
"
119+
ERROR ✖ Validation Error:
58120
59-
Invalid value for option 'shell': /bin/sh
121+
Invalid value for option 'shell': /bin/sh
60122
61-
Failed
123+
Failed
62124
63-
See https://github.com/okonet/lint-staged#command-line-flags"
64-
`)
125+
See https://github.com/okonet/lint-staged#command-line-flags"
126+
`)
127+
})
65128
})
66129
})

0 commit comments

Comments
 (0)
Please sign in to comment.