@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/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
+ }
@@ -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
+ }
@@ -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
+ }