From 39356ebb579f8071922121624fc0f23668f3c5d0 Mon Sep 17 00:00:00 2001 From: jeffmo Date: Thu, 15 May 2014 14:53:50 -0700 Subject: [PATCH] Make `npm install -g jest-cli` work reasonably well Previously, if you were to `npm install -g jest-cli`, life would suck if you had an old version of jest installed globally...but your tests only work properly with new versions. This makes the jest binary try to be smart and look for a jest binary that might already exist in CWD project -- and if one is found, use that to run the tests instead of the global version. --- bin/jest.js | 380 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 237 insertions(+), 143 deletions(-) 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(); +}