-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
21 changed files
with
18,742 additions
and
0 deletions.
There are no files selected for viewing
36 changes: 36 additions & 0 deletions
36
frontend/packages/figma-to-css-variables/bin/fetchFigmaLocalVariables.mjs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { promises as fs } from 'node:fs' | ||
import { mkdir } from 'node:fs/promises' | ||
|
||
/** | ||
* Fetches local variables from the Figma API and saves them to a temporary file. | ||
* @returns {Promise<void>} | ||
*/ | ||
export async function fetchFigmaLocalVariables() { | ||
const FIGMA_FILE_KEY = process.env.FIGMA_FILE_KEY | ||
const FIGMA_ACCESS_TOKEN = process.env.FIGMA_ACCESS_TOKEN | ||
|
||
if (!FIGMA_FILE_KEY || !FIGMA_ACCESS_TOKEN) { | ||
throw new Error( | ||
'FIGMA_FILE_KEY and FIGMA_ACCESS_TOKEN environment variables are required.', | ||
) | ||
} | ||
|
||
const url = `https://api.figma.com/v1/files/${FIGMA_FILE_KEY}/variables/local` | ||
const headers = { | ||
'X-Figma-Token': FIGMA_ACCESS_TOKEN, | ||
} | ||
|
||
const response = await fetch(url, { headers }) | ||
if (!response.ok) { | ||
throw new Error(`Failed to fetch variables: ${response.statusText}`) | ||
} | ||
|
||
const data = await response.json() | ||
|
||
await mkdir('tmp', { recursive: true }) | ||
await fs.writeFile( | ||
'tmp/local-variables.json', | ||
JSON.stringify(data.meta, null, 2), | ||
) | ||
console.info('Local variables fetched and saved to tmp/local-variables.json') | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { fetchFigmaLocalVariables } from './fetchFigmaLocalVariables.mjs' | ||
import { runStyleDictionary } from './runStyleDictionary.mjs' | ||
import { transformVariablesForStyleDictionary } from './transformVariablesForStyleDictionary.mjs' | ||
|
||
/** | ||
* Main function to handle options and execute the necessary actions. | ||
*/ | ||
async function main() { | ||
const args = process.argv.slice(2) | ||
|
||
if (args.includes('--fetch')) { | ||
await fetchFigmaLocalVariables() | ||
} else if (args.includes('--transform')) { | ||
await transformVariablesForStyleDictionary() | ||
} else if (args.includes('--generate')) { | ||
await runStyleDictionary() | ||
} else { | ||
// Default to running all steps | ||
await fetchFigmaLocalVariables() | ||
await transformVariablesForStyleDictionary() | ||
await runStyleDictionary() | ||
} | ||
} | ||
|
||
main().catch((err) => { | ||
console.error('Error:', err) | ||
}) |
56 changes: 56 additions & 0 deletions
56
frontend/packages/figma-to-css-variables/bin/runStyleDictionary.mjs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import fs from 'node:fs' | ||
import path from 'node:path' | ||
import StyleDictionary from 'style-dictionary' | ||
|
||
function source(input) { | ||
return [`${input}/**/*.json`] | ||
} | ||
|
||
/** | ||
* Generates CSS variables using Style Dictionary. | ||
* @returns {Promise<void>} | ||
*/ | ||
export async function runStyleDictionary() { | ||
try { | ||
const inputDir = path.resolve('tmp/transformed-variables') | ||
const subDirs = fs | ||
.readdirSync(inputDir, { withFileTypes: true }) | ||
.filter((dirent) => dirent.isDirectory()) | ||
.map((dirent) => dirent.name) | ||
|
||
for (const subDir of subDirs) { | ||
const subDirInput = path.join(inputDir, subDir) | ||
const subDirOutput = `${path.join('build/css', subDir)}/` | ||
|
||
const config = { | ||
source: [source(subDirInput)], | ||
platforms: { | ||
css: { | ||
transformGroup: 'css', | ||
buildPath: subDirOutput, | ||
files: [ | ||
{ | ||
destination: 'variables.css', | ||
format: 'css/variables', | ||
}, | ||
], | ||
}, | ||
}, | ||
log: { | ||
warnings: 'warn', // 'warn' | 'error' | 'disabled' | ||
verbosity: 'verbose', // 'default' | 'silent' | 'verbose' | ||
errors: { | ||
brokenReferences: 'console', // 'throw' | 'console' | ||
}, | ||
}, | ||
} | ||
|
||
const styleDictionary = new StyleDictionary(config) | ||
styleDictionary.buildAllPlatforms() | ||
} | ||
|
||
console.info('Style Dictionary build complete.') | ||
} catch (error) { | ||
console.error(`Error during Style Dictionary generation: ${error.message}`) | ||
} | ||
} |
125 changes: 125 additions & 0 deletions
125
frontend/packages/figma-to-css-variables/bin/transformVariablesForStyleDictionary.mjs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import { promises as fs } from 'node:fs' | ||
import { mkdir } from 'node:fs/promises' | ||
import path from 'node:path' | ||
|
||
/** | ||
* Retrieves the token value from the variable based on the mode. | ||
* @param {Object} variable | ||
* @param {string} modeId | ||
* @param {Object[]} localVariables | ||
* @returns {string|number} | ||
*/ | ||
function tokenValueFromVariable(variable, modeId, localVariables) { | ||
const value = variable.valuesByMode[modeId] | ||
if (typeof value === 'object') { | ||
if ('type' in value && value.type === 'VARIABLE_ALIAS') { | ||
const aliasedVariable = localVariables[value.id] | ||
return `{${aliasedVariable?.name.replace(/\//g, '.')}}` | ||
} | ||
if ('r' in value) { | ||
return rgbToHex(value) // Assuming rgbToHex is defined elsewhere | ||
} | ||
|
||
throw new Error(`Format of variable value is invalid: ${value}`) | ||
} | ||
|
||
if (typeof value === 'number') { | ||
return Number.isInteger(value) ? value : Math.floor(value * 100) / 100 | ||
} | ||
|
||
return value | ||
} | ||
|
||
function rgbToHex({ r, g, b, a }) { | ||
if (a === undefined) { | ||
a = 1 | ||
} | ||
|
||
const toHex = (value) => { | ||
const hex = Math.round(value * 255).toString(16) | ||
return hex.length === 1 ? `0${hex}` : hex | ||
} | ||
|
||
const hex = [toHex(r), toHex(g), toHex(b)].join('') | ||
return `#${hex}${a !== 1 ? toHex(a) : ''}` | ||
} | ||
|
||
/** | ||
* Infers token type from the variable. | ||
* @param {Object} variable | ||
* @returns {string} | ||
*/ | ||
function tokenTypeFromVariable(variable) { | ||
switch (variable.resolvedType) { | ||
case 'BOOLEAN': | ||
return 'boolean' | ||
case 'COLOR': | ||
return 'color' | ||
case 'FLOAT': | ||
return 'number' | ||
case 'STRING': | ||
return 'string' | ||
} | ||
} | ||
|
||
/** | ||
* Transforms Figma local variables into Style Dictionary format. | ||
* @returns {Promise<void>} | ||
*/ | ||
export async function transformVariablesForStyleDictionary() { | ||
const localVariablesPath = 'tmp/local-variables.json' | ||
const transformedDir = 'tmp/transformed-variables' | ||
|
||
const data = JSON.parse(await fs.readFile(localVariablesPath, 'utf-8')) | ||
const localVariables = Object.values(data.variables) | ||
const localVariableCollections = data.variableCollections | ||
|
||
await mkdir(transformedDir, { recursive: true }) | ||
|
||
const fileContents = {} | ||
|
||
for (const variable of localVariables) { | ||
if (variable.remote) continue // Skip remote variables | ||
|
||
const collection = localVariableCollections[variable.variableCollectionId] | ||
if (collection) { | ||
for (const mode of collection.modes) { | ||
const fileName = `${mode.name}/${collection.name}.json` | ||
const filePath = path.join(transformedDir, fileName) | ||
|
||
if (!fileContents[filePath]) { | ||
fileContents[filePath] = {} | ||
} | ||
|
||
let obj = fileContents[filePath] | ||
for (const groupName of variable.name.split('/')) { | ||
obj[groupName] = obj[groupName] || {} | ||
obj = obj[groupName] | ||
} | ||
|
||
const token = { | ||
type: tokenTypeFromVariable(variable), | ||
value: tokenValueFromVariable(variable, mode.modeId, localVariables), | ||
description: variable.description || '', | ||
extensions: { | ||
'com.figma': { | ||
hiddenFromPublishing: variable.hiddenFromPublishing, | ||
scopes: variable.scopes, | ||
codeSyntax: variable.codeSyntax, | ||
}, | ||
}, | ||
} | ||
|
||
Object.assign(obj, token) | ||
} | ||
} | ||
} | ||
|
||
for (const [filePath, content] of Object.entries(fileContents)) { | ||
const dir = path.dirname(filePath) | ||
await mkdir(dir, { recursive: true }) | ||
await fs.writeFile(filePath, JSON.stringify(content, null, 2)) | ||
} | ||
|
||
console.info('Variables transformed and saved to tmp/transformed-variables/') | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"extends": ["../../packages/configs/biome.jsonc"] | ||
} |
Oops, something went wrong.