feat: vue upgrade monorepo support, --from option, and a new vue migrate --from command (#5091)

* refactor(migrator): rename `installed` to `baseVersion`

* feat: `vue upgrade --from` option and a new `vue migrate` command

* fix: fix support for `vuePlugins.resolveFrom` option

* chore: add a fixme comment

* fix: use loadModule instead of manually calculating the package.json path

This also fixes support for monorepo.
(TODO: tests)

* fix: treat `resolveFrom` as `context`, fixing edge cases

* fix: use read-pkg instead of loadModule, avoid messing up require cache

* fix: getInstalledVersion still requires `loadModule` to support monorepo
This commit is contained in:
Haoqun Jiang
2020-01-28 16:32:35 +08:00
committed by GitHub
parent 592b305d7e
commit 02a0e8a187
10 changed files with 183 additions and 106 deletions

View File

@@ -0,0 +1 @@
test.todo('upgrade: should respect plugin resolveFrom')

View File

@@ -166,3 +166,5 @@ test('should prompt if invoking in a git repository with uncommited changes', as
])
await invoke(`babel`, {}, project.dir)
})
test.todo('invoke: should respect plugin resolveFrom')

View File

@@ -177,7 +177,8 @@ program
program
.command('upgrade [plugin-name]')
.description('(experimental) upgrade vue cli service / plugins')
.option('-t, --to <version>', 'upgrade <package-name> to a version that is not latest')
.option('-t, --to <version>', 'Upgrade <package-name> to a version that is not latest')
.option('-f, --from <version>', 'Skip probing installed plugin, assuming it is upgraded from the designated version')
.option('-r, --registry <url>', 'Use specified npm registry when installing dependencies')
.option('--all', 'Upgrade all plugins')
.option('--next', 'Also check for alpha / beta / rc versions when upgrading')
@@ -185,6 +186,15 @@ program
require('../lib/upgrade')(packageName, cleanArgs(cmd))
})
program
.command('migrate [plugin-name]')
.description('(experimental) run migrator for an already-installed cli plugin')
// TODO: use `requiredOption` after upgrading to commander 4.x
.option('-f, --from <version>', 'The base version for the migrator to migrate from')
.action((packageName, cmd) => {
require('../lib/migrate')(packageName, cleanArgs(cmd))
})
program
.command('info')
.description('print debugging information about your environment')

View File

