forked from vuejs/vue-cli
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: extract Upgrader to a standalone file for easier testing
- Loading branch information
1 parent
1b0562a
commit 29b9383
Showing
4 changed files
with
253 additions
and
251 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,245 @@ | ||
const fs = require('fs') | ||
const path = require('path') | ||
const chalk = require('chalk') | ||
const execa = require('execa') | ||
const { | ||
log, | ||
done, | ||
|
||
logWithSpinner, | ||
stopSpinner, | ||
|
||
isPlugin, | ||
resolvePluginId, | ||
loadModule, | ||
|
||
hasProjectGit | ||
} = require('@vue/cli-shared-utils') | ||
|
||
const Migrator = require('./Migrator') | ||
const tryGetNewerRange = require('./util/tryGetNewerRange') | ||
const readFiles = require('./util/readFiles') | ||
|
||
const getPackageJson = require('./util/getPackageJson') | ||
const PackageManager = require('./util/ProjectPackageManager') | ||
|
||
const isTestOrDebug = process.env.VUE_CLI_TEST || process.env.VUE_CLI_DEBUG | ||
|
||
module.exports = class Upgrader { | ||
constructor (context = process.cwd()) { | ||
this.context = context | ||
this.pkg = getPackageJson(this.context) | ||
this.pm = new PackageManager({ context }) | ||
} | ||
|
||
async upgradeAll () { | ||
// TODO: should confirm for major version upgrades | ||
// for patch & minor versions, upgrade directly | ||
// for major versions, prompt before upgrading | ||
const upgradable = await this.getUpgradable() | ||
|
||
if (!upgradable.length) { | ||
done('Seems all plugins are up to date. Good work!') | ||
return | ||
} | ||
|
||
for (const p of upgradable) { | ||
await this.upgrade(p.name, { to: p.latest }) | ||
} | ||
|
||
done('All plugins are up to date!') | ||
} | ||
|
||
async upgrade (pluginId, options) { | ||
const packageName = resolvePluginId(pluginId) | ||
|
||
let depEntry, required | ||
for (const depType of ['dependencies', 'devDependencies', 'optionalDependencies']) { | ||
if (this.pkg[depType] && this.pkg[depType][packageName]) { | ||
depEntry = depType | ||
required = this.pkg[depType][packageName] | ||
break | ||
} | ||
} | ||
if (!required) { | ||
throw new Error(`Can't find ${chalk.yellow(packageName)} in ${chalk.yellow('package.json')}`) | ||
} | ||
|
||
let targetVersion = options.to || 'latest' | ||
// if the targetVersion is not an exact version | ||
if (!/\d+\.\d+\.\d+/.test(targetVersion)) { | ||
if (targetVersion === 'latest') { | ||
logWithSpinner(`Getting latest version of ${packageName}`) | ||
} else { | ||
logWithSpinner(`Getting max satisfying version of ${packageName}@${options.to}`) | ||
} | ||
|
||
targetVersion = await this.pm.getRemoteVersion(packageName, targetVersion) | ||
stopSpinner() | ||
} | ||
|
||
const installed = this.pm.getInstalledVersion(packageName) | ||
if (targetVersion === installed) { | ||
log(`Already installed ${packageName}@${targetVersion}`) | ||
|
||
const newRange = tryGetNewerRange(`^${targetVersion}`, required) | ||
if (newRange !== required) { | ||
this.pkg[depEntry][packageName] = newRange | ||
fs.writeFileSync(path.resolve(this.context, 'package.json'), JSON.stringify(this.pkg, null, 2)) | ||
log(`${chalk.green('✔')} Updated version range in ${chalk.yellow('package.json')}`) | ||
} | ||
return | ||
} | ||
|
||
log(`Upgrading ${packageName} from ${installed} to ${targetVersion}`) | ||
await this.pm.upgrade(`${packageName}@^${targetVersion}`) | ||
|
||
await this.runMigrator(packageName, { installed }) | ||
} | ||
|
||
async runMigrator (packageName, options) { | ||
const pluginMigrator = loadModule(`${packageName}/migrator`, this.context) | ||
if (!pluginMigrator) { return } | ||
|
||
const plugin = { | ||
id: packageName, | ||
apply: pluginMigrator, | ||
installed: options.installed | ||
} | ||
|
||
const createCompleteCbs = [] | ||
const migrator = new Migrator(this.context, { | ||
plugin: plugin, | ||
|
||
pkg: this.pkg, | ||
files: await readFiles(this.context), | ||
completeCbs: createCompleteCbs, | ||
invoking: true | ||
}) | ||
|
||
log(`🚀 Running migrator of ${packageName}`) | ||
await migrator.generate({ | ||
extractConfigFiles: true, | ||
checkExisting: true | ||
}) | ||
|
||
const newDeps = migrator.pkg.dependencies | ||
const newDevDeps = migrator.pkg.devDependencies | ||
const depsChanged = | ||
JSON.stringify(newDeps) !== JSON.stringify(this.pkg.dependencies) || | ||
JSON.stringify(newDevDeps) !== JSON.stringify(this.pkg.devDependencies) | ||
|
||
if (!isTestOrDebug && depsChanged) { | ||
log(`📦 Installing additional dependencies...`) | ||
log() | ||
await this.pm.install() | ||
} | ||
|
||
if (createCompleteCbs.length) { | ||
logWithSpinner('⚓', `Running completion hooks...`) | ||
for (const cb of createCompleteCbs) { | ||
await cb() | ||
} | ||
stopSpinner() | ||
log() | ||
} | ||
|
||
log(`${chalk.green('✔')} Successfully invoked migrator for plugin: ${chalk.cyan(plugin.id)}`) | ||
if (!process.env.VUE_CLI_TEST && hasProjectGit(this.context)) { | ||
const { stdout } = await execa('git', [ | ||
'ls-files', | ||
'--exclude-standard', | ||
'--modified', | ||
'--others' | ||
], { | ||
cwd: this.context | ||
}) | ||
if (stdout.trim()) { | ||
log(` The following files have been updated / added:\n`) | ||
log( | ||
chalk.red( | ||
stdout | ||
.split(/\r?\n/g) | ||
.map(line => ` ${line}`) | ||
.join('\n') | ||
) | ||
) | ||
log() | ||
log( | ||
` You should review these changes with ${chalk.cyan( | ||
`git diff` | ||
)} and commit them.` | ||
) | ||
log() | ||
} | ||
} | ||
|
||
migrator.printExitLogs() | ||
} | ||
|
||
async getUpgradable () { | ||
const upgradable = [] | ||
|
||
// get current deps | ||
// filter @vue/cli-service, @vue/cli-plugin-* & vue-cli-plugin-* | ||
for (const depType of ['dependencies', 'devDependencies', 'optionalDependencies']) { | ||
for (const [name, range] of Object.entries(this.pkg[depType] || {})) { | ||
if (name !== '@vue/cli-service' && !isPlugin(name)) { | ||
continue | ||
} | ||
|
||
const installed = await this.pm.getInstalledVersion(name) | ||
const wanted = await this.pm.getRemoteVersion(name, range) | ||
|
||
const latest = await this.pm.getRemoteVersion(name) | ||
|
||
if (installed !== latest) { | ||
// always list @vue/cli-service as the first one | ||
// as it's depended by all other plugins | ||
if (name === '@vue/cli-service') { | ||
upgradable.unshift({ name, installed, wanted, latest }) | ||
} else { | ||
upgradable.push({ name, installed, wanted, latest }) | ||
} | ||
} | ||
} | ||
} | ||
|
||
return upgradable | ||
} | ||
|
||
async checkForUpdates () { | ||
logWithSpinner('Gathering package information...') | ||
const upgradable = await this.getUpgradable() | ||
stopSpinner() | ||
|
||
if (!upgradable.length) { | ||
done('Seems all plugins are up to date. Good work!') | ||
return | ||
} | ||
|
||
// format the output | ||
// adapted from @angular/cli | ||
const names = upgradable.map(dep => dep.name) | ||
let namePad = Math.max(...names.map(x => x.length)) + 2 | ||
if (!Number.isFinite(namePad)) { | ||
namePad = 30 | ||
} | ||
const pads = [namePad, 12, 12, 12, 0] | ||
console.log( | ||
' ' + | ||
['Name', 'Installed', 'Wanted', 'Latest', 'Command to upgrade'].map( | ||
(x, i) => chalk.underline(x.padEnd(pads[i])) | ||
).join('') | ||
) | ||
for (const p of upgradable) { | ||
const fields = [p.name, p.installed, p.wanted, p.latest, `vue upgrade ${p.name}`] | ||
// TODO: highlight the diff part, like in `yarn outdated` | ||
console.log(' ' + fields.map((x, i) => x.padEnd(pads[i])).join('')) | ||
} | ||
|
||
console.log(`Run ${chalk.yellow('vue upgrade --all')} to upgrade all the above plugins`) | ||
|
||
return upgradable | ||
} | ||
} |
Oops, something went wrong.