github-desktop/script/generate-octicons.ts
2024-02-14 14:12:12 -06:00

128 lines
3.2 KiB
TypeScript

/* generate-octicons
*
* Utility script for generating a strongly typed representation of all
* octicons distributed by the octicons NPM package. Enumerates the icons
* and generates the TypeScript class containing just what Desktop needs.
*/
import * as fs from 'fs'
import * as Path from 'path'
import * as cp from 'child_process'
import { check } from 'reserved-words'
import toCamelCase from 'to-camel-case'
// Basic type for the @primer/octicons package.
type OcticonsLib = Record<
string,
{
readonly symbol: string
readonly heights: Record<
string,
{
readonly ast: {
readonly children: ReadonlyArray<{
readonly attributes: {
readonly d: string
}
}>
}
readonly options: {
readonly height: string
readonly width: string
}
}
>
}
>
interface IOcticonVariant {
p: string[]
h: number
w: number
}
interface IOcticon {
/** JS friendly name of the icon. */
readonly name: string
/** Size variants of the icon. */
readonly variants: Record<PropertyKey, IOcticonVariant>
}
function getJsFriendlyName(name: string) {
const sanitizedName = toCamelCase(name)
return check(sanitizedName, 'es6', true) ? sanitizedName + '_' : sanitizedName
}
async function generateIconData(): Promise<ReadonlyArray<IOcticon>> {
const octicons: OcticonsLib = require('@primer/octicons')
return Object.keys(octicons)
.sort()
.map(name => {
const octicon = octicons[name]
if (Object.keys(octicon.heights).length === 0) {
throw new Error(`Unexpected empty sizes array for ${octicon.symbol}`)
}
const variants: Record<PropertyKey, IOcticonVariant> = {}
Object.entries(octicon.heights).forEach(([height, data]) => {
variants[height] = {
p: data.ast.children.map(c => c.attributes.d),
h: parseInt(data.options.height, 10),
w: parseInt(data.options.width, 10),
}
})
return { name: getJsFriendlyName(name), variants }
})
}
generateIconData().then(results => {
console.log(`Writing ${results.length} octicons...`)
const out = fs.createWriteStream(
Path.resolve(__dirname, '../app/src/ui/octicons/octicons.generated.ts'),
{
encoding: 'utf-8',
}
)
out.write(`/*
* This file is automatically generated by the generate-octicons tool.
* Manually changing this file will only lead to sadness.
*/
export type OcticonSymbolVariant = {
/** SVG path element data */
readonly p: string[]
/** The width of the symbol */
readonly w: number
/** The height of the symbol */
readonly h: number
}
export type OcticonSymbolVariants = Record<PropertyKey, OcticonSymbolVariant>
export type OcticonSymbol = OcticonSymbolVariant | OcticonSymbolVariants\n\n`)
results.forEach(icon => {
out.write(
`export const ${icon.name}: OcticonSymbolVariants = ${JSON.stringify(
icon.variants
)}\n`
)
})
out.end()
console.log('Ensuring generated file is formatted correctly...')
const root = Path.dirname(__dirname)
const yarnExecutable = process.platform === 'win32' ? 'yarn.cmd' : 'yarn'
return cp.spawn(yarnExecutable, ['lint:fix'], { cwd: root, stdio: 'inherit' })
})