@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.
- package/LICENSE +21 -0
- package/MIGRATION.md +72 -0
- package/README.md +1120 -0
- package/bin/lint-staged.js +175 -0
- package/lib/chunkFiles.js +65 -0
- package/lib/colors.js +106 -0
- package/lib/configFiles.js +30 -0
- package/lib/debug.js +27 -0
- package/lib/execGit.js +37 -0
- package/lib/figures.js +9 -0
- package/lib/file.js +53 -0
- package/lib/generateTasks.js +69 -0
- package/lib/getAbortController.js +18 -0
- package/lib/getDiffCommand.js +19 -0
- package/lib/getFunctionTask.js +37 -0
- package/lib/getRenderer.js +63 -0
- package/lib/getSpawnedTask.js +160 -0
- package/lib/getSpawnedTasks.js +76 -0
- package/lib/getStagedFiles.js +86 -0
- package/lib/gitWorkflow.js +434 -0
- package/lib/groupFilesByConfig.js +62 -0
- package/lib/index.d.ts +125 -0
- package/lib/index.js +184 -0
- package/lib/killSubprocesses.js +42 -0
- package/lib/loadConfig.js +102 -0
- package/lib/messages.js +91 -0
- package/lib/normalizePath.js +50 -0
- package/lib/parseGitZOutput.js +11 -0
- package/lib/printTaskOutput.js +12 -0
- package/lib/readStdin.js +19 -0
- package/lib/resolveConfig.js +15 -0
- package/lib/resolveGitRepo.js +65 -0
- package/lib/runAll.js +372 -0
- package/lib/searchConfigs.js +186 -0
- package/lib/state.js +102 -0
- package/lib/symbols.js +29 -0
- package/lib/validateBraces.js +95 -0
- package/lib/validateConfig.js +108 -0
- package/lib/validateOptions.js +33 -0
- package/lib/version.js +6 -0
- package/package.json +93 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { SUPPORTS_COLOR } from './colors.js'
|
|
2
|
+
import { createDebug, enableDebug } from './debug.js'
|
|
3
|
+
import { execGit } from './execGit.js'
|
|
4
|
+
import {
|
|
5
|
+
GIT_ERROR,
|
|
6
|
+
NO_CONFIGURATION,
|
|
7
|
+
PREVENTED_EMPTY_COMMIT,
|
|
8
|
+
PREVENTED_TASK_MODIFICATIONS,
|
|
9
|
+
restoreStashExample,
|
|
10
|
+
UNSTAGED_CHANGES_BACKUP_STASH_LOCATION,
|
|
11
|
+
} from './messages.js'
|
|
12
|
+
import { printTaskOutput } from './printTaskOutput.js'
|
|
13
|
+
import { runAll } from './runAll.js'
|
|
14
|
+
import { cleanupSkipped } from './state.js'
|
|
15
|
+
import {
|
|
16
|
+
ApplyEmptyCommitError,
|
|
17
|
+
ConfigNotFoundError,
|
|
18
|
+
FailOnChangesError,
|
|
19
|
+
GetBackupStashError,
|
|
20
|
+
GitError,
|
|
21
|
+
RestoreUnstagedChangesError,
|
|
22
|
+
} from './symbols.js'
|
|
23
|
+
import { validateOptions } from './validateOptions.js'
|
|
24
|
+
import { getVersion } from './version.js'
|
|
25
|
+
|
|
26
|
+
const debugLog = createDebug('lint-staged')
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get the maximum length of a command-line argument string based on current platform
|
|
30
|
+
*
|
|
31
|
+
* https://serverfault.com/questions/69430/what-is-the-maximum-length-of-a-command-line-in-mac-os-x
|
|
32
|
+
* https://support.microsoft.com/en-us/help/830473/command-prompt-cmd-exe-command-line-string-limitation
|
|
33
|
+
* https://unix.stackexchange.com/a/120652
|
|
34
|
+
*/
|
|
35
|
+
const getMaxArgLength = () => {
|
|
36
|
+
switch (process.platform) {
|
|
37
|
+
case 'darwin':
|
|
38
|
+
return 262144
|
|
39
|
+
case 'win32':
|
|
40
|
+
return 8191
|
|
41
|
+
default:
|
|
42
|
+
return 131072
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @typedef {(...any) => void} LogFunction
|
|
48
|
+
* @typedef {{ error: LogFunction, log: LogFunction, warn: LogFunction }} Logger
|
|
49
|
+
*
|
|
50
|
+
* Root lint-staged function that is called from `bin/lint-staged`.
|
|
51
|
+
*
|
|
52
|
+
* @param {object} options
|
|
53
|
+
* @param {Object} [options.allowEmpty] - Allow empty commits when tasks revert all staged changes
|
|
54
|
+
* @param {boolean} [options.color] - Enable or disable ANSI color codes in output.
|
|
55
|
+
* @param {boolean | number} [options.concurrent] - The number of tasks to run concurrently, or false to run tasks serially
|
|
56
|
+
* @param {object} [options.config] - Object with configuration for programmatic API
|
|
57
|
+
* @param {string} [options.configPath] - Path to configuration file
|
|
58
|
+
* @param {boolean} [options.continueOnError] - Run all tasks to completion even if one fails
|
|
59
|
+
* @param {Object} [options.cwd] - Current working directory
|
|
60
|
+
* @param {boolean} [options.debug] - Enable debug mode
|
|
61
|
+
* @param {string} [options.diff] - Override the default "--staged" flag of "git diff" to get list of files
|
|
62
|
+
* @param {string} [options.diffFilter] - Override the default "--diff-filter=ACMR" flag of "git diff" to get list of files
|
|
63
|
+
* @param {boolean} [options.failOnChanges] - Fail with exit code 1 when tasks modify tracked files
|
|
64
|
+
* @param {boolean} [options.hidePartiallyStaged] - Whether to hide unstaged changes from partially staged files before running tasks
|
|
65
|
+
* @param {boolean} [options.hideUnstaged] - Whether to hide all unstaged changes before running tasks
|
|
66
|
+
* @param {number} [options.maxArgLength] - Maximum argument string length
|
|
67
|
+
* @param {boolean} [options.quiet] - Disable lint-staged’s own console output
|
|
68
|
+
* @param {boolean} [options.relative] - Pass relative filepaths to tasks
|
|
69
|
+
* @param {boolean} [options.revert] - revert to original state in case of errors
|
|
70
|
+
* @param {boolean} [options.stash] - Enable the backup stash, and revert in case of errors
|
|
71
|
+
* @param {boolean} [options.verbose] - Show task output even when tasks succeed; by default only failed output is shown
|
|
72
|
+
* @param {Logger} [logger]
|
|
73
|
+
*
|
|
74
|
+
* @returns {Promise<boolean>} Promise of whether the task passed or failed
|
|
75
|
+
*/
|
|
76
|
+
const lintStaged = async (
|
|
77
|
+
{
|
|
78
|
+
allowEmpty = false,
|
|
79
|
+
color = SUPPORTS_COLOR,
|
|
80
|
+
concurrent = true,
|
|
81
|
+
config: configObject,
|
|
82
|
+
configPath,
|
|
83
|
+
continueOnError = false,
|
|
84
|
+
cwd,
|
|
85
|
+
debug = false,
|
|
86
|
+
diff,
|
|
87
|
+
diffFilter,
|
|
88
|
+
failOnChanges = false,
|
|
89
|
+
hideUnstaged = false,
|
|
90
|
+
hidePartiallyStaged = !hideUnstaged,
|
|
91
|
+
maxArgLength = getMaxArgLength() / 2,
|
|
92
|
+
quiet = false,
|
|
93
|
+
relative = false,
|
|
94
|
+
// Stashing should be disabled by default when the `diff` option is used
|
|
95
|
+
stash = diff === undefined,
|
|
96
|
+
// Default to false when using failOnChanges; cannot revert to original state without stash
|
|
97
|
+
revert = !failOnChanges && !!stash,
|
|
98
|
+
verbose = false,
|
|
99
|
+
} = {},
|
|
100
|
+
logger = console
|
|
101
|
+
) => {
|
|
102
|
+
// Seemingly enable debug twice (also done in bin), so that it also works when using the Node.js API
|
|
103
|
+
if (debug) {
|
|
104
|
+
enableDebug(logger)
|
|
105
|
+
|
|
106
|
+
debugLog(
|
|
107
|
+
'Running `lint-staged@%s` on Node.js %s (%s)',
|
|
108
|
+
await getVersion(),
|
|
109
|
+
process.version,
|
|
110
|
+
process.platform
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const gitVersion = await execGit(['version', '--build-options'], { cwd })
|
|
115
|
+
debugLog('%s', gitVersion)
|
|
116
|
+
|
|
117
|
+
const options = {
|
|
118
|
+
allowEmpty,
|
|
119
|
+
color,
|
|
120
|
+
concurrent,
|
|
121
|
+
configObject,
|
|
122
|
+
configPath,
|
|
123
|
+
continueOnError,
|
|
124
|
+
cwd,
|
|
125
|
+
debug,
|
|
126
|
+
diff,
|
|
127
|
+
diffFilter,
|
|
128
|
+
failOnChanges,
|
|
129
|
+
hidePartiallyStaged,
|
|
130
|
+
hideUnstaged,
|
|
131
|
+
maxArgLength,
|
|
132
|
+
quiet,
|
|
133
|
+
relative,
|
|
134
|
+
revert,
|
|
135
|
+
stash,
|
|
136
|
+
verbose,
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
await validateOptions(options, logger)
|
|
140
|
+
|
|
141
|
+
// Unset GIT_LITERAL_PATHSPECS to not mess with path interpretation
|
|
142
|
+
debugLog('Unset GIT_LITERAL_PATHSPECS (was `%s`)', process.env.GIT_LITERAL_PATHSPECS)
|
|
143
|
+
delete process.env.GIT_LITERAL_PATHSPECS
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
const ctx = await runAll(options, logger)
|
|
147
|
+
debugLog('Tasks were executed successfully!')
|
|
148
|
+
printTaskOutput(ctx, logger)
|
|
149
|
+
return true
|
|
150
|
+
} catch (runAllError) {
|
|
151
|
+
if (runAllError?.ctx?.errors) {
|
|
152
|
+
const { ctx } = runAllError
|
|
153
|
+
|
|
154
|
+
if (ctx.errors.has(ConfigNotFoundError)) {
|
|
155
|
+
logger.error(NO_CONFIGURATION)
|
|
156
|
+
} else if (ctx.errors.has(ApplyEmptyCommitError)) {
|
|
157
|
+
logger.warn(PREVENTED_EMPTY_COMMIT)
|
|
158
|
+
} else if (ctx.errors.has(FailOnChangesError)) {
|
|
159
|
+
logger.warn(PREVENTED_TASK_MODIFICATIONS + '\n')
|
|
160
|
+
logger.warn(restoreStashExample(ctx.backupHash))
|
|
161
|
+
} else if (ctx.errors.has(RestoreUnstagedChangesError)) {
|
|
162
|
+
logger.warn(UNSTAGED_CHANGES_BACKUP_STASH_LOCATION)
|
|
163
|
+
logger.warn(ctx.unstagedPatch)
|
|
164
|
+
} else if (
|
|
165
|
+
(ctx.errors.has(GitError) || cleanupSkipped(ctx)) &&
|
|
166
|
+
!ctx.errors.has(GetBackupStashError)
|
|
167
|
+
) {
|
|
168
|
+
logger.error(GIT_ERROR)
|
|
169
|
+
if (ctx.shouldBackup) {
|
|
170
|
+
// No sense to show this if the backup stash itself is missing.
|
|
171
|
+
logger.error(restoreStashExample(ctx.backupHash) + '\n')
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
printTaskOutput(ctx, logger)
|
|
176
|
+
return false
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Probably a compilation error in the config js file. Pass it up to the outer error handler for logging.
|
|
180
|
+
throw runAllError
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export default lintStaged
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { exec } from 'node:child_process'
|
|
2
|
+
import { promisify } from 'node:util'
|
|
3
|
+
|
|
4
|
+
const execAsync = promisify(exec)
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* End process by pid, forcefully, including child processes
|
|
8
|
+
*
|
|
9
|
+
* @see {@link https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/taskkill}
|
|
10
|
+
*
|
|
11
|
+
* @param {number} pid
|
|
12
|
+
*/
|
|
13
|
+
const killWin32Subprocesses = async (pid) => {
|
|
14
|
+
await execAsync(`taskkill /pid ${pid} /T /F`)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Kill all processes in the group by using negative pid
|
|
19
|
+
*
|
|
20
|
+
* @see {@link https://pubs.opengroup.org/onlinepubs/9699919799/functions/kill.html}
|
|
21
|
+
*
|
|
22
|
+
* @param {number} pid
|
|
23
|
+
*/
|
|
24
|
+
const killUnixSubprocesses = async (pid) => {
|
|
25
|
+
process.kill(-pid, 'SIGKILL')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {number} pid
|
|
30
|
+
* @param {boolean} [isWin32]
|
|
31
|
+
*/
|
|
32
|
+
export const killSubProcesses = async (pid, isWin32 = process.platform === 'win32') => {
|
|
33
|
+
try {
|
|
34
|
+
if (isWin32) {
|
|
35
|
+
await killWin32Subprocesses(pid)
|
|
36
|
+
} else {
|
|
37
|
+
await killUnixSubprocesses(pid)
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
/** ignore errors */
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/** @typedef {import('./index').Logger} Logger */
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs/promises'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { pathToFileURL } from 'node:url'
|
|
6
|
+
|
|
7
|
+
import { CONFIG_NAME, PACKAGE_JSON_FILE, PACKAGE_YAML_FILES } from './configFiles.js'
|
|
8
|
+
import { createDebug } from './debug.js'
|
|
9
|
+
import { failedToLoadConfig } from './messages.js'
|
|
10
|
+
import { resolveConfig } from './resolveConfig.js'
|
|
11
|
+
|
|
12
|
+
const debugLog = createDebug('lint-staged:loadConfig')
|
|
13
|
+
|
|
14
|
+
const readFile = async (filename) => fs.readFile(path.resolve(filename), 'utf-8')
|
|
15
|
+
|
|
16
|
+
const jsonParse = async (filename) => {
|
|
17
|
+
const isPackageFile = PACKAGE_JSON_FILE.includes(path.basename(filename))
|
|
18
|
+
try {
|
|
19
|
+
const content = await readFile(filename)
|
|
20
|
+
const json = JSON.parse(content)
|
|
21
|
+
return isPackageFile ? json[CONFIG_NAME] : json
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (path.basename(filename) === PACKAGE_JSON_FILE) {
|
|
24
|
+
debugLog('Ignoring invalid JSON file %s', filename)
|
|
25
|
+
return undefined
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
throw error
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const yamlParse = async (filename) => {
|
|
33
|
+
const isPackageFile = PACKAGE_YAML_FILES.includes(path.basename(filename))
|
|
34
|
+
try {
|
|
35
|
+
const [YAML, content] = await Promise.all([import('yaml'), readFile(filename)])
|
|
36
|
+
const yaml = YAML.parse(content)
|
|
37
|
+
return isPackageFile ? yaml[CONFIG_NAME] : yaml
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (isPackageFile) {
|
|
40
|
+
debugLog('Ignoring invalid YAML file %s', filename)
|
|
41
|
+
return undefined
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
throw error
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const dynamicImport = (path) => import(pathToFileURL(path)).then((module) => module.default)
|
|
49
|
+
|
|
50
|
+
const NO_EXT = 'noExt'
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* `lilconfig` doesn't support yaml files by default,
|
|
54
|
+
* so we add custom loaders for those. Files without
|
|
55
|
+
* an extensions are assumed to be yaml — this
|
|
56
|
+
* assumption is in `cosmiconfig` as well.
|
|
57
|
+
*/
|
|
58
|
+
const loaders = {
|
|
59
|
+
[NO_EXT]: yamlParse,
|
|
60
|
+
'.cjs': dynamicImport,
|
|
61
|
+
'.cts': dynamicImport,
|
|
62
|
+
'.js': dynamicImport,
|
|
63
|
+
'.json': jsonParse,
|
|
64
|
+
'.mjs': dynamicImport,
|
|
65
|
+
'.mts': dynamicImport,
|
|
66
|
+
'.ts': dynamicImport,
|
|
67
|
+
'.yaml': yamlParse,
|
|
68
|
+
'.yml': yamlParse,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const loadConfigByExt = async (filename) => {
|
|
72
|
+
const filepath = path.resolve(filename)
|
|
73
|
+
const ext = path.extname(filepath) || NO_EXT
|
|
74
|
+
const loader = loaders[ext]
|
|
75
|
+
const config = await loader(filepath)
|
|
76
|
+
|
|
77
|
+
return { config, filepath }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** @param {string} configPath */
|
|
81
|
+
export const loadConfig = async (configPath, logger) => {
|
|
82
|
+
try {
|
|
83
|
+
debugLog('Loading configuration from `%s`...', configPath)
|
|
84
|
+
const result = await loadConfigByExt(resolveConfig(configPath))
|
|
85
|
+
|
|
86
|
+
// config is a promise when using the `dynamicImport` loader
|
|
87
|
+
const config = (await result.config) ?? null
|
|
88
|
+
const filepath = result.filepath
|
|
89
|
+
|
|
90
|
+
if (config) {
|
|
91
|
+
debugLog('Successfully loaded config from `%s`:\n%O', filepath, config)
|
|
92
|
+
} else {
|
|
93
|
+
debugLog('Found no config in %s', filepath)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return { config, filepath }
|
|
97
|
+
} catch (error) {
|
|
98
|
+
debugLog('Failed to load configuration from `%s` with error:\n', configPath, error)
|
|
99
|
+
logger.error(failedToLoadConfig(configPath))
|
|
100
|
+
return {}
|
|
101
|
+
}
|
|
102
|
+
}
|
package/lib/messages.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { inspect } from 'node:util'
|
|
2
|
+
|
|
3
|
+
import { bold, red, yellow } from './colors.js'
|
|
4
|
+
import { error, info, warning } from './figures.js'
|
|
5
|
+
|
|
6
|
+
export const configurationError = (opt, helpMsg, value) =>
|
|
7
|
+
`${red(`${error} Validation Error:`)}
|
|
8
|
+
|
|
9
|
+
Invalid value for '${bold(opt)}': ${bold(inspect(value))}
|
|
10
|
+
|
|
11
|
+
${helpMsg}`
|
|
12
|
+
|
|
13
|
+
export const NOT_GIT_REPO = red(`${error} Current directory is not a git directory!`)
|
|
14
|
+
|
|
15
|
+
export const FAILED_GET_STAGED_FILES = red(`${error} Failed to get staged files!`)
|
|
16
|
+
|
|
17
|
+
export const incorrectBraces = (before, after) =>
|
|
18
|
+
yellow(
|
|
19
|
+
`${warning} Detected incorrect braces with only single value: \`${before}\`. Reformatted as: \`${after}\`
|
|
20
|
+
`
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
export const NO_CONFIGURATION = `${error} lint-staged could not find any valid configuration.`
|
|
24
|
+
|
|
25
|
+
export const NO_STAGED_FILES = `${info} lint-staged could not find any staged files.`
|
|
26
|
+
|
|
27
|
+
export const NO_TASKS = `${info} lint-staged could not find any staged files matching configured tasks.`
|
|
28
|
+
|
|
29
|
+
export const skippingBackup = (hasInitialCommit, diff) => {
|
|
30
|
+
const reason =
|
|
31
|
+
diff !== undefined
|
|
32
|
+
? '`--diff` was used'
|
|
33
|
+
: (hasInitialCommit ? '`--no-stash` was used' : 'there’s no initial commit yet') +
|
|
34
|
+
'. This might result in data loss'
|
|
35
|
+
|
|
36
|
+
return yellow(`${warning} Skipping backup because ${reason}.\n`)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const SKIPPING_HIDE_PARTIALLY_CHANGED = yellow(
|
|
40
|
+
`${warning} Skipping hiding unstaged changes from partially staged files because \`--no-hide-partially-staged\` was used.\n`
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
export const DEPRECATED_GIT_ADD = yellow(
|
|
44
|
+
`${warning} 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.
|
|
45
|
+
`
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
export const TASK_ERROR = 'Skipped because of errors from tasks.'
|
|
49
|
+
|
|
50
|
+
export const PREVENTED_TASK_MODIFICATIONS = `\n${error} lint-staged failed because \`--fail-on-changes\` was used.`
|
|
51
|
+
|
|
52
|
+
export const SKIPPED_GIT_ERROR = 'Skipped because of previous git error.'
|
|
53
|
+
|
|
54
|
+
export const GIT_ERROR = `\n ${red(`${error} lint-staged failed due to a git error.`)}`
|
|
55
|
+
|
|
56
|
+
export const invalidOption = (name, value, message) => `${red(`${error} Validation Error:`)}
|
|
57
|
+
|
|
58
|
+
Invalid value for option '${bold(name)}': ${bold(value)}
|
|
59
|
+
|
|
60
|
+
${message}
|
|
61
|
+
|
|
62
|
+
See https://github.com/okonet/lint-staged#command-line-flags`
|
|
63
|
+
|
|
64
|
+
export const PREVENTED_EMPTY_COMMIT = `
|
|
65
|
+
${yellow(`${warning} lint-staged prevented an empty git commit.
|
|
66
|
+
Use the --allow-empty option to continue, or check your task configuration`)}
|
|
67
|
+
`
|
|
68
|
+
|
|
69
|
+
export const restoreStashExample = (
|
|
70
|
+
hash = 'h0a0s0h0'
|
|
71
|
+
) => `Any lost modifications can be restored from a git stash:
|
|
72
|
+
|
|
73
|
+
> git stash list --format="%h %s"
|
|
74
|
+
${hash} On main: lint-staged automatic backup
|
|
75
|
+
> git apply --index ${hash}`
|
|
76
|
+
|
|
77
|
+
export const CONFIG_STDIN_ERROR = red(`${error} Failed to read config from stdin.`)
|
|
78
|
+
|
|
79
|
+
export const failedToLoadConfig = (filepath) =>
|
|
80
|
+
red(`${error} Failed to read config from file "${filepath}".`)
|
|
81
|
+
|
|
82
|
+
export const failedToParseConfig = (
|
|
83
|
+
filepath,
|
|
84
|
+
error
|
|
85
|
+
) => `${red(`${error} Failed to parse config from file "${filepath}".`)}
|
|
86
|
+
|
|
87
|
+
${error}
|
|
88
|
+
|
|
89
|
+
See https://github.com/okonet/lint-staged#configuration.`
|
|
90
|
+
|
|
91
|
+
export const UNSTAGED_CHANGES_BACKUP_STASH_LOCATION = `Unstaged changes have been kept back in a patch file:`
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reimplementation of "normalize-path"
|
|
3
|
+
* @see https://github.com/jonschlinkert/normalize-path/blob/52c3a95ebebc2d98c1ad7606cbafa7e658656899/index.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/*!
|
|
7
|
+
* normalize-path <https://github.com/jonschlinkert/normalize-path>
|
|
8
|
+
*
|
|
9
|
+
* Copyright (c) 2014-2018, Jon Schlinkert.
|
|
10
|
+
* Released under the MIT License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import path from 'node:path'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A file starting with \\?\
|
|
17
|
+
* @see https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#win32-file-namespaces
|
|
18
|
+
*/
|
|
19
|
+
const WIN32_FILE_NS = '\\\\?\\'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A file starting with \\.\
|
|
23
|
+
* @see https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#win32-file-namespaces
|
|
24
|
+
*/
|
|
25
|
+
const WIN32_DEVICE_NS = '\\\\.\\'
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Normalize input file path to use POSIX separators
|
|
29
|
+
* @param {String} input
|
|
30
|
+
* @returns String
|
|
31
|
+
*/
|
|
32
|
+
export const normalizePath = (input) => {
|
|
33
|
+
if (input === path.posix.sep || input === path.win32.sep) {
|
|
34
|
+
return path.posix.sep
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let normalized = input.split(/[/\\]+/).join(path.posix.sep)
|
|
38
|
+
|
|
39
|
+
/** Handle win32 Namespaced paths by changing e.g. \\.\ to //./ */
|
|
40
|
+
if (input.startsWith(WIN32_FILE_NS) || input.startsWith(WIN32_DEVICE_NS)) {
|
|
41
|
+
normalized = normalized.replace(/^\/(\.|\?)/, '//$1')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Remove trailing slash */
|
|
45
|
+
if (normalized.endsWith(path.posix.sep)) {
|
|
46
|
+
normalized = normalized.slice(0, -1)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return normalized
|
|
50
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Return array of strings split from the output of `git <something> -z`.
|
|
3
|
+
* With `-z`, git prints `fileA\u0000fileB\u0000fileC\u0000` so we need to
|
|
4
|
+
* remove the last occurrence of `\u0000` before splitting
|
|
5
|
+
*/
|
|
6
|
+
export const parseGitZOutput = (input) =>
|
|
7
|
+
input
|
|
8
|
+
? input
|
|
9
|
+
.replace(/\u0000$/, '') // eslint-disable-line no-control-regex
|
|
10
|
+
.split('\u0000')
|
|
11
|
+
: []
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handle logging of listr `ctx.output` to the specified `logger`
|
|
3
|
+
* @param {Object} ctx - The listr initial state
|
|
4
|
+
* @param {Object} logger - The logger
|
|
5
|
+
*/
|
|
6
|
+
export const printTaskOutput = (ctx = {}, logger) => {
|
|
7
|
+
if (!Array.isArray(ctx.output)) return
|
|
8
|
+
const log = ctx.errors?.size > 0 ? logger.error : logger.log
|
|
9
|
+
for (const line of ctx.output) {
|
|
10
|
+
log(line)
|
|
11
|
+
}
|
|
12
|
+
}
|
package/lib/readStdin.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns a promise resolving to the first line written to stdin after invoking.
|
|
5
|
+
* @warn will never resolve if called after writing to stdin
|
|
6
|
+
*
|
|
7
|
+
* @returns {Promise<string>}
|
|
8
|
+
*/
|
|
9
|
+
export const readStdin = () => {
|
|
10
|
+
const readline = createInterface({ input: process.stdin })
|
|
11
|
+
|
|
12
|
+
return new Promise((resolve) => {
|
|
13
|
+
readline.prompt()
|
|
14
|
+
readline.on('line', (line) => {
|
|
15
|
+
readline.close()
|
|
16
|
+
resolve(line)
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createRequire } from 'node:module'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* require() does not exist for ESM, so we must create it to use require.resolve().
|
|
5
|
+
* @see https://nodejs.org/api/module.html#modulecreaterequirefilename
|
|
6
|
+
*/
|
|
7
|
+
const require = createRequire(import.meta.url)
|
|
8
|
+
|
|
9
|
+
export function resolveConfig(configPath) {
|
|
10
|
+
try {
|
|
11
|
+
return require.resolve(configPath)
|
|
12
|
+
} catch {
|
|
13
|
+
return configPath
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
|
|
3
|
+
import { createDebug } from './debug.js'
|
|
4
|
+
import { execGit } from './execGit.js'
|
|
5
|
+
import { normalizePath } from './normalizePath.js'
|
|
6
|
+
|
|
7
|
+
const debugLog = createDebug('lint-staged:resolveGitRepo')
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Relative path up to the repo top-level directory
|
|
11
|
+
* @example "../"
|
|
12
|
+
*/
|
|
13
|
+
const CDUP = '--show-cdup'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Absolute repo top-level directory
|
|
17
|
+
*
|
|
18
|
+
* @example <caption>Git on macOS</caption>
|
|
19
|
+
* "/Users/iiro/Documents/git/lint-staged"
|
|
20
|
+
*
|
|
21
|
+
* @example <caption>Git for Windows</caption>
|
|
22
|
+
* "C:\Users\iiro\Documents\git\lint-staged"
|
|
23
|
+
*
|
|
24
|
+
* @example <caption>Git installed with MSYS2, this doesn't work when used as CWD with Node.js child_process</caption>
|
|
25
|
+
* "/c/Users/iiro/Documents/git/lint-staged"
|
|
26
|
+
*/
|
|
27
|
+
const TOPLEVEL = '--show-toplevel'
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Absolute .git directory, similar to top-level
|
|
31
|
+
*
|
|
32
|
+
* @example "/Users/iiro/Documents/git/lint-staged/.git"
|
|
33
|
+
*/
|
|
34
|
+
const ABSOLUTE_GIT_DIR = '--absolute-git-dir'
|
|
35
|
+
|
|
36
|
+
/** Resolve git directory and possible submodule paths */
|
|
37
|
+
export const resolveGitRepo = async (cwd = process.cwd()) => {
|
|
38
|
+
try {
|
|
39
|
+
debugLog('Resolving git repo from `%s`', cwd)
|
|
40
|
+
|
|
41
|
+
// Unset GIT_DIR before running any git operations in case it's pointing to an incorrect location
|
|
42
|
+
debugLog('Unset GIT_DIR (was `%s`)', process.env.GIT_DIR)
|
|
43
|
+
delete process.env.GIT_DIR
|
|
44
|
+
debugLog('Unset GIT_WORK_TREE (was `%s`)', process.env.GIT_WORK_TREE)
|
|
45
|
+
delete process.env.GIT_WORK_TREE
|
|
46
|
+
|
|
47
|
+
/** Git rev-parse returns all three flag values on separate lines */
|
|
48
|
+
const revParseOutput = await execGit(['rev-parse', CDUP, TOPLEVEL, ABSOLUTE_GIT_DIR], {
|
|
49
|
+
cwd,
|
|
50
|
+
})
|
|
51
|
+
const [relativeTopLevelDir, topLevel, absoluteGitDir] = revParseOutput.split('\n')
|
|
52
|
+
|
|
53
|
+
const topLevelDir = normalizePath(path.join(cwd, relativeTopLevelDir))
|
|
54
|
+
debugLog('Resolved git repository top-level directory to be `%s`', topLevelDir)
|
|
55
|
+
|
|
56
|
+
const relativeGitConfigDir = path.relative(topLevel, absoluteGitDir)
|
|
57
|
+
const gitConfigDir = normalizePath(path.join(topLevelDir, relativeGitConfigDir))
|
|
58
|
+
debugLog('Resolved git config directory to be `%s`', gitConfigDir)
|
|
59
|
+
|
|
60
|
+
return { topLevelDir, gitConfigDir }
|
|
61
|
+
} catch (error) {
|
|
62
|
+
debugLog('Failed to resolve git repo with error:', error)
|
|
63
|
+
return { error, topLevelDir: null, gitConfigDir: null }
|
|
64
|
+
}
|
|
65
|
+
}
|