Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add delete and restore functionality for tokens #233

Open
wants to merge 1 commit into
base: next
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,257 changes: 817 additions & 440 deletions package-lock.json

Large diffs are not rendered by default.

25 changes: 15 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
@@ -42,8 +42,8 @@
"build:grid": "node grid.mjs",
"site-tokens": "node ./site-tokens.mjs",
"validate": "node ./validate.mjs",
"format": "eslint --fix \"{style-dictionary,tokens-studio}/**/*.mjs\"",
"lint": "eslint \"style-dictionary/**/*.mjs\"",
"format": "standard --fix \"{style-dictionary,tokens-studio}/**/*.mjs\"",
"lint": "standard \"style-dictionary/**/*.mjs\"",
"tokens:update": "node ./tokens-studio/token-updater.mjs"
},
"devDependencies": {
@@ -52,7 +52,6 @@
"concat": "^1.0.3",
"cross-env": "^7.0.3",
"dir-to-json": "^1.0.0",
"eslint": "^8.57.0",
"fs-extra": "^11.2.0",
"glob": "^7.1.6",
"json5": "^2.2.3",
@@ -61,19 +60,25 @@
"query-string": "^9.1.0",
"require-json5": "^1.3.0",
"rimraf": "^6.0.1",
"standard": "^17.1.0",
"standard": "^17.1.2",
"style-dictionary": "^4.0.1",
"tinycolor2": "^1.6.0"
},
"engines": {
"node": ">= 20.0.0",
"npm": ">= 10.0.0"
},
"eslintConfig": {
"extends": "standard",
"extends": "./node_modules/standard/eslintrc.json",
"ignorePatterns": [
"dist/",
"node_modules/"
]
},
"engines": {
"node": ">= 20.0.0",
"npm": ">= 10.0.0"
],
"env": {
"node": true
},
"rules": {
"no-console": "off"
}
}
}
58 changes: 29 additions & 29 deletions style-dictionary/actions/concat-files.mjs
Original file line number Diff line number Diff line change
@@ -1,60 +1,60 @@
import fs from 'fs-extra';
import concat from 'concat';
import path from 'path';
import { getDirname } from '../utils.mjs';
import fs from 'fs-extra'
import concat from 'concat'
import path from 'path'
import { getDirname } from '../utils.mjs'

const __dirname = getDirname(import.meta.url);
const __dirname = getDirname(import.meta.url)

export const concatFiles = (StyleDictionary) => {
StyleDictionary.registerAction({
name: 'concat-files',
do: (dictionary, config) => {
try {
// Read files from the specified build path
const buildPath = path.join(__dirname, '../../', config.buildPath);
const files = fs.readdirSync(buildPath);
const buildPath = path.join(__dirname, '../../', config.buildPath)
const files = fs.readdirSync(buildPath)

if (files.length === 0) {
console.warn('No files found in the build path.');
return;
console.warn('No files found in the build path.')
return
}

// Determine the file extension from the first file
const extension = path.extname(files[0]);
const allPaths = files.map(f => path.join(buildPath, f));
const concatPaths = allPaths.filter(p => !path.basename(p).includes('no_concat'));
const noConcatPaths = allPaths.filter(p => path.basename(p).includes('no_concat'));
const extension = path.extname(files[0])
const allPaths = files.map(f => path.join(buildPath, f))
const concatPaths = allPaths.filter(p => !path.basename(p).includes('no_concat'))
const noConcatPaths = allPaths.filter(p => path.basename(p).includes('no_concat'))

// Rename files with 'no_concat' in their name
noConcatPaths.forEach(p => {
const newPath = p.replace('.no_concat', '');
fs.renameSync(p, newPath);
});
const newPath = p.replace('.no_concat', '')
fs.renameSync(p, newPath)
})

// Concatenate files
concat(concatPaths).then((r) => {
const outFile = path.join(__dirname, '../../', config.buildPath, `cdr-tokens${extension}`);
fs.outputFileSync(outFile, r);
});
const outFile = path.join(__dirname, '../../', config.buildPath, `cdr-tokens${extension}`)
fs.outputFileSync(outFile, r)
})

// Remove concatenated files
concatPaths.forEach(p => {
fs.removeSync(p);
});
fs.removeSync(p)
})

