新增通用 CSS 到 OOXML 翻译修复,统一字体、字距、精确行距、段落、列表、表格、引用、代码块与行内代码连续性,不引入按主题 ID 分支。 新增 MdTP Mono 并统一 Serif、Sans、Mono 三字体包的 Chromium 与 DOCX 使用链;字体声明、嵌入部件和 Word/WPS 实际采用均进入硬门禁。 重建封面整页及正文语义块视觉差分,14 套主题、纵横两个方向、五组页边距共 140 个真实场景全部通过,阻断失败和诊断失败均为零。 源码服务、Docker Web API 与实际安装 Desktop 的 red-briefing 导出均包含 5 个字体部件;Word/WPS 原生渲染和逐页复核通过。修复 Docker 构建上下文与运行层复用软链接,并完善 v0.6.1 版本、发行说明和发布归集。 验证:npm test(116 个文件、616 项测试)、npm run typecheck、npm run build、git diff --check 全部通过。Desktop 安装器与 ZIP、Docker v0.6.1 镜像已生成;Windows 产物仍为未签名内部发行。
255 lines
8.1 KiB
TypeScript
255 lines
8.1 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"
|
|
);
|
|
});
|
|
|
|
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-with-font-pack.mjs`,
|
|
"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("已生成内置字体包");
|
|
});
|
|
|
|
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"
|
|
);
|
|
});
|
|
});
|