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)
// --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: {
// 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
// - 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(
[
{
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'
}
],
{
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)
render('config/typescript')
}
+ if (needsEslint) {
+ renderEslint(root, result)
+ }
+
// Render code template.
// prettier-ignore
const codeTemplate =
projectName: result.projectName || defaultProjectName,
packageManager,
needsTypeScript,
- needsTests
+ needsTests,
+ needsEslint
})
)
--- /dev/null
+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)
+}