├── .gitignore ├── index.js ├── bin ├── n-run ├── n-clean ├── n-copy ├── n-embed ├── n-pipe └── n-concat ├── src ├── index.js ├── utilities │ ├── exit.js │ ├── each.js │ ├── make.js │ ├── find.js │ └── cmd.js ├── pipe.js ├── clean.js ├── concat.js ├── copy.js ├── run.js └── embed.js ├── package.json ├── LICENSE ├── .jshintrc └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./src'); 2 | -------------------------------------------------------------------------------- /bin/n-run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var root = path.join(path.dirname(fs.realpathSync(__filename))); 5 | require(path.join(root, '../src/run'))(); 6 | -------------------------------------------------------------------------------- /bin/n-clean: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var root = path.join(path.dirname(fs.realpathSync(__filename))); 5 | require(path.join(root, '../src/clean'))(); 6 | -------------------------------------------------------------------------------- /bin/n-copy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var root = path.join(path.dirname(fs.realpathSync(__filename))); 5 | require(path.join(root, '../src/copy'))(); 6 | -------------------------------------------------------------------------------- /bin/n-embed: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var root = path.join(path.dirname(fs.realpathSync(__filename))); 5 | require(path.join(root, '../src/embed'))(); 6 | -------------------------------------------------------------------------------- /bin/n-pipe: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var root = path.join(path.dirname(fs.realpathSync(__filename))); 5 | require(path.join(root, '../src/pipe'))(); 6 | -------------------------------------------------------------------------------- /bin/n-concat: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var root = path.join(path.dirname(fs.realpathSync(__filename))); 5 | require(path.join(root, '../src/concat'))(); 6 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | clean: require('./clean'), 5 | concat: require('./concat'), 6 | copy: require('./copy'), 7 | pipe: require('./pipe'), 8 | run: require('./run') 9 | }; 10 | -------------------------------------------------------------------------------- /src/utilities/exit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Exits with an error to the optional handler or console. 5 | * @param {Error=} err 6 | * @param {function(Error=)} done 7 | */ 8 | module.exports = function(err, done) { 9 | if (done) return done(err); 10 | if (err) console.error(err.stack || err); 11 | }; 12 | -------------------------------------------------------------------------------- /src/utilities/each.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Invokes the handler for each item in series. 5 | * @param {(Array.|T)} items 6 | * @param {function(T, function(Error=))} handler 7 | * @param {function(Error=)} done 8 | * @template T 9 | */ 10 | module.exports = function(items, handler, done) { 11 | var i = -1; 12 | if (!Array.isArray(items)) items = [items]; 13 | (function next(err) { 14 | i += 1; 15 | if (!err && i < items.length) return handler(items[i], next); 16 | if (done) done(err); 17 | })(); 18 | }; 19 | -------------------------------------------------------------------------------- /src/utilities/make.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var each = require('./each'); 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | 6 | /** 7 | * Makes directories for the file path. 8 | * @param {string} directoryPath 9 | * @param {function()} done 10 | */ 11 | module.exports = function(filePath, done) { 12 | var directoryPaths = []; 13 | var previousPath = path.resolve(filePath); 14 | while (true) { 15 | var currentPath = path.join(previousPath, '..'); 16 | if (currentPath === previousPath) break; 17 | directoryPaths.push(currentPath); 18 | previousPath = currentPath; 19 | } 20 | each(directoryPaths.reverse(), function(directoryPath, next) { 21 | fs.mkdir(directoryPath, function() { 22 | next(); 23 | }); 24 | }, done); 25 | }; 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Roel van Uden", 3 | "description": "Cross-platform command-line tools to help use npm as a build tool.", 4 | "keywords": ["build", "cli", "clean", "concat", "copy", "exec", "glob", "npm", "pipe", "run", "shell", "tool"], 5 | "license": "MIT", 6 | "main": "src/index.js", 7 | "name": "npm-build-tools", 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com/Deathspike/npm-build-tools.git" 11 | }, 12 | "version": "2.2.5", 13 | "bin": { 14 | "n-clean": "./bin/n-clean", 15 | "n-concat": "./bin/n-concat", 16 | "n-copy": "./bin/n-copy", 17 | "n-embed": "./bin/n-embed", 18 | "n-pipe": "./bin/n-pipe", 19 | "n-run": "./bin/n-run" 20 | }, 21 | "dependencies": { 22 | "commander": "^2.7.1", 23 | "chokidar": "^1.0.0-rc4", 24 | "glob": "^5.0.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/utilities/find.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var each = require('./each'); 3 | var glob = require('glob'); 4 | 5 | /** 6 | * Finds the files matched by the patterns in the source directory. 7 | * @param {(Array.|string)} patterns 8 | * @param {string} sourcePath 9 | * @param {string=} ignorePatterns 10 | * @param {function(Error, Array.=)} done 11 | */ 12 | module.exports = function(patterns, sourcePath, ignorePatterns, done) { 13 | var relativePaths = {}; 14 | if (typeof ignorePatterns === 'function') { 15 | done = ignorePatterns; 16 | ignorePatterns = null; 17 | } 18 | each(patterns, function(pattern, next) { 19 | glob(pattern, {cwd: sourcePath, nodir: true, ignore: ignorePatterns}, function(err, foundPaths) { 20 | if (err) return next(err); 21 | foundPaths.forEach(function(foundPath) { 22 | relativePaths[foundPath] = true; 23 | }); 24 | next(); 25 | }); 26 | }, function(err) { 27 | if (err) return done(err); 28 | done(undefined, Object.keys(relativePaths)); 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Roel van Uden 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to 5 | deal in the Software without restriction, including without limitation the 6 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | sell copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/pipe.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var cmd = require('./utilities/cmd'); 3 | var exit = require('./utilities/exit'); 4 | var fs = require('fs'); 5 | var make = require('./utilities/make'); 6 | 7 | /** 8 | * ... 9 | * @param {({args: Array.}|string)=} input 10 | * @param {function(Error=)=} done 11 | */ 12 | module.exports = function(input, done) { 13 | parse(input, function(err, options) { 14 | if (err) return exit(err, done); 15 | var destinationPath = options.args[0]; 16 | make(destinationPath, function() { 17 | var next = done || exit; 18 | var writeStream = fs.createWriteStream(destinationPath); 19 | process.stdin.on('error', next).on('close', next).pipe(writeStream); 20 | }); 21 | }); 22 | }; 23 | 24 | /** 25 | * Parses input into options. 26 | * @param {({args: Array.}|string)=} input 27 | * @param {function(Error, {args: Array.}=)} done 28 | */ 29 | function parse(input, done) { 30 | if (input && input.args) return done(undefined, input); 31 | if (input) done(undefined, {args: [].concat(input)}); 32 | cmd([], done); 33 | } 34 | 35 | if (module === require.main) { 36 | module.exports(); 37 | } 38 | -------------------------------------------------------------------------------- /src/clean.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var cmd = require('./utilities/cmd'); 3 | var each = require('./utilities/each'); 4 | var exit = require('./utilities/exit'); 5 | var fs = require('fs'); 6 | var path = require('path'); 7 | 8 | /** 9 | * ... 10 | * @param {({args: Array.}|Array.|string)=} input 11 | * @param {function(Error=)=} done 12 | */ 13 | module.exports = function(input, done) { 14 | parse(input, function(err, options) { 15 | if (err) return exit(err, done); 16 | each(options.args, function(directoryPath, next) { 17 | clean(directoryPath, true, next); 18 | }, function(err) { 19 | if (err) return exit(err, done); 20 | if (done) done(); 21 | }); 22 | }); 23 | }; 24 | 25 | /** 26 | * Cleans the files and directories in the directory path. 27 | * @param {string} directoryPath 28 | * @param {function(Error=)} done 29 | */ 30 | function clean(directoryPath, isRoot, done) { 31 | fs.stat(directoryPath, function(err, stat) { 32 | if (err) return done(isRoot ? undefined : err); 33 | if (stat.isFile()) return fs.unlink(directoryPath, done); 34 | fs.readdir(directoryPath, function(err, relativePaths) { 35 | if (err) return done(err); 36 | each(relativePaths, function(relativePath, next) { 37 | var absolutePath = path.join(directoryPath, relativePath); 38 | fs.stat(absolutePath, function(err, stats) { 39 | if (err) return next(err); 40 | if (!stats.isDirectory()) return fs.unlink(absolutePath, next); 41 | clean(absolutePath, false, next); 42 | }); 43 | }, function(err) { 44 | if (err) return done(err); 45 | fs.rmdir(directoryPath, done); 46 | }); 47 | }); 48 | }); 49 | } 50 | 51 | /** 52 | * Parses input into options. 53 | * @param {({args: Array.}|Array.|string)=} input 54 | * @param {function(Error, {args: Array.}=)} done 55 | */ 56 | function parse(input, done) { 57 | if (input && input.args) return done(undefined, input); 58 | if (input) done(undefined, {args: [].concat(input)}); 59 | cmd([], done); 60 | } 61 | 62 | if (module === require.main) { 63 | module.exports(); 64 | } 65 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise" : true, 3 | "camelcase" : true, 4 | "curly" : false, 5 | "eqeqeq" : true, 6 | "es3" : true, 7 | "forin" : true, 8 | "freeze" : true, 9 | "futurehostile": true, 10 | "immed" : true, 11 | "indent" : 4, 12 | "latedef" : "nofunc", 13 | "newcap" : true, 14 | "noarg" : true, 15 | "noempty" : true, 16 | "nonbsp" : true, 17 | "nonew" : true, 18 | "plusplus" : true, 19 | "quotmark" : "single", 20 | "undef" : true, 21 | "unused" : true, 22 | "singleGroups" : true, 23 | "strict" : true, 24 | "maxparams" : 5, 25 | "maxdepth" : 5, 26 | "maxstatements": 25, 27 | "maxcomplexity": 5, 28 | "maxlen" : 120, 29 | 30 | "asi" : false, 31 | "boss" : false, 32 | "debug" : false, 33 | "eqnull" : false, 34 | "evil" : false, 35 | "expr" : false, 36 | "esnext" : false, 37 | "funcscope" : false, 38 | "globalstrict" : false, 39 | "iterator" : false, 40 | "lastsemic" : false, 41 | "laxbreak" : false, 42 | "laxcomma" : false, 43 | "loopfunc" : false, 44 | "maxerr" : 50, 45 | "moz" : false, 46 | "multistr" : false, 47 | "notypeof" : false, 48 | "noyield" : false, 49 | "proto" : false, 50 | "scripturl" : false, 51 | "scope" : false, 52 | "shadow" : false, 53 | "sub" : false, 54 | "supernew" : false, 55 | "validthis" : false, 56 | "withstmt" : false, 57 | 58 | "browser" : false, 59 | "browserify" : false, 60 | "couch" : false, 61 | "devel" : false, 62 | "dojo" : false, 63 | "jasmine" : false, 64 | "jquery" : false, 65 | "mootools" : false, 66 | "mocha" : false, 67 | "node" : true, 68 | "nonstandard" : false, 69 | "phantom" : false, 70 | "prototypejs" : false, 71 | "qunit" : false, 72 | "rhino" : false, 73 | "shelljs" : false, 74 | "typed" : false, 75 | "worker" : false, 76 | "wsh" : false, 77 | "yui" : false, 78 | 79 | "globals" : [] 80 | } 81 | -------------------------------------------------------------------------------- /src/concat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var cmd = require('./utilities/cmd'); 3 | var each = require('./utilities/each'); 4 | var exit = require('./utilities/exit'); 5 | var find = require('./utilities/find'); 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | 9 | /** 10 | * ... 11 | * @param {({args: Array., divider?: string, source?: string}|Array.|string)=} input 12 | * @param {function(Error=)=} done 13 | */ 14 | module.exports = function(input, done) { 15 | parse(input, function(err, options) { 16 | if (err) return exit(err, done); 17 | var sourcePath = options.source || process.cwd(); 18 | var ignore = options.ignore || null; 19 | find(options.args, sourcePath, ignore, function(err, relativePaths) { 20 | if (err) return exit(err, done); 21 | var divider = options.divider || '\n'; 22 | concat(divider, sourcePath, relativePaths, function(err) { 23 | if (err) return exit(err, done); 24 | if (done) done(); 25 | }); 26 | }); 27 | }); 28 | }; 29 | 30 | /** 31 | * Concatenate files from the source path into the standard output. 32 | * @param {string} divider 33 | * @param {string} sourcePath 34 | * @param {Array.} relativePaths 35 | * @param {function(Error)=} done 36 | */ 37 | function concat(divider, sourcePath, relativePaths, done) { 38 | var writeDivider = false; 39 | each(relativePaths, function(relativePath, next) { 40 | var absoluteSourcePath = path.resolve(sourcePath, relativePath); 41 | var readStream = fs.createReadStream(absoluteSourcePath); 42 | if (writeDivider) process.stdout.write(divider); 43 | writeDivider = true; 44 | readStream.on('close', next).on('error', next).pipe(process.stdout); 45 | }, done); 46 | } 47 | 48 | /** 49 | * Parses input into options. 50 | * @param {({args: Array., divider?: string, source?: string}|Array.|string)=} input 51 | * @param {function(Error, {args: Array., divider?: string, source?: string}=)} done 52 | */ 53 | function parse(input, done) { 54 | if (input && input.args) return done(undefined, input); 55 | if (input) done(undefined, {args: [].concat(input)}); 56 | cmd([ 57 | {option: '-d, --divider ', text: 'The divider. (Default: \\n)'}, 58 | {option: '-s, --source ', text: 'The source path.'}, 59 | {option: '-i, --ignore ', text: 'Add a pattern or an array of patterns to exclude matches.'} 60 | ], done); 61 | } 62 | 63 | if (module === require.main) { 64 | module.exports(); 65 | } 66 | -------------------------------------------------------------------------------- /src/copy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var cmd = require('./utilities/cmd'); 3 | var each = require('./utilities/each'); 4 | var exit = require('./utilities/exit'); 5 | var find = require('./utilities/find'); 6 | var fs = require('fs'); 7 | var make = require('./utilities/make'); 8 | var path = require('path'); 9 | 10 | /** 11 | * ... 12 | * @param {{args: Array., destination?: string, source?: string}=} input 13 | * @param {function(Error=)=} done 14 | */ 15 | module.exports = function(input, done) { 16 | parse(input, function(err, options) { 17 | if (err) return exit(err, done); 18 | var destinationPath = options.destination || process.cwd(); 19 | var sourcePath = options.source || process.cwd(); 20 | var ignore = options.ignore || null; 21 | find(options.args, sourcePath, ignore, function(err, relativePaths) { 22 | if (err) return exit(err, done); 23 | copy(sourcePath, destinationPath, relativePaths, function(err) { 24 | if (err) return exit(err, done); 25 | if (done) done(); 26 | }); 27 | }); 28 | }); 29 | }; 30 | 31 | /** 32 | * Copies files from the source path to the destination path. 33 | * @param {string} sourcePath 34 | * @param {string} destinationPath 35 | * @param {Array.} relativePaths 36 | * @param {function(Error=)} done 37 | */ 38 | function copy(sourcePath, destinationPath, relativePaths, done) { 39 | each(relativePaths, function(relativePath, next) { 40 | var absoluteDestinationPath = path.join(destinationPath, relativePath); 41 | var absoluteSourcePath = path.resolve(sourcePath, relativePath); 42 | if (absoluteDestinationPath === absoluteSourcePath) return next(); 43 | make(absoluteDestinationPath, function() { 44 | var readStream = fs.createReadStream(absoluteSourcePath); 45 | var writeStream = fs.createWriteStream(absoluteDestinationPath); 46 | readStream.on('close', next).on('error', next).pipe(writeStream); 47 | }); 48 | }, done); 49 | } 50 | 51 | /** 52 | * Parses input into options. 53 | * @param {{args: Array., destination?: string, source?: string}=} input 54 | * @param {function(Error, {args: Array., destination?: string, source?: string}=)} done 55 | */ 56 | function parse(input, done) { 57 | if (input && input.args) return done(undefined, input); 58 | if (input) done(undefined, {args: [].concat(input)}); 59 | cmd([ 60 | {option: '-d, --destination ', text: 'The destination path.'}, 61 | {option: '-s, --source ', text: 'The source path.'}, 62 | {option: '-i, --ignore ', text: 'Add a pattern or an array of patterns to exclude matches.'} 63 | ], done); 64 | } 65 | 66 | if (module === require.main) { 67 | module.exports(); 68 | } 69 | -------------------------------------------------------------------------------- /src/run.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var childProcess = require('child_process'); 3 | var chokidar = require('chokidar'); 4 | var cmd = require('./utilities/cmd'); 5 | var each = require('./utilities/each'); 6 | var exit = require('./utilities/exit'); 7 | var shell = process.platform === 'win32' ? ['cmd', '/c'] : ['sh', '-c']; 8 | 9 | /** 10 | * ... 11 | * @param {({args: Array., source?: string, watch?: Array.}|Array.|string)=} input 12 | * @param {function(Error=)=} done 13 | */ 14 | module.exports = function(input, done) { 15 | parse(input, function(err, options) { 16 | if (err) return exit(err, done); 17 | var sourcePath = options.source || process.cwd(); 18 | if (options.watch) return watch(sourcePath, options.watch, options.args); 19 | run(options.args, function(err) { 20 | if (err) return exit(err, done); 21 | if (done) done(); 22 | }); 23 | }); 24 | }; 25 | 26 | /** 27 | * Runs each command in parallel. 28 | * @param {Array.} commands 29 | * @param {function(Error=)} done 30 | */ 31 | function run(commands, done) { 32 | var children = []; 33 | var pending = commands.length || 1; 34 | var err; 35 | each(commands, function(command, next) { 36 | children.push(childProcess.spawn(shell[0], [shell[1], command], { 37 | stdio: ['pipe', process.stdout, process.stderr] 38 | }).on('exit', function(code) { 39 | if (!err && code > 0) { 40 | err = new Error('`' + command + '` failed with exit code ' + code); 41 | children.forEach(function(child) { 42 | if (child.connected) child.kill(); 43 | }); 44 | } 45 | pending -= 1; 46 | if (pending === 0) done(err); 47 | })); 48 | next(); 49 | }); 50 | } 51 | 52 | /** 53 | * Parses input into options. 54 | * @param {({args: Array., source?: string, watch?: Array.}|Array.|string)=} input 55 | * @param {function(Error, {args: Array., source?: string, watch?: Array.}=)} done 56 | */ 57 | function parse(input, done) { 58 | if (input && input.args) return done(undefined, input); 59 | if (input) done(undefined, {args: [].concat(input)}); 60 | cmd([ 61 | {option: '-s, --source ', text: 'The source path (for expand/watch).'}, 62 | {option: '-w, --watch ', text: 'The watched files.', parse: function(value) { 63 | return value.split(','); 64 | }} 65 | ], done); 66 | } 67 | 68 | /** 69 | * Watches the patterns in the source path and runs commands on changes. 70 | * @param {string} sourcePath 71 | * @param {(Array.|string)} patterns 72 | * @param {Array.} commands 73 | */ 74 | function watch(sourcePath, patterns, commands) { 75 | var isBusy = false; 76 | var isPending = false; 77 | var runnable = function() { 78 | if (isBusy) return (isPending = true); 79 | isBusy = true; 80 | run(commands, function(err) { 81 | if (err) console.error(err.stack || err); 82 | isBusy = false; 83 | if (isPending) runnable(isPending = false); 84 | }); 85 | }; 86 | chokidar.watch(patterns, { 87 | cwd: sourcePath, 88 | ignoreInitial: true 89 | }).on('add', runnable).on('change', runnable).on('unlink', runnable); 90 | } 91 | 92 | if (module === require.main) { 93 | module.exports(); 94 | } 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # npm-build-tools 2 | 3 | Cross-platform command-line tools to help use npm as a build tool. This collection of command-line tools was inspired by the following blog post by Keith Cirkel: [How to Use npm as a Build Tool](http://blog.keithcirkel.co.uk/how-to-use-npm-as-a-build-tool/). Incorporating the described approach is a hard when aiming for cross-platform support, and this collection of tools emerged to solve the pitfalls I encountered. 4 | 5 | ## Commands 6 | 7 | ### n-clean 8 | 9 | Cleans a directory or file. The effect is similar to `rm -rf`. Example: 10 | 11 | n-clean www 12 | 13 | ### n-concat 14 | 15 | Concatenates the matched files and prints to `stdout`. Example: 16 | 17 | n-concat --source src --ignore "scripts/assets/**" "scripts/**/*.js" 18 | 19 | *Globs* are supported. Additional command line options: 20 | 21 | * `-s, --source ` contains the source path. 22 | * `-i, --ignore ` Add a pattern or an array of patterns to exclude matches. 23 | 24 | ### n-copy 25 | 26 | Copies the matched files to the destination folder. Example: 27 | 28 | n-copy --source src --destination www --ignore "scripts/assets/**" "*" "content/**/*" 29 | 30 | *Globs* are supported. Additional command line options: 31 | 32 | * `-d, --divider` contains the divider (default \n). 33 | * `-s, --source ` contains the source path. 34 | * `-i, --ignore ` Add a pattern or an array of patterns to exclude matches. 35 | 36 | ### n-embed 37 | 38 | Transforms HTML files into an embedded angular $templateCache wrapper module. 39 | 40 | n-embed --source src "views/**/*.html" 41 | 42 | Additional command line options: 43 | 44 | * `-m, --module ` contains the module name (default: tml). 45 | * `-s, --source ` contains the source path. 46 | * `-i, --ignore ` Add a pattern or an array of patterns to exclude matches. 47 | 48 | ### n-pipe 49 | 50 | Pipe `stdin` to a file. Similar to `> file`. Example: 51 | 52 | n-pipe non/existent/file.dat 53 | 54 | Unlike built-in commands, `n-pipe` creates directories when necessary. 55 | 56 | ### n-run 57 | 58 | Executes command(s) in parallel. Example: 59 | 60 | n-run "echo Hello world!" "echo Hello world!" 61 | 62 | A watcher can be created to run command(s) on a file change. Example: 63 | 64 | n-run -w "*.js" "echo The file changed!" 65 | 66 | Glob expansions are supported with `$g[]`. Example: 67 | 68 | n-run "jshint $g[*.js]" 69 | 70 | Variable expansions (from `package.json/config`) are supported with `$v[]`. Example: 71 | 72 | n-run "n-concat $v[js-bower-dependencies]" 73 | 74 | Additional command line options: 75 | 76 | * `-s, --source ` contains the source path (for expand/watch). 77 | * `-w, --watch ` contains the watched files. 78 | 79 | ## Examples 80 | 81 | Concatenate dependency files and pipe to `www/scripts/dep.min.js`: 82 | 83 | n-concat angular.min.js bootstrap.min.js jquery.min.js | n-pipe www/scripts/dep.min.js 84 | 85 | Copy static assets from the `src` directory to the `www` directory: 86 | 87 | n-copy --source src --destination www "*" "content/**/*" 88 | 89 | Compiling with `browserify` to `www/scripts/apps.min.js`: 90 | 91 | browserify src/scripts/app.js | n-pipe www/scripts/app.min.js 92 | 93 | Deleting the `www` folder: 94 | 95 | n-clean www 96 | -------------------------------------------------------------------------------- /src/embed.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var cmd = require('./utilities/cmd'); 3 | var each = require('./utilities/each'); 4 | var exit = require('./utilities/exit'); 5 | var find = require('./utilities/find'); 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | 9 | /** 10 | * ... 11 | * @param {({args: Array.}|Array.|string)=} input 12 | * @param {function(Error=)=} done 13 | */ 14 | module.exports = function(input, done) { 15 | parse(input, function(err, options) { 16 | if (err) return exit(err, done); 17 | var sourcePath = options.source || process.cwd(); 18 | var ignore = options.ignore || null; 19 | find(options.args, sourcePath, ignore, function(err, relativePaths) { 20 | if (err) return exit(err, done); 21 | embed(sourcePath, relativePaths, options.module || 'tml', function(err) { 22 | if (err) return exit(err, done); 23 | if (done) done(); 24 | }); 25 | }); 26 | }); 27 | }; 28 | 29 | /** 30 | * Embed the relative paths in an angular-specific template wrapper. 31 | * @param {string} sourcePath 32 | * @param {Array.} relativePaths 33 | * @param {string} name 34 | * @param {function(Error=)} done 35 | */ 36 | function embed(sourcePath, relativePaths, name, done) { 37 | var items = ''; 38 | each(relativePaths, function(relativePath, next) { 39 | var fullPath = path.join(sourcePath, relativePath); 40 | fs.readFile(fullPath, 'utf8', function(err, text) { 41 | if (err) return next(err); 42 | items += ' $templateCache.put(\'' + 43 | inline(relativePath) + '\', \'' + 44 | inline(text.trim()) + '\');\n'; 45 | next(); 46 | }); 47 | }, function(err) { 48 | if (err) return done(err); 49 | console.log( 50 | 'angular.module(\'' + inline(name) + '\', []).run([\'$templateCache\', function($templateCache) {\n' + 51 | items + 52 | '}]);'); 53 | done(); 54 | }); 55 | } 56 | 57 | /** 58 | * Escapes for inline embedding. 59 | * @param {string} text 60 | * @returns {string} 61 | */ 62 | function inline(text) { 63 | var result = ''; 64 | for (var i = 0; i < text.length; i += 1) { 65 | var value = text.charAt(i); 66 | if (value === '\'') result += '\\\''; 67 | else if (value === '\\') result += '\\\\'; 68 | else if (value === '\b') result += '\\b'; 69 | else if (value === '\f') result += '\\f'; 70 | else if (value === '\n') result += '\\n'; 71 | else if (value === '\r') result += '\\r'; 72 | else if (value === '\t') result += '\\t'; 73 | else result += value; 74 | } 75 | return result; 76 | } 77 | 78 | /** 79 | * Parses input into options. 80 | * @param {({args: Array.}|Array.|string)=} input 81 | * @param {function(Error, {args: Array.}=)} done 82 | */ 83 | function parse(input, done) { 84 | if (input && input.args) return done(undefined, input); 85 | if (input) done(undefined, {args: [].concat(input)}); 86 | cmd([ 87 | {option: '-m, --module ', text: 'The module name. (Default: tml)'}, 88 | {option: '-s, --source ', text: 'The source path.'}, 89 | {option: '-i, --ignore ', text: 'Add a pattern or an array of patterns to exclude matches.'} 90 | ], done); 91 | } 92 | 93 | if (module === require.main) { 94 | module.exports(); 95 | } 96 | -------------------------------------------------------------------------------- /src/utilities/cmd.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Command = require('commander').Command; 3 | var each = require('./each'); 4 | var find = require('./find'); 5 | var fs = require('fs'); 6 | 7 | /** 8 | * Processes command line arguments and expands variables/globs. 9 | * @param {Array.<{option: string, text: string}>} entries 10 | * @param {function(Error, {Object.}=)} done 11 | */ 12 | module.exports = function(entries, done) { 13 | var options = loadOptions(entries); 14 | loadConfig(function(err, config) { 15 | if (err) return done(err); 16 | var sourcePath = options.source || process.cwd(); 17 | var ignore = options.ignore || null; 18 | variables(options.args, config, function(err) { 19 | if (err) return done(err); 20 | globs(sourcePath, ignore, options.args, function(err, args) { 21 | if (err) return done(err); 22 | options.args = args; 23 | done(undefined, options); 24 | }); 25 | }); 26 | }); 27 | }; 28 | 29 | /** 30 | * Iterates through each command and expands each glob pattern. 31 | * @param {string} sourcePath 32 | * @param {Array.} commands 33 | * @param {function(Error, Array.=)} done 34 | */ 35 | function globs(sourcePath, ignore, commands, done) { 36 | var expression = /\$g\[(.+?)\]/g; 37 | var result = []; 38 | each(commands, function(command, next) { 39 | var map = {}; 40 | var match; 41 | var matches = []; 42 | while ((match = expression.exec(command))) matches.push(match); 43 | each(matches, function(patternMatch, next) { 44 | find(patternMatch[1], sourcePath, ignore, function(err, relativePaths) { 45 | if (err) return next(err); 46 | map[patternMatch[1]] = relativePaths; 47 | next(); 48 | }); 49 | }, function(err) { 50 | if (err) return next(err); 51 | result.push(command.replace(expression, function(match, matchKey) { 52 | return map[matchKey].join(' '); 53 | })); 54 | next(); 55 | }); 56 | }, function(err) { 57 | if (err) return done(err); 58 | done(undefined, result); 59 | }); 60 | } 61 | 62 | /** 63 | * Loads the configuration. 64 | * @param {function(Error, Object=)} 65 | */ 66 | function loadConfig(done) { 67 | fs.stat('package.json', function(err) { 68 | if (err) return done(undefined, {}); 69 | fs.readFile('package.json', function(err, contents) { 70 | if (err) return done(err); 71 | try { 72 | done(undefined, JSON.parse(contents).config || {}); 73 | } catch(err) { 74 | done(err); 75 | } 76 | }); 77 | }); 78 | } 79 | 80 | /** 81 | * Loads the options. 82 | * @param {Array.<{option: string, text: string}>} entries 83 | * @returns {Object.} 84 | */ 85 | function loadOptions(entries) { 86 | var options = new Command().version(require('../../package').version); 87 | entries.forEach(function(entry) { 88 | options.option(entry.option, entry.text, entry.parse); 89 | }); 90 | return options.parse(process.argv); 91 | } 92 | 93 | /** 94 | * Expands each variable. 95 | * @param {Object} root 96 | * @param {Object.} config 97 | * @param {function()} done 98 | */ 99 | function variables(root, config, done) { 100 | var expression = /\$v\[(.+?)\]/g; 101 | each(Object.keys(root), function(key, next) { 102 | var value = root[key]; 103 | if (typeof value === 'object') return variables(value, config, next); 104 | if (typeof value !== 'string') return next(); 105 | root[key] = value.replace(expression, function(match, matchKey) { 106 | return config[matchKey] || match; 107 | }); 108 | next(); 109 | }, done); 110 | } 111 | --------------------------------------------------------------------------------