From 878e5d5c16d7b2121c188094410117a6cc9c1db9 Mon Sep 17 00:00:00 2001
From: Joost Kersjes <joostkersjes@live.nl>
Date: Thu, 11 Apr 2024 20:34:17 +0200
Subject: [PATCH 1/3] feat: add options to generate a flat config

---
 index.js       | 88 ++++++++++++++++++++++++++++++++++++++++++--------
 package.json   |  2 ++
 pnpm-lock.yaml | 61 ++++++++++++++++++++++++++++++++++
 3 files changed, 138 insertions(+), 13 deletions(-)

diff --git a/index.js b/index.js
index b976859..1efe9da 100644
--- a/index.js
+++ b/index.js
@@ -52,9 +52,12 @@ export function deepMerge (target, obj) {
 // This is also used in `create-vue`
 export default function createConfig ({
   vueVersion = '3.x', // '2.x' | '3.x' (TODO: 2.7 / vue-demi)
+  configFormat = 'eslintrc', // eslintrc | flat
 
-  styleGuide = 'default', // default | airbnb | typescript
-  hasTypeScript = false, // js | ts
+  filePatterns = [], // flat format only - e.g. '**/*.vue', '**/*.js', etc.
+
+  styleGuide = 'default', // default | airbnb | standard
+  hasTypeScript = false, // true | false
   needsPrettier = false, // true | false
 
   additionalConfig = {}, // e.g. Cypress, createAliasSetting for Airbnb, etc.
@@ -69,13 +72,18 @@ export default function createConfig ({
   addDependency('eslint')
   addDependency('eslint-plugin-vue')
 
-  if (styleGuide !== 'default' || hasTypeScript || needsPrettier) {
+  if (configFormat === 'flat') {
+    addDependency('@eslint/eslintrc')
+    addDependency('@eslint/js')
+  } else if (styleGuide !== 'default' || hasTypeScript || needsPrettier) {
     addDependency('@rushstack/eslint-patch')
   }
 
   const language = hasTypeScript ? 'typescript' : 'javascript'
 
-  const eslintConfig = {
+  const flatConfigExtends = []
+  const flatConfigImports = []
+  const eslintrcConfig = {
     root: true,
     extends: [
       vueVersion.startsWith('2')
@@ -85,49 +93,77 @@ export default function createConfig ({
   }
   const addDependencyAndExtend = (name) => {
     addDependency(name)
-    eslintConfig.extends.push(name)
+    eslintrcConfig.extends.push(name)
   }
 
   switch (`${styleGuide}-${language}`) {
     case 'default-javascript':
-      eslintConfig.extends.push('eslint:recommended')
+      eslintrcConfig.extends.push('eslint:recommended')
+      flatConfigImports.push(`import js from '@eslint/js'`)
+      flatConfigExtends.push('js.configs.recommended')
       break
     case 'default-typescript':
-      eslintConfig.extends.push('eslint:recommended')
+      eslintrcConfig.extends.push('eslint:recommended')
+      flatConfigImports.push(`import js from '@eslint/js'`)
+      flatConfigExtends.push('js.configs.recommended')
       addDependencyAndExtend('@vue/eslint-config-typescript')
+      flatConfigExtends.push(`...compat.extends('@vue/eslint-config-typescript')`)
       break
     case 'airbnb-javascript':
     case 'standard-javascript':
       addDependencyAndExtend(`@vue/eslint-config-${styleGuide}`)
+      flatConfigExtends.push(`...compat.extends('@vue/eslint-config-${styleGuide}')`)
       break
     case 'airbnb-typescript':
     case 'standard-typescript':
       addDependencyAndExtend(`@vue/eslint-config-${styleGuide}-with-typescript`)
+      flatConfigExtends.push(`...compat.extends('@vue/eslint-config-${styleGuide}-with-typescript')`)
       break
     default:
       throw new Error(`unexpected combination of styleGuide and language: ${styleGuide}-${language}`)
   }
 
+  flatConfigImports.push(`import pluginVue from 'eslint-plugin-vue'`)
+  flatConfigExtends.push(
+    vueVersion.startsWith('2')
+      ? `...pluginVue.configs['flat/vue2-essential']`
+      : `...pluginVue.configs['flat/essential']`
+  )
+
   deepMerge(pkg.devDependencies, additionalDependencies)
-  deepMerge(eslintConfig, additionalConfig)
+  deepMerge(eslintrcConfig, additionalConfig)
+
+  const flatConfigEntry = {
+    files: filePatterns
+  }
+  deepMerge(flatConfigEntry, additionalConfig)
 
   if (needsPrettier) {
     addDependency('prettier')
     addDependency('@vue/eslint-config-prettier')
-    eslintConfig.extends.push('@vue/eslint-config-prettier/skip-formatting')
+    eslintrcConfig.extends.push('@vue/eslint-config-prettier/skip-formatting')
+    flatConfigExtends.push(`...compat.extends('@vue/eslint-config-prettier/skip-formatting')`)
   }
 
+  const configFilename = configFormat === 'flat'
+    ? 'eslint.config.js'
+    : '.eslintrc.cjs'
   const files = {
-    '.eslintrc.cjs': ''
+    [configFilename]: ''
   }
 
   if (styleGuide === 'default') {
     // Both Airbnb & Standard have already set `env: node`
-    files['.eslintrc.cjs'] += '/* eslint-env node */\n'
+    if (configFormat === 'eslintrc') {
+      files['.eslintrc.cjs'] += '/* eslint-env node */\n'
+    }
 
     // Both Airbnb & Standard have already set `ecmaVersion`
     // The default in eslint-plugin-vue is 2020, which doesn't support top-level await
-    eslintConfig.parserOptions = {
+    eslintrcConfig.parserOptions = {
+      ecmaVersion: 'latest'
+    }
+    flatConfigEntry.languageOptions = {
       ecmaVersion: 'latest'
     }
   }
@@ -136,7 +172,33 @@ export default function createConfig ({
     files['.eslintrc.cjs'] += "require('@rushstack/eslint-patch/modern-module-resolution')\n\n"
   }
 
-  files['.eslintrc.cjs'] += `module.exports = ${stringifyJS(eslintConfig, styleGuide)}\n`
+  // eslint.config.js | .eslintrc.cjs
+  if (configFormat === 'flat') {
+    files['eslint.config.js'] += "import path from 'node:path'\n"
+    files['eslint.config.js'] += "import { fileURLToPath } from 'node:url'\n\n"
+
+    flatConfigImports.forEach((pkgImport) => {
+      files['eslint.config.js'] += `${pkgImport}\n`
+    })
+    files['eslint.config.js'] += '\n'
+
+    // neccesary for compatibility until all packages support flat config
+    files['eslint.config.js'] += 'const __filename = fileURLToPath(import.meta.url)\n'
+    files['eslint.config.js'] += 'const __dirname = path.dirname(__filename)\n'
+    files['eslint.config.js'] += 'const compat = new FlatCompat({\n'
+    files['eslint.config.js'] += '  baseDirectory: __dirname\n'
+    files['eslint.config.js'] += '})\n\n'
+    
+    files['eslint.config.js'] += 'export default [\n'
+    flatConfigExtends.forEach((usage) => {
+      files['eslint.config.js'] += `  ${usage},\n`
+    })
+
+    const [, ...keep] = stringifyJS([flatConfigEntry], styleGuide).split('{')
+    files['eslint.config.js'] += `  {${keep.join('{')}\n`
+  } else {
+    files['.eslintrc.cjs'] += `module.exports = ${stringifyJS(eslintrcConfig, styleGuide)}\n`
+  }
 
   // .editorconfig & .prettierrc.json
   if (editorconfigs[styleGuide]) {
diff --git a/package.json b/package.json
index d80996b..8d0a1fc 100644
--- a/package.json
+++ b/package.json
@@ -37,6 +37,8 @@
     "kolorist": "^1.8.0"
   },
   "devDependencies": {
+    "@eslint/eslintrc": "^3.0.2",
+    "@eslint/js": "^9.0.0",
     "@rushstack/eslint-patch": "^1.10.1",
     "@vue/eslint-config-airbnb": "^8.0.0",
     "@vue/eslint-config-airbnb-with-typescript": "^8.0.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e3cf1a5..bff37e8 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -16,6 +16,12 @@ dependencies:
     version: 1.8.0
 
 devDependencies:
+  '@eslint/eslintrc':
+    specifier: ^3.0.2
+    version: 3.0.2
+  '@eslint/js':
+    specifier: ^9.0.0
+    version: 9.0.0
   '@rushstack/eslint-patch':
     specifier: ^1.10.1
     version: 1.10.1
@@ -112,11 +118,33 @@ packages:
       - supports-color
     dev: true
 
+  /@eslint/eslintrc@3.0.2:
+    resolution: {integrity: sha512-wV19ZEGEMAC1eHgrS7UQPqsdEiCIbTKTasEfcXAigzoXICcqZSjBZEHlZwNVvKg6UBCjSlos84XiLqsRJnIcIg==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    dependencies:
+      ajv: 6.12.6
+      debug: 4.3.4
+      espree: 10.0.1
+      globals: 14.0.0
+      ignore: 5.2.4
+      import-fresh: 3.3.0
+      js-yaml: 4.1.0
+      minimatch: 3.1.2
+      strip-json-comments: 3.1.1
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /@eslint/js@8.57.0:
     resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dev: true
 
+  /@eslint/js@9.0.0:
+    resolution: {integrity: sha512-RThY/MnKrhubF6+s1JflwUjPEsnCEmYCWwqa/aRISKWNXGZ9epUwft4bUMM35SdKF9xvBrLydAM1RDHd1Z//ZQ==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    dev: true
+
   /@humanwhocodes/config-array@0.11.14:
     resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==}
     engines: {node: '>=10.10.0'}
@@ -745,12 +773,26 @@ packages:
       acorn: 8.10.0
     dev: true
 
+  /acorn-jsx@5.3.2(acorn@8.11.3):
+    resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
+    peerDependencies:
+      acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+    dependencies:
+      acorn: 8.11.3
+    dev: true
+
   /acorn@8.10.0:
     resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==}
     engines: {node: '>=0.4.0'}
     hasBin: true
     dev: true
 
+  /acorn@8.11.3:
+    resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==}
+    engines: {node: '>=0.4.0'}
+    hasBin: true
+    dev: true
+
   /ajv@6.12.6:
     resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
     dependencies:
@@ -1931,6 +1973,11 @@ packages:
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dev: true
 
+  /eslint-visitor-keys@4.0.0:
+    resolution: {integrity: sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    dev: true
+
   /eslint@8.57.0:
     resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -1978,6 +2025,15 @@ packages:
       - supports-color
     dev: true
 
+  /espree@10.0.1:
+    resolution: {integrity: sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    dependencies:
+      acorn: 8.11.3
+      acorn-jsx: 5.3.2(acorn@8.11.3)
+      eslint-visitor-keys: 4.0.0
+    dev: true
+
   /espree@9.6.1:
     resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -2216,6 +2272,11 @@ packages:
       type-fest: 0.20.2
     dev: true
 
+  /globals@14.0.0:
+    resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
+    engines: {node: '>=18'}
+    dev: true
+
   /globalthis@1.0.3:
     resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==}
     engines: {node: '>= 0.4'}

From 38972836ccc6eb1fba9349811fb4ba87b1fcecf6 Mon Sep 17 00:00:00 2001
From: Joost Kersjes <joostkersjes@live.nl>
Date: Thu, 11 Apr 2024 22:09:24 +0200
Subject: [PATCH 2/3] feat: support flat config files in bin

---
 bin/create-eslint-config.js | 73 ++++++++++++++++++++++++-------
 index.js                    | 87 +++++++++++++++++++++++++++----------
 2 files changed, 120 insertions(+), 40 deletions(-)

diff --git a/bin/create-eslint-config.js b/bin/create-eslint-config.js
index 2c858d1..a6d24b9 100644
--- a/bin/create-eslint-config.js
+++ b/bin/create-eslint-config.js
@@ -42,14 +42,19 @@ const indent = inferIndent(rawPkgJson)
 const pkg = JSON.parse(rawPkgJson)
 
 // 1. check for existing config files
-// `.eslintrc.*`, `eslintConfig` in `package.json`
+// `.eslintrc.*`, `eslint.config.*` and `eslintConfig` in `package.json`
 // ask if wanna overwrite?
-
-// https://eslint.org/docs/latest/user-guide/configuring/configuration-files#configuration-file-formats
-// The experimental `eslint.config.js` isn't supported yet
-const eslintConfigFormats = ['js', 'cjs', 'yaml', 'yml', 'json']
-for (const fmt of eslintConfigFormats) {
-  const configFileName = `.eslintrc.${fmt}`
+const eslintConfigFormats = [
+  '.eslintrc.js',
+  '.eslintrc.cjs',
+  '.eslintrc.yaml',
+  '.eslintrc.yml',
+  '.eslintrc.json',
+  'eslint.config.js',
+  'eslint.config.mjs',
+  'eslint.config.cjs'
+]
+for (const configFileName of eslintConfigFormats) {
   const fullConfigPath = path.resolve(cwd, configFileName)
   if (existsSync(fullConfigPath)) {
     const { shouldRemove } = await prompt({
@@ -88,7 +93,39 @@ if (pkg.eslintConfig) {
   }
 }
 
-// 2. Check Vue
+// 2. Config format
+let configFormat
+try { 
+  const eslintVersion = requireInCwd('eslint/package.json').version
+  console.info(dim(`Detected ESLint version: ${eslintVersion}`))
+  const [major, minor] = eslintVersion.split('.')
+  if (parseInt(major) >= 9) {
+    configFormat = 'flat'
+  } else if (parseInt(major) === 8 && parseInt(minor) >= 57) {
+    throw eslintVersion
+  } else {
+    configFormat = 'eslintrc'
+  }
+} catch (e) {
+  const anwsers = await prompt({
+    type: 'select',
+    name: 'configFormat',
+    message: 'Which configuration file format should be used?',
+    choices: [
+      {
+        name: 'flat',
+        message: 'eslint.config.js (a.k.a. Flat Config, the new default)'
+      },
+      {
+        name: 'eslintrc',
+        message: `.eslintrc.cjs (deprecated with ESLint v9.0.0)`
+      },
+    ]
+  })
+  configFormat = anwsers.configFormat
+}
+
+// 3. Check Vue
 // Not detected? Choose from Vue 2 or 3
 // TODO: better support for 2.7 and vue-demi
 let vueVersion
@@ -108,7 +145,7 @@ try {
   vueVersion = anwsers.vueVersion
 }
 
-// 3. Choose a style guide
+// 4. Choose a style guide
 // - Error Prevention (ESLint Recommended)
 // - Standard
 // - Airbnb
@@ -132,10 +169,10 @@ const { styleGuide } = await prompt({
   ]
 })
 
-// 4. Check TypeScript
-// 4.1 Allow JS?
-// 4.2 Allow JS in Vue?
-// 4.3 Allow JSX (TSX, if answered no in 4.1) in Vue?
+// 5. Check TypeScript
+// 5.1 Allow JS?
+// 5.2 Allow JS in Vue?
+// 5.3 Allow JSX (TSX, if answered no in 5.1) in Vue?
 let hasTypeScript = false
 const additionalConfig = {}
 try {
@@ -200,7 +237,7 @@ if (hasTypeScript && styleGuide !== 'default') {
   }
 }
 
-// 5. If Airbnb && !TypeScript
+// 6. If Airbnb && !TypeScript
 // Does your project use any path aliases?
 // Show [snippet prompts](https://github.com/enquirer/enquirer#snippet-prompt) for the user to input aliases
 if (styleGuide === 'airbnb' && !hasTypeScript) {
@@ -255,7 +292,7 @@ if (styleGuide === 'airbnb' && !hasTypeScript) {
   }
 }
 
-// 6. Do you need Prettier to format your codebase?
+// 7. Do you need Prettier to format your codebase?
 const { needsPrettier } = await prompt({
   type: 'toggle',
   disabled: 'No',
@@ -266,6 +303,8 @@ const { needsPrettier } = await prompt({
 
 const { pkg: pkgToExtend, files } = createConfig({
   vueVersion,
+  configFormat,
+
   styleGuide,
   hasTypeScript,
   needsPrettier,
@@ -291,6 +330,8 @@ for (const [name, content] of Object.entries(files)) {
   writeFileSync(fullPath, content, 'utf-8')
 }
 
+const configFilename = configFormat === 'flat' ? 'eslint.config.js' : '.eslintrc.cjs'
+
 // Prompt: Run `npm install` or `yarn` or `pnpm install`
 const userAgent = process.env.npm_config_user_agent ?? ''
 const packageManager = /pnpm/.test(userAgent) ? 'pnpm' : /yarn/.test(userAgent) ? 'yarn' : 'npm'
@@ -300,7 +341,7 @@ const lintCommand = packageManager === 'npm' ? 'npm run lint' : `${packageManage
 
 console.info(
   '\n' +
-  `${bold(yellow('package.json'))} and ${bold(blue('.eslintrc.cjs'))} have been updated.\n` +
+  `${bold(yellow('package.json'))} and ${bold(blue(configFilename))} have been updated.\n` +
   `Now please run ${bold(green(installCommand))} to re-install the dependencies.\n` +
   `Then you can run ${bold(green(lintCommand))} to lint your files.`
 )
diff --git a/index.js b/index.js
index 1efe9da..c46717b 100644
--- a/index.js
+++ b/index.js
@@ -8,7 +8,7 @@ import versionMap from './versionMap.cjs'
 const CREATE_ALIAS_SETTING_PLACEHOLDER = 'CREATE_ALIAS_SETTING_PLACEHOLDER'
 export { CREATE_ALIAS_SETTING_PLACEHOLDER }
 
-function stringifyJS (value, styleGuide) {
+function stringifyJS (value, styleGuide, configFormat) {
   // eslint-disable-next-line no-shadow
   const result = stringify(value, (val, indent, stringify, key) => {
     if (key === 'CREATE_ALIAS_SETTING_PLACEHOLDER') {
@@ -18,6 +18,10 @@ function stringifyJS (value, styleGuide) {
     return stringify(val)
   }, 2)
 
+  if (configFormat === 'flat') {
+    return result.replace('CREATE_ALIAS_SETTING_PLACEHOLDER: ', '...createAliasSetting')
+  }
+
   return result.replace(
     'CREATE_ALIAS_SETTING_PLACEHOLDER: ',
     `...require('@vue/eslint-config-${styleGuide}/createAliasSetting')`
@@ -72,17 +76,15 @@ export default function createConfig ({
   addDependency('eslint')
   addDependency('eslint-plugin-vue')
 
-  if (configFormat === 'flat') {
-    addDependency('@eslint/eslintrc')
-    addDependency('@eslint/js')
-  } else if (styleGuide !== 'default' || hasTypeScript || needsPrettier) {
-    addDependency('@rushstack/eslint-patch')
+  if (
+    configFormat === "eslintrc" &&
+    (styleGuide !== "default" || hasTypeScript || needsPrettier)
+  ) {
+    addDependency("@rushstack/eslint-patch");
   }
 
   const language = hasTypeScript ? 'typescript' : 'javascript'
 
-  const flatConfigExtends = []
-  const flatConfigImports = []
   const eslintrcConfig = {
     root: true,
     extends: [
@@ -96,6 +98,20 @@ export default function createConfig ({
     eslintrcConfig.extends.push(name)
   }
 
+  let needsFlatCompat = false
+  const flatConfigExtends = []
+  const flatConfigImports = []
+  flatConfigImports.push(`import pluginVue from 'eslint-plugin-vue'`)
+  flatConfigExtends.push(
+    vueVersion.startsWith('2')
+      ? `...pluginVue.configs['flat/vue2-essential']`
+      : `...pluginVue.configs['flat/essential']`
+  )
+
+  if (configFormat === 'flat' && styleGuide === 'default') {
+    addDependency('@eslint/js')
+  }
+
   switch (`${styleGuide}-${language}`) {
     case 'default-javascript':
       eslintrcConfig.extends.push('eslint:recommended')
@@ -107,41 +123,53 @@ export default function createConfig ({
       flatConfigImports.push(`import js from '@eslint/js'`)
       flatConfigExtends.push('js.configs.recommended')
       addDependencyAndExtend('@vue/eslint-config-typescript')
+      needsFlatCompat = true
       flatConfigExtends.push(`...compat.extends('@vue/eslint-config-typescript')`)
       break
     case 'airbnb-javascript':
     case 'standard-javascript':
       addDependencyAndExtend(`@vue/eslint-config-${styleGuide}`)
+      needsFlatCompat = true
       flatConfigExtends.push(`...compat.extends('@vue/eslint-config-${styleGuide}')`)
       break
     case 'airbnb-typescript':
     case 'standard-typescript':
       addDependencyAndExtend(`@vue/eslint-config-${styleGuide}-with-typescript`)
+      needsFlatCompat = true
       flatConfigExtends.push(`...compat.extends('@vue/eslint-config-${styleGuide}-with-typescript')`)
       break
     default:
       throw new Error(`unexpected combination of styleGuide and language: ${styleGuide}-${language}`)
   }
 
-  flatConfigImports.push(`import pluginVue from 'eslint-plugin-vue'`)
-  flatConfigExtends.push(
-    vueVersion.startsWith('2')
-      ? `...pluginVue.configs['flat/vue2-essential']`
-      : `...pluginVue.configs['flat/essential']`
-  )
-
   deepMerge(pkg.devDependencies, additionalDependencies)
   deepMerge(eslintrcConfig, additionalConfig)
 
+  if (additionalConfig?.extends) {
+    needsFlatCompat = true
+    additionalConfig.extends.forEach((pkgName) => {
+      flatConfigExtends.push(`...compat.extends('${pkgName}')`)
+    })
+  }
+
   const flatConfigEntry = {
     files: filePatterns
   }
-  deepMerge(flatConfigEntry, additionalConfig)
+  if (additionalConfig?.settings?.[CREATE_ALIAS_SETTING_PLACEHOLDER]) {
+    flatConfigImports.push(
+      `import createAliasSetting from '@vue/eslint-config-${styleGuide}/createAliasSetting'`
+    )
+    flatConfigEntry.settings = {
+      [CREATE_ALIAS_SETTING_PLACEHOLDER]: 
+        additionalConfig.settings[CREATE_ALIAS_SETTING_PLACEHOLDER]
+    }
+  }
 
   if (needsPrettier) {
     addDependency('prettier')
     addDependency('@vue/eslint-config-prettier')
     eslintrcConfig.extends.push('@vue/eslint-config-prettier/skip-formatting')
+    needsFlatCompat = true
     flatConfigExtends.push(`...compat.extends('@vue/eslint-config-prettier/skip-formatting')`)
   }
 
@@ -174,27 +202,38 @@ export default function createConfig ({
 
   // eslint.config.js | .eslintrc.cjs
   if (configFormat === 'flat') {
-    files['eslint.config.js'] += "import path from 'node:path'\n"
-    files['eslint.config.js'] += "import { fileURLToPath } from 'node:url'\n\n"
+    if (needsFlatCompat) {
+      files['eslint.config.js'] += "import path from 'node:path'\n"
+      files['eslint.config.js'] += "import { fileURLToPath } from 'node:url'\n\n"
+
+      addDependency('@eslint/eslintrc')
+      files['eslint.config.js'] += "import { FlatCompat } from '@eslint/eslintrc'\n"
+    }
 
+    // imports
     flatConfigImports.forEach((pkgImport) => {
       files['eslint.config.js'] += `${pkgImport}\n`
     })
     files['eslint.config.js'] += '\n'
 
     // neccesary for compatibility until all packages support flat config
-    files['eslint.config.js'] += 'const __filename = fileURLToPath(import.meta.url)\n'
-    files['eslint.config.js'] += 'const __dirname = path.dirname(__filename)\n'
-    files['eslint.config.js'] += 'const compat = new FlatCompat({\n'
-    files['eslint.config.js'] += '  baseDirectory: __dirname\n'
-    files['eslint.config.js'] += '})\n\n'
+    if (needsFlatCompat) {
+      files['eslint.config.js'] += 'const __filename = fileURLToPath(import.meta.url)\n'
+      files['eslint.config.js'] += 'const __dirname = path.dirname(__filename)\n'
+      files['eslint.config.js'] += 'const compat = new FlatCompat({\n'
+      files['eslint.config.js'] += '  baseDirectory: __dirname'
+      if (pkg.devDependencies['@vue/eslint-config-typescript']) {
+        files['eslint.config.js'] += ',\n  recommendedConfig: js.configs.recommended'
+      }
+      files['eslint.config.js'] += '\n})\n\n'
+    }
     
     files['eslint.config.js'] += 'export default [\n'
     flatConfigExtends.forEach((usage) => {
       files['eslint.config.js'] += `  ${usage},\n`
     })
 
-    const [, ...keep] = stringifyJS([flatConfigEntry], styleGuide).split('{')
+    const [, ...keep] = stringifyJS([flatConfigEntry], styleGuide, "flat").split('{')
     files['eslint.config.js'] += `  {${keep.join('{')}\n`
   } else {
     files['.eslintrc.cjs'] += `module.exports = ${stringifyJS(eslintrcConfig, styleGuide)}\n`

From af60961c2feb56bf9a8c68e34801a4c40ccd224f Mon Sep 17 00:00:00 2001
From: Joost Kersjes <joostkersjes@live.nl>
Date: Fri, 12 Apr 2024 19:40:39 +0200
Subject: [PATCH 3/3] feat: add file patterns to flat config

---
 index.js | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/index.js b/index.js
index c46717b..79a0066 100644
--- a/index.js
+++ b/index.js
@@ -58,8 +58,6 @@ export default function createConfig ({
   vueVersion = '3.x', // '2.x' | '3.x' (TODO: 2.7 / vue-demi)
   configFormat = 'eslintrc', // eslintrc | flat
 
-  filePatterns = [], // flat format only - e.g. '**/*.vue', '**/*.js', etc.
-
   styleGuide = 'default', // default | airbnb | standard
   hasTypeScript = false, // true | false
   needsPrettier = false, // true | false
@@ -153,7 +151,9 @@ export default function createConfig ({
   }
 
   const flatConfigEntry = {
-    files: filePatterns
+    files: language === 'javascript'
+      ? ['**/*.vue','**/*.js','**/*.jsx','**/*.cjs','**/*.mjs']
+      : ['**/*.vue','**/*.js','**/*.jsx','**/*.cjs','**/*.mjs','**/*.ts','**/*.tsx','**/*.cts','**/*.mts']
   }
   if (additionalConfig?.settings?.[CREATE_ALIAS_SETTING_PLACEHOLDER]) {
     flatConfigImports.push(