1 | 'use strict';
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | let Mustache;
|
7 | let downloadRepo;
|
8 |
|
9 |
|
10 |
|
11 | const cp = require('child_process');
|
12 | const lp = require('log-pose');
|
13 | const fs = require('fs-extra');
|
14 | const path = require('path');
|
15 | const glob = require('glob');
|
16 | const rimraf = require('rimraf');
|
17 | const _ = require('./constants');
|
18 |
|
19 |
|
20 | const reTransformHelper = /\{\{\s*([a-z]\w+)\s+([.\w]+)\s*\}\}/g;
|
21 |
|
22 | function _merge(obj) {
|
23 | const args = Array.prototype.slice.call(arguments, 1);
|
24 |
|
25 | args.forEach(data => {
|
26 | Object.keys(data).forEach(key => {
|
27 | obj[key] = data[key];
|
28 | });
|
29 | });
|
30 |
|
31 | return obj;
|
32 | }
|
33 |
|
34 | function _render(str) {
|
35 | const args = Array.prototype.slice.call(arguments, 1);
|
36 | const obj = args.reduce((prev, x) => _merge(prev, x), {});
|
37 |
|
38 | Mustache = Mustache || require('mustache');
|
39 |
|
40 | return Mustache.render(str.replace(reTransformHelper, '{{#$1}}$2{{/$1}}'), obj);
|
41 | }
|
42 |
|
43 | function _prompt($, input) {
|
44 | const enquirer = $.haki.getEnquirer();
|
45 |
|
46 |
|
47 | lp.pause();
|
48 |
|
49 | return enquirer.prompt(input.map(p => {
|
50 | p.message = p.message || p.name;
|
51 | p.type = p.type || 'input';
|
52 |
|
53 |
|
54 | if (p.options) {
|
55 | p.choices = p.options.map(c => {
|
56 | c = typeof c === 'string' ? { name: c } : c;
|
57 | c.message = c.message || c.name;
|
58 | return c;
|
59 | });
|
60 |
|
61 | delete p.options;
|
62 | }
|
63 |
|
64 | return p;
|
65 | }))
|
66 | .then(response => {
|
67 | lp.resume();
|
68 |
|
69 |
|
70 | if (!Object.keys(response).length) {
|
71 | throw new Error('Missing input');
|
72 | }
|
73 |
|
74 | const out = input.reduce((prev, cur) => {
|
75 |
|
76 | if (typeof response[cur.name] === 'undefined') {
|
77 | throw new Error(`Invalid ${cur.name} input`);
|
78 | }
|
79 |
|
80 | const found = cur.choices && cur.choices
|
81 | .find(x => x.name === response[cur.name]);
|
82 |
|
83 | prev[cur.name] = found ? (found.value || found.name) : response[cur.name];
|
84 |
|
85 | return prev;
|
86 | }, {});
|
87 |
|
88 | return out;
|
89 | })
|
90 | .catch(error => {
|
91 | lp.resume();
|
92 | throw error;
|
93 | });
|
94 | }
|
95 |
|
96 | function _exec(cmd, currentPath) {
|
97 | return new Promise((resolve, reject) => {
|
98 | const env = {};
|
99 |
|
100 |
|
101 | env.PATH = process.env.PATH;
|
102 |
|
103 | const ps = Array.isArray(cmd)
|
104 | ? cp.spawn(cmd[0], cmd.slice(1), { env, cwd: currentPath })
|
105 | : cp.exec(cmd, { env, cwd: currentPath });
|
106 |
|
107 | let stderr = '';
|
108 | let stdout = '';
|
109 |
|
110 | ps.stdout.on('data', data => {
|
111 | stdout += data;
|
112 | });
|
113 |
|
114 | ps.stderr.on('data', data => {
|
115 | stderr += data;
|
116 | });
|
117 |
|
118 | ps.on('close', code => {
|
119 | if (code === 0) {
|
120 | resolve(stdout);
|
121 | } else {
|
122 | reject(new Error(stderr));
|
123 | }
|
124 | });
|
125 | });
|
126 | }
|
127 |
|
128 | function _askIf($, err, label, options, skipFlag) {
|
129 | return Promise.resolve()
|
130 | .then(() => {
|
131 |
|
132 | if (!err) {
|
133 | return;
|
134 | }
|
135 |
|
136 |
|
137 | if (skipFlag) {
|
138 | return 'skip';
|
139 | }
|
140 |
|
141 | return _prompt($, [{
|
142 | options,
|
143 | name: 'action',
|
144 | type: 'select',
|
145 | message: label,
|
146 | default: options[0].name,
|
147 | }]).then(({ action }) => action);
|
148 | });
|
149 | }
|
150 |
|
151 | function _install($, task, logger, quietly, destPath) {
|
152 | const result = { type: 'install' };
|
153 |
|
154 | const tasks = [];
|
155 |
|
156 | _.DEPS.forEach(key => {
|
157 | const args = [];
|
158 |
|
159 |
|
160 | if (key === 'optionalDependencies' && $.options.installOpts === false) {
|
161 | return;
|
162 | }
|
163 |
|
164 |
|
165 | if (key === 'devDependencies' && $.options.installDev === false) {
|
166 | return;
|
167 | }
|
168 |
|
169 |
|
170 | if (key === 'dependencies' && $.options.install === false) {
|
171 | return;
|
172 | }
|
173 |
|
174 |
|
175 | if (typeof task[key] === 'undefined') {
|
176 | return;
|
177 | }
|
178 |
|
179 |
|
180 | (task[key] || []).slice().forEach(dep => {
|
181 | if (Array.isArray(dep)) {
|
182 | Array.prototype.push.apply(args, dep.filter(x => x));
|
183 | } else if (dep) {
|
184 | args.push(dep);
|
185 | }
|
186 | });
|
187 |
|
188 |
|
189 | const _deps = args.slice();
|
190 |
|
191 | tasks.push(() => {
|
192 |
|
193 | if (args.length && $.options.yarn === true) {
|
194 | args.unshift('add');
|
195 | }
|
196 |
|
197 |
|
198 | if ($.options.yarn !== true) {
|
199 | args.unshift('install');
|
200 | }
|
201 |
|
202 | args.unshift($.options.yarn !== true ? 'npm' : 'yarn');
|
203 |
|
204 |
|
205 | if (args.length > 2) {
|
206 | if (key === 'devDependencies') {
|
207 | args.push(`--${$.options.yarn !== true ? 'save-dev' : 'dev'}`);
|
208 | } else if (key === 'optionalDependencies') {
|
209 | args.push(`--${$.options.yarn !== true ? 'save-optional' : 'optional'}`);
|
210 | } else if ($.options.yarn !== true) {
|
211 | args.push('--save');
|
212 | }
|
213 | }
|
214 |
|
215 | args.push('--silent');
|
216 | args.push('--no-progress');
|
217 |
|
218 | if (key !== 'optionalDependencies') {
|
219 | if ($.options.yarn !== true) {
|
220 | args.push('--no-optional');
|
221 | } else {
|
222 | args.push('--ignore-optional');
|
223 | }
|
224 | }
|
225 |
|
226 | return logger('install', _deps.join(' '), end => _exec(args, destPath)
|
227 | .then(data => {
|
228 |
|
229 | if (task[key]) {
|
230 | result[key] = task[key];
|
231 | }
|
232 |
|
233 | if (!end) {
|
234 | logger.printf(`\r\r${data.replace(/\n+$/, '\n')}`);
|
235 | } else if (!quietly) {
|
236 | end(() => logger.printf(`\r\r${data.replace(/\n+$/, '\n')}`));
|
237 | } else {
|
238 | end();
|
239 | }
|
240 | }));
|
241 | });
|
242 | });
|
243 |
|
244 | return tasks
|
245 | .reduce((prev, cur) => prev.then(() => cur()),
|
246 | Promise.resolve()).then(() => result);
|
247 | }
|
248 |
|
249 | function _runTask($, task, logger, _helpers) {
|
250 | const _values = {};
|
251 |
|
252 | const _changes = [];
|
253 | const _failures = [];
|
254 |
|
255 |
|
256 | const options = $.options || {};
|
257 | const defaults = $.defaults || {};
|
258 |
|
259 |
|
260 | Object.keys(defaults).forEach(key => {
|
261 |
|
262 | if (typeof task.validate === 'object' && typeof task.validate[key] === 'function') {
|
263 | const test = task.validate[key](defaults[key]);
|
264 |
|
265 |
|
266 | if (test !== true) {
|
267 | throw new Error(test || `Invalid input for '${key}'`);
|
268 | }
|
269 | }
|
270 |
|
271 | _values[key] = defaults[key];
|
272 | });
|
273 |
|
274 |
|
275 | if (typeof task === 'function') {
|
276 | task = task(_values, $.haki) || {};
|
277 | }
|
278 |
|
279 |
|
280 | if (task.arguments) {
|
281 |
|
282 | if (!$.options.data) {
|
283 | throw new Error('Missing data for arguments');
|
284 | }
|
285 |
|
286 | task.arguments.forEach(key => {
|
287 | _values[key] = $.options.data.shift();
|
288 | });
|
289 | }
|
290 |
|
291 |
|
292 | let _actions = task.actions || [];
|
293 |
|
294 |
|
295 | const run = () => Promise.resolve()
|
296 | .then(() => {
|
297 | let _prompts = task.prompts || [];
|
298 |
|
299 |
|
300 | if (typeof _prompts === 'function') {
|
301 | _prompts = _prompts(_values, $.haki) || [];
|
302 | }
|
303 |
|
304 |
|
305 | if (typeof _prompts.then === 'function') {
|
306 | return _prompts;
|
307 | }
|
308 |
|
309 |
|
310 | _prompts = _prompts
|
311 | .filter(p => {
|
312 |
|
313 | if (typeof _values[p.name] === 'undefined') {
|
314 |
|
315 | if (!p.validate
|
316 | && typeof task.validate === 'object'
|
317 | && typeof task.validate[p.name] === 'function') {
|
318 | p.validate = task.validate[p.name];
|
319 | }
|
320 |
|
321 | return true;
|
322 | }
|
323 |
|
324 | return false;
|
325 | });
|
326 |
|
327 | return (_prompts.length && _prompt($, _prompts)) || undefined;
|
328 | })
|
329 | .then(response => {
|
330 |
|
331 | Object.assign(_values, response);
|
332 |
|
333 |
|
334 | if (typeof _actions === 'function') {
|
335 | _actions = _actions.call($.haki, _values, options) || [];
|
336 | }
|
337 |
|
338 |
|
339 | if (typeof _actions.then === 'function') {
|
340 | return _actions;
|
341 | }
|
342 |
|
343 | logger.printf('\r{% wait Loading %s task%s ... %}\r\r', _actions.length, _actions.length === 1 ? '' : 's');
|
344 |
|
345 | return _actions.reduce((prev, a) => {
|
346 |
|
347 | if (!a) {
|
348 | return prev;
|
349 | }
|
350 |
|
351 | let _tpl;
|
352 | let _src;
|
353 | let _dest;
|
354 |
|
355 | Object.keys(_.SHORTHANDS).forEach(key => {
|
356 |
|
357 | if (a[key]) {
|
358 | a.type = key;
|
359 | a[_.SHORTHANDS[key]] = a[key];
|
360 | }
|
361 | });
|
362 |
|
363 | const _srcPath = () => {
|
364 |
|
365 | if (!(a.src && typeof a.src === 'string')) {
|
366 | throw new Error(`Invalid src, given '${a.src}'`);
|
367 | }
|
368 |
|
369 |
|
370 | if (a.src.indexOf('*') !== -1 || (a.src.indexOf('{') && a.src.indexOf('}'))) {
|
371 | return glob.sync(a.src, { cwd: task.basePath || '' }).map(x => path.join(task.basePath || '', x));
|
372 | }
|
373 |
|
374 |
|
375 | if (!fs.existsSync(path.join(task.basePath || '', a.src))) {
|
376 | throw new Error(`Source '${a.src}' does not exists`);
|
377 | }
|
378 |
|
379 | return [path.join(task.basePath || '', a.src)];
|
380 | };
|
381 |
|
382 | const _destPath = () => {
|
383 |
|
384 | if (!(a.dest && typeof a.dest === 'string')) {
|
385 | throw new Error(`Invalid dest, given '${a.dest}'`);
|
386 | }
|
387 |
|
388 | return path.join($.cwd, _render(a.dest, _values, _helpers, _.HELPERS));
|
389 | };
|
390 |
|
391 | const _getTemplate = () => {
|
392 |
|
393 | if (!(typeof a.template === 'undefined' && typeof a.templateFile === 'undefined')) {
|
394 | const tpl = a.templateFile
|
395 | ? fs.readFileSync(path.join(task.basePath || '', a.templateFile)).toString()
|
396 | : a.template;
|
397 |
|
398 | return _render(tpl, _values, _helpers, _.HELPERS);
|
399 | }
|
400 |
|
401 | return a.content;
|
402 | };
|
403 |
|
404 | const _sourceFiles = () => {
|
405 | if (typeof _src === 'string' && fs.statSync(_src).isDirectory()) {
|
406 | return glob.sync(`${_src}/**/*`, { dot: true, nodir: true });
|
407 | }
|
408 |
|
409 | return !Array.isArray(_src)
|
410 | ? [_src]
|
411 | : _src;
|
412 | };
|
413 |
|
414 | const _repository = () => {
|
415 | const _url = a.gitUrl ? _render(a.gitUrl, _values, _helpers, _.HELPERS) : '';
|
416 |
|
417 |
|
418 | if (!(_url && _url.indexOf('/') > 0)) {
|
419 | throw new Error(`Invalid gitUrl, given '${_url}'`);
|
420 | }
|
421 |
|
422 | return _url;
|
423 | };
|
424 |
|
425 | return prev.then(() => {
|
426 |
|
427 | if (typeof a === 'function') {
|
428 | return Promise.resolve(a.call($.haki, _values, options));
|
429 | }
|
430 |
|
431 | const skipMe = a.skipIfExists || task.skipIfExists;
|
432 |
|
433 | switch (a.type) {
|
434 | case 'copy': {
|
435 | _src = _srcPath();
|
436 |
|
437 | let _skipAll = false;
|
438 | let _replaceAll = false;
|
439 |
|
440 | return options.copy !== false && _sourceFiles().reduce((_prev, cur, i) => _prev.then(() => {
|
441 | _dest = path.join($.cwd, _render(a.dest || '', _values, _helpers, _.HELPERS), path.relative(path.dirname(_src[i]), cur));
|
442 |
|
443 | return logger('write', path.relative($.cwd, _dest), end => _askIf($,
|
444 | _skipAll || _replaceAll ? false : fs.existsSync(_dest) && options.force !== true,
|
445 | `Replace '${path.relative($.cwd, _dest)}'`,
|
446 | _.MULTIPLE_REPLACE_CHOICES, skipMe)
|
447 | .then(result => {
|
448 |
|
449 | if (result === 'abort') {
|
450 | throw new Error(`Source '${path.relative($.cwd, _dest)}' won't be copied!`);
|
451 | }
|
452 |
|
453 |
|
454 | if (result === 'replaceAll') {
|
455 | _replaceAll = true;
|
456 | }
|
457 |
|
458 |
|
459 | if (result === 'skipAll') {
|
460 | _skipAll = true;
|
461 | }
|
462 |
|
463 |
|
464 | if (!result || _replaceAll || result === 'replace') {
|
465 | fs.outputFileSync(_dest, _render(fs.readFileSync(cur).toString(), _values, _helpers, _.HELPERS));
|
466 | }
|
467 |
|
468 |
|
469 | if (end) {
|
470 | if (_skipAll || result === 'skip') {
|
471 | end(path.relative($.cwd, _dest), 'skip', 'end');
|
472 | } else {
|
473 | end();
|
474 | }
|
475 | }
|
476 | }));
|
477 | }), Promise.resolve());
|
478 | }
|
479 |
|
480 | case 'modify':
|
481 | _dest = _destPath();
|
482 |
|
483 | return logger('change', path.relative($.cwd, _dest), end => {
|
484 | const isAfter = !!a.after;
|
485 | const pattern = a.after || a.before || a.pattern;
|
486 |
|
487 |
|
488 | if (!(pattern && (typeof pattern === 'string' || pattern instanceof RegExp))) {
|
489 | throw new Error(`Invalid pattern, given '${pattern}'`);
|
490 | }
|
491 |
|
492 | _tpl = _getTemplate() || '';
|
493 | _dest = _destPath();
|
494 |
|
495 |
|
496 | if (!fs.existsSync(_dest)) {
|
497 |
|
498 | if (!a.defaultContent) {
|
499 | throw new Error(`Missing ${path.relative($.cwd, _dest)} file`);
|
500 | }
|
501 |
|
502 | fs.outputFileSync(_dest, _render(a.defaultContent, _values, _helpers, _.HELPERS));
|
503 | }
|
504 |
|
505 | _changes.push({
|
506 | type: a.type,
|
507 | dest: path.relative($.cwd, _dest),
|
508 | });
|
509 |
|
510 | const unless = typeof a.unless === 'string'
|
511 | ? new RegExp(_render(a.unless, _values, _helpers, _.HELPERS))
|
512 | : a.unless;
|
513 |
|
514 | const content = fs.readFileSync(_dest).toString();
|
515 |
|
516 |
|
517 | if (a.unless && (unless instanceof RegExp && unless.test(content))) {
|
518 |
|
519 | if (end) {
|
520 | end(path.relative($.cwd, _dest), 'skip', 'end');
|
521 | }
|
522 | return;
|
523 | }
|
524 |
|
525 | const regexp = !(pattern instanceof RegExp)
|
526 | ? new RegExp(_render(pattern, _values, _helpers, _.HELPERS))
|
527 | : pattern;
|
528 |
|
529 |
|
530 | if (a.deleteContent && !regexp.test(content)) {
|
531 |
|
532 | if (end) {
|
533 | end(path.relative($.cwd, _dest), 'skip', 'end');
|
534 | }
|
535 | return;
|
536 | }
|
537 |
|
538 | const output = a.deleteContent
|
539 | ? content.replace(regexp, '')
|
540 | : content.replace(regexp, isAfter ? `$&${_tpl}` : `${_tpl}$&`);
|
541 |
|
542 | fs.outputFileSync(_dest, output);
|
543 |
|
544 |
|
545 | if (end) {
|
546 | end();
|
547 | }
|
548 | });
|
549 |
|
550 | case 'extend':
|
551 | _dest = _destPath();
|
552 |
|
553 | return logger('extend', path.relative($.cwd, _dest), () => {
|
554 |
|
555 | if (typeof a.callback !== 'function') {
|
556 | throw new Error(`Invalid callback, given '${a.callback}'`);
|
557 | }
|
558 |
|
559 | const data = fs.existsSync(_dest)
|
560 | ? fs.readJsonSync(_dest)
|
561 | : {};
|
562 |
|
563 | _changes.push({
|
564 | type: a.type,
|
565 | dest: path.relative($.cwd, _dest),
|
566 | });
|
567 |
|
568 | const _utils = _merge({}, _helpers, _.HELPERS);
|
569 |
|
570 | Object.keys(_utils).forEach(k => {
|
571 | if (typeof _values[k] === 'undefined') {
|
572 | _values[k] = v => _utils[k]()(v, y => y.substr(3, y.length - 6));
|
573 | }
|
574 | });
|
575 |
|
576 | a.callback(data, _values);
|
577 | fs.outputJsonSync(_dest, data, {
|
578 | spaces: 2,
|
579 | });
|
580 | });
|
581 |
|
582 | case 'clone':
|
583 | downloadRepo = downloadRepo || require('download-github-repo');
|
584 |
|
585 | _src = _repository();
|
586 | _dest = _destPath();
|
587 |
|
588 | return options.clone !== false && logger('clone', _src, end => _askIf($, (fs.existsSync(_dest)
|
589 | ? fs.readdirSync(_dest).length !== 0 : false) && options.force !== true,
|
590 | `Overwrite '${path.relative($.cwd, _dest) || '.'}' with '${_src}'`,
|
591 | _.SINGLE_REPLACE_CHOICES, skipMe)
|
592 | .then(result => new Promise((resolve, reject) => {
|
593 |
|
594 | if (result === 'abort') {
|
595 | reject(new Error(`Repository '${_src}' won't be cloned!`));
|
596 | return;
|
597 | }
|
598 |
|
599 |
|
600 | if (result === 'skip') {
|
601 | resolve();
|
602 | return;
|
603 | }
|
604 |
|
605 | downloadRepo(_src, _dest, err => {
|
606 | if (err) {
|
607 | reject(new Error(`Not found https://github.com/${_src}`));
|
608 | } else {
|
609 | _changes.push({
|
610 | type: a.type,
|
611 | repository: _src,
|
612 | });
|
613 | resolve();
|
614 | }
|
615 | });
|
616 | }).then(() => end && end(`${path.relative($.cwd, _dest) || '.'} (${_src})`))));
|
617 |
|
618 | case 'add':
|
619 | _tpl = _getTemplate() || '';
|
620 | _dest = _destPath();
|
621 |
|
622 |
|
623 | return options.add !== false && logger('write', path.relative($.cwd, _dest), end => _askIf($, fs.existsSync(_dest) && options.force !== true,
|
624 | `Replace '${path.relative($.cwd, _dest)}'`,
|
625 | _.SINGLE_REPLACE_CHOICES, skipMe)
|
626 | .then(result => {
|
627 |
|
628 | if (result === 'abort') {
|
629 | throw new Error(`Source '${path.relative($.cwd, _dest)}' won't be added!`);
|
630 | }
|
631 |
|
632 |
|
633 | if (!result || result === 'replace') {
|
634 | _changes.push({
|
635 | type: a.type,
|
636 | dest: path.relative($.cwd, _dest),
|
637 | });
|
638 |
|
639 | fs.outputFileSync(_dest, _tpl);
|
640 | }
|
641 |
|
642 |
|
643 | if (end && result === 'skip') {
|
644 | return end(path.relative($.cwd, _dest), 'skip', 'end');
|
645 | }
|
646 |
|
647 | if (end) {
|
648 | end();
|
649 | }
|
650 | }));
|
651 |
|
652 | case 'exec':
|
653 |
|
654 | if (!(a.command && (typeof a.command === 'string'))) {
|
655 | throw new Error(`Invalid command, given '${a.command}'`);
|
656 | }
|
657 |
|
658 | a.command = _render(a.command || '', _values, _helpers, _.HELPERS);
|
659 |
|
660 | return options.exec !== false && logger('exec', a.command, end => _exec(a.command)
|
661 | .then(result => {
|
662 | _changes.push({
|
663 | type: a.type,
|
664 | stdOut: result,
|
665 | });
|
666 |
|
667 | if (!end) {
|
668 | logger.printf(`\r\r${result.replace(/\n+$/, '\n')}`);
|
669 | } else if (!(options.quiet || a.quiet)) {
|
670 | end(() => logger.printf(`\r\r${result.replace(/\n+$/, '\n')}`));
|
671 | } else {
|
672 | end();
|
673 | }
|
674 | }));
|
675 |
|
676 | case 'clean':
|
677 | _dest = _destPath();
|
678 |
|
679 | return logger('clean', path.relative($.cwd, _dest), end => _askIf($, options.force !== true,
|
680 | `Delete '${path.relative($.cwd, _dest) || '.'}'`,
|
681 | _.SINGLE_DELETE_CHOICES).then(result => {
|
682 |
|
683 | if (result === 'abort') {
|
684 | throw new Error(`Output '${path.relative($.cwd, _dest) || '.'}' won't be destroyed!`);
|
685 | }
|
686 |
|
687 |
|
688 | if (result === 'skip') {
|
689 |
|
690 | if (end) {
|
691 | end(path.relative($.cwd, _dest), 'skip', 'end');
|
692 | }
|
693 | return;
|
694 | }
|
695 |
|
696 | rimraf.sync(_dest);
|
697 |
|
698 | if (end) {
|
699 | end();
|
700 | }
|
701 | }));
|
702 |
|
703 |
|
704 |
|
705 | case 'render':
|
706 | return (Array.isArray(a.dest) ? a.dest : [a.dest]).forEach(dest => {
|
707 | _dest = path.join($.cwd, _render(dest, _values, _helpers, _.HELPERS));
|
708 | _tpl = _render(fs.readFileSync(_dest).toString(), _values, _helpers, _.HELPERS);
|
709 |
|
710 | fs.outputFileSync(_dest, _tpl);
|
711 | });
|
712 |
|
713 | case 'install':
|
714 | return _install($, a, logger, a.quiet || options.quiet,
|
715 | path.join($.cwd, _render(a.dest || '', _values, _helpers, _.HELPERS)))
|
716 | .then(result => {
|
717 | _changes.push(result);
|
718 | });
|
719 |
|
720 | default:
|
721 | throw new Error(`Unsupported '${a.type || JSON.stringify(a)}' action`);
|
722 | }
|
723 | })
|
724 | .catch(err => {
|
725 | _failures.push(err);
|
726 |
|
727 |
|
728 | if (a.abortOnFail || task.abortOnFail) {
|
729 | throw err;
|
730 | }
|
731 |
|
732 | logger.printf('\r%s\r\n', (options.debug && err.stack) || err.message);
|
733 | });
|
734 | }, Promise.resolve());
|
735 | })
|
736 | .then(() => ({ values: _values, changes: _changes, failures: _failures }))
|
737 | .catch(error => {
|
738 |
|
739 | if (task.abortOnFail) {
|
740 | throw error;
|
741 | }
|
742 |
|
743 | return {
|
744 | error,
|
745 | values: _values,
|
746 | changes: _changes,
|
747 | failures: _failures,
|
748 | };
|
749 | });
|
750 |
|
751 |
|
752 | if (task.quiet || options.quiet) {
|
753 |
|
754 | const _logger = logger;
|
755 |
|
756 |
|
757 | logger = function $logger() {
|
758 | return Promise.resolve().then(() => Promise.all(Array.prototype.slice.call(arguments)
|
759 | .filter(cb => typeof cb === 'function')
|
760 | .map(cb => cb())));
|
761 | };
|
762 |
|
763 |
|
764 | logger.write = () => {};
|
765 | logger.printf = () => {};
|
766 |
|
767 |
|
768 | return _logger('Running tasks...', end => run().then(() => end && end('Tasks completed')));
|
769 | }
|
770 |
|
771 | return run();
|
772 | }
|
773 |
|
774 | module.exports = function Haki(cwd, options) {
|
775 | options = options || {};
|
776 |
|
777 |
|
778 | if (typeof cwd === 'object') {
|
779 | options = _merge(options, cwd);
|
780 | cwd = options.cwd;
|
781 | }
|
782 |
|
783 | const _helpers = {};
|
784 | const _tasks = {};
|
785 |
|
786 |
|
787 | const _logger = (lp.setLogger(options.stdout, options.stderr)
|
788 | .setLevel(options.verbose ? 3 : options.debug ? 2 : options.info ? 1 : options.log)
|
789 | .getLogger(options.depth));
|
790 |
|
791 |
|
792 | _logger.printf = _logger.printf || _logger.write;
|
793 |
|
794 |
|
795 | cwd = cwd || options.cwd || process.cwd();
|
796 |
|
797 | delete options.cwd;
|
798 |
|
799 | let _enquirer;
|
800 |
|
801 | return {
|
802 | load(file) {
|
803 |
|
804 | if (!(file && typeof file === 'string')) {
|
805 | throw new Error(`File must be a string, given '${file}'`);
|
806 | }
|
807 |
|
808 | try {
|
809 | file = require.resolve(file);
|
810 | } catch (e) {
|
811 | file = path.resolve(cwd, file);
|
812 | }
|
813 |
|
814 | require(file)(this);
|
815 |
|
816 | return this;
|
817 | },
|
818 |
|
819 | prompt(opts) {
|
820 |
|
821 | if (!(opts && typeof opts === 'object')) {
|
822 | throw new Error(`Prompt options are invalid, given '${opts}'`);
|
823 | }
|
824 |
|
825 | return _prompt({ haki: this }, !Array.isArray(opts) ? [opts] : opts);
|
826 | },
|
827 |
|
828 | getEnquirer() {
|
829 | if (!_enquirer) {
|
830 | const Enquirer = require('enquirer');
|
831 |
|
832 | _enquirer = new Enquirer({
|
833 | show: options.log !== false,
|
834 | stdout: options.stdout,
|
835 | stderr: options.stderr,
|
836 | });
|
837 | }
|
838 |
|
839 | return _enquirer;
|
840 | },
|
841 |
|
842 | getLogger() {
|
843 | return _logger;
|
844 | },
|
845 |
|
846 | getPath(dest) {
|
847 |
|
848 | if (dest && typeof dest !== 'string') {
|
849 | throw new Error(`Path must be a string, given '${dest}'`);
|
850 | }
|
851 |
|
852 | return path.join(cwd, dest || '');
|
853 | },
|
854 |
|
855 | addHelper(name, fn) {
|
856 |
|
857 | if (!(name && typeof name === 'string')) {
|
858 | throw new Error(`Helper name must be a string, given '${name}'`);
|
859 | }
|
860 |
|
861 |
|
862 | if (typeof fn !== 'function') {
|
863 | throw new Error(`Helper for '${name}' must be a function, given '${fn}'`);
|
864 | }
|
865 |
|
866 |
|
867 | _helpers[name] = () => (text, render) => fn(text, _expr => {
|
868 |
|
869 | if (!_expr) {
|
870 | throw new Error(`Missing expression for '${name}' helper`);
|
871 | }
|
872 |
|
873 | return render(!(_expr.charAt() === '{' && _expr.substr(_expr.length - 1, 1) === '}')
|
874 | ? `{{{${_expr}}}}`
|
875 | : _expr);
|
876 | });
|
877 |
|
878 | return this;
|
879 | },
|
880 |
|
881 | getHelperList() {
|
882 | return Object.keys(_helpers).concat(Object.keys(_.HELPERS));
|
883 | },
|
884 |
|
885 | renderString(value, data) {
|
886 |
|
887 | if (!(value && typeof value === 'string')) {
|
888 | throw new Error(`Template must be a string, given '${value}'`);
|
889 | }
|
890 |
|
891 | return _render(value, data || {}, _helpers, _.HELPERS);
|
892 | },
|
893 |
|
894 | setGenerator(name, opts) {
|
895 |
|
896 | if (!(name && typeof name === 'string')) {
|
897 | throw new Error(`Generator name must be a string, given '${name}'`);
|
898 | }
|
899 |
|
900 | _tasks[name] = opts || {};
|
901 | _tasks[name].run = defaults => this.runGenerator(_tasks[name], defaults);
|
902 |
|
903 | return this;
|
904 | },
|
905 |
|
906 | getGenerator(name) {
|
907 |
|
908 | if (!_tasks[name]) {
|
909 | throw new Error(`The '${name}' generator does not exists`);
|
910 | }
|
911 |
|
912 | return _tasks[name];
|
913 | },
|
914 |
|
915 | runGenerator(name, defaults) {
|
916 |
|
917 | if (typeof name === 'object') {
|
918 | return Promise.resolve()
|
919 | .then(() => _runTask({
|
920 | cwd,
|
921 | options,
|
922 | defaults,
|
923 | haki: this,
|
924 | }, name, _logger, _helpers));
|
925 | }
|
926 |
|
927 | return this.getGenerator(name).run(defaults);
|
928 | },
|
929 |
|
930 | hasGenerator(name) {
|
931 | return typeof _tasks[name] !== 'undefined';
|
932 | },
|
933 |
|
934 | getGeneratorList(hints) {
|
935 | return Object.keys(_tasks).map(t => ({
|
936 | name: t,
|
937 | message: (_tasks[t].description
|
938 | && (hints && `${t} - ${_tasks[t].description}`))
|
939 | || _tasks[t].description
|
940 | || t,
|
941 | }));
|
942 | },
|
943 |
|
944 | chooseGeneratorList(defaults) {
|
945 |
|
946 | if (!Object.keys(_tasks).length) {
|
947 | throw new Error('There are no registered generators');
|
948 | }
|
949 |
|
950 | return _prompt({ haki: this }, [{
|
951 | name: 'task',
|
952 | type: 'autocomplete',
|
953 | message: 'Choose a generator:',
|
954 | options: this.getGeneratorList(true),
|
955 | }]).then(({ task }) => this.runGenerator(task, defaults));
|
956 | },
|
957 | };
|
958 | };
|