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.
- package/README.md +230 -0
- package/dist/api-client.d.ts +44 -0
- package/dist/api-client.js +137 -0
- package/dist/common/config.d.ts +11 -0
- package/dist/common/config.js +70 -0
- package/dist/common/path-generator.d.ts +39 -0
- package/dist/common/path-generator.js +192 -0
- package/dist/common/tree-formatter.d.ts +19 -0
- package/dist/common/tree-formatter.js +129 -0
- package/dist/common/tree-processor.d.ts +35 -0
- package/dist/common/tree-processor.js +145 -0
- package/dist/common/types.d.ts +38 -0
- package/dist/common/types.js +5 -0
- package/dist/download/downloader.d.ts +46 -0
- package/dist/download/downloader.js +251 -0
- package/dist/download/index.d.ts +16 -0
- package/dist/download/index.js +168 -0
- package/dist/download/link-localizer.d.ts +13 -0
- package/dist/download/link-localizer.js +116 -0
- package/dist/download/markdown-converter.d.ts +1 -0
- package/dist/download/markdown-converter.js +96 -0
- package/dist/download/summary-generator.d.ts +46 -0
- package/dist/download/summary-generator.js +188 -0
- package/dist/download/turndown-rules.d.ts +2 -0
- package/dist/download/turndown-rules.js +394 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +65 -0
- package/dist/tree/index.d.ts +11 -0
- package/dist/tree/index.js +91 -0
- package/dist/wiki/index.d.ts +7 -0
- package/dist/wiki/index.js +22 -0
- package/dist/wiki/wiki-generator.d.ts +3 -0
- package/dist/wiki/wiki-generator.js +357 -0
- package/dist/wiki/wiki-types.d.ts +61 -0
- package/dist/wiki/wiki-types.js +3 -0
- package/dist/wiki/wiki-utils.d.ts +33 -0
- package/dist/wiki/wiki-utils.js +180 -0
- package/docs_catalog.json +28 -0
- package/package.json +29 -0
- package/wiki_config.json +198 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
/**
|
|
8
|
+
* CLI 入口
|
|
9
|
+
*
|
|
10
|
+
* 三个子命令:
|
|
11
|
+
* - download: 下载文档到本地 Markdown 文件
|
|
12
|
+
* - tree: 查看目录树结构(不下载)
|
|
13
|
+
* - wiki: 从JSON树文件生成Wiki页面
|
|
14
|
+
*/
|
|
15
|
+
const commander_1 = require("commander");
|
|
16
|
+
const index_js_1 = require("./download/index.js");
|
|
17
|
+
const index_js_2 = require("./tree/index.js");
|
|
18
|
+
const index_js_3 = require("./wiki/index.js");
|
|
19
|
+
const package_json_1 = __importDefault(require("../package.json"));
|
|
20
|
+
const program = new commander_1.Command();
|
|
21
|
+
program
|
|
22
|
+
.name(package_json_1.default.name)
|
|
23
|
+
.description(package_json_1.default.description)
|
|
24
|
+
.version(package_json_1.default.version);
|
|
25
|
+
// =============================================================================
|
|
26
|
+
// download 子命令
|
|
27
|
+
// =============================================================================
|
|
28
|
+
program
|
|
29
|
+
.command("download")
|
|
30
|
+
.description("Download HarmonyOS docs to local Markdown files")
|
|
31
|
+
.option("-c, --catalog <catalogName...>", "catalog name(s), can specify multiple")
|
|
32
|
+
.option("-n, --node-name <name>", "target node name")
|
|
33
|
+
.option("-i, --node-id <id>", "target node id")
|
|
34
|
+
.option("-u, --url <url>", "download from URL (auto-detect catalog and node)")
|
|
35
|
+
.option("-o, --output <dir>", "output directory", "./docs")
|
|
36
|
+
.option("--leaf-only", "only download leaf nodes", false)
|
|
37
|
+
.option("--no-cpp", "exclude C++ related docs")
|
|
38
|
+
.option("--concurrency <n>", "download concurrency", "5")
|
|
39
|
+
.option("--dry-run", "preview directory tree without downloading", false)
|
|
40
|
+
.option("-s, --summary", "generate summary JSON file after download", true)
|
|
41
|
+
.option("-l, --localize", "localize doc links to relative paths", false)
|
|
42
|
+
.action(index_js_1.handleDownload);
|
|
43
|
+
// =============================================================================
|
|
44
|
+
// tree 子命令
|
|
45
|
+
// =============================================================================
|
|
46
|
+
program
|
|
47
|
+
.command("tree")
|
|
48
|
+
.description("Display catalog tree structure")
|
|
49
|
+
.option("-c, --catalog <catalogName>", "catalog name (if not specified, show all catalogs from docs_catalog.json)")
|
|
50
|
+
.option("-n, --node-name <name>", "target node name")
|
|
51
|
+
.option("-i, --node-id <id>", "target node id")
|
|
52
|
+
.option("-d, --depth <n>", "display depth limit")
|
|
53
|
+
.option("-o, --output <dir>", "output directory for JSON", "./docs")
|
|
54
|
+
.option("--no-cpp", "exclude C++ related nodes")
|
|
55
|
+
.action(index_js_2.handleTree);
|
|
56
|
+
// =============================================================================
|
|
57
|
+
// wiki 子命令
|
|
58
|
+
// =============================================================================
|
|
59
|
+
program
|
|
60
|
+
.command("wiki")
|
|
61
|
+
.description("Generate Wiki pages from JSON tree files")
|
|
62
|
+
.option("-c, --config <path>", "config file path (default: built-in wiki_config.json)")
|
|
63
|
+
.option("-d, --docs <dir>", "docs directory path", "./docs")
|
|
64
|
+
.action(index_js_3.handleWiki);
|
|
65
|
+
program.parse();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Tree 命令选项 */
|
|
2
|
+
export interface TreeOptions {
|
|
3
|
+
catalog?: string;
|
|
4
|
+
nodeName?: string;
|
|
5
|
+
nodeId?: string;
|
|
6
|
+
depth?: string;
|
|
7
|
+
output: string;
|
|
8
|
+
cpp: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** Tree 命令处理函数 */
|
|
11
|
+
export declare function handleTree(opts: TreeOptions): Promise<void>;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleTree = handleTree;
|
|
4
|
+
/**
|
|
5
|
+
* Tree 命令入口
|
|
6
|
+
*
|
|
7
|
+
* 职责:
|
|
8
|
+
* 1. 处理 CLI 选项
|
|
9
|
+
* 2. 调用 common/ 模块执行目录树显示
|
|
10
|
+
*/
|
|
11
|
+
const tree_processor_js_1 = require("../common/tree-processor.js");
|
|
12
|
+
const path_generator_js_1 = require("../common/path-generator.js");
|
|
13
|
+
const tree_formatter_js_1 = require("../common/tree-formatter.js");
|
|
14
|
+
const config_js_1 = require("../common/config.js");
|
|
15
|
+
/** Tree 命令处理函数 */
|
|
16
|
+
async function handleTree(opts) {
|
|
17
|
+
try {
|
|
18
|
+
const catalogName = opts.catalog;
|
|
19
|
+
const nodeName = opts.nodeName;
|
|
20
|
+
const nodeId = opts.nodeId;
|
|
21
|
+
const depth = opts.depth ? parseInt(opts.depth, 10) : undefined;
|
|
22
|
+
const noCpp = opts.cpp === false;
|
|
23
|
+
if (catalogName) {
|
|
24
|
+
// 单 catalog 模式
|
|
25
|
+
// 仅有 -c 参数时,额外输出 JSON 文件
|
|
26
|
+
const isJsonMode = !nodeName && !nodeId && !depth && !noCpp;
|
|
27
|
+
if (isJsonMode) {
|
|
28
|
+
const result = await (0, path_generator_js_1.saveCatalogJson)(catalogName, opts.output);
|
|
29
|
+
console.log(`已保存树结构 (${result.treeCount} 个顶层节点) -> ${result.treePath}`);
|
|
30
|
+
console.log(`已保存平铺节点列表 (${result.flatCount} 个节点) -> ${result.flatPath}`);
|
|
31
|
+
}
|
|
32
|
+
// 始终输出树形结构
|
|
33
|
+
console.log(`\n正在获取目录树: ${catalogName} ...`);
|
|
34
|
+
const flatNodes = await (0, tree_processor_js_1.fetchCatalogTree)(catalogName);
|
|
35
|
+
// 定位目标节点
|
|
36
|
+
let rootId;
|
|
37
|
+
if (nodeName || nodeId) {
|
|
38
|
+
const target = (0, tree_processor_js_1.findNode)(flatNodes, nodeId, nodeName);
|
|
39
|
+
if (!target) {
|
|
40
|
+
console.error(`错误: 找不到节点: ${nodeId || nodeName}`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
rootId = target.nodeId;
|
|
44
|
+
}
|
|
45
|
+
// buildTreeOutput 内部处理 C++ 过滤,skipIds 参数保留但不再使用
|
|
46
|
+
const output = (0, tree_formatter_js_1.buildTreeOutput)(flatNodes, rootId, catalogName, new Set(), false, depth, noCpp);
|
|
47
|
+
console.log(output);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
// 批量模式:使用 docs_catalog.json 配置文件
|
|
51
|
+
const catalogsWithTopLevel = (0, config_js_1.getAllCatalogsWithTopLevel)();
|
|
52
|
+
if (catalogsWithTopLevel.length === 0) {
|
|
53
|
+
console.error("错误: docs_catalog.json 中没有配置任何分类");
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
// 批量模式下,如果没有指定节点和过滤选项,生成 JSON 文件
|
|
57
|
+
const isJsonMode = !nodeName && !nodeId && !depth && !noCpp;
|
|
58
|
+
let currentTopLevel = "";
|
|
59
|
+
for (const { topLevel, catalog } of catalogsWithTopLevel) {
|
|
60
|
+
// 输出分隔提示
|
|
61
|
+
if (topLevel !== currentTopLevel) {
|
|
62
|
+
currentTopLevel = topLevel;
|
|
63
|
+
console.log(`\n=== ${topLevel} ===`);
|
|
64
|
+
}
|
|
65
|
+
// 生成 JSON 文件
|
|
66
|
+
if (isJsonMode) {
|
|
67
|
+
const result = await (0, path_generator_js_1.saveCatalogJson)(catalog.id, opts.output);
|
|
68
|
+
console.log(`已保存树结构 (${result.treeCount} 个顶层节点) -> ${result.treePath}`);
|
|
69
|
+
console.log(`已保存平铺节点列表 (${result.flatCount} 个节点) -> ${result.flatPath}`);
|
|
70
|
+
}
|
|
71
|
+
console.log(`\n正在获取目录树: ${catalog.name} (${catalog.id}) ...`);
|
|
72
|
+
const flatNodes = await (0, tree_processor_js_1.fetchCatalogTree)(catalog.id);
|
|
73
|
+
// 定位目标节点
|
|
74
|
+
let rootId;
|
|
75
|
+
if (nodeName || nodeId) {
|
|
76
|
+
const target = (0, tree_processor_js_1.findNode)(flatNodes, nodeId, nodeName);
|
|
77
|
+
if (target) {
|
|
78
|
+
rootId = target.nodeId;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// buildTreeOutput 内部处理 C++ 过滤
|
|
82
|
+
const output = (0, tree_formatter_js_1.buildTreeOutput)(flatNodes, rootId, catalog.id, new Set(), false, depth, noCpp);
|
|
83
|
+
console.log(output);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
console.error("错误:", err instanceof Error ? err.message : String(err));
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleWiki = handleWiki;
|
|
4
|
+
/**
|
|
5
|
+
* Wiki 命令入口
|
|
6
|
+
*
|
|
7
|
+
* 职责:
|
|
8
|
+
* 1. 处理 CLI 选项
|
|
9
|
+
* 2. 调用 wiki-generator 执行 Wiki 生成
|
|
10
|
+
*/
|
|
11
|
+
const wiki_generator_js_1 = require("./wiki-generator.js");
|
|
12
|
+
/** Wiki 命令处理函数 */
|
|
13
|
+
async function handleWiki(opts) {
|
|
14
|
+
try {
|
|
15
|
+
const result = await (0, wiki_generator_js_1.generateWiki)(opts.config, opts.docs);
|
|
16
|
+
console.log(`\n生成完成: ${result.kitCount} kits + ${result.topicCount} topics, ${result.skippedCount} skipped`);
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
console.error("错误:", err instanceof Error ? err.message : String(err));
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateWiki = generateWiki;
|
|
4
|
+
/** Wiki生成器主逻辑 */
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const path_1 = require("path");
|
|
7
|
+
const wiki_utils_js_1 = require("./wiki-utils.js");
|
|
8
|
+
/** 生成Kit概念页内容 */
|
|
9
|
+
function buildWikiPage(kitNode, guideLeaves, apiLeaves, guideNodeMap, apiNodeMap, guideSummaryMap, apiSummaryMap, kitDesc, guidePrefix, apiPrefix) {
|
|
10
|
+
const kitName = kitNode.nodeName;
|
|
11
|
+
const kitZh = (0, wiki_utils_js_1.getKitZhName)(kitName);
|
|
12
|
+
const kitKey = (0, wiki_utils_js_1.getKitKey)(kitName);
|
|
13
|
+
const slug = kitKey ? kitKey.toLowerCase().replace(/_/g, "-") : kitZh;
|
|
14
|
+
const today = (0, wiki_utils_js_1.getToday)();
|
|
15
|
+
let content = `---
|
|
16
|
+
title: "${kitZh} (${kitKey})"
|
|
17
|
+
type: concept
|
|
18
|
+
tags: ["harmonyos", "kit", "${kitZh}"]
|
|
19
|
+
sources: []
|
|
20
|
+
last_updated: ${today}
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
# ${kitZh} (${kitKey})
|
|
24
|
+
|
|
25
|
+
${kitDesc}
|
|
26
|
+
|
|
27
|
+
## 相关文档
|
|
28
|
+
|
|
29
|
+
### 开发指南
|
|
30
|
+
|
|
31
|
+
`;
|
|
32
|
+
for (const leaf of guideLeaves) {
|
|
33
|
+
const pathParts = (0, wiki_utils_js_1.buildPathParts)(leaf, guideNodeMap, true);
|
|
34
|
+
const relPath = pathParts.join("/") + ".md";
|
|
35
|
+
const link = `${guidePrefix}/${relPath}`;
|
|
36
|
+
const summary = guideSummaryMap.get(leaf.nodeId) || "";
|
|
37
|
+
const summaryPart = summary ? ` — ${summary}` : "";
|
|
38
|
+
content += `- [${leaf.nodeName}](<${link}>)${summaryPart}\n`;
|
|
39
|
+
}
|
|
40
|
+
if (apiLeaves.length > 0) {
|
|
41
|
+
content += "\n### API参考\n\n";
|
|
42
|
+
for (const leaf of apiLeaves) {
|
|
43
|
+
const pathParts = (0, wiki_utils_js_1.buildPathParts)(leaf, apiNodeMap, true);
|
|
44
|
+
const relPath = pathParts.join("/") + ".md";
|
|
45
|
+
const link = `${apiPrefix}/${relPath}`;
|
|
46
|
+
const summary = apiSummaryMap.get(leaf.nodeId) || "";
|
|
47
|
+
const summaryPart = summary ? ` — ${summary}` : "";
|
|
48
|
+
content += `- [${leaf.nodeName}](<${link}>)${summaryPart}\n`;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
content += `
|
|
52
|
+
## 连接
|
|
53
|
+
|
|
54
|
+
- [[HarmonyOS应用开发]] — 总览
|
|
55
|
+
`;
|
|
56
|
+
return [slug, content];
|
|
57
|
+
}
|
|
58
|
+
/** 生成Topic概念页内容 */
|
|
59
|
+
function buildTopicPage(catKey, catInfo, guideLeaves, apiLeaves, guideNodeMap, apiNodeMap, guideSummaryMap, apiSummaryMap, guidePrefix, apiPrefix) {
|
|
60
|
+
const today = (0, wiki_utils_js_1.getToday)();
|
|
61
|
+
let content = `---
|
|
62
|
+
title: "${catInfo.zh}"
|
|
63
|
+
type: concept
|
|
64
|
+
tags: ["harmonyos", "${catKey}"]
|
|
65
|
+
sources: []
|
|
66
|
+
last_updated: ${today}
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
# ${catInfo.zh}
|
|
70
|
+
|
|
71
|
+
${catInfo.desc}
|
|
72
|
+
|
|
73
|
+
## 相关文档
|
|
74
|
+
|
|
75
|
+
### 开发指南
|
|
76
|
+
|
|
77
|
+
`;
|
|
78
|
+
for (const leaf of guideLeaves) {
|
|
79
|
+
const pathParts = (0, wiki_utils_js_1.buildPathParts)(leaf, guideNodeMap, true);
|
|
80
|
+
const relPath = pathParts.join("/") + ".md";
|
|
81
|
+
const link = `${guidePrefix}/${relPath}`;
|
|
82
|
+
const summary = guideSummaryMap.get(leaf.nodeId) || "";
|
|
83
|
+
const summaryPart = summary ? ` — ${summary}` : "";
|
|
84
|
+
content += `- [${leaf.nodeName}](<${link}>)${summaryPart}\n`;
|
|
85
|
+
}
|
|
86
|
+
if (apiLeaves.length > 0) {
|
|
87
|
+
content += "\n### API参考\n\n";
|
|
88
|
+
for (const leaf of apiLeaves) {
|
|
89
|
+
const pathParts = (0, wiki_utils_js_1.buildPathParts)(leaf, apiNodeMap, true);
|
|
90
|
+
const relPath = pathParts.join("/") + ".md";
|
|
91
|
+
const link = `${apiPrefix}/${relPath}`;
|
|
92
|
+
const summary = apiSummaryMap.get(leaf.nodeId) || "";
|
|
93
|
+
const summaryPart = summary ? ` — ${summary}` : "";
|
|
94
|
+
content += `- [${leaf.nodeName}](<${link}>)${summaryPart}\n`;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
content += `
|
|
98
|
+
## 连接
|
|
99
|
+
|
|
100
|
+
- [[HarmonyOS应用开发]] — 总览
|
|
101
|
+
`;
|
|
102
|
+
return [catKey, content];
|
|
103
|
+
}
|
|
104
|
+
/** 生成index.md */
|
|
105
|
+
function buildIndex(createdPages, guideSummarySet, apiSummarySet, guideClassifiedUnique, apiClassifiedUnique, outputDir, topLevel) {
|
|
106
|
+
const today = (0, wiki_utils_js_1.getToday)();
|
|
107
|
+
const guideTotal = guideSummarySet.size;
|
|
108
|
+
const apiTotal = apiSummarySet.size;
|
|
109
|
+
// 按类别分组
|
|
110
|
+
const byCategory = new Map();
|
|
111
|
+
for (const page of createdPages) {
|
|
112
|
+
const list = byCategory.get(page.category) || [];
|
|
113
|
+
list.push(page);
|
|
114
|
+
byCategory.set(page.category, list);
|
|
115
|
+
}
|
|
116
|
+
const lines = [
|
|
117
|
+
`# HarmonyOS ${topLevel} Wiki Index`,
|
|
118
|
+
"",
|
|
119
|
+
`> 自动生成于 ${today} | 共 ${guideTotal} 篇指南 + ${apiTotal} 篇API参考(仅统计有效文档) | ${guideClassifiedUnique} 篇指南 + ${apiClassifiedUnique} 篇API参考已归类至概念页`,
|
|
120
|
+
"",
|
|
121
|
+
"## Overview",
|
|
122
|
+
"- [Overview](overview.md) — 全局概览",
|
|
123
|
+
"",
|
|
124
|
+
"## 按Kit/服务分类",
|
|
125
|
+
"",
|
|
126
|
+
];
|
|
127
|
+
const sortedCategories = [...byCategory.keys()].sort();
|
|
128
|
+
for (const cat of sortedCategories) {
|
|
129
|
+
const pages = byCategory.get(cat).sort((a, b) => a.zh.localeCompare(b.zh));
|
|
130
|
+
for (const page of pages) {
|
|
131
|
+
lines.push(`- [${page.zh} (${page.kitKey})](concepts/${page.slug}.md) — ${page.desc}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
lines.push("");
|
|
135
|
+
lines.push("## Sources");
|
|
136
|
+
lines.push("");
|
|
137
|
+
const indexContent = lines.join("\n");
|
|
138
|
+
const indexPath = (0, path_1.join)(outputDir, "index.md");
|
|
139
|
+
(0, fs_1.writeFileSync)(indexPath, indexContent, "utf-8");
|
|
140
|
+
console.log(`[INDEX] index.md (${createdPages.length} kits)`);
|
|
141
|
+
}
|
|
142
|
+
/** 为单个 topLevel 生成 Wiki */
|
|
143
|
+
async function generateWikiForTopLevel(topLevel, catalogDataList, config, parentDir) {
|
|
144
|
+
const actualOutputDir = (0, path_1.join)(parentDir, `${topLevel}-wiki`);
|
|
145
|
+
console.log(`\n=== ${topLevel} ===`);
|
|
146
|
+
console.log(`输出目录: ${actualOutputDir}`);
|
|
147
|
+
// 按类型分离指南和API参考
|
|
148
|
+
let guideData;
|
|
149
|
+
let apiData;
|
|
150
|
+
for (const data of catalogDataList) {
|
|
151
|
+
if (data.catalogName === "指南") {
|
|
152
|
+
guideData = data;
|
|
153
|
+
}
|
|
154
|
+
else if (data.catalogName === "API参考") {
|
|
155
|
+
apiData = data;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (!guideData && !apiData) {
|
|
159
|
+
console.log(`[SKIP] ${topLevel}: 未找到指南或API参考数据`);
|
|
160
|
+
return { kitCount: 0, topicCount: 0, skippedCount: 0, createdPages: [] };
|
|
161
|
+
}
|
|
162
|
+
// 构建查找表
|
|
163
|
+
const guideNodeMap = guideData ? (0, wiki_utils_js_1.buildNodeMap)(guideData.tree) : new Map();
|
|
164
|
+
const apiNodeMap = apiData ? (0, wiki_utils_js_1.buildNodeMap)(apiData.tree) : new Map();
|
|
165
|
+
const guideSummaryMap = guideData ? (0, wiki_utils_js_1.buildSummaryMap)(guideData.summary) : new Map();
|
|
166
|
+
const apiSummaryMap = apiData ? (0, wiki_utils_js_1.buildSummaryMap)(apiData.summary) : new Map();
|
|
167
|
+
const guideSummarySet = guideData ? (0, wiki_utils_js_1.buildSummarySet)(guideData.summary) : new Set();
|
|
168
|
+
const apiSummarySet = apiData ? (0, wiki_utils_js_1.buildSummarySet)(apiData.summary) : new Set();
|
|
169
|
+
// 查找所有Kit节点
|
|
170
|
+
const guideKits = guideData ? (0, wiki_utils_js_1.findKitNodes)(guideData.tree) : [];
|
|
171
|
+
const apiKits = apiData ? (0, wiki_utils_js_1.findKitNodes)(apiData.tree) : [];
|
|
172
|
+
const apiKitByName = new Map();
|
|
173
|
+
for (const kit of apiKits) {
|
|
174
|
+
apiKitByName.set(kit.nodeName, kit);
|
|
175
|
+
}
|
|
176
|
+
(0, fs_1.mkdirSync)((0, path_1.join)(actualOutputDir, "concepts"), { recursive: true });
|
|
177
|
+
let created = 0;
|
|
178
|
+
let skipped = 0;
|
|
179
|
+
const createdPages = [];
|
|
180
|
+
const allGuideNodeIds = new Set();
|
|
181
|
+
const allApiNodeIds = new Set();
|
|
182
|
+
const guidePrefix = guideData?.linkPrefix || "";
|
|
183
|
+
const apiPrefix = apiData?.linkPrefix || "";
|
|
184
|
+
// 第一步:生成Kit概念页
|
|
185
|
+
for (const kitNode of guideKits) {
|
|
186
|
+
const kitName = kitNode.nodeName;
|
|
187
|
+
const kitKey = (0, wiki_utils_js_1.getKitKey)(kitName);
|
|
188
|
+
const kitInfo = config.kitCategories[kitKey];
|
|
189
|
+
if (!kitInfo) {
|
|
190
|
+
console.log(`[SKIP] No kitCategories entry for: ${kitName} (key: ${kitKey})`);
|
|
191
|
+
skipped++;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
const guideLeaves = (0, wiki_utils_js_1.collectLeafNodes)(kitNode, guideSummarySet);
|
|
195
|
+
for (const leaf of guideLeaves) {
|
|
196
|
+
allGuideNodeIds.add(leaf.nodeId);
|
|
197
|
+
}
|
|
198
|
+
const apiKit = apiKitByName.get(kitName);
|
|
199
|
+
const apiLeaves = apiKit ? (0, wiki_utils_js_1.collectLeafNodes)(apiKit, apiSummarySet) : [];
|
|
200
|
+
for (const leaf of apiLeaves) {
|
|
201
|
+
allApiNodeIds.add(leaf.nodeId);
|
|
202
|
+
}
|
|
203
|
+
const [slug, content] = buildWikiPage(kitNode, guideLeaves, apiLeaves, guideNodeMap, apiNodeMap, guideSummaryMap, apiSummaryMap, kitInfo.desc, guidePrefix, apiPrefix);
|
|
204
|
+
const filepath = (0, path_1.join)(actualOutputDir, "concepts", `${slug}.md`);
|
|
205
|
+
(0, fs_1.writeFileSync)(filepath, content, "utf-8");
|
|
206
|
+
created++;
|
|
207
|
+
createdPages.push({
|
|
208
|
+
slug,
|
|
209
|
+
kitKey,
|
|
210
|
+
zh: kitInfo.zh,
|
|
211
|
+
desc: kitInfo.desc,
|
|
212
|
+
category: kitInfo.zh,
|
|
213
|
+
guideCount: guideLeaves.length,
|
|
214
|
+
apiCount: apiLeaves.length,
|
|
215
|
+
});
|
|
216
|
+
console.log(`[OK] ${kitName} -> ${slug}.md (${guideLeaves.length} guides, ${apiLeaves.length} API)`);
|
|
217
|
+
}
|
|
218
|
+
// 第二步:处理仅在API树中存在的Kit
|
|
219
|
+
const guideKitNames = new Set(guideKits.map((n) => n.nodeName));
|
|
220
|
+
for (const kitNode of apiKits) {
|
|
221
|
+
if (guideKitNames.has(kitNode.nodeName)) {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const kitName = kitNode.nodeName;
|
|
225
|
+
const kitKey = (0, wiki_utils_js_1.getKitKey)(kitName);
|
|
226
|
+
const kitInfo = config.kitCategories[kitKey];
|
|
227
|
+
if (!kitInfo) {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
const apiLeaves = (0, wiki_utils_js_1.collectLeafNodes)(kitNode, apiSummarySet);
|
|
231
|
+
for (const leaf of apiLeaves) {
|
|
232
|
+
allApiNodeIds.add(leaf.nodeId);
|
|
233
|
+
}
|
|
234
|
+
const [slug, content] = buildWikiPage(kitNode, [], apiLeaves, guideNodeMap, apiNodeMap, guideSummaryMap, apiSummaryMap, kitInfo.desc, guidePrefix, apiPrefix);
|
|
235
|
+
const filepath = (0, path_1.join)(actualOutputDir, "concepts", `${slug}.md`);
|
|
236
|
+
(0, fs_1.writeFileSync)(filepath, content, "utf-8");
|
|
237
|
+
created++;
|
|
238
|
+
createdPages.push({
|
|
239
|
+
slug,
|
|
240
|
+
kitKey,
|
|
241
|
+
zh: kitInfo.zh,
|
|
242
|
+
desc: kitInfo.desc,
|
|
243
|
+
category: kitInfo.zh,
|
|
244
|
+
guideCount: 0,
|
|
245
|
+
apiCount: apiLeaves.length,
|
|
246
|
+
});
|
|
247
|
+
console.log(`[OK] ${kitName} -> ${slug}.md (0 guides, ${apiLeaves.length} API)`);
|
|
248
|
+
}
|
|
249
|
+
// 第三步:生成Topic概念页
|
|
250
|
+
const topicGuideLeaves = new Map();
|
|
251
|
+
const guideSummary = guideData?.summary || [];
|
|
252
|
+
for (const node of guideSummary) {
|
|
253
|
+
const topics = (0, wiki_utils_js_1.matchTopic)(node.nodeName, config.topicCategories);
|
|
254
|
+
for (const t of topics) {
|
|
255
|
+
const list = topicGuideLeaves.get(t) || [];
|
|
256
|
+
list.push(node);
|
|
257
|
+
topicGuideLeaves.set(t, list);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const topicApiLeaves = new Map();
|
|
261
|
+
const apiSummary = apiData?.summary || [];
|
|
262
|
+
for (const node of apiSummary) {
|
|
263
|
+
const topics = (0, wiki_utils_js_1.matchTopic)(node.nodeName, config.topicCategories);
|
|
264
|
+
for (const t of topics) {
|
|
265
|
+
const list = topicApiLeaves.get(t) || [];
|
|
266
|
+
list.push(node);
|
|
267
|
+
topicApiLeaves.set(t, list);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// 去重:如果topic key和某个Kit key重叠,跳过该topic
|
|
271
|
+
const kitKeysLower = new Set();
|
|
272
|
+
for (const key of Object.keys(config.kitCategories)) {
|
|
273
|
+
kitKeysLower.add(key.replace("_Kit", "").toLowerCase());
|
|
274
|
+
}
|
|
275
|
+
let topicCreated = 0;
|
|
276
|
+
for (const [catKey, catInfo] of Object.entries(config.topicCategories)) {
|
|
277
|
+
if (kitKeysLower.has(catKey)) {
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
const gLeaves = topicGuideLeaves.get(catKey) || [];
|
|
281
|
+
const aLeaves = topicApiLeaves.get(catKey) || [];
|
|
282
|
+
if (gLeaves.length === 0 && aLeaves.length === 0) {
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
const [slug, content] = buildTopicPage(catKey, catInfo, gLeaves, aLeaves, guideNodeMap, apiNodeMap, guideSummaryMap, apiSummaryMap, guidePrefix, apiPrefix);
|
|
286
|
+
const filepath = (0, path_1.join)(actualOutputDir, "concepts", `${slug}.md`);
|
|
287
|
+
(0, fs_1.writeFileSync)(filepath, content, "utf-8");
|
|
288
|
+
topicCreated++;
|
|
289
|
+
createdPages.push({
|
|
290
|
+
slug,
|
|
291
|
+
kitKey: catKey,
|
|
292
|
+
zh: catInfo.zh,
|
|
293
|
+
desc: catInfo.desc,
|
|
294
|
+
category: catInfo.zh,
|
|
295
|
+
guideCount: gLeaves.length,
|
|
296
|
+
apiCount: aLeaves.length,
|
|
297
|
+
});
|
|
298
|
+
for (const leaf of gLeaves) {
|
|
299
|
+
allGuideNodeIds.add(leaf.nodeId);
|
|
300
|
+
}
|
|
301
|
+
for (const leaf of aLeaves) {
|
|
302
|
+
allApiNodeIds.add(leaf.nodeId);
|
|
303
|
+
}
|
|
304
|
+
console.log(`[TOPIC] ${catInfo.zh} -> ${slug}.md (${gLeaves.length} guides, ${aLeaves.length} API)`);
|
|
305
|
+
}
|
|
306
|
+
// 生成index.md
|
|
307
|
+
buildIndex(createdPages, guideSummarySet, apiSummarySet, allGuideNodeIds.size, allApiNodeIds.size, actualOutputDir, topLevel);
|
|
308
|
+
console.log(`\n${topLevel} 完成: ${created} kits + ${topicCreated} topics, ${skipped} skipped`);
|
|
309
|
+
return {
|
|
310
|
+
kitCount: created,
|
|
311
|
+
topicCount: topicCreated,
|
|
312
|
+
skippedCount: skipped,
|
|
313
|
+
createdPages,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
/** 执行Wiki生成(按 topLevel 分开) */
|
|
317
|
+
async function generateWiki(configPath, docsDir) {
|
|
318
|
+
// 加载配置,如果未指定则使用包内的默认配置
|
|
319
|
+
const actualConfigPath = configPath || (0, path_1.join)(__dirname, "..", "..", "wiki_config.json");
|
|
320
|
+
const config = (0, wiki_utils_js_1.loadWikiConfig)(actualConfigPath);
|
|
321
|
+
// docs目录
|
|
322
|
+
const actualInputDir = (0, path_1.resolve)(docsDir);
|
|
323
|
+
// wiki一定和docs平级
|
|
324
|
+
const parentDir = (0, path_1.resolve)(actualInputDir, "..");
|
|
325
|
+
console.log(`输入目录: ${actualInputDir}`);
|
|
326
|
+
// 加载所有catalog数据
|
|
327
|
+
const catalogDataList = (0, wiki_utils_js_1.loadAllCatalogData)(actualInputDir);
|
|
328
|
+
// 按 topLevel 分组
|
|
329
|
+
const byTopLevel = new Map();
|
|
330
|
+
for (const data of catalogDataList) {
|
|
331
|
+
const list = byTopLevel.get(data.topLevel) || [];
|
|
332
|
+
list.push(data);
|
|
333
|
+
byTopLevel.set(data.topLevel, list);
|
|
334
|
+
}
|
|
335
|
+
if (byTopLevel.size === 0) {
|
|
336
|
+
throw new Error("未找到任何 catalog 数据");
|
|
337
|
+
}
|
|
338
|
+
// 对每个 topLevel 生成 wiki
|
|
339
|
+
let totalKitCount = 0;
|
|
340
|
+
let totalTopicCount = 0;
|
|
341
|
+
let totalSkippedCount = 0;
|
|
342
|
+
const allCreatedPages = [];
|
|
343
|
+
for (const [topLevel, dataList] of byTopLevel) {
|
|
344
|
+
const result = await generateWikiForTopLevel(topLevel, dataList, config, parentDir);
|
|
345
|
+
totalKitCount += result.kitCount;
|
|
346
|
+
totalTopicCount += result.topicCount;
|
|
347
|
+
totalSkippedCount += result.skippedCount;
|
|
348
|
+
allCreatedPages.push(...result.createdPages);
|
|
349
|
+
}
|
|
350
|
+
console.log(`\n全部完成: ${totalKitCount} kits + ${totalTopicCount} topics, ${totalSkippedCount} skipped`);
|
|
351
|
+
return {
|
|
352
|
+
kitCount: totalKitCount,
|
|
353
|
+
topicCount: totalTopicCount,
|
|
354
|
+
skippedCount: totalSkippedCount,
|
|
355
|
+
createdPages: allCreatedPages,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** Wiki生成器类型定义 */
|
|
2
|
+
/** Kit分类信息 */
|
|
3
|
+
export interface KitCategoryInfo {
|
|
4
|
+
zh: string;
|
|
5
|
+
desc: string;
|
|
6
|
+
}
|
|
7
|
+
/** Topic分类信息 */
|
|
8
|
+
export interface TopicCategoryInfo {
|
|
9
|
+
zh: string;
|
|
10
|
+
desc: string;
|
|
11
|
+
patterns: string[];
|
|
12
|
+
}
|
|
13
|
+
/** Wiki配置 */
|
|
14
|
+
export interface WikiConfig {
|
|
15
|
+
kitCategories: Record<string, KitCategoryInfo>;
|
|
16
|
+
topicCategories: Record<string, TopicCategoryInfo>;
|
|
17
|
+
}
|
|
18
|
+
/** 目录树节点 */
|
|
19
|
+
export interface TreeNode {
|
|
20
|
+
nodeId: string;
|
|
21
|
+
nodeName: string;
|
|
22
|
+
isLeaf?: boolean;
|
|
23
|
+
parent?: string;
|
|
24
|
+
children?: TreeNode[];
|
|
25
|
+
relateDocument?: string;
|
|
26
|
+
[key: string]: unknown;
|
|
27
|
+
}
|
|
28
|
+
/** 摘要节点 */
|
|
29
|
+
export interface SummaryNode {
|
|
30
|
+
nodeId: string;
|
|
31
|
+
nodeName: string;
|
|
32
|
+
summary?: string;
|
|
33
|
+
parent?: string;
|
|
34
|
+
[key: string]: unknown;
|
|
35
|
+
}
|
|
36
|
+
/** 生成的页面信息 */
|
|
37
|
+
export interface CreatedPage {
|
|
38
|
+
slug: string;
|
|
39
|
+
kitKey: string;
|
|
40
|
+
zh: string;
|
|
41
|
+
desc: string;
|
|
42
|
+
category: string;
|
|
43
|
+
guideCount: number;
|
|
44
|
+
apiCount: number;
|
|
45
|
+
}
|
|
46
|
+
/** Wiki生成结果 */
|
|
47
|
+
export interface WikiGenerateResult {
|
|
48
|
+
kitCount: number;
|
|
49
|
+
topicCount: number;
|
|
50
|
+
skippedCount: number;
|
|
51
|
+
createdPages: CreatedPage[];
|
|
52
|
+
}
|
|
53
|
+
/** Catalog数据(已加载的树和摘要) */
|
|
54
|
+
export interface CatalogData {
|
|
55
|
+
topLevel: string;
|
|
56
|
+
catalogId: string;
|
|
57
|
+
catalogName: string;
|
|
58
|
+
tree: TreeNode[];
|
|
59
|
+
summary: SummaryNode[];
|
|
60
|
+
linkPrefix: string;
|
|
61
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { WikiConfig, TreeNode, SummaryNode, CatalogData } from "./wiki-types.js";
|
|
2
|
+
/** 加载JSON文件 */
|
|
3
|
+
export declare function loadJson<T>(filePath: string): T;
|
|
4
|
+
/** 加载Wiki配置 */
|
|
5
|
+
export declare function loadWikiConfig(configPath: string): WikiConfig;
|
|
6
|
+
/** 加载所有Catalog数据 */
|
|
7
|
+
export declare function loadAllCatalogData(inputDir: string): CatalogData[];
|
|
8
|
+
/** 构建节点映射表 (nodeId -> node) */
|
|
9
|
+
export declare function buildNodeMap(treeData: TreeNode[]): Map<string, TreeNode>;
|
|
10
|
+
/** 构建摘要映射表 (nodeId -> summary) */
|
|
11
|
+
export declare function buildSummaryMap(summaryData: SummaryNode[]): Map<string, string>;
|
|
12
|
+
/** 构建摘要节点ID集合 */
|
|
13
|
+
export declare function buildSummarySet(summaryData: SummaryNode[]): Set<string>;
|
|
14
|
+
/** 判断是否为Kit节点 (匹配 "XXX Kit(YYY)" 格式) */
|
|
15
|
+
export declare function isKitNode(node: TreeNode): boolean;
|
|
16
|
+
/** 查找所有Kit节点 */
|
|
17
|
+
export declare function findKitNodes(treeData: TreeNode[]): TreeNode[];
|
|
18
|
+
/** 收集叶子节点 */
|
|
19
|
+
export declare function collectLeafNodes(node: TreeNode, summarySet?: Set<string>): TreeNode[];
|
|
20
|
+
/** 清理文件名中的非法字符 */
|
|
21
|
+
export declare function sanitizeName(name: string): string;
|
|
22
|
+
/** 构建从根节点到指定节点的路径 */
|
|
23
|
+
export declare function buildPathParts(node: TreeNode, nodeMap: Map<string, TreeNode>, sanitize?: boolean): string[];
|
|
24
|
+
/** 从Kit节点名提取中文描述 */
|
|
25
|
+
export declare function getKitZhName(nodeName: string): string;
|
|
26
|
+
/** 从Kit节点名获取Kit Key */
|
|
27
|
+
export declare function getKitKey(nodeName: string): string;
|
|
28
|
+
/** 匹配Topic分类 */
|
|
29
|
+
export declare function matchTopic(nodeName: string, topicCategories: Record<string, {
|
|
30
|
+
patterns: string[];
|
|
31
|
+
}>): string[];
|
|
32
|
+
/** 获取当前日期字符串 */
|
|
33
|
+
export declare function getToday(): string;
|