diff --git a/packages/cli/bin/hyperframes-localize-fonts.mjs b/packages/cli/bin/hyperframes-localize-fonts.mjs new file mode 100644 index 0000000000..0992603056 --- /dev/null +++ b/packages/cli/bin/hyperframes-localize-fonts.mjs @@ -0,0 +1,12 @@ +#!/usr/bin/env node + +import { runtimeVersionError } from "../dist/runtimeVersion.js"; + +const error = runtimeVersionError(process.versions.node); +if (error) { + console.error(error); + process.exitCode = 1; +} else { + const { main } = await import("../dist/fontLocalizeCli.js"); + process.exitCode = await main(); +} diff --git a/packages/cli/package.json b/packages/cli/package.json index 0caee8ea6b..00281dfb13 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -9,7 +9,8 @@ "directory": "packages/cli" }, "bin": { - "hyperframes": "./bin/hyperframes.mjs" + "hyperframes": "./bin/hyperframes.mjs", + "hyperframes-localize-fonts": "./bin/hyperframes-localize-fonts.mjs" }, "files": [ "bin", diff --git a/packages/cli/src/fontLocalize.test.ts b/packages/cli/src/fontLocalize.test.ts new file mode 100644 index 0000000000..86a9d23844 --- /dev/null +++ b/packages/cli/src/fontLocalize.test.ts @@ -0,0 +1,109 @@ +import { describe, expect, it, vi } from "vitest"; +import { runFontLocalize, stampFontVersions, type FontLocalizeIo } from "./fontLocalize.js"; + +function makeIo(input: string): { + io: FontLocalizeIo; + output: string[]; + errors: string[]; +} { + const output: string[] = []; + const errors: string[] = []; + return { + io: { + readInput: async () => input, + writeOutput: (value) => output.push(value), + writeError: (value) => errors.push(value), + }, + output, + errors, + }; +} + +describe("runFontLocalize", () => { + it("writes only the localized document to stdout", async () => { + const harness = makeIo("source"); + const localize = vi.fn(async () => "localized"); + + const exitCode = await runFontLocalize(harness.io, localize); + + expect(exitCode).toBe(0); + expect(localize).toHaveBeenCalledWith("source"); + expect(harness.output).toEqual(["localized"]); + expect(harness.errors).toEqual([]); + }); + + it("rejects blank input without calling the resolver", async () => { + const harness = makeIo(" \n"); + const localize = vi.fn(async (html: string) => html); + + const exitCode = await runFontLocalize(harness.io, localize); + + expect(exitCode).toBe(2); + expect(localize).not.toHaveBeenCalled(); + expect(harness.output).toEqual([]); + expect(harness.errors.join(" ")).toContain("input is empty"); + }); + + it("fails without echoing source HTML or resolver details", async () => { + const source = ''; + const harness = makeIo(source); + const localize = vi.fn(async () => { + throw new Error(`fetch failed for ${source}`); + }); + + const exitCode = await runFontLocalize(harness.io, localize); + + expect(exitCode).toBe(1); + expect(harness.output).toEqual([]); + expect(harness.errors.join(" ")).toContain("font localization failed (Error)"); + expect(harness.errors.join(" ")).not.toContain("signed.example"); + expect(harness.errors.join(" ")).not.toContain(""); + }); + + it("fails closed when the resolver returns an empty document", async () => { + const harness = makeIo("source"); + + const exitCode = await runFontLocalize(harness.io, async () => "\n"); + + expect(exitCode).toBe(1); + expect(harness.output).toEqual([]); + expect(harness.errors.join(" ")).toContain("empty output"); + }); +}); + +describe("stampFontVersions", () => { + it("records producer and localizer versions inside the document head", () => { + const stamped = stampFontVersions( + "x", + { producer: "0.8.15", localizer: "0.8.16" }, + ); + + expect(stamped).toContain(''); + expect(stamped).toContain(''); + expect(stamped.indexOf("hyperframes-font-compiler-version")).toBeLessThan( + stamped.indexOf(""), + ); + }); + + it("inserts both diagnostic stamps after a doctype when no head close exists", () => { + const stamped = stampFontVersions("
x
", { + producer: "0.8.15", + localizer: "0.8.16", + }); + + expect(stamped).toMatch( + /^/, + ); + }); + + it("inserts both diagnostic stamps at the start when no head or doctype exists", () => { + const stamped = stampFontVersions("
x
", { + producer: "0.8.15