@@ -28,7 +28,7 @@ module.exports = class Migrator extends Generator {
// apply migrators from plugins
const api = new MigratorAPI(
plugin.id,
plugin.installed,
plugin.baseVersion,
this,
plugin.options,
this.rootOptions

View File

@@ -8,15 +8,15 @@ class MigratorAPI extends GeneratorAPI {
* @param {object} options - options passed to this plugin
* @param {object} rootOptions - root options (the entire preset)
*/
constructor (id, installedVersion, migrator, options, rootOptions) {
constructor (id, baseVersion, migrator, options, rootOptions) {
super(id, migrator, options, rootOptions)
this.installedVersion = installedVersion
this.baseVersion = baseVersion
this.migrator = this.generator
}
fromVersion (range) {
return semver.satisfies(this.installedVersion, range)
return semver.satisfies(this.baseVersion, range)
}
}

View File

@@ -11,23 +11,20 @@ const {
isPlugin,
resolvePluginId,
loadModule
} = require('@vue/cli-shared-utils')
const Migrator = require('./Migrator')
const tryGetNewerRange = require('./util/tryGetNewerRange')
const readFiles = require('./util/readFiles')
const getChangedFiles = require('./util/getChangedFiles')
const getPackageJson = require('./util/getPackageJson')
const getPkg = require('./util/getPkg')
const PackageManager = require('./util/ProjectPackageManager')
const isTestOrDebug = process.env.VUE_CLI_TEST || process.env.VUE_CLI_DEBUG
const { runMigrator } = require('./migrate')
module.exports = class Upgrader {
constructor (context = process.cwd()) {
this.context = context
this.pkg = getPackageJson(this.context)
this.pkg = getPkg(this.context)
this.pm = new PackageManager({ context })
}
@@ -43,7 +40,8 @@ module.exports = class Upgrader {
}
for (const p of upgradable) {
this.pkg = getPackageJson(this.context)
// reread to avoid accidentally writing outdated package.json back
this.pkg = getPkg(this.context)
await this.upgrade(p.name, { to: p.latest })
}
@@ -65,6 +63,14 @@ module.exports = class Upgrader {
throw new Error(`Can't find ${chalk.yellow(packageName)} in ${chalk.yellow('package.json')}`)
}
const installed = options.from || this.pm.getInstalledVersion(packageName)
if (!installed) {
throw new Error(
`Can't find ${chalk.yellow(packageName)} in ${chalk.yellow('node_modules')}. Please install the dependencies first.\n` +
`Or to force upgrade, you can specify your current plugin version with the ${chalk.cyan('--from')} option`
)
}
let targetVersion = options.to || 'latest'
// if the targetVersion is not an exact version
if (!/\d+\.\d+\.\d+/.test(targetVersion)) {
@@ -84,7 +90,6 @@ module.exports = class Upgrader {
stopSpinner()
}
const installed = this.pm.getInstalledVersion(packageName)
if (targetVersion === installed) {
log(`Already installed ${packageName}@${targetVersion}`)
@@ -102,76 +107,19 @@ module.exports = class Upgrader {
// the cached `pkg` field won't automatically update after running `this.pm.upgrade`
this.pkg[depEntry][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 afterInvokeCbs = []
const migrator = new Migrator(this.context, {
plugin: plugin,
pkg: this.pkg,
files: await readFiles(this.context),
afterInvokeCbs,
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 (afterInvokeCbs.length) {
logWithSpinner('⚓', `Running completion hooks...`)
for (const cb of afterInvokeCbs) {
await cb()
}
stopSpinner()
log()
}
log(
`${chalk.green(
'✔'
)} Successfully invoked migrator for plugin: ${chalk.cyan(plugin.id)}`
)
const changedFiles = getChangedFiles(this.context)
if (changedFiles.length) {
log(` The following files have been updated / added:\n`)
log(chalk.red(changedFiles.map(line => ` ${line}`).join('\n')))
log()
log(
` You should review these changes with ${chalk.cyan(
'git diff'
)} and commit them.`
if (pluginMigrator) {
await runMigrator(
this.context,
{
id: packageName,
apply: pluginMigrator,
baseVersion: installed
},
this.pkg
)
log()
}
migrator.printExitLogs()
}
async getUpgradable (includeNext) {
@@ -188,8 +136,8 @@ module.exports = class Upgrader {
const installed = await this.pm.getInstalledVersion(name)
const wanted = await this.pm.getRemoteVersion(name, range)
if (installed === 'N/A') {
throw new Error('At least one dependency is not installed. Please run npm install or yarn before trying to upgrade')
if (!installed) {
throw new Error(`At least one dependency can't be found. Please install the dependencies before trying to upgrade`)
}
let latest = await this.pm.getRemoteVersion(name)
@@ -242,7 +190,7 @@ module.exports = class Upgrader {
for (const p of upgradable) {
const fields = [
p.name,
p.installed,
p.installed || 'N/A',
p.wanted,
p.latest,
`vue upgrade ${p.name}${includeNext ? ' --next' : ''}`

View File

@@ -1,5 +1,3 @@
const fs = require('fs-extra')
const path = require('path')
const inquirer = require('inquirer')
const {
chalk,
@@ -18,21 +16,10 @@ const Generator = require('./Generator')
const confirmIfGitDirty = require('./util/confirmIfGitDirty')
const readFiles = require('./util/readFiles')
const getPkg = require('./util/getPkg')
const getChangedFiles = require('./util/getChangedFiles')
const PackageManager = require('./util/ProjectPackageManager')
function getPkg (context) {
const pkgPath = path.resolve(context, 'package.json')
if (!fs.existsSync(pkgPath)) {
throw new Error(`package.json not found in ${chalk.yellow(context)}`)
}
const pkg = fs.readJsonSync(pkgPath)
if (pkg.vuePlugins && pkg.vuePlugins.resolveFrom) {
return getPkg(path.resolve(context, pkg.vuePlugins.resolveFrom))
}
return pkg
}
async function invoke (pluginName, options = {}, context = process.cwd()) {
if (!(await confirmIfGitDirty(context))) {
return

View File

@@ -0,0 +1,109 @@
const {
chalk,
log,
error,
logWithSpinner,
stopSpinner,
loadModule,
resolvePluginId
} = require('@vue/cli-shared-utils')
const Migrator = require('./Migrator')
const PackageManager = require('./util/ProjectPackageManager')
const readFiles = require('./util/readFiles')
const getPkg = require('./util/getPkg')
const getChangedFiles = require('./util/getChangedFiles')
const isTestOrDebug = process.env.VUE_CLI_TEST || process.env.VUE_CLI_DEBUG
async function runMigrator (context, plugin, pkg = getPkg(context)) {
const afterInvokeCbs = []
const migrator = new Migrator(context, {
plugin,
pkg,
files: await readFiles(context),
afterInvokeCbs
})
log(`🚀 Running migrator of ${plugin.id}`)
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 pm = new PackageManager({ context })
await pm.install()
}
if (afterInvokeCbs.length) {
logWithSpinner('⚓', `Running completion hooks...`)
for (const cb of afterInvokeCbs) {
await cb()
}
stopSpinner()
log()
}
log(
`${chalk.green(
'✔'
)} Successfully invoked migrator for plugin: ${chalk.cyan(plugin.id)}`
)
const changedFiles = getChangedFiles(context)
if (changedFiles.length) {
log(` The following files have been updated / added:\n`)
log(chalk.red(changedFiles.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 migrate (pluginId, { from }, context = process.cwd()) {
// TODO: remove this after upgrading to commander 4.x
if (!from) {
throw new Error(`Required option 'from' not specified`)
}
const pluginName = resolvePluginId(pluginId)
const pluginMigrator = loadModule(`${pluginName}/migrator`, context)
if (!pluginMigrator) {
log(`There's no migrator in ${pluginName}`)
return
}
await runMigrator(context, {
id: pluginName,
apply: pluginMigrator,
baseVersion: from
})
}
module.exports = (...args) => {
return migrate(...args).catch(err => {
error(err)
if (!process.env.VUE_CLI_TEST) {
process.exit(1)
}
})
}
module.exports.runMigrator = runMigrator

View File

@@ -10,6 +10,9 @@ const {
semver,
request,
resolvePkg,
loadModule,
hasYarn,
hasProjectYarn,
hasPnpm3OrLater,
@@ -25,7 +28,6 @@ const {
} = require('@vue/cli-shared-utils')
const { loadOptions } = require('../options')
const getPackageJson = require('./getPackageJson')
const { executeCommand } = require('./executeCommand')
const registries = require('./registries')
@@ -81,7 +83,7 @@ function stripVersion (packageName) {
class PackageManager {
constructor ({ context, forcePackageManager } = {}) {
this.context = context
this.context = context || process.cwd()
if (forcePackageManager) {
this.bin = forcePackageManager
@@ -100,6 +102,17 @@ class PackageManager {
)
PACKAGE_MANAGER_CONFIG[this.bin] = PACKAGE_MANAGER_CONFIG.npm
}
// Plugin may be located in another location if `resolveFrom` presents.
const projectPkg = resolvePkg(this.context)
const resolveFrom = projectPkg && projectPkg.vuePlugins && projectPkg.vuePlugins.resolveFrom
// Logically, `resolveFrom` and `context` are distinct fields.
// But in Vue CLI we only care about plugins.
// So it is fine to let all other operations take place in the `resolveFrom` directory.
if (resolveFrom) {
this.context = path.resolve(context, resolveFrom)
}
}
// Any command that implemented registry-related feature should support
@@ -217,13 +230,9 @@ class PackageManager {
getInstalledVersion (packageName) {
// for first level deps, read package.json directly is way faster than `npm list`
try {
const packageJson = getPackageJson(
path.resolve(this.context, 'node_modules', packageName)
)
const packageJson = loadModule(`${packageName}/package.json`, this.context, true)
return packageJson.version
} catch (e) {
return 'N/A'
}
} catch (e) {}
}
async runCommand (args) {

View File

@@ -1,7 +1,10 @@
// Get the package.json containing all the `vue-cli-pluin-*` dependencies
// See issue #1815
const fs = require('fs')
const path = require('path')
module.exports = function getPackageJson (projectPath) {
function getPackageJson (projectPath) {
const packagePath = path.join(projectPath, 'package.json')
let packageJson
@@ -19,3 +22,11 @@ module.exports = function getPackageJson (projectPath) {
return packageJson
}
module.exports = function getPkg (context) {
const pkg = getPackageJson(context)
if (pkg.vuePlugins && pkg.vuePlugins.resolveFrom) {
return getPackageJson(path.resolve(context, pkg.vuePlugins.resolveFrom))
}
return pkg
}