UNPKG

14.1 kBPlain TextView Raw
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
11const path = require('path');
12const wargs = require('wargs');
13
14// common helpers
15const die = process.exit.bind(process);
16
17const $ = require('./lib/utils');
18
19const 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
27let _;
28
29try {
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!
70const _level = _.flags.verbose ? 'verbose' : _.flags.debug ? 'debug' : 'info';
71
72const logger = require('log-pose')
73 .setLevel((_.flags.quiet && !_.flags.version && !_.flags.help) ? false : _level)
74 .getLogger(12, process.stdout, process.stderr);
75
76if (_.flags.debug && _.flags.verbose) {
77 require('debug').enable('*');
78 require('log-pose').setLevel(false);
79}
80
81// local debug
82const debug = require('debug')('tarima');
83
84const thisPkg = require(path.join(__dirname, '../package.json'));
85
86_.flags.env = (_.flags.env !== true ? _.flags.env : '') || 'development';
87
88// defaults
89process.name = 'tarima';
90
91process.env.NODE_ENV = _.flags.env;
92
93const gitDir = path.join(__dirname, '../.git');
94
95logger.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
100debug('v%s - node %s%s', thisPkg.version, process.version);
101
102if (_.flags.version) {
103 die();
104}
105
106const _bin = Object.keys(thisPkg.bin)[0];
107
108if (_.flags.help) {
109 logger.write(`
110Usage:
111 ${_bin} [watch] ...
112
113Examples:
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
118Options:
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
166function _debug(e) {
167 return (_.flags.verbose && e.stack) || e.toString();
168}
169
170const 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
186const spawn = require('child_process').spawn;
187
188// empty dummy
189let mainPkg = {};
190
191const cwd = process.cwd();
192const pkg = path.join(cwd, 'package.json');
193
194// load .env
195const env = require('dotenv').config();
196
197if (env.error && env.error.code !== 'ENOENT') {
198 $.errLog(env.error);
199 die(1);
200}
201
202delete env.error;
203
204if ($.isFile(pkg)) {
205 debug('config %s', pkg);
206
207 mainPkg = $.readJSON(pkg);
208}
209
210let isWatching = false;
211
212if (_._[0] === 'watch') {
213 isWatching = true;
214 _._.shift();
215}
216
217const _src = _._;
218
219const 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
248try {
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}
256let configFile = _.flags.config === true ? 'config' : _.flags.config;
257
258if (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
268if (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
284defaultConfig.bundleOptions.rollup = defaultConfig.bundleOptions.rollup || {};
285
286// setup rollup format
287if (_.flags.umd) {
288 defaultConfig.bundleOptions.rollup.format = 'umd';
289}
290
291if (_.flags.amd) {
292 defaultConfig.bundleOptions.rollup.format = 'amd';
293}
294
295if (_.flags.cjs) {
296 defaultConfig.bundleOptions.rollup.format = 'cjs';
297}
298
299if (_.flags.iife) {
300 defaultConfig.bundleOptions.rollup.format = 'iife';
301}
302
303delete defaultConfig.extensions;
304
305function 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
322if (_.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
338if (_.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
357const _globals = defaultConfig.globals || defaultConfig.env || {};
358
359$.merge(env, _globals);
360$.merge(env, _globals[_.flags.env] || {});
361
362// merge only ENV_VARS_IN_CAPS
363Object.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
370defaultConfig.bundleOptions.locals = defaultConfig.bundleOptions.locals || {};
371defaultConfig.bundleOptions.locals.env = env;
372defaultConfig.bundleOptions.locals.pkg = mainPkg;
373
374Object.keys(env).forEach(key => {
375 defaultConfig.bundleOptions.globals[key] = env[key];
376});
377
378if (_.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
390if (_.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
402defaultConfig.bundleOptions.compileDebug = _.flags.debug;
403defaultConfig.bundleOptions.verboseDebug = _.flags.verbose;
404
405const isDev = process.env.NODE_ENV === 'development' || isWatching;
406
407const cmd = _.raw || [];
408
409let child;
410
411function 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
429const _close = process.version.split('.')[1] === '6' ? 'exit' : 'close';
430
431function 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
495process.on('SIGINT', () => {
496 logger.printf('\r\r');
497
498 if (child) {
499 child.kill('SIGINT');
500 }
501
502 die();
503});
504
505let _restart;
506
507process.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
571process.on('exit', exitCode => {
572 if (!isDev && !exitCode) {
573 logger.write('\r\n');
574 }
575});
576
577logger.info('{% log Output to: %} {% yellow %s %}\n', path.relative(cwd, defaultConfig.output) || '.');
578
579logger.info('{% log Reading from %} {% yellow %s %} {% gray source%s %}\n',
580 defaultConfig.from.length,
581 defaultConfig.from.length === 1 ? '' : 's');
582
583if (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}