import { createHash } from "node:crypto"; import { cp, mkdtemp, mkdir, readFile, readdir, rm, writeFile, } from "node:fs/promises"; import { tmpdir } from "node:os"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { spawnSync } from "node:child_process"; const version = "17.0.3"; const expectedSha256 = "a0855654b633045ae2337537e77f1bb4361162f7fcd910e613eaab1d6d9c5fca"; const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const outputRoot = join(repositoryRoot, "src/markdown/assets/twemoji"); const assetMapPath = join(repositoryRoot, "src/markdown/twemoji-assets.ts"); const temporaryRoot = await mkdtemp(join(tmpdir(), "methanium-twemoji-")); try { const archivePath = join(temporaryRoot, `twemoji-${version}.tar.gz`); const response = await fetch( `https://codeload.github.com/jdecked/twemoji/tar.gz/refs/tags/v${version}`, ); if (!response.ok) { throw new Error(`Could not download Twemoji: ${response.status}`); } const archive = Buffer.from(await response.arrayBuffer()); const actualSha256 = createHash("sha256").update(archive).digest("hex"); if (actualSha256 !== expectedSha256) { throw new Error( `Twemoji archive checksum mismatch: expected ${expectedSha256}, received ${actualSha256}`, ); } await writeFile(archivePath, archive); const extractedRoot = join(temporaryRoot, "extracted"); await mkdir(extractedRoot); const extraction = spawnSync( "tar", ["-xzf", archivePath, "-C", extractedRoot], { stdio: "inherit", }, ); if (extraction.status !== 0) throw new Error("Could not extract Twemoji"); const sourceRoot = join(extractedRoot, `twemoji-${version}`); await rm(outputRoot, { force: true, recursive: true }); await mkdir(outputRoot, { recursive: true }); await cp(join(sourceRoot, "assets/svg"), join(outputRoot, "svg"), { recursive: true, }); await cp(join(sourceRoot, "LICENSE"), join(outputRoot, "LICENSE")); await cp( join(sourceRoot, "LICENSE-GRAPHICS"), join(outputRoot, "LICENSE-GRAPHICS"), ); for (const licenseName of ["LICENSE", "LICENSE-GRAPHICS"]) { const licensePath = join(outputRoot, licenseName); const license = await readFile(licensePath, "utf8"); await writeFile(licensePath, license.replace(/[\t ]+(?=\r?$)/gm, "")); } await writeFile( join(outputRoot, "ATTRIBUTION.md"), [ "# Twemoji Attribution", "", `Twemoji ${version} graphics are provided by the Twemoji project:`, "https://github.com/jdecked/twemoji", "", "Copyright 2023 Twitter, Inc and other contributors.", "Graphics licensed under CC-BY 4.0.", "Code licensed under the MIT License.", "", ].join("\n"), ); const files = (await readdir(join(outputRoot, "svg"))) .filter((file) => file.endsWith(".svg")) .sort(); const entries = files.map((file) => { const hexcode = file.slice(0, -4); return ` ${JSON.stringify(hexcode)}: new URL(${JSON.stringify(`./assets/twemoji/svg/${file}`)}, import.meta.url).href,`; }); await writeFile( assetMapPath, [ `// Generated by scripts/update-twemoji.mjs from Twemoji ${version}.`, "export const twemojiAssetUrls: Readonly> = {", ...entries, "};", "", ].join("\n"), ); const license = await readFile(join(outputRoot, "ATTRIBUTION.md"), "utf8"); console.log( `Vendored ${files.length} Twemoji SVGs. ${license.split("\n")[0]}`, ); } finally { await rm(temporaryRoot, { force: true, recursive: true }); }