























1+import { mkdtemp, rm, stat, utimes, writeFile } from "node:fs/promises";
2+import { tmpdir } from "node:os";
3+import { join } from "node:path";
4+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5+6+const jitiCalls = vi.hoisted(() => ({
7+imports: [] as string[],
8+options: [] as Array<Record<string, unknown>>,
9+}));
10+11+vi.mock("jiti/static", () => ({
12+createJiti: vi.fn((_url: string, options: Record<string, unknown>) => {
13+jitiCalls.options.push(options);
14+return {
15+import: vi.fn(async (target: string) => {
16+jitiCalls.imports.push(target);
17+return async (api: { registerCommand: (id: string, command: unknown) => void }) => {
18+api.registerCommand(`jiti-${jitiCalls.imports.length}`, {
19+description: "probe",
20+handler() {},
21+});
22+};
23+}),
24+};
25+}),
26+}));
27+28+const tempDirs: string[] = [];
29+30+beforeEach(() => {
31+vi.resetModules();
32+jitiCalls.imports.length = 0;
33+jitiCalls.options.length = 0;
34+});
35+36+afterEach(async () => {
37+await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { force: true, recursive: true })));
38+});
39+40+describe("loadExtensions native JavaScript path", () => {
41+it("loads compiled JavaScript extensions without creating a jiti loader", async () => {
42+const { loadExtensions } = await import("./loader.js");
43+const dir = await mkdtemp(join(tmpdir(), "openclaw-extension-js-"));
44+tempDirs.push(dir);
45+const extensionPath = join(dir, "extension.mjs");
46+await writeFile(
47+extensionPath,
48+`
49+export default async function extension(api) {
50+ api.registerCommand("native-js-probe", {
51+ description: "probe",
52+ handler() {},
53+ });
54+}
55+`,
56+);
57+58+const result = await loadExtensions([extensionPath], dir);
59+60+expect(result.errors).toEqual([]);
61+expect(result.extensions).toHaveLength(1);
62+expect(result.extensions[0]?.commands.has("native-js-probe")).toBe(true);
63+expect(jitiCalls.options).toEqual([]);
64+expect(jitiCalls.imports).toEqual([]);
65+});
66+67+it("reloads native JavaScript extensions when the file changes without stat-key drift", async () => {
68+const { loadExtensions } = await import("./loader.js");
69+const dir = await mkdtemp(join(tmpdir(), "openclaw-extension-js-"));
70+tempDirs.push(dir);
71+const extensionPath = join(dir, "extension.cjs");
72+const beforeSource = `
73+module.exports = async function(api) {
74+ api.registerCommand("native-reload-one", {
75+ description: "probe",
76+ handler() {},
77+ });
78+};
79+`;
80+const afterSource = `
81+module.exports = async function(api) {
82+ api.registerCommand("native-reload-two", {
83+ description: "probe",
84+ handler() {},
85+ });
86+};
87+`;
88+expect(afterSource).toHaveLength(beforeSource.length);
89+await writeFile(extensionPath, beforeSource);
90+91+const before = await loadExtensions([extensionPath], dir);
92+const beforeStat = await stat(extensionPath);
93+94+await writeFile(extensionPath, afterSource);
95+await utimes(extensionPath, beforeStat.atime, beforeStat.mtime);
96+const after = await loadExtensions([extensionPath], dir);
97+98+expect(before.errors).toEqual([]);
99+expect(before.extensions[0]?.commands.has("native-reload-one")).toBe(true);
100+expect(after.errors).toEqual([]);
101+expect(after.extensions[0]?.commands.has("native-reload-two")).toBe(true);
102+expect(jitiCalls.options).toEqual([]);
103+});
104+105+it("loads transpiled CommonJS default exports through the native path", async () => {
106+const { loadExtensions } = await import("./loader.js");
107+const dir = await mkdtemp(join(tmpdir(), "openclaw-extension-js-"));
108+tempDirs.push(dir);
109+const extensionPath = join(dir, "extension.cjs");
110+await writeFile(
111+extensionPath,
112+`
113+exports.default = async function(api) {
114+ api.registerCommand("native-cjs-default-probe", {
115+ description: "probe",
116+ handler() {},
117+ });
118+};
119+`,
120+);
121+122+const result = await loadExtensions([extensionPath], dir);
123+124+expect(result.errors).toEqual([]);
125+expect(result.extensions).toHaveLength(1);
126+expect(result.extensions[0]?.commands.has("native-cjs-default-probe")).toBe(true);
127+expect(jitiCalls.options).toEqual([]);
128+});
129+130+it("keeps CommonJS-shaped .js extensions on jiti", async () => {
131+const { loadExtensions } = await import("./loader.js");
132+const dir = await mkdtemp(join(tmpdir(), "openclaw-extension-js-"));
133+tempDirs.push(dir);
134+await writeFile(join(dir, "package.json"), '{"type":"module"}\n');
135+const extensionPath = join(dir, "extension.js");
136+await writeFile(
137+extensionPath,
138+`
139+module.exports = async function(api) {
140+ api.registerCommand("should-not-native-load", {
141+ description: "probe",
142+ handler() {},
143+ });
144+};
145+`,
146+);
147+148+const result = await loadExtensions([extensionPath], dir);
149+150+expect(result.errors).toEqual([]);
151+expect(result.extensions).toHaveLength(1);
152+expect(jitiCalls.options).toHaveLength(1);
153+expect(jitiCalls.imports).toEqual([extensionPath]);
154+});
155+156+it("keeps plain ESM .js extensions on jiti", async () => {
157+const { loadExtensions } = await import("./loader.js");
158+const dir = await mkdtemp(join(tmpdir(), "openclaw-extension-js-"));
159+tempDirs.push(dir);
160+const extensionPath = join(dir, "extension.js");
161+await writeFile(
162+extensionPath,
163+`
164+export default async function extension(api) {
165+ api.registerCommand("should-not-native-load", {
166+ description: "probe",
167+ handler() {},
168+ });
169+}
170+`,
171+);
172+173+const result = await loadExtensions([extensionPath], dir);
174+175+expect(result.errors).toEqual([]);
176+expect(result.extensions).toHaveLength(1);
177+expect(jitiCalls.options).toHaveLength(1);
178+expect(jitiCalls.imports).toEqual([extensionPath]);
179+});
180+181+it("keeps SDK-alias JavaScript extensions on one shared jiti loader", async () => {
182+const { loadExtensions } = await import("./loader.js");
183+const dir = await mkdtemp(join(tmpdir(), "openclaw-extension-js-"));
184+tempDirs.push(dir);
185+const firstPath = join(dir, "first.js");
186+const secondPath = join(dir, "second.js");
187+const source = `
188+require("@openclaw/plugin-sdk/agent-sessions");
189+module.exports = async function(api) {
190+ api.registerCommand("should-not-native-load", {
191+ description: "probe",
192+ handler() {},
193+ });
194+};
195+`;
196+await writeFile(firstPath, source);
197+await writeFile(secondPath, source);
198+199+const result = await loadExtensions([firstPath, secondPath], dir);
200+201+expect(result.errors).toEqual([]);
202+expect(result.extensions).toHaveLength(2);
203+expect(jitiCalls.options).toHaveLength(1);
204+expect(jitiCalls.imports).toEqual([firstPath, secondPath]);
205+});
206+207+it("keeps TypeBox-alias JavaScript extensions on jiti", async () => {
208+const { loadExtensions } = await import("./loader.js");
209+const dir = await mkdtemp(join(tmpdir(), "openclaw-extension-js-"));
210+tempDirs.push(dir);
211+const extensionPath = join(dir, "extension.js");
212+await writeFile(
213+extensionPath,
214+`
215+require("typebox");
216+module.exports = async function(api) {
217+ api.registerCommand("should-not-native-load", {
218+ description: "probe",
219+ handler() {},
220+ });
221+};
222+`,
223+);
224+225+const result = await loadExtensions([extensionPath], dir);
226+227+expect(result.errors).toEqual([]);
228+expect(result.extensions).toHaveLength(1);
229+expect(jitiCalls.options).toHaveLength(1);
230+expect(jitiCalls.imports).toEqual([extensionPath]);
231+});
232+233+it("keeps multi-file JavaScript extensions on jiti for graph-wide aliases", async () => {
234+const { loadExtensions } = await import("./loader.js");
235+const dir = await mkdtemp(join(tmpdir(), "openclaw-extension-js-"));
236+tempDirs.push(dir);
237+const extensionPath = join(dir, "extension.js");
238+await writeFile(
239+extensionPath,
240+`
241+require("./helper.js");
242+module.exports = async function(api) {
243+ api.registerCommand("should-not-native-load", {
244+ description: "probe",
245+ handler() {},
246+ });
247+};
248+`,
249+);
250+await writeFile(join(dir, "helper.js"), 'require("typebox");\n');
251+252+const result = await loadExtensions([extensionPath], dir);
253+254+expect(result.errors).toEqual([]);
255+expect(result.extensions).toHaveLength(1);
256+expect(jitiCalls.options).toHaveLength(1);
257+expect(jitiCalls.imports).toEqual([extensionPath]);
258+});
259+260+it("keeps ESM re-export JavaScript extensions on jiti for graph-wide aliases", async () => {
261+const { loadExtensions } = await import("./loader.js");
262+const dir = await mkdtemp(join(tmpdir(), "openclaw-extension-js-"));
263+tempDirs.push(dir);
264+await writeFile(join(dir, "package.json"), '{"type":"module"}\n');
265+const extensionPath = join(dir, "extension.js");
266+await writeFile(
267+extensionPath,
268+`
269+export { helper } from "./helper.js";
270+export default async function extension(api) {
271+ api.registerCommand("should-not-native-load", {
272+ description: "probe",
273+ handler() {},
274+ });
275+}
276+`,
277+);
278+await writeFile(join(dir, "helper.js"), 'import "typebox"; export const helper = true;\n');
279+280+const result = await loadExtensions([extensionPath], dir);
281+282+expect(result.errors).toEqual([]);
283+expect(result.extensions).toHaveLength(1);
284+expect(jitiCalls.options).toHaveLength(1);
285+expect(jitiCalls.imports).toEqual([extensionPath]);
286+});
287+288+it("keeps minified ESM relative imports on jiti for graph-wide aliases", async () => {
289+const { loadExtensions } = await import("./loader.js");
290+const dir = await mkdtemp(join(tmpdir(), "openclaw-extension-js-"));
291+tempDirs.push(dir);
292+const extensionPath = join(dir, "extension.mjs");
293+await writeFile(
294+extensionPath,
295+`import{helper}from"./helper.mjs";export{helper}from"./helper.mjs";export default async function extension(api){api.registerCommand("should-not-native-load",{description:"probe",handler(){}});}`,
296+);
297+await writeFile(join(dir, "helper.mjs"), 'import "typebox"; export const helper = true;\n');
298+299+const result = await loadExtensions([extensionPath], dir);
300+301+expect(result.errors).toEqual([]);
302+expect(result.extensions).toHaveLength(1);
303+expect(jitiCalls.options).toHaveLength(1);
304+expect(jitiCalls.imports).toEqual([extensionPath]);
305+});
306+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。