@v1hz/md2docx 1.0.4 → 1.1.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/config/config.json +1 -1
- package/config/config.schema.json +1 -1
- package/package.json +1 -1
- package/src/cli.ts +4 -0
- package/src/index.ts +30 -4
- package/src/paths.ts +3 -6
- package/src/style/extract.ts +1 -8
- package/src/style/generate.ts +31 -0
package/config/config.json
CHANGED
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -132,6 +132,7 @@ export function formatHelp(configOptions: ConfigOption[]): string {
|
|
|
132
132
|
const lines = [
|
|
133
133
|
"用法:",
|
|
134
134
|
" md2docx <md-path> [选项]",
|
|
135
|
+
" md2docx clean",
|
|
135
136
|
"",
|
|
136
137
|
"选项:",
|
|
137
138
|
" -o <path> 输出 docx 路径",
|
|
@@ -140,6 +141,9 @@ export function formatHelp(configOptions: ConfigOption[]): string {
|
|
|
140
141
|
" -v, --version 显示版本号",
|
|
141
142
|
" -h, --help 显示帮助",
|
|
142
143
|
"",
|
|
144
|
+
"子命令:",
|
|
145
|
+
" clean 清除 tmp/ 缓存",
|
|
146
|
+
"",
|
|
143
147
|
"配置覆盖:",
|
|
144
148
|
];
|
|
145
149
|
|
package/src/index.ts
CHANGED
|
@@ -1,17 +1,41 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
-
import { mkdirSync, writeFileSync } from "fs";
|
|
3
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from "fs";
|
|
4
4
|
import { dirname, parse, resolve } from "path";
|
|
5
5
|
import { $ } from "bun";
|
|
6
6
|
|
|
7
7
|
import { applyConfigOverrides, formatHelp, getConfigOptions, parseCliArgs } from "./cli";
|
|
8
8
|
import { loadConfig } from "./config";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
CONFIG_PATH,
|
|
11
|
+
CONFIG_SCHEMA_PATH,
|
|
12
|
+
PKG_DIR,
|
|
13
|
+
TMP_DIR,
|
|
14
|
+
formattedMdPath,
|
|
15
|
+
preprocessDir,
|
|
16
|
+
} from "./paths";
|
|
10
17
|
import { preprocess } from "./preprocess/index";
|
|
11
18
|
import { startWebEditor } from "./web";
|
|
19
|
+
import { ensureTemplateDocx } from "./style/generate";
|
|
20
|
+
|
|
21
|
+
async function cleanCache(): Promise<number> {
|
|
22
|
+
const resolved = resolve(TMP_DIR);
|
|
23
|
+
if (existsSync(resolved)) {
|
|
24
|
+
rmSync(resolved, { recursive: true, force: true });
|
|
25
|
+
console.log(`已清除缓存:${resolved}`);
|
|
26
|
+
} else {
|
|
27
|
+
console.log("缓存目录不存在,无需清理。");
|
|
28
|
+
}
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
12
31
|
|
|
13
32
|
export async function run(args: string[]): Promise<number> {
|
|
14
33
|
try {
|
|
34
|
+
// 检查子命令
|
|
35
|
+
const subcommand = args[0];
|
|
36
|
+
if (subcommand === "clean") {
|
|
37
|
+
return await cleanCache();
|
|
38
|
+
}
|
|
15
39
|
const schema = JSON.parse(await Bun.file(CONFIG_SCHEMA_PATH).text());
|
|
16
40
|
const configOptions = getConfigOptions(schema);
|
|
17
41
|
const cli = parseCliArgs(args, configOptions);
|
|
@@ -33,7 +57,7 @@ export async function run(args: string[]): Promise<number> {
|
|
|
33
57
|
}
|
|
34
58
|
|
|
35
59
|
const mdPath = resolve(cli.mdPath!);
|
|
36
|
-
if (!(
|
|
60
|
+
if (!existsSync(mdPath)) throw new Error(`找不到 Markdown 文件:${mdPath}`);
|
|
37
61
|
|
|
38
62
|
const config = await loadConfig(cli.configPath ?? CONFIG_PATH);
|
|
39
63
|
const cfg = applyConfigOverrides(config, cli.overrides);
|
|
@@ -46,10 +70,12 @@ export async function run(args: string[]): Promise<number> {
|
|
|
46
70
|
writeFileSync(mdOutput, formattedMd, "utf-8");
|
|
47
71
|
|
|
48
72
|
if (cfg.pandoc.enabled) {
|
|
73
|
+
const templatePath = await ensureTemplateDocx();
|
|
49
74
|
const configuredName = cfg.pandoc.outputName.replaceAll("{file_name}", baseName);
|
|
50
75
|
const docxOutput = resolve(cli.outputPath ?? configuredName);
|
|
51
76
|
mkdirSync(dirname(docxOutput), { recursive: true });
|
|
52
|
-
const result =
|
|
77
|
+
const result =
|
|
78
|
+
await $`pandoc ${mdOutput} -o ${docxOutput} --reference-doc=${templatePath}`.nothrow();
|
|
53
79
|
if (result.exitCode !== 0) {
|
|
54
80
|
console.error(`pandoc 转换失败 (exit code ${result.exitCode}):`, result.stderr.toString());
|
|
55
81
|
return 1;
|
package/src/paths.ts
CHANGED
|
@@ -27,11 +27,8 @@ export function formattedMdPath(baseName: string): string {
|
|
|
27
27
|
/** 样式模块目录 */
|
|
28
28
|
export const STYLE_DIR = `${TMP_DIR}/style`;
|
|
29
29
|
|
|
30
|
-
/**
|
|
31
|
-
export const
|
|
32
|
-
|
|
33
|
-
/** 样式 JSON 文件路径(提取结果的输出 / 模板生成的输入) */
|
|
34
|
-
export const STYLE_JSON = `${STYLE_DIR}/style.json`;
|
|
30
|
+
/** 样式配置文件(相对于包安装目录),用户维护的样式定义 */
|
|
31
|
+
export const STYLE_CONFIG = resolve(PKG_DIR, "config/style.json");
|
|
35
32
|
|
|
36
33
|
/** 生成的模板 docx 文件路径 */
|
|
37
|
-
export const STYLE_TEMPLATE_DOCX = `${STYLE_DIR}/
|
|
34
|
+
export const STYLE_TEMPLATE_DOCX = `${STYLE_DIR}/style.docx`;
|
package/src/style/extract.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFileSync
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
2
|
import { DOMParser } from "@xmldom/xmldom";
|
|
3
3
|
import { useNamespaces } from "xpath";
|
|
4
4
|
import PizZip from "pizzip";
|
|
@@ -372,10 +372,3 @@ export function extractStylesFromDocx(docxPath: string): ExtractedStyles {
|
|
|
372
372
|
|
|
373
373
|
return result;
|
|
374
374
|
}
|
|
375
|
-
|
|
376
|
-
if (import.meta.main) {
|
|
377
|
-
const { STYLE_REF_DOCX, STYLE_JSON } = await import("../paths");
|
|
378
|
-
const styles = extractStylesFromDocx(STYLE_REF_DOCX);
|
|
379
|
-
writeFileSync(STYLE_JSON, JSON.stringify(styles, null, 2), "utf-8");
|
|
380
|
-
console.log(`Styles extracted from ${STYLE_REF_DOCX} to ${STYLE_JSON}`);
|
|
381
|
-
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { readFileSync, mkdirSync } from "fs";
|
|
2
|
+
import { Document, Packer } from "docx";
|
|
3
|
+
import { dirname } from "path";
|
|
4
|
+
import { STYLE_CONFIG, STYLE_TEMPLATE_DOCX } from "../paths";
|
|
5
|
+
|
|
6
|
+
export async function generateTemplateDocx(): Promise<void> {
|
|
7
|
+
const styles = JSON.parse(readFileSync(STYLE_CONFIG, "utf-8"));
|
|
8
|
+
const doc = new Document({
|
|
9
|
+
styles: styles,
|
|
10
|
+
sections: [],
|
|
11
|
+
});
|
|
12
|
+
const buf = await Packer.toBuffer(doc);
|
|
13
|
+
mkdirSync(dirname(STYLE_TEMPLATE_DOCX), { recursive: true });
|
|
14
|
+
await Bun.write(STYLE_TEMPLATE_DOCX, buf);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let _generatePromise: Promise<void> | null = null;
|
|
18
|
+
|
|
19
|
+
export async function ensureTemplateDocx(): Promise<string> {
|
|
20
|
+
const exists = await Bun.file(STYLE_TEMPLATE_DOCX).exists();
|
|
21
|
+
if (!exists) {
|
|
22
|
+
if (_generatePromise === null) {
|
|
23
|
+
_generatePromise = generateTemplateDocx().catch((e) => {
|
|
24
|
+
_generatePromise = null; // 重置,允许下次重试
|
|
25
|
+
throw e;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
await _generatePromise;
|
|
29
|
+
}
|
|
30
|
+
return STYLE_TEMPLATE_DOCX;
|
|
31
|
+
}
|