@aigne/doc-smith 0.9.10 → 0.9.11-beta

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 (308) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +189 -219
  3. package/README.zh.md +270 -0
  4. package/agents/bash-executor/index.mjs +347 -0
  5. package/agents/clear/ai/intent.md +142 -0
  6. package/agents/clear/choose-contents.mjs +13 -65
  7. package/agents/clear/clear-auth-tokens.mjs +17 -21
  8. package/agents/clear/clear-deployment-config.mjs +33 -24
  9. package/agents/clear/index.yaml +1 -9
  10. package/agents/content-checker/ai/intent.md +209 -0
  11. package/agents/content-checker/clean-invalid-docs.mjs +254 -0
  12. package/agents/content-checker/index.mjs +191 -0
  13. package/agents/content-checker/validate-content.mjs +983 -0
  14. package/agents/generate-images/generate-image.yaml +75 -0
  15. package/agents/generate-images/generate-summary.mjs +213 -0
  16. package/agents/generate-images/index.yaml +39 -0
  17. package/agents/generate-images/prepare-generation.mjs +286 -0
  18. package/agents/generate-images/prepare-image-generation.mjs +130 -0
  19. package/{prompts/detail/diagram/generate-image-system.md → agents/generate-images/prompts/system.md} +22 -56
  20. package/agents/generate-images/prompts/user.md +85 -0
  21. package/agents/generate-images/save-image-result.mjs +247 -0
  22. package/agents/generate-images/scan-image-slots.mjs +247 -0
  23. package/agents/localize/index.yaml +19 -42
  24. package/{prompts/translate → agents/localize/prompts}/translate-document.md +0 -139
  25. package/agents/localize/translate-documents/generate-summary.mjs +163 -0
  26. package/agents/localize/translate-documents/load-glossary.mjs +52 -0
  27. package/agents/localize/translate-documents/prepare-translation.mjs +249 -0
  28. package/agents/localize/translate-documents/save-translation.mjs +171 -0
  29. package/agents/localize/translate-documents/translate-document-to-language.mjs +209 -0
  30. package/agents/localize/translate-documents/translate-document.yaml +23 -0
  31. package/agents/localize/translate-documents/translate-to-languages.yaml +10 -0
  32. package/agents/localize/translate-images/check-image-translation.mjs +225 -0
  33. package/agents/localize/translate-images/detect-text/detect-and-update-shared.mjs +148 -0
  34. package/agents/localize/translate-images/detect-text/detect-image-text.yaml +44 -0
  35. package/agents/localize/translate-images/detect-text/detect-images-text.yaml +21 -0
  36. package/agents/localize/translate-images/detect-text/prompts/detect-image-text-system.md +43 -0
  37. package/agents/localize/translate-images/detect-text/prompts/detect-image-text-user.md +14 -0
  38. package/agents/localize/translate-images/detect-text/save-text-detection.mjs +105 -0
  39. package/agents/localize/translate-images/prepare-image-input.mjs +124 -0
  40. package/agents/localize/translate-images/save-image-translation.mjs +172 -0
  41. package/agents/localize/translate-images/scan-doc-images.mjs +165 -0
  42. package/agents/localize/translate-images/translate-doc-images.yaml +24 -0
  43. package/agents/localize/{translate-diagram.yaml → translate-images/translate-image.yaml} +25 -14
  44. package/agents/publish/ai/intent.md +182 -0
  45. package/agents/publish/check.mjs +107 -0
  46. package/agents/publish/index.yaml +9 -14
  47. package/agents/publish/publish-docs.mjs +81 -61
  48. package/agents/publish/translate-meta.mjs +79 -58
  49. package/agents/save-document/index.mjs +260 -0
  50. package/agents/structure-checker/index.mjs +307 -0
  51. package/agents/structure-checker/validate-structure.mjs +477 -0
  52. package/agents/update-image/analyze-feedback.yaml +37 -0
  53. package/agents/update-image/index.yaml +78 -0
  54. package/agents/update-image/load-existing-image.mjs +211 -0
  55. package/agents/update-image/prompts/analyze-feedback-system.md +43 -0
  56. package/agents/update-image/prompts/analyze-feedback-user.md +15 -0
  57. package/aigne.yaml +26 -139
  58. package/package.json +16 -48
  59. package/scripts/README.md +90 -0
  60. package/scripts/install.sh +86 -0
  61. package/scripts/uninstall.sh +52 -0
  62. package/skills/doc-smith/SKILL.md +285 -0
  63. package/skills/doc-smith/ai/intent/sources-improve.md +290 -0
  64. package/skills/doc-smith/references/changeset-guide.md +171 -0
  65. package/skills/doc-smith/references/document-content-guide.md +214 -0
  66. package/skills/doc-smith/references/document-structure-schema.md +138 -0
  67. package/skills/doc-smith/references/patch-guide.md +96 -0
  68. package/skills/doc-smith/references/structure-confirmation-guide.md +133 -0
  69. package/skills/doc-smith/references/structure-planning-guide.md +149 -0
  70. package/skills/doc-smith/references/update-workflow.md +108 -0
  71. package/skills/doc-smith/references/user-intent-guide.md +175 -0
  72. package/skills/doc-smith/references/workspace-initialization.md +376 -0
  73. package/skills/doc-smith-docs-detail/SKILL.md +356 -0
  74. package/skills/doc-smith-docs-detail/ai/intent.md +271 -0
  75. package/skills-entry/doc-smith/ai/intent.md +260 -0
  76. package/skills-entry/doc-smith/index.mjs +66 -0
  77. package/skills-entry/doc-smith/prompt.md +57 -0
  78. package/skills-entry/doc-smith/utils.mjs +27 -0
  79. package/skills-entry/doc-smith-docs-detail/batch.yaml +56 -0
  80. package/skills-entry/doc-smith-docs-detail/index.mjs +95 -0
  81. package/skills-entry/doc-smith-docs-detail/prompt.md +64 -0
  82. package/utils/afs-factory.mjs +183 -0
  83. package/utils/agent-constants.mjs +97 -0
  84. package/utils/{auth-utils.mjs → auth.mjs} +6 -9
  85. package/{agents/utils/update-branding.mjs → utils/branding.mjs} +3 -4
  86. package/utils/config.mjs +261 -0
  87. package/utils/constants.mjs +32 -0
  88. package/utils/deploy.mjs +3 -3
  89. package/utils/docs-converter.mjs +454 -0
  90. package/utils/docs.mjs +212 -0
  91. package/utils/document-paths.mjs +172 -0
  92. package/utils/files.mjs +74 -0
  93. package/utils/git.mjs +65 -0
  94. package/utils/{blocklet.mjs → http.mjs} +18 -0
  95. package/utils/image-slots.mjs +57 -0
  96. package/utils/image-utils.mjs +114 -0
  97. package/utils/project.mjs +95 -0
  98. package/utils/sources-path-resolver.mjs +76 -0
  99. package/utils/{upload-files.mjs → upload.mjs} +3 -3
  100. package/utils/workspace.mjs +371 -0
  101. package/agents/chat/chat-system.md +0 -38
  102. package/agents/chat/index.mjs +0 -59
  103. package/agents/chat/skills/generate-document.yaml +0 -15
  104. package/agents/chat/skills/list-documents.mjs +0 -15
  105. package/agents/chat/skills/update-document.yaml +0 -24
  106. package/agents/clear/clear-document-config.mjs +0 -36
  107. package/agents/clear/clear-document-structure.mjs +0 -102
  108. package/agents/clear/clear-generated-docs.mjs +0 -142
  109. package/agents/clear/clear-media-description.mjs +0 -129
  110. package/agents/create/aggregate-document-structure.mjs +0 -21
  111. package/agents/create/analyze-diagram-type-llm.yaml +0 -159
  112. package/agents/create/analyze-diagram-type.mjs +0 -455
  113. package/agents/create/check-document-structure.yaml +0 -30
  114. package/agents/create/check-need-generate-structure.mjs +0 -138
  115. package/agents/create/document-structure-tools/add-document.mjs +0 -85
  116. package/agents/create/document-structure-tools/delete-document.mjs +0 -116
  117. package/agents/create/document-structure-tools/move-document.mjs +0 -109
  118. package/agents/create/document-structure-tools/update-document.mjs +0 -84
  119. package/agents/create/generate-diagram-image.yaml +0 -91
  120. package/agents/create/generate-structure.yaml +0 -106
  121. package/agents/create/index.yaml +0 -45
  122. package/agents/create/refine-document-structure.yaml +0 -12
  123. package/agents/create/replace-d2-with-image.mjs +0 -610
  124. package/agents/create/update-document-structure.yaml +0 -54
  125. package/agents/create/user-add-document/add-documents-to-structure.mjs +0 -90
  126. package/agents/create/user-add-document/find-documents-to-add-links.yaml +0 -47
  127. package/agents/create/user-add-document/index.yaml +0 -46
  128. package/agents/create/user-add-document/prepare-documents-to-translate.mjs +0 -22
  129. package/agents/create/user-add-document/print-add-document-summary.mjs +0 -63
  130. package/agents/create/user-add-document/review-documents-with-new-links.mjs +0 -110
  131. package/agents/create/user-remove-document/find-documents-with-invalid-links.mjs +0 -78
  132. package/agents/create/user-remove-document/index.yaml +0 -40
  133. package/agents/create/user-remove-document/prepare-documents-to-translate.mjs +0 -22
  134. package/agents/create/user-remove-document/print-remove-document-summary.mjs +0 -53
  135. package/agents/create/user-remove-document/remove-documents-from-structure.mjs +0 -99
  136. package/agents/create/user-remove-document/review-documents-with-invalid-links.mjs +0 -115
  137. package/agents/create/user-review-document-structure.mjs +0 -139
  138. package/agents/create/utils/init-current-content.mjs +0 -34
  139. package/agents/create/utils/merge-document-structures.mjs +0 -36
  140. package/agents/evaluate/code-snippet.mjs +0 -97
  141. package/agents/evaluate/document-structure.yaml +0 -67
  142. package/agents/evaluate/document.yaml +0 -82
  143. package/agents/evaluate/generate-report.mjs +0 -85
  144. package/agents/evaluate/index.yaml +0 -46
  145. package/agents/history/index.yaml +0 -6
  146. package/agents/history/view.mjs +0 -78
  147. package/agents/init/check.mjs +0 -16
  148. package/agents/init/index.mjs +0 -643
  149. package/agents/init/validate.mjs +0 -16
  150. package/agents/localize/choose-language.mjs +0 -107
  151. package/agents/localize/record-translation-history.mjs +0 -23
  152. package/agents/localize/save-doc-translation-or-skip.mjs +0 -18
  153. package/agents/localize/set-review-content.mjs +0 -58
  154. package/agents/localize/translate-document-wrapper.mjs +0 -34
  155. package/agents/localize/translate-document.yaml +0 -24
  156. package/agents/localize/translate-multilingual.yaml +0 -57
  157. package/agents/localize/translate-or-skip-diagram.mjs +0 -52
  158. package/agents/media/batch-generate-media-description.yaml +0 -46
  159. package/agents/media/generate-media-description.yaml +0 -50
  160. package/agents/media/load-media-description.mjs +0 -454
  161. package/agents/prefs/index.mjs +0 -203
  162. package/agents/schema/document-structure-item.yaml +0 -26
  163. package/agents/schema/document-structure-refine-item.yaml +0 -23
  164. package/agents/schema/document-structure.yaml +0 -29
  165. package/agents/update/batch-generate-document.yaml +0 -27
  166. package/agents/update/batch-update-document.yaml +0 -7
  167. package/agents/update/check-diagram-flag.mjs +0 -116
  168. package/agents/update/check-document.mjs +0 -162
  169. package/agents/update/check-generate-diagram.mjs +0 -106
  170. package/agents/update/check-update-is-single.mjs +0 -53
  171. package/agents/update/document-tools/update-document-content.mjs +0 -303
  172. package/agents/update/generate-diagram.yaml +0 -80
  173. package/agents/update/generate-document.yaml +0 -70
  174. package/agents/update/handle-document-update.yaml +0 -103
  175. package/agents/update/index.yaml +0 -69
  176. package/agents/update/pre-check-generate-diagram.yaml +0 -44
  177. package/agents/update/save-and-translate-document.mjs +0 -80
  178. package/agents/update/update-document-detail.yaml +0 -71
  179. package/agents/update/update-single/update-single-document-detail.mjs +0 -322
  180. package/agents/update/update-single-document.yaml +0 -7
  181. package/agents/update/user-review-document.mjs +0 -272
  182. package/agents/utils/action-success.mjs +0 -16
  183. package/agents/utils/analyze-document-feedback-intent.yaml +0 -32
  184. package/agents/utils/analyze-feedback-intent.mjs +0 -253
  185. package/agents/utils/analyze-structure-feedback-intent.yaml +0 -29
  186. package/agents/utils/check-detail-result.mjs +0 -51
  187. package/agents/utils/check-feedback-refiner.mjs +0 -81
  188. package/agents/utils/choose-docs.mjs +0 -251
  189. package/agents/utils/document-icon-generate.yaml +0 -52
  190. package/agents/utils/document-title-streamline.yaml +0 -48
  191. package/agents/utils/ensure-document-icons.mjs +0 -129
  192. package/agents/utils/exit.mjs +0 -6
  193. package/agents/utils/feedback-refiner.yaml +0 -50
  194. package/agents/utils/find-item-by-path.mjs +0 -114
  195. package/agents/utils/find-user-preferences-by-path.mjs +0 -37
  196. package/agents/utils/format-document-structure.mjs +0 -35
  197. package/agents/utils/generate-document-or-skip.mjs +0 -41
  198. package/agents/utils/handle-diagram-operations.mjs +0 -263
  199. package/agents/utils/load-all-document-content.mjs +0 -30
  200. package/agents/utils/load-document-all-content.mjs +0 -96
  201. package/agents/utils/load-sources.mjs +0 -405
  202. package/agents/utils/map-reasoning-effort-level.mjs +0 -15
  203. package/agents/utils/post-generate.mjs +0 -133
  204. package/agents/utils/read-current-document-content.mjs +0 -46
  205. package/agents/utils/save-doc-translation.mjs +0 -30
  206. package/agents/utils/save-doc.mjs +0 -54
  207. package/agents/utils/save-output.mjs +0 -26
  208. package/agents/utils/save-sidebar.mjs +0 -38
  209. package/agents/utils/skip-if-content-exists.mjs +0 -27
  210. package/agents/utils/streamline-document-titles-if-needed.mjs +0 -88
  211. package/agents/utils/transform-detail-data-sources.mjs +0 -45
  212. package/assets/report-template/report.html +0 -198
  213. package/docs-mcp/analyze-content-relevance.yaml +0 -50
  214. package/docs-mcp/analyze-docs-relevance.yaml +0 -59
  215. package/docs-mcp/docs-search.yaml +0 -42
  216. package/docs-mcp/get-docs-detail.mjs +0 -41
  217. package/docs-mcp/get-docs-structure.mjs +0 -16
  218. package/docs-mcp/read-doc-content.mjs +0 -119
  219. package/prompts/common/document/content-rules-core.md +0 -20
  220. package/prompts/common/document/markdown-syntax-rules.md +0 -65
  221. package/prompts/common/document/media-file-list-usage-rules.md +0 -18
  222. package/prompts/common/document/openapi-usage-rules.md +0 -189
  223. package/prompts/common/document/role-and-personality.md +0 -16
  224. package/prompts/common/document/user-preferences.md +0 -9
  225. package/prompts/common/document-structure/conflict-resolution-guidance.md +0 -16
  226. package/prompts/common/document-structure/document-icon-generate.md +0 -116
  227. package/prompts/common/document-structure/document-structure-rules.md +0 -43
  228. package/prompts/common/document-structure/document-title-streamline.md +0 -86
  229. package/prompts/common/document-structure/glossary.md +0 -7
  230. package/prompts/common/document-structure/intj-traits.md +0 -5
  231. package/prompts/common/document-structure/openapi-usage-rules.md +0 -28
  232. package/prompts/common/document-structure/output-constraints.md +0 -18
  233. package/prompts/common/document-structure/user-locale-rules.md +0 -10
  234. package/prompts/common/document-structure/user-preferences.md +0 -9
  235. package/prompts/detail/custom/admonition-usage-rules.md +0 -94
  236. package/prompts/detail/custom/code-block-usage-rules.md +0 -163
  237. package/prompts/detail/custom/custom-components/x-card-usage-rules.md +0 -63
  238. package/prompts/detail/custom/custom-components/x-cards-usage-rules.md +0 -83
  239. package/prompts/detail/custom/custom-components/x-field-desc-usage-rules.md +0 -120
  240. package/prompts/detail/custom/custom-components/x-field-group-usage-rules.md +0 -80
  241. package/prompts/detail/custom/custom-components/x-field-usage-rules.md +0 -189
  242. package/prompts/detail/custom/custom-components-usage-rules.md +0 -18
  243. package/prompts/detail/diagram/generate-image-user.md +0 -81
  244. package/prompts/detail/diagram/guide.md +0 -29
  245. package/prompts/detail/diagram/official-examples.md +0 -712
  246. package/prompts/detail/diagram/pre-check.md +0 -23
  247. package/prompts/detail/diagram/role-and-personality.md +0 -2
  248. package/prompts/detail/diagram/rules.md +0 -46
  249. package/prompts/detail/diagram/system-prompt.md +0 -1139
  250. package/prompts/detail/diagram/user-prompt.md +0 -43
  251. package/prompts/detail/generate/detail-example.md +0 -457
  252. package/prompts/detail/generate/document-rules.md +0 -45
  253. package/prompts/detail/generate/system-prompt.md +0 -61
  254. package/prompts/detail/generate/user-prompt.md +0 -99
  255. package/prompts/detail/jsx/rules.md +0 -6
  256. package/prompts/detail/update/system-prompt.md +0 -121
  257. package/prompts/detail/update/user-prompt.md +0 -41
  258. package/prompts/evaluate/document-structure.md +0 -93
  259. package/prompts/evaluate/document.md +0 -149
  260. package/prompts/media/media-description/system-prompt.md +0 -43
  261. package/prompts/media/media-description/user-prompt.md +0 -17
  262. package/prompts/structure/check-document-structure.md +0 -93
  263. package/prompts/structure/document-rules.md +0 -21
  264. package/prompts/structure/find-documents-to-add-links.md +0 -52
  265. package/prompts/structure/generate/system-prompt.md +0 -13
  266. package/prompts/structure/generate/user-prompt.md +0 -137
  267. package/prompts/structure/review/structure-review-system.md +0 -81
  268. package/prompts/structure/structure-example.md +0 -89
  269. package/prompts/structure/structure-getting-started.md +0 -10
  270. package/prompts/structure/update/system-prompt.md +0 -93
  271. package/prompts/structure/update/user-prompt.md +0 -43
  272. package/prompts/translate/admonition.md +0 -20
  273. package/prompts/translate/code-block.md +0 -33
  274. package/prompts/utils/analyze-document-feedback-intent.md +0 -54
  275. package/prompts/utils/analyze-structure-feedback-intent.md +0 -43
  276. package/prompts/utils/feedback-refiner.md +0 -105
  277. package/types/document-schema.mjs +0 -55
  278. package/types/document-structure-schema.mjs +0 -261
  279. package/utils/check-document-has-diagram.mjs +0 -95
  280. package/utils/conflict-detector.mjs +0 -149
  281. package/utils/constants/index.mjs +0 -620
  282. package/utils/constants/linter.mjs +0 -102
  283. package/utils/d2-utils.mjs +0 -205
  284. package/utils/debug.mjs +0 -3
  285. package/utils/delete-diagram-images.mjs +0 -99
  286. package/utils/diagram-version-utils.mjs +0 -14
  287. package/utils/docs-finder-utils.mjs +0 -548
  288. package/utils/evaluate/report-utils.mjs +0 -132
  289. package/utils/extract-api.mjs +0 -32
  290. package/utils/file-utils.mjs +0 -960
  291. package/utils/history-utils.mjs +0 -203
  292. package/utils/icon-map.mjs +0 -26
  293. package/utils/image-compress.mjs +0 -154
  294. package/utils/kroki-utils.mjs +0 -173
  295. package/utils/linter/index.mjs +0 -50
  296. package/utils/load-config.mjs +0 -78
  297. package/utils/markdown/index.mjs +0 -26
  298. package/utils/markdown-checker.mjs +0 -694
  299. package/utils/mermaid-validator.mjs +0 -140
  300. package/utils/mermaid-worker-pool.mjs +0 -250
  301. package/utils/mermaid-worker.mjs +0 -233
  302. package/utils/openapi/index.mjs +0 -28
  303. package/utils/preferences-utils.mjs +0 -175
  304. package/utils/request.mjs +0 -10
  305. package/utils/sync-diagram-to-translations.mjs +0 -272
  306. package/utils/translate-diagram-images.mjs +0 -807
  307. package/utils/utils.mjs +0 -1354
  308. /package/{prompts/translate → agents/localize/prompts}/glossary.md +0 -0