console.log('Successfully removed concatenated files');
console.log('Successfully removed concatenated files')
} catch (error) {
console.error('Error during file concatenation process:', error);
console.error('Error during file concatenation process:', error)
}
},
undo: (dictionary, config) => {
try {
const buildPath = path.join(__dirname, '../../', config.buildPath);
fs.removeSync(buildPath);
console.log(`Successfully removed ${buildPath}`);
const buildPath = path.join(__dirname, '../../', config.buildPath)
fs.removeSync(buildPath)
console.log(`Successfully removed ${buildPath}`)
} catch (error) {
console.error('Error removing build path:', error);
console.error('Error removing build path:', error)
}
}
});
};
})
}
32 changes: 16 additions & 16 deletions style-dictionary/actions/include-deprecate-scss.mjs
Original file line number Diff line number Diff line change
@@ -1,39 +1,39 @@
import fs from 'fs-extra';
import path from 'path';
import { getDirname } from '../utils.mjs';
import fs from 'fs-extra'
import path from 'path'
import { getDirname } from '../utils.mjs'

const __dirname = getDirname(import.meta.url);
const __dirname = getDirname(import.meta.url)

export const includeDeprecateScss = (StyleDictionary) => {
// Register custom action for Style Dictionary
StyleDictionary.registerAction({
name: 'include-deprecate-scss',
do: (dictionary, config) => {
try {
const deprecateScss = path.join(__dirname, '../utilities/deprecate.scss');
const outputDir = path.join(__dirname, '../../', config.buildPath);
const outputFile = path.join(outputDir, 'deprecate.scss');
const deprecateScss = path.join(__dirname, '../utilities/deprecate.scss')
const outputDir = path.join(__dirname, '../../', config.buildPath)
const outputFile = path.join(outputDir, 'deprecate.scss')

// Ensure the output directory exists
fs.ensureDirSync(outputDir);
fs.ensureDirSync(outputDir)

// Copy the SCSS file
fs.copyFileSync(deprecateScss, outputFile);
console.log(`Successfully copied to ${outputFile}`);
fs.copyFileSync(deprecateScss, outputFile)
console.log(`Successfully copied to ${outputFile}`)
} catch (error) {
console.error('Error including deprecate SCSS:', error);
console.error('Error including deprecate SCSS:', error)
}
},
undo: (dictionary, config) => {
try {
const outputDir = path.join(__dirname, '../../', config.buildPath);
const outputDir = path.join(__dirname, '../../', config.buildPath)

// Remove the output directory and its contents
fs.removeSync(outputDir);
console.log(`Successfully removed ${outputDir}`);
fs.removeSync(outputDir)
console.log(`Successfully removed ${outputDir}`)
} catch (error) {
console.error('Error removing deprecate SCSS directory:', error);
console.error('Error removing deprecate SCSS directory:', error)
}
}
});
})
}
34 changes: 17 additions & 17 deletions style-dictionary/actions/include-display-less.mjs
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
import fs from 'fs-extra';
import path from 'path';
import { getDirname } from '../utils.mjs';
import fs from 'fs-extra'
import path from 'path'
import { getDirname } from '../utils.mjs'

const __dirname = getDirname(import.meta.url);
const __dirname = getDirname(import.meta.url)

