Skip to content

Commit 7744889

Browse files
authored
feat: implement vue upgrade (#2428)
* feat: add vue upgrade command * feat: implement vue upgrade
1 parent 1e200c5 commit 7744889

File tree

8 files changed

+275
-1
lines changed

8 files changed

+275
-1
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const vueCliUpgrade = require('../index')
2+
3+
vueCliUpgrade()
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const path = require('path')
2+
const getPackageJson = require('./get-package-json')
3+
4+
module.exports = function getInstalledVersion (packageName) {
5+
// for first level deps, read package.json directly is way faster than `npm list`
6+
try {
7+
const packageJson = getPackageJson(
8+
path.resolve(process.cwd(), 'node_modules', packageName)
9+
)
10+
return packageJson.version
11+
} catch (e) {
12+
return 'N/A'
13+
}
14+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const fs = require('fs')
2+
const path = require('path')
3+
4+
module.exports = function getPackageJson (projectPath) {
5+
const packagePath = path.join(projectPath, 'package.json')
6+
7+
let packageJson
8+
try {
9+
packageJson = fs.readFileSync(packagePath, 'utf-8')
10+
} catch (err) {
11+
throw new Error(`${packagePath} not exist`)
12+
}
13+
14+
try {
15+
packageJson = JSON.parse(packageJson)
16+
} catch (err) {
17+
throw new Error('The package.json is malformed')
18+
}
19+
20+
return packageJson
21+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
const execa = require('execa')
2+
3+
function getMaxSatisfying (packageName, range) {
4+
let version = JSON.parse(
5+
execa.shellSync(`npm view ${packageName}@${range} version --json`).stdout
6+
)
7+
8+
if (typeof version !== 'string') {
9+
version = version[0]
10+
}
11+
12+
return version
13+
}
14+
15+
module.exports = function getUpgradableVersion (
16+
packageName,
17+
currRange,
18+
semverLevel
19+
) {
20+
let newRange
21+
if (semverLevel === 'patch') {
22+
const currMaxVersion = getMaxSatisfying(packageName, currRange)
23+
newRange = `~${currMaxVersion}`
24+
const newMaxVersion = getMaxSatisfying(packageName, newRange)
25+
newRange = `~${newMaxVersion}`
26+
} else if (semverLevel === 'minor') {
27+
const currMaxVersion = getMaxSatisfying(packageName, currRange)
28+
newRange = `^${currMaxVersion}`
29+
const newMaxVersion = getMaxSatisfying(packageName, newRange)
30+
newRange = `^${newMaxVersion}`
31+
} else if (semverLevel === 'major') {
32+
newRange = `^${getMaxSatisfying(packageName, 'latest')}`
33+
} else {
34+
throw new Error('Release type must be one of patch | minor | major!')
35+
}
36+
37+
return newRange
38+
}

packages/@vue/cli-upgrade/index.js

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
const fs = require('fs')
2+
const path = require('path')
3+
4+
const chalk = require('chalk')
5+
const Table = require('cli-table')
6+
const inquirer = require('inquirer')
7+
8+
/* eslint-disable node/no-extraneous-require */
9+
const {
10+
hasYarn,
11+
logWithSpinner,
12+
stopSpinner
13+
} = require('@vue/cli-shared-utils')
14+
const { loadOptions } = require('@vue/cli/lib/options')
15+
const { installDeps } = require('@vue/cli/lib/util/installDeps')
16+
/* eslint-enable node/no-extraneous-require */
17+
18+
const getPackageJson = require('./get-package-json')
19+
const getInstalledVersion = require('./get-installed-version')
20+
const getUpgradableVersion = require('./get-upgradable-version')
21+
22+
const projectPath = process.cwd()
23+
24+
// - Resolve the version to upgrade to.
25+
// - `vue upgrade [patch|minor|major]`: defaults to minor
26+
// - If already latest, print message and exit
27+
// - Otherwise, confirm via prompt
28+
29+
function isCorePackage (packageName) {
30+
return (
31+
packageName === '@vue/cli-service' ||
32+
packageName.startsWith('@vue/cli-plugin-')
33+
)
34+
}
35+
36+
function shouldUseYarn () {
37+
// infer from lockfiles first
38+
if (fs.existsSync(path.resolve(projectPath, 'package-lock.json'))) {
39+
return false
40+
}
41+
42+
if (fs.existsSync(path.resolve(projectPath, 'yarn.lock')) && hasYarn()) {
43+
return true
44+
}
45+
46+
// fallback to packageManager field in ~/.vuerc
47+
const { packageManager } = loadOptions()
48+
if (packageManager) {
49+
return packageManager === 'yarn'
50+
}
51+
52+
return hasYarn()
53+
}
54+
55+
module.exports = async function vueCliUpgrade (semverLevel = 'minor') {
56+
// get current deps
57+
// filter @vue/cli-service & @vue/cli-plugin-*
58+
const pkg = getPackageJson(projectPath)
59+
const upgradableDepMaps = new Map([
60+
['dependencies', new Map()],
61+
['devDependencies', new Map()],
62+
['optionalDependencies', new Map()]
63+
])
64+
65+
logWithSpinner('Gathering update information...')
66+
for (const depType of upgradableDepMaps.keys()) {
67+
for (const [packageName, currRange] of Object.entries(pkg[depType] || {})) {
68+
if (!isCorePackage(packageName)) {
69+
continue
70+
}
71+
72+
const upgradable = getUpgradableVersion(
73+
packageName,
74+
currRange,
75+
semverLevel
76+
)
77+
if (upgradable !== currRange) {
78+
upgradableDepMaps.get(depType).set(packageName, upgradable)
79+
}
80+
}
81+
}
82+
83+
const table = new Table({
84+
head: ['package', 'installed', '', 'upgraded'],
85+
colAligns: ['left', 'right', 'right', 'right'],
86+
chars: {
87+
top: '',
88+
'top-mid': '',
89+
'top-left': '',
90+
'top-right': '',
91+
bottom: '',
92+
'bottom-mid': '',
93+
'bottom-left': '',
94+
'bottom-right': '',
95+
left: '',
96+
'left-mid': '',
97+
mid: '',
98+
'mid-mid': '',
99+
right: '',
100+
'right-mid': '',
101+
middle: ''
102+
}
103+
})
104+
105+
for (const [depType, depMap] of upgradableDepMaps.entries()) {
106+
for (const packageName of depMap.keys()) {
107+
const installedVersion = getInstalledVersion(packageName)
108+
const upgradedVersion = depMap.get(packageName)
109+
table.push([packageName, installedVersion, '→', upgradedVersion])
110+
111+
pkg[depType][packageName] = upgradedVersion
112+
}
113+
}
114+
115+
stopSpinner()
116+
117+
if ([...upgradableDepMaps.values()].every(depMap => depMap.size === 0)) {
118+
console.log('Already up-to-date.')
119+
return
120+
}
121+
122+
console.log('These packages can be upgraded:\n')
123+
console.log(table.toString())
124+
console.log(
125+
`\nView complete changelog at ${chalk.blue(
126+
'https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md'
127+
)}\n`
128+
)
129+
130+
const useYarn = shouldUseYarn()
131+
const { confirmed } = await inquirer.prompt([
132+
{
133+
name: 'confirmed',
134+
type: 'confirm',
135+
message: `Upgrade ${chalk.yellow('package.json')} and run ${chalk.blue(
136+
useYarn ? 'yarn install' : 'npm install'
137+
)}?`
138+
}
139+
])
140+
141+
if (!confirmed) {
142+
return
143+
}
144+
145+
fs.writeFileSync(path.resolve(projectPath, 'package.json'), JSON.stringify(pkg, null, 2))
146+
console.log()
147+
console.log(`${chalk.yellow('package.json')} saved`)
148+
if (useYarn) {
149+
await installDeps(projectPath, 'yarn')
150+
} else {
151+
await installDeps(projectPath, 'npm')
152+
}
153+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "@vue/cli-upgrade",
3+
"version": "3.0.1",
4+
"description": "utility to upgrade vue cli service / plugins in vue apps",
5+
"main": "index.js",
6+
"repository": {
7+
"type": "git",
8+
"url": "git+https://github.com/vuejs/vue-cli.git"
9+
},
10+
"keywords": [
11+
"vue",
12+
"cli",
13+
"upgrade",
14+
"update"
15+
],
16+
"author": "Haoqun Jiang <[email protected]>",
17+
"license": "MIT",
18+
"bugs": {
19+
"url": "https://github.com/vuejs/vue-cli/issues"
20+
},
21+
"homepage": "https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/cli-upgrade#readme",
22+
"dependencies": {
23+
"chalk": "^2.4.1",
24+
"cli-table": "^0.3.1",
25+
"execa": "^0.10.0",
26+
"inquirer": "^6.0.0"
27+
}
28+
}

packages/@vue/cli/bin/vue.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,13 @@ program
148148
require('../lib/config')(value, cleanArgs(cmd))
149149
})
150150

151+
program
152+
.command('upgrade [semverLevel]')
153+
.description('upgrade vue cli service / plugins (default semverLevel: minor)')
154+
.action((semverLevel, cmd) => {
155+
loadCommand('upgrade', '@vue/cli-upgrade')(semverLevel, cleanArgs(cmd))
156+
})
157+
151158
// output help information on unknown commands
152159
program
153160
.arguments('<command>')
@@ -189,12 +196,16 @@ if (!process.argv.slice(2).length) {
189196
program.outputHelp()
190197
}
191198

199+
function camelize (str) {
200+
return str.replace(/-(\w)/g, (_, c) => c ? c.toUpperCase() : '')
201+
}
202+
192203
// commander passes the Command object itself as options,
193204
// extract only actual options into a fresh object.
194205
function cleanArgs (cmd) {
195206
const args = {}
196207
cmd.options.forEach(o => {
197-
const key = o.long.replace(/^--/, '')
208+
const key = camelize(o.long.replace(/^--/, ''))
198209
// if an option is not present and Command has a method with the same name
199210
// it should not be copied
200211
if (typeof cmd[key] !== 'function' && typeof cmd[key] !== 'undefined') {

yarn.lock

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3601,6 +3601,12 @@ cli-spinners@^1.0.1, cli-spinners@^1.1.0:
36013601
resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-1.3.1.tgz#002c1990912d0d59580c93bd36c056de99e4259a"
36023602
integrity sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg==
36033603

3604+
cli-table@^0.3.1:
3605+
version "0.3.1"
3606+
resolved "http://registry.npm.taobao.org/cli-table/download/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23"
3607+
dependencies:
3608+
colors "1.0.3"
3609+
36043610
cli-truncate@^0.2.1:
36053611
version "0.2.1"
36063612
resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574"

0 commit comments

Comments
 (0)