zengen 0.2.2 → 0.2.4
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/.github/workflows/pages.yml +3 -0
- package/.zen/meta.json +49 -9
- package/.zen/src/ar-SA/74541be9d53c64107a548e09847244d52bff803960942cd2bfbd2a8600afb805.md +71 -0
- package/.zen/src/da-DK/74541be9d53c64107a548e09847244d52bff803960942cd2bfbd2a8600afb805.md +71 -0
- package/.zen/src/de-DE/74541be9d53c64107a548e09847244d52bff803960942cd2bfbd2a8600afb805.md +71 -0
- package/.zen/src/en-US/74541be9d53c64107a548e09847244d52bff803960942cd2bfbd2a8600afb805.md +71 -0
- package/.zen/src/es-ES/74541be9d53c64107a548e09847244d52bff803960942cd2bfbd2a8600afb805.md +71 -0
- package/.zen/src/es-MX/74541be9d53c64107a548e09847244d52bff803960942cd2bfbd2a8600afb805.md +71 -0
- package/.zen/src/fi-FI/74541be9d53c64107a548e09847244d52bff803960942cd2bfbd2a8600afb805.md +71 -0
- package/.zen/src/fr-FR/74541be9d53c64107a548e09847244d52bff803960942cd2bfbd2a8600afb805.md +71 -0
- package/.zen/src/hi-IN/74541be9d53c64107a548e09847244d52bff803960942cd2bfbd2a8600afb805.md +71 -0
- package/.zen/src/id-ID/74541be9d53c64107a548e09847244d52bff803960942cd2bfbd2a8600afb805.md +71 -0
- package/.zen/src/it-IT/74541be9d53c64107a548e09847244d52bff803960942cd2bfbd2a8600afb805.md +71 -0
- package/.zen/src/ja-JP/74541be9d53c64107a548e09847244d52bff803960942cd2bfbd2a8600afb805.md +71 -0
- package/.zen/src/ko-KR/74541be9d53c64107a548e09847244d52bff803960942cd2bfbd2a8600afb805.md +71 -0
- package/.zen/src/nl-NL/74541be9d53c64107a548e09847244d52bff803960942cd2bfbd2a8600afb805.md +71 -0
- package/.zen/src/no-NO/74541be9d53c64107a548e09847244d52bff803960942cd2bfbd2a8600afb805.md +71 -0
- package/.zen/src/pl-PL/74541be9d53c64107a548e09847244d52bff803960942cd2bfbd2a8600afb805.md +71 -0
- package/.zen/src/pt-BR/74541be9d53c64107a548e09847244d52bff803960942cd2bfbd2a8600afb805.md +71 -0
- package/.zen/src/pt-PT/74541be9d53c64107a548e09847244d52bff803960942cd2bfbd2a8600afb805.md +71 -0
- package/.zen/src/ru-RU/74541be9d53c64107a548e09847244d52bff803960942cd2bfbd2a8600afb805.md +71 -0
- package/.zen/src/sv-SE/74541be9d53c64107a548e09847244d52bff803960942cd2bfbd2a8600afb805.md +71 -0
- package/.zen/src/th-TH/74541be9d53c64107a548e09847244d52bff803960942cd2bfbd2a8600afb805.md +71 -0
- package/.zen/src/tr-TR/74541be9d53c64107a548e09847244d52bff803960942cd2bfbd2a8600afb805.md +71 -0
- package/.zen/src/uk-UA/74541be9d53c64107a548e09847244d52bff803960942cd2bfbd2a8600afb805.md +71 -0
- package/.zen/src/vi-VN/74541be9d53c64107a548e09847244d52bff803960942cd2bfbd2a8600afb805.md +71 -0
- package/.zen/src/zh-Hans/74541be9d53c64107a548e09847244d52bff803960942cd2bfbd2a8600afb805.md +71 -0
- package/.zen/src/zh-Hant/74541be9d53c64107a548e09847244d52bff803960942cd2bfbd2a8600afb805.md +71 -0
- package/README.md +26 -36
- package/assets/templates/default/layout.html +58 -3
- package/dist/ai/translateMarkdown.d.ts +1 -1
- package/dist/ai/translateMarkdown.d.ts.map +1 -1
- package/dist/ai/translateMarkdown.js +33 -2
- package/dist/ai/translateMarkdown.js.map +1 -1
- package/dist/build/pipeline.d.ts.map +1 -1
- package/dist/build/pipeline.js +2 -40
- package/dist/build/pipeline.js.map +1 -1
- package/dist/languages.d.ts.map +1 -1
- package/dist/languages.js +23 -0
- package/dist/languages.js.map +1 -1
- package/dist/process/scanSourceFiles.d.ts +5 -0
- package/dist/process/scanSourceFiles.d.ts.map +1 -0
- package/dist/process/scanSourceFiles.js +64 -0
- package/dist/process/scanSourceFiles.js.map +1 -0
- package/dist/process/template.d.ts.map +1 -1
- package/dist/process/template.js +29 -6
- package/dist/process/template.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/sha256.d.ts +2 -0
- package/dist/utils/sha256.d.ts.map +1 -0
- package/dist/utils/sha256.js +7 -0
- package/dist/utils/sha256.js.map +1 -0
- package/package.json +2 -2
- package/src/ai/translateMarkdown.ts +33 -2
- package/src/build/pipeline.ts +1 -45
- package/src/languages.ts +27 -0
- package/src/process/scanSourceFiles.ts +65 -0
- package/src/process/template.ts +38 -6
- package/src/types.ts +1 -0
- package/src/utils/sha256.ts +4 -0
- package/dist/scan/files.d.ts +0 -7
- package/dist/scan/files.d.ts.map +0 -1
- package/dist/scan/files.js +0 -54
- package/dist/scan/files.js.map +0 -1
- package/src/scan/files.ts +0 -17
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.scanSourceFiles = scanSourceFiles;
|
|
7
|
+
const promises_1 = require("fs/promises");
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const findEntries_1 = require("../findEntries");
|
|
10
|
+
const metadata_1 = require("../metadata");
|
|
11
|
+
const paths_1 = require("../paths");
|
|
12
|
+
const sha256_1 = require("../utils/sha256");
|
|
13
|
+
const extractLinksFromMarkdown = (content) => {
|
|
14
|
+
const linkRegex = /\[.*?\]\((.*?)\)/g;
|
|
15
|
+
const links = [];
|
|
16
|
+
let match;
|
|
17
|
+
while ((match = linkRegex.exec(content)) !== null) {
|
|
18
|
+
links.push(match[1]);
|
|
19
|
+
}
|
|
20
|
+
return links;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* 扫描源文件
|
|
24
|
+
*/
|
|
25
|
+
async function scanSourceFiles() {
|
|
26
|
+
console.log(`🔍 Scanning source directory...`);
|
|
27
|
+
const markdownFiles = await (0, findEntries_1.findMarkdownEntries)(paths_1.INPUT_DIR);
|
|
28
|
+
const hashes = new Set();
|
|
29
|
+
for (const relativePath of markdownFiles) {
|
|
30
|
+
const fullPath = path_1.default.join(paths_1.INPUT_DIR, relativePath);
|
|
31
|
+
try {
|
|
32
|
+
// 检查文件是否存在
|
|
33
|
+
const content = await (0, promises_1.readFile)(fullPath, 'utf-8'); // 确保文件可读
|
|
34
|
+
const hash = (0, sha256_1.sha256)(content);
|
|
35
|
+
const links = extractLinksFromMarkdown(content);
|
|
36
|
+
console.info(` - Found file: ${relativePath} (hash: ${hash})`);
|
|
37
|
+
console.info(` Links: ${links.join(', ') || 'None'}`);
|
|
38
|
+
hashes.add(hash);
|
|
39
|
+
const metaWithSameHash = metadata_1.MetaData.files.find(f => f.hash === hash);
|
|
40
|
+
if (metaWithSameHash) {
|
|
41
|
+
metaWithSameHash.path = relativePath;
|
|
42
|
+
metaWithSameHash.links = links;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
// 如果没有相同哈希的元数据,则添加一个新的占位符
|
|
46
|
+
metadata_1.MetaData.files.push({
|
|
47
|
+
hash,
|
|
48
|
+
path: relativePath,
|
|
49
|
+
links,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.warn(`⚠️ File not found or inaccessible: ${fullPath}`, error);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// 移除不再存在的文件元数据
|
|
58
|
+
metadata_1.MetaData.files = metadata_1.MetaData.files.filter(f => hashes.has(f.hash));
|
|
59
|
+
console.log(`✅ Found ${metadata_1.MetaData.files.length} Markdown files`);
|
|
60
|
+
if (metadata_1.MetaData.files.length === 0) {
|
|
61
|
+
console.warn(`⚠️ No Markdown files found in ${paths_1.INPUT_DIR}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=scanSourceFiles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanSourceFiles.js","sourceRoot":"","sources":["../../src/process/scanSourceFiles.ts"],"names":[],"mappings":";;;;;AAoBA,0CA4CC;AAhED,0CAAuC;AACvC,gDAAwB;AACxB,gDAAqD;AACrD,0CAAuC;AACvC,oCAAqC;AACrC,4CAAyC;AAEzC,MAAM,wBAAwB,GAAG,CAAC,OAAe,EAAY,EAAE;IAC7D,MAAM,SAAS,GAAG,mBAAmB,CAAC;IACtC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,KAAK,CAAC;IACV,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAClD,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AAEF;;GAEG;AACI,KAAK,UAAU,eAAe;IACnC,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;IAC/C,MAAM,aAAa,GAAG,MAAM,IAAA,iCAAmB,EAAC,iBAAS,CAAC,CAAC;IAC3D,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IAEjC,KAAK,MAAM,YAAY,IAAI,aAAa,EAAE,CAAC;QACzC,MAAM,QAAQ,GAAG,cAAI,CAAC,IAAI,CAAC,iBAAS,EAAE,YAAY,CAAC,CAAC;QAEpD,IAAI,CAAC;YACH,WAAW;YAEX,MAAM,OAAO,GAAG,MAAM,IAAA,mBAAQ,EAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS;YAE5D,MAAM,IAAI,GAAG,IAAA,eAAM,EAAC,OAAO,CAAC,CAAC;YAC7B,MAAM,KAAK,GAAG,wBAAwB,CAAC,OAAO,CAAC,CAAC;YAChD,OAAO,CAAC,IAAI,CAAC,mBAAmB,YAAY,WAAW,IAAI,GAAG,CAAC,CAAC;YAChE,OAAO,CAAC,IAAI,CAAC,cAAc,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,MAAM,EAAE,CAAC,CAAC;YAEzD,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAEjB,MAAM,gBAAgB,GAAG,mBAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;YACnE,IAAI,gBAAgB,EAAE,CAAC;gBACrB,gBAAgB,CAAC,IAAI,GAAG,YAAY,CAAC;gBACrC,gBAAgB,CAAC,KAAK,GAAG,KAAK,CAAC;YACjC,CAAC;iBAAM,CAAC;gBACN,0BAA0B;gBAC1B,mBAAQ,CAAC,KAAK,CAAC,IAAI,CAAC;oBAClB,IAAI;oBACJ,IAAI,EAAE,YAAY;oBAClB,KAAK;iBACN,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC,sCAAsC,QAAQ,EAAE,EAAE,KAAK,CAAC,CAAC;QACxE,CAAC;IACH,CAAC;IACD,eAAe;IACf,mBAAQ,CAAC,KAAK,GAAG,mBAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAEhE,OAAO,CAAC,GAAG,CAAC,WAAW,mBAAQ,CAAC,KAAK,CAAC,MAAM,iBAAiB,CAAC,CAAC;IAE/D,IAAI,mBAAQ,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChC,OAAO,CAAC,IAAI,CAAC,iCAAiC,iBAAS,EAAE,CAAC,CAAC;IAC7D,CAAC;AACH,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/process/template.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/process/template.ts"],"names":[],"mappings":"AAyLA;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAuCrD"}
|
package/dist/process/template.js
CHANGED
|
@@ -60,10 +60,14 @@ function generateLanguageSwitcher(templateData) {
|
|
|
60
60
|
})
|
|
61
61
|
.join('');
|
|
62
62
|
return `<div class="language-switcher">
|
|
63
|
-
<span class="lang-label">Language:</span>
|
|
64
63
|
<ul class="lang-list">${items}</ul>
|
|
65
64
|
</div>`;
|
|
66
65
|
}
|
|
66
|
+
const generateTagsHtml = (tags) => {
|
|
67
|
+
return `<ul class="tags-list">${tags
|
|
68
|
+
.map(tag => `<li class="tag-item">${tag}</li>`)
|
|
69
|
+
.join('')}</ul>`;
|
|
70
|
+
};
|
|
67
71
|
/**
|
|
68
72
|
* 生成导航 HTML
|
|
69
73
|
* @param navigation 导航树
|
|
@@ -93,6 +97,25 @@ async function generateNavigationHtml(data) {
|
|
|
93
97
|
})
|
|
94
98
|
.join('')}</ul>`;
|
|
95
99
|
}
|
|
100
|
+
const replaceInnerLinks = (data, markdownContent) => {
|
|
101
|
+
let content = markdownContent;
|
|
102
|
+
for (const link of data.file.links) {
|
|
103
|
+
if (URL.canParse(link))
|
|
104
|
+
continue; // 跳过绝对 URL
|
|
105
|
+
const targetPath = path.resolve('/', path.dirname(data.file.path), link).slice(1);
|
|
106
|
+
const targetFile = metadata_1.MetaData.files.find(f => f.path === targetPath);
|
|
107
|
+
if (!targetFile) {
|
|
108
|
+
console.warn(`⚠️ Link target not found for ${link} in file ${data.file.path}`);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
// 替换链接
|
|
112
|
+
const targetLink = path.join(metadata_1.MetaData.options.baseUrl ?? '/', data.lang, targetFile.hash + '.html');
|
|
113
|
+
// 全局替换链接
|
|
114
|
+
const linksRegex = new RegExp(`\\]\\(${link.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\)`, 'g');
|
|
115
|
+
content = content.replace(linksRegex, `](${targetLink})`);
|
|
116
|
+
}
|
|
117
|
+
return content;
|
|
118
|
+
};
|
|
96
119
|
/**
|
|
97
120
|
* 简单的模板变量替换
|
|
98
121
|
* @param template 模板字符串
|
|
@@ -103,7 +126,7 @@ async function renderTemplate(template, data) {
|
|
|
103
126
|
const { options: { langs = [] }, } = metadata_1.MetaData;
|
|
104
127
|
const markdownContent = data.content;
|
|
105
128
|
const { frontmatter, body } = (0, frontmatter_1.parseFrontmatter)(markdownContent);
|
|
106
|
-
const htmlContent = (0, convertMarkdownToHtml_1.convertMarkdownToHtml)(body);
|
|
129
|
+
const htmlContent = (0, convertMarkdownToHtml_1.convertMarkdownToHtml)(replaceInnerLinks(data, body));
|
|
107
130
|
let result = template;
|
|
108
131
|
// 替换导航
|
|
109
132
|
const navigationHtml = await generateNavigationHtml(data);
|
|
@@ -114,15 +137,15 @@ async function renderTemplate(template, data) {
|
|
|
114
137
|
// 替换元数据变量
|
|
115
138
|
if (frontmatter) {
|
|
116
139
|
result = result.replace(/{{summary}}/g, frontmatter.summary || '');
|
|
117
|
-
result = result.replace(/{{tags}}/g, frontmatter.tags
|
|
118
|
-
result = result.replace(/{{inferred_date}}/g, frontmatter.inferred_date || '');
|
|
119
|
-
result = result.replace(/{{inferred_lang}}/g, frontmatter.inferred_lang || '');
|
|
140
|
+
result = result.replace(/{{tags}}/g, generateTagsHtml(frontmatter.tags || []));
|
|
141
|
+
result = result.replace(/{{inferred_date}}/g, frontmatter.inferred_date || '--');
|
|
142
|
+
result = result.replace(/{{inferred_lang}}/g, frontmatter.inferred_lang || '--');
|
|
120
143
|
}
|
|
121
144
|
// 替换语言相关变量
|
|
122
145
|
result = result.replace(/{{lang}}/g, data.lang || '');
|
|
123
146
|
if (langs && langs.length > 1 && data.lang) {
|
|
124
147
|
const langSwitcher = generateLanguageSwitcher(data);
|
|
125
|
-
result = result.replace(
|
|
148
|
+
result = result.replace(/{{language_switcher}}/g, langSwitcher);
|
|
126
149
|
}
|
|
127
150
|
return result;
|
|
128
151
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/process/template.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/process/template.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,UAAU,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,YAAY,CAAC;IACtB,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;CACH"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,UAAU,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,YAAY,CAAC;IACtB,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;CACH"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sha256.d.ts","sourceRoot":"","sources":["../../src/utils/sha256.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,MAAM,GAAI,SAAS,MAAM,KAAG,MACW,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sha256 = void 0;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
const sha256 = (content) => (0, crypto_1.createHash)('sha256').update(content).digest('hex');
|
|
6
|
+
exports.sha256 = sha256;
|
|
7
|
+
//# sourceMappingURL=sha256.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sha256.js","sourceRoot":"","sources":["../../src/utils/sha256.ts"],"names":[],"mappings":";;;AAAA,mCAAoC;AAE7B,MAAM,MAAM,GAAG,CAAC,OAAe,EAAU,EAAE,CAChD,IAAA,mBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AADxC,QAAA,MAAM,UACkC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zengen",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "ZEN - A minimalist Markdown documentation site builder",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"build": "npx rimraf dist && tsc",
|
|
12
12
|
"dev": "ts-node src/cli.ts",
|
|
13
|
-
"build:doc": "npm run build && node dist/cli.js build --lang zh-Hans --lang en-US --lang es-ES --lang pt-PT --lang
|
|
13
|
+
"build:doc": "npm run build && node dist/cli.js build --base-url /ZEN --lang zh-Hans --lang en-US --lang ja-JP --lang ko-KR --lang es-ES --lang fr-FR --lang de-DE --lang ru-RU --lang pt-PT --lang it-IT --lang nl-NL --lang pl-PL --lang sv-SE --lang fi-FI --lang da-DK --lang no-NO --lang zh-Hant --lang hi-IN --lang ar-SA --lang th-TH --lang vi-VN --lang id-ID --lang pt-BR --lang es-MX --lang tr-TR --lang uk-UA --verbose",
|
|
14
14
|
"test": "npm run build && node --test dist/**/*.test.js",
|
|
15
15
|
"test:types": "tsc --noEmit",
|
|
16
16
|
"test:build": "npm run build && test -f dist/index.js && test -f dist/cli.js",
|
|
@@ -1,16 +1,47 @@
|
|
|
1
|
+
import { LANGUAGE_NAMES } from '../languages';
|
|
1
2
|
import { completeMessages, OpenAIMessage } from '../services/openai';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
|
-
|
|
5
|
+
|
|
5
6
|
* @param content Markdown 内容
|
|
6
7
|
* @param targetLang 目标语言代码(例如:zh-Hans, en-US)
|
|
7
8
|
* @returns Promise<string> 翻译后的 Markdown 内容
|
|
8
9
|
*/
|
|
9
10
|
export async function translateMarkdown(content: string, targetLang: string): Promise<string> {
|
|
11
|
+
const langName = LANGUAGE_NAMES[targetLang];
|
|
12
|
+
const lang = `${langName} (${targetLang})`;
|
|
10
13
|
const messages: OpenAIMessage[] = [
|
|
11
14
|
{
|
|
12
15
|
role: 'system',
|
|
13
|
-
content:
|
|
16
|
+
content: [
|
|
17
|
+
`You are a professional document translator.`,
|
|
18
|
+
`Translate the following markdown content into ${lang}`,
|
|
19
|
+
targetLang === 'ja-JP'
|
|
20
|
+
? [
|
|
21
|
+
//
|
|
22
|
+
'使用日语母语者自然的表达方式。',
|
|
23
|
+
'采用适合技术/专业文档的礼貌、正式语体(丁寧体/です・ます体)',
|
|
24
|
+
'确保语法和字符集完全符合日语规范。',
|
|
25
|
+
'绝对禁止使用繁体中文汉字。',
|
|
26
|
+
'所有日语汉字必须使用标准的 **日本常用汉字(Jōyō kanji)** 字形。',
|
|
27
|
+
'特别检查以下字形示例,确保使用日文标准字形:',
|
|
28
|
+
'1. “国” - 使用“国”而非“國”',
|
|
29
|
+
'2. “学” - 使用“学”而非“學”',
|
|
30
|
+
'3. “広” - 使用“広”而非“廣”',
|
|
31
|
+
'4. “円” - 使用“円”而非“圓”',
|
|
32
|
+
'5. “医” - 使用“医”而非“醫”',
|
|
33
|
+
'6. “図” - 使用“図”而非“圖”',
|
|
34
|
+
'7. “対” - 使用“対”而非“對”',
|
|
35
|
+
'8. “声” - 使用“声”而非“聲”',
|
|
36
|
+
'9. “芸” - 使用“芸”而非“藝”',
|
|
37
|
+
'10. “験” - 使用“験”而非“驗”',
|
|
38
|
+
].join('\n')
|
|
39
|
+
: ``,
|
|
40
|
+
`Preserve the original markdown formatting, including headings, lists, code blocks, links, and images.`,
|
|
41
|
+
`Do not change any non-text elements or their formatting.`,
|
|
42
|
+
`Ensure that technical terms and code snippets remain unchanged.`,
|
|
43
|
+
`Provide a natural and fluent translation suitable for readers familiar with the subject matter.`,
|
|
44
|
+
].join('\n'),
|
|
14
45
|
},
|
|
15
46
|
{
|
|
16
47
|
role: 'user',
|
package/src/build/pipeline.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import * as fs from 'fs/promises';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { translateMarkdown } from '../ai/translateMarkdown';
|
|
4
|
-
import { findMarkdownEntries } from '../findEntries';
|
|
5
4
|
import { loadMetaData, MetaData, saveMetaData } from '../metadata';
|
|
6
5
|
import { INPUT_DIR, ZEN_DIR, ZEN_DIST_DIR, ZEN_SRC_DIR } from '../paths';
|
|
7
6
|
import { extractMetadataByAI } from '../process/extractMetadataByAI';
|
|
7
|
+
import { scanSourceFiles } from '../process/scanSourceFiles';
|
|
8
8
|
import { renderTemplates } from '../process/template';
|
|
9
|
-
import { calculateFileHash } from '../scan/files';
|
|
10
9
|
import { BuildOptions } from '../types';
|
|
11
10
|
import { updateFrontmatter } from '../utils/frontmatter';
|
|
12
11
|
|
|
@@ -28,49 +27,6 @@ async function validateConfig(options: BuildOptions): Promise<void> {
|
|
|
28
27
|
MetaData.options = options;
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
/**
|
|
32
|
-
* 扫描源文件
|
|
33
|
-
*/
|
|
34
|
-
async function scanSourceFiles(): Promise<void> {
|
|
35
|
-
console.log(`🔍 Scanning source directory...`);
|
|
36
|
-
const markdownFiles = await findMarkdownEntries(INPUT_DIR);
|
|
37
|
-
const hashes = new Set<string>();
|
|
38
|
-
|
|
39
|
-
for (const relativePath of markdownFiles) {
|
|
40
|
-
const fullPath = path.join(INPUT_DIR, relativePath);
|
|
41
|
-
|
|
42
|
-
try {
|
|
43
|
-
// 检查文件是否存在
|
|
44
|
-
await fs.access(fullPath);
|
|
45
|
-
|
|
46
|
-
const hash = await calculateFileHash(fullPath);
|
|
47
|
-
|
|
48
|
-
hashes.add(hash);
|
|
49
|
-
|
|
50
|
-
const metaWithSameHash = MetaData.files.find(f => f.hash === hash);
|
|
51
|
-
if (metaWithSameHash) {
|
|
52
|
-
metaWithSameHash.path = relativePath;
|
|
53
|
-
} else {
|
|
54
|
-
// 如果没有相同哈希的元数据,则添加一个新的占位符
|
|
55
|
-
MetaData.files.push({
|
|
56
|
-
hash,
|
|
57
|
-
path: relativePath,
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
} catch (error) {
|
|
61
|
-
console.warn(`⚠️ File not found or inaccessible: ${fullPath}`, error);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
// 移除不再存在的文件元数据
|
|
65
|
-
MetaData.files = MetaData.files.filter(f => hashes.has(f.hash));
|
|
66
|
-
|
|
67
|
-
console.log(`✅ Found ${MetaData.files.length} Markdown files`);
|
|
68
|
-
|
|
69
|
-
if (MetaData.files.length === 0) {
|
|
70
|
-
console.warn(`⚠️ No Markdown files found in ${INPUT_DIR}`);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
30
|
/**
|
|
75
31
|
* 存储母语文件到 .zen/src
|
|
76
32
|
*/
|
package/src/languages.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export const LANGUAGE_NAMES: Record<string, string> = {
|
|
2
|
+
// 现有语言
|
|
2
3
|
'zh-Hans': '简体中文',
|
|
3
4
|
'en-US': 'English',
|
|
4
5
|
'ja-JP': '日本語',
|
|
@@ -7,4 +8,30 @@ export const LANGUAGE_NAMES: Record<string, string> = {
|
|
|
7
8
|
'fr-FR': 'Français',
|
|
8
9
|
'de-DE': 'Deutsch',
|
|
9
10
|
'ru-RU': 'Русский',
|
|
11
|
+
|
|
12
|
+
// 补充缺失的项目支持语言
|
|
13
|
+
'pt-PT': 'Português',
|
|
14
|
+
|
|
15
|
+
// 欧洲主要语言
|
|
16
|
+
'it-IT': 'Italiano',
|
|
17
|
+
'nl-NL': 'Nederlands',
|
|
18
|
+
'pl-PL': 'Polski',
|
|
19
|
+
'sv-SE': 'Svenska',
|
|
20
|
+
'fi-FI': 'Suomi',
|
|
21
|
+
'da-DK': 'Dansk',
|
|
22
|
+
'no-NO': 'Norsk',
|
|
23
|
+
|
|
24
|
+
// 亚洲主要语言
|
|
25
|
+
'zh-Hant': '繁體中文',
|
|
26
|
+
'hi-IN': 'हिन्दी',
|
|
27
|
+
'ar-SA': 'العربية',
|
|
28
|
+
'th-TH': 'ไทย',
|
|
29
|
+
'vi-VN': 'Tiếng Việt',
|
|
30
|
+
'id-ID': 'Bahasa Indonesia',
|
|
31
|
+
|
|
32
|
+
// 其他重要语言
|
|
33
|
+
'pt-BR': 'Português (Brasil)',
|
|
34
|
+
'es-MX': 'Español (México)',
|
|
35
|
+
'tr-TR': 'Türkçe',
|
|
36
|
+
'uk-UA': 'Українська',
|
|
10
37
|
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { findMarkdownEntries } from '../findEntries';
|
|
4
|
+
import { MetaData } from '../metadata';
|
|
5
|
+
import { INPUT_DIR } from '../paths';
|
|
6
|
+
import { sha256 } from '../utils/sha256';
|
|
7
|
+
|
|
8
|
+
const extractLinksFromMarkdown = (content: string): string[] => {
|
|
9
|
+
const linkRegex = /\[.*?\]\((.*?)\)/g;
|
|
10
|
+
const links: string[] = [];
|
|
11
|
+
let match;
|
|
12
|
+
while ((match = linkRegex.exec(content)) !== null) {
|
|
13
|
+
links.push(match[1]);
|
|
14
|
+
}
|
|
15
|
+
return links;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 扫描源文件
|
|
20
|
+
*/
|
|
21
|
+
export async function scanSourceFiles(): Promise<void> {
|
|
22
|
+
console.log(`🔍 Scanning source directory...`);
|
|
23
|
+
const markdownFiles = await findMarkdownEntries(INPUT_DIR);
|
|
24
|
+
const hashes = new Set<string>();
|
|
25
|
+
|
|
26
|
+
for (const relativePath of markdownFiles) {
|
|
27
|
+
const fullPath = path.join(INPUT_DIR, relativePath);
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
// 检查文件是否存在
|
|
31
|
+
|
|
32
|
+
const content = await readFile(fullPath, 'utf-8'); // 确保文件可读
|
|
33
|
+
|
|
34
|
+
const hash = sha256(content);
|
|
35
|
+
const links = extractLinksFromMarkdown(content);
|
|
36
|
+
console.info(` - Found file: ${relativePath} (hash: ${hash})`);
|
|
37
|
+
console.info(` Links: ${links.join(', ') || 'None'}`);
|
|
38
|
+
|
|
39
|
+
hashes.add(hash);
|
|
40
|
+
|
|
41
|
+
const metaWithSameHash = MetaData.files.find(f => f.hash === hash);
|
|
42
|
+
if (metaWithSameHash) {
|
|
43
|
+
metaWithSameHash.path = relativePath;
|
|
44
|
+
metaWithSameHash.links = links;
|
|
45
|
+
} else {
|
|
46
|
+
// 如果没有相同哈希的元数据,则添加一个新的占位符
|
|
47
|
+
MetaData.files.push({
|
|
48
|
+
hash,
|
|
49
|
+
path: relativePath,
|
|
50
|
+
links,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.warn(`⚠️ File not found or inaccessible: ${fullPath}`, error);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// 移除不再存在的文件元数据
|
|
58
|
+
MetaData.files = MetaData.files.filter(f => hashes.has(f.hash));
|
|
59
|
+
|
|
60
|
+
console.log(`✅ Found ${MetaData.files.length} Markdown files`);
|
|
61
|
+
|
|
62
|
+
if (MetaData.files.length === 0) {
|
|
63
|
+
console.warn(`⚠️ No Markdown files found in ${INPUT_DIR}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
package/src/process/template.ts
CHANGED
|
@@ -31,11 +31,16 @@ function generateLanguageSwitcher(templateData: TemplateData): string {
|
|
|
31
31
|
.join('');
|
|
32
32
|
|
|
33
33
|
return `<div class="language-switcher">
|
|
34
|
-
<span class="lang-label">Language:</span>
|
|
35
34
|
<ul class="lang-list">${items}</ul>
|
|
36
35
|
</div>`;
|
|
37
36
|
}
|
|
38
37
|
|
|
38
|
+
const generateTagsHtml = (tags: string[]): string => {
|
|
39
|
+
return `<ul class="tags-list">${tags
|
|
40
|
+
.map(tag => `<li class="tag-item">${tag}</li>`)
|
|
41
|
+
.join('')}</ul>`;
|
|
42
|
+
};
|
|
43
|
+
|
|
39
44
|
/**
|
|
40
45
|
* 生成导航 HTML
|
|
41
46
|
* @param navigation 导航树
|
|
@@ -79,6 +84,33 @@ async function generateNavigationHtml(data: TemplateData): Promise<string> {
|
|
|
79
84
|
.join('')}</ul>`;
|
|
80
85
|
}
|
|
81
86
|
|
|
87
|
+
const replaceInnerLinks = (data: TemplateData, markdownContent: string): string => {
|
|
88
|
+
let content = markdownContent;
|
|
89
|
+
for (const link of data.file.links) {
|
|
90
|
+
if (URL.canParse(link)) continue; // 跳过绝对 URL
|
|
91
|
+
|
|
92
|
+
const targetPath = path.resolve('/', path.dirname(data.file.path), link).slice(1);
|
|
93
|
+
|
|
94
|
+
const targetFile = MetaData.files.find(f => f.path === targetPath);
|
|
95
|
+
|
|
96
|
+
if (!targetFile) {
|
|
97
|
+
console.warn(`⚠️ Link target not found for ${link} in file ${data.file.path}`);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
// 替换链接
|
|
101
|
+
const targetLink = path.join(
|
|
102
|
+
MetaData.options.baseUrl ?? '/',
|
|
103
|
+
data.lang,
|
|
104
|
+
targetFile.hash + '.html'
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// 全局替换链接
|
|
108
|
+
const linksRegex = new RegExp(`\\]\\(${link.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\)`, 'g');
|
|
109
|
+
content = content.replace(linksRegex, `](${targetLink})`);
|
|
110
|
+
}
|
|
111
|
+
return content;
|
|
112
|
+
};
|
|
113
|
+
|
|
82
114
|
interface TemplateData {
|
|
83
115
|
file: MetaDataStore['files'][0];
|
|
84
116
|
content: string;
|
|
@@ -98,7 +130,7 @@ async function renderTemplate(template: string, data: TemplateData): Promise<str
|
|
|
98
130
|
const markdownContent = data.content;
|
|
99
131
|
const { frontmatter, body } = parseFrontmatter(markdownContent);
|
|
100
132
|
|
|
101
|
-
const htmlContent = convertMarkdownToHtml(body);
|
|
133
|
+
const htmlContent = convertMarkdownToHtml(replaceInnerLinks(data, body));
|
|
102
134
|
|
|
103
135
|
let result = template;
|
|
104
136
|
|
|
@@ -113,16 +145,16 @@ async function renderTemplate(template: string, data: TemplateData): Promise<str
|
|
|
113
145
|
// 替换元数据变量
|
|
114
146
|
if (frontmatter) {
|
|
115
147
|
result = result.replace(/{{summary}}/g, frontmatter.summary || '');
|
|
116
|
-
result = result.replace(/{{tags}}/g, frontmatter.tags
|
|
117
|
-
result = result.replace(/{{inferred_date}}/g, frontmatter.inferred_date || '');
|
|
118
|
-
result = result.replace(/{{inferred_lang}}/g, frontmatter.inferred_lang || '');
|
|
148
|
+
result = result.replace(/{{tags}}/g, generateTagsHtml(frontmatter.tags || []));
|
|
149
|
+
result = result.replace(/{{inferred_date}}/g, frontmatter.inferred_date || '--');
|
|
150
|
+
result = result.replace(/{{inferred_lang}}/g, frontmatter.inferred_lang || '--');
|
|
119
151
|
}
|
|
120
152
|
|
|
121
153
|
// 替换语言相关变量
|
|
122
154
|
result = result.replace(/{{lang}}/g, data.lang || '');
|
|
123
155
|
if (langs && langs.length > 1 && data.lang) {
|
|
124
156
|
const langSwitcher = generateLanguageSwitcher(data);
|
|
125
|
-
result = result.replace(
|
|
157
|
+
result = result.replace(/{{language_switcher}}/g, langSwitcher);
|
|
126
158
|
}
|
|
127
159
|
|
|
128
160
|
return result;
|
package/src/types.ts
CHANGED
package/dist/scan/files.d.ts
DELETED
package/dist/scan/files.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../src/scan/files.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQzE"}
|
package/dist/scan/files.js
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.calculateFileHash = calculateFileHash;
|
|
37
|
-
const crypto = __importStar(require("crypto"));
|
|
38
|
-
const fs = __importStar(require("fs/promises"));
|
|
39
|
-
/**
|
|
40
|
-
* 计算文件内容的 SHA256 哈希值
|
|
41
|
-
* @param filePath 文件路径
|
|
42
|
-
* @returns 文件的哈希值,如果读取失败则返回空字符串
|
|
43
|
-
*/
|
|
44
|
-
async function calculateFileHash(filePath) {
|
|
45
|
-
try {
|
|
46
|
-
const content = await fs.readFile(filePath, 'utf-8');
|
|
47
|
-
return crypto.createHash('sha256').update(content).digest('hex');
|
|
48
|
-
}
|
|
49
|
-
catch (error) {
|
|
50
|
-
console.warn(`⚠️ Failed to calculate hash for ${filePath}:`, error);
|
|
51
|
-
return '';
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
//# sourceMappingURL=files.js.map
|
package/dist/scan/files.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"files.js","sourceRoot":"","sources":["../../src/scan/files.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAQA,8CAQC;AAhBD,+CAAiC;AACjC,gDAAkC;AAElC;;;;GAIG;AACI,KAAK,UAAU,iBAAiB,CAAC,QAAgB;IACtD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACrD,OAAO,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACnE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,mCAAmC,QAAQ,GAAG,EAAE,KAAK,CAAC,CAAC;QACpE,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
|
package/src/scan/files.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import * as crypto from 'crypto';
|
|
2
|
-
import * as fs from 'fs/promises';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* 计算文件内容的 SHA256 哈希值
|
|
6
|
-
* @param filePath 文件路径
|
|
7
|
-
* @returns 文件的哈希值,如果读取失败则返回空字符串
|
|
8
|
-
*/
|
|
9
|
-
export async function calculateFileHash(filePath: string): Promise<string> {
|
|
10
|
-
try {
|
|
11
|
-
const content = await fs.readFile(filePath, 'utf-8');
|
|
12
|
-
return crypto.createHash('sha256').update(content).digest('hex');
|
|
13
|
-
} catch (error) {
|
|
14
|
-
console.warn(`⚠️ Failed to calculate hash for ${filePath}:`, error);
|
|
15
|
-
return '';
|
|
16
|
-
}
|
|
17
|
-
}
|