@aigne/doc-smith 0.7.1 → 0.8.0
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/.github/workflows/ci.yml +9 -1
- package/CHANGELOG.md +15 -0
- package/README.md +5 -0
- package/agents/check-detail-result.mjs +7 -0
- package/agents/check-detail.mjs +3 -1
- package/agents/detail-regenerator.yaml +3 -0
- package/agents/find-items-by-paths.mjs +9 -1
- package/agents/input-generator.mjs +3 -3
- package/agents/load-sources.mjs +2 -1
- package/agents/save-docs.mjs +1 -5
- package/codecov.yml +15 -0
- package/package.json +1 -1
- package/prompts/content-detail-generator.md +1 -0
- package/prompts/document/custom-components.md +36 -12
- package/tests/check-detail-result.test.mjs +656 -17
- package/tests/conflict-resolution.test.mjs +118 -0
- package/tests/input-generator.test.mjs +594 -1
- package/tests/kroki-utils.test.mjs +588 -0
- package/tests/load-sources.test.mjs +362 -2
- package/tests/mermaid-validation.test.mjs +541 -0
- package/tests/save-docs.test.mjs +1 -1
- package/tests/utils.test.mjs +2020 -2
- package/utils/conflict-detector.mjs +0 -59
- package/utils/file-utils.mjs +5 -0
- package/utils/kroki-utils.mjs +4 -0
- package/utils/markdown-checker.mjs +3 -3
- package/utils/mermaid-validator.mjs +0 -13
- package/utils/utils.mjs +11 -5
- package/tests/all-validation-cases.test.mjs +0 -686
|
@@ -923,12 +923,12 @@ describe("loadSources", () => {
|
|
|
923
923
|
try {
|
|
924
924
|
await unlink(link1);
|
|
925
925
|
} catch {
|
|
926
|
-
// Ignore cleanup errors
|
|
926
|
+
// Ignore cleanup errors since they don't affect test results
|
|
927
927
|
}
|
|
928
928
|
try {
|
|
929
929
|
await unlink(link2);
|
|
930
930
|
} catch {
|
|
931
|
-
// Ignore cleanup errors
|
|
931
|
+
// Ignore cleanup errors since they don't affect test results
|
|
932
932
|
}
|
|
933
933
|
} catch {
|
|
934
934
|
// Skip on systems that don't support symlinks
|
|
@@ -961,6 +961,366 @@ describe("loadSources", () => {
|
|
|
961
961
|
});
|
|
962
962
|
});
|
|
963
963
|
|
|
964
|
+
describe("File type handling and media assets", () => {
|
|
965
|
+
test("should handle individual files as sourcesPath", async () => {
|
|
966
|
+
// Test line 41: stats.isFile() branch
|
|
967
|
+
const singleFile = path.join(testDir, "single-file.js");
|
|
968
|
+
await writeFile(singleFile, "console.log('single file');");
|
|
969
|
+
|
|
970
|
+
const result = await loadSources({
|
|
971
|
+
sourcesPath: singleFile,
|
|
972
|
+
useDefaultPatterns: false,
|
|
973
|
+
outputDir: tempDir,
|
|
974
|
+
docsDir: path.join(testDir, "docs"),
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
expect(result.datasourcesList).toBeDefined();
|
|
978
|
+
expect(result.datasourcesList.length).toBe(1);
|
|
979
|
+
expect(result.datasourcesList[0].sourceId).toContain("single-file.js");
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
test("should process media files correctly", async () => {
|
|
983
|
+
// Create media files to test lines 151-159
|
|
984
|
+
const mediaDir = path.join(testDir, "media");
|
|
985
|
+
await mkdir(mediaDir, { recursive: true });
|
|
986
|
+
|
|
987
|
+
await writeFile(path.join(mediaDir, "image.jpg"), "fake image data");
|
|
988
|
+
await writeFile(path.join(mediaDir, "video.mp4"), "fake video data");
|
|
989
|
+
await writeFile(path.join(mediaDir, "logo.svg"), "<svg></svg>");
|
|
990
|
+
await writeFile(path.join(mediaDir, "animation.webp"), "fake webp data");
|
|
991
|
+
|
|
992
|
+
const result = await loadSources({
|
|
993
|
+
sourcesPath: mediaDir,
|
|
994
|
+
includePatterns: ["**/*"],
|
|
995
|
+
useDefaultPatterns: false,
|
|
996
|
+
outputDir: tempDir,
|
|
997
|
+
docsDir: path.join(testDir, "docs"),
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
expect(result.assetsContent).toBeDefined();
|
|
1001
|
+
expect(result.assetsContent).toContain("Available Media Assets");
|
|
1002
|
+
expect(result.assetsContent).toContain("image.jpg");
|
|
1003
|
+
expect(result.assetsContent).toContain("video.mp4");
|
|
1004
|
+
expect(result.assetsContent).toContain("logo.svg");
|
|
1005
|
+
expect(result.assetsContent).toContain('type: "image"');
|
|
1006
|
+
expect(result.assetsContent).toContain('type: "video"');
|
|
1007
|
+
|
|
1008
|
+
// Test media file processing logic (lines 243-266)
|
|
1009
|
+
expect(result.assetsContent).toContain("```yaml");
|
|
1010
|
+
expect(result.assetsContent).toContain("assets:");
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
test("should handle glob pattern errors gracefully", async () => {
|
|
1014
|
+
const invalidGlobPattern = "./invalid/**/*.{unclosed";
|
|
1015
|
+
|
|
1016
|
+
const result = await loadSources({
|
|
1017
|
+
sourcesPath: [invalidGlobPattern],
|
|
1018
|
+
useDefaultPatterns: false,
|
|
1019
|
+
outputDir: tempDir,
|
|
1020
|
+
docsDir: path.join(testDir, "docs"),
|
|
1021
|
+
});
|
|
1022
|
+
|
|
1023
|
+
expect(result.datasourcesList).toBeDefined();
|
|
1024
|
+
expect(Array.isArray(result.datasourcesList)).toBe(true);
|
|
1025
|
+
// Should handle gracefully without crashing
|
|
1026
|
+
});
|
|
1027
|
+
});
|
|
1028
|
+
|
|
1029
|
+
describe("Document path and structure plan handling", () => {
|
|
1030
|
+
test("should load existing structure plan", async () => {
|
|
1031
|
+
const structurePlan = {
|
|
1032
|
+
sections: ["Introduction", "API", "Examples"],
|
|
1033
|
+
lastUpdated: new Date().toISOString(),
|
|
1034
|
+
};
|
|
1035
|
+
|
|
1036
|
+
await writeFile(path.join(tempDir, "structure-plan.json"), JSON.stringify(structurePlan));
|
|
1037
|
+
|
|
1038
|
+
const result = await loadSources({
|
|
1039
|
+
sourcesPath: testDir,
|
|
1040
|
+
includePatterns: ["*.js"],
|
|
1041
|
+
useDefaultPatterns: false,
|
|
1042
|
+
outputDir: tempDir,
|
|
1043
|
+
docsDir: path.join(testDir, "docs"),
|
|
1044
|
+
});
|
|
1045
|
+
|
|
1046
|
+
expect(result.originalStructurePlan).toEqual(structurePlan);
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
test("should handle malformed structure plan JSON", async () => {
|
|
1050
|
+
await writeFile(path.join(tempDir, "structure-plan.json"), "{ invalid json content");
|
|
1051
|
+
|
|
1052
|
+
const result = await loadSources({
|
|
1053
|
+
sourcesPath: testDir,
|
|
1054
|
+
includePatterns: ["*.js"],
|
|
1055
|
+
useDefaultPatterns: false,
|
|
1056
|
+
outputDir: tempDir,
|
|
1057
|
+
docsDir: path.join(testDir, "docs"),
|
|
1058
|
+
});
|
|
1059
|
+
|
|
1060
|
+
expect(result.originalStructurePlan).toBeUndefined();
|
|
1061
|
+
});
|
|
1062
|
+
|
|
1063
|
+
test("should load document content by docPath", async () => {
|
|
1064
|
+
const docsDir = path.join(testDir, "docs");
|
|
1065
|
+
const docContent = "# API Documentation\n\nThis is the API documentation content.";
|
|
1066
|
+
|
|
1067
|
+
await writeFile(path.join(docsDir, "api-overview.md"), docContent);
|
|
1068
|
+
|
|
1069
|
+
const result = await loadSources({
|
|
1070
|
+
sourcesPath: testDir,
|
|
1071
|
+
"doc-path": "/api/overview",
|
|
1072
|
+
includePatterns: ["*.md"],
|
|
1073
|
+
useDefaultPatterns: false,
|
|
1074
|
+
outputDir: tempDir,
|
|
1075
|
+
docsDir: docsDir,
|
|
1076
|
+
});
|
|
1077
|
+
|
|
1078
|
+
expect(result.content).toBe(docContent);
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
test("should handle boardId-based doc path format", async () => {
|
|
1082
|
+
const docsDir = path.join(testDir, "docs");
|
|
1083
|
+
const docContent = "# Board specific documentation";
|
|
1084
|
+
|
|
1085
|
+
await writeFile(path.join(docsDir, "user-guide.md"), docContent);
|
|
1086
|
+
|
|
1087
|
+
const result = await loadSources({
|
|
1088
|
+
sourcesPath: testDir,
|
|
1089
|
+
"doc-path": "board123-user-guide",
|
|
1090
|
+
boardId: "board123",
|
|
1091
|
+
includePatterns: ["*.md"],
|
|
1092
|
+
useDefaultPatterns: false,
|
|
1093
|
+
outputDir: tempDir,
|
|
1094
|
+
docsDir: docsDir,
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
expect(result.content).toBe(docContent);
|
|
1098
|
+
});
|
|
1099
|
+
|
|
1100
|
+
test("should handle non-existent doc path gracefully", async () => {
|
|
1101
|
+
const result = await loadSources({
|
|
1102
|
+
sourcesPath: testDir,
|
|
1103
|
+
"doc-path": "/non-existent/doc",
|
|
1104
|
+
includePatterns: ["*.md"],
|
|
1105
|
+
useDefaultPatterns: false,
|
|
1106
|
+
outputDir: tempDir,
|
|
1107
|
+
docsDir: path.join(testDir, "docs"),
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1110
|
+
expect(result.content).toBeUndefined();
|
|
1111
|
+
});
|
|
1112
|
+
|
|
1113
|
+
test("should handle includePatterns empty gracefully", async () => {
|
|
1114
|
+
const docsDir = path.join(testDir, "docs");
|
|
1115
|
+
const docContent = "# API Documentation\n\nThis is the API documentation content.";
|
|
1116
|
+
|
|
1117
|
+
await writeFile(path.join(docsDir, "api-overview.md"), docContent);
|
|
1118
|
+
|
|
1119
|
+
const result = await loadSources({
|
|
1120
|
+
sourcesPath: testDir,
|
|
1121
|
+
"doc-path": "/api/overview",
|
|
1122
|
+
includePatterns: null,
|
|
1123
|
+
useDefaultPatterns: false,
|
|
1124
|
+
outputDir: tempDir,
|
|
1125
|
+
docsDir: docsDir,
|
|
1126
|
+
});
|
|
1127
|
+
|
|
1128
|
+
expect(result.files?.length).toBe(0);
|
|
1129
|
+
});
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
describe("Word and line counting", () => {
|
|
1133
|
+
test("should count words and lines in source content", async () => {
|
|
1134
|
+
// Create files with known content for counting
|
|
1135
|
+
await writeFile(
|
|
1136
|
+
path.join(testDir, "count-test.js"),
|
|
1137
|
+
"// This file has words and lines\nconst message = 'hello world';\nconsole.log(message);\n\n// Another comment\nfunction test() {\n return true;\n}",
|
|
1138
|
+
);
|
|
1139
|
+
|
|
1140
|
+
const result = await loadSources({
|
|
1141
|
+
sourcesPath: testDir,
|
|
1142
|
+
includePatterns: ["count-test.js"],
|
|
1143
|
+
useDefaultPatterns: false,
|
|
1144
|
+
outputDir: tempDir,
|
|
1145
|
+
docsDir: path.join(testDir, "docs"),
|
|
1146
|
+
});
|
|
1147
|
+
|
|
1148
|
+
expect(result.totalWords).toBeGreaterThan(0);
|
|
1149
|
+
expect(result.totalLines).toBeGreaterThan(0);
|
|
1150
|
+
expect(typeof result.totalWords).toBe("number");
|
|
1151
|
+
expect(typeof result.totalLines).toBe("number");
|
|
1152
|
+
});
|
|
1153
|
+
});
|
|
1154
|
+
|
|
1155
|
+
describe("Media file path and metadata processing", () => {
|
|
1156
|
+
test("should correctly process media file relativePath, fileName, and description", async () => {
|
|
1157
|
+
// Create media files in a specific structure to test the exact logic
|
|
1158
|
+
const mediaSubDir = path.join(testDir, "assets", "images");
|
|
1159
|
+
await mkdir(mediaSubDir, { recursive: true });
|
|
1160
|
+
|
|
1161
|
+
const imageFile = path.join(mediaSubDir, "company-logo.png");
|
|
1162
|
+
const videoFile = path.join(mediaSubDir, "demo-video.mp4");
|
|
1163
|
+
const svgFile = path.join(mediaSubDir, "icon-arrow.svg");
|
|
1164
|
+
|
|
1165
|
+
await writeFile(imageFile, "fake png data");
|
|
1166
|
+
await writeFile(videoFile, "fake video data");
|
|
1167
|
+
await writeFile(svgFile, "<svg>fake svg</svg>");
|
|
1168
|
+
|
|
1169
|
+
const docsDir = path.join(testDir, "docs");
|
|
1170
|
+
|
|
1171
|
+
const result = await loadSources({
|
|
1172
|
+
sourcesPath: mediaSubDir,
|
|
1173
|
+
includePatterns: ["**/*"],
|
|
1174
|
+
useDefaultPatterns: false,
|
|
1175
|
+
outputDir: tempDir,
|
|
1176
|
+
docsDir: docsDir,
|
|
1177
|
+
});
|
|
1178
|
+
|
|
1179
|
+
expect(result.assetsContent).toBeDefined();
|
|
1180
|
+
|
|
1181
|
+
// Check that relativePath calculation worked (line 151)
|
|
1182
|
+
expect(result.assetsContent).toContain("../assets/images/company-logo.png");
|
|
1183
|
+
expect(result.assetsContent).toContain("../assets/images/demo-video.mp4");
|
|
1184
|
+
expect(result.assetsContent).toContain("../assets/images/icon-arrow.svg");
|
|
1185
|
+
|
|
1186
|
+
// Check that fileName extraction worked (line 152)
|
|
1187
|
+
expect(result.assetsContent).toContain('name: "company-logo.png"');
|
|
1188
|
+
expect(result.assetsContent).toContain('name: "demo-video.mp4"');
|
|
1189
|
+
expect(result.assetsContent).toContain('name: "icon-arrow.svg"');
|
|
1190
|
+
|
|
1191
|
+
// Test with complex filenames to ensure path.parse works correctly
|
|
1192
|
+
const complexFile = path.join(mediaSubDir, "my-complex.file-name.with.dots.jpg");
|
|
1193
|
+
await writeFile(complexFile, "fake jpg data");
|
|
1194
|
+
|
|
1195
|
+
const result2 = await loadSources({
|
|
1196
|
+
sourcesPath: mediaSubDir,
|
|
1197
|
+
includePatterns: ["my-complex.file-name.with.dots.jpg"],
|
|
1198
|
+
useDefaultPatterns: false,
|
|
1199
|
+
outputDir: tempDir,
|
|
1200
|
+
docsDir: docsDir,
|
|
1201
|
+
});
|
|
1202
|
+
|
|
1203
|
+
expect(result2.assetsContent).toContain('name: "my-complex.file-name.with.dots.jpg"');
|
|
1204
|
+
expect(result2.assetsContent).toContain(
|
|
1205
|
+
"../assets/images/my-complex.file-name.with.dots.jpg",
|
|
1206
|
+
);
|
|
1207
|
+
});
|
|
1208
|
+
|
|
1209
|
+
test("should handle media files with same docsDir path correctly", async () => {
|
|
1210
|
+
// Test when media files are in the same directory as docsDir
|
|
1211
|
+
const docsDir = path.join(testDir, "docs");
|
|
1212
|
+
const mediaFile = path.join(docsDir, "logo.png");
|
|
1213
|
+
|
|
1214
|
+
await writeFile(mediaFile, "fake logo data");
|
|
1215
|
+
|
|
1216
|
+
const result = await loadSources({
|
|
1217
|
+
sourcesPath: docsDir,
|
|
1218
|
+
includePatterns: ["*.png"],
|
|
1219
|
+
useDefaultPatterns: false,
|
|
1220
|
+
outputDir: tempDir,
|
|
1221
|
+
docsDir: docsDir,
|
|
1222
|
+
});
|
|
1223
|
+
|
|
1224
|
+
// When file is in docsDir, relativePath should be just the filename
|
|
1225
|
+
expect(result.assetsContent).toContain('path: "logo.png"');
|
|
1226
|
+
expect(result.assetsContent).toContain('name: "logo.png"');
|
|
1227
|
+
});
|
|
1228
|
+
|
|
1229
|
+
test("should handle media files in parent directory relative to docsDir", async () => {
|
|
1230
|
+
// Test when media files are in parent directory of docsDir
|
|
1231
|
+
const docsDir = path.join(testDir, "documentation");
|
|
1232
|
+
await mkdir(docsDir, { recursive: true });
|
|
1233
|
+
|
|
1234
|
+
const mediaFile = path.join(testDir, "header-image.jpg");
|
|
1235
|
+
await writeFile(mediaFile, "fake header data");
|
|
1236
|
+
|
|
1237
|
+
const result = await loadSources({
|
|
1238
|
+
sourcesPath: testDir,
|
|
1239
|
+
includePatterns: ["header-image.jpg"],
|
|
1240
|
+
useDefaultPatterns: false,
|
|
1241
|
+
outputDir: tempDir,
|
|
1242
|
+
docsDir: docsDir,
|
|
1243
|
+
});
|
|
1244
|
+
|
|
1245
|
+
// When file is in parent of docsDir, relativePath should use ../
|
|
1246
|
+
expect(result.assetsContent).toContain('path: "../header-image.jpg"');
|
|
1247
|
+
expect(result.assetsContent).toContain('name: "header-image.jpg"');
|
|
1248
|
+
});
|
|
1249
|
+
});
|
|
1250
|
+
|
|
1251
|
+
describe("Edge cases and error handling", () => {
|
|
1252
|
+
test("should handle empty media files list", async () => {
|
|
1253
|
+
const result = await loadSources({
|
|
1254
|
+
sourcesPath: testDir,
|
|
1255
|
+
includePatterns: ["*.js"], // Only include non-media files
|
|
1256
|
+
useDefaultPatterns: false,
|
|
1257
|
+
outputDir: tempDir,
|
|
1258
|
+
docsDir: path.join(testDir, "docs"),
|
|
1259
|
+
});
|
|
1260
|
+
|
|
1261
|
+
expect(result.assetsContent).toBeDefined();
|
|
1262
|
+
expect(result.assetsContent).toContain("Available Media Assets");
|
|
1263
|
+
// Should have basic header even with no media files
|
|
1264
|
+
});
|
|
1265
|
+
|
|
1266
|
+
test("should handle mixed source and media files", async () => {
|
|
1267
|
+
// Create mixed content
|
|
1268
|
+
await writeFile(path.join(testDir, "mixed-test.js"), "console.log('code');");
|
|
1269
|
+
await writeFile(path.join(testDir, "mixed-image.png"), "fake png data");
|
|
1270
|
+
await writeFile(path.join(testDir, "mixed-doc.md"), "# Documentation");
|
|
1271
|
+
|
|
1272
|
+
const result = await loadSources({
|
|
1273
|
+
sourcesPath: testDir,
|
|
1274
|
+
includePatterns: ["mixed-*"],
|
|
1275
|
+
useDefaultPatterns: false,
|
|
1276
|
+
outputDir: tempDir,
|
|
1277
|
+
docsDir: path.join(testDir, "docs"),
|
|
1278
|
+
});
|
|
1279
|
+
|
|
1280
|
+
expect(result.datasourcesList.length).toBeGreaterThan(0);
|
|
1281
|
+
expect(result.assetsContent).toContain("mixed-image.png");
|
|
1282
|
+
|
|
1283
|
+
// Verify both source files and media files are processed
|
|
1284
|
+
const sourceFiles = result.datasourcesList.map((f) => f.sourceId);
|
|
1285
|
+
expect(sourceFiles.some((f) => f.includes("mixed-test.js"))).toBe(true);
|
|
1286
|
+
expect(sourceFiles.some((f) => f.includes("mixed-doc.md"))).toBe(true);
|
|
1287
|
+
});
|
|
1288
|
+
|
|
1289
|
+
test("should handle various media file types", async () => {
|
|
1290
|
+
const mediaTypes = [
|
|
1291
|
+
{ name: "test.jpg", type: "image" },
|
|
1292
|
+
{ name: "test.jpeg", type: "image" },
|
|
1293
|
+
{ name: "test.png", type: "image" },
|
|
1294
|
+
{ name: "test.gif", type: "image" },
|
|
1295
|
+
{ name: "test.webp", type: "image" },
|
|
1296
|
+
{ name: "test.svg", type: "image" },
|
|
1297
|
+
{ name: "test.mp4", type: "video" },
|
|
1298
|
+
{ name: "test.mov", type: "video" },
|
|
1299
|
+
{ name: "test.avi", type: "video" },
|
|
1300
|
+
{ name: "test.webm", type: "video" },
|
|
1301
|
+
];
|
|
1302
|
+
|
|
1303
|
+
// Create all media file types
|
|
1304
|
+
for (const media of mediaTypes) {
|
|
1305
|
+
await writeFile(path.join(testDir, media.name), `fake ${media.type} data`);
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
const result = await loadSources({
|
|
1309
|
+
sourcesPath: testDir,
|
|
1310
|
+
includePatterns: ["test.*"],
|
|
1311
|
+
useDefaultPatterns: false,
|
|
1312
|
+
outputDir: tempDir,
|
|
1313
|
+
docsDir: path.join(testDir, "docs"),
|
|
1314
|
+
});
|
|
1315
|
+
|
|
1316
|
+
// Verify all media types are properly categorized
|
|
1317
|
+
for (const media of mediaTypes) {
|
|
1318
|
+
expect(result.assetsContent).toContain(media.name);
|
|
1319
|
+
expect(result.assetsContent).toContain(`type: "${media.type}"`);
|
|
1320
|
+
}
|
|
1321
|
+
});
|
|
1322
|
+
});
|
|
1323
|
+
|
|
964
1324
|
// Global cleanup to ensure test directories are fully removed
|
|
965
1325
|
afterAll(async () => {
|
|
966
1326
|
const testDirBase = path.join(__dirname, "test-content-generator");
|