From 9c90f50a435474ff656a8b13dbc35fe9ae3f0a78 Mon Sep 17 00:00:00 2001 From: Richard Ward Date: Wed, 31 Dec 2025 00:25:39 +0000 Subject: [PATCH] update: validator,validate,styles (1 files) --- .../seed/scripts/validate_styles.lua | 421 +++++++++++++++++- 1 file changed, 410 insertions(+), 11 deletions(-) diff --git a/packages/package_validator/seed/scripts/validate_styles.lua b/packages/package_validator/seed/scripts/validate_styles.lua index 0f3e7b1e6..93429e77b 100644 --- a/packages/package_validator/seed/scripts/validate_styles.lua +++ b/packages/package_validator/seed/scripts/validate_styles.lua @@ -1,4 +1,5 @@ --- Validate styles.json structure and content +--- Supports both V1 (array) and V2 (object) schema formats ---@param package_path string The path to the package ---@return table result Validation result with success and messages @@ -28,7 +29,7 @@ local function validate_styles(package_path) return result end - -- Try to parse JSON (basic validation) + -- Try to parse JSON local success, styles = pcall(function() return require("json").decode(content) end) @@ -39,15 +40,28 @@ local function validate_styles(package_path) return result end - -- Validate it's an array if type(styles) ~= "table" then result.success = false - table.insert(result.messages, "styles.json must be an array of style objects") + table.insert(result.messages, "styles.json must be either an array (V1) or object (V2)") return result end - -- Validate each style entry + -- Detect schema version + local schema_version = styles.schema_version or "1.0.0" + + -- Check if it's V2 (has schema_version or package field) + if styles.schema_version or styles.package then + return validate_styles_v2(styles, result) + else + -- V1: Array of style entries + return validate_styles_v1(styles, result) + end +end + +--- Validate V1 schema (array format) +local function validate_styles_v1(styles, result) local seen_ids = {} + for i, style in ipairs(styles) do local prefix = "Style entry #" .. i @@ -56,7 +70,6 @@ local function validate_styles(package_path) result.success = false table.insert(result.messages, prefix .. " missing required field: id") else - -- Check for duplicate IDs if seen_ids[style.id] then result.success = false table.insert(result.messages, prefix .. " has duplicate id: " .. style.id) @@ -68,7 +81,6 @@ local function validate_styles(package_path) result.success = false table.insert(result.messages, prefix .. " missing required field: type") else - -- Validate type enum local valid_types = { global = true, component = true, @@ -77,8 +89,7 @@ local function validate_styles(package_path) } if not valid_types[style.type] then result.success = false - table.insert(result.messages, prefix .. " has invalid type: " .. style.type .. - " (must be: global, component, utility, or animation)") + table.insert(result.messages, prefix .. " has invalid type: " .. style.type) end end @@ -107,20 +118,408 @@ local function validate_styles(package_path) table.insert(result.warnings, prefix .. " category should be a string") end - -- Validate CSS is not empty if style.css and style.css:match("^%s*$") then table.insert(result.warnings, prefix .. " has empty CSS content") end end - -- Summary if #styles == 0 then table.insert(result.warnings, "styles.json contains no style entries") else - table.insert(result.messages, "Found " .. #styles .. " style entries") + table.insert(result.messages, "V1 schema: Found " .. #styles .. " style entries") end return result end +--- Validate V2 schema (object format) +local function validate_styles_v2(styles, result) + -- Validate schema_version + if styles.schema_version then + if type(styles.schema_version) ~= "string" then + result.success = false + table.insert(result.messages, "schema_version must be a string") + elseif not styles.schema_version:match("^%d+%.%d+%.%d+$") then + table.insert(result.warnings, "schema_version should follow semantic versioning (e.g., 2.0.0)") + end + end + + -- Validate package field + if styles.package then + if type(styles.package) ~= "string" then + result.success = false + table.insert(result.messages, "package must be a string") + end + end + + -- Validate tokens section + if styles.tokens then + if type(styles.tokens) ~= "table" then + result.success = false + table.insert(result.messages, "tokens must be an object") + else + validate_tokens(styles.tokens, result) + end + end + + -- Validate selectors section + if styles.selectors then + if type(styles.selectors) ~= "table" then + result.success = false + table.insert(result.messages, "selectors must be an array") + else + validate_selectors(styles.selectors, result) + end + end + + -- Validate effects section + if styles.effects then + if type(styles.effects) ~= "table" then + result.success = false + table.insert(result.messages, "effects must be an array") + else + validate_effects(styles.effects, result) + end + end + + -- Validate appearance section + if styles.appearance then + if type(styles.appearance) ~= "table" then + result.success = false + table.insert(result.messages, "appearance must be an array") + else + validate_appearance(styles.appearance, result) + end + end + + -- Validate layouts section + if styles.layouts then + if type(styles.layouts) ~= "table" then + result.success = false + table.insert(result.messages, "layouts must be an array") + else + validate_layouts(styles.layouts, result) + end + end + + -- Validate transitions section + if styles.transitions then + if type(styles.transitions) ~= "table" then + result.success = false + table.insert(result.messages, "transitions must be an array") + else + validate_transitions(styles.transitions, result) + end + end + + -- Validate rules section + if styles.rules then + if type(styles.rules) ~= "table" then + result.success = false + table.insert(result.messages, "rules must be an array") + else + validate_rules(styles.rules, result) + end + end + + -- Validate environments section + if styles.environments then + if type(styles.environments) ~= "table" then + result.success = false + table.insert(result.messages, "environments must be an array") + else + validate_environments(styles.environments, result) + end + end + + table.insert(result.messages, "V2 schema validated") + + return result +end + +--- Validate tokens section +function validate_tokens(tokens, result) + if tokens.colors and type(tokens.colors) ~= "table" then + result.success = false + table.insert(result.messages, "tokens.colors must be an object") + end + if tokens.spacing and type(tokens.spacing) ~= "table" then + result.success = false + table.insert(result.messages, "tokens.spacing must be an object") + end + if tokens.typography and type(tokens.typography) ~= "table" then + result.success = false + table.insert(result.messages, "tokens.typography must be an object") + end +end + +--- Validate selectors section +function validate_selectors(selectors, result) + local seen_ids = {} + + for i, selector in ipairs(selectors) do + local prefix = "Selector #" .. i + + if not selector.id then + result.success = false + table.insert(result.messages, prefix .. " missing required field: id") + else + if seen_ids[selector.id] then + result.success = false + table.insert(result.messages, prefix .. " has duplicate id: " .. selector.id) + end + seen_ids[selector.id] = true + end + + if not selector.predicate then + result.success = false + table.insert(result.messages, prefix .. " missing required field: predicate") + elseif type(selector.predicate) ~= "table" then + result.success = false + table.insert(result.messages, prefix .. " predicate must be an object") + else + -- Validate predicate structure + if not selector.predicate.targetType then + table.insert(result.warnings, prefix .. " predicate missing targetType") + end + if selector.predicate.classes and type(selector.predicate.classes) ~= "table" then + result.success = false + table.insert(result.messages, prefix .. " predicate.classes must be an array") + end + if selector.predicate.states and type(selector.predicate.states) ~= "table" then + result.success = false + table.insert(result.messages, prefix .. " predicate.states must be an array") + end + end + end +end + +--- Validate effects section +function validate_effects(effects, result) + local seen_ids = {} + + for i, effect in ipairs(effects) do + local prefix = "Effect #" .. i + + if not effect.id then + result.success = false + table.insert(result.messages, prefix .. " missing required field: id") + else + if seen_ids[effect.id] then + result.success = false + table.insert(result.messages, prefix .. " has duplicate id: " .. effect.id) + end + seen_ids[effect.id] = true + end + + if not effect.properties then + result.success = false + table.insert(result.messages, prefix .. " missing required field: properties") + elseif type(effect.properties) ~= "table" then + result.success = false + table.insert(result.messages, prefix .. " properties must be an object") + end + end +end + +--- Validate appearance section +function validate_appearance(appearance, result) + local seen_ids = {} + + for i, app in ipairs(appearance) do + local prefix = "Appearance #" .. i + + if not app.id then + result.success = false + table.insert(result.messages, prefix .. " missing required field: id") + else + if seen_ids[app.id] then + result.success = false + table.insert(result.messages, prefix .. " has duplicate id: " .. app.id) + end + seen_ids[app.id] = true + end + + if not app.layers then + result.success = false + table.insert(result.messages, prefix .. " missing required field: layers") + elseif type(app.layers) ~= "table" then + result.success = false + table.insert(result.messages, prefix .. " layers must be an array") + else + for j, layer in ipairs(app.layers) do + if not layer.type then + result.success = false + table.insert(result.messages, prefix .. " layer #" .. j .. " missing type") + else + local valid_layer_types = { + background = true, + foreground = true, + border = true, + shadow = true, + filter = true + } + if not valid_layer_types[layer.type] then + table.insert(result.warnings, prefix .. " layer #" .. j .. " has unknown type: " .. layer.type) + end + end + end + end + end +end + +--- Validate layouts section +function validate_layouts(layouts, result) + local seen_ids = {} + + for i, layout in ipairs(layouts) do + local prefix = "Layout #" .. i + + if not layout.id then + result.success = false + table.insert(result.messages, prefix .. " missing required field: id") + else + if seen_ids[layout.id] then + result.success = false + table.insert(result.messages, prefix .. " has duplicate id: " .. layout.id) + end + seen_ids[layout.id] = true + end + + if not layout.type then + result.success = false + table.insert(result.messages, prefix .. " missing required field: type") + else + local valid_layout_types = { + flow = true, + flex = true, + grid = true, + absolute = true, + sticky = true + } + if not valid_layout_types[layout.type] then + result.success = false + table.insert(result.messages, prefix .. " has invalid type: " .. layout.type) + end + end + + if not layout.constraints then + table.insert(result.warnings, prefix .. " missing constraints") + elseif type(layout.constraints) ~= "table" then + result.success = false + table.insert(result.messages, prefix .. " constraints must be an object") + end + end +end + +--- Validate transitions section +function validate_transitions(transitions, result) + local seen_ids = {} + + for i, transition in ipairs(transitions) do + local prefix = "Transition #" .. i + + if not transition.id then + result.success = false + table.insert(result.messages, prefix .. " missing required field: id") + else + if seen_ids[transition.id] then + result.success = false + table.insert(result.messages, prefix .. " has duplicate id: " .. transition.id) + end + seen_ids[transition.id] = true + end + + if not transition.trigger then + table.insert(result.warnings, prefix .. " missing trigger") + end + + if not transition.properties then + table.insert(result.warnings, prefix .. " missing properties") + elseif type(transition.properties) ~= "table" then + result.success = false + table.insert(result.messages, prefix .. " properties must be an array") + end + end +end + +--- Validate rules section (the cascade layer) +function validate_rules(rules, result) + local seen_ids = {} + + for i, rule in ipairs(rules) do + local prefix = "Rule #" .. i + + if not rule.id then + result.success = false + table.insert(result.messages, prefix .. " missing required field: id") + else + if seen_ids[rule.id] then + result.success = false + table.insert(result.messages, prefix .. " has duplicate id: " .. rule.id) + end + seen_ids[rule.id] = true + end + + if not rule.selector then + result.success = false + table.insert(result.messages, prefix .. " missing required field: selector") + elseif type(rule.selector) ~= "string" then + result.success = false + table.insert(result.messages, prefix .. " selector must be a string reference") + end + + if not rule.priority then + table.insert(result.warnings, prefix .. " missing priority (cascade resolution)") + elseif type(rule.priority) ~= "table" then + result.success = false + table.insert(result.messages, prefix .. " priority must be an object") + else + -- Validate priority structure + if not rule.priority.importance then + table.insert(result.warnings, prefix .. " priority missing importance") + end + if not rule.priority.specificity then + table.insert(result.warnings, prefix .. " priority missing specificity") + elseif type(rule.priority.specificity) ~= "table" then + result.success = false + table.insert(result.messages, prefix .. " priority.specificity must be an object") + end + end + + if rule.enabled ~= nil and type(rule.enabled) ~= "boolean" then + result.success = false + table.insert(result.messages, prefix .. " enabled must be a boolean") + end + end +end + +--- Validate environments section +function validate_environments(environments, result) + local seen_ids = {} + + for i, env in ipairs(environments) do + local prefix = "Environment #" .. i + + if not env.id then + result.success = false + table.insert(result.messages, prefix .. " missing required field: id") + else + if seen_ids[env.id] then + result.success = false + table.insert(result.messages, prefix .. " has duplicate id: " .. env.id) + end + seen_ids[env.id] = true + end + + if not env.conditions then + result.success = false + table.insert(result.messages, prefix .. " missing required field: conditions") + elseif type(env.conditions) ~= "table" then + result.success = false + table.insert(result.messages, prefix .. " conditions must be an object") + end + end +end + return validate_styles