UNPKG

wedecode

Version:

微信小程序源代码还原工具, 线上代码安全审计

985 lines (984 loc) 35.4 kB
import express from "express"; import cors from "cors"; import multer from "multer"; import path from "node:path"; import fs from "node:fs"; import { WebSocketServer } from "ws"; import { createServer } from "node:http"; import { spawn } from "node:child_process"; import { fileURLToPath } from "url"; var define_process_env_default = {}; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); class WorkspaceServer { app; server; wss; workspaces = /* @__PURE__ */ new Map(); executions = /* @__PURE__ */ new Map(); workspacesDir; uploadsDir; outputDir; constructor(port = 3e3) { this.app = express(); this.server = createServer(this.app); this.wss = new WebSocketServer({ server: this.server }); this.workspacesDir = define_process_env_default.WORKSPACE_ROOT ? path.join(define_process_env_default.WORKSPACE_ROOT, "workspaces") : path.join(process.cwd(), "workspaces"); this.uploadsDir = path.join(this.workspacesDir, "uploads"); this.outputDir = path.join(this.workspacesDir, "output"); this.setupMiddleware(); this.setupRoutes(); this.setupWebSocket(); this.ensureDirectories(); this.loadWorkspaces(); this.server.listen(port, () => { console.log(""); console.log("🤖 可视化反编译小程序"); console.log(`🚀 访问地址: http://localhost:${port}`); console.log(""); }); } setupMiddleware() { this.app.use(cors()); this.app.use(express.json({ limit: "2gb" })); this.app.use(express.urlencoded({ extended: true, limit: "2gb" })); const publicPath = path.join(__dirname, "../../public"); this.app.use("/static", express.static(publicPath)); this.app.use(express.static(publicPath)); } setupRoutes() { this.app.get("/api/workspaces", this.getWorkspaces.bind(this)); this.app.post("/api/workspaces", this.createWorkspace.bind(this)); this.app.get("/api/workspaces/:id", this.getWorkspace.bind(this)); this.app.delete("/api/workspaces/:id", this.deleteWorkspace.bind(this)); this.app.get("/api/workspaces/:id/files", this.getFileTree.bind(this)); this.app.get("/api/workspaces/:id/file", this.getFile.bind(this)); this.app.post("/api/workspaces/:id/file", this.saveFile.bind(this)); this.app.put("/api/workspaces/:id/file", this.createFile.bind(this)); this.app.delete("/api/workspaces/:id/file", this.deleteFile.bind(this)); this.app.post("/api/workspaces/:id/execute", this.executeCommand.bind(this)); this.app.get("/api/workspaces/:id/executions", this.getExecutions.bind(this)); this.app.delete("/api/executions/:executionId", this.killExecution.bind(this)); const storage = multer.diskStorage({ destination: (req, file, cb) => { const id = req.params.id; const workspace = this.workspaces.get(id); const destDir = workspace ? path.join(workspace.path, "uploads") : this.uploadsDir; try { fs.mkdirSync(destDir, { recursive: true }); } catch { } cb(null, destDir); }, filename: (req, file, cb) => { cb(null, file.originalname); } }); const upload = multer({ storage, limits: { fileSize: 500 * 1024 * 1024, // 单个文件最大 500MB files: 200, // 最多 200 个文件 fieldSize: 2 * 1024 * 1024 * 1024 // 总请求大小最大 2GB } }); this.app.post( "/api/workspaces/:id/decompile", upload.fields([{ name: "wxapkg", maxCount: 100 }, { name: "options", maxCount: 1 }]), this.handleUploadError.bind(this), this.decompileWxapkg.bind(this) ); this.app.post("/api/workspaces/:id/compile-folder", this.compileFolderWxapkg.bind(this)); this.app.post("/api/workspaces/:id/batch-decompile", this.batchDecompileWorkspace.bind(this)); this.app.get("/api/workspaces/:id/download", this.downloadWorkspace.bind(this)); this.app.put("/api/workspaces/:id/appinfo", this.updateWorkspaceAppInfo.bind(this)); this.app.get("/api/health", (req, res) => { res.json({ status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() }); }); this.app.get("/", (req, res) => { const indexPath = path.join(__dirname, "../../public/index.html"); if (fs.existsSync(indexPath)) { res.sendFile(indexPath); } else { res.status(404).send(` <!DOCTYPE html> <html> <head> <title>Wedecode - 文件未找到</title> <meta charset="UTF-8"> </head> <body> <h1>Wedecode 微信小程序反编译工具</h1> <p>抱歉,前端文件未找到。</p> <p>请确保 public 目录已正确包含在安装包中。</p> <p>当前查找路径: ${indexPath}</p> <p>API 服务正常运行在: <a href="/api/health">/api/health</a></p> </body> </html> `); } }); } setupWebSocket() { this.wss.on("connection", (ws) => { console.log("WebSocket client connected"); ws.on("message", (message) => { try { const data = JSON.parse(message.toString()); this.handleWebSocketMessage(ws, data); } catch (error) { console.error("WebSocket message error:", error); } }); ws.on("close", () => { console.log("WebSocket client disconnected"); }); }); } handleWebSocketMessage(ws, data) { switch (data.type) { case "subscribe": ws.workspaceId = data.workspaceId; break; case "ping": ws.send(JSON.stringify({ type: "pong" })); break; } } broadcastToWorkspace(workspaceId, message) { this.wss.clients.forEach((client) => { if (client.workspaceId === workspaceId && client.readyState === 1) { client.send(JSON.stringify(message)); } }); } ensureDirectories() { [this.workspacesDir, this.uploadsDir, this.outputDir].forEach((dir) => { if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } }); } loadWorkspaces() { try { const configPath = path.join(this.workspacesDir, "workspaces.json"); if (fs.existsSync(configPath)) { const data = fs.readFileSync(configPath, "utf-8"); const workspacesData = JSON.parse(data); workspacesData.forEach((workspace) => { this.workspaces.set(workspace.id, { ...workspace, createdAt: new Date(workspace.createdAt), updatedAt: new Date(workspace.updatedAt) }); }); } } catch (error) { console.error("Failed to load workspaces:", error); } } saveWorkspaces() { try { const configPath = path.join(this.workspacesDir, "workspaces.json"); const workspacesData = Array.from(this.workspaces.values()); fs.writeFileSync(configPath, JSON.stringify(workspacesData, null, 2)); } catch (error) { console.error("Failed to save workspaces:", error); } } generateId() { return Math.random().toString(36).substring(2) + Date.now().toString(36); } handleUploadError(error, req, res, next) { if (error instanceof multer.MulterError) { switch (error.code) { case "LIMIT_FILE_SIZE": res.status(413).json({ error: "文件大小超过限制", message: "单个文件大小不能超过 500MB", code: "FILE_TOO_LARGE" }); return; case "LIMIT_FILE_COUNT": res.status(413).json({ error: "文件数量超过限制", message: "最多只能上传 200 个文件", code: "TOO_MANY_FILES" }); return; case "LIMIT_FIELD_VALUE": res.status(413).json({ error: "请求数据过大", message: "总请求大小不能超过 2GB", code: "REQUEST_TOO_LARGE" }); return; default: res.status(400).json({ error: "文件上传错误", message: error.message, code: "UPLOAD_ERROR" }); return; } } next(error); } // API 路由处理器 async getWorkspaces(req, res) { try { const workspaces = Array.from(this.workspaces.values()); res.json(workspaces); } catch (error) { res.status(500).json({ error: "Failed to get workspaces" }); } } async createWorkspace(req, res) { try { const { name, type, description } = req.body; const id = this.generateId(); const workspacePath = path.join(this.workspacesDir, id); const workspace = { id, name, type, description, createdAt: /* @__PURE__ */ new Date(), updatedAt: /* @__PURE__ */ new Date(), path: workspacePath }; fs.mkdirSync(workspacePath, { recursive: true }); this.workspaces.set(id, workspace); this.saveWorkspaces(); res.json(workspace); } catch (error) { console.error("Create workspace error:", error); res.status(500).json({ error: "Failed to create workspace" }); } } async getWorkspace(req, res) { try { const { id } = req.params; const workspace = this.workspaces.get(id); if (!workspace) { res.status(404).json({ error: "Workspace not found" }); return; } res.json(workspace); } catch (error) { res.status(500).json({ error: "Failed to get workspace" }); } } async deleteWorkspace(req, res) { try { const { id } = req.params; const workspace = this.workspaces.get(id); if (!workspace) { res.status(404).json({ error: "Workspace not found" }); return; } if (fs.existsSync(workspace.path)) { fs.rmSync(workspace.path, { recursive: true, force: true }); } this.workspaces.delete(id); this.saveWorkspaces(); res.json({ success: true }); } catch (error) { res.status(500).json({ error: "Failed to delete workspace" }); } } async getFileTree(req, res) { try { const { id } = req.params; const workspace = this.workspaces.get(id); if (!workspace) { res.status(404).json({ error: "Workspace not found" }); return; } const fileTree = this.buildFileTree(workspace.path, workspace.path); res.json(fileTree); } catch (error) { res.status(500).json({ error: "Failed to get file tree" }); } } buildFileTree(dirPath, rootPath) { const stats = fs.statSync(dirPath); const name = path.basename(dirPath); const relativePath = path.relative(rootPath, dirPath); const node = { name, path: relativePath || ".", type: stats.isDirectory() ? "directory" : "file" }; if (stats.isDirectory()) { try { const children = fs.readdirSync(dirPath).filter((child) => !child.startsWith(".")).map((child) => this.buildFileTree(path.join(dirPath, child), rootPath)).sort((a, b) => { if (a.type !== b.type) { return a.type === "directory" ? -1 : 1; } return a.name.localeCompare(b.name); }); node.children = children; } catch (error) { node.children = []; } } else { node.size = stats.size; } return node; } async getFile(req, res) { try { const { id } = req.params; const filepath = req.query.path; const workspace = this.workspaces.get(id); if (!workspace) { res.status(404).json({ error: "Workspace not found" }); return; } const fullPath = path.join(workspace.path, filepath); if (!fs.existsSync(fullPath)) { res.status(404).json({ error: "File not found" }); return; } const content = fs.readFileSync(fullPath, "utf-8"); res.json({ content, path: filepath }); } catch (error) { res.status(500).json({ error: "Failed to read file" }); } } async saveFile(req, res) { try { const { id } = req.params; const { filepath, content } = req.body; const workspace = this.workspaces.get(id); if (!workspace) { res.status(404).json({ error: "Workspace not found" }); return; } const fullPath = path.join(workspace.path, filepath); const dir = path.dirname(fullPath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } fs.writeFileSync(fullPath, content, "utf-8"); workspace.updatedAt = /* @__PURE__ */ new Date(); this.saveWorkspaces(); this.broadcastToWorkspace(id, { type: "fileChanged", path: filepath, content }); res.json({ success: true }); } catch (error) { res.status(500).json({ error: "Failed to save file" }); } } async createFile(req, res) { try { const { id } = req.params; const { filepath, content = "", type = "file" } = req.body; const workspace = this.workspaces.get(id); if (!workspace) { res.status(404).json({ error: "Workspace not found" }); return; } const fullPath = path.join(workspace.path, filepath); if (type === "directory") { fs.mkdirSync(fullPath, { recursive: true }); } else { const dir = path.dirname(fullPath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } fs.writeFileSync(fullPath, content, "utf-8"); } workspace.updatedAt = /* @__PURE__ */ new Date(); this.saveWorkspaces(); this.broadcastToWorkspace(id, { type: "fileCreated", path: filepath, fileType: type }); res.json({ success: true }); } catch (error) { res.status(500).json({ error: "Failed to create file" }); } } async deleteFile(req, res) { try { const { id } = req.params; const filepath = req.query.path; const workspace = this.workspaces.get(id); if (!workspace) { res.status(404).json({ error: "Workspace not found" }); return; } const fullPath = path.join(workspace.path, filepath); if (!fs.existsSync(fullPath)) { res.status(404).json({ error: "File not found" }); return; } const stats = fs.statSync(fullPath); if (stats.isDirectory()) { fs.rmSync(fullPath, { recursive: true, force: true }); } else { fs.unlinkSync(fullPath); } workspace.updatedAt = /* @__PURE__ */ new Date(); this.saveWorkspaces(); this.broadcastToWorkspace(id, { type: "fileDeleted", path: filepath }); res.json({ success: true }); } catch (error) { res.status(500).json({ error: "Failed to delete file" }); } } async executeCommand(req, res) { var _a, _b; try { const { id } = req.params; const { command } = req.body; const workspace = this.workspaces.get(id); if (!workspace) { res.status(404).json({ error: "Workspace not found" }); return; } const executionId = this.generateId(); const execution = { id: executionId, workspaceId: id, command, status: "running", output: [], startTime: /* @__PURE__ */ new Date() }; this.executions.set(executionId, execution); const commandParts = command.trim().split(/\s+/); const cmd = commandParts[0]; const args = commandParts.slice(1); const childProcess = spawn(cmd, args, { cwd: workspace.path, stdio: ["pipe", "pipe", "pipe"] }); execution.process = childProcess; (_a = childProcess.stdout) == null ? void 0 : _a.on("data", (data) => { const text = data.toString(); execution.output.push(text); this.broadcastToWorkspace(id, { type: "executionOutput", executionId, data: text, stream: "stdout" }); }); (_b = childProcess.stderr) == null ? void 0 : _b.on("data", (data) => { const text = data.toString(); execution.output.push(text); this.broadcastToWorkspace(id, { type: "executionOutput", executionId, data: text, stream: "stderr" }); }); childProcess.on("close", (code) => { execution.status = code === 0 ? "completed" : "error"; execution.endTime = /* @__PURE__ */ new Date(); delete execution.process; this.broadcastToWorkspace(id, { type: "executionComplete", executionId, status: execution.status, exitCode: code }); }); res.json({ executionId, status: "started" }); } catch (error) { res.status(500).json({ error: "Failed to execute command" }); } } async getExecutions(req, res) { try { const { id } = req.params; const executions = Array.from(this.executions.values()).filter((exec) => exec.workspaceId === id); res.json(executions); } catch (error) { res.status(500).json({ error: "Failed to get executions" }); } } async killExecution(req, res) { try { const { executionId } = req.params; const execution = this.executions.get(executionId); if (!execution || !execution.process) { res.status(404).json({ error: "Execution not found or not running" }); return; } execution.process.kill("SIGTERM"); execution.status = "error"; execution.endTime = /* @__PURE__ */ new Date(); delete execution.process; res.json({ success: true }); } catch (error) { res.status(500).json({ error: "Failed to kill execution" }); } } async decompileWxapkg(req, res) { try { const { id } = req.params; const workspace = this.workspaces.get(id); if (!workspace) { res.status(404).json({ error: "Workspace not found" }); return; } const filesObj = req.files; const wxapkgFiles = (filesObj == null ? void 0 : filesObj.wxapkg) || []; if (!wxapkgFiles || wxapkgFiles.length === 0) { res.status(400).json({ error: "No wxapkg file(s) uploaded" }); return; } let optionsObj = {}; if (req.body.options) { try { const parsed = JSON.parse(req.body.options); if (Array.isArray(parsed)) { optionsObj = { clear: parsed.includes("--clear"), px: parsed.includes("--px"), unpackOnly: parsed.includes("--unpack-only"), wxid: null }; } else { optionsObj = parsed; } } catch (error) { console.warn("Failed to parse options:", error); } } const isUnpackOnly = optionsObj.unpackOnly || false; workspace.unpackOnly = isUnpackOnly; this.saveWorkspaces(); console.log(`[文件上传] 工作区 ${id} 上传了 ${wxapkgFiles.length} 个文件: ${wxapkgFiles.map((f) => f.originalname).join(", ")}`); res.json({ success: true, message: `Successfully uploaded ${wxapkgFiles.length} file(s). Use batch decompile to process all files.`, fileCount: wxapkgFiles.length, files: wxapkgFiles.map((f) => f.originalname) }); } catch (error) { console.error("Upload error:", error); res.status(500).json({ error: "Failed to upload files" }); } } async updateWorkspaceAppInfo(req, res) { const workspaceId = req.params.id; const workspace = this.workspaces.get(workspaceId); if (!workspace) { res.status(404).json({ error: "Workspace not found" }); return; } try { const { appInfo } = req.body; workspace.appInfo = appInfo; workspace.updatedAt = /* @__PURE__ */ new Date(); this.saveWorkspaces(); res.json({ success: true, message: "App info updated successfully", appInfo: workspace.appInfo }); } catch (error) { console.error("Update app info error:", error); res.status(500).json({ error: "Failed to update app info" }); } } async downloadWorkspace(req, res) { var _a, _b; const workspaceId = req.params.id; const workspace = this.workspaces.get(workspaceId); if (!workspace) { console.error(`[下载错误] 工作区未找到: ${workspaceId}`); res.status(404).json({ error: "Workspace not found" }); return; } try { let outputPath = path.join(workspace.path, "OUTPUT"); let folderType = "OUTPUT"; if (!fs.existsSync(outputPath)) { outputPath = path.join(workspace.path, "decompiled"); folderType = "decompiled"; } console.log(`[下载] 检查输出路径: ${outputPath} (${folderType})`); if (!fs.existsSync(outputPath)) { console.error(`[下载错误] 反编译结果文件夹不存在: ${outputPath}`); if (!fs.existsSync(workspace.path)) { console.error(`[下载错误] 工作区目录不存在: ${workspace.path}`); res.status(404).json({ error: "Workspace directory not found" }); return; } try { const workspaceContents = fs.readdirSync(workspace.path); console.log(`[下载] 工作区目录内容: ${workspaceContents.join(", ")}`); } catch (err) { console.error(`[下载错误] 无法读取工作区目录: ${err}`); } res.status(404).json({ error: "Decompilation result folder not found. Please ensure decompilation completed successfully." }); return; } try { const outputContents = fs.readdirSync(outputPath); console.log(`[下载] ${folderType}文件夹内容: ${outputContents.join(", ")}`); if (outputContents.length === 0) { console.error(`[下载错误] ${folderType}文件夹为空: ${outputPath}`); res.status(404).json({ error: `${folderType} folder is empty. Please ensure decompilation completed successfully.` }); return; } } catch (err) { console.error(`[下载错误] 无法读取${folderType}文件夹: ${err}`); res.status(500).json({ error: `Cannot read ${folderType} folder` }); return; } let fileName = "反编译结果"; console.log(`[下载调试] workspace.appInfo:`, workspace.appInfo); if (workspace.appInfo) { console.log(`[下载调试] 找到appInfo,nickname: ${workspace.appInfo.nickname}, username: ${workspace.appInfo.username}, appid: ${workspace.appInfo.appid}`); if (workspace.appInfo.nickname) { fileName = workspace.appInfo.nickname; console.log(`[下载调试] 使用nickname作为文件名: ${fileName}`); } else if (workspace.appInfo.username) { fileName = workspace.appInfo.username; console.log(`[下载调试] 使用username作为文件名: ${fileName}`); } else if (workspace.appInfo.appid) { fileName = workspace.appInfo.appid; console.log(`[下载调试] 使用appid作为文件名: ${fileName}`); } } else { console.log(`[下载调试] 没有找到appInfo,使用默认文件名: ${fileName}`); } if (workspace.unpackOnly) { fileName += "-仅解包"; } fileName = fileName.replace(/[<>:"/\\|?*]/g, "_"); const now = /* @__PURE__ */ new Date(); const month = String(now.getMonth() + 1).padStart(2, "0"); const day = String(now.getDate()).padStart(2, "0"); const hours = String(now.getHours()).padStart(2, "0"); const minutes = String(now.getMinutes()).padStart(2, "0"); const seconds = String(now.getSeconds()).padStart(2, "0"); const timestamp = `${month}${day}-${hours}${minutes}${seconds}`; const zipFileName = `${fileName}_${timestamp}.zip`; const zipPath = path.join(this.uploadsDir, zipFileName); console.log(`[下载] 创建压缩包: ${zipFileName}`); console.log(`[下载] 压缩包路径: ${zipPath}`); const zipProcess = spawn("zip", ["-r", zipPath, "."], { cwd: outputPath, stdio: "pipe" }); let zipOutput = ""; let zipError = ""; (_a = zipProcess.stdout) == null ? void 0 : _a.on("data", (data) => { zipOutput += data.toString(); }); (_b = zipProcess.stderr) == null ? void 0 : _b.on("data", (data) => { zipError += data.toString(); }); zipProcess.on("close", (code) => { console.log(`[下载] zip进程退出码: ${code}`); if (zipOutput) console.log(`[下载] zip输出: ${zipOutput}`); if (zipError) console.error(`[下载] zip错误: ${zipError}`); if (code === 0 && fs.existsSync(zipPath)) { const zipStats = fs.statSync(zipPath); console.log(`[下载] 压缩包创建成功,大小: ${zipStats.size} bytes`); res.setHeader("Content-Type", "application/zip"); res.setHeader("Content-Disposition", `attachment; filename="${encodeURIComponent(zipFileName)}"`); res.setHeader("Content-Length", zipStats.size.toString()); const fileStream = fs.createReadStream(zipPath); fileStream.pipe(res); fileStream.on("end", () => { console.log(`[下载] 文件发送完成,清理临时文件: ${zipPath}`); fs.unlink(zipPath, (err) => { if (err) console.error("Failed to delete temp zip file:", err); }); }); fileStream.on("error", (err) => { console.error(`[下载错误] 文件流错误: ${err}`); res.status(500).json({ error: "File stream error" }); }); } else { console.error(`[下载错误] 压缩包创建失败,退出码: ${code}, 文件存在: ${fs.existsSync(zipPath)}`); res.status(500).json({ error: `Failed to create zip file. Exit code: ${code}` }); } }); zipProcess.on("error", (error) => { console.error(`[下载错误] zip进程错误: ${error}`); res.status(500).json({ error: `Zip process error: ${error.message}` }); }); } catch (error) { console.error(`[下载错误] 下载异常: ${error}`); res.status(500).json({ error: `Failed to download workspace: ${error instanceof Error ? error.message : "Unknown error"}` }); } } async compileFolderWxapkg(req, res) { var _a, _b; try { const { id } = req.params; const workspace = this.workspaces.get(id); if (!workspace) { res.status(404).json({ error: "Workspace not found" }); return; } const { folderPath, options = [] } = req.body; if (!folderPath) { res.status(400).json({ error: "Folder path is required" }); return; } const fullFolderPath = path.resolve(folderPath); if (!fs.existsSync(fullFolderPath)) { res.status(400).json({ error: "Folder path does not exist" }); return; } const stats = fs.statSync(fullFolderPath); if (!stats.isDirectory()) { res.status(400).json({ error: "Path is not a directory" }); return; } let parsedOptions = []; if (Array.isArray(options)) { parsedOptions = options; } const isUnpackOnly = parsedOptions.includes("--unpack-only"); workspace.unpackOnly = isUnpackOnly; this.saveWorkspaces(); const executionId = this.generateId(); const outputPath = path.join(workspace.path, "OUTPUT"); if (!fs.existsSync(outputPath)) { fs.mkdirSync(outputPath, { recursive: true }); } const command = [ process.execPath, "dist/wedecode.js", fullFolderPath, "--out", outputPath, "--clear", ...parsedOptions ]; const execution = { id: executionId, workspaceId: id, command: command.join(" "), status: "running", output: [], startTime: /* @__PURE__ */ new Date() }; this.executions.set(executionId, execution); const childProcess = spawn(command[0], command.slice(1), { cwd: path.join(__dirname, "../.."), stdio: ["pipe", "pipe", "pipe"], env: { ...define_process_env_default, WEDECODE_CHILD_PROCESS: "true" // 标识这是一个子进程 } }); execution.process = childProcess; (_a = childProcess.stdout) == null ? void 0 : _a.on("data", (data) => { const output = data.toString(); execution.output.push(output); console.log(`[文件夹编译进程] ${output.trim()}`); this.broadcastToWorkspace(id, { type: "execution", executionId, event: "output", data: output }); }); (_b = childProcess.stderr) == null ? void 0 : _b.on("data", (data) => { const output = data.toString(); execution.output.push(output); console.error(`[文件夹编译进程错误] ${output.trim()}`); this.broadcastToWorkspace(id, { type: "execution", executionId, event: "error", data: output }); }); childProcess.on("close", (code) => { execution.status = code === 0 ? "completed" : "error"; execution.endTime = /* @__PURE__ */ new Date(); if (code === 0) { console.log(`[文件夹编译进程] 编译完成,退出码: ${code}`); } else { console.error(`[文件夹编译进程] 编译失败,退出码: ${code}`); } this.broadcastToWorkspace(id, { type: "execution", executionId, event: "exit", code }); }); childProcess.on("error", (error) => { execution.status = "error"; execution.endTime = /* @__PURE__ */ new Date(); console.error(`[文件夹编译进程] 进程错误: ${error.message}`); this.broadcastToWorkspace(id, { type: "execution", executionId, event: "error", data: error.message }); }); res.json({ success: true, executionId, message: "Folder compilation started" }); } catch (error) { console.error("Folder compilation error:", error); res.status(500).json({ error: "Failed to start folder compilation" }); } } async batchDecompileWorkspace(req, res) { var _a, _b; const id = req.params.id; const workspace = this.workspaces.get(id); if (!workspace) { res.status(404).json({ error: "Workspace not found" }); return; } try { const { options = {} } = req.body; let parsedOptions = []; try { const optionsObj = typeof options === "string" ? JSON.parse(options) : options; if (optionsObj.px) parsedOptions.push("--px"); if (optionsObj.unpackOnly) parsedOptions.push("--unpack-only"); if (optionsObj.wxid) parsedOptions.push("--wxid", optionsObj.wxid); if (optionsObj.clear) parsedOptions.push("--clear"); } catch (error) { console.warn("Failed to parse options:", error); } const workspaceUploadDir = path.join(workspace.path, "uploads"); if (!fs.existsSync(workspaceUploadDir)) { res.status(400).json({ error: "No uploaded files found in workspace" }); return; } const wxapkgFiles = fs.readdirSync(workspaceUploadDir).filter((file) => file.endsWith(".wxapkg")).map((file) => path.join(workspaceUploadDir, file)); if (wxapkgFiles.length === 0) { res.status(400).json({ error: "No .wxapkg files found in workspace" }); return; } console.log(`[批量反编译] 工作区 ${id} 找到 ${wxapkgFiles.length} 个文件:`, wxapkgFiles.map((f) => path.basename(f))); const outputPath = path.join(workspace.path, "OUTPUT"); if (!fs.existsSync(outputPath)) { fs.mkdirSync(outputPath, { recursive: true }); } const executionId = this.generateId(); const command = [ process.execPath, "dist/wedecode.js", workspaceUploadDir, "--out", outputPath, "--clear", ...parsedOptions ]; console.log("执行命令:", command.join(" ")); const execution = { id: executionId, workspaceId: id, command: command.join(" "), status: "running", output: [], startTime: /* @__PURE__ */ new Date() }; this.executions.set(executionId, execution); console.log(`[批量反编译] 启动命令: ${command.join(" ")}`); const childProcess = spawn(command[0], command.slice(1), { cwd: path.join(__dirname, "../.."), stdio: ["pipe", "pipe", "pipe"], env: { ...define_process_env_default, WEDECODE_CHILD_PROCESS: "true" // 标识这是一个子进程 } }); execution.process = childProcess; (_a = childProcess.stdout) == null ? void 0 : _a.on("data", (data) => { const output = data.toString(); execution.output.push(output); console.log(`[批量反编译进程] ${output.trim()}`); this.broadcastToWorkspace(id, { type: "execution", executionId, event: "output", data: output }); }); (_b = childProcess.stderr) == null ? void 0 : _b.on("data", (data) => { const output = data.toString(); execution.output.push(output); console.error(`[批量反编译进程错误] ${output.trim()}`); this.broadcastToWorkspace(id, { type: "execution", executionId, event: "error", data: output }); }); childProcess.on("close", (code) => { execution.status = code === 0 ? "completed" : "error"; execution.endTime = /* @__PURE__ */ new Date(); if (code === 0) { console.log(`[批量反编译进程] 批量反编译完成,退出码: ${code}`); } else { console.error(`[批量反编译进程] 批量反编译失败,退出码: ${code}`); } this.broadcastToWorkspace(id, { type: "execution", executionId, event: "exit", code }); }); childProcess.on("error", (error) => { execution.status = "error"; execution.endTime = /* @__PURE__ */ new Date(); console.error(`[批量反编译进程] 进程错误: ${error.message}`); this.broadcastToWorkspace(id, { type: "execution", executionId, event: "error", data: error.message }); }); res.json({ success: true, executionId, message: `Batch decompilation started for ${wxapkgFiles.length} files`, fileCount: wxapkgFiles.length }); } catch (error) { console.error("Batch decompilation error:", error); res.status(500).json({ error: "Failed to start batch decompilation" }); } } } export { WorkspaceServer as W };