export const includeDisplayLess = (StyleDictionary) => {
StyleDictionary.registerAction({
name: 'include-display-less',
do: (dictionary, config) => {
try {
const lessFile = path.join(__dirname, '../utilities/display.less');
const outputDir = path.join(__dirname, '../../', config.buildPath);
const outputFile = path.join(outputDir, 'display.less');
const lessFile = path.join(__dirname, '../utilities/display.less')
const outputDir = path.join(__dirname, '../../', config.buildPath)
const outputFile = path.join(outputDir, 'display.less')

// Ensure the output directory exists
fs.ensureDirSync(outputDir);
fs.ensureDirSync(outputDir)

// Copy the LESS file to the output directory
fs.copyFileSync(lessFile, outputFile);
console.log(`Successfully copied ${lessFile} to ${outputFile}`);
fs.copyFileSync(lessFile, outputFile)
console.log(`Successfully copied ${lessFile} to ${outputFile}`)
} catch (error) {
console.error('Error including display LESS file:', error);
console.error('Error including display LESS file:', error)
}
},
undo: (dictionary, config) => {
try {
const outputDir = path.join(__dirname, '../../', config.buildPath);
const outputDir = path.join(__dirname, '../../', config.buildPath)

// Remove the output directory and its contents
fs.removeSync(outputDir);
console.log(`Successfully removed ${outputDir}`);
fs.removeSync(outputDir)
console.log(`Successfully removed ${outputDir}`)
} catch (error) {
console.error('Error removing display LESS file directory:', error);
console.error('Error removing display LESS file directory:', error)
}
}
});
};
})
}
34 changes: 17 additions & 17 deletions style-dictionary/actions/include-display-scss.mjs
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
import fs from 'fs-extra';
import path from 'path';
import { getDirname } from '../utils.mjs';
import fs from 'fs-extra'
import path from 'path'
import { getDirname } from '../utils.mjs'

const __dirname = getDirname(import.meta.url);
const __dirname = getDirname(import.meta.url)

export const includeDisplayScss = (StyleDictionary) => {
StyleDictionary.registerAction({
name: 'include-display-scss',
do: (dictionary, config) => {
try {
const scssFile = path.join(__dirname, '../utilities/display.scss');
const outputDir = path.join(__dirname, '../../', config.buildPath);
const outputFile = path.join(outputDir, 'display.scss');
const scssFile = path.join(__dirname, '../utilities/display.scss')
const outputDir = path.join(__dirname, '../../', config.buildPath)
const outputFile = path.join(outputDir, 'display.scss')

// Ensure the output directory exists
fs.ensureDirSync(outputDir);
fs.ensureDirSync(outputDir)

// Copy the SCSS file to the output directory
fs.copyFileSync(scssFile, outputFile);
console.log(`Successfully copied ${scssFile} to ${outputFile}`);
fs.copyFileSync(scssFile, outputFile)
console.log(`Successfully copied ${scssFile} to ${outputFile}`)
} catch (error) {
console.error('Error including display SCSS file:', error);
console.error('Error including display SCSS file:', error)
}
},
undo: (dictionary, config) => {
try {
const outputDir = path.join(__dirname, '../../', config.buildPath);
const outputDir = path.join(__dirname, '../../', config.buildPath)

// Remove the output directory and its contents
fs.removeSync(outputDir);
console.log(`Successfully removed ${outputDir}`);
fs.removeSync(outputDir)
console.log(`Successfully removed ${outputDir}`)
} catch (error) {
console.error('Error removing display SCSS file directory:', error);
console.error('Error removing display SCSS file directory:', error)
}
}
});
};
})
}
34 changes: 17 additions & 17 deletions style-dictionary/actions/include-media-queries-less.mjs
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
import fs from 'fs-extra';
import path from 'path';
import { getDirname } from '../utils.mjs';
import fs from 'fs-extra'
import path from 'path'
import { getDirname } from '../utils.mjs'

const __dirname = getDirname(import.meta.url);
const __dirname = getDirname(import.meta.url)

export const includeMediaQueriesLess = (StyleDictionary) => {
StyleDictionary.registerAction({
name: 'include-media-queries-less',
do: (dictionary, config) => {
try {
const lessFile = path.join(__dirname, '../utilities/media-queries.less');
const outputDir = path.join(__dirname, '../../', config.buildPath);
const outputFile = path.join(outputDir, 'media-queries.less');
const lessFile = path.join(__dirname, '../utilities/media-queries.less')
const outputDir = path.join(__dirname, '../../', config.buildPath)
const outputFile = path.join(outputDir, 'media-queries.less')

// Ensure the output directory exists
fs.ensureDirSync(outputDir);
fs.ensureDirSync(outputDir)

// Copy the LESS file to the output directory
fs.copyFileSync(lessFile, outputFile);
console.log(`Successfully copied ${lessFile} to ${outputFile}`);
fs.copyFileSync(lessFile, outputFile)
console.log(`Successfully copied ${lessFile} to ${outputFile}`)
} catch (error) {
console.error('Error including media queries LESS file:', error);
console.error('Error including media queries LESS file:', error)
}
},
undo: (dictionary, config) => {
try {
const outputDir = path.join(__dirname, '../../', config.buildPath);
const outputDir = path.join(__dirname, '../../', config.buildPath)

// Remove the output directory and its contents
fs.removeSync(outputDir);
console.log(`Successfully removed ${outputDir}`);
fs.removeSync(outputDir)
console.log(`Successfully removed ${outputDir}`)
} catch (error) {
console.error('Error removing media queries LESS file directory:', error);
console.error('Error removing media queries LESS file directory:', error)
}
}
});
};
})
}
34 changes: 17 additions & 17 deletions style-dictionary/actions/include-media-queries-scss.mjs
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
import fs from 'fs-extra';
import path from 'path';
import { getDirname } from '../utils.mjs';
import fs from 'fs-extra'
import path from 'path'
import { getDirname } from '../utils.mjs'

