Skip to content

Commit 31cc262

Browse files
committed
fix(test): avoid scanning bundled source metadata
1 parent 721fb65 commit 31cc262

2 files changed

Lines changed: 141 additions & 14 deletions

File tree

scripts/lib/bundled-plugin-source-utils.mjs

Lines changed: 74 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { spawnSync } from "node:child_process";
12
import fs from "node:fs";
23
import path from "node:path";
34

@@ -9,36 +10,95 @@ export function readIfExists(filePath) {
910
}
1011
}
1112

12-
export function collectBundledPluginSources(params = {}) {
13-
const repoRoot = path.resolve(params.repoRoot ?? process.cwd());
13+
function collectTrackedBundledPluginSourceCandidates(repoRoot) {
14+
const result = spawnSync(
15+
"git",
16+
[
17+
"ls-files",
18+
"--",
19+
":(glob)extensions/*/openclaw.plugin.json",
20+
":(glob)extensions/*/package.json",
21+
],
22+
{
23+
cwd: repoRoot,
24+
encoding: "utf8",
25+
stdio: ["ignore", "pipe", "ignore"],
26+
},
27+
);
28+
if (result.status !== 0) {
29+
return null;
30+
}
31+
32+
const candidatesByDir = new Map();
33+
for (const rawLine of result.stdout.split("\n")) {
34+
const line = rawLine.trim().replaceAll("\\", "/");
35+
const match = /^extensions\/([^/]+)\/(openclaw\.plugin\.json|package\.json)$/u.exec(line);
36+
if (!match?.[1] || !match[2]) {
37+
continue;
38+
}
39+
const current = candidatesByDir.get(match[1]) ?? {
40+
dirName: match[1],
41+
manifestPath: null,
42+
packageJsonPath: null,
43+
pluginDir: path.join(repoRoot, "extensions", match[1]),
44+
};
45+
if (match[2] === "openclaw.plugin.json") {
46+
current.manifestPath = path.join(repoRoot, line);
47+
} else {
48+
current.packageJsonPath = path.join(repoRoot, line);
49+
}
50+
candidatesByDir.set(match[1], current);
51+
}
52+
53+
return [...candidatesByDir.values()].toSorted((left, right) =>
54+
left.dirName.localeCompare(right.dirName),
55+
);
56+
}
57+
58+
function collectBundledPluginSourceCandidatesFromDirectory(repoRoot) {
1459
const extensionsRoot = path.join(repoRoot, "extensions");
1560
if (!fs.existsSync(extensionsRoot)) {
1661
return [];
1762
}
1863

64+
return fs
65+
.readdirSync(extensionsRoot, { withFileTypes: true })
66+
.filter((dirent) => dirent.isDirectory())
67+
.map((dirent) => {
68+
const pluginDir = path.join(extensionsRoot, dirent.name);
69+
const manifestPath = path.join(pluginDir, "openclaw.plugin.json");
70+
const packageJsonPath = path.join(pluginDir, "package.json");
71+
return {
72+
dirName: dirent.name,
73+
manifestPath: fs.existsSync(manifestPath) ? manifestPath : null,
74+
packageJsonPath: fs.existsSync(packageJsonPath) ? packageJsonPath : null,
75+
pluginDir,
76+
};
77+
})
78+
.toSorted((left, right) => left.dirName.localeCompare(right.dirName));
79+
}
80+
81+
export function collectBundledPluginSources(params = {}) {
82+
const repoRoot = path.resolve(params.repoRoot ?? process.cwd());
1983
const requirePackageJson = params.requirePackageJson === true;
2084
const entries = [];
21-
for (const dirent of fs.readdirSync(extensionsRoot, { withFileTypes: true })) {
22-
if (!dirent.isDirectory()) {
23-
continue;
24-
}
25-
26-
const pluginDir = path.join(extensionsRoot, dirent.name);
27-
const manifestPath = path.join(pluginDir, "openclaw.plugin.json");
28-
const packageJsonPath = path.join(pluginDir, "package.json");
29-
if (!fs.existsSync(manifestPath)) {
85+
const candidates =
86+
collectTrackedBundledPluginSourceCandidates(repoRoot) ??
87+
collectBundledPluginSourceCandidatesFromDirectory(repoRoot);
88+
for (const { dirName, manifestPath, packageJsonPath, pluginDir } of candidates) {
89+
if (!manifestPath) {
3090
continue;
3191
}
32-
if (requirePackageJson && !fs.existsSync(packageJsonPath)) {
92+
if (requirePackageJson && !packageJsonPath) {
3393
continue;
3494
}
3595

3696
entries.push({
37-
dirName: dirent.name,
97+
dirName,
3898
pluginDir,
3999
manifestPath,
40100
manifest: JSON.parse(fs.readFileSync(manifestPath, "utf8")),
41-
...(fs.existsSync(packageJsonPath)
101+
...(packageJsonPath
42102
? {
43103
packageJsonPath,
44104
packageJson: JSON.parse(fs.readFileSync(packageJsonPath, "utf8")),
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { execFileSync } from "node:child_process";
2+
import { describe, expect, it } from "vitest";
3+
import { collectBundledPluginSources } from "../../scripts/lib/bundled-plugin-source-utils.mjs";
4+
5+
describe("scripts/lib/bundled-plugin-source-utils.mjs", () => {
6+
it("collects bundled plugin sources with package metadata", () => {
7+
const sources = collectBundledPluginSources({
8+
repoRoot: process.cwd(),
9+
requirePackageJson: true,
10+
});
11+
12+
expect(sources.some((source) => source.dirName === "telegram")).toBe(true);
13+
expect(sources.every((source) => source.packageJsonPath)).toBe(true);
14+
expect(sources).toEqual(
15+
[...sources].toSorted((left, right) => left.dirName.localeCompare(right.dirName)),
16+
);
17+
});
18+
19+
it("discovers repo bundled plugin sources without scanning extension directories", () => {
20+
const output = execFileSync(
21+
process.execPath,
22+
[
23+
"--input-type=module",
24+
"--eval",
25+
`
26+
import fs from "node:fs";
27+
import { syncBuiltinESMExports } from "node:module";
28+
const counts = { existsSync: 0, readdirSync: 0 };
29+
const originalExistsSync = fs.existsSync;
30+
const originalReaddirSync = fs.readdirSync;
31+
fs.existsSync = (...args) => {
32+
counts.existsSync += 1;
33+
return originalExistsSync(...args);
34+
};
35+
fs.readdirSync = (...args) => {
36+
counts.readdirSync += 1;
37+
return originalReaddirSync(...args);
38+
};
39+
syncBuiltinESMExports();
40+
const utils = await import("./scripts/lib/bundled-plugin-source-utils.mjs");
41+
const sources = utils.collectBundledPluginSources({
42+
repoRoot: process.cwd(),
43+
requirePackageJson: true,
44+
});
45+
console.log(JSON.stringify({
46+
channels: sources.filter((source) => Array.isArray(source.manifest?.channels) && source.manifest.channels.length > 0).length,
47+
counts,
48+
sources: sources.length,
49+
}));
50+
`,
51+
],
52+
{
53+
cwd: process.cwd(),
54+
encoding: "utf8",
55+
},
56+
);
57+
58+
const payload = JSON.parse(output) as {
59+
channels: number;
60+
counts: { existsSync: number; readdirSync: number };
61+
sources: number;
62+
};
63+
expect(payload.sources).toBeGreaterThan(0);
64+
expect(payload.channels).toBeGreaterThan(0);
65+
expect(payload.counts).toEqual({ existsSync: 0, readdirSync: 0 });
66+
});
67+
});

0 commit comments

Comments
 (0)