feat: implement vue upgrade (#2428)

* feat: add vue upgrade command

* feat: implement vue upgrade
This commit is contained in:
Haoqun Jiang
2018-10-31 01:07:39 +08:00
committed by GitHub
parent 1e200c5726
commit 77448897d4
8 changed files with 275 additions and 1 deletions

View File

@@ -0,0 +1,3 @@
const vueCliUpgrade = require('../index')
vueCliUpgrade()

View File

@@ -0,0 +1,14 @@
const path = require('path')
const getPackageJson = require('./get-package-json')
module.exports = function getInstalledVersion (packageName) {
// for first level deps, read package.json directly is way faster than `npm list`
try {
const packageJson = getPackageJson(
path.resolve(process.cwd(), 'node_modules', packageName)
)
return packageJson.version
} catch (e) {
return 'N/A'
}
}

View File

@@ -0,0 +1,21 @@
const fs = require('fs')
const path = require('path')
module.exports = function getPackageJson (projectPath) {
const packagePath = path.join(projectPath, 'package.json')
let packageJson
try {
packageJson = fs.readFileSync(packagePath, 'utf-8')
} catch (err) {
throw new Error(`${packagePath} not exist`)
}
try {
packageJson = JSON.parse(packageJson)
} catch (err) {
throw new Error('The package.json is malformed')
}
return packageJson
}

View File

@@ -0,0 +1,38 @@
const execa = require('execa')
function getMaxSatisfying (packageName, range) {
let version = JSON.parse(
execa.shellSync(`npm view ${packageName}@${range} version --json`).stdout
)
if (typeof version !== 'string') {
version = version[0]
}
return version
}
module.exports = function getUpgradableVersion (
packageName,
currRange,
semverLevel
) {
let newRange
if (semverLevel === 'patch') {
const currMaxVersion = getMaxSatisfying(packageName, currRange)
newRange = `~${currMaxVersion}`
const newMaxVersion = getMaxSatisfying(packageName, newRange)
newRange = `~${newMaxVersion}`
} else if (semverLevel === 'minor') {
const currMaxVersion = getMaxSatisfying(packageName, currRange)
newRange = `^${currMaxVersion}`
const newMaxVersion = getMaxSatisfying(packageName, newRange)
newRange = `^${newMaxVersion}`
} else if (semverLevel === 'major') {
newRange = `^${getMaxSatisfying(packageName, 'latest')}`
} else {
throw new Error('Release type must be one of patch | minor | major!')
}
return newRange
}

View File

@@ -0,0 +1,153 @@
const fs = require('fs')
const path = require('path')
const chalk = require('chalk')
const Table = require('cli-table')
const inquirer = require('inquirer')
/* eslint-disable node/no-extraneous-require */
const {
hasYarn,
logWithSpinner,
stopSpinner
} = require('@vue/cli-shared-utils')
const { loadOptions } = require('@vue/cli/lib/options')
const { installDeps } = require('@vue/cli/lib/util/installDeps')
/* eslint-enable node/no-extraneous-require */
const getPackageJson = require('./get-package-json')
const getInstalledVersion = require('./get-installed-version')
const getUpgradableVersion = require('./get-upgradable-version')
const projectPath = process.cwd()
// - Resolve the version to upgrade to.
// - `vue upgrade [patch|minor|major]`: defaults to minor
// - If already latest, print message and exit
// - Otherwise, confirm via prompt
function isCorePackage (packageName) {
return (
packageName === '@vue/cli-service' ||
packageName.startsWith('@vue/cli-plugin-')
)
}
function shouldUseYarn () {
// infer from lockfiles first
if (fs.existsSync(path.resolve(projectPath, 'package-lock.json'))) {
return false
}
if (fs.existsSync(path.resolve(projectPath, 'yarn.lock')) && hasYarn()) {
return true
}
// fallback to packageManager field in ~/.vuerc
const { packageManager } = loadOptions()
if (packageManager) {
return packageManager === 'yarn'
}
return hasYarn()
}
module.exports = async function vueCliUpgrade (semverLevel = 'minor') {
// get current deps
// filter @vue/cli-service & @vue/cli-plugin-*
const pkg = getPackageJson(projectPath)
const upgradableDepMaps = new Map([
['dependencies', new Map()],
['devDependencies', new Map()],
['optionalDependencies', new Map()]
])
logWithSpinner('Gathering update information...')
for (const depType of upgradableDepMaps.keys()) {
for (const [packageName, currRange] of Object.entries(pkg[depType] || {})) {
if (!isCorePackage(packageName)) {
continue
}
const upgradable = getUpgradableVersion(
packageName,
currRange,
semverLevel
)
if (upgradable !== currRange) {
upgradableDepMaps.get(depType).set(packageName, upgradable)
}
}
}
const table = new Table({
head: ['package', 'installed', '', 'upgraded'],
colAligns: ['left', 'right', 'right', 'right'],
chars: {
top: '',
'top-mid': '',
'top-left': '',
'top-right': '',
bottom: '',
'bottom-mid': '',
'bottom-left': '',
'bottom-right': '',
left: '',
'left-mid': '',
mid: '',
'mid-mid': '',
right: '',
'right-mid': '',
middle: ''
}
})
for (const [depType, depMap] of upgradableDepMaps.entries()) {
for (const packageName of depMap.keys()) {
const installedVersion = getInstalledVersion(packageName)
const upgradedVersion = depMap.get(packageName)
table.push([packageName, installedVersion, '→', upgradedVersion])
pkg[depType][packageName] = upgradedVersion
}
}
stopSpinner()
if ([...upgradableDepMaps.values()].every(depMap => depMap.size === 0)) {
console.log('Already up-to-date.')
return
}
console.log('These packages can be upgraded:\n')
console.log(table.toString())
console.log(
`\nView complete changelog at ${chalk.blue(
'https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md'
)}\n`
)
const useYarn = shouldUseYarn()
const { confirmed } = await inquirer.prompt([
{
name: 'confirmed',
type: 'confirm',
message: `Upgrade ${chalk.yellow('package.json')} and run ${chalk.blue(
useYarn ? 'yarn install' : 'npm install'
)}?`
}
])
if (!confirmed) {
return
}
fs.writeFileSync(path.resolve(projectPath, 'package.json'), JSON.stringify(pkg, null, 2))
console.log()
console.log(`${chalk.yellow('package.json')} saved`)
if (useYarn) {
await installDeps(projectPath, 'yarn')
} else {
await installDeps(projectPath, 'npm')
}
}

View File

@@ -0,0 +1,28 @@
{
"name": "@vue/cli-upgrade",
"version": "3.0.1",
"description": "utility to upgrade vue cli service / plugins in vue apps",
"main": "index.js",
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/vue-cli.git"
},
"keywords": [
"vue",
"cli",
"upgrade",
"update"
],
"author": "Haoqun Jiang <haoqunjiang+npm@gmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/vuejs/vue-cli/issues"
},
"homepage": "https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/cli-upgrade#readme",
"dependencies": {
"chalk": "^2.4.1",
"cli-table": "^0.3.1",
"execa": "^0.10.0",
"inquirer": "^6.0.0"
}
}

