]> git.ipfire.org Git - thirdparty/vuejs/create-vue.git/commitdiff
feat: support ESLint 9 Flat Config (#573)
authorHaoqun Jiang <haoqunjiang@gmail.com>
Wed, 9 Oct 2024 06:34:30 +0000 (14:34 +0800)
committerGitHub <noreply@github.com>
Wed, 9 Oct 2024 06:34:30 +0000 (14:34 +0800)
LICENSE
__test__/renderEslint.spec.ts [deleted file]
package.json
pnpm-lock.yaml
scripts/build.mjs
template/config/typescript/package.json
utils/renderEslint.ts

diff --git a/LICENSE b/LICENSE
index 947d78c0fc7f3315e6af9dbf3cd0d8ac84f2f05e..609a8694b511439fbbc29f2a5abeb82eee36d82a 100644 (file)
--- a/LICENSE
+++ b/LICENSE
@@ -270,35 +270,6 @@ Repository: git://github.com/mde/ejs.git
 >    limitations under the License.
 >
 
-## javascript-stringify
-
-License: MIT
-By: Blake Embrey
-Repository: git+https://github.com/blakeembrey/javascript-stringify.git
-
-> The MIT License (MIT)
->
-> Copyright (c) 2013 Blake Embrey (hello@blakeembrey.com)
->
-> Permission is hereby granted, free of charge, to any person obtaining a copy
-> of this software and associated documentation files (the "Software"), to deal
-> in the Software without restriction, including without limitation the rights
-> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-> copies of the Software, and to permit persons to whom the Software is
-> furnished to do so, subject to the following conditions:
->
-> The above copyright notice and this permission notice shall be included in
-> all copies or substantial portions of the Software.
->
-> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-> THE SOFTWARE.
->
-
 ## kleur
 
 License: MIT
diff --git a/__test__/renderEslint.spec.ts b/__test__/renderEslint.spec.ts
deleted file mode 100644 (file)
index 2a15066..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-import { it, describe, expect } from 'vitest'
-import { getAdditionalConfigAndDependencies } from '../utils/renderEslint'
-
-describe('renderEslint', () => {
-  it('should get additional dependencies and config with no test flags', () => {
-    const { additionalConfig, additionalDependencies } = getAdditionalConfigAndDependencies({
-      needsVitest: false,
-      needsCypress: false,
-      needsCypressCT: false,
-      needsPlaywright: false
-    })
-    expect(additionalConfig).toStrictEqual({})
-    expect(additionalDependencies).toStrictEqual({})
-  })
-
-  it('should get additional dependencies and config with for vitest', () => {
-    const { additionalConfig, additionalDependencies } = getAdditionalConfigAndDependencies({
-      needsVitest: true,
-      needsCypress: false,
-      needsCypressCT: false,
-      needsPlaywright: false
-    })
-    expect(additionalConfig.overrides[0].files).toStrictEqual([
-      'src/**/*.{test,spec}.{js,ts,jsx,tsx}'
-    ])
-    expect(additionalConfig.overrides[0].extends).toStrictEqual([
-      'plugin:@vitest/legacy-recommended'
-    ])
-    expect(additionalDependencies['@vitest/eslint-plugin']).not.toBeUndefined()
-  })
-
-  it('should get additional dependencies and config with for cypress', () => {
-    const { additionalConfig, additionalDependencies } = getAdditionalConfigAndDependencies({
-      needsVitest: false,
-      needsCypress: true,
-      needsCypressCT: false,
-      needsPlaywright: false
-    })
-    expect(additionalConfig.overrides[0].files).toStrictEqual([
-      'cypress/e2e/**/*.{cy,spec}.{js,ts,jsx,tsx}',
-      'cypress/support/**/*.{js,ts,jsx,tsx}'
-    ])
-    expect(additionalConfig.overrides[0].extends).toStrictEqual(['plugin:cypress/recommended'])
-    expect(additionalDependencies['eslint-plugin-cypress']).not.toBeUndefined()
-  })
-
-  it('should get additional dependencies and config with for cypress with component testing', () => {
-    const { additionalConfig, additionalDependencies } = getAdditionalConfigAndDependencies({
-      needsVitest: false,
-      needsCypress: true,
-      needsCypressCT: true,
-      needsPlaywright: false
-    })
-    expect(additionalConfig.overrides[0].files).toStrictEqual([
-      '**/__tests__/*.{cy,spec}.{js,ts,jsx,tsx}',
-      'cypress/e2e/**/*.{cy,spec}.{js,ts,jsx,tsx}',
-      'cypress/support/**/*.{js,ts,jsx,tsx}'
-    ])
-    expect(additionalConfig.overrides[0].extends).toStrictEqual(['plugin:cypress/recommended'])
-    expect(additionalDependencies['eslint-plugin-cypress']).not.toBeUndefined()
-  })
-
-  it('should get additional dependencies and config with for playwright', () => {
-    const { additionalConfig, additionalDependencies } = getAdditionalConfigAndDependencies({
-      needsVitest: false,
-      needsCypress: false,
-      needsCypressCT: false,
-      needsPlaywright: true
-    })
-    expect(additionalConfig.overrides[0].files).toStrictEqual([
-      'e2e/**/*.{test,spec}.{js,ts,jsx,tsx}'
-    ])
-    expect(additionalConfig.overrides[0].extends).toStrictEqual(['plugin:playwright/recommended'])
-    expect(additionalDependencies['eslint-plugin-playwright']).not.toBeUndefined()
-  })
-})
index ce82290e4a6968e5a88cf96e79522ea72e140257..05d000db8c25508d1ddbc194c2383c7b6bf1a335 100644 (file)
@@ -41,7 +41,7 @@
     "@types/eslint": "^9.6.1",
     "@types/node": "^20.16.10",
     "@types/prompts": "^2.4.9",
-    "@vue/create-eslint-config": "^0.3.3",
+    "@vue/create-eslint-config": "0.4.1",
     "@vue/tsconfig": "^0.5.1",
     "ejs": "^3.1.10",
     "esbuild": "^0.18.20",
index a4adc2e372abb334f35cca8ff858ed7a250a25b4..b3e6e06c2f13c45d5316f76ccf73238185586813 100644 (file)
@@ -21,8 +21,8 @@ importers:
         specifier: ^2.4.9
         version: 2.4.9
       '@vue/create-eslint-config':
-        specifier: ^0.3.3
-        version: 0.3.3
+        specifier: 0.4.1
+        version: 0.4.1
       '@vue/tsconfig':
         specifier: ^0.5.1
         version: 0.5.1
@@ -175,11 +175,11 @@ importers:
         specifier: ^6.2.3
         version: 6.2.3
       typescript:
-        specifier: ~5.6.0
-        version: 5.6.2
+        specifier: ~5.5.4
+        version: 5.5.4
       vue-tsc:
         specifier: ^2.1.6
-        version: 2.1.6(typescript@5.6.2)
+        version: 2.1.6(typescript@5.5.4)
 
   template/config/vitest:
     dependencies:
@@ -1072,8 +1072,8 @@ packages:
   '@vue/compiler-vue2@2.7.16':
     resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==}
 
