Skip to content

Commit 5fff25a

Browse files
committed
feat: add eslint prompts
It's a work-in-progress feature, though. I haven't thought about how to test it yet.
1 parent f0351c9 commit 5fff25a

File tree

5 files changed

+165
-6
lines changed

5 files changed

+165
-6
lines changed

index.js

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import renderTemplate from './utils/renderTemplate.js'
1212
import { postOrderDirectoryTraverse, preOrderDirectoryTraverse } from './utils/directoryTraverse.js'
1313
import generateReadme from './utils/generateReadme.js'
1414
import getCommand from './utils/getCommand.js'
15+
import renderEslint from './utils/renderEslint.js'
1516

1617
function isValidPackageName(projectName) {
1718
return /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(projectName)
@@ -47,6 +48,8 @@ async function init() {
4748
// --router / --vue-router
4849
// --pinia
4950
// --with-tests / --tests / --cypress
51+
// --eslint
52+
// --eslint-with-prettier (only support prettier through eslint for simplicity)
5053
// --force (for force overwriting)
5154
const argv = minimist(process.argv.slice(2), {
5255
alias: {
@@ -61,8 +64,15 @@ async function init() {
6164
// if any of the feature flags is set, we would skip the feature prompts
6265
// use `??` instead of `||` once we drop Node.js 12 support
6366
const isFeatureFlagsUsed =
64-
typeof (argv.default || argv.ts || argv.jsx || argv.router || argv.pinia || argv.tests) ===
65-
'boolean'
67+
typeof (
68+
argv.default ||
69+
argv.ts ||
70+
argv.jsx ||
71+
argv.router ||
72+
argv.pinia ||
73+
argv.tests ||
74+
argv.eslint
75+
) === 'boolean'
6676

6777
let targetDir = argv._[0]
6878
const defaultProjectName = !targetDir ? 'vue-project' : targetDir
@@ -81,6 +91,8 @@ async function init() {
8191
// - Install Vue Router for SPA development?
8292
// - Install Pinia for state management?
8393
// - Add Cypress for testing?
94+
// - Add ESLint for code quality?
95+
// - Add Prettier for code formatting?
8496
result = await prompts(
8597
[
8698
{
@@ -155,6 +167,27 @@ async function init() {
155167
initial: false,
156168
active: 'Yes',
157169
inactive: 'No'
170+
},
171+
{
172+
name: 'needsEslint',
173+
type: () => (isFeatureFlagsUsed ? null : 'toggle'),
174+
message: 'Add ESLint for code quality?',
175+
initial: false,
176+
active: 'Yes',
177+
inactive: 'No'
178+
},
179+
{
180+
name: 'needsPrettier',
181+
type: (prev, values = {}) => {
182+
if (isFeatureFlagsUsed || !values.needsEslint) {
183+
return null
184+
}
185+
return 'toggle'
186+
},
187+
message: 'Add Prettier for code formatting?',
188+
initial: false,
189+
active: 'Yes',
190+
inactive: 'No'
158191
}
159192
],
160193
{
@@ -177,7 +210,9 @@ async function init() {
177210
needsTypeScript = argv.typescript,
178211
needsRouter = argv.router,
179212
needsPinia = argv.pinia,
180-
needsTests = argv.tests
213+
needsTests = argv.tests,
214+
needsEslint = argv.eslint || argv['eslint-with-prettier'],
215+
needsPrettier = argv['eslint-with-prettier']
181216
} = result
182217
const root = path.join(cwd, targetDir)
183218

@@ -222,6 +257,10 @@ async function init() {
222257
render('config/typescript')
223258
}
224259

260+
if (needsEslint) {
261+
renderEslint(root, result)
262+
}
263+
225264
// Render code template.
226265
// prettier-ignore
227266
const codeTemplate =
@@ -298,7 +337,8 @@ async function init() {
298337
projectName: result.projectName || defaultProjectName,
299338
packageManager,
300339
needsTypeScript,
301-
needsTests
340+
needsTests,
341+
needsEslint
302342
})
303343
)
304344

template/config/cypress/cypress/plugins/index.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
/* eslint-env node */
2-
/* eslint-disable @typescript-eslint/no-var-requires */
32
/// <reference types="cypress" />
43
// ***********************************************************
54
// This example plugins/index.js can be used to load plugins

template/eslint/package.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"devDependencies": {
3+
"@rushstack/eslint-patch": "^1.1.0",
4+
"@vue/eslint-config-prettier": "^7.0.0",
5+
"@vue/eslint-config-typescript": "^10.0.0",
6+
"eslint": "^8.5.0",
7+
"eslint-plugin-cypress": "^2.12.1",
8+
"eslint-plugin-vue": "^8.2.0",
9+
"prettier": "^2.5.1"
10+
}
11+
}

utils/generateReadme.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ export default function generateReadme({
1414
projectName,
1515
packageManager,
1616
needsTypeScript,
17-
needsTests
17+
needsTests,
18+
needsEslint
1819
}) {
1920
let readme = `# ${projectName}
2021
@@ -72,6 +73,16 @@ ${getCommand(packageManager, 'test:e2e')} # or \`${getCommand(
7273
`
7374
}
7475

76+
if (needsEslint) {
77+
npmScriptsDescriptions += `
78+
### Lint with [ESLint](https://eslint.org/)
79+
80+
\`\`\`sh
81+
${getCommand(packageManager, 'lint')}
82+
\`\`\`
83+
`
84+
}
85+
7586
readme += npmScriptsDescriptions
7687

7788
return readme

utils/renderEslint.js

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import fs from 'fs'
2+
import path from 'path'
3+
4+
import { devDependencies as allEslintDeps } from '../template/eslint/package.json'
5+
import deepMerge from './deepMerge.js'
6+
import sortDependencies from './sortDependencies.js'
7+
8+
const dependencies = {}
9+
function addEslintDependency(name) {
10+
dependencies[name] = allEslintDeps[name]
11+
}
12+
13+
addEslintDependency('eslint')
14+
addEslintDependency('eslint-plugin-vue')
15+
16+
const config = {
17+
root: true,
18+
extends: ['plugin:vue/vue3-essential'],
19+
env: {
20+
'vue/setup-compiler-macros': true
21+
}
22+
}
23+
24+
const cypressOverrides = [
25+
{
26+
files: ['**/__tests__/*.spec.{js,ts,jsx,tsx}', 'cypress/integration/**.spec.{js,ts,jsx,tsx}'],
27+
extends: ['plugin:cypress/recommended']
28+
}
29+
]
30+
31+
function configureEslint({ language, styleGuide, needsPrettier, needsCypress }) {
32+
switch (`${styleGuide}-${language}`) {
33+
case 'default-javascript':
34+
config.extends.push('eslint:recommended')
35+
break
36+
case 'default-typescript':
37+
addEslintDependency('@vue/eslint-config-typescript')
38+
config.extends.push('eslint:recommended')
39+
config.extends.push('@vue/eslint-config-typescript/recommended')
40+
break
41+
// TODO: airbnb and standard
42+
}
43+
44+
if (needsPrettier) {
45+
addEslintDependency('prettier')
46+
addEslintDependency('@vue/eslint-config-prettier')
47+
config.extends.push('@vue/eslint-config-prettier')
48+
}
49+
50+
if (needsCypress) {
51+
addEslintDependency('eslint-plugin-cypress')
52+
config.overrides = cypressOverrides
53+
}
54+
55+
// generate the configuration file
56+
let configuration = '/* eslint-env node */\n'
57+
if (styleGuide !== 'default' || language !== 'typescript' || needsPrettier) {
58+
addEslintDependency('@rushstack/eslint-patch')
59+
configuration += `require("@rushstack/eslint-patch/modern-module-resolution");\n\n`
60+
}
61+
configuration += `module.exports = ${JSON.stringify(config, undefined, 2)}\n`
62+
63+
return {
64+
dependencies,
65+
configuration
66+
}
67+
}
68+
69+
export default function renderEslint(rootDir, { needsTypeScript, needsTests, needsPrettier }) {
70+
const { dependencies, configuration } = configureEslint({
71+
language: needsTypeScript ? 'typescript' : 'javascript',
72+
// we currently don't support other style guides
73+
styleGuide: 'default',
74+
needsPrettier,
75+
// we curently only support Cypress, will add Vitest support later
76+
needsCypress: needsTests
77+
})
78+
79+
// update package.json
80+
const packageJsonPath = path.resolve(rootDir, 'package.json')
81+
const existingPkg = JSON.parse(fs.readFileSync(packageJsonPath))
82+
const pkg = sortDependencies(
83+
deepMerge(existingPkg, {
84+
scripts: {
85+
// Note that we reuse .gitignore here to avoid duplicating the configuration
86+
lint: needsTypeScript
87+
? 'eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore'
88+
: 'eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore'
89+
},
90+
devDependencies: dependencies
91+
})
92+
)
93+
fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2) + '\n')
94+
95+
// write to .eslintrc.cjs
96+
const eslintrcPath = path.resolve(rootDir, '.eslintrc.cjs')
97+
fs.writeFileSync(eslintrcPath, configuration)
98+
}

0 commit comments

Comments
 (0)