fix(tests): Correct Figma ingest test
This commit is contained in:
198
packages/dss-rules/lib/index.js
Normal file
198
packages/dss-rules/lib/index.js
Normal file
@@ -0,0 +1,198 @@
|
||||
/**
|
||||
* @dss/rules - Design System Rules Package
|
||||
*
|
||||
* Provides versioned rule definitions for enterprise design system enforcement.
|
||||
* Pull-based distribution via npm for consistent rule versions across 60+ projects.
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Rule categories
|
||||
const CATEGORIES = ['colors', 'spacing', 'typography', 'components', 'accessibility'];
|
||||
|
||||
/**
|
||||
* Load all rules from the rules directory
|
||||
* @returns {Object} Rules organized by category
|
||||
*/
|
||||
function loadRules() {
|
||||
const rules = {};
|
||||
const rulesDir = path.join(__dirname, '..', 'rules');
|
||||
|
||||
for (const category of CATEGORIES) {
|
||||
const rulePath = path.join(rulesDir, `${category}.json`);
|
||||
if (fs.existsSync(rulePath)) {
|
||||
try {
|
||||
const content = fs.readFileSync(rulePath, 'utf-8');
|
||||
rules[category] = JSON.parse(content);
|
||||
} catch (error) {
|
||||
console.error(`Failed to load rules for ${category}:`, error.message);
|
||||
rules[category] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rules for a specific category
|
||||
* @param {string} category - Rule category (colors, spacing, etc.)
|
||||
* @returns {Object|null} Rule definitions or null if not found
|
||||
*/
|
||||
function getRulesByCategory(category) {
|
||||
const rules = loadRules();
|
||||
return rules[category] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all rule IDs across all categories
|
||||
* @returns {string[]} Array of rule IDs in format "category/rule-id"
|
||||
*/
|
||||
function getAllRuleIds() {
|
||||
const rules = loadRules();
|
||||
const ids = [];
|
||||
|
||||
for (const [category, ruleSet] of Object.entries(rules)) {
|
||||
if (ruleSet && ruleSet.rules) {
|
||||
for (const rule of ruleSet.rules) {
|
||||
ids.push(`${category}/${rule.id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific rule by its full ID
|
||||
* @param {string} ruleId - Full rule ID in format "category/rule-id"
|
||||
* @returns {Object|null} Rule definition or null
|
||||
*/
|
||||
function getRule(ruleId) {
|
||||
const [category, id] = ruleId.split('/');
|
||||
const ruleSet = getRulesByCategory(category);
|
||||
|
||||
if (!ruleSet || !ruleSet.rules) return null;
|
||||
|
||||
return ruleSet.rules.find(r => r.id === id) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a value against rule patterns
|
||||
* @param {string} ruleId - Full rule ID
|
||||
* @param {string} value - Value to validate
|
||||
* @returns {Object} Validation result {valid, violations}
|
||||
*/
|
||||
function validateValue(ruleId, value) {
|
||||
const rule = getRule(ruleId);
|
||||
if (!rule) {
|
||||
return { valid: true, violations: [], error: `Rule not found: ${ruleId}` };
|
||||
}
|
||||
|
||||
const violations = [];
|
||||
|
||||
// Check forbidden patterns
|
||||
if (rule.patterns?.forbidden) {
|
||||
for (const pattern of rule.patterns.forbidden) {
|
||||
const regex = new RegExp(pattern, 'gi');
|
||||
const matches = value.match(regex);
|
||||
if (matches) {
|
||||
violations.push({
|
||||
rule: ruleId,
|
||||
pattern,
|
||||
matches,
|
||||
severity: rule.severity || 'warning',
|
||||
message: `Found forbidden pattern: ${matches.join(', ')}`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
valid: violations.length === 0,
|
||||
violations
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get required tokens from all rule sets
|
||||
* @returns {Object} Required tokens organized by category
|
||||
*/
|
||||
function getRequiredTokens() {
|
||||
const rules = loadRules();
|
||||
const required = {};
|
||||
|
||||
for (const [category, ruleSet] of Object.entries(rules)) {
|
||||
if (ruleSet?.tokens?.required) {
|
||||
required[category] = ruleSet.tokens.required;
|
||||
}
|
||||
}
|
||||
|
||||
return required;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get severity level for a rule
|
||||
* @param {string} ruleId - Full rule ID
|
||||
* @returns {string} Severity level (error, warning, info)
|
||||
*/
|
||||
function getRuleSeverity(ruleId) {
|
||||
const rule = getRule(ruleId);
|
||||
if (!rule) return 'warning';
|
||||
|
||||
// Rule-specific severity overrides category default
|
||||
if (rule.severity) return rule.severity;
|
||||
|
||||
// Fall back to category default
|
||||
const [category] = ruleId.split('/');
|
||||
const ruleSet = getRulesByCategory(category);
|
||||
return ruleSet?.severity || 'warning';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get package version
|
||||
* @returns {string} Package version
|
||||
*/
|
||||
function getVersion() {
|
||||
const packagePath = path.join(__dirname, '..', 'package.json');
|
||||
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
|
||||
return pkg.version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export configuration for CI/CD integration
|
||||
* @returns {Object} Configuration object for CI pipelines
|
||||
*/
|
||||
function getCIConfig() {
|
||||
return {
|
||||
version: getVersion(),
|
||||
categories: CATEGORIES,
|
||||
errorSeverities: ['error'],
|
||||
warningSeverities: ['warning'],
|
||||
blockingRules: getAllRuleIds().filter(id => getRuleSeverity(id) === 'error'),
|
||||
advisoryRules: getAllRuleIds().filter(id => getRuleSeverity(id) !== 'error')
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
// Rule loading
|
||||
loadRules,
|
||||
getRulesByCategory,
|
||||
getAllRuleIds,
|
||||
getRule,
|
||||
|
||||
// Validation
|
||||
validateValue,
|
||||
getRuleSeverity,
|
||||
|
||||
// Token helpers
|
||||
getRequiredTokens,
|
||||
|
||||
// Metadata
|
||||
getVersion,
|
||||
getCIConfig,
|
||||
|
||||
// Constants
|
||||
CATEGORIES
|
||||
};
|
||||
Reference in New Issue
Block a user