UNPKG

liangjing-lint-start

Version:

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

350 lines (327 loc) 12.4 kB
#!/usr/bin/env node // Node 12 兼容版:使用 CommonJS 语法 const fs = require("fs-extra"); const path = require("path"); const child_process = require("child_process"); // 检查 Node 版本 function getNodeMajorVersion() { const version = process.version; const match = version.match(/v(\d+)/); return match ? parseInt(match[1], 10) : 14; } const nodeMajor = getNodeMajorVersion(); let inquirerVersion = "inquirer@9"; if (nodeMajor < 14) { inquirerVersion = "inquirer@8"; } // 先询问包管理器,再切换 registry,再安装 inquirer (async () => { // 1. 询问包管理器 let packageManager = "npm"; try { // 这里用最基础的 stdin/stdout 兼容 Node 12,无需 inquirer process.stdout.write("请选择包管理器(npm/yarn,默认npm):"); process.stdin.setEncoding("utf8"); const input = await new Promise((resolve) => { process.stdin.once("data", (data) => resolve(data.trim())); }); if (input && input.toLowerCase().startsWith("y")) packageManager = "yarn"; } catch (e) {} // 2. 切换 registry try { if (packageManager === "yarn") { child_process.execSync( "yarn config set registry https://registry.npmjs.org/", { stdio: "inherit" } ); } else { child_process.execSync( "npm config set registry https://registry.npmjs.org/", { stdio: "inherit" } ); } console.log("✅ 已设置 registry 为 https://registry.npmjs.org/"); } catch (e) { console.warn("⚠️ 设置 registry 失败,如遇网络问题请手动切换。"); } // 3. 清理 lock 文件 const lockFile = packageManager === "yarn" ? path.join(process.cwd(), "yarn.lock") : path.join(process.cwd(), "package-lock.json"); if (fs.existsSync(lockFile)) { fs.removeSync(lockFile); console.log("✅ 已移除 lock 文件,确保全新安装依赖。"); } // 3.5. 临时移走 package.json 和 lock 文件,确保只装 inquirer const cwd = process.cwd(); const pkgPath = path.join(cwd, "package.json"); const pkgBak = path.join(cwd, "package.json.liangjingbak"); const lockBak = packageManager === "yarn" ? path.join(cwd, "yarn.lock.liangjingbak") : path.join(cwd, "package-lock.json.liangjingbak"); let pkgMoved = false, lockMoved = false; if (fs.existsSync(pkgPath)) { fs.moveSync(pkgPath, pkgBak, { overwrite: true }); pkgMoved = true; } const lockFile2 = packageManager === "yarn" ? path.join(cwd, "yarn.lock") : path.join(cwd, "package-lock.json"); if (fs.existsSync(lockFile2)) { fs.moveSync(lockFile2, lockBak, { overwrite: true }); lockMoved = true; } // 4. 安装 inquirer(只装 inquirer,不写入 package.json,不理会 peer/engines) console.log(`\n📦 正在安装兼容的 inquirer 版本: ${inquirerVersion} ...`); try { if (packageManager === "yarn") { child_process.execSync( `yarn add inquirer@${inquirerVersion.replace( "inquirer@", "" )} --dev --ignore-scripts --ignore-engines --no-lockfile --silent`, { stdio: "inherit" } ); } else { child_process.execSync( `npm install inquirer@${inquirerVersion.replace( "inquirer@", "" )} --no-save --ignore-scripts --legacy-peer-deps --silent`, { stdio: "inherit" } ); } console.log("✅ inquirer 安装成功!"); } catch (e) { console.error("❌ inquirer 安装失败,请手动安装:", inquirerVersion); // 恢复文件 if (pkgMoved) fs.moveSync(pkgBak, pkgPath, { overwrite: true }); if (lockMoved) fs.moveSync(lockBak, lockFile, { overwrite: true }); process.exit(1); } // 安装后恢复 package.json 和 lock 文件 if (pkgMoved) fs.moveSync(pkgBak, pkgPath, { overwrite: true }); if (lockMoved) fs.moveSync(lockBak, lockFile, { overwrite: true }); // 5. require inquirer 并继续后续流程 const inquirer = require("inquirer"); const templateDir = path.join(__dirname, "templates"); // 清理 node_modules 但保留 inquirer const fsExtra = require("fs-extra"); const nodeModulesPath = path.join(process.cwd(), "node_modules"); if (fsExtra.existsSync(nodeModulesPath)) { try { console.log("\n🧹 正在清理 node_modules(保留 inquirer)..."); const inquirerPath = path.join(nodeModulesPath, "inquirer"); const inquirerExists = fsExtra.existsSync(inquirerPath); let tempInquirer = null; if (inquirerExists) { tempInquirer = path.join(process.cwd(), "__temp_inquirer"); fsExtra.moveSync(inquirerPath, tempInquirer, { overwrite: true }); } fsExtra.removeSync(nodeModulesPath); fsExtra.ensureDirSync(nodeModulesPath); if (inquirerExists) { fsExtra.moveSync(tempInquirer, inquirerPath, { overwrite: true }); } console.log("✅ 已清理 node_modules(已保留 inquirer)!"); } catch (e) { console.warn("⚠️ 清理 node_modules 失败,如有需要请手动删除。"); } } // 删除用户原有相关依赖 const removeDeps = [ "eslint", "prettier", "eslint-config-prettier", "eslint-plugin-prettier", "eslint-plugin-react", "eslint-plugin-react-hooks", "@typescript-eslint/parser", "@typescript-eslint/eslint-plugin", ]; try { console.log("\n🧹 正在移除旧的 lint 相关依赖..."); require("child_process").execSync( `${packageManager} remove ` + removeDeps.join(" "), { stdio: "inherit" } ); console.log("✅ 旧依赖已移除!"); } catch (e) { console.warn("⚠️ 某些依赖移除失败,如未安装可忽略。"); } // 询问是否是 TypeScript 项目 const { isTsProject } = await inquirer.prompt([ { type: "list", name: "isTsProject", message: "请选择项目模版(Choose your project template):", choices: ["TypeScript", "javaScript"], default: "TypeScript", }, ]); // 生成 ESLint 配置 const eslintSource = isTsProject === "TypeScript" ? path.join(templateDir, "ts-eslint.js") : path.join(templateDir, "eslint.js"); const eslintDest = path.join(process.cwd(), ".eslintrc.js"); fs.copySync(eslintSource, eslintDest); console.log(`✅ 已生成 .eslintrc.js 配置文件: ${eslintDest}`); // 生成 Prettier 配置 const prettierSource = path.join(templateDir, "prettier.js"); const prettierDest = path.join(process.cwd(), ".prettierrc.js"); fs.copySync(prettierSource, prettierDest); console.log(`✅ 已生成 .prettierrc.js 配置文件: ${prettierDest}`); // 检查本地 eslint 版本 let eslintVersion = null; try { const eslintPkgPath = require.resolve("eslint/package.json", { paths: [process.cwd()], }); eslintVersion = require(eslintPkgPath).version; } catch (e) { // eslint 未安装 } // 根据 eslint 版本和 node 版本选择依赖 // prettier 及相关依赖版本选择逻辑 let prettierVer, prettierConfigVer, prettierPluginVer, reactPluginVer, reactHooksPluginVer, tsParserVer, tsPluginVer; if (nodeMajor < 14) { prettierVer = "prettier@2"; prettierConfigVer = "eslint-config-prettier@6"; prettierPluginVer = "eslint-plugin-prettier@3"; reactPluginVer = "eslint-plugin-react@7"; reactHooksPluginVer = "eslint-plugin-react-hooks@2"; tsParserVer = "@typescript-eslint/parser@2"; tsPluginVer = "@typescript-eslint/eslint-plugin@2"; } else if (eslintVersion && /^6\./.test(eslintVersion)) { prettierVer = "prettier@2"; prettierConfigVer = "eslint-config-prettier@6"; prettierPluginVer = "eslint-plugin-prettier@3"; reactPluginVer = "eslint-plugin-react@7"; reactHooksPluginVer = "eslint-plugin-react-hooks@2"; tsParserVer = "@typescript-eslint/parser@2"; tsPluginVer = "@typescript-eslint/eslint-plugin@2"; } else if (eslintVersion && /^7\./.test(eslintVersion)) { prettierVer = "prettier@2"; prettierConfigVer = "eslint-config-prettier@8"; prettierPluginVer = "eslint-plugin-prettier@4"; reactPluginVer = "eslint-plugin-react@7"; reactHooksPluginVer = "eslint-plugin-react-hooks@4"; tsParserVer = "@typescript-eslint/parser@4"; tsPluginVer = "@typescript-eslint/eslint-plugin@4"; } else { // 默认最新 prettierVer = "prettier"; prettierConfigVer = "eslint-config-prettier"; prettierPluginVer = "eslint-plugin-prettier"; reactPluginVer = "eslint-plugin-react"; reactHooksPluginVer = "eslint-plugin-react-hooks"; tsParserVer = "@typescript-eslint/parser"; tsPluginVer = "@typescript-eslint/eslint-plugin"; } const baseDeps = [ nodeMajor < 14 ? "eslint@6" : "eslint", prettierVer, prettierConfigVer, prettierPluginVer, ]; const reactDeps = [reactPluginVer, reactHooksPluginVer]; const tsDeps = [tsParserVer, tsPluginVer]; const deps = baseDeps.concat(reactDeps, isTsProject ? tsDeps : []); console.log("\n📦 开始安装依赖..."); try { let installCmd = ""; if (packageManager === "yarn") { installCmd = `yarn add -D ${deps.join(" ")} --ignore-engines`; } else { installCmd = `npm install -D ${deps.join(" ")} --legacy-peer-deps`; } require("child_process").execSync(installCmd, { stdio: "inherit" }); console.log("✅ 依赖安装完成!"); } catch (e) { console.error("❌ 依赖安装失败,请手动安装以下依赖:", deps.join(" ")); } // 修改 package.json,添加 lint/format 脚本 const pkgPath2 = path.join(process.cwd(), "package.json"); if (fs.existsSync(pkgPath2)) { const pkg = fs.readJsonSync(pkgPath2); pkg.scripts = pkg.scripts || {}; pkg.scripts.lint = isTsProject ? "eslint --ext .js,.jsx,.ts,.tsx src" : "eslint --ext .js,.jsx src"; pkg.scripts.format = isTsProject ? 'prettier --write "src/**/*.{js,jsx,ts,tsx,json,css,md}"' : 'prettier --write "src/**/*.{js,jsx,json,css,md}"'; fs.writeJsonSync(pkgPath2, pkg, { spaces: 2 }); console.log("✅ 已为 package.json 添加 lint/format 脚本"); } else { console.warn("⚠️ 未找到 package.json,请手动添加 lint/format 脚本。"); } // 自动执行格式化前,统一询问用户是否需要自动运行 format 脚本(不再判断 Node 版本) const { confirmFormat } = await inquirer.prompt([ { type: "list", name: "confirmFormat", message: "是否现在自动运行 format 脚本格式化代码?", choices: ["是的,为我格式化全部代码", "不需要,我稍后手动运行"], default: "是的,为我格式化全部代码", }, ]); if (confirmFormat === "是的,为我格式化全部代码") { try { const runFormatCmd = packageManager === "yarn" ? "yarn format" : "npm run format"; console.log("\n✨ 正在自动格式化代码..."); require("child_process").execSync(runFormatCmd, { stdio: "inherit" }); console.log("✅ 代码已自动格式化!"); } catch (e) { console.warn("⚠️ 自动格式化失败,请手动运行 format 脚本。"); } } else { console.warn("⚠️ 已跳过自动格式化,你可以稍后手动运行 format 脚本。"); } // 自动生成统一的 VS Code TypeScript 团队配置 const vscodeDir = path.join(process.cwd(), ".vscode"); const vscodeSettingsPath = path.join(vscodeDir, "settings.json"); fs.ensureDirSync(vscodeDir); fs.writeJsonSync( vscodeSettingsPath, { "typescript.tsdk": "node_modules/typescript/lib", "typescript.enablePromptUseWorkspaceTsdk": true, "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.fixAll": "always", "source.organizeImports": "always", }, "editor.suggestSelection": "first", "editor.quickSuggestions": { other: true, comments: false, strings: false, }, "eslint.enable": true, "eslint.validate": [ "javascript", "javascriptreact", "typescript", "typescriptreact", ], }, { spaces: 2 } ); console.log( "✅ 已生成 .vscode/settings.json,统一团队 TypeScript 类型提示和格式化规则" ); console.log("\n🎉 项目配置成功!"); })();