Skip to content

Commit 3897283

Browse files
committed
feat: support flat config files in bin
1 parent 878e5d5 commit 3897283

File tree

2 files changed

+120
-40
lines changed

2 files changed

+120
-40
lines changed

Diff for: bin/create-eslint-config.js

+57-16
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,19 @@ const indent = inferIndent(rawPkgJson)
4242
const pkg = JSON.parse(rawPkgJson)
4343

4444
// 1. check for existing config files
45-
// `.eslintrc.*`, `eslintConfig` in `package.json`
45+
// `.eslintrc.*`, `eslint.config.*` and `eslintConfig` in `package.json`
4646
// ask if wanna overwrite?
47-
48-
// https://eslint.org/docs/latest/user-guide/configuring/configuration-files#configuration-file-formats
49-
// The experimental `eslint.config.js` isn't supported yet
50-
const eslintConfigFormats = ['js', 'cjs', 'yaml', 'yml', 'json']
51-
for (const fmt of eslintConfigFormats) {
52-
const configFileName = `.eslintrc.${fmt}`
47+
const eslintConfigFormats = [
48+
'.eslintrc.js',
49+
'.eslintrc.cjs',
50+
'.eslintrc.yaml',
51+
'.eslintrc.yml',
52+
'.eslintrc.json',
53+
'eslint.config.js',
54+
'eslint.config.mjs',
55+
'eslint.config.cjs'
56+
]
57+
for (const configFileName of eslintConfigFormats) {
5358
const fullConfigPath = path.resolve(cwd, configFileName)
5459
if (existsSync(fullConfigPath)) {
5560
const { shouldRemove } = await prompt({
@@ -88,7 +93,39 @@ if (pkg.eslintConfig) {
8893
}
8994
}
9095

91-
// 2. Check Vue
96+
// 2. Config format
97+
let configFormat
98+
try {
99+
const eslintVersion = requireInCwd('eslint/package.json').version
100+
console.info(dim(`Detected ESLint version: ${eslintVersion}`))
101+
const [major, minor] = eslintVersion.split('.')
102+
if (parseInt(major) >= 9) {
103+
configFormat = 'flat'
104+
} else if (parseInt(major) === 8 && parseInt(minor) >= 57) {
105+
throw eslintVersion
106+
} else {
107+
configFormat = 'eslintrc'
108+
}
109+
} catch (e) {
110+
const anwsers = await prompt({
111+
type: 'select',
112+
name: 'configFormat',
113+
message: 'Which configuration file format should be used?',
114+
choices: [
115+
{
116+
name: 'flat',
117+
message: 'eslint.config.js (a.k.a. Flat Config, the new default)'
118+
},
119+
{
120+
name: 'eslintrc',
121+
message: `.eslintrc.cjs (deprecated with ESLint v9.0.0)`
122+
},
123+
]
124+
})
125+
configFormat = anwsers.configFormat
126+
}
127+
128+
// 3. Check Vue
92129
// Not detected? Choose from Vue 2 or 3
93130
// TODO: better support for 2.7 and vue-demi
94131
let vueVersion
@@ -108,7 +145,7 @@ try {
108145
vueVersion = anwsers.vueVersion
109146
}
110147

111-
// 3. Choose a style guide
148+
// 4. Choose a style guide
112149
// - Error Prevention (ESLint Recommended)
113150
// - Standard
114151
// - Airbnb
@@ -132,10 +169,10 @@ const { styleGuide } = await prompt({
132169
]
133170
})
134171

135-
// 4. Check TypeScript
136-
// 4.1 Allow JS?
137-
// 4.2 Allow JS in Vue?
138-
// 4.3 Allow JSX (TSX, if answered no in 4.1) in Vue?
172+
// 5. Check TypeScript
173+
// 5.1 Allow JS?
174+
// 5.2 Allow JS in Vue?
175+
// 5.3 Allow JSX (TSX, if answered no in 5.1) in Vue?
139176
let hasTypeScript = false
140177
const additionalConfig = {}
141178
try {
@@ -200,7 +237,7 @@ if (hasTypeScript && styleGuide !== 'default') {
200237
}
201238
}
202239

203-
// 5. If Airbnb && !TypeScript
240+
// 6. If Airbnb && !TypeScript
204241
// Does your project use any path aliases?
205242
// Show [snippet prompts](https://github.com/enquirer/enquirer#snippet-prompt) for the user to input aliases
206243
if (styleGuide === 'airbnb' && !hasTypeScript) {
@@ -255,7 +292,7 @@ if (styleGuide === 'airbnb' && !hasTypeScript) {
255292
}
256293
}
257294

258-
// 6. Do you need Prettier to format your codebase?
295+
// 7. Do you need Prettier to format your codebase?
259296
const { needsPrettier } = await prompt({
260297
type: 'toggle',
261298
disabled: 'No',
@@ -266,6 +303,8 @@ const { needsPrettier } = await prompt({
266303

267304
const { pkg: pkgToExtend, files } = createConfig({
268305
vueVersion,
306+
configFormat,
307+
269308
styleGuide,
270309
hasTypeScript,
271310
needsPrettier,
@@ -291,6 +330,8 @@ for (const [name, content] of Object.entries(files)) {
291330
writeFileSync(fullPath, content, 'utf-8')
292331
}
293332

333+
const configFilename = configFormat === 'flat' ? 'eslint.config.js' : '.eslintrc.cjs'
334+
294335
// Prompt: Run `npm install` or `yarn` or `pnpm install`
295336
const userAgent = process.env.npm_config_user_agent ?? ''
296337
const packageManager = /pnpm/.test(userAgent) ? 'pnpm' : /yarn/.test(userAgent) ? 'yarn' : 'npm'
@@ -300,7 +341,7 @@ const lintCommand = packageManager === 'npm' ? 'npm run lint' : `${packageManage
300341

301342
console.info(
302343
'\n' +
303-
`${bold(yellow('package.json'))} and ${bold(blue('.eslintrc.cjs'))} have been updated.\n` +
344+
`${bold(yellow('package.json'))} and ${bold(blue(configFilename))} have been updated.\n` +
304345
`Now please run ${bold(green(installCommand))} to re-install the dependencies.\n` +
305346
`Then you can run ${bold(green(lintCommand))} to lint your files.`
306347
)

Diff for: index.js

+63-24
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import versionMap from './versionMap.cjs'
88
const CREATE_ALIAS_SETTING_PLACEHOLDER = 'CREATE_ALIAS_SETTING_PLACEHOLDER'
99
export { CREATE_ALIAS_SETTING_PLACEHOLDER }
1010

11-
function stringifyJS (value, styleGuide) {
11+
function stringifyJS (value, styleGuide, configFormat) {
1212
// eslint-disable-next-line no-shadow
1313
const result = stringify(value, (val, indent, stringify, key) => {
1414
if (key === 'CREATE_ALIAS_SETTING_PLACEHOLDER') {
@@ -18,6 +18,10 @@ function stringifyJS (value, styleGuide) {
1818
return stringify(val)
1919
}, 2)
2020

21+
if (configFormat === 'flat') {
22+
return result.replace('CREATE_ALIAS_SETTING_PLACEHOLDER: ', '...createAliasSetting')
23+
}
24+
2125
return result.replace(
2226
'CREATE_ALIAS_SETTING_PLACEHOLDER: ',
2327
`...require('@vue/eslint-config-${styleGuide}/createAliasSetting')`
@@ -72,17 +76,15 @@ export default function createConfig ({
7276
addDependency('eslint')
7377
addDependency('eslint-plugin-vue')
7478

75-
if (configFormat === 'flat') {
76-
addDependency('@eslint/eslintrc')
77-
addDependency('@eslint/js')
78-
} else if (styleGuide !== 'default' || hasTypeScript || needsPrettier) {
79-
addDependency('@rushstack/eslint-patch')
79+
if (
80+
configFormat === "eslintrc" &&
81+
(styleGuide !== "default" || hasTypeScript || needsPrettier)
82+
) {
83+
addDependency("@rushstack/eslint-patch");
8084
}
8185

8286
const language = hasTypeScript ? 'typescript' : 'javascript'
8387

84-
const flatConfigExtends = []
85-
const flatConfigImports = []
8688
const eslintrcConfig = {
8789
root: true,
8890
extends: [
@@ -96,6 +98,20 @@ export default function createConfig ({
9698
eslintrcConfig.extends.push(name)
9799
}
98100

101+
let needsFlatCompat = false
102+
const flatConfigExtends = []
103+
const flatConfigImports = []
104+
flatConfigImports.push(`import pluginVue from 'eslint-plugin-vue'`)
105+
flatConfigExtends.push(
106+
vueVersion.startsWith('2')
107+
? `...pluginVue.configs['flat/vue2-essential']`
108+
: `...pluginVue.configs['flat/essential']`
109+
)
110+
111+
if (configFormat === 'flat' && styleGuide === 'default') {
112+
addDependency('@eslint/js')
113+
}
114+
99115
switch (`${styleGuide}-${language}`) {
100116
case 'default-javascript':
101117
eslintrcConfig.extends.push('eslint:recommended')
@@ -107,41 +123,53 @@ export default function createConfig ({
107123
flatConfigImports.push(`import js from '@eslint/js'`)
108124
flatConfigExtends.push('js.configs.recommended')
109125
addDependencyAndExtend('@vue/eslint-config-typescript')
126+
needsFlatCompat = true
110127
flatConfigExtends.push(`...compat.extends('@vue/eslint-config-typescript')`)
111128
break
112129
case 'airbnb-javascript':
113130
case 'standard-javascript':
114131
addDependencyAndExtend(`@vue/eslint-config-${styleGuide}`)
132+
needsFlatCompat = true
115133
flatConfigExtends.push(`...compat.extends('@vue/eslint-config-${styleGuide}')`)
116134
break
117135
case 'airbnb-typescript':
118136
case 'standard-typescript':
119137
addDependencyAndExtend(`@vue/eslint-config-${styleGuide}-with-typescript`)
138+
needsFlatCompat = true
120139
flatConfigExtends.push(`...compat.extends('@vue/eslint-config-${styleGuide}-with-typescript')`)
121140
break
122141
default:
123142
throw new Error(`unexpected combination of styleGuide and language: ${styleGuide}-${language}`)
124143
}
125144

126-
flatConfigImports.push(`import pluginVue from 'eslint-plugin-vue'`)
127-
flatConfigExtends.push(
128-
vueVersion.startsWith('2')
129-
? `...pluginVue.configs['flat/vue2-essential']`
130-
: `...pluginVue.configs['flat/essential']`
131-
)
132-
133145
deepMerge(pkg.devDependencies, additionalDependencies)
134146
deepMerge(eslintrcConfig, additionalConfig)
135147

148+
if (additionalConfig?.extends) {
149+
needsFlatCompat = true
150+
additionalConfig.extends.forEach((pkgName) => {
151+
flatConfigExtends.push(`...compat.extends('${pkgName}')`)
152+
})
153+
}
154+
136155
const flatConfigEntry = {
137156
files: filePatterns
138157
}
139-
deepMerge(flatConfigEntry, additionalConfig)
158+
if (additionalConfig?.settings?.[CREATE_ALIAS_SETTING_PLACEHOLDER]) {
159+
flatConfigImports.push(
160+
`import createAliasSetting from '@vue/eslint-config-${styleGuide}/createAliasSetting'`
161+
)
162+
flatConfigEntry.settings = {
163+
[CREATE_ALIAS_SETTING_PLACEHOLDER]:
164+
additionalConfig.settings[CREATE_ALIAS_SETTING_PLACEHOLDER]
165+
}
166+
}
140167

141168
if (needsPrettier) {
142169
addDependency('prettier')
143170
addDependency('@vue/eslint-config-prettier')
144171
eslintrcConfig.extends.push('@vue/eslint-config-prettier/skip-formatting')
172+
needsFlatCompat = true
145173
flatConfigExtends.push(`...compat.extends('@vue/eslint-config-prettier/skip-formatting')`)
146174
}
147175

@@ -174,27 +202,38 @@ export default function createConfig ({
174202

175203
// eslint.config.js | .eslintrc.cjs
176204
if (configFormat === 'flat') {
177-
files['eslint.config.js'] += "import path from 'node:path'\n"
178-
files['eslint.config.js'] += "import { fileURLToPath } from 'node:url'\n\n"
205+
if (needsFlatCompat) {
206+
files['eslint.config.js'] += "import path from 'node:path'\n"
207+
files['eslint.config.js'] += "import { fileURLToPath } from 'node:url'\n\n"
208+
209+
addDependency('@eslint/eslintrc')
210+
files['eslint.config.js'] += "import { FlatCompat } from '@eslint/eslintrc'\n"
211+
}
179212

213+
// imports
180214
flatConfigImports.forEach((pkgImport) => {
181215
files['eslint.config.js'] += `${pkgImport}\n`
182216
})
183217
files['eslint.config.js'] += '\n'
184218

185219
// neccesary for compatibility until all packages support flat config
186-
files['eslint.config.js'] += 'const __filename = fileURLToPath(import.meta.url)\n'
187-
files['eslint.config.js'] += 'const __dirname = path.dirname(__filename)\n'
188-
files['eslint.config.js'] += 'const compat = new FlatCompat({\n'
189-
files['eslint.config.js'] += ' baseDirectory: __dirname\n'
190-
files['eslint.config.js'] += '})\n\n'
220+
if (needsFlatCompat) {
221+
files['eslint.config.js'] += 'const __filename = fileURLToPath(import.meta.url)\n'
222+
files['eslint.config.js'] += 'const __dirname = path.dirname(__filename)\n'
223+
files['eslint.config.js'] += 'const compat = new FlatCompat({\n'
224+
files['eslint.config.js'] += ' baseDirectory: __dirname'
225+
if (pkg.devDependencies['@vue/eslint-config-typescript']) {
226+
files['eslint.config.js'] += ',\n recommendedConfig: js.configs.recommended'
227+
}
228+
files['eslint.config.js'] += '\n})\n\n'
229+
}
191230

192231
files['eslint.config.js'] += 'export default [\n'
193232
flatConfigExtends.forEach((usage) => {
194233
files['eslint.config.js'] += ` ${usage},\n`
195234
})
196235

197-
const [, ...keep] = stringifyJS([flatConfigEntry], styleGuide).split('{')
236+
const [, ...keep] = stringifyJS([flatConfigEntry], styleGuide, "flat").split('{')
198237
files['eslint.config.js'] += ` {${keep.join('{')}\n`
199238
} else {
200239
files['.eslintrc.cjs'] += `module.exports = ${stringifyJS(eslintrcConfig, styleGuide)}\n`

0 commit comments

Comments
 (0)