From cb88d751b8fd928de65c33956c0b33a2e50638a7 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 2 May 2026 09:53:12 -0700 Subject: [PATCH] fix(build): externalize qrcode terminal --- src/infra/tsdown-config.test.ts | 21 ++++++++++++++++--- tsdown.config.ts | 36 +++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/src/infra/tsdown-config.test.ts b/src/infra/tsdown-config.test.ts index 6cf49267868..b67b9e6fc29 100644 --- a/src/infra/tsdown-config.test.ts +++ b/src/infra/tsdown-config.test.ts @@ -26,10 +26,18 @@ type TsdownOnLog = ( ) => void; type TsdownInputOptions = ( - options: { onLog?: TsdownOnLog }, + options: { external?: TsdownExternalOption; onLog?: TsdownOnLog }, format?: unknown, context?: unknown, -) => { onLog?: TsdownOnLog } | undefined; +) => { external?: TsdownExternalOption; onLog?: TsdownOnLog } | undefined; + +type TsdownExternalOption = string | RegExp | Array | TsdownExternalFunction; + +type TsdownExternalFunction = ( + id: string, + parentId: string | undefined, + isResolved: boolean, +) => boolean | null | undefined; function asConfigArray(config: unknown): TsdownConfigEntry[] { return Array.isArray(config) ? (config as TsdownConfigEntry[]) : [config as TsdownConfigEntry]; @@ -135,15 +143,22 @@ describe("tsdown config", () => { it("externalizes known heavy native dependencies", () => { const unifiedGraph = unifiedDistGraph(); const neverBundle = unifiedGraph?.deps?.neverBundle; + const external = unifiedGraph?.inputOptions?.({})?.external; if (typeof neverBundle === "function") { expect(neverBundle("@lancedb/lancedb")).toBe(true); expect(neverBundle("@matrix-org/matrix-sdk-crypto-nodejs")).toBe(true); expect(neverBundle("matrix-js-sdk/lib/client.js")).toBe(true); + expect(neverBundle("qrcode-terminal/lib/main.js")).toBe(true); expect(neverBundle("not-a-runtime-dependency")).toBe(false); } else { - expect(neverBundle).toEqual(expect.arrayContaining(["@lancedb/lancedb", "matrix-js-sdk"])); + expect(neverBundle).toEqual( + expect.arrayContaining(["@lancedb/lancedb", "matrix-js-sdk", "qrcode-terminal"]), + ); } + expect(typeof external).toBe("function"); + const externalize = external as TsdownExternalFunction; + expect(externalize("qrcode-terminal/lib/main.js", undefined, false)).toBe(true); }); it("suppresses unresolved imports from extension source", () => { diff --git a/tsdown.config.ts b/tsdown.config.ts index 89e2af8a78a..10e7f482677 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -23,6 +23,11 @@ type InputOptionsReturn = InputOptionsFactory extends ( ? Return : never; type OnLogFunction = InputOptionsArg extends { onLog?: infer OnLog } ? NonNullable : never; +type ExternalOptionFunction = ( + id: string, + parentId: string | undefined, + isResolved: boolean, +) => boolean | null | undefined; const env = { NODE_ENV: "production", @@ -38,12 +43,37 @@ function normalizedLogHaystack(log: { message?: string; id?: string; importer?: return [log.message, log.id, log.importer].filter(Boolean).join("\n").replaceAll("\\", "/"); } +function matchesExternalOption( + option: unknown, + id: string, + parentId: string | undefined, + isResolved: boolean, +): boolean { + if (!option) { + return false; + } + if (typeof option === "function") { + return (option as ExternalOptionFunction)(id, parentId, isResolved) === true; + } + if (typeof option === "string") { + return option === id; + } + if (option instanceof RegExp) { + return option.test(id); + } + if (Array.isArray(option)) { + return option.some((entry) => matchesExternalOption(entry, id, parentId, isResolved)); + } + return false; +} + function buildInputOptions(options: InputOptionsArg): InputOptionsReturn { if (process.env.OPENCLAW_BUILD_VERBOSE === "1") { return undefined; } const previousOnLog = typeof options.onLog === "function" ? options.onLog : undefined; + const previousExternal = (options as { external?: unknown }).external; function isSuppressedLog(log: { code?: string; @@ -66,6 +96,12 @@ function buildInputOptions(options: InputOptionsArg): InputOptionsReturn { return { ...options, + external(id: string, parentId: string | undefined, isResolved: boolean) { + return ( + shouldNeverBundleDependency(id) || + matchesExternalOption(previousExternal, id, parentId, isResolved) + ); + }, onLog(...args: Parameters) { const [level, log, defaultHandler] = args; if (isSuppressedLog(log)) {