diff --git a/package.json b/package.json index b275b4ec0c..d01c17cf95 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "ini": "^1.3.4", "invariant": "^2.2.0", "is-builtin-module": "^1.0.0", + "is-ci": "^1.0.10", "leven": "^2.0.0", "loud-rejection": "^1.2.0", "minimatch": "^3.0.3", diff --git a/src/cli/commands/install.js b/src/cli/commands/install.js index 3dada9a1c6..890368b481 100644 --- a/src/cli/commands/install.js +++ b/src/cli/commands/install.js @@ -25,8 +25,15 @@ import * as crypto from '../../util/crypto.js'; import map from '../../util/map.js'; const invariant = require('invariant'); +const userHome = require('user-home'); +const semver = require('semver'); const emoji = require('node-emoji'); +const isCI = require('is-ci'); const path = require('path'); +const fs2 = require('fs'); + +const YARN_VERSION = require('../../../package.json').version; +const ONE_DAY = 1000 * 60 * 60 * 24; export type InstallPrepared = { skip: boolean, @@ -69,6 +76,43 @@ type Flags = { tilde: boolean, }; +/** + * Try and detect the installation method for Yarn and provide a command to update it with. + */ + +function getUpdateCommand(): ?string { + // Tarball install + if (fs2.existsSync(path.join(userHome, '.yarn'))) { + return 'yarn self-update'; + } + + // OSX + if (fs2.existsSync('/usr/local/Cellar')) { + return 'brew upgrade yarn'; + } + + // Debian + if (fs2.existsSync('/usr/share/lintian/overrides/yarn')) { + return 'sudo apt-get install yarn'; + } + + // npm + if (__dirname.indexOf('node_modules') >= 0) { + return 'npm upgrade --global yarn'; + } + + return null; +} + +function getUpdateInstaller(): ?string { + // Windows + if (fs2.existsSync('C:/Program Files/Yarn') || fs2.existsSync('C:/Program Files (x86)/Yarn')) { + return 'https://yarnpkg.com/latest.msi'; + } + + return null; +} + function normalizeFlags(config: Config, rawFlags: Object): Flags { const flags = { // install @@ -273,6 +317,8 @@ export class Install { */ async init(): Promise> { + this.checkUpdate(); + let [depRequests, rawPatterns] = await this.fetchRequestFromCwd(); const match = await this.matchesIntegrityHash(rawPatterns); @@ -355,6 +401,7 @@ export class Install { // fin! await this.saveLockfileAndIntegrity(rawPatterns); + this.maybeOutputUpdate(); this.config.requestManager.clearCache(); return patterns; } @@ -640,6 +687,71 @@ export class Install { return request; } + + /** + * Check for updates every day and output a nag message if there's a newer version. + */ + + checkUpdate() { + if (!process.stdout.isTTY || isCI) { + // don't show upgrade dialog on CI or non-TTY terminals + return; + } + + // only check for updates once a day + const lastUpdateCheck = Number(this.config.getOption('lastUpdateCheck')) || 0; + if (lastUpdateCheck && Date.now() - lastUpdateCheck < ONE_DAY) { + return; + } + + // don't bug for updates on tagged releases + if (YARN_VERSION.indexOf('-') >= 0) { + return; + } + + this._checkUpdate().catch(() => { + // swallow errors + }); + } + + async _checkUpdate(): Promise { + let latestVersion = await this.config.requestManager.request({ + url: 'https://yarnpkg.com/latest-version', + }); + invariant(typeof latestVersion === 'string', 'expected string'); + latestVersion = latestVersion.trim(); + if (!semver.valid(latestVersion)) { + return; + } + + // ensure we only check for updates periodically + this.config.registries.yarn.saveHomeConfig({ + lastUpdateCheck: Date.now(), + }); + + if (semver.gt(latestVersion, YARN_VERSION)) { + this.maybeOutputUpdate = () => { + this.reporter.warn(this.reporter.lang('yarnOutdated', latestVersion, YARN_VERSION)); + + const command = getUpdateCommand(); + if (command) { + this.reporter.info(this.reporter.lang('yarnOutdatedCommand', command)); + } else { + const installer = getUpdateInstaller(); + if (installer) { + this.reporter.info(this.reporter.lang('yarnOutdatedInstaller', installer)); + } + } + }; + } + } + + /** + * Method to override with a possible upgrade message. + */ + + maybeOutputUpdate() {} + maybeOutputUpdate: any; } export function _setFlags(commander: Object) { diff --git a/src/config.js b/src/config.js index 4f3235d5de..d93c4006c8 100644 --- a/src/config.js +++ b/src/config.js @@ -376,6 +376,8 @@ export default class Config { return file; } } + + return null; }); } diff --git a/src/reporters/lang/en.js b/src/reporters/lang/en.js index 2679533698..ab5bcde58f 100644 --- a/src/reporters/lang/en.js +++ b/src/reporters/lang/en.js @@ -72,6 +72,10 @@ const messages = { unexpectedError: 'An unexpected error occurred, please open a bug report with the information provided in $0.', jsonError: 'Error parsing JSON at $0, $1.', + yarnOutdated: "Your current version of Yarn is out of date. The latest version is $0 while you're on $1.", + yarnOutdatedInstaller: 'To upgrade, download the latest installer at $0.', + yarnOutdatedCommand: 'To upgrade, run $0.', + tooManyArguments: 'Too many arguments, maximum of $0.', tooFewArguments: 'Not enough arguments, expected at least $0.', noArguments: "This command doesn't require any arguments.", diff --git a/yarn.lock b/yarn.lock index aeb7aa32dc..e7cf892e74 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1,5 +1,7 @@ # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. # yarn lockfile v1 + + abab@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.3.tgz#b81de5f7274ec4e756d797cd834f303642724e5d" @@ -1329,6 +1331,10 @@ chokidar@^1.4.3, chokidar@^1.5.2: optionalDependencies: fsevents "^1.0.0" +ci-info@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.0.0.tgz#dc5285f2b4e251821683681c381c3388f46ec534" + cipher-base@^1.0.0, cipher-base@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.3.tgz#eeabf194419ce900da3018c207d212f2a6df0a07" @@ -2671,6 +2677,12 @@ is-builtin-module@^1.0.0: dependencies: builtin-modules "^1.0.0" +is-ci: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.0.10.tgz#f739336b2632365061a9d48270cd56ae3369318e" + dependencies: + ci-info "^1.0.0" + is-ci@^1.0.9: version "1.0.9" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.0.9.tgz#de2c5ffe49ab3237fda38c47c8a3bbfd55bbcca7" @@ -5192,4 +5204,3 @@ yargs@~3.27.0: os-locale "^1.4.0" window-size "^0.1.2" y18n "^3.2.0" -