From: Haoqun Jiang Date: Sat, 25 Dec 2021 14:11:53 +0000 (+0800) Subject: feat: add eslint prompts X-Git-Tag: v3.0.4~7 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5fff25a873699942c70361761d01e8e3085564dc;p=thirdparty%2Fvuejs%2Fcreate-vue.git feat: add eslint prompts It's a work-in-progress feature, though. I haven't thought about how to test it yet. --- diff --git a/index.js b/index.js index 4aeebbe5..e582f7f3 100755 --- 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 }) ) diff --git a/template/config/cypress/cypress/plugins/index.js b/template/config/cypress/cypress/plugins/index.js index 932b68e3..db04a79b 100644 --- a/template/config/cypress/cypress/plugins/index.js +++ b/template/config/cypress/cypress/plugins/index.js @@ -1,5 +1,4 @@ /* eslint-env node */ -/* eslint-disable @typescript-eslint/no-var-requires */ /// // *********************************************************** // 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 index 00000000..613c57e3 --- /dev/null +++ b/template/eslint/package.json @@ -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" + } +} diff --git a/utils/generateReadme.js b/utils/generateReadme.js index 03b32c1c..a4574244 100644 --- a/utils/generateReadme.js +++ b/utils/generateReadme.js @@ -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 index 00000000..2da4f7ca --- /dev/null +++ b/utils/renderEslint.js @@ -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) +}