@aigne/doc-smith 0.2.5 ā 0.2.6
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/README.md +1 -0
- package/agents/check-detail-result.mjs +15 -136
- package/agents/check-structure-plan.mjs +56 -7
- package/agents/detail-generator-and-translate.yaml +7 -1
- package/agents/detail-regenerator.yaml +3 -1
- package/agents/docs-generator.yaml +2 -1
- package/agents/find-item-by-path.mjs +63 -14
- package/agents/input-generator.mjs +25 -6
- package/agents/language-selector.mjs +101 -0
- package/agents/publish-docs.mjs +44 -153
- package/agents/retranslate.yaml +74 -0
- package/agents/save-docs.mjs +12 -2
- package/agents/save-single-doc.mjs +19 -0
- package/agents/structure-planning.yaml +6 -0
- package/agents/translate.yaml +3 -0
- package/aigne.yaml +5 -1
- package/package.json +16 -7
- package/prompts/check-structure-planning-result.md +4 -7
- package/prompts/content-detail-generator.md +1 -2
- package/prompts/structure-planning.md +7 -2
- package/prompts/translator.md +4 -0
- package/tests/test-all-validation-cases.mjs +707 -0
- package/utils/markdown-checker.mjs +386 -0
- package/utils/mermaid-validator.mjs +158 -0
- package/utils/mermaid-worker-pool.mjs +254 -0
- package/utils/mermaid-worker.mjs +242 -0
- package/utils/utils.mjs +119 -19
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.6](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.2.5...v0.2.6) (2025-08-12)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Miscellaneous Chores
|
|
7
|
+
|
|
8
|
+
* release 0.2.6 ([c5b5ea5](https://github.com/AIGNE-io/aigne-doc-smith/commit/c5b5ea5c404d44f3b0d420f0b57e4ae64ae5d624))
|
|
9
|
+
|
|
3
10
|
## [0.2.5](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.2.4...v0.2.5) (2025-08-08)
|
|
4
11
|
|
|
5
12
|
|
package/README.md
CHANGED
|
@@ -206,6 +206,7 @@ aigne doc publish --appUrl https://your-discuss-kit-instance.com
|
|
|
206
206
|
npx --no doc-smith run --entry-agent init
|
|
207
207
|
npx --no doc-smith run --entry-agent generate
|
|
208
208
|
npx --no doc-smith run --entry-agent update
|
|
209
|
+
npx --no doc-smith run --entry-agent retranslate
|
|
209
210
|
npx --no doc-smith run --entry-agent publish
|
|
210
211
|
```
|
|
211
212
|
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
+
import { checkMarkdown } from "../utils/markdown-checker.mjs";
|
|
2
|
+
|
|
1
3
|
export default async function checkDetailResult({
|
|
2
4
|
structurePlan,
|
|
3
5
|
reviewContent,
|
|
4
6
|
}) {
|
|
5
|
-
const linkRegex = /(?<!\!)\[([^\]]+)\]\(([^)]+)\)/g;
|
|
6
|
-
const tableSeparatorRegex = /^\s*\|\s*-+\s*\|\s*$/;
|
|
7
|
-
const codeBlockRegex = /^\s+```(?:\w+)?$/;
|
|
8
|
-
|
|
9
7
|
let isApproved = true;
|
|
10
8
|
const detailFeedback = [];
|
|
11
9
|
|
|
@@ -25,141 +23,22 @@ export default async function checkDetailResult({
|
|
|
25
23
|
allowedLinks.add(flatPath);
|
|
26
24
|
});
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// Only check links that processContent would process
|
|
35
|
-
// Exclude external links and mailto
|
|
36
|
-
if (/^(https?:\/\/|mailto:)/.test(trimLink)) continue;
|
|
37
|
-
|
|
38
|
-
// Preserve anchors
|
|
39
|
-
const [path, hash] = trimLink.split("#");
|
|
40
|
-
|
|
41
|
-
// Only process relative paths or paths starting with /
|
|
42
|
-
if (!path) continue;
|
|
43
|
-
|
|
44
|
-
// Check if this link is in the allowed links set
|
|
45
|
-
if (!allowedLinks.has(trimLink)) {
|
|
46
|
-
isApproved = false;
|
|
47
|
-
detailFeedback.push(
|
|
48
|
-
`Found a dead link in ${source}: [${match[1]}](${trimLink}), ensure the link exists in the structure plan path`
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
const performAllChecks = (text, source) => {
|
|
55
|
-
// Split text into lines once and perform all checks in a single pass
|
|
56
|
-
const lines = text.split("\n");
|
|
57
|
-
|
|
58
|
-
// State variables for different checks
|
|
59
|
-
let inCodeBlock = false;
|
|
60
|
-
let codeBlockIndentLevel = 0;
|
|
61
|
-
let codeBlockStartLine = 0;
|
|
62
|
-
let inMermaidBlock = false;
|
|
63
|
-
let mermaidStartLine = 0;
|
|
64
|
-
|
|
65
|
-
for (let i = 0; i < lines.length; i++) {
|
|
66
|
-
const line = lines[i];
|
|
67
|
-
const lineNumber = i + 1;
|
|
26
|
+
// Run comprehensive markdown validation with all checks
|
|
27
|
+
try {
|
|
28
|
+
const markdownErrors = await checkMarkdown(reviewContent, "result", {
|
|
29
|
+
allowedLinks,
|
|
30
|
+
});
|
|
68
31
|
|
|
69
|
-
|
|
70
|
-
if (tableSeparatorRegex.test(line)) {
|
|
71
|
-
isApproved = false;
|
|
72
|
-
detailFeedback.push(
|
|
73
|
-
`Found an incorrect table separator in ${source} at line ${lineNumber}: ${line.trim()}`
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Check code block markers and indentation
|
|
78
|
-
if (codeBlockRegex.test(line)) {
|
|
79
|
-
if (!inCodeBlock) {
|
|
80
|
-
// Starting a new code block
|
|
81
|
-
inCodeBlock = true;
|
|
82
|
-
codeBlockStartLine = lineNumber;
|
|
83
|
-
// Calculate indentation level of the code block marker
|
|
84
|
-
const match = line.match(/^(\s*)(```)/);
|
|
85
|
-
codeBlockIndentLevel = match ? match[1].length : 0;
|
|
86
|
-
} else {
|
|
87
|
-
// Ending the code block
|
|
88
|
-
inCodeBlock = false;
|
|
89
|
-
codeBlockIndentLevel = 0;
|
|
90
|
-
}
|
|
91
|
-
} else if (inCodeBlock) {
|
|
92
|
-
// If we're inside a code block, check if content has proper indentation
|
|
93
|
-
const contentIndentLevel = line.match(/^(\s*)/)[1].length;
|
|
94
|
-
|
|
95
|
-
// If code block marker has indentation, content should have at least the same indentation
|
|
96
|
-
if (
|
|
97
|
-
codeBlockIndentLevel > 0 &&
|
|
98
|
-
contentIndentLevel < codeBlockIndentLevel
|
|
99
|
-
) {
|
|
100
|
-
isApproved = false;
|
|
101
|
-
detailFeedback.push(
|
|
102
|
-
`Found code block with inconsistent indentation in ${source} at line ${codeBlockStartLine}: code block marker has ${codeBlockIndentLevel} spaces indentation but content at line ${lineNumber} has only ${contentIndentLevel} spaces indentation`
|
|
103
|
-
);
|
|
104
|
-
// Reset to avoid multiple errors for the same code block
|
|
105
|
-
inCodeBlock = false;
|
|
106
|
-
codeBlockIndentLevel = 0;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Check mermaid block markers
|
|
111
|
-
if (/^\s*```mermaid\s*$/.test(line)) {
|
|
112
|
-
inMermaidBlock = true;
|
|
113
|
-
mermaidStartLine = lineNumber;
|
|
114
|
-
} else if (inMermaidBlock && /^\s*```\s*$/.test(line)) {
|
|
115
|
-
inMermaidBlock = false;
|
|
116
|
-
} else if (inMermaidBlock) {
|
|
117
|
-
// If we're inside a mermaid block, check for backticks in node labels
|
|
118
|
-
// Check for node definitions with backticks in labels
|
|
119
|
-
// Pattern: A["label with backticks"] or A{"label with backticks"}
|
|
120
|
-
const nodeLabelRegex =
|
|
121
|
-
/[A-Za-z0-9_]+\["([^"]*`[^"]*)"\]|[A-Za-z0-9_]+{"([^}]*`[^}]*)"}/g;
|
|
122
|
-
let match;
|
|
123
|
-
|
|
124
|
-
while ((match = nodeLabelRegex.exec(line)) !== null) {
|
|
125
|
-
const label = match[1] || match[2];
|
|
126
|
-
isApproved = false;
|
|
127
|
-
detailFeedback.push(
|
|
128
|
-
`Found backticks in Mermaid node label in ${source} at line ${lineNumber}: "${label}" - backticks in node labels cause rendering issues in Mermaid diagrams`
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Check for edge descriptions with numbered list format
|
|
133
|
-
// Pattern: -- "1. description" --> or similar variants
|
|
134
|
-
const edgeDescriptionRegex = /--\s*"([^"]*)"\s*-->/g;
|
|
135
|
-
let edgeMatch;
|
|
136
|
-
|
|
137
|
-
while ((edgeMatch = edgeDescriptionRegex.exec(line)) !== null) {
|
|
138
|
-
const description = edgeMatch[1];
|
|
139
|
-
// Check if description starts with number followed by period
|
|
140
|
-
if (/^\d+\.\s/.test(description)) {
|
|
141
|
-
isApproved = false;
|
|
142
|
-
detailFeedback.push(
|
|
143
|
-
`Unsupported markdown: list - Found numbered list format in Mermaid edge description in ${source} at line ${lineNumber}: "${description}" - numbered lists in edge descriptions are not supported`
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Check single line content (this needs to be done after the loop)
|
|
151
|
-
const newlineCount = (text.match(/\n/g) || []).length;
|
|
152
|
-
if (newlineCount === 0 && text.trim().length > 0) {
|
|
32
|
+
if (markdownErrors.length > 0) {
|
|
153
33
|
isApproved = false;
|
|
154
|
-
detailFeedback.push(
|
|
155
|
-
`Found single line content in ${source}: content appears to be on only one line, check for missing line breaks`
|
|
156
|
-
);
|
|
34
|
+
detailFeedback.push(...markdownErrors);
|
|
157
35
|
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
36
|
+
} catch (error) {
|
|
37
|
+
isApproved = false;
|
|
38
|
+
detailFeedback.push(
|
|
39
|
+
`Found markdown validation error in result: ${error.message}`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
163
42
|
|
|
164
43
|
return {
|
|
165
44
|
isApproved,
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getCurrentGitHead,
|
|
3
3
|
hasFileChangesBetweenCommits,
|
|
4
|
+
loadConfigFromFile,
|
|
5
|
+
saveValueToConfig,
|
|
6
|
+
getProjectInfo,
|
|
4
7
|
} from "../utils/utils.mjs";
|
|
5
8
|
|
|
6
9
|
export default async function checkStructurePlan(
|
|
7
|
-
{ originalStructurePlan, feedback, lastGitHead,
|
|
10
|
+
{ originalStructurePlan, feedback, lastGitHead, ...rest },
|
|
8
11
|
options
|
|
9
12
|
) {
|
|
10
13
|
// Check if we need to regenerate structure plan
|
|
@@ -38,17 +41,13 @@ export default async function checkStructurePlan(
|
|
|
38
41
|
1. 对äŗę°å¢ēå
容ļ¼åÆä»„ę ¹ę®éč¦ę°å¢čē¹ļ¼ę蔄å
å°åęčē¹å±ē¤ŗ
|
|
39
42
|
2. č°Øę
å é¤čē¹ļ¼é¤éčē¹å
³č sourceIds é½č¢«å é¤äŗ
|
|
40
43
|
3. äøč½äæ®ę¹åęčē¹ē path
|
|
44
|
+
4. ę ¹ę®ęę°ē Data Sources ęéč¦ę“ę°čē¹ē sourceIdsļ¼å¦ę²”ę大ēååļ¼åÆä»„äøę“ę°ć
|
|
41
45
|
`;
|
|
42
46
|
}
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
// If no regeneration needed, return original structure plan
|
|
46
|
-
if (
|
|
47
|
-
originalStructurePlan &&
|
|
48
|
-
!feedback &&
|
|
49
|
-
!shouldRegenerate &&
|
|
50
|
-
!forceRegenerate
|
|
51
|
-
) {
|
|
50
|
+
if (originalStructurePlan && !feedback && !shouldRegenerate) {
|
|
52
51
|
return {
|
|
53
52
|
structurePlan: originalStructurePlan,
|
|
54
53
|
};
|
|
@@ -62,9 +61,59 @@ export default async function checkStructurePlan(
|
|
|
62
61
|
...rest,
|
|
63
62
|
});
|
|
64
63
|
|
|
64
|
+
let message = "";
|
|
65
|
+
|
|
66
|
+
// Check and save project information if user hasn't modified it
|
|
67
|
+
if (result.projectName || result.projectDesc) {
|
|
68
|
+
try {
|
|
69
|
+
const currentConfig = await loadConfigFromFile();
|
|
70
|
+
const projectInfo = await getProjectInfo();
|
|
71
|
+
|
|
72
|
+
// Check if user has modified project information
|
|
73
|
+
const userModifiedProjectName =
|
|
74
|
+
currentConfig?.projectName &&
|
|
75
|
+
currentConfig.projectName !== projectInfo.name;
|
|
76
|
+
const userModifiedProjectDesc =
|
|
77
|
+
currentConfig?.projectDesc &&
|
|
78
|
+
currentConfig.projectDesc !== projectInfo.description;
|
|
79
|
+
|
|
80
|
+
// If user hasn't modified project info and it's not from GitHub, save AI output
|
|
81
|
+
if (!userModifiedProjectName && !userModifiedProjectDesc) {
|
|
82
|
+
let hasUpdated = false;
|
|
83
|
+
// Don't update if the current info is from GitHub (meaningful repository info)
|
|
84
|
+
if (
|
|
85
|
+
result.projectName &&
|
|
86
|
+
result.projectName !== projectInfo.name &&
|
|
87
|
+
!projectInfo.fromGitHub
|
|
88
|
+
) {
|
|
89
|
+
await saveValueToConfig("projectName", result.projectName);
|
|
90
|
+
message += `Project name: \`${result.projectName}\``;
|
|
91
|
+
hasUpdated = true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (
|
|
95
|
+
result.projectDesc &&
|
|
96
|
+
result.projectDesc !== projectInfo.description &&
|
|
97
|
+
!projectInfo.fromGitHub
|
|
98
|
+
) {
|
|
99
|
+
await saveValueToConfig("projectDesc", result.projectDesc);
|
|
100
|
+
message += `\nProject description: \`${result.projectDesc}\``;
|
|
101
|
+
hasUpdated = true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (hasUpdated) {
|
|
105
|
+
message = `\n### Auto-updated Project Info to \`.aigne/doc-smith/config.yaml\`\n\n${message}\n\n`;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.warn("Failed to check/save project information:", error.message);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
65
113
|
return {
|
|
66
114
|
...result,
|
|
67
115
|
feedback: "", // clear feedback
|
|
116
|
+
projectInfoMessage: message,
|
|
68
117
|
originalStructurePlan: originalStructurePlan
|
|
69
118
|
? originalStructurePlan
|
|
70
119
|
: JSON.parse(JSON.stringify(result.structurePlan || [])),
|
|
@@ -15,9 +15,15 @@ skills:
|
|
|
15
15
|
reflection:
|
|
16
16
|
reviewer: ./check-detail-result.mjs
|
|
17
17
|
is_approved: isApproved
|
|
18
|
-
max_iterations:
|
|
18
|
+
max_iterations: 5
|
|
19
19
|
return_last_on_max_iterations: true
|
|
20
20
|
task_title: Generate detail for '{{ title }}'
|
|
21
|
+
- type: transform
|
|
22
|
+
jsonata: |
|
|
23
|
+
$merge([
|
|
24
|
+
$,
|
|
25
|
+
{ "feedback": "" }
|
|
26
|
+
])
|
|
21
27
|
- ./batch-translate.yaml
|
|
22
28
|
- ./save-single-doc.mjs
|
|
23
29
|
input_schema:
|
|
@@ -1,11 +1,25 @@
|
|
|
1
|
-
import { readdir } from "node:fs/promises";
|
|
1
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
|
|
4
|
+
// Helper function to get action-specific text based on isTranslate flag
|
|
5
|
+
function getActionText(isTranslate, baseText) {
|
|
6
|
+
const action = isTranslate ? "retranslate" : "update";
|
|
7
|
+
return baseText.replace("{action}", action);
|
|
8
|
+
}
|
|
9
|
+
|
|
4
10
|
export default async function findItemByPath(
|
|
5
|
-
{
|
|
11
|
+
{
|
|
12
|
+
"doc-path": docPath,
|
|
13
|
+
structurePlanResult,
|
|
14
|
+
boardId,
|
|
15
|
+
docsDir,
|
|
16
|
+
isTranslate,
|
|
17
|
+
feedback,
|
|
18
|
+
},
|
|
6
19
|
options
|
|
7
20
|
) {
|
|
8
21
|
let foundItem = null;
|
|
22
|
+
let selectedFileContent = null;
|
|
9
23
|
|
|
10
24
|
// If docPath is empty, let user select from available documents
|
|
11
25
|
if (!docPath) {
|
|
@@ -22,14 +36,12 @@ export default async function findItemByPath(
|
|
|
22
36
|
);
|
|
23
37
|
|
|
24
38
|
if (mainLanguageFiles.length === 0) {
|
|
25
|
-
throw new Error(
|
|
26
|
-
"Please provide a doc-path parameter to specify which document to update"
|
|
27
|
-
);
|
|
39
|
+
throw new Error("No documents found in the docs directory");
|
|
28
40
|
}
|
|
29
41
|
|
|
30
42
|
// Let user select a file
|
|
31
43
|
const selectedFile = await options.prompts.search({
|
|
32
|
-
message: "Select a document to
|
|
44
|
+
message: getActionText(isTranslate, "Select a document to {action}:"),
|
|
33
45
|
source: async (input, { signal }) => {
|
|
34
46
|
if (!input || input.trim() === "") {
|
|
35
47
|
return mainLanguageFiles.map((file) => ({
|
|
@@ -51,9 +63,19 @@ export default async function findItemByPath(
|
|
|
51
63
|
});
|
|
52
64
|
|
|
53
65
|
if (!selectedFile) {
|
|
54
|
-
throw new Error(
|
|
55
|
-
|
|
66
|
+
throw new Error("No document selected");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Read the selected .md file content
|
|
70
|
+
try {
|
|
71
|
+
const selectedFilePath = join(docsDir, selectedFile);
|
|
72
|
+
selectedFileContent = await readFile(selectedFilePath, "utf-8");
|
|
73
|
+
} catch (readError) {
|
|
74
|
+
console.warn(
|
|
75
|
+
`ā ļø Could not read content from ${selectedFile}:`,
|
|
76
|
+
readError.message
|
|
56
77
|
);
|
|
78
|
+
selectedFileContent = null;
|
|
57
79
|
}
|
|
58
80
|
|
|
59
81
|
// Convert filename back to path
|
|
@@ -71,15 +93,17 @@ export default async function findItemByPath(
|
|
|
71
93
|
return itemFlattenedPath === flatName;
|
|
72
94
|
});
|
|
73
95
|
if (!foundItemByFile) {
|
|
74
|
-
throw new Error(
|
|
75
|
-
"Please provide a doc-path parameter to specify which document to update"
|
|
76
|
-
);
|
|
96
|
+
throw new Error("No document found");
|
|
77
97
|
}
|
|
78
98
|
|
|
79
99
|
docPath = foundItemByFile.path;
|
|
80
100
|
} catch (error) {
|
|
101
|
+
console.error(error);
|
|
81
102
|
throw new Error(
|
|
82
|
-
|
|
103
|
+
getActionText(
|
|
104
|
+
isTranslate,
|
|
105
|
+
"Please provide a doc-path parameter to specify which document to {action}"
|
|
106
|
+
)
|
|
83
107
|
);
|
|
84
108
|
}
|
|
85
109
|
}
|
|
@@ -111,8 +135,33 @@ export default async function findItemByPath(
|
|
|
111
135
|
);
|
|
112
136
|
}
|
|
113
137
|
|
|
114
|
-
//
|
|
115
|
-
|
|
138
|
+
// Prompt for feedback if not provided
|
|
139
|
+
let userFeedback = feedback;
|
|
140
|
+
if (!userFeedback) {
|
|
141
|
+
const feedbackMessage = getActionText(
|
|
142
|
+
isTranslate,
|
|
143
|
+
"Please provide feedback for the {action} (press Enter to skip):"
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
userFeedback = await options.prompts.input({
|
|
147
|
+
message: feedbackMessage,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Merge the found item with originalStructurePlan and add content if available
|
|
152
|
+
const result = {
|
|
116
153
|
...foundItem,
|
|
117
154
|
};
|
|
155
|
+
|
|
156
|
+
// Add content if we read it from user selection
|
|
157
|
+
if (selectedFileContent !== null) {
|
|
158
|
+
result.content = selectedFileContent;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Add feedback to result if provided
|
|
162
|
+
if (userFeedback && userFeedback.trim()) {
|
|
163
|
+
result.feedback = userFeedback.trim();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return result;
|
|
118
167
|
}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { writeFile, mkdir, readFile } from "node:fs/promises";
|
|
2
2
|
import { join, dirname } from "node:path";
|
|
3
3
|
import chalk from "chalk";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
validatePath,
|
|
6
|
+
getAvailablePaths,
|
|
7
|
+
getProjectInfo,
|
|
8
|
+
} from "../utils/utils.mjs";
|
|
5
9
|
import {
|
|
6
10
|
SUPPORTED_LANGUAGES,
|
|
7
11
|
DOCUMENT_STYLES,
|
|
8
12
|
TARGET_AUDIENCES,
|
|
9
13
|
} from "../utils/constants.mjs";
|
|
10
|
-
|
|
11
14
|
// UI constants
|
|
12
15
|
const PRESS_ENTER_TO_FINISH = "Press Enter to finish";
|
|
13
16
|
|
|
@@ -180,6 +183,12 @@ export default async function init(
|
|
|
180
183
|
// If no paths entered, use default
|
|
181
184
|
input.sourcesPath = sourcePaths.length > 0 ? sourcePaths : ["./"];
|
|
182
185
|
|
|
186
|
+
// Save project info to config
|
|
187
|
+
const projectInfo = await getProjectInfo();
|
|
188
|
+
input.projectName = projectInfo.name;
|
|
189
|
+
input.projectDesc = projectInfo.description;
|
|
190
|
+
input.projectLogo = projectInfo.icon;
|
|
191
|
+
|
|
183
192
|
// Generate YAML content
|
|
184
193
|
const yamlContent = generateYAML(input, outputPath);
|
|
185
194
|
|
|
@@ -193,13 +202,17 @@ export default async function init(
|
|
|
193
202
|
|
|
194
203
|
await writeFile(filePath, yamlContent, "utf8");
|
|
195
204
|
console.log(`\nš Configuration saved to: ${chalk.cyan(filePath)}`);
|
|
205
|
+
// Print YAML content for user review
|
|
206
|
+
console.log(chalk.cyan("---"));
|
|
207
|
+
console.log(chalk.cyan(yamlContent));
|
|
208
|
+
console.log(chalk.cyan("---"));
|
|
196
209
|
console.log(
|
|
197
|
-
"š” You can edit the configuration file anytime to modify settings
|
|
210
|
+
"š” You can edit the configuration file anytime to modify settings.\n"
|
|
198
211
|
);
|
|
199
212
|
console.log(
|
|
200
213
|
`š Run ${chalk.cyan(
|
|
201
214
|
"'aigne doc generate'"
|
|
202
|
-
)} to start documentation generation
|
|
215
|
+
)} to start documentation generation!\n`
|
|
203
216
|
);
|
|
204
217
|
|
|
205
218
|
return {};
|
|
@@ -215,12 +228,18 @@ export default async function init(
|
|
|
215
228
|
/**
|
|
216
229
|
* Generate YAML configuration content
|
|
217
230
|
* @param {Object} input - Input object
|
|
218
|
-
* @param {string} outputPath - Output path for directory configuration
|
|
219
231
|
* @returns {string} YAML string
|
|
220
232
|
*/
|
|
221
|
-
function generateYAML(input
|
|
233
|
+
function generateYAML(input) {
|
|
222
234
|
let yaml = "";
|
|
223
235
|
|
|
236
|
+
// Add project information at the beginning
|
|
237
|
+
yaml += `# Project information for documentation publishing\n`;
|
|
238
|
+
yaml += `projectName: ${input.projectName || ""}\n`;
|
|
239
|
+
yaml += `projectDesc: ${input.projectDesc || ""}\n`;
|
|
240
|
+
yaml += `projectLogo: ${input.projectLogo || ""}\n`;
|
|
241
|
+
yaml += `\n`;
|
|
242
|
+
|
|
224
243
|
// Add rules (required field)
|
|
225
244
|
yaml += `rules: |\n`;
|
|
226
245
|
if (input.rules && input.rules.trim()) {
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { SUPPORTED_LANGUAGES } from "../utils/constants.mjs";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Interactive language selector for translation from configured languages
|
|
5
|
+
* @param {Object} params
|
|
6
|
+
* @param {Array<string>} [params.languages] - Pre-selected languages
|
|
7
|
+
* @param {Array<string>} params.translateLanguages - Available languages from config
|
|
8
|
+
* @param {Object} options - Options object with prompts
|
|
9
|
+
* @returns {Promise<Object>} Selected languages
|
|
10
|
+
*/
|
|
11
|
+
export default async function languageSelector(
|
|
12
|
+
{ languages, translateLanguages },
|
|
13
|
+
options
|
|
14
|
+
) {
|
|
15
|
+
let selectedLanguages = [];
|
|
16
|
+
|
|
17
|
+
// Check if translateLanguages is available from config
|
|
18
|
+
if (
|
|
19
|
+
!translateLanguages ||
|
|
20
|
+
!Array.isArray(translateLanguages) ||
|
|
21
|
+
translateLanguages.length === 0
|
|
22
|
+
) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
"No translation languages configured in config.yaml. Please add translateLanguages to your configuration."
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// If languages are provided as parameter, validate against configured languages
|
|
29
|
+
if (languages && Array.isArray(languages) && languages.length > 0) {
|
|
30
|
+
const validLanguages = languages.filter((lang) =>
|
|
31
|
+
translateLanguages.includes(lang)
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
if (validLanguages.length > 0) {
|
|
35
|
+
selectedLanguages = validLanguages;
|
|
36
|
+
} else {
|
|
37
|
+
console.log(`ā ļø Invalid languages provided: ${languages.join(", ")}`);
|
|
38
|
+
console.log(
|
|
39
|
+
"Available configured languages:",
|
|
40
|
+
translateLanguages.join(", ")
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// If no valid languages were provided, let user select from configured languages
|
|
46
|
+
if (selectedLanguages.length === 0) {
|
|
47
|
+
// Create choices from configured languages with labels
|
|
48
|
+
const choices = translateLanguages.map((langCode) => {
|
|
49
|
+
const supportedLang = SUPPORTED_LANGUAGES.find(
|
|
50
|
+
(l) => l.code === langCode
|
|
51
|
+
);
|
|
52
|
+
return {
|
|
53
|
+
name: supportedLang
|
|
54
|
+
? `${supportedLang.label} (${supportedLang.sample})`
|
|
55
|
+
: langCode,
|
|
56
|
+
value: langCode,
|
|
57
|
+
short: langCode,
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
selectedLanguages = await options.prompts.checkbox({
|
|
62
|
+
message: "Select languages to translate:",
|
|
63
|
+
choices: choices,
|
|
64
|
+
validate: (answer) => {
|
|
65
|
+
if (answer.length === 0) {
|
|
66
|
+
return "Please select at least one language";
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (selectedLanguages.length === 0) {
|
|
74
|
+
throw new Error("No languages selected for re-translation");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
selectedLanguages,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
languageSelector.input_schema = {
|
|
83
|
+
type: "object",
|
|
84
|
+
properties: {
|
|
85
|
+
languages: {
|
|
86
|
+
type: "array",
|
|
87
|
+
items: {
|
|
88
|
+
type: "string",
|
|
89
|
+
},
|
|
90
|
+
description: "Pre-selected languages for translation",
|
|
91
|
+
},
|
|
92
|
+
translateLanguages: {
|
|
93
|
+
type: "array",
|
|
94
|
+
items: {
|
|
95
|
+
type: "string",
|
|
96
|
+
},
|
|
97
|
+
description: "Available translation languages from config",
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
required: ["translateLanguages"],
|
|
101
|
+
};
|