-  '@vue/create-eslint-config@0.3.3':
-    resolution: {integrity: sha512-eqy1kH6/0++oiRM5EkYrVaGjArrAJAhztdtKLB9FuKlid25jwUQ6nVMyuFXhxxnxl/ypxnGndMUGpVtYfNUX6w==}
+  '@vue/create-eslint-config@0.4.1':
+    resolution: {integrity: sha512-9AQ37YCSTSqP6vRv9rp9jri9Iv34UJP0WLD+FsVNYjDOxT37533zgOGZk4w3sw0KIAX8gxcgYx+5QFDuNQUhoA==}
     engines: {node: ^16.14.0 || >= 18.0.0}
     hasBin: true
 
@@ -2467,9 +2467,6 @@ packages:
     engines: {node: '>=10'}
     hasBin: true
 
-  javascript-stringify@2.1.0:
-    resolution: {integrity: sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==}
-
   joi@17.13.3:
     resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==}
 
@@ -3441,6 +3438,11 @@ packages:
     resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==}
     engines: {node: '>=8'}
 
+  typescript@5.5.4:
+    resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==}
+    engines: {node: '>=14.17'}
+    hasBin: true
+
   typescript@5.6.2:
     resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==}
     engines: {node: '>=14.17'}
@@ -4682,10 +4684,10 @@ snapshots:
       de-indent: 1.0.2
       he: 1.2.0
 
