@aigne/doc-smith 0.8.5 → 0.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.aigne/doc-smith/output/structure-plan.json +1 -5
- package/CHANGELOG.md +25 -0
- package/README.md +3 -3
- package/agents/{chat.yaml → chat/index.yaml} +7 -7
- package/agents/generate/check-document-structure.yaml +30 -0
- package/agents/{check-structure-plan.mjs → generate/check-need-generate-structure.mjs} +21 -21
- package/agents/generate/generate-structure.yaml +58 -0
- package/agents/{docs-generator.yaml → generate/index.yaml} +15 -16
- package/agents/generate/refine-document-structure.yaml +12 -0
- package/agents/{input-generator.mjs → init/index.mjs} +34 -27
- package/agents/{manage-prefs.mjs → prefs/index.mjs} +16 -16
- package/agents/publish/index.yaml +17 -0
- package/agents/{publish-docs.mjs → publish/publish-docs.mjs} +15 -16
- package/agents/schema/{structure-plan-result.yaml → document-execution-structure.yaml} +3 -3
- package/agents/schema/document-structure.yaml +26 -0
- package/agents/{language-selector.mjs → translate/choose-language.mjs} +5 -5
- package/agents/{retranslate.yaml → translate/index.yaml} +17 -18
- package/agents/translate/translate-document.yaml +32 -0
- package/agents/{batch-translate.yaml → translate/translate-multilingual.yaml} +5 -5
- package/agents/update/batch-generate-document.yaml +19 -0
- package/agents/{check-detail.mjs → update/check-document.mjs} +16 -16
- package/agents/{detail-generator-and-translate.yaml → update/generate-and-translate-document.yaml} +23 -23
- package/agents/update/generate-document.yaml +50 -0
- package/agents/{detail-regenerator.yaml → update/index.yaml} +16 -17
- package/agents/{action-success.mjs → utils/action-success.mjs} +2 -2
- package/agents/{check-detail-result.mjs → utils/check-detail-result.mjs} +3 -3
- package/agents/{check-feedback-refiner.mjs → utils/check-feedback-refiner.mjs} +6 -6
- package/agents/{find-items-by-paths.mjs → utils/choose-docs.mjs} +25 -10
- package/agents/{docs-fs.yaml → utils/docs-fs-actor.yaml} +3 -1
- package/agents/utils/feedback-refiner.yaml +50 -0
- package/agents/{find-item-by-path.mjs → utils/find-item-by-path.mjs} +17 -7
- package/agents/{find-user-preferences-by-path.mjs → utils/find-user-preferences-by-path.mjs} +1 -1
- package/agents/utils/format-document-structure.mjs +25 -0
- package/agents/{load-sources.mjs → utils/load-sources.mjs} +41 -28
- package/agents/{save-docs.mjs → utils/save-docs.mjs} +16 -16
- package/agents/{save-single-doc.mjs → utils/save-single-doc.mjs} +2 -2
- package/agents/{transform-detail-datasources.mjs → utils/transform-detail-datasources.mjs} +1 -1
- package/aigne.yaml +35 -35
- package/docs/cli-reference.md +1 -1
- package/docs/features-generate-documentation.md +1 -1
- package/docs/features-update-and-refine.md +2 -2
- package/docs-mcp/analyze-docs-relevance.yaml +10 -10
- package/docs-mcp/docs-search.yaml +5 -3
- package/package.json +10 -8
- package/prompts/{document → detail/custom}/custom-code-block.md +6 -6
- package/prompts/detail/custom/custom-components.md +172 -0
- package/prompts/{document → detail}/d2-chart/rules.md +95 -1
- package/prompts/{document → detail}/detail-example.md +80 -61
- package/prompts/{document/detail-generator.md → detail/document-rules.md} +4 -8
- package/prompts/{content-detail-generator.md → detail/generate-document.md} +48 -25
- package/prompts/{check-structure-planning-result.md → structure/check-document-structure.md} +23 -17
- package/prompts/{document/structure-planning.md → structure/document-rules.md} +0 -2
- package/prompts/{structure-planning.md → structure/generate-structure.md} +51 -30
- package/prompts/{document → structure}/structure-example.md +2 -2
- package/prompts/{document → structure}/structure-getting-started.md +2 -2
- package/prompts/translate/glossary.md +6 -0
- package/prompts/{translator.md → translate/translate-document.md} +29 -10
- package/prompts/{feedback-refiner.md → utils/feedback-refiner.md} +8 -8
- package/tests/agents/chat/chat.test.mjs +46 -0
- package/tests/agents/generate/check-document-structure.test.mjs +51 -0
- package/tests/agents/generate/check-need-generate-structure.test.mjs +292 -0
- package/tests/agents/generate/generate-structure.test.mjs +51 -0
- package/tests/{input-generator.test.mjs → agents/init/init.test.mjs} +19 -17
- package/tests/agents/prefs/prefs.test.mjs +431 -0
- package/tests/agents/publish/publish-docs.test.mjs +642 -0
- package/tests/agents/translate/choose-language.test.mjs +311 -0
- package/tests/agents/translate/translate-document.test.mjs +51 -0
- package/tests/agents/update/check-document.test.mjs +523 -0
- package/tests/agents/update/generate-document.test.mjs +51 -0
- package/tests/agents/utils/action-success.test.mjs +54 -0
- package/tests/{check-detail-result.test.mjs → agents/utils/check-detail-result.test.mjs} +98 -98
- package/tests/agents/utils/check-feedback-refiner.test.mjs +478 -0
- package/tests/agents/utils/choose-docs.test.mjs +417 -0
- package/tests/agents/utils/exit.test.mjs +70 -0
- package/tests/agents/utils/feedback-refiner.test.mjs +51 -0
- package/tests/agents/utils/find-item-by-path.test.mjs +526 -0
- package/tests/agents/utils/find-user-preferences-by-path.test.mjs +382 -0
- package/tests/agents/utils/format-document-structure.test.mjs +264 -0
- package/tests/agents/utils/fs.test.mjs +267 -0
- package/tests/{load-sources.test.mjs → agents/utils/load-sources.test.mjs} +153 -25
- package/tests/{save-docs.test.mjs → agents/utils/save-docs.test.mjs} +11 -5
- package/tests/agents/utils/save-output.test.mjs +315 -0
- package/tests/agents/utils/save-single-doc.test.mjs +364 -0
- package/tests/agents/utils/transform-detail-datasources.test.mjs +363 -0
- package/tests/utils/auth-utils.test.mjs +358 -0
- package/tests/utils/blocklet.test.mjs +334 -0
- package/tests/{conflict-resolution.test.mjs → utils/conflict-detector.test.mjs} +3 -3
- package/tests/utils/constants.test.mjs +295 -0
- package/tests/utils/d2-utils.test.mjs +423 -0
- package/tests/utils/deploy.test.mjs +365 -0
- package/tests/utils/docs-finder-utils.test.mjs +625 -0
- package/tests/utils/file-utils.test.mjs +213 -0
- package/tests/{kroki-utils.test.mjs → utils/kroki-utils.test.mjs} +2 -2
- package/tests/utils/load-config.test.mjs +141 -0
- package/tests/{mermaid-validation.test.mjs → utils/mermaid-validator.test.mjs} +2 -2
- package/tests/utils/mock-chat-model.mjs +12 -0
- package/tests/{preferences-utils.test.mjs → utils/preferences-utils.test.mjs} +1 -1
- package/tests/{save-value-to-config.test.mjs → utils/save-value-to-config.test.mjs} +61 -4
- package/tests/utils/utils.test.mjs +939 -0
- package/utils/auth-utils.mjs +1 -1
- package/utils/conflict-detector.mjs +1 -1
- package/utils/constants.mjs +5 -3
- package/utils/d2-utils.mjs +194 -0
- package/utils/deploy.mjs +3 -3
- package/utils/docs-finder-utils.mjs +26 -26
- package/utils/icon-map.mjs +26 -0
- package/{agents → utils}/load-config.mjs +2 -18
- package/utils/markdown-checker.mjs +5 -5
- package/agents/batch-docs-detail-generator.yaml +0 -19
- package/agents/check-structure-planning-result.yaml +0 -30
- package/agents/content-detail-generator.yaml +0 -50
- package/agents/feedback-refiner.yaml +0 -52
- package/agents/format-structure-plan.mjs +0 -25
- package/agents/reflective-structure-planner.yaml +0 -12
- package/agents/schema/structure-plan.yaml +0 -26
- package/agents/structure-planning.yaml +0 -58
- package/agents/team-publish-docs.yaml +0 -18
- package/agents/translate.yaml +0 -31
- package/prompts/document/custom-components.md +0 -104
- package/tests/README.md +0 -93
- package/tests/utils.test.mjs +0 -2067
- /package/agents/{exit.mjs → utils/exit.mjs} +0 -0
- /package/agents/{fs.mjs → utils/fs.mjs} +0 -0
- /package/agents/{save-output.mjs → utils/save-output.mjs} +0 -0
- /package/prompts/{document → detail}/d2-chart/official-examples.md +0 -0
- /package/prompts/{document → detail}/jsx/rules.md +0 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
|
|
2
|
+
import * as fsPromises from "node:fs/promises";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import fs from "../../../agents/utils/fs.mjs";
|
|
5
|
+
|
|
6
|
+
describe("fs utility", () => {
|
|
7
|
+
let readFileSpy;
|
|
8
|
+
let writeFileSpy;
|
|
9
|
+
let mkdirSpy;
|
|
10
|
+
let rmSpy;
|
|
11
|
+
let readdirSpy;
|
|
12
|
+
let joinSpy;
|
|
13
|
+
let dirnameSpy;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
// Spy on fs/promises methods
|
|
17
|
+
readFileSpy = spyOn(fsPromises, "readFile").mockResolvedValue("mocked file content");
|
|
18
|
+
writeFileSpy = spyOn(fsPromises, "writeFile").mockResolvedValue();
|
|
19
|
+
mkdirSpy = spyOn(fsPromises, "mkdir").mockResolvedValue();
|
|
20
|
+
rmSpy = spyOn(fsPromises, "rm").mockResolvedValue();
|
|
21
|
+
readdirSpy = spyOn(fsPromises, "readdir").mockResolvedValue([
|
|
22
|
+
{ name: "file1.txt", parentPath: "/test/root", isDirectory: () => false },
|
|
23
|
+
{ name: "subdir", parentPath: "/test/root", isDirectory: () => true },
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
// Spy on path methods
|
|
27
|
+
joinSpy = spyOn(path, "join").mockImplementation((...paths) => paths.join("/"));
|
|
28
|
+
dirnameSpy = spyOn(path, "dirname").mockImplementation(
|
|
29
|
+
(path) => path.split("/").slice(0, -1).join("/") || "/",
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
// Restore all spies
|
|
35
|
+
readFileSpy?.mockRestore();
|
|
36
|
+
writeFileSpy?.mockRestore();
|
|
37
|
+
mkdirSpy?.mockRestore();
|
|
38
|
+
rmSpy?.mockRestore();
|
|
39
|
+
readdirSpy?.mockRestore();
|
|
40
|
+
joinSpy?.mockRestore();
|
|
41
|
+
dirnameSpy?.mockRestore();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// ERROR HANDLING TESTS
|
|
45
|
+
test("should throw error when rootDir is not specified", async () => {
|
|
46
|
+
await expect(
|
|
47
|
+
fs({
|
|
48
|
+
action: "read_file",
|
|
49
|
+
path: "test.txt",
|
|
50
|
+
}),
|
|
51
|
+
).rejects.toThrow("Root directory is not specified");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("should throw error when rootDir is empty string", async () => {
|
|
55
|
+
await expect(
|
|
56
|
+
fs({
|
|
57
|
+
rootDir: "",
|
|
58
|
+
action: "read_file",
|
|
59
|
+
path: "test.txt",
|
|
60
|
+
}),
|
|
61
|
+
).rejects.toThrow("Root directory is not specified");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// READ_FILE ACTION TESTS
|
|
65
|
+
test("should read file successfully", async () => {
|
|
66
|
+
readFileSpy.mockResolvedValue("test file content");
|
|
67
|
+
|
|
68
|
+
const result = await fs({
|
|
69
|
+
rootDir: "/test/root",
|
|
70
|
+
action: "read_file",
|
|
71
|
+
path: "test.txt",
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
expect(joinSpy).toHaveBeenCalledWith("/test/root", "test.txt");
|
|
75
|
+
expect(readFileSpy).toHaveBeenCalledWith("/test/root/test.txt", "utf-8");
|
|
76
|
+
expect(result).toEqual({
|
|
77
|
+
status: "ok",
|
|
78
|
+
path: "/test/root/test.txt",
|
|
79
|
+
content: "test file content",
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("should handle read file with nested path", async () => {
|
|
84
|
+
await fs({
|
|
85
|
+
rootDir: "/project",
|
|
86
|
+
action: "read_file",
|
|
87
|
+
path: "src/components/Button.tsx",
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
expect(joinSpy).toHaveBeenCalledWith("/project", "src/components/Button.tsx");
|
|
91
|
+
expect(readFileSpy).toHaveBeenCalledWith("/project/src/components/Button.tsx", "utf-8");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// WRITE_FILE ACTION TESTS
|
|
95
|
+
test("should write file successfully", async () => {
|
|
96
|
+
const result = await fs({
|
|
97
|
+
rootDir: "/test/root",
|
|
98
|
+
action: "write_file",
|
|
99
|
+
path: "output.txt",
|
|
100
|
+
content: "Hello, world!",
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
expect(dirnameSpy).toHaveBeenCalledWith("/test/root/output.txt");
|
|
104
|
+
expect(mkdirSpy).toHaveBeenCalledWith("/test/root", { recursive: true });
|
|
105
|
+
expect(writeFileSpy).toHaveBeenCalledWith("/test/root/output.txt", "Hello, world!");
|
|
106
|
+
expect(result).toEqual({
|
|
107
|
+
status: "ok",
|
|
108
|
+
path: "/test/root/output.txt",
|
|
109
|
+
content: "Hello, world!",
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("should write file with empty content when content is not provided", async () => {
|
|
114
|
+
const result = await fs({
|
|
115
|
+
rootDir: "/test/root",
|
|
116
|
+
action: "write_file",
|
|
117
|
+
path: "empty.txt",
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
expect(writeFileSpy).toHaveBeenCalledWith("/test/root/empty.txt", "");
|
|
121
|
+
expect(result.content).toBeUndefined(); // The function returns the original content parameter
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("should create directories recursively when writing file", async () => {
|
|
125
|
+
dirnameSpy.mockReturnValue("/test/root/deep/nested");
|
|
126
|
+
|
|
127
|
+
await fs({
|
|
128
|
+
rootDir: "/test/root",
|
|
129
|
+
action: "write_file",
|
|
130
|
+
path: "deep/nested/file.txt",
|
|
131
|
+
content: "nested content",
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
expect(mkdirSpy).toHaveBeenCalledWith("/test/root/deep/nested", {
|
|
135
|
+
recursive: true,
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// DELETE_FILE ACTION TESTS
|
|
140
|
+
test("should delete file successfully", async () => {
|
|
141
|
+
const result = await fs({
|
|
142
|
+
rootDir: "/test/root",
|
|
143
|
+
action: "delete_file",
|
|
144
|
+
path: "to-delete.txt",
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
expect(joinSpy).toHaveBeenCalledWith("/test/root", "to-delete.txt");
|
|
148
|
+
expect(rmSpy).toHaveBeenCalledWith("/test/root/to-delete.txt", {
|
|
149
|
+
recursive: true,
|
|
150
|
+
force: true,
|
|
151
|
+
});
|
|
152
|
+
expect(result).toEqual({
|
|
153
|
+
status: "ok",
|
|
154
|
+
path: "/test/root/to-delete.txt",
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("should delete directory recursively", async () => {
|
|
159
|
+
await fs({
|
|
160
|
+
rootDir: "/project",
|
|
161
|
+
action: "delete_file",
|
|
162
|
+
path: "build",
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
expect(rmSpy).toHaveBeenCalledWith("/project/build", {
|
|
166
|
+
recursive: true,
|
|
167
|
+
force: true,
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// LIST_DIRECTORY ACTION TESTS
|
|
172
|
+
test("should list directory entries successfully", async () => {
|
|
173
|
+
const result = await fs({
|
|
174
|
+
rootDir: "/test/root",
|
|
175
|
+
action: "list_directory",
|
|
176
|
+
path: "src",
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
expect(joinSpy).toHaveBeenCalledWith("/test/root", "src");
|
|
180
|
+
expect(readdirSpy).toHaveBeenCalledWith("/test/root/src", { withFileTypes: true });
|
|
181
|
+
expect(result).toEqual({
|
|
182
|
+
status: "ok",
|
|
183
|
+
entries: [
|
|
184
|
+
{ path: "/test/root/file1.txt", isDirectory: false },
|
|
185
|
+
{ path: "/test/root/subdir", isDirectory: true },
|
|
186
|
+
],
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("should handle empty directory listing", async () => {
|
|
191
|
+
readdirSpy.mockResolvedValue([]);
|
|
192
|
+
|
|
193
|
+
const result = await fs({
|
|
194
|
+
rootDir: "/test/root",
|
|
195
|
+
action: "list_directory",
|
|
196
|
+
path: "empty-dir",
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
expect(result.entries).toEqual([]);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// PATH HANDLING TESTS
|
|
203
|
+
test("should handle root directory with trailing slash", async () => {
|
|
204
|
+
await fs({
|
|
205
|
+
rootDir: "/test/root/",
|
|
206
|
+
action: "read_file",
|
|
207
|
+
path: "file.txt",
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
expect(joinSpy).toHaveBeenCalledWith("/test/root/", "file.txt");
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("should handle path with leading slash", async () => {
|
|
214
|
+
await fs({
|
|
215
|
+
rootDir: "/test/root",
|
|
216
|
+
action: "read_file",
|
|
217
|
+
path: "/absolute/path.txt",
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
expect(joinSpy).toHaveBeenCalledWith("/test/root", "/absolute/path.txt");
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("should handle complex nested paths", async () => {
|
|
224
|
+
await fs({
|
|
225
|
+
rootDir: "/project",
|
|
226
|
+
action: "write_file",
|
|
227
|
+
path: "src/components/ui/Button/index.tsx",
|
|
228
|
+
content: "export default Button;",
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
expect(joinSpy).toHaveBeenCalledWith("/project", "src/components/ui/Button/index.tsx");
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// UNDEFINED ACTION TEST
|
|
235
|
+
test("should return undefined for unknown action", async () => {
|
|
236
|
+
const result = await fs({
|
|
237
|
+
rootDir: "/test/root",
|
|
238
|
+
action: "unknown_action",
|
|
239
|
+
path: "test.txt",
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
expect(result).toBeUndefined();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// EDGE CASES
|
|
246
|
+
test("should handle special characters in paths", async () => {
|
|
247
|
+
await fs({
|
|
248
|
+
rootDir: "/test/root",
|
|
249
|
+
action: "read_file",
|
|
250
|
+
path: "文件名 with spaces & symbols!.txt",
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
expect(joinSpy).toHaveBeenCalledWith("/test/root", "文件名 with spaces & symbols!.txt");
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("should handle null content for write_file", async () => {
|
|
257
|
+
const result = await fs({
|
|
258
|
+
rootDir: "/test/root",
|
|
259
|
+
action: "write_file",
|
|
260
|
+
path: "null-content.txt",
|
|
261
|
+
content: null,
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
expect(writeFileSpy).toHaveBeenCalledWith("/test/root/null-content.txt", "");
|
|
265
|
+
expect(result.content).toBe(null);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
import { afterAll, afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
-
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
1
|
+
import { afterAll, afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
|
|
2
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import path, { dirname } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import loadSources from "
|
|
5
|
+
import loadSources from "../../../agents/utils/load-sources.mjs";
|
|
6
6
|
|
|
7
7
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
8
|
|
|
9
|
-
describe("
|
|
9
|
+
describe("load-sources", () => {
|
|
10
10
|
let testDir;
|
|
11
11
|
let tempDir;
|
|
12
12
|
|
|
13
13
|
beforeEach(async () => {
|
|
14
|
-
// Create test directory structure
|
|
15
|
-
|
|
14
|
+
// Create test directory structure with unique name to avoid conflicts
|
|
15
|
+
const uniqueId = `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
16
|
+
testDir = path.join(__dirname, `test-content-generator-${uniqueId}`);
|
|
16
17
|
tempDir = path.join(testDir, "temp");
|
|
17
18
|
|
|
18
19
|
await mkdir(testDir, { recursive: true });
|
|
@@ -687,6 +688,9 @@ describe("loadSources", () => {
|
|
|
687
688
|
await mkdir(deepPath, { recursive: true });
|
|
688
689
|
await writeFile(path.join(deepPath, "deep.js"), "console.log('deep');");
|
|
689
690
|
|
|
691
|
+
const content = await readFile(path.join(deepPath, "deep.js"), "utf8");
|
|
692
|
+
console.log("deep content", content);
|
|
693
|
+
|
|
690
694
|
const result = await loadSources({
|
|
691
695
|
sourcesPath: testDir,
|
|
692
696
|
includePatterns: ["**/*.js"],
|
|
@@ -1026,14 +1030,14 @@ describe("loadSources", () => {
|
|
|
1026
1030
|
});
|
|
1027
1031
|
});
|
|
1028
1032
|
|
|
1029
|
-
describe("Document path and structure
|
|
1030
|
-
test("should load existing structure
|
|
1031
|
-
const
|
|
1033
|
+
describe("Document path and document structure handling", () => {
|
|
1034
|
+
test("should load existing document structure", async () => {
|
|
1035
|
+
const documentStructure = {
|
|
1032
1036
|
sections: ["Introduction", "API", "Examples"],
|
|
1033
1037
|
lastUpdated: new Date().toISOString(),
|
|
1034
1038
|
};
|
|
1035
1039
|
|
|
1036
|
-
await writeFile(path.join(tempDir, "structure-plan.json"), JSON.stringify(
|
|
1040
|
+
await writeFile(path.join(tempDir, "structure-plan.json"), JSON.stringify(documentStructure));
|
|
1037
1041
|
|
|
1038
1042
|
const result = await loadSources({
|
|
1039
1043
|
sourcesPath: testDir,
|
|
@@ -1043,10 +1047,10 @@ describe("loadSources", () => {
|
|
|
1043
1047
|
docsDir: path.join(testDir, "docs"),
|
|
1044
1048
|
});
|
|
1045
1049
|
|
|
1046
|
-
expect(result.
|
|
1050
|
+
expect(result.originalDocumentStructure).toEqual(documentStructure);
|
|
1047
1051
|
});
|
|
1048
1052
|
|
|
1049
|
-
test("should handle malformed structure
|
|
1053
|
+
test("should handle malformed document structure JSON", async () => {
|
|
1050
1054
|
await writeFile(path.join(tempDir, "structure-plan.json"), "{ invalid json content");
|
|
1051
1055
|
|
|
1052
1056
|
const result = await loadSources({
|
|
@@ -1057,7 +1061,120 @@ describe("loadSources", () => {
|
|
|
1057
1061
|
docsDir: path.join(testDir, "docs"),
|
|
1058
1062
|
});
|
|
1059
1063
|
|
|
1060
|
-
expect(result.
|
|
1064
|
+
expect(result.originalDocumentStructure).toBeUndefined();
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
test("should handle non-ENOENT errors when reading document structure JSON", async () => {
|
|
1068
|
+
// Import fs promises module to spy on
|
|
1069
|
+
const fsPromises = await import("node:fs/promises");
|
|
1070
|
+
|
|
1071
|
+
// Get the original readFile function
|
|
1072
|
+
const originalReadFile = fsPromises.readFile;
|
|
1073
|
+
|
|
1074
|
+
// Create a spy that only throws error for structure-plan.json
|
|
1075
|
+
const readFileSpy = spyOn(fsPromises, "readFile").mockImplementation((filePath, encoding) => {
|
|
1076
|
+
if (filePath.includes("structure-plan.json")) {
|
|
1077
|
+
const error = new Error("Permission denied");
|
|
1078
|
+
error.code = "EACCES";
|
|
1079
|
+
throw error;
|
|
1080
|
+
}
|
|
1081
|
+
// For all other files, use the original readFile
|
|
1082
|
+
return originalReadFile(filePath, encoding);
|
|
1083
|
+
});
|
|
1084
|
+
|
|
1085
|
+
try {
|
|
1086
|
+
const result = await loadSources({
|
|
1087
|
+
sourcesPath: testDir,
|
|
1088
|
+
includePatterns: ["*.js"],
|
|
1089
|
+
useDefaultPatterns: false,
|
|
1090
|
+
outputDir: tempDir,
|
|
1091
|
+
docsDir: path.join(testDir, "docs"),
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
expect(result.originalDocumentStructure).toBeUndefined();
|
|
1095
|
+
} finally {
|
|
1096
|
+
// Restore the original readFile function
|
|
1097
|
+
readFileSpy.mockRestore();
|
|
1098
|
+
}
|
|
1099
|
+
});
|
|
1100
|
+
|
|
1101
|
+
test("should handle non-ENOENT errors when reading docPath file", async () => {
|
|
1102
|
+
// Import fs promises module to spy on
|
|
1103
|
+
const fsPromises = await import("node:fs/promises");
|
|
1104
|
+
|
|
1105
|
+
// Get the original readFile function
|
|
1106
|
+
const originalReadFile = fsPromises.readFile;
|
|
1107
|
+
|
|
1108
|
+
// Create a spy that only throws error for the specific docPath file
|
|
1109
|
+
const readFileSpy = spyOn(fsPromises, "readFile").mockImplementation((filePath, encoding) => {
|
|
1110
|
+
// Check if this is the docPath file (api-overview.md)
|
|
1111
|
+
if (filePath.includes("api-overview.md")) {
|
|
1112
|
+
const error = new Error("Permission denied");
|
|
1113
|
+
error.code = "EACCES";
|
|
1114
|
+
throw error;
|
|
1115
|
+
}
|
|
1116
|
+
// For all other files, use the original readFile
|
|
1117
|
+
return originalReadFile(filePath, encoding);
|
|
1118
|
+
});
|
|
1119
|
+
|
|
1120
|
+
try {
|
|
1121
|
+
const result = await loadSources({
|
|
1122
|
+
sourcesPath: testDir,
|
|
1123
|
+
"doc-path": "/api/overview",
|
|
1124
|
+
includePatterns: ["*.js"],
|
|
1125
|
+
useDefaultPatterns: false,
|
|
1126
|
+
outputDir: tempDir,
|
|
1127
|
+
docsDir: path.join(testDir, "docs"),
|
|
1128
|
+
});
|
|
1129
|
+
|
|
1130
|
+
expect(result.content).toBeUndefined();
|
|
1131
|
+
} finally {
|
|
1132
|
+
// Restore the original readFile function
|
|
1133
|
+
readFileSpy.mockRestore();
|
|
1134
|
+
}
|
|
1135
|
+
});
|
|
1136
|
+
|
|
1137
|
+
test("should handle non-ENOENT errors when reading boardId-based docPath file", async () => {
|
|
1138
|
+
// Import fs promises module to spy on
|
|
1139
|
+
const fsPromises = await import("node:fs/promises");
|
|
1140
|
+
|
|
1141
|
+
// Get the original readFile function
|
|
1142
|
+
const originalReadFile = fsPromises.readFile;
|
|
1143
|
+
|
|
1144
|
+
// Create a spy that only throws error for the boardId file
|
|
1145
|
+
const readFileSpy = spyOn(fsPromises, "readFile").mockImplementation((filePath, encoding) => {
|
|
1146
|
+
// First call will fail for the original format (board123-api-overview.md)
|
|
1147
|
+
// Second call should fail for the boardId format (api-overview.md)
|
|
1148
|
+
if (filePath.includes("board123-api-overview.md")) {
|
|
1149
|
+
const error = new Error("File not found");
|
|
1150
|
+
error.code = "ENOENT";
|
|
1151
|
+
throw error;
|
|
1152
|
+
}
|
|
1153
|
+
if (filePath.includes("api-overview.md") && !filePath.includes("board123-")) {
|
|
1154
|
+
const error = new Error("Permission denied");
|
|
1155
|
+
error.code = "EACCES";
|
|
1156
|
+
throw error;
|
|
1157
|
+
}
|
|
1158
|
+
// For all other files, use the original readFile
|
|
1159
|
+
return originalReadFile(filePath, encoding);
|
|
1160
|
+
});
|
|
1161
|
+
|
|
1162
|
+
try {
|
|
1163
|
+
const result = await loadSources({
|
|
1164
|
+
sourcesPath: testDir,
|
|
1165
|
+
"doc-path": "board123-api-overview",
|
|
1166
|
+
boardId: "board123",
|
|
1167
|
+
includePatterns: ["*.js"],
|
|
1168
|
+
useDefaultPatterns: false,
|
|
1169
|
+
outputDir: tempDir,
|
|
1170
|
+
docsDir: path.join(testDir, "docs"),
|
|
1171
|
+
});
|
|
1172
|
+
|
|
1173
|
+
expect(result.content).toBeUndefined();
|
|
1174
|
+
} finally {
|
|
1175
|
+
// Restore the original readFile function
|
|
1176
|
+
readFileSpy.mockRestore();
|
|
1177
|
+
}
|
|
1061
1178
|
});
|
|
1062
1179
|
|
|
1063
1180
|
test("should load document content by docPath", async () => {
|
|
@@ -1323,20 +1440,31 @@ describe("loadSources", () => {
|
|
|
1323
1440
|
|
|
1324
1441
|
// Global cleanup to ensure test directories are fully removed
|
|
1325
1442
|
afterAll(async () => {
|
|
1326
|
-
|
|
1443
|
+
// Clean up all test-content-generator directories (including unique ones)
|
|
1327
1444
|
try {
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1445
|
+
const { readdir } = await import("node:fs/promises");
|
|
1446
|
+
const entries = await readdir(__dirname, { withFileTypes: true });
|
|
1447
|
+
|
|
1448
|
+
for (const entry of entries) {
|
|
1449
|
+
if (entry.isDirectory() && entry.name.startsWith("test-content-generator")) {
|
|
1450
|
+
const testDirToClean = path.join(__dirname, entry.name);
|
|
1451
|
+
try {
|
|
1452
|
+
await rm(testDirToClean, { recursive: true, force: true });
|
|
1453
|
+
} catch {
|
|
1454
|
+
// Try with system command as fallback
|
|
1455
|
+
try {
|
|
1456
|
+
const { exec } = await import("node:child_process");
|
|
1457
|
+
const { promisify } = await import("node:util");
|
|
1458
|
+
const execAsync = promisify(exec);
|
|
1459
|
+
await execAsync(`rm -rf "${testDirToClean}"`);
|
|
1460
|
+
} catch {
|
|
1461
|
+
console.warn(`Warning: Could not fully clean up test directory: ${testDirToClean}`);
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1339
1465
|
}
|
|
1466
|
+
} catch (error) {
|
|
1467
|
+
console.warn(`Warning: Could not clean up test directories: ${error.message}`);
|
|
1340
1468
|
}
|
|
1341
1469
|
});
|
|
1342
1470
|
});
|
|
@@ -2,14 +2,17 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
|
2
2
|
import { mkdir, readdir, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import saveDocs from "
|
|
5
|
+
import saveDocs from "../../../agents/utils/save-docs.mjs";
|
|
6
6
|
|
|
7
7
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
8
|
|
|
9
|
-
describe("
|
|
9
|
+
describe("save-docs", () => {
|
|
10
10
|
let testDir;
|
|
11
11
|
|
|
12
12
|
beforeEach(async () => {
|
|
13
|
+
// Set test environment variable to prevent actual config file modification
|
|
14
|
+
process.env.NODE_ENV = "test";
|
|
15
|
+
|
|
13
16
|
// Create a temporary test directory
|
|
14
17
|
testDir = join(__dirname, "test-docs");
|
|
15
18
|
await mkdir(testDir, { recursive: true });
|
|
@@ -32,6 +35,9 @@ describe("saveDocs", () => {
|
|
|
32
35
|
});
|
|
33
36
|
|
|
34
37
|
afterEach(async () => {
|
|
38
|
+
// Clean up environment variable
|
|
39
|
+
delete process.env.NODE_ENV;
|
|
40
|
+
|
|
35
41
|
// Clean up test directory
|
|
36
42
|
try {
|
|
37
43
|
await rm(testDir, { recursive: true, force: true });
|
|
@@ -46,8 +52,8 @@ describe("saveDocs", () => {
|
|
|
46
52
|
expect(initialFiles).toContain("getting-started.md");
|
|
47
53
|
expect(initialFiles).toContain("old-file.md");
|
|
48
54
|
|
|
49
|
-
// Test structure
|
|
50
|
-
const
|
|
55
|
+
// Test document structure
|
|
56
|
+
const documentStructure = [
|
|
51
57
|
{
|
|
52
58
|
path: "/overview",
|
|
53
59
|
title: "Overview",
|
|
@@ -64,7 +70,7 @@ describe("saveDocs", () => {
|
|
|
64
70
|
const translateLanguages = ["zh", "en"];
|
|
65
71
|
|
|
66
72
|
const result = await saveDocs({
|
|
67
|
-
|
|
73
|
+
documentExecutionStructure: documentStructure,
|
|
68
74
|
docsDir: testDir,
|
|
69
75
|
translateLanguages,
|
|
70
76
|
});
|