status-web/packages/colors/scripts/sync.ts

143 lines
3.4 KiB
TypeScript

import { intro, isCancel, outro, spinner, text } from '@clack/prompts'
import * as Figma from 'figma-api'
import fs from 'fs-extra'
import type { GetFileNodesResult } from 'figma-api/lib/api-types'
const FILE_KEY = 'v98g9ZiaSHYUdKWrbFg9eM'
intro('🟥🟧🟨🟩🟦🟪🟫⬛⬜')
const personalAccessToken = await text({
message: 'Your personal Figma access token:',
validate(value) {
if (value.length === 0) return `required`
},
})
if (isCancel(personalAccessToken)) {
outro('Bye!')
process.exit(0)
}
function sortObjectByKey<T extends Record<string, any>>(obj: T): T {
const sortedObj: Record<string, any> = {}
Object.keys(obj)
.sort()
.forEach(key => {
sortedObj[key] = obj[key]
})
return sortedObj as T
}
const figma = new Figma.Api({
personalAccessToken,
})
/**
* Get file styles from Figma
*/
const s = spinner()
s.start('Fetching styles from Figma')
const styles = await figma.getFileStyles(FILE_KEY)
if (styles.error) {
console.log('error:', styles.error)
outro('Error!')
process.exit(1)
}
s.stop('Done!')
/**
* Get colors tokens from Figma
*/
const s2 = spinner()
s2.start('Fetching tokens from Figma')
const nodeIds = styles
.meta!.styles.filter(style => style.style_type === 'FILL')
.map(style => style.node_id)
const { nodes } = (await figma.getFileNodes(
FILE_KEY,
nodeIds,
)) as GetFileNodesResult<'FRAME'>
s2.stop('Done!')
/**
* Generate files
*/
const s3 = spinner()
s3.start('Generating colors')
const colors: Record<string, Record<string, string>> = {}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
for (const [_nodeId, value] of Object.entries(nodes)) {
const { document } = value!
const { r, g, b } = document.fills[0].color!
const a = document.fills[0].opacity ?? 1
const red = Math.round(r * 255)
const green = Math.round(g * 255)
const blue = Math.round(b * 255)
const alpha = Math.round(a * 100)
const name = toKebabCase(document.name).replace('-/-', '/')
const [namespace, ...rest] = name.split('/')
const tokenName = normalizeName(namespace, rest.join('-'))
colors[namespace] ??= {}
colors[namespace][tokenName] = `rgba(${red} ${green} ${blue} / ${alpha}%)`
}
/**
* Write tokens to src folder
*/
fs.ensureDirSync('./src')
for (const [namespace, value] of Object.entries(colors)) {
const fileName = `./src/${namespace}.ts`
const tokens = JSON.stringify(sortObjectByKey(value), null, 2)
fs.writeFileSync(fileName, `export const ${namespace} = ${tokens}`, {
encoding: 'utf-8',
})
}
// re-export from index
fs.writeFileSync(
'./src/index.ts',
Object.keys(colors)
.map(key => `export { ${key} } from './${key}'`)
.join('\n'),
{ encoding: 'utf-8' },
)
s3.stop('Done!')
function normalizeName(_tokenName: string, value: string): string {
let normalizedValue = value
normalizedValue = value.replace('solid-', '')
normalizedValue = normalizedValue.replace('%', '')
normalizedValue = normalizedValue.replace('-transparent-opa', '/')
normalizedValue = normalizedValue.replace('-transparent-', '/')
normalizedValue = normalizedValue.replace('transparent-', '/')
normalizedValue = normalizedValue.replace('-opa-', '/')
return normalizedValue
}
function toKebabCase(str: string): string {
return str
.replace(/([a-z])([A-Z])/g, '$1-$2') // convert camel case to kebab case
.replace(/[\s_]+/g, '-') // replace spaces and underscores with hyphens
.toLowerCase()
}