diff --git a/bin/jest.js b/bin/jest.js index b72cd1f5b633..428d3f106b40 100755 --- a/bin/jest.js +++ b/bin/jest.js @@ -18,6 +18,15 @@ var Q = require('q'); var TestRunner = require('../src/TestRunner'); var utils = require('../src/lib/utils'); +var _jestVersion = null; +function _getJestVersion() { + if (_jestVersion === null) { + var pkgJsonPath = path.resolve(__dirname, '..', 'package.json'); + _jestVersion = require(pkgJsonPath).version; + } + return _jestVersion; +} + function _findChangedFiles(dirPath) { var deferred = Q.defer(); @@ -102,166 +111,251 @@ function _wrapDesc(desc) { }, ['']).join(indent); } -var argv = optimist - .usage('Usage: $0 [--config=] [TestPathRegExp]') - .options({ - config: { - alias: 'c', - description: _wrapDesc( - 'The path to a jest config file specifying how to find and execute ' + - 'tests.' - ), - type: 'string' - }, - coverage: { - description: _wrapDesc( - 'Indicates that test coverage information should be collected and ' + - 'reported in the output.' - ), - type: 'boolean' - }, - maxWorkers: { - alias: 'w', - description: _wrapDesc( - 'Specifies the maximum number of workers the worker-pool will spawn ' + - 'for running tests. This defaults to the number of the cores ' + - 'available on your machine. (its usually best not to override this ' + - 'default)' - ), - type: 'string' // no, optimist -- its a number.. :( - }, - onlyChanged: { - alias: 'o', - description: _wrapDesc( - 'Attempts to identify which tests to run based on which files have ' + - 'changed in the current repository. Only works if you\'re running ' + - 'tests in a git repository at the moment.' - ), - type: 'boolean' - }, - runInBand: { - alias: 'i', - description: _wrapDesc( - 'Run all tests serially in the current process (rather than creating ' + - 'a worker pool of child processes that run tests). This is sometimes ' + - 'useful for debugging, but such use cases are pretty rare.' - ), - type: 'boolean' - } - }) - .check(function(argv) { - if (argv.runInBand && argv.hasOwnProperty('maxWorkers')) { - throw ( - "Both --runInBand and --maxWorkers were specified, but these two " + - "options don't make sense together. Which is it?" - ); - } +function runCLI(argv, packageRoot) { + if (argv.version) { + console.log('v' + _getJestVersion()); + process.exit(0); + } - if (argv.onlyChanged && argv._.length > 0) { - throw ( - "Both --onlyChanged and a path pattern were specified, but these two " + - "options don't make sense together. Which is it? Do you want to run " + - "tests for changed files? Or for a specific set of files?" - ); + var config; + if (argv.config) { + config = utils.loadConfigFromFile(argv.config); + } else { + var pkgJsonPath = path.join(packageRoot, 'package.json'); + var pkgJson = fs.existsSync(pkgJsonPath) ? require(pkgJsonPath) : {}; + + // First look to see if there is a package.json file with a jest config in it + if (pkgJson.jest) { + if (!pkgJson.jest.hasOwnProperty('rootDir')) { + pkgJson.jest.rootDir = packageRoot; + } else { + pkgJson.jest.rootDir = path.resolve(packageRoot, pkgJson.jest.rootDir); + } + config = utils.normalizeConfig(pkgJson.jest); + config.name = pkgJson.name; + config = Q(config); + + // If not, use a sane default config + } else { + config = Q(utils.normalizeConfig({ + name: packageRoot.replace(/[/\\]/g, '_'), + rootDir: packageRoot, + testPathDirs: [packageRoot], + testPathIgnorePatterns: ['/node_modules/.+'] + })); } - }) - .argv + } -if (argv.help) { - optimist.showHelp(); - process.exit(); -} + config.done(function(config) { + var pathPattern = + argv._.length === 0 + ? /.*/ + : new RegExp(argv._.join('|')); -var config; -if (argv.config) { - config = utils.loadConfigFromFile(argv.config); -} else { - var cwd = process.cwd(); + var testRunnerOpts = {}; + if (argv.maxWorkers) { + testRunnerOpts.maxWorkers = argv.maxWorkers; + } - var pkgJsonPath = path.join(cwd, 'package.json'); - var pkgJson = fs.existsSync(pkgJsonPath) ? require(pkgJsonPath) : {}; + if (argv.coverage) { + config.collectCoverage = true; + } - // First look to see if there is a package.json file with a jest config in it - if (pkgJson.jest) { - if (!pkgJson.jest.hasOwnProperty('rootDir')) { - pkgJson.jest.rootDir = cwd; + var testRunner = new TestRunner(config, testRunnerOpts); + + function _runTestsOnPathPattern(pathPattern) { + return testRunner.findTestPathsMatching(pathPattern) + .then(function(matchingTestPaths) { + console.log('Found ' + matchingTestPaths.length + ' matching tests...'); + if (argv.runInBand) { + return testRunner.runTestsInBand(matchingTestPaths, _onResultReady); + } else { + return testRunner.runTestsParallel(matchingTestPaths, _onResultReady); + } + }) + .then(_onRunComplete); } - config = utils.normalizeConfig(pkgJson.jest); - config.name = pkgJson.name; - config = Q(config); - // If not, use a sane default config - } else { - config = Q(utils.normalizeConfig({ - name: cwd.replace(/[/\\]/g, '_'), - rootDir: cwd, - testPathDirs: [cwd], - testPathIgnorePatterns: ['/node_modules/.+'] - }, cwd)); - } + if (argv.onlyChanged) { + console.log('Looking for changed files...'); + + var testPathDirsAreGit = config.testPathDirs.map(_verifyIsGitRepository); + Q.all(testPathDirsAreGit).then(function(results) { + if (!results.every(function(result) { return result; })) { + console.error( + 'It appears that one of your testPathDirs does not exist ' + + 'with in a git repository. Currently --onlyChanged only works ' + + 'with git projects.\n' + ); + process.exit(1); + } + + return Q.all(config.testPathDirs.map(_findChangedFiles)); + }).then(function(changedPathSets) { + // Collapse changed files from each of the testPathDirs into a single list + // of changed file paths + var changedPaths = []; + changedPathSets.forEach(function(pathSet) { + changedPaths = changedPaths.concat(pathSet); + }); + return testRunner.findTestsRelatedTo(changedPaths); + }).done(function(affectedTestPaths) { + if (affectedTestPaths.length > 0) { + _runTestsOnPathPattern(new RegExp(affectedTestPaths.join('|'))).done(); + } else { + console.log('No tests to run!'); + } + }); + } else { + _runTestsOnPathPattern(pathPattern).done(); + } + }); } -config.done(function(config) { - var pathPattern = - argv._.length === 0 - ? /.*/ - : new RegExp(argv._.join('|')); +function _main() { + var argv = optimist + .usage('Usage: $0 [--config=] [TestPathRegExp]') + .options({ + config: { + alias: 'c', + description: _wrapDesc( + 'The path to a jest config file specifying how to find and execute ' + + 'tests.' + ), + type: 'string' + }, + coverage: { + description: _wrapDesc( + 'Indicates that test coverage information should be collected and ' + + 'reported in the output.' + ), + type: 'boolean' + }, + maxWorkers: { + alias: 'w', + description: _wrapDesc( + 'Specifies the maximum number of workers the worker-pool will spawn ' + + 'for running tests. This defaults to the number of the cores ' + + 'available on your machine. (its usually best not to override this ' + + 'default)' + ), + type: 'string' // no, optimist -- its a number.. :( + }, + onlyChanged: { + alias: 'o', + description: _wrapDesc( + 'Attempts to identify which tests to run based on which files have ' + + 'changed in the current repository. Only works if you\'re running ' + + 'tests in a git repository at the moment.' + ), + type: 'boolean' + }, + runInBand: { + alias: 'i', + description: _wrapDesc( + 'Run all tests serially in the current process (rather than creating ' + + 'a worker pool of child processes that run tests). This is sometimes ' + + 'useful for debugging, but such use cases are pretty rare.' + ), + type: 'boolean' + }, + version: { + alias: 'v', + description: _wrapDesc('Print the version and exit'), + type: 'boolean' + } + }) + .check(function(argv) { + if (argv.runInBand && argv.hasOwnProperty('maxWorkers')) { + throw ( + "Both --runInBand and --maxWorkers were specified, but these two " + + "options don't make sense together. Which is it?" + ); + } - var testRunnerOpts = {}; - if (argv.maxWorkers) { - testRunnerOpts.maxWorkers = argv.maxWorkers; - } + if (argv.onlyChanged && argv._.length > 0) { + throw ( + "Both --onlyChanged and a path pattern were specified, but these two " + + "options don't make sense together. Which is it? Do you want to run " + + "tests for changed files? Or for a specific set of files?" + ); + } + }) + .argv - if (argv.coverage) { - config.collectCoverage = true; + if (argv.help) { + optimist.showHelp(); + process.exit(0); } - var testRunner = new TestRunner(config, testRunnerOpts); + var cwd = process.cwd(); - function _runTestsOnPathPattern(pathPattern) { - return testRunner.findTestPathsMatching(pathPattern) - .then(function(matchingTestPaths) { - console.log('Found ' + matchingTestPaths.length + ' matching tests...'); - if (argv.runInBand) { - return testRunner.runTestsInBand(matchingTestPaths, _onResultReady); - } else { - return testRunner.runTestsParallel(matchingTestPaths, _onResultReady); - } - }) - .then(_onRunComplete); + // Is the cwd somewhere within an npm package? + var cwdPackageRoot = cwd; + while (!fs.existsSync(path.join(cwdPackageRoot, 'package.json'))) { + if (cwdPackageRoot === '/') { + cwdPackageRoot = cwd; + break; + } + cwdPackageRoot = path.resolve(cwdPackageRoot, '..'); } - if (argv.onlyChanged) { - console.log('Looking for changed files...'); + // Is there a version of Jest installed at our cwdPackageRoot? + var cwdJestBinPath = path.join( + cwdPackageRoot, + 'node_modules', + '.bin', + 'jest' + ); + + // Is there a package.json at our cwdPackageRoot that indicates that there + // should be a version of Jest installed? + var cwdPkgJsonPath = path.join(cwdPackageRoot, 'package.json'); + + // If a version of Jest was found installed in the CWD package, run using that + if (fs.existsSync(cwdJestBinPath)) { + var jestBinary = require(cwdJestBinPath); + if (!jestBinary.runCLI) { + console.error( + 'This project requires an older version of Jest than what you have ' + + 'installed globally.\n' + + 'Please upgrade this project past Jest version 0.1.5' + ); + process.exit(1); + } - var testPathDirsAreGit = config.testPathDirs.map(_verifyIsGitRepository); - Q.all(testPathDirsAreGit).then(function(results) { - if (!results.every(function(result) { return result; })) { - console.error( - 'It appears that one of your testPathDirs does not exist ' + - 'with in a git repository. Currently --onlyChanged only works ' + - 'with git projects.\n' - ); - process.exit(1); - } + jestBinary.runCLI(argv, cwdPackageRoot); + return; + } - return Q.all(config.testPathDirs.map(_findChangedFiles)); - }).then(function(changedPathSets) { - // Collapse changed files from each of the testPathDirs into a single list - // of changed file paths - var changedPaths = []; - changedPathSets.forEach(function(pathSet) { - changedPaths = changedPaths.concat(pathSet); - }); - return testRunner.findTestsRelatedTo(changedPaths); - }).done(function(affectedTestPaths) { - if (affectedTestPaths.length > 0) { - _runTestsOnPathPattern(new RegExp(affectedTestPaths.join('|'))).done(); - } else { - console.log('No tests to run!'); - } - }); - } else { - _runTestsOnPathPattern(pathPattern).done(); + // If a package.json was found in the CWD package indicating a specific + // version of Jest to be used, bail out and ask the user to `npm install` + // first + if (fs.existsSync(cwdPkgJsonPath)) { + var cwdPkgJson = require(cwdPkgJsonPath); + var cwdPkgDeps = cwdPkgJson.dependencies; + var cwdPkgDevDeps = cwdPkgJson.devDependencies; + + var thisJestVersion = _getJestVersion(); + + if (cwdPkgDeps && cwdPkgDeps['jest-cli'] + || cwdPkgDevDeps && cwdPkgDevDeps['jest-cli']) { + console.error( + 'Please run `npm install` to use the version of Jest intended for ' + + 'this project.' + ); + process.exit(1); + } + } + + if (!argv.version && cwdPackageRoot) { + console.log('Using Jest CLI v' + _getJestVersion()); } -}); + runCLI(argv, cwdPackageRoot); +} + +exports.runCLI = runCLI; + +if (require.main === module) { + _main(); +}