UNPKG

liangjing-lint-start

Version:

增强版 React/TypeScript 项目 lint 和 prettier 配置工具 - 支持智能检测、多框架、配置预设

791 lines (691 loc) 22.1 kB
#!/usr/bin/env node const fs = require("fs-extra"); const path = require("path"); const child_process = require("child_process"); // 检查并安装 inquirer let inquirer; (function ensureInquirer() { function getNodeMajorVersion() { const version = process.version; const match = version.match(/v(\d+)/); return match ? parseInt(match[1], 10) : 14; } const nodeMajor = getNodeMajorVersion(); let inquirerVersion = nodeMajor < 14 ? "8" : "9"; try { inquirer = require("inquirer"); } catch (e) { console.log(`\n📦 正在安装兼容的 inquirer 版本: inquirer@${inquirerVersion} ...`); try { if (require("fs").existsSync("yarn.lock")) { require("child_process").execSync( `yarn add inquirer@${inquirerVersion} --dev --ignore-scripts --ignore-engines --no-lockfile --silent`, { stdio: "inherit" } ); } else { require("child_process").execSync( `npm install inquirer@${inquirerVersion} --no-save --ignore-scripts --legacy-peer-deps --silent`, { stdio: "inherit" } ); } inquirer = require("inquirer"); } catch (e2) { console.error("❌ inquirer 安装失败,请手动安装: npm install inquirer"); process.exit(1); } } })(); // 导入配置预设 let presets; try { presets = require("./config/presets"); } catch (e) { // 如果配置文件不存在,使用内置预设 presets = { standard: { eslint: { extends: [ "eslint:recommended", "@typescript-eslint/recommended", "plugin:react/recommended", "plugin:react-hooks/recommended", "prettier", ], rules: { "@typescript-eslint/no-explicit-any": "warn", "@typescript-eslint/no-unused-vars": [ "error", { argsIgnorePattern: "^_" }, ], "react/prop-types": "off", "no-console": "warn", }, }, prettier: { printWidth: 80, tabWidth: 2, semi: true, singleQuote: true, trailingComma: "es5", }, }, strict: { eslint: {}, prettier: {} }, relaxed: { eslint: {}, prettier: {} }, team: { eslint: {}, prettier: {} }, frameworks: {}, }; } // 配置常量 const CONFIG = { SUPPORTED_NODE_VERSION: 16, REGISTRY_URL: "https://registry.npmjs.org/", DEPENDENCIES_TO_REMOVE: [ "eslint", "prettier", "eslint-config-prettier", "eslint-plugin-prettier", "eslint-plugin-react", "eslint-plugin-react-hooks", "@typescript-eslint/parser", "@typescript-eslint/eslint-plugin", ], TEMPLATES: { typescript: "enhanced-ts-eslint.js", javascript: "eslint.js", prettier: "enhanced-prettier.js", tslint: "tslint.json", }, }; // 工具函数类 class Utils { static getNodeMajorVersion() { const version = process.version; const match = version.match(/v(\d+)/); return match ? parseInt(match[1], 10) : 16; } static async getUserInput(prompt, defaultValue = "") { if (!inquirer) inquirer = require("inquirer"); const res = await inquirer.prompt([ { type: "input", name: "result", message: prompt, default: defaultValue, }, ]); return res.result; } static async getChoice(prompt, choices, defaultChoice = 0) { if (!inquirer) inquirer = require("inquirer"); const res = await inquirer.prompt([ { type: "list", name: "result", message: prompt, choices: choices.map((c) => ({ name: `${c.name} - ${c.description}`, value: c, })), default: defaultChoice, }, ]); return res.result; } static detectProjectType() { const cwd = process.cwd(); const packageJsonPath = path.join(cwd, "package.json"); let detectedFramework = "react"; let detectedLanguage = "javascript"; if (fs.existsSync(packageJsonPath)) { try { const packageJson = fs.readJsonSync(packageJsonPath); const deps = { ...packageJson.dependencies, ...packageJson.devDependencies, }; // 检测框架 if (deps["next"] || deps["@next/core"]) { detectedFramework = "nextjs"; } else if (deps["vue"] || deps["@vue/core"]) { detectedFramework = "vue"; } else if (deps["vite"]) { detectedFramework = "vite"; } // 检测语言 if ( deps.typescript || deps["@types/node"] || fs.existsSync(path.join(cwd, "tsconfig.json")) ) { detectedLanguage = "typescript"; } } catch (e) { console.warn("⚠️ 无法读取 package.json,将使用默认配置"); } } // 检测 TypeScript 文件 if (detectedLanguage === "javascript") { const srcDirs = ["src", "lib", "app", "pages"]; for (const dir of srcDirs) { const dirPath = path.join(cwd, dir); if (fs.existsSync(dirPath)) { const files = fs.readdirSync(dirPath); if ( files.some((file) => file.endsWith(".ts") || file.endsWith(".tsx")) ) { detectedLanguage = "typescript"; break; } } } } return { framework: detectedFramework, language: detectedLanguage }; } static async execCommand(command, options = {}) { return new Promise((resolve, reject) => { child_process.exec( command, { stdio: "inherit", ...options }, (error, stdout, stderr) => { if (error) { reject({ error, stdout, stderr }); } else { resolve({ stdout, stderr }); } } ); }); } } // 进度管理器 class ProgressManager { constructor() { this.steps = []; this.currentStep = 0; } addSteps(steps) { this.steps = steps; } nextStep(message) { this.currentStep++; const progress = Math.round((this.currentStep / this.steps.length) * 100); const progressBar = this.createProgressBar(progress); console.log( `\n[${this.currentStep}/${this.steps.length}] ${progressBar} ${message}` ); } createProgressBar(progress) { const width = 20; const filled = Math.round((progress / 100) * width); const empty = width - filled; return `[${"█".repeat(filled)}${"░".repeat(empty)}] ${progress}%`; } complete() { console.log("\n🎉 配置完成!所有步骤已成功执行。"); } } // 增强的包管理器 class PackageManager { constructor() { this.manager = this.detectPackageManager(); } detectPackageManager() { const cwd = process.cwd(); if (fs.existsSync(path.join(cwd, "yarn.lock"))) { return "yarn"; } if (fs.existsSync(path.join(cwd, "pnpm-lock.yaml"))) { return "pnpm"; } return "npm"; } async setRegistry() { try { const command = this.getRegistryCommand(); await Utils.execCommand(command); console.log(`✅ 已设置 ${this.manager} registry 为官方源`); } catch (e) { console.warn("⚠️ 设置 registry 失败,如遇网络问题请手动切换"); } } getRegistryCommand() { switch (this.manager) { case "yarn": return `yarn config set registry ${CONFIG.REGISTRY_URL}`; case "pnpm": return `pnpm config set registry ${CONFIG.REGISTRY_URL}`; default: return `npm config set registry ${CONFIG.REGISTRY_URL}`; } } async installDependencies(deps, isDev = true) { const devFlag = isDev ? this.getDevFlag() : ""; const command = `${this.manager} ${this.getInstallCommand()} ${deps.join( " " )} ${devFlag}`; console.log(`📦 正在安装依赖: ${deps.join(", ")}`); try { await Utils.execCommand(command); return true; } catch (e) { console.error(`❌ 安装依赖失败: ${(e.error && e.error.message) || e}`); return false; } } getInstallCommand() { return this.manager === "npm" ? "install" : "add"; } getDevFlag() { switch (this.manager) { case "yarn": case "pnpm": return "--dev"; default: return "--save-dev"; } } async removeDependencies(deps) { const command = `${this.manager} remove ${deps.join(" ")}`; try { await Utils.execCommand(command); console.log("✅ 旧依赖已移除"); } catch (e) { console.warn("⚠️ 某些依赖移除失败,如未安装可忽略"); } } async addScripts() { const packageJsonPath = path.join(process.cwd(), "package.json"); if (fs.existsSync(packageJsonPath)) { try { const packageJson = fs.readJsonSync(packageJsonPath); if (!packageJson.scripts) { packageJson.scripts = {}; } // 添加 lint 和 format 脚本 packageJson.scripts.lint = "eslint . --ext .js,.jsx,.ts,.tsx"; packageJson.scripts["lint:fix"] = "eslint . --ext .js,.jsx,.ts,.tsx --fix"; packageJson.scripts.format = 'prettier --write "**/*.{js,jsx,ts,tsx,json,css,md}"'; packageJson.scripts["format:check"] = 'prettier --check "**/*.{js,jsx,ts,tsx,json,css,md}"'; fs.writeJsonSync(packageJsonPath, packageJson, { spaces: 2 }); console.log("✅ 已添加 lint 和 format 脚本到 package.json"); } catch (e) { console.warn("⚠️ 无法更新 package.json 脚本"); } } } } // 增强的配置生成器 class ConfigGenerator { constructor(templateDir) { this.templateDir = templateDir; } async generateConfigs(options) { const { projectType, preset, framework, includeTslint = false } = options; const configs = []; // 根据预设生成配置 const presetConfig = presets[preset] || presets.standard; // 生成 ESLint 配置 await this.generateEslintConfig(projectType, presetConfig, framework); configs.push(".eslintrc.js"); // 生成 TSLint 配置(如果需要) if (includeTslint && projectType === "typescript") { await this.generateTslintConfig(); configs.push("tslint.json"); } // 生成 Prettier 配置 await this.generatePrettierConfig(presetConfig); configs.push(".prettierrc.js"); // 生成忽略文件 await this.generateIgnoreFiles(); configs.push(".eslintignore", ".prettierignore"); // 生成 VSCode 配置 await this.generateVSCodeConfig(); configs.push(".vscode/settings.json"); return configs; } async generateEslintConfig(projectType, presetConfig, framework) { let config = { ...presetConfig.eslint }; // 添加框架特定配置 if (framework && presets.frameworks[framework]) { const frameworkConfig = presets.frameworks[framework]; config.extends = [ ...(config.extends || []), ...(frameworkConfig.extends || []), ]; config.rules = { ...config.rules, ...frameworkConfig.rules }; config.env = { ...config.env, ...frameworkConfig.env }; config.plugins = [ ...(config.plugins || []), ...(frameworkConfig.plugins || []), ]; } // TypeScript 特定配置 if (projectType === "typescript") { config.parser = "@typescript-eslint/parser"; config.parserOptions = { ...config.parserOptions, project: "./tsconfig.json", ecmaVersion: "latest", sourceType: "module", }; } const configContent = `module.exports = ${JSON.stringify( config, null, 2 )};`; const dest = path.join(process.cwd(), ".eslintrc.js"); fs.writeFileSync(dest, configContent); console.log("✅ 已生成 .eslintrc.js 配置文件"); } async generateTslintConfig() { const source = path.join(this.templateDir, CONFIG.TEMPLATES.tslint); const dest = path.join(process.cwd(), "tslint.json"); if (!fs.existsSync(source)) { console.warn("⚠️ TSLint 模板文件不存在,跳过生成"); return; } fs.copySync(source, dest); console.log("✅ 已生成 tslint.json 配置文件"); console.log( "⚠️ 注意:TSLint 已被官方弃用,建议使用 ESLint + @typescript-eslint" ); } async generatePrettierConfig(presetConfig) { const config = presetConfig.prettier; const configContent = `module.exports = ${JSON.stringify( config, null, 2 )};`; const dest = path.join(process.cwd(), ".prettierrc.js"); fs.writeFileSync(dest, configContent); console.log("✅ 已生成 .prettierrc.js 配置文件"); } async generateIgnoreFiles() { // .eslintignore const eslintIgnoreContent = [ "node_modules/", "dist/", "build/", "coverage/", "*.min.js", "*.bundle.js", ".next/", "out/", "public/", ].join("\n"); fs.writeFileSync( path.join(process.cwd(), ".eslintignore"), eslintIgnoreContent ); console.log("✅ 已生成 .eslintignore 文件"); // .prettierignore const prettierIgnoreContent = [ "node_modules/", "dist/", "build/", "coverage/", "*.min.js", "*.bundle.js", "package-lock.json", "yarn.lock", "pnpm-lock.yaml", ".next/", "out/", ].join("\n"); fs.writeFileSync( path.join(process.cwd(), ".prettierignore"), prettierIgnoreContent ); console.log("✅ 已生成 .prettierignore 文件"); } async generateVSCodeConfig() { const vscodeDir = path.join(process.cwd(), ".vscode"); const settingsPath = path.join(vscodeDir, "settings.json"); fs.ensureDirSync(vscodeDir); const settings = { "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.fixAll.eslint": true, }, "eslint.validate": [ "javascript", "javascriptreact", "typescript", "typescriptreact", ], "typescript.preferences.importModuleSpecifier": "relative", }; fs.writeJsonSync(settingsPath, settings, { spaces: 2 }); console.log("✅ 已生成 VSCode 配置文件"); } } // 主程序 async function main() { console.log("🚀 欢迎使用 liangjing-lint-start 增强版配置工具!"); console.log("📋 正在智能分析您的项目...\n"); // 检查 Node 版本 const nodeVersion = Utils.getNodeMajorVersion(); if (nodeVersion < CONFIG.SUPPORTED_NODE_VERSION) { console.warn( `⚠️ 建议使用 Node.js ${CONFIG.SUPPORTED_NODE_VERSION}+ 版本以获得最佳体验` ); console.warn(`当前版本: ${process.version}\n`); } try { // 检测项目配置 const detected = Utils.detectProjectType(); console.log(`🔍 智能检测结果:`); console.log(` 框架: ${detected.framework}`); console.log(` 语言: ${detected.language}`); // 选择配置预设 const presetChoices = [ { name: "standard", description: "标准模式 - 平衡代码质量和开发效率(推荐)", }, { name: "strict", description: "严格模式 - 最高代码质量要求" }, { name: "relaxed", description: "宽松模式 - 适合快速开发或遗留项目" }, { name: "team", description: "团队模式 - 适合多人协作项目" }, ]; const selectedPreset = await Utils.getChoice( "请选择配置预设:", presetChoices, 0 ); // 确认项目类型 const languageChoices = [ { name: "typescript", description: "TypeScript 项目" }, { name: "javascript", description: "JavaScript 项目" }, ]; const defaultLanguageIndex = detected.language === "typescript" ? 0 : 1; const selectedLanguage = await Utils.getChoice( "确认项目语言:", languageChoices, defaultLanguageIndex ); // 询问是否生成 TSLint 配置(仅 TypeScript 项目) let includeTslint = false; if (selectedLanguage.name === "typescript") { if (!inquirer) inquirer = require("inquirer"); const tslintChoice = await inquirer.prompt([ { type: "confirm", name: "includeTslint", message: "是否同时生成 TSLint 配置?[注意:TSLint 已被弃用,建议使用 ESLint]", default: false, }, ]); includeTslint = tslintChoice.includeTslint; } // 初始化进度管理器 const progress = new ProgressManager(); progress.addSteps([ "配置包管理器", "清理旧依赖", "安装新依赖", "生成配置文件", "添加脚本命令", "验证配置", ]); // 步骤 1: 配置包管理器 progress.nextStep("配置包管理器和源..."); const packageManager = new PackageManager(); console.log(`📦 检测到包管理器: ${packageManager.manager}`); await packageManager.setRegistry(); // 步骤 2: 清理旧依赖 progress.nextStep("清理旧的 lint 相关依赖..."); await packageManager.removeDependencies(CONFIG.DEPENDENCIES_TO_REMOVE); // 步骤 3: 安装新依赖 progress.nextStep("安装新的 lint 和 prettier 依赖..."); const dependencies = getDependenciesByType( selectedLanguage.name, detected.framework, includeTslint ); const installSuccess = await packageManager.installDependencies( dependencies ); if (!installSuccess) { throw new Error("依赖安装失败"); } // 步骤 4: 生成配置文件 progress.nextStep("生成配置文件..."); const templateDir = path.join(__dirname, "templates"); const configGenerator = new ConfigGenerator(templateDir); const generatedConfigs = await configGenerator.generateConfigs({ projectType: selectedLanguage.name, preset: selectedPreset.name, framework: detected.framework, includeTslint: includeTslint, }); // 步骤 5: 添加脚本命令 progress.nextStep("添加 package.json 脚本..."); await packageManager.addScripts(); // 步骤 6: 验证配置 progress.nextStep("验证配置文件..."); await validateConfigs(generatedConfigs); // 完成 progress.complete(); // 询问是否运行格式化 if (!inquirer) inquirer = require("inquirer"); const formatChoice = await inquirer.prompt([ { type: "confirm", name: "runFormat", message: "是否立即运行代码格式化?", default: false, }, ]); if (formatChoice.runFormat) { console.log("\n🎨 正在格式化代码..."); await runCodeFormat(packageManager); } console.log("\n🎯 配置完成!您现在可以:"); console.log(` • 运行 '${packageManager.manager} run lint' 检查代码`); console.log( ` • 运行 '${packageManager.manager} run lint:fix' 自动修复问题` ); console.log(` • 运行 '${packageManager.manager} run format' 格式化代码`); console.log(" • 在 VSCode 中享受自动格式化和错误提示"); console.log("\n💡 提示: 已为您生成 VSCode 配置,重启编辑器以生效"); // 正常完成,主动退出进程 process.exit(0); } catch (error) { console.error("\n❌ 配置过程中出现错误:"); console.error(error.message); console.log("\n🔧 建议解决方案:"); console.log(" • 检查网络连接"); console.log(" • 确保有写入权限"); console.log(" • 尝试手动清理 node_modules 后重试"); console.log(" • 检查 Node.js 版本是否符合要求"); process.exit(1); } } // 根据项目类型和框架获取依赖 function getDependenciesByType(projectType, framework, includeTslint = false) { const baseDeps = [ "eslint", "prettier", "eslint-config-prettier", "eslint-plugin-prettier", ]; // React 相关依赖 if (framework === "react" || framework === "nextjs" || framework === "vite") { baseDeps.push( "eslint-plugin-react", "eslint-plugin-react-hooks", "eslint-plugin-jsx-a11y" ); } // TypeScript 相关依赖 if (projectType === "typescript") { baseDeps.push( "@typescript-eslint/parser", "@typescript-eslint/eslint-plugin" ); // TSLint 相关依赖(如果需要) if (includeTslint) { baseDeps.push("tslint", "tslint-react", "tslint-config-prettier"); } } // 框架特定依赖 switch (framework) { case "nextjs": baseDeps.push("eslint-config-next"); break; case "vue": baseDeps.push( "eslint-plugin-vue", "@vue/eslint-config-typescript", "@vue/eslint-config-prettier" ); break; case "vite": baseDeps.push("eslint-plugin-react-refresh"); break; } // 通用增强依赖 baseDeps.push("eslint-plugin-import", "eslint-import-resolver-typescript"); return baseDeps; } // 验证配置文件 async function validateConfigs(configs) { for (const config of configs) { const filePath = path.join(process.cwd(), config); if (!fs.existsSync(filePath)) { throw new Error(`配置文件生成失败: ${config}`); } // 验证 JavaScript 配置文件语法 if (config.endsWith(".js")) { try { delete require.cache[require.resolve(filePath)]; require(filePath); console.log(`✅ ${config} 语法验证通过`); } catch (e) { throw new Error(`${config} 语法错误: ${e.message}`); } } } } // 运行代码格式化 async function runCodeFormat(packageManager) { try { const command = `${packageManager.manager} run format`; await Utils.execCommand(command); console.log("✅ 代码格式化完成"); } catch (e) { console.warn("⚠️ 代码格式化失败,请手动运行 format 脚本"); } } // 启动程序 if (require.main === module) { main().catch(console.error); } module.exports = { main, Utils, PackageManager, ConfigGenerator };