diff --git a/scripts/design-to-css.js b/scripts/design-to-css.js new file mode 100644 index 0000000..04dbab3 --- /dev/null +++ b/scripts/design-to-css.js @@ -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();