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