





















@@ -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 { normalizeBundledPluginStringList } from "./bundled-plugin-scan.js";
56import {
67BUNDLED_AUTO_ENABLE_PROVIDER_PLUGIN_IDS,
@@ -13,32 +14,73 @@ import { pluginTestRepoRoot as repoRoot } from "./generated-plugin-test-helpers.
1314import type { OpenClawPackageManifest } from "./manifest.js";
1415import type { PluginManifest } from "./manifest.js";
151616-function readManifestRecords(): PluginManifest[] {
17-const extensionsDir = path.join(repoRoot, "extensions");
17+function listGitExtensionPackagePaths(extensionsDir: string): string[] | null {
18+const relativeDir = path.relative(repoRoot, extensionsDir).split(path.sep).join("/");
19+if (!relativeDir || relativeDir.startsWith("..") || path.isAbsolute(relativeDir)) {
20+return null;
21+}
22+const result = spawnSync("git", ["ls-files", "--", relativeDir], {
23+cwd: repoRoot,
24+encoding: "utf8",
25+stdio: ["ignore", "pipe", "ignore"],
26+});
27+if (result.status !== 0) {
28+return null;
29+}
30+return result.stdout
31+.split("\n")
32+.map((line) => line.trim().replaceAll("\\", "/"))
33+.filter((line) => /^extensions\/[^/]+\/package\.json$/u.test(line))
34+.map((line) => path.join(repoRoot, ...line.split("/")))
35+.toSorted();
36+}
37+38+function listExtensionPackagePaths(extensionsDir: string): string[] {
39+const gitPaths = listGitExtensionPackagePaths(extensionsDir);
40+if (gitPaths) {
41+return gitPaths;
42+}
43+1844return fs
1945.readdirSync(extensionsDir, { withFileTypes: true })
2046.filter((entry) => entry.isDirectory())
21-.map((entry) => path.join(extensionsDir, entry.name))
22-.filter((pluginDir) => {
23-const packagePath = path.join(pluginDir, "package.json");
24-if (!fs.existsSync(packagePath)) {
25-return false;
26-}
47+.map((entry) => path.join(extensionsDir, entry.name, "package.json"))
48+.filter((packagePath) => fs.existsSync(packagePath));
49+}
50+51+function readManifestRecords(): PluginManifest[] {
52+const extensionsDir = path.join(repoRoot, "extensions");
53+return listExtensionPackagePaths(extensionsDir)
54+.filter((packagePath) => {
2755const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf-8")) as {
2856openclaw?: OpenClawPackageManifest;
2957};
3058return normalizeBundledPluginStringList(packageJson.openclaw?.extensions).length > 0;
3159})
3260.map(
33-(pluginDir) =>
61+(packagePath) =>
3462JSON.parse(
35-fs.readFileSync(path.join(pluginDir, "openclaw.plugin.json"), "utf-8"),
63+fs.readFileSync(path.join(path.dirname(packagePath), "openclaw.plugin.json"), "utf-8"),
3664) as PluginManifest,
3765)
3866.toSorted((left, right) => left.id.localeCompare(right.id));
3967}
40684169describe("bundled capability metadata", () => {
70+it("lists bundled extension packages from git without scanning extension dirs", () => {
71+const extensionsDir = path.join(repoRoot, "extensions");
72+const readDir = vi.spyOn(fs, "readdirSync");
73+try {
74+const packagePaths = listExtensionPackagePaths(extensionsDir);
75+76+expect(packagePaths.length).toBeGreaterThan(0);
77+expect(packagePaths.every((file) => file.endsWith("package.json"))).toBe(true);
78+expect(readDir).not.toHaveBeenCalled();
79+} finally {
80+readDir.mockRestore();
81+}
82+});
83+4284it("keeps contract snapshots aligned with bundled plugin manifests", () => {
4385const expected = readManifestRecords()
4486.map(buildBundledPluginContractSnapshot)
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。