Просмотр исходного кода

fix(core): lazy-load notification transports

Ma 6 месяцев назад
Родитель
Сommit
b08d59bc85

+ 1 - 1
.github/workflows/ci.yml

@@ -13,7 +13,7 @@ jobs:
       fail-fast: false
       matrix:
         os: [ubuntu-latest, windows-latest]
-        node-version: [20, 22]
+        node-version: [20, 22, 24]
 
     steps:
       - uses: actions/checkout@v4

+ 5 - 1
.github/workflows/release.yml

@@ -13,7 +13,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        node-version: [20, 22]
+        node-version: [20, 22, 24]
 
     steps:
       - uses: actions/checkout@v4
@@ -185,6 +185,8 @@ jobs:
 
           npx inkos --version
           test -f node_modules/@actalk/inkos-studio/dist/api/index.js
+          node -e "import('@actalk/inkos-core').then(m => { if (!m.PipelineRunner) throw new Error('installed core import missing PipelineRunner'); console.log('installed core import OK') })"
+          test -f node_modules/@actalk/inkos-core/dist/notify/telegram.js || test -f node_modules/@actalk/inkos/node_modules/@actalk/inkos-core/dist/notify/telegram.js
           echo "Canary verification passed"
           rm -rf "$TMPDIR"
 
@@ -283,6 +285,8 @@ jobs:
 
           npx inkos --version
           test -f node_modules/@actalk/inkos-studio/dist/api/index.js
+          node -e "import('@actalk/inkos-core').then(m => { if (!m.PipelineRunner) throw new Error('installed core import missing PipelineRunner'); console.log('installed core import OK') })"
+          test -f node_modules/@actalk/inkos-core/dist/notify/telegram.js || test -f node_modules/@actalk/inkos/node_modules/@actalk/inkos-core/dist/notify/telegram.js
           echo "Release verification passed"
           rm -rf "$TMPDIR"
 

+ 20 - 0
packages/core/src/__tests__/index-notify-lazy.test.ts

@@ -0,0 +1,20 @@
+import { afterEach, describe, expect, it, vi } from "vitest";
+
+afterEach(() => {
+  vi.doUnmock("../notify/telegram.js");
+  vi.resetModules();
+});
+
+describe("core root exports", () => {
+  it("does not load Telegram notification transport during root import", async () => {
+    vi.resetModules();
+    vi.doMock("../notify/telegram.js", () => {
+      throw new Error("telegram module should not load during root import");
+    });
+
+    const core = await import("../index.js");
+
+    expect(core).toHaveProperty("PipelineRunner");
+    expect(core).toHaveProperty("sendTelegram");
+  }, 10_000);
+});

+ 37 - 4
packages/core/src/index.ts

@@ -310,7 +310,40 @@ export { validateRuntimeState, type RuntimeStateValidationIssue } from "./state/
 
 // Notify
 export { dispatchNotification, dispatchWebhookEvent, type NotifyMessage } from "./notify/dispatcher.js";
-export { sendTelegram, type TelegramConfig } from "./notify/telegram.js";
-export { sendFeishu, type FeishuConfig } from "./notify/feishu.js";
-export { sendWechatWork, type WechatWorkConfig } from "./notify/wechat-work.js";
-export { sendWebhook, type WebhookConfig, type WebhookEvent, type WebhookPayload } from "./notify/webhook.js";
+export type { TelegramConfig } from "./notify/telegram.js";
+export type { FeishuConfig } from "./notify/feishu.js";
+export type { WechatWorkConfig } from "./notify/wechat-work.js";
+export type { WebhookConfig, WebhookEvent, WebhookPayload } from "./notify/webhook.js";
+
+export async function sendTelegram(
+  config: import("./notify/telegram.js").TelegramConfig,
+  message: string,
+): Promise<void> {
+  const transport = await import("./notify/telegram.js");
+  await transport.sendTelegram(config, message);
+}
+
+export async function sendFeishu(
+  config: import("./notify/feishu.js").FeishuConfig,
+  title: string,
+  text: string,
+): Promise<void> {
+  const transport = await import("./notify/feishu.js");
+  await transport.sendFeishu(config, title, text);
+}
+
+export async function sendWechatWork(
+  config: import("./notify/wechat-work.js").WechatWorkConfig,
+  text: string,
+): Promise<void> {
+  const transport = await import("./notify/wechat-work.js");
+  await transport.sendWechatWork(config, text);
+}
+
+export async function sendWebhook(
+  config: import("./notify/webhook.js").WebhookConfig,
+  payload: import("./notify/webhook.js").WebhookPayload,
+): Promise<void> {
+  const transport = await import("./notify/webhook.js");
+  await transport.sendWebhook(config, payload);
+}

+ 36 - 26
packages/core/src/notify/dispatcher.ts

@@ -1,8 +1,5 @@
 import type { NotifyChannel } from "../models/project.js";
-import { sendTelegram } from "./telegram.js";
-import { sendFeishu } from "./feishu.js";
-import { sendWechatWork } from "./wechat-work.js";
-import { sendWebhook, type WebhookPayload } from "./webhook.js";
+import type { WebhookPayload } from "./webhook.js";
 
 export interface NotifyMessage {
   readonly title: string;
@@ -19,36 +16,48 @@ export async function dispatchNotification(
     try {
       switch (channel.type) {
         case "telegram":
-          await sendTelegram(
-            { botToken: channel.botToken, chatId: channel.chatId },
-            fullText,
-          );
+          {
+            const { sendTelegram } = await import("./telegram.js");
+            await sendTelegram(
+              { botToken: channel.botToken, chatId: channel.chatId },
+              fullText,
+            );
+          }
           break;
         case "feishu":
-          await sendFeishu(
-            { webhookUrl: channel.webhookUrl },
-            message.title,
-            message.body,
-          );
+          {
+            const { sendFeishu } = await import("./feishu.js");
+            await sendFeishu(
+              { webhookUrl: channel.webhookUrl },
+              message.title,
+              message.body,
+            );
+          }
           break;
         case "wechat-work":
-          await sendWechatWork(
-            { webhookUrl: channel.webhookUrl },
-            fullText,
-          );
+          {
+            const { sendWechatWork } = await import("./wechat-work.js");
+            await sendWechatWork(
+              { webhookUrl: channel.webhookUrl },
+              fullText,
+            );
+          }
           break;
         case "webhook":
           // Webhook channels are handled by dispatchWebhookEvent for structured events.
           // For generic text notifications, send as a pipeline-complete event.
-          await sendWebhook(
-            { url: channel.url, secret: channel.secret, events: channel.events },
-            {
-              event: "pipeline-complete",
-              bookId: "",
-              timestamp: new Date().toISOString(),
-              data: { title: message.title, body: message.body },
-            },
-          );
+          {
+            const { sendWebhook } = await import("./webhook.js");
+            await sendWebhook(
+              { url: channel.url, secret: channel.secret, events: channel.events },
+              {
+                event: "pipeline-complete",
+                bookId: "",
+                timestamp: new Date().toISOString(),
+                data: { title: message.title, body: message.body },
+              },
+            );
+          }
           break;
       }
     } catch (e) {
@@ -73,6 +82,7 @@ export async function dispatchWebhookEvent(
   const tasks = webhookChannels.map(async (channel) => {
     if (channel.type !== "webhook") return;
     try {
+      const { sendWebhook } = await import("./webhook.js");
       await sendWebhook(
         { url: channel.url, secret: channel.secret, events: channel.events },
         payload,