From f1bd1d90a395c39c13439274efed3c0c7ecdf58b Mon Sep 17 00:00:00 2001 From: Steve Kellock Date: Tue, 21 Mar 2017 11:07:29 -0400 Subject: [PATCH] Enforces global dependencies are available. --- packages/ignite-cli/package.json | 5 +- packages/ignite-cli/src/cli/check.js | 52 +++++--- .../src/cli/enforceGlobalDependency.js | 125 ++++++++++++++++++ .../ignite-cli/src/extensions/reactNative.js | 12 -- packages/ignite-cli/src/lib/exitCodes.js | 7 +- 5 files changed, 169 insertions(+), 32 deletions(-) create mode 100644 packages/ignite-cli/src/cli/enforceGlobalDependency.js diff --git a/packages/ignite-cli/package.json b/packages/ignite-cli/package.json index 370a9441d..d61469d3d 100644 --- a/packages/ignite-cli/package.json +++ b/packages/ignite-cli/package.json @@ -32,10 +32,11 @@ "gluegun": "^0.16.0", "gluegun-patching": "^0.3.0", "minimist": "^1.2.0", + "pretty-error": "^2.0.2", "ramda": "^0.23.0", "ramdasauce": "^1.2.0", - "shelljs": "^0.7.6", - "pretty-error": "^2.0.2" + "semver": "^5.3.0", + "shelljs": "^0.7.6" }, "devDependencies": { "ava": "^0.18.1", diff --git a/packages/ignite-cli/src/cli/check.js b/packages/ignite-cli/src/cli/check.js index d15c678ee..bdd3f69b7 100644 --- a/packages/ignite-cli/src/cli/check.js +++ b/packages/ignite-cli/src/cli/check.js @@ -1,34 +1,52 @@ +// NOTE: this file is intentionally written with es3 + // check the node version var Sniff = require('gluegun/sniff') -var Shell = require('shelljs') // check the node version if (!Sniff.isNewEnough) { - console.log('Node.js 7.6+ is required to run. You have ' + Sniff.nodeVersion + '. Womp, womp.') + console.log( + 'Node.js 7.6+ is required to run. You have ' + + Sniff.nodeVersion + + '. Womp, womp.' + ) process.exit(1) } // check for async and await if (!Sniff.hasAsyncAwait) { - console.log('The async feature is not available. Please ensure your Node is up to date.') + console.log( + 'The async feature is not available. Please ensure your Node is up to date.' + ) process.exit(2) } -// check the yarn version is >= 0.20 -function getYarnVersion () { - return Shell.exec('yarn --version', {silent: true}).stdout.replace('\n', '').split('.') -} +// lets check our global dependencies +var enforceGlobalDependency = require('./enforceGlobalDependency') +var exitCodes = require('../lib/exitCodes') -function checkYarn (yarnVersion) { - var major = parseInt(yarnVersion[0], 10) - var minor = parseInt(yarnVersion[1], 10) - return (major >= 1) || (minor >= 20) -} +// react-native-cli +var rnCli = enforceGlobalDependency({ + optional: false, + range: '>=2.0.0', + which: 'react-native', + versionCommand: 'react-native --version', + installMessage: 'To install: npm i -g react-native-cli' +}) -var hasYarn = Shell.which('yarn') -if (hasYarn && !checkYarn(getYarnVersion())) { - console.log('You have yarn installed but it\'s version ' + getYarnVersion().join('.') + '. Ignite requires yarn 20.0+.') - console.log('Run `brew upgrade yarn` if you use Homebrew or visit https://yarnpkg.com/en/docs/install.') - process.exit(3) +if (!rnCli) { + process.exit(exitCodes.INVALID_GLOBAL_DEPENDENCY) } +// yarn +var yarn = enforceGlobalDependency({ + optional: true, + range: '>=0.21.0', + which: 'yarn', + versionCommand: 'yarn --version', + installMessage: 'See https://yarnpkg.com/en/docs/install on how to upgrade for your OS.' +}) + +if (!yarn) { + process.exit(exitCodes.INVALID_GLOBAL_DEPENDENCY) +} diff --git a/packages/ignite-cli/src/cli/enforceGlobalDependency.js b/packages/ignite-cli/src/cli/enforceGlobalDependency.js new file mode 100644 index 000000000..2326c6acb --- /dev/null +++ b/packages/ignite-cli/src/cli/enforceGlobalDependency.js @@ -0,0 +1,125 @@ +// NOTE: this file is intentionally written with es3 +var shell = require('shelljs') +var semver = require('semver') +var ramda = require('ramda') + +/** + * Extracts the version number from a somewhere within a string. + * + * This looks for things that look like semver (e.g. 0.0.0) and picks the first one. + * + * @param {string} raw - The raw text which came from running the version command. + */ +function defaultVersionMatcher (raw) { + // sanity check + if (ramda.isNil(raw) || ramda.isEmpty(raw)) return null + + try { + // look for something that looks like semver + var rx = /([0-9]+\.[0-9]+\.[0-9]+)/ + var match = ramda.match(rx, raw) + if (match.length > 0) { + return match[0] + } else { + return null + } + } catch (err) { + // something tragic happened + return false + } +} + +/** + * Verifies the dependency which is installed is compatible with ignite. + * + * @param {any} opts The options to enforce. + * @param {boolean} opts.optional Is this an optional dependency? + * @param {string} opts.range The semver range to test against. + * @param {string} opts.which The command to run `which` on. + * @param {string} opts.versionCommand The command to run which returns text containing the version number. + * @param {string} opts.installMessage What to print should we fail. + * @param {function} opts.versionMatcher A way to override the method to discover the version number. + * @return {boolean} `true` if we meet the requirements; otherwise `false`. + */ +function enforce (opts = {}) { + // opts to pass in + var optional = opts.optional || false + var range = opts.range + var which = opts.which + var versionCommand = opts.versionCommand + var installMessage = opts.installMessage + var versionMatcher = opts.versionMatcher || defaultVersionMatcher + + /** + * Prints a friendly message that they don't meet the requirement. + * + * @param {string} installedVersion - current version if installed. + */ + function printNotMetMessage (installedVersion) { + console.log('Ignite requires ' + which + ' ' + range + ' to be installed.') + if (installedVersion) { + console.log('') + console.log('You currently have ' + installedVersion + ' installed.') + } + console.log('') + console.log(installMessage) + } + + /** + * Gets the version from the global dependency. + * + * @return {string} The version number or null. + */ + function getVersion () { + // parse the version number + try { + // grab the raw output + var result = shell.exec(versionCommand, { silent: true }) + var rawOut = ramda.trim(result.stdout || '') + var rawErr = ramda.trim(result.stderr || '') // java -version does this... grr + + // assign the "right" one to raw + var raw = rawOut + if (ramda.isEmpty(raw)) { + raw = rawErr + } + if (ramda.isEmpty(raw)) { + raw = null + } + + // and run it by the version matcher + return versionMatcher(raw) + } catch (err) { + return null + } + } + + // are we installed? + var isInstalled = Boolean(shell.which(which)) + + if (!isInstalled) { + if (optional) { + return true + } else { + printNotMetMessage() + return false + } + } + + // which version is installed? + try { + var installedVersion = getVersion() + var isMet = semver.satisfies(installedVersion, range) + + // dependency has minimum met, we're good. + if (isMet) return true + } catch (err) { + // can't parse? just catch and we'll fallback to an error. + } + + // o snap, time to upgrade + printNotMetMessage(installedVersion) + return false +} + +module.exports = enforce diff --git a/packages/ignite-cli/src/extensions/reactNative.js b/packages/ignite-cli/src/extensions/reactNative.js index 279d1272c..4a7688795 100644 --- a/packages/ignite-cli/src/extensions/reactNative.js +++ b/packages/ignite-cli/src/extensions/reactNative.js @@ -56,18 +56,6 @@ function attach (plugin, command, context) { rnOptions.push('--skip-jest') } - // install React Native CLI if it isn't found - const rncliSpinner = print.spin(`checking react-native-cli`) - const cliInstalled = await system.which('react-native') - if (cliInstalled) { - rncliSpinner.stop() - } else { - // No React Native installed, let's get it - rncliSpinner.text = 'installing react-native-cli' - await system.run('npm install -g react-native-cli') - rncliSpinner.succeed(`installed react-native-cli`) - } - // react-native init const cmd = trim(`react-native init ${name} ${rnOptions.join(' ')}`) log('initializing react native') diff --git a/packages/ignite-cli/src/lib/exitCodes.js b/packages/ignite-cli/src/lib/exitCodes.js index 1dafd3b50..d6ccaa47a 100644 --- a/packages/ignite-cli/src/lib/exitCodes.js +++ b/packages/ignite-cli/src/lib/exitCodes.js @@ -60,5 +60,10 @@ module.exports = { /** * node_modules/react-native already exists. */ - EXISTING_REACT_NATIVE: 11 + EXISTING_REACT_NATIVE: 11, + + /** + * One of the global dependencies are not met. + */ + INVALID_GLOBAL_DEPENDENCY: 12 }