1 |
|
2 |
|
3 | import debug from 'debug'
|
4 | import { Listr } from 'listr2'
|
5 |
|
6 | import { chunkFiles } from './chunkFiles.js'
|
7 | import { execGit } from './execGit.js'
|
8 | import { generateTasks } from './generateTasks.js'
|
9 | import { getRenderer } from './getRenderer.js'
|
10 | import { getStagedFiles } from './getStagedFiles.js'
|
11 | import { GitWorkflow } from './gitWorkflow.js'
|
12 | import { makeCmdTasks } from './makeCmdTasks.js'
|
13 | import {
|
14 | DEPRECATED_GIT_ADD,
|
15 | FAILED_GET_STAGED_FILES,
|
16 | NOT_GIT_REPO,
|
17 | NO_STAGED_FILES,
|
18 | NO_TASKS,
|
19 | SKIPPED_GIT_ERROR,
|
20 | skippingBackup,
|
21 | } from './messages.js'
|
22 | import { resolveGitRepo } from './resolveGitRepo.js'
|
23 | import {
|
24 | applyModificationsSkipped,
|
25 | cleanupEnabled,
|
26 | cleanupSkipped,
|
27 | getInitialState,
|
28 | hasPartiallyStagedFiles,
|
29 | restoreOriginalStateEnabled,
|
30 | restoreOriginalStateSkipped,
|
31 | restoreUnstagedChangesSkipped,
|
32 | } from './state.js'
|
33 | import { GitRepoError, GetStagedFilesError, GitError } from './symbols.js'
|
34 |
|
35 | const debugLog = debug('lint-staged:runAll')
|
36 |
|
37 | const createError = (ctx) => Object.assign(new Error('lint-staged failed'), { ctx })
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 | export const runAll = async (
|
58 | {
|
59 | allowEmpty = false,
|
60 | concurrent = true,
|
61 | config,
|
62 | cwd = process.cwd(),
|
63 | debug = false,
|
64 | maxArgLength,
|
65 | quiet = false,
|
66 | relative = false,
|
67 | shell = false,
|
68 | stash = true,
|
69 | verbose = false,
|
70 | },
|
71 | logger = console
|
72 | ) => {
|
73 | debugLog('Running all linter scripts')
|
74 |
|
75 | const ctx = getInitialState({ quiet })
|
76 |
|
77 | const { gitDir, gitConfigDir } = await resolveGitRepo(cwd)
|
78 | if (!gitDir) {
|
79 | if (!quiet) ctx.output.push(NOT_GIT_REPO)
|
80 | ctx.errors.add(GitRepoError)
|
81 | throw createError(ctx)
|
82 | }
|
83 |
|
84 |
|
85 |
|
86 | const hasInitialCommit = await execGit(['log', '-1'], { cwd: gitDir })
|
87 | .then(() => true)
|
88 | .catch(() => false)
|
89 |
|
90 |
|
91 | ctx.shouldBackup = hasInitialCommit && stash
|
92 | if (!ctx.shouldBackup) {
|
93 | logger.warn(skippingBackup(hasInitialCommit))
|
94 | }
|
95 |
|
96 | const files = await getStagedFiles({ cwd: gitDir })
|
97 | if (!files) {
|
98 | if (!quiet) ctx.output.push(FAILED_GET_STAGED_FILES)
|
99 | ctx.errors.add(GetStagedFilesError)
|
100 | throw createError(ctx, GetStagedFilesError)
|
101 | }
|
102 | debugLog('Loaded list of staged files in git:\n%O', files)
|
103 |
|
104 |
|
105 | if (files.length === 0) {
|
106 | if (!quiet) ctx.output.push(NO_STAGED_FILES)
|
107 | return ctx
|
108 | }
|
109 |
|
110 | const stagedFileChunks = chunkFiles({ baseDir: gitDir, files, maxArgLength, relative })
|
111 | const chunkCount = stagedFileChunks.length
|
112 | if (chunkCount > 1) debugLog(`Chunked staged files into ${chunkCount} part`, chunkCount)
|
113 |
|
114 |
|
115 |
|
116 | let hasDeprecatedGitAdd = false
|
117 |
|
118 | const listrOptions = {
|
119 | ctx,
|
120 | exitOnError: false,
|
121 | nonTTYRenderer: 'verbose',
|
122 | registerSignalListeners: false,
|
123 | ...getRenderer({ debug, quiet }),
|
124 | }
|
125 |
|
126 | const listrTasks = []
|
127 |
|
128 |
|
129 | const matchedFiles = new Set()
|
130 |
|
131 | for (const [index, files] of stagedFileChunks.entries()) {
|
132 | const chunkTasks = generateTasks({ config, cwd, gitDir, files, relative })
|
133 | const chunkListrTasks = []
|
134 |
|
135 | for (const task of chunkTasks) {
|
136 | const subTasks = await makeCmdTasks({
|
137 | commands: task.commands,
|
138 | files: task.fileList,
|
139 | gitDir,
|
140 | renderer: listrOptions.renderer,
|
141 | shell,
|
142 | verbose,
|
143 | })
|
144 |
|
145 |
|
146 | task.fileList.forEach((file) => {
|
147 | matchedFiles.add(file)
|
148 | })
|
149 |
|
150 | hasDeprecatedGitAdd =
|
151 | hasDeprecatedGitAdd || subTasks.some((subTask) => subTask.command === 'git add')
|
152 |
|
153 | chunkListrTasks.push({
|
154 | title: `Running tasks for ${task.pattern}`,
|
155 | task: async () =>
|
156 | new Listr(subTasks, {
|
157 |
|
158 |
|
159 | ...listrOptions,
|
160 | concurrent: false,
|
161 | exitOnError: true,
|
162 | }),
|
163 | skip: () => {
|
164 |
|
165 | if (task.fileList.length === 0) {
|
166 | return `No staged files match ${task.pattern}`
|
167 | }
|
168 | return false
|
169 | },
|
170 | })
|
171 | }
|
172 |
|
173 | listrTasks.push({
|
174 |
|
175 | title:
|
176 | chunkCount > 1 ? `Running tasks (chunk ${index + 1}/${chunkCount})...` : 'Running tasks...',
|
177 | task: () => new Listr(chunkListrTasks, { ...listrOptions, concurrent }),
|
178 | skip: () => {
|
179 |
|
180 | if (ctx.errors.has(GitError)) return SKIPPED_GIT_ERROR
|
181 |
|
182 | if (chunkListrTasks.every((task) => task.skip())) return 'No tasks to run.'
|
183 | return false
|
184 | },
|
185 | })
|
186 | }
|
187 |
|
188 | if (hasDeprecatedGitAdd) {
|
189 | logger.warn(DEPRECATED_GIT_ADD)
|
190 | }
|
191 |
|
192 |
|
193 |
|
194 | if (listrTasks.every((task) => task.skip())) {
|
195 | if (!quiet) ctx.output.push(NO_TASKS)
|
196 | return ctx
|
197 | }
|
198 |
|
199 |
|
200 | const matchedFileChunks = chunkFiles({
|
201 |
|
202 | baseDir: cwd,
|
203 | files: Array.from(matchedFiles),
|
204 | maxArgLength,
|
205 | relative: false,
|
206 | })
|
207 |
|
208 | const git = new GitWorkflow({ allowEmpty, gitConfigDir, gitDir, matchedFileChunks })
|
209 |
|
210 | const runner = new Listr(
|
211 | [
|
212 | {
|
213 | title: 'Preparing...',
|
214 | task: (ctx) => git.prepare(ctx),
|
215 | },
|
216 | {
|
217 | title: 'Hiding unstaged changes to partially staged files...',
|
218 | task: (ctx) => git.hideUnstagedChanges(ctx),
|
219 | enabled: hasPartiallyStagedFiles,
|
220 | },
|
221 | ...listrTasks,
|
222 | {
|
223 | title: 'Applying modifications...',
|
224 | task: (ctx) => git.applyModifications(ctx),
|
225 | skip: applyModificationsSkipped,
|
226 | },
|
227 | {
|
228 | title: 'Restoring unstaged changes to partially staged files...',
|
229 | task: (ctx) => git.restoreUnstagedChanges(ctx),
|
230 | enabled: hasPartiallyStagedFiles,
|
231 | skip: restoreUnstagedChangesSkipped,
|
232 | },
|
233 | {
|
234 | title: 'Reverting to original state because of errors...',
|
235 | task: (ctx) => git.restoreOriginalState(ctx),
|
236 | enabled: restoreOriginalStateEnabled,
|
237 | skip: restoreOriginalStateSkipped,
|
238 | },
|
239 | {
|
240 | title: 'Cleaning up...',
|
241 | task: (ctx) => git.cleanup(ctx),
|
242 | enabled: cleanupEnabled,
|
243 | skip: cleanupSkipped,
|
244 | },
|
245 | ],
|
246 | listrOptions
|
247 | )
|
248 |
|
249 | await runner.run()
|
250 |
|
251 | if (ctx.errors.size > 0) {
|
252 | throw createError(ctx)
|
253 | }
|
254 |
|
255 | return ctx
|
256 | }
|