previewService.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  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.startPreview = startPreview;
  7. exports.updatePreviewConfig = updatePreviewConfig;
  8. exports.stopPreview = stopPreview;
  9. exports.getPreviewStatus = getPreviewStatus;
  10. const child_process_1 = require("child_process");
  11. const child_process_2 = require("child_process");
  12. const path_1 = __importDefault(require("path"));
  13. const fs_1 = __importDefault(require("fs"));
  14. const http_1 = __importDefault(require("http"));
  15. const configGenerator_1 = require("./configGenerator");
  16. const TEMPLATE_DIR = path_1.default.resolve(__dirname, "../../../../templates/coloring");
  17. const PREVIEW_PORT = 5199;
  18. let viteProcess = null;
  19. let currentCreativeId = null;
  20. /**
  21. * 等待 HTTP 服务就绪
  22. */
  23. function waitForReady(url, maxRetries = 15) {
  24. return new Promise((resolve, reject) => {
  25. let tries = 0;
  26. function check() {
  27. http_1.default.get(url, (res) => {
  28. if (res.statusCode === 200)
  29. resolve();
  30. else
  31. retry();
  32. }).on("error", retry);
  33. }
  34. function retry() {
  35. if (++tries >= maxRetries) {
  36. reject(new Error(`Preview server did not start within ${maxRetries}s`));
  37. return;
  38. }
  39. setTimeout(check, 1000);
  40. }
  41. check();
  42. });
  43. }
  44. /**
  45. * 启动实时预览。等待 Vite dev server 就绪后才返回。
  46. */
  47. async function startPreview(creativeId, theme, storageDir) {
  48. // 1. 停止旧的预览(如果有)
  49. stopPreview();
  50. // 2. 创建 symlink
  51. (0, configGenerator_1.createAssetsSymlink)(creativeId, storageDir);
  52. // 3. 生成配置
  53. const configContent = (0, configGenerator_1.generateAdConfig)({ creativeId, theme, storageDir });
  54. const configPath = path_1.default.join(TEMPLATE_DIR, "src", "filler", "_ad_config_.ts");
  55. fs_1.default.writeFileSync(configPath, configContent, "utf-8");
  56. // 4. 用 vite.config.preview.js(硬编码 base 路径)覆盖默认 config
  57. const previewBase = process.env.PREVIEW_BASE_PATH || "/";
  58. const originalConfig = fs_1.default.readFileSync(path_1.default.join(TEMPLATE_DIR, "vite.config.js"), "utf-8");
  59. // 在 return 语句前注入 base
  60. const patchedConfig = originalConfig.replace("return {", `return {\n base: "${previewBase}",`);
  61. const previewConfigPath = path_1.default.join(TEMPLATE_DIR, "vite.config.preview.js");
  62. fs_1.default.writeFileSync(previewConfigPath, patchedConfig, "utf-8");
  63. console.log(`[preview] Starting Vite dev server on port ${PREVIEW_PORT} (base: ${previewBase})...`);
  64. viteProcess = (0, child_process_1.spawn)(path_1.default.join(TEMPLATE_DIR, "node_modules", ".bin", "vite"), [
  65. "--port", String(PREVIEW_PORT),
  66. "--strictPort",
  67. "--config", previewConfigPath,
  68. ], {
  69. cwd: TEMPLATE_DIR,
  70. env: { ...process.env, AD_CONFIG_PATH: "src/filler/_ad_config_.ts" },
  71. stdio: ["ignore", "pipe", "pipe"],
  72. });
  73. viteProcess.stdout?.on("data", (data) => {
  74. console.log(`[preview:vite] ${data.toString().trim()}`);
  75. });
  76. viteProcess.stderr?.on("data", (data) => {
  77. console.log(`[preview:vite] ${data.toString().trim()}`);
  78. });
  79. viteProcess.on("exit", (code) => {
  80. console.log(`[preview] Vite dev server exited (code ${code})`);
  81. viteProcess = null;
  82. currentCreativeId = null;
  83. });
  84. currentCreativeId = creativeId;
  85. // 5. 等待 Vite 就绪
  86. const localUrl = `http://localhost:${PREVIEW_PORT}`;
  87. console.log("[preview] Waiting for Vite to be ready...");
  88. await waitForReady(localUrl);
  89. console.log("[preview] Vite is ready.");
  90. // 生产环境通过 nginx 代理暴露公网 URL,本地开发直接用 localhost
  91. const publicUrl = process.env.PREVIEW_PUBLIC_URL || localUrl;
  92. return { url: publicUrl };
  93. }
  94. /**
  95. * 更新预览配置(主题变更时调用)。Vite HMR 会自动检测并刷新页面。
  96. */
  97. function updatePreviewConfig(creativeId, theme, storageDir) {
  98. if (currentCreativeId !== creativeId)
  99. return;
  100. const configContent = (0, configGenerator_1.generateAdConfig)({ creativeId, theme, storageDir });
  101. const configPath = path_1.default.join(TEMPLATE_DIR, "src", "filler", "_ad_config_.ts");
  102. fs_1.default.writeFileSync(configPath, configContent, "utf-8");
  103. console.log(`[preview] Config updated for creative ${creativeId}`);
  104. }
  105. /**
  106. * 停止预览
  107. */
  108. function stopPreview() {
  109. if (viteProcess) {
  110. console.log("[preview] Stopping Vite dev server...");
  111. try {
  112. viteProcess.kill("SIGTERM");
  113. }
  114. catch {
  115. // ignore
  116. }
  117. viteProcess = null;
  118. }
  119. currentCreativeId = null;
  120. // 清理临时 vite config
  121. try {
  122. const previewConfigPath = path_1.default.join(TEMPLATE_DIR, "vite.config.preview.js");
  123. if (fs_1.default.existsSync(previewConfigPath))
  124. fs_1.default.unlinkSync(previewConfigPath);
  125. }
  126. catch { }
  127. // 确保端口释放
  128. try {
  129. (0, child_process_2.execSync)(`lsof -ti :${PREVIEW_PORT} | xargs kill -9 2>/dev/null`, { stdio: "ignore" });
  130. }
  131. catch { }
  132. }
  133. function getPreviewStatus() {
  134. return {
  135. active: viteProcess !== null && currentCreativeId !== null,
  136. creativeId: currentCreativeId,
  137. url: currentCreativeId ? `http://localhost:${PREVIEW_PORT}` : null,
  138. };
  139. }
  140. //# sourceMappingURL=previewService.js.map