buildService.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. "use strict";
  2. var __importDefault = (this && this.__importDefault) || function (mod) {
  3. return (mod && mod.__esModule) ? mod : { "default": mod };
  4. };
  5. Object.defineProperty(exports, "__esModule", { value: true });
  6. exports.BuildService = void 0;
  7. const child_process_1 = require("child_process");
  8. const path_1 = __importDefault(require("path"));
  9. const fs_1 = __importDefault(require("fs"));
  10. const archiver_1 = __importDefault(require("archiver"));
  11. const configGenerator_1 = require("./configGenerator");
  12. const storageService_1 = require("./storageService");
  13. const TEMPLATE_DIR = path_1.default.resolve(__dirname, "../../../../templates/coloring");
  14. const BUILD_TIMEOUT_MS = 120_000; // 单次构建超时 120s
  15. class BuildService {
  16. db;
  17. storageDir;
  18. queue = [];
  19. running = false;
  20. constructor(db, storageDir) {
  21. this.db = db;
  22. this.storageDir = storageDir;
  23. }
  24. enqueue(buildId, creativeId, platforms, theme) {
  25. this.queue.push(() => this.build(buildId, creativeId, platforms, theme));
  26. if (!this.running) {
  27. this.processQueue();
  28. }
  29. }
  30. async processQueue() {
  31. this.running = true;
  32. while (this.queue.length > 0) {
  33. const task = this.queue.shift();
  34. try {
  35. await task();
  36. }
  37. catch (err) {
  38. console.error("[build] Queue task failed:", err);
  39. }
  40. }
  41. this.running = false;
  42. }
  43. async build(buildId, creativeId, platforms, theme) {
  44. const startTime = new Date().toISOString();
  45. try {
  46. // 更新状态 → building
  47. this.db
  48. .prepare("UPDATE builds SET status = 'building', started_at = ? WHERE id = ?")
  49. .run(startTime, buildId);
  50. // 1. 创建 symlink
  51. (0, configGenerator_1.createAssetsSymlink)(creativeId, this.storageDir);
  52. // 2. 生成 _ad_config_.ts
  53. const configContent = (0, configGenerator_1.generateAdConfig)({
  54. creativeId,
  55. theme,
  56. storageDir: this.storageDir,
  57. });
  58. const configPath = path_1.default.join(TEMPLATE_DIR, "src", "filler", "_ad_config_.ts");
  59. fs_1.default.writeFileSync(configPath, configContent, "utf-8");
  60. console.log(`[build] Generated _ad_config_.ts for creative ${creativeId}`);
  61. // 3. 构建输出目录
  62. const buildOutputDir = path_1.default.join(this.storageDir, "creatives", creativeId, "builds", buildId);
  63. (0, storageService_1.ensureDir)(buildOutputDir);
  64. // 4. 逐平台构建
  65. const results = [];
  66. for (const platform of platforms) {
  67. console.log(`[build] Building ${platform} for creative ${creativeId}...`);
  68. await this.runViteBuild(platform);
  69. await this.collectOutput(buildOutputDir, platform, results);
  70. }
  71. // 5. 复制预览产物(用第一个平台的输出即可,供真机扫码测试)
  72. const previewSrc = path_1.default.join(TEMPLATE_DIR, "dist", platforms[0], "index.html");
  73. const previewDir = path_1.default.join(this.storageDir, "previews");
  74. (0, storageService_1.ensureDir)(previewDir);
  75. const previewPath = path_1.default.join(previewDir, `${buildId}.html`);
  76. fs_1.default.copyFileSync(previewSrc, previewPath);
  77. console.log(`[build] Preview file: ${previewPath} (from ${platforms[0]})`);
  78. // 7. 打包 ZIP
  79. await this.createZip(buildOutputDir, results);
  80. // 6. 更新数据库
  81. const finishedAt = new Date().toISOString();
  82. this.db
  83. .prepare(`UPDATE builds SET status = 'completed', results = ?, finished_at = ? WHERE id = ?`)
  84. .run(JSON.stringify(results), finishedAt, buildId);
  85. // 更新创意状态
  86. this.db
  87. .prepare("UPDATE creatives SET status = 'built', updated_at = datetime('now') WHERE id = ?")
  88. .run(creativeId);
  89. console.log(`[build] Build ${buildId} completed: ${results.map((r) => r.platform).join(", ")}`);
  90. }
  91. catch (err) {
  92. console.error(`[build] Build ${buildId} failed:`, err.message);
  93. this.db
  94. .prepare("UPDATE builds SET status = 'failed', error_log = ? WHERE id = ?")
  95. .run(err.message || "Unknown error", buildId);
  96. this.db
  97. .prepare("UPDATE creatives SET status = 'assets_ready', updated_at = datetime('now') WHERE id = ?")
  98. .run(creativeId);
  99. }
  100. finally {
  101. // 清理临时文件
  102. (0, configGenerator_1.cleanupBuildArtifacts)();
  103. }
  104. }
  105. runViteBuild(platform) {
  106. return new Promise((resolve, reject) => {
  107. const cmd = `cd ${TEMPLATE_DIR} && AD_CONFIG_PATH=src/filler/_ad_config_.ts npx vite build --mode ${platform}`;
  108. console.log(`[build] Executing: ${cmd}`);
  109. (0, child_process_1.exec)(cmd, { timeout: BUILD_TIMEOUT_MS }, (error, stdout, stderr) => {
  110. if (stdout)
  111. console.log(`[vite:${platform}]`, stdout.slice(-500));
  112. if (stderr && !stderr.includes("vite"))
  113. console.error(`[vite:${platform}]`, stderr.slice(-500));
  114. if (error) {
  115. reject(new Error(`Vite build failed for ${platform}: ${error.message}`));
  116. }
  117. else {
  118. resolve();
  119. }
  120. });
  121. });
  122. }
  123. async collectOutput(buildOutputDir, platform, results) {
  124. const distPath = path_1.default.join(TEMPLATE_DIR, "dist", platform, "index.html");
  125. const destDir = path_1.default.join(buildOutputDir, platform);
  126. (0, storageService_1.ensureDir)(destDir);
  127. const destPath = path_1.default.join(destDir, "index.html");
  128. if (!fs_1.default.existsSync(distPath)) {
  129. throw new Error(`Build output not found for platform ${platform}: ${distPath}`);
  130. }
  131. fs_1.default.copyFileSync(distPath, destPath);
  132. const stat = fs_1.default.statSync(destPath);
  133. results.push({ platform, fileSize: stat.size });
  134. }
  135. createZip(buildOutputDir, results) {
  136. return new Promise((resolve, reject) => {
  137. const zipPath = path_1.default.join(buildOutputDir, "all.zip");
  138. const output = fs_1.default.createWriteStream(zipPath);
  139. const archive = (0, archiver_1.default)("zip", { zlib: { level: 9 } });
  140. output.on("close", resolve);
  141. archive.on("error", reject);
  142. archive.pipe(output);
  143. for (const r of results) {
  144. const filePath = path_1.default.join(buildOutputDir, r.platform, "index.html");
  145. if (fs_1.default.existsSync(filePath)) {
  146. archive.file(filePath, { name: `${r.platform}/index.html` });
  147. }
  148. }
  149. archive.finalize();
  150. });
  151. }
  152. }
  153. exports.BuildService = BuildService;
  154. //# sourceMappingURL=buildService.js.map