"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BuildService = void 0; const child_process_1 = require("child_process"); const path_1 = __importDefault(require("path")); const fs_1 = __importDefault(require("fs")); const archiver_1 = __importDefault(require("archiver")); const configGenerator_1 = require("./configGenerator"); const storageService_1 = require("./storageService"); const TEMPLATE_DIR = path_1.default.resolve(__dirname, "../../../../templates/coloring"); const BUILD_TIMEOUT_MS = 120_000; // 单次构建超时 120s class BuildService { db; storageDir; queue = []; running = false; constructor(db, storageDir) { this.db = db; this.storageDir = storageDir; } enqueue(buildId, creativeId, platforms, theme) { this.queue.push(() => this.build(buildId, creativeId, platforms, theme)); if (!this.running) { this.processQueue(); } } async processQueue() { this.running = true; while (this.queue.length > 0) { const task = this.queue.shift(); try { await task(); } catch (err) { console.error("[build] Queue task failed:", err); } } this.running = false; } async build(buildId, creativeId, platforms, theme) { const startTime = new Date().toISOString(); try { // 更新状态 → building this.db .prepare("UPDATE builds SET status = 'building', started_at = ? WHERE id = ?") .run(startTime, buildId); // 1. 创建 symlink (0, configGenerator_1.createAssetsSymlink)(creativeId, this.storageDir); // 2. 生成 _ad_config_.ts const configContent = (0, configGenerator_1.generateAdConfig)({ creativeId, theme, storageDir: this.storageDir, }); const configPath = path_1.default.join(TEMPLATE_DIR, "src", "filler", "_ad_config_.ts"); fs_1.default.writeFileSync(configPath, configContent, "utf-8"); console.log(`[build] Generated _ad_config_.ts for creative ${creativeId}`); // 3. 构建输出目录 const buildOutputDir = path_1.default.join(this.storageDir, "creatives", creativeId, "builds", buildId); (0, storageService_1.ensureDir)(buildOutputDir); // 4. 逐平台构建 const results = []; for (const platform of platforms) { console.log(`[build] Building ${platform} for creative ${creativeId}...`); await this.runViteBuild(platform); await this.collectOutput(buildOutputDir, platform, results); } // 5. 复制预览产物(用第一个平台的输出即可,供真机扫码测试) const previewSrc = path_1.default.join(TEMPLATE_DIR, "dist", platforms[0], "index.html"); const previewDir = path_1.default.join(this.storageDir, "previews"); (0, storageService_1.ensureDir)(previewDir); const previewPath = path_1.default.join(previewDir, `${buildId}.html`); fs_1.default.copyFileSync(previewSrc, previewPath); console.log(`[build] Preview file: ${previewPath} (from ${platforms[0]})`); // 7. 打包 ZIP await this.createZip(buildOutputDir, results); // 6. 更新数据库 const finishedAt = new Date().toISOString(); this.db .prepare(`UPDATE builds SET status = 'completed', results = ?, finished_at = ? WHERE id = ?`) .run(JSON.stringify(results), finishedAt, buildId); // 更新创意状态 this.db .prepare("UPDATE creatives SET status = 'built', updated_at = datetime('now') WHERE id = ?") .run(creativeId); console.log(`[build] Build ${buildId} completed: ${results.map((r) => r.platform).join(", ")}`); } catch (err) { console.error(`[build] Build ${buildId} failed:`, err.message); this.db .prepare("UPDATE builds SET status = 'failed', error_log = ? WHERE id = ?") .run(err.message || "Unknown error", buildId); this.db .prepare("UPDATE creatives SET status = 'assets_ready', updated_at = datetime('now') WHERE id = ?") .run(creativeId); } finally { // 清理临时文件 (0, configGenerator_1.cleanupBuildArtifacts)(); } } runViteBuild(platform) { return new Promise((resolve, reject) => { const cmd = `cd ${TEMPLATE_DIR} && AD_CONFIG_PATH=src/filler/_ad_config_.ts npx vite build --mode ${platform}`; console.log(`[build] Executing: ${cmd}`); (0, child_process_1.exec)(cmd, { timeout: BUILD_TIMEOUT_MS }, (error, stdout, stderr) => { if (stdout) console.log(`[vite:${platform}]`, stdout.slice(-500)); if (stderr && !stderr.includes("vite")) console.error(`[vite:${platform}]`, stderr.slice(-500)); if (error) { reject(new Error(`Vite build failed for ${platform}: ${error.message}`)); } else { resolve(); } }); }); } async collectOutput(buildOutputDir, platform, results) { const distPath = path_1.default.join(TEMPLATE_DIR, "dist", platform, "index.html"); const destDir = path_1.default.join(buildOutputDir, platform); (0, storageService_1.ensureDir)(destDir); const destPath = path_1.default.join(destDir, "index.html"); if (!fs_1.default.existsSync(distPath)) { throw new Error(`Build output not found for platform ${platform}: ${distPath}`); } fs_1.default.copyFileSync(distPath, destPath); const stat = fs_1.default.statSync(destPath); results.push({ platform, fileSize: stat.size }); } createZip(buildOutputDir, results) { return new Promise((resolve, reject) => { const zipPath = path_1.default.join(buildOutputDir, "all.zip"); const output = fs_1.default.createWriteStream(zipPath); const archive = (0, archiver_1.default)("zip", { zlib: { level: 9 } }); output.on("close", resolve); archive.on("error", reject); archive.pipe(output); for (const r of results) { const filePath = path_1.default.join(buildOutputDir, r.platform, "index.html"); if (fs_1.default.existsSync(filePath)) { archive.file(filePath, { name: `${r.platform}/index.html` }); } } archive.finalize(); }); } } exports.BuildService = BuildService; //# sourceMappingURL=buildService.js.map