



























1+import fs from "node:fs/promises";
2+import os from "node:os";
3+import path from "node:path";
4+import { afterEach, beforeEach, describe, expect, it } from "vitest";
5+import { runPluginPayloadSmokeCheck } from "./plugin-payload-validation.js";
6+7+describe("runPluginPayloadSmokeCheck", () => {
8+let tmpRoot: string;
9+beforeEach(async () => {
10+tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-payload-smoke-"));
11+});
12+afterEach(async () => {
13+await fs.rm(tmpRoot, { recursive: true, force: true });
14+});
15+16+async function writePackage(
17+dir: string,
18+manifest: Record<string, unknown>,
19+mainContent?: string,
20+) {
21+await fs.mkdir(dir, { recursive: true });
22+await fs.writeFile(path.join(dir, "package.json"), JSON.stringify(manifest), "utf8");
23+const main = typeof manifest.main === "string" ? manifest.main : "index.js";
24+if (mainContent !== undefined) {
25+const target = path.join(dir, main);
26+await fs.mkdir(path.dirname(target), { recursive: true });
27+await fs.writeFile(target, mainContent, "utf8");
28+}
29+}
30+31+it("reports ok for a record whose package.json + main file exist", async () => {
32+const dir = path.join(tmpRoot, "discord");
33+await writePackage(
34+dir,
35+{ name: "@openclaw/discord", main: "dist/index.js" },
36+"module.exports = {};",
37+);
38+const result = await runPluginPayloadSmokeCheck({
39+records: { discord: { source: "npm", installPath: dir } },
40+env: {},
41+});
42+expect(result.failures).toEqual([]);
43+expect(result.checked).toEqual(["discord"]);
44+});
45+46+it("reports a failure when the package directory is missing", async () => {
47+const dir = path.join(tmpRoot, "brave");
48+const result = await runPluginPayloadSmokeCheck({
49+records: { brave: { source: "npm", installPath: dir } },
50+env: {},
51+});
52+expect(result.failures).toEqual([
53+{
54+pluginId: "brave",
55+installPath: dir,
56+reason: "missing-package-dir",
57+detail: expect.stringContaining(dir),
58+},
59+]);
60+});
61+62+it("reports a failure when the package.json is missing", async () => {
63+const dir = path.join(tmpRoot, "brave");
64+await fs.mkdir(dir, { recursive: true });
65+const result = await runPluginPayloadSmokeCheck({
66+records: { brave: { source: "npm", installPath: dir } },
67+env: {},
68+});
69+expect(result.failures).toEqual([
70+{
71+pluginId: "brave",
72+installPath: dir,
73+reason: "missing-package-json",
74+detail: expect.stringContaining("package.json"),
75+},
76+]);
77+});
78+79+it("reports a failure when the main entry file is missing on disk", async () => {
80+const dir = path.join(tmpRoot, "brave");
81+await writePackage(dir, { name: "@openclaw/brave", main: "dist/index.js" });
82+const result = await runPluginPayloadSmokeCheck({
83+records: { brave: { source: "npm", installPath: dir } },
84+env: {},
85+});
86+expect(result.failures).toHaveLength(1);
87+expect(result.failures[0]).toMatchObject({
88+pluginId: "brave",
89+reason: "missing-main-entry",
90+});
91+expect(result.failures[0]?.detail).toContain("dist/index.js");
92+});
93+94+it("accepts a manifest with no main field (OpenClaw plugins commonly use `exports` or `openclaw.extensions`)", async () => {
95+const dir = path.join(tmpRoot, "matrix");
96+await writePackage(dir, { name: "@openclaw/plugin-matrix" });
97+const result = await runPluginPayloadSmokeCheck({
98+records: { matrix: { source: "npm", installPath: dir } },
99+env: {},
100+});
101+expect(result.failures).toEqual([]);
102+});
103+104+it("accepts a manifest that declares only `exports` and no `main`", async () => {
105+const dir = path.join(tmpRoot, "qa");
106+await writePackage(dir, {
107+name: "@openclaw/qa-channel",
108+exports: { ".": "./index.js", "./api.js": "./api.js" },
109+});
110+const result = await runPluginPayloadSmokeCheck({
111+records: { qa: { source: "npm", installPath: dir } },
112+env: {},
113+});
114+expect(result.failures).toEqual([]);
115+});
116+117+it("accepts a manifest that declares an existing `openclaw.extensions` entry and no `main`", async () => {
118+const dir = path.join(tmpRoot, "brave");
119+await writePackage(dir, {
120+name: "@openclaw/brave-plugin",
121+openclaw: { extensions: ["./index.js"] },
122+});
123+await fs.writeFile(path.join(dir, "index.js"), "export default {};\n", "utf8");
124+const result = await runPluginPayloadSmokeCheck({
125+records: { brave: { source: "npm", installPath: dir } },
126+env: {},
127+});
128+expect(result.failures).toEqual([]);
129+});
130+131+it("reports a failure when an `openclaw.extensions` entry file is missing", async () => {
132+const dir = path.join(tmpRoot, "brave");
133+await writePackage(dir, {
134+name: "@openclaw/brave-plugin",
135+openclaw: { extensions: ["./dist/index.js"] },
136+});
137+const result = await runPluginPayloadSmokeCheck({
138+records: { brave: { source: "npm", installPath: dir } },
139+env: {},
140+});
141+expect(result.failures).toHaveLength(1);
142+expect(result.failures[0]).toMatchObject({
143+pluginId: "brave",
144+reason: "missing-extension-entry",
145+});
146+expect(result.failures[0]?.detail).toContain("./dist/index.js");
147+});
148+149+it("reports a failure when `main` resolves to a directory rather than a file", async () => {
150+const dir = path.join(tmpRoot, "dir-main");
151+await fs.mkdir(dir, { recursive: true });
152+await fs.writeFile(
153+path.join(dir, "package.json"),
154+JSON.stringify({ name: "dir-main", main: "lib" }),
155+"utf8",
156+);
157+await fs.mkdir(path.join(dir, "lib"), { recursive: true });
158+const result = await runPluginPayloadSmokeCheck({
159+records: { x: { source: "npm", installPath: dir } },
160+env: {},
161+});
162+expect(result.failures).toHaveLength(1);
163+expect(result.failures[0]).toMatchObject({ pluginId: "x", reason: "missing-main-entry" });
164+});
165+166+it("reports a failure when `main` is a symlink whose target is missing", async () => {
167+const dir = path.join(tmpRoot, "broken-symlink");
168+await fs.mkdir(dir, { recursive: true });
169+await fs.writeFile(
170+path.join(dir, "package.json"),
171+JSON.stringify({ name: "broken-symlink", main: "dist/entry.js" }),
172+"utf8",
173+);
174+await fs.mkdir(path.join(dir, "dist"), { recursive: true });
175+await fs.symlink(
176+path.join(dir, "dist", "missing-target.js"),
177+path.join(dir, "dist", "entry.js"),
178+);
179+const result = await runPluginPayloadSmokeCheck({
180+records: { x: { source: "npm", installPath: dir } },
181+env: {},
182+});
183+expect(result.failures).toHaveLength(1);
184+expect(result.failures[0]).toMatchObject({
185+pluginId: "x",
186+reason: "missing-main-entry",
187+});
188+});
189+190+it("reports a failure when package.json cannot be parsed", async () => {
191+const dir = path.join(tmpRoot, "broken");
192+await fs.mkdir(dir, { recursive: true });
193+await fs.writeFile(path.join(dir, "package.json"), "not-json", "utf8");
194+const result = await runPluginPayloadSmokeCheck({
195+records: { broken: { source: "npm", installPath: dir } },
196+env: {},
197+});
198+expect(result.failures).toHaveLength(1);
199+expect(result.failures[0]).toMatchObject({
200+pluginId: "broken",
201+reason: "invalid-package-json",
202+});
203+});
204+205+it("reports a failure when an install record is missing installPath", async () => {
206+const result = await runPluginPayloadSmokeCheck({
207+records: {
208+discord: { source: "npm" } as unknown as { source: "npm"; installPath?: string },
209+},
210+env: {},
211+});
212+expect(result.checked).toEqual(["discord"]);
213+expect(result.failures).toEqual([
214+{
215+pluginId: "discord",
216+reason: "missing-install-path",
217+detail: "Install path is missing from the plugin install record.",
218+},
219+]);
220+});
221+222+it("only checks records whose source is package-tracked (npm/clawhub/git/marketplace)", async () => {
223+const dir = path.join(tmpRoot, "tracked");
224+await writePackage(dir, { name: "tracked" }, "module.exports = {};");
225+const records = {
226+bundled: { source: "bundled", installPath: dir } as never,
227+npm: { source: "npm" as const, installPath: dir },
228+};
229+const result = await runPluginPayloadSmokeCheck({
230+ records,
231+env: {},
232+});
233+expect(result.checked).toEqual(["npm"]);
234+});
235+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。