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