-  '@vue/create-eslint-config@0.3.3':
+  '@vue/create-eslint-config@0.4.1':
     dependencies:
+      ejs: 3.1.10
       enquirer: 2.4.1
-      javascript-stringify: 2.1.0
       kolorist: 1.8.0
 
   '@vue/devtools-api@6.6.4': {}
@@ -4716,7 +4718,7 @@ snapshots:
     dependencies:
       rfdc: 1.4.1
 
-  '@vue/language-core@2.1.6(typescript@5.6.2)':
+  '@vue/language-core@2.1.6(typescript@5.5.4)':
     dependencies:
       '@volar/language-core': 2.4.2
       '@vue/compiler-dom': 3.5.8
@@ -4727,7 +4729,7 @@ snapshots:
       muggle-string: 0.4.1
       path-browserify: 1.0.1
     optionalDependencies:
-      typescript: 5.6.2
+      typescript: 5.5.4
 
   '@vue/reactivity@3.5.10':
     dependencies:
@@ -6114,8 +6116,6 @@ snapshots:
       filelist: 1.0.4
       minimatch: 3.1.2
 
-  javascript-stringify@2.1.0: {}
-
   joi@17.13.3:
     dependencies:
       '@hapi/hoek': 9.3.0
@@ -7212,6 +7212,8 @@ snapshots:
 
   type-fest@0.7.1: {}
 
+  typescript@5.5.4: {}
+
   typescript@5.6.2: {}
 
   undici-types@6.19.8: {}
@@ -7397,12 +7399,12 @@ snapshots:
       '@vue/devtools-api': 6.6.4
       vue: 3.5.10(typescript@5.6.2)
 
-  vue-tsc@2.1.6(typescript@5.6.2):
+  vue-tsc@2.1.6(typescript@5.5.4):
     dependencies:
       '@volar/typescript': 2.4.2
-      '@vue/language-core': 2.1.6(typescript@5.6.2)
+      '@vue/language-core': 2.1.6(typescript@5.5.4)
       semver: 7.5.4
-      typescript: 5.6.2
+      typescript: 5.5.4
 
   vue@3.5.10(typescript@5.6.2):
     dependencies:
index f1d780beee1c36d9adabe34db609b5a9b043875b..373acb5eb8b1dfeb145002d02dc529d08cbfe8a3 100644 (file)
@@ -1,3 +1,5 @@
+import fs from 'node:fs'
+import path from 'node:path'
 import * as esbuild from 'esbuild'
 import esbuildPluginLicense from 'esbuild-plugin-license'
 
@@ -48,6 +50,48 @@ await esbuild.build({
         })
       }
     },