View File

@@ -148,6 +148,13 @@ program
require('../lib/config')(value, cleanArgs(cmd))
})
program
.command('upgrade [semverLevel]')
.description('upgrade vue cli service / plugins (default semverLevel: minor)')
.action((semverLevel, cmd) => {
loadCommand('upgrade', '@vue/cli-upgrade')(semverLevel, cleanArgs(cmd))
})
// output help information on unknown commands
program
.arguments('<command>')
@@ -189,12 +196,16 @@ if (!process.argv.slice(2).length) {
program.outputHelp()
}
function camelize (str) {
return str.replace(/-(\w)/g, (_, c) => c ? c.toUpperCase() : '')
}
// commander passes the Command object itself as options,
// extract only actual options into a fresh object.
function cleanArgs (cmd) {
const args = {}
cmd.options.forEach(o => {
const key = o.long.replace(/^--/, '')
const key = camelize(o.long.replace(/^--/, ''))
// if an option is not present and Command has a method with the same name
// it should not be copied
if (typeof cmd[key] !== 'function' && typeof cmd[key] !== 'undefined') {

View File

@@ -3601,6 +3601,12 @@ cli-spinners@^1.0.1, cli-spinners@^1.1.0:
resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-1.3.1.tgz#002c1990912d0d59580c93bd36c056de99e4259a"
integrity sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg==
cli-table@^0.3.1:
version "0.3.1"
resolved "http://registry.npm.taobao.org/cli-table/download/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23"
dependencies:
colors "1.0.3"
cli-truncate@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574"