#!/usr/bin/env node // Remove any command line information before bower/rc/optimist are loaded and attempt to hijack // our CLI args. This is a total hack but works around the issue described here: // https://github.com/bower/bower/issues/543 // // This can be removed once bower is updated to 1.9.3 (which updates rc to 0.3.0) // var args = process.argv.slice(2); process.argv = [process.argv[0], process.argv[1]]; process.on('uncaughtException', function(err) { verbose = false; if (err.code === 'EMFILE') { console.error('\n\n\tEMFILE error: Too many files open at one time. Fix this by running\n\n\t\tulimit -n 9999\n\n'); } else { console.error('Unhandled Exception:', err.stack || err); } process.exit(128); }); var _ = require('underscore'), fs = require('fs'), lumbar = require('../lib/lumbar'), dirname = require('path').dirname, growl = require('growl'); var LONG_USAGE="lumbar help\n"+ " Print this long help message.\n"+ "lumbar build [--package name] [--config file] [--minimize] [--use path [--with options]] lumbarFile [outputDir]\n"+ " Build out the package(s).\n"+ "lumbar watch [--package name] [--config file] [--minimize] [--use path [--with options]] lumbarFile [outputDir]\n"+ " Start watching the files for changes and rebuild as necessary.\n"+ "\n"+ "--package: represents the name of a corresponding package listed\n"+ " under 'packages' in lumbarFile.\n"+ "\n"+ "--module: build only a specific module. May be defined multiple times to build multiple.\n"+ "\n"+ "--minimize: to shrink the resultant file.\n"+ "\n"+ "--sourceMap: generate a source map for the output modules.\n"+ " WARN: This copies the source files into the module output unobfuscated.\n" " care should be exercised when used with production releases\n" "\n"+ "--config: is the name and path to the lumbar config file, if\n"+ " not given then lumbar.json is assumed.\n"+ "\n"+ "--use: path to your plugin to load\n"+ "\n"+ " --with: an optional json config object to pass to your plugin.\n"+ "\n"+ " --name: an optional name to assign to your plugin.\n"+ "\n"+ "lumbarFile: is the name and path to the lumbar config file, if\n"+ " not given then lumbar.json is assumed.\n"+ "\n"+ "outputDir: Required. Designates where the files will be placed.\n"+ "\n"; var configFile, packageName, modules = [], libraries = [], minimize = false, sourceMap = false, build = true, // build by default. watch = false, verbose = false, modeSeen; var lumbarFile = 'lumbar.json'; var outputDir; /** * Import paths. */ var plugins = [], files = []; var arg; while (args.length) { arg = args.shift(); var value = arg.split('='); arg = value[0]; value = value[1]; switch (arg) { case '-h': case '--help': console.error(LONG_USAGE); process.exit(0); break; case '-c': case '--config': configFile = args.shift(); if (!configFile) throw new Error('--config required'); break; case '-p': case '--package': packageName = args.shift(); if (!packageName) throw new Error('--package required'); break; case '--module': var moduleName = args.shift(); if (!moduleName) throw new Error('--module required'); modules.push(moduleName); break; case '--library': if (!value) throw new Error('--library= required'); libraries.push(value); break; case '-m': case '--minimize': minimize = true; break; case '-s': case '--sourceMap': sourceMap = value || true; break; case '-b': case '--build': case 'build': if (modeSeen) { files.push(arg); } else { modeSeen = true; build = true; watch = false; } break; case '-w': case '--watch': case 'watch': if (modeSeen) { files.push(arg); } else { modeSeen = true; watch = true; build = false; } break; case '-v': case '--verbose': verbose = true; break; case '-u': case '--use': var options; var path = args.shift(); if (!path) throw new Error('--use required'); if ('--with' == args[0]) { args.shift(); options = args.shift(); if (!options) throw new Error('--with required'); try { options = eval('(' + options + ')'); } catch (err) { console.error('Failed to parse plugin options:', options); throw err; } } plugins.push({ path: path, options: options, name: 'PlugIn_' + plugins.length }); break; default: files.push(arg); } } if (build || watch) { var file = files.shift(); try { var stat = fs.statSync(file); if (stat.isFile()) { lumbarFile = file; file = files.shift(); if (file) { outputDir = file; } } else { outputDir = file; } } catch (err) { outputDir = file; } if (files.length) { console.error(LONG_USAGE); process.exit(3); return true; } } else { console.error(LONG_USAGE); process.exit(2); } plugins = plugins.map(function(plugin) { var path = plugin.path; var name = plugin.name; var options = plugin.options; try { var fn = require(path); } catch (err) { try { // Load from the current directory if not found in the normal lookup paths fn = require(process.cwd() + '/node_modules/' + path); } catch (err) { // Last case, attempt to load from the plugins dir fn = require('../lib/plugins/' + path); } } lumbar.plugin(name, 'function' != typeof fn ? fn : fn(options)); return name; }); var options = { verbose: verbose, libraries: libraries, packageConfigFile: configFile, minimize: minimize, sourceMap: sourceMap, outdir: outputDir, plugins: plugins }; // invoke init() from ../lib/lumbar.js. var arise = lumbar.init(lumbarFile, options), // is worker going to point to watch or build func? worker = watch ? arise.watch : arise.build, lastMessage, watchOutput = {}, growlList = [], osType = require('os').type(); function safeGrowl(msg, options) { if (require('os').type().toLowerCase().match(/^windows/)) { return; } try { growl(msg, options); } catch (err) { if (err.errno === 'EMFILE') { _.defer(safeGrowl, msg, options); } else { throw err; } } } var pingGrowl = _.debounce(function() { safeGrowl('compiled: ' + growlList.map(function(file) { return file.substring(options.outdir.length); }).join('\n\t'), {title: 'Lumbar'}); growlList = []; }, 1000); arise.on('watch-change', function(status) { if (!watchOutput[status.fileName]) { console.log('\t\033[90mchanged\033[0m ' + status.fileName); watchOutput[status.fileName] = true; } }); arise.on('log', function(log) { if (verbose) { console.log(log); } }); arise.on('output', function(status) { if (status.error) { return; } watchOutput = {}; if (status.watch) { console.log('\033[90mwatching\033[0m ' + status.fileName); } else { console.log('\033[90mcompiled\033[0m ' + status.fileName); } if (status.warnings && status.warnings.length) { if (watch) { safeGrowl(status.warnings.length + ' warnings in ' + status.fileName, { title: 'Lumbar warnings', sticky: true }); } console.log('\t\033[91m' + status.warnings.length + ' warnings\033[0m'); _.each(status.warnings, function(warning) { console.log('\t\033[91mwarning\033[0m ' + warning.msg); console.log('\t\tat ' + warning.file + ':' + warning.line + (warning.column ? ',' + warning.column : '')); _.each(warning.context, function(line) { console.log('\t' + line); }); console.log(); }); } if (watch) { growlList.push(status.fileName); pingGrowl(); } }); arise.on('error', function(err) { if ((err.source || err).code === 'EMFILE') { throw err.source || err; } var message = err.stack || err.message; if (lastMessage !== message) { lastMessage = message; console.error(message); try { safeGrowl(err.message, { title: 'Lumbar error', sticky: true }); } catch (err) { console.log(err); throw err; } } }); // execute either watch() or build() and pass lumbar as the context. worker.call(arise, packageName, modules, function(err, status) { if (err) { throw err; } });