新增能力:将 DOCX 发布验收拆分为四套独立 140,支持真实语料冻结、指纹复用、失败与基础设施错误独立统计,并为表格换行、全 JSON 围栏、代码连续性和长文档分页建立通用门禁。 问题修复:冻结 Paged.js 分片前的逻辑表格列轨并传递打印几何,统一 Markdown 表格换行、代码、段落与 OOXML 翻译;改进 PDF 文本流排序、语义块映射、颜色与栅格比较,消除窄字符重叠和跨行范围符号误报。 兼容与部署:版本统一为 0.6.2;正式 Docker 镜像内置固定 Chromium、Pandoc 3.9.0.2 和 Serif/Sans/Mono 字体;Desktop NSIS 与 ZIP 继续直接内置字体,无需系统字体安装。 验证结果:合成基线与长庆严格 280/280,M4N 140/140;健康数据残余误报 6/115(5.22%),均核查为重复表头自动对齐/取样误报且基础设施错误为 0。全项目测试、类型检查、生产构建和 git diff --check 通过;正式 Docker、NSIS、ZIP、离线镜像、部署包、清单及 SHA-256 均已生成并校验。
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.2",
|
|
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"
|
|
);
|
|
});
|
|
});
|