New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add automatic check for updates that nags the user when there's a new version available #1429
Changes from 1 commit
e864da9
6e707f3
6c41fee
e2aa1d6
f4c2fe6
6e89da4
0e81f3f
ef68b73
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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'))) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't |
||
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'; | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also add Chocolatey: |
||
return null; | ||
} | ||
|
||
function getUpdateInstaller(): ?string { | ||
// Windows | ||
if (fs2.existsSync('C:/Program Files/Yarn') || fs2.existsSync('C:/Program Files (x86)/Yarn')) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This won't work as the user can select any directory during installation. Using the environment variable ( |
||
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<Array<string>> { | ||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is still valuable for non-TTY environments. Why should we avoid showing a notice about Yarn being outdated just because the user is piping the output to a file, for example? |
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I wonder if we should bug on nightly builds? |
||
if (YARN_VERSION.indexOf('-') >= 0) { | ||
return; | ||
} | ||
|
||
this._checkUpdate().catch(() => { | ||
// swallow errors | ||
}); | ||
} | ||
|
||
async _checkUpdate(): Promise<void> { | ||
let latestVersion = await this.config.requestManager.request({ | ||
url: 'https://yarnpkg.com/latest-version', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to better automate this getting updated There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I'm going to have a webhook that runs when releases are updated, or just a cronjob. The tricky thing is that we can't bump the version number until we verify that all files are attached to the release, and the Windows installer is attached separately from everything else (as it's built on AppVeyor). |
||
}); | ||
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)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Optional, but can we use something like boxen (or just render the ANSI codes ourself) to render a fancy box around it, like |
||
} | ||
} | ||
}; | ||
} | ||
} | ||
|
||
/** | ||
* Method to override with a possible upgrade message. | ||
*/ | ||
|
||
maybeOutputUpdate() {} | ||
maybeOutputUpdate: any; | ||
} | ||
|
||
export function _setFlags(commander: Object) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -376,6 +376,8 @@ export default class Config { | |
return file; | ||
} | ||
} | ||
|
||
return null; | ||
}); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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.', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is totally optional, but we could download the installer and run it for the user. Maybe we'll hold off on that for now and do it as an enhancement! |
||
yarnOutdatedCommand: 'To upgrade, run $0.', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's put the command on its own separate line. @sindresorhus said:
|
||
|
||
tooManyArguments: 'Too many arguments, maximum of $0.', | ||
tooFewArguments: 'Not enough arguments, expected at least $0.', | ||
noArguments: "This command doesn't require any arguments.", | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of doing this inferrence, let's save a token somewhere as part of the packaging process. @cpojer said:
My idea for that was to add an extra
installationMethod
field topackage.json
when building the packages. For example, something like this would be bundled in the Debian package:What do you think? #942 is the issue that covers(-ish) that. If you agree that that's a good idea, let's complete #942 before this one, and then rebase this on top of that.
The main issue I see with inference is when the user has multiple installations of Yarn. That's a bit silly, but they might have installed via the tarball and then also done
npm install -g yarn
for some reason. The instructions might tell them to update the wrong one.