mirror of
https://github.com/vuejs/vue-cli.git
synced 2026-03-12 20:19:55 -05:00
feat: implement a migrator to upgrade to eslint 6 (#5085)
* refactor: extract deps & config logic to separate files * feat: implement a migrator to upgrade to eslint 6 * fix: add required deps for eslint v4 * test: move migrator tests to each standalone plugins * refactor: use spread operator instead of Object.assign
This commit is contained in:
@@ -1,20 +1,7 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const create = require('@vue/cli-test-utils/createTestProject')
|
||||
const create = require('@vue/cli-test-utils/createUpgradableProject')
|
||||
const { logs } = require('@vue/cli-shared-utils')
|
||||
|
||||
const Upgrader = require('../lib/Upgrader')
|
||||
|
||||
jest.setTimeout(300000)
|
||||
|
||||
const outsideTestFolder = path.resolve(__dirname, '../../../../../vue-upgrade-tests')
|
||||
|
||||
beforeAll(() => {
|
||||
if (!fs.existsSync(outsideTestFolder)) {
|
||||
fs.mkdirSync(outsideTestFolder)
|
||||
}
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
process.env.VUE_CLI_TEST_DO_INSTALL_PLUGIN = true
|
||||
})
|
||||
@@ -26,12 +13,12 @@ test('upgrade: plugin-babel v3.5', async () => {
|
||||
version: '3.5.3'
|
||||
}
|
||||
}
|
||||
}, outsideTestFolder)
|
||||
})
|
||||
|
||||
const pkg = JSON.parse(await project.read('package.json'))
|
||||
expect(pkg.dependencies).not.toHaveProperty('core-js')
|
||||
|
||||
await (new Upgrader(project.dir)).upgrade('babel', {})
|
||||
await project.upgrade('babel')
|
||||
|
||||
const updatedPkg = JSON.parse(await project.read('package.json'))
|
||||
expect(updatedPkg.dependencies).toHaveProperty('core-js')
|
||||
@@ -49,31 +36,13 @@ test('upgrade: plugin-babel with core-js 2', async () => {
|
||||
version: '3.8.0'
|
||||
}
|
||||
}
|
||||
}, outsideTestFolder)
|
||||
})
|
||||
|
||||
const pkg = JSON.parse(await project.read('package.json'))
|
||||
expect(pkg.dependencies['core-js']).toMatch('^2')
|
||||
|
||||
await (new Upgrader(project.dir)).upgrade('babel', {})
|
||||
await project.upgrade('babel')
|
||||
|
||||
const updatedPkg = JSON.parse(await project.read('package.json'))
|
||||
expect(updatedPkg.dependencies['core-js']).toMatch('^3')
|
||||
})
|
||||
|
||||
test('upgrade: should add eslint to devDependencies', async () => {
|
||||
const project = await create('plugin-eslint-v3.0', {
|
||||
plugins: {
|
||||
'@vue/cli-plugin-eslint': {
|
||||
version: '3.0.0'
|
||||
}
|
||||
}
|
||||
}, outsideTestFolder)
|
||||
|
||||
const pkg = JSON.parse(await project.read('package.json'))
|
||||
expect(pkg.devDependencies).not.toHaveProperty('eslint')
|
||||
|
||||
await (new Upgrader(project.dir)).upgrade('eslint', {})
|
||||
|
||||
const updatedPkg = JSON.parse(await project.read('package.json'))
|
||||
expect(updatedPkg.devDependencies.eslint).toMatch('^4')
|
||||
})
|
||||
@@ -0,0 +1,68 @@
|
||||
jest.setTimeout(300000)
|
||||
jest.mock('inquirer')
|
||||
|
||||
beforeEach(() => {
|
||||
process.env.VUE_CLI_TEST_DO_INSTALL_PLUGIN = true
|
||||
})
|
||||
|
||||
const create = require('@vue/cli-test-utils/createUpgradableProject')
|
||||
const { expectPrompts } = require('inquirer')
|
||||
|
||||
test('upgrade: should add eslint to devDependencies', async () => {
|
||||
const project = await create('plugin-eslint-v3.0', {
|
||||
plugins: {
|
||||
'@vue/cli-plugin-eslint': {
|
||||
version: '3.0.0'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const pkg = JSON.parse(await project.read('package.json'))
|
||||
expect(pkg.devDependencies).not.toHaveProperty('eslint')
|
||||
|
||||
expectPrompts([
|
||||
{
|
||||
message: `Your current ESLint version is v4`,
|
||||
confirm: false
|
||||
}
|
||||
])
|
||||
|
||||
await project.upgrade('eslint')
|
||||
|
||||
const updatedPkg = JSON.parse(await project.read('package.json'))
|
||||
expect(updatedPkg.devDependencies.eslint).toMatch('^4')
|
||||
})
|
||||
|
||||
test.only('upgrade: should upgrade eslint from v5 to v6', async () => {
|
||||
const project = await create('plugin-eslint-with-eslint-5', {
|
||||
plugins: {
|
||||
'@vue/cli-plugin-eslint': {
|
||||
version: '3.12.1',
|
||||
config: 'airbnb'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const pkg = JSON.parse(await project.read('package.json'))
|
||||
expect(pkg.devDependencies.eslint).toMatch('^5')
|
||||
|
||||
expectPrompts([
|
||||
{
|
||||
message: `Your current ESLint version is v5`,
|
||||
confirm: true
|
||||
}
|
||||
])
|
||||
|
||||
try {
|
||||
await project.upgrade('eslint')
|
||||
} catch (e) {
|
||||
// TODO:
|
||||
// Currently the `afterInvoke` hook will fail,
|
||||
// because deps are not correctly installed in test env.
|
||||
// Need to fix later.
|
||||
}
|
||||
|
||||
const updatedPkg = JSON.parse(await project.read('package.json'))
|
||||
expect(updatedPkg.devDependencies.eslint).toMatch('^6')
|
||||
expect(updatedPkg.devDependencies).toHaveProperty('eslint-plugin-import')
|
||||
})
|
||||
45
packages/@vue/cli-plugin-eslint/eslintDeps.js
Normal file
45
packages/@vue/cli-plugin-eslint/eslintDeps.js
Normal file
@@ -0,0 +1,45 @@
|
||||
const DEPS_MAP = {
|
||||
base: {
|
||||
eslint: '^6.7.2',
|
||||
'eslint-plugin-vue': '^6.1.2'
|
||||
},
|
||||
airbnb: {
|
||||
'@vue/eslint-config-airbnb': '^5.0.1',
|
||||
'eslint-plugin-import': '^2.18.2'
|
||||
},
|
||||
prettier: {
|
||||
'@vue/eslint-config-prettier': '^6.0.0',
|
||||
'eslint-plugin-prettier': '^3.1.1',
|
||||
prettier: '^1.19.1'
|
||||
},
|
||||
standard: {
|
||||
'@vue/eslint-config-standard': '^5.1.0',
|
||||
'eslint-plugin-import': '^2.18.2',
|
||||
'eslint-plugin-node': '^9.1.0',
|
||||
'eslint-plugin-promise': '^4.2.1',
|
||||
'eslint-plugin-standard': '^4.0.0'
|
||||
},
|
||||
typescript: {
|
||||
'@vue/eslint-config-typescript': '^5.0.1',
|
||||
'@typescript-eslint/eslint-plugin': '^2.10.0',
|
||||
'@typescript-eslint/parser': '^2.10.0'
|
||||
}
|
||||
}
|
||||
|
||||
exports.DEPS_MAP = DEPS_MAP
|
||||
|
||||
exports.getDeps = function (api, preset) {
|
||||
const deps = Object.assign({}, DEPS_MAP.base, DEPS_MAP[preset])
|
||||
|
||||
if (api.hasPlugin('typescript')) {
|
||||
Object.assign(deps, DEPS_MAP.typescript)
|
||||
}
|
||||
|
||||
if (api.hasPlugin('babel') && !api.hasPlugin('typescript')) {
|
||||
Object.assign(deps, {
|
||||
'babel-eslint': '^10.0.3'
|
||||
})
|
||||
}
|
||||
|
||||
return deps
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
exports.config = api => {
|
||||
exports.config = (api, preset) => {
|
||||
const config = {
|
||||
root: true,
|
||||
env: { node: true },
|
||||
@@ -11,11 +11,35 @@ exports.config = api => {
|
||||
'no-debugger': makeJSOnlyValue(`process.env.NODE_ENV === 'production' ? 'error' : 'off'`)
|
||||
}
|
||||
}
|
||||
|
||||
if (api.hasPlugin('babel') && !api.hasPlugin('typescript')) {
|
||||
config.parserOptions = {
|
||||
parser: 'babel-eslint'
|
||||
}
|
||||
}
|
||||
|
||||
if (preset === 'airbnb') {
|
||||
config.extends.push('@vue/airbnb')
|
||||
} else if (preset === 'standard') {
|
||||
config.extends.push('@vue/standard')
|
||||
} else if (preset === 'prettier') {
|
||||
config.extends.push(...['eslint:recommended', '@vue/prettier'])
|
||||
} else {
|
||||
// default
|
||||
config.extends.push('eslint:recommended')
|
||||
}
|
||||
|
||||
if (api.hasPlugin('typescript')) {
|
||||
// typically, typescript ruleset should be appended to the end of the `extends` array
|
||||
// but that is not the case for prettier, as there are conflicting rules
|
||||
if (preset === 'prettier') {
|
||||
config.extends.pop()
|
||||
config.extends.push(...['@vue/typescript/recommended', '@vue/prettier', '@vue/prettier/@typescript-eslint'])
|
||||
} else {
|
||||
config.extends.push('@vue/typescript/recommended')
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
|
||||
@@ -2,74 +2,15 @@ const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
module.exports = (api, { config, lintOn = [] }, _, invoking) => {
|
||||
const eslintConfig = require('../eslintOptions').config(api)
|
||||
const extentions = require('../eslintOptions').extensions(api)
|
||||
.map(ext => ext.replace(/^\./, '')) // remove the leading `.`
|
||||
const eslintConfig = require('../eslintOptions').config(api, config)
|
||||
const devDependencies = require('../eslintDeps').getDeps(api, config)
|
||||
|
||||
const pkg = {
|
||||
scripts: {
|
||||
lint: 'vue-cli-service lint'
|
||||
},
|
||||
eslintConfig,
|
||||
devDependencies: {
|
||||
eslint: '^6.7.2',
|
||||
'eslint-plugin-vue': '^6.1.2'
|
||||
}
|
||||
}
|
||||
|
||||
if (api.hasPlugin('babel') && !api.hasPlugin('typescript')) {
|
||||
pkg.devDependencies['babel-eslint'] = '^10.0.3'
|
||||
}
|
||||
|
||||
switch (config) {
|
||||
case 'airbnb':
|
||||
eslintConfig.extends.push('@vue/airbnb')
|
||||
Object.assign(pkg.devDependencies, {
|
||||
'@vue/eslint-config-airbnb': '^5.0.1',
|
||||
'eslint-plugin-import': '^2.18.2'
|
||||
})
|
||||
break
|
||||
case 'standard':
|
||||
eslintConfig.extends.push('@vue/standard')
|
||||
Object.assign(pkg.devDependencies, {
|
||||
'@vue/eslint-config-standard': '^5.1.0',
|
||||
'eslint-plugin-import': '^2.18.2',
|
||||
'eslint-plugin-node': '^9.1.0',
|
||||
'eslint-plugin-promise': '^4.2.1',
|
||||
'eslint-plugin-standard': '^4.0.0'
|
||||
})
|
||||
break
|
||||
case 'prettier':
|
||||
eslintConfig.extends.push(
|
||||
...(api.hasPlugin('typescript')
|
||||
? ['eslint:recommended', '@vue/typescript/recommended', '@vue/prettier', '@vue/prettier/@typescript-eslint']
|
||||
: ['eslint:recommended', '@vue/prettier']
|
||||
)
|
||||
)
|
||||
Object.assign(pkg.devDependencies, {
|
||||
'@vue/eslint-config-prettier': '^6.0.0',
|
||||
'eslint-plugin-prettier': '^3.1.1',
|
||||
prettier: '^1.19.1'
|
||||
})
|
||||
break
|
||||
default:
|
||||
// default
|
||||
eslintConfig.extends.push('eslint:recommended')
|
||||
break
|
||||
}
|
||||
|
||||
// typescript support
|
||||
if (api.hasPlugin('typescript')) {
|
||||
Object.assign(pkg.devDependencies, {
|
||||
'@vue/eslint-config-typescript': '^5.0.1',
|
||||
'@typescript-eslint/eslint-plugin': '^2.10.0',
|
||||
'@typescript-eslint/parser': '^2.10.0'
|
||||
})
|
||||
if (config !== 'prettier') {
|
||||
// for any config other than `prettier`,
|
||||
// typescript ruleset should be appended to the end of the `extends` array
|
||||
eslintConfig.extends.push('@vue/typescript/recommended')
|
||||
}
|
||||
devDependencies
|
||||
}
|
||||
|
||||
const editorConfigTemplatePath = path.resolve(__dirname, `./template/${config}/_editorconfig`)
|
||||
@@ -102,6 +43,8 @@ module.exports = (api, { config, lintOn = [] }, _, invoking) => {
|
||||
pkg.gitHooks = {
|
||||
'pre-commit': 'lint-staged'
|
||||
}
|
||||
const extentions = require('../eslintOptions').extensions(api)
|
||||
.map(ext => ext.replace(/^\./, '')) // remove the leading `.`
|
||||
pkg['lint-staged'] = {
|
||||
[`*.{${extentions.join(',')}}`]: ['vue-cli-service lint', 'git add']
|
||||
}
|
||||
@@ -157,10 +100,6 @@ module.exports.applyTS = api => {
|
||||
parser: '@typescript-eslint/parser'
|
||||
}
|
||||
},
|
||||
devDependencies: {
|
||||
'@vue/eslint-config-typescript': '^5.0.1',
|
||||
'@typescript-eslint/eslint-plugin': '^2.7.0',
|
||||
'@typescript-eslint/parser': '^2.7.0'
|
||||
}
|
||||
devDependencies: require('../eslintDeps').DEPS_MAP.typescript
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,26 +1,74 @@
|
||||
module.exports = (api) => {
|
||||
const inquirer = require('inquirer')
|
||||
const { semver } = require('@vue/cli-shared-utils')
|
||||
|
||||
module.exports = async (api) => {
|
||||
const pkg = require(api.resolve('package.json'))
|
||||
|
||||
let localESLintRange = pkg.devDependencies.eslint
|
||||
|
||||
// if project is scaffolded by Vue CLI 3.0.x or earlier,
|
||||
// the ESLint dependency (ESLint v4) is inside @vue/cli-plugin-eslint;
|
||||
// in Vue CLI v4 it should be extracted to the project dependency list.
|
||||
if (api.fromVersion('^3')) {
|
||||
const pkg = require(api.resolve('package.json'))
|
||||
const hasESLint = [
|
||||
'dependencies',
|
||||
'devDependencies',
|
||||
'peerDependencies',
|
||||
'optionalDependencies'
|
||||
].some(depType =>
|
||||
Object.keys(pkg[depType] || {}).includes('eslint')
|
||||
)
|
||||
if (api.fromVersion('^3') && !localESLintRange) {
|
||||
localESLintRange = '^4.19.1'
|
||||
api.extendPackage({
|
||||
devDependencies: {
|
||||
eslint: localESLintRange,
|
||||
'babel-eslint': '^8.2.5',
|
||||
'eslint-plugin-vue': '^4.5.0'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (!hasESLint) {
|
||||
const localESLintMajor = semver.major(
|
||||
semver.maxSatisfying(
|
||||
['4.99.0', '5.99.0', '6.99.0'],
|
||||
localESLintRange
|
||||
)
|
||||
)
|
||||
|
||||
if (localESLintMajor === 6) {
|
||||
return
|
||||
}
|
||||
|
||||
const { confirmUpgrade } = await inquirer.prompt([{
|
||||
name: 'confirmUpgrade',
|
||||
type: 'confirm',
|
||||
message:
|
||||
`Your current ESLint version is v${localESLintMajor}.` +
|
||||
`The lastest major version is v6.\n` +
|
||||
`Do you want to upgrade? (May contain breaking changes)\n`
|
||||
}])
|
||||
|
||||
if (confirmUpgrade) {
|
||||
const { getDeps } = require('../eslintDeps')
|
||||
|
||||
const newDeps = getDeps(api)
|
||||
if (pkg.devDependencies['@vue/eslint-config-airbnb']) {
|
||||
Object.assign(newDeps, getDeps(api, 'airbnb'))
|
||||
}
|
||||
if (pkg.devDependencies['@vue/eslint-config-standard']) {
|
||||
Object.assign(newDeps, getDeps(api, 'standard'))
|
||||
}
|
||||
if (pkg.devDependencies['@vue/eslint-config-prettier']) {
|
||||
Object.assign(newDeps, getDeps(api, 'prettier'))
|
||||
}
|
||||
|
||||
api.extendPackage({ devDependencies: newDeps })
|
||||
|
||||
// in case anyone's upgrading from the legacy `typescript-eslint-parser`
|
||||
if (api.hasPlugin('typescript')) {
|
||||
api.extendPackage({
|
||||
devDependencies: {
|
||||
eslint: '^4.19.1'
|
||||
eslintConfig: {
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser'
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: add a prompt for users to optionally upgrade their eslint configs to a new major version
|
||||
// TODO:
|
||||
// transform `@vue/prettier` to `eslint:recommended` + `@vue/prettier`
|
||||
// transform `@vue/typescript` to `@vue/typescript/recommended` and also fix prettier compatibility for it
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"@vue/cli-shared-utils": "^4.1.2",
|
||||
"eslint-loader": "^2.1.2",
|
||||
"globby": "^9.2.0",
|
||||
"inquirer": "^6.3.1",
|
||||
"webpack": "^4.0.0",
|
||||
"yorkie": "^2.0.0"
|
||||
},
|
||||
|
||||
24
packages/@vue/cli-test-utils/createUpgradableProject.js
Normal file
24
packages/@vue/cli-test-utils/createUpgradableProject.js
Normal file
@@ -0,0 +1,24 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const createTestProject = require('./createTestProject')
|
||||
const Upgrader = require('@vue/cli/lib/Upgrader')
|
||||
|
||||
const outsideTestFolder = path.resolve(__dirname, '../../../../vue-upgrade-tests')
|
||||
|
||||
module.exports = async function createUpgradableProject (...args) {
|
||||
if (!fs.existsSync(outsideTestFolder)) {
|
||||
fs.mkdirSync(outsideTestFolder)
|
||||
}
|
||||
process.env.VUE_CLI_TEST_DO_INSTALL_PLUGIN = true
|
||||
|
||||
const project = await createTestProject(...args, outsideTestFolder)
|
||||
const upgrade = async function upgrade (pluginName, options) {
|
||||
return (new Upgrader(project.dir)).upgrade(pluginName, options || {})
|
||||
}
|
||||
|
||||
return {
|
||||
...project,
|
||||
upgrade
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user