import { mkdir, rm, writeFile } from "node:fs/promises"; import path from "node:path"; import { createRequire } from "node:module"; import { fileURLToPath } from "node:url"; import { emojis as tiptapEmojis, type EmojiItem } from "@tiptap/extension-emoji"; const require = createRequire(import.meta.url); interface EmojibaseItem { emoji?: string; group?: number; } const APPLE_ASSET_PREFIX = "https://cdn.jsdelivr.net/npm/emoji-datasource-apple/img/apple/64/"; const VARIATION_SELECTOR_RE = /[\uFE0E\uFE0F]/gu; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const sharedGeneratedDir = path.resolve(__dirname, "../src/shared/emoji/generated"); const clientGeneratedDir = path.resolve(__dirname, "../src/client/lib/emoji/generated"); function normalizeEmoji(emoji: string): string { return emoji.replace(VARIATION_SELECTOR_RE, ""); } function getAppleAssetName(item: EmojiItem): string | null { if (!item.fallbackImage?.startsWith(APPLE_ASSET_PREFIX)) { return null; } return item.fallbackImage.slice(APPLE_ASSET_PREFIX.length); } function renderModule(header: string, value: unknown): string { return `${header}${JSON.stringify(value, null, 2)} as const;\n`; } async function main() { const emojibaseData = require("emojibase-data/en/data.json") as EmojibaseItem[]; const emojibaseMessages = require("emojibase-data/en/messages.json") as { groups: Record; }; const iconData: Record = {}; const groupOverrides: Record = {}; const baseEmojiMap = new Map(); for (const item of tiptapEmojis) { if (!item.emoji) continue; const normalizedEmoji = normalizeEmoji(item.emoji); baseEmojiMap.set(normalizedEmoji, item); const asset = getAppleAssetName(item); if (asset) { iconData[normalizedEmoji] = asset; } } for (const emojibaseItem of emojibaseData) { if (!emojibaseItem.emoji) { continue; } const baseEmoji = normalizeEmoji(emojibaseItem.emoji); const baseItem = baseEmojiMap.get(baseEmoji); if (!baseItem) continue; const groupMessage = typeof emojibaseItem.group === "number" ? (emojibaseMessages.groups[String(emojibaseItem.group)]?.message ?? "") : ""; if (!baseItem.group && groupMessage) { groupOverrides[baseEmoji] = groupMessage; } } await mkdir(sharedGeneratedDir, { recursive: true }); await mkdir(clientGeneratedDir, { recursive: true }); await writeFile( path.join(sharedGeneratedDir, "emoji-icon-data.ts"), renderModule( `// This file is generated by scripts/generate-emoji-data.ts\n\nexport const EMOJI_ICON_DATA = `, iconData, ), ); await writeFile( path.join(clientGeneratedDir, "emoji-group-overrides.ts"), renderModule( `// This file is generated by scripts/generate-emoji-data.ts\n\nexport const EMOJI_GROUP_OVERRIDES = `, groupOverrides, ), ); // Older versions of this script wrote both maps under the client directory. // Clean up the stale icon-data file if a previous checkout still has it so // it does not shadow the shared module via tsconfig path resolution. await rm(path.join(clientGeneratedDir, "emoji-icon-data.ts"), { force: true }); await rm(path.join(clientGeneratedDir, "emoji-tone-variants.ts"), { force: true }); } await main();