openclaw/test/scripts/check-deprecated-api-usage.test.ts
Peter Steinberger e649be315d
refactor(plugin-sdk)!: retire deprecated compatibility facades (#162333)
Retire the five SDK compatibility facades under the approved September 30 owner decision, and migrate in-repository callers onto their focused contracts. Keep implementations with their canonical owners and remove forwarding exports that become unused after the cutover.

BREAKING CHANGE: remove openclaw/plugin-sdk/channel-lifecycle, channel-message, channel-reply-pipeline, config-runtime, and infra-runtime. Use channel-outbound/channel-inbound, config-contracts and focused configuration/infra entrypoints. The new system-event-runtime entrypoint supplies public snapshot inspection and consumption. Update affected external plugins before upgrading the host; this retirement does not certify universal external migration.

Package exports, SDK entry inventories, compatibility tombstones, migration docs, and surface budgets move together. Canonical API comparison confirms exactly five removed entrypoints, 869 removed export paths, 35 focused additions, and no retained-export signature changes. Net production reduction: 1,320 lines.
2026-10-01 04:12:01 -07:00

185 lines
7.9 KiB
TypeScript

// Check Deprecated Api Usage tests cover check deprecated api usage script behavior.
import { spawnSync } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { describe, expect, it } from "vitest";
import { buildDeprecatedPluginSdkModuleSpecifiers } from "../../scripts/lib/deprecated-plugin-sdk-usage.mts";
const GUARD_SCRIPT_PATH = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
"../../scripts/check-deprecated-api-usage.mts",
);
function runRules(sourceByRepoPath: Record<string, string>, ruleIds = ["facade-internal-imports"]) {
// realpath first: macOS os.tmpdir() is a /var -> /private/var symlink and the
// script reports repo-relative paths from its resolved cwd.
const fixtureRoot = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), "deprecated-guard-")));
try {
for (const [repoPath, source] of Object.entries(sourceByRepoPath)) {
const filePath = path.join(fixtureRoot, repoPath);
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, source);
}
const accountingPath = path.join(fixtureRoot, "account-source-io.mjs");
fs.writeFileSync(
accountingPath,
`import fs from "node:fs";
import path from "node:path";
import { syncBuiltinESMExports } from "node:module";
const counts = { files: {}, directories: {} };
const root = process.cwd();
function count(kind, file) {
if (typeof file !== "string") return;
const relative = path.relative(root, file).split(path.sep).join("/");
if (!/^(src|extensions|packages)(\\/|$)/.test(relative)) return;
counts[kind][relative] = (counts[kind][relative] ?? 0) + 1;
}
for (const [method, kind] of [["readFileSync", "files"], ["readdirSync", "directories"]]) {
const original = fs[method];
fs[method] = function (file, ...args) {
const result = original.call(this, file, ...args);
count(kind, file);
return result;
};
}
syncBuiltinESMExports();
process.on("exit", () => fs.writeFileSync("source-io.json", JSON.stringify(counts)));
`,
);
const result = spawnSync(
process.execPath,
[
"--import",
pathToFileURL(accountingPath).href,
GUARD_SCRIPT_PATH,
...ruleIds.map((id) => `--rule=${id}`),
],
{ cwd: fixtureRoot, encoding: "utf8" },
);
const io: { files: Record<string, number>; directories: Record<string, number> } = JSON.parse(
fs.readFileSync(path.join(fixtureRoot, "source-io.json"), "utf8"),
);
return { ...result, io };
} finally {
fs.rmSync(fixtureRoot, { recursive: true, force: true });
}
}
describe("scripts/check-deprecated-api-usage", () => {
it("keeps removed root and private compatibility aliases out of the inventory", () => {
const specifiers = buildDeprecatedPluginSdkModuleSpecifiers();
for (const removedSpecifier of [
"openclaw/plugin-sdk",
"openclaw/plugin-sdk/agent-dir-compat",
"openclaw/plugin-sdk/test-utils",
]) {
expect(specifiers).not.toContain(removedSpecifier);
}
});
it("flags internal facade imports across static, relative, scoped, and dynamic forms", () => {
const result = runRules({
"src/channels/probe.ts": [
'import { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline";',
'export { runChannelInboundEvent } from "../plugin-sdk/inbound-reply-dispatch.js";',
'const facade = await import ("../plugin-sdk/channel-message.js", { with: {} });',
].join("\n"),
});
expect(result.status).toBe(1);
expect(result.stderr).toContain(
"src/channels/probe.ts:1: openclaw/plugin-sdk/channel-reply-pipeline",
);
expect(result.stderr).toContain(
"src/channels/probe.ts:2: ../plugin-sdk/inbound-reply-dispatch.js",
);
expect(result.stderr).toContain("src/channels/probe.ts:3: ../plugin-sdk/channel-message.js");
});
it("allows canonical compat re-exports and test files", () => {
const result = runRules({
"src/plugin-sdk/inbound-reply-dispatch.ts":
'export { runChannelInboundEvent } from "./channel-inbound.js";',
"src/plugin-sdk/inbound-reply-dispatch.test.ts":
'const mod = await import("openclaw/plugin-sdk/inbound-reply-dispatch");',
});
expect(result.stderr).toBe("");
expect(result.status).toBe(0);
});
it("reads overlapping rule inputs once while preserving complete diagnostic order", () => {
const result = runRules(
{
"src/a.ts":
'import { x } from "openclaw/plugin-sdk/inbound-reply-dispatch";\nimport { y } from "@openclaw/plugin-sdk/inbound-reply-dispatch";\ndeliverOutboundPayloads();',
"extensions/probe/src/a.ts":
'export { x } from "openclaw/plugin-sdk/inbound-reply-dispatch";\ndeliverOutboundPayloads();',
"packages/a.ts":
'import { x } from "openclaw/plugin-sdk/command-auth";\ndeliverOutboundPayloads();',
"src/infra/outbound/deliver.ts": "deliverOutboundPayloads();",
"src/a.test.ts": "deliverOutboundPayloads();",
},
[
"message-api",
"facade-internal-imports",
"extension-plugin-sdk-compat-subpaths",
"plugin-sdk-compat-subpaths",
],
);
expect(result.status).toBe(1);
expect(result.stdout).toBe("");
expect(result.stderr).toBe(
[
"Deprecated API usage guard failed:",
"- plugin-sdk-compat-subpaths: src/a.ts:1: openclaw/plugin-sdk/inbound-reply-dispatch (use focused non-deprecated plugin SDK subpaths)",
"- plugin-sdk-compat-subpaths: src/a.ts:2: @openclaw/plugin-sdk/inbound-reply-dispatch (use focused non-deprecated plugin SDK subpaths)",
"- plugin-sdk-compat-subpaths: packages/a.ts:1: openclaw/plugin-sdk/command-auth (use focused non-deprecated plugin SDK subpaths)",
"- extension-plugin-sdk-compat-subpaths: extensions/probe/src/a.ts:1: openclaw/plugin-sdk/inbound-reply-dispatch (extensions must use focused non-deprecated plugin SDK subpaths)",
"- facade-internal-imports: src/a.ts:1: openclaw/plugin-sdk/inbound-reply-dispatch (use openclaw/plugin-sdk/channel-inbound)",
"- facade-internal-imports: src/a.ts:2: @openclaw/plugin-sdk/inbound-reply-dispatch (use openclaw/plugin-sdk/channel-inbound)",
"- facade-internal-imports: extensions/probe/src/a.ts:1: openclaw/plugin-sdk/inbound-reply-dispatch (use openclaw/plugin-sdk/channel-inbound)",
"- message-api: src/a.ts:3: deliverOutboundPayloads (use sendDurableMessageBatch or deliverInboundReplyWithMessageSendContext)",
"- message-api: extensions/probe/src/a.ts:2: deliverOutboundPayloads (use sendDurableMessageBatch or deliverInboundReplyWithMessageSendContext)",
"- message-api: packages/a.ts:2: deliverOutboundPayloads (use sendDurableMessageBatch or deliverInboundReplyWithMessageSendContext)",
"",
].join("\n"),
);
expect(result.io.files).toEqual({
"src/a.ts": 1,
"src/infra/outbound/deliver.ts": 1,
"packages/a.ts": 1,
"extensions/probe/src/a.ts": 1,
});
expect(result.io.directories).toEqual({
src: 1,
"src/infra": 1,
"src/infra/outbound": 1,
packages: 1,
extensions: 1,
"extensions/probe": 1,
"extensions/probe/src": 1,
});
});
it("does not read a file allowed by the only selected rule", () => {
const result = runRules({ "src/infra/outbound/deliver.ts": "deliverOutboundPayloads();" }, [
"message-api",
]);
expect(result.status).toBe(0);
expect(result.stderr).toBe("");
expect(result.io.files).toEqual({});
});
it("rejects unknown rules before reading any source directories", () => {
const result = runRules({ "src/a.ts": "deliverOutboundPayloads();" }, ["unknown"]);
expect(result.status).toBe(1);
expect(result.stderr).toBe("Unknown deprecated API usage rule(s): unknown\n");
expect(result.io).toEqual({ files: {}, directories: {} });
});
});