+
+    {
+      name: '@vue/create-eslint-config fix',
+      setup(build) {
+        // Update esbuild to support the import attributes syntax in this PR is too risky.
+        // TODO: update esbuild and remove the hack.
+        build.onLoad({ filter: /@vue.create-eslint-config.index.js$/ }, (args) => {
+          const text = fs.readFileSync(args.path, 'utf8')
+          return {
+            contents: text.replace(`with { type: 'json' }`, ''),
+            loader: 'js'
+          }
+        })
+
+        // The renderEjsFile.js module uses file system APIs therefore after bundling it will not work.
+        // So we need to preprocess it to remove the file system APIs.
+        build.onLoad({ filter: /@vue.create-eslint-config.renderEjsFile\.js$/ }, (args) => {
+          const pkgDir = path.dirname(args.path)
+          const templatesDir = path.resolve(pkgDir, './templates')
+
+          const allTemplateFileNames = fs.readdirSync(templatesDir)
+          const templateFiles = Object.fromEntries(
+            allTemplateFileNames.map((fileName) => {
+              const content = fs.readFileSync(path.resolve(templatesDir, fileName), 'utf8')
+              return [`./templates/${fileName}`, content]
+            })
+          )
+
+          return {
+            contents: `
+              import ejs from 'ejs'
+              const templates = ${JSON.stringify(templateFiles)}
+              export default function renderEjsFile(filePath, data) {
+                return ejs.render(templates[filePath], data, {})
+              }
+            `,
+            loader: 'js'
+          }
+        })
+      }
+    },
+
     esbuildPluginLicense({
       thirdParty: {
         includePrivate: false,
index eb9f87a26af8e0f3965ac0d5efd74ea6982ddc74..e0b2d8c22765f9d435b79a30bca26b8ca183ecfa 100644 (file)
@@ -7,7 +7,7 @@
   "devDependencies": {
     "@types/node": "^20.16.10",
     "npm-run-all2": "^6.2.3",
-    "typescript": "~5.6.0",
+    "typescript": "~5.5.4",
     "vue-tsc": "^2.1.6"
   }
 }
index fb061814e7769a3fcfc3af1776e46386e8277f86..3a7dc801e16d6948f93a6b593790ec811506c95a 100644 (file)
@@ -1,8 +1,6 @@
 import * as fs from 'node:fs'
 import * as path from 'node:path'
 
-import type { Linter } from 'eslint'
-
 import createESLintConfig from '@vue/create-eslint-config'
 
 import sortDependencies from './sortDependencies'
@@ -15,7 +13,7 @@ export default function renderEslint(
   rootDir,
   { needsTypeScript, needsVitest, needsCypress, needsCypressCT, needsPrettier, needsPlaywright }
 ) {
-  const { additionalConfig, additionalDependencies } = getAdditionalConfigAndDependencies({
+  const additionalConfigs = getAdditionalConfigs({
     needsVitest,
     needsCypress,
     needsCypressCT,
@@ -23,21 +21,15 @@ export default function renderEslint(
   })
 
   const { pkg, files } = createESLintConfig({
-    vueVersion: '3.x',
-    // we currently don't support other style guides
     styleGuide: 'default',
     hasTypeScript: needsTypeScript,
     needsPrettier,
 
-    additionalConfig,
-    additionalDependencies
+    additionalConfigs
   })
 
   const scripts: Record<string, string> = {
-    // Note that we reuse .gitignore here to avoid duplicating the configuration
-    lint: needsTypeScript
-      ? 'eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore'
-      : 'eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore'
+    lint: 'eslint . --fix'
   }
 
   // Theoretically, we could add Prettier without requring ESLint.
@@ -54,62 +46,90 @@ export default function renderEslint(
   const packageJsonPath = path.resolve(rootDir, 'package.json')
   const existingPkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
   const updatedPkg = sortDependencies(deepMerge(deepMerge(existingPkg, pkg), { scripts }))
-  fs.writeFileSync(packageJsonPath, JSON.stringify(updatedPkg, null, 2) + '\n', 'utf-8')
+  fs.writeFileSync(packageJsonPath, JSON.stringify(updatedPkg, null, 2) + '\n', 'utf8')
 
-  // write to .eslintrc.cjs, .prettierrc.json, etc.
+  // write to eslint.config.mjs, .prettierrc.json, .editorconfig, etc.
   for (const [fileName, content] of Object.entries(files)) {
     const fullPath = path.resolve(rootDir, fileName)
-    fs.writeFileSync(fullPath, content as string, 'utf-8')
+    fs.writeFileSync(fullPath, content as string, 'utf8')
   }
 }
 
+type ConfigItemInESLintTemplate = {
+  importer: string
+  content: string
+}
+type AdditionalConfig = {
+  devDependencies: Record<string, string>
+  beforeVuePlugin?: Array<ConfigItemInESLintTemplate>
+  afterVuePlugin?: Array<ConfigItemInESLintTemplate>
+}
+type AdditionalConfigArray = Array<AdditionalConfig>
+
 // visible for testing
-export function getAdditionalConfigAndDependencies({
+export function getAdditionalConfigs({
   needsVitest,
   needsCypress,
   needsCypressCT,
   needsPlaywright
 }) {
-  const additionalConfig: Linter.Config = {}
-  const additionalDependencies = {}
+  const additionalConfigs: AdditionalConfigArray = []
 
   if (needsVitest) {
-    additionalConfig.overrides = [
-      {
-        files: ['src/**/*.{test,spec}.{js,ts,jsx,tsx}'],
-        extends: ['plugin:@vitest/legacy-recommended']
-      }
-    ]
-
-    additionalDependencies['@vitest/eslint-plugin'] = eslintDeps['@vitest/eslint-plugin']
+    additionalConfigs.push({
+      devDependencies: { '@vitest/eslint-plugin': eslintDeps['@vitest/eslint-plugin'] },
+      afterVuePlugin: [
+        {
+          importer: `import pluginVitest from '@vitest/eslint-plugin'`,
+          content: `
+  {
+    ...pluginVitest.configs['recommended'],
+    files: ['src/**/__tests__/*'],
+  },`
+        }
+      ]
+    })
   }
 
   if (needsCypress) {
-    additionalConfig.overrides = [
-      {
-        files: needsCypressCT
-          ? [
-              '**/__tests__/*.{cy,spec}.{js,ts,jsx,tsx}',
-              'cypress/e2e/**/*.{cy,spec}.{js,ts,jsx,tsx}',
-              'cypress/support/**/*.{js,ts,jsx,tsx}'
-            ]
-          : ['cypress/e2e/**/*.{cy,spec}.{js,ts,jsx,tsx}', 'cypress/support/**/*.{js,ts,jsx,tsx}'],
-        extends: ['plugin:cypress/recommended']
-      }
-    ]
-
-    additionalDependencies['eslint-plugin-cypress'] = eslintDeps['eslint-plugin-cypress']
+    additionalConfigs.push({
+      devDependencies: { 'eslint-plugin-cypress': eslintDeps['eslint-plugin-cypress'] },
+      afterVuePlugin: [
+        {
+          importer: "import pluginCypress from 'eslint-plugin-cypress/flat'",
+          content: `
+  {
+    ...pluginCypress.configs.recommended,
+    files: [
+      ${[
+        ...(needsCypressCT ? ["'**/__tests__/*.{cy,spec}.{js,ts,jsx,tsx}',"] : []),
+        'cypress/e2e/**/*.{cy,spec}.{js,ts,jsx,tsx}',
+        'cypress/support/**/*.{js,ts,jsx,tsx}'
+      ]
+        .map(JSON.stringify.bind(JSON))
+        .join(',\n      ')}
+    ],
+  },`
+        }
+      ]
+    })
   }
 
   if (needsPlaywright) {
-    additionalConfig.overrides = [
-      {
-        files: ['e2e/**/*.{test,spec}.{js,ts,jsx,tsx}'],
-        extends: ['plugin:playwright/recommended']
-      }
-    ]
-
-    additionalDependencies['eslint-plugin-playwright'] = eslintDeps['eslint-plugin-playwright']
+    additionalConfigs.push({
+      devDependencies: { 'eslint-plugin-playwright': eslintDeps['eslint-plugin-playwright'] },
+      afterVuePlugin: [
+        {
+          importer: "import pluginPlaywright from 'eslint-plugin-playwright'",
+          content: `
+  {
+    ...pluginPlaywright.configs['flat/recommended'],
+    files: ['e2e/**/*.{test,spec}.{js,ts,jsx,tsx}'],
+  },`
+        }
+      ]
+    })
   }
-  return { additionalConfig, additionalDependencies }
+
+  return additionalConfigs
 }