Skip to content
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
123 changes: 123 additions & 0 deletions scripts/design-to-css.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/**
* design-to-css.js
*
* A professional utility to transform design.md specifications into CSS Custom Properties.
* Part of the Crypto Disco Strategic Contribution Package.
*
* @version 1.0.0
* @license Apache-2.0
*/

import fs from 'fs';
import path from 'path';

/**
* Main execution function
*/
function main() {
const args = process.argv.slice(2);
const inputPath = args[0] || 'DESIGN.md';
const outputPath = args[1] || 'design-tokens.css';

console.log(`🏗️ Transforming ${inputPath} to ${outputPath}...`);

if (!fs.existsSync(inputPath)) {
console.error(`❌ Error: Input file ${inputPath} not found.`);
process.exit(1);
}

try {
const content = fs.readFileSync(inputPath, 'utf8');
const yamlPart = extractYaml(content);
if (!yamlPart) {
throw new Error('No valid YAML frontmatter found.');
}

const data = parseSimplifiedYaml(yamlPart);
const cssContent = generateCss(data);

fs.writeFileSync(outputPath, cssContent);
console.log(`✅ Success! CSS tokens written to ${outputPath}`);
} catch (err) {
console.error(`❌ Transformation failed: ${err.message}`);
process.exit(1);
}
}

/**
* Extracts YAML frontmatter between --- delimeters
*/
function extractYaml(content) {
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
return match ? match[1] : null;
}

/**
* A simplified YAML parser for flat and nested tokens (recursive)
* Handles basic key-value pairs and indentation-based nesting
*/
function parseSimplifiedYaml(yamlStr) {
const lines = yamlStr.split(/\r?\n/);
const result = {};
const stack = [{ indent: -1, obj: result }];

for (let line of lines) {
if (!line.trim() || line.trim().startsWith('#')) continue;

const indent = line.search(/\S/);
const [keyPart, ...valParts] = line.trim().split(':');
const key = keyPart.trim();
let value = valParts.join(':').trim();

// Strip inline comments (only if # is preceded by whitespace)
const commentIndex = value.indexOf(' #');
if (commentIndex !== -1) {
value = value.substring(0, commentIndex).trim();
}

while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
stack.pop();
}

if (value === '' || value === undefined) {
// It's a parent object
const newObj = {};
stack[stack.length - 1].obj[key] = newObj;
stack.push({ indent, obj: newObj });
} else {
// It's a leaf value
// Remove quotes if present
const cleanVal = value.replace(/^['"](.*)['"]$/, '$1');
stack[stack.length - 1].obj[key] = cleanVal;
}
}
return result;
}

/**
* Generates CSS Custom Properties from the token data
*/
function generateCss(data) {
let css = `/**\n * Generated by design-to-css.js\n * Source: DESIGN.md\n */\n\n:root {\n`;

function walk(obj, prefix = '') {
const entries = Object.entries(obj);
for (const [key, value] of entries) {
const fullKey = prefix ? `${prefix}-${key}` : key;
if (typeof value === 'object' && value !== null) {
walk(value, fullKey);
} else {
// Ignore metadata fields like version, name, description
if (!['version', 'name', 'description'].includes(fullKey)) {
css += ` --${fullKey}: ${value};\n`;
}
}
}
}

walk(data);
css += `}\n`;
return css;
}

main();