UNPKG

6.99 kBJavaScriptView Raw
1//-------------------------------------
2//-- Flow
3//-------------------------------------
4'use strict';
5
6const chalk = require('chalk');
7const log = require('fancy-log');
8const globAll = require('glob-all');
9const gulp = require('gulp');
10const emoji = require('node-emoji');
11const ora = require('ora');
12const pluralize = require('pluralize');
13const fss = require('@absolunet/fss');
14const { terminal } = require('@absolunet/terminal');
15const env = require('~/helpers/env');
16const paths = require('~/helpers/paths');
17const toolbox = require('~/helpers/toolbox');
18
19
20const __ = {
21 standingSpinner: false, // Recceing spinner
22 totalGuards: 0, // Total of called guards
23 activeGuards: {}, // Registry of guards that are currently running
24 ignoredChanges: {}, // Registry of changes that were made during a watched task
25 cascadeSkip: false, // In watch mode, is currently skipping tasks because of one failed task ?
26 watchSkip: {}, // In watch mode, skip these tasks
27 delayedLog: false // Patch for stylelint reporter
28};
29
30
31
32
33
34
35const START = 'Starting';
36const END = 'Finished';
37const HALT = 'Halting';
38const ALERT = 'Alerted';
39const TASK = 'task';
40const SEQUENCE = 'sequence';
41const DEPENDENCIES = 'dependencies';
42
43
44const logStep = (action, scope, name, start) => {
45 const logType = action === HALT ? log : log.warn;
46 const logAction = action === HALT ? chalk.yellow(action) : action;
47 const nameStyles = scope === TASK ? chalk.cyan : chalk.cyan.underline;
48 const logName = action === START ? nameStyles(name) : nameStyles.dim(name);
49 const logScopedName = scope === DEPENDENCIES ? `${logName} ${scope}` : `${scope} ${logName}`;
50 const logTime = start ? `after ${chalk.magenta(`${(new Date() - start) / 1000}s`)}` : '';
51
52 logType(`${logAction} ${logScopedName} ${logTime}`);
53};
54
55
56const logGuard = (action, name, file) => {
57 switch (action) {
58
59 case START:
60 return `${emoji.get('guardsman')} Guard n°${__.totalGuards + 1} standing guard...`;
61
62 case ALERT:
63 return terminal.echo(`${emoji.get('mega')} Guard n°${__.totalGuards} alerted by ${chalk.magenta(file.split(`${paths.directory.root}/`)[1])} calling ${chalk.cyan(name)}`);
64
65 case END:
66 return terminal.echo(`${emoji.get('zzz')} Guard n°${__.activeGuards[name]} duty is completed ${chalk.cyan.dim(`(${name})`)}${
67 __.ignoredChanges[name] ? chalk.yellow(` ⚠ ${pluralize('change', __.ignoredChanges[name], true)} ${__.ignoredChanges[name] === 1 ? 'was' : 'were'} ignored`) : ''
68 }\n`);
69
70 default: return undefined;
71
72 }
73};
74
75
76
77
78
79
80const isSkipping = (name) => {
81 return __.cascadeSkip || __.watchSkip[name];
82};
83
84
85const runTask = ({ name, task, start }) => {
86 return task({ taskName: name })
87
88 // Log task as completed
89 .on('finish', () => {
90 if (!__.cascadeSkip) {
91
92 // Patch for stylelint to make reporter show this
93 if (name === 'styles-lint') {
94 __.delayedLog = { name, start };
95 } else {
96 logStep(END, TASK, name, start);
97 }
98 } else {
99 logStep(HALT, TASK, name);
100 }
101 })
102
103 // Error
104 .on('error', function() {
105
106 // In watch mode, cascade skip all pending tasks
107 if (env.watching) {
108 __.cascadeSkip = true;
109
110 // In run mode, rage quit (╯°□°)╯︵ ┻━┻
111 } else {
112 terminal.exit();
113 }
114
115 // Close stream
116 this.emit('end');
117 })
118 ;
119};
120
121
122
123
124
125
126class Flow {
127
128 //-- Create gulp task
129 createTask(name, task, dependencies) {
130 gulp.task(name, (callback) => {
131
132 // Run task if not skipping tasks
133 if (!isSkipping(name)) {
134 const start = new Date();
135
136 // Run task dependencies first
137 if (dependencies) {
138 logStep(START, DEPENDENCIES, name);
139
140 return gulp.series(dependencies, () => { // eslint-disable-line consistent-return
141
142 // Run task if not skipping
143 if (!isSkipping()) {
144 logStep(END, DEPENDENCIES, name);
145 logStep(START, TASK, name);
146
147 // Then run task
148 return runTask({ name, task, start }).on('finish', () => {
149
150 // Close stream
151 return callback ? callback() : undefined;
152 });
153 }
154
155 // Halted
156 logStep(HALT, DEPENDENCIES, name);
157
158 // Close stream
159 return callback ? callback() : undefined;
160 })();
161 }
162
163 // If no dependencies run task immediately
164 logStep(START, TASK, name);
165
166 return runTask({ name, task, start });
167 }
168
169 // ADD VERBOSE MODE LOGGING SKIPS
170
171 // Else skip task
172 return toolbox.selfClosingStream();
173 });
174 }
175
176
177 //-- Create tasks sequence
178 createSequence(name, sequence, { cleanPaths = [], cleanBundle } = {}) {
179 gulp.task(name, (callback) => {
180 const start = new Date();
181 logStep(START, SEQUENCE, name);
182
183 // Global paths to delete
184 const list = cleanPaths;
185
186 // Bundles paths to delete
187 if (cleanBundle) {
188 for (const bundleName of Object.keys(env.bundles)) {
189 list.push(...cleanBundle({ name: bundleName, bundle: env.bundles[bundleName] }));
190 }
191 }
192
193 // Delete
194 list.forEach((path) => {
195 fss.remove(path);
196 });
197
198 // Run sequence
199 gulp.series(sequence, () => {
200
201 // Log sequence as completed
202 if (!__.cascadeSkip) {
203 logStep(END, SEQUENCE, name, start);
204 } else {
205 logStep(HALT, SEQUENCE, name);
206 }
207
208 // Close stream
209 return callback ? callback() : undefined;
210 })();
211 });
212 }
213
214
215 //-- Create watch tasks sequence
216 watchSequence(name, patterns, sequence) {
217 __.activeGuards[name] = 0;
218 __.ignoredChanges[name] = 0;
219
220 // Can't trust chokidar to do globbing
221 const files = globAll.sync(patterns);
222
223 return gulp.watch(files, { queue: false }, gulp.series(sequence, (callback) => {
224
225 // Log watcher as completed
226 logGuard(END, name);
227 __.activeGuards[name] = 0;
228 __.ignoredChanges[name] = 0;
229
230 // When there is no more running watchers
231 let anyGuardLeft = false;
232 Object.keys(__.activeGuards).forEach((key) => {
233 if (__.activeGuards[key]) {
234 anyGuardLeft = true;
235 }
236 });
237
238 if (!anyGuardLeft) {
239 this.startWatchSpinner();
240 }
241
242 callback();
243 }))
244
245 // When watcher triggered
246 .on('all', (action, triggeredPath) => {
247 if (__.activeGuards[name]) {
248 ++__.ignoredChanges[name];
249 } else {
250 __.activeGuards[name] = ++__.totalGuards;
251 __.standingSpinner.stop();
252 logGuard(ALERT, name, triggeredPath);
253 }
254 })
255 ;
256 }
257
258
259 //-- Start watch spinner
260 startWatchSpinner() {
261 __.cascadeSkip = false;
262
263 terminal.spacer();
264 __.standingSpinner = ora({
265 text: logGuard(START),
266 spinner: {
267 interval: 250,
268 frames: ['●', '○']
269 },
270 color: 'green'
271 }).start();
272 }
273
274
275 //-- Add a task to skip
276 skipOnWatch(task) {
277 __.watchSkip[task] = true;
278 }
279
280
281 //-- Show delayed log
282 showDelayedLog(error) {
283 if (!(error && !env.watching)) {
284 const { name, start } = __.delayedLog;
285 logStep(error ? HALT : END, TASK, name, start);
286 __.delayedLog = undefined;
287 }
288 }
289
290}
291
292
293module.exports = new Flow();