UNPKG

build-deploy-tools

Version:

构建部署工具包 - 支持文件复制、SVN操作、系统通知确认等功能,具有科技感进度条和现代化UI

784 lines (689 loc) 26 kB
/** * SVN操作模块 * 提供SVN更新、提交、删除等功能 */ const { execSync } = require('child_process') const { retryOperation, RETRY_CONFIG, createProgressBar, createMultiStageProgressBar, delay } = require('./utils') const { createTechMultiStageProgress, createTechSpinner, showTechSuccess, showTechError } = require('./modern-progress') /** * 统一的消息格式化函数 * @param {string} type - 消息类型: 'info', 'success', 'warning', 'error', 'system' * @param {string} message - 消息内容 * @returns {string} 格式化后的消息 */ function formatSystemMessage(type, message) { const icons = { info: '📊', success: '✅', warning: '⚠️', error: '❌', system: '🔧', process: '🔄', engine: '🚀' } const icon = icons[type] || '📊' return `${icon} ${message}` } /** * 清理提交信息,移除可能导致SVN错误的特殊字符 * @param {string} message - 原始提交信息 * @returns {string} 清理后的提交信息 */ function sanitizeCommitMessage(message) { if (!message || typeof message !== 'string') { return '更新构建文件' } return message .trim() // 移除可能导致SVN解析错误的字符 .replace(/["'`]/g, '') // 移除引号 .replace(/[\r\n\t]/g, ' ') // 替换换行符和制表符为空格 .replace(/\s+/g, ' ') // 合并多个空格 .replace(/[<>|&]/g, '') // 移除可能的XML/命令行特殊字符 // 移除版本号前的冒号(这是导致 E020024 错误的主要原因) .replace(/^([vV]?\d+\.\d+\.\d+):\s*/, '$1 - ') // v1.4.0: → v1.4.0 - .replace(/^([^:]+):\s*/, '$1 - ') // 其他冒号开头的格式 .substring(0, 200) // 限制长度 .trim() } /** * 执行SVN命令 * @param {string} command - SVN命令 * @param {string} cwd - 执行目录 * @param {string} errorMessage - 错误提示信息 * @param {number} timeout - 超时时间(毫秒) * @param {boolean} showProgress - 是否显示进度条 * @returns {Promise<boolean>} 执行结果 */ async function executeSvn ( command, cwd, errorMessage, timeout = RETRY_CONFIG.svnTimeout, showProgress = false ) { let progressBar = null try { console.log(`🔧 执行系统命令: ${command}`) if (showProgress) { progressBar = createProgressBar(timeout, `⏳ 系统正在执行: ${command}`) } execSync(command, { cwd, stdio: 'pipe', timeout: timeout }) if (progressBar) { progressBar.stop() } return true } catch (error) { if (progressBar) { progressBar.stop() } if (error.status === 'ETIMEDOUT') { console.error(`❌ ${errorMessage}: 系统命令执行超时 (${timeout / 1000}秒)`) } else { console.error(`❌ ${errorMessage}: ${error.message}`) } throw error } } /** * 高级SVN工作目录清理(增强版) * 处理工作队列中断和文件占用问题 * @param {string} cwd - 执行目录 * @returns {Promise<boolean>} 清理结果 */ async function forceCleanupSvn(cwd) { console.log(formatSystemMessage('engine', '初始化量子清理引擎...')) // 阶段1: 尝试基本清理 try { console.log(formatSystemMessage('system', '第一阶段: 启动核心清理协议...')) await executeSvn('svn cleanup', cwd, 'SVN核心清理失败', 60000, false) console.log(formatSystemMessage('success', '核心清理协议执行完成')) return true } catch (error) { console.log(formatSystemMessage('warning', `核心清理协议失败: ${error.message}`)) } // 阶段2: 尝试带参数的清理 const advancedCleanupCommands = [ 'svn cleanup --remove-unversioned', 'svn cleanup --remove-ignored', 'svn cleanup --include-externals' ] for (const command of advancedCleanupCommands) { try { console.log(formatSystemMessage('system', `第二阶段: 部署高级清理算法 - ${command}...`)) await executeSvn(command, cwd, 'SVN高级清理算法失败', 60000, false) console.log(formatSystemMessage('success', `高级清理算法执行成功: ${command}`)) return true } catch (error) { console.log(formatSystemMessage('warning', `高级清理算法失败: ${command} - ${error.message}`)) } } // 阶段3: 尝试手动解决工作队列问题 try { console.log(formatSystemMessage('system', '第三阶段: 启动智能修复系统...')) await handleSvnWorkQueue(cwd) // 再次尝试基本清理 await executeSvn('svn cleanup', cwd, 'SVN最终修复失败', 60000, false) console.log(formatSystemMessage('success', '智能修复系统执行成功')) return true } catch (error) { console.log(formatSystemMessage('warning', `智能修复系统失败: ${error.message}`)) } console.log(formatSystemMessage('error', '所有修复算法均失败,系统需要人工干预')) return false } /** * 处理SVN工作队列问题 * @param {string} cwd - 执行目录 * @returns {Promise<void>} */ async function handleSvnWorkQueue(cwd) { const path = require('path') const fs = require('fs').promises try { // 尝试查找并处理.svn/wc.db文件 const svnDir = path.join(cwd, '.svn') const wcDbPath = path.join(svnDir, 'wc.db') console.log('🔍 检查SVN工作数据库...') try { const stats = await fs.stat(wcDbPath) console.log(`📊 找到SVN数据库: ${wcDbPath} (大小: ${stats.size} bytes)`) // 尝试使用sqlite3命令修复数据库(如果可用) try { const { execSync } = require('child_process') console.log(formatSystemMessage('system', '尝试修复数据库完整性...')) // 尝试使用SQLite命令修复 execSync(`sqlite3 "${wcDbPath}" "PRAGMA integrity_check;"`, { cwd: cwd, stdio: 'pipe', timeout: 30000 }) console.log(formatSystemMessage('success', '数据库完整性检查通过')) } catch (sqliteError) { console.log(formatSystemMessage('warning', 'SQLite修复失败或不可用,跳过此步骤')) } } catch (statError) { console.log(formatSystemMessage('warning', '未找到SVN数据库文件')) } // 尝试关闭可能占用文件的进程 await attemptFileUnlock(cwd) } catch (error) { console.log(`⚠️ 工作队列处理失败: ${error.message}`) throw error } } /** * 尝试解锁被占用的文件 * @param {string} cwd - 执行目录 * @returns {Promise<void>} */ async function attemptFileUnlock(cwd) { try { console.log(formatSystemMessage('system', '启动文件解锁引擎...')) // 在Windows上尝试使用handle.exe或lsof查找占用文件的进程 const { execSync } = require('child_process') const os = require('os') if (os.platform() === 'win32') { try { // 尝试使用Windows的tasklist命令 console.log('🔍 扫描系统进程占用矩阵...') // 等待一段时间,让可能的文件操作完成 await delay(2000) // 尝试强制垃圾回收 if (global.gc) { global.gc() console.log(formatSystemMessage('success', '执行内存量子清理')) } console.log(formatSystemMessage('success', '文件解锁引擎执行完成')) } catch (winError) { console.log(formatSystemMessage('warning', '文件解锁引擎失败,继续执行')) } } } catch (error) { console.log(formatSystemMessage('warning', `文件解锁失败: ${error.message}`)) } } /** * 执行SVN更新,支持自动重试和cleanup(优化版) * @param {string} cwd - 执行目录 * @returns {Promise<boolean>} 更新结果 */ async function executeSvnUpdate (cwd) { const spinner = createTechSpinner('🌐 初始化版本同步引擎...', 'ice') spinner.start() return await retryOperation( async () => { try { spinner.text = '📊 扫描远程仓库版本矩阵...' await executeSvn( 'svn update', cwd, '版本同步引擎失败', RETRY_CONFIG.svnTimeout, false ) spinner.succeed('✅ 版本同步引擎执行完成') return true } catch (error) { // 检查是否需要cleanup const needsCleanup = error.message && ( error.message.includes('cleanup') || error.message.includes('locked') || error.message.includes('Previous operation has not finished') || error.message.includes('E155037') ) if (needsCleanup) { spinner.text = '🧹 启动数据库修复协议...' await forceCleanupSvn(cwd) spinner.text = '🔄 重新初始化同步引擎...' await executeSvn( 'svn update', cwd, '版本同步引擎失败', RETRY_CONFIG.svnTimeout, false ) spinner.succeed('✅ 版本同步引擎执行完成') return true } spinner.fail(`❌ 版本同步引擎失败: ${error.message}`) throw error } }, RETRY_CONFIG.maxRetries, RETRY_CONFIG.retryDelay, 'SVN同步' ) } /** * SVN删除文件或目录(无进程占用版) * 在父目录执行删除命令,避免进程占用问题 * @param {string} targetDir - 目标目录的完整路径 * @returns {Promise<boolean>} 删除结果 */ async function executeSvnDelete (targetDir) { const path = require('path') const parentDir = path.dirname(targetDir) const folderName = path.basename(targetDir) return await retryOperation( async () => { console.log(formatSystemMessage('system', `启动文件清除协议: ${folderName}`)) try { // 第一次尝试:在父目录执行删除,不使用--force await executeSvn(`svn delete "${folderName}"`, parentDir, '文件清除协议失败') console.log(formatSystemMessage('success', '文件清除协议执行成功')) return true } catch (error) { // 检查是否是工作队列问题 if (error.message && ( error.message.includes('work queue') || error.message.includes('E155009') || error.message.includes('Previous operation has not finished') || error.message.includes('E155037') )) { console.log(formatSystemMessage('system', '检测到数据库异常,启动智能修复系统...')) await forceCleanupSvn(parentDir) // 清理后再次尝试删除 try { await executeSvn(`svn delete "${folderName}"`, parentDir, '修复后清除协议失败') console.log(formatSystemMessage('success', '修复后清除协议执行成功')) return true } catch (cleanupError) { console.log('🔄 修复后仍失败,启动强制清除模式...') } } try { // 如果失败,尝试使用--force参数 console.log('🔄 启动强制清除模式...') await executeSvn(`svn delete --force "${folderName}"`, parentDir, '强制清除模式失败') console.log(formatSystemMessage('success', '强制清除模式执行成功')) return true } catch (forceError) { // 强制删除也失败时,检查是否是工作队列问题 if (forceError.message && ( forceError.message.includes('Previous operation has not finished') || forceError.message.includes('E155037') )) { console.log('🧹 强制删除也遇到工作队列问题,再次清理...') await forceCleanupSvn(parentDir) try { await executeSvn(`svn delete --force "${folderName}"`, parentDir, 'SVN最终删除失败') console.log('✅ SVN最终删除成功') return true } catch (finalError) { console.log(formatSystemMessage('warning', 'SVN删除最终失败,文件可能不在版本控制中')) return true // 继续执行,不报错 } } else { // 其他错误,可能文件不在版本控制中 console.log('SVN删除失败,文件可能不在版本控制中') return true // 继续执行,不报错 } } } }, RETRY_CONFIG.maxRetries, RETRY_CONFIG.retryDelay, 'SVN删除' ) } /** * SVN添加文件(无进程占用版) * 在父目录执行添加命令,避免进程占用问题 * @param {string} targetDir - 目标目录的完整路径 * @returns {Promise<boolean>} 添加结果 */ async function executeSvnAdd (targetDir) { const path = require('path') const parentDir = path.dirname(targetDir) const folderName = path.basename(targetDir) const spinner = createTechSpinner(`📁 启动文件索引引擎: ${folderName}`, 'matrix') spinner.start() try { // 第一次尝试:在父目录执行添加,不使用--force await executeSvn(`svn add "${folderName}"`, parentDir, '文件索引引擎失败') spinner.succeed('✅ 文件索引引擎执行完成') return true } catch (error) { // 如果遇到锁定问题,先清理再重试 if (error.message && ( error.message.includes('Previous operation has not finished') || error.message.includes('E155037') || error.message.includes('locked') || error.message.includes('work queue') || error.message.includes('E155009') )) { spinner.text = '🧹 启动数据库修复协议...' await forceCleanupSvn(parentDir) try { // 清理后再次尝试不使用--force await executeSvn(`svn add "${folderName}"`, parentDir, '文件索引引擎失败') spinner.succeed('✅ 文件索引引擎执行完成') return true } catch (secondError) { // 如果还是失败,使用--force参数 spinner.text = '🔄 启动强制索引模式...' await executeSvn(`svn add "${folderName}" --force`, parentDir, '强制索引模式失败') spinner.succeed('✅ 强制索引模式执行完成') return true } } else { // 其他错误,尝试使用--force参数 try { spinner.text = '🔄 启动强制索引模式...' await executeSvn(`svn add "${folderName}" --force`, parentDir, '强制索引模式失败') spinner.succeed('✅ 强制索引模式执行完成') return true } catch (forceError) { spinner.fail(`❌ 文件索引引擎失败: ${forceError.message}`) throw forceError } } } } /** * SVN提交,支持自动重试和科技感进度显示 * @param {string} targetDir - 目标目录 * @param {string} parentDir - 父目录(用于提交) * @param {string} commitMessage - 提交信息 * @param {boolean} showDetailedProgress - 是否显示详细进度条 * @returns {Promise<boolean>} 提交结果 */ async function commitToSvnWithRetry ( targetDir, parentDir, commitMessage = '更新构建文件', showDetailedProgress = true ) { return await retryOperation( async () => { let techProgress = null try { if (showDetailedProgress) { // 定义SVN提交的各个阶段 - 科技感配置 const stages = [ { name: '系统状态扫描', type: 'scanning', showProgress: true }, { name: '文件索引构建', type: 'building', showProgress: true }, { name: '提交数据准备', type: 'processing', showProgress: true }, { name: '数据传输执行', type: 'uploading', showProgress: true }, { name: '完整性验证', type: 'checking', showProgress: true } ] techProgress = createTechMultiStageProgress(stages, { operation: 'SVN智能提交系统', theme: 'cyber', showStageDetails: true, animationSpeed: 120 }) techProgress.start() } else { console.log('📤 提交到SVN...') } // 阶段1: 系统状态扫描(优化) if (techProgress) { techProgress.updateStage(50, '🔍 初始化系统状态扫描器...') // 检查是否有冲突文件 try { const statusOutput = execSync('svn status', { cwd: parentDir, encoding: 'utf8', stdio: 'pipe' }) if (statusOutput.includes('C ')) { throw new Error('检测到数据冲突,系统需要人工干预') } techProgress.updateStage(100, '🔍 系统状态扫描器执行完成') } catch (error) { if (techProgress) techProgress.error(`系统状态扫描器失败: ${error.message}`) throw error } techProgress.nextStage('✅ 系统状态扫描器执行完成') } // 阶段2: 文件索引构建(优化) if (techProgress) { techProgress.updateStage(40, '📁 启动文件索引引擎...') } await executeSvnAdd(targetDir) if (techProgress) { techProgress.updateStage(100, '📁 文件索引引擎执行完成') techProgress.nextStage('✅ 文件索引引擎执行完成') } // 阶段3: 提交数据准备(优化) if (techProgress) { techProgress.updateStage(70, `📝 初始化提交数据包: "${commitMessage}"`) techProgress.updateStage(100, '📝 提交数据包编译完成') techProgress.nextStage('✅ 提交数据包编译完成') } // 阶段4: 数据传输执行 if (techProgress) { techProgress.updateStage(5, '📤 建立SVN服务器连接...') } try { // 清理提交信息,防止SVN解析错误 const sanitizedMessage = sanitizeCommitMessage(commitMessage) // 优化:只在信息被清理时才显示详细信息 if (sanitizedMessage !== commitMessage) { console.log(formatSystemMessage('info', `原始: "${commitMessage}"`)) console.log(formatSystemMessage('info', `清理后: "${sanitizedMessage}"`)) } // 使用增强的提交执行函数,直接传递清理后的信息 await executeTechSvnCommit( sanitizedMessage, parentDir, techProgress ) } catch (error) { if (techProgress) techProgress.error(`数据传输失败: ${error.message}`) throw error } if (techProgress) { techProgress.nextStage('✅ 数据传输执行完成') } // 阶段5: 完整性验证(优化) if (techProgress) { techProgress.updateStage(60, '✨ 启动数据完整性验证器...') try { // 获取最新的提交信息进行验证 const logOutput = execSync('svn log -l 1', { cwd: parentDir, encoding: 'utf8', stdio: 'pipe' }) techProgress.updateStage(100, '✨ 数据完整性验证器执行完成') // 显示提交的修订号 const revisionMatch = logOutput.match(/r(\d+)/) if (revisionMatch) { techProgress.nextStage( `🎉 数据传输成功! 版本标识: r${revisionMatch[1]}` ) showTechSuccess( `数据传输成功 - 版本标识: r${revisionMatch[1]}`, 'cyber' ) } else { techProgress.nextStage('✅ 数据完整性验证器执行完成') } } catch (verifyError) { // 验证失败不影响主流程,只是警告 console.log(formatSystemMessage('warning', '数据完整性验证失败,但传输可能已成功')) if (techProgress) { techProgress.nextStage('⚠️ 数据传输完成,验证器跳过') } } } if (!showDetailedProgress) { console.log(formatSystemMessage('success', 'SVN提交成功')) } return true } catch (error) { if (techProgress) { techProgress.error(`SVN智能提交系统故障: ${error.message}`) } else { showTechError(`SVN提交失败: ${error.message}`) } throw error } }, RETRY_CONFIG.maxRetries, RETRY_CONFIG.retryDelay, 'SVN智能提交' ) } /** * 科技感 SVN提交执行引擎 * @param {string} commitMessage - 清理后的提交信息 * @param {string} cwd - 执行目录 * @param {Object} techProgress - 科技感进度条对象 * @returns {Promise<void>} */ async function executeTechSvnCommit (commitMessage, cwd, techProgress = null) { return new Promise((resolve, reject) => { const { spawn } = require('child_process') if (techProgress) { techProgress.updateStage(15, '🚀 初始化传输协议...') } // 正确构建SVN命令参数 const args = ['commit', '-m', commitMessage] // 优化:减少冗余的命令输出 // console.log(`📝 执行命令: svn commit -m "${commitMessage}"`) // 使用spawn来实时获取输出 const svnProcess = spawn('svn', args, { cwd: cwd, stdio: ['pipe', 'pipe', 'pipe'] }) let output = '' let errorOutput = '' let progressStep = 15 let dataTransferred = 0 // 优化的进度更新(减少频繁更新) const progressMessages = [ '📡 初始化传输协议...', '📊 扫描数据差异矩阵...', '🌐 执行数据传输序列...', '✨ 数据传输序列完成...' ] let messageIndex = 0 const progressInterval = setInterval(() => { if (progressStep < 80 && messageIndex < progressMessages.length) { progressStep += 20 if (techProgress) { techProgress.updateStage(progressStep, progressMessages[messageIndex]) } messageIndex++ } }, 1000) // 监听数据传输(优化,减少频繁更新) svnProcess.stdout.on('data', data => { output += data.toString() dataTransferred += data.length // 减少进度更新频率 if (techProgress && progressStep < 75 && dataTransferred % 10240 === 0) { // 每10KB更新一次 progressStep = Math.min(75, progressStep + 5) const sizeKB = (dataTransferred / 1024).toFixed(1) techProgress.updateStage(progressStep, `📊 数据传输量: ${sizeKB}KB`) } }) svnProcess.stderr.on('data', data => { errorOutput += data.toString() // 优化:减少冗余的日志输出 const output = data.toString().trim() if ( !output.includes('Transmitting file data') && !output.includes('Committed revision') && output.length > 0 ) { // 只显示重要的SVN输出信息 if (output.includes('revision') || output.includes('Committed')) { console.log(`📊 SVN: ${output}`) } } }) svnProcess.on('close', code => { clearInterval(progressInterval) if (code === 0) { if (techProgress) { techProgress.updateStage(100, '🎉 数据传输序列执行成功') } resolve() } else { const error = new Error( `SVN传输引擎故障: ${errorOutput || '未知系统错误'}` ) reject(error) } }) svnProcess.on('error', error => { clearInterval(progressInterval) reject(new Error(`SVN传输引擎启动失败: ${error.message}`)) }) // 设置超时保护 setTimeout(() => { clearInterval(progressInterval) svnProcess.kill('SIGTERM') reject(new Error('SVN传输引擎超时 - 系统安全保护机制触发')) }, RETRY_CONFIG.commitTimeout) }) } /** * 检查SVN状态 * @param {string} cwd - 执行目录 * @returns {Promise<string>} SVN状态信息 */ async function getSvnStatus (cwd) { try { const result = execSync('svn status', { cwd, encoding: 'utf8', timeout: RETRY_CONFIG.svnTimeout }) return result.toString() } catch (error) { console.error(`❌ 获取SVN状态失败: ${error.message}`) throw error } } /** * 检查是否为SVN工作目录 * @param {string} cwd - 执行目录 * @returns {Promise<boolean>} 是否为SVN目录 */ async function isSvnWorkingDirectory (cwd) { try { execSync('svn info', { cwd, stdio: 'pipe', timeout: 5000 }) return true } catch (error) { return false } } module.exports = { executeSvn, executeSvnUpdate, executeSvnDelete, executeSvnAdd, commitToSvnWithRetry, getSvnStatus, isSvnWorkingDirectory, sanitizeCommitMessage, forceCleanupSvn, handleSvnWorkQueue, attemptFileUnlock }