Skip to content

Commit

Permalink
refactor: extract Upgrader to a standalone file for easier testing
Browse files Browse the repository at this point in the history
  • Loading branch information
haoqunjiang committed Aug 4, 2019
1 parent 1b0562a commit 29b9383
Show file tree
Hide file tree
Showing 4 changed files with 253 additions and 251 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
const fs = require('fs')
const path = require('path')
const create = require('@vue/cli-test-utils/createTestProject')
// const { logs } = require('@vue/cli-shared-utils')
const { logs } = require('@vue/cli-shared-utils')

const Upgrader = require('../lib/Upgrader')

jest.setTimeout(300000)

Expand All @@ -26,13 +28,12 @@ test('upgrade: plugin-babel v3.5', async () => {
const pkg = JSON.parse(await project.read('package.json'))
expect(pkg.dependencies).not.toHaveProperty('core-js')

await project.run(`${require.resolve('../bin/vue')} upgrade @vue/babel`)
await (new Upgrader(project.dir)).upgrade('@vue/babel', {})

const updatedPkg = JSON.parse(await project.read('package.json'))
expect(updatedPkg.dependencies).toHaveProperty('core-js')

// TODO: run upgrade in the same process so that we can access logs
// expect(logs.log.some(([msg]) => msg.match('core-js has been upgraded'))).toBe(true)
expect(logs.log.some(([msg]) => msg.match('core-js has been upgraded'))).toBe(true)
})

test('upgrade: plugin-babel with core-js 2', async () => {
Expand All @@ -48,7 +49,7 @@ test('upgrade: plugin-babel with core-js 2', async () => {
const pkg = JSON.parse(await project.read('package.json'))
expect(pkg.dependencies['core-js']).toMatch('^2')

await project.run(`${require.resolve('../bin/vue')} upgrade @vue/babel --to next`)
await (new Upgrader(project.dir)).upgrade('@vue/babel', {})

const updatedPkg = JSON.parse(await project.read('package.json'))
expect(updatedPkg.dependencies['core-js']).toMatch('^3')
Expand Down
245 changes: 245 additions & 0 deletions packages/@vue/cli/lib/Upgrader.js
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
}
}
Loading

0 comments on commit 29b9383

Please sign in to comment.