buildService.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  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. 构建默认产物(无 --mode,用于真机扫码测试)
  72. console.log(`[build] Building default for preview...`);
  73. await this.runViteBuild("default", "");
  74. const defaultDist = path_1.default.join(TEMPLATE_DIR, "dist", "index.html");
  75. const previewDir = path_1.default.join(this.storageDir, "previews");
  76. (0, storageService_1.ensureDir)(previewDir);
  77. const previewPath = path_1.default.join(previewDir, `${buildId}.html`);
  78. fs_1.default.copyFileSync(defaultDist, previewPath);
  79. console.log(`[build] Preview file: ${previewPath}`);
  80. // 7. 打包 ZIP
  81. await this.createZip(buildOutputDir, results);
  82. // 6. 更新数据库
  83. const finishedAt = new Date().toISOString();
  84. this.db
  85. .prepare(`UPDATE builds SET status = 'completed', results = ?, finished_at = ? WHERE id = ?`)
  86. .run(JSON.stringify(results), finishedAt, buildId);
  87. // 更新创意状态
  88. this.db
  89. .prepare("UPDATE creatives SET status = 'built', updated_at = datetime('now') WHERE id = ?")
  90. .run(creativeId);
  91. console.log(`[build] Build ${buildId} completed: ${results.map((r) => r.platform).join(", ")}`);
  92. }
  93. catch (err) {
  94. console.error(`[build] Build ${buildId} failed:`, err.message);
  95. this.db
  96. .prepare("UPDATE builds SET status = 'failed', error_log = ? WHERE id = ?")
  97. .run(err.message || "Unknown error", buildId);
  98. this.db
  99. .prepare("UPDATE creatives SET status = 'assets_ready', updated_at = datetime('now') WHERE id = ?")
  100. .run(creativeId);
  101. }
  102. finally {
  103. // 清理临时文件
  104. (0, configGenerator_1.cleanupBuildArtifacts)();
  105. }
  106. }
  107. runViteBuild(platform, modeFlag) {
  108. return new Promise((resolve, reject) => {
  109. const mode = modeFlag ?? `--mode ${platform}`;
  110. const cmd = `cd ${TEMPLATE_DIR} && AD_CONFIG_PATH=src/filler/_ad_config_.ts npx vite build ${mode}`.trim();
  111. console.log(`[build] Executing: ${cmd}`);
  112. (0, child_process_1.exec)(cmd, { timeout: BUILD_TIMEOUT_MS }, (error, stdout, stderr) => {
  113. if (stdout)
  114. console.log(`[vite:${platform}]`, stdout.slice(-500));
  115. if (stderr && !stderr.includes("vite"))
  116. console.error(`[vite:${platform}]`, stderr.slice(-500));
  117. if (error) {
  118. reject(new Error(`Vite build failed for ${platform}: ${error.message}`));
  119. }
  120. else {
  121. resolve();
  122. }
  123. });
  124. });
  125. }
  126. async collectOutput(buildOutputDir, platform, results) {
  127. const distPath = path_1.default.join(TEMPLATE_DIR, "dist", platform, "index.html");
  128. const destDir = path_1.default.join(buildOutputDir, platform);
  129. (0, storageService_1.ensureDir)(destDir);
  130. const destPath = path_1.default.join(destDir, "index.html");
  131. if (!fs_1.default.existsSync(distPath)) {
  132. throw new Error(`Build output not found for platform ${platform}: ${distPath}`);
  133. }
  134. fs_1.default.copyFileSync(distPath, destPath);
  135. const stat = fs_1.default.statSync(destPath);
  136. results.push({ platform, fileSize: stat.size });
  137. }
  138. createZip(buildOutputDir, results) {
  139. return new Promise((resolve, reject) => {
  140. const zipPath = path_1.default.join(buildOutputDir, "all.zip");
  141. const output = fs_1.default.createWriteStream(zipPath);
  142. const archive = (0, archiver_1.default)("zip", { zlib: { level: 9 } });
  143. output.on("close", resolve);
  144. archive.on("error", reject);
  145. archive.pipe(output);
  146. for (const r of results) {
  147. const filePath = path_1.default.join(buildOutputDir, r.platform, "index.html");
  148. if (fs_1.default.existsSync(filePath)) {
  149. archive.file(filePath, { name: `${r.platform}/index.html` });
  150. }
  151. }
  152. archive.finalize();
  153. });
  154. }
  155. }
  156. exports.BuildService = BuildService;
  157. //# sourceMappingURL=buildService.js.map