UNPKG

14.1 kBJavaScriptView Raw
1#!/usr/bin/env node
2
3'use strict';
4
5/* eslint-disable global-require */
6/* eslint-disable no-nested-ternary */
7
8const path = require('path');
9const wargs = require('wargs');
10
11// common helpers
12const die = process.exit.bind(process);
13
14const $ = require('./lib/utils'); // eslint-disable-line
15
16const 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
24let _;
25
26try {
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// nice logs!
67const _level = _.flags.verbose ? 'verbose' : _.flags.debug ? 'debug' : 'info';
68
69const logger = require('log-pose')
70 .setLevel((_.flags.quiet && !_.flags.version && !_.flags.help) ? false : _level)
71 .getLogger(12, process.stdout, process.stderr);
72
73if (_.flags.debug && _.flags.verbose) {
74 require('debug').enable('*'); // eslint-disable-line
75 require('log-pose').setLevel(false); // eslint-disable-line
76}
77
78// local debug
79const debug = require('debug')('tarima');
80
81const thisPkg = require(path.join(__dirname, '../package.json'));
82
83_.flags.env = (_.flags.env !== true ? _.flags.env : '') || 'development';
84
85// defaults
86process.name = 'tarima';
87
88process.env.NODE_ENV = _.flags.env;
89
90const gitDir = path.join(__dirname, '../.git');
91
92logger.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
97debug('v%s - node %s%s', thisPkg.version, process.version);
98
99if (_.flags.version) {
100 die();
101}
102
103const _bin = Object.keys(thisPkg.bin)[0];
104
105if (_.flags.help) {
106 logger.write(`
107Usage:
108 ${_bin} [watch] ...
109
110Examples:
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
115Options:
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
163function _debug(e) {
164 return (_.flags.verbose && e.stack) || e.toString();
165}
166
167const run = (opts, cb) => {
168 const _runner = require('./lib');
169
170 debug('settings %s', JSON.stringify(opts, null, 2));
171
172 // delay once resolver loads
173 process.nextTick(() => {
174 try {
175 _runner(opts, logger, cb);
176 } catch (e) {
177 $.errLog(_debug(e));
178 die(1);
179 }
180 });
181};
182
183const { spawn } = require('child_process'); // eslint-disable-line
184
185// empty dummy
186let mainPkg = {};
187
188const cwd = process.cwd();
189const pkg = path.join(cwd, 'package.json');
190
191// load .env
192const env = require('dotenv').config(); // eslint-disable-line
193
194if (env.error && env.error.code !== 'ENOENT') {
195 $.errLog(env.error);
196 die(1);
197}
198
199delete env.error;
200
201if ($.isFile(pkg)) {
202 debug('config %s', pkg);
203
204 mainPkg = $.readJSON(pkg);
205}
206
207let isWatching = false;
208
209if (_._[0] === 'watch') {
210 isWatching = true;
211 _._.shift();
212}
213
214const _src = _._;
215
216const 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// apply package settings
245try {
246 $.merge(defaultConfig, mainPkg.tarima || {});
247} catch (e) {
248 $.errLog(`Configuration mismatch: ${_debug(e)}`);
249 die(1);
250}
251
252// support for tarima.CONFIG.{js,json}
253let configFile = _.flags.config === true ? 'config' : _.flags.config;
254
255if (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
265if (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// normalize extensions
279$.merge(defaultConfig.bundleOptions.extensions, defaultConfig.extensions || {});
280
281defaultConfig.bundleOptions.rollup = defaultConfig.bundleOptions.rollup || {};
282
283// setup rollup format
284if (_.flags.umd) {
285 defaultConfig.bundleOptions.rollup.format = 'umd';
286}
287
288if (_.flags.amd) {
289 defaultConfig.bundleOptions.rollup.format = 'amd';
290}
291
292if (_.flags.cjs) {
293 defaultConfig.bundleOptions.rollup.format = 'cjs';
294}
295
296if (_.flags.iife) {
297 defaultConfig.bundleOptions.rollup.format = 'iife';
298}
299
300delete defaultConfig.extensions;
301
302function 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
319if (_.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
335if (_.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// apply globals first
354const _globals = defaultConfig.globals || defaultConfig.env || {};
355
356$.merge(env, _globals);
357$.merge(env, _globals[_.flags.env] || {});
358
359// merge only ENV_VARS_IN_CAPS
360Object.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// package info
367defaultConfig.bundleOptions.locals = defaultConfig.bundleOptions.locals || {};
368defaultConfig.bundleOptions.locals.env = env;
369defaultConfig.bundleOptions.locals.pkg = mainPkg;
370
371Object.keys(env).forEach(key => {
372 defaultConfig.bundleOptions.globals[key] = env[key];
373});
374
375if (_.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
387if (_.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
399defaultConfig.bundleOptions.compileDebug = _.flags.debug;
400defaultConfig.bundleOptions.verboseDebug = _.flags.verbose;
401
402const isDev = process.env.NODE_ENV === 'development' || isWatching;
403
404const cmd = _.raw || [];
405
406let child;
407
408function 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
426const _close = process.version.split('.')[1] === '6' ? 'exit' : 'close';
427
428function exec(onError) {
429 function restart() {
430 // restart
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
492process.on('SIGINT', () => {
493 logger.printf('\r\r');
494
495 if (child) {
496 child.kill('SIGINT');
497 }
498
499 die();
500});
501
502let _restart;
503
504process.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// clean exit
564process.on('exit', exitCode => {
565 if (!isDev && !exitCode) {
566 logger.write('\r\n');
567 }
568});
569
570logger.info('{% log Output to: %} {% yellow %s %}\n', path.relative(cwd, defaultConfig.output) || '.');
571
572logger.info('{% log Reading from %} {% yellow %s %} {% gray source%s %}\n',
573 defaultConfig.from.length,
574 defaultConfig.from.length === 1 ? '' : 's');
575
576if (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}