const __dirname = getDirname(import.meta.url);
const __dirname = getDirname(import.meta.url)

export const includeMediaQueriesScss = (StyleDictionary) => {
StyleDictionary.registerAction({
name: 'include-media-queries-scss',
do: (dictionary, config) => {
try {
const scssFile = path.join(__dirname, '../utilities/media-queries.scss');
const outputDir = path.join(__dirname, '../../', config.buildPath);
const outputFile = path.join(outputDir, 'media-queries.scss');
const scssFile = path.join(__dirname, '../utilities/media-queries.scss')
const outputDir = path.join(__dirname, '../../', config.buildPath)
const outputFile = path.join(outputDir, 'media-queries.scss')

// Ensure the output directory exists
fs.ensureDirSync(outputDir);
fs.ensureDirSync(outputDir)

// Copy the SCSS file to the output directory
fs.copyFileSync(scssFile, outputFile);
console.log(`Successfully copied ${scssFile} to ${outputFile}`);
fs.copyFileSync(scssFile, outputFile)
console.log(`Successfully copied ${scssFile} to ${outputFile}`)
} catch (error) {
console.error('Error including media queries SCSS file:', error);
console.error('Error including media queries SCSS file:', error)
}
},
undo: (dictionary, config) => {
try {
const outputDir = path.join(__dirname, '../../', config.buildPath);
const outputDir = path.join(__dirname, '../../', config.buildPath)

// Remove the output directory and its contents
fs.removeSync(outputDir);
console.log(`Successfully removed ${outputDir}`);
fs.removeSync(outputDir)
console.log(`Successfully removed ${outputDir}`)
} catch (error) {
console.error('Error removing media queries SCSS file directory:', error);
console.error('Error removing media queries SCSS file directory:', error)
}
}
});
};
})
}
14 changes: 7 additions & 7 deletions style-dictionary/formats/figma.mjs
Original file line number Diff line number Diff line change
@@ -21,20 +21,20 @@ export const figma = (StyleDictionary) => {
$type: value.$type,
...(value.original.$description && { $description: value.original.$description }),
...(value.filePath && { filePath: value.filePath })
};
}
}
return value;
});
});
};
return value
})
})
}

// First preserve references, then clean metadata
const transformedTokens = cleanMeta(
preserveReferences(dictionary.tokens),
{ cleanMeta: propsToRemove }
);
)

return JSON.stringify(transformedTokens, null, 2);
return JSON.stringify(transformedTokens, null, 2)
}
})
}
224 changes: 133 additions & 91 deletions tokens-studio/token-updater.mjs
Original file line number Diff line number Diff line change
@@ -6,16 +6,16 @@ const __dirname = getDirname(import.meta.url)
const FIGMA_TOKENS_PATH = path.resolve(__dirname, '../dist/rei-dot-com/figma/figma.json')
const OPTIONS_FOLDER = path.resolve(__dirname, '../tokens/_options')

let optionsTokens = new Set()
const optionsTokens = new Set()

