mirror of
https://github.com/vuejs/vue-cli.git
synced 2026-02-12 18:08:35 -06:00
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:
1
packages/@vue/cli/__tests__/Upgrader.spec.js
Normal file
1
packages/@vue/cli/__tests__/Upgrader.spec.js
Normal file
@@ -0,0 +1 @@
|
||||
test.todo('upgrade: should respect plugin resolveFrom')
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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' : ''}`
|
||||
|
||||
@@ -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
|
||||
|
||||
109
packages/@vue/cli/lib/migrate.js
Normal file
109
packages/@vue/cli/lib/migrate.js
Normal 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
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user