
























@@ -1,3 +1,4 @@
1+import { spawnSync } from "node:child_process";
12import fs from "node:fs";
23import os from "node:os";
34import path from "node:path";
@@ -93,6 +94,11 @@ function collectBundledChannelEntrypointOffenders(
93949495function listSourceBundledPluginRoots(): string[] {
9596const extensionsDir = path.resolve("extensions");
97+const externalRoots = listExternalSourceBundledPluginRoots(extensionsDir);
98+if (externalRoots) {
99+return externalRoots;
100+}
101+96102return fs
97103.readdirSync(extensionsDir, { withFileTypes: true })
98104.filter((entry) => entry.isDirectory())
@@ -104,6 +110,73 @@ function listSourceBundledPluginRoots(): string[] {
104110);
105111}
106112113+function listExternalSourceBundledPluginRoots(extensionsDir: string): string[] | null {
114+return (
115+listGitSourceBundledPluginRoots(extensionsDir) ??
116+listFindSourceBundledPluginRoots(extensionsDir)
117+);
118+}
119+120+function listGitSourceBundledPluginRoots(extensionsDir: string): string[] | null {
121+const result = spawnSync(
122+"git",
123+["ls-files", "--", "extensions/*/package.json", "extensions/*/openclaw.plugin.json"],
124+{
125+cwd: process.cwd(),
126+encoding: "utf8",
127+maxBuffer: 4 * 1024 * 1024,
128+stdio: ["ignore", "pipe", "ignore"],
129+},
130+);
131+if (result.status !== 0) {
132+return null;
133+}
134+return packageMarkerPathsToRoots(result.stdout.split("\n"), extensionsDir);
135+}
136+137+function listFindSourceBundledPluginRoots(extensionsDir: string): string[] | null {
138+const result = spawnSync(
139+"find",
140+[
141+extensionsDir,
142+"-mindepth",
143+"2",
144+"-maxdepth",
145+"2",
146+"(",
147+"-name",
148+"package.json",
149+"-o",
150+"-name",
151+"openclaw.plugin.json",
152+")",
153+],
154+{
155+cwd: process.cwd(),
156+encoding: "utf8",
157+maxBuffer: 4 * 1024 * 1024,
158+stdio: ["ignore", "pipe", "ignore"],
159+},
160+);
161+if (result.status !== 0) {
162+return null;
163+}
164+return packageMarkerPathsToRoots(result.stdout.split("\n"), extensionsDir);
165+}
166+167+function packageMarkerPathsToRoots(markerPaths: string[], extensionsDir: string): string[] {
168+return [
169+ ...new Set(
170+markerPaths
171+.map((line) => line.trim())
172+.filter((line) => line.length > 0)
173+.map((line) => path.resolve(line))
174+.map((line) => path.dirname(line))
175+.filter((line) => path.dirname(line) === extensionsDir),
176+),
177+].toSorted();
178+}
179+107180afterEach(() => {
108181delete (globalThis as { __openclawBundledChannelReenter?: () => void })
109182.__openclawBundledChannelReenter;
@@ -120,6 +193,21 @@ afterEach(() => {
120193describe("bundled channel entry shape guards", () => {
121194const bundledPluginRoots = listSourceBundledPluginRoots();
122195196+it("lists source bundled plugin roots without in-process directory scans", () => {
197+const readDir = vi.spyOn(fs, "readdirSync");
198+try {
199+const roots = listSourceBundledPluginRoots();
200+201+expect(roots.length).toBeGreaterThan(0);
202+expect(roots.every((root) => path.dirname(root) === path.resolve("extensions"))).toBe(
203+true,
204+);
205+expect(readDir).not.toHaveBeenCalled();
206+} finally {
207+readDir.mockRestore();
208+}
209+});
210+123211it("treats missing bundled discovery results as empty", async () => {
124212vi.doMock("../../plugins/bundled-channel-runtime.js", async (importOriginal) => {
125213const actual =
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。