@aigne/doc-smith 0.2.5 → 0.2.8

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +1 -0
  3. package/agents/check-detail-result.mjs +13 -139
  4. package/agents/check-detail.mjs +4 -6
  5. package/agents/check-structure-plan.mjs +56 -12
  6. package/agents/detail-generator-and-translate.yaml +7 -1
  7. package/agents/detail-regenerator.yaml +3 -1
  8. package/agents/docs-generator.yaml +2 -1
  9. package/agents/find-item-by-path.mjs +64 -15
  10. package/agents/input-generator.mjs +31 -11
  11. package/agents/language-selector.mjs +89 -0
  12. package/agents/load-config.mjs +2 -2
  13. package/agents/load-sources.mjs +13 -40
  14. package/agents/publish-docs.mjs +47 -161
  15. package/agents/retranslate.yaml +74 -0
  16. package/agents/save-docs.mjs +19 -21
  17. package/agents/save-output.mjs +2 -9
  18. package/agents/save-single-doc.mjs +20 -1
  19. package/agents/schema/structure-plan.yaml +1 -1
  20. package/agents/structure-planning.yaml +6 -0
  21. package/agents/transform-detail-datasources.mjs +2 -5
  22. package/agents/translate.yaml +3 -0
  23. package/aigne.yaml +5 -1
  24. package/biome.json +13 -3
  25. package/docs-mcp/get-docs-structure.mjs +1 -1
  26. package/docs-mcp/read-doc-content.mjs +1 -4
  27. package/package.json +20 -7
  28. package/prompts/check-structure-planning-result.md +4 -7
  29. package/prompts/content-detail-generator.md +1 -2
  30. package/prompts/structure-planning.md +7 -2
  31. package/prompts/translator.md +4 -0
  32. package/tests/check-detail-result.test.mjs +8 -19
  33. package/tests/load-sources.test.mjs +65 -161
  34. package/tests/test-all-validation-cases.mjs +741 -0
  35. package/tests/test-save-docs.mjs +6 -17
  36. package/utils/constants.mjs +1 -2
  37. package/utils/markdown-checker.mjs +453 -0
  38. package/utils/mermaid-validator.mjs +153 -0
  39. package/utils/mermaid-worker-pool.mjs +250 -0
  40. package/utils/mermaid-worker.mjs +233 -0
  41. package/utils/utils.mjs +162 -114
@@ -0,0 +1,89 @@
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({ languages, translateLanguages }, options) {
12
+ let selectedLanguages = [];
13
+
14
+ // Check if translateLanguages is available from config
15
+ if (
16
+ !translateLanguages ||
17
+ !Array.isArray(translateLanguages) ||
18
+ translateLanguages.length === 0
19
+ ) {
20
+ throw new Error(
21
+ "No translation languages configured in config.yaml. Please add translateLanguages to your configuration.",
22
+ );
23
+ }
24
+
25
+ // If languages are provided as parameter, validate against configured languages
26
+ if (languages && Array.isArray(languages) && languages.length > 0) {
27
+ const validLanguages = languages.filter((lang) => translateLanguages.includes(lang));
28
+
29
+ if (validLanguages.length > 0) {
30
+ selectedLanguages = validLanguages;
31
+ } else {
32
+ console.log(`⚠️ Invalid languages provided: ${languages.join(", ")}`);
33
+ console.log("Available configured languages:", translateLanguages.join(", "));
34
+ }
35
+ }
36
+
37
+ // If no valid languages were provided, let user select from configured languages
38
+ if (selectedLanguages.length === 0) {
39
+ // Create choices from configured languages with labels
40
+ const choices = translateLanguages.map((langCode) => {
41
+ const supportedLang = SUPPORTED_LANGUAGES.find((l) => l.code === langCode);
42
+ return {
43
+ name: supportedLang ? `${supportedLang.label} (${supportedLang.sample})` : langCode,
44
+ value: langCode,
45
+ short: langCode,
46
+ };
47
+ });
48
+
49
+ selectedLanguages = await options.prompts.checkbox({
50
+ message: "Select languages to translate:",
51
+ choices: choices,
52
+ validate: (answer) => {
53
+ if (answer.length === 0) {
54
+ return "Please select at least one language";
55
+ }
56
+ return true;
57
+ },
58
+ });
59
+ }
60
+
61
+ if (selectedLanguages.length === 0) {
62
+ throw new Error("No languages selected for re-translation");
63
+ }
64
+
65
+ return {
66
+ selectedLanguages,
67
+ };
68
+ }
69
+
70
+ languageSelector.input_schema = {
71
+ type: "object",
72
+ properties: {
73
+ languages: {
74
+ type: "array",
75
+ items: {
76
+ type: "string",
77
+ },
78
+ description: "Pre-selected languages for translation",
79
+ },
80
+ translateLanguages: {
81
+ type: "array",
82
+ items: {
83
+ type: "string",
84
+ },
85
+ description: "Available translation languages from config",
86
+ },
87
+ },
88
+ required: ["translateLanguages"],
89
+ };
@@ -1,5 +1,5 @@
1
- import path from "node:path";
2
1
  import fs from "node:fs/promises";
