Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
26 changes: 26 additions & 0 deletions .github/workflows/css-variables-sync.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: CSS Variables Validation

on:
pull_request:
paths:
- 'src/css-variables.ts'

jobs:
validate:
runs-on: self-hosted
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0

- name: Checkout theme builder repo
uses: actions/checkout@v2
with:
repository: ${{ secrets.THEME_BUILDER_REPO }}
path: theme-builder
ref: release
token: ${{ secrets.GITHUB_TOKEN }}

- name: Validate CSS variables
run: |
node scripts/validate-css-variables.js "$(cat theme-builder/src/data/sdkVariable.ts)"
162 changes: 162 additions & 0 deletions scripts/validate-css-variables.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
#!/usr/bin/env node

/**
* Script to validate CSS variables consistency between repositories
* This script extracts CSS variable names from the TypeScript interface
* and compares them with the implementation in another repository
*/

const fs = require('fs');
const path = require('path');

/**
* Extract CSS variable names from the TypeScript interface
* @param {string} filePath - Path to the css-variables.ts file
* @returns {string[]} Array of CSS variable names
*/
function extractVariablesFromInterface(filePath) {
try {
const content = fs.readFileSync(filePath, 'utf8');

// Extract variable names using regex
const variableRegex = /'--ts-var-[^']+'/g;
const matches = content.match(variableRegex);

if (!matches) {
console.error('No CSS variables found in the interface file');
return [];
}

// Remove quotes and sort for consistent comparison
return matches.map(match => match.replace(/'/g, '')).sort();
} catch (error) {
console.error(`Error reading interface file: ${error.message}`);
return [];
}
}

/**
* Extract CSS variable names from the implementation object
* @param {string} content - Content of the implementation file
* @returns {string[]} Array of CSS variable names
*/
function extractVariablesFromImplementation(content) {
try {
// Extract variable names from object keys
const variableRegex = /'--ts-var-[^']+':/g;
const matches = content.match(variableRegex);

if (!matches) {
console.error('No CSS variables found in the implementation');
return [];
}

// Remove quotes and colon, then sort for consistent comparison
return matches.map(match => match.replace(/[':]/g, '')).sort();
} catch (error) {
console.error(`Error parsing implementation: ${error.message}`);
return [];
}
}
Comment on lines +43 to +60

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current logic for extracting variables from the implementation string is a bit brittle. The regular expression on line 46 doesn't account for optional whitespace between the variable name and the colon, which could cause validation to fail on slightly different but valid formatting. The suggestion below adjusts the regex to handle whitespace and updates the mapping function to trim the result, making the extraction more robust.

Suggested change
function extractVariablesFromImplementation(content) {
try {
// Extract variable names from object keys
const variableRegex = /'--ts-var-[^']+':/g;
const matches = content.match(variableRegex);
if (!matches) {
console.error('No CSS variables found in the implementation');
return [];
}
// Remove quotes and colon, then sort for consistent comparison
return matches.map(match => match.replace(/[':]/g, '')).sort();
} catch (error) {
console.error(`Error parsing implementation: ${error.message}`);
return [];
}
}
function extractVariablesFromImplementation(content) {
try {
// Extract variable names from object keys
const variableRegex = /'--ts-var-[^']+'\s*:/g;
const matches = content.match(variableRegex);
if (!matches) {
console.error('No CSS variables found in the implementation');
return [];
}
// Remove quotes and colon, then sort for consistent comparison
return matches.map(match => match.replace(/[':]/g, '').trim()).sort();
} catch (error) {
console.error(`Error parsing implementation: ${error.message}`);
return [];
}
}


/**
* Compare two arrays of CSS variables and report differences
* @param {string[]} interfaceVars - Variables from TypeScript interface
* @param {string[]} implementationVars - Variables from implementation
* @returns {object} Comparison result
*/
function compareVariables(interfaceVars, implementationVars) {
const missingInImplementation = interfaceVars.filter(varName => !implementationVars.includes(varName));
const extraInImplementation = implementationVars.filter(varName => !interfaceVars.includes(varName));
Comment on lines +69 to +70

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For better performance, especially as the number of CSS variables grows, it's more efficient to use Set for checking inclusion. The current approach using Array.prototype.includes() inside a filter() results in O(n*m) complexity. Using Set.prototype.has() would reduce this to O(n+m).

You could refactor this section to something like this:

const implementationSet = new Set(implementationVars);
const interfaceSet = new Set(interfaceVars);

const missingInImplementation = interfaceVars.filter(varName => !implementationSet.has(varName));
const extraInImplementation = implementationVars.filter(varName => !interfaceSet.has(varName));


return {
interfaceCount: interfaceVars.length,
implementationCount: implementationVars.length,
missingInImplementation,
extraInImplementation,
isConsistent: missingInImplementation.length === 0 && extraInImplementation.length === 0
};
}

/**
* Main validation function
*/
function validateCSSVariables() {
console.log('🔍 Validating CSS variables consistency...\n');

// Path to the interface file
const interfacePath = path.join(__dirname, '..', 'src', 'css-variables.ts');

// Check if interface file exists
if (!fs.existsSync(interfacePath)) {
console.error(`❌ Interface file not found: ${interfacePath}`);
process.exit(1);
}

// Extract variables from interface
const interfaceVars = extractVariablesFromInterface(interfacePath);
console.log(`📋 Found ${interfaceVars.length} variables in TypeScript interface`);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The script continues execution even if extractVariablesFromInterface fails, which can lead to confusing output (e.g., reporting all variables as 'extra'). It would be more robust to exit immediately on such an error. To do this, you can modify extractVariablesFromInterface to return null on error (in its catch block) instead of an empty array. Then, you can apply the suggestion below to check for this null value and exit.

Suggested change
const interfaceVars = extractVariablesFromInterface(interfacePath);
console.log(`📋 Found ${interfaceVars.length} variables in TypeScript interface`);
const interfaceVars = extractVariablesFromInterface(interfacePath);
if (interfaceVars === null) {
process.exit(1);
}
console.log(`📋 Found ${interfaceVars.length} variables in TypeScript interface`);


// Get implementation content from command line argument or environment
const implementationContent = process.argv[2] || process.env.CSS_VARS_IMPLEMENTATION;

if (!implementationContent) {
console.log('⚠️ No implementation content provided. Use:');
console.log(' node validate-css-variables.js "implementation content"');
console.log(' or set CSS_VARS_IMPLEMENTATION environment variable');
console.log('\n📋 Variables in interface:');
interfaceVars.forEach(varName => console.log(` - ${varName}`));
return;
}

// Extract variables from implementation
const implementationVars = extractVariablesFromImplementation(implementationContent);
console.log(`🔧 Found ${implementationVars.length} variables in implementation`);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to the interface variable extraction, if extractVariablesFromImplementation fails, the script continues with an empty array. This can be misleading as it would report all interface variables as 'missing'. It's better to handle this failure explicitly and exit. To do this, you should modify extractVariablesFromImplementation to return null on error. Then, you can apply the suggestion below to check for null and exit.

Suggested change
const implementationVars = extractVariablesFromImplementation(implementationContent);
console.log(`🔧 Found ${implementationVars.length} variables in implementation`);
const implementationVars = extractVariablesFromImplementation(implementationContent);
if (implementationVars === null) {
process.exit(1);
}
console.log(`🔧 Found ${implementationVars.length} variables in implementation`);


// Compare variables
const comparison = compareVariables(interfaceVars, implementationVars);

console.log('\n📊 Comparison Results:');
console.log(` Interface variables: ${comparison.interfaceCount}`);
console.log(` Implementation variables: ${comparison.implementationCount}`);

if (comparison.isConsistent) {
console.log('\n✅ CSS variables are consistent between repositories!');
process.exit(0);
} else {
console.log('\n❌ CSS variables are NOT consistent:');

if (comparison.missingInImplementation.length > 0) {
console.log('\n🔴 Missing in implementation:');
comparison.missingInImplementation.forEach(varName => {
console.log(` - ${varName}`);
});
}

if (comparison.extraInImplementation.length > 0) {
console.log('\n🟡 Extra in implementation:');
comparison.extraInImplementation.forEach(varName => {
console.log(` - ${varName}`);
});
}

console.log('\n💡 To fix this:');
console.log(' 1. Add missing variables to the implementation');
console.log(' 2. Remove extra variables from the implementation');
console.log(' 3. Or update the TypeScript interface if needed');

process.exit(1);
}
}

// Run validation if this script is executed directly
if (require.main === module) {
validateCSSVariables();
}

module.exports = {
extractVariablesFromInterface,
extractVariablesFromImplementation,
compareVariables,
validateCSSVariables
};
Loading