新增能力:14 套主题按纵横方向与五组页边距组成的 140 场景视觉矩阵全部通过,封面执行整页严格门禁,正文执行分页无关的语义块门禁。 问题修复:统一 CSS 到 WordprocessingML 的通用翻译,修复字体嵌入、中文标点字距、精确行距、打印媒体与行内代码连续性,不包含按主题 ID 的特调。 发行变化:Desktop 安装器与免安装 ZIP 直接内置 Serif、Sans、Mono 三套字体包;NSIS 移除字体选择和伴随安装逻辑,不再发布独立字体安装器或字体 ZIP。正式附件收敛为 Desktop 安装器、Desktop ZIP、Docker 离线镜像、Web 部署包和发行说明。 部署兼容:Docker 镜像 md-to-pdf:v0.6.1 使用完整无缓存路径构建,固定 Chromium、Pandoc 3.9.0.2 和三套内置字体包;支持覆盖安装 v0.6.0,Windows 安装器保持未签名的内部发布状态。 验证结果:116 个测试文件、616 项测试、全项目类型检查、生产构建和 git diff --check 全部通过;140/140 视觉矩阵阻断失败和诊断失败均为 0;源码服务、Docker Web API 与实际安装 Desktop 的 DOCX 字体嵌入链路均通过。
266 lines
8.6 KiB
TypeScript
266 lines
8.6 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
import { fileURLToPath } from "node:url";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
interface PackageManifest {
|
|
version?: string;
|
|
productName?: string;
|
|
scripts?: Record<string, string>;
|
|
}
|
|
|
|
interface ProductBrand {
|
|
appId: string;
|
|
englishName: string;
|
|
displayName: string;
|
|
windowTitle: string;
|
|
executableName: string;
|
|
artifactPrefix: string;
|
|
}
|
|
|
|
interface PandocRuntimeManifest {
|
|
version: string;
|
|
artifacts: Array<{
|
|
platform: string;
|
|
architecture: string;
|
|
sha256: string;
|
|
licenseFiles: string[];
|
|
files?: Record<string, { bytes: number; sha256: string }>;
|
|
}>;
|
|
}
|
|
|
|
const desktopRoot = fileURLToPath(new URL("../", import.meta.url));
|
|
const projectRoot = fileURLToPath(new URL("../../../", import.meta.url));
|
|
|
|
function readManifest<T = PackageManifest>(path: string): T {
|
|
return JSON.parse(readFileSync(path, "utf8")) as T;
|
|
}
|
|
|
|
describe("桌面发行构建链", () => {
|
|
it("打包前按依赖顺序完整重建内嵌 Web", () => {
|
|
const rootManifest = readManifest(`${projectRoot}/package.json`);
|
|
const desktopManifest = readManifest(`${desktopRoot}/package.json`);
|
|
const webBuild = rootManifest.scripts?.["build:web-runtime"] ?? "";
|
|
const expectedWorkspaces = [
|
|
"@md-to-pdf/markdown-echarts",
|
|
"@md-to-pdf/core",
|
|
"@md-to-pdf/renderer",
|
|
"@md-to-pdf/application",
|
|
"@md-to-pdf/preview-engine",
|
|
"@md-to-pdf/web"
|
|
];
|
|
|
|
let previousIndex = -1;
|
|
for (const workspace of expectedWorkspaces) {
|
|
const workspaceIndex = webBuild.indexOf(workspace);
|
|
expect(workspaceIndex).toBeGreaterThan(previousIndex);
|
|
previousIndex = workspaceIndex;
|
|
}
|
|
|
|
expect(desktopManifest.scripts?.["build:embedded-web"]).toBe(
|
|
"npm --prefix ../.. run build:web-runtime"
|
|
);
|
|
expect(desktopManifest.scripts?.package).toMatch(
|
|
/^npm run build:embedded-web && /
|
|
);
|
|
expect(desktopManifest.scripts?.make).toMatch(
|
|
/^npm run build:embedded-web && /
|
|
);
|
|
expect(desktopManifest.scripts?.package).toContain(
|
|
"npm run prepare:pandoc"
|
|
);
|
|
expect(desktopManifest.scripts?.make).toContain(
|
|
"npm run prepare:pandoc"
|
|
);
|
|
expect(rootManifest.scripts?.["desktop:package"]).toBe(
|
|
"npm run build:font-pack && npm run package -w @md-to-pdf/desktop"
|
|
);
|
|
expect(rootManifest.scripts?.["desktop:make"]).toBe(
|
|
"npm run build:font-pack && node scripts/build-desktop-release.mjs"
|
|
);
|
|
expect(rootManifest.scripts?.["desktop:make:with-font-pack"]).toBeUndefined();
|
|
});
|
|
|
|
it("将固定 Pandoc 最小运行时作为 Desktop 独立资源打包", () => {
|
|
const desktopManifest = readManifest(`${desktopRoot}/package.json`);
|
|
const runtimeManifest = readManifest<PandocRuntimeManifest>(
|
|
`${projectRoot}/packages/docx-engine/src/pandoc-runtime.json`
|
|
);
|
|
const builderConfig = readFileSync(
|
|
`${desktopRoot}/electron-builder.config.cjs`,
|
|
"utf8"
|
|
);
|
|
const prepareScript = readFileSync(
|
|
`${desktopRoot}/scripts/prepare-pandoc-runtime.mjs`,
|
|
"utf8"
|
|
);
|
|
const windowsArtifact = runtimeManifest.artifacts.find(
|
|
(artifact) =>
|
|
artifact.platform === "win32" && artifact.architecture === "x64"
|
|
);
|
|
|
|
expect(runtimeManifest.version).toBe("3.9.0.2");
|
|
expect(windowsArtifact).toMatchObject({
|
|
sha256:
|
|
"C97542F2800F446E788D9F74237856D995421AD1BB3CC8324286840C5F272D3A",
|
|
licenseFiles: ["COPYING.rtf", "COPYRIGHT.txt"],
|
|
files: {
|
|
"pandoc.exe": {
|
|
bytes: 231056136,
|
|
sha256:
|
|
"E83F8354C0F507222B5684797B9C5AE766F03889785995D14AAC27816EC456BA"
|
|
}
|
|
}
|
|
});
|
|
expect(desktopManifest.scripts?.["prepare:pandoc"]).toBe(
|
|
"node scripts/prepare-pandoc-runtime.mjs"
|
|
);
|
|
expect(builderConfig).toContain(
|
|
"`.runtime/pandoc/${pandocRuntime.version}/windows-x86_64`"
|
|
);
|
|
expect(builderConfig).toContain('"pandoc.exe"');
|
|
expect(builderConfig).toContain('"COPYING.rtf"');
|
|
expect(builderConfig).toContain('"COPYRIGHT.txt"');
|
|
expect(prepareScript).toContain("sha256(downloaded)");
|
|
expect(prepareScript).toContain("smokeTestPandoc");
|
|
expect(prepareScript).toContain('process.argv.includes("--check")');
|
|
});
|
|
|
|
it("将内置主题示例作为独立资源复制到发行目录", () => {
|
|
const builderConfig = readFileSync(
|
|
`${desktopRoot}/electron-builder.config.cjs`,
|
|
"utf8"
|
|
);
|
|
|
|
expect(builderConfig).toContain('from: "../../samples"');
|
|
expect(builderConfig).toContain('to: "samples"');
|
|
});
|
|
|
|
it("保持安装身份并从统一配置生成 MorphDoc 发行名称", () => {
|
|
const brand = readManifest<ProductBrand>(
|
|
`${projectRoot}/product.json`
|
|
);
|
|
const desktopManifest = readManifest(
|
|
`${desktopRoot}/package.json`
|
|
);
|
|
const builderConfig = readFileSync(
|
|
`${desktopRoot}/electron-builder.config.cjs`,
|
|
"utf8"
|
|
);
|
|
const installerInclude = readFileSync(
|
|
`${desktopRoot}/build/installer.nsh`,
|
|
"utf8"
|
|
);
|
|
|
|
expect(brand).toMatchObject({
|
|
appId: "com.md-to-pdf.desktop",
|
|
englishName: "MorphDoc",
|
|
displayName: "墨呈",
|
|
windowTitle: "墨呈 · MorphDoc",
|
|
executableName: "MorphDoc",
|
|
artifactPrefix: "MorphDoc"
|
|
});
|
|
expect(desktopManifest).toMatchObject({
|
|
version: "0.6.1",
|
|
productName: "MorphDoc"
|
|
});
|
|
expect(builderConfig).toContain("appId: brand.appId");
|
|
expect(builderConfig).toContain("productName: brand.englishName");
|
|
expect(builderConfig).toContain("executableName: brand.executableName");
|
|
expect(builderConfig).toContain("shortcutName: brand.displayName");
|
|
expect(installerInclude).toContain(
|
|
'!define MORPHDOC_EXECUTABLE "MorphDoc.exe"'
|
|
);
|
|
expect(installerInclude).toContain(
|
|
'!define MORPHDOC_DISPLAY_NAME "墨呈"'
|
|
);
|
|
expect(installerInclude).toContain(
|
|
'!define INSTALL_REGISTRY_KEY "Software\\${APP_GUID}.MorphDoc"'
|
|
);
|
|
expect(installerInclude).toContain(
|
|
'DeleteRegKey SHCTX "${MORPHDOC_LEGACY_INSTALL_REGISTRY_KEY}"'
|
|
);
|
|
expect(installerInclude).toContain(
|
|
"Software\\Classes\\Applications\\${MORPHDOC_LEGACY_EXECUTABLE}"
|
|
);
|
|
});
|
|
|
|
it("在安装版与便携版中强制内置完整字体包集合", () => {
|
|
const builderConfig = readFileSync(
|
|
`${desktopRoot}/electron-builder.config.cjs`,
|
|
"utf8"
|
|
);
|
|
const desktopMain = readFileSync(
|
|
`${desktopRoot}/src/main.ts`,
|
|
"utf8"
|
|
);
|
|
const bundledBuild = readFileSync(
|
|
`${projectRoot}/scripts/build-desktop-release.mjs`,
|
|
"utf8"
|
|
);
|
|
const installerInclude = readFileSync(
|
|
`${desktopRoot}/build/installer.nsh`,
|
|
"utf8"
|
|
);
|
|
|
|
expect(builderConfig).toContain('../../output/font-packs/root');
|
|
expect(builderConfig).toContain('to: "font-packs"');
|
|
expect(desktopMain).toContain(
|
|
'path.join(process.resourcesPath, "font-packs")'
|
|
);
|
|
expect(desktopMain).toContain("getDesktopUserFontPackDirectory()");
|
|
expect(bundledBuild).not.toContain("createDesktopCompanionInclude");
|
|
expect(bundledBuild).toContain("已生成字体直接内置");
|
|
expect(installerInclude).not.toContain("FONT_PACK_COMPANION");
|
|
expect(installerInclude).not.toContain("FontPackCompanionPage");
|
|
expect(installerInclude).not.toContain("/WITHFONTPACK=");
|
|
});
|
|
|
|
it("将 DOCX Lua Filter 复制到桌面主进程资源目录", () => {
|
|
const buildScript = readFileSync(
|
|
`${desktopRoot}/scripts/build-main.mjs`,
|
|
"utf8"
|
|
);
|
|
|
|
expect(buildScript).toContain(
|
|
"packages/docx-engine/assets/docx-media-filter.lua"
|
|
);
|
|
expect(buildScript).toContain("dist/docx-media-filter.lua");
|
|
});
|
|
|
|
it("为 ESM 主进程中的 CommonJS 字体转换依赖保留 __dirname", () => {
|
|
const buildScript = readFileSync(
|
|
`${desktopRoot}/scripts/build-main.mjs`,
|
|
"utf8"
|
|
);
|
|
|
|
expect(buildScript).toContain(
|
|
'dirname as __mdToPdfDirname'
|
|
);
|
|
expect(buildScript).toContain(
|
|
'fileURLToPath as __mdToPdfFileURLToPath'
|
|
);
|
|
expect(buildScript).toContain(
|
|
"const __dirname = __mdToPdfDirname(__mdToPdfFileURLToPath(import.meta.url));"
|
|
);
|
|
});
|
|
|
|
it("在 Docker Web 构建前复制样例并保留到运行镜像", () => {
|
|
const dockerfile = readFileSync(
|
|
`${projectRoot}/deploy/Dockerfile`,
|
|
"utf8"
|
|
);
|
|
const samplesBuildCopyIndex = dockerfile.indexOf(
|
|
"COPY samples ./samples"
|
|
);
|
|
const webBuildIndex = dockerfile.indexOf(
|
|
"npm run build -w @md-to-pdf/web"
|
|
);
|
|
|
|
expect(samplesBuildCopyIndex).toBeGreaterThan(-1);
|
|
expect(webBuildIndex).toBeGreaterThan(samplesBuildCopyIndex);
|
|
expect(dockerfile).toContain(
|
|
"COPY --chown=pwuser:pwuser --from=build /app/samples ./samples"
|
|
);
|
|
});
|
|
});
|