feat: 完善字体包发行与 Docker 内置字体
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { lstat, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { dirname, join, posix, resolve } from "node:path";
|
||||
import { zipSync } from "fflate";
|
||||
import { verifyFontPack } from "./builder.js";
|
||||
|
||||
const ARCHIVE_MTIME = new Date("1980-01-01T00:00:00.000Z");
|
||||
|
||||
export interface BuildFontPackArchiveOptions {
|
||||
packRoot: string;
|
||||
appVersion: string;
|
||||
packId: string;
|
||||
packVersion: string;
|
||||
outputDirectory: string;
|
||||
reportPath?: string;
|
||||
}
|
||||
|
||||
export interface FontPackArchiveReport {
|
||||
packId: string;
|
||||
packVersion: string;
|
||||
artifactPath: string;
|
||||
artifactName: string;
|
||||
bytes: number;
|
||||
sha256: string;
|
||||
entries: string[];
|
||||
}
|
||||
|
||||
function sha256(content: Uint8Array) {
|
||||
return createHash("sha256").update(content).digest("hex").toUpperCase();
|
||||
}
|
||||
|
||||
export function fontPackArchiveName(packId: string, version: string) {
|
||||
return `MorphDoc-FontPack-${packId}-${version}.zip`;
|
||||
}
|
||||
|
||||
async function collectFiles(directory: string, prefix: string) {
|
||||
const result: Array<{ name: string; content: Uint8Array }> = [];
|
||||
const visit = async (currentDirectory: string, relativeDirectory: string) => {
|
||||
const entries = await readdir(currentDirectory, { withFileTypes: true });
|
||||
entries.sort((left, right) => left.name.localeCompare(right.name, "en"));
|
||||
for (const entry of entries) {
|
||||
const entryPath = join(currentDirectory, entry.name);
|
||||
const status = await lstat(entryPath);
|
||||
if (status.isSymbolicLink()) {
|
||||
throw new Error(`字体包归档不允许符号链接:${entry.name}`);
|
||||
}
|
||||
const relativePath = posix.join(
|
||||
prefix,
|
||||
relativeDirectory.replaceAll("\\", "/"),
|
||||
entry.name
|
||||
);
|
||||
if (status.isDirectory()) {
|
||||
await visit(entryPath, posix.join(relativeDirectory, entry.name));
|
||||
} else if (status.isFile()) {
|
||||
result.push({ name: relativePath, content: await readFile(entryPath) });
|
||||
} else {
|
||||
throw new Error(`字体包归档只允许普通文件:${entry.name}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
await visit(directory, "");
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function createDeterministicFontPackArchive(
|
||||
packDirectory: string,
|
||||
prefix: string
|
||||
) {
|
||||
const files = await collectFiles(resolve(packDirectory), prefix);
|
||||
const entries: Record<string, Uint8Array> = {};
|
||||
for (const file of files) {
|
||||
entries[file.name] = file.content;
|
||||
}
|
||||
return {
|
||||
content: zipSync(entries, { level: 9, mtime: ARCHIVE_MTIME }),
|
||||
entries: files.map((file) => file.name)
|
||||
};
|
||||
}
|
||||
|
||||
export async function buildFontPackArchive(
|
||||
options: BuildFontPackArchiveOptions
|
||||
): Promise<FontPackArchiveReport> {
|
||||
const verified = await verifyFontPack({
|
||||
root: options.packRoot,
|
||||
appVersion: options.appVersion,
|
||||
packId: options.packId,
|
||||
packVersion: options.packVersion
|
||||
});
|
||||
const archive = await createDeterministicFontPackArchive(
|
||||
verified.outputDirectory,
|
||||
`${verified.packId}/${verified.packVersion}`
|
||||
);
|
||||
const outputDirectory = resolve(options.outputDirectory);
|
||||
await mkdir(outputDirectory, { recursive: true });
|
||||
const artifactName = fontPackArchiveName(
|
||||
verified.packId,
|
||||
verified.packVersion
|
||||
);
|
||||
const artifactPath = join(outputDirectory, artifactName);
|
||||
await writeFile(artifactPath, archive.content);
|
||||
const report: FontPackArchiveReport = {
|
||||
packId: verified.packId,
|
||||
packVersion: verified.packVersion,
|
||||
artifactPath,
|
||||
artifactName,
|
||||
bytes: archive.content.byteLength,
|
||||
sha256: sha256(archive.content),
|
||||
entries: archive.entries
|
||||
};
|
||||
if (options.reportPath) {
|
||||
const reportPath = resolve(options.reportPath);
|
||||
await mkdir(dirname(reportPath), { recursive: true });
|
||||
await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`, "utf8");
|
||||
}
|
||||
return report;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { resolve } from "node:path";
|
||||
import { buildFontPackArchive } from "./archive.js";
|
||||
import { buildFontPack, verifyFontPack } from "./builder.js";
|
||||
import { buildWindowsFontPackInstaller } from "./windows-installer.js";
|
||||
|
||||
@@ -86,7 +87,25 @@ async function main() {
|
||||
)
|
||||
});
|
||||
}
|
||||
throw new Error("命令必须是 build、verify 或 windows-installer");
|
||||
if (command === "archive") {
|
||||
return buildFontPackArchive({
|
||||
packRoot: resolve(option(values, "root", "output/font-packs/root")),
|
||||
appVersion: option(values, "app-version", "0.6.0"),
|
||||
packId: option(values, "pack-id", "mdtp-serif-sc"),
|
||||
packVersion: option(values, "pack-version", "1.0.0"),
|
||||
outputDirectory: resolve(
|
||||
option(values, "output", "output/font-packs/release")
|
||||
),
|
||||
reportPath: resolve(
|
||||
option(
|
||||
values,
|
||||
"report",
|
||||
"output/font-packs/archive-report.json"
|
||||
)
|
||||
)
|
||||
});
|
||||
}
|
||||
throw new Error("命令必须是 build、verify、archive 或 windows-installer");
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./archive.js";
|
||||
export * from "./builder.js";
|
||||
export * from "./recipe.js";
|
||||
export * from "./windows-installer.js";
|
||||
|
||||
@@ -55,7 +55,7 @@ function define(name: string, value: string) {
|
||||
}
|
||||
|
||||
export function windowsFontPackInstallerName(packId: string, version: string) {
|
||||
return `md-to-pdf-font-pack-${packId}-${version}-${WINDOWS_FONT_PACK_ARCHITECTURE}-Setup.exe`;
|
||||
return `MorphDoc-FontPack-${packId}-${version}-${WINDOWS_FONT_PACK_ARCHITECTURE}-Setup.exe`;
|
||||
}
|
||||
|
||||
export function createWindowsFontPackInstallerArguments(options: {
|
||||
|
||||
Reference in New Issue
Block a user