]> git.ipfire.org Git - thirdparty/vuejs/create-vue.git/commitdiff
feat: add eslint prompts
authorHaoqun Jiang <haoqunjiang@gmail.com>
Sat, 25 Dec 2021 14:11:53 +0000 (22:11 +0800)
committerHaoqun Jiang <haoqunjiang@gmail.com>
Sat, 25 Dec 2021 14:16:39 +0000 (22:16 +0800)
It's a work-in-progress feature, though.
I haven't thought about how to test it yet.

index.js
template/config/cypress/cypress/plugins/index.js
template/eslint/package.json [new file with mode: 0644]
utils/generateReadme.js
utils/renderEslint.js [new file with mode: 0644]

index 4aeebbe52d23a205e7a2d1a14dacd1fecf1a8194..e582f7f3896e9c91438aa55aaa76d06a300dc715 100755 (executable)
--- a/index.js
+++ b/index.js
@@ -12,6 +12,7 @@ import renderTemplate from './utils/renderTemplate.js'
 import { postOrderDirectoryTraverse, preOrderDirectoryTraverse } from './utils/directoryTraverse.js'
 import generateReadme from './utils/generateReadme.js'
 import getCommand from './utils/getCommand.js'
+import renderEslint from './utils/renderEslint.js'
 
 function isValidPackageName(projectName) {
   return /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(projectName)
@@ -47,6 +48,8 @@ async function init() {
   // --router / --vue-router
   // --pinia
   // --with-tests / --tests / --cypress
+  // --eslint
+  // --eslint-with-prettier (only support prettier through eslint for simplicity)
   // --force (for force overwriting)
   const argv = minimist(process.argv.slice(2), {
     alias: {
@@ -61,8 +64,15 @@ async function init() {
   // if any of the feature flags is set, we would skip the feature prompts
   // use `??` instead of `||` once we drop Node.js 12 support
   const isFeatureFlagsUsed =
-    typeof (argv.default || argv.ts || argv.jsx || argv.router || argv.pinia || argv.tests) ===
-    'boolean'
+    typeof (
+      argv.default ||
+      argv.ts ||
+      argv.jsx ||
+      argv.router ||
+      argv.pinia ||
+      argv.tests ||
+      argv.eslint
+    ) === 'boolean'
 
   let targetDir = argv._[0]
   const defaultProjectName = !targetDir ? 'vue-project' : targetDir
@@ -81,6 +91,8 @@ async function init() {
     // - Install Vue Router for SPA development?
     // - Install Pinia for state management?
     // - Add Cypress for testing?
+    // - Add ESLint for code quality?
+    // - Add Prettier for code formatting?
     result = await prompts(
       [
         {
@@ -155,6 +167,27 @@ async function init() {
           initial: false,
           active: 'Yes',
           inactive: 'No'
+        },
+        {
+          name: 'needsEslint',
+          type: () => (isFeatureFlagsUsed ? null : 'toggle'),
+          message: 'Add ESLint for code quality?',
+          initial: false,
+          active: 'Yes',
+          inactive: 'No'
+        },
+        {
+          name: 'needsPrettier',
+          type: (prev, values = {}) => {
+            if (isFeatureFlagsUsed || !values.needsEslint) {
+              return null
+            }
+            return 'toggle'
+          },
+          message: 'Add Prettier for code formatting?',
+          initial: false,
+          active: 'Yes',
+          inactive: 'No'
         }
       ],
       {
@@ -177,7 +210,9 @@ async function init() {
     needsTypeScript = argv.typescript,
     needsRouter = argv.router,
     needsPinia = argv.pinia,
-    needsTests = argv.tests
+    needsTests = argv.tests,
+    needsEslint = argv.eslint || argv['eslint-with-prettier'],
+    needsPrettier = argv['eslint-with-prettier']
   } = result
   const root = path.join(cwd, targetDir)
 
@@ -222,6 +257,10 @@ async function init() {
     render('config/typescript')
   }
 
+  if (needsEslint) {
+    renderEslint(root, result)
+  }
+
   // Render code template.
   // prettier-ignore
   const codeTemplate =
@@ -298,7 +337,8 @@ async function init() {
       projectName: result.projectName || defaultProjectName,
       packageManager,
       needsTypeScript,
-      needsTests
+      needsTests,
+      needsEslint
     })
   )
 
index 932b68e3710623aaa66a72199aabc3d9e61fe8da..db04a79b144a296fc35399b9fa3fc083e3e5c57f 100644 (file)
@@ -1,5 +1,4 @@
 /* eslint-env node */
-/* eslint-disable @typescript-eslint/no-var-requires */
 /// <reference types="cypress" />
 // ***********************************************************
 // This example plugins/index.js can be used to load plugins
diff --git a/template/eslint/package.json b/template/eslint/package.json
new file mode 100644 (file)
index 0000000..613c57e
--- /dev/null
@@ -0,0 +1,11 @@
+{
+  "devDependencies": {
+    "@rushstack/eslint-patch": "^1.1.0",
+    "@vue/eslint-config-prettier": "^7.0.0",
+    "@vue/eslint-config-typescript": "^10.0.0",
+    "eslint": "^8.5.0",
+    "eslint-plugin-cypress": "^2.12.1",
+    "eslint-plugin-vue": "^8.2.0",
+    "prettier": "^2.5.1"
+  }
+}
index 03b32c1c4b56e97f4b080fb6c3b6664de9b6c19b..a45742449ba1dcb3b535fc0c5c1fd89fe15a4c81 100644 (file)
@@ -14,7 +14,8 @@ export default function generateReadme({
   projectName,
   packageManager,
   needsTypeScript,
-  needsTests
+  needsTests,
+  needsEslint
 }) {
   let readme = `# ${projectName}
 
@@ -72,6 +73,16 @@ ${getCommand(packageManager, 'test:e2e')} # or \`${getCommand(
 `
   }
 
+  if (needsEslint) {
+    npmScriptsDescriptions += `
+### Lint with [ESLint](https://eslint.org/)
+
+\`\`\`sh
+${getCommand(packageManager, 'lint')}
+\`\`\`
+`
+  }
+
   readme += npmScriptsDescriptions
 
   return readme
diff --git a/utils/renderEslint.js b/utils/renderEslint.js
new file mode 100644 (file)
index 0000000..2da4f7c
--- /dev/null
@@ -0,0 +1,98 @@
+import fs from 'fs'
+import path from 'path'
+
+import { devDependencies as allEslintDeps } from '../template/eslint/package.json'
+import deepMerge from './deepMerge.js'
+import sortDependencies from './sortDependencies.js'
+
+const dependencies = {}
+function addEslintDependency(name) {
+  dependencies[name] = allEslintDeps[name]
+}
+
+addEslintDependency('eslint')
+addEslintDependency('eslint-plugin-vue')
+
+const config = {
+  root: true,
+  extends: ['plugin:vue/vue3-essential'],
+  env: {
+    'vue/setup-compiler-macros': true
+  }
+}
+
+const cypressOverrides = [
+  {
+    files: ['**/__tests__/*.spec.{js,ts,jsx,tsx}', 'cypress/integration/**.spec.{js,ts,jsx,tsx}'],
+    extends: ['plugin:cypress/recommended']
+  }
+]
+
+function configureEslint({ language, styleGuide, needsPrettier, needsCypress }) {
+  switch (`${styleGuide}-${language}`) {
+    case 'default-javascript':
+      config.extends.push('eslint:recommended')
+      break
+    case 'default-typescript':
+      addEslintDependency('@vue/eslint-config-typescript')
+      config.extends.push('eslint:recommended')
+      config.extends.push('@vue/eslint-config-typescript/recommended')
+      break
+    // TODO: airbnb and standard
+  }
+
+  if (needsPrettier) {
+    addEslintDependency('prettier')
+    addEslintDependency('@vue/eslint-config-prettier')
+    config.extends.push('@vue/eslint-config-prettier')
+  }
+
+  if (needsCypress) {
+    addEslintDependency('eslint-plugin-cypress')
+    config.overrides = cypressOverrides
+  }
+
+  // generate the configuration file
+  let configuration = '/* eslint-env node */\n'
+  if (styleGuide !== 'default' || language !== 'typescript' || needsPrettier) {
+    addEslintDependency('@rushstack/eslint-patch')
+    configuration += `require("@rushstack/eslint-patch/modern-module-resolution");\n\n`
+  }
+  configuration += `module.exports = ${JSON.stringify(config, undefined, 2)}\n`
+
+  return {
+    dependencies,
+    configuration
+  }
+}
+
+export default function renderEslint(rootDir, { needsTypeScript, needsTests, needsPrettier }) {
+  const { dependencies, configuration } = configureEslint({
+    language: needsTypeScript ? 'typescript' : 'javascript',
+    // we currently don't support other style guides
+    styleGuide: 'default',
+    needsPrettier,
+    // we curently only support Cypress, will add Vitest support later
+    needsCypress: needsTests
+  })
+
+  // update package.json
+  const packageJsonPath = path.resolve(rootDir, 'package.json')
+  const existingPkg = JSON.parse(fs.readFileSync(packageJsonPath))
+  const pkg = sortDependencies(
+    deepMerge(existingPkg, {
+      scripts: {
+        // 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'
+      },
+      devDependencies: dependencies
+    })
+  )
+  fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2) + '\n')
+
+  // write to .eslintrc.cjs
+  const eslintrcPath = path.resolve(rootDir, '.eslintrc.cjs')
+  fs.writeFileSync(eslintrcPath, configuration)
+}