async function loadOptionsTokens() {
async function loadOptionsTokens () {
try {
const files = await fs.readdir(OPTIONS_FOLDER)

for (const file of files) {
if (file.endsWith('.json') || file.endsWith('.json5')) {
const content = await fs.readJson(path.join(OPTIONS_FOLDER, file))

if (content.options) {
traverseAndStoreColorTokens(content.options, [])
}
@@ -27,10 +27,10 @@ async function loadOptionsTokens() {
}
}

function traverseAndStoreColorTokens(obj, parentPath) {
function traverseAndStoreColorTokens (obj, parentPath) {
for (const [key, value] of Object.entries(obj)) {
const currentPath = [...parentPath, key]

if (value.$value !== undefined) {
optionsTokens.add(currentPath.join('.'))
} else if (typeof value === 'object' && value !== null && !key.startsWith('$')) {
@@ -39,13 +39,34 @@ function traverseAndStoreColorTokens(obj, parentPath) {
}
}

function isColorToken(value, parentType = null) {
function isColorToken (value, parentType = null) {
if (value.$type === 'color') return true
if (parentType === 'color') return true
return false
}

function flattenTokens(obj, parentPath = [], result = {}, filePathMap = {}, parentType = null) {
function isReference (value) {
return typeof value === 'string' && value.startsWith('{') && value.endsWith('}')
}

function normalizeReference (reference) {
const cleanPath = reference.replace(/[{}]/g, '').replace(/^options\./, '')

const refParts = cleanPath.split('.')
let testPath = ''

for (const part of refParts) {
testPath = testPath ? `${testPath}.${part}` : part
if (optionsTokens.has(testPath)) {
return `options.${cleanPath}`
}
}

return cleanPath
}

// Get all tokens from an object with their full paths
function getAllTokens (obj, parentPath = [], parentType = null, result = {}) {
for (const [key, value] of Object.entries(obj)) {
const currentPath = [...parentPath, key]
const currentType = value.$type || parentType
@@ -55,120 +76,141 @@ function flattenTokens(obj, parentPath = [], result = {}, filePathMap = {}, pare
const tokenPath = currentPath.join('.')
result[tokenPath] = {
...value,
filePath: value.filePath,
parentType: currentType
}

if (value.filePath) {
const filePath = value.filePath
if (!filePathMap[filePath]) {
filePathMap[filePath] = {}
}
filePathMap[filePath][tokenPath] = result[tokenPath]
}
}
} else if (typeof value === 'object' && value !== null) {
flattenTokens(value, currentPath, result, filePathMap, currentType)
} else if (typeof value === 'object' && value !== null && !key.startsWith('$')) {
getAllTokens(value, currentPath, currentType, result)
}
}

return { flatTokens: result, filePathMap }
return result
}

function isReference(value) {
return typeof value === 'string' && value.startsWith('{') && value.endsWith('}')
}
// Clean up empty objects after deleting tokens
function cleanupEmptyObjects (obj) {
if (typeof obj !== 'object' || obj === null) return

function normalizeReference(reference) {
// Remove the curly braces and any existing options prefix
const cleanPath = reference.replace(/[{}]/g, '').replace(/^options\./, '')

// Check if this reference is to an options token
const refParts = cleanPath.split('.')
let testPath = ''

// Try to match increasingly specific paths
for (const part of refParts) {
testPath = testPath ? `${testPath}.${part}` : part
if (optionsTokens.has(testPath)) {
return `options.${cleanPath}`
for (const key of Object.keys(obj)) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
cleanupEmptyObjects(obj[key])
if (Object.keys(obj[key]).length === 0) {
delete obj[key]
}
}
}

return cleanPath
}

async function updateTokensInPlace(obj, sourceTokens, parentPath = [], parentType = null) {
for (const [key, value] of Object.entries(obj)) {
const currentPath = [...parentPath, key]
const tokenPath = currentPath.join('.')
const currentType = value.$type || parentType
// Update or delete tokens in target file based on source
async function updateFile (targetPath, sourceTokens) {
console.log(`Processing file: ${targetPath}`)

if (value.$value !== undefined && isColorToken(value, currentType)) {
try {
// Ensure directory exists
await fs.ensureDir(path.dirname(targetPath))

// Read or create target file
let targetContent
try {
targetContent = await fs.readJson(targetPath)
} catch (error) {
targetContent = {}
}

const targetTokens = getAllTokens(targetContent)
let hasChanges = false

// Handle updates and deletions
for (const [tokenPath, targetValue] of Object.entries(targetTokens)) {
const sourceToken = sourceTokens[tokenPath]
if (sourceToken) {
if (isReference(sourceToken.$value)) {
const normalizedRef = normalizeReference(sourceToken.$value)
const newValue = `{${normalizedRef}}`
if (value.$value !== newValue) {
console.log(`Updating token ${tokenPath} from ${value.$value} to ${newValue}`)
value.$value = newValue
const pathParts = tokenPath.split('.')
let current = targetContent

if (!sourceToken || sourceToken.filePath !== targetPath) {
console.log(`Deleting token ${tokenPath}`)
for (let i = 0; i < pathParts.length - 1; i++) {
current = current[pathParts[i]]
if (!current) break
}
if (current) {
delete current[pathParts[pathParts.length - 1]]
hasChanges = true
}
} else if (sourceToken) {
const newValue = isReference(sourceToken.$value)
? `{${normalizeReference(sourceToken.$value)}}`
: sourceToken.$value

if (targetValue.$value !== newValue) {
console.log(`Updating token ${tokenPath}`)
for (let i = 0; i < pathParts.length - 1; i++) {
current = current[pathParts[i]]
}
} else if (value.$value !== sourceToken.$value) {
console.log(`Updating token ${tokenPath} from ${value.$value} to ${sourceToken.$value}`)
value.$value = sourceToken.$value
current[pathParts[pathParts.length - 1]].$value = newValue
hasChanges = true
}
}
} else if (typeof value === 'object' && value !== null) {
await updateTokensInPlace(value, sourceTokens, currentPath, currentType)
}
}
}

async function updateTokens(targetFilePath) {
try {
console.log(`Processing file: ${targetFilePath}`)

const targetContent = await fs.readJson(targetFilePath)
const sourceContent = await fs.readJson(FIGMA_TOKENS_PATH)
// Add new tokens
for (const [tokenPath, sourceToken] of Object.entries(sourceTokens)) {
if (sourceToken.filePath === targetPath && !targetTokens[tokenPath]) {
console.log(`Restoring token ${tokenPath}`)
const pathParts = tokenPath.split('.')
let current = targetContent

for (let i = 0; i < pathParts.length - 1; i++) {
if (!current[pathParts[i]]) {
current[pathParts[i]] = {}
}
current = current[pathParts[i]]
}

const { flatTokens: sourceFlatTokens } = flattenTokens(sourceContent)

await updateTokensInPlace(targetContent, sourceFlatTokens)
const newValue = isReference(sourceToken.$value)
? `{${normalizeReference(sourceToken.$value)}}`
: sourceToken.$value

await fs.writeJson(targetFilePath, targetContent, { spaces: 2 })
current[pathParts[pathParts.length - 1]] = {
$value: newValue,
$type: sourceToken.$type
}
hasChanges = true
}
}

return {
file: targetFilePath,
updated: true
if (hasChanges) {
cleanupEmptyObjects(targetContent)
await fs.writeJson(targetPath, targetContent, { spaces: 2 })
}
} catch (error) {
console.error(`Error updating tokens in ${targetFilePath}:`, error)
throw error
}
}

async function getUniqueFilePaths() {
try {
const content = await fs.readJson(FIGMA_TOKENS_PATH)
const { filePathMap } = flattenTokens(content)
return Object.keys(filePathMap)
return { file: targetPath, updated: hasChanges }
} catch (error) {
console.error('Error getting unique files:', error)
console.error(`Error processing file ${targetPath}:`, error)
throw error
}
}

async function main() {
async function main () {
try {
// Load options tokens first
await loadOptionsTokens()

const files = await getUniqueFilePaths()
const results = []

for (const file of files) {
const update = await updateTokens(file)
results.push(update)
// Read source Figma tokens
const sourceContent = await fs.readJson(FIGMA_TOKENS_PATH)
const sourceTokens = getAllTokens(sourceContent)

// Get unique file paths from source tokens
const filePaths = new Set()
for (const token of Object.values(sourceTokens)) {
if (token.filePath) {
filePaths.add(token.filePath)
}
}

// Process each file
const results = []
for (const filePath of filePaths) {
const result = await updateFile(filePath, sourceTokens)
results.push(result)
}

return results
@@ -178,4 +220,4 @@ async function main() {
}
}

main()
main()