1 | 'use strict'
|
2 |
|
3 |
|
4 |
|
5 | const chalk = require('chalk')
|
6 | const Listr = require('listr')
|
7 | const symbols = require('log-symbols')
|
8 |
|
9 | const chunkFiles = require('./chunkFiles')
|
10 | const generateTasks = require('./generateTasks')
|
11 | const getStagedFiles = require('./getStagedFiles')
|
12 | const GitWorkflow = require('./gitWorkflow')
|
13 | const makeCmdTasks = require('./makeCmdTasks')
|
14 | const resolveGitRepo = require('./resolveGitRepo')
|
15 |
|
16 | const debugLog = require('debug')('lint-staged:run')
|
17 |
|
18 | const getRenderer = ({ debug, quiet }) => {
|
19 | if (quiet) return 'silent'
|
20 |
|
21 | const isDumbTerminal = process.env.TERM === 'dumb'
|
22 | if (debug || isDumbTerminal) return 'verbose'
|
23 | return 'update'
|
24 | }
|
25 |
|
26 | const MESSAGES = {
|
27 | TASK_ERROR: 'Skipped because of errors from tasks.',
|
28 | GIT_ERROR: 'Skipped because of previous git error.'
|
29 | }
|
30 |
|
31 | const shouldSkipApplyModifications = ctx => {
|
32 |
|
33 | if (ctx.gitError) {
|
34 | return MESSAGES.GIT_ERROR
|
35 | }
|
36 |
|
37 | if (ctx.taskError) {
|
38 | return MESSAGES.TASK_ERROR
|
39 | }
|
40 | }
|
41 |
|
42 | const shouldSkipRevert = ctx => {
|
43 |
|
44 | if (ctx.gitError && !ctx.gitApplyEmptyCommit && !ctx.gitApplyModificationsError) {
|
45 | return MESSAGES.GIT_ERROR
|
46 | }
|
47 | }
|
48 |
|
49 | const shouldSkipCleanup = ctx => {
|
50 |
|
51 | if (ctx.gitError && !ctx.gitApplyEmptyCommit && !ctx.gitApplyModificationsError) {
|
52 | return MESSAGES.GIT_ERROR
|
53 | }
|
54 |
|
55 | if (ctx.gitRestoreOriginalStateError) {
|
56 | return MESSAGES.GIT_ERROR
|
57 | }
|
58 | }
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 | const runAll = async (
|
77 | {
|
78 | allowEmpty = false,
|
79 | config,
|
80 | cwd = process.cwd(),
|
81 | debug = false,
|
82 | maxArgLength,
|
83 | quiet = false,
|
84 | relative = false,
|
85 | shell = false,
|
86 | concurrent = true
|
87 | },
|
88 | logger = console
|
89 | ) => {
|
90 | debugLog('Running all linter scripts')
|
91 |
|
92 | const { gitDir, gitConfigDir } = await resolveGitRepo(cwd)
|
93 | if (!gitDir) throw new Error('Current directory is not a git directory!')
|
94 |
|
95 | const files = await getStagedFiles({ cwd: gitDir })
|
96 | if (!files) throw new Error('Unable to get staged files!')
|
97 | debugLog('Loaded list of staged files in git:\n%O', files)
|
98 |
|
99 |
|
100 | if (files.length === 0) {
|
101 | logger.log('No staged files found.')
|
102 | return 'No tasks to run.'
|
103 | }
|
104 |
|
105 | const stagedFileChunks = chunkFiles({ files, gitDir, maxArgLength, relative })
|
106 | const chunkCount = stagedFileChunks.length
|
107 | if (chunkCount > 1) debugLog(`Chunked staged files into ${chunkCount} part`, chunkCount)
|
108 |
|
109 |
|
110 |
|
111 | let hasDeprecatedGitAdd = false
|
112 |
|
113 | const listrOptions = {
|
114 | dateFormat: false,
|
115 | exitOnError: false,
|
116 | renderer: getRenderer({ debug, quiet })
|
117 | }
|
118 |
|
119 | const listrTasks = []
|
120 |
|
121 | for (const [index, files] of stagedFileChunks.entries()) {
|
122 | const chunkTasks = generateTasks({ config, cwd, gitDir, files, relative })
|
123 | const chunkListrTasks = []
|
124 |
|
125 | for (const task of chunkTasks) {
|
126 | const subTasks = await makeCmdTasks({
|
127 | commands: task.commands,
|
128 | files: task.fileList,
|
129 | gitDir,
|
130 | shell
|
131 | })
|
132 |
|
133 | if (subTasks.some(subTask => subTask.command === 'git add')) {
|
134 | hasDeprecatedGitAdd = true
|
135 | }
|
136 |
|
137 | chunkListrTasks.push({
|
138 | title: `Running tasks for ${task.pattern}`,
|
139 | task: async () =>
|
140 | new Listr(subTasks, {
|
141 |
|
142 |
|
143 | dateFormat: false,
|
144 | concurrent: false,
|
145 | exitOnError: true
|
146 | }),
|
147 | skip: () => {
|
148 |
|
149 | if (task.fileList.length === 0) {
|
150 | return `No staged files match ${task.pattern}`
|
151 | }
|
152 | return false
|
153 | }
|
154 | })
|
155 | }
|
156 |
|
157 | listrTasks.push({
|
158 |
|
159 | title:
|
160 | chunkCount > 1 ? `Running tasks (chunk ${index + 1}/${chunkCount})...` : 'Running tasks...',
|
161 | task: () => new Listr(chunkListrTasks, { ...listrOptions, concurrent }),
|
162 | skip: (ctx = {}) => {
|
163 |
|
164 | if (ctx.gitError) return MESSAGES.GIT_ERROR
|
165 |
|
166 | if (chunkListrTasks.every(task => task.skip())) return 'No tasks to run.'
|
167 | return false
|
168 | }
|
169 | })
|
170 | }
|
171 |
|
172 | if (hasDeprecatedGitAdd) {
|
173 | logger.warn(`${symbols.warning} ${chalk.yellow(
|
174 | `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.`
|
175 | )}
|
176 | `)
|
177 | }
|
178 |
|
179 |
|
180 |
|
181 | if (listrTasks.every(task => task.skip())) {
|
182 | logger.log('No staged files match any of provided globs.')
|
183 | return 'No tasks to run.'
|
184 | }
|
185 |
|
186 | const git = new GitWorkflow({ allowEmpty, gitConfigDir, gitDir, stagedFileChunks })
|
187 |
|
188 | const runner = new Listr(
|
189 | [
|
190 | {
|
191 | title: 'Preparing...',
|
192 | task: ctx => git.stashBackup(ctx)
|
193 | },
|
194 | ...listrTasks,
|
195 | {
|
196 | title: 'Applying modifications...',
|
197 | task: ctx => git.applyModifications(ctx),
|
198 | skip: shouldSkipApplyModifications
|
199 | },
|
200 | {
|
201 | title: 'Reverting to original state...',
|
202 | task: ctx => git.restoreOriginalState(ctx),
|
203 | enabled: ctx => ctx.taskError || ctx.gitApplyEmptyCommit || ctx.gitApplyModificationsError,
|
204 | skip: shouldSkipRevert
|
205 | },
|
206 | {
|
207 | title: 'Cleaning up...',
|
208 | task: ctx => git.dropBackup(ctx),
|
209 | skip: shouldSkipCleanup
|
210 | }
|
211 | ],
|
212 | listrOptions
|
213 | )
|
214 |
|
215 | try {
|
216 | await runner.run({})
|
217 | } catch (error) {
|
218 | if (error.context.gitApplyEmptyCommit) {
|
219 | logger.warn(`
|
220 | ${symbols.warning} ${chalk.yellow(`lint-staged prevented an empty git commit.
|
221 | Use the --allow-empty option to continue, or check your task configuration`)}
|
222 | `)
|
223 | } else if (error.context.gitError && !error.context.gitGetBackupStashError) {
|
224 | logger.error(`\n ${symbols.error} ${chalk.red(`lint-staged failed due to a git error.`)}`)
|
225 |
|
226 | if (error.context.emptyGitRepo) {
|
227 | logger.error(
|
228 | `\n The initial commit is needed for lint-staged to work.
|
229 | Please use the --no-verify flag to skip running lint-staged.`
|
230 | )
|
231 | } else {
|
232 |
|
233 | logger.error(` Any lost modifications can be restored from a git stash:
|
234 |
|
235 | > git stash list
|
236 | stash@{0}: On master: automatic lint-staged backup
|
237 | > git stash pop stash@{0}\n`)
|
238 | }
|
239 | }
|
240 |
|
241 | throw error
|
242 | }
|
243 | }
|
244 |
|
245 | module.exports = runAll
|
246 |
|
247 | module.exports.shouldSkip = {
|
248 | shouldSkipApplyModifications,
|
249 | shouldSkipRevert,
|
250 | shouldSkipCleanup
|
251 | }
|