///
import * as fs from 'node:fs'
import * as path from 'node:path'
import { dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import { flattenTree, type FlattenedToken } from './flattenTree.js'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
type TokenTree = Parameters[0]
type GenerationLogger = {
log(message: string): void
write(): void
}
function createGenerationLogger(logFilePath: string): GenerationLogger {
const messages = [`Token SCSS generation log`, `Generated at ${new Date().toISOString()}`, '']
return {
log(message: string) {
messages.push(message)
},
write() {
fs.writeFileSync(logFilePath, `${messages.join('\n')}\n`)
},
}
}
function processJson(jsonData: TokenTree, prefix: string, lineFormatter: (line: string) => string, fileBaseName: string) {
const flattened = flattenTree(jsonData, prefix)
return flattened.map((obj: FlattenedToken) => {
let { name, value, isDeprecated, comment } = obj
let line = ''
if (!name) {
name = fileBaseName || 'root-token'
}
line = `${name}: ${value};`
if (isDeprecated && comment) {
line += ` // ${comment} DEPRECATED`
} else if (isDeprecated) {
line += ` // DEPRECATED`
} else if (comment) {
line += ` // ${comment}`
}
return lineFormatter(line)
})
}
function processJsonFile(filePath: string, targetDir: string, moduleTargetDir: string, logger: GenerationLogger) {
const data = fs.readFileSync(filePath, 'utf8')
const jsonData = JSON.parse(data) as TokenTree
const fileBaseName = path.basename(filePath, '.json')
const sourceFileName = path.basename(filePath)
const scssLines = processJson(jsonData, '', (line) => '$' + line, fileBaseName)
const scssFilePath = path.join(targetDir, `_${fileBaseName}.scss`)
const scssHeader = `// This file is generated from ${path.basename(filePath)}`
const scssContent = `${scssHeader}\n\n${scssLines.join('\n')}\n`
fs.writeFileSync(scssFilePath, scssContent)
logger.log(`Generated ${path.relative(targetDir, scssFilePath)} from src/${sourceFileName}`)
const moduleScssFilePath = path.join(moduleTargetDir, `_${fileBaseName}.module.scss`)
const moduleScssLines = processJson(jsonData, '', (line) => ' ' + line, fileBaseName)
const moduleScssHeader = `// This file is generated from ${path.basename(filePath)}`
const moduleScssContent = `${moduleScssHeader}\n\n:export {\n${moduleScssLines.join('\n')}\n}\n`
fs.writeFileSync(moduleScssFilePath, moduleScssContent)
logger.log(`Generated ${path.relative(targetDir, moduleScssFilePath)} from src/${sourceFileName}`)
const combinedScssFilePath = path.join(targetDir, '_combined.scss')
const combinedModuleScssFilePath = path.join(moduleTargetDir, '_combined.module.scss')
if (!fs.existsSync(combinedScssFilePath)) {
fs.writeFileSync(combinedScssFilePath, '')
}
if (!fs.existsSync(combinedModuleScssFilePath)) {
fs.writeFileSync(combinedModuleScssFilePath, '')
}
const combinedScssContent = fs.readFileSync(combinedScssFilePath, 'utf8')
const newHeader = `\n// This part is generated from ${path.basename(filePath)}\n`
const newContent = processJson(jsonData, '', (line) => '$' + fileBaseName + '-' + line, fileBaseName).join('\n') + '\n'
fs.writeFileSync(combinedScssFilePath, combinedScssContent + newHeader + newContent)
logger.log(`Added src/${sourceFileName} tokens to ${path.relative(targetDir, combinedScssFilePath)}`)
const newModuleContent = processJson(jsonData, '', (line) => ' ' + fileBaseName + '-' + line, fileBaseName).join('\n') + '\n'
const combinedModuleScssContent = fs.readFileSync(combinedModuleScssFilePath, 'utf8')
const updatedModuleScssContent = combinedModuleScssContent.replace('}\n', newModuleContent + '}\n')
fs.writeFileSync(combinedModuleScssFilePath, updatedModuleScssContent)
logger.log(`Added src/${sourceFileName} tokens to ${path.relative(targetDir, combinedModuleScssFilePath)}`)
}
function main() {
const dir = path.join(__dirname, '../src')
const targetDir = path.join(__dirname, '../')
const moduleTargetDir = path.join(targetDir, 'generated-modules')
const logFilePath = path.join(targetDir, 'generation.log')
const logger = createGenerationLogger(logFilePath)
fs.mkdirSync(moduleTargetDir, { recursive: true })
const combinedScssFilePath = path.join(targetDir, '_combined.scss')
fs.writeFileSync(combinedScssFilePath, '')
const combinedModuleScssFilePath = path.join(moduleTargetDir, '_combined.module.scss')
fs.writeFileSync(combinedModuleScssFilePath, ':export {\n}\n')
const tokenFiles = fs.readdirSync(dir).filter((file) => path.extname(file) === '.json')
tokenFiles.forEach((file) => processJsonFile(path.join(dir, file), targetDir, moduleTargetDir, logger))
logger.write()
console.log('\x1b[32m', `Generated ${tokenFiles.length} token SCSS partials and module files. Details: generation.log`, '\x1b[0m')
}
main()