mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-24 08:21:39 +00:00
174 lines
5.9 KiB
TypeScript
174 lines
5.9 KiB
TypeScript
import path from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
import {
|
|
classifyPluginBoundaryImport,
|
|
compareViolationBaseline,
|
|
findPluginBoundaryViolations,
|
|
toBaselineKey,
|
|
} from "../../scripts/check-plugin-boundary-ratchet.mjs";
|
|
|
|
const repoRoot = "/repo";
|
|
const extensionFile = "/repo/extensions/example/src/index.ts";
|
|
|
|
describe("check-plugin-boundary-ratchet", () => {
|
|
it("allows public plugin-sdk imports", () => {
|
|
expect(
|
|
classifyPluginBoundaryImport("openclaw/plugin-sdk/discord", extensionFile, { repoRoot }),
|
|
).toBeNull();
|
|
expect(
|
|
classifyPluginBoundaryImport("openclaw/plugin-sdk", extensionFile, { repoRoot }),
|
|
).toBeNull();
|
|
});
|
|
|
|
it("allows compat for now", () => {
|
|
expect(
|
|
classifyPluginBoundaryImport("openclaw/plugin-sdk/compat", extensionFile, { repoRoot }),
|
|
).toBeNull();
|
|
});
|
|
|
|
it("rejects plugin-sdk-internal imports", () => {
|
|
expect(
|
|
classifyPluginBoundaryImport("../../../src/plugin-sdk-internal/discord.js", extensionFile, {
|
|
repoRoot,
|
|
}),
|
|
).toMatchObject({
|
|
kind: "plugin-sdk-internal",
|
|
});
|
|
});
|
|
|
|
it("does not reject same-plugin files that merely contain plugin-sdk-internal in the filename", () => {
|
|
expect(
|
|
classifyPluginBoundaryImport("./plugin-sdk-internal-fixture.js", extensionFile, { repoRoot }),
|
|
).toBeNull();
|
|
});
|
|
|
|
it("rejects direct core src imports", () => {
|
|
expect(
|
|
classifyPluginBoundaryImport(
|
|
"../../src/config/config.js",
|
|
"/repo/extensions/example/index.ts",
|
|
{
|
|
repoRoot,
|
|
},
|
|
),
|
|
).toMatchObject({
|
|
kind: "core-src",
|
|
});
|
|
});
|
|
|
|
it("ignores same-plugin relative imports", () => {
|
|
expect(classifyPluginBoundaryImport("./helpers.js", extensionFile, { repoRoot })).toBeNull();
|
|
expect(
|
|
classifyPluginBoundaryImport("../shared/util.js", extensionFile, { repoRoot }),
|
|
).toBeNull();
|
|
});
|
|
|
|
it("rejects cross-extension relative imports", () => {
|
|
expect(
|
|
classifyPluginBoundaryImport("../../other-plugin/src/helper.js", extensionFile, { repoRoot }),
|
|
).toMatchObject({
|
|
kind: "cross-extension",
|
|
});
|
|
});
|
|
|
|
it("finds import and dynamic import violations", () => {
|
|
const source = `
|
|
import { x } from "../../../src/config/config.js";
|
|
export { y } from "../../../src/plugin-sdk-internal/discord.js";
|
|
const z = await import("../../../src/runtime.js");
|
|
`;
|
|
expect(
|
|
findPluginBoundaryViolations(source, "/repo/extensions/example/nested/file.ts", { repoRoot }),
|
|
).toEqual([
|
|
{
|
|
kind: "core-src",
|
|
line: 2,
|
|
preferredReplacement:
|
|
"Use openclaw/plugin-sdk/*, openclaw/extension-api, or openclaw/plugin-sdk/compat temporarily.",
|
|
reason: "reaches into core src/** from an extension",
|
|
specifier: "../../../src/config/config.js",
|
|
},
|
|
{
|
|
kind: "plugin-sdk-internal",
|
|
line: 3,
|
|
preferredReplacement:
|
|
"Use openclaw/plugin-sdk/* or openclaw/plugin-sdk/compat temporarily.",
|
|
reason: "reaches into non-public plugin-sdk-internal implementation",
|
|
specifier: "../../../src/plugin-sdk-internal/discord.js",
|
|
},
|
|
{
|
|
kind: "core-src",
|
|
line: 4,
|
|
preferredReplacement:
|
|
"Use openclaw/plugin-sdk/*, openclaw/extension-api, or openclaw/plugin-sdk/compat temporarily.",
|
|
reason: "reaches into core src/** from an extension",
|
|
specifier: "../../../src/runtime.js",
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("finds require and test mock violations", () => {
|
|
const source = `
|
|
const x = require("../../../src/config/config.js");
|
|
vi.mock("../../../src/plugin-sdk-internal/discord.js", () => ({}));
|
|
jest.mock("../../other-plugin/src/helper.js", () => ({}));
|
|
`;
|
|
expect(
|
|
findPluginBoundaryViolations(source, "/repo/extensions/example/nested/file.test.ts", {
|
|
repoRoot,
|
|
}),
|
|
).toEqual([
|
|
{
|
|
kind: "core-src",
|
|
line: 2,
|
|
preferredReplacement:
|
|
"Use openclaw/plugin-sdk/*, openclaw/extension-api, or openclaw/plugin-sdk/compat temporarily.",
|
|
reason: "reaches into core src/** from an extension",
|
|
specifier: "../../../src/config/config.js",
|
|
},
|
|
{
|
|
kind: "plugin-sdk-internal",
|
|
line: 3,
|
|
preferredReplacement:
|
|
"Use openclaw/plugin-sdk/* or openclaw/plugin-sdk/compat temporarily.",
|
|
reason: "reaches into non-public plugin-sdk-internal implementation",
|
|
specifier: "../../../src/plugin-sdk-internal/discord.js",
|
|
},
|
|
{
|
|
kind: "cross-extension",
|
|
line: 4,
|
|
preferredReplacement:
|
|
"Keep relative imports within the same plugin root, or expose a public surface via openclaw/plugin-sdk/*, openclaw/extension-api, or a dedicated shared package.",
|
|
reason: "reaches into another extension via a relative import",
|
|
specifier: "../../other-plugin/src/helper.js",
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("compares current violations to the baseline by path and specifier", () => {
|
|
const current = [
|
|
{ path: "extensions/a/index.ts", specifier: "../../src/config/config.js" },
|
|
{ path: "extensions/b/index.ts", specifier: "../../../src/plugin-sdk-internal/discord.js" },
|
|
];
|
|
const baseline = [
|
|
{ path: "extensions/a/index.ts", specifier: "../../src/config/config.js" },
|
|
{ path: "extensions/c/index.ts", specifier: "../../src/runtime.js" },
|
|
];
|
|
expect(compareViolationBaseline(current, baseline)).toEqual({
|
|
newViolations: [
|
|
{ path: "extensions/b/index.ts", specifier: "../../../src/plugin-sdk-internal/discord.js" },
|
|
],
|
|
resolvedViolations: [{ path: "extensions/c/index.ts", specifier: "../../src/runtime.js" }],
|
|
});
|
|
});
|
|
|
|
it("builds a stable baseline key", () => {
|
|
expect(
|
|
toBaselineKey({
|
|
path: path.join("extensions", "a", "index.ts"),
|
|
specifier: "../../src/config/config.js",
|
|
}),
|
|
).toBe("extensions/a/index.ts::../../src/config/config.js");
|
|
});
|
|
});
|