mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-25 06:14:59 +00:00
526 lines
16 KiB
Lua
526 lines
16 KiB
Lua
--- 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
|
|
|
|
local function validate_styles(package_path)
|
|
local result = {
|
|
success = true,
|
|
messages = {},
|
|
warnings = {}
|
|
}
|
|
|
|
local styles_path = package_path .. "/seed/styles.json"
|
|
|
|
-- Check if styles.json exists (optional file)
|
|
local file = io.open(styles_path, "r")
|
|
if not file then
|
|
table.insert(result.warnings, "No seed/styles.json file found (optional)")
|
|
return result
|
|
end
|
|
|
|
-- Read and parse JSON
|
|
local content = file:read("*all")
|
|
file:close()
|
|
|
|
if content == "" then
|
|
result.success = false
|
|
table.insert(result.messages, "styles.json is empty")
|
|
return result
|
|
end
|
|
|
|
-- Try to parse JSON
|
|
local success, styles = pcall(function()
|
|
return require("json").decode(content)
|
|
end)
|
|
|
|
if not success then
|
|
result.success = false
|
|
table.insert(result.messages, "styles.json is not valid JSON: " .. tostring(styles))
|
|
return result
|
|
end
|
|
|
|
if type(styles) ~= "table" then
|
|
result.success = false
|
|
table.insert(result.messages, "styles.json must be either an array (V1) or object (V2)")
|
|
return result
|
|
end
|
|
|
|
-- 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
|
|
|
|
-- Check required fields
|
|
if not style.id then
|
|
result.success = false
|
|
table.insert(result.messages, prefix .. " missing required field: id")
|
|
else
|
|
if seen_ids[style.id] then
|
|
result.success = false
|
|
table.insert(result.messages, prefix .. " has duplicate id: " .. style.id)
|
|
end
|
|
seen_ids[style.id] = true
|
|
end
|
|
|
|
if not style.type then
|
|
result.success = false
|
|
table.insert(result.messages, prefix .. " missing required field: type")
|
|
else
|
|
local valid_types = {
|
|
global = true,
|
|
component = true,
|
|
utility = true,
|
|
animation = true
|
|
}
|
|
if not valid_types[style.type] then
|
|
result.success = false
|
|
table.insert(result.messages, prefix .. " has invalid type: " .. style.type)
|
|
end
|
|
end
|
|
|
|
if not style.css then
|
|
result.success = false
|
|
table.insert(result.messages, prefix .. " missing required field: css")
|
|
elseif type(style.css) ~= "string" then
|
|
result.success = false
|
|
table.insert(result.messages, prefix .. " css field must be a string")
|
|
end
|
|
|
|
-- Validate optional fields
|
|
if style.priority and type(style.priority) ~= "number" then
|
|
table.insert(result.warnings, prefix .. " priority should be a number")
|
|
end
|
|
|
|
if style.className and type(style.className) ~= "string" then
|
|
table.insert(result.warnings, prefix .. " className should be a string")
|
|
end
|
|
|
|
if style.description and type(style.description) ~= "string" then
|
|
table.insert(result.warnings, prefix .. " description should be a string")
|
|
end
|
|
|
|
if style.category and type(style.category) ~= "string" then
|
|
table.insert(result.warnings, prefix .. " category should be a string")
|
|
end
|
|
|
|
if style.css and style.css:match("^%s*$") then
|
|
table.insert(result.warnings, prefix .. " has empty CSS content")
|
|
end
|
|
end
|
|
|
|
if #styles == 0 then
|
|
table.insert(result.warnings, "styles.json contains no style entries")
|
|
else
|
|
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
|