hm-doc-tool 0.3.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.
Files changed (40) hide show
  1. package/README.md +230 -0
  2. package/dist/api-client.d.ts +44 -0
  3. package/dist/api-client.js +137 -0
  4. package/dist/common/config.d.ts +11 -0
  5. package/dist/common/config.js +70 -0
  6. package/dist/common/path-generator.d.ts +39 -0
  7. package/dist/common/path-generator.js +192 -0
  8. package/dist/common/tree-formatter.d.ts +19 -0
  9. package/dist/common/tree-formatter.js +129 -0
  10. package/dist/common/tree-processor.d.ts +35 -0
  11. package/dist/common/tree-processor.js +145 -0
  12. package/dist/common/types.d.ts +38 -0
  13. package/dist/common/types.js +5 -0
  14. package/dist/download/downloader.d.ts +46 -0
  15. package/dist/download/downloader.js +251 -0
  16. package/dist/download/index.d.ts +16 -0
  17. package/dist/download/index.js +168 -0
  18. package/dist/download/link-localizer.d.ts +13 -0
  19. package/dist/download/link-localizer.js +116 -0
  20. package/dist/download/markdown-converter.d.ts +1 -0
  21. package/dist/download/markdown-converter.js +96 -0
  22. package/dist/download/summary-generator.d.ts +46 -0
  23. package/dist/download/summary-generator.js +188 -0
  24. package/dist/download/turndown-rules.d.ts +2 -0
  25. package/dist/download/turndown-rules.js +394 -0
  26. package/dist/index.d.ts +2 -0
  27. package/dist/index.js +65 -0
  28. package/dist/tree/index.d.ts +11 -0
  29. package/dist/tree/index.js +91 -0
  30. package/dist/wiki/index.d.ts +7 -0
  31. package/dist/wiki/index.js +22 -0
  32. package/dist/wiki/wiki-generator.d.ts +3 -0
  33. package/dist/wiki/wiki-generator.js +357 -0
  34. package/dist/wiki/wiki-types.d.ts +61 -0
  35. package/dist/wiki/wiki-types.js +3 -0
  36. package/dist/wiki/wiki-utils.d.ts +33 -0
  37. package/dist/wiki/wiki-utils.js +180 -0
  38. package/docs_catalog.json +28 -0
  39. package/package.json +29 -0
  40. package/wiki_config.json +198 -0
