


















@@ -1,7 +1,8 @@
1+import { spawnSync } from "node:child_process";
12import fs from "node:fs";
23import path from "node:path";
34import JSON5 from "json5";
4-import { describe, expect, it } from "vitest";
5+import { describe, expect, it, vi } from "vitest";
56import { OpenClawSchema } from "../config/zod-schema.js";
6778const CHANNEL_DOCS_DIR = path.join(process.cwd(), "docs", "channels");
@@ -10,13 +11,79 @@ function lineNumberAt(source: string, index: number): number {
1011return source.slice(0, index).split("\n").length;
1112}
121314+function listChannelDocFiles(): string[] {
15+const externalFiles = listExternalChannelDocFiles();
16+if (externalFiles) {
17+return externalFiles;
18+}
19+return fs
20+.readdirSync(CHANNEL_DOCS_DIR)
21+.filter((entry) => entry.endsWith(".md"))
22+.map((fileName) => path.join(CHANNEL_DOCS_DIR, fileName))
23+.toSorted();
24+}
25+26+function listExternalChannelDocFiles(): string[] | null {
27+return listGitChannelDocFiles() ?? listFindChannelDocFiles();
28+}
29+30+function listGitChannelDocFiles(): string[] | null {
31+const result = spawnSync("git", ["ls-files", "--", "docs/channels/*.md"], {
32+cwd: process.cwd(),
33+encoding: "utf8",
34+maxBuffer: 1024 * 1024,
35+stdio: ["ignore", "pipe", "ignore"],
36+});
37+if (result.status !== 0) {
38+return null;
39+}
40+return result.stdout
41+.split("\n")
42+.map((line) => line.trim())
43+.filter((line) => line.length > 0)
44+.map((filePath) => path.join(process.cwd(), filePath))
45+.toSorted();
46+}
47+48+function listFindChannelDocFiles(): string[] | null {
49+const result = spawnSync(
50+"find",
51+[CHANNEL_DOCS_DIR, "-maxdepth", "1", "-type", "f", "-name", "*.md"],
52+{
53+cwd: process.cwd(),
54+encoding: "utf8",
55+maxBuffer: 1024 * 1024,
56+stdio: ["ignore", "pipe", "ignore"],
57+},
58+);
59+if (result.status !== 0) {
60+return null;
61+}
62+return result.stdout
63+.split("\n")
64+.map((line) => line.trim())
65+.filter((line) => line.length > 0)
66+.toSorted();
67+}
68+1369describe("channel docs config examples", () => {
70+it("lists channel docs without scanning the docs directory in-process", () => {
71+const readDir = vi.spyOn(fs, "readdirSync");
72+try {
73+const files = listChannelDocFiles();
74+75+expect(files.length).toBeGreaterThan(0);
76+expect(files.every((filePath) => filePath.endsWith(".md"))).toBe(true);
77+expect(readDir).not.toHaveBeenCalled();
78+} finally {
79+readDir.mockRestore();
80+}
81+});
82+1483it("keeps channel docs JSON fences parseable", () => {
1584const failures: string[] = [];
16-for (const fileName of fs
17-.readdirSync(CHANNEL_DOCS_DIR)
18-.filter((entry) => entry.endsWith(".md"))) {
19-const docPath = path.join(CHANNEL_DOCS_DIR, fileName);
85+for (const docPath of listChannelDocFiles()) {
86+const fileName = path.basename(docPath);
2087const markdown = fs.readFileSync(docPath, "utf8");
2188const blocks = markdown.matchAll(/```(?:json5|json)\n([\s\S]*?)```/g);
2289for (const match of blocks) {
@@ -41,10 +108,8 @@ describe("channel docs config examples", () => {
4110842109it("keeps OpenClaw channel config snippets parseable and schema-valid", () => {
43110const failures: string[] = [];
44-for (const fileName of fs
45-.readdirSync(CHANNEL_DOCS_DIR)
46-.filter((entry) => entry.endsWith(".md"))) {
47-const docPath = path.join(CHANNEL_DOCS_DIR, fileName);
111+for (const docPath of listChannelDocFiles()) {
112+const fileName = path.basename(docPath);
48113const markdown = fs.readFileSync(docPath, "utf8");
49114const blocks = markdown.matchAll(/```(?:json5|json)\n([\s\S]*?)```/g);
50115for (const match of blocks) {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。