mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 01:29:56 +00:00
Add opt-in aggregate Linux process-tree memory containment to the managed tooling runner. Own the invocation-specific scope through verified cleanup before releasing its resource claim; preserve uncapped caller behavior. Validated real-kernel OOM, descendant cleanup, signals, cancellation and packaged launcher behavior; focused lifecycle/preload tests and exact-head CI/security gates passed. Related: #160010.
551 lines
20 KiB
TypeScript
551 lines
20 KiB
TypeScript
// Covers bundling rules encoded in the root tsdown config.
|
|
import { readFileSync } from "node:fs";
|
|
import path from "node:path";
|
|
import { expectDefined } from "@openclaw/normalization-core/expect";
|
|
import { bundledPluginRoot } from "openclaw/plugin-sdk/test-fixtures";
|
|
import type { TsdownPluginOption } from "tsdown";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import { buildPluginSdkPackageExports } from "../../scripts/lib/plugin-sdk-entries.mts";
|
|
import { importFreshModule } from "../../src/plugin-sdk/test-helpers/import-fresh.js";
|
|
import { OPENCLAW_AGENT_SCHEMA_SQL } from "../../src/state/openclaw-agent-schema.js";
|
|
import { OPENCLAW_STATE_SCHEMA_SQL } from "../../src/state/openclaw-state-schema.js";
|
|
import tsdownConfig, {
|
|
createStateSchemaInlinePlugin,
|
|
STATE_SCHEMA_INLINE_PLUGIN_NAME,
|
|
} from "../../tsdown.config.ts";
|
|
|
|
type TsdownConfigEntry = {
|
|
deps?: {
|
|
alwaysBundle?: string[] | ((id: string) => boolean);
|
|
neverBundle?: string[] | ((id: string) => boolean);
|
|
};
|
|
entry?: Record<string, string> | string[];
|
|
inputOptions?: TsdownInputOptions;
|
|
minify?: unknown;
|
|
dts?: boolean | { emitDtsOnly?: boolean };
|
|
define?: Record<string, unknown>;
|
|
outputOptions?: { codeSplitting?: boolean; chunkFileNames?: string };
|
|
outExtensions?: () => { js: string };
|
|
outDir?: string;
|
|
plugins?: TsdownPluginOption;
|
|
};
|
|
|
|
type TsdownLog = {
|
|
code?: string;
|
|
message?: string;
|
|
id?: string;
|
|
importer?: string;
|
|
plugin?: string;
|
|
};
|
|
|
|
type TsdownOnLog = (
|
|
level: string,
|
|
log: TsdownLog,
|
|
defaultHandler: (level: string, log: TsdownLog) => void,
|
|
) => void;
|
|
|
|
type TsdownInputOptions = (
|
|
options: { external?: TsdownExternalOption; onLog?: TsdownOnLog },
|
|
format?: unknown,
|
|
context?: unknown,
|
|
) => { external?: TsdownExternalOption; onLog?: TsdownOnLog } | undefined;
|
|
|
|
type TsdownExternalOption = string | RegExp | Array<string | RegExp> | 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];
|
|
}
|
|
|
|
// Keep config assertions aligned with tsdown's nested, async plugin slots.
|
|
async function resolvePluginNames(plugins: TsdownPluginOption): Promise<string[]> {
|
|
const resolved = await plugins;
|
|
if (!resolved) {
|
|
return [];
|
|
}
|
|
if (Array.isArray(resolved)) {
|
|
return (await Promise.all(resolved.map(resolvePluginNames))).flat();
|
|
}
|
|
if (!("name" in resolved)) {
|
|
throw new Error("expected a named plugin in build config assertions");
|
|
}
|
|
return [resolved.name];
|
|
}
|
|
|
|
function entryKeys(config: TsdownConfigEntry): string[] {
|
|
if (!config.entry || Array.isArray(config.entry)) {
|
|
return [];
|
|
}
|
|
return Object.keys(config.entry);
|
|
}
|
|
|
|
function entrySources(config: TsdownConfigEntry): Record<string, string> {
|
|
if (!config.entry || Array.isArray(config.entry)) {
|
|
return {};
|
|
}
|
|
return config.entry;
|
|
}
|
|
|
|
function requireStandaloneRuntimeGraph(entry: string): TsdownConfigEntry {
|
|
const graphs = asConfigArray(tsdownConfig).filter(
|
|
(config) =>
|
|
!(typeof config.dts === "object" && config.dts.emitDtsOnly) &&
|
|
entryKeys(config).includes(entry),
|
|
);
|
|
expect(graphs).toHaveLength(1);
|
|
return expectDefined(graphs[0], `${entry} standalone graph`);
|
|
}
|
|
|
|
function requireNativeHookRelayGraph(): TsdownConfigEntry {
|
|
const graphs = asConfigArray(tsdownConfig).filter((config) =>
|
|
entryKeys(config).includes("native-hook-relay/entry"),
|
|
);
|
|
expect(graphs).toHaveLength(1);
|
|
return expectDefined(graphs[0], "native hook relay graph");
|
|
}
|
|
|
|
function bundledEntry(pluginId: string): string {
|
|
return `${bundledPluginRoot(pluginId)}/index`;
|
|
}
|
|
|
|
function unifiedDistGraph(): TsdownConfigEntry | undefined {
|
|
return asConfigArray(tsdownConfig).find((config) =>
|
|
entryKeys(config).includes("plugins/runtime/index"),
|
|
);
|
|
}
|
|
|
|
function requireUnifiedDistGraph(): TsdownConfigEntry {
|
|
const distGraph = unifiedDistGraph();
|
|
if (!distGraph) {
|
|
throw new Error("expected unified dist graph");
|
|
}
|
|
return distGraph;
|
|
}
|
|
|
|
function readGatewayRunLoopSource(): string {
|
|
return readFileSync(new URL("../../src/cli/gateway-cli/run-loop.ts", import.meta.url), "utf8");
|
|
}
|
|
|
|
function readAgentAuthDiscoverySource(): string {
|
|
return readFileSync(new URL("../../src/agents/agent-auth-discovery.ts", import.meta.url), "utf8");
|
|
}
|
|
|
|
afterEach(() => vi.unstubAllEnvs());
|
|
|
|
describe("tsdown config", () => {
|
|
it.each(["0", "1"])(
|
|
"emits QA transport facades only for private QA builds (%s)",
|
|
async (mode) => {
|
|
vi.stubEnv("OPENCLAW_BUILD_PRIVATE_QA", mode);
|
|
const { default: selectedConfigs } = await importFreshModule<
|
|
typeof import("../../tsdown.config.ts")
|
|
>(import.meta.url, `../../tsdown.config.ts?private-qa=${mode}`);
|
|
const runtimeEntries = asConfigArray(selectedConfigs)
|
|
.filter((config) => !(typeof config.dts === "object" && config.dts.emitDtsOnly))
|
|
.flatMap((config) => Object.entries(entrySources(config)));
|
|
const packageExports = buildPluginSdkPackageExports();
|
|
for (const subpath of ["qa-channel", "qa-channel-protocol", "qa-lab", "qa-runtime"]) {
|
|
const matches = runtimeEntries.filter(([name]) => name === `plugin-sdk/${subpath}`);
|
|
expect(matches).toEqual(
|
|
mode === "1" ? [[`plugin-sdk/${subpath}`, `src/plugin-sdk/${subpath}.ts`]] : [],
|
|
);
|
|
expect(Object.hasOwn(packageExports, `./plugin-sdk/${subpath}`)).toBe(false);
|
|
}
|
|
},
|
|
);
|
|
|
|
it("minifies only the sealed deploy worker while preserving runtime names", () => {
|
|
const configs = asConfigArray(tsdownConfig);
|
|
const deployWorker = configs.find((config) => entryKeys(config).includes("worker/worker"));
|
|
const rsyncReceiver = configs.find((config) =>
|
|
entryKeys(config).includes("worker/workspace-rsync-receiver"),
|
|
);
|
|
const githubExecLauncher = configs.find((config) =>
|
|
entryKeys(config).includes("worker/github-exec-launcher"),
|
|
);
|
|
|
|
expect(deployWorker?.minify).toEqual({
|
|
codegen: true,
|
|
compress: true,
|
|
mangle: { keepNames: true },
|
|
});
|
|
expect(rsyncReceiver?.minify).toBeUndefined();
|
|
expect(githubExecLauncher?.minify).toBeUndefined();
|
|
expect(requireUnifiedDistGraph().minify).toBeUndefined();
|
|
});
|
|
|
|
it.each([
|
|
{
|
|
exportName: "OPENCLAW_STATE_SCHEMA_SQL",
|
|
modulePath: "src/state/openclaw-state-schema.ts",
|
|
schemaPath: "src/state/openclaw-state-schema.sql",
|
|
sourceValue: OPENCLAW_STATE_SCHEMA_SQL,
|
|
},
|
|
{
|
|
exportName: "OPENCLAW_AGENT_SCHEMA_SQL",
|
|
modulePath: "src/state/openclaw-agent-schema.ts",
|
|
schemaPath: "src/state/openclaw-agent-schema.sql",
|
|
sourceValue: OPENCLAW_AGENT_SCHEMA_SQL,
|
|
},
|
|
])("inlines canonical schema bytes for $modulePath", (schema) => {
|
|
const rootDir = process.cwd();
|
|
const watchedPaths: string[] = [];
|
|
const plugin = createStateSchemaInlinePlugin(rootDir);
|
|
let cacheKeyGenerator: ((context: { id: string }) => string | undefined) | undefined;
|
|
plugin.configureVitest({
|
|
defineCacheKeyGenerator: (generator) => {
|
|
cacheKeyGenerator = generator;
|
|
},
|
|
});
|
|
const result = plugin.load.call(
|
|
{ addWatchFile: (filePath: string) => watchedPaths.push(filePath) },
|
|
path.resolve(rootDir, schema.modulePath),
|
|
);
|
|
const schemaPath = path.resolve(rootDir, schema.schemaPath);
|
|
const canonicalSql = readFileSync(schemaPath, "utf8");
|
|
|
|
expect(result).not.toBeNull();
|
|
const match = result?.code.match(
|
|
new RegExp(`^export const ${schema.exportName} = (.*);\\n$`, "su"),
|
|
);
|
|
expect(match?.[1]).toBeDefined();
|
|
expect(JSON.parse(match?.[1] ?? "null")).toBe(canonicalSql);
|
|
expect(schema.sourceValue).toBe(canonicalSql);
|
|
expect(watchedPaths).toEqual([schemaPath]);
|
|
expect(cacheKeyGenerator?.({ id: path.resolve(rootDir, schema.modulePath) })).toBe(
|
|
canonicalSql,
|
|
);
|
|
expect(cacheKeyGenerator?.({ id: path.resolve(rootDir, "src/index.ts") })).toBeUndefined();
|
|
});
|
|
|
|
it("installs schema inlining only on executable runtime graphs", async () => {
|
|
const configs = asConfigArray(tsdownConfig);
|
|
const unifiedGraph = requireUnifiedDistGraph();
|
|
const workerGraph = configs.find(
|
|
(config) => entrySources(config)["worker/worker"] === "src/worker/worker-deploy-entry.ts",
|
|
);
|
|
const handoffGraph = configs.find((config) =>
|
|
entryKeys(config).includes("managed-handoff-runtime"),
|
|
);
|
|
const executableGraphs = new Set([
|
|
unifiedGraph,
|
|
expectDefined(workerGraph, "deploy worker graph"),
|
|
requireStandaloneRuntimeGraph("worker/file-tool-planning.worker"),
|
|
requireStandaloneRuntimeGraph("worker/image-processor.worker"),
|
|
requireStandaloneRuntimeGraph("worker/sqlite-store.worker"),
|
|
expectDefined(handoffGraph, "managed handoff graph"),
|
|
requireNativeHookRelayGraph(),
|
|
requireStandaloneRuntimeGraph("infra/sqlite-readonly-location.worker"),
|
|
requireStandaloneRuntimeGraph("infra/sqlite-source-revision.worker"),
|
|
requireStandaloneRuntimeGraph("state/openclaw-state-read.worker"),
|
|
requireStandaloneRuntimeGraph("agents/harness/native-hook-relay-client.worker"),
|
|
requireStandaloneRuntimeGraph("process/spawn-broker/worker"),
|
|
requireStandaloneRuntimeGraph("state/openclaw-state-lease-heartbeat.worker"),
|
|
requireStandaloneRuntimeGraph("tooling/managed-memory-launcher"),
|
|
]);
|
|
|
|
for (const config of configs) {
|
|
const inlinePlugins = (await resolvePluginNames(config.plugins)).filter(
|
|
(name) => name === STATE_SCHEMA_INLINE_PLUGIN_NAME,
|
|
);
|
|
expect(inlinePlugins).toHaveLength(executableGraphs.has(config) ? 1 : 0);
|
|
}
|
|
});
|
|
|
|
it("isolates relay startup from shared runtime chunks while retaining lazy fallback", async () => {
|
|
const relay = requireNativeHookRelayGraph();
|
|
expect(entrySources(relay)).toEqual({
|
|
"native-hook-relay/entry": "src/cli/native-hook-relay-entry.ts",
|
|
});
|
|
expect(relay).not.toBe(requireUnifiedDistGraph());
|
|
expect(relay.dts).toBe(false);
|
|
expect(relay.outputOptions?.codeSplitting).not.toBe(false);
|
|
expect(relay.outputOptions?.chunkFileNames).toBe("native-hook-relay/[name]-[hash].mjs");
|
|
// Only the shared graph may publish the global plugin ownership manifest.
|
|
expect(await resolvePluginNames(relay.plugins)).not.toContain(
|
|
"openclaw:runtime-dependency-ownership",
|
|
);
|
|
});
|
|
|
|
it("keeps core, plugin runtime, plugin-sdk, bundled root plugins, and bundled hooks in one dist graph", () => {
|
|
const distGraph = requireUnifiedDistGraph();
|
|
|
|
const keys = entryKeys(distGraph);
|
|
for (const entry of [
|
|
"acp/control-plane/manager",
|
|
"agents/auth-profiles.runtime",
|
|
"agents/model-catalog.runtime",
|
|
"agents/models-config.runtime",
|
|
"cli/gateway-lifecycle.runtime",
|
|
"agents/compaction-planning.worker",
|
|
"config/sessions/session-accessor.sqlite-archive.worker",
|
|
"plugin-sdk/sqlite-runtime",
|
|
"state/openclaw-database-verify.worker",
|
|
"plugins/memory-state",
|
|
"subagent-registry.runtime",
|
|
"link-understanding/apply.runtime",
|
|
"media-understanding/apply.runtime",
|
|
"index",
|
|
"commands/status.summary.runtime",
|
|
"docker-healthcheck",
|
|
"provider-dispatcher.runtime",
|
|
"plugins/hook-runner-global",
|
|
"plugins/provider-discovery.runtime",
|
|
"plugins/provider-runtime.runtime",
|
|
"plugins/runtime/index",
|
|
"plugins/synthetic-auth.runtime",
|
|
"web-fetch/runtime",
|
|
"mcp/openclaw-tools-serve",
|
|
"mcp/plugin-tools-serve",
|
|
bundledEntry("active-memory"),
|
|
"bundled/boot-md/handler",
|
|
]) {
|
|
expect(keys).toContain(entry);
|
|
}
|
|
});
|
|
|
|
it.each([
|
|
{
|
|
label: "read-only snapshot child",
|
|
entry: "infra/sqlite-readonly-location.worker",
|
|
source: "src/infra/sqlite-readonly-location.worker.ts",
|
|
},
|
|
{
|
|
label: "raw source revision child",
|
|
entry: "infra/sqlite-source-revision.worker",
|
|
source: "src/infra/sqlite-source-revision.worker.ts",
|
|
},
|
|
{
|
|
label: "shared-state reader",
|
|
entry: "state/openclaw-state-read.worker",
|
|
source: "src/state/openclaw-state-read.worker.ts",
|
|
},
|
|
{
|
|
label: "native hook locator worker",
|
|
entry: "agents/harness/native-hook-relay-client.worker",
|
|
source: "src/agents/harness/native-hook-relay-client.worker.ts",
|
|
},
|
|
{
|
|
label: "spawn broker",
|
|
entry: "process/spawn-broker/worker",
|
|
source: "src/process/spawn-broker/worker.ts",
|
|
},
|
|
{
|
|
label: "state lease heartbeat",
|
|
entry: "state/openclaw-state-lease-heartbeat.worker",
|
|
source: "src/state/openclaw-state-lease-heartbeat.worker.ts",
|
|
},
|
|
])("emits the $label once without sealing its package loaders", ({ entry, source }) => {
|
|
const child = requireStandaloneRuntimeGraph(entry);
|
|
expect(entrySources(child)).toEqual({ [entry]: path.resolve(source) });
|
|
expect(child.outputOptions).toEqual({ codeSplitting: false });
|
|
expect(child.outExtensions?.().js).toBe(".js");
|
|
expect(child.define?.SEALED_RUNTIME_BUILD).toBeUndefined();
|
|
});
|
|
|
|
it("builds the Docker healthcheck as a stable dist entry", () => {
|
|
const distGraph = requireUnifiedDistGraph();
|
|
|
|
expect(entrySources(distGraph)["docker-healthcheck"]).toBe("src/docker-healthcheck.ts");
|
|
});
|
|
|
|
it("keeps root-package-excluded external plugins out of the root dist graph", () => {
|
|
const distGraph = requireUnifiedDistGraph();
|
|
const keys = entryKeys(distGraph);
|
|
const hasPluginEntry = (pluginId: string) =>
|
|
keys.some((entry) => entry.startsWith(`${bundledPluginRoot(pluginId)}/`));
|
|
|
|
expect(hasPluginEntry("amazon-bedrock")).toBe(false);
|
|
expect(hasPluginEntry("amazon-bedrock-mantle")).toBe(false);
|
|
});
|
|
|
|
it("keeps gateway lifecycle lazy runtime behind one stable dist entry", () => {
|
|
const distGraph = requireUnifiedDistGraph();
|
|
|
|
expect(entrySources(distGraph)["cli/gateway-lifecycle.runtime"]).toBe(
|
|
"src/cli/gateway-cli/lifecycle.runtime.ts",
|
|
);
|
|
});
|
|
|
|
it("keeps lazy transcript reconciliation behind one stable dist entry", () => {
|
|
const distGraph = requireUnifiedDistGraph();
|
|
|
|
expect(entrySources(distGraph)["config/sessions/session-transcript-reconcile"]).toBe(
|
|
"src/config/sessions/session-transcript-reconcile.ts",
|
|
);
|
|
});
|
|
|
|
it("keeps reply dispatcher lazy runtime behind one root stable dist entry", () => {
|
|
const distGraph = requireUnifiedDistGraph();
|
|
|
|
expect(entrySources(distGraph)["provider-dispatcher.runtime"]).toBe(
|
|
"src/auto-reply/reply/provider-dispatcher.runtime.ts",
|
|
);
|
|
});
|
|
|
|
it("keeps gateway shutdown hook runner behind one stable dist entry", () => {
|
|
const distGraph = requireUnifiedDistGraph();
|
|
|
|
expect(entrySources(distGraph)["plugins/hook-runner-global"]).toBe(
|
|
"src/plugins/hook-runner-global.ts",
|
|
);
|
|
});
|
|
|
|
it("keeps worker environment bootstrap behind one stable dist entry", () => {
|
|
const distGraph = requireUnifiedDistGraph();
|
|
|
|
expect(entrySources(distGraph)["gateway/worker-environments/runtime"]).toBe(
|
|
"src/gateway/worker-environments/runtime.ts",
|
|
);
|
|
});
|
|
|
|
it("preserves the reload entry lazy-loaded by already-running v2026.9.1 Gateways", () => {
|
|
const distGraph = requireUnifiedDistGraph();
|
|
|
|
expect(entrySources(distGraph)["gateway/plugin-channel-reload-targets"]).toBe(
|
|
"src/gateway/plugin-channel-reload-targets.ts",
|
|
);
|
|
});
|
|
|
|
it("keeps PI model discovery synthetic auth refs behind one stable runtime dist entry", () => {
|
|
const distGraph = requireUnifiedDistGraph();
|
|
const importSpecifiers = [
|
|
...readAgentAuthDiscoverySource().matchAll(
|
|
/from ["']([^"']*synthetic-auth\.runtime\.js)["']/gu,
|
|
),
|
|
].map((match) => match[1]);
|
|
|
|
expect(importSpecifiers).toEqual(["../plugins/synthetic-auth.runtime.js"]);
|
|
expect(entrySources(distGraph)["plugins/synthetic-auth.runtime"]).toBe(
|
|
"src/plugins/synthetic-auth.runtime.ts",
|
|
);
|
|
});
|
|
|
|
it("keeps Telegram ingress worker behind one root stable dist entry", () => {
|
|
const distGraph = requireUnifiedDistGraph();
|
|
|
|
expect(entrySources(distGraph)["telegram-ingress-worker.runtime"]).toBe(
|
|
"extensions/telegram/src/telegram-ingress-worker.runtime.ts",
|
|
);
|
|
});
|
|
|
|
it("routes gateway run-loop lifecycle imports through the stable runtime boundary", () => {
|
|
const importSpecifiers = [
|
|
...readGatewayRunLoopSource().matchAll(/import\(["']([^"']+)["']\)/gu),
|
|
].map((match) => match[1]);
|
|
|
|
expect(new Set(importSpecifiers)).toEqual(new Set(["./lifecycle.runtime.js"]));
|
|
});
|
|
|
|
it("keeps bundled plugins out of separate dependency-staging graphs", () => {
|
|
const extensionGraphs = asConfigArray(tsdownConfig).filter(
|
|
(config) => typeof config.outDir === "string" && config.outDir.startsWith("dist/extensions/"),
|
|
);
|
|
|
|
expect(extensionGraphs).toStrictEqual([]);
|
|
});
|
|
|
|
it("does not emit plugin-sdk or hooks from a separate dist graph", () => {
|
|
const configs = asConfigArray(tsdownConfig);
|
|
const hookEntries = configs.flatMap((config) =>
|
|
Array.isArray(config.entry)
|
|
? config.entry.filter((entry) => entry.includes("src/hooks/"))
|
|
: [],
|
|
);
|
|
|
|
expect(configs.map((config) => config.outDir)).not.toContain("dist/plugin-sdk");
|
|
expect(hookEntries).toStrictEqual([]);
|
|
});
|
|
|
|
it("bundles SDK-owned helpers while retaining native package ownership", () => {
|
|
for (const graph of [
|
|
requireUnifiedDistGraph(),
|
|
requireStandaloneRuntimeGraph("infra/sqlite-readonly-location.worker"),
|
|
]) {
|
|
const alwaysBundle = graph.deps?.alwaysBundle;
|
|
const external = graph.inputOptions?.({})?.external;
|
|
if (typeof alwaysBundle !== "function" || typeof external !== "function") {
|
|
throw new Error("expected runtime graph dependency predicates");
|
|
}
|
|
|
|
expect(alwaysBundle("@openclaw/fs-safe")).toBe(false);
|
|
expect(alwaysBundle("@openclaw/fs-safe/path")).toBe(false);
|
|
expect(external("@openclaw/fs-safe/path", undefined, false)).toBe(true);
|
|
expect(alwaysBundle("openclaw/plugin-sdk/ssrf-runtime-internal")).toBe(true);
|
|
expect(alwaysBundle("openclaw/plugin-sdk/ssrf-runtime")).toBe(false);
|
|
expect(alwaysBundle("zod")).toBe(true);
|
|
expect(alwaysBundle("zod/v4/core")).toBe(true);
|
|
for (const id of ["typebox", "typebox/schema", "typebox/format", "typebox/system"]) {
|
|
expect(alwaysBundle(id)).toBe(false);
|
|
expect(external(id, undefined, false)).toBe(true);
|
|
}
|
|
expect(alwaysBundle("not-a-runtime-dependency")).toBe(false);
|
|
}
|
|
});
|
|
|
|
it("suppresses unresolved imports from extension source", () => {
|
|
const configured = unifiedDistGraph()?.inputOptions?.({})?.onLog;
|
|
const handled: TsdownLog[] = [];
|
|
|
|
configured?.(
|
|
"warn",
|
|
{
|
|
code: "UNRESOLVED_IMPORT",
|
|
message: "Could not resolve '@azure/identity' in extensions/msteams/src/sdk.ts",
|
|
},
|
|
(_level, log) => handled.push(log),
|
|
);
|
|
|
|
expect(handled).toStrictEqual([]);
|
|
});
|
|
|
|
it("keeps unresolved imports outside extension source visible", () => {
|
|
const configured = unifiedDistGraph()?.inputOptions?.({})?.onLog;
|
|
const handled: TsdownLog[] = [];
|
|
const log = {
|
|
code: "UNRESOLVED_IMPORT",
|
|
message: "Could not resolve 'missing-dependency' in src/index.ts",
|
|
};
|
|
|
|
configured?.("warn", log, (_level, forwardedLog) => handled.push(forwardedLog));
|
|
|
|
expect(handled).toEqual([log]);
|
|
});
|
|
|
|
it("suppresses rolldown-plugin-dts CommonJS dts warnings from bundled zod locales", () => {
|
|
const configured = unifiedDistGraph()?.inputOptions?.({})?.onLog;
|
|
const handled: TsdownLog[] = [];
|
|
|
|
configured?.(
|
|
"warn",
|
|
{
|
|
code: "PLUGIN_WARNING",
|
|
plugin: "rolldown-plugin-dts:fake-js",
|
|
message:
|
|
"/abs/path/node_modules/zod/v4/locales/ur.d.cts uses CommonJS dts syntax. CommonJS dts modules cannot be reliably bundled by rolldown-plugin-dts. Please mark this module as external in your Rolldown config.",
|
|
},
|
|
(_level, log) => handled.push(log),
|
|
);
|
|
|
|
expect(handled).toStrictEqual([]);
|
|
});
|
|
|
|
it("keeps other rolldown-plugin-dts warnings visible", () => {
|
|
const configured = unifiedDistGraph()?.inputOptions?.({})?.onLog;
|
|
const handled: TsdownLog[] = [];
|
|
const log = {
|
|
code: "PLUGIN_WARNING",
|
|
plugin: "rolldown-plugin-dts:fake-js",
|
|
message: "some other dts warning that should not be hidden",
|
|
};
|
|
|
|
configured?.("warn", log, (_level, forwardedLog) => handled.push(forwardedLog));
|
|
|
|
expect(handled).toEqual([log]);
|
|
});
|
|
});
|