/** * @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 };