@@ -0,0 +1,454 @@
1
+ import { join, relative, dirname, basename } from "node:path";
2
+ import fs from "fs-extra";
3
+ import { parse as yamlParse } from "yaml";
4
+ import { parseSlots } from "./image-slots.mjs";
5
+ import { findImageWithFallback } from "./image-utils.mjs";
6
+ import { PATHS } from "./agent-constants.mjs";
7
+ import {
8
+ isSourcesAbsolutePath,
9
+ parseSourcesPath,
10
+ resolveSourcesPath,
11
+ } from "./sources-path-resolver.mjs";
12
+ import { loadConfigFromFile } from "./config.mjs";
13
+
14
+ /**
15
+ * Scan document directory and identify all document directories containing .meta.yaml
16
+ * @param {string} docsDir - Document root directory path
17
+ * @returns {Promise<Array>} Document list, each containing {dirPath, dirName, locale, content, depth}
18
+ */
19
+ export async function scanDocuments(docsDir) {
20
+ const documents = [];
21
+
22
+ async function scanDir(currentPath) {
23
+ const entries = await fs.readdir(currentPath, { withFileTypes: true });
24
+
25
+ // Check if directory contains .meta.yaml
26
+ const hasMetaFile = entries.some((entry) => entry.isFile() && entry.name === ".meta.yaml");
27
+
28
+ if (hasMetaFile) {
29
+ // This is a document directory, read all language files
30
+ const markdownFiles = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".md"));
31
+
32
+ // Calculate document depth (level relative to docs/)
33
+ const relativePath = relative(docsDir, currentPath);
34
+ const depth = relativePath === "" ? 0 : relativePath.split("/").length;
35
+
36
+ const dirName = basename(currentPath);
37
+
38
+ for (const file of markdownFiles) {
39
+ const locale = file.name.replace(".md", "");
40
+ const filePath = join(currentPath, file.name);
41
+ const content = await fs.readFile(filePath, "utf8");
42
+
43
+ documents.push({
44
+ dirPath: currentPath,
45
+ dirName,
46
+ locale,
47
+ content,
48
+ depth,
49
+ relativePath,
50
+ });
51
+ }
52
+ }
53
+
54
+ // Recursively scan subdirectories
55
+ const subDirs = entries.filter((entry) => entry.isDirectory());
56
+ for (const subDir of subDirs) {
57
+ await scanDir(join(currentPath, subDir.name));
58
+ }
59
+ }
60
+
61
+ await scanDir(docsDir);
62
+ return documents;
63
+ }
64
+
65
+ /**
66
+ * Calculate target path based on document depth and language
67
+ * @param {string} relativePath - Path relative to docs/
68
+ * @param {string} dirName - Document directory name
69
+ * @param {string} locale - Language code
70
+ * @param {number} depth - Document depth
71
+ * @returns {string} Target file path (relative to target directory)
72
+ */
73
+ export function getTargetPath(relativePath, dirName, locale, depth) {
74
+ // English documents have no language suffix, other languages have suffix
75
+ const suffix = locale === "en" ? ".md" : `.${locale}.md`;
76
+ const fileName = `${dirName}${suffix}`;
77
+
78
+ if (depth === 1) {
79
+ // Single level path: move file to root directory
80
+ return fileName;
81
+ }
82
+
83
+ // Multi-level path: keep parent directory, use directory name as file name
84
+ const parentPath = dirname(relativePath);
85
+ return join(parentPath, fileName);
86
+ }
87
+
88
+ /**
89
+ * Add .md suffix to internal links
90
+ * @param {string} content - Document content
91
+ * @returns {string} Processed content
92
+ */
93
+ export function addMarkdownSuffixToLinks(content) {
94
+ // Match Markdown links: [text](path)
95
+ // But not images: ![alt](path)
96
+ // Not external links (http:// or https://)
97
+ // Not links that already have .md suffix
98
+ // Not media file links (images, videos, etc.)
99
+
100
+ // Media file extensions
101
+ const mediaExtensions = /\.(jpg|jpeg|png|gif|webp|svg|mp4|webm|mov|avi|pdf)$/i;
102
+
103
+ return content.replace(/(?<!!)\[([^\]]+)\]\(([^)]+)\)/g, (match, text, url) => {
104
+ // Skip external links
105
+ if (url.startsWith("http://") || url.startsWith("https://")) {
106
+ return match;
107
+ }
108
+
109
+ // Skip links that already have .md suffix
110
+ if (url.includes(".md")) {
111
+ return match;
112
+ }
113
+
114
+ // Skip non-document links (such as mailto:, #anchor, etc.)
115
+ if (url.includes(":") || url.startsWith("#")) {
116
+ return match;
117
+ }
118
+
119
+ // Skip media file links (images, videos, PDFs, etc.)
120
+ if (mediaExtensions.test(url)) {
121
+ return match;
122
+ }
123
+
124
+ // Separate path and anchor
125
+ const hashIndex = url.indexOf("#");
126
+ if (hashIndex !== -1) {
127
+ const path = url.substring(0, hashIndex);
128
+ const hash = url.substring(hashIndex);
129
+ return `[${text}](${path}.md${hash})`;
130
+ }
131
+
132
+ // Add .md suffix
133
+ return `[${text}](${url}.md)`;
134
+ });
135
+ }
136
+
137
+ /**
138
+ * Adjust image paths (based on document depth)
139
+ * @param {string} content - Document content
140
+ * @param {number} depth - Document depth
141
+ * @returns {string} Processed content
142
+ */
143
+ export function adjustImagePaths(content, depth) {
144
+ // Documents at depth 1 move up one level, need to remove one ../
145
+ // Documents at depth 2+ keep paths unchanged
146
+
147
+ if (depth !== 1) {
148
+ return content;
149
+ }
150
+
151
+ // Match image links: ![alt](path)
152
+ return content.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (match, alt, path) => {
153
+ // Only process relative paths (paths containing ../)
154
+ if (!path.startsWith("../")) {
155
+ return match;
156
+ }
157
+
158
+ // Remove one ../
159
+ const newPath = path.replace(/^\.\.\//, "");
160
+ return `![${alt}](${newPath})`;
161
+ });
162
+ }
163
+
164
+ /**
165
+ * Read primary language from config file
166
+ * @returns {Promise<string|null>} - Primary language code, returns null if read fails
167
+ */
168
+ async function loadMainLocale() {
169
+ try {
170
+ const configPath = PATHS.CONFIG;
171
+ if (!(await fs.pathExists(configPath))) {
172
+ return null;
173
+ }
174
+ const content = await fs.readFile(configPath, "utf8");
175
+ const config = yamlParse(content);
176
+ return config?.locale || null;
177
+ } catch (_error) {
178
+ return null;
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Replace AFS image slots in document with actual image references
184
+ * @param {string} content - Document content
185
+ * @param {string} docPath - Document path (for calculating relative paths and generating keys)
186
+ * @param {string} locale - Current document language
187
+ * @param {string} mainLocale - Primary language
188
+ * @param {number} depth - Document depth (for calculating relative paths)
189
+ * @param {string} assetsDir - Assets directory path
190
+ * @returns {Promise<string>} - Content with replacements
191
+ */
192
+ async function replaceImageSlots(
193
+ content,
194
+ docPath,
195
+ locale,
196
+ mainLocale,
197
+ depth,
198
+ assetsDir = PATHS.ASSETS_DIR,
199
+ ) {
200
+ // Parse all slots
201
+ const slots = parseSlots(content, docPath);
202
+
203
+ if (slots.length === 0) {
204
+ return content;
205
+ }
206
+
207
+ // Replace each slot
208
+ let result = content;
209
+ for (const slot of slots) {
210
+ const { key, desc, raw } = slot;
211
+
212
+ // Find image
213
+ const imagePath = await findImageWithFallback(key, locale, mainLocale, assetsDir);
214
+
215
+ if (imagePath) {
216
+ // Calculate relative path prefix
217
+ // Target document is at least in targetDir root, needs at least 1 ../ to access assets/ parallel to targetDir
218
+ // depth 0/1: ../assets/{key}/images/{lang}.jpg (from tmp-docs/overview.md to assets/)
219
+ // depth 2: ../../assets/{key}/images/{lang}.jpg (from tmp-docs/api/auth.md to assets/)
220
+ // depth N: N ../'s (minimum 1)
221
+ const pathPrefix = "../".repeat(Math.max(depth, 1));
222
+ const imageRef = `${pathPrefix}assets/${imagePath}`;
223
+
224
+ // Replace slot with image reference
225
+ const imageMarkdown = `![${desc}](${imageRef})`;
226
+ result = result.replace(raw, imageMarkdown);
227
+ }
228
+ // If image doesn't exist, keep slot unchanged (or could choose to remove)
229
+ }
230
+
231
+ return result;
232
+ }
233
+
234
+ /**
235
+ * Process single /sources/... image path
236
+ * @param {string} imagePath - Image path
237
+ * @param {number} depth - Document depth
238
+ * @param {string} targetDir - Target directory
239
+ * @param {Array} sourcesConfig - Sources configuration
240
+ * @param {string} workspaceBase - Workspace base path
241
+ * @param {Set} processedImages - Set of processed images
242
+ * @returns {Promise<{newPath: string, copied: boolean} | null>} - New path and whether file was copied
243
+ */
244
+ async function processSourcesImagePath(
245
+ imagePath,
246
+ depth,
247
+ targetDir,
248
+ sourcesConfig,
249
+ workspaceBase,
250
+ processedImages,
251
+ ) {
252
+ if (!isSourcesAbsolutePath(imagePath)) {
253
+ return null;
254
+ }
255
+
256
+ // Parse path, get relative path portion
257
+ const relativePath = parseSourcesPath(imagePath);
258
+ if (!relativePath) {
259
+ console.warn(`⚠️ Invalid sources path format: ${imagePath}`);
260
+ return null;
261
+ }
262
+
263
+ // Get physical path (automatically search in each source)
264
+ const resolved = await resolveSourcesPath(imagePath, sourcesConfig, workspaceBase);
265
+ if (!resolved) {
266
+ console.warn(`⚠️ Cannot find image in any source: ${imagePath}`);
267
+ return null;
268
+ }
269
+
270
+ const { physicalPath } = resolved;
271
+
272
+ // Copy to sources subdirectory in temp directory
273
+ // Maintain same path structure as execution layer: targetDir/../sources/<relativePath>
274
+ const targetImagePath = join(dirname(targetDir), "sources", relativePath);
275
+
276
+ let copied = false;
277
+ if (!processedImages.has(targetImagePath)) {
278
+ await fs.ensureDir(dirname(targetImagePath));
279
+ await fs.copy(physicalPath, targetImagePath);
280
+ processedImages.add(targetImagePath);
281
+ copied = true;
282
+ }
283
+
284
+ // Calculate relative path
285
+ // depth 0/1: ../sources/path/to/image.png
286
+ // depth 2: ../../sources/path/to/image.png
287
+ const pathPrefix = "../".repeat(Math.max(depth, 1));
288
+ const newPath = `${pathPrefix}sources/${relativePath}`;
289
+
290
+ return { newPath, copied };
291
+ }
292
+
293
+ /**
294
+ * Process /sources/... absolute path images in document
295
+ * Supports two formats:
296
+ * - Markdown: ![alt](/sources/path/to/image.png)
297
+ * - HTML: <img src="/sources/path/to/image.png" ... />
298
+ * @param {string} content - Document content
299
+ * @param {number} depth - Document depth (for calculating relative paths)
300
+ * @param {string} targetDir - Target directory (temp directory)
301
+ * @param {Array} sourcesConfig - sources configuration from config.yaml
302
+ * @param {string} workspaceBase - Workspace base path
303
+ * @returns {Promise<{content: string, copiedCount: number}>} - Processed content and count of copied images
304
+ */
305
+ async function processSourcesImages(content, depth, targetDir, sourcesConfig, workspaceBase) {
306
+ let result = content;
307
+ const processedImages = new Set();
308
+ let copiedCount = 0;
309
+
310
+ // 1. Process Markdown format images: ![alt](/sources/path/to/image.png)
311
+ const markdownImageRegex = /!\[([^\]]*)\]\(([^)]+)\)/g;
312
+ const markdownMatches = [...content.matchAll(markdownImageRegex)];
313
+
314
+ for (const match of markdownMatches) {
315
+ const [fullMatch, alt, imagePath] = match;
316
+
317
+ const processResult = await processSourcesImagePath(
318
+ imagePath,
319
+ depth,
320
+ targetDir,
321
+ sourcesConfig,
322
+ workspaceBase,
323
+ processedImages,
324
+ );
325
+
326
+ if (processResult) {
327
+ const { newPath, copied } = processResult;
328
+ if (copied) copiedCount++;
329
+ result = result.replace(fullMatch, `![${alt}](${newPath})`);
330
+ }
331
+ }
332
+
333
+ // 2. Process HTML img tags: <img src="/sources/path/to/image.png" ... />
334
+ const htmlImgRegex = /<img\s+([^>]*?)src=["']([^"']+)["']([^>]*?)\/?>/gi;
335
+ const htmlMatches = [...result.matchAll(htmlImgRegex)];
336
+
337
+ for (const match of htmlMatches) {
338
+ const [fullMatch, beforeSrc, imagePath, afterSrc] = match;
339
+
340
+ const processResult = await processSourcesImagePath(
341
+ imagePath,
342
+ depth,
343
+ targetDir,
344
+ sourcesConfig,
345
+ workspaceBase,
346
+ processedImages,
347
+ );
348
+
349
+ if (processResult) {
350
+ const { newPath, copied } = processResult;
351
+ if (copied) copiedCount++;
352
+ // Rebuild img tag, keeping other attributes unchanged
353
+ const newImgTag = `<img ${beforeSrc}src="${newPath}"${afterSrc}/>`;
354
+ result = result.replace(fullMatch, newImgTag);
355
+ }
356
+ }
357
+
358
+ return { content: result, copiedCount };
359
+ }
360
+
361
+ /**
362
+ * Copy documents to temp directory and perform conversion
363
+ * @param {string} sourceDir - Source document directory
364
+ * @param {string} targetDir - Target directory
365
+ * @returns {Promise<Object>} Conversion statistics
366
+ */
367
+ export async function copyDocumentsToTemp(sourceDir, targetDir) {
368
+ // Scan all documents
369
+ const documents = await scanDocuments(sourceDir);
370
+
371
+ if (documents.length === 0) {
372
+ console.warn("⚠️ No documents found to convert.");
373
+ return { total: 0, converted: 0 };
374
+ }
375
+
376
+ // Read primary language (for image fallback)
377
+ const mainLocale = await loadMainLocale();
378
+
379
+ // Load sources config (for processing /sources/... absolute paths)
380
+ const config = await loadConfigFromFile();
381
+ const sourcesConfig = config?.sources || [];
382
+
383
+ const stats = {
384
+ total: documents.length,
385
+ converted: 0,
386
+ depth1: 0,
387
+ depth2Plus: 0,
388
+ slotsReplaced: 0,
389
+ sourcesCopied: 0,
390
+ };
391
+
392
+ // Process each document
393
+ for (const doc of documents) {
394
+ const { relativePath, dirName, locale, content, depth } = doc;
395
+
396
+ // Calculate target path
397
+ const targetPath = getTargetPath(relativePath, dirName, locale, depth);
398
+ const fullTargetPath = join(targetDir, targetPath);
399
+
400
+ // Process content
401
+ let processedContent = content;
402
+
403
+ // 1. Adjust image paths in original document (must be before replaceImageSlots to avoid processing newly generated paths)
404
+ processedContent = adjustImagePaths(processedContent, depth);
405
+
406
+ // 2. Replace AFS image slots with actual image references
407
+ // Use relativePath as docPath (need to add leading /)
408
+ const docPath = relativePath ? `/${relativePath}` : `/${dirName}`;
409
+ const contentBeforeSlotReplace = processedContent;
410
+ processedContent = await replaceImageSlots(
411
+ processedContent,
412
+ docPath,
413
+ locale,
414
+ mainLocale,
415
+ depth,
416
+ PATHS.ASSETS_DIR,
417
+ );
418
+ // Count replaced slots
419
+ if (contentBeforeSlotReplace !== processedContent) {
420
+ const slotsBefore = (contentBeforeSlotReplace.match(/<!--\s*afs:image/g) || []).length;
421
+ const slotsAfter = (processedContent.match(/<!--\s*afs:image/g) || []).length;
422
+ stats.slotsReplaced += slotsBefore - slotsAfter;
423
+ }
424
+
425
+ // 3. Process /sources/... absolute path images
426
+ if (sourcesConfig.length > 0) {
427
+ const sourcesResult = await processSourcesImages(
428
+ processedContent,
429
+ depth,
430
+ targetDir,
431
+ sourcesConfig,
432
+ PATHS.WORKSPACE_BASE,
433
+ );
434
+ processedContent = sourcesResult.content;
435
+ stats.sourcesCopied += sourcesResult.copiedCount;
436
+ }
437
+
438
+ // 4. Add .md suffix to internal links
439
+ processedContent = addMarkdownSuffixToLinks(processedContent);
440
+
441
+ // Create target directory and write file
442
+ await fs.ensureDir(dirname(fullTargetPath));
443
+ await fs.writeFile(fullTargetPath, processedContent, "utf8");
444
+
445
+ stats.converted++;
446
+ if (depth === 1) {
447
+ stats.depth1++;
448
+ } else {
449
+ stats.depth2Plus++;
450
+ }
451
+ }
452
+
453
+ return stats;
454
+ }
package/utils/docs.mjs ADDED
@@ -0,0 +1,212 @@
1
+ import { readFile, access, readdir } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { parse as yamlParse } from "yaml";
4
+
5
+ /**
6
+ * Check if a path exists
7
+ * @param {string} path - Path to check
8
+ * @returns {Promise<boolean>} - True if path exists
9
+ */
10
+ async function pathExists(path) {
11
+ try {
12
+ await access(path);
13
+ return true;
14
+ } catch {
15
+ return false;
16
+ }
17
+ }
18
+
19
+ /**
20
+ * Convert YAML structure to array structure
21
+ * @param {Object} yamlData - Parsed YAML data
22
+ * @returns {Array} - Array of document structure items
23
+ */
24
+ function convertYamlToStructure(yamlData) {
25
+ if (!yamlData || !yamlData.documents) {
26
+ return [];
27
+ }
28
+
29
+ const result = [];
30
+ const processNode = (node, parentId = null) => {
31
+ const item = {
32
+ path: node.path,
33
+ title: node.title,
34
+ parentId,
35
+ };
36
+
37
+ if (node.icon) {
38
+ item.icon = node.icon;
39
+ }
40
+
41
+ result.push(item);
42
+
43
+ if (node.children && Array.isArray(node.children)) {
44
+ for (const child of node.children) {
45
+ processNode(child, node.path);
46
+ }
47
+ }
48
+ };
49
+
50
+ for (const doc of yamlData.documents) {
51
+ processNode(doc);
52
+ }
53
+ return result;
54
+ }
55
+
56
+ /**
57
+ * Load document structure from output directory
58
+ * @param {string} outputDir - Output directory path
59
+ * @returns {Promise<Array|null>} - Document structure array or null
60
+ */
61
+ export async function loadDocumentStructure(outputDir) {
62
+ if (!outputDir) {
63
+ return null;
64
+ }
65
+
66
+ // Try loading document-structure.yaml as fallback
67
+ try {
68
+ const yamlPath = join(outputDir, "document-structure.yaml");
69
+ const yamlExists = await pathExists(yamlPath);
70
+
71
+ if (yamlExists) {
72
+ const yamlContent = await readFile(yamlPath, "utf8");
73
+ if (yamlContent?.trim()) {
74
+ try {
75
+ const parsed = yamlParse(yamlContent);
76
+ if (parsed?.documents) {
77
+ return convertYamlToStructure(parsed);
78
+ }
79
+ } catch (parseError) {
80
+ console.error(`Failed to parse document-structure.yaml: ${parseError.message}`);
81
+ }
82
+ }
83
+ }
84
+ } catch (readError) {
85
+ if (readError.code !== "ENOENT") {
86
+ console.warn(`Error reading document-structure.yaml: ${readError.message}`);
87
+ }
88
+ }
89
+
90
+ return null;
91
+ }
92
+
93
+ /**
94
+ * Build a tree structure from a flat document structure array
95
+ * @param {Array} documentStructure - Flat document structure array
96
+ * @returns {Object} - Object with rootNodes array and nodeMap
97
+ */
98
+ export function buildDocumentTree(documentStructure) {
99
+ const nodeMap = new Map();
100
+ const rootNodes = [];
101
+
102
+ documentStructure.forEach((node) => {
103
+ nodeMap.set(node.path, {
104
+ ...node,
105
+ children: [],
106
+ });
107
+ });
108
+
109
+ documentStructure.forEach((node) => {
110
+ if (node.parentId) {
111
+ const parent = nodeMap.get(node.parentId);
112
+ if (parent) {
113
+ parent.children.push(nodeMap.get(node.path));
114
+ } else {
115
+ rootNodes.push(nodeMap.get(node.path));
116
+ }
117
+ } else {
118
+ rootNodes.push(nodeMap.get(node.path));
119
+ }
120
+ });
121
+
122
+ return { rootNodes, nodeMap };
123
+ }
124
+
125
+ /**
126
+ * Recursively generate sidebar text from document tree nodes
127
+ * @param {Array} nodes - Array of tree nodes
128
+ * @param {string} indent - Current indentation level
129
+ * @returns {string} - Formatted sidebar text
130
+ */
131
+ function walk(nodes, indent = "") {
132
+ let out = "";
133
+ for (const node of nodes) {
134
+ const realIndent = node.parentId === null ? "" : indent;
135
+
136
+ // Convert path to .md file path
137
+ // Keep directory structure instead of flattening
138
+ let linkPath;
139
+ if (node.path.endsWith(".md")) {
140
+ linkPath = node.path.startsWith("/") ? node.path : `/${node.path}`;
141
+ } else {
142
+ // Add .md suffix while preserving directory structure
143
+ const relPath = node.path.replace(/^\//, "");
144
+ linkPath = `/${relPath}.md`;
145
+ }
146
+
147
+ out += `${realIndent}* [${node.title}](${linkPath})\n`;
148
+
149
+ if (node.children && node.children.length > 0) {
150
+ out += walk(node.children, `${indent} `);
151
+ }
152
+ }
153
+ return out;
154
+ }
155
+
156
+ /**
157
+ * Generate sidebar markdown from document structure
158
+ * @param {Array} documentStructure - Flat document structure array
159
+ * @returns {string} - Formatted sidebar markdown
160
+ */
161
+ export function generateSidebar(documentStructure) {
162
+ const { rootNodes } = buildDocumentTree(documentStructure);
163
+ return walk(rootNodes).replace(/\n+$/, "");
164
+ }
165
+
166
+ /**
167
+ * Get main language files from docs directory
168
+ * @param {string} docsDir - Documentation directory
169
+ * @returns {Promise<Array>} - Array of markdown files
170
+ */
171
+ export async function getMainLanguageFiles(docsDir) {
172
+ try {
173
+ await access(docsDir);
174
+ } catch (error) {
175
+ if (error.code === "ENOENT") {
176
+ return [];
177
+ }
178
+ throw error;
179
+ }
180
+
181
+ const files = await readdir(docsDir);
182
+
183
+ const filteredFiles = [];
184
+ const filesSet = new Set(files);
185
+ const processedBaseNames = new Set();
186
+
187
+ for (const file of files) {
188
+ if (!file.endsWith(".md") || file === "_sidebar.md") {
189
+ continue;
190
+ }
191
+
192
+ const localeMatch = file.match(/^(.+)\.\w{2}(-\w+)?\.md$/);
193
+
194
+ if (localeMatch) {
195
+ const baseName = localeMatch[1];
196
+ const baseFileName = `${baseName}.md`;
197
+
198
+ if (!filesSet.has(baseFileName) && !processedBaseNames.has(baseName)) {
199
+ filteredFiles.push(file);
200
+ processedBaseNames.add(baseName);
201
+ }
202
+ } else {
203
+ const baseName = file.replace(/\.md$/, "");
204
+ if (!processedBaseNames.has(baseName)) {
205
+ filteredFiles.push(file);
206
+ processedBaseNames.add(baseName);
207
+ }
208
+ }
209
+ }
210
+
211
+ return filteredFiles;
212
+ }