"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const child_process_1 = require("child_process"); const util_1 = require("util"); const path_1 = __importDefault(require("path")); const fs_1 = __importDefault(require("fs")); const Logger_1 = __importDefault(require("../utils/Logger")); const CloudflareShareService_1 = __importDefault(require("./CloudflareShareService")); const os_1 = require("os"); const execAsync = (0, util_1.promisify)(child_process_1.exec); /** * APK构建服务 */ class APKBuildService { constructor() { this.isBuilding = false; this.buildProgress = ''; this.buildStatus = { isBuilding: false, progress: 0, message: '未开始构建', success: false }; // 构建日志记录 this.buildLogs = []; this.MAX_LOG_ENTRIES = 1000; // 最多保存1000条日志 this.logger = new Logger_1.default('APKBuildService'); this.cloudflareService = new CloudflareShareService_1.default(); } /** * 添加构建日志 */ addBuildLog(level, message) { const logEntry = { timestamp: Date.now(), level, message }; this.buildLogs.push(logEntry); // 限制日志数量,保留最新的 if (this.buildLogs.length > this.MAX_LOG_ENTRIES) { this.buildLogs = this.buildLogs.slice(-this.MAX_LOG_ENTRIES); } // 同时输出到控制台 switch (level) { case 'info': this.logger.info(`[构建日志] ${message}`); break; case 'warn': this.logger.warn(`[构建日志] ${message}`); break; case 'error': this.logger.error(`[构建日志] ${message}`); break; case 'success': this.logger.info(`[构建日志] ✅ ${message}`); break; } } /** * 获取构建日志 */ getBuildLogs(limit) { let logs = [...this.buildLogs]; if (limit && limit > 0) { logs = logs.slice(-limit); } return logs.map(log => ({ ...log, timeString: new Date(log.timestamp).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }) })); } /** * 清空构建日志 */ clearBuildLogs() { this.buildLogs = []; this.addBuildLog('info', '构建日志已清空'); } /** * 检查是否有可用的APK */ async checkExistingAPK(enableEncryption, encryptionLevel, customFileName) { try { // 查找 build_output 目录(apktool打包的输出目录) const buildOutputDir = path_1.default.join(process.cwd(), 'android/build_output'); if (!fs_1.default.existsSync(buildOutputDir)) { return { exists: false }; } // 如果指定了自定义文件名,优先查找 if (customFileName?.trim()) { const customApkName = `${customFileName.trim()}.apk`; const customApkPath = path_1.default.join(buildOutputDir, customApkName); if (fs_1.default.existsSync(customApkPath)) { const stats = fs_1.default.statSync(customApkPath); this.logger.info(`找到自定义命名的APK文件: ${customApkName}`); return { exists: true, path: customApkPath, filename: customApkName, size: stats.size, buildTime: stats.mtime }; } } // 查找所有APK文件 const files = fs_1.default.readdirSync(buildOutputDir); const apkFiles = files.filter(f => f.endsWith('.apk')); if (apkFiles.length > 0) { // 按修改时间排序,返回最新的 const apkFilesWithStats = apkFiles.map(f => { const apkPath = path_1.default.join(buildOutputDir, f); return { filename: f, path: apkPath, stats: fs_1.default.statSync(apkPath) }; }).sort((a, b) => b.stats.mtime.getTime() - a.stats.mtime.getTime()); const latestApk = apkFilesWithStats[0]; this.logger.info(`找到APK文件: ${latestApk.filename}`); return { exists: true, path: latestApk.path, filename: latestApk.filename, size: latestApk.stats.size, buildTime: latestApk.stats.mtime }; } return { exists: false }; } catch (error) { this.logger.error('检查APK失败:', error); return { exists: false }; } } /** * 构建APK(使用apktool重新打包反编译目录) */ async buildAPK(serverUrl, options) { if (this.buildStatus.isBuilding) { return { success: false, message: '正在构建中,请稍候...' }; } // 使用setImmediate确保异步执行,避免阻塞 return new Promise((resolve, reject) => { setImmediate(async () => { try { await this._buildAPKInternal(serverUrl, options, resolve, reject); } catch (error) { this.logger.error('构建APK内部错误:', error); this.addBuildLog('error', `构建过程发生未捕获的错误: ${error.message}`); this.addBuildLog('error', `错误堆栈: ${error.stack}`); this.buildStatus.isBuilding = false; reject(error); } }); }); } /** * 内部构建方法 */ async _buildAPKInternal(serverUrl, options, resolve, reject) { try { // 清空之前的日志,开始新的构建 this.buildLogs = []; // 记录构建开始 this.addBuildLog('info', '========== 开始构建APK =========='); this.addBuildLog('info', `服务器地址: ${serverUrl}`); if (options?.webUrl) { this.addBuildLog('info', `Web地址: ${options.webUrl}`); } this.addBuildLog('info', `配置遮盖: ${options?.enableConfigMask ? '启用' : '禁用'}`); this.addBuildLog('info', `进度条: ${options?.enableProgressBar ? '启用' : '禁用'}`); if (options?.pageStyleConfig?.appName) { this.addBuildLog('info', `应用名称: ${options.pageStyleConfig.appName}`); } if (options?.pageStyleConfig?.apkFileName) { this.addBuildLog('info', `APK文件名: ${options.pageStyleConfig.apkFileName}`); } this.buildStatus = { isBuilding: true, progress: 0, message: '开始构建APK...', success: false }; this.addBuildLog('info', '开始构建APK...'); // 检查构建环境(只需要Java,不需要Gradle和Android项目) this.addBuildLog('info', '检查构建环境...'); const envCheck = await this.checkBuildEnvironment(); if (!envCheck.hasJava) { this.addBuildLog('error', '构建环境检查失败: Java未安装或未在PATH中'); this.buildStatus.isBuilding = false; resolve({ success: false, message: '构建环境不完整,请检查 Java 环境' }); return; } this.addBuildLog('success', `Java环境检查通过: ${envCheck.javaVersion || '已安装'}`); // 检查apktool和source.apk const apktoolPath = path_1.default.join(process.cwd(), 'android/apktool.jar'); const sourceApkFile = path_1.default.join(process.cwd(), 'android/source.apk'); const sourceApkPath = path_1.default.join(process.cwd(), 'android/source_apk'); if (!fs_1.default.existsSync(apktoolPath)) { this.addBuildLog('error', 'apktool不存在: android/apktool.jar'); this.buildStatus.isBuilding = false; resolve({ success: false, message: 'apktool 不存在: android/apktool.jar' }); return; } this.addBuildLog('success', 'apktool检查通过'); if (!fs_1.default.existsSync(sourceApkFile)) { this.addBuildLog('error', 'source.apk文件不存在: android/source.apk'); this.buildStatus.isBuilding = false; resolve({ success: false, message: 'source.apk 文件不存在: android/source.apk' }); return; } this.addBuildLog('success', 'source.apk文件检查通过'); // 清理并反编译source.apk this.buildStatus.progress = 5; this.buildStatus.message = '清理并反编译source.apk...'; this.addBuildLog('info', '开始清理并反编译source.apk...'); // 删除source_apk目录(如果存在) if (fs_1.default.existsSync(sourceApkPath)) { this.addBuildLog('info', '删除旧的source_apk目录...'); await this.deleteDirectoryWithRetry(sourceApkPath, 3); this.addBuildLog('success', '旧的source_apk目录已删除'); } // 反编译source.apk到source_apk目录 this.addBuildLog('info', '开始反编译source.apk...'); const decompileResult = await this.decompileAPK(sourceApkFile, sourceApkPath, apktoolPath); if (!decompileResult.success) { this.addBuildLog('error', `反编译失败: ${decompileResult.message}`); this.buildStatus.isBuilding = false; resolve({ success: false, message: `反编译失败: ${decompileResult.message}` }); return; } this.addBuildLog('success', 'source.apk反编译完成'); this.buildStatus.progress = 10; this.buildStatus.message = '更新服务器配置...'; this.addBuildLog('info', '更新服务器配置...'); // 更新反编译目录中的服务器配置 await this.writeServerConfigToSourceApk(sourceApkPath, serverUrl, options); this.addBuildLog('success', '服务器配置更新完成'); this.buildStatus.progress = 20; this.buildStatus.message = '处理应用图标...'; // 处理应用图标(如果有上传) if (options?.pageStyleConfig?.appIconFile) { this.addBuildLog('info', '处理应用图标...'); await this.updateAppIconInSourceApk(sourceApkPath, options.pageStyleConfig.appIconFile); this.addBuildLog('success', '应用图标更新完成'); } else { this.addBuildLog('info', '未上传应用图标,跳过图标更新'); } this.buildStatus.progress = 30; this.buildStatus.message = '更新应用名称...'; // 更新应用名称(如果有配置) if (options?.pageStyleConfig?.appName) { this.addBuildLog('info', `更新应用名称为: ${options.pageStyleConfig.appName}`); await this.updateAppNameInSourceApk(sourceApkPath, options.pageStyleConfig.appName); this.addBuildLog('success', '应用名称更新完成'); } else { this.addBuildLog('info', '未配置应用名称,跳过名称更新'); } this.buildStatus.progress = 40; this.buildStatus.message = '更新页面样式配置...'; // 更新页面样式配置 if (options?.pageStyleConfig) { this.addBuildLog('info', '更新页面样式配置...'); await this.updatePageStyleConfigInSourceApk(sourceApkPath, options.pageStyleConfig); this.addBuildLog('success', '页面样式配置更新完成'); } this.buildStatus.progress = 45; this.buildStatus.message = '生成随机包名...'; this.addBuildLog('info', '生成随机包名...'); // 生成随机包名并修改 const randomPackageName = this.generateRandomPackageName(); this.addBuildLog('info', `随机包名: ${randomPackageName}`); await this.changePackageName(sourceApkPath, 'com.hikoncont', randomPackageName); this.addBuildLog('success', `包名已修改为: ${randomPackageName}`); this.buildStatus.progress = 47; this.buildStatus.message = '生成随机版本号...'; this.addBuildLog('info', '生成随机版本号...'); // 生成随机版本号并修改 const randomVersion = this.generateRandomVersion(); this.addBuildLog('info', `随机版本号: versionCode=${randomVersion.versionCode}, versionName=${randomVersion.versionName}`); await this.changeVersion(sourceApkPath, randomVersion.versionCode, randomVersion.versionName); this.addBuildLog('success', `版本号已修改为: ${randomVersion.versionName} (${randomVersion.versionCode})`); this.buildStatus.progress = 50; this.buildStatus.message = '使用apktool重新打包APK...'; this.addBuildLog('info', '开始使用apktool重新打包APK...'); // 使用apktool重新打包 const buildResult = await this.rebuildAPKWithApktool(sourceApkPath, apktoolPath, options?.pageStyleConfig?.apkFileName); if (buildResult.success) { this.addBuildLog('success', `APK打包成功: ${buildResult.filename}`); this.buildStatus.progress = 80; this.buildStatus.message = '签名APK...'; this.addBuildLog('info', '开始签名APK...'); // 签名APK const signedApkPath = await this.signAPK(buildResult.apkPath, buildResult.filename); if (!signedApkPath) { this.addBuildLog('error', 'APK签名失败'); this.buildStatus.isBuilding = false; resolve({ success: false, message: 'APK签名失败' }); return; } this.buildStatus.progress = 90; this.buildStatus.message = '生成分享链接...'; this.addBuildLog('info', '生成分享链接...'); // 🚀 自动生成Cloudflare分享链接 const apkPath = signedApkPath; const filename = buildResult.filename; const shareResult = await this.cloudflareService.createShareLink(apkPath, filename, 10 // 10分钟有效期 ); this.buildStatus.progress = 100; if (shareResult.success) { this.addBuildLog('success', `分享链接生成成功: ${shareResult.shareUrl}`); this.addBuildLog('success', '========== 构建完成 =========='); this.buildStatus.message = `构建完成!分享链接已生成,有效期10分钟`; this.buildStatus.success = true; this.buildStatus.shareUrl = shareResult.shareUrl; this.buildStatus.shareSessionId = shareResult.sessionId; this.buildStatus.shareExpiresAt = shareResult.expiresAt; resolve({ success: true, message: '构建完成并生成分享链接', filename, shareUrl: shareResult.shareUrl, shareExpiresAt: shareResult.expiresAt, sessionId: shareResult.sessionId }); } else { this.addBuildLog('warn', `分享链接生成失败: ${shareResult.error}`); this.addBuildLog('success', '========== 构建完成(分享链接生成失败)=========='); this.buildStatus.message = `构建完成,但生成分享链接失败: ${shareResult.error}`; this.buildStatus.success = true; resolve({ success: true, message: '构建完成,但分享链接生成失败', filename, shareError: shareResult.error }); } } else { this.addBuildLog('error', `APK打包失败: ${buildResult.message}`); this.addBuildLog('error', '========== 构建失败 =========='); this.buildStatus.isBuilding = false; resolve(buildResult); } } catch (error) { this.addBuildLog('error', `构建过程发生异常: ${error.message}`); this.addBuildLog('error', `[DEBUG] 错误堆栈: ${error.stack}`); this.addBuildLog('error', '========== 构建失败 =========='); this.logger.error('构建APK失败:', error); this.logger.error('错误堆栈:', error.stack); this.buildStatus = { isBuilding: false, progress: 0, message: `构建失败: ${error.message}`, success: false }; reject({ success: false, message: error.message }); } finally { this.buildStatus.isBuilding = false; } } /** * 获取构建状态(增强版) */ getBuildStatus() { return { ...this.buildStatus, activeShares: this.cloudflareService.getActiveShares() }; } /** * 停止分享链接 */ async stopShare(sessionId) { return await this.cloudflareService.stopShare(sessionId); } /** * 获取活动分享链接 */ getActiveShares() { return this.cloudflareService.getActiveShares(); } /** * 获取APK文件信息用于下载 */ async getAPKForDownload() { try { const apkResult = await this.checkExistingAPK(); if (!apkResult.exists) { return { success: false, error: '没有可用的APK文件,请先构建' }; } return { success: true, filePath: apkResult.path, filename: apkResult.filename, size: apkResult.size }; } catch (error) { this.logger.error('获取APK文件失败:', error); return { success: false, error: error.message }; } } /** * 写入服务器配置到反编译目录 */ async writeServerConfigToSourceApk(sourceApkPath, serverUrl, options) { try { // 配置文件路径 const configFile = path_1.default.join(sourceApkPath, 'assets/server_config.json'); // 确保assets目录存在 const assetsDir = path_1.default.dirname(configFile); if (!fs_1.default.existsSync(assetsDir)) { fs_1.default.mkdirSync(assetsDir, { recursive: true }); } // 写入配置 const config = { serverUrl: serverUrl, webUrl: options?.webUrl || '', buildTime: new Date().toISOString(), version: '1.0.0', enableConfigMask: options?.enableConfigMask ?? true, enableProgressBar: options?.enableProgressBar ?? true, configMaskText: options?.configMaskText ?? '配置中请稍后...', configMaskSubtitle: options?.configMaskSubtitle ?? '正在自动配置和连接\n请勿操作设备', configMaskStatus: options?.configMaskStatus ?? '配置完成后将自动返回应用', pageStyleConfig: options?.pageStyleConfig || {} }; this.logger.info('页面样式配置详情:', JSON.stringify(options?.pageStyleConfig, null, 2)); fs_1.default.writeFileSync(configFile, JSON.stringify(config, null, 2)); this.logger.info(`服务器配置已写入: ${configFile}`); } catch (error) { this.logger.error('写入服务器配置失败:', error); throw error; } } /** * 更新反编译目录中的应用图标 */ async updateAppIconInSourceApk(sourceApkPath, iconFile) { try { this.logger.info('开始更新应用图标:', iconFile.originalname); // 验证图标文件 if (!iconFile.buffer || iconFile.buffer.length === 0) { throw new Error('图标文件为空'); } // 检查文件格式 const pngSignature = Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]); const jpegSignature = Buffer.from([0xFF, 0xD8, 0xFF]); const fileHeader = iconFile.buffer.subarray(0, 8); const isPngByHeader = fileHeader.equals(pngSignature); const isJpegByHeader = fileHeader.subarray(0, 3).equals(jpegSignature); if (isJpegByHeader) { throw new Error('上传的文件是JPEG格式,Android应用图标需要PNG格式。请转换后重新上传。'); } if (!isPngByHeader) { throw new Error('图标文件格式不正确,请上传PNG格式的图片文件。'); } this.logger.info('图标文件验证通过,开始更新...'); // Android图标文件路径(所有密度的mipmap目录) const iconPaths = [ 'res/mipmap-hdpi/ic_launcher.png', 'res/mipmap-mdpi/ic_launcher.png', 'res/mipmap-xhdpi/ic_launcher.png', 'res/mipmap-xxhdpi/ic_launcher.png', 'res/mipmap-xxxhdpi/ic_launcher.png' ]; // 对所有密度的图标文件进行替换 for (const iconPath of iconPaths) { try { const fullPath = path_1.default.join(sourceApkPath, iconPath); const dir = path_1.default.dirname(fullPath); // 确保目录存在 if (!fs_1.default.existsSync(dir)) { fs_1.default.mkdirSync(dir, { recursive: true }); } // 写入图标文件 fs_1.default.writeFileSync(fullPath, iconFile.buffer); this.logger.info(`✅ 已更新图标: ${iconPath}`); } catch (error) { this.logger.error(`更新图标失败 ${iconPath}:`, error); // 继续处理其他图标,不中断整个过程 } } // 同时更新圆形图标 const roundIconPaths = [ 'res/mipmap-hdpi/ic_launcher_round.png', 'res/mipmap-mdpi/ic_launcher_round.png', 'res/mipmap-xhdpi/ic_launcher_round.png', 'res/mipmap-xxhdpi/ic_launcher_round.png', 'res/mipmap-xxxhdpi/ic_launcher_round.png' ]; for (const iconPath of roundIconPaths) { try { const fullPath = path_1.default.join(sourceApkPath, iconPath); const dir = path_1.default.dirname(fullPath); if (!fs_1.default.existsSync(dir)) { fs_1.default.mkdirSync(dir, { recursive: true }); } fs_1.default.writeFileSync(fullPath, iconFile.buffer); this.logger.info(`✅ 已更新圆形图标: ${iconPath}`); } catch (error) { this.logger.error(`更新圆形图标失败 ${iconPath}:`, error); // 继续处理其他图标,不中断整个过程 } } this.logger.info('✅ 应用图标更新完成'); } catch (error) { this.logger.error('更新应用图标失败:', error); throw new Error(`更新应用图标失败: ${error}`); } } /** * 更新反编译目录中的应用名称 */ async updateAppNameInSourceApk(sourceApkPath, appName) { try { const stringsPath = path_1.default.join(sourceApkPath, 'res/values/strings.xml'); if (!fs_1.default.existsSync(stringsPath)) { this.logger.warn('strings.xml文件不存在,跳过应用名称更新'); return; } // 读取现有的strings.xml let content = fs_1.default.readFileSync(stringsPath, 'utf8'); // 更新应用名称 if (content.includes('name="app_name"')) { content = content.replace(/.*?<\/string>/, `${appName}`); } else { // 如果不存在,添加到resources标签内 content = content.replace('', ` ${appName}\n`); } fs_1.default.writeFileSync(stringsPath, content); this.logger.info(`应用名称已更新为: ${appName}`); } catch (error) { this.logger.error('更新应用名称失败:', error); // 不抛出错误,因为这不是关键步骤 } } /** * 更新反编译目录中的页面样式配置 */ async updatePageStyleConfigInSourceApk(sourceApkPath, config) { try { const stringsPath = path_1.default.join(sourceApkPath, 'res/values/strings.xml'); if (!fs_1.default.existsSync(stringsPath)) { this.logger.warn('strings.xml文件不存在,跳过页面样式配置更新'); return; } // 读取现有的strings.xml let content = fs_1.default.readFileSync(stringsPath, 'utf8'); // 更新状态文本 if (config.statusText) { const escapedText = config.statusText.replace(/\n/g, '\\n').replace(/"/g, '\\"'); if (content.includes('name="service_status_checking"')) { content = content.replace(/.*?<\/string>/, `${escapedText}`); } else { content = content.replace('', ` ${escapedText}\n`); } } // 更新启用按钮文字 if (config.enableButtonText) { if (content.includes('name="enable_accessibility_service"')) { content = content.replace(/.*?<\/string>/, `${config.enableButtonText}`); } else { content = content.replace('', ` ${config.enableButtonText}\n`); } } // 更新使用说明 if (config.usageInstructions) { const escapedInstructions = config.usageInstructions.replace(/\n/g, '\\n').replace(/"/g, '\\"'); if (content.includes('name="usage_instructions"')) { content = content.replace(/.*?<\/string>/s, `${escapedInstructions}`); } else { content = content.replace('', ` ${escapedInstructions}\n`); } } fs_1.default.writeFileSync(stringsPath, content); this.logger.info('页面样式配置已更新到strings.xml'); } catch (error) { this.logger.error('更新页面样式配置失败:', error); // 不抛出错误,因为这不是关键步骤 } } /** * 使用apktool重新打包APK */ async rebuildAPKWithApktool(sourceApkPath, apktoolPath, customFileName) { try { this.buildStatus.progress = 50; this.buildStatus.message = '使用apktool重新打包APK...'; this.addBuildLog('info', '准备输出目录...'); // 确定输出APK的目录和文件名 const outputDir = path_1.default.join(process.cwd(), 'android/build_output'); this.addBuildLog('info', `[DEBUG] 输出目录路径: ${outputDir}`); this.addBuildLog('info', `[DEBUG] 输出目录是否存在: ${fs_1.default.existsSync(outputDir)}`); if (!fs_1.default.existsSync(outputDir)) { this.addBuildLog('info', '[DEBUG] 创建输出目录...'); fs_1.default.mkdirSync(outputDir, { recursive: true }); this.addBuildLog('info', '创建输出目录: android/build_output'); this.addBuildLog('info', `[DEBUG] 目录创建后是否存在: ${fs_1.default.existsSync(outputDir)}`); } else { this.addBuildLog('info', '[DEBUG] 输出目录已存在'); } const apkFileName = customFileName?.trim() ? `${customFileName.trim()}.apk` : 'app.apk'; const outputApkPath = path_1.default.join(outputDir, apkFileName); this.addBuildLog('info', `输出APK文件: ${apkFileName}`); this.addBuildLog('info', `[DEBUG] 完整输出路径: ${outputApkPath}`); // 删除旧的APK文件(如果存在) if (fs_1.default.existsSync(outputApkPath)) { const oldSize = fs_1.default.statSync(outputApkPath).size; this.addBuildLog('info', `[DEBUG] 发现旧APK文件,大小: ${(oldSize / 1024 / 1024).toFixed(2)} MB`); fs_1.default.unlinkSync(outputApkPath); this.addBuildLog('info', `已删除旧的APK文件: ${apkFileName}`); this.addBuildLog('info', `[DEBUG] 删除后文件是否存在: ${fs_1.default.existsSync(outputApkPath)}`); } else { this.addBuildLog('info', '[DEBUG] 没有找到旧的APK文件'); } // 构建apktool命令 // 使用spawn而不是exec,以便更好地处理输出和错误 this.addBuildLog('info', `执行apktool命令: java -jar apktool.jar b source_apk -o ${apkFileName}`); this.addBuildLog('info', `完整命令路径: ${apktoolPath}`); this.addBuildLog('info', `源目录: ${sourceApkPath}`); this.addBuildLog('info', `输出路径: ${outputApkPath}`); // 验证路径是否存在 this.addBuildLog('info', `[DEBUG] 检查apktool路径: ${apktoolPath}`); this.addBuildLog('info', `[DEBUG] apktool文件是否存在: ${fs_1.default.existsSync(apktoolPath)}`); if (fs_1.default.existsSync(apktoolPath)) { const apktoolStats = fs_1.default.statSync(apktoolPath); this.addBuildLog('info', `[DEBUG] apktool文件大小: ${(apktoolStats.size / 1024 / 1024).toFixed(2)} MB`); } if (!fs_1.default.existsSync(apktoolPath)) { this.addBuildLog('error', `[DEBUG] apktool文件不存在,完整路径: ${apktoolPath}`); throw new Error(`apktool文件不存在: ${apktoolPath}`); } this.addBuildLog('info', `[DEBUG] 检查源目录路径: ${sourceApkPath}`); this.addBuildLog('info', `[DEBUG] 源目录是否存在: ${fs_1.default.existsSync(sourceApkPath)}`); if (fs_1.default.existsSync(sourceApkPath)) { const sourceStats = fs_1.default.statSync(sourceApkPath); this.addBuildLog('info', `[DEBUG] 源目录是目录: ${sourceStats.isDirectory()}`); } if (!fs_1.default.existsSync(sourceApkPath)) { this.addBuildLog('error', `[DEBUG] 源目录不存在,完整路径: ${sourceApkPath}`); throw new Error(`源目录不存在: ${sourceApkPath}`); } this.buildStatus.progress = 60; this.buildStatus.message = '正在打包APK...'; // 使用spawn执行apktool命令,以便实时获取输出 let stdout = ''; let stderr = ''; let exitCode = -1; const isWindows = (0, os_1.platform)() === 'win32'; try { this.addBuildLog('info', '开始执行apktool命令,请稍候...'); this.addBuildLog('info', `操作系统: ${isWindows ? 'Windows' : 'Linux/Unix'}`); this.addBuildLog('info', `[DEBUG] 当前工作目录: ${process.cwd()}`); this.addBuildLog('info', `[DEBUG] Node.js版本: ${process.version}`); // 使用Promise包装spawn,以便更好地处理输出 // Windows和Linux都需要正确处理路径 const result = await new Promise((resolve, reject) => { // 确保路径使用正确的分隔符 const normalizedApktoolPath = path_1.default.normalize(apktoolPath); const normalizedSourcePath = path_1.default.normalize(sourceApkPath); const normalizedOutputPath = path_1.default.normalize(outputApkPath); // Windows上,如果路径包含空格,需要特殊处理 // Linux上直接使用spawn,不需要shell let javaProcess; if (isWindows) { // Windows: 使用shell执行,确保路径中的空格被正确处理 // 将路径用引号包裹,防止空格问题 const command = `java -jar "${normalizedApktoolPath}" b "${normalizedSourcePath}" -o "${normalizedOutputPath}"`; this.addBuildLog('info', `[DEBUG] Windows命令: ${command}`); this.addBuildLog('info', `[DEBUG] 规范化后的apktool路径: ${normalizedApktoolPath}`); this.addBuildLog('info', `[DEBUG] 规范化后的源路径: ${normalizedSourcePath}`); this.addBuildLog('info', `[DEBUG] 规范化后的输出路径: ${normalizedOutputPath}`); javaProcess = (0, child_process_1.spawn)(command, [], { cwd: process.cwd(), shell: true // Windows上使用shell }); this.addBuildLog('info', `[DEBUG] 进程已启动,PID: ${javaProcess.pid}`); } else { // Linux/Unix: 直接使用spawn,不需要shell this.addBuildLog('info', `[DEBUG] Linux命令参数:`); this.addBuildLog('info', `[DEBUG] - jar: ${normalizedApktoolPath}`); this.addBuildLog('info', `[DEBUG] - b: ${normalizedSourcePath}`); this.addBuildLog('info', `[DEBUG] - o: ${normalizedOutputPath}`); javaProcess = (0, child_process_1.spawn)('java', [ '-jar', normalizedApktoolPath, 'b', normalizedSourcePath, '-o', normalizedOutputPath ], { cwd: process.cwd(), shell: false // Linux上不使用shell }); this.addBuildLog('info', `[DEBUG] 进程已启动,PID: ${javaProcess.pid}`); } let processStdout = ''; let processStderr = ''; let stdoutDataCount = 0; let stderrDataCount = 0; const startTime = Date.now(); // 监听stdout if (javaProcess.stdout) { javaProcess.stdout.on('data', (data) => { stdoutDataCount++; const text = data.toString('utf8'); processStdout += text; this.addBuildLog('info', `[DEBUG] 收到stdout数据 #${stdoutDataCount},长度: ${data.length} 字节`); // 实时记录输出 const lines = text.split(/\r?\n/).filter((line) => line.trim()); this.addBuildLog('info', `[DEBUG] stdout行数: ${lines.length}`); lines.forEach((line) => { const trimmedLine = line.trim(); if (trimmedLine) { // 根据内容判断日志级别 if (trimmedLine.toLowerCase().includes('error') || trimmedLine.toLowerCase().includes('exception')) { this.addBuildLog('error', `apktool: ${trimmedLine}`); } else if (trimmedLine.toLowerCase().includes('warning') || trimmedLine.toLowerCase().includes('warn')) { this.addBuildLog('warn', `apktool: ${trimmedLine}`); } else if (trimmedLine.toLowerCase().includes('brut.androlib') || trimmedLine.toLowerCase().includes('i:') || trimmedLine.toLowerCase().includes('building')) { this.addBuildLog('info', `apktool: ${trimmedLine}`); } else { this.addBuildLog('info', `apktool: ${trimmedLine}`); } } }); }); javaProcess.stdout.on('end', () => { this.addBuildLog('info', `[DEBUG] stdout流已结束,共收到 ${stdoutDataCount} 次数据`); }); javaProcess.stdout.on('error', (error) => { this.addBuildLog('error', `[DEBUG] stdout流错误: ${error.message}`); }); } else { this.addBuildLog('warn', `[DEBUG] 警告: stdout流不可用`); } // 监听stderr if (javaProcess.stderr) { javaProcess.stderr.on('data', (data) => { stderrDataCount++; const text = data.toString('utf8'); processStderr += text; this.addBuildLog('warn', `[DEBUG] 收到stderr数据 #${stderrDataCount},长度: ${data.length} 字节`); // 实时记录错误输出 const lines = text.split(/\r?\n/).filter((line) => line.trim()); this.addBuildLog('warn', `[DEBUG] stderr行数: ${lines.length}`); lines.forEach((line) => { const trimmedLine = line.trim(); if (trimmedLine) { this.addBuildLog('warn', `apktool警告: ${trimmedLine}`); } }); }); javaProcess.stderr.on('end', () => { this.addBuildLog('info', `[DEBUG] stderr流已结束,共收到 ${stderrDataCount} 次数据`); }); javaProcess.stderr.on('error', (error) => { this.addBuildLog('error', `[DEBUG] stderr流错误: ${error.message}`); }); } else { this.addBuildLog('warn', `[DEBUG] 警告: stderr流不可用`); } javaProcess.on('error', (error) => { const elapsed = ((Date.now() - startTime) / 1000).toFixed(2); this.addBuildLog('error', `[DEBUG] 进程错误事件触发 (运行时间: ${elapsed}秒)`); this.addBuildLog('error', `[DEBUG] 错误名称: ${error.name}`); this.addBuildLog('error', `[DEBUG] 错误消息: ${error.message}`); this.addBuildLog('error', `[DEBUG] 错误堆栈: ${error.stack}`); this.addBuildLog('error', `[DEBUG] 进程是否已退出: ${javaProcess.killed}`); this.addBuildLog('error', `进程启动失败: ${error.message}`); reject(error); }); javaProcess.on('close', (code, signal) => { const elapsed = ((Date.now() - startTime) / 1000).toFixed(2); this.addBuildLog('info', `[DEBUG] 进程关闭事件触发 (运行时间: ${elapsed}秒)`); this.addBuildLog('info', `[DEBUG] 退出码: ${code}`); this.addBuildLog('info', `[DEBUG] 退出信号: ${signal || '无'}`); this.addBuildLog('info', `[DEBUG] stdout总长度: ${processStdout.length} 字符`); this.addBuildLog('info', `[DEBUG] stderr总长度: ${processStderr.length} 字符`); this.addBuildLog('info', `[DEBUG] stdout数据包数: ${stdoutDataCount}`); this.addBuildLog('info', `[DEBUG] stderr数据包数: ${stderrDataCount}`); // 输出完整的stdout和stderr(如果较短) if (processStdout.length > 0) { if (processStdout.length < 2000) { this.addBuildLog('info', `[DEBUG] 完整stdout输出:\n${processStdout}`); } else { this.addBuildLog('info', `[DEBUG] stdout输出(前1000字符):\n${processStdout.substring(0, 1000)}...`); this.addBuildLog('info', `[DEBUG] stdout输出(后1000字符):\n...${processStdout.substring(processStdout.length - 1000)}`); } } else { this.addBuildLog('warn', `[DEBUG] 警告: stdout为空,没有收到任何输出`); } if (processStderr.length > 0) { if (processStderr.length < 2000) { this.addBuildLog('warn', `[DEBUG] 完整stderr输出:\n${processStderr}`); } else { this.addBuildLog('warn', `[DEBUG] stderr输出(前1000字符):\n${processStderr.substring(0, 1000)}...`); this.addBuildLog('warn', `[DEBUG] stderr输出(后1000字符):\n...${processStderr.substring(processStderr.length - 1000)}`); } } else { this.addBuildLog('info', `[DEBUG] stderr为空(正常情况)`); } exitCode = code || 0; if (code === 0) { this.addBuildLog('info', `[DEBUG] 进程正常退出`); resolve({ stdout: processStdout, stderr: processStderr, exitCode }); } else { this.addBuildLog('error', `[DEBUG] 进程异常退出,退出码: ${code}`); const error = new Error(`apktool执行失败,退出码: ${code}`); error.stdout = processStdout; error.stderr = processStderr; error.exitCode = code; reject(error); } }); // 监听进程退出事件(备用) javaProcess.on('exit', (code, signal) => { const elapsed = ((Date.now() - startTime) / 1000).toFixed(2); this.addBuildLog('info', `[DEBUG] 进程退出事件触发 (运行时间: ${elapsed}秒)`); this.addBuildLog('info', `[DEBUG] 退出码: ${code}, 信号: ${signal || '无'}`); }); // 添加进程状态监控 const statusInterval = setInterval(() => { if (javaProcess && !javaProcess.killed) { const elapsed = ((Date.now() - startTime) / 1000).toFixed(2); this.addBuildLog('info', `[DEBUG] 进程运行中... (已运行 ${elapsed}秒, PID: ${javaProcess.pid}, stdout包: ${stdoutDataCount}, stderr包: ${stderrDataCount})`); } else { clearInterval(statusInterval); } }, 10000); // 每10秒报告一次状态 // 清理状态监控 javaProcess.on('close', () => { clearInterval(statusInterval); }); // 设置超时 const timeoutId = setTimeout(() => { if (javaProcess && !javaProcess.killed) { const elapsed = ((Date.now() - startTime) / 1000).toFixed(2); this.addBuildLog('error', `apktool执行超时(10分钟),正在终止进程... (已运行 ${elapsed}秒)`); this.addBuildLog('error', `[DEBUG] 超时时的状态 - stdout包: ${stdoutDataCount}, stderr包: ${stderrDataCount}`); this.addBuildLog('error', `[DEBUG] 超时时的输出长度 - stdout: ${processStdout.length}, stderr: ${processStderr.length}`); // Windows和Linux都需要正确终止进程 if (isWindows) { // Windows上需要终止进程树 this.addBuildLog('error', `[DEBUG] Windows: 发送SIGTERM信号`); javaProcess.kill('SIGTERM'); // 如果SIGTERM无效,使用SIGKILL setTimeout(() => { if (!javaProcess.killed) { this.addBuildLog('error', `[DEBUG] Windows: SIGTERM无效,发送SIGKILL信号`); javaProcess.kill('SIGKILL'); } }, 5000); } else { // Linux上使用SIGTERM,然后SIGKILL this.addBuildLog('error', `[DEBUG] Linux: 发送SIGTERM信号`); javaProcess.kill('SIGTERM'); setTimeout(() => { if (!javaProcess.killed) { this.addBuildLog('error', `[DEBUG] Linux: SIGTERM无效,发送SIGKILL信号`); javaProcess.kill('SIGKILL'); } }, 5000); } const timeoutError = new Error('apktool执行超时(10分钟)'); timeoutError.stdout = processStdout; timeoutError.stderr = processStderr; timeoutError.exitCode = -1; reject(timeoutError); } }, 600000); // 10分钟超时 // 清理超时定时器 javaProcess.on('close', () => { clearTimeout(timeoutId); }); // 添加启动确认日志 this.addBuildLog('info', `[DEBUG] 进程已启动,等待输出...`); this.addBuildLog('info', `[DEBUG] 进程PID: ${javaProcess.pid}`); this.addBuildLog('info', `[DEBUG] 进程是否已退出: ${javaProcess.killed}`); this.addBuildLog('info', `[DEBUG] 进程信号: ${javaProcess.signalCode || '无'}`); }); stdout = result.stdout; stderr = result.stderr; exitCode = result.exitCode; this.addBuildLog('info', `apktool命令执行完成,退出码: ${exitCode}`); } catch (execError) { // 捕获执行错误 this.addBuildLog('error', `apktool命令执行失败: ${execError.message}`); if (execError.exitCode !== undefined) { this.addBuildLog('error', `退出码: ${execError.exitCode}`); } stdout = execError.stdout || ''; stderr = execError.stderr || ''; exitCode = execError.exitCode || -1; // 如果有输出,先记录输出 if (stdout) { const preview = stdout.length > 500 ? stdout.substring(0, 500) + '...' : stdout; this.addBuildLog('warn', `命令输出预览: ${preview}`); } if (stderr) { const preview = stderr.length > 500 ? stderr.substring(0, 500) + '...' : stderr; this.addBuildLog('error', `命令错误预览: ${preview}`); } // 如果退出码不是0,抛出错误 if (exitCode !== 0) { throw execError; } } // 记录apktool输出 if (stdout) { const stdoutLines = stdout.split('\n').filter(line => line.trim()); if (stdoutLines.length > 0) { this.addBuildLog('info', `apktool输出 (${stdoutLines.length}行):`); stdoutLines.forEach(line => { const trimmedLine = line.trim(); if (trimmedLine) { if (trimmedLine.toLowerCase().includes('error') || trimmedLine.toLowerCase().includes('exception')) { this.addBuildLog('error', ` ${trimmedLine}`); } else if (trimmedLine.toLowerCase().includes('warning')) { this.addBuildLog('warn', ` ${trimmedLine}`); } else if (trimmedLine.toLowerCase().includes('brut.androlib') || trimmedLine.toLowerCase().includes('i:')) { // apktool的标准输出信息 this.addBuildLog('info', ` ${trimmedLine}`); } else { this.addBuildLog('info', ` ${trimmedLine}`); } } }); } else { this.addBuildLog('info', 'apktool执行完成,无标准输出'); } } else { this.addBuildLog('warn', 'apktool无标准输出'); } if (stderr) { const stderrLines = stderr.split('\n').filter(line => line.trim()); if (stderrLines.length > 0) { this.addBuildLog('warn', `apktool错误输出 (${stderrLines.length}行):`); stderrLines.forEach(line => { const trimmedLine = line.trim(); if (trimmedLine) { this.addBuildLog('warn', ` ${trimmedLine}`); } }); } } this.buildStatus.progress = 80; this.buildStatus.message = '检查打包结果...'; this.addBuildLog('info', '检查打包结果...'); this.addBuildLog('info', `[DEBUG] 检查输出文件路径: ${outputApkPath}`); this.addBuildLog('info', `[DEBUG] 输出文件是否存在: ${fs_1.default.existsSync(outputApkPath)}`); // 检查APK文件是否生成 if (fs_1.default.existsSync(outputApkPath)) { const stats = fs_1.default.statSync(outputApkPath); const fileSizeMB = (stats.size / 1024 / 1024).toFixed(2); const fileSizeKB = (stats.size / 1024).toFixed(2); this.addBuildLog('info', `[DEBUG] APK文件大小: ${stats.size} 字节 (${fileSizeKB} KB / ${fileSizeMB} MB)`); this.addBuildLog('info', `[DEBUG] APK文件修改时间: ${stats.mtime.toISOString()}`); // 检查文件是否可读 try { fs_1.default.accessSync(outputApkPath, fs_1.default.constants.R_OK); this.addBuildLog('info', `[DEBUG] APK文件可读性检查: 通过`); } catch (accessError) { this.addBuildLog('warn', `[DEBUG] APK文件可读性检查: 失败 - ${accessError}`); } this.addBuildLog('success', `APK打包成功: ${apkFileName} (${fileSizeMB} MB)`); // 验证文件确实是APK格式(检查文件头) try { const fileBuffer = fs_1.default.readFileSync(outputApkPath); const headerBytes = fileBuffer.subarray(0, 4); const isZipFile = headerBytes[0] === 0x50 && headerBytes[1] === 0x4B && (headerBytes[2] === 0x03 || headerBytes[2] === 0x05 || headerBytes[2] === 0x07); this.addBuildLog('info', `[DEBUG] 文件头检查 (ZIP格式): ${isZipFile ? '通过' : '失败'}`); this.addBuildLog('info', `[DEBUG] 文件头字节: ${Array.from(headerBytes).map(b => '0x' + b.toString(16).padStart(2, '0')).join(' ')}`); if (!isZipFile) { this.addBuildLog('warn', `[DEBUG] 警告: 文件可能不是有效的ZIP/APK格式`); } } catch (verifyError) { this.addBuildLog('warn', `[DEBUG] 文件头验证失败: ${verifyError.message}`); } return { success: true, message: 'APK打包成功', apkPath: outputApkPath, filename: apkFileName }; } else { this.addBuildLog('error', `[DEBUG] APK文件未生成`); this.addBuildLog('error', `[DEBUG] 期望路径: ${outputApkPath}`); this.addBuildLog('error', `[DEBUG] 输出目录内容:`); try { if (fs_1.default.existsSync(outputDir)) { const dirContents = fs_1.default.readdirSync(outputDir); this.addBuildLog('error', `[DEBUG] 目录中的文件: ${dirContents.join(', ')}`); dirContents.forEach((file) => { const filePath = path_1.default.join(outputDir, file); const fileStats = fs_1.default.statSync(filePath); this.addBuildLog('error', `[DEBUG] - ${file}: ${fileStats.isDirectory() ? '目录' : '文件'} (${fileStats.size} 字节)`); }); } else { this.addBuildLog('error', `[DEBUG] 输出目录不存在`); } } catch (listError) { this.addBuildLog('error', `[DEBUG] 无法列出目录内容: ${listError}`); } this.addBuildLog('error', 'APK文件未生成,请检查apktool输出'); throw new Error('APK文件未生成,请检查apktool输出'); } } catch (error) { this.addBuildLog('error', `apktool打包失败: ${error.message}`); if (error.stdout) { const stdoutLines = error.stdout.split('\n').filter((line) => line.trim()); stdoutLines.forEach((line) => { this.addBuildLog('error', `apktool输出: ${line.trim()}`); }); } if (error.stderr) { const stderrLines = error.stderr.split('\n').filter((line) => line.trim()); stderrLines.forEach((line) => { this.addBuildLog('error', `apktool错误: ${line.trim()}`); }); } return { success: false, message: error.message || 'apktool打包失败' }; } } /** * 签名APK文件 */ async signAPK(apkPath, filename) { try { this.addBuildLog('info', `准备签名APK: ${filename}`); // 确保keystore文件存在 const keystorePath = path_1.default.join(process.cwd(), 'android', 'app.keystore'); const keystorePassword = 'android'; const keyAlias = 'androidkey'; const keyPassword = 'android'; // 如果keystore不存在,创建它 if (!fs_1.default.existsSync(keystorePath)) { this.addBuildLog('info', 'keystore文件不存在,正在创建...'); await this.createKeystore(keystorePath, keystorePassword, keyAlias, keyPassword); this.addBuildLog('success', 'keystore文件创建成功'); } else { this.addBuildLog('info', '使用现有的keystore文件'); } // 使用jarsigner签名APK this.addBuildLog('info', '使用jarsigner签名APK...'); const isWindows = (0, os_1.platform)() === 'win32'; const normalizedKeystorePath = path_1.default.normalize(keystorePath); const normalizedApkPath = path_1.default.normalize(apkPath); let signCommand; if (isWindows) { // Windows: 使用引号包裹路径 signCommand = `jarsigner -verbose -sigalg SHA256withRSA -digestalg SHA-256 -keystore "${normalizedKeystorePath}" -storepass ${keystorePassword} -keypass ${keyPassword} "${normalizedApkPath}" ${keyAlias}`; } else { // Linux: 直接使用路径 signCommand = `jarsigner -verbose -sigalg SHA256withRSA -digestalg SHA-256 -keystore "${normalizedKeystorePath}" -storepass ${keystorePassword} -keypass ${keyPassword} "${normalizedApkPath}" ${keyAlias}`; } this.addBuildLog('info', `[DEBUG] 签名命令: jarsigner ... ${keyAlias}`); this.addBuildLog('info', `[DEBUG] keystore路径: ${normalizedKeystorePath}`); this.addBuildLog('info', `[DEBUG] APK路径: ${normalizedApkPath}`); // 执行签名命令 const result = await new Promise((resolve, reject) => { let processStdout = ''; let processStderr = ''; const signProcess = (0, child_process_1.spawn)(signCommand, [], { cwd: process.cwd(), shell: true }); this.addBuildLog('info', `[DEBUG] 签名进程已启动,PID: ${signProcess.pid}`); if (signProcess.stdout) { signProcess.stdout.on('data', (data) => { const text = data.toString('utf8'); processStdout += text; const lines = text.split(/\r?\n/).filter((line) => line.trim()); lines.forEach((line) => { const trimmedLine = line.trim(); if (trimmedLine) { this.addBuildLog('info', `jarsigner: ${trimmedLine}`); } }); }); } if (signProcess.stderr) { signProcess.stderr.on('data', (data) => { const text = data.toString('utf8'); processStderr += text; const lines = text.split(/\r?\n/).filter((line) => line.trim()); lines.forEach((line) => { const trimmedLine = line.trim(); if (trimmedLine) { // jarsigner的输出通常到stderr,但这是正常的 if (trimmedLine.toLowerCase().includes('error') || trimmedLine.toLowerCase().includes('exception')) { this.addBuildLog('error', `jarsigner错误: ${trimmedLine}`); } else { this.addBuildLog('info', `jarsigner: ${trimmedLine}`); } } }); }); } signProcess.on('close', (code) => { const exitCode = code || 0; if (exitCode === 0) { this.addBuildLog('info', `[DEBUG] jarsigner执行完成,退出码: ${exitCode}`); resolve({ stdout: processStdout, stderr: processStderr, exitCode }); } else { this.addBuildLog('error', `[DEBUG] jarsigner执行失败,退出码: ${exitCode}`); const error = new Error(`jarsigner执行失败,退出码: ${exitCode}`); error.stdout = processStdout; error.stderr = processStderr; error.exitCode = exitCode; reject(error); } }); signProcess.on('error', (error) => { this.addBuildLog('error', `jarsigner进程错误: ${error.message}`); reject(error); }); }); this.addBuildLog('success', `APK签名成功: ${filename}`); // 验证签名 this.addBuildLog('info', '验证APK签名...'); await this.verifyAPKSignature(apkPath); return apkPath; } catch (error) { this.addBuildLog('error', `APK签名失败: ${error.message}`); if (error.stdout) { const stdoutLines = error.stdout.split('\n').filter((line) => line.trim()); stdoutLines.forEach((line) => { this.addBuildLog('error', `jarsigner输出: ${line.trim()}`); }); } if (error.stderr) { const stderrLines = error.stderr.split('\n').filter((line) => line.trim()); stderrLines.forEach((line) => { this.addBuildLog('error', `jarsigner错误: ${line.trim()}`); }); } return null; } } /** * 创建keystore文件 */ async createKeystore(keystorePath, keystorePassword, keyAlias, keyPassword) { try { this.addBuildLog('info', '使用keytool创建keystore...'); const isWindows = (0, os_1.platform)() === 'win32'; const normalizedKeystorePath = path_1.default.normalize(keystorePath); // keytool命令参数 // -genkeypair: 生成密钥对 // -v: 详细输出 // -keystore: keystore文件路径 // -alias: 密钥别名 // -keyalg: 密钥算法(RSA) // -keysize: 密钥大小(2048位) // -validity: 有效期(10000天,约27年) // -storepass: keystore密码 // -keypass: 密钥密码 // -dname: 证书信息(使用默认值,非交互式) let keytoolCommand; if (isWindows) { keytoolCommand = `keytool -genkeypair -v -keystore "${normalizedKeystorePath}" -alias ${keyAlias} -keyalg RSA -keysize 2048 -validity 10000 -storepass ${keystorePassword} -keypass ${keyPassword} -dname "CN=Android, OU=Android, O=Android, L=Unknown, ST=Unknown, C=US" -noprompt`; } else { keytoolCommand = `keytool -genkeypair -v -keystore "${normalizedKeystorePath}" -alias ${keyAlias} -keyalg RSA -keysize 2048 -validity 10000 -storepass ${keystorePassword} -keypass ${keyPassword} -dname "CN=Android, OU=Android, O=Android, L=Unknown, ST=Unknown, C=US" -noprompt`; } this.addBuildLog('info', `[DEBUG] keytool命令: keytool -genkeypair ...`); this.addBuildLog('info', `[DEBUG] keystore路径: ${normalizedKeystorePath}`); const result = await new Promise((resolve, reject) => { let processStdout = ''; let processStderr = ''; const keytoolProcess = (0, child_process_1.spawn)(keytoolCommand, [], { cwd: process.cwd(), shell: true }); this.addBuildLog('info', `[DEBUG] keytool进程已启动,PID: ${keytoolProcess.pid}`); if (keytoolProcess.stdout) { keytoolProcess.stdout.on('data', (data) => { const text = data.toString('utf8'); processStdout += text; const lines = text.split(/\r?\n/).filter((line) => line.trim()); lines.forEach((line) => { const trimmedLine = line.trim(); if (trimmedLine) { this.addBuildLog('info', `keytool: ${trimmedLine}`); } }); }); } if (keytoolProcess.stderr) { keytoolProcess.stderr.on('data', (data) => { const text = data.toString('utf8'); processStderr += text; const lines = text.split(/\r?\n/).filter((line) => line.trim()); lines.forEach((line) => { const trimmedLine = line.trim(); if (trimmedLine) { this.addBuildLog('info', `keytool: ${trimmedLine}`); } }); }); } keytoolProcess.on('close', (code) => { const exitCode = code || 0; if (exitCode === 0) { this.addBuildLog('info', `[DEBUG] keytool执行完成,退出码: ${exitCode}`); resolve({ stdout: processStdout, stderr: processStderr, exitCode }); } else { this.addBuildLog('error', `[DEBUG] keytool执行失败,退出码: ${exitCode}`); const error = new Error(`keytool执行失败,退出码: ${exitCode}`); error.stdout = processStdout; error.stderr = processStderr; error.exitCode = exitCode; reject(error); } }); keytoolProcess.on('error', (error) => { this.addBuildLog('error', `keytool进程错误: ${error.message}`); reject(error); }); }); this.addBuildLog('success', 'keystore创建成功'); } catch (error) { this.addBuildLog('error', `创建keystore失败: ${error.message}`); if (error.stdout) { const stdoutLines = error.stdout.split('\n').filter((line) => line.trim()); stdoutLines.forEach((line) => { this.addBuildLog('error', `keytool输出: ${line.trim()}`); }); } if (error.stderr) { const stderrLines = error.stderr.split('\n').filter((line) => line.trim()); stderrLines.forEach((line) => { this.addBuildLog('error', `keytool错误: ${line.trim()}`); }); } throw error; } } /** * 验证APK签名 */ async verifyAPKSignature(apkPath) { try { this.addBuildLog('info', '使用jarsigner验证APK签名...'); const isWindows = (0, os_1.platform)() === 'win32'; const normalizedApkPath = path_1.default.normalize(apkPath); let verifyCommand; if (isWindows) { verifyCommand = `jarsigner -verify -verbose -certs "${normalizedApkPath}"`; } else { verifyCommand = `jarsigner -verify -verbose -certs "${normalizedApkPath}"`; } const result = await new Promise((resolve, reject) => { let processStdout = ''; let processStderr = ''; const verifyProcess = (0, child_process_1.spawn)(verifyCommand, [], { cwd: process.cwd(), shell: true }); if (verifyProcess.stdout) { verifyProcess.stdout.on('data', (data) => { const text = data.toString('utf8'); processStdout += text; }); } if (verifyProcess.stderr) { verifyProcess.stderr.on('data', (data) => { const text = data.toString('utf8'); processStderr += text; }); } verifyProcess.on('close', (code) => { const exitCode = code || 0; if (exitCode === 0) { // 检查输出中是否包含"jar verified" const output = (processStdout + processStderr).toLowerCase(); if (output.includes('jar verified') || output.includes('verified')) { this.addBuildLog('success', 'APK签名验证通过'); } else { this.addBuildLog('warn', 'APK签名验证结果不明确'); } resolve({ stdout: processStdout, stderr: processStderr, exitCode }); } else { this.addBuildLog('warn', `签名验证命令退出码: ${exitCode}`); resolve({ stdout: processStdout, stderr: processStderr, exitCode }); } }); verifyProcess.on('error', (error) => { this.addBuildLog('warn', `签名验证命令执行失败: ${error.message}`); // 不抛出错误,因为验证失败不影响使用 resolve({ stdout: processStdout, stderr: processStderr, exitCode: -1 }); }); }); } catch (error) { this.addBuildLog('warn', `签名验证过程出错: ${error.message}`); // 不抛出错误,因为验证失败不影响使用 } } /** * 反编译APK */ async decompileAPK(apkPath, outputDir, apktoolPath) { try { this.addBuildLog('info', `反编译APK: ${apkPath} -> ${outputDir}`); const isWindows = (0, os_1.platform)() === 'win32'; const normalizedApkPath = path_1.default.normalize(apkPath); const normalizedOutputDir = path_1.default.normalize(outputDir); const normalizedApktoolPath = path_1.default.normalize(apktoolPath); // 构建apktool反编译命令 let decompileCommand; if (isWindows) { // Windows: 使用引号包裹路径 decompileCommand = `java -jar "${normalizedApktoolPath}" d "${normalizedApkPath}" -o "${normalizedOutputDir}"`; } else { // Linux: 直接使用路径 decompileCommand = `java -jar "${normalizedApktoolPath}" d "${normalizedApkPath}" -o "${normalizedOutputDir}"`; } this.addBuildLog('info', `[DEBUG] 反编译命令: apktool d ...`); this.addBuildLog('info', `[DEBUG] APK路径: ${normalizedApkPath}`); this.addBuildLog('info', `[DEBUG] 输出目录: ${normalizedOutputDir}`); // 执行反编译命令 const result = await new Promise((resolve, reject) => { let processStdout = ''; let processStderr = ''; const decompileProcess = (0, child_process_1.spawn)(decompileCommand, [], { cwd: process.cwd(), shell: true }); this.addBuildLog('info', `[DEBUG] 反编译进程已启动,PID: ${decompileProcess.pid}`); if (decompileProcess.stdout) { decompileProcess.stdout.on('data', (data) => { const text = data.toString('utf8'); processStdout += text; const lines = text.split(/\r?\n/).filter((line) => line.trim()); lines.forEach((line) => { const trimmedLine = line.trim(); if (trimmedLine) { this.addBuildLog('info', `apktool: ${trimmedLine}`); } }); }); } if (decompileProcess.stderr) { decompileProcess.stderr.on('data', (data) => { const text = data.toString('utf8'); processStderr += text; const lines = text.split(/\r?\n/).filter((line) => line.trim()); lines.forEach((line) => { const trimmedLine = line.trim(); if (trimmedLine) { // apktool的输出通常到stderr,但这是正常的 if (trimmedLine.toLowerCase().includes('error') || trimmedLine.toLowerCase().includes('exception')) { this.addBuildLog('error', `apktool错误: ${trimmedLine}`); } else { this.addBuildLog('info', `apktool: ${trimmedLine}`); } } }); }); } decompileProcess.on('close', (code) => { const exitCode = code || 0; if (exitCode === 0) { this.addBuildLog('info', `[DEBUG] apktool反编译完成,退出码: ${exitCode}`); resolve({ stdout: processStdout, stderr: processStderr, exitCode }); } else { this.addBuildLog('error', `[DEBUG] apktool反编译失败,退出码: ${exitCode}`); const error = new Error(`apktool反编译失败,退出码: ${exitCode}`); error.stdout = processStdout; error.stderr = processStderr; error.exitCode = exitCode; reject(error); } }); decompileProcess.on('error', (error) => { this.addBuildLog('error', `apktool进程错误: ${error.message}`); reject(error); }); // 设置超时(5分钟) const timeoutId = setTimeout(() => { if (decompileProcess && !decompileProcess.killed) { this.addBuildLog('error', 'apktool反编译超时(5分钟),正在终止进程...'); if (isWindows) { decompileProcess.kill('SIGTERM'); setTimeout(() => { if (!decompileProcess.killed) { decompileProcess.kill('SIGKILL'); } }, 5000); } else { decompileProcess.kill('SIGTERM'); setTimeout(() => { if (!decompileProcess.killed) { decompileProcess.kill('SIGKILL'); } }, 5000); } const timeoutError = new Error('apktool反编译超时(5分钟)'); timeoutError.stdout = processStdout; timeoutError.stderr = processStderr; timeoutError.exitCode = -1; reject(timeoutError); } }, 300000); // 5分钟超时 decompileProcess.on('close', () => { clearTimeout(timeoutId); }); }); // 检查输出目录是否创建成功 if (fs_1.default.existsSync(outputDir)) { const files = fs_1.default.readdirSync(outputDir); if (files.length > 0) { this.addBuildLog('success', `反编译成功,输出目录包含 ${files.length} 个项目`); return { success: true, message: '反编译成功' }; } else { this.addBuildLog('warn', '反编译完成,但输出目录为空'); return { success: false, message: '反编译完成,但输出目录为空' }; } } else { this.addBuildLog('error', '反编译完成,但输出目录不存在'); return { success: false, message: '反编译完成,但输出目录不存在' }; } } catch (error) { this.addBuildLog('error', `反编译APK失败: ${error.message}`); if (error.stdout) { const stdoutLines = error.stdout.split('\n').filter((line) => line.trim()); stdoutLines.forEach((line) => { this.addBuildLog('error', `apktool输出: ${line.trim()}`); }); } if (error.stderr) { const stderrLines = error.stderr.split('\n').filter((line) => line.trim()); stderrLines.forEach((line) => { this.addBuildLog('error', `apktool错误: ${line.trim()}`); }); } return { success: false, message: error.message || '反编译APK失败' }; } } /** * 生成随机版本号 */ generateRandomVersion() { // 生成随机versionCode(1000000-9999999之间的随机数) const versionCode = Math.floor(Math.random() * 9000000) + 1000000; // 生成随机versionName(格式:主版本.次版本.修订版本) // 主版本:1-99 // 次版本:0-999 // 修订版本:0-9999 const major = Math.floor(Math.random() * 99) + 1; const minor = Math.floor(Math.random() * 1000); const patch = Math.floor(Math.random() * 10000); const versionName = `${major}.${minor}.${patch}`; return { versionCode, versionName }; } /** * 修改APK版本号 */ async changeVersion(sourceApkPath, versionCode, versionName) { try { this.addBuildLog('info', `开始修改版本号: versionCode=${versionCode}, versionName=${versionName}`); // 1. 修改apktool.yml中的版本信息 const apktoolYmlPath = path_1.default.join(sourceApkPath, 'apktool.yml'); if (fs_1.default.existsSync(apktoolYmlPath)) { let ymlContent = fs_1.default.readFileSync(apktoolYmlPath, 'utf8'); // 替换versionCode ymlContent = ymlContent.replace(/versionCode:\s*\d+/g, `versionCode: ${versionCode}`); // 替换versionName ymlContent = ymlContent.replace(/versionName:\s*[\d.]+/g, `versionName: ${versionName}`); fs_1.default.writeFileSync(apktoolYmlPath, ymlContent, 'utf8'); this.addBuildLog('info', 'apktool.yml中的版本号已更新'); } // 2. 修改AndroidManifest.xml中的版本信息(如果存在) const manifestPath = path_1.default.join(sourceApkPath, 'AndroidManifest.xml'); if (fs_1.default.existsSync(manifestPath)) { let manifestContent = fs_1.default.readFileSync(manifestPath, 'utf8'); let modified = false; // 替换android:versionCode(如果存在) if (manifestContent.includes('android:versionCode')) { manifestContent = manifestContent.replace(/android:versionCode=["']\d+["']/g, `android:versionCode="${versionCode}"`); modified = true; } // 替换android:versionName(如果存在) if (manifestContent.includes('android:versionName')) { manifestContent = manifestContent.replace(/android:versionName=["'][^"']+["']/g, `android:versionName="${versionName}"`); modified = true; } // 替换platformBuildVersionCode(如果存在) if (manifestContent.includes('platformBuildVersionCode')) { manifestContent = manifestContent.replace(/platformBuildVersionCode=["']\d+["']/g, `platformBuildVersionCode="${versionCode}"`); modified = true; } // 替换platformBuildVersionName(如果存在) if (manifestContent.includes('platformBuildVersionName')) { manifestContent = manifestContent.replace(/platformBuildVersionName=["'][^"']+["']/g, `platformBuildVersionName="${versionName}"`); modified = true; } if (modified) { fs_1.default.writeFileSync(manifestPath, manifestContent, 'utf8'); this.addBuildLog('info', 'AndroidManifest.xml中的版本号已更新'); } } this.addBuildLog('success', '版本号修改完成'); } catch (error) { this.addBuildLog('error', `修改版本号失败: ${error.message}`); throw error; } } /** * 生成随机包名 */ generateRandomPackageName() { // 生成类似 com.abc123def456 的随机包名 const randomString = () => { const chars = 'abcdefghijklmnopqrstuvwxyz'; const nums = '0123456789'; let result = ''; // 3-5个小写字母 for (let i = 0; i < 3 + Math.floor(Math.random() * 3); i++) { result += chars.charAt(Math.floor(Math.random() * chars.length)); } // 3-6个数字 for (let i = 0; i < 3 + Math.floor(Math.random() * 4); i++) { result += nums.charAt(Math.floor(Math.random() * nums.length)); } // 3-5个小写字母 for (let i = 0; i < 3 + Math.floor(Math.random() * 3); i++) { result += chars.charAt(Math.floor(Math.random() * chars.length)); } return result; }; // 生成两级包名:com.xxxxx const firstLevel = ['com', 'net', 'org', 'io'][Math.floor(Math.random() * 4)]; const secondLevel = randomString(); return `${firstLevel}.${secondLevel}`; } /** * 修改APK包名 */ async changePackageName(sourceApkPath, oldPackageName, newPackageName) { try { this.addBuildLog('info', `开始修改包名: ${oldPackageName} -> ${newPackageName}`); // 1. 修改AndroidManifest.xml const manifestPath = path_1.default.join(sourceApkPath, 'AndroidManifest.xml'); if (fs_1.default.existsSync(manifestPath)) { let manifestContent = fs_1.default.readFileSync(manifestPath, 'utf8'); // 替换package属性 manifestContent = manifestContent.replace(new RegExp(`package=["']${oldPackageName.replace(/\./g, '\\.')}["']`, 'g'), `package="${newPackageName}"`); // 替换所有包名引用(在android:name等属性中) const oldPackageRegex = new RegExp(oldPackageName.replace(/\./g, '\\.'), 'g'); manifestContent = manifestContent.replace(oldPackageRegex, newPackageName); fs_1.default.writeFileSync(manifestPath, manifestContent, 'utf8'); this.addBuildLog('info', 'AndroidManifest.xml已更新'); } // 2. 先更新所有smali文件中的包名引用(必须在重命名目录之前) // 这是关键步骤:先更新文件内容,再重命名目录,避免引用不匹配 this.addBuildLog('info', '开始更新所有smali文件中的包名引用(关键步骤:先更新文件内容)...'); await this.updateAllSmaliFiles(sourceApkPath, oldPackageName, newPackageName); this.addBuildLog('success', '所有smali文件中的包名引用已更新'); // 3. 重命名smali目录结构(使用复制+删除方式,避免Windows权限问题) this.addBuildLog('info', '开始重命名smali目录结构...'); await this.renameAllSmaliDirectories(sourceApkPath, oldPackageName, newPackageName); // 4. 更新apktool.yml文件(如果存在) const apktoolYmlPath = path_1.default.join(sourceApkPath, 'apktool.yml'); if (fs_1.default.existsSync(apktoolYmlPath)) { try { let ymlContent = fs_1.default.readFileSync(apktoolYmlPath, 'utf8'); const oldPackageRegex = new RegExp(oldPackageName.replace(/\./g, '\\.'), 'g'); if (ymlContent.includes(oldPackageName)) { ymlContent = ymlContent.replace(oldPackageRegex, newPackageName); fs_1.default.writeFileSync(apktoolYmlPath, ymlContent, 'utf8'); this.addBuildLog('info', 'apktool.yml已更新'); } } catch (error) { this.addBuildLog('warn', `更新apktool.yml失败: ${error.message}`); } } // 5. 替换其他可能包含包名的文件 // 检查res目录下的XML文件 const resDir = path_1.default.join(sourceApkPath, 'res'); if (fs_1.default.existsSync(resDir)) { this.addBuildLog('info', '检查res目录中的包名引用...'); await this.replacePackageNameInDirectory(resDir, oldPackageName, newPackageName, ['.xml']); } this.addBuildLog('success', '包名修改完成'); } catch (error) { this.addBuildLog('error', `修改包名失败: ${error.message}`); throw error; } } /** * 复制目录(递归) */ async copyDirectory(src, dest) { // 确保目标目录存在 if (!fs_1.default.existsSync(dest)) { fs_1.default.mkdirSync(dest, { recursive: true }); } const entries = fs_1.default.readdirSync(src, { withFileTypes: true }); for (const entry of entries) { const srcPath = path_1.default.join(src, entry.name); const destPath = path_1.default.join(dest, entry.name); if (entry.isDirectory()) { await this.copyDirectory(srcPath, destPath); } else { fs_1.default.copyFileSync(srcPath, destPath); } } } /** * 删除目录(带重试机制,跨平台兼容) */ async deleteDirectoryWithRetry(dirPath, maxRetries = 3) { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { if (fs_1.default.existsSync(dirPath)) { // 先尝试删除文件,再删除目录 const entries = fs_1.default.readdirSync(dirPath, { withFileTypes: true }); for (const entry of entries) { const entryPath = path_1.default.join(dirPath, entry.name); if (entry.isDirectory()) { await this.deleteDirectoryWithRetry(entryPath, maxRetries); } else { // 尝试删除文件,如果失败则等待后重试 let fileDeleted = false; for (let fileAttempt = 1; fileAttempt <= maxRetries; fileAttempt++) { try { fs_1.default.unlinkSync(entryPath); fileDeleted = true; break; } catch (error) { if (fileAttempt < maxRetries) { this.addBuildLog('warn', `删除文件失败,等待后重试 (${fileAttempt}/${maxRetries}): ${entryPath}`); await new Promise(resolve => setTimeout(resolve, 500 * fileAttempt)); } else { throw error; } } } if (!fileDeleted) { throw new Error(`无法删除文件: ${entryPath}`); } } } // 删除空目录(跨平台兼容) try { fs_1.default.rmdirSync(dirPath); } catch (rmdirError) { // 如果rmdirSync失败,尝试使用rmSync(Node.js 14.14.0+) if (typeof fs_1.default.rmSync === 'function') { try { fs_1.default.rmSync(dirPath, { recursive: true, force: true }); } catch (rmError) { throw rmdirError; // 如果都失败,抛出原始错误 } } else { throw rmdirError; } } return; } } catch (error) { if (attempt < maxRetries) { this.addBuildLog('warn', `删除目录失败,等待后重试 (${attempt}/${maxRetries}): ${dirPath}`); await new Promise(resolve => setTimeout(resolve, 1000 * attempt)); } else { this.addBuildLog('error', `删除目录失败,已重试${maxRetries}次: ${dirPath}`); // 不抛出错误,继续执行(可能被其他进程占用) this.addBuildLog('warn', '目录可能被其他进程占用,将在后续清理中处理'); } } } } /** * 清理空的目录 */ cleanupEmptyDirectories(baseDir, packageParts) { let currentDir = baseDir; for (let i = packageParts.length - 1; i >= 0; i--) { currentDir = path_1.default.join(currentDir, packageParts[i]); if (fs_1.default.existsSync(currentDir)) { try { const files = fs_1.default.readdirSync(currentDir); if (files.length === 0) { fs_1.default.rmdirSync(currentDir); this.addBuildLog('info', `已删除空目录: ${currentDir}`); } else { break; } } catch { // 忽略错误 } } } } /** * 更新所有smali文件中的包名引用(包括smali和smali_classes*目录) */ async updateAllSmaliFiles(sourceApkPath, oldPackageName, newPackageName) { const oldPackageSmali = oldPackageName.replace(/\./g, '/'); const newPackageSmali = newPackageName.replace(/\./g, '/'); // 处理主smali目录 const smaliDir = path_1.default.join(sourceApkPath, 'smali'); if (fs_1.default.existsSync(smaliDir)) { this.addBuildLog('info', '更新smali目录中的文件...'); await this.replacePackageNameInSmaliFiles(smaliDir, oldPackageName, newPackageName, oldPackageSmali, newPackageSmali); } // 处理smali_classes2, smali_classes3等目录(如果有) for (let i = 2; i <= 10; i++) { const smaliClassDir = path_1.default.join(sourceApkPath, `smali_classes${i}`); if (fs_1.default.existsSync(smaliClassDir)) { this.addBuildLog('info', `更新smali_classes${i}目录中的文件...`); await this.replacePackageNameInSmaliFiles(smaliClassDir, oldPackageName, newPackageName, oldPackageSmali, newPackageSmali); } } } /** * 重命名所有smali目录结构(包括smali和smali_classes*目录) */ async renameAllSmaliDirectories(sourceApkPath, oldPackageName, newPackageName) { const oldPackagePath = oldPackageName.split('.'); const newPackagePath = newPackageName.split('.'); // 处理主smali目录 const smaliDir = path_1.default.join(sourceApkPath, 'smali'); if (fs_1.default.existsSync(smaliDir)) { await this.renameSmaliDirectory(smaliDir, oldPackagePath, newPackagePath); } // 处理smali_classes2, smali_classes3等目录 for (let i = 2; i <= 10; i++) { const smaliClassDir = path_1.default.join(sourceApkPath, `smali_classes${i}`); if (fs_1.default.existsSync(smaliClassDir)) { await this.renameSmaliDirectory(smaliClassDir, oldPackagePath, newPackagePath); } } } /** * 重命名单个smali目录 */ async renameSmaliDirectory(smaliDir, oldPackagePath, newPackagePath) { const oldSmaliPath = path_1.default.join(smaliDir, ...oldPackagePath); const newSmaliPath = path_1.default.join(smaliDir, ...newPackagePath); if (fs_1.default.existsSync(oldSmaliPath)) { // 确保新目录的父目录存在 const newSmaliParent = path_1.default.dirname(newSmaliPath); if (!fs_1.default.existsSync(newSmaliParent)) { fs_1.default.mkdirSync(newSmaliParent, { recursive: true }); } // 如果新目录已存在,先删除(跨平台兼容) if (fs_1.default.existsSync(newSmaliPath)) { this.addBuildLog('info', '删除已存在的新目录...'); await this.deleteDirectoryWithRetry(newSmaliPath, 1); } // 使用复制+删除方式,避免Windows权限问题(跨平台兼容) // 使用path.sep显示路径,但smali路径始终使用/(Android标准) const displayOldPath = oldPackagePath.join('/'); const displayNewPath = newPackagePath.join('/'); this.addBuildLog('info', `复制目录: ${displayOldPath} -> ${displayNewPath}`); await this.copyDirectory(oldSmaliPath, newSmaliPath); // 删除旧目录(使用重试机制) this.addBuildLog('info', '删除旧目录...'); await this.deleteDirectoryWithRetry(oldSmaliPath, 3); this.addBuildLog('success', `smali目录已重命名: ${oldPackagePath.join('.')} -> ${newPackagePath.join('.')}`); // 清理空的旧目录 this.cleanupEmptyDirectories(smaliDir, oldPackagePath); } else { this.addBuildLog('warn', `旧目录不存在: ${oldSmaliPath}`); } } /** * 递归替换smali文件中的包名 */ async replacePackageNameInSmaliFiles(dir, oldPackageName, newPackageName, oldPackageSmali, newPackageSmali) { // 如果没有提供smali格式的包名,自动生成 if (!oldPackageSmali) { oldPackageSmali = oldPackageName.replace(/\./g, '/'); } if (!newPackageSmali) { newPackageSmali = newPackageName.replace(/\./g, '/'); } const files = fs_1.default.readdirSync(dir); for (const file of files) { const filePath = path_1.default.join(dir, file); const stat = fs_1.default.statSync(filePath); if (stat.isDirectory()) { await this.replacePackageNameInSmaliFiles(filePath, oldPackageName, newPackageName); } else if (file.endsWith('.smali')) { try { let content = fs_1.default.readFileSync(filePath, 'utf8'); // 替换包名引用(Lcom/hikoncont/... -> L新包名/...) const oldPackagePath = oldPackageName.replace(/\./g, '/'); const newPackagePath = newPackageName.replace(/\./g, '/'); // 1. 替换类定义中的包名(.class public Lcom/hikoncont/...) content = content.replace(new RegExp(`\\.class[^\\n]*L${oldPackagePath.replace(/\//g, '\\/')}/`, 'g'), (match) => match.replace(`L${oldPackagePath}/`, `L${newPackagePath}/`)); // 2. 替换类路径引用(Lcom/hikoncont/... -> L新包名/...) // 使用单词边界确保不会误替换 content = content.replace(new RegExp(`L${oldPackagePath.replace(/\//g, '\\/')}/`, 'g'), `L${newPackagePath}/`); // 3. 替换完整类名引用(com.hikoncont.ClassName -> 新包名.ClassName) content = content.replace(new RegExp(oldPackageName.replace(/\./g, '\\.'), 'g'), newPackageName); // 4. 替换字符串中的包名引用("com.hikoncont" -> "新包名") content = content.replace(new RegExp(`"${oldPackageName.replace(/\./g, '\\.')}"`, 'g'), `"${newPackageName}"`); // 5. 替换字符串中的包名引用('com.hikoncont' -> '新包名') content = content.replace(new RegExp(`'${oldPackageName.replace(/\./g, '\\.')}'`, 'g'), `'${newPackageName}'`); fs_1.default.writeFileSync(filePath, content, 'utf8'); } catch (error) { this.addBuildLog('warn', `替换文件失败 ${filePath}: ${error.message}`); } } } } /** * 在目录中递归替换包名(用于XML等文件) */ async replacePackageNameInDirectory(dir, oldPackageName, newPackageName, extensions) { if (!fs_1.default.existsSync(dir)) { return; } const files = fs_1.default.readdirSync(dir); for (const file of files) { const filePath = path_1.default.join(dir, file); const stat = fs_1.default.statSync(filePath); if (stat.isDirectory()) { await this.replacePackageNameInDirectory(filePath, oldPackageName, newPackageName, extensions); } else { const ext = path_1.default.extname(file); if (extensions.includes(ext)) { try { let content = fs_1.default.readFileSync(filePath, 'utf8'); const oldPackageRegex = new RegExp(oldPackageName.replace(/\./g, '\\.'), 'g'); if (content.includes(oldPackageName)) { content = content.replace(oldPackageRegex, newPackageName); fs_1.default.writeFileSync(filePath, content, 'utf8'); } } catch (error) { // 忽略错误 } } } } } /** * 检查构建环境(用于apktool打包) */ async checkBuildEnvironment() { const result = { hasJava: false, javaVersion: undefined, errors: [] }; try { // 检查Java(必需) try { const { stdout } = await execAsync('java -version', { timeout: 10000 }); result.hasJava = true; result.javaVersion = stdout.split('\n')[0]; } catch { result.errors.push('Java未安装或未在PATH中'); } // 检查apktool const apktoolPath = path_1.default.join(process.cwd(), 'android/apktool.jar'); if (!fs_1.default.existsSync(apktoolPath)) { result.errors.push('apktool不存在: android/apktool.jar'); } // 检查source.apk文件 const sourceApkFile = path_1.default.join(process.cwd(), 'android/source.apk'); if (!fs_1.default.existsSync(sourceApkFile)) { result.errors.push('source.apk文件不存在: android/source.apk'); } } catch (error) { this.logger.error('检查构建环境失败:', error); result.errors.push(error.message); } return result; } /** * 销毁服务 */ destroy() { this.cloudflareService.destroy(); } } exports.default = APKBuildService; //# sourceMappingURL=APKBuildService.js.map