@depup/lint-staged 16.3.2-depup.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { userInfo } from 'node:os'
4
+
5
+ import { Option, program } from 'commander'
6
+
7
+ import { createDebug, enableDebug } from '../lib/debug.js'
8
+ import lintStaged from '../lib/index.js'
9
+ import { CONFIG_STDIN_ERROR, restoreStashExample } from '../lib/messages.js'
10
+ import { readStdin } from '../lib/readStdin.js'
11
+ import { getVersion } from '../lib/version.js'
12
+
13
+ const debugLog = createDebug('lint-staged:bin')
14
+
15
+ // Do not terminate main Listr process on SIGINT
16
+ process.on('SIGINT', () => {})
17
+
18
+ program
19
+ .version(await getVersion())
20
+
21
+ /**
22
+ * This shouldn't be necessary for lint-staged, but add migration step just in case
23
+ * to preserve old behavior of "commander".
24
+ *
25
+ * @todo remove this in the major version
26
+ * @see https://github.com/tj/commander.js/releases/tag/v13.0.0
27
+ * */
28
+ .allowExcessArguments()
29
+
30
+ .addOption(
31
+ new Option('--allow-empty', 'allow empty commits when tasks revert all staged changes').default(
32
+ false
33
+ )
34
+ )
35
+ .addOption(
36
+ new Option(
37
+ '-p, --concurrent <number|boolean>',
38
+ 'the number of tasks to run concurrently, or false for serial'
39
+ ).default(true)
40
+ )
41
+ .addOption(
42
+ new Option('-c, --config [path]', 'path to configuration file, or - to read from stdin')
43
+ )
44
+ .addOption(
45
+ new Option('--cwd [path]', 'run all tasks in specific directory, instead of the current')
46
+ )
47
+ .addOption(new Option('-d, --debug', 'print additional debug information').default(false))
48
+ .addOption(
49
+ new Option(
50
+ '--diff [string]',
51
+ 'override the default "--staged" flag of "git diff" to get list of files. Implies "--no-stash".'
52
+ ).implies({ stash: false })
53
+ )
54
+ .addOption(
55
+ new Option(
56
+ '--diff-filter [string]',
57
+ 'override the default "--diff-filter=ACMR" flag of "git diff" to get list of files'
58
+ )
59
+ )
60
+ .addOption(
61
+ new Option('--continue-on-error', 'run all tasks to completion even if one fails').default(
62
+ false
63
+ )
64
+ )
65
+ .addOption(
66
+ new Option('--fail-on-changes', 'fail with exit code 1 when tasks modify tracked files')
67
+ .default(false)
68
+ .implies({ revert: false })
69
+ )
70
+ .addOption(
71
+ new Option(
72
+ '--max-arg-length [number]',
73
+ 'maximum length of the command-line argument string'
74
+ ).default(0)
75
+ )
76
+
77
+ /**
78
+ * We don't want to show the `--revert` flag because it's on by default, and only show the
79
+ * negatable flag `--no-rever` instead. There seems to be a bug in Commander.js where
80
+ * configuring only the latter won't actually set the default value.
81
+ */
82
+ .addOption(
83
+ new Option('--revert', 'revert to original state in case of errors').default(true).hideHelp()
84
+ )
85
+ .addOption(
86
+ new Option('--no-revert', 'do not revert to original state in case of errors.').default(false)
87
+ )
88
+
89
+ .addOption(new Option('--stash', 'enable the backup stash').default(true).hideHelp())
90
+ .addOption(
91
+ new Option('--no-stash', 'disable the backup stash. Implies "--no-revert".')
92
+ .default(false)
93
+ .implies({ revert: false })
94
+ )
95
+
96
+ .addOption(
97
+ new Option('--hide-partially-staged', 'hide unstaged changes from partially staged files')
98
+ .default(true)
99
+ .hideHelp()
100
+ )
101
+ .addOption(
102
+ new Option(
103
+ '--no-hide-partially-staged',
104
+ 'disable hiding unstaged changes from partially staged files'
105
+ ).default(false)
106
+ )
107
+
108
+ .addOption(
109
+ new Option('--hide-unstaged', 'hide all unstaged changes, instead of just partially staged')
110
+ .default(false)
111
+ .implies({ hidePartiallyStaged: false })
112
+ )
113
+
114
+ .addOption(new Option('-q, --quiet', 'disable lint-staged’s own console output').default(false))
115
+ .addOption(new Option('-r, --relative', 'pass relative filepaths to tasks').default(false))
116
+ .addOption(
117
+ new Option(
118
+ '-v, --verbose',
119
+ 'show task output even when tasks succeed; by default only failed output is shown'
120
+ ).default(false)
121
+ )
122
+
123
+ .addHelpText('afterAll', '\n' + restoreStashExample())
124
+
125
+ const cliOptions = program.parse(process.argv).opts()
126
+
127
+ if (cliOptions.debug) {
128
+ enableDebug()
129
+ }
130
+
131
+ const options = {
132
+ allowEmpty: !!cliOptions.allowEmpty,
133
+ concurrent: JSON.parse(cliOptions.concurrent),
134
+ configPath: cliOptions.config,
135
+ continueOnError: !!cliOptions.continueOnError,
136
+ cwd: cliOptions.cwd,
137
+ debug: !!cliOptions.debug,
138
+ diff: cliOptions.diff,
139
+ diffFilter: cliOptions.diffFilter,
140
+ failOnChanges: !!cliOptions.failOnChanges,
141
+ hidePartiallyStaged: !!cliOptions.hidePartiallyStaged, // commander inverts `no-<x>` flags to `!x`
142
+ hideUnstaged: !!cliOptions.hideUnstaged,
143
+ maxArgLength: cliOptions.maxArgLength || undefined,
144
+ quiet: !!cliOptions.quiet,
145
+ relative: !!cliOptions.relative,
146
+ revert: !!cliOptions.revert, // commander inverts `no-<x>` flags to `!x`
147
+ stash: !!cliOptions.stash, // commander inverts `no-<x>` flags to `!x`
148
+ verbose: !!cliOptions.verbose,
149
+ }
150
+
151
+ try {
152
+ const { shell } = userInfo()
153
+ debugLog('Using shell: %s', shell)
154
+ } catch {
155
+ debugLog('Could not determine current shell')
156
+ }
157
+
158
+ debugLog('Options parsed from command-line: %o', options)
159
+
160
+ if (options.configPath === '-') {
161
+ delete options.configPath
162
+ try {
163
+ debugLog('Reading config from stdin')
164
+ options.config = JSON.parse(await readStdin())
165
+ } catch (error) {
166
+ debugLog(CONFIG_STDIN_ERROR, error)
167
+ console.error(CONFIG_STDIN_ERROR)
168
+ process.exit(1)
169
+ }
170
+ }
171
+
172
+ const passed = await lintStaged(options)
173
+ if (!passed) {
174
+ process.exitCode = 1
175
+ }
@@ -0,0 +1,65 @@
1
+ import path from 'node:path'
2
+
3
+ import { createDebug } from './debug.js'
4
+ import { normalizePath } from './normalizePath.js'
5
+
6
+ const debugLog = createDebug('lint-staged:chunkFiles')
7
+
8
+ /**
9
+ * Chunk array into sub-arrays
10
+ * @param {Array} arr
11
+ * @param {Number} chunkCount
12
+ * @returns {Array<Array>}
13
+ */
14
+ const chunkArray = (arr, chunkCount) => {
15
+ if (chunkCount === 1) return [arr]
16
+ const chunked = []
17
+ let position = 0
18
+ for (let i = 0; i < chunkCount; i++) {
19
+ const chunkLength = Math.ceil((arr.length - position) / (chunkCount - i))
20
+ chunked.push([])
21
+ chunked[i] = arr.slice(position, chunkLength + position)
22
+ position += chunkLength
23
+ }
24
+ return chunked
25
+ }
26
+
27
+ /**
28
+ * Chunk files into sub-arrays based on the length of the resulting argument string
29
+ *
30
+ * @typedef {import('./getStagedFiles.js').StagedFile[]} StagedFile
31
+ *
32
+ * @param {Object} opts
33
+ * @param {Array<StagedFile>} opts.files
34
+ * @param {String} [opts.baseDir] The optional base directory to resolve relative paths.
35
+ * @param {number} [opts.maxArgLength] the maximum argument string length
36
+ * @param {Boolean} [opts.relative] whether files are relative to `topLevelDir` or should be resolved as absolute
37
+ * @returns {Array<Array<StagedFile>>}
38
+ */
39
+ export const chunkFiles = ({ files, baseDir, maxArgLength = null, relative = false }) => {
40
+ const normalizedFiles = files.map((file) => {
41
+ return {
42
+ filepath: normalizePath(
43
+ relative || !baseDir ? file.filepath : path.resolve(baseDir, file.filepath)
44
+ ),
45
+ status: file.status,
46
+ }
47
+ })
48
+
49
+ if (!maxArgLength) {
50
+ debugLog('Skip chunking files because of undefined maxArgLength')
51
+ return [normalizedFiles] // wrap in an array to return a single chunk
52
+ }
53
+
54
+ /** Calculate total character length of all filepaths, with added spaces in between */
55
+ const fileListLength =
56
+ normalizedFiles.reduce((sum, file) => sum + file.filepath.length, 0) +
57
+ Math.max(normalizedFiles.length - 1, 0)
58
+
59
+ debugLog(
60
+ `Resolved an argument string length of ${fileListLength} characters from ${normalizedFiles.length} files`
61
+ )
62
+ const chunkCount = Math.min(Math.ceil(fileListLength / maxArgLength), normalizedFiles.length)
63
+ debugLog(`Creating ${chunkCount} chunks for maxArgLength of ${maxArgLength}`)
64
+ return chunkArray(normalizedFiles, chunkCount)
65
+ }
package/lib/colors.js ADDED
@@ -0,0 +1,106 @@
1
+ import nodeTty from 'node:tty'
2
+
3
+ /**
4
+ * @example NO_COLOR
5
+ * @example NO_COLOR=1
6
+ * @example NO_COLOR=true
7
+ */
8
+ const TRUTHRY_ENV_VAR_VALUES = ['', '1', 'true']
9
+
10
+ /**
11
+ * @example FORCE_COLOR=0
12
+ * @example FORCE_COLOR=false
13
+ */
14
+ const FALSY_ENV_VAR_VALUES = ['0', 'false']
15
+
16
+ /**
17
+ * @returns `true` if ANSI colors are supported
18
+ *
19
+ * @param {NodeJS.Process} [p]
20
+ * @param {boolean} [isTty]
21
+ */
22
+ export const supportsAnsiColors = (p = process, isTty = nodeTty.isatty(1)) => {
23
+ const noColor = p?.env?.NO_COLOR?.toLowerCase()
24
+ if (TRUTHRY_ENV_VAR_VALUES.includes(noColor)) {
25
+ return false
26
+ }
27
+
28
+ const forceColor = p?.env?.FORCE_COLOR?.toLowerCase()
29
+ if (TRUTHRY_ENV_VAR_VALUES.includes(forceColor)) {
30
+ return true
31
+ } else if (FALSY_ENV_VAR_VALUES.includes(forceColor)) {
32
+ return false
33
+ }
34
+
35
+ const forceTty = p?.env?.FORCE_TTY
36
+ if (TRUTHRY_ENV_VAR_VALUES.includes(forceTty)) {
37
+ return true
38
+ } else if (FALSY_ENV_VAR_VALUES.includes(forceTty)) {
39
+ return false
40
+ }
41
+
42
+ if (isTty) {
43
+ return true
44
+ }
45
+
46
+ /**
47
+ * Assume CI supports color
48
+ * @see {@link https://github.com/alexeyraspopov/picocolors/blob/0e7c4af2de299dd7bc5916f2bddd151fa2f66740/picocolors.js#L4}
49
+ * @see {@link https://github.com/tinylibs/tinyrainbow/blob/071034bf2eafa28d91ef0ba48a3837420d81a40a/src/index.ts#L91}
50
+ */
51
+ if (TRUTHRY_ENV_VAR_VALUES.includes(p?.env?.CI)) {
52
+ return true
53
+ }
54
+
55
+ if (p?.env?.TERM && p.env.TERM === 'dumb') {
56
+ return false
57
+ }
58
+
59
+ /**
60
+ * Assume Windows supports color
61
+ * @see {@link https://github.com/alexeyraspopov/picocolors/blob/0e7c4af2de299dd7bc5916f2bddd151fa2f66740/picocolors.js#L4}
62
+ * @see {@link https://github.com/tinylibs/tinyrainbow/blob/071034bf2eafa28d91ef0ba48a3837420d81a40a/src/index.ts#L89}
63
+ */
64
+ if (p?.platform === 'win32') {
65
+ return true
66
+ }
67
+
68
+ return false
69
+ }
70
+
71
+ /**
72
+ * @deprecated replace this with Node.js builtin after minimum supported version is >=20.18.0
73
+ * @example util.styleText('red', 'test') !== 'text'
74
+ */
75
+ export const SUPPORTS_COLOR = supportsAnsiColors()
76
+
77
+ const ANSI_RESET = '\u001B[0m'
78
+
79
+ /**
80
+ * @callback WrapAnsi
81
+ * @param {string} text
82
+ * @returns {string}
83
+ */
84
+ /**
85
+ * @deprecated replace this with Node.js builtin after minimum supported version is >=20.18.0
86
+ * @example (format) => (text) => util.styleText(format, text)
87
+ *
88
+ * @param {string} code
89
+ * @param {boolean} [supported]
90
+ * @returns {WrapAnsi}
91
+ *
92
+ */
93
+ export const wrapAnsiColor = (code, supported = SUPPORTS_COLOR) => {
94
+ if (supported) {
95
+ return (text) => code + text + ANSI_RESET
96
+ }
97
+
98
+ return (text) => text
99
+ }
100
+
101
+ export const red = wrapAnsiColor('\u001B[0;31m')
102
+ export const green = wrapAnsiColor('\u001B[0;32m')
103
+ export const yellow = wrapAnsiColor('\u001B[0;33m')
104
+ export const blue = wrapAnsiColor('\u001B[0;34m')
105
+ export const blackBright = wrapAnsiColor('\u001B[0;90m')
106
+ export const bold = wrapAnsiColor('\u001b[1m')
@@ -0,0 +1,30 @@
1
+ export const CONFIG_NAME = 'lint-staged'
2
+
3
+ export const PACKAGE_JSON_FILE = 'package.json'
4
+
5
+ export const PACKAGE_YAML_FILES = ['package.yaml', 'package.yml']
6
+
7
+ /**
8
+ * The list of files `lint-staged` will read configuration
9
+ * from, in the declared order.
10
+ */
11
+ export const CONFIG_FILE_NAMES = [
12
+ PACKAGE_JSON_FILE,
13
+ ...PACKAGE_YAML_FILES,
14
+ '.lintstagedrc',
15
+ '.lintstagedrc.json',
16
+ '.lintstagedrc.yaml',
17
+ '.lintstagedrc.yml',
18
+ '.lintstagedrc.mjs',
19
+ '.lintstagedrc.mts',
20
+ '.lintstagedrc.js',
21
+ '.lintstagedrc.ts',
22
+ '.lintstagedrc.cjs',
23
+ '.lintstagedrc.cts',
24
+ 'lint-staged.config.mjs',
25
+ 'lint-staged.config.mts',
26
+ 'lint-staged.config.js',
27
+ 'lint-staged.config.ts',
28
+ 'lint-staged.config.cjs',
29
+ 'lint-staged.config.cts',
30
+ ]
package/lib/debug.js ADDED
@@ -0,0 +1,27 @@
1
+ import { formatWithOptions } from 'node:util'
2
+
3
+ import { blackBright, SUPPORTS_COLOR } from './colors.js'
4
+
5
+ const format = (...args) => formatWithOptions({ colors: SUPPORTS_COLOR }, ...args)
6
+
7
+ let activeLogger
8
+
9
+ export const enableDebug = (logger = console) => {
10
+ if (!activeLogger) {
11
+ activeLogger = logger
12
+ }
13
+ }
14
+
15
+ /** @param {string} name */
16
+ export const createDebug = (name) => {
17
+ let previous = process.hrtime.bigint()
18
+
19
+ return (...args) => {
20
+ if (!activeLogger) return
21
+
22
+ const now = process.hrtime.bigint()
23
+ const ms = (now - previous) / 1_000_000n
24
+ previous = now
25
+ activeLogger.debug(blackBright(name + ': ') + format(...args) + blackBright(` +${ms}ms`))
26
+ }
27
+ }
package/lib/execGit.js ADDED
@@ -0,0 +1,37 @@
1
+ import { exec } from 'tinyexec'
2
+
3
+ import { createDebug } from './debug.js'
4
+
5
+ const debugLog = createDebug('lint-staged:execGit')
6
+
7
+ /**
8
+ * Explicitly never recurse commands into submodules, overriding local/global configuration.
9
+ * @see https://git-scm.com/docs/git-config#Documentation/git-config.txt-submodulerecurse
10
+ */
11
+ const NO_SUBMODULE_RECURSE = ['-c', 'submodule.recurse=false']
12
+
13
+ // exported for tests
14
+ export const GIT_GLOBAL_OPTIONS = [...NO_SUBMODULE_RECURSE]
15
+
16
+ /** @type {(cmd: string[], options?: { cwd?: string }) => Promise<string>} */
17
+ export const execGit = async (cmd, options) => {
18
+ debugLog('Running git command:', cmd)
19
+ const result = exec('git', [...NO_SUBMODULE_RECURSE, ...cmd], {
20
+ nodeOptions: {
21
+ cwd: options?.cwd,
22
+ stdio: ['ignore'],
23
+ },
24
+ })
25
+
26
+ let output = ''
27
+ for await (const line of result) {
28
+ output += line + '\n'
29
+ }
30
+ output = output.trimEnd()
31
+
32
+ if (result.exitCode > 0) {
33
+ throw new Error(output, { cause: result })
34
+ }
35
+
36
+ return output
37
+ }
package/lib/figures.js ADDED
@@ -0,0 +1,9 @@
1
+ import { figures } from 'listr2'
2
+
3
+ import { blue, red, yellow } from './colors.js'
4
+
5
+ export const info = blue(figures.arrowRight)
6
+
7
+ export const error = red(figures.cross)
8
+
9
+ export const warning = yellow(figures.warning)
package/lib/file.js ADDED
@@ -0,0 +1,53 @@
1
+ import fs from 'node:fs/promises'
2
+
3
+ import { createDebug } from './debug.js'
4
+
5
+ const debugLog = createDebug('lint-staged:file')
6
+
7
+ /**
8
+ * Read contents of a file to buffer
9
+ * @param {String} filename
10
+ * @param {Boolean} [ignoreENOENT=true] — Whether to throw if the file doesn't exist
11
+ * @returns {Promise<Buffer>}
12
+ */
13
+ export const readFile = async (filename, ignoreENOENT = true) => {
14
+ debugLog('Reading file `%s`', filename)
15
+ try {
16
+ return await fs.readFile(filename)
17
+ } catch (error) {
18
+ if (ignoreENOENT && error.code === 'ENOENT') {
19
+ debugLog("File `%s` doesn't exist, ignoring...", filename)
20
+ return null // no-op file doesn't exist
21
+ } else {
22
+ throw error
23
+ }
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Remove a file
29
+ * @param {String} filename
30
+ * @param {Boolean} [ignoreENOENT=true] — Whether to throw if the file doesn't exist
31
+ */
32
+ export const unlink = async (filename, ignoreENOENT = true) => {
33
+ debugLog('Removing file `%s`', filename)
34
+ try {
35
+ await fs.unlink(filename)
36
+ } catch (error) {
37
+ if (ignoreENOENT && error.code === 'ENOENT') {
38
+ debugLog("File `%s` doesn't exist, ignoring...", filename)
39
+ } else {
40
+ throw error
41
+ }
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Write buffer to file
47
+ * @param {String} filename
48
+ * @param {Buffer} buffer
49
+ */
50
+ export const writeFile = async (filename, buffer) => {
51
+ debugLog('Writing file `%s`', filename)
52
+ await fs.writeFile(filename, buffer)
53
+ }
@@ -0,0 +1,69 @@
1
+ import path from 'node:path'
2
+
3
+ import micromatch from 'micromatch'
4
+
5
+ import { createDebug } from './debug.js'
6
+ import { normalizePath } from './normalizePath.js'
7
+
8
+ const debugLog = createDebug('lint-staged:generateTasks')
9
+
10
+ /**
11
+ * Generates all task commands, and filelist
12
+ *
13
+ * @param {object} options
14
+ * @param {Object} [options.config] - Task configuration
15
+ * @param {Object} [options.cwd] - Current working directory
16
+ * @param {import('./getStagedFiles.js').StagedFile[]} [options.files] - Staged filepaths
17
+ * @param {boolean} [options.relative] - Whether filepaths to should be relative to cwd
18
+ */
19
+ export const generateTasks = ({ config, cwd = process.cwd(), files, relative = false }) => {
20
+ debugLog('Generating linter tasks')
21
+
22
+ /** @type {StagedFile[]} */
23
+ const relativeFiles = files.map((file) => ({
24
+ filepath: normalizePath(path.relative(cwd, file.filepath)),
25
+ status: file.status,
26
+ }))
27
+
28
+ return Object.entries(config).map(([pattern, commands]) => {
29
+ const isParentDirPattern = pattern.startsWith('../')
30
+
31
+ // Only worry about children of the CWD unless the pattern explicitly
32
+ // specifies that it concerns a parent directory.
33
+ const filteredFiles = relativeFiles.filter((file) => {
34
+ if (isParentDirPattern) return true
35
+ return !file.filepath.startsWith('..') && !path.isAbsolute(file.filepath)
36
+ })
37
+
38
+ const matches = micromatch(
39
+ filteredFiles.map((file) => file.filepath),
40
+ pattern,
41
+ {
42
+ cwd,
43
+ dot: true,
44
+ // If the pattern doesn't look like a path, enable `matchBase` to
45
+ // match against filenames in every directory. This makes `*.js`
46
+ // match both `test.js` and `subdirectory/test.js`.
47
+ matchBase: !pattern.includes('/'),
48
+ posixSlashes: true,
49
+ strictBrackets: true,
50
+ }
51
+ )
52
+
53
+ const fileList = filteredFiles.flatMap((file) =>
54
+ matches.includes(file.filepath)
55
+ ? [
56
+ {
57
+ filepath: normalizePath(relative ? file.filepath : path.resolve(cwd, file.filepath)),
58
+ status: file.status,
59
+ },
60
+ ]
61
+ : []
62
+ )
63
+
64
+ const task = { pattern, commands, fileList }
65
+ debugLog('Generated task: \n%O', task)
66
+
67
+ return task
68
+ })
69
+ }
@@ -0,0 +1,18 @@
1
+ export const Signal = {
2
+ SIGINT: 'SIGINT',
3
+ SIGKILL: 'SIGKILL',
4
+ }
5
+
6
+ /**
7
+ * Get an AbortController used to cancel running tasks on failure/interruption.
8
+ * @returns AbortController
9
+ */
10
+ export const getAbortController = (nodeProcess = process) => {
11
+ const abortController = new AbortController()
12
+
13
+ nodeProcess.on(Signal.SIGINT, () => {
14
+ abortController.abort(Signal.SIGINT)
15
+ })
16
+
17
+ return abortController
18
+ }
@@ -0,0 +1,19 @@
1
+ /** @type {(diff?: string, diffFilter?: string) => string[]} */
2
+ export const getDiffCommand = (diff, diffFilter) => {
3
+ /**
4
+ * Docs for --diff-filter option:
5
+ * @see https://git-scm.com/docs/git-diff#Documentation/git-diff.txt---diff-filterACDMRTUXB82308203
6
+ */
7
+ const diffFilterArg = diffFilter !== undefined ? diffFilter.trim() : 'ACMR'
8
+
9
+ /** Use `--diff branch1...branch2` or `--diff="branch1 branch2", or fall back to default staged files */
10
+ const diffArgs = diff !== undefined ? diff.trim().split(' ') : ['--staged']
11
+
12
+ /**
13
+ * Docs for -z option:
14
+ * @see https://git-scm.com/docs/git-diff#Documentation/git-diff.txt--z
15
+ */
16
+ const diffCommand = ['diff', `--diff-filter=${diffFilterArg}`, ...diffArgs]
17
+
18
+ return diffCommand
19
+ }
@@ -0,0 +1,37 @@
1
+ import { createDebug } from './debug.js'
2
+ import { createTaskError } from './getSpawnedTask.js'
3
+
4
+ const debugLog = createDebug('lint-staged:getFunctionTasks')
5
+
6
+ /**
7
+ * @typedef {{ title: string; task: Function }} FunctionTask
8
+ * @type {(commands: FunctionTask|Array<string|Function>|string|Function) => boolean}
9
+ * @returns `true` if command is a function task
10
+ */
11
+ export const isFunctionTask = (commands) => typeof commands === 'object' && !Array.isArray(commands)
12
+
13
+ /**
14
+ * Handles function configuration and pushes the tasks into the task array
15
+ *
16
+ * @param {object} command
17
+ * @param {import('./getStagedFiles.js').StagedFile[]} files
18
+ * @throws {Error} If the function configuration is not valid
19
+ */
20
+ export const getFunctionTask = async (command, files) => {
21
+ debugLog('Creating Listr tasks for function %o', command)
22
+
23
+ const task = async (ctx) => {
24
+ try {
25
+ await command.task(files.map((file) => file.filepath))
26
+ } catch (e) {
27
+ throw createTaskError(command.title, e, ctx)
28
+ }
29
+ }
30
+
31
+ return [
32
+ {
33
+ title: command.title,
34
+ task,
35
+ },
36
+ ]
37
+ }