1234567import { existsSync, readFileSync } from "node:fs";8import { resolve } from "node:path";9import { describe, expect, it } from "vitest";1011type ContentScript = {12 matches?: string[];13 js?: string[];14 run_at?: string;15 all_frames?: boolean;16};1718type ExtensionManifest = {19 version?: string;20 permissions?: string[];21 host_permissions?: string[];22 content_security_policy?: Record<string, string>;23 content_scripts?: ContentScript[];24 background?: {25 service_worker?: string;26 scripts?: string[];27 type?: string;28 };29};303132const CRXJS_ADDED_KEYS = ["web_accessible_resources"];3334const sourcePath = (target: "chrome" | "firefox") =>35 resolve(process.cwd(), target === "chrome" ? "manifest.json" : "manifest.firefox.json");36const builtPath = (target: "chrome" | "firefox") =>37 resolve(process.cwd(), `dist-${target}`, "manifest.json");3839function read(path: string): ExtensionManifest {40 return JSON.parse(readFileSync(path, "utf8")) as ExtensionManifest;41}4243for (const target of ["chrome", "firefox"] as const) {44 describe.skipIf(!existsSync(builtPath(target)))(45 `built ${target} manifest matches the reviewed source manifest`,46 () => {47 it("adds no permission and no host permission", () => {48 const source = read(sourcePath(target));49 const built = read(builtPath(target));5051 expect(built.permissions ?? []).toEqual(source.permissions ?? []);52 expect(built.host_permissions ?? []).toEqual(source.host_permissions ?? []);53 });5455 it("keeps the content security policy byte-identical", () => {56 const source = read(sourcePath(target));57 const built = read(builtPath(target));5859 expect(built.content_security_policy).toEqual(source.content_security_policy);60 });6162 it("keeps content_scripts matches, run_at and frame scope identical", () => {63 const source = read(sourcePath(target));64 const built = read(builtPath(target));6566 const shape = (scripts: ContentScript[] | undefined) =>67 (scripts ?? []).map((script) => ({68 matches: script.matches,69 run_at: script.run_at,70 all_frames: script.all_frames,71 scriptCount: script.js?.length ?? 0,72 }));7374 expect(shape(built.content_scripts)).toEqual(shape(source.content_scripts));75 });7677 it("keeps the same background kind and module type", () => {78 const source = read(sourcePath(target));79 const built = read(builtPath(target));8081 expect(Object.keys(built.background ?? {}).sort()).toEqual(82 Object.keys(source.background ?? {}).sort(),83 );84 expect(built.background?.type).toBe(source.background?.type);85 });8687 it("introduces no top-level key beyond the known crxjs addition", () => {88 const source = read(sourcePath(target));89 const built = JSON.parse(readFileSync(builtPath(target), "utf8")) as Record<90 string,91 unknown92 >;9394 const added = Object.keys(built).filter(95 (key) => !(key in source) && !CRXJS_ADDED_KEYS.includes(key),96 );97 expect(added).toEqual([]);98 });99 },100 );101}102103104105106107108109describe("product version parity across release inputs", () => {110 it("package.json and both source manifests carry the same version", () => {111 const pkg = JSON.parse(112 readFileSync(resolve(process.cwd(), "package.json"), "utf8"),113 ) as { version?: string };114115 expect(pkg.version).toMatch(/^\d+\.\d+\.\d+$/);116 expect(read(sourcePath("chrome")).version).toBe(pkg.version);117 expect(read(sourcePath("firefox")).version).toBe(pkg.version);118 });119});