


























@@ -110,6 +110,221 @@ describe("runPostUpgradeProbes — plugin.entry_unresolved", () => {
110110}
111111});
112112113+it("flags an entry that escapes the plugin package directory", async () => {
114+const root = await makeFixtureRoot("entry-escape");
115+try {
116+const pluginDir = path.join(root, "user-plugins", "escape");
117+await fs.mkdir(pluginDir, { recursive: true });
118+// Create a sibling file outside the plugin root that the entry resolves to.
119+const outsideDir = path.join(root, "outside");
120+await fs.mkdir(outsideDir, { recursive: true });
121+await fs.writeFile(path.join(outsideDir, "leak.js"), "export default {};", "utf-8");
122+await fs.writeFile(
123+path.join(pluginDir, "package.json"),
124+JSON.stringify({
125+name: "escape",
126+version: "0.0.1",
127+type: "module",
128+openclaw: { extensions: ["../outside/leak.js"] },
129+}),
130+"utf-8",
131+);
132+await fs.writeFile(
133+path.join(pluginDir, "openclaw.plugin.json"),
134+JSON.stringify({ id: "escape" }),
135+"utf-8",
136+);
137+138+const installsPath = path.join(root, "plugins", "installs.json");
139+await fs.mkdir(path.dirname(installsPath), { recursive: true });
140+await fs.writeFile(
141+installsPath,
142+JSON.stringify({
143+version: 1,
144+plugins: [
145+{
146+pluginId: "escape",
147+manifestPath: path.join(pluginDir, "openclaw.plugin.json"),
148+rootDir: pluginDir,
149+enabled: true,
150+packageJson: { path: "package.json" },
151+},
152+],
153+}),
154+"utf-8",
155+);
156+157+const report = await runPostUpgradeProbes({ installsPath });
158+const finding = report.findings.find((f) => f.code === "plugin.entry_unresolved");
159+expect(finding).toBeDefined();
160+expect(finding?.level).toBe("error");
161+expect(finding?.plugin).toBe("escape");
162+expect(finding?.message).toMatch(/escapes plugin directory/);
163+} finally {
164+await fs.rm(root, { recursive: true, force: true });
165+}
166+});
167+168+it("accepts a TypeScript source entry that ships a compiled dist peer", async () => {
169+const root = await makeFixtureRoot("ts-with-dist");
170+try {
171+const pluginDir = path.join(root, "user-plugins", "ts-dist");
172+await fs.mkdir(path.join(pluginDir, "src"), { recursive: true });
173+await fs.mkdir(path.join(pluginDir, "dist"), { recursive: true });
174+await fs.writeFile(path.join(pluginDir, "src", "index.ts"), "export default {};", "utf-8");
175+await fs.writeFile(path.join(pluginDir, "dist", "index.js"), "export default {};", "utf-8");
176+// No explicit runtimeExtensions; the resolver should infer dist/index.js.
177+await fs.writeFile(
178+path.join(pluginDir, "package.json"),
179+JSON.stringify({
180+name: "ts-dist",
181+version: "0.0.1",
182+type: "module",
183+openclaw: { extensions: ["./src/index.ts"] },
184+}),
185+"utf-8",
186+);
187+await fs.writeFile(
188+path.join(pluginDir, "openclaw.plugin.json"),
189+JSON.stringify({ id: "ts-dist" }),
190+"utf-8",
191+);
192+193+const installsPath = path.join(root, "plugins", "installs.json");
194+await fs.mkdir(path.dirname(installsPath), { recursive: true });
195+await fs.writeFile(
196+installsPath,
197+JSON.stringify({
198+version: 1,
199+plugins: [
200+{
201+pluginId: "ts-dist",
202+manifestPath: path.join(pluginDir, "openclaw.plugin.json"),
203+rootDir: pluginDir,
204+enabled: true,
205+packageJson: { path: "package.json" },
206+},
207+],
208+}),
209+"utf-8",
210+);
211+212+const report = await runPostUpgradeProbes({ installsPath });
213+expect(report.findings.filter((f) => f.code === "plugin.entry_unresolved")).toHaveLength(0);
214+} finally {
215+await fs.rm(root, { recursive: true, force: true });
216+}
217+});
218+219+it("flags a TypeScript source-only entry with no compiled output", async () => {
220+const root = await makeFixtureRoot("ts-source-only");
221+try {
222+const pluginDir = path.join(root, "user-plugins", "ts-only");
223+await fs.mkdir(path.join(pluginDir, "src"), { recursive: true });
224+await fs.writeFile(path.join(pluginDir, "src", "index.ts"), "export default {};", "utf-8");
225+// Source exists, no dist peer — installed plugins must ship compiled JS.
226+await fs.writeFile(
227+path.join(pluginDir, "package.json"),
228+JSON.stringify({
229+name: "ts-only",
230+version: "0.0.1",
231+type: "module",
232+openclaw: { extensions: ["./src/index.ts"] },
233+}),
234+"utf-8",
235+);
236+await fs.writeFile(
237+path.join(pluginDir, "openclaw.plugin.json"),
238+JSON.stringify({ id: "ts-only" }),
239+"utf-8",
240+);
241+242+const installsPath = path.join(root, "plugins", "installs.json");
243+await fs.mkdir(path.dirname(installsPath), { recursive: true });
244+await fs.writeFile(
245+installsPath,
246+JSON.stringify({
247+version: 1,
248+plugins: [
249+{
250+pluginId: "ts-only",
251+manifestPath: path.join(pluginDir, "openclaw.plugin.json"),
252+rootDir: pluginDir,
253+enabled: true,
254+packageJson: { path: "package.json" },
255+},
256+],
257+}),
258+"utf-8",
259+);
260+261+const report = await runPostUpgradeProbes({ installsPath });
262+const finding = report.findings.find((f) => f.code === "plugin.entry_unresolved");
263+expect(finding).toBeDefined();
264+expect(finding?.level).toBe("error");
265+expect(finding?.plugin).toBe("ts-only");
266+expect(finding?.message).toMatch(/compiled runtime output/);
267+} finally {
268+await fs.rm(root, { recursive: true, force: true });
269+}
270+});
271+272+it("flags a runtimeExtensions length mismatch", async () => {
273+const root = await makeFixtureRoot("runtime-len-mismatch");
274+try {
275+const pluginDir = path.join(root, "user-plugins", "len-mismatch");
276+await fs.mkdir(path.join(pluginDir, "dist"), { recursive: true });
277+await fs.writeFile(path.join(pluginDir, "dist", "a.js"), "export default {};", "utf-8");
278+await fs.writeFile(path.join(pluginDir, "dist", "b.js"), "export default {};", "utf-8");
279+await fs.writeFile(
280+path.join(pluginDir, "package.json"),
281+JSON.stringify({
282+name: "len-mismatch",
283+version: "0.0.1",
284+type: "module",
285+openclaw: {
286+extensions: ["./dist/a.js", "./dist/b.js"],
287+runtimeExtensions: ["./dist/a.js"],
288+},
289+}),
290+"utf-8",
291+);
292+await fs.writeFile(
293+path.join(pluginDir, "openclaw.plugin.json"),
294+JSON.stringify({ id: "len-mismatch" }),
295+"utf-8",
296+);
297+298+const installsPath = path.join(root, "plugins", "installs.json");
299+await fs.mkdir(path.dirname(installsPath), { recursive: true });
300+await fs.writeFile(
301+installsPath,
302+JSON.stringify({
303+version: 1,
304+plugins: [
305+{
306+pluginId: "len-mismatch",
307+manifestPath: path.join(pluginDir, "openclaw.plugin.json"),
308+rootDir: pluginDir,
309+enabled: true,
310+packageJson: { path: "package.json" },
311+},
312+],
313+}),
314+"utf-8",
315+);
316+317+const report = await runPostUpgradeProbes({ installsPath });
318+const finding = report.findings.find((f) => f.code === "plugin.entry_unresolved");
319+expect(finding).toBeDefined();
320+expect(finding?.level).toBe("error");
321+expect(finding?.plugin).toBe("len-mismatch");
322+expect(finding?.message).toMatch(/runtimeExtensions length/);
323+} finally {
324+await fs.rm(root, { recursive: true, force: true });
325+}
326+});
327+113328it("does not flag entry_unresolved when runtimeExtensions exists even if source entry is missing", async () => {
114329const root = await makeFixtureRoot("runtime-extensions");
115330try {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。