























@@ -1,7 +1,8 @@
1+import { spawnSync } from "node:child_process";
12import fs from "node:fs";
23import path from "node:path";
34import { BUNDLED_PLUGIN_PATH_PREFIX } from "openclaw/plugin-sdk/test-fixtures";
4-import { describe, expect, it } from "vitest";
5+import { describe, expect, it, vi } from "vitest";
56import { GUARDED_EXTENSION_PUBLIC_SURFACE_BASENAMES } from "../src/plugin-sdk/test-helpers/public-artifacts.js";
6778const repoRoot = path.resolve(import.meta.dirname, "..");
@@ -18,7 +19,35 @@ const BROAD_PUBLIC_SOURCE_ARTIFACT_BASENAMES = new Set(["api.js", "runtime-api.j
1819const ROOTDIR_BOUNDARY_CANARY_RE =
1920/(^|\/)__rootdir_boundary_canary__\.(?:[cm]?ts|[cm]?js|tsx|jsx)$/u;
202122+function listGitFiles(dir: string): string[] | null {
23+const relativeRoot = path.relative(repoRoot, dir).replaceAll(path.sep, "/");
24+if (!relativeRoot || relativeRoot.startsWith("..") || path.isAbsolute(relativeRoot)) {
25+return null;
26+}
27+const result = spawnSync("git", ["ls-files", "--", relativeRoot], {
28+cwd: repoRoot,
29+encoding: "utf8",
30+stdio: ["ignore", "pipe", "ignore"],
31+});
32+if (result.status !== 0) {
33+return null;
34+}
35+return result.stdout
36+.split("\n")
37+.map((line) => line.trim().replaceAll("\\", "/"))
38+.filter((line) => line.length > 0)
39+.toSorted((left, right) => left.localeCompare(right));
40+}
41+2142function walk(dir: string, entries: string[] = []): string[] {
43+const gitFiles = listGitFiles(dir);
44+if (gitFiles) {
45+entries.push(
46+ ...gitFiles.filter((file) => file.endsWith(".test.ts") || file.endsWith(".test.tsx")),
47+);
48+return entries;
49+}
50+2251for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
2352const fullPath = path.join(dir, entry.name);
2453if (entry.isDirectory()) {
@@ -37,6 +66,19 @@ function walk(dir: string, entries: string[] = []): string[] {
3766}
38673968function walkCode(dir: string, entries: string[] = []): string[] {
69+const gitFiles = listGitFiles(dir);
70+if (gitFiles) {
71+entries.push(
72+ ...gitFiles.filter((file) => {
73+if (!file.endsWith(".ts") && !file.endsWith(".tsx")) {
74+return false;
75+}
76+return !ROOTDIR_BOUNDARY_CANARY_RE.test(file);
77+}),
78+);
79+return entries;
80+}
81+4082for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
4183const fullPath = path.join(dir, entry.name);
4284if (entry.isDirectory()) {
@@ -100,6 +142,15 @@ function getImportBasename(importPath: string): string {
100142}
101143102144function collectBundledPluginIds(): Set<string> {
145+const extensionFiles = listGitFiles(path.join(repoRoot, "extensions"));
146+if (extensionFiles) {
147+return new Set(
148+extensionFiles
149+.map((file) => /^extensions\/([^/]+)\//u.exec(file)?.[1])
150+.filter((pluginId): pluginId is string => Boolean(pluginId)),
151+);
152+}
153+103154return new Set(
104155fs
105156.readdirSync(path.join(repoRoot, "extensions"), { withFileTypes: true })
@@ -145,6 +196,22 @@ function isAllowedCoreContractSuite(file: string, imports: readonly string[]): b
145196}
146197147198describe("non-extension test boundaries", () => {
199+it("lists boundary scan files from git without walking repo roots", () => {
200+const readdirSync = vi.spyOn(fs, "readdirSync");
201+try {
202+const srcTests = walk(path.join(repoRoot, "src"));
203+const srcCode = walkCode(path.join(repoRoot, "src"));
204+const pluginIds = collectBundledPluginIds();
205+206+expect(srcTests.length).toBeGreaterThan(0);
207+expect(srcCode.length).toBeGreaterThan(0);
208+expect(pluginIds.size).toBeGreaterThan(0);
209+expect(readdirSync).not.toHaveBeenCalled();
210+} finally {
211+readdirSync.mockRestore();
212+}
213+});
214+148215it("keeps plugin-owned behavior suites under the bundled plugin tree", () => {
149216const testFiles = [
150217 ...walk(path.join(repoRoot, "src")),
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。