259 lines
8.3 KiB
TypeScript
259 lines
8.3 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 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.0",
|
|
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 installerInclude = readFileSync(
|
|
`${desktopRoot}/build/installer.nsh`,
|
|
"utf8"
|
|
);
|
|
const companionBuild = readFileSync(
|
|
`${projectRoot}/scripts/build-desktop-with-font-pack.mjs`,
|
|
"utf8"
|
|
);
|
|
|
|
expect(builderConfig).toContain("MD_TO_PDF_NSIS_INCLUDE");
|
|
expect(installerInclude).toContain("FONT_PACK_COMPANION_SHA256");
|
|
expect(installerInclude).toContain("StdUtils.HashFile");
|
|
expect(installerInclude).toContain("/WITHFONTPACK=");
|
|
expect(companionBuild).toContain("createDesktopCompanionInclude");
|
|
expect(companionBuild).toContain("MD_TO_PDF_NSIS_INCLUDE");
|
|
expect(companionBuild).toContain(
|
|
"MorphDoc-FontPack-mdtp-serif-sc-1.0.0-x86_64-Setup.exe"
|
|
);
|
|
expect(companionBuild).toContain(
|
|
"MorphDoc-FontPack-mdtp-serif-sc-1.0.0.zip"
|
|
);
|
|
});
|
|
|
|
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"
|
|
);
|
|
});
|
|
});
|