Skip to content

Commit 1a9f098

Browse files
committed
feat: support flat config files in bin
1 parent 92fcd27 commit 1a9f098

File tree

2 files changed

+73
-28
lines changed

2 files changed

+73
-28
lines changed

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) >= 21) {
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 generated?',
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
)

index.js

+16-12
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,6 @@ export default function createConfig ({
8181

8282
const language = hasTypeScript ? 'typescript' : 'javascript'
8383

84-
const flatConfigExtends = []
85-
const flatConfigImports = []
8684
const eslintrcConfig = {
8785
root: true,
8886
extends: [
@@ -96,6 +94,15 @@ export default function createConfig ({
9694
eslintrcConfig.extends.push(name)
9795
}
9896

97+
const flatConfigExtends = []
98+
const flatConfigImports = []
99+
flatConfigImports.push(`import pluginVue from 'eslint-plugin-vue'`)
100+
flatConfigExtends.push(
101+
vueVersion.startsWith('2')
102+
? `...pluginVue.configs['flat/vue2-essential']`
103+
: `...pluginVue.configs['flat/essential']`
104+
)
105+
99106
switch (`${styleGuide}-${language}`) {
100107
case 'default-javascript':
101108
eslintrcConfig.extends.push('eslint:recommended')
@@ -123,20 +130,14 @@ export default function createConfig ({
123130
throw new Error(`unexpected combination of styleGuide and language: ${styleGuide}-${language}`)
124131
}
125132

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-
133133
deepMerge(pkg.devDependencies, additionalDependencies)
134134
deepMerge(eslintrcConfig, additionalConfig)
135135

136-
const flatConfigEntry = {
137-
files: filePatterns
136+
if (additionalConfig?.extends) {
137+
additionalConfig.extends.forEach((pkgName) => {
138+
flatConfigExtends.push(`...compat.extends('${pkgName}')`)
139+
})
138140
}
139-
deepMerge(flatConfigEntry, additionalConfig)
140141

141142
if (needsPrettier) {
142143
addDependency('prettier')
@@ -152,6 +153,9 @@ export default function createConfig ({
152153
[configFilename]: ''
153154
}
154155

156+
const flatConfigEntry = {
157+
files: filePatterns
158+
}
155159
if (styleGuide === 'default') {
156160
// Both Airbnb & Standard have already set `env: node`
157161
files[configFilename] += '/* eslint-env node */\n'

0 commit comments

Comments
 (0)