UNPKG

@pgg/lint-installer

Version:

一键安装和配置代码质量工具链的CLI工具

557 lines 21.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.installDevTools = installDevTools; const node_child_process_1 = require("node:child_process"); const path = __importStar(require("node:path")); const chalk_1 = __importDefault(require("chalk")); const fs = __importStar(require("fs-extra")); const inquirer_1 = __importDefault(require("inquirer")); const ora_1 = __importDefault(require("ora")); const DEV_DEPENDENCIES = { '@antfu/eslint-config': 'latest', '@commitlint/cli': 'latest', '@commitlint/config-conventional': 'latest', '@ls-lint/ls-lint': 'latest', 'czg': 'latest', 'eslint': 'latest', 'eslint-plugin-format': 'latest', 'lint-staged': 'latest', 'simple-git-hooks': 'latest', }; // 降级版本配置,当最新版本安装失败时使用 const FALLBACK_DEPENDENCIES = { '@antfu/eslint-config': '^4.17.0', '@commitlint/cli': '^19.8.1', '@commitlint/config-conventional': '^19.8.1', '@ls-lint/ls-lint': '^2.3.1', 'czg': '^1.12.0', 'eslint': '^9.31.0', 'eslint-plugin-format': '^1.0.1', 'lint-staged': '^16.1.2', 'simple-git-hooks': '^2.13.0', }; const SCRIPTS = { 'lint:eslint': 'eslint --fix', 'lint:ls': 'ls-lint', 'lint': 'git add . && npx lint-staged', 'commit': 'git add . && czg && git pull && git push', }; const GIT_HOOKS = { 'commit-msg': 'npx commitlint --config commitlint.config.cjs --edit', 'pre-commit': 'npx lint-staged', }; const LINT_STAGED = { '*': [ 'ls-lint', 'eslint --fix', ], }; // 需要清理的配置文件列表 const CONFIG_FILES_TO_CLEAN = [ // ESLint 配置文件 '.eslintrc.js', '.eslintrc.cjs', '.eslintrc.json', '.eslintrc.yml', '.eslintrc.yaml', 'eslint.config.js', 'eslint.config.ts', 'eslint.config.mjs', 'eslint.config.cjs', // Prettier 配置文件 '.prettierrc', '.prettierrc.json', '.prettierrc.yml', '.prettierrc.yaml', '.prettierrc.js', '.prettierrc.cjs', '.prettierrc.mjs', 'prettier.config.js', 'prettier.config.cjs', 'prettier.config.mjs', // CommitLint 配置文件 'commitlint.config.js', 'commitlint.config.ts', 'commitlint.config.cjs', 'commitlint.config.mjs', '.commitlintrc.js', '.commitlintrc.json', '.commitlintrc.yml', '.commitlintrc.yaml', // Husky 配置 '.husky/', // 其他 lint 工具配置 '.ls-lint.yml', '.ls-lint.yaml', // VSCode 配置 '.vscode/', ]; // 需要清理的依赖项列表 const DEPENDENCIES_TO_CLEAN = [ // ESLint 相关 '@typescript-eslint/eslint-plugin', '@typescript-eslint/parser', '@vue/eslint-config-prettier', '@vue/eslint-config-typescript', 'eslint-config-prettier', 'eslint-plugin-prettier', 'eslint-plugin-vue', 'eslint-plugin-import', 'eslint-plugin-node', 'eslint-plugin-promise', 'eslint-plugin-standard', '@eslint/js', // Prettier 相关 'prettier', // Husky 相关 'husky', // 其他可能冲突的工具 '@vitest/eslint-plugin', 'eslint-plugin-playwright', ]; // 需要清理的脚本列表 const SCRIPTS_TO_CLEAN = [ 'lint:prettier', 'lint:fix', 'format', 'format:check', 'prepare', // husky 相关 ]; // 获取包的最新版本号 async function getLatestVersion(packageName) { try { const result = (0, node_child_process_1.execSync)(`npm view ${packageName} version`, { encoding: 'utf-8' }); return `^${result.trim()}`; } catch { // 如果获取失败,返回降级版本 return FALLBACK_DEPENDENCIES[packageName] || 'latest'; } } // 获取所有包的最新版本号 async function getLatestVersions() { const spinner = (0, ora_1.default)('正在获取最新版本信息...').start(); const versions = {}; try { const packageNames = Object.keys(DEV_DEPENDENCIES); for (const packageName of packageNames) { spinner.text = `正在获取 ${packageName} 的最新版本...`; versions[packageName] = await getLatestVersion(packageName); } spinner.succeed('版本信息获取完成'); return versions; } catch { spinner.fail('获取版本信息失败,将使用降级版本'); return FALLBACK_DEPENDENCIES; } } async function createPackageJson(targetDir) { const packageJsonPath = path.join(targetDir, 'package.json'); const dirName = path.basename(targetDir); const basicPackageJson = { name: dirName, version: '1.0.0', description: '', main: 'index.js', scripts: { test: 'echo "Error: no test specified" && exit 1', }, keywords: [], author: '', license: 'ISC', }; const spinner = (0, ora_1.default)('正在创建 package.json...').start(); try { await fs.writeJson(packageJsonPath, basicPackageJson, { spaces: 2 }); spinner.succeed('package.json 创建完成'); } catch (error) { spinner.fail('创建 package.json 失败'); throw error; } } async function installDevTools(targetDir, versionStrategy) { const packageJsonPath = path.join(targetDir, 'package.json'); // 检查是否存在 package.json,不存在则创建 if (!await fs.pathExists(packageJsonPath)) { await createPackageJson(targetDir); } // 首先检测包管理器(在修改任何文件之前) const packageManager = await detectPackageManager(targetDir); // 确定版本策略 let selectedStrategy; if (versionStrategy && ['latest', 'stable'].includes(versionStrategy)) { selectedStrategy = versionStrategy; console.log(chalk_1.default.blue(`📦 使用命令行指定的版本策略: ${versionStrategy === 'latest' ? '最新版本' : '稳定版本'}`)); } else { // 询问用户选择版本策略 const { strategy } = await inquirer_1.default.prompt([ { type: 'list', name: 'strategy', message: '请选择依赖版本策略:', choices: [ { name: '🚀 最新版本 (获取每个包的最新版本)', value: 'latest', }, { name: '🛡️ 稳定版本 (使用预设的稳定版本)', value: 'stable', }, ], }, ]); selectedStrategy = strategy; } // 根据选择获取依赖版本 let dependenciesToUse; if (selectedStrategy === 'latest') { dependenciesToUse = await getLatestVersions(); } else { dependenciesToUse = FALLBACK_DEPENDENCIES; console.log(chalk_1.default.blue('📦 使用预设的稳定版本')); } const spinner = (0, ora_1.default)('正在更新 package.json...').start(); try { // 读取并更新 package.json const packageJson = await fs.readJson(packageJsonPath); // 添加 devDependencies(使用用户选择的版本) packageJson.devDependencies = { ...packageJson.devDependencies, ...dependenciesToUse, }; // 添加 scripts packageJson.scripts = { ...packageJson.scripts, ...SCRIPTS, }; // 添加 simple-git-hooks 配置 packageJson['simple-git-hooks'] = GIT_HOOKS; // 添加 lint-staged 配置 packageJson['lint-staged'] = LINT_STAGED; // 写回 package.json await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 }); spinner.succeed('package.json 更新完成'); // 清理已有配置 await cleanExistingConfigs(targetDir); // 更新 .gitignore 文件 await updateGitignore(targetDir); // 复制配置文件 await copyConfigFiles(targetDir); // 安装依赖 await installDependencies(targetDir, packageManager); // 初始化 git hooks await initializeGitHooks(targetDir); } catch (error) { spinner.fail('更新失败'); throw error; } } async function cleanExistingConfigs(targetDir) { const spinner = (0, ora_1.default)('正在清理已有配置文件...').start(); const packageJsonPath = path.join(targetDir, 'package.json'); try { let cleanedFiles = 0; let cleanedDeps = 0; let cleanedScripts = 0; // 1. 清理配置文件 for (const configFile of CONFIG_FILES_TO_CLEAN) { const filePath = path.join(targetDir, configFile); if (await fs.pathExists(filePath)) { const stat = await fs.stat(filePath); if (stat.isDirectory()) { await fs.remove(filePath); cleanedFiles++; spinner.text = `正在清理配置目录: ${configFile}`; } else { await fs.remove(filePath); cleanedFiles++; spinner.text = `正在清理配置文件: ${configFile}`; } } } // 2. 清理 package.json 中的依赖项和脚本 if (await fs.pathExists(packageJsonPath)) { const packageJson = await fs.readJson(packageJsonPath); let packageJsonChanged = false; // 清理依赖项 if (packageJson.devDependencies) { for (const dep of DEPENDENCIES_TO_CLEAN) { if (packageJson.devDependencies[dep]) { delete packageJson.devDependencies[dep]; cleanedDeps++; packageJsonChanged = true; spinner.text = `正在清理依赖: ${dep}`; } } } if (packageJson.dependencies) { for (const dep of DEPENDENCIES_TO_CLEAN) { if (packageJson.dependencies[dep]) { delete packageJson.dependencies[dep]; cleanedDeps++; packageJsonChanged = true; spinner.text = `正在清理依赖: ${dep}`; } } } // 清理脚本 if (packageJson.scripts) { for (const script of SCRIPTS_TO_CLEAN) { if (packageJson.scripts[script]) { delete packageJson.scripts[script]; cleanedScripts++; packageJsonChanged = true; spinner.text = `正在清理脚本: ${script}`; } } } // 清理可能存在的旧配置(只清理husky,保留lint-staged和simple-git-hooks) const configsToRemove = ['husky']; for (const config of configsToRemove) { if (packageJson[config]) { delete packageJson[config]; packageJsonChanged = true; spinner.text = `正在清理配置: ${config}`; } } // 如果有变更,写回文件 if (packageJsonChanged) { await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 }); } } const totalCleaned = cleanedFiles + cleanedDeps + cleanedScripts; if (totalCleaned > 0) { spinner.succeed(`配置清理完成 (清理了 ${cleanedFiles} 个文件, ${cleanedDeps} 个依赖, ${cleanedScripts} 个脚本)`); } else { spinner.succeed('未发现需要清理的配置'); } } catch { spinner.fail('配置清理失败'); console.warn(chalk_1.default.yellow('⚠️ 配置清理失败,但安装将继续进行')); console.warn(chalk_1.default.yellow('⚠️ 建议手动检查并清理可能冲突的配置文件')); } } async function copyConfigFiles(targetDir) { const spinner = (0, ora_1.default)('正在复制配置文件...').start(); try { const templatesDir = path.join(__dirname, '../templates'); // 复制所有配置文件 const configFiles = [ 'eslint.config.mjs', 'commitlint.config.cjs', '.ls-lint.yml', ]; for (const file of configFiles) { const sourcePath = path.join(templatesDir, file); const targetPath = path.join(targetDir, file); if (await fs.pathExists(sourcePath)) { await fs.copy(sourcePath, targetPath); } } // 复制 VSCode 配置文件 const vscodeSourceDir = path.join(templatesDir, '.vscode'); const vscodeTargetDir = path.join(targetDir, '.vscode'); if (await fs.pathExists(vscodeSourceDir)) { await fs.ensureDir(vscodeTargetDir); await fs.copy(vscodeSourceDir, vscodeTargetDir); } spinner.succeed('配置文件复制完成'); } catch (error) { spinner.fail('配置文件复制失败'); throw error; } } async function installDependencies(targetDir, packageManager) { const packageJsonPath = path.join(targetDir, 'package.json'); try { const installCommand = packageManager === 'pnpm' ? 'pnpm install' : packageManager === 'yarn' ? 'yarn install' : 'npm install'; console.log(chalk_1.default.blue(`📦 正在安装依赖 (使用 ${packageManager})...`)); try { // 安装依赖,显示实时进度 (0, node_child_process_1.execSync)(installCommand, { cwd: targetDir, stdio: 'inherit', }); console.log(chalk_1.default.green(`✅ 依赖安装完成 (使用 ${packageManager})`)); } catch { console.log(chalk_1.default.yellow('⚠️ 安装失败,正在使用稳定版本重试...')); // 读取并更新 package.json 为降级版本 const packageJson = await fs.readJson(packageJsonPath); packageJson.devDependencies = { ...packageJson.devDependencies, ...FALLBACK_DEPENDENCIES, }; await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 }); console.log(chalk_1.default.blue(`📦 正在安装稳定版本依赖 (使用 ${packageManager})...`)); // 重新安装降级版本,显示实时进度 (0, node_child_process_1.execSync)(installCommand, { cwd: targetDir, stdio: 'inherit', }); console.log(chalk_1.default.green(`✅ 依赖安装完成 (使用 ${packageManager} - 稳定版本)`)); console.log(chalk_1.default.yellow('⚠️ 已自动降级到稳定版本,确保兼容性')); } } catch (error) { console.log(chalk_1.default.red('❌ 依赖安装失败')); throw error; } } async function updateGitignore(targetDir) { const gitignorePath = path.join(targetDir, '.gitignore'); if (!await fs.pathExists(gitignorePath)) { return; } const spinner = (0, ora_1.default)('正在检查 .gitignore 文件...').start(); try { const content = await fs.readFile(gitignorePath, 'utf-8'); const lines = content.split('\n'); // 检查是否包含 .vscode/ 相关规则(包括忽略和否定规则) const vscodeIgnorePatterns = ['.vscode/', '.vscode', '.vscode/*']; const vscodeNegatePatterns = ['!.vscode/', '!.vscode', '!.vscode/*']; let hasVscodeIgnore = false; const modifiedLines = lines.filter((line) => { const trimmedLine = line.trim(); // 检查普通忽略规则 if (vscodeIgnorePatterns.includes(trimmedLine)) { hasVscodeIgnore = true; return false; // 移除这一行 } // 检查否定规则(以!开头的规则) if (vscodeNegatePatterns.includes(trimmedLine)) { hasVscodeIgnore = true; return false; // 移除这一行 } // 检查更复杂的否定规则,如 !.vscode/extensions.json if (trimmedLine.startsWith('!.vscode/')) { hasVscodeIgnore = true; return false; // 移除这一行 } return true; }); if (hasVscodeIgnore) { // 写回修改后的内容 await fs.writeFile(gitignorePath, modifiedLines.join('\n')); spinner.succeed('.gitignore 文件已更新,移除了 .vscode/ 忽略规则'); } else { spinner.succeed('.gitignore 文件检查完成,未发现 .vscode/ 忽略规则'); } } catch { spinner.fail('.gitignore 文件处理失败'); console.warn(chalk_1.default.yellow('⚠️ .gitignore 文件处理失败,请手动检查并移除 .vscode/ 相关忽略规则')); } } async function initializeGitHooks(targetDir) { const spinner = (0, ora_1.default)('正在初始化 Git Hooks...').start(); try { // 检查是否存在 .git 目录 const gitDir = path.join(targetDir, '.git'); if (!fs.existsSync(gitDir)) { spinner.text = '检测到项目未初始化 Git,正在执行 git init...'; (0, node_child_process_1.execSync)('git init', { cwd: targetDir, stdio: 'pipe', }); spinner.text = '正在初始化 Git Hooks...'; } (0, node_child_process_1.execSync)('npx simple-git-hooks', { cwd: targetDir, stdio: 'pipe', }); spinner.succeed('Git Hooks 初始化完成'); } catch { spinner.fail('Git Hooks 初始化失败'); console.warn(chalk_1.default.yellow('⚠️ 请手动运行: git init && npx simple-git-hooks')); } } async function detectPackageManager(targetDir) { const lockFiles = [ { file: 'pnpm-lock.yaml', manager: 'pnpm' }, { file: 'yarn.lock', manager: 'yarn' }, { file: 'package-lock.json', manager: 'npm' }, ]; const existingManagers = lockFiles.filter(({ file }) => { const exists = fs.existsSync(path.join(targetDir, file)); return exists; }); if (existingManagers.length === 0) { console.log(chalk_1.default.yellow('⚠️ 未检测到包管理器锁文件,默认使用 pnpm')); return 'pnpm'; } if (existingManagers.length === 1) { const manager = existingManagers[0].manager; console.log(chalk_1.default.blue(`📦 检测到项目使用 ${manager},将使用相同的包管理器`)); return manager; } // 存在多个包管理器锁文件,询问用户选择 console.log(chalk_1.default.yellow('⚠️ 检测到多个包管理器锁文件,请选择要使用的包管理器:')); const choices = existingManagers.map(({ file, manager }) => ({ name: `${manager} (${file})`, value: manager, })); const { selectedManager } = await inquirer_1.default.prompt([ { type: 'list', name: 'selectedManager', message: '请选择要使用的包管理器:', choices, }, ]); console.log(chalk_1.default.green(`✅ 已选择使用 ${selectedManager}`)); return selectedManager; } //# sourceMappingURL=installer.js.map