2
+ import path from "node:path";
3
3
  import { parse } from "yaml";
4
4
 
5
5
  export default async function loadConfig({ config }) {
@@ -8,7 +8,7 @@ export default async function loadConfig({ config }) {
8
8
  try {
9
9
  // Check if config file exists
10
10
  await fs.access(configPath);
11
- } catch (error) {
11
+ } catch (_error) {
12
12
  console.log(`Config file not found: ${configPath}`);
13
13
  console.log("Please run 'aigne doc init' to create the config file.");
14
14
  throw new Error(`Config file not found: ${configPath}`);
@@ -1,14 +1,8 @@
1
1
  import { access, readFile, stat } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { glob } from "glob";
4
- import {
5
- getCurrentGitHead,
6
- getModifiedFilesBetweenCommits,
7
- } from "../utils/utils.mjs";
8
- import {
9
- DEFAULT_INCLUDE_PATTERNS,
10
- DEFAULT_EXCLUDE_PATTERNS,
11
- } from "../utils/constants.mjs";
4
+ import { DEFAULT_EXCLUDE_PATTERNS, DEFAULT_INCLUDE_PATTERNS } from "../utils/constants.mjs";
5
+ import { getCurrentGitHead, getModifiedFilesBetweenCommits } from "../utils/utils.mjs";
12
6
 
13
7
  /**
14
8
  * Load .gitignore patterns from a directory
@@ -42,12 +36,7 @@ async function loadGitignore(dir) {
42
36
  * @param {string[]} gitignorePatterns - .gitignore patterns
43
37
  * @returns {Promise<string[]>} Array of file paths
44
38
  */
45
- async function getFilesWithGlob(
46
- dir,
47
- includePatterns,
48
- excludePatterns,
49
- gitignorePatterns
50
- ) {
39
+ async function getFilesWithGlob(dir, includePatterns, excludePatterns, gitignorePatterns) {
51
40
  // Prepare all ignore patterns
52
41
  const allIgnorePatterns = [];
53
42
 
@@ -88,9 +77,7 @@ async function getFilesWithGlob(
88
77
 
89
78
  return files;
90
79
  } catch (error) {
91
- console.warn(
92
- `Warning: Error during glob search in ${dir}: ${error.message}`
93
- );
80
+ console.warn(`Warning: Error during glob search in ${dir}: ${error.message}`);
94
81
  return [];
95
82
  }
96
83
  }
@@ -143,14 +130,8 @@ export default async function loadSources({
143
130
  : [excludePatterns]
144
131
  : [];
145
132
 
146
- finalIncludePatterns = [
147
- ...DEFAULT_INCLUDE_PATTERNS,
148
- ...userInclude,
149
- ];
150
- finalExcludePatterns = [
151
- ...DEFAULT_EXCLUDE_PATTERNS,
152
- ...userExclude,
153
- ];
133
+ finalIncludePatterns = [...DEFAULT_INCLUDE_PATTERNS, ...userInclude];
134
+ finalExcludePatterns = [...DEFAULT_EXCLUDE_PATTERNS, ...userExclude];
154
135
  } else {
155
136
  // Use only user patterns
156
137
  if (includePatterns) {
@@ -170,7 +151,7 @@ export default async function loadSources({
170
151
  dir,
171
152
  finalIncludePatterns,
172
153
  finalExcludePatterns,
173
- gitignorePatterns
154
+ gitignorePatterns,
174
155
  );
175
156
  allFiles = allFiles.concat(filesInDir);
176
157
  }
@@ -194,7 +175,7 @@ export default async function loadSources({
194
175
  sourceId: relativePath,
195
176
  content,
196
177
  };
197
- })
178
+ }),
198
179
  );
199
180
 
200
181
  // Get the last structure plan result
@@ -253,13 +234,8 @@ export default async function loadSources({
253
234
  try {
254
235
  currentGitHead = getCurrentGitHead();
255
236
  if (currentGitHead && currentGitHead !== lastGitHead) {
256
- modifiedFiles = getModifiedFilesBetweenCommits(
257
- lastGitHead,
258
- currentGitHead
259
- );
260
- console.log(
261
- `Detected ${modifiedFiles.length} modified files since last generation`
262
- );
237
+ modifiedFiles = getModifiedFilesBetweenCommits(lastGitHead, currentGitHead);
238
+ console.log(`Detected ${modifiedFiles.length} modified files since last generation`);
263
239
  }
264
240
  } catch (error) {
265
241
  console.warn("Failed to detect git changes:", error.message);
@@ -290,18 +266,15 @@ loadSources.input_schema = {
290
266
  },
291
267
  includePatterns: {
292
268
  anyOf: [{ type: "string" }, { type: "array", items: { type: "string" } }],
293
- description:
294
- "Glob patterns to filter files by path or filename. If not set, include all.",
269
+ description: "Glob patterns to filter files by path or filename. If not set, include all.",
295
270
  },
296
271
  excludePatterns: {
297
272
  anyOf: [{ type: "string" }, { type: "array", items: { type: "string" } }],
298
- description:
299
- "Glob patterns to exclude files by path or filename. If not set, exclude none.",
273
+ description: "Glob patterns to exclude files by path or filename. If not set, exclude none.",
300
274
  },
301
275
  useDefaultPatterns: {
302
276
  type: "boolean",
303
- description:
304
- "Whether to use default include/exclude patterns. Defaults to true.",
277
+ description: "Whether to use default include/exclude patterns. Defaults to true.",
305
278
  },
306
279
  "doc-path": {
307
280
  type: "string",
@@ -1,125 +1,17 @@
1
- import { join } from "node:path";
2
- import { joinURL } from "ufo";
3
- import open from "open";
4
- import { publishDocs as publishDocsFn } from "@aigne/publish-docs";
5
- import { createConnect } from "@aigne/cli/utils/load-aigne.js";
6
1
  import { existsSync, mkdirSync } from "node:fs";
7
2
  import { readFile, writeFile } from "node:fs/promises";
8
3
  import { homedir } from "node:os";
4
+ import { basename, join } from "node:path";
5
+ import { createConnect } from "@aigne/aigne-hub";
6
+ import { publishDocs as publishDocsFn } from "@aigne/publish-docs";
7
+ import open from "open";
8
+ import { joinURL } from "ufo";
9
9
  import { parse, stringify } from "yaml";
10
- import { execSync } from "node:child_process";
11
- import { basename } from "node:path";
12
10
  import { loadConfigFromFile, saveValueToConfig } from "../utils/utils.mjs";
13
11
 
14
12
  const WELLKNOWN_SERVICE_PATH_PREFIX = "/.well-known/service";
15
13
  const DEFAULT_APP_URL = "https://docsmith.aigne.io";
16
14
 
17
- /**
18
- * Get GitHub repository information
19
- * @param {string} repoUrl - The repository URL
20
- * @returns {Promise<Object>} - Repository information
21
- */
22
- async function getGitHubRepoInfo(repoUrl) {
23
- try {
24
- // Extract owner and repo from GitHub URL
25
- const match = repoUrl.match(
26
- /github\.com[\/:]([^\/]+)\/([^\/]+?)(?:\.git)?$/
27
- );
28
- if (!match) return null;
29
-
30
- const [, owner, repo] = match;
31
- const apiUrl = `https://api.github.com/repos/${owner}/${repo}`;
32
-
33
- const response = await fetch(apiUrl);
34
- if (!response.ok) return null;
35
-
36
- const data = await response.json();
37
- return {
38
- name: data.name,
39
- description: data.description || "",
40
- icon: data.owner?.avatar_url || "",
41
- };
42
- } catch (error) {
43
- console.warn("Failed to fetch GitHub repository info:", error.message);
44
- return null;
45
- }
46
- }
47
-
48
- /**
49
- * Get project information with user confirmation
50
- * @param {Object} options - Options object containing prompts
51
- * @returns {Promise<Object>} - Project information including name, description, and icon
52
- */
53
- async function getProjectInfo(options) {
54
- let repoInfo = null;
55
- let defaultName = basename(process.cwd());
56
- let defaultDescription = "";
57
- let defaultIcon = "";
58
-
59
- // Check if we're in a git repository
60
- try {
61
- const gitRemote = execSync("git remote get-url origin", {
62
- encoding: "utf8",
63
- stdio: ["pipe", "pipe", "ignore"],
64
- }).trim();
65
-
66
- // Extract repository name from git remote URL
67
- const repoName = gitRemote.split("/").pop().replace(".git", "");
68
- defaultName = repoName;
69
-
70
- // If it's a GitHub repository, try to get additional info
71
- if (gitRemote.includes("github.com")) {
72
- repoInfo = await getGitHubRepoInfo(gitRemote);
73
- if (repoInfo) {
74
- defaultDescription = repoInfo.description;
75
- defaultIcon = repoInfo.icon;
76
- }
77
- }
78
- } catch (error) {
79
- // Not in git repository or no origin remote, use current directory name
80
- console.warn("No git repository found, using current directory name");
81
- }
82
-
83
- // Prompt user for project information
84
- console.log("\n📋 Project Information for Documentation Platform");
85
-
86
- const projectName = await options.prompts.input({
87
- message: "Project name:",
88
- default: defaultName,
89
- validate: (input) => {
90
- if (!input || input.trim() === "") {
91
- return "Project name cannot be empty";
92
- }
93
- return true;
94
- },
95
- });
96
-
97
- const projectDescription = await options.prompts.input({
98
- message: "Project description (optional):",
99
- default: defaultDescription,
100
- });
101
-
102
- const projectIcon = await options.prompts.input({
103
- message: "Project icon URL (optional):",
104
- default: defaultIcon,
105
- validate: (input) => {
106
- if (!input || input.trim() === "") return true;
107
- try {
108
- new URL(input);
109
- return true;
110
- } catch {
111
- return "Please enter a valid URL";
112
- }
113
- },
114
- });
115
-
116
- return {
117
- name: projectName.trim(),
118
- description: projectDescription.trim(),
119
- icon: projectIcon.trim(),
120
- };
121
- }
122
-
123
15
  /**
124
16
  * Get access token from environment, config file, or prompt user for authorization
125
17
  * @param {string} appUrl - The application URL
@@ -142,7 +34,7 @@ async function getAccessToken(appUrl) {
142
34
  const data = await readFile(DOC_SMITH_ENV_FILE, "utf8");
143
35
  if (data.includes("DOC_DISCUSS_KIT_ACCESS_TOKEN")) {
144
36
  const envs = parse(data);
145
- if (envs[hostname] && envs[hostname].DOC_DISCUSS_KIT_ACCESS_TOKEN) {
37
+ if (envs[hostname]?.DOC_DISCUSS_KIT_ACCESS_TOKEN) {
146
38
  accessToken = envs[hostname].DOC_DISCUSS_KIT_ACCESS_TOKEN;
147
39
  }
148
40
  }
@@ -207,7 +99,7 @@ async function getAccessToken(appUrl) {
207
99
  }
208
100
 
209
101
  export default async function publishDocs(
210
- { docsDir, appUrl, boardId, boardName, boardDesc, boardCover },
102
+ { docsDir, appUrl, boardId, projectName, projectDesc, projectLogo },
211
103
  options
212
104
  ) {
213
105
  // Check if DOC_DISCUSS_KIT_URL is set in environment variables
@@ -222,7 +114,7 @@ export default async function publishDocs(
222
114
  // Check if appUrl is default and not saved in config (only when not using env variable)
223
115
  const config = await loadConfigFromFile();
224
116
  const isDefaultAppUrl = appUrl === DEFAULT_APP_URL;
225
- const hasAppUrlInConfig = config && config.appUrl;
117
+ const hasAppUrlInConfig = config?.appUrl;
226
118
 
227
119
  if (!useEnvAppUrl && isDefaultAppUrl && !hasAppUrlInConfig) {
228
120
  const choice = await options.prompts.select({
@@ -260,58 +152,52 @@ export default async function publishDocs(
260
152
 
261
153
  const sidebarPath = join(docsDir, "_sidebar.md");
262
154
 
263
- let projectInfo = {
264
- name: boardName,
265
- description: boardDesc,
266
- icon: boardCover,
155
+ // Get project info from config
156
+ const projectInfo = {
157
+ name: projectName || config?.projectName || basename(process.cwd()),
158
+ description: projectDesc || config?.projectDesc || "",
159
+ icon: projectLogo || config?.projectLogo || "",
267
160
  };
268
161
 
269
- // Only get project info if we need to create a new board
270
- if (!boardName) {
271
- projectInfo = await getProjectInfo(options);
272
-
273
- // save project info to config
274
- await saveValueToConfig("boardName", projectInfo.name);
275
- await saveValueToConfig("boardDesc", projectInfo.description);
276
- await saveValueToConfig("boardCover", projectInfo.icon);
277
- }
162
+ try {
163
+ const { success, boardId: newBoardId } = await publishDocsFn({
164
+ sidebarPath,
165
+ accessToken,
166
+ appUrl,
167
+ boardId,
168
+ autoCreateBoard: true,
169
+ // Pass additional project information if available
170
+ boardName: projectInfo.name,
171
+ boardDesc: projectInfo.description,
172
+ boardCover: projectInfo.icon,
173
+ });
278
174
 
279
- const {
280
- success,
281
- boardId: newBoardId,
282
- docsUrl,
283
- } = await publishDocsFn({
284
- sidebarPath,
285
- accessToken,
286
- appUrl,
287
- boardId,
288
- autoCreateBoard: true,
289
- // Pass additional project information if available
290
- boardName: projectInfo.name,
291
- boardDesc: projectInfo.description,
292
- boardCover: projectInfo.icon,
293
- });
175
+ // Save values to config.yaml if publish was successful
176
+ if (success) {
177
+ // Save appUrl to config only when not using environment variable
178
+ if (!useEnvAppUrl) {
179
+ await saveValueToConfig("appUrl", appUrl);
180
+ }
294
181
 
295
- // Save values to config.yaml if publish was successful
296
- if (success) {
297
- // Save appUrl to config only when not using environment variable
298
- if (!useEnvAppUrl) {
299
- await saveValueToConfig("appUrl", appUrl);
182
+ // Save boardId to config if it was auto-created
183
+ if (boardId !== newBoardId) {
184
+ await saveValueToConfig(
185
+ "boardId",
186
+ newBoardId,
187
+ "⚠️ Warning: boardId is auto-generated by system, please do not edit manually"
188
+ );
189
+ }
300
190
  }
301
191
 
302
- // Save boardId to config if it was auto-created
303
- if (boardId !== newBoardId) {
304
- await saveValueToConfig("boardId", newBoardId);
305
- }
192
+ const message = `✅ Documentation Published Successfully!`;
193
+ return {
194
+ message,
195
+ };
196
+ } catch (error) {
197
+ return {
198
+ message: `❌ Failed to publish docs: ${error.message}`,
199
+ };
306
200
  }
307
-
308
- // const message = `## ✅ Documentation Published Successfully!
309
-
310
- // Documentation is now available at: \`${docsUrl}\`
311
- // `;
312
- return {
313
- // message,
314
- };
315
201
  }
316
202
 
317
203
  publishDocs.input_schema = {
@@ -0,0 +1,74 @@
1
+ type: team
2
+ name: retranslate
3
+ alias:
4
+ - rt
5
+ - translate-again
6
+ description: Re-translate individual document content to selected languages
7
+ skills:
8
+ - url: ./input-generator.mjs
9
+ default_input:
10
+ skipIfExists: true
11
+ - ./load-config.mjs
12
+ - ./load-sources.mjs
13
+ - type: transform
14
+ jsonata: |
15
+ $merge([
16
+ $,
17
+ {
18
+ 'structurePlan': originalStructurePlan,
19
+ 'structurePlanResult': $map(originalStructurePlan, function($item) {
20
+ $merge([
21
+ $item,
22
+ {
23
+ 'translates': [$map(translateLanguages, function($lang) { {"language": $lang} })]
24
+ }
25
+ ])
26
+ })
27
+ }
28
+ ])
29
+ - url: ./find-item-by-path.mjs
30
+ default_input:
31
+ isTranslate: true
32
+ - ./language-selector.mjs
33
+ - type: transform
34
+ jsonata: |
35
+ $merge([
36
+ $,
37
+ {
38
+ 'translates': [$map(selectedLanguages, function($lang) { {"language": $lang} })]
39
+ }
40
+ ])
41
+ - ./batch-translate.yaml
42
+ - url: ./save-single-doc.mjs
43
+ default_input:
44
+ isTranslate: true
45
+ isShowMessage: true
46
+ input_schema:
47
+ type: object
48
+ properties:
49
+ glossary:
50
+ type: string
51
+ description: Glossary of terms for consistent terminology
52
+ doc-path:
53
+ type: string
54
+ description: Document path to retranslate
55
+ # languages:
56
+ # type: array
57
+ # items:
58
+ # type: string
59
+ # description: Languages to translate to
60
+ feedback:
61
+ type: string
62
+ description: Feedback for translation improvement
63
+ output_schema:
64
+ type: object
65
+ properties:
66
+ title:
67
+ type: string
68
+ description:
69
+ type: string
70
+ path:
71
+ type: string
72
+ content:
73
+ type: string
74
+ mode: sequential
@@ -1,5 +1,6 @@
1
- import { writeFile, readdir, unlink } from "node:fs/promises";
1
+ import { readdir, unlink, writeFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
+ import { shutdownMermaidWorkerPool } from "../utils/mermaid-worker-pool.mjs";
3
4
  import { getCurrentGitHead, saveGitHeadToConfig } from "../utils/utils.mjs";
4
5
 
5
6
  /**
@@ -14,8 +15,9 @@ export default async function saveDocs({
14
15
  docsDir,
15
16
  translateLanguages = [],
16
17
  locale,
18
+ projectInfoMessage,
17
19
  }) {
18
- const results = [];
20
+ const _results = [];
19
21
  // Save current git HEAD to config.yaml for change detection
20
22
  try {
21
23
  const gitHead = getCurrentGitHead();
@@ -35,20 +37,16 @@ export default async function saveDocs({
35
37
 
36
38
  // Clean up invalid .md files that are no longer in the structure plan
37
39
  try {
38
- await cleanupInvalidFiles(
39
- structurePlan,
40
- docsDir,
41
- translateLanguages,
42
- locale
43
- );
40
+ await cleanupInvalidFiles(structurePlan, docsDir, translateLanguages, locale);
44
41
  } catch (err) {
45
42
  console.error("Failed to cleanup invalid .md files:", err.message);
46
43
  }
47
44
 
48
45
  const message = `## ✅ Documentation Generated Successfully!
49
46
 
50
- Successfully generated **${structurePlan.length}** documents and saved to: \`${docsDir}\`
51
-
47
+ Successfully generated **${structurePlan.length}** documents and saved to:
48
+ \`${docsDir}\`
49
+ ${projectInfoMessage || ""}
52
50
  ### 🚀 Next Steps
53
51
 
54
52
  1. Publish Documentation
@@ -78,6 +76,13 @@ export default async function saveDocs({
78
76
  ---
79
77
  `;
80
78
 
79
+ // Shutdown mermaid worker pool to ensure clean exit
80
+ try {
81
+ await shutdownMermaidWorkerPool();
82
+ } catch (error) {
83
+ console.warn("Failed to shutdown mermaid worker pool:", error.message);
84
+ }
85
+
81
86
  return {
82
87
  message,
83
88
  };
@@ -102,12 +107,7 @@ function generateFileName(flatName, language) {
102
107
  * @param {string} locale - Main language locale (e.g., 'en', 'zh', 'fr')
103
108
  * @returns {Promise<Array<{ path: string, success: boolean, error?: string }>>}
104
109
  */
105
- async function cleanupInvalidFiles(
106
- structurePlan,
107
- docsDir,
108
- translateLanguages,
109
- locale
110
- ) {
110
+ async function cleanupInvalidFiles(structurePlan, docsDir, translateLanguages, locale) {
111
111
  const results = [];
112
112
 
113
113
  try {
@@ -135,7 +135,7 @@ async function cleanupInvalidFiles(
135
135
 
136
136
  // Find files to delete (files that are not in expectedFiles and not _sidebar.md)
137
137
  const filesToDelete = mdFiles.filter(
138
- (file) => !expectedFiles.has(file) && file !== "_sidebar.md"
138
+ (file) => !expectedFiles.has(file) && file !== "_sidebar.md",
139
139
  );
140
140
 
141
141
  // Delete invalid files
@@ -158,9 +158,7 @@ async function cleanupInvalidFiles(
158
158
  }
159
159
 
160
160
  if (filesToDelete.length > 0) {
161
- console.log(
162
- `Cleaned up ${filesToDelete.length} invalid .md files from ${docsDir}`
163
- );
161
+ console.log(`Cleaned up ${filesToDelete.length} invalid .md files from ${docsDir}`);
164
162
  }
165
163
  } catch (err) {
166
164
  // If docsDir doesn't exist or can't be read, that's okay
@@ -199,7 +197,7 @@ function generateSidebar(structurePlan) {
199
197
  for (const key of Object.keys(node)) {
200
198
  const item = node[key];
201
199
  const fullSegments = [...parentSegments, key];
202
- const flatFile = fullSegments.join("-") + ".md";
200
+ const flatFile = `${fullSegments.join("-")}.md`;
203
201
  if (item.__title) {
204
202
  const realIndent = item.__parentId === null ? "" : indent;
205
203
  out += `${realIndent}* [${item.__title}](/${flatFile})\n`;
@@ -1,12 +1,7 @@
1
1
  import { promises as fs } from "node:fs";
2
2
  import { join } from "node:path";
3
3
 
4
- export default async function saveOutput({
5
- savePath,
6
- fileName,
7
- saveKey,
8
- ...rest
9
- }) {
4
+ export default async function saveOutput({ savePath, fileName, saveKey, ...rest }) {
10
5
  if (!(saveKey in rest)) {
11
6
  console.warn(`saveKey "${saveKey}" not found in input, skip saving.`);
12
7
  return {
@@ -17,9 +12,7 @@ export default async function saveOutput({
17
12
 
18
13
  const value = rest[saveKey];
19
14
  const content =
20
- typeof value === "object" && value !== null
21
- ? JSON.stringify(value, null, 2)
22
- : String(value);
15
+ typeof value === "object" && value !== null ? JSON.stringify(value, null, 2) : String(value);
23
16
  await fs.mkdir(savePath, { recursive: true });
24
17
  const filePath = join(savePath, fileName);
25
18
  await fs.writeFile(filePath, content, "utf8");