feat!: redesigns vue upgrade, supports code migration (#4090)

This commit is contained in:
Haoqun Jiang
2019-07-02 16:23:34 +08:00
committed by GitHub
parent 273d05fe08
commit 867c6eaf2c
29 changed files with 619 additions and 304 deletions

View File

@@ -0,0 +1,26 @@
const { chalk } = require('@vue/cli-shared-utils')
module.exports = (api) => {
// TODO: backport this part to v3
// if (api.fromVersion('<=3.5.3')) {
// // add core-js@2 as dependency
// api.extendPackage({
// dependencies: {
// 'core-js': '^2.6.5'
// }
// })
// }
if (api.fromVersion('^3')) {
api.extendPackage({
dependencies: {
'core-js': '^3.1.2'
}
}, true)
// TODO: implement a codemod to migrate polyfills
api.exitLog(`core-js has been upgraded from v2 to v3.
If you have any custom polyfills defined in ${chalk.yellow('babael.config.js')}, please be aware their names may have been changed.
For more complete changelog, see https://github.com/zloirock/core-js/blob/master/CHANGELOG.md#300---20190319`)
}
}

View File

@@ -3,6 +3,7 @@ module.exports = (api, {
tsLint,
lintOn = []
}, _, invoking) => {
debugger
if (typeof lintOn === 'string') {
lintOn = lintOn.split(',')
}

View File

@@ -17,3 +17,4 @@
exports.chalk = require('chalk')
exports.execa = require('execa')
exports.semver = require('semver')

View File

@@ -43,6 +43,10 @@ exports.resumeSpinner = () => {
spinner.start()
}
exports.failSpinner = (text) => {
spinner.fail(text)
}
// silent all logs except errors during tests and keep record
if (process.env.VUE_CLI_TEST) {
require('./_silence')('spinner', exports)

View File

@@ -211,7 +211,7 @@ function install ({ id, type, range }, context) {
arg = id
}
await installPackage(cwd.get(), getCommand(cwd.get()), null, arg, type === 'devDependencies')
await installPackage(cwd.get(), getCommand(cwd.get()), arg, type === 'devDependencies')
logs.add({
message: `Dependency ${id} installed`,
@@ -239,7 +239,7 @@ function uninstall ({ id }, context) {
const dep = findOne(id, context)
await uninstallPackage(cwd.get(), getCommand(cwd.get()), null, id)
await uninstallPackage(cwd.get(), getCommand(cwd.get()), id)
logs.add({
message: `Dependency ${id} uninstalled`,
@@ -265,7 +265,7 @@ function update ({ id }, context) {
const dep = findOne(id, context)
const { current, wanted } = await getVersion(dep, context)
await updatePackage(cwd.get(), getCommand(cwd.get()), null, id)
await updatePackage(cwd.get(), getCommand(cwd.get()), id)
logs.add({
message: `Dependency ${id} updated from ${current} to ${wanted}`,
@@ -310,7 +310,7 @@ function updateAll (context) {
args: [updatedDeps.length]
})
await updatePackage(cwd.get(), getCommand(cwd.get()), null, updatedDeps.map(
await updatePackage(cwd.get(), getCommand(cwd.get()), updatedDeps.map(
p => p.id
).join(' '))

View File

@@ -334,7 +334,7 @@ function install (id, context) {
if (process.env.VUE_CLI_DEBUG && isOfficialPlugin(id)) {
mockInstall(id, context)
} else {
await installPackage(cwd.get(), getCommand(cwd.get()), null, id)
await installPackage(cwd.get(), getCommand(cwd.get()), id)
}
await initPrompts(id, context)
installationStep = 'config'
@@ -412,7 +412,7 @@ function uninstall (id, context) {
if (process.env.VUE_CLI_DEBUG && isOfficialPlugin(id)) {
mockUninstall(id, context)
} else {
await uninstallPackage(cwd.get(), getCommand(cwd.get()), null, id)
await uninstallPackage(cwd.get(), getCommand(cwd.get()), id)
}
currentPluginId = null
installationStep = null
@@ -520,7 +520,7 @@ function update ({ id, full }, context) {
if (localPath) {
await updateLocalPackage({ cwd: cwd.get(), id, localPath, full }, context)
} else {
await updatePackage(cwd.get(), getCommand(cwd.get()), null, id)
await updatePackage(cwd.get(), getCommand(cwd.get()), id)
}
logs.add({
@@ -583,7 +583,7 @@ async function updateAll (context) {
args: [updatedPlugins.length]
})
await updatePackage(cwd.get(), getCommand(cwd.get()), null, updatedPlugins.map(
await updatePackage(cwd.get(), getCommand(cwd.get()), updatedPlugins.map(
p => p.id
).join(' '))

View File

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

View File

@@ -1,38 +0,0 @@
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

@@ -1,153 +0,0 @@
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

@@ -1,30 +0,0 @@
{
"name": "@vue/cli-upgrade",
"version": "4.0.0-alpha.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",
"directory": "packages/@vue/cli-upgrade"
},
"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": {
"@vue/cli-shared-utils": "^4.0.0-alpha.1",
"chalk": "^2.4.1",
"cli-table": "^0.3.1",
"execa": "^1.0.0",
"inquirer": "^6.3.1"
}
}

View File

@@ -0,0 +1,55 @@
const fs = require('fs')
const path = require('path')
const create = require('@vue/cli-test-utils/createTestProject')
// const { logs } = require('@vue/cli-shared-utils')
jest.setTimeout(200000)
const outsideTestFolder = path.resolve(__dirname, '../../../../../vue-upgrade-tests')
beforeAll(() => {
if (!fs.existsSync(outsideTestFolder)) {
fs.mkdirSync(outsideTestFolder)
}
})
test('upgrade: plugin-babel v3.5', async () => {
process.env.VUE_CLI_TEST_DO_INSTALL_PLUGIN = true
const project = await create('plugin-babel-legacy', {
plugins: {
'@vue/cli-plugin-babel': {
version: '3.5.3'
}
}
}, outsideTestFolder)
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`)
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)
})
test('upgrade: plugin-babel with core-js 2', async () => {
process.env.VUE_CLI_TEST_DO_INSTALL_PLUGIN = true
const project = await create('plugin-babel-v3', {
plugins: {
'@vue/cli-plugin-babel': {
version: '3.8.0'
}
}
}, outsideTestFolder)
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`)
const updatedPkg = JSON.parse(await project.read('package.json'))
expect(updatedPkg.dependencies['core-js']).toMatch('^3')
})

View File

@@ -166,10 +166,12 @@ program
})
program
.command('upgrade [semverLevel]')
.description('upgrade vue cli service / plugins (default semverLevel: minor)')
.action((semverLevel, cmd) => {
loadCommand('upgrade', '@vue/cli-upgrade')(semverLevel, cleanArgs(cmd))
.command('upgrade [package-name]')
.description('(experimental) upgrade vue cli service / plugins')
.option('-t, --to <version>', 'upgrade <package-name> to a version that is not latest')
.option('-r, --registry <url>', 'Use specified npm registry when installing dependencies')
.action((packageName, cmd) => {
require('../lib/upgrade')(packageName, cleanArgs(cmd))
})
program

View File

@@ -112,7 +112,7 @@ module.exports = class Creator extends EventEmitter {
let latestMinor = `${semver.major(latest)}.${semver.minor(latest)}.0`
// if using `next` branch of cli
if (semver.gt(current, latest) && semver.prerelease(current)) {
if (semver.gte(current, latest) && semver.prerelease(current)) {
latestMinor = current
}
// generate package.json with plugin dependencies
@@ -136,6 +136,7 @@ module.exports = class Creator extends EventEmitter {
((/^@vue/.test(dep)) ? `^${latestMinor}` : `latest`)
)
})
// write package.json
await writeFileTree(context, {
'package.json': JSON.stringify(pkg, null, 2)
@@ -155,11 +156,12 @@ module.exports = class Creator extends EventEmitter {
log(`⚙ Installing CLI plugins. This might take a while...`)
log()
this.emit('creation', { event: 'plugins-install' })
if (isTestOrDebug) {
if (isTestOrDebug && !process.env.VUE_CLI_TEST_DO_INSTALL_PLUGIN) {
// in development, avoid installation process
await require('./util/setupDevProject')(context)
} else {
await installDeps(context, packageManager, cliOptions.registry)
await installDeps(context, packageManager)
}
// run generator
@@ -180,7 +182,7 @@ module.exports = class Creator extends EventEmitter {
this.emit('creation', { event: 'deps-install' })
log()
if (!isTestOrDebug) {
await installDeps(context, packageManager, cliOptions.registry)
await installDeps(context, packageManager)
}
// run complete cbs if any (injected by generators)

View File

@@ -173,8 +173,9 @@ class GeneratorAPI {
* files are written to disk.
*
* @param {object | () => object} fields - Fields to merge.
* @param {boolean} forceNewVersion - Ignore version conflicts when updating dependency version
*/
extendPackage (fields) {
extendPackage (fields, forceNewVersion) {
const pkg = this.generator.pkg
const toMerge = isFunction(fields) ? fields(pkg) : fields
for (const key in toMerge) {
@@ -186,7 +187,8 @@ class GeneratorAPI {
this.id,
existing || {},
value,
this.generator.depSources
this.generator.depSources,
forceNewVersion
)
} else if (!(key in pkg)) {
pkg[key] = value

View File

@@ -0,0 +1,29 @@
const Generator = require('./Generator')
const MigratorAPI = require('./MigratorAPI')
const inferRootOptions = require('./util/inferRootOptions')
module.exports = class Migrator extends Generator {
constructor (context, {
plugin,
pkg = {},
completeCbs = [],
files = {},
invoking = false
} = {}) {
super(context, {
pkg,
plugins: [],
completeCbs,
files,
invoking
})
this.plugins = [plugin]
const rootOptions = inferRootOptions(pkg)
// apply migrators from plugins
const api = new MigratorAPI(plugin.id, plugin.installed, this, plugin.options, rootOptions)
plugin.apply(api, plugin.options, rootOptions, invoking)
}
}

View File

@@ -0,0 +1,23 @@
const semver = require('semver')
const GeneratorAPI = require('./GeneratorAPI')
class MigratorAPI extends GeneratorAPI {
/**
* @param {string} id - Id of the owner plugin
* @param {Migrator} migrator - The invoking Migrator instance
* @param {object} options - options passed to this plugin
* @param {object} rootOptions - root options (the entire preset)
*/
constructor (id, installedVersion, migrator, options, rootOptions) {
super(id, migrator, options, rootOptions)
this.installedVersion = installedVersion
this.migrator = this.generator
}
fromVersion (range) {
return semver.satisfies(this.installedVersion, range)
}
}
module.exports = MigratorAPI

View File

@@ -28,7 +28,7 @@ async function add (pluginName, options = {}, context = process.cwd()) {
log()
const packageManager = loadOptions().packageManager || (hasProjectYarn(context) ? 'yarn' : hasProjectPnpm(context) ? 'pnpm' : 'npm')
await installPackage(context, packageManager, options.registry, packageName)
await installPackage(context, packageManager, packageName)
log(`${chalk.green('✔')} Successfully installed plugin: ${chalk.cyan(packageName)}`)
log()

View File

@@ -2,13 +2,7 @@ const fs = require('fs-extra')
const path = require('path')
const execa = require('execa')
const chalk = require('chalk')
const globby = require('globby')
const inquirer = require('inquirer')
const { isBinaryFileSync } = require('isbinaryfile')
const Generator = require('./Generator')
const { loadOptions } = require('./options')
const { installDeps } = require('./util/installDeps')
const normalizeFilePaths = require('./util/normalizeFilePaths')
const {
log,
error,
@@ -21,23 +15,10 @@ const {
loadModule
} = require('@vue/cli-shared-utils')
async function readFiles (context) {
const files = await globby(['**'], {
cwd: context,
onlyFiles: true,
gitignore: true,
ignore: ['**/node_modules/**', '**/.git/**'],
dot: true
})
const res = {}
for (const file of files) {
const name = path.resolve(context, file)
res[file] = isBinaryFileSync(name)
? fs.readFileSync(name)
: fs.readFileSync(name, 'utf-8')
}
return normalizeFilePaths(res)
}
const Generator = require('./Generator')
const { loadOptions } = require('./options')
const { installDeps } = require('./util/installDeps')
const readFiles = require('./util/readFiles')
function getPkg (context) {
const pkgPath = path.resolve(context, 'package.json')
@@ -146,7 +127,7 @@ async function runGenerator (context, plugin, pkg = getPkg(context)) {
log()
const packageManager =
loadOptions().packageManager || (hasProjectYarn(context) ? 'yarn' : hasProjectPnpm(context) ? 'pnpm' : 'npm')
await installDeps(context, packageManager, plugin.options && plugin.options.registry)
await installDeps(context, packageManager)
}
if (createCompleteCbs.length) {

View File

@@ -0,0 +1,271 @@
const fs = require('fs')
const path = require('path')
const chalk = require('chalk')
const execa = require('execa')
const {
log,
error,
done,
logWithSpinner,
stopSpinner,
isPlugin,
resolvePluginId,
loadModule,
hasProjectGit
} = require('@vue/cli-shared-utils')
const Migrator = require('./Migrator')
const { getCommand, getVersion } = require('./util/packageManager')
const { installDeps, updatePackage } = require('./util/installDeps')
const { linkPackage } = require('./util/linkBin')
const getPackageJson = require('./util/getPackageJson')
const getInstalledVersion = require('./util/getInstalledVersion')
const tryGetNewerRange = require('./util/tryGetNewerRange')
const readFiles = require('./util/readFiles')
const isTestOrDebug = process.env.VUE_CLI_TEST || process.env.VUE_CLI_DEBUG
async function runMigrator (packageName, options, context) {
const pluginMigrator = loadModule(`${packageName}/migrator`, context)
if (!pluginMigrator) { return }
const plugin = {
id: packageName,
apply: pluginMigrator,
installed: options.installed
}
const pkg = getPackageJson(context)
const createCompleteCbs = []
const migrator = new Migrator(context, {
plugin: plugin,
pkg,
files: await readFiles(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(pkg.dependencies) ||
JSON.stringify(newDevDeps) !== JSON.stringify(pkg.devDependencies)
if (!isTestOrDebug && depsChanged) {
log(`📦 Installing additional dependencies...`)
log()
const packageManager = getCommand(context)
await installDeps(context, packageManager)
}
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(context)) {
const { stdout } = await execa('git', [
'ls-files',
'--exclude-standard',
'--modified',
'--others'
], {
cwd: 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 function upgradeSinglePackage (pluginId, options, context) {
const packageName = resolvePluginId(pluginId)
const pkg = getPackageJson(context)
let depEntry, required
for (const depType of ['dependencies', 'devDependencies', 'optionalDependencies']) {
if (pkg[depType] && pkg[depType][packageName]) {
depEntry = depType
required = pkg[depType][packageName]
break
}
}
if (!required) {
throw new Error(`Can't find ${chalk.yellow(packageName)} in ${chalk.yellow('package.json')}`)
}
const installed = getInstalledVersion(packageName)
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 getVersion(packageName, targetVersion, context)
stopSpinner()
}
if (targetVersion === installed) {
log(`Already installed ${packageName}@${targetVersion}`)
const newRange = tryGetNewerRange(`^${targetVersion}`, required)
if (newRange !== required) {
pkg[depEntry][packageName] = newRange
fs.writeFileSync(path.resolve(context, 'package.json'), JSON.stringify(pkg, null, 2))
log(`${chalk.green('✔')} Updated version range in ${chalk.yellow('package.json')}`)
}
return
}
log(`Upgrading ${packageName} from ${installed} to ${targetVersion}`)
if (isTestOrDebug) {
// link packages in current repo for test
await linkPackage(path.resolve(__dirname, `../../../${packageName}`), path.join(context, 'node_modules', packageName))
} else {
await updatePackage(context, getCommand(context), `${packageName}@^${targetVersion}`)
}
await runMigrator(packageName, { installed }, context)
}
async function getUpgradable (context) {
// get current deps
// filter @vue/cli-service, @vue/cli-plugin-* & vue-cli-plugin-*
const pkg = getPackageJson(context)
const upgradable = []
for (const depType of ['dependencies', 'devDependencies', 'optionalDependencies']) {
for (const [name, range] of Object.entries(pkg[depType] || {})) {
if (name !== '@vue/cli-service' && !isPlugin(name)) {
continue
}
const installed = await getInstalledVersion(name)
const wanted = await getVersion(name, range, context)
const latest = await getVersion(name, 'latest', context)
if (installed !== latest) {
upgradable.push({ name, installed, wanted, latest })
}
}
}
return upgradable
}
async function checkForUpdates (context) {
logWithSpinner('Gathering pacakage information...')
const upgradable = await getUpgradable(context)
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}`]
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
}
async function upgradeAll (context) {
// TODO: should confirm for major version upgrades
// for patch & minor versions, upgrade directly
// for major versions, prompt before upgrading
const upgradable = await getUpgradable(context)
if (!upgradable.length) {
done('Seems all plugins are up to date. Good work!')
return
}
for (const p of upgradable) {
await upgradeSinglePackage(p.name, { to: p.latest }, context)
}
done('All plugins are up to date!')
}
async function upgrade (packageName, options, context = process.cwd()) {
if (!packageName) {
if (options.to) {
error(`Must specify a package name to upgrade to ${options.to}`)
process.exit(1)
}
if (options.all) {
return upgradeAll(context)
}
return checkForUpdates(context)
}
return upgradeSinglePackage(packageName, options, context)
}
module.exports = (...args) => {
return upgrade(...args).catch(err => {
error(err)
if (!process.env.VUE_CLI_TEST) {
process.exit(1)
}
})
}

View File

@@ -1,5 +1,5 @@
const path = require('path')
const getPackageJson = require('./get-package-json')
const getPackageJson = require('./getPackageJson')
module.exports = function getInstalledVersion (packageName) {
// for first level deps, read package.json directly is way faster than `npm list`

View File

@@ -1,9 +1,10 @@
const { request } = require('@vue/cli-shared-utils')
const { getRegistry } = require('./packageManager')
module.exports = async function getPackageVersion (id, range = '') {
const registry = (await require('./shouldUseTaobao')())
? `https://registry.npm.taobao.org`
: `https://registry.npmjs.org`
module.exports = async function getPackageVersion (id, range = '', registry) {
if (!registry) {
registry = await getRegistry()
}
let result
try {

View File

@@ -3,7 +3,7 @@ const chalk = require('chalk')
const execa = require('execa')
const readline = require('readline')
const registries = require('./registries')
const shouldUseTaobao = require('./shouldUseTaobao')
const { getRegistry } = require('./packageManager')
const debug = require('debug')('vue-cli:install')
@@ -91,14 +91,8 @@ function renderProgressBar (curr, total) {
process.stderr.write(`[${complete}${incomplete}]${bar}`)
}
async function addRegistryToArgs (command, args, cliRegistry) {
const altRegistry = (
cliRegistry || (
(await shouldUseTaobao(command))
? registries.taobao
: null
)
)
async function addRegistryToArgs (command, args) {
const altRegistry = await getRegistry({ packageManager: command })
if (altRegistry) {
args.push(`--registry=${altRegistry}`)
@@ -194,12 +188,12 @@ function executeCommand (command, args, targetDir) {
})
}
exports.installDeps = async function installDeps (targetDir, command, cliRegistry) {
exports.installDeps = async function installDeps (targetDir, command) {
checkPackageManagerIsSupported(command)
const args = packageManagerConfig[command].installDeps
await addRegistryToArgs(command, args, cliRegistry)
await addRegistryToArgs(command, args)
debug(`command: `, command)
debug(`args: `, args)
@@ -207,14 +201,14 @@ exports.installDeps = async function installDeps (targetDir, command, cliRegistr
await executeCommand(command, args, targetDir)
}
exports.installPackage = async function (targetDir, command, cliRegistry, packageName, dev = true) {
exports.installPackage = async function (targetDir, command, packageName, dev = true) {
checkPackageManagerIsSupported(command)
const args = packageManagerConfig[command].installPackage
if (dev) args.push('-D')
await addRegistryToArgs(command, args, cliRegistry)
await addRegistryToArgs(command, args)
args.push(packageName)
@@ -224,12 +218,12 @@ exports.installPackage = async function (targetDir, command, cliRegistry, packag
await executeCommand(command, args, targetDir)
}
exports.uninstallPackage = async function (targetDir, command, cliRegistry, packageName) {
exports.uninstallPackage = async function (targetDir, command, packageName) {
checkPackageManagerIsSupported(command)
const args = packageManagerConfig[command].uninstallPackage
await addRegistryToArgs(command, args, cliRegistry)
await addRegistryToArgs(command, args)
args.push(packageName)
@@ -239,12 +233,12 @@ exports.uninstallPackage = async function (targetDir, command, cliRegistry, pack
await executeCommand(command, args, targetDir)
}
exports.updatePackage = async function (targetDir, command, cliRegistry, packageName) {
exports.updatePackage = async function (targetDir, command, packageName) {
checkPackageManagerIsSupported(command)
const args = packageManagerConfig[command].updatePackage
await addRegistryToArgs(command, args, cliRegistry)
await addRegistryToArgs(command, args)
packageName.split(' ').forEach(name => args.push(name))

View File

@@ -19,3 +19,12 @@ exports.linkBin = async (src, dest) => {
await fs.chmod(dest, '755')
}
}
exports.linkPackage = async (src, dest) => {
if (!process.env.VUE_CLI_TEST && !process.env.VUE_CLI_DEBUG) {
throw new Error(`linkPackage should only be used during tests or debugging.`)
}
await fs.remove(dest)
await fs.symlink(src, dest, 'dir')
}

View File

@@ -1,7 +1,12 @@
const semver = require('semver')
const { warn } = require('@vue/cli-shared-utils')
module.exports = function resolveDeps (generatorId, to, from, sources) {
const tryGetNewerRange = require('./tryGetNewerRange')
const extractSemver = r => r.replace(/^.+#semver:/, '')
const injectSemver = (r, v) => semver.validRange(r) ? v : r.replace(/#semver:.+$/, `#semver:${v}`)
module.exports = function resolveDeps (generatorId, to, from, sources, forceNewVersion) {
const res = Object.assign({}, to)
for (const name in from) {
const r1 = to[name]
@@ -37,7 +42,14 @@ module.exports = function resolveDeps (generatorId, to, from, sources) {
sources[name] = generatorId
}
// warn incompatible version requirements
if (!semver.validRange(r1semver) || !semver.validRange(r2semver) || !semver.intersects(r1semver, r2semver)) {
if (
!forceNewVersion &&
(
!semver.validRange(r1semver) ||
!semver.validRange(r2semver) ||
!semver.intersects(r1semver, r2semver)
)
) {
warn(
`conflicting versions for project dependency "${name}":\n\n` +
`- ${r1} injected by generator "${sourceGeneratorId}"\n` +
@@ -49,16 +61,3 @@ module.exports = function resolveDeps (generatorId, to, from, sources) {
}
return res
}
const leadRE = /^(~|\^|>=?)/
const rangeToVersion = r => r.replace(leadRE, '').replace(/x/g, '0')
const extractSemver = r => r.replace(/^.+#semver:/, '')
const injectSemver = (r, v) => semver.validRange(r) ? v : r.replace(/#semver:.+$/, `#semver:${v}`)
function tryGetNewerRange (r1, r2) {
const v1 = rangeToVersion(r1)
const v2 = rangeToVersion(r2)
if (semver.valid(v1) && semver.valid(v2)) {
return semver.gt(v1, v2) ? r1 : r2
}
}

View File

@@ -0,0 +1,102 @@
const execa = require('execa')
const minimist = require('minimist')
const semver = require('semver')
const LRU = require('lru-cache')
const {
hasYarn,
hasProjectYarn,
hasPnpm3OrLater,
hasProjectPnpm
} = require('@vue/cli-shared-utils')
const { loadOptions } = require('../options')
const registries = require('./registries')
const shouldUseTaobao = require('./shouldUseTaobao')
function getCommand (cwd) {
if (!cwd) {
return loadOptions().packageManager || (hasYarn() ? 'yarn' : hasPnpm3OrLater() ? 'pnpm' : 'npm')
}
return hasProjectYarn(cwd) ? 'yarn' : hasProjectPnpm(cwd) ? 'pnpm' : 'npm'
}
// Any command that implemented registry-related feature should support
// `-r` / `--registry` option
async function getRegistry ({ cwd, packageManager } = {}) {
const args = minimist(process.argv, {
alias: {
r: 'registry'
}
})
if (args.registry) {
return args.registry
}
if (await shouldUseTaobao()) {
return registries.taobao
}
if (!packageManager) {
packageManager = getCommand(cwd)
}
const { stdout } = await execa(packageManager, ['config', 'get', 'registry'])
return stdout
}
const metadataCache = new LRU({
max: 200,
maxAge: 1000 * 60 * 30 // 30 min.
})
async function getMetadata (packageName, { field = '', packageManager, cwd } = {}) {
if (!packageManager) {
packageManager = getCommand(cwd)
}
const registry = await getRegistry({ cwd, packageManager })
const metadataKey = `${packageManager}-${registry}-${packageName}`
let metadata = metadataCache.get(metadataKey)
if (metadata) {
return metadata
}
const { stdout } = await execa(
packageManager,
[
'info',
packageName,
field,
'--json',
'--registry',
registry
]
)
metadata = JSON.parse(stdout)
if (packageManager === 'yarn') {
// `yarn info` outputs messages in the form of `{"type": "inspect", data: {}}`
metadata = metadata.data
}
metadataCache.set(metadataKey, metadata)
return metadata
}
async function getVersion (packageName, versionRange, cwd) {
const metadata = await getMetadata(packageName, { cwd })
if (Object.keys(metadata['dist-tags']).includes(versionRange)) {
return metadata['dist-tags'][versionRange]
}
const versions = Array.isArray(metadata.versions) ? metadata.versions : Object.keys(metadata.versions)
return semver.maxSatisfying(versions, versionRange)
}
module.exports = {
getCommand,
getRegistry,
getMetadata,
getVersion
}

View File

@@ -0,0 +1,24 @@
const fs = require('fs-extra')
const path = require('path')
const globby = require('globby')
const { isBinaryFileSync } = require('isbinaryfile')
const normalizeFilePaths = require('./normalizeFilePaths')
module.exports = async function readFiles (context) {
const files = await globby(['**'], {
cwd: context,
onlyFiles: true,
gitignore: true,
ignore: ['**/node_modules/**', '**/.git/**'],
dot: true
})
const res = {}
for (const file of files) {
const name = path.resolve(context, file)
res[file] = isBinaryFileSync(name)
? fs.readFileSync(name)
: fs.readFileSync(name, 'utf-8')
}
return normalizeFilePaths(res)
}

View File

@@ -0,0 +1,12 @@
const semver = require('semver')
const leadRE = /^(~|\^|>=?)/
const rangeToVersion = r => r.replace(leadRE, '').replace(/x/g, '0')
module.exports = function tryGetNewerRange (r1, r2) {
const v1 = rangeToVersion(r1)
const v2 = rangeToVersion(r2)
if (semver.valid(v1) && semver.valid(v2)) {
return semver.gt(v1, v2) ? r1 : r2
}
}

View File

@@ -47,6 +47,7 @@
"js-yaml": "^3.13.1",
"jscodeshift": "^0.6.4",
"lodash.clonedeep": "^4.5.0",
"lru-cache": "^5.1.1",
"minimist": "^1.2.0",
"recast": "^0.18.1",
"request": "^2.87.0",