├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bin └── build ├── index.js ├── lib ├── argify.js ├── defaultify.js ├── envify.js ├── printify.js └── run.js ├── package.json └── test ├── fixtures ├── .eslintrc ├── .jscsrc └── .jshintrc ├── integration.test.js ├── mocha.opts ├── mocks.js └── unit ├── argify.test.js ├── defaultify.test.js ├── printify.test.js └── run.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | *.swp 30 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | - "0.12" 5 | - "4.0" 6 | 7 | before_install: 8 | - travis_retry npm install -g npm@2.14.2 9 | - travis_retry npm install 10 | 11 | script: 12 | - npm test 13 | 14 | matrix: 15 | allow_failures: 16 | - node_js: "0.10" 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Charlie Robbins 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fashion-show 2 | 3 | Build consistent and versioned styleguides by including and running consistent lint files across projects. 4 | 5 | - [Motivation](#motivation) 6 | - [Writing your requireable styleguide](#writing-your-requireable-styleguide) 7 | - [API Documentation](#api-documentation) 8 | 9 | ## Motivation 10 | 11 | Your styleguide should be **verisoned** and **consistent** across all of your projects. This means that you should only have _ONE_ `.jshintrc` and/or `.jscsrc` file anywhere. But how can this be accomplished easily? By making your own styleguide using `fashion-show`. 12 | 13 | ## Writing your "requireable" styleguide 14 | 15 | **A fully working example at [indexzero/styleguide](https://github.com/indexzero/styleguide).** Basically it happens in a few steps: 16 | 17 | - **1.** Make a new repository, `your-styleguide` 18 | - **2.** Add your `.elintrc` and `.jscsrc` files: 19 | ``` 20 | cd your-styleguide 21 | mkdir dist 22 | mkdir dotfiles 23 | touch dotfiles/.eslintrc 24 | touch dotfiles/.jscsrc 25 | 26 | # Also works with jshint 27 | # touch dotfiles/.jshintrc 28 | ``` 29 | - **3.** Install `fashion-show` and your favorite linters: `jscs`, `eslint` and `jshint` are supported. 30 | ``` 31 | npm install fashion-show jscs eslint --save 32 | ``` 33 | - **4.** "Build" your dotfiles on prepublish (i.e. remove comments) 34 | ``` js 35 | "scripts": { 36 | "prepublish": "./node_modules/.bin/fashion-show-build" 37 | } 38 | ``` 39 | - **5.** Write a simple wrapper script to "lint" 40 | ``` js 41 | var path = require('path'); 42 | 43 | require('fashion-show')({ 44 | commands: ['jscs', 'eslint'], 45 | rc: path.join(__dirname, '..', 'dist') 46 | }, function (err, code) { 47 | if (err) { return process.exit(1); } 48 | process.exit(code); 49 | }); 50 | ``` 51 | - **6.** Expose that script as a "bin" in `your-styleguide` 52 | ``` js 53 | "bin": { 54 | "your-styleguide": "./bin/your-styleguide" 55 | } 56 | ``` 57 | - **7.** Depend on `your-styleguide` 58 | ``` 59 | cd your-styleguide 60 | npm publish 61 | cd some/other/project 62 | npm install your-styleguide --save-dev 63 | ``` 64 | - **8.** Use the bin you created on "pretest" 65 | ``` 66 | "scripts": { 67 | "pretest": "your-styleguide lib test" 68 | } 69 | ``` 70 | 71 | ## API Documentation 72 | 73 | At its core `fashion-show` will run the CLI versions of the lint tools you choose to use it with. A [comparison of JavaScript lint CLI options](https://github.com/indexzero/js-lint-compat/blob/master/CLI-OPTIONS.md) is available if you're interested in exploring this in depth, but `fashion-show` has gone to length to pick the best tool for the job where applicable so when you run: 74 | 75 | ``` js 76 | require('fashion-show')(options, function (err, code) { 77 | if (err) { return process.exit(1); } 78 | process.exit(code); 79 | }); 80 | ``` 81 | 82 | The list of all available `options` is: 83 | 84 | | option name | example | jshint | jscs | eslint | 85 | |:--------------|:---------------------|:--------------|:-------------|:-------------| 86 | | `commands` | `['jscs', 'eslint']` | `---` | `---` | `---` | 87 | | `targets` | `['lib/', 'test/']` | `...args` | `...args` | `...args` | 88 | | `rc` | `'../rc'` | `--config` | `--config` | `--config` | 89 | | `fix` | `true` | `---` | `--fix` | `---` | 90 | | `exts` | `['.jsx']` | `--extra-ext` | `---` | `--ext .js` | 91 | | `reporter` | `'checkstyle'` | `--reporter` | `--reporter` | `--format` | 92 | | `format` | `'checkstyle'` | `---` | `---` | `--format` | 93 | | `global` | `['my-global']` | `--prereq` | `---` | `--global` | 94 | | `binPath` | `node_modules/.bin` | `---` | `---` | `---` | 95 | 96 | All of these options are also configurable through the binary scripts that you define in **Step 5** above: 97 | 98 | | CLI option | option name | Sample usage | 99 | |:----------------|:--------------|:-----------------| 100 | | `...args` | `targets` | `lib/ test/` | 101 | | `-c,--command` | `commands` | `-c jscs` | 102 | | `-r,--rc` | `rc` | `-d ~/.lintrcs` | 103 | | `-f,--fix` | `fix` | `--fix` | 104 | | `-e,--ext` | `exts` | `--ext .jsx` | 105 | | `-r,--reporter` | `reporter` | `-r checkstyle` | 106 | | `-g,--global` | `global` | `-g my-global` | 107 | 108 | #### # `commands` 109 | 110 | Array of commands to actually run against. Each item in the Array can be a string command or an object: 111 | 112 | ``` js 113 | { 114 | 'command': 'jscs', 115 | 'args': ['extra', 'jscs', 'specific', 'args'] 116 | } 117 | ``` 118 | 119 | #### # `targets` 120 | 121 | The set of targets to run the given commands against. 122 | 123 | #### # `dist` 124 | 125 | Directory where all of your lint files is located. It will be default look for `.{command}rc`: `.jscsrc`, `.jshintrc`, `.eslintrc` 126 | 127 | #### # `reporter` 128 | 129 | Reporter passed to the linters that you are running. 130 | 131 | #### # `fix` 132 | 133 | If enabled will turn on [auto fixing in `jscs`](http://jscs.info/overview.html#cli) (Currently whitespace rules, EOF rule, and validateIndentation) 134 | 135 | #### # `exts` 136 | 137 | Set of **additional** extensions that you want to include running lint(s) against. 138 | 139 | #### # `global` 140 | 141 | Set of additional globals that you wish to enable 142 | 143 | 144 | ## Tests 145 | 146 | Tests are written with `mocha` and code coverage is provided by `istanbul`: 147 | 148 | ``` 149 | npm test 150 | ``` 151 | 152 | ##### Author: [Charlie Robbins](charlie.robbins@gmail.com) 153 | ##### License: MIT 154 | -------------------------------------------------------------------------------- /bin/build: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require('fs'), 4 | path = require('path'), 5 | util = require('util'); 6 | 7 | var yargs = require('yargs') 8 | .usage('Usage: $0 -i input-dir/ -o output-dir/') 9 | .option('pretty', { 10 | alias: 'p', 11 | boolean: true, 12 | description: 'Stringifies individual keys instead of the entire file.', 13 | default: false 14 | }) 15 | .option('input', { 16 | alias: 'i', 17 | string: true, 18 | description: 'Directory with input lint files', 19 | default: path.join(process.cwd(), 'dotfiles') 20 | }) 21 | .option('output', { 22 | alias: 'o', 23 | string: true, 24 | description: 'Directory to output final lint files', 25 | default: path.join(process.cwd(), 'dist') 26 | }) 27 | .option('help', { 28 | alias: 'h', 29 | boolean: true, 30 | description: 'Displays this message' 31 | }); 32 | 33 | var argv = yargs.argv, 34 | isComment = /^\s+\/\//, 35 | inputDir = path.resolve(argv.input), 36 | outputDir = path.resolve(argv.output), 37 | config; 38 | 39 | if (argv.help) { 40 | return yargs.showHelp(); 41 | } 42 | 43 | // 44 | // ### function stripNull (obj, key) 45 | // Deletes the `key` from the `obj` if the 46 | // value is EXPLICITLY null. 47 | // 48 | function stripNull(obj, key) { 49 | if (obj[key] == null) { 50 | delete obj[key]; 51 | return; 52 | } 53 | 54 | if (!Array.isArray(obj[key]) && typeof obj[key] === 'object') { 55 | Object.keys(obj[key]).forEach(stripNull.bind(null, obj[key])); 56 | if (!Object.keys(obj[key]).length) { 57 | delete obj[key]; 58 | } 59 | } 60 | } 61 | 62 | // 63 | // ### function readAndStrip(file) 64 | // Reads a file specified and strips out any 65 | // comments that exist in it. 66 | // 67 | function readAndStrip(file) { 68 | var filtered; 69 | 70 | try { 71 | filtered = JSON.parse( 72 | fs.readFileSync(path.join(inputDir, file), 'utf8') 73 | .split('\n') 74 | .filter(function (line) { 75 | return !isComment.test(line) && line.replace(/\s/g, ''); 76 | }) 77 | .join('\n') 78 | ); 79 | } catch (ex) { 80 | return null; 81 | } 82 | 83 | console.log('Read dotfiles/%s', file); 84 | return filtered; 85 | } 86 | 87 | // 88 | // ### function isObject 89 | // Silly isObject check 90 | // 91 | function isObject(o) { 92 | var ptype = Object.prototype.toString 93 | .call(o) 94 | .slice(8,14) 95 | .toLowerCase(); 96 | 97 | return ptype === 'object'; 98 | } 99 | 100 | // 101 | // ### function nestedStringify(obj) 102 | // Does a nested stringify of things 103 | // 104 | function nestedStringify(obj) { 105 | if (!isObject(obj)) { 106 | return JSON.stringify(obj); 107 | } 108 | 109 | return Object.keys(obj).reduce(function (acc, key, i, arr) { 110 | acc += util.format('\n "%s": %s', key, JSON.stringify(obj[key])); 111 | if (i !== arr.length - 1) { 112 | acc += ','; 113 | } 114 | 115 | return acc; 116 | }, '{') + '\n }'; 117 | } 118 | 119 | // 120 | // ### function stringify(obj) 121 | // Optionally stringifies each key individually of the 122 | // obj or the entire obj. 123 | // 124 | function stringify(obj) { 125 | if (!argv.pretty) { 126 | return JSON.stringify(obj, null, 2); 127 | } 128 | 129 | return Object.keys(obj).reduce(function (acc, key, i, arr) { 130 | acc += util.format('\n "%s": %s', key, nestedStringify(obj[key])); 131 | if (i !== arr.length - 1) { 132 | acc += ','; 133 | } 134 | 135 | return acc; 136 | }, '{') + '\n}'; 137 | } 138 | 139 | var files = { 140 | jscsrc: readAndStrip('.jscsrc'), 141 | jshintrc: readAndStrip('.jshintrc'), 142 | eslintrc: readAndStrip('.eslintrc') 143 | }; 144 | 145 | console.log('Strip all `null` properties from .jscsrc'); 146 | Object.keys(files.jscsrc).forEach(stripNull.bind(null, files.jscsrc)); 147 | 148 | // 149 | // Now write them all out 150 | // 151 | Object.keys(files).forEach(function (file) { 152 | if (!files[file]) { return; } 153 | 154 | console.log('Write to dist/%s', file); 155 | fs.writeFileSync( 156 | path.join(outputDir, '.' + file), 157 | stringify(files[file]), 158 | 'utf8' 159 | ); 160 | }); 161 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | path = require('path'), 3 | async = require('async'), 4 | debug = require('diagnostics')('fashion-show'), 5 | defaultify = require('./lib/defaultify'), 6 | run = require('./lib/run'); 7 | 8 | var rootDir = path.join(__dirname); 9 | 10 | var fashionShow = module.exports = function (defaults, callback) { 11 | var options = defaultify(defaults), 12 | commands = options.commands; 13 | 14 | debug('env.$PATH', process.env.PATH); 15 | async.parallel( 16 | commands.map(function (command) { 17 | debug('run.invoke { command: %s }', command); 18 | return async.apply(run, command, options); 19 | }), 20 | function (err, exits) { 21 | debug('run.finish { err: %s, exits: %j }', err, exits); 22 | 23 | if (err) { 24 | console.dir(err); 25 | return callback(err); 26 | } 27 | 28 | var code = 0; 29 | exits.forEach(function (args) { 30 | var exit = args[0], 31 | signal = args[1]; 32 | 33 | // 34 | // Set the "meta" error code to the first 35 | // unsuccessful error code returned by `jscs` 36 | // OR `jshint`. 37 | // 38 | if (!code) { 39 | if (exit) { code = exit; } 40 | else if (signal) { 41 | code = 1; 42 | } 43 | } 44 | }); 45 | 46 | callback(null, code); 47 | } 48 | ); 49 | }; 50 | -------------------------------------------------------------------------------- /lib/argify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'), 4 | debug = require('diagnostics')('fashion-show:argify'); 5 | 6 | /** 7 | * function argify(command, opts) 8 | * Returns a fully formed arguments array for the 9 | * specified options. 10 | */ 11 | var argify = module.exports = function (command, opts) { 12 | if (!opts || !opts.targets || !opts.targets.length) { 13 | throw new Error('opts and opts.targets are required'); 14 | } 15 | var basename = path.basename(command); 16 | 17 | var config = ['-c', path.join(opts.rc || '', '.' + basename + 'rc')]; 18 | debug('config', config); 19 | 20 | var extended = argify[basename](opts); 21 | debug('lint-specific', extended); 22 | 23 | return config 24 | .concat(extended) 25 | .concat(opts.targets); 26 | }; 27 | 28 | /** 29 | * function jscs(opts) 30 | * Returns a fully formed arguments array for the 31 | * specified options for jscs. 32 | */ 33 | argify.jscs = function (opts) { 34 | return [ 35 | opts.fix && '--fix', 36 | opts.reporter && '--reporter=' + opts.reporter 37 | ].filter(Boolean); 38 | }; 39 | 40 | /** 41 | * function eslint(opts) 42 | * Returns a fully formed arguments array for the 43 | * specified options for eslint. 44 | */ 45 | argify.eslint = function (opts) { 46 | // 47 | // eslint doesn't have a "--reporter" option, but has a "--format" option 48 | // 49 | var format = opts.format 50 | || opts.reporter; 51 | 52 | var exts = opts.exts && opts.exts.length; 53 | return [ 54 | // 55 | // TODO: Support opts.global 56 | // 57 | format && '--format=' + format, 58 | exts && '--ext=' + opts.exts.join(',') 59 | ].filter(Boolean); 60 | }; 61 | 62 | /** 63 | * function jshint(opts) 64 | * Returns a fully formed arguments array for the 65 | * specified options for jshint. 66 | */ 67 | argify.jshint = function (opts) { 68 | var exts = opts.exts && opts.exts.length; 69 | return [ 70 | // 71 | // TODO: Support opts.global 72 | // 73 | opts.reporter && '--reporter=' + opts.reporter, 74 | exts && '--extra-ext=' + opts.exts.join(',') 75 | ].filter(Boolean); 76 | }; 77 | -------------------------------------------------------------------------------- /lib/defaultify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'), 4 | minimist = require('minimist'), 5 | debug = require('diagnostics')('fashion-show:defaultify'); 6 | 7 | /** 8 | * Returns the fully options mixing in the specified `defaults` 9 | * along with any `argv` (whether explicit or implicit). 10 | */ 11 | module.exports = function defaultify(defaults) { 12 | defaults = defaults || {}; 13 | var argv = minimist(defaults.argv || process.argv.slice(2), { 14 | alias: { 15 | commands: 'c', 16 | rc: 'r', 17 | fix: 'f', 18 | ext: 'e', 19 | path: 'p', 20 | reporter: 'r', 21 | global: 'g' 22 | } 23 | }); 24 | 25 | debug('defaultify.defaults', defaults); 26 | 27 | if (argv.command && !Array.isArray(argv.command)) { 28 | argv.command = [argv.command]; 29 | } 30 | 31 | if (argv.ext && !Array.isArray(argv.ext)) { 32 | argv.ext = [argv.ext]; 33 | } 34 | 35 | if (argv._ && !argv._.length) { 36 | argv._ = null; 37 | } 38 | 39 | var options = { 40 | commands: toArray(argv.command || defaults.commands || ['jscs', 'eslint']), 41 | targets: toArray(argv._ || defaults.targets || ['lib']), 42 | rc: argv.rc || defaults.rc || defaults.configDir, 43 | cwd: argv.cwd || defaults.cwd || process.cwd(), 44 | fix: argv.fix || defaults.fix, 45 | env: defaults.env || {}, 46 | binPath: argv.path || defaults.binPath, 47 | reporter: argv.reporter || defaults.reporter, 48 | format: argv.format || defaults.format, 49 | global: argv.global || defaults.global, 50 | exts: toArray(argv.ext || defaults.exts || []) 51 | }; 52 | 53 | if (!options.binPath) { 54 | options.binPath = path.join( 55 | path.dirname(options.rc || ''), 56 | 'node_modules', 57 | '.bin' 58 | ); 59 | } 60 | 61 | debug('defaultify.options', options); 62 | return options; 63 | }; 64 | 65 | /** 66 | * Returns an Array-ified version of the obj. 67 | */ 68 | function toArray(obj) { 69 | return Array.isArray(obj) ? obj : [obj]; 70 | } 71 | -------------------------------------------------------------------------------- /lib/envify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assign = require('object-assign'); 4 | var delimiter = require('path').delimiter; 5 | 6 | /** 7 | * Returns process.env with `options.binPath` prepended 8 | * to the $PATH to ensure that the corrent lint binaries 9 | * are used by `fashion-show`. 10 | */ 11 | module.exports = function (options) { 12 | var env = assign({}, process.env, options.env); 13 | env.PATH = options.binPath + delimiter + env.PATH; 14 | return env; 15 | }; 16 | -------------------------------------------------------------------------------- /lib/printify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var chalk = require('chalk'); 4 | 5 | var isArg = /^-/; 6 | 7 | /** 8 | * function printify (lint) 9 | * Displays the information about the lint being run 10 | */ 11 | module.exports = function printify(lint, options) { 12 | var rc = options.rc 13 | ? options.rc.split('/').slice(-2).join('/') + '/' 14 | : ''; 15 | 16 | var lastArg; 17 | var args = lint.args 18 | .slice(0, lint.args.length - options.targets.length) 19 | .map(function (arg) { 20 | var match; 21 | if (lastArg === '-c' && (match = /\/(\..*)$/.exec(arg))) { 22 | arg = rc + match[1]; 23 | } 24 | 25 | lastArg = arg; 26 | return isArg.test(arg) 27 | ? chalk.yellow(arg) 28 | : chalk.cyan(arg); 29 | }).join(' '); 30 | 31 | var targets = options.targets.map(function (path) { 32 | return path.replace(/\/\/$/, '/'); 33 | }).join(' '); 34 | 35 | console.log('Running %s with options: %s %s', chalk.white.bold(lint.bin), 36 | args, chalk.magenta(targets)); 37 | }; 38 | -------------------------------------------------------------------------------- /lib/run.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var spawn = require('child_process').spawn, 4 | debug = require('diagnostics'), 5 | argify = require('./argify'), 6 | envify = require('./envify'), 7 | printify = require('./printify'); 8 | 9 | /* 10 | * Runs the specified `command` in the local node_modules bin with the 11 | * `args` against the `targets` provided. 12 | * 13 | * @param {options} Options to run the specified command. 14 | * @param {command} String command to run 15 | * @param {args} Array Explicit arguments to pass to command 16 | * @param {targets} Array List of (local) directory targets to run against. 17 | */ 18 | module.exports = function run(command, options, callback) { 19 | var lint = { 20 | // 21 | // Remark: how will be expose commands with relative? 22 | // Will we assume that `npm run` will suffice? 23 | // 24 | bin: command, 25 | // 26 | // Create the arguments for this specific command 27 | // 28 | args: argify(command, options), 29 | // 30 | // Run in `process.cwd()` to ensure that all relative 31 | // paths work. 32 | // 33 | options: { cwd: options.cwd } 34 | }; 35 | 36 | debug('fashion-show:run:lint.bin')(lint.bin); 37 | debug('fashion-show:run:lint.args')('%j', lint.args); 38 | debug('fashion-show:run:lint.options')('%j', lint.options); 39 | 40 | // 41 | // Set the environment variables of the lint binary to run. 42 | // n.b. we prepend the `binPath` from `envify` defaulting to 43 | // the node_modules/.bin directory relative to `options.rc`. 44 | // 45 | lint.options.env = envify(options); 46 | 47 | // Run the .CMD executible on Windows. 48 | if (/^win/.test(process.platform)) { 49 | lint.bin = lint.bin + '.CMD'; 50 | } 51 | 52 | var child = spawn(lint.bin, lint.args, lint.options); 53 | printify(lint, options); 54 | 55 | child.stdout.pipe(process.stdout); 56 | child.stderr.pipe(process.stderr); 57 | child.on('exit', function (code, signal) { 58 | var namespace = 'fashion-show:run:exit:' + command; 59 | debug(namespace)('code', code); 60 | debug(namespace)('code', signal); 61 | callback(null, code, signal); 62 | }); 63 | }; 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fashion-show", 3 | "description": "Build consistent and versioned styleguides by including and running consistent lint files across projects.", 4 | "version": "3.3.3", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "istanbul cover ./node_modules/.bin/_mocha test/" 8 | }, 9 | "bin": { 10 | "fashion-show-build": "./bin/build" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/indexzero/fashion-show" 15 | }, 16 | "keywords": [ 17 | "jshint", 18 | "jscs", 19 | "lint", 20 | "style", 21 | "code-style", 22 | "style-guide", 23 | "javascript" 24 | ], 25 | "author": "Charlie Robbins ", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/indexzero/fashion-show/issues" 29 | }, 30 | "homepage": "https://github.com/indexzero/fashion-show", 31 | "dependencies": { 32 | "async": "~0.9.0", 33 | "chalk": "^1.1.0", 34 | "diagnostics": "1.1.0", 35 | "minimist": "^1.1.2", 36 | "object-assign": "^4.0.1", 37 | "yargs": "~3.5.4" 38 | }, 39 | "devDependencies": { 40 | "assume": "^1.5.1", 41 | "eslint": "^1.3.1", 42 | "istanbul": "^0.3.17", 43 | "jscs": "^2.1.1", 44 | "jshint": "^2.8.0", 45 | "mocha": "^3.5.0", 46 | "mocha-istanbul": "^0.3.0", 47 | "proxyquire": "^1.8.0", 48 | "std-mocks": "^1.0.1", 49 | "strip-ansi": "^4.0.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/fixtures/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "espree", 3 | "env": { 4 | "node": true, 5 | "mocha": true 6 | }, 7 | "ecmaFeatures": {}, 8 | "rules": { 9 | "no-alert": 0, 10 | "no-array-constructor": 0, 11 | "no-bitwise": 0, 12 | "no-caller": 0, 13 | "no-catch-shadow": 0, 14 | "no-class-assign": 0, 15 | "no-cond-assign": 2, 16 | "no-console": 0, 17 | "no-const-assign": 0, 18 | "no-constant-condition": 2, 19 | "no-continue": 0, 20 | "no-control-regex": 2, 21 | "no-debugger": 2, 22 | "no-delete-var": 2, 23 | "no-div-regex": 0, 24 | "no-dupe-class-members": 0, 25 | "no-dupe-keys": 2, 26 | "no-dupe-args": 2, 27 | "no-duplicate-case": 2, 28 | "no-else-return": 0, 29 | "no-empty": 2, 30 | "no-empty-character-class": 2, 31 | "no-empty-label": 0, 32 | "no-eq-null": 0, 33 | "no-eval": 0, 34 | "no-ex-assign": 2, 35 | "no-extend-native": 0, 36 | "no-extra-bind": 0, 37 | "no-extra-boolean-cast": 2, 38 | "no-extra-parens": 0, 39 | "no-extra-semi": 2, 40 | "no-fallthrough": 2, 41 | "no-floating-decimal": 0, 42 | "no-func-assign": 2, 43 | "no-implicit-coercion": 0, 44 | "no-implied-eval": 0, 45 | "no-inline-comments": 0, 46 | "no-inner-declarations": [2, "functions"], 47 | "no-invalid-regexp": 2, 48 | "no-invalid-this": 0, 49 | "no-irregular-whitespace": 2, 50 | "no-iterator": 0, 51 | "no-label-var": 0, 52 | "no-labels": 0, 53 | "no-lone-blocks": 0, 54 | "no-lonely-if": 0, 55 | "no-loop-func": 0, 56 | "no-mixed-requires": [0, false], 57 | "no-mixed-spaces-and-tabs": [2, false], 58 | "linebreak-style": [0, "unix"], 59 | "no-multi-spaces": 0, 60 | "no-multi-str": 0, 61 | "no-multiple-empty-lines": [0, {"max": 2}], 62 | "no-native-reassign": 0, 63 | "no-negated-in-lhs": 2, 64 | "no-nested-ternary": 0, 65 | "no-new": 0, 66 | "no-new-func": 0, 67 | "no-new-object": 0, 68 | "no-new-require": 0, 69 | "no-new-wrappers": 0, 70 | "no-obj-calls": 2, 71 | "no-octal": 2, 72 | "no-octal-escape": 0, 73 | "no-param-reassign": 0, 74 | "no-path-concat": 0, 75 | "no-plusplus": 0, 76 | "no-process-env": 0, 77 | "no-process-exit": 0, 78 | "no-proto": 0, 79 | "no-redeclare": 2, 80 | "no-regex-spaces": 2, 81 | "no-restricted-modules": 0, 82 | "no-restricted-syntax": 0, 83 | "no-return-assign": 0, 84 | "no-script-url": 0, 85 | "no-self-compare": 0, 86 | "no-sequences": 0, 87 | "no-shadow": 0, 88 | "no-shadow-restricted-names": 0, 89 | "no-spaced-func": 0, 90 | "no-sparse-arrays": 2, 91 | "no-sync": 0, 92 | "no-ternary": 0, 93 | "no-trailing-spaces": 0, 94 | "no-this-before-super": 0, 95 | "no-throw-literal": 0, 96 | "no-undef": 2, 97 | "no-undef-init": 0, 98 | "no-undefined": 0, 99 | "no-unexpected-multiline": 0, 100 | "no-underscore-dangle": 0, 101 | "no-unneeded-ternary": 0, 102 | "no-unreachable": 2, 103 | "no-unused-expressions": 0, 104 | "no-unused-vars": [2, {"vars": "all", "args": "after-used"}], 105 | "no-use-before-define": 0, 106 | "no-useless-call": 0, 107 | "no-useless-concat": 0, 108 | "no-void": 0, 109 | "no-var": 0, 110 | "no-warning-comments": [0, { "terms": ["todo", "fixme", "xxx"], "location": "start" }], 111 | "no-with": 0, 112 | 113 | "array-bracket-spacing": [0, "never"], 114 | "arrow-parens": 0, 115 | "arrow-spacing": 0, 116 | "accessor-pairs": 0, 117 | "block-scoped-var": 0, 118 | "block-spacing": 0, 119 | "brace-style": [0, "1tbs"], 120 | "callback-return": 0, 121 | "camelcase": 0, 122 | "comma-dangle": [2, "never"], 123 | "comma-spacing": 0, 124 | "comma-style": 0, 125 | "complexity": [0, 11], 126 | "computed-property-spacing": [0, "never"], 127 | "consistent-return": 0, 128 | "consistent-this": [0, "that"], 129 | "constructor-super": 0, 130 | "curly": [0, "all"], 131 | "default-case": 0, 132 | "dot-location": 0, 133 | "dot-notation": [0, { "allowKeywords": true }], 134 | "eol-last": 0, 135 | "eqeqeq": 0, 136 | "func-names": 0, 137 | "func-style": [0, "declaration"], 138 | "generator-star-spacing": 0, 139 | "global-require": 0, 140 | "guard-for-in": 0, 141 | "handle-callback-err": 0, 142 | "id-length": 0, 143 | "indent": 0, 144 | "init-declarations": 0, 145 | "key-spacing": [0, { "beforeColon": false, "afterColon": true }], 146 | "lines-around-comment": 0, 147 | "max-depth": [0, 4], 148 | "max-len": [0, 80, 4], 149 | "max-nested-callbacks": [0, 2], 150 | "max-params": [0, 3], 151 | "max-statements": [0, 10], 152 | "new-cap": 0, 153 | "new-parens": 0, 154 | "newline-after-var": 0, 155 | "object-curly-spacing": [0, "never"], 156 | "object-shorthand": 0, 157 | "one-var": [0, "always"], 158 | "operator-assignment": [0, "always"], 159 | "operator-linebreak": 0, 160 | "padded-blocks": 0, 161 | "prefer-arrow-callback": 0, 162 | "prefer-const": 0, 163 | "prefer-spread": 0, 164 | "prefer-reflect": 0, 165 | "prefer-template": 0, 166 | "quote-props": 0, 167 | "quotes": [0, "double"], 168 | "radix": 0, 169 | "id-match": 0, 170 | "require-yield": 0, 171 | "semi": 0, 172 | "semi-spacing": [0, {"before": false, "after": true}], 173 | "sort-vars": 0, 174 | "space-after-keywords": [0, "always"], 175 | "space-before-keywords": [0, "always"], 176 | "space-before-blocks": [0, "always"], 177 | "space-before-function-paren": [0, "always"], 178 | "space-in-parens": [0, "never"], 179 | "space-infix-ops": 0, 180 | "space-return-throw-case": 0, 181 | "space-unary-ops": [0, { "words": true, "nonwords": false }], 182 | "spaced-comment": 0, 183 | "strict": 0, 184 | "use-isnan": 2, 185 | "valid-jsdoc": 0, 186 | "valid-typeof": 2, 187 | "vars-on-top": 0, 188 | "wrap-iife": 0, 189 | "wrap-regex": 0, 190 | "yoda": [0, "never"] 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /test/fixtures/.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": true, 3 | "requireCurlyBraces": [ 4 | "if", 5 | "else", 6 | "for", 7 | "while", 8 | "do", 9 | "try", 10 | "catch", 11 | "switch" 12 | ], 13 | "requireSpaceBeforeKeywords": [ 14 | "else", 15 | "while", 16 | "catch" 17 | ], 18 | "requireSpaceAfterKeywords": true, 19 | "requireSpaceBeforeBlockStatements": true, 20 | "requireParenthesesAroundIIFE": true, 21 | "requireSpacesInConditionalExpression": true, 22 | "requireSpacesInAnonymousFunctionExpression": { 23 | "beforeOpeningRoundBrace": true, 24 | "beforeOpeningCurlyBrace": true 25 | }, 26 | "requireSpacesInNamedFunctionExpression": { 27 | "beforeOpeningCurlyBrace": true 28 | }, 29 | "disallowSpacesInNamedFunctionExpression": { 30 | "beforeOpeningRoundBrace": true 31 | }, 32 | "requireSpacesInFunctionDeclaration": { 33 | "beforeOpeningCurlyBrace": true 34 | }, 35 | "disallowSpacesInFunctionDeclaration": { 36 | "beforeOpeningRoundBrace": true 37 | }, 38 | "disallowSpacesInCallExpression": true, 39 | "requireBlocksOnNewline": 1, 40 | "disallowPaddingNewlinesInBlocks": true, 41 | "requirePaddingNewlinesBeforeKeywords": [], 42 | "disallowKeywordsOnNewLine": [ 43 | "else", 44 | "catch" 45 | ], 46 | "disallowEmptyBlocks": true, 47 | "requireSpacesInsideObjectBrackets": "all", 48 | "disallowSpacesInsideArrayBrackets": "all", 49 | "disallowSpacesInsideParentheses": true, 50 | "validateParameterSeparator": ", ", 51 | "disallowQuotedKeysInObjects": true, 52 | "requireDotNotation": true, 53 | "requireSemicolons": true, 54 | "requireSpaceBetweenArguments": true, 55 | "disallowSpaceAfterObjectKeys": true, 56 | "requireSpaceBeforeObjectValues": true, 57 | "requireCommaBeforeLineBreak": true, 58 | "disallowSpaceAfterPrefixUnaryOperators": true, 59 | "disallowSpaceBeforePostfixUnaryOperators": true, 60 | "requireSpaceBeforeBinaryOperators": true, 61 | "requireSpaceAfterBinaryOperators": true, 62 | "requireCamelCaseOrUpperCaseIdentifiers": "ignoreProperties", 63 | "requireCapitalizedConstructors": true, 64 | "disallowKeywords": [ 65 | "with" 66 | ], 67 | "disallowMultipleLineStrings": true, 68 | "disallowMultipleLineBreaks": true, 69 | "disallowMixedSpacesAndTabs": true, 70 | "disallowTrailingWhitespace": true, 71 | "requireLineFeedAtFileEnd": true, 72 | "disallowTrailingComma": true, 73 | "validateLineBreaks": "LF", 74 | "validateIndentation": 2, 75 | "requireSpaceAfterLineComment": true, 76 | "maximumLineLength": 100, 77 | "requireFunctionDeclarations": true, 78 | "disallowNewlineBeforeBlockStatements": true, 79 | "safeContextKeyword": [ 80 | "self", 81 | "that" 82 | ], 83 | "validateQuoteMarks": { 84 | "mark": "'", 85 | "escape": true 86 | } 87 | } -------------------------------------------------------------------------------- /test/fixtures/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "browser": false, 3 | "browserify": false, 4 | "couch": true, 5 | "devel": true, 6 | "dojo": false, 7 | "jasmine": false, 8 | "jquery": true, 9 | "mocha": true, 10 | "node": true, 11 | "phantom": true, 12 | "prototypejs": false, 13 | "qunit": false, 14 | "rhino": false, 15 | "shelljs": false, 16 | "typed": true, 17 | "worker": false, 18 | "wsh": true, 19 | "yui": false, 20 | "asi": false, 21 | "bitwise": false, 22 | "boss": false, 23 | "debug": false, 24 | "eqeqeq": true, 25 | "eqnull": true, 26 | "evil": false, 27 | "expr": false, 28 | "forin": false, 29 | "freeze": true, 30 | "futurehostile": true, 31 | "globalstrict": false, 32 | "lastsemic": false, 33 | "latedef": "nofunc", 34 | "laxbreak": true, 35 | "loopfunc": true, 36 | "maxdepth": 4, 37 | "maxerr": 100, 38 | "maxparams": 4, 39 | "mootools": false, 40 | "noarg": true, 41 | "nocomma": true, 42 | "nonbsp": true, 43 | "nonew": true, 44 | "passfail": false, 45 | "plusplus": false, 46 | "regexdash": false, 47 | "regexp": true, 48 | "scripturl": true, 49 | "shadow": true, 50 | "singleGroups": true, 51 | "strict": false, 52 | "sub": true, 53 | "supernew": true, 54 | "trailing": true, 55 | "undef": true, 56 | "unused": true, 57 | "white": false 58 | } -------------------------------------------------------------------------------- /test/integration.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'), 4 | assume = require('assume'), 5 | stdMocks = require('std-mocks'), 6 | stripAnsi = require('strip-ansi'), 7 | fashionShow = require('../index'); 8 | 9 | var rootDir = path.join(__dirname, '..'); 10 | 11 | /** 12 | * Returns a function which runs the specified 13 | * linter `commands` and asserts a particular 14 | * output from `fashion-show`. 15 | */ 16 | function assumeLinterRuns(commands, expected) { 17 | expected = expected || {}; 18 | expected.stdout = expected.stdout || [expectedOutput(commands)]; 19 | 20 | return function (done) { 21 | stdMocks.use(); 22 | 23 | fashionShow({ 24 | commands: commands, 25 | rc: path.join('.', 'test', 'fixtures'), 26 | targets: ['lib'], 27 | cwd: rootDir 28 | }, function (err, code) { 29 | stdMocks.restore(); 30 | 31 | var output = stdMocks.flush(); 32 | output.stdout = output.stdout 33 | .map(function (line) { 34 | return stripAnsi('' + line).trim(); 35 | }); 36 | 37 | if (err || code || output.stdout.length !== expected.stdout.length) { 38 | ['stdout', 'stderr'].forEach(function (pipe) { 39 | console.log(output[pipe].join('\n')); 40 | }); 41 | } 42 | 43 | assume(err).equals(null); 44 | assume(code).equals(0); 45 | 46 | assume(output.stdout.length).gte(expected.stdout.length); 47 | assume(output.stdout[0]).equals(expected.stdout[0]); 48 | done(); 49 | }); 50 | }; 51 | } 52 | 53 | /** 54 | * Returns the expected output for a specified linter 55 | */ 56 | function expectedOutput(linter) { 57 | return [ 58 | 'Running ' + linter + ' with options:', 59 | '-c test/fixtures/.' + linter + 'rc', 60 | 'lib' 61 | ].join(' '); 62 | } 63 | 64 | describe('fashion-show (integration)', function () { 65 | it('should run eslint correctly', assumeLinterRuns('eslint')); 66 | it('should run jshint correctly', assumeLinterRuns('jshint')); 67 | it('should run jscs correctly', assumeLinterRuns('jscs')); 68 | }); 69 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --ui bdd 2 | --recursive 3 | -------------------------------------------------------------------------------- /test/mocks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var EventEmitter = require('events').EventEmitter, 4 | stream = require('stream'), 5 | util = require('util'); 6 | 7 | /** 8 | * function childProcess 9 | * A proper, simple mock for `child_process` when 10 | * using `proxyquire`. 11 | */ 12 | exports.childProcess = function (defaults) { 13 | defaults = defaults || {}; 14 | return { 15 | spawn: function spawn(script, opts) { 16 | return new ChildProc(script, opts, defaults); 17 | } 18 | }; 19 | }; 20 | 21 | /** 22 | * function Child(script, opts) 23 | * A proper, simple mock for a child process when 24 | * using `proxyquire`. 25 | */ 26 | function ChildProc(script, opts, defaults) { 27 | EventEmitter.call(this); 28 | this.script = script; 29 | this.options = opts; 30 | this.stdout = new stream.Stream(); 31 | this.stderr = new stream.Stream(); 32 | 33 | setImmediate(this.emit, 'exit', 0, null); 34 | } 35 | 36 | util.inherits(ChildProc, EventEmitter); 37 | -------------------------------------------------------------------------------- /test/unit/argify.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assume = require('assume'), 4 | argify = require('../../lib/argify'); 5 | 6 | /** 7 | * Exports a test suite assets that common (i.e. shared) 8 | * options for linters. 9 | * 10 | * @param {String} command Name of the linter to run 11 | */ 12 | function assumeSharedOptions(command) { 13 | return function () { 14 | it('{ reporter }', function () { 15 | var mixin = argify[command]({ reporter: 'spec' }); 16 | assume(mixin).deep.equals(['--reporter=spec']); 17 | }); 18 | 19 | it ('{} (no args)', function () { 20 | var mixin = argify[command]({}); 21 | assume(mixin).deep.equals([]); 22 | }); 23 | }; 24 | } 25 | 26 | /* 27 | * Returns a function with no arguments 28 | * that invokes argify the `command` and `opts` 29 | * provided. 30 | */ 31 | function invokeify(command, opts) { 32 | return function () { 33 | argify(command, opts); 34 | }; 35 | } 36 | 37 | describe('argify (unit)', function () { 38 | describe('argify(command, opts)', function () { 39 | it('jscs { rc, fix, reporter, targets }', function () { 40 | var args = argify('jscs', { 41 | rc: '/path/to/rcfile', 42 | fix: true, 43 | reporter: 'spec', 44 | targets: ['lib/', 'test/*.js'] 45 | }); 46 | 47 | assume(args).deep.equal([ 48 | '-c', '/path/to/rcfile/.jscsrc', 49 | '--fix', 50 | '--reporter=spec', 51 | 'lib/', 'test/*.js' 52 | ]); 53 | }); 54 | 55 | it('eslint { rc, reporter, targets }', function () { 56 | var args = argify('eslint', { 57 | rc: '/path/to/rcfile', 58 | reporter: 'spec', 59 | targets: ['lib/', 'test/*.js'] 60 | }); 61 | 62 | assume(args).deep.equal([ 63 | '-c', '/path/to/rcfile/.eslintrc', 64 | '--format=spec', 65 | 'lib/', 'test/*.js' 66 | ]); 67 | 68 | it('{ reporter }', function () { 69 | var mixin = argify[command]({ reporter: 'spec' }); 70 | assume(mixin).deep.equals(['--format=spec']); 71 | }); 72 | 73 | it('{ format }', function () { 74 | var mixin = argify[command]({ format: 'spec' }); 75 | assume(mixin).deep.equals(['--format=spec']); 76 | }); 77 | 78 | 79 | it ('{} (no args)', function () { 80 | var mixin = argify[command]({}); 81 | assume(mixin).deep.equals([]); 82 | }); 83 | 84 | }); 85 | 86 | it('jshint { rc, reporter, targets }', function () { 87 | var args = argify('jshint', { 88 | rc: '/path/to/rcfile', 89 | reporter: 'spec', 90 | targets: ['lib/', 'test/*.js'] 91 | }); 92 | 93 | assume(args).deep.equal([ 94 | '-c', '/path/to/rcfile/.jshintrc', 95 | '--reporter=spec', 96 | 'lib/', 'test/*.js' 97 | ]); 98 | }); 99 | 100 | it('throws with no opts or opts.targets', function () { 101 | var msg = 'opts and opts.targets are required'; 102 | 103 | assume(invokeify('jscs')).throws(msg); 104 | assume(invokeify('eslint', {})).throws(msg); 105 | assume(invokeify('jshint', { targets: [] })).throws(msg); 106 | }); 107 | }); 108 | 109 | describe('jscs', function () { 110 | it('{ reporter, fix }', function () { 111 | var mixin = argify.jscs({ fix: true, reporter: 'spec' }); 112 | assume(mixin).deep.equals(['--fix', '--reporter=spec']); 113 | }); 114 | 115 | it('{ fix }', function () { 116 | var mixin = argify.jscs({ fix: true }); 117 | assume(mixin).deep.equals(['--fix']); 118 | }); 119 | }); 120 | 121 | describe('shared options', function () { 122 | describe('jscs', assumeSharedOptions('jscs')); 123 | describe('jshint', assumeSharedOptions('jshint')); 124 | }); 125 | }); 126 | -------------------------------------------------------------------------------- /test/unit/defaultify.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assume = require('assume'), 4 | defaultify = require('../../lib/defaultify'); 5 | 6 | describe('defaultify (unit)', function () { 7 | it('should have expected defaults', function () { 8 | var oldArgv = process.argv; 9 | process.argv = []; 10 | 11 | var defaults = defaultify(); 12 | process.argv = oldArgv; 13 | 14 | assume(defaults.commands).deep.equals(['jscs', 'eslint']); 15 | assume(defaults.targets).deep.equals(['lib']); 16 | assume(defaults.rc).equals(undefined); 17 | assume(defaults.fix).equals(undefined); 18 | assume(defaults.reporter).equals(undefined); 19 | assume(defaults.global).equals(undefined); 20 | assume(defaults.exts).deep.equals([]); 21 | }); 22 | 23 | it('--rc'); 24 | it('--fix'); 25 | it('--reporter'); 26 | it('--global'); 27 | it('--command (one)'); 28 | it('--command (multiple)'); 29 | it('--ext (one)'); 30 | it('--ext (multiple)'); 31 | it('argv._ (one)'); 32 | it('argv._ (multiple)'); 33 | 34 | it('{ commands }'); 35 | it('{ targets }'); 36 | it('{ rc }'); 37 | it('{ fix }'); 38 | it('{ reporter }'); 39 | it('{ global }'); 40 | it('{ exts }'); 41 | }); 42 | -------------------------------------------------------------------------------- /test/unit/printify.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assume = require('assume'), 4 | stdMocks = require('std-mocks'); 5 | 6 | describe('printify (unit)', function () { 7 | it('should output the correct logs'); 8 | }); 9 | -------------------------------------------------------------------------------- /test/unit/run.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'), 4 | assume = require('assume'), 5 | proxyquire = require('proxyquire'), 6 | mocks = require('../mocks'); 7 | 8 | var run = proxyquire(path.join(__dirname, '..', '..', 'lib', 'run'), { 9 | child_process: mocks.childProcess() 10 | }); 11 | 12 | describe('run (unit)', function () { 13 | it('should run eslint'); 14 | it('should run jshint'); 15 | it('should run jscs'); 16 | }); 17 | --------------------------------------------------------------------------------