1 |
|
2 |
|
3 |
|
4 |
|
5 | 'use strict';
|
6 |
|
7 | const gulp = require('gulp'),
|
8 | gulpClean = require('gulp-clean'),
|
9 | prettyTime = require('pretty-hrtime'),
|
10 | extend = require('extend'),
|
11 | EventEmitter = require('events').EventEmitter,
|
12 | util = require('../utils'),
|
13 | T = require('../tools'),
|
14 | serconf = require('../serconf'),
|
15 | DeployManager = require('../deploy/deployManager'),
|
16 | TaskNode = require('./taskNode'),
|
17 | TaskNodeFun = require('./taskNodeFun');
|
18 |
|
19 | process.setMaxListeners(0);
|
20 |
|
21 |
|
22 | const
|
23 | isStartServer = T.hasArg(['s', 'server']),
|
24 | isOpenBrowser = T.hasArg(['o', 'open-browser']),
|
25 | isSkipBackup = T.hasArg('skip-backup');
|
26 |
|
27 | class Gupack extends EventEmitter {
|
28 | constructor(config, gulpInstance) {
|
29 | super();
|
30 | this.env = process.env.NODE_ENV || T.getArg(['e', 'env']) || 'local';
|
31 | this.project = T.getArg(['p', 'project']);
|
32 | this.basePath = T.getArg('cwdir') || process.cwd();
|
33 | this.sourceDir = 'src';
|
34 | this.buildDir = 'dist';
|
35 | this.indexFile = '';
|
36 | this.host = serconf.servers.host;
|
37 | this.port = serconf.servers.port;
|
38 | this.sport = serconf.servers.sport;
|
39 | this.liveDelay = serconf.servers.liveDelay;
|
40 | this.devServer = isStartServer;
|
41 | this.openBrowser = isOpenBrowser;
|
42 | this.hostname = null;
|
43 |
|
44 | this.startClean = true;
|
45 | this.isRunAloneTask = false;
|
46 | this.watch = true;
|
47 | this.watchOption = {};
|
48 | this.cleans = [];
|
49 | this.cleansFilter = ['!.svn', '!.git'];
|
50 | this.buildTasks = [];
|
51 | this.tasks = [];
|
52 | this.relies = {};
|
53 | this.watchers = {};
|
54 | this.basePath = process.cwd();
|
55 | this.gulp = gulpInstance || gulp;
|
56 | this.server = null;
|
57 | this.runIndex = 0;
|
58 | this.taskStartCallback = null;
|
59 | this.taskStopCallback = null;
|
60 | this.allTaskDoneCallback = null;
|
61 | this.isAllTaskDone = true;
|
62 | this.isDeploy = T.getArg('skip-deploy');
|
63 | this.deploy = null;
|
64 | this.deploies = [];
|
65 |
|
66 | for (let prop in config) {
|
67 | if (config.hasOwnProperty(prop)) {
|
68 | this[prop] = config[prop];
|
69 | }
|
70 | }
|
71 |
|
72 | this.init();
|
73 | }
|
74 |
|
75 | init() {
|
76 |
|
77 | this.initPaths();
|
78 |
|
79 | this.initDeploy();
|
80 |
|
81 | this.initTasks();
|
82 |
|
83 |
|
84 |
|
85 | this.watch && this.initWatch();
|
86 |
|
87 | this.initClean();
|
88 | }
|
89 |
|
90 | initPaths() {
|
91 | if (!T.isAbsolutePath(this.sourceDir)) {
|
92 | this.sourceDir = T.Path.resolve(this.basePath, this.sourceDir || 'src');
|
93 | }
|
94 | if (!T.isAbsolutePath(this.buildDir)) {
|
95 | this.buildDir = T.Path.resolve(this.basePath, this.buildDir || 'dist');
|
96 | }
|
97 | }
|
98 |
|
99 |
|
100 | initDeploy() {
|
101 | if (util.isObject(this.deploy)) {
|
102 | if (!this.deploy['localPath']) {
|
103 | this.deploy.localPath = T.Path.join(this.buildDir, '/**/*');
|
104 | }
|
105 | this.deploies.push(this.deploy);
|
106 | } else if (util.isArray(this.deploy)) {
|
107 | this.deploies = this.deploy.map((d) => {
|
108 | if (!d['localPath']) {
|
109 | d.localPath = T.Path.join(this.buildDir, '/**/*');
|
110 | }
|
111 | return d;
|
112 | });
|
113 | }
|
114 | const deploies = extend(true, [], this.deploies);
|
115 | this.deployManager = new DeployManager(deploies, this.watch);
|
116 | }
|
117 |
|
118 | initClean() {
|
119 | if (!util.isArray(this.cleans)) return false;
|
120 | this.cleansFilter = this.cleansFilter.concat(this.buildDir).map((cf) => {
|
121 | return '!' + cf.replace(/^!/i, '');
|
122 | });
|
123 | if (this.cleans.length > 0) {
|
124 | this.cleans = this.cleans.concat(this.cleansFilter);
|
125 | this.gulp.task('build._cleans', () => {
|
126 | return this.gulp.src(this.cleans).pipe(
|
127 | gulpClean({
|
128 | read: true
|
129 | })
|
130 | );
|
131 | });
|
132 |
|
133 | this.tasks.unshift('build._cleans');
|
134 | }
|
135 | }
|
136 |
|
137 | initTasks() {
|
138 | if (!util.isObject(this.buildTasks)) {
|
139 | T.log.error(
|
140 | `× ${new TypeError(
|
141 | '参数 buildTask 未找到或类型错误(buildTask必须是一个对象Object)' +
|
142 | '\n× 请您检查项目的gupack-config.js文件配置'
|
143 | )}`
|
144 | );
|
145 | return false;
|
146 | }
|
147 |
|
148 | let tasks = this.buildTasks;
|
149 | for (let key in tasks) {
|
150 | if (tasks.hasOwnProperty(key)) {
|
151 | const task = tasks[key];
|
152 | const taskNode = !util.isFunction(task)
|
153 | ? new TaskNode(task, this)
|
154 | : new TaskNodeFun(task);
|
155 | taskNode.name = key;
|
156 | this.buildTasks[key] = taskNode;
|
157 | }
|
158 | }
|
159 | this.setTask();
|
160 |
|
161 | this.tasks = Object.keys(this.buildTasks).map((taskName) => {
|
162 | let tasker = this.buildTasks[taskName];
|
163 |
|
164 | if (!tasker.fun) {
|
165 | this.gulp.task(taskName, tasker['rely'], (done) => {
|
166 | return !!tasker.run === true ? tasker.getTaskFunction(done) : done();
|
167 | });
|
168 | } else {
|
169 | this.gulp.task(taskName, tasker['rely'], tasker.fun);
|
170 | }
|
171 |
|
172 | return taskName;
|
173 | });
|
174 | }
|
175 |
|
176 | initWatch() {
|
177 |
|
178 | if (
|
179 | util.isArray(this.watch) ||
|
180 | util.isString(this.watch) ||
|
181 | util.isObject(this.watch)
|
182 | ) {
|
183 | let watchSource = [],
|
184 | ts = null;
|
185 | if (util.isArray(this.watch)) {
|
186 | watchSource = this.watch.map((w) => T.Path.resolve(this.sourceDir, w));
|
187 | }
|
188 | if (util.isString(this.watch)) {
|
189 | watchSource = [T.Path.resolve(this.sourceDir, this.watch)];
|
190 | }
|
191 | if (util.isObject(this.watch)) {
|
192 | ts = [];
|
193 | Object.keys(this.watch).map((w) => {
|
194 | watchSource.push(T.Path.resolve(this.sourceDir, this.watch[w]));
|
195 | ts.push(w);
|
196 | });
|
197 | }
|
198 |
|
199 | this.gulp.task('build._watch', () => {
|
200 | this.gulp.watch(watchSource, this.watchOption || {}, ts || this.tasks);
|
201 | });
|
202 |
|
203 | return this.tasks.push('build._watch');
|
204 | }
|
205 |
|
206 |
|
207 | let watchers = this.watchers,
|
208 | relies = this.relies;
|
209 |
|
210 | Object.keys(watchers).length > 0 &&
|
211 | (() => {
|
212 | this.gulp.task('build._watch', () => {
|
213 | let source, watcher;
|
214 |
|
215 | Object.keys(watchers).forEach((k) => {
|
216 | source = watchers[k];
|
217 | let ts = [];
|
218 |
|
219 |
|
220 | Object.keys(relies).forEach((rely) => {
|
221 | if (relies[rely] && relies[rely].indexOf(k) !== -1) {
|
222 | ts.unshift(rely);
|
223 | }
|
224 | });
|
225 | ts.push(k);
|
226 | watcher = this.gulp.watch(source, this.watchOption || {}, ts);
|
227 | });
|
228 | });
|
229 |
|
230 | this.tasks.push('build._watch');
|
231 | })();
|
232 | }
|
233 |
|
234 | recombineTasks() {
|
235 |
|
236 | let paiallels = [],
|
237 | sequences = [];
|
238 |
|
239 | Object.keys(this.relies).forEach((rely) => {
|
240 | if (util.isArray(this.relies[rely]) && this.relies[rely].length !== 0) {
|
241 | this.relies[rely].forEach((task) => {
|
242 | if (paiallels.indexOf(task) === -1) {
|
243 | paiallels.unshift(task);
|
244 | }
|
245 | });
|
246 | }
|
247 | });
|
248 |
|
249 | Object.keys(this.relies).forEach((rely) => {
|
250 | if (this.relies[rely] && paiallels.indexOf(rely) !== -1) {
|
251 | let index = paiallels.indexOf(rely);
|
252 | let removeTemp = paiallels.splice(index, 1);
|
253 | sequences.unshift(removeTemp[0]);
|
254 | } else if (paiallels.indexOf(rely) === -1) {
|
255 | sequences.push(rely);
|
256 | }
|
257 | });
|
258 | paiallels.length !== 0 && sequences.unshift(paiallels);
|
259 | this.tasks = sequences;
|
260 | }
|
261 |
|
262 | setTask() {
|
263 | let tasks = this.buildTasks;
|
264 | let tns = Object.keys(tasks);
|
265 |
|
266 | tns.forEach((tn) => {
|
267 | let task = tasks[tn];
|
268 | task.name = tn;
|
269 | if (task.watch && !this.nowatch) {
|
270 | this.watchers[tn] = task.watch;
|
271 | }
|
272 | this.relies[tn] = task.rely;
|
273 | task.dest && task.beforeClean !== false && this.cleans.push(task.dest);
|
274 | });
|
275 | }
|
276 |
|
277 | addEvent() {
|
278 | let self = this,
|
279 | logCache = [],
|
280 |
|
281 | isStart = false,
|
282 | tasks = Object.keys(this.buildTasks),
|
283 | finishMsg = `√ Builded finish OK =^_^= (^_^) =^_^= !!! \n\r`;
|
284 |
|
285 | this.gulp.on('task_start', (e) => {
|
286 | this.isAllTaskDone = false;
|
287 | let name = e.task,
|
288 | task = this.buildTasks[name],
|
289 | isRun =
|
290 | (task instanceof TaskNode || task instanceof TaskNodeFun) &&
|
291 | task.run === true;
|
292 | if (util.isFunction(this.taskStartCallback)) {
|
293 | return this.taskStartCallback.call(this, e);
|
294 | }
|
295 | if (tasks.indexOf(name) > -1 && logCache.indexOf(name) === -1 && isRun) {
|
296 | T.log.green(`→ [${T.getTime()}] Starting '${name}' ...`);
|
297 | }
|
298 | if (!isStart) {
|
299 | isStart = true;
|
300 | this.server && this.server.emitBuilding();
|
301 | }
|
302 | });
|
303 | this.gulp.on('task_stop', (e) => {
|
304 | this.isAllTaskDone = false;
|
305 | let name = e.task,
|
306 | task = this.buildTasks[name],
|
307 | isRun =
|
308 | (task instanceof TaskNode || task instanceof TaskNodeFun) &&
|
309 | task.run === true,
|
310 | duration = prettyTime(e.hrDuration);
|
311 | if (util.isFunction(this.taskStopCallback)) {
|
312 | return this.taskStopCallback.call(this, e);
|
313 | }
|
314 | if (tasks.indexOf(name) > -1 && logCache.indexOf(name) === -1 && isRun) {
|
315 | T.log.green(
|
316 | `√ [${T.getTime()}] Finished '${e.task}', after ${T.msg.yellow(
|
317 | duration
|
318 | )}`
|
319 | );
|
320 | logCache.push(name);
|
321 | this.emit('finish_task', {
|
322 | task: e.task,
|
323 | name,
|
324 | duration
|
325 | });
|
326 | }
|
327 | });
|
328 |
|
329 | this.gulp.on('stop', (e) => {
|
330 | isStart = false;
|
331 | logCache = [];
|
332 |
|
333 |
|
334 | if (this.isRunAloneTask) {
|
335 | return process.exit(0);
|
336 | }
|
337 |
|
338 | if (util.isFunction(this.allTaskDoneCallback)) {
|
339 | this.allTaskDoneCallback.call(this, e);
|
340 | _doneNext.apply(this);
|
341 | return false;
|
342 | } else {
|
343 | T.log.yellow(finishMsg);
|
344 | }
|
345 | this.emit('finish_all_task', {
|
346 | event: e,
|
347 | message: finishMsg
|
348 | });
|
349 |
|
350 |
|
351 | _doneNext.apply(this);
|
352 | });
|
353 |
|
354 | this.gulp.on('task_err', (e) => {
|
355 | self.server && self.server.stop();
|
356 | T.log.error(
|
357 | `'${e.task}' errored after ${prettyTime(
|
358 | e.hrDuration
|
359 | )} \n\r ${Gupack.formatError(e)}`
|
360 | );
|
361 | });
|
362 |
|
363 | this.gulp.on('task_not_found', (err) => {
|
364 | self.server && self.server.stop();
|
365 | T.log.error(
|
366 | `Task \'' + err.task + '\' is not found \n\r Please check the documentation for proper gupack-config file formatting`
|
367 | );
|
368 | });
|
369 |
|
370 | function _doneNext() {
|
371 | this.emit('finish_all_task_next_before', {
|
372 | gupack: this
|
373 | });
|
374 |
|
375 |
|
376 | this.devServer && this.startServer();
|
377 |
|
378 | const deploies = extend(true, [], this.deploies);
|
379 | if (this.isDeploy) {
|
380 | if (deploies.length > 0) {
|
381 | T.log.yellow(
|
382 | `→ has deploys. but now skip deploy(arg: --skip-deploy).`
|
383 | );
|
384 | }
|
385 | return T.log.end();
|
386 | }
|
387 | if (!this.deployManager) {
|
388 | this.deployManager = new DeployManager(deploies);
|
389 | } else {
|
390 | this.deployManager.setDeploy(deploies);
|
391 | }
|
392 | this.deployManager.startDeploy(isSkipBackup, this.watch);
|
393 | this.emit('finish_all_task_next_after', {
|
394 | gupack: this
|
395 | });
|
396 | this.isAllTaskDone = true;
|
397 | }
|
398 | }
|
399 |
|
400 | startServer() {
|
401 | if (!this.devServer || this.runIndex) return;
|
402 | process.nextTick(() => {
|
403 |
|
404 | if (util.isFunction(this.devServer)) {
|
405 | this.devServer.call(this);
|
406 | }
|
407 | if (this.devServer === true) {
|
408 | this.server = require('../gupackServer');
|
409 | this.server.createLiveServer(this);
|
410 | }
|
411 | this.runIndex++;
|
412 | });
|
413 | }
|
414 |
|
415 | setDefaultTask() {
|
416 | this.gulp.task('default', this.tasks);
|
417 |
|
418 | }
|
419 |
|
420 | run() {
|
421 | let _run = () => {
|
422 | if (!this.isAllTaskDone) {
|
423 | return T.log.yellow(`» The last task has not been completed )====( `);
|
424 | }
|
425 | this.addEvent();
|
426 |
|
427 | this.setDefaultTask();
|
428 | this.gulp.start.apply(this.gulp, this.tasks);
|
429 | };
|
430 |
|
431 | if (this.startClean) {
|
432 | Gupack.cleanBuildDir((e) => {
|
433 | _run();
|
434 | }, this.buildDir);
|
435 | } else {
|
436 | _run();
|
437 | }
|
438 | }
|
439 |
|
440 | runTask(name) {
|
441 | let ts = [];
|
442 | if (util.isArray(name)) {
|
443 | name.forEach((nm) => {
|
444 | ts.push(nm);
|
445 | });
|
446 | } else {
|
447 | ts = [name];
|
448 | }
|
449 |
|
450 | ts = ts.filter((t) => {
|
451 | let isIndex = util.isObject(this.buildTasks[t]);
|
452 | if (!isIndex) T.log.red(`× not fount task: '${t}'`);
|
453 | return isIndex;
|
454 | });
|
455 |
|
456 | if (this.startClean) {
|
457 | ts.forEach((t) => {
|
458 | let taskNode = this.buildTasks[t],
|
459 | dest = taskNode.dest;
|
460 | if (!T.fsa.removeSync(dest))
|
461 | T.log.green(`√ '${dest}' clean successfully !!!`);
|
462 | else T.log.green(`× '${dest}' clean failed (╯︵╰,)`);
|
463 | });
|
464 | }
|
465 |
|
466 | if (ts.length === 0) {
|
467 | T.log.error(`× ${new TypeError('not fount task')}`);
|
468 | } else {
|
469 | this.isRunAloneTask = true;
|
470 | this.addEvent();
|
471 | this.gulp.task('default', ts);
|
472 | this.gulp.start.apply(this.gulp, ts);
|
473 | }
|
474 | }
|
475 |
|
476 | runDeploy() {
|
477 | this.deployManager.startDeploy(isSkipBackup, false);
|
478 | }
|
479 |
|
480 | runBackup() {
|
481 | this.deployManager.startBackup(null);
|
482 | }
|
483 |
|
484 | runRollback() {
|
485 | this.deployManager.startRollback();
|
486 | }
|
487 |
|
488 | tree() {
|
489 | const str = [];
|
490 | const self = this;
|
491 |
|
492 | function getRelys(task = []) {
|
493 | task.forEach((rely) => {
|
494 | str.push(T.msg.gray(`\n -→ ${rely}`));
|
495 | if (self.relies[rely]) {
|
496 | getRelys(self.relies[rely]);
|
497 | }
|
498 | });
|
499 | }
|
500 |
|
501 | Object.keys(this.relies).forEach((name, i) => {
|
502 | str.push(T.msg.green(`\n<${i + 1}>${name}`));
|
503 | this.relies[name] && getRelys(this.relies[name]);
|
504 | });
|
505 | console.log(str.join(''));
|
506 | }
|
507 |
|
508 | static cleanBuildDir(cb, dist) {
|
509 | let startDate = Date.now();
|
510 | T.log.green('→ clean starting ......');
|
511 | T.fsa.remove(T.Path.join(dist, '**/*'), (err) => {
|
512 | if (err) {
|
513 | T.log.error('× clean failed: ' + err.toString());
|
514 | } else {
|
515 | let time = (Date.now() - startDate) * 0.001;
|
516 | time = time.toFixed(2) + ' s';
|
517 | T.log.green(`√ clean successfully !!!, after ${T.msg.yellow(time)}`);
|
518 | cb && cb();
|
519 | }
|
520 | });
|
521 | }
|
522 | static formatError(e) {
|
523 | if (!e.err) {
|
524 | return e.message;
|
525 | }
|
526 | if (typeof e.err.showStack === 'boolean') {
|
527 | return e.err.toString();
|
528 | }
|
529 | if (e.err.stack) {
|
530 | return e.err.stack;
|
531 | }
|
532 | return new Error(String(e.err)).stack;
|
533 | }
|
534 | }
|
535 |
|
536 | module.exports = Gupack;
|