xhs-md-renderer 0.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/LICENSE +21 -0
- package/README.md +49 -0
- package/bin/xhs-md-render.js +3 -0
- package/dist/browser-renderer.d.ts +8 -0
- package/dist/browser-renderer.js +156 -0
- package/dist/browser-renderer.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +418 -0
- package/dist/index.js.map +1 -0
- package/dist/vendor/core/image.d.ts +27 -0
- package/dist/vendor/core/image.js +265 -0
- package/dist/vendor/core/image.js.map +1 -0
- package/dist/vendor/core/index.d.ts +6 -0
- package/dist/vendor/core/index.js +6 -0
- package/dist/vendor/core/index.js.map +1 -0
- package/dist/vendor/core/layout.d.ts +22 -0
- package/dist/vendor/core/layout.js +276 -0
- package/dist/vendor/core/layout.js.map +1 -0
- package/dist/vendor/core/models.d.ts +138 -0
- package/dist/vendor/core/models.js +2 -0
- package/dist/vendor/core/models.js.map +1 -0
- package/dist/vendor/core/node-images.d.ts +2 -0
- package/dist/vendor/core/node-images.js +327 -0
- package/dist/vendor/core/node-images.js.map +1 -0
- package/dist/vendor/core/node.d.ts +31 -0
- package/dist/vendor/core/node.js +199 -0
- package/dist/vendor/core/node.js.map +1 -0
- package/dist/vendor/core/parser.d.ts +6 -0
- package/dist/vendor/core/parser.js +158 -0
- package/dist/vendor/core/parser.js.map +1 -0
- package/dist/vendor/core/preview.d.ts +6 -0
- package/dist/vendor/core/preview.js +325 -0
- package/dist/vendor/core/preview.js.map +1 -0
- package/dist/vendor/core/themes.d.ts +21 -0
- package/dist/vendor/core/themes.js +124 -0
- package/dist/vendor/core/themes.js.map +1 -0
- package/package.json +57 -0
- package/web-dist/assets/index-Br8RJhby.js +45 -0
- package/web-dist/assets/index-CwEw55IG.css +1 -0
- package/web-dist/index.html +13 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 xxih
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# xhs-md-renderer
|
|
2
|
+
|
|
3
|
+
Render Markdown into Xiaohongshu-friendly image pages from the command line.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g xhs-md-renderer
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or run it on demand:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx xhs-md-renderer --input ./note.md --output ./.xhs-output
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
xhs-md-renderer --input ./note.md --output ./.xhs-output
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Common options:
|
|
24
|
+
|
|
25
|
+
- `--renderer auto|node|browser`
|
|
26
|
+
- `--title "My Note"`
|
|
27
|
+
- `--theme paper`
|
|
28
|
+
- `--font-family "PingFang SC, sans-serif"`
|
|
29
|
+
- `--font-size 16`
|
|
30
|
+
- `--config-dir ./.xhs-md-renderer`
|
|
31
|
+
|
|
32
|
+
## Config Directory
|
|
33
|
+
|
|
34
|
+
The CLI looks for a project config directory named `.xhs-md-renderer` in this order:
|
|
35
|
+
|
|
36
|
+
1. `--config-dir <path>`
|
|
37
|
+
2. Search upward from the input Markdown file directory
|
|
38
|
+
3. Built-in defaults
|
|
39
|
+
|
|
40
|
+
Supported files inside the config directory:
|
|
41
|
+
|
|
42
|
+
- `render.json`
|
|
43
|
+
- `avatar.png|jpg|jpeg|webp|gif`
|
|
44
|
+
|
|
45
|
+
## Notes
|
|
46
|
+
|
|
47
|
+
- The Node renderer works best when the machine has usable CJK fonts installed.
|
|
48
|
+
- The browser renderer needs a local Chromium-based browser.
|
|
49
|
+
- Every export writes `manifest.json`, `pages.json`, and `layout-report.json`.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ExportBundle, RenderConfigOverrides } from "./vendor/core/index.js";
|
|
2
|
+
export declare function writeBrowserExportBundle(input: {
|
|
3
|
+
markdown: string;
|
|
4
|
+
title?: string;
|
|
5
|
+
outputDir: string;
|
|
6
|
+
renderConfig?: RenderConfigOverrides;
|
|
7
|
+
markdownFilePath?: string;
|
|
8
|
+
}): Promise<ExportBundle>;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import { extname, join, normalize, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { chromium } from "playwright-core";
|
|
6
|
+
import { prepareExportDocument } from "./vendor/core/node.js";
|
|
7
|
+
const WEB_DIST_CANDIDATES = [
|
|
8
|
+
fileURLToPath(new URL("../web-dist", import.meta.url)),
|
|
9
|
+
fileURLToPath(new URL("../../../apps/web/dist", import.meta.url))
|
|
10
|
+
];
|
|
11
|
+
const DEFAULT_BROWSER_PATHS = [
|
|
12
|
+
process.env.XHS_MD_BROWSER_EXECUTABLE_PATH,
|
|
13
|
+
process.env.CHROME_PATH,
|
|
14
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
15
|
+
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
16
|
+
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
17
|
+
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
|
|
18
|
+
"/usr/bin/google-chrome",
|
|
19
|
+
"/usr/bin/google-chrome-stable",
|
|
20
|
+
"/usr/bin/chromium-browser",
|
|
21
|
+
"/usr/bin/chromium"
|
|
22
|
+
];
|
|
23
|
+
const MIME_TYPES = {
|
|
24
|
+
".css": "text/css; charset=utf-8",
|
|
25
|
+
".html": "text/html; charset=utf-8",
|
|
26
|
+
".ico": "image/x-icon",
|
|
27
|
+
".js": "text/javascript; charset=utf-8",
|
|
28
|
+
".json": "application/json; charset=utf-8",
|
|
29
|
+
".png": "image/png",
|
|
30
|
+
".svg": "image/svg+xml; charset=utf-8"
|
|
31
|
+
};
|
|
32
|
+
async function isFile(targetPath) {
|
|
33
|
+
try {
|
|
34
|
+
return (await stat(targetPath)).isFile();
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function resolveBrowserExecutablePath() {
|
|
41
|
+
for (const browserPath of DEFAULT_BROWSER_PATHS) {
|
|
42
|
+
if (!browserPath) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (await isFile(browserPath)) {
|
|
46
|
+
return browserPath;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
throw new Error("No supported local Chromium browser found. Set XHS_MD_BROWSER_EXECUTABLE_PATH or install Chrome, Chromium, Edge, or Brave.");
|
|
50
|
+
}
|
|
51
|
+
async function ensureWebDistDir() {
|
|
52
|
+
for (const rootDir of WEB_DIST_CANDIDATES) {
|
|
53
|
+
const distIndexPath = join(rootDir, "index.html");
|
|
54
|
+
if (await isFile(distIndexPath)) {
|
|
55
|
+
return rootDir;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
throw new Error(`Web dist not found. Expected one of: ${WEB_DIST_CANDIDATES.join(", ")}. Run npm run build first.`);
|
|
59
|
+
}
|
|
60
|
+
async function startStaticServer(rootDir) {
|
|
61
|
+
const server = createServer(async (request, response) => {
|
|
62
|
+
const requestUrl = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
63
|
+
const pathname = requestUrl.pathname === "/" ? "/index.html" : requestUrl.pathname;
|
|
64
|
+
const candidatePath = normalize(join(rootDir, pathname));
|
|
65
|
+
const safePath = candidatePath.startsWith(rootDir) ? candidatePath : join(rootDir, "index.html");
|
|
66
|
+
const filePath = (await isFile(safePath)) ? safePath : join(rootDir, "index.html");
|
|
67
|
+
try {
|
|
68
|
+
const fileBuffer = await readFile(filePath);
|
|
69
|
+
response.writeHead(200, {
|
|
70
|
+
"Content-Type": MIME_TYPES[extname(filePath)] ?? "application/octet-stream",
|
|
71
|
+
"Cache-Control": "no-store"
|
|
72
|
+
});
|
|
73
|
+
response.end(fileBuffer);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
response.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
|
|
77
|
+
response.end("Not found");
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
await new Promise((resolvePromise, rejectPromise) => {
|
|
81
|
+
server.once("error", rejectPromise);
|
|
82
|
+
server.listen(0, "127.0.0.1", () => resolvePromise());
|
|
83
|
+
});
|
|
84
|
+
const address = server.address();
|
|
85
|
+
if (!address || typeof address === "string") {
|
|
86
|
+
throw new Error("Unable to resolve local preview server address.");
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
origin: `http://127.0.0.1:${address.port}`,
|
|
90
|
+
close: async () => {
|
|
91
|
+
await new Promise((resolvePromise, rejectPromise) => {
|
|
92
|
+
server.close((error) => (error ? rejectPromise(error) : resolvePromise()));
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export async function writeBrowserExportBundle(input) {
|
|
98
|
+
const rootDir = await ensureWebDistDir();
|
|
99
|
+
const prepared = await prepareExportDocument(input);
|
|
100
|
+
const outputDir = resolve(input.outputDir);
|
|
101
|
+
const pagesDir = join(outputDir, "pages");
|
|
102
|
+
const browserExecutablePath = await resolveBrowserExecutablePath();
|
|
103
|
+
const server = await startStaticServer(rootDir);
|
|
104
|
+
let browser;
|
|
105
|
+
try {
|
|
106
|
+
browser = await chromium.launch({
|
|
107
|
+
executablePath: browserExecutablePath,
|
|
108
|
+
headless: true
|
|
109
|
+
});
|
|
110
|
+
const page = await browser.newPage({
|
|
111
|
+
viewport: {
|
|
112
|
+
width: Math.min(prepared.config.width, 1600),
|
|
113
|
+
height: Math.min(prepared.config.height, 1200)
|
|
114
|
+
},
|
|
115
|
+
deviceScaleFactor: 1
|
|
116
|
+
});
|
|
117
|
+
await page.addInitScript((payload) => {
|
|
118
|
+
window.__XHS_RENDER_PAYLOAD__ =
|
|
119
|
+
payload;
|
|
120
|
+
}, prepared);
|
|
121
|
+
await page.goto(`${server.origin}/?mode=render`, {
|
|
122
|
+
waitUntil: "networkidle"
|
|
123
|
+
});
|
|
124
|
+
await page.waitForFunction(() => window.__XHS_RENDER_STATUS__?.ready === true);
|
|
125
|
+
await mkdir(pagesDir, { recursive: true });
|
|
126
|
+
await writeFile(join(outputDir, "manifest.json"), JSON.stringify(prepared.manifest, null, 2), "utf8");
|
|
127
|
+
await writeFile(join(outputDir, "layout-report.json"), JSON.stringify(prepared.layoutReport, null, 2), "utf8");
|
|
128
|
+
await writeFile(join(outputDir, "pages.json"), JSON.stringify(prepared.pages, null, 2), "utf8");
|
|
129
|
+
const bundlePages = [];
|
|
130
|
+
for (const manifestPage of prepared.manifest.pages) {
|
|
131
|
+
const locator = page.locator(`[data-export-page="${manifestPage.id}"]`);
|
|
132
|
+
const png = await locator.screenshot({
|
|
133
|
+
type: "png"
|
|
134
|
+
});
|
|
135
|
+
const outputFilePath = join(pagesDir, manifestPage.fileName);
|
|
136
|
+
await writeFile(outputFilePath, png);
|
|
137
|
+
bundlePages.push({
|
|
138
|
+
model: prepared.pages[manifestPage.index],
|
|
139
|
+
fileName: manifestPage.fileName,
|
|
140
|
+
png
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
manifest: prepared.manifest,
|
|
145
|
+
layoutReport: prepared.layoutReport,
|
|
146
|
+
pages: bundlePages
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
if (browser) {
|
|
151
|
+
await browser.close();
|
|
152
|
+
}
|
|
153
|
+
await server.close();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=browser-renderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-renderer.js","sourceRoot":"","sources":["../src/browser-renderer.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFile, stat } from "node:fs/promises";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
import { writeExportBundle } from "./vendor/core/node.js";
|
|
6
|
+
import { writeBrowserExportBundle } from "./browser-renderer.js";
|
|
7
|
+
const DEFAULT_CONFIG_DIR_NAME = ".xhs-md-renderer";
|
|
8
|
+
const DEFAULT_CONFIG_FILE_NAME = "render.json";
|
|
9
|
+
const DEFAULT_AVATAR_FILE_NAMES = [
|
|
10
|
+
"avatar.png",
|
|
11
|
+
"avatar.jpg",
|
|
12
|
+
"avatar.jpeg",
|
|
13
|
+
"avatar.webp",
|
|
14
|
+
"avatar.gif"
|
|
15
|
+
];
|
|
16
|
+
function printHelp() {
|
|
17
|
+
console.log(`
|
|
18
|
+
xhs-md-render
|
|
19
|
+
|
|
20
|
+
Usage:
|
|
21
|
+
xhs-md-render --input <file.md> --output <dir> [--config-dir ./.xhs-md-renderer]
|
|
22
|
+
[--renderer auto|node|browser]
|
|
23
|
+
[--title "My Note"] [--theme default]
|
|
24
|
+
[--font-family "..."] [--font-size 16]
|
|
25
|
+
[--name "小明"] [--handle "@xiaoming"]
|
|
26
|
+
[--date "2026/03/16"] [--hide-date]
|
|
27
|
+
[--footer-left "左侧文案"] [--footer-right "右侧文案"] [--hide-footer]
|
|
28
|
+
[--avatar ./avatar.png]
|
|
29
|
+
[--body-bottom-padding 180] [--warning-threshold 180]
|
|
30
|
+
|
|
31
|
+
Default config discovery:
|
|
32
|
+
1. If --config-dir is provided, read that directory.
|
|
33
|
+
2. Otherwise search upward from the input Markdown directory for .xhs-md-renderer/.
|
|
34
|
+
3. If found, read .xhs-md-renderer/render.json and optional avatar.* files.
|
|
35
|
+
`.trim());
|
|
36
|
+
}
|
|
37
|
+
function parseNumericFlag(raw, flagName) {
|
|
38
|
+
if (raw === undefined) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
const parsed = Number(raw);
|
|
42
|
+
if (!Number.isFinite(parsed)) {
|
|
43
|
+
throw new Error(`${flagName} must be a number.`);
|
|
44
|
+
}
|
|
45
|
+
return parsed;
|
|
46
|
+
}
|
|
47
|
+
function parseCliOptions(argv) {
|
|
48
|
+
const { values } = parseArgs({
|
|
49
|
+
args: argv,
|
|
50
|
+
options: {
|
|
51
|
+
input: { type: "string", short: "i" },
|
|
52
|
+
output: { type: "string", short: "o" },
|
|
53
|
+
renderer: { type: "string" },
|
|
54
|
+
title: { type: "string", short: "t" },
|
|
55
|
+
theme: { type: "string" },
|
|
56
|
+
"font-family": { type: "string" },
|
|
57
|
+
"font-size": { type: "string" },
|
|
58
|
+
name: { type: "string" },
|
|
59
|
+
handle: { type: "string" },
|
|
60
|
+
date: { type: "string" },
|
|
61
|
+
"hide-date": { type: "boolean" },
|
|
62
|
+
"hide-footer": { type: "boolean" },
|
|
63
|
+
"footer-left": { type: "string" },
|
|
64
|
+
"footer-right": { type: "string" },
|
|
65
|
+
"config-dir": { type: "string" },
|
|
66
|
+
avatar: { type: "string" },
|
|
67
|
+
"body-bottom-padding": { type: "string" },
|
|
68
|
+
"warning-threshold": { type: "string" },
|
|
69
|
+
help: { type: "boolean", short: "h" }
|
|
70
|
+
},
|
|
71
|
+
allowPositionals: false
|
|
72
|
+
});
|
|
73
|
+
if (values.help) {
|
|
74
|
+
printHelp();
|
|
75
|
+
process.exit(0);
|
|
76
|
+
}
|
|
77
|
+
if (!values.input || !values.output) {
|
|
78
|
+
printHelp();
|
|
79
|
+
throw new Error("Both --input and --output are required.");
|
|
80
|
+
}
|
|
81
|
+
const options = {
|
|
82
|
+
input: values.input,
|
|
83
|
+
output: values.output
|
|
84
|
+
};
|
|
85
|
+
if (values.renderer !== undefined) {
|
|
86
|
+
if (values.renderer !== "auto" && values.renderer !== "browser" && values.renderer !== "node") {
|
|
87
|
+
throw new Error("--renderer must be one of: auto, browser, node.");
|
|
88
|
+
}
|
|
89
|
+
options.renderer = values.renderer;
|
|
90
|
+
}
|
|
91
|
+
if (values.title !== undefined) {
|
|
92
|
+
options.title = values.title;
|
|
93
|
+
}
|
|
94
|
+
if (values.theme !== undefined) {
|
|
95
|
+
options.themeId = values.theme;
|
|
96
|
+
}
|
|
97
|
+
if (values["font-family"] !== undefined) {
|
|
98
|
+
options.fontFamily = values["font-family"];
|
|
99
|
+
}
|
|
100
|
+
const fontSize = parseNumericFlag(values["font-size"], "--font-size");
|
|
101
|
+
const bodyBottomPadding = parseNumericFlag(values["body-bottom-padding"], "--body-bottom-padding");
|
|
102
|
+
const warningThreshold = parseNumericFlag(values["warning-threshold"], "--warning-threshold");
|
|
103
|
+
if (fontSize !== undefined) {
|
|
104
|
+
options.fontSize = fontSize;
|
|
105
|
+
}
|
|
106
|
+
if (bodyBottomPadding !== undefined) {
|
|
107
|
+
options.bodyBottomPadding = bodyBottomPadding;
|
|
108
|
+
}
|
|
109
|
+
if (warningThreshold !== undefined) {
|
|
110
|
+
options.warningThreshold = warningThreshold;
|
|
111
|
+
}
|
|
112
|
+
if (values.name !== undefined) {
|
|
113
|
+
options.name = values.name;
|
|
114
|
+
}
|
|
115
|
+
if (values.handle !== undefined) {
|
|
116
|
+
options.handle = values.handle;
|
|
117
|
+
}
|
|
118
|
+
if (values.date !== undefined) {
|
|
119
|
+
options.dateText = values.date;
|
|
120
|
+
options.showDate = true;
|
|
121
|
+
}
|
|
122
|
+
if (values["hide-date"]) {
|
|
123
|
+
options.showDate = false;
|
|
124
|
+
}
|
|
125
|
+
if (values["footer-left"] !== undefined) {
|
|
126
|
+
options.footerLeft = values["footer-left"];
|
|
127
|
+
}
|
|
128
|
+
if (values["footer-right"] !== undefined) {
|
|
129
|
+
options.footerRight = values["footer-right"];
|
|
130
|
+
}
|
|
131
|
+
if (values["hide-footer"]) {
|
|
132
|
+
options.showFooter = false;
|
|
133
|
+
}
|
|
134
|
+
if (values["config-dir"] !== undefined) {
|
|
135
|
+
options.configDir = values["config-dir"];
|
|
136
|
+
}
|
|
137
|
+
if (values.avatar !== undefined) {
|
|
138
|
+
options.avatarPath = values.avatar;
|
|
139
|
+
}
|
|
140
|
+
return options;
|
|
141
|
+
}
|
|
142
|
+
async function isDirectory(targetPath) {
|
|
143
|
+
try {
|
|
144
|
+
return (await stat(targetPath)).isDirectory();
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
async function isFile(targetPath) {
|
|
151
|
+
try {
|
|
152
|
+
return (await stat(targetPath)).isFile();
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async function discoverConfigDir(inputPath, explicitConfigDir) {
|
|
159
|
+
if (explicitConfigDir) {
|
|
160
|
+
const resolvedConfigDir = resolve(explicitConfigDir);
|
|
161
|
+
if (!(await isDirectory(resolvedConfigDir))) {
|
|
162
|
+
throw new Error(`Config directory not found: ${resolvedConfigDir}`);
|
|
163
|
+
}
|
|
164
|
+
return resolvedConfigDir;
|
|
165
|
+
}
|
|
166
|
+
let currentDir = dirname(inputPath);
|
|
167
|
+
while (true) {
|
|
168
|
+
const candidate = join(currentDir, DEFAULT_CONFIG_DIR_NAME);
|
|
169
|
+
if (await isDirectory(candidate)) {
|
|
170
|
+
return candidate;
|
|
171
|
+
}
|
|
172
|
+
const parentDir = dirname(currentDir);
|
|
173
|
+
if (parentDir === currentDir) {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
currentDir = parentDir;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async function readConfigFile(configDir) {
|
|
180
|
+
if (!configDir) {
|
|
181
|
+
return { config: {} };
|
|
182
|
+
}
|
|
183
|
+
const configFilePath = join(configDir, DEFAULT_CONFIG_FILE_NAME);
|
|
184
|
+
if (!(await isFile(configFilePath))) {
|
|
185
|
+
return {
|
|
186
|
+
configFilePath,
|
|
187
|
+
config: {}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
const raw = await readFile(configFilePath, "utf8");
|
|
191
|
+
const parsed = JSON.parse(raw);
|
|
192
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
193
|
+
throw new Error(`Invalid config file: ${configFilePath}`);
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
configFilePath,
|
|
197
|
+
config: parsed
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function mergeRenderConfigOverrides(base = {}, override = {}) {
|
|
201
|
+
const mergedProfile = {
|
|
202
|
+
...(base.profile ?? {}),
|
|
203
|
+
...(override.profile ?? {})
|
|
204
|
+
};
|
|
205
|
+
const mergedLayout = {
|
|
206
|
+
...(base.layout ?? {}),
|
|
207
|
+
...(override.layout ?? {})
|
|
208
|
+
};
|
|
209
|
+
return {
|
|
210
|
+
...base,
|
|
211
|
+
...override,
|
|
212
|
+
...(Object.keys(mergedProfile).length === 0 ? {} : { profile: mergedProfile }),
|
|
213
|
+
...(Object.keys(mergedLayout).length === 0 ? {} : { layout: mergedLayout })
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function buildCliRenderConfig(options) {
|
|
217
|
+
const profile = {
|
|
218
|
+
...(options.name === undefined ? {} : { name: options.name }),
|
|
219
|
+
...(options.handle === undefined ? {} : { handle: options.handle }),
|
|
220
|
+
...(options.showDate === undefined ? {} : { showDate: options.showDate }),
|
|
221
|
+
...(options.dateText === undefined ? {} : { dateText: options.dateText }),
|
|
222
|
+
...(options.showFooter === undefined ? {} : { showFooter: options.showFooter }),
|
|
223
|
+
...(options.footerLeft === undefined ? {} : { footerLeft: options.footerLeft }),
|
|
224
|
+
...(options.footerRight === undefined ? {} : { footerRight: options.footerRight })
|
|
225
|
+
};
|
|
226
|
+
const layout = {
|
|
227
|
+
...(options.bodyBottomPadding === undefined
|
|
228
|
+
? {}
|
|
229
|
+
: { bodyBottomPadding: options.bodyBottomPadding }),
|
|
230
|
+
...(options.warningThreshold === undefined
|
|
231
|
+
? {}
|
|
232
|
+
: { warningThreshold: options.warningThreshold })
|
|
233
|
+
};
|
|
234
|
+
return {
|
|
235
|
+
...(options.themeId === undefined ? {} : { themeId: options.themeId }),
|
|
236
|
+
...(options.fontFamily === undefined ? {} : { fontFamily: options.fontFamily }),
|
|
237
|
+
...(options.fontSize === undefined ? {} : { fontSize: options.fontSize }),
|
|
238
|
+
...(Object.keys(profile).length === 0 ? {} : { profile }),
|
|
239
|
+
...(Object.keys(layout).length === 0 ? {} : { layout })
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function toMimeType(filePath) {
|
|
243
|
+
const lowerPath = filePath.toLowerCase();
|
|
244
|
+
if (lowerPath.endsWith(".png")) {
|
|
245
|
+
return "image/png";
|
|
246
|
+
}
|
|
247
|
+
if (lowerPath.endsWith(".jpg") || lowerPath.endsWith(".jpeg")) {
|
|
248
|
+
return "image/jpeg";
|
|
249
|
+
}
|
|
250
|
+
if (lowerPath.endsWith(".webp")) {
|
|
251
|
+
return "image/webp";
|
|
252
|
+
}
|
|
253
|
+
if (lowerPath.endsWith(".gif")) {
|
|
254
|
+
return "image/gif";
|
|
255
|
+
}
|
|
256
|
+
throw new Error(`Unsupported avatar file type: ${filePath}`);
|
|
257
|
+
}
|
|
258
|
+
async function readAvatarAsDataUrl(filePath) {
|
|
259
|
+
const avatarBuffer = await readFile(filePath);
|
|
260
|
+
return `data:${toMimeType(filePath)};base64,${avatarBuffer.toString("base64")}`;
|
|
261
|
+
}
|
|
262
|
+
async function detectDefaultAvatarPath(configDir) {
|
|
263
|
+
for (const fileName of DEFAULT_AVATAR_FILE_NAMES) {
|
|
264
|
+
const candidate = join(configDir, fileName);
|
|
265
|
+
if (await isFile(candidate)) {
|
|
266
|
+
return candidate;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return undefined;
|
|
270
|
+
}
|
|
271
|
+
async function resolveAvatarOverride(input) {
|
|
272
|
+
if (input.cliAvatarPath) {
|
|
273
|
+
const resolvedAvatarPath = resolve(input.cliAvatarPath);
|
|
274
|
+
if (!(await isFile(resolvedAvatarPath))) {
|
|
275
|
+
throw new Error(`Avatar file not found: ${resolvedAvatarPath}`);
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
avatarSrc: await readAvatarAsDataUrl(resolvedAvatarPath),
|
|
279
|
+
avatarSourcePath: resolvedAvatarPath
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
if (!input.configDir) {
|
|
283
|
+
return {};
|
|
284
|
+
}
|
|
285
|
+
if (input.fileProfile?.avatarPath) {
|
|
286
|
+
const resolvedAvatarPath = resolve(input.configDir, input.fileProfile.avatarPath);
|
|
287
|
+
if (!(await isFile(resolvedAvatarPath))) {
|
|
288
|
+
throw new Error(`Avatar file not found: ${resolvedAvatarPath}`);
|
|
289
|
+
}
|
|
290
|
+
return {
|
|
291
|
+
avatarSrc: await readAvatarAsDataUrl(resolvedAvatarPath),
|
|
292
|
+
avatarSourcePath: resolvedAvatarPath
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
const defaultAvatarPath = await detectDefaultAvatarPath(input.configDir);
|
|
296
|
+
if (!defaultAvatarPath) {
|
|
297
|
+
return {};
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
avatarSrc: await readAvatarAsDataUrl(defaultAvatarPath),
|
|
301
|
+
avatarSourcePath: defaultAvatarPath
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
function stripProfileFileOnlyFields(profile) {
|
|
305
|
+
if (!profile) {
|
|
306
|
+
return undefined;
|
|
307
|
+
}
|
|
308
|
+
const { avatarPath: _avatarPath, ...profileConfig } = profile;
|
|
309
|
+
return profileConfig;
|
|
310
|
+
}
|
|
311
|
+
function printLayoutSummary(layoutReport) {
|
|
312
|
+
console.log("Layout feedback:");
|
|
313
|
+
for (const page of layoutReport.pages) {
|
|
314
|
+
if (page.status === "overflow") {
|
|
315
|
+
console.log(` - Page ${page.pageNumber} [overflow] exceeded safe area by ${page.overflowAmount}px. ${page.recommendation}`);
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
if (page.status === "warning") {
|
|
319
|
+
console.log(` - Page ${page.pageNumber} [warning] remaining bottom space ${page.remainingBottomSpace}px, below warning threshold ${page.warningThreshold}px. ${page.recommendation}`);
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
console.log(` - Page ${page.pageNumber} [ok] remaining bottom space ${page.remainingBottomSpace}px.`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function shouldFallbackToBrowser(error) {
|
|
326
|
+
if (!(error instanceof Error)) {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
return error.message.includes("Unsupported OpenType signature ttcf");
|
|
330
|
+
}
|
|
331
|
+
async function main() {
|
|
332
|
+
const options = parseCliOptions(process.argv.slice(2));
|
|
333
|
+
const inputPath = resolve(options.input);
|
|
334
|
+
const outputPath = resolve(options.output);
|
|
335
|
+
const markdown = await readFile(inputPath, "utf8");
|
|
336
|
+
const configDir = await discoverConfigDir(inputPath, options.configDir);
|
|
337
|
+
const { configFilePath, config } = await readConfigFile(configDir);
|
|
338
|
+
const strippedProfile = stripProfileFileOnlyFields(config.renderConfig?.profile);
|
|
339
|
+
const fileRenderConfig = {
|
|
340
|
+
...(config.renderConfig ?? {}),
|
|
341
|
+
...(strippedProfile === undefined ? {} : { profile: strippedProfile })
|
|
342
|
+
};
|
|
343
|
+
const avatarResolveInput = {};
|
|
344
|
+
if (options.avatarPath !== undefined) {
|
|
345
|
+
avatarResolveInput.cliAvatarPath = options.avatarPath;
|
|
346
|
+
}
|
|
347
|
+
if (configDir !== undefined) {
|
|
348
|
+
avatarResolveInput.configDir = configDir;
|
|
349
|
+
}
|
|
350
|
+
if (config.renderConfig?.profile !== undefined) {
|
|
351
|
+
avatarResolveInput.fileProfile = config.renderConfig.profile;
|
|
352
|
+
}
|
|
353
|
+
const avatarOverride = await resolveAvatarOverride(avatarResolveInput);
|
|
354
|
+
const mergedRenderConfig = mergeRenderConfigOverrides(fileRenderConfig, buildCliRenderConfig(options));
|
|
355
|
+
if (avatarOverride.avatarSrc) {
|
|
356
|
+
mergedRenderConfig.profile = {
|
|
357
|
+
...(mergedRenderConfig.profile ?? {}),
|
|
358
|
+
avatarSrc: avatarOverride.avatarSrc
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
const exportInput = {
|
|
362
|
+
markdown,
|
|
363
|
+
markdownFilePath: inputPath,
|
|
364
|
+
outputDir: outputPath,
|
|
365
|
+
renderConfig: mergedRenderConfig
|
|
366
|
+
};
|
|
367
|
+
const resolvedTitle = options.title ?? config.title;
|
|
368
|
+
if (resolvedTitle !== undefined) {
|
|
369
|
+
exportInput.title = resolvedTitle;
|
|
370
|
+
}
|
|
371
|
+
let bundle;
|
|
372
|
+
let rendererUsed;
|
|
373
|
+
if (options.renderer === "browser") {
|
|
374
|
+
bundle = await writeBrowserExportBundle(exportInput);
|
|
375
|
+
rendererUsed = "browser";
|
|
376
|
+
}
|
|
377
|
+
else if (options.renderer === "node") {
|
|
378
|
+
bundle = await writeExportBundle(exportInput);
|
|
379
|
+
rendererUsed = "node";
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
try {
|
|
383
|
+
bundle = await writeExportBundle(exportInput);
|
|
384
|
+
rendererUsed = "node";
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
if (!shouldFallbackToBrowser(error)) {
|
|
388
|
+
throw error;
|
|
389
|
+
}
|
|
390
|
+
console.warn("Node renderer does not support the requested TTC font. Falling back to browser renderer.");
|
|
391
|
+
bundle = await writeBrowserExportBundle(exportInput);
|
|
392
|
+
rendererUsed = "browser";
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (configDir) {
|
|
396
|
+
console.log(`Config directory: ${configDir}`);
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
console.log(`Config directory: not found (searched upward from ${dirname(inputPath)} for ${DEFAULT_CONFIG_DIR_NAME})`);
|
|
400
|
+
}
|
|
401
|
+
if (configFilePath && (await isFile(configFilePath))) {
|
|
402
|
+
console.log(`Config file: ${configFilePath}`);
|
|
403
|
+
}
|
|
404
|
+
if (avatarOverride.avatarSourcePath) {
|
|
405
|
+
console.log(`Avatar source: ${avatarOverride.avatarSourcePath}`);
|
|
406
|
+
}
|
|
407
|
+
console.log(`Renderer: ${rendererUsed}`);
|
|
408
|
+
printLayoutSummary(bundle.layoutReport);
|
|
409
|
+
console.log(`Rendered ${bundle.pages.length} pages.`);
|
|
410
|
+
console.log(`Output: ${outputPath}`);
|
|
411
|
+
console.log(`Layout report: ${join(outputPath, "layout-report.json")}`);
|
|
412
|
+
}
|
|
413
|
+
main().catch((error) => {
|
|
414
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
415
|
+
console.error(message);
|
|
416
|
+
process.exit(1);
|
|
417
|
+
});
|
|
418
|
+
//# sourceMappingURL=index.js.map
|