@@ -0,0 +1,192 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.safeFileName = safeFileName;
4
+ exports.buildOutputPaths = buildOutputPaths;
5
+ exports.collectDirectories = collectDirectories;
6
+ exports.saveCatalogJson = saveCatalogJson;
7
+ /**
8
+ * 路径和下载任务生成模块
9
+ *
10
+ * 职责:
11
+ * 1. 文件名安全化
12
+ * 2. 计算输出路径并生成下载任务列表
13
+ * 3. 收集输出所需的全部目录路径
14
+ * 4. 保存目录树 JSON 文件
15
+ */
16
+ const node_fs_1 = require("node:fs");
17
+ const node_path_1 = require("node:path");
18
+ const api_client_js_1 = require("../api-client.js");
19
+ const config_js_1 = require("./config.js");
20
+ const tree_processor_js_1 = require("./tree-processor.js");
21
+ // =============================================================================
22
+ // 文件名安全化
23
+ // =============================================================================
24
+ /** 将文件/目录名中的非法字符及空格替换为下划线 */
25
+ function safeFileName(name) {
26
+ return name.replace(/[<>:"/\\|?*\s]/g, "_");
27
+ }
28
+ // =============================================================================
29
+ // 路径计算(公共逻辑)
30
+ // =============================================================================
31
+ /** 检查节点是否有关联文档(relateDocument 非空且非 "_") */
32
+ function hasValidRelateDocument(node) {
33
+ return !!node.relateDocument && node.relateDocument !== "_";
34
+ }
35
+ /**
36
+ * 计算根路径和所有节点的相对路径
37
+ *
38
+ * 路径计算使用全量 nodes 以保证父子关系完整;
39
+ * taskNodes 用于决定需要为哪些节点计算路径(可以是过滤后的子集)。
40
+ *
41
+ * @param nodes - 全量平铺节点(用于构建 nodeMap,保证父子关系完整)
42
+ * @param rootId - 指定子树根节点 ID,undefined 表示整个 catalog
43
+ * @param catalogName - catalog 名称
44
+ * @param outputBase - 输出根目录
45
+ * @param taskNodes - 需要计算路径的节点子集(如经过 --no-cpp 过滤后的)
46
+ * @returns 根路径和节点相对路径映射
47
+ */
48
+ function buildPathMap(nodes, rootId, catalogName, outputBase, taskNodes) {
49
+ const nodeMap = new Map(nodes.map((n) => [n.nodeId, n]));
50
+ const nodesForPath = taskNodes || nodes;
51
+ // 确定输出根路径:指定了 rootId 则用节点名,否则用 catalog 显示名
52
+ const catalogNameResult = (0, config_js_1.findCatalogName)(catalogName);
53
+ const displayName = catalogNameResult || catalogName;
54
+ let rootPath;
55
+ if (rootId) {
56
+ const rootNode = nodeMap.get(rootId);
57
+ rootPath = rootNode
58
+ ? `${outputBase}/${safeFileName(rootNode.nodeName)}`
59
+ : `${outputBase}/${safeFileName(displayName)}`;
60
+ }
61
+ else {
62
+ rootPath = `${outputBase}/${safeFileName(displayName)}`;
63
+ }
64
+ // 递归计算每个节点相对于 rootPath 的路径
65
+ const pathMap = new Map();
66
+ function buildPath(nodeId) {
67
+ if (pathMap.has(nodeId))
68
+ return pathMap.get(nodeId);
69
+ const node = nodeMap.get(nodeId);
70
+ if (!node)
71
+ return "";
72
+ // rootId 节点自身:路径为空(rootPath 已包含其名称)
73
+ if (nodeId === rootId) {
74
+ pathMap.set(nodeId, "");
75
+ return "";
76
+ }
77
+ // 顶级节点(无 parent 或 parent 不在 nodeMap 中):返回自身名称
78
+ if (!node.parent || !nodeMap.has(node.parent)) {
79
+ const relPath = safeFileName(node.nodeName);
80
+ pathMap.set(nodeId, relPath);
81
+ return relPath;
82
+ }
83
+ // 普通节点:递归拼接父路径
84
+ const parentPath = buildPath(node.parent);
85
+ const relPath = parentPath
86
+ ? `${parentPath}/${safeFileName(node.nodeName)}`
87
+ : safeFileName(node.nodeName);
88
+ pathMap.set(nodeId, relPath);
89
+ return relPath;
90
+ }
91
+ for (const node of nodesForPath)
92
+ buildPath(node.nodeId);
93
+ return { rootPath, pathMap };
94
+ }
95
+ /**
96
+ * 计算输出路径并生成下载任务列表
97
+ *
98
+ * @param nodes - 全量平铺节点(用于路径计算)
99
+ * @param rootId - 指定子树根节点 ID,undefined 表示整个 catalog
100
+ * @param catalogName - catalog 名称
101
+ * @param outputBase - 输出根目录
102
+ * @param taskNodes - 需要生成任务的节点子集(如经过 --no-cpp 过滤后的)
103
+ * @returns 下载任务列表
104
+ */
105
+ function buildOutputPaths(nodes, rootId, catalogName, outputBase, taskNodes) {
106
+ const { rootPath, pathMap } = buildPathMap(nodes, rootId, catalogName, outputBase, taskNodes);
107
+ const nodesForTasks = taskNodes || nodes;
108
+ const tasks = [];
109
+ for (const node of nodesForTasks) {
110
+ const relPath = pathMap.get(node.nodeId) ?? "";
111
+ if (node.isLeaf || node.children.length === 0) {
112
+ // 叶子节点 → 直接在父目录下生成 .md
113
+ if (hasValidRelateDocument(node)) {
114
+ tasks.push({
115
+ nodeId: node.nodeId,
116
+ nodeName: node.nodeName,
117
+ relateDocument: node.relateDocument,
118
+ outputDir: rootPath + (relPath ? `/${relPath}` : "").replace(/\/[^/]+$/, ""),
119
+ fileName: safeFileName(node.nodeName) + ".md",
120
+ isLeaf: true,
121
+ });
122
+ }
123
+ }
124
+ else {
125
+ // 非叶子节点 → 创建同名文件夹,若有 relateDocument 则在文件夹内放自身 .md
126
+ if (hasValidRelateDocument(node)) {
127
+ tasks.push({
128
+ nodeId: node.nodeId,
129
+ nodeName: node.nodeName,
130
+ relateDocument: node.relateDocument,
131
+ outputDir: `${rootPath}/${relPath}`,
132
+ fileName: safeFileName(node.nodeName) + ".md",
133
+ isLeaf: false,
134
+ });
135
+ }
136
+ }
137
+ }
138
+ return tasks;
139
+ }
140
+ /**
141
+ * 收集输出所需的全部目录路径(去重、按深度排序)
142
+ *
143
+ * @param nodes - 全量平铺节点
144
+ * @param rootId - 指定子树根节点 ID
145
+ * @param catalogName - catalog 名称
146
+ * @param outputBase - 输出根目录
147
+ * @param taskNodes - 需要创建目录的节点子集
148
+ * @returns 目录路径数组,按深度从浅到深排序
149
+ */
150
+ function collectDirectories(nodes, rootId, catalogName, outputBase, taskNodes) {
151
+ const { rootPath, pathMap } = buildPathMap(nodes, rootId, catalogName, outputBase, taskNodes);
152
+ const nodesForTasks = taskNodes || nodes;
153
+ const dirs = new Set();
154
+ dirs.add(rootPath);
155
+ for (const node of nodesForTasks) {
156
+ const relPath = pathMap.get(node.nodeId) ?? "";
157
+ if (!node.isLeaf && node.children.length > 0) {
158
+ // 非叶子节点 → 添加其文件夹路径
159
+ dirs.add(`${rootPath}/${relPath}`);
160
+ }
161
+ else {
162
+ // 叶子节点 → 添加其父目录路径
163
+ const lastSlash = relPath.lastIndexOf("/");
164
+ if (lastSlash > 0) {
165
+ dirs.add(`${rootPath}/${relPath.substring(0, lastSlash)}`);
166
+ }
167
+ }
168
+ }
169
+ return Array.from(dirs).sort((a, b) => a.split("/").length - b.split("/").length);
170
+ }
171
+ // =============================================================================
172
+ // JSON 保存
173
+ // =============================================================================
174
+ /**
175
+ * 保存目录树 JSON 文件(参考 fetch_catalog.py)
176
+ * 输出两个文件:{catalog}_tree.json(原始树结构)和 {catalog}_nodes.json(平铺节点)
177
+ *
178
+ * @param catalogName - catalog 名称
179
+ * @param outputDir - 输出目录路径
180
+ * @returns 保存的文件路径
181
+ */
182
+ async function saveCatalogJson(catalogName, outputDir) {
183
+ const tree = await (0, api_client_js_1.getCatalogTree)(catalogName);
184
+ const absDir = (0, node_path_1.resolve)(outputDir);
185
+ (0, node_fs_1.mkdirSync)(absDir, { recursive: true });
186
+ const treePath = (0, node_path_1.resolve)(absDir, `${catalogName}_tree.json`);
187
+ (0, node_fs_1.writeFileSync)(treePath, JSON.stringify(tree, null, 2), "utf-8");
188
+ const flat = (0, tree_processor_js_1.flattenNodes)(tree);
189
+ const flatPath = (0, node_path_1.resolve)(absDir, `${catalogName}_nodes.json`);
190
+ (0, node_fs_1.writeFileSync)(flatPath, JSON.stringify(flat, null, 2), "utf-8");
191
+ return { treePath, flatPath, treeCount: tree.length, flatCount: flat.length };
192
+ }
@@ -0,0 +1,19 @@
1
+ import type { FlatNode } from "./types.js";
2
+ /**
3
+ * 生成树形文本输出(使用 box-drawing 字符:├── └── │)
4
+ *
5
+ * 规则:
6
+ * - 指定了 rootId → 该节点作为根
7
+ * - 未指定 rootId → catalog 中文名作为根,其下挂所有顶层节点
8
+ * - 非叶子节点显示为 "节点名/",若有关联文档则在子列表首行显示 "节点名.md"
9
+ * - 叶子节点显示为 "节点名.md"
10
+ *
11
+ * @param nodes - 全量平铺节点
12
+ * @param rootId - 指定子树根节点 ID
13
+ * @param catalogName - catalog 名称
14
+ * @param skipIds - 保留参数(未使用,过滤逻辑已内置)
15
+ * @param leafOnly - 是否只显示叶子节点
16
+ * @param maxDepth - 最大显示深度
17
+ * @param noCpp - 是否过滤 C++ 相关文档
18
+ */
19
+ export declare function buildTreeOutput(nodes: FlatNode[], rootId: string | undefined, catalogName: string, _skipIds: Set<string>, leafOnly: boolean, maxDepth?: number, noCpp?: boolean): string;
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildTreeOutput = buildTreeOutput;
4
+ /**
5
+ * 树形文本格式化输出模块
6
+ *
7
+ * 职责:
8
+ * 1. 生成树形文本输出(box-drawing 字符:├── └── │)
9
+ */
10
+ const config_js_1 = require("./config.js");
11
+ const tree_processor_js_1 = require("./tree-processor.js");
12
+ const tree_processor_js_2 = require("./tree-processor.js");
13
+ /** 检查节点是否有关联文档(relateDocument 非空且非 "_") */
14
+ function hasValidRelateDocument(node) {
15
+ return !!node.relateDocument && node.relateDocument !== "_";
16
+ }
17
+ /**
18
+ * 生成树形文本输出(使用 box-drawing 字符:├── └── │)
19
+ *
20
+ * 规则:
21
+ * - 指定了 rootId → 该节点作为根
22
+ * - 未指定 rootId → catalog 中文名作为根,其下挂所有顶层节点
23
+ * - 非叶子节点显示为 "节点名/",若有关联文档则在子列表首行显示 "节点名.md"
24
+ * - 叶子节点显示为 "节点名.md"
25
+ *
26
+ * @param nodes - 全量平铺节点
27
+ * @param rootId - 指定子树根节点 ID
28
+ * @param catalogName - catalog 名称
29
+ * @param skipIds - 保留参数(未使用,过滤逻辑已内置)
30
+ * @param leafOnly - 是否只显示叶子节点
31
+ * @param maxDepth - 最大显示深度
32
+ * @param noCpp - 是否过滤 C++ 相关文档
33
+ */
34
+ function buildTreeOutput(nodes, rootId, catalogName, _skipIds, leafOnly, maxDepth, noCpp = false) {
35
+ const nodeMap = new Map(nodes.map((n) => [n.nodeId, n]));
36
+ // 收集子树或全量
37
+ let subtreeNodes;
38
+ if (rootId) {
39
+ subtreeNodes = (0, tree_processor_js_1.collectSubtree)(nodes, rootId);
40
+ }
41
+ else {
42
+ subtreeNodes = [...nodes];
43
+ }
44
+ // 过滤 C++(含后代)
45
+ if (noCpp) {
46
+ const skipSet = (0, tree_processor_js_2.buildCppSkipSet)(nodes, catalogName, noCpp);
47
+ subtreeNodes = subtreeNodes.filter((n) => !skipSet.has(n.nodeId));
48
+ }
49
+ // 过滤 leafOnly
50
+ if (leafOnly) {
51
+ subtreeNodes = subtreeNodes.filter((n) => n.isLeaf || n.children.length === 0);
52
+ }
53
+ const filteredIds = new Set(subtreeNodes.map((n) => n.nodeId));
54
+ // 找出根级节点(parent 不在过滤后集合中,或 parent 为空,或是 rootId 自身)
55
+ const rootNodes = subtreeNodes.filter((n) => !n.parent || !filteredIds.has(n.parent) || n.nodeId === rootId);
56
+ const lines = [];
57
+ /** 递归打印节点及其子节点 */
58
+ function printNode(node, prefix, isLast, depth) {
59
+ if (maxDepth !== undefined && depth > maxDepth)
60
+ return;
61
+ const connector = isLast ? "└── " : "├── ";
62
+ const isLeafNode = node.isLeaf || node.children.length === 0;
63
+ const hasDoc = hasValidRelateDocument(node);
64
+ const label = isLeafNode ? `${node.nodeName}.md` : `${node.nodeName}/`;
65
+ lines.push(`${prefix}${connector}${label}`);
66
+ if (!isLeafNode) {
67
+ const childPrefix = prefix + (isLast ? " " : "│ ");
68
+ // 构建子项列表:先放自身 .md,再放子节点
69
+ const children = [];
70
+ if (hasDoc)
71
+ children.push({ node: null, isOwnMd: true });
72
+ const childNodes = node.children
73
+ .map((id) => nodeMap.get(id))
74
+ .filter((n) => !!n && filteredIds.has(n.nodeId));
75
+ for (const child of childNodes)
76
+ children.push({ node: child, isOwnMd: false });
77
+ for (let i = 0; i < children.length; i++) {
78
+ const isLastChild = i === children.length - 1;
79
+ const childConnector = isLastChild ? "└── " : "├── ";
80
+ if (children[i].isOwnMd) {
81
+ lines.push(`${childPrefix}${childConnector}${node.nodeName}.md`);
82
+ }
83
+ else {
84
+ printNode(children[i].node, childPrefix, isLastChild, depth + 1);
85
+ }
86
+ }
87
+ }
88
+ }
89
+ if (rootId) {
90
+ // 指定了节点 → 该节点作为根
91
+ const rootNode = nodeMap.get(rootId);
92
+ if (rootNode) {
93
+ const isLeafNode = rootNode.isLeaf || rootNode.children.length === 0;
94
+ const hasDoc = hasValidRelateDocument(rootNode);
95
+ lines.push(isLeafNode ? `${rootNode.nodeName}.md` : `${rootNode.nodeName}/`);
96
+ if (!isLeafNode) {
97
+ const children = [];
98
+ if (hasDoc)
99
+ children.push({ node: null, isOwnMd: true });
100
+ const childNodes = rootNode.children
101
+ .map((id) => nodeMap.get(id))
102
+ .filter((n) => !!n && filteredIds.has(n.nodeId));
103
+ for (const child of childNodes)
104
+ children.push({ node: child, isOwnMd: false });
105
+ for (let i = 0; i < children.length; i++) {
106
+ const isLastChild = i === children.length - 1;
107
+ const childConnector = isLastChild ? "└── " : "├── ";
108
+ if (children[i].isOwnMd) {
109
+ lines.push(`${childConnector}${rootNode.nodeName}.md`);
110
+ }
111
+ else {
112
+ printNode(children[i].node, "", isLastChild, 1);
113
+ }
114
+ }
115
+ }
116
+ }
117
+ }
118
+ else {
119
+ // 未指定节点 → displayName 作为根,其下挂所有顶层节点
120
+ const catalogNameResult = (0, config_js_1.findCatalogName)(catalogName);
121
+ const displayName = catalogNameResult || catalogName;
122
+ lines.push(`${displayName}/`);
123
+ for (let i = 0; i < rootNodes.length; i++) {
124
+ const isLast = i === rootNodes.length - 1;
125
+ printNode(rootNodes[i], "", isLast, 1);
126
+ }
127
+ }
128
+ return lines.join("\n");
129
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * 树结构处理模块
3
+ *
4
+ * 职责:
5
+ * 1. 将嵌套 CatalogNode[] 平铺为 FlatNode[]
6
+ * 2. 节点查找、子树收集
7
+ * 3. C++ 文档过滤(--no-cpp)
8
+ */
9
+ import { type CatalogNode } from "../api-client.js";
10
+ import type { FlatNode } from "./types.js";
11
+ /** 将嵌套 CatalogNode[] 递归平铺为 FlatNode[] */
12
+ export declare function flattenNodes(nodes: CatalogNode[]): FlatNode[];
13
+ /** 在平铺列表中按 nodeId 或 nodeName 查找节点,优先 nodeId */
14
+ export declare function findNode(nodes: FlatNode[], nodeId?: string, nodeName?: string): FlatNode | undefined;
15
+ /** 收集 rootId 下的所有后代节点(含 rootId 自身) */
16
+ export declare function collectSubtree(nodes: FlatNode[], rootId: string): FlatNode[];
17
+ /** 收集多个根节点下所有后代 nodeId 集合(含根自身) */
18
+ export declare function collectSubtreeIds(nodes: FlatNode[], rootIds: Set<string>): Set<string>;
19
+ /**
20
+ * 获取目录树并平铺
21
+ * @param catalogName - catalog 名称
22
+ * @returns 平铺后的节点列表
23
+ */
24
+ export declare function fetchCatalogTree(catalogName: string): Promise<FlatNode[]>;
25
+ /**
26
+ * 构建 harmonyos-references 中 "C API" 子树的 nodeId 集合
27
+ * 仅对 harmonyos-references catalog 生效,其他 catalog 返回空集
28
+ */
29
+ export declare function buildCppSkipIds(nodes: FlatNode[], catalogName: string): Set<string>;
30
+ /**
31
+ * 构建完整的 C++ 过滤集合,包含:
32
+ * 1. harmonyos-references 中 "C API" 整棵子树
33
+ * 2. 所有 catalog 中 nodeName 含 "c++" 或 ".h" 的节点及其全部后代
34
+ */
35
+ export declare function buildCppSkipSet(nodes: FlatNode[], catalogName: string, noCpp: boolean): Set<string>;
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.flattenNodes = flattenNodes;
4
+ exports.findNode = findNode;
5
+ exports.collectSubtree = collectSubtree;
6
+ exports.collectSubtreeIds = collectSubtreeIds;
7
+ exports.fetchCatalogTree = fetchCatalogTree;
8
+ exports.buildCppSkipIds = buildCppSkipIds;
9
+ exports.buildCppSkipSet = buildCppSkipSet;
10
+ /**
11
+ * 树结构处理模块
12
+ *
13
+ * 职责:
14
+ * 1. 将嵌套 CatalogNode[] 平铺为 FlatNode[]
15
+ * 2. 节点查找、子树收集
16
+ * 3. C++ 文档过滤(--no-cpp)
17
+ */
18
+ const api_client_js_1 = require("../api-client.js");
19
+ // =============================================================================
20
+ // 树结构操作
21
+ // =============================================================================
22
+ /** 将嵌套 CatalogNode[] 递归平铺为 FlatNode[] */
23
+ function flattenNodes(nodes) {
24
+ const result = [];
25
+ function walk(list) {
26
+ for (const node of list) {
27
+ const childIds = node.children?.map((c) => c.nodeId) ?? [];
28
+ result.push({
29
+ nodeId: node.nodeId,
30
+ nodeName: node.nodeName,
31
+ relateDocument: node.relateDocument ?? null,
32
+ parent: node.parent ?? null,
33
+ children: childIds,
34
+ isLeaf: node.isLeaf ?? childIds.length === 0,
35
+ });
36
+ if (node.children?.length)
37
+ walk(node.children);
38
+ }
39
+ }
40
+ walk(nodes);
41
+ return result;
42
+ }
43
+ /** 在平铺列表中按 nodeId 或 nodeName 查找节点,优先 nodeId */
44
+ function findNode(nodes, nodeId, nodeName) {
45
+ if (nodeId)
46
+ return nodes.find((n) => n.nodeId === nodeId);
47
+ if (nodeName)
48
+ return nodes.find((n) => n.nodeName === nodeName);
49
+ return undefined;
50
+ }
51
+ /** 收集 rootId 下的所有后代节点(含 rootId 自身) */
52
+ function collectSubtree(nodes, rootId) {
53
+ const nodeMap = new Map(nodes.map((n) => [n.nodeId, n]));
54
+ const result = [];
55
+ function walk(id) {
56
+ const node = nodeMap.get(id);
57
+ if (!node)
58
+ return;
59
+ result.push(node);
60
+ for (const childId of node.children)
61
+ walk(childId);
62
+ }
63
+ walk(rootId);
64
+ return result;
65
+ }
66
+ /** 收集多个根节点下所有后代 nodeId 集合(含根自身) */
67
+ function collectSubtreeIds(nodes, rootIds) {
68
+ const nodeMap = new Map(nodes.map((n) => [n.nodeId, n]));
69
+ const result = new Set();
70
+ function walk(id) {
71
+ if (result.has(id))
72
+ return;
73
+ result.add(id);
74
+ const node = nodeMap.get(id);
75
+ if (!node)
76
+ return;
77
+ for (const childId of node.children)
78
+ walk(childId);
79
+ }
80
+ for (const rootId of rootIds)
81
+ walk(rootId);
82
+ return result;
83
+ }
84
+ /**
85
+ * 获取目录树并平铺
86
+ * @param catalogName - catalog 名称
87
+ * @returns 平铺后的节点列表
88
+ */
89
+ async function fetchCatalogTree(catalogName) {
90
+ const tree = await (0, api_client_js_1.getCatalogTree)(catalogName);
91
+ return flattenNodes(tree);
92
+ }
93
+ // =============================================================================
94
+ // C++ 过滤
95
+ // =============================================================================
96
+ /**
97
+ * 构建 harmonyos-references 中 "C API" 子树的 nodeId 集合
98
+ * 仅对 harmonyos-references catalog 生效,其他 catalog 返回空集
99
+ */
100
+ function buildCppSkipIds(nodes, catalogName) {
101
+ if (catalogName !== "harmonyos-references")
102
+ return new Set();
103
+ const cApiIds = nodes.filter((n) => n.nodeName === "C API").map((n) => n.nodeId);
104
+ if (cApiIds.length === 0)
105
+ return new Set();
106
+ return collectSubtreeIds(nodes, new Set(cApiIds));
107
+ }
108
+ /**
109
+ * 构建完整的 C++ 过滤集合,包含:
110
+ * 1. harmonyos-references 中 "C API" 整棵子树
111
+ * 2. 所有 catalog 中 nodeName 含 "c++" 或 ".h" 的节点及其全部后代
112
+ */
113
+ function buildCppSkipSet(nodes, catalogName, noCpp) {
114
+ if (!noCpp)
115
+ return new Set();
116
+ const nodeMap = new Map(nodes.map((n) => [n.nodeId, n]));
117
+ const skipIds = buildCppSkipIds(nodes, catalogName);
118
+ // 找到所有 nodeName 包含 c++ 或 .h 的节点
119
+ const cppNodeIds = [];
120
+ for (const node of nodes) {
121
+ if (skipIds.has(node.nodeId))
122
+ continue;
123
+ const name = node.nodeName.toLowerCase();
124
+ if (name.includes("c++") || name.includes(".h")) {
125
+ cppNodeIds.push(node.nodeId);
126
+ }
127
+ }
128
+ // 收集这些节点及其全部后代
129
+ const result = new Set(skipIds);
130
+ for (const nodeId of cppNodeIds) {
131
+ collectDescendants(nodeMap, nodeId, result);
132
+ }
133
+ return result;
134
+ }
135
+ /** 递归收集 nodeId 及其所有后代加入 result 集合 */
136
+ function collectDescendants(nodeMap, nodeId, result) {
137
+ if (result.has(nodeId))
138
+ return;
139
+ result.add(nodeId);
140
+ const node = nodeMap.get(nodeId);
141
+ if (!node)
142
+ return;
143
+ for (const childId of node.children)
144
+ collectDescendants(nodeMap, childId, result);
145
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * 共享类型定义
3
+ */
4
+ /** docs_catalog.json 中的 catalog 项 */
5
+ export interface CatalogItem {
6
+ id: string;
7
+ name: string;
8
+ }
9
+ /** docs_catalog.json 中的顶层分类 */
10
+ export interface TopLevelCatalog {
11
+ top_level: string;
12
+ catalogs: CatalogItem[];
13
+ }
14
+ /** 包含顶层分类信息的 catalog */
15
+ export interface CatalogWithTopLevel {
16
+ topLevel: string;
17
+ catalog: CatalogItem;
18
+ }
19
+ /** 平铺后的节点,children 只存 nodeId 字符串 */
20
+ export interface FlatNode {
21
+ nodeId: string;
22
+ nodeName: string;
23
+ relateDocument: string | null;
24
+ parent: string | null;
25
+ children: string[];
26
+ isLeaf: boolean;
27
+ }
28
+ /** 下载任务:一个节点对应一个待下载/写入的 .md 文件 */
29
+ export interface OutputTask {
30
+ nodeId: string;
31
+ nodeName: string;
32
+ relateDocument: string | null;
33
+ /** 写入目录的绝对路径 */
34
+ outputDir: string;
35
+ /** .md 文件名(含扩展名) */
36
+ fileName: string;
37
+ isLeaf: boolean;
38
+ }
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ /**
3
+ * 共享类型定义
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,46 @@
1
+ import type { FlatNode, OutputTask } from "../common/types.js";
2
+ /**
3
+ * 通过 URL 下载单篇文档(不建目录,直接写入 outputDir)
4
+ *
5
+ * @param objectId - 文档 objectId
6
+ * @param catalogName - catalog 名称
7
+ * @param nodeName - 节点名称(用作文件名)
8
+ * @param outputDir - 输出目录
9
+ */
10
+ export declare function downloadSingle(objectId: string, catalogName: string, nodeName: string, outputDir: string): Promise<void>;
11
+ /** 下载选项 */
12
+ export interface DownloadOptions {
13
+ outputDir: string;
14
+ concurrency: number;
15
+ leafOnly: boolean;
16
+ noCpp: boolean;
17
+ dryRun: boolean;
18
+ /** 顶层分类名称(如 "application"),用于构建输出路径 */
19
+ topLevel?: string;
20
+ }
21
+ /**
22
+ * 下载文档子树
23
+ *
24
+ * 完整流程:
25
+ * 1. 获取目录树 → 平铺为 FlatNode[]
26
+ * 2. 定位目标节点(按 nodeId 或 nodeName)
27
+ * 3. 收集子树节点
28
+ * 4. C++ 过滤(--no-cpp,含后代)
29
+ * 5. leafOnly 过滤
30
+ * 6. 计算输出路径(用全量 nodes 算路径,用过滤后 subtreeNodes 生成任务)
31
+ * 7. --dry-run 则打印树形结构后返回
32
+ * 8. 创建所有目录(先建目录避免并发竞态)
33
+ * 9. 并发下载文档并写入文件
34
+ * 10. 输出汇总(成功数、失败列表)
35
+ *
36
+ * @param catalogName - catalog 名称
37
+ * @param rootId - 子树根节点 ID,undefined 表示整个 catalog
38
+ * @param nodeName - 按名称定位节点(与 nodeId 二选一)
39
+ * @param nodeId - 按 ID 定位节点(优先于 nodeName)
40
+ * @param options - 下载选项
41
+ */
42
+ export declare function downloadSubtree(catalogName: string, rootId: string | undefined, nodeName: string | undefined, nodeId: string | undefined, options: DownloadOptions): Promise<{
43
+ tasks: OutputTask[];
44
+ nodes: FlatNode[];
45
+ outputDir: string;
46
+ } | undefined>;