Files
openclaw/src/plugins/contracts/plugin-sdk-index.bundle.test.ts
2026-04-12 05:15:15 +01:00

114 lines
4.6 KiB
TypeScript

import fs from "node:fs/promises";
import { createRequire } from "node:module";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { afterAll, describe, expect, it } from "vitest";
import { buildPluginSdkEntrySources, pluginSdkEntrypoints } from "../../plugin-sdk/entrypoints.js";
import { createSuiteTempRootTracker } from "../test-helpers/fs-fixtures.js";
import { resolveBundledPluginFile } from "./test-helpers/bundled-plugin-roots.js";
const require = createRequire(import.meta.url);
const tsdownModuleUrl = pathToFileURL(require.resolve("tsdown")).href;
const bundledRepresentativeEntrypoints = ["matrix-runtime-heavy"] as const;
const bundleTempRootTracker = createSuiteTempRootTracker(
"openclaw-plugin-sdk-build",
path.join(process.cwd(), "node_modules", ".cache"),
);
const matrixRuntimeCoverageEntries = {
"matrix-runtime-sdk": resolveBundledPluginFile({
pluginId: "matrix",
relativePath: "src/matrix/sdk.ts",
}),
} as const;
const bundledCoverageEntrySources = {
...buildPluginSdkEntrySources(bundledRepresentativeEntrypoints),
...matrixRuntimeCoverageEntries,
};
const bareMatrixSdkImportPattern = /(?:from|require|import)\s*\(?\s*["']matrix-js-sdk["']/;
async function listBuiltJsFiles(rootDir: string): Promise<string[]> {
const entries = await fs.readdir(rootDir, { withFileTypes: true });
const nested = await Promise.all(
entries.map(async (entry) => {
const entryPath = path.join(rootDir, entry.name);
if (entry.isDirectory()) {
return await listBuiltJsFiles(entryPath);
}
return entry.isFile() && entry.name.endsWith(".js") ? [entryPath] : [];
}),
);
return nested.flat();
}
describe("plugin-sdk bundled exports", () => {
afterAll(() => {
bundleTempRootTracker.cleanup();
});
it("emits importable bundled subpath entries", { timeout: 120_000 }, async () => {
const bundleTempRoot = bundleTempRootTracker.ensureSuiteTempRoot();
const outDir = path.join(bundleTempRoot, "bundle");
await fs.rm(outDir, { recursive: true, force: true });
await fs.mkdir(outDir, { recursive: true });
const { build } = await import(tsdownModuleUrl);
await build({
clean: false,
config: false,
dts: false,
deps: {
// Match the production host build contract: Matrix SDK packages stay
// external so the heavy runtime surface does not fold multiple
// matrix-js-sdk entrypoints into one bundle artifact.
neverBundle: ["@lancedb/lancedb", "@matrix-org/matrix-sdk-crypto-nodejs", "matrix-js-sdk"],
},
// Full plugin-sdk coverage belongs to `pnpm build`, package contract
// guardrails, and `plugin-sdk-subpaths.test.ts`. This file only keeps the expensive
// bundler path honest across representative entrypoint families plus the
// Matrix SDK runtime import surface that historically crashed plugin
// loading when bare and deep SDK entrypoints mixed.
entry: bundledCoverageEntrySources,
env: { NODE_ENV: "production" },
fixedExtension: false,
logLevel: "error",
outDir,
platform: "node",
});
expect(pluginSdkEntrypoints.length).toBeGreaterThan(bundledRepresentativeEntrypoints.length);
await Promise.all(
bundledRepresentativeEntrypoints.map(async (entry) => {
await expect(fs.stat(path.join(outDir, `${entry}.js`))).resolves.toBeTruthy();
}),
);
await Promise.all(
Object.keys(matrixRuntimeCoverageEntries).map(async (entry) => {
await expect(fs.stat(path.join(outDir, `${entry}.js`))).resolves.toBeTruthy();
}),
);
const builtJsFiles = await listBuiltJsFiles(outDir);
const filesWithBareMatrixSdkImports = (
await Promise.all(
builtJsFiles.map(async (filePath) => {
const contents = await fs.readFile(filePath, "utf8");
return bareMatrixSdkImportPattern.test(contents) ? filePath : null;
}),
)
).filter((filePath): filePath is string => filePath !== null);
expect(filesWithBareMatrixSdkImports).toEqual([]);
// Export list and package-specifier coverage already live in
// plugin-sdk-package-contract-guardrails.test.ts and plugin-sdk-subpaths.test.ts. Keep this file
// focused on the expensive part: can tsdown emit working bundle artifacts?
const importResults = await Promise.all(
bundledRepresentativeEntrypoints.map(async (entry) => [
entry,
typeof (await import(pathToFileURL(path.join(outDir, `${entry}.js`)).href)),
]),
);
expect(Object.fromEntries(importResults)).toEqual(
Object.fromEntries(bundledRepresentativeEntrypoints.map((entry) => [entry, "object"])),
);
});
});