all-lint
Version:
A library for code style, includes Prettier、ESLint、StyleLint、CommitLint
488 lines (441 loc) • 15.9 kB
JavaScript
const path = require('path')
const { execSync, exec } = require('child_process')
const chalk = require('chalk')
const {
isIncludeArray,
filterReg,
getProcessDir,
getPackageJsonPath,
getFileListInDir,
writeFileSync,
appendFileSync,
getAbsolutePath,
isExistFileInDir
} = require('./util')
const {
LANGUAGE_PRESETS,
COMMITLINT_PRESETS,
ESLINT_PRESETS,
STYLELINT_PRESETS,
PRETTIER_PRESETS,
ALL_LINT_DEPENDENCIES_REGEXP,
LINT_DEPENDENCIES,
VSCODE_EXTENSIONS
} = require('./constant')
/**
* 检查node环境 >= 16
*/
const checkNodeEnv = () => {
const version = execSync('node -v')
const versionNum = version.toString().split('.')[ 0 ].split('v')[ 1 ]
if (versionNum < 16) {
console.log(chalk.red('node版本过低,请升级至16以上'))
return false
}
return true
}
/**
* 检测目标环境的预制依赖是否已经安装
* @param {string[]} lintFeatures 目标环境要素 ['typescript', 'react', 'less']
* @returns boolean true: 已经安装 false: 未安装
*/
const checkLanguagePreset = (lintFeatures) => {
// 获取项目的package.json, 根据配置目标检查dependencies是否已经安装
const packagePath = getPackageJsonPath()
const { dependencies, devDependencies } = require(packagePath)
const dependenciesList = [...Object.keys(dependencies), ...Object.keys(devDependencies)]
const unInstalledCompiler = [] // 未安装的编译器
if (lintFeatures.includes('typescript') && !isIncludeArray(dependenciesList, LANGUAGE_PRESETS.typescript)) {
unInstalledCompiler.push('Typescript')
}
if (lintFeatures.includes('css') && !isIncludeArray(dependenciesList, LANGUAGE_PRESETS.css)) {
unInstalledCompiler.push('Css')
}
if (lintFeatures.includes('less') && !isIncludeArray(dependenciesList, LANGUAGE_PRESETS.less)) {
unInstalledCompiler.push('Less')
}
if (lintFeatures.includes('react') && !isIncludeArray(dependenciesList, LANGUAGE_PRESETS.react)) {
unInstalledCompiler.push('React')
}
if (unInstalledCompiler.length > 0) {
console.log(chalk.red(`\n检测到${unInstalledCompiler.join('、')}编译器未安装,请先安装`))
return false
}
return true
}
/**
* 检查lint环境是否已经安装
* return { installedLintName, installedLintConfigList }
*/
const checkLintEnv = () => {
const processDir = getProcessDir()
const fileList = getFileListInDir(processDir)
const eslintConfigs = filterReg(fileList, ESLINT_PRESETS.configRegexp)
const stylelintConfigs = filterReg(fileList, STYLELINT_PRESETS.configRegexp)
const commitlintConfigs = filterReg(fileList, COMMITLINT_PRESETS.configRegexp)
const prettierConfigs = filterReg(fileList, PRETTIER_PRESETS.configRegexp)
const installedLintName = [] // 已经安装的lint工具名
const installedLintConfigList = [] // 已经安装的lint工具配置文件列表
if (eslintConfigs.length > 0) {
installedLintName.push('ESLint')
installedLintConfigList.push(...eslintConfigs)
}
if (stylelintConfigs.length > 0) {
installedLintName.push('StyleLint')
installedLintConfigList.push(...stylelintConfigs)
}
if (commitlintConfigs.length > 0) {
installedLintName.push('CommitLint')
installedLintConfigList.push(...commitlintConfigs)
}
if (prettierConfigs.length > 0) {
installedLintName.push('Prettier')
installedLintConfigList.push(...prettierConfigs)
}
return {
installedLintName,
installedLintConfigList
}
}
/**
* 移除package.json文件中dependencies中的lint相关依赖
*/
const removeLintDependencies = () => {
const packagePath = getPackageJsonPath()
writePackageJson(packagePath, (packageJson) => {
const { dependencies = {}, devDependencies = {}, optionalDependencies = {}, scripts = {} } = packageJson
const dependenciesList = [...Object.keys(dependencies), ...Object.keys(devDependencies), ...Object.keys(optionalDependencies)]
const lintDependencies = filterReg(dependenciesList, ALL_LINT_DEPENDENCIES_REGEXP)
lintDependencies.forEach((item) => {
delete dependencies[ item ]
delete devDependencies[ item ]
delete optionalDependencies[ item ]
})
scripts[ 'prepare' ] && delete scripts[ 'prepare' ]
return packageJson
})
}
/**
* 添加dependencies到package.json文件的devDependencies中, 并排序
* @param {string[]} dependencies 依赖列表, 例如 ['eslint@^8.39.0', 'prettier@^2.8.8']
*/
const addLintDependencies = (dependencies) => {
const packagePath = getPackageJsonPath()
writePackageJson(packagePath, (packageJson) => {
const { devDependencies = {} } = packageJson
dependencies.forEach((item) => {
const [name, version] = item.split('@^')
devDependencies[ `${name}` ] = `^${version}`
})
packageJson.devDependencies = sortDependencies(devDependencies)
return packageJson
})
}
/**
* 添加lint执行脚本到package.json文件的scripts中,以及 lint-staged配置
*/
const addLintScripts = (lintFeatures) => {
const packagePath = getPackageJsonPath()
writePackageJson(packagePath, (packageJson) => {
const { scripts = {}, 'lint-staged': lintStaged = {} } = packageJson
if (lintFeatures.includes('typescript')) {
scripts[ 'lint:js' ] = 'eslint --ext .js,.jsx,.ts,.tsx ./src'
scripts[ 'lint-fix:js' ] = 'eslint --ext .js,.jsx,.ts,.tsx ./src --fix'
lintStaged[ '**/*.{ts,tsx,js,.jsx}' ] = 'eslint --ext .ts,.tsx,.js,.jsx'
} else if (lintFeatures.includes('javascript')) {
scripts[ 'lint:js' ] = 'eslint --ext .js,.jsx ./src'
scripts[ 'lint-fix:js' ] = 'eslint --ext .js,.jsx ./src --fix'
lintStaged[ '**/*.{js,.jsx}' ] = 'eslint --ext .js,.jsx'
}
if (lintFeatures.includes('less')) {
scripts[ 'lint:css' ] = 'stylelint "**/*.{css,less}"'
scripts[ 'lint-fix:css' ] = 'stylelint "**/*.{css,less}" --fix'
lintStaged[ '**/*.{css,less}' ] = 'stylelint'
} else if (lintFeatures.includes('css')) {
scripts[ 'lint:css' ] = 'stylelint "**/*.css"'
scripts[ 'lint-fix:css' ] = 'stylelint "**/*.css" --fix'
lintStaged[ '**/*.css' ] = 'stylelint'
}
if (lintFeatures.includes('prettier')) {
scripts[ 'lint:prettier' ] = 'prettier --check "**/*.{html,json,md}"'
scripts[ 'lint-fix:prettier' ] = 'prettier --write "**/*.{html,json,md}"'
lintStaged[ '**/*.{html,json,md}' ] = 'prettier --check'
}
scripts[ 'prepare' ] = 'husky install'
packageJson.scripts = scripts
packageJson[ 'lint-staged' ] = lintStaged
return packageJson
})
}
/**
* 排序dependencies
*/
const sortDependencies = (dependencies) => {
const dependenciesList = Object.keys(dependencies)
const sortedDependencies = {}
dependenciesList.sort().forEach((item) => {
sortedDependencies[ item ] = dependencies[ item ]
})
return sortedDependencies
}
/**
* 写入package.json文件
* @param {string} packagePath
* @param {function} callback
*/
const writePackageJson = (packagePath, callback) => {
const packageJson = require(packagePath)
const newPackageJson = callback(packageJson)
writeFileSync(packagePath, JSON.stringify(newPackageJson, null, 2))
}
/**
* 检查指定package是否已安装在package.json的dependencies中
* @param {string[]} packageNames 依赖列表, 例如 ['husky', 'lint-stage']
* @returns {boolean[]} 依赖是否已安装列表
*/
const checkInstalledPackage = (packageNames) => {
const packagePath = getPackageJsonPath()
const { dependencies = {}, devDependencies = {}, optionalDependencies = {} } = require(packagePath)
const dependenciesList = [...Object.keys(dependencies), ...Object.keys(devDependencies), ...Object.keys(optionalDependencies)]
return packageNames.map((item) => dependenciesList.includes(item))
}
/**
* 根据目标环境安装lint相关依赖
*/
const installLintDependencies = async (lintFeatures) => {
// 根据lint要素得到lint的所有依赖
const lintDependencies = getLintDependencies(lintFeatures)
// 检查 husky、lint-staged 是否已经安装
const [isInstalledHusky, isInstalledLintStaged] = checkInstalledPackage(['husky', 'lint-staged'])
!isInstalledHusky && lintDependencies.push('husky')
!isInstalledLintStaged && lintDependencies.push('lint-staged')
// 根据dependencies获取最新版本号
const latestLintDependencies = await getLatestLintDependencies(lintDependencies)
// 将lint依赖写入package.json
addLintDependencies(latestLintDependencies)
// 检查删除prepare脚本
const packageJsonPath = getPackageJsonPath()
const { scripts = {} } = require(packageJsonPath)
scripts.prepare && execSync('npm pkg delete script.prepare')
// 执行npm install
execSync('npm install')
setPackageJsonSettings([{ 'scripts.prepare': 'husky install' }])
execSync('npm run prepare')
}
/**
* 根据target得到lintFeatures
* lintFeatures的值为 LINT_DEPENDENCIES 里的keys
* @param {object} target chatTarget
* @returns { {lintFeature: string[], lintPluginTools: string[]} }
*/
const getLintFeatures = (target) => {
const { language, framework, styleLanguage } = target
const lintFeatures = []
const lintTools = []
if (language === 'javascript') {
lintFeatures.push('javascript')
lintTools.push('eslint')
} else if (language === 'typescript') {
lintFeatures.push('javascript', 'typescript')
lintTools.push('eslint')
}
if (framework === 'react') {
lintFeatures.push('react')
}
if (styleLanguage === 'css') {
lintFeatures.push('css')
lintTools.push('stylelint')
} else if (styleLanguage === 'less') {
lintFeatures.push('css', 'less')
lintTools.push('stylelint')
}
lintFeatures.push('prettier', 'commitlint')
lintTools.push('prettier')
return { lintFeatures, lintTools, lintPluginTools: lintTools.map(item => VSCODE_EXTENSIONS[ item ]) }
}
/**
* 根据lint要素得到lint的所有依赖
* @param {string[]} lintFeatures
* @returns
*/
const getLintDependencies = (lintFeatures) => {
const lintDependencies = []
lintFeatures.forEach((item) => {
lintDependencies.push(...LINT_DEPENDENCIES[ item ])
})
return lintDependencies
}
/**
* 根据依赖列表获取依赖的版本号
*/
const getLatestLintDependencies = async (dependencies) => {
const dependenciesList = []
const promiseList = dependencies.map((item) => new Promise((resolve) => {
exec(`npm view ${item} version`, (err, stdout) => {
if (err) {
console.error(err)
}
resolve(stdout.toString().trim())
})
}))
const latestVersions = await Promise.all(promiseList)
dependencies.forEach((item, index) => {
dependenciesList.push(`${item}@^${latestVersions[ index ]}`)
})
return dependenciesList
}
/**
* 根据chat target获取lint配置目标
* @param {string} target
* @returns {object} lint配置目标 { eslint: 'react', stylelint: 'less', commitlint: 'commitlint', prettier: 'prettier' }
*/
const getLintConfigTarget = (lintFeatures) => {
const lintTarget = {}
if (lintFeatures.includes('typescript')) {
lintTarget.eslint = lintFeatures.includes('react') ? 'react-typescript' : 'tslint'
} else if (lintFeatures.includes('javascript')) {
lintTarget.eslint = lintFeatures.includes('react') ? 'react' : 'eslint'
}
if (lintFeatures.includes('less')) {
lintTarget.stylelint = 'lesslint'
} else if (lintFeatures.includes('css')) {
lintTarget.stylelint = 'stylelint'
}
lintTarget.commitlint = 'commitlint'
lintTarget.prettier = 'prettier'
return lintTarget
}
/**
* 获取当前项目名称
* @returns {string} 项目名称
*/
const getProjectName = () => {
const currentPackageJsonPath = getAbsolutePath('../package.json')
const { name } = require(currentPackageJsonPath)
return name
}
/**
* 根据lintTarget批量生成lint配置文件到项目的根目录
* @param {string} lintFeatures
*/
const generateLintConfigs = (lintFeatures) => {
// 根据target获取lint配置目标
const lintTarget = getLintConfigTarget(lintFeatures)
// 获取当前项目名称
const projectName = getProjectName()
const processDir = getProcessDir()
Object.entries(lintTarget).forEach(([key, value]) => {
const lintConfigPath = path.join(processDir, `.${key}rc.js`)
const content =
`module.exports = {
extends: [require.resolve('${projectName}/config/${value}')],
rules: {}
}
`
writeFileSync(lintConfigPath, content)
if (key === 'commitlint') return
const lintIgnorePath = path.join(processDir, `.${key}ignore`)
const ignoreContent = `node_modules
dist
.cache
.vscode
.husky
.idea
.DS_Store`
writeFileSync(lintIgnorePath, ignoreContent)
})
}
/**
* 生产.vscode/settings.json文件
* 这里为固定内容,包含了eslint(javascript、typescript、react)、prettier等的所有配置
*/
const generateVscodeSettings = () => {
const processDir = getProcessDir()
const settingsPath = path.join(processDir, '.vscode/settings.json')
const isExistSettings = isExistFileInDir(processDir, '.vscode/settings.json')
const settingsContent = isExistSettings ? require(settingsPath) : {}
settingsContent[ 'javascript.format.enable' ] = false
settingsContent[ 'editor.formatOnSave' ] = true
settingsContent[ 'editor.codeActionsOnSave' ] = {
'source.fixAll.eslint': true
}
settingsContent[ 'eslint.validate' ] = [
'javascript',
'javascriptreact',
'typescript',
'typescriptreact'
]
settingsContent[ '[javascript]' ] = {
'editor.defaultFormatter': 'dbaeumer.vscode-eslint'
}
settingsContent[ '[javascriptreact]' ] = {
'editor.defaultFormatter': 'dbaeumer.vscode-eslint'
}
settingsContent[ '[typescript]' ] = {
'editor.defaultFormatter': 'dbaeumer.vscode-eslint'
}
settingsContent[ '[typescriptreact]' ] = {
'editor.defaultFormatter': 'dbaeumer.vscode-eslint'
}
settingsContent[ '[css]' ] = {
'editor.defaultFormatter': 'stylelint.vscode-stylelint'
}
settingsContent[ '[less]' ] = {
'editor.defaultFormatter': 'stylelint.vscode-stylelint'
}
settingsContent[ '[scss]' ] = {
'editor.defaultFormatter': 'stylelint.vscode-stylelint'
}
settingsContent[ 'editor.defaultFormatter' ] = 'esbenp.prettier-vscode'
writeFileSync(settingsPath, JSON.stringify(settingsContent, null, 2))
}
/**
* 生成husky配置文件
*/
const generateHuskyConfig = () => {
const processDir = getProcessDir()
const preCommitPath = path.join(processDir, '.husky/pre-commit')
const commitMsgPath = path.join(processDir, '.husky/commit-msg')
// 生成.husky/commit-msg文件,这里覆盖原有的commit-msg文件
const commitMsgContent = `#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx --no-install commitlint --edit "$1"`
writeFileSync(commitMsgPath, commitMsgContent)
// 生成.husky/pre-commit文件
// 如果存在.husky/pre-commit文件,则在原有的基础上添加npm run lint-staged命令
const isExistPreCommit = isExistFileInDir(processDir, '.husky/pre-commit')
if (isExistPreCommit) {
appendFileSync(preCommitPath, `npm run lint-staged`)
} else {
writeFileSync(preCommitPath, `#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged`)
}
// 修改.husky/* 文件的权限,以防止报错
execSync('chmod ug+x .husky/*')
}
/**
* 设置package.json的配置
* @param {string|string[]} settings [{key: value}, {key: value}]
*/
const setPackageJsonSettings = (settings) => {
if (Array.isArray(settings)) {
let setting = ''
settings.forEach((item) => {
setting += `${Object.keys(item)[ 0 ]}='${Object.values(item)[ 0 ]} '`
})
execSync(`npm pkg set ${setting.trim()}`)
}
}
module.exports = {
checkNodeEnv,
checkLanguagePreset,
checkLintEnv,
getLintFeatures,
removeLintDependencies,
installLintDependencies,
generateLintConfigs,
generateVscodeSettings,
addLintScripts,
generateHuskyConfig
}