把 README 解析和文件清单抽到 tools/lib/book.mjs,EPUB 构建改用它(产物逐字节不变)

This commit is contained in:
albert4719 committed 2026-09-19 09:41:13 +08:00
1 parent 28065912b7
commit c7c0896ccf
3 files changed
+52 -37

No files matched your search

File renamed without changes.
+4 -37
View File
@@ -3,52 +3,23 @@
// 只依赖 marked;zip 自己写(EPUB 要求 mimetype 第一个且不压缩,通用 zip 库不一定保证)。
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
import { resolve, dirname, posix } from 'node:path';
import { fileURLToPath } from 'node:url';
import { deflateRawSync } from 'node:zlib';
import { execSync } from 'node:child_process';
import { Marked } from 'marked';
import { ROOT, REPO, SITE, TITLE, read, readBook, gitCommit, stripBackLink } from '../lib/book.mjs';
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
const OUT = resolve(ROOT, process.argv[2] ?? 'dist/HowToLiveBetter.epub');
const REPO = 'https://github.com/eternity4719/HowToLiveBetter';
const SITE = 'https://eternity4719.github.io/HowToLiveBetter/';
const RELEASE = `${REPO}/releases/download/epub-latest/HowToLiveBetter.epub`;
const TITLE = '高性价比人生指南';
const BOOK_ID = 'urn:uuid:5c0c1c0e-6a5c-4d2b-9b1e-7d1f0a4e8c31';
const NOW = new Date();
const COMMIT = gitCommit();
const esc = s => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
const read = p => readFileSync(resolve(ROOT, p), 'utf8');
const plain = html => html.replace(/<[^>]+>/g, '');
function gitCommit() {
try {
return execSync('git rev-parse HEAD', { cwd: ROOT, stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
} catch {
return process.env.GITHUB_SHA ?? '';
}
}
// ---------- 从 README 取内容与文件清单 ----------
const readme = read('README.md');
const readmeLines = readme.split('\n');
const between = (from, to) => {
const a = readmeLines.findIndex(l => l.startsWith(from));
const b = readmeLines.findIndex((l, i) => i > a && l.startsWith(to));
if (a < 0 || b < 0) throw new Error(`README 里找不到 ${from} 到 ${to} 这一段`);
return readmeLines.slice(a, b).join('\n');
};
const description = between('# 高性价比人生指南', '[![')
.split('\n').slice(1).map(l => l.replace(/<[^>]+>/g, '').trim()).filter(Boolean).join('');
const frontMd = between('## 这本书想回答的问题', '## 目录');
const contentsMd = between('## 目录', '## 正文')
.replace(/^## 目录/, '# 各节简介')
.split('\n\n').filter(p => !p.includes('index.html')).join('\n\n');
const unique = arr => [...new Set(arr)];
const bookFiles = unique([...contentsMd.matchAll(/\]\((book\/[^)#]+\.md)\)/g)].map(m => m[1]));
const docFiles = unique([...readme.matchAll(/\]\((docs\/[^)#/]+\.md)\)/g)].map(m => m[1]));
if (bookFiles.length === 0) throw new Error('README 目录里没找到 book/ 文件');
const book = readBook();
const { description, frontMd, bookFiles, docFiles } = book;
const contentsMd = book.contentsMd.replace(/^## 目录/, '# 各节简介');
// ---------- 页面清单 ----------
// 每页:xhtml 文件名、来源 md 的仓库路径(用来解析相对链接)、md 正文
@@ -62,10 +33,6 @@ const pages = [
const pageByPath = new Map(pages.map(p => [p.src, p.file]));
pageByPath.set('README.md', 'front.xhtml');
function stripBackLink(md) {
return md.replace(/^\[← 回总目录\]\([^)]*\)\s*\n/, '');
}
function aboutMd() {
const date = NOW.toISOString().slice(0, 10);
const commitLine = COMMIT ? `- 对应提交:[${COMMIT.slice(0, 7)}](${REPO}/commit/${COMMIT})` : '';
+48
View File
@@ -0,0 +1,48 @@
// README 的结构解析和文件清单:EPUB(tools/epub)和 PDF(tools/pdf)两套构建共用。
// 只认 README 里的结构,不维护文件名单——新增一节或一篇长文,两套构建都自动跟上。
import { readFileSync } from 'node:fs';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { execSync } from 'node:child_process';
export const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
export const REPO = 'https://github.com/eternity4719/HowToLiveBetter';
export const SITE = 'https://eternity4719.github.io/HowToLiveBetter/';
export const TITLE = '高性价比人生指南';
export const RELEASE = `${REPO}/releases/download/epub-latest`;
export const read = p => readFileSync(resolve(ROOT, p), 'utf8');
export const unique = arr => [...new Set(arr)];
export function gitCommit() {
try {
return execSync('git rev-parse HEAD', { cwd: ROOT, stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
} catch {
return process.env.GITHUB_SHA ?? '';
}
}
export function stripBackLink(md) {
return md.replace(/^\[← 回总目录\]\([^)]*\)\s*\n/, '');
}
// README 里从某个标题到下一个标题之间的一段
export function readBook() {
const readme = read('README.md');
const lines = readme.split('\n');
const between = (from, to) => {
const a = lines.findIndex(l => l.startsWith(from));
const b = lines.findIndex((l, i) => i > a && l.startsWith(to));
if (a < 0 || b < 0) throw new Error(`README 里找不到 ${from} 到 ${to} 这一段`);
return lines.slice(a, b).join('\n');
};
const description = between('# 高性价比人生指南', '[![')
.split('\n').slice(1).map(l => l.replace(/<[^>]+>/g, '').trim()).filter(Boolean).join('');
const frontMd = between('## 这本书想回答的问题', '## 目录');
const contentsMd = between('## 目录', '## 正文')
.split('\n\n').filter(p => !p.includes('index.html')).join('\n\n');
const bookFiles = unique([...contentsMd.matchAll(/\]\((book\/[^)#]+\.md)\)/g)].map(m => m[1]));
const docFiles = unique([...readme.matchAll(/\]\((docs\/[^)#/]+\.md)\)/g)].map(m => m[1]));
if (bookFiles.length === 0) throw new Error('README 目录里没找到 book/ 文件');
return { readme, description, frontMd, contentsMd, bookFiles, docFiles };
}