





















@@ -1,6 +1,7 @@
1+import { spawnSync } from "node:child_process";
12import fs from "node:fs";
23import path from "node:path";
3-import { describe, expect, it } from "vitest";
4+import { describe, expect, it, vi } from "vitest";
45import { collectBundledChannelConfigs } from "./bundled-channel-config-metadata.js";
56import {
67type BundledPluginMetadata,
@@ -130,14 +131,86 @@ function listRepoBundledPluginMetadata(): readonly BundledPluginMetadata[] {
130131}
131132132133function listRepoBundledPluginManifestsUncached() {
134+const bundledPluginsDir = path.join(repoRoot, "extensions");
135+return listRepoBundledPluginManifestDirs()
136+.flatMap((dirName) => {
137+const result = loadPluginManifest(path.join(bundledPluginsDir, dirName), false);
138+return result.ok ? [{ dirName, manifest: result.manifest }] : [];
139+});
140+}
141+142+function listRepoBundledPluginManifestDirs(): string[] {
143+const externalDirs = listExternalRepoBundledPluginManifestDirs();
144+if (externalDirs) {
145+return externalDirs;
146+}
133147const bundledPluginsDir = path.join(repoRoot, "extensions");
134148return fs
135149.readdirSync(bundledPluginsDir, { withFileTypes: true })
136150.filter((entry) => entry.isDirectory())
137-.flatMap((entry) => {
138-const result = loadPluginManifest(path.join(bundledPluginsDir, entry.name), false);
139-return result.ok ? [{ dirName: entry.name, manifest: result.manifest }] : [];
140-});
151+.map((entry) => entry.name)
152+.toSorted();
153+}
154+155+function listExternalRepoBundledPluginManifestDirs(): string[] | null {
156+const manifestFiles =
157+listGitRepoBundledPluginManifestFiles() ?? listFindRepoBundledPluginManifestFiles();
158+if (!manifestFiles) {
159+return null;
160+}
161+return manifestFiles
162+.flatMap((file) => {
163+const match = /^extensions\/([^/]+)\/openclaw\.plugin\.json$/u.exec(file);
164+return match?.[1] ? [match[1]] : [];
165+})
166+.toSorted();
167+}
168+169+function listGitRepoBundledPluginManifestFiles(): string[] | null {
170+const result = spawnSync("git", ["ls-files", "--", "extensions/*/openclaw.plugin.json"], {
171+cwd: repoRoot,
172+encoding: "utf8",
173+maxBuffer: 1024 * 1024,
174+stdio: ["ignore", "pipe", "ignore"],
175+});
176+if (result.status !== 0) {
177+return null;
178+}
179+return result.stdout
180+.split("\n")
181+.map((line) => line.trim())
182+.filter((line) => line.length > 0)
183+.toSorted();
184+}
185+186+function listFindRepoBundledPluginManifestFiles(): string[] | null {
187+const result = spawnSync(
188+"find",
189+[
190+path.join(repoRoot, "extensions"),
191+"-maxdepth",
192+"2",
193+"-type",
194+"f",
195+"-name",
196+"openclaw.plugin.json",
197+],
198+{
199+cwd: repoRoot,
200+encoding: "utf8",
201+maxBuffer: 1024 * 1024,
202+stdio: ["ignore", "pipe", "ignore"],
203+},
204+);
205+if (result.status !== 0) {
206+return null;
207+}
208+return result.stdout
209+.split("\n")
210+.map((line) => line.trim())
211+.filter((line) => line.length > 0)
212+.map((file) => path.relative(repoRoot, file).split(path.sep).join("/"))
213+.toSorted();
141214}
142215143216function listRepoBundledPluginManifests() {
@@ -261,6 +334,19 @@ function createInstalledPluginIndexForManifests(
261334}
262335263336describe("bundled plugin metadata", () => {
337+it("lists bundled plugin manifests without scanning extension directories in-process", () => {
338+const readDir = vi.spyOn(fs, "readdirSync");
339+try {
340+const manifests = listRepoBundledPluginManifestsUncached();
341+342+expect(manifests.length).toBeGreaterThan(0);
343+expect(manifests.every((entry) => entry.dirName.length > 0)).toBe(true);
344+expect(readDir).not.toHaveBeenCalled();
345+} finally {
346+readDir.mockRestore();
347+}
348+});
349+264350it(
265351"matches the runtime metadata snapshot",
266352{ timeout: BUNDLED_PLUGIN_METADATA_TEST_TIMEOUT_MS },
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。