1 | #!/usr/bin/env node
|
2 |
|
3 | 'use strict';
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | const path = require('path');
|
9 | const wargs = require('wargs');
|
10 |
|
11 |
|
12 | const die = process.exit.bind(process);
|
13 |
|
14 | const $ = require('./lib/utils');
|
15 |
|
16 | const DEFAULTS = {
|
17 | bundle: '**/index.js',
|
18 | public: 'public',
|
19 | output: 'build',
|
20 | env: process.env.NODE_ENV || 'development',
|
21 | port: process.env.PORT || 3000,
|
22 | };
|
23 |
|
24 | let _;
|
25 |
|
26 | try {
|
27 | _ = wargs(process.argv.slice(2), {
|
28 | boolean: 'qvVmdfhUACI',
|
29 | default: DEFAULTS,
|
30 | alias: {
|
31 | U: 'umd',
|
32 | A: 'amd',
|
33 | C: 'cjs',
|
34 | I: 'iife',
|
35 | b: 'bundle',
|
36 | q: 'quiet',
|
37 | W: 'public',
|
38 | O: 'output',
|
39 | e: 'env',
|
40 | h: 'help',
|
41 | c: 'config',
|
42 | v: 'version',
|
43 | V: 'verbose',
|
44 | x: 'exclude',
|
45 | l: 'plugins',
|
46 | L: 'devPlugins',
|
47 | r: 'reloader',
|
48 | m: 'minify',
|
49 | d: 'debug',
|
50 | f: 'force',
|
51 | w: 'watch',
|
52 | y: 'only',
|
53 | o: 'open',
|
54 | p: 'port',
|
55 | P: 'proxy',
|
56 | R: 'rename',
|
57 | G: 'globals',
|
58 | E: 'extensions',
|
59 | },
|
60 | });
|
61 | } catch (e) {
|
62 | $.errLog(`${e.message || e.toString()} (add --help for usage info)`);
|
63 | die(1);
|
64 | }
|
65 |
|
66 |
|
67 | const _level = _.flags.verbose ? 'verbose' : _.flags.debug ? 'debug' : 'info';
|
68 |
|
69 | const logger = require('log-pose')
|
70 | .setLevel((_.flags.quiet && !_.flags.version && !_.flags.help) ? false : _level)
|
71 | .getLogger(12, process.stdout, process.stderr);
|
72 |
|
73 | if (_.flags.debug && _.flags.verbose) {
|
74 | require('debug').enable('*');
|
75 | require('log-pose').setLevel(false);
|
76 | }
|
77 |
|
78 |
|
79 | const debug = require('debug')('tarima');
|
80 |
|
81 | const thisPkg = require(path.join(__dirname, '../package.json'));
|
82 |
|
83 | _.flags.env = (_.flags.env !== true ? _.flags.env : '') || 'development';
|
84 |
|
85 |
|
86 | process.name = 'tarima';
|
87 |
|
88 | process.env.NODE_ENV = _.flags.env;
|
89 |
|
90 | const gitDir = path.join(__dirname, '../.git');
|
91 |
|
92 | logger.printf('{% green %s v%s %} {% gray (node %s - %s%s) %}\n',
|
93 | thisPkg.name,
|
94 | thisPkg.version,
|
95 | process.version, $.isDir(gitDir) ? 'git:' : '', process.env.NODE_ENV);
|
96 |
|
97 | debug('v%s - node %s%s', thisPkg.version, process.version);
|
98 |
|
99 | if (_.flags.version) {
|
100 | die();
|
101 | }
|
102 |
|
103 | const _bin = Object.keys(thisPkg.bin)[0];
|
104 |
|
105 | if (_.flags.help) {
|
106 | logger.write(`
|
107 | Usage:
|
108 | ${_bin} [watch] ...
|
109 |
|
110 | Examples:
|
111 | ${_bin} app/assets js:es6 css:less
|
112 | ${_bin} src/**/*.js API_KEY=*secret* PORT=3000
|
113 | ${_bin} watch lib -R "**/*:{basedir/1}/{fname}" -R "**/mock:{basedir/2}/api/{fname}"
|
114 |
|
115 | Options:
|
116 | -e, --env Customization per environment (e.g. -e production)
|
117 |
|
118 | -O, --output Destination for generated files
|
119 | -W, --public Public directory for serving assets
|
120 | -c, --config Use configuration file (e.g. -c ./config.js)
|
121 | You may also specify a suffix, e.g. -c DEV will map to ./tarima.DEV.{js,json}
|
122 |
|
123 | -m, --minify Apply optimizations for final sources (uglify, csso)
|
124 | -l, --plugins Shorthand option for loading plugins (e.g. -l tarima-bower -l talavera)
|
125 | -L, --devPlugins Same as --plugins, development only (e.g. -L tarima-lr)
|
126 |
|
127 | -o, --open Open browser (requires browser-sync/live-reload, see below)
|
128 | -p, --port Enable custom port for serving files (e.g. tarima-lr)
|
129 | -P, --proxy Enable proxying for local server (e.g. tarima-lr)
|
130 |
|
131 | -f, --force Force rendering/bundling of all given sources
|
132 | -b, --bundle Scripts matching this will be bundled (e.g. -b "**/main/*.js")
|
133 | -U, --umd Save bundles as UMD wrapper
|
134 | -A, --amd Save bundles as AMD wrapper
|
135 | -C, --cjs Save bundles as CommonJS wrapper
|
136 | -I, --iife Save bundles as IIFE wrapper (default)
|
137 |
|
138 | -q, --quiet Minimize output logs
|
139 | -d, --debug Enable debug mode when transpiling
|
140 | -V, --verbose Enable verbose logs (use for trouble-shooting)
|
141 |
|
142 | -y, --only Filter out non-matching sources using src.indexOf("substr")
|
143 | -x, --exclude Filter out sources using globs (e.g. -x test/broken -x .coffee)
|
144 |
|
145 | Exclude patterns:
|
146 | - *foo -> !*foo
|
147 | - .bar -> !**/*.bar
|
148 | - x.y -> !**/x.y
|
149 | - foo -> !**/foo/**
|
150 | - foo/bar -> !**/foo/bar/**
|
151 |
|
152 | -r, --reloader Load module for reset stuff on changes (e.g. -r bin/server)
|
153 | -w, --watching Append additional directories for watch (e.g. -w _src -w bin)
|
154 |
|
155 | -E, --extensions Enable hidden extensions (e.g. -E .es6.js -E .post.css -E .js.hbs.pug)
|
156 | -G, --globals Shorthand for global variables (e.g. -G FOO=BAR -G AKI_PEY=xyz)
|
157 | -R, --rename Custom naming expressions (e.g. -R "**/*:{basedir/1}/{fname}")
|
158 |
|
159 | `);
|
160 | die(1);
|
161 | }
|
162 |
|
163 | function _debug(e) {
|
164 | return (_.flags.verbose && e.stack) || e.toString();
|
165 | }
|
166 |
|
167 | const run = (opts, cb) => {
|
168 | const _runner = require('./lib');
|
169 |
|
170 | debug('settings %s', JSON.stringify(opts, null, 2));
|
171 |
|
172 |
|
173 | process.nextTick(() => {
|
174 | try {
|
175 | _runner(opts, logger, cb);
|
176 | } catch (e) {
|
177 | $.errLog(_debug(e));
|
178 | die(1);
|
179 | }
|
180 | });
|
181 | };
|
182 |
|
183 | const { spawn } = require('child_process');
|
184 |
|
185 |
|
186 | let mainPkg = {};
|
187 |
|
188 | const cwd = process.cwd();
|
189 | const pkg = path.join(cwd, 'package.json');
|
190 |
|
191 |
|
192 | const env = require('dotenv').config();
|
193 |
|
194 | if (env.error && env.error.code !== 'ENOENT') {
|
195 | $.errLog(env.error);
|
196 | die(1);
|
197 | }
|
198 |
|
199 | delete env.error;
|
200 |
|
201 | if ($.isFile(pkg)) {
|
202 | debug('config %s', pkg);
|
203 |
|
204 | mainPkg = $.readJSON(pkg);
|
205 | }
|
206 |
|
207 | let isWatching = false;
|
208 |
|
209 | if (_._[0] === 'watch') {
|
210 | isWatching = true;
|
211 | _._.shift();
|
212 | }
|
213 |
|
214 | const _src = _._;
|
215 |
|
216 | const defaultConfig = {
|
217 | cwd,
|
218 | watch: isWatching,
|
219 | force: _.flags.force === true,
|
220 | bundle: $.toArray(_.flags.bundle),
|
221 | plugins: $.toArray(_.flags.plugins),
|
222 | watching: $.toArray(_.flags.watching),
|
223 | devPlugins: $.toArray(_.flags.devPlugins),
|
224 | rename: $.toArray(_.flags.rename),
|
225 | from: _src,
|
226 | output: _.flags.output || DEFAULTS.output,
|
227 | public: _.flags.public || DEFAULTS.public,
|
228 | cacheFile: '.tarima',
|
229 | filter: [],
|
230 | notifications: {
|
231 | title: mainPkg.name || path.basename(cwd),
|
232 | okIcon: path.join(__dirname, 'ok.png'),
|
233 | errIcon: path.join(__dirname, 'err.png'),
|
234 | },
|
235 | bundleOptions: {
|
236 | globals: _.data,
|
237 | extensions: _.params,
|
238 | optimizations: _.flags.minify,
|
239 | },
|
240 | flags: _.flags,
|
241 | reloader: _.flags.reloader,
|
242 | };
|
243 |
|
244 |
|
245 | try {
|
246 | $.merge(defaultConfig, mainPkg.tarima || {});
|
247 | } catch (e) {
|
248 | $.errLog(`Configuration mismatch: ${_debug(e)}`);
|
249 | die(1);
|
250 | }
|
251 |
|
252 |
|
253 | let configFile = _.flags.config === true ? 'config' : _.flags.config;
|
254 |
|
255 | if (configFile && configFile.indexOf('.') === -1) {
|
256 | const fixedConfig = path.join(cwd, `tarima.${configFile}`);
|
257 |
|
258 | [`${fixedConfig}.js`, `${fixedConfig}.json`].forEach(file => {
|
259 | if ($.isFile(file)) {
|
260 | configFile = file;
|
261 | }
|
262 | });
|
263 | }
|
264 |
|
265 | if (configFile) {
|
266 | if (!$.isFile(configFile)) {
|
267 | logger.info('\r{% fail Missing file: %s %}\n', configFile);
|
268 | die(1);
|
269 | }
|
270 |
|
271 | logger.info('{% log Loading settings from %s %}\n', path.relative(cwd, configFile));
|
272 |
|
273 | debug('config %s', configFile);
|
274 |
|
275 | $.merge(defaultConfig, require(path.resolve(configFile)));
|
276 | }
|
277 |
|
278 |
|
279 | $.merge(defaultConfig.bundleOptions.extensions, defaultConfig.extensions || {});
|
280 |
|
281 | defaultConfig.bundleOptions.rollup = defaultConfig.bundleOptions.rollup || {};
|
282 |
|
283 |
|
284 | if (_.flags.umd) {
|
285 | defaultConfig.bundleOptions.rollup.format = 'umd';
|
286 | }
|
287 |
|
288 | if (_.flags.amd) {
|
289 | defaultConfig.bundleOptions.rollup.format = 'amd';
|
290 | }
|
291 |
|
292 | if (_.flags.cjs) {
|
293 | defaultConfig.bundleOptions.rollup.format = 'cjs';
|
294 | }
|
295 |
|
296 | if (_.flags.iife) {
|
297 | defaultConfig.bundleOptions.rollup.format = 'iife';
|
298 | }
|
299 |
|
300 | delete defaultConfig.extensions;
|
301 |
|
302 | function fixedValue(string) {
|
303 | if (/^-?\d+(\.\d+)?$/.test(string)) {
|
304 | return parseFloat(string);
|
305 | }
|
306 |
|
307 | const values = {
|
308 | true: true,
|
309 | false: false,
|
310 | };
|
311 |
|
312 | if (typeof values[string] !== 'undefined') {
|
313 | return values[string];
|
314 | }
|
315 |
|
316 | return string || null;
|
317 | }
|
318 |
|
319 | if (_.flags.only) {
|
320 | const test = $.toArray(_.flags.only);
|
321 |
|
322 | debug('--only %s', test.join(' '));
|
323 |
|
324 | defaultConfig.filter.push(value => {
|
325 | value = path.relative(cwd, value);
|
326 |
|
327 | for (let i = 0; i < test.length; i += 1) {
|
328 | if (value.indexOf(test[i]) > -1) {
|
329 | return true;
|
330 | }
|
331 | }
|
332 | });
|
333 | }
|
334 |
|
335 | if (_.flags.exclude) {
|
336 | const test = $.toArray(_.flags.exclude);
|
337 |
|
338 | debug('--exclude %s', test.join(' '));
|
339 |
|
340 | test.forEach(skip => {
|
341 | if (skip.indexOf('*') > -1) {
|
342 | defaultConfig.filter.push(`!${skip}`);
|
343 | } else if (skip.substr(0, 1) === '.') {
|
344 | defaultConfig.filter.push(`!**/*${skip}`);
|
345 | } else if (skip.indexOf('.') > -1) {
|
346 | defaultConfig.filter.push(`!**/${skip}`);
|
347 | } else {
|
348 | defaultConfig.filter.push(`!**/${skip}/**`);
|
349 | }
|
350 | });
|
351 | }
|
352 |
|
353 |
|
354 | const _globals = defaultConfig.globals || defaultConfig.env || {};
|
355 |
|
356 | $.merge(env, _globals);
|
357 | $.merge(env, _globals[_.flags.env] || {});
|
358 |
|
359 |
|
360 | Object.keys(process.env).forEach(key => {
|
361 | if (/^[A-Z][A-Z\d_]*$/.test(key) && typeof env[key] === 'undefined') {
|
362 | env[key] = process.env[key];
|
363 | }
|
364 | });
|
365 |
|
366 |
|
367 | defaultConfig.bundleOptions.locals = defaultConfig.bundleOptions.locals || {};
|
368 | defaultConfig.bundleOptions.locals.env = env;
|
369 | defaultConfig.bundleOptions.locals.pkg = mainPkg;
|
370 |
|
371 | Object.keys(env).forEach(key => {
|
372 | defaultConfig.bundleOptions.globals[key] = env[key];
|
373 | });
|
374 |
|
375 | if (_.flags.globals) {
|
376 | const test = $.toArray(_.flags.globals);
|
377 |
|
378 | debug('--globals %s', test.join(' '));
|
379 |
|
380 | test.forEach(value => {
|
381 | const parts = value.split('=');
|
382 |
|
383 | defaultConfig.bundleOptions.globals[parts[0]] = fixedValue(parts[1]);
|
384 | });
|
385 | }
|
386 |
|
387 | if (_.flags.extensions) {
|
388 | const test = $.toArray(_.flags.extensions);
|
389 |
|
390 | debug('--extensions %s', test.join(' '));
|
391 |
|
392 | test.forEach(exts => {
|
393 | const parts = exts.replace(/^\./, '').split('.').reverse();
|
394 |
|
395 | defaultConfig.bundleOptions.extensions[parts.shift()] = parts;
|
396 | });
|
397 | }
|
398 |
|
399 | defaultConfig.bundleOptions.compileDebug = _.flags.debug;
|
400 | defaultConfig.bundleOptions.verboseDebug = _.flags.verbose;
|
401 |
|
402 | const isDev = process.env.NODE_ENV === 'development' || isWatching;
|
403 |
|
404 | const cmd = _.raw || [];
|
405 |
|
406 | let child;
|
407 |
|
408 | function infoFiles(result) {
|
409 | if (isDev && result.output.length) {
|
410 | $.notify(`${result.output.length} file${result.output.length !== 1 ? 's' : ''}\n${result.output.slice(0, 3).join('\n')}`,
|
411 | defaultConfig.notifications.title,
|
412 | defaultConfig.notifications.okIcon);
|
413 | }
|
414 |
|
415 | if (!isWatching) {
|
416 | if (!result.output.length) {
|
417 | logger.printf('\r\r{% end Without changes %}\n');
|
418 | } else {
|
419 | logger.printf('\r\r{% end %s file%s written %}\n',
|
420 | result.output.length,
|
421 | result.output.length !== 1 ? 's' : '');
|
422 | }
|
423 | }
|
424 | }
|
425 |
|
426 | const _close = process.version.split('.')[1] === '6' ? 'exit' : 'close';
|
427 |
|
428 | function exec(onError) {
|
429 | function restart() {
|
430 |
|
431 | if (child) {
|
432 | child.kill('SIGINT');
|
433 | }
|
434 |
|
435 | const _cmd = cmd
|
436 | .map(arg => (arg.indexOf(' ') === -1 ? arg : `"${arg}"`)).join(' ');
|
437 |
|
438 | logger.printf('\r\r{% gray $ %s %}\r\n', _cmd);
|
439 |
|
440 | debug('exec %s', _cmd);
|
441 |
|
442 | child = spawn(cmd[0], cmd.slice(1), {
|
443 | cwd: defaultConfig.cwd || defaultConfig.output,
|
444 | detached: true,
|
445 | });
|
446 |
|
447 | child.stdout.pipe(process.stdout);
|
448 |
|
449 | const errors = [];
|
450 |
|
451 | child.stderr.on('data', data => {
|
452 | const line = data.toString().trim();
|
453 |
|
454 | if (line) {
|
455 | errors.push(line);
|
456 | }
|
457 | });
|
458 |
|
459 | child.on(_close, exitCode => {
|
460 | let message = `${_cmd}\n— `;
|
461 | let icon = defaultConfig.notifications.okIcon;
|
462 |
|
463 | if (exitCode || errors.length) {
|
464 | icon = defaultConfig.notifications.errIcon;
|
465 | message += 'Error';
|
466 | } else {
|
467 | message += 'Done';
|
468 | }
|
469 |
|
470 | $.notify(message, defaultConfig.notifications.title, icon);
|
471 |
|
472 | debug('exec %s - %s', exitCode, _cmd);
|
473 |
|
474 | if (errors.length) {
|
475 | $.errLog(errors.join('\n'));
|
476 | onError({ msg: errors.join('\n') });
|
477 | }
|
478 |
|
479 | if (exitCode && !isDev) {
|
480 | die(exitCode);
|
481 | }
|
482 |
|
483 | if (!isDev) {
|
484 | die();
|
485 | }
|
486 | });
|
487 | }
|
488 |
|
489 | return restart;
|
490 | }
|
491 |
|
492 | process.on('SIGINT', () => {
|
493 | logger.printf('\r\r');
|
494 |
|
495 | if (child) {
|
496 | child.kill('SIGINT');
|
497 | }
|
498 |
|
499 | die();
|
500 | });
|
501 |
|
502 | let _restart;
|
503 |
|
504 | process.nextTick(() => {
|
505 | if (!logger.isEnabled() && !(_.flags.debug && _.flags.verbose)) {
|
506 | process.stdout.write('\rProcessing sources...\r');
|
507 | }
|
508 |
|
509 | run(defaultConfig, function done(err, result) {
|
510 | if (err) {
|
511 | debug('failed %s', err);
|
512 |
|
513 | if (_.flags.quiet && err.filename) {
|
514 | $.errLog(`Failed source ${err.filename}`);
|
515 | }
|
516 |
|
517 | $.errLog(_debug(err));
|
518 |
|
519 | if (!isWatching) {
|
520 | die(1);
|
521 | }
|
522 |
|
523 | return;
|
524 | }
|
525 |
|
526 | if (!logger.isEnabled() && !(_.flags.debug && _.flags.verbose)) {
|
527 | process.stdout.write(`\r${result.output.length} file${
|
528 | result.output.length === 1 ? '' : 's'
|
529 | } ${result.output.length === 1 ? 'was' : 'were'} built\n`);
|
530 | }
|
531 |
|
532 | debug('done %s file%s added',
|
533 | result.output.length,
|
534 | result.output.length === 1 ? '' : 's');
|
535 |
|
536 | infoFiles(result);
|
537 |
|
538 | if (!isDev && err) {
|
539 | if (child) {
|
540 | child.kill();
|
541 | }
|
542 |
|
543 | die(1);
|
544 | }
|
545 |
|
546 | if (cmd.length && !err) {
|
547 | _restart = _restart || exec(this.emit.bind(null, 'error'));
|
548 | _restart();
|
549 | return;
|
550 | }
|
551 |
|
552 | if (isDev) {
|
553 | logger.printf('\r\r{% log Waiting for changes... %} {% gray [press CTRL-C to quit] %}\r');
|
554 | return;
|
555 | }
|
556 |
|
557 | if (!_.flags.reloader) {
|
558 | die();
|
559 | }
|
560 | });
|
561 | });
|
562 |
|
563 |
|
564 | process.on('exit', exitCode => {
|
565 | if (!isDev && !exitCode) {
|
566 | logger.write('\r\n');
|
567 | }
|
568 | });
|
569 |
|
570 | logger.info('{% log Output to: %} {% yellow %s %}\n', path.relative(cwd, defaultConfig.output) || '.');
|
571 |
|
572 | logger.info('{% log Reading from %} {% yellow %s %} {% gray source%s %}\n',
|
573 | defaultConfig.from.length,
|
574 | defaultConfig.from.length === 1 ? '' : 's');
|
575 |
|
576 | if (isWatching && defaultConfig.watching.length) {
|
577 | logger.info('{% log Watching from %} {% yellow %s %} {% gray source%s %}\n',
|
578 | defaultConfig.watching.length,
|
579 | defaultConfig.watching.length === 1 ? '' : 's');
|
580 | }
|