// Builds src/design/icons.generated.ts from Material Symbols Rounded (design-spec §2.12; Apache-2.0): // outline = @material-symbols/svg-500 (FILL 0, weight 500), filled = @material-symbols/svg-600 (FILL 1, weight 600). // Only the icons listed here are embedded; a feature plan adds a name and runs `npm run icons:import`. // `--check` compares with the file on disk instead of writing. import { existsSync, readFileSync, writeFileSync } from 'node:fs'; /** The 33 category icon tokens of SPEC §9.7, in CategoryIcons.ALL order. Both variants (picker and tiles). */ export const CATEGORY_ICONS = [ 'shopping_cart', 'restaurant', 'local_cafe', 'home', 'bolt', 'water_drop', 'wifi', 'directions_car', 'train', 'local_gas_station', 'flight', 'hotel', 'luggage', 'movie', 'music_note', 'sports_esports', 'fitness_center', 'medical_services', 'medication', 'shopping_bag', 'checkroom', 'pets', 'child_care', 'school', 'card_giftcard', 'celebration', 'build', 'chair', 'subscriptions', 'category', 'key', 'shield', 'account_balance', ]; /** UI icons of design-spec §2.12 plus the web's own (search, keyboard, logout, language, theme). Outline variant. */ const UI_OUTLINE = [ 'home', 'bar_chart', 'settings', 'add', 'close', 'arrow_back', 'more_vert', 'edit', 'delete', 'check', 'swap_horiz', 'handshake', 'cloud_upload', 'cloud_off', 'cloud_done', 'sync', 'sync_problem', 'warning', 'error', 'info', 'check_circle', 'chevron_right', 'expand_more', 'expand_less', 'inventory_2', 'unarchive', 'event', 'call_split', 'notes', 'person', 'apps', 'backspace', 'calculate', 'date_range', 'content_paste', 'link', 'dns', 'person_add', 'add_a_photo', 'photo_library', 'receipt_long', 'schedule', 'pause', 'event_repeat', 'drag_indicator', 'restart_alt', 'lock', 'download', 'refresh', 'phonelink_erase', 'smartphone', 'block', 'devices', 'add_link', 'key', 'share', 'content_copy', 'memory', 'open_in_new', 'arrow_forward', 'search', 'keyboard', 'logout', 'language', 'contrast', 'lightbulb', ]; /** Filled UI icons of design-spec §2.12 (selected nav, status, handshake, app mark). */ const UI_FILLED = [ 'home', 'bar_chart', 'settings', 'handshake', 'cloud_done', 'warning', 'error', 'check_circle', 'receipt_long', 'search', 'lightbulb', ]; /** * Spec token names that Material Symbols 0.48 ships under another file name (the glyph is the same). The spec's name * stays the key in the generated tables. The APK-update icons of S-17 are not embedded: the web has no APK update. */ const FILE_NAMES = { card_giftcard: 'redeem', expand_more: 'keyboard_arrow_down', expand_less: 'keyboard_arrow_up', phonelink_erase: 'mobile_cancel', smartphone: 'mobile', }; const root = new URL('../', import.meta.url); function pathOf(pkg, file) { const url = new URL(`node_modules/@material-symbols/${pkg}/rounded/${file}.svg`, root); const svg = readFileSync(url, 'utf-8'); if (!svg.includes('viewBox="0 -960 960 960"')) throw new Error(`${pkg}/${file}: unexpected viewBox`); const paths = [...svg.matchAll(//g)].map((m) => m[1]); if (paths.length !== 1) throw new Error(`${pkg}/${file}: expected one path, found ${paths.length}`); return paths[0]; } function table(names, pkg, suffix) { const unique = [...new Set(names)].sort(); return unique.map((name) => ` ${name}: '${pathOf(pkg, (FILE_NAMES[name] ?? name) + suffix)}',`).join('\n'); } const text = `// Generated by scripts/gen-icons.mjs (npm run icons:import). Do not edit. // Material Symbols Rounded, viewBox 0 -960 960 960; outline from @material-symbols/svg-500, filled from // @material-symbols/svg-600. Licence: Apache License 2.0 (Google). export const OUTLINE = { ${table([...CATEGORY_ICONS, ...UI_OUTLINE], 'svg-500', '')} } as const; export const FILLED = { ${table([...CATEGORY_ICONS, ...UI_FILLED], 'svg-600', '-fill')} } as const; export const CATEGORY_ICON_TOKENS = [ ${CATEGORY_ICONS.map((n) => ` '${n}',`).join('\n')} ] as const; export type OutlineIconName = keyof typeof OUTLINE; export type FilledIconName = keyof typeof FILLED; export type CategoryIconToken = (typeof CATEGORY_ICON_TOKENS)[number]; `; const target = new URL('src/design/icons.generated.ts', root); if (process.argv.includes('--check')) { const current = existsSync(target) ? readFileSync(target, 'utf-8') : ''; if (current !== text) { console.error('src/design/icons.generated.ts is stale: run npm run icons:import'); process.exit(1); } } else { writeFileSync(target, text); console.log( `src/design/icons.generated.ts: ${new Set([...CATEGORY_ICONS, ...UI_OUTLINE]).size} outline, ${new Set([...CATEGORY_ICONS, ...UI_FILLED]).size} filled`, ); }