Skip to main content
Glama
deleonio
by deleonio
css-must-be-in-layer.js3.87 kB
const stylelint = require('stylelint'); const path = require('path'); const ruleName = 'kolibri/css-must-be-in-layer'; const messages = stylelint.utils.ruleMessages(ruleName, { rejected: (selector, filePath) => `CSS rule "${selector}" must be inside a @layer declaration in style.scss files: ${filePath}`, rejectedInclude: (includeParams, filePath) => `@include ${includeParams} must be inside a @layer declaration in style.scss files: ${filePath}`, }); const meta = { url: 'https://github.com/public-ui/kolibri/blob/main/AGENTS.md#theming', fixable: false, }; /** * Custom stylelint rule to ensure all CSS content in style.scss files is inside @layer: * - Only checks style.scss files in packages/components/src/components/ * - Reports CSS rules, declarations, and @include statements outside of @layer * - Ignores @use, @import, and other non-CSS content */ const ruleFunction = (primaryOption) => { return (root, result) => { if (!primaryOption) return; const filePath = result.root.source.input.from; if (!filePath) return; // Normalize path separators for cross-platform compatibility const normalizedPath = filePath.replace(/\\/g, '/'); const fileName = path.basename(filePath); // Only check style.scss files in components directory const isComponentStyleFile = normalizedPath.includes('/packages/components/src/components/') && fileName === 'style.scss'; if (!isComponentStyleFile) return; // Track if we're currently inside a @layer declaration let insideLayer = false; // Check all rules (selectors with declarations) root.walkRules((rule) => { // Check if this rule is inside a @layer let currentParent = rule.parent; let isInLayer = false; while (currentParent && currentParent !== root) { if (currentParent.type === 'atrule' && currentParent.name === 'layer') { isInLayer = true; break; } currentParent = currentParent.parent; } if (!isInLayer) { stylelint.utils.report({ message: messages.rejected(rule.selector, normalizedPath), node: rule, result, ruleName, }); } }); // Check all @include statements that generate CSS content root.walkAtRules('include', (atRule) => { // These mixins generate CSS content and should be in a layer const cssGeneratingMixins = [ 'kol-alert', 'kol-form-field', 'kol-input-container', 'kol-input', 'kol-button', 'kol-link', 'kol-pagination', 'kol-popover-button', 'kol-table-settings', 'kol-table-stateless', 'kol-custom-suggestions-option', 'kol-custom-suggestions-options-group', 'kol-custom-suggestions-toggle', ]; const isCssGeneratingMixin = cssGeneratingMixins.some((mixin) => atRule.params.includes(mixin)); if (isCssGeneratingMixin) { // Check if this @include is inside a @layer let currentParent = atRule.parent; let isInLayer = false; while (currentParent && currentParent !== root) { if (currentParent.type === 'atrule' && currentParent.name === 'layer') { isInLayer = true; break; } currentParent = currentParent.parent; } if (!isInLayer) { stylelint.utils.report({ message: messages.rejectedInclude(atRule.params, normalizedPath), node: atRule, result, ruleName, }); } } }); // Check for root-level declarations (CSS properties not in rules) root.walkDecls((decl) => { // Only check declarations that are direct children of root if (decl.parent === root) { stylelint.utils.report({ message: messages.rejected(`declaration "${decl.prop}: ${decl.value}"`, normalizedPath), node: decl, result, ruleName, }); } }); }; }; ruleFunction.ruleName = ruleName; ruleFunction.messages = messages; ruleFunction.meta = meta; module.exports = stylelint.createPlugin(ruleName, ruleFunction);

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/deleonio/public-ui-kolibri'

If you have feedback or need assistance with the MCP directory API, please join our Discord server