UNPKG

build-deploy-tools

Version:

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

418 lines (362 loc) 11.9 kB
/** * 通用工具函数模块 * 提供延迟、重试、进度条等功能 */ // 重试配置常量(优化版) const RETRY_CONFIG = { maxRetries: 3, // 减少重试次数,提高速度 retryDelay: 1500, // 减少重试延迟 svnTimeout: 30000, // 优化超时时间 cleanupTimeout: 60000, // 减少cleanup超时时间 commitTimeout: 120000 // 优化提交超时时间 } /** * 延迟函数 * @param {number} ms - 延迟时间(毫秒) * @returns {Promise} Promise对象 */ function delay (ms) { return new Promise(resolve => setTimeout(resolve, ms)) } /** * 通用重试函数 * @param {Function} fn - 要重试的函数 * @param {number} maxRetries - 最大重试次数 * @param {number} retryDelay - 重试延迟时间 * @param {string} operationName - 操作名称(用于日志) * @returns {Promise} 执行结果 */ async function retryOperation ( fn, maxRetries = RETRY_CONFIG.maxRetries, retryDelay = RETRY_CONFIG.retryDelay, operationName = '操作' ) { let lastError let currentDelay = retryDelay for (let attempt = 1; attempt <= maxRetries + 1; attempt++) { try { console.log(`🔄 ${operationName} (第${attempt}次尝试)`) const result = await fn() if (result !== false && result !== null) { if (attempt > 1) { console.log(`✅ ${operationName}在第${attempt}次尝试后成功`) } return result } throw new Error(`${operationName}返回失败结果`) } catch (error) { lastError = error console.error( `❌ ${operationName}${attempt}次尝试失败: ${error.message}` ) if (attempt <= maxRetries) { // 优化的重试延迟策略 if (error.message && ( error.message.includes('locked') || error.message.includes('E155037') || error.message.includes('Previous operation has not finished') )) { // SVN锁定错误,使用适度延迟 currentDelay = Math.min(currentDelay * 1.2, 5000) // 减少最大延迟 console.log(`⏳ SVN锁定错误,${currentDelay / 1000}秒后重试...`) } else { console.log(`⏳ ${currentDelay / 1000}秒后重试...`) } await delay(currentDelay) } else { console.error(`❌ ${operationName}${maxRetries + 1}次尝试后仍然失败`) throw lastError } } } } /** * 创建进度条 * @param {number} duration - 进度条持续时间(毫秒) * @param {string} message - 进度条显示消息 * @returns {Object} 包含stop和setMessage方法的控制对象 */ function createProgressBar (duration, message) { const totalSteps = 50 let currentStep = 0 let progressInterval console.log(`${message}`) const updateProgress = () => { currentStep++ const percent = Math.min(100, Math.round((currentStep / totalSteps) * 100)) const filled = Math.round((currentStep / totalSteps) * 20) const empty = 20 - filled const bar = '█'.repeat(filled) + '░'.repeat(empty) process.stdout.write(`\r⏳ 进度: [${bar}] ${percent}%`) if (currentStep >= totalSteps) { currentStep = 0 // 重置,让进度条循环 } } progressInterval = setInterval(updateProgress, duration / totalSteps) return { stop: () => { clearInterval(progressInterval) process.stdout.write('\r✅ 操作完成! \n') }, setMessage: newMessage => { process.stdout.write(`\r${newMessage} \n`) } } } /** * 创建多阶段进度条 - 专为SVN提交等复杂操作设计 * @param {Array} stages - 阶段配置数组,每个阶段包含 {name, duration, emoji} * @param {string} operation - 操作名称 * @returns {Object} 包含nextStage、updateProgress、stop方法的控制对象 */ function createMultiStageProgressBar (stages, operation = '操作') { let currentStageIndex = 0 let currentStageProgress = 0 let progressInterval = null let isCompleted = false const totalStages = stages.length const barWidth = 30 // 显示初始状态 console.log(`\n🚀 开始${operation}...`) displayProgress() function displayProgress () { if (isCompleted) return const currentStage = stages[currentStageIndex] || { name: '完成', emoji: '✅' } const overallProgress = (currentStageIndex + currentStageProgress / 100) / totalStages const overallPercent = Math.round(overallProgress * 100) // 创建总体进度条 const filled = Math.round(overallProgress * barWidth) const empty = barWidth - filled const progressBar = '█'.repeat(filled) + '░'.repeat(empty) // 创建当前阶段进度条 const stageFilled = Math.round((currentStageProgress / 100) * 15) const stageEmpty = 15 - stageFilled const stageBar = '▓'.repeat(stageFilled) + '▒'.repeat(stageEmpty) // 显示进度信息 const stageInfo = `${currentStage.emoji} ${currentStage.name } [${stageBar}] ${Math.round(currentStageProgress)}%` const overallInfo = `总进度: [${progressBar}] ${overallPercent}% (${currentStageIndex + 1 }/${totalStages})` // 清除之前的输出并显示新的进度 process.stdout.write('\r\x1b[K') // 清除当前行 process.stdout.write('\r\x1b[1A\x1b[K') // 上移一行并清除 process.stdout.write(`${stageInfo}\n${overallInfo}`) } function startStageProgress () { if (progressInterval) { clearInterval(progressInterval) } currentStageProgress = 0 const currentStage = stages[currentStageIndex] if (!currentStage) return const updateInterval = currentStage.duration / 100 // 100步完成 progressInterval = setInterval(() => { if (currentStageProgress < 100) { currentStageProgress += 2 // 每次增加2% displayProgress() } }, updateInterval) } // 开始第一个阶段 if (stages.length > 0) { startStageProgress() } return { /** * 进入下一个阶段 * @param {string} customMessage - 自定义阶段完成消息 */ nextStage: (customMessage = null) => { if (isCompleted) return // 完成当前阶段 currentStageProgress = 100 displayProgress() if (customMessage) { console.log(`\n💡 ${customMessage}`) } // 移动到下一阶段 currentStageIndex++ if (currentStageIndex < totalStages) { startStageProgress() } else { // 所有阶段完成 isCompleted = true if (progressInterval) { clearInterval(progressInterval) } console.log(`\n🎉 ${operation}完成!\n`) } }, /** * 更新当前阶段进度 * @param {number} progress - 进度百分比 (0-100) * @param {string} message - 可选的状态消息 */ updateProgress: (progress, message = null) => { if (isCompleted) return currentStageProgress = Math.min(100, Math.max(0, progress)) displayProgress() if (message) { console.log(`\n💬 ${message}`) } }, /** * 停止进度条 * @param {string} message - 完成消息 */ stop: (message = '操作完成') => { isCompleted = true if (progressInterval) { clearInterval(progressInterval) } console.log(`\n✅ ${message}\n`) }, /** * 错误停止 * @param {string} error - 错误消息 */ error: error => { isCompleted = true if (progressInterval) { clearInterval(progressInterval) } console.log(`\n❌ 操作失败: ${error}\n`) } } } /** * 获取打包文件名 * 优先从npm配置获取,其次从命令行参数获取,最后使用默认值 * @returns {string} 打包文件名 */ function getFileName () { // 优先从 npm config 获取 const npm_config_build = process.env.npm_config_build if (npm_config_build) { return npm_config_build } // 从环境变量获取 const BUILD_NAME = process.env.BUILD_NAME if (BUILD_NAME) { return BUILD_NAME } // 从命令行参数获取 const args = process.argv.slice(2) const buildArg = args.find(arg => arg.startsWith('--build=')) if (buildArg) { return buildArg.split('=')[1] } return 'vam3' // 默认值 } /** * 获取目标目录 * 优先级:命令行参数 > npm_config_target > TARGET_DIR > 默认值 * @param {string} defaultValue - 默认目标目录 * @returns {string} 目标目录路径 */ function getTargetDir (defaultValue = 'D:/Work/Vue3/yiyumsaas') { // 从命令行参数获取 const args = process.argv.slice(2) const targetArg = args.find(arg => arg.startsWith('--target=')) if (targetArg) { return targetArg.split('=')[1] } // 从 npm config 获取 const npm_config_target = process.env.npm_config_target if (npm_config_target) { return npm_config_target } // 从环境变量获取 const TARGET_DIR = process.env.TARGET_DIR if (TARGET_DIR) { return TARGET_DIR } return defaultValue } /** * 获取源目录 * 优先级:命令行参数 > npm_config_source > SOURCE_DIR > 默认值 * @param {string} defaultValue - 默认源目录(相对于当前工作目录) * @returns {string} 源目录路径 */ function getSourceDir (defaultValue = null) { // 从命令行参数获取 const args = process.argv.slice(2) const sourceArg = args.find(arg => arg.startsWith('--source=')) if (sourceArg) { return sourceArg.split('=')[1] } // 从 npm config 获取 const npm_config_source = process.env.npm_config_source if (npm_config_source) { return npm_config_source } // 从环境变量获取 const SOURCE_DIR = process.env.SOURCE_DIR if (SOURCE_DIR) { return SOURCE_DIR } // 如果没有指定,返回null,让调用者使用默认逻辑 return defaultValue } /** * 解析自动化配置 * @returns {Object} 自动化配置对象 */ function getAutoConfig () { return { isAutoMode: process.env.npm_config_auto === 'true' || process.env.AUTO_MODE === 'true' || process.argv.includes('--auto') || process.env.CI === 'true', // CI环境自动启用自动模式 autoCommit: process.env.npm_config_commit_cli === 'true' || process.env.AUTO_COMMIT === 'true' || process.argv.includes('--commit'), // 自动提交到SVN useNotification: process.env.npm_config_notification !== 'false' && process.env.USE_NOTIFICATION !== 'false' && !process.argv.includes('--no-notification') // 使用通知(默认启用) } } /** * 获取所有环境变量配置 * @returns {Object} 环境变量配置对象 */ function getEnvConfig () { return { // 构建相关 buildName: getFileName(), sourceDir: getSourceDir(), targetDir: getTargetDir(), // 自动化配置 ...getAutoConfig(), // 其他环境变量 nodeEnv: process.env.NODE_ENV || 'development', ci: process.env.CI === 'true', // 提交相关 commitMessage: process.env.COMMIT_MESSAGE || null, useVcsHistory: process.env.USE_VCS_HISTORY !== 'false', // 重试配置 maxRetries: parseInt(process.env.MAX_RETRIES) || null, retryDelay: parseInt(process.env.RETRY_DELAY) || null } } module.exports = { RETRY_CONFIG, delay, retryOperation, createProgressBar, createMultiStageProgressBar, getFileName, getTargetDir, getSourceDir, getAutoConfig, getEnvConfig }