1 |
|
2 |
|
3 |
|
4 | 'use strict';
|
5 |
|
6 | const chalk = require('chalk');
|
7 | const log = require('fancy-log');
|
8 | const globAll = require('glob-all');
|
9 | const gulp = require('gulp');
|
10 | const emoji = require('node-emoji');
|
11 | const ora = require('ora');
|
12 | const pluralize = require('pluralize');
|
13 | const fss = require('@absolunet/fss');
|
14 | const { terminal } = require('@absolunet/terminal');
|
15 | const env = require('~/helpers/env');
|
16 | const paths = require('~/helpers/paths');
|
17 | const toolbox = require('~/helpers/toolbox');
|
18 |
|
19 |
|
20 | const __ = {
|
21 | standingSpinner: false,
|
22 | totalGuards: 0,
|
23 | activeGuards: {},
|
24 | ignoredChanges: {},
|
25 | cascadeSkip: false,
|
26 | watchSkip: {},
|
27 | delayedLog: false
|
28 | };
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 | const START = 'Starting';
|
36 | const END = 'Finished';
|
37 | const HALT = 'Halting';
|
38 | const ALERT = 'Alerted';
|
39 | const TASK = 'task';
|
40 | const SEQUENCE = 'sequence';
|
41 | const DEPENDENCIES = 'dependencies';
|
42 |
|
43 |
|
44 | const 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 |
|
56 | const 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 |
|
80 | const isSkipping = (name) => {
|
81 | return __.cascadeSkip || __.watchSkip[name];
|
82 | };
|
83 |
|
84 |
|
85 | const runTask = ({ name, task, start }) => {
|
86 | return task({ taskName: name })
|
87 |
|
88 |
|
89 | .on('finish', () => {
|
90 | if (!__.cascadeSkip) {
|
91 |
|
92 |
|
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 |
|
104 | .on('error', function() {
|
105 |
|
106 |
|
107 | if (env.watching) {
|
108 | __.cascadeSkip = true;
|
109 |
|
110 |
|
111 | } else {
|
112 | terminal.exit();
|
113 | }
|
114 |
|
115 |
|
116 | this.emit('end');
|
117 | })
|
118 | ;
|
119 | };
|
120 |
|
121 |
|
122 |
|
123 |
|
124 |
|
125 |
|
126 | class Flow {
|
127 |
|
128 |
|
129 | createTask(name, task, dependencies) {
|
130 | gulp.task(name, (callback) => {
|
131 |
|
132 |
|
133 | if (!isSkipping(name)) {
|
134 | const start = new Date();
|
135 |
|
136 |
|
137 | if (dependencies) {
|
138 | logStep(START, DEPENDENCIES, name);
|
139 |
|
140 | return gulp.series(dependencies, () => {
|
141 |
|
142 |
|
143 | if (!isSkipping()) {
|
144 | logStep(END, DEPENDENCIES, name);
|
145 | logStep(START, TASK, name);
|
146 |
|
147 |
|
148 | return runTask({ name, task, start }).on('finish', () => {
|
149 |
|
150 |
|
151 | return callback ? callback() : undefined;
|
152 | });
|
153 | }
|
154 |
|
155 |
|
156 | logStep(HALT, DEPENDENCIES, name);
|
157 |
|
158 |
|
159 | return callback ? callback() : undefined;
|
160 | })();
|
161 | }
|
162 |
|
163 |
|
164 | logStep(START, TASK, name);
|
165 |
|
166 | return runTask({ name, task, start });
|
167 | }
|
168 |
|
169 |
|
170 |
|
171 |
|
172 | return toolbox.selfClosingStream();
|
173 | });
|
174 | }
|
175 |
|
176 |
|
177 |
|
178 | createSequence(name, sequence, { cleanPaths = [], cleanBundle } = {}) {
|
179 | gulp.task(name, (callback) => {
|
180 | const start = new Date();
|
181 | logStep(START, SEQUENCE, name);
|
182 |
|
183 |
|
184 | const list = cleanPaths;
|
185 |
|
186 |
|
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 |
|
194 | list.forEach((path) => {
|
195 | fss.remove(path);
|
196 | });
|
197 |
|
198 |
|
199 | gulp.series(sequence, () => {
|
200 |
|
201 |
|
202 | if (!__.cascadeSkip) {
|
203 | logStep(END, SEQUENCE, name, start);
|
204 | } else {
|
205 | logStep(HALT, SEQUENCE, name);
|
206 | }
|
207 |
|
208 |
|
209 | return callback ? callback() : undefined;
|
210 | })();
|
211 | });
|
212 | }
|
213 |
|
214 |
|
215 |
|
216 | watchSequence(name, patterns, sequence) {
|
217 | __.activeGuards[name] = 0;
|
218 | __.ignoredChanges[name] = 0;
|
219 |
|
220 |
|
221 | const files = globAll.sync(patterns);
|
222 |
|
223 | return gulp.watch(files, { queue: false }, gulp.series(sequence, (callback) => {
|
224 |
|
225 |
|
226 | logGuard(END, name);
|
227 | __.activeGuards[name] = 0;
|
228 | __.ignoredChanges[name] = 0;
|
229 |
|
230 |
|
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 |
|
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 |
|
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 |
|
276 | skipOnWatch(task) {
|
277 | __.watchSkip[task] = true;
|
278 | }
|
279 |
|
280 |
|
281 |
|
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 |
|
293 | module.exports = new Flow();
|