@aigne/doc-smith 0.5.1 → 0.6.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/CHANGELOG.md +7 -0
- package/agents/check-detail-result.mjs +2 -1
- package/agents/check-detail.mjs +1 -0
- package/agents/load-sources.mjs +63 -9
- package/agents/publish-docs.mjs +2 -0
- package/package.json +8 -8
- package/prompts/content-detail-generator.md +5 -1
- package/prompts/document/detail-generator.md +1 -0
- package/tests/check-detail-result.test.mjs +51 -2
- package/utils/auth-utils.mjs +1 -1
- package/utils/constants.mjs +14 -2
- package/utils/markdown-checker.mjs +89 -9
- package/utils/utils.mjs +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.0](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.5.1...v0.6.0) (2025-08-27)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* complete support for media processing before publish ([#63](https://github.com/AIGNE-io/aigne-doc-smith/issues/63)) ([5257ca1](https://github.com/AIGNE-io/aigne-doc-smith/commit/5257ca1756f47487b65a1813949e547b6fc51aca))
|
|
9
|
+
|
|
3
10
|
## [0.5.1](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.5.0...v0.5.1) (2025-08-26)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { checkMarkdown } from "../utils/markdown-checker.mjs";
|
|
2
2
|
|
|
3
|
-
export default async function checkDetailResult({ structurePlan, reviewContent }) {
|
|
3
|
+
export default async function checkDetailResult({ structurePlan, reviewContent, docsDir }) {
|
|
4
4
|
let isApproved = true;
|
|
5
5
|
const detailFeedback = [];
|
|
6
6
|
|
|
@@ -24,6 +24,7 @@ export default async function checkDetailResult({ structurePlan, reviewContent }
|
|
|
24
24
|
try {
|
|
25
25
|
const markdownErrors = await checkMarkdown(reviewContent, "result", {
|
|
26
26
|
allowedLinks,
|
|
27
|
+
baseDir: docsDir,
|
|
27
28
|
});
|
|
28
29
|
|
|
29
30
|
if (markdownErrors.length > 0) {
|
package/agents/check-detail.mjs
CHANGED
package/agents/load-sources.mjs
CHANGED
|
@@ -86,17 +86,55 @@ export default async function loadSources({
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
files = [...new Set(files)];
|
|
89
|
+
|
|
90
|
+
// Define media file extensions
|
|
91
|
+
const mediaExtensions = [
|
|
92
|
+
".jpg",
|
|
93
|
+
".jpeg",
|
|
94
|
+
".png",
|
|
95
|
+
".gif",
|
|
96
|
+
".bmp",
|
|
97
|
+
".webp",
|
|
98
|
+
".svg",
|
|
99
|
+
".mp4",
|
|
100
|
+
".mov",
|
|
101
|
+
".avi",
|
|
102
|
+
".mkv",
|
|
103
|
+
".webm",
|
|
104
|
+
".m4v",
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
// Separate source files from media files
|
|
108
|
+
const sourceFiles = [];
|
|
109
|
+
const mediaFiles = [];
|
|
89
110
|
let allSources = "";
|
|
90
|
-
|
|
111
|
+
|
|
112
|
+
await Promise.all(
|
|
91
113
|
files.map(async (file) => {
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
114
|
+
const ext = path.extname(file).toLowerCase();
|
|
115
|
+
|
|
116
|
+
if (mediaExtensions.includes(ext)) {
|
|
117
|
+
// This is a media file
|
|
118
|
+
const relativePath = path.relative(docsDir, file);
|
|
119
|
+
const fileName = path.basename(file);
|
|
120
|
+
const description = path.parse(fileName).name;
|
|
121
|
+
|
|
122
|
+
mediaFiles.push({
|
|
123
|
+
name: fileName,
|
|
124
|
+
path: relativePath,
|
|
125
|
+
description,
|
|
126
|
+
});
|
|
127
|
+
} else {
|
|
128
|
+
// This is a source file
|
|
129
|
+
const content = await readFile(file, "utf8");
|
|
130
|
+
const relativePath = path.relative(process.cwd(), file);
|
|
131
|
+
allSources += `// sourceId: ${relativePath}\n${content}\n`;
|
|
132
|
+
|
|
133
|
+
sourceFiles.push({
|
|
134
|
+
sourceId: relativePath,
|
|
135
|
+
content,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
100
138
|
}),
|
|
101
139
|
);
|
|
102
140
|
|
|
@@ -164,6 +202,17 @@ export default async function loadSources({
|
|
|
164
202
|
}
|
|
165
203
|
}
|
|
166
204
|
|
|
205
|
+
// Generate assets content from media files
|
|
206
|
+
let assetsContent = "# Available Media Assets for Documentation\n\n";
|
|
207
|
+
|
|
208
|
+
if (mediaFiles.length > 0) {
|
|
209
|
+
const mediaMarkdown = mediaFiles
|
|
210
|
+
.map((file) => ``)
|
|
211
|
+
.join("\n\n");
|
|
212
|
+
|
|
213
|
+
assetsContent += mediaMarkdown;
|
|
214
|
+
}
|
|
215
|
+
|
|
167
216
|
// Count words and lines in allSources
|
|
168
217
|
let totalWords = 0;
|
|
169
218
|
let totalLines = 0;
|
|
@@ -188,6 +237,7 @@ export default async function loadSources({
|
|
|
188
237
|
modifiedFiles,
|
|
189
238
|
totalWords,
|
|
190
239
|
totalLines,
|
|
240
|
+
assetsContent,
|
|
191
241
|
};
|
|
192
242
|
}
|
|
193
243
|
|
|
@@ -257,6 +307,10 @@ loadSources.output_schema = {
|
|
|
257
307
|
items: { type: "string" },
|
|
258
308
|
description: "Array of modified files since last generation",
|
|
259
309
|
},
|
|
310
|
+
assetsContent: {
|
|
311
|
+
type: "string",
|
|
312
|
+
description: "Markdown content for available media assets",
|
|
313
|
+
},
|
|
260
314
|
},
|
|
261
315
|
};
|
|
262
316
|
|
package/agents/publish-docs.mjs
CHANGED
|
@@ -98,6 +98,8 @@ export default async function publishDocs(
|
|
|
98
98
|
boardName: projectInfo.name,
|
|
99
99
|
boardDesc: projectInfo.description,
|
|
100
100
|
boardCover: projectInfo.icon,
|
|
101
|
+
mediaFolder: docsDir,
|
|
102
|
+
cacheFilePath: join(".aigne", "doc-smith", "upload-cache.yaml"),
|
|
101
103
|
boardMeta,
|
|
102
104
|
});
|
|
103
105
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aigne/doc-smith",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
"author": "Arcblock <blocklet@arcblock.io> https://github.com/blocklet",
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@aigne/aigne-hub": "^0.6.
|
|
16
|
-
"@aigne/anthropic": "^0.11.
|
|
17
|
-
"@aigne/cli": "^1.41.
|
|
18
|
-
"@aigne/core": "^1.55.
|
|
19
|
-
"@aigne/gemini": "^0.9.
|
|
20
|
-
"@aigne/openai": "^0.12.
|
|
21
|
-
"@aigne/publish-docs": "^0.
|
|
15
|
+
"@aigne/aigne-hub": "^0.6.10",
|
|
16
|
+
"@aigne/anthropic": "^0.11.10",
|
|
17
|
+
"@aigne/cli": "^1.41.1",
|
|
18
|
+
"@aigne/core": "^1.55.1",
|
|
19
|
+
"@aigne/gemini": "^0.9.10",
|
|
20
|
+
"@aigne/openai": "^0.12.4",
|
|
21
|
+
"@aigne/publish-docs": "^0.6.0",
|
|
22
22
|
"chalk": "^5.5.0",
|
|
23
23
|
"dompurify": "^3.2.6",
|
|
24
24
|
"glob": "^11.0.3",
|
|
@@ -16,6 +16,11 @@
|
|
|
16
16
|
{{ detailDataSources }}
|
|
17
17
|
|
|
18
18
|
{{ additionalInformation }}
|
|
19
|
+
|
|
20
|
+
<media_list>
|
|
21
|
+
{{ assetsContent }}
|
|
22
|
+
</media_list>
|
|
23
|
+
|
|
19
24
|
</datasources>
|
|
20
25
|
|
|
21
26
|
<terms>
|
|
@@ -93,7 +98,6 @@ parentId: {{parentId}}
|
|
|
93
98
|
- 媒体资源以 markdown 格式提供,示例:
|
|
94
99
|
- 在生成结果中以 markdown 格式展示图片
|
|
95
100
|
- 根据资源描述,在上下文相关的位置,合理的展示图片,让结果展示效果更丰富
|
|
96
|
-
- 只使用完整的远程图片URL(如 https://example.com/image.jpg),禁止使用相对路径(如 ./images/photo.png 或 ../assets/logo.png),确保发布后图片能正常访问
|
|
97
101
|
|
|
98
102
|
</media_rules>
|
|
99
103
|
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
- **代码块原子性**:将每个代码块(例如 ```mermaid ... ```)视为一个**不可分割的原子单元**。必须一次性完整生成,从开始标记(```mermaid)到结束标记(```)之间的所有内容都不能省略或截断。
|
|
28
28
|
- **确保 Markdown 语法**:Markdown 格式正确,特别是表格的分隔线(例如 `|---|---|---|`),需要与表格数据列数一致。
|
|
29
29
|
- README 文件只做参考,你需要从代码中获取最新、最完整的信息
|
|
30
|
+
- 忽略详情顶部的标签信息,这是程序处理的,不需要在生成时输出
|
|
30
31
|
</document_rules>
|
|
31
32
|
|
|
32
33
|
<TONE_STYLE>
|
|
@@ -45,10 +45,59 @@ describe("checkDetailResult", () => {
|
|
|
45
45
|
expect(result.detailFeedback).toContain("dead link");
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
-
test("should approve content with image syntax", async () => {
|
|
48
|
+
test("should approve content with external image syntax", async () => {
|
|
49
49
|
const structurePlan = [];
|
|
50
50
|
const reviewContent =
|
|
51
|
-
"This is an image .\n\nThis has proper structure.";
|
|
51
|
+
"This is an image .\n\nThis has proper structure.";
|
|
52
|
+
const result = await checkDetailResult({ structurePlan, reviewContent });
|
|
53
|
+
expect(result.isApproved).toBe(true);
|
|
54
|
+
expect(result.detailFeedback).toBe("");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("should approve content with valid local image path", async () => {
|
|
58
|
+
const structurePlan = [];
|
|
59
|
+
const reviewContent =
|
|
60
|
+
"This is a valid image .\n\nThis has proper structure.";
|
|
61
|
+
const docsDir = "/Users/lban/arcblock/code/aigne-doc-smith";
|
|
62
|
+
const result = await checkDetailResult({ structurePlan, reviewContent, docsDir });
|
|
63
|
+
expect(result.isApproved).toBe(true);
|
|
64
|
+
expect(result.detailFeedback).toBe("");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("should reject content with invalid local image path", async () => {
|
|
68
|
+
const structurePlan = [];
|
|
69
|
+
const reviewContent =
|
|
70
|
+
"This is an invalid image .\n\nThis has proper structure.";
|
|
71
|
+
const docsDir = "/Users/lban/arcblock/code/aigne-doc-smith";
|
|
72
|
+
const result = await checkDetailResult({ structurePlan, reviewContent, docsDir });
|
|
73
|
+
expect(result.isApproved).toBe(false);
|
|
74
|
+
expect(result.detailFeedback).toContain("Found invalid local image");
|
|
75
|
+
expect(result.detailFeedback).toContain("only valid media resources can be used");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("should approve content with absolute image path that exists", async () => {
|
|
79
|
+
const structurePlan = [];
|
|
80
|
+
const reviewContent =
|
|
81
|
+
"This is an absolute image .\n\nThis has proper structure.";
|
|
82
|
+
const result = await checkDetailResult({ structurePlan, reviewContent });
|
|
83
|
+
expect(result.isApproved).toBe(true);
|
|
84
|
+
expect(result.detailFeedback).toBe("");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("should reject content with absolute image path that doesn't exist", async () => {
|
|
88
|
+
const structurePlan = [];
|
|
89
|
+
const reviewContent =
|
|
90
|
+
"This is an invalid absolute image .\n\nThis has proper structure.";
|
|
91
|
+
const result = await checkDetailResult({ structurePlan, reviewContent });
|
|
92
|
+
expect(result.isApproved).toBe(false);
|
|
93
|
+
expect(result.detailFeedback).toContain("Found invalid local image");
|
|
94
|
+
expect(result.detailFeedback).toContain("only valid media resources can be used");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("should approve content with external image URL", async () => {
|
|
98
|
+
const structurePlan = [];
|
|
99
|
+
const reviewContent =
|
|
100
|
+
"This is an external image .\n\nThis has proper structure.";
|
|
52
101
|
const result = await checkDetailResult({ structurePlan, reviewContent });
|
|
53
102
|
expect(result.isApproved).toBe(true);
|
|
54
103
|
expect(result.detailFeedback).toBe("");
|
package/utils/auth-utils.mjs
CHANGED
|
@@ -91,7 +91,7 @@ export async function getAccessToken(appUrl) {
|
|
|
91
91
|
source: `AIGNE DocSmith connect to Discuss Kit`,
|
|
92
92
|
closeOnSuccess: true,
|
|
93
93
|
appName: "AIGNE DocSmith",
|
|
94
|
-
appLogo: "https://
|
|
94
|
+
appLogo: "https://docsmith.aigne.io/image-bin/uploads/a7910a71364ee15a27e86f869ad59009.svg",
|
|
95
95
|
openPage: (pageUrl) => open(pageUrl),
|
|
96
96
|
});
|
|
97
97
|
|
package/utils/constants.mjs
CHANGED
|
@@ -86,15 +86,27 @@ export const DEFAULT_INCLUDE_PATTERNS = [
|
|
|
86
86
|
"*.yml",
|
|
87
87
|
"*Dockerfile",
|
|
88
88
|
"*Makefile",
|
|
89
|
+
// Media files
|
|
90
|
+
"*.jpg",
|
|
91
|
+
"*.jpeg",
|
|
92
|
+
"*.png",
|
|
93
|
+
"*.gif",
|
|
94
|
+
"*.bmp",
|
|
95
|
+
"*.webp",
|
|
96
|
+
"*.svg",
|
|
97
|
+
"*.mp4",
|
|
98
|
+
"*.mov",
|
|
99
|
+
"*.avi",
|
|
100
|
+
"*.mkv",
|
|
101
|
+
"*.webm",
|
|
102
|
+
"*.m4v",
|
|
89
103
|
];
|
|
90
104
|
|
|
91
105
|
export const DEFAULT_EXCLUDE_PATTERNS = [
|
|
92
106
|
"**/aigne-docs/**",
|
|
93
107
|
"**/doc-smith/**",
|
|
94
108
|
"**/.aigne/**",
|
|
95
|
-
"**/assets/**",
|
|
96
109
|
"**/data/**",
|
|
97
|
-
"**/images/**",
|
|
98
110
|
"**/public/**",
|
|
99
111
|
"**/static/**",
|
|
100
112
|
"**/vendor/**",
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
1
3
|
import remarkGfm from "remark-gfm";
|
|
2
4
|
import remarkLint from "remark-lint";
|
|
3
5
|
import remarkParse from "remark-parse";
|
|
@@ -159,6 +161,72 @@ function checkCodeBlockIndentation(codeBlockContent, codeBlockIndent, source, er
|
|
|
159
161
|
}
|
|
160
162
|
}
|
|
161
163
|
|
|
164
|
+
/**
|
|
165
|
+
* Check for local images and verify their existence
|
|
166
|
+
* @param {string} markdown - The markdown content
|
|
167
|
+
* @param {string} source - Source description for error reporting
|
|
168
|
+
* @param {Array} errorMessages - Array to push error messages to
|
|
169
|
+
* @param {string} [markdownFilePath] - Path to the markdown file for resolving relative paths
|
|
170
|
+
* @param {string} [baseDir] - Base directory for resolving relative paths (alternative to markdownFilePath)
|
|
171
|
+
*/
|
|
172
|
+
function checkLocalImages(markdown, source, errorMessages, markdownFilePath, baseDir) {
|
|
173
|
+
const imageRegex = /!\[([^\]]*)\]\(([^)]+)\)/g;
|
|
174
|
+
let match;
|
|
175
|
+
|
|
176
|
+
while ((match = imageRegex.exec(markdown)) !== null) {
|
|
177
|
+
const imagePath = match[2].trim();
|
|
178
|
+
const altText = match[1];
|
|
179
|
+
|
|
180
|
+
// Skip external URLs (http/https)
|
|
181
|
+
if (/^https?:\/\//.test(imagePath)) continue;
|
|
182
|
+
|
|
183
|
+
// Skip data URLs
|
|
184
|
+
if (/^data:/.test(imagePath)) continue;
|
|
185
|
+
|
|
186
|
+
// Check if it's a local path
|
|
187
|
+
if (!imagePath.startsWith("/") && !imagePath.includes("://")) {
|
|
188
|
+
// It's a relative local path, check if file exists
|
|
189
|
+
try {
|
|
190
|
+
let resolvedPath;
|
|
191
|
+
if (markdownFilePath) {
|
|
192
|
+
// Resolve relative to the markdown file's directory
|
|
193
|
+
const markdownDir = path.dirname(markdownFilePath);
|
|
194
|
+
resolvedPath = path.resolve(markdownDir, imagePath);
|
|
195
|
+
} else if (baseDir) {
|
|
196
|
+
// Resolve relative to the provided base directory
|
|
197
|
+
resolvedPath = path.resolve(baseDir, imagePath);
|
|
198
|
+
} else {
|
|
199
|
+
// Fallback to current working directory
|
|
200
|
+
resolvedPath = path.resolve(imagePath);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
204
|
+
errorMessages.push(
|
|
205
|
+
`Found invalid local image in ${source}:  - only valid media resources can be used`,
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
} catch {
|
|
209
|
+
errorMessages.push(
|
|
210
|
+
`Found invalid local image in ${source}:  - only valid media resources can be used`,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
} else if (imagePath.startsWith("/")) {
|
|
214
|
+
// Absolute local path
|
|
215
|
+
try {
|
|
216
|
+
if (!fs.existsSync(imagePath)) {
|
|
217
|
+
errorMessages.push(
|
|
218
|
+
`Found invalid local image in ${source}:  - only valid media resources can be used`,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
} catch {
|
|
222
|
+
errorMessages.push(
|
|
223
|
+
`Found invalid local image in ${source}:  - only valid media resources can be used`,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
162
230
|
/**
|
|
163
231
|
* Check content structure and formatting issues
|
|
164
232
|
* @param {string} markdown - The markdown content
|
|
@@ -224,7 +292,7 @@ function checkContentStructure(markdown, source, errorMessages) {
|
|
|
224
292
|
}
|
|
225
293
|
|
|
226
294
|
// Check if content ends with proper punctuation (indicating completeness)
|
|
227
|
-
const validEndingPunctuation = [".", "。", ")", "|"];
|
|
295
|
+
const validEndingPunctuation = [".", "。", ")", "|", "*"];
|
|
228
296
|
const trimmedText = markdown.trim();
|
|
229
297
|
const hasValidEnding = validEndingPunctuation.some((punct) => trimmedText.endsWith(punct));
|
|
230
298
|
|
|
@@ -241,6 +309,8 @@ function checkContentStructure(markdown, source, errorMessages) {
|
|
|
241
309
|
* @param {string} [source] - Source description for error reporting (e.g., "result")
|
|
242
310
|
* @param {Object} [options] - Additional options for validation
|
|
243
311
|
* @param {Array} [options.allowedLinks] - Set of allowed links for link validation
|
|
312
|
+
* @param {string} [options.filePath] - Path to the markdown file for resolving relative image paths
|
|
313
|
+
* @param {string} [options.baseDir] - Base directory for resolving relative image paths (alternative to filePath)
|
|
244
314
|
* @returns {Promise<Array<string>>} - Array of error messages in check-detail-result format
|
|
245
315
|
*/
|
|
246
316
|
export async function checkMarkdown(markdown, source = "content", options = {}) {
|
|
@@ -248,8 +318,8 @@ export async function checkMarkdown(markdown, source = "content", options = {})
|
|
|
248
318
|
const errorMessages = [];
|
|
249
319
|
|
|
250
320
|
try {
|
|
251
|
-
// Extract allowed links from options
|
|
252
|
-
const { allowedLinks } = options;
|
|
321
|
+
// Extract allowed links, file path, and base directory from options
|
|
322
|
+
const { allowedLinks, filePath, baseDir } = options;
|
|
253
323
|
|
|
254
324
|
// Create unified processor with markdown parsing and linting
|
|
255
325
|
// Use individual rules instead of presets to have better control
|
|
@@ -292,7 +362,10 @@ export async function checkMarkdown(markdown, source = "content", options = {})
|
|
|
292
362
|
checkDeadLinks(markdown, source, allowedLinks, errorMessages);
|
|
293
363
|
}
|
|
294
364
|
|
|
295
|
-
// 2. Check
|
|
365
|
+
// 2. Check local images existence
|
|
366
|
+
checkLocalImages(markdown, source, errorMessages, filePath, baseDir);
|
|
367
|
+
|
|
368
|
+
// 3. Check content structure and formatting issues
|
|
296
369
|
checkContentStructure(markdown, source, errorMessages);
|
|
297
370
|
|
|
298
371
|
// Check mermaid code blocks and other custom validations
|
|
@@ -319,30 +392,35 @@ export async function checkMarkdown(markdown, source = "content", options = {})
|
|
|
319
392
|
// Check for backticks in node labels
|
|
320
393
|
const nodeLabelRegex = /[A-Za-z0-9_]+\["([^"]*`[^"]*)"\]|[A-Za-z0-9_]+{"([^}]*`[^}]*)"}/g;
|
|
321
394
|
let match;
|
|
322
|
-
|
|
395
|
+
match = nodeLabelRegex.exec(mermaidContent);
|
|
396
|
+
while (match !== null) {
|
|
323
397
|
const label = match[1] || match[2];
|
|
324
398
|
errorMessages.push(
|
|
325
399
|
`Found backticks in Mermaid node label in ${source} at line ${line}: "${label}" - backticks in node labels cause rendering issues in Mermaid diagrams`,
|
|
326
400
|
);
|
|
401
|
+
match = nodeLabelRegex.exec(mermaidContent);
|
|
327
402
|
}
|
|
328
403
|
|
|
329
404
|
// Check for numbered list format in edge descriptions
|
|
330
405
|
const edgeDescriptionRegex = /--\s*"([^"]*)"\s*-->/g;
|
|
331
406
|
let edgeMatch;
|
|
332
|
-
|
|
407
|
+
edgeMatch = edgeDescriptionRegex.exec(mermaidContent);
|
|
408
|
+
while (edgeMatch !== null) {
|
|
333
409
|
const description = edgeMatch[1];
|
|
334
410
|
if (/^\d+\.\s/.test(description)) {
|
|
335
411
|
errorMessages.push(
|
|
336
412
|
`Unsupported markdown: list - Found numbered list format in Mermaid edge description in ${source} at line ${line}: "${description}" - numbered lists in edge descriptions are not supported`,
|
|
337
413
|
);
|
|
338
414
|
}
|
|
415
|
+
edgeMatch = edgeDescriptionRegex.exec(mermaidContent);
|
|
339
416
|
}
|
|
340
417
|
|
|
341
418
|
// Check for numbered list format in node labels (for both [] and {} syntax)
|
|
342
419
|
const nodeLabelWithNumberRegex =
|
|
343
420
|
/[A-Za-z0-9_]+\["([^"]*\d+\.\s[^"]*)"\]|[A-Za-z0-9_]+{"([^}]*\d+\.\s[^}]*)"}/g;
|
|
344
421
|
let numberMatch;
|
|
345
|
-
|
|
422
|
+
numberMatch = nodeLabelWithNumberRegex.exec(mermaidContent);
|
|
423
|
+
while (numberMatch !== null) {
|
|
346
424
|
const label = numberMatch[1] || numberMatch[2];
|
|
347
425
|
// Check if the label contains numbered list format
|
|
348
426
|
if (/\d+\.\s/.test(label)) {
|
|
@@ -350,12 +428,14 @@ export async function checkMarkdown(markdown, source = "content", options = {})
|
|
|
350
428
|
`Unsupported markdown: list - Found numbered list format in Mermaid node label in ${source} at line ${line}: "${label}" - numbered lists in node labels cause rendering issues`,
|
|
351
429
|
);
|
|
352
430
|
}
|
|
431
|
+
numberMatch = nodeLabelWithNumberRegex.exec(mermaidContent);
|
|
353
432
|
}
|
|
354
433
|
|
|
355
434
|
// Check for special characters in node labels that should be quoted
|
|
356
435
|
const nodeWithSpecialCharsRegex = /([A-Za-z0-9_]+)\[([^\]]*[(){}:;,\-\s.][^\]]*)\]/g;
|
|
357
436
|
let specialCharMatch;
|
|
358
|
-
|
|
437
|
+
specialCharMatch = nodeWithSpecialCharsRegex.exec(mermaidContent);
|
|
438
|
+
while (specialCharMatch !== null) {
|
|
359
439
|
const nodeId = specialCharMatch[1];
|
|
360
440
|
const label = specialCharMatch[2];
|
|
361
441
|
|
|
@@ -373,6 +453,7 @@ export async function checkMarkdown(markdown, source = "content", options = {})
|
|
|
373
453
|
);
|
|
374
454
|
}
|
|
375
455
|
}
|
|
456
|
+
specialCharMatch = nodeWithSpecialCharsRegex.exec(mermaidContent);
|
|
376
457
|
}
|
|
377
458
|
}
|
|
378
459
|
});
|
|
@@ -431,7 +512,6 @@ export async function checkMarkdown(markdown, source = "content", options = {})
|
|
|
431
512
|
// Format messages in check-detail-result style
|
|
432
513
|
file.messages.forEach((message) => {
|
|
433
514
|
const line = message.line || "unknown";
|
|
434
|
-
const _column = message.column || "unknown";
|
|
435
515
|
const reason = message.reason || "Unknown markdown issue";
|
|
436
516
|
const ruleId = message.ruleId || message.source || "markdown";
|
|
437
517
|
|
package/utils/utils.mjs
CHANGED
|
@@ -149,8 +149,8 @@ export function getCurrentGitHead() {
|
|
|
149
149
|
* @param {string} gitHead - The current git HEAD commit hash
|
|
150
150
|
*/
|
|
151
151
|
export async function saveGitHeadToConfig(gitHead) {
|
|
152
|
-
if (!gitHead) {
|
|
153
|
-
return; // Skip if no git HEAD available
|
|
152
|
+
if (!gitHead || process.env.NODE_ENV === 'test' || process.env.BUN_TEST) {
|
|
153
|
+
return; // Skip if no git HEAD available or in test environment
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
try {
|