mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
Refactor package validation and quick guide modules
- Simplified structure validation by re-exporting functions from structure_config and validate_structure modules for better maintainability. - Consolidated validation logic in validate.lua, delegating to validate_package module for clearer orchestration. - Introduced new quick guide functionalities including step management (add, remove, update, reset ordering) and media handling (thumbnail and video URL validation). - Added utility functions for URL validation and step creation, enhancing the quick guide's usability. - Established type definitions for steps and media states to improve code clarity and type safety. - Enhanced schema editor with new field and relation definitions, providing a more robust structure for database schema management.
This commit is contained in:
@@ -1,130 +1,30 @@
|
||||
-- CLI tool for validating packages
|
||||
-- Usage: lua cli.lua <package_name> [options]
|
||||
--- CLI tool facade for validating packages
|
||||
--- Usage: lua cli.lua <package_name> [options]
|
||||
---@module cli
|
||||
|
||||
local validate = require("validate")
|
||||
local parse_args = require("parse_args")
|
||||
local print_help = require("print_help")
|
||||
local output_json = require("output_json")
|
||||
local output_human = require("output_human")
|
||||
|
||||
---@class CLI
|
||||
local M = {}
|
||||
|
||||
-- Parse command line arguments
|
||||
function M.parse_args(args)
|
||||
local options = {
|
||||
package_name = nil,
|
||||
skipStructure = false,
|
||||
skipLua = false,
|
||||
verbose = false,
|
||||
json_output = false
|
||||
}
|
||||
M.parse_args = parse_args
|
||||
M.print_help = print_help
|
||||
M.output_json = output_json
|
||||
M.output_human = output_human
|
||||
|
||||
local i = 1
|
||||
while i <= #args do
|
||||
local arg = args[i]
|
||||
|
||||
if arg == "--skip-structure" then
|
||||
options.skipStructure = true
|
||||
elseif arg == "--skip-lua" then
|
||||
options.skipLua = true
|
||||
elseif arg == "--verbose" or arg == "-v" then
|
||||
options.verbose = true
|
||||
elseif arg == "--json" then
|
||||
options.json_output = true
|
||||
elseif arg == "--help" or arg == "-h" then
|
||||
M.print_help()
|
||||
os.exit(0)
|
||||
elseif not options.package_name then
|
||||
options.package_name = arg
|
||||
end
|
||||
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
return options
|
||||
end
|
||||
|
||||
-- Print help message
|
||||
function M.print_help()
|
||||
print([[
|
||||
Package Validator CLI
|
||||
|
||||
Usage:
|
||||
lua cli.lua <package_name> [options]
|
||||
|
||||
Arguments:
|
||||
package_name Name of the package to validate
|
||||
|
||||
Options:
|
||||
--skip-structure Skip folder structure validation
|
||||
--skip-lua Skip Lua file validation
|
||||
--verbose, -v Show detailed validation information
|
||||
--json Output results as JSON
|
||||
--help, -h Show this help message
|
||||
|
||||
Examples:
|
||||
# Validate audit_log package
|
||||
lua cli.lua audit_log
|
||||
|
||||
# Validate with verbose output
|
||||
lua cli.lua audit_log --verbose
|
||||
|
||||
# Skip structure validation
|
||||
lua cli.lua audit_log --skip-structure
|
||||
|
||||
# Output as JSON (for CI/CD integration)
|
||||
lua cli.lua audit_log --json
|
||||
|
||||
Exit Codes:
|
||||
0 - Validation passed
|
||||
1 - Validation failed
|
||||
2 - Invalid arguments or package not found
|
||||
]])
|
||||
end
|
||||
|
||||
-- Output results as JSON
|
||||
function M.output_json(results)
|
||||
local json_output = {
|
||||
valid = results.valid,
|
||||
errors = results.errors,
|
||||
warnings = results.warnings
|
||||
}
|
||||
|
||||
-- Simple JSON serialization
|
||||
local function serialize_array(arr)
|
||||
local items = {}
|
||||
for _, item in ipairs(arr) do
|
||||
table.insert(items, '"' .. item:gsub('"', '\\"') .. '"')
|
||||
end
|
||||
return "[" .. table.concat(items, ",") .. "]"
|
||||
end
|
||||
|
||||
print("{")
|
||||
print(' "valid": ' .. tostring(results.valid) .. ',')
|
||||
print(' "errors": ' .. serialize_array(results.errors) .. ',')
|
||||
print(' "warnings": ' .. serialize_array(results.warnings))
|
||||
print("}")
|
||||
end
|
||||
|
||||
-- Output results in human-readable format
|
||||
function M.output_human(results, verbose)
|
||||
local output = validate.format_results(results)
|
||||
print(output)
|
||||
|
||||
if verbose and #results.errors > 0 then
|
||||
print("\n--- Detailed Error Information ---")
|
||||
for i, err in ipairs(results.errors) do
|
||||
print(i .. ". " .. err)
|
||||
end
|
||||
end
|
||||
|
||||
if verbose and #results.warnings > 0 then
|
||||
print("\n--- Detailed Warning Information ---")
|
||||
for i, warn in ipairs(results.warnings) do
|
||||
print(i .. ". " .. warn)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Main entry point
|
||||
--- Main entry point
|
||||
---@param args string[] Command line arguments
|
||||
function M.run(args)
|
||||
local options = M.parse_args(args)
|
||||
local options = parse_args(args)
|
||||
|
||||
if options.show_help then
|
||||
print_help()
|
||||
os.exit(0)
|
||||
end
|
||||
|
||||
if not options.package_name then
|
||||
print("Error: Package name is required")
|
||||
@@ -151,9 +51,9 @@ function M.run(args)
|
||||
|
||||
-- Output results
|
||||
if options.json_output then
|
||||
M.output_json(results)
|
||||
output_json(results)
|
||||
else
|
||||
M.output_human(results, options.verbose)
|
||||
output_human(results, options.verbose)
|
||||
end
|
||||
|
||||
-- Exit with appropriate code
|
||||
|
||||
@@ -1,126 +1,30 @@
|
||||
-- Component JSON schema definitions
|
||||
--- Component JSON schema validation facade
|
||||
--- Re-exports component validation functions for backward compatibility
|
||||
---@module component_schema
|
||||
|
||||
local validate_component = require("validate_component")
|
||||
local validate_layout = require("validate_layout")
|
||||
local validate_components = require("validate_components")
|
||||
|
||||
---@class ComponentSchema
|
||||
local M = {}
|
||||
|
||||
-- Validate a single component structure
|
||||
function M.validate_component(component, index)
|
||||
local errors = {}
|
||||
local prefix = index and ("components[" .. index .. "]") or "component"
|
||||
--- Validate a single component structure
|
||||
---@param component Component The component to validate
|
||||
---@param index? number Optional component index for error messages
|
||||
---@return string[] errors List of validation errors
|
||||
M.validate_component = validate_component
|
||||
|
||||
-- Required fields
|
||||
if not component.id then
|
||||
table.insert(errors, prefix .. ": Missing required field 'id'")
|
||||
elseif type(component.id) ~= "string" then
|
||||
table.insert(errors, prefix .. ": 'id' must be a string")
|
||||
end
|
||||
--- Validate layout structure recursively
|
||||
---@param layout ComponentLayout The layout to validate
|
||||
---@param path string The current path for error messages
|
||||
---@return string[] errors List of validation errors
|
||||
M.validate_layout = validate_layout
|
||||
|
||||
if not component.type then
|
||||
table.insert(errors, prefix .. ": Missing required field 'type'")
|
||||
elseif type(component.type) ~= "string" then
|
||||
table.insert(errors, prefix .. ": 'type' must be a string")
|
||||
end
|
||||
|
||||
-- Optional but recommended fields
|
||||
if component.name and type(component.name) ~= "string" then
|
||||
table.insert(errors, prefix .. ": 'name' must be a string")
|
||||
end
|
||||
|
||||
if component.description and type(component.description) ~= "string" then
|
||||
table.insert(errors, prefix .. ": 'description' must be a string")
|
||||
end
|
||||
|
||||
-- Validate props if present
|
||||
if component.props then
|
||||
if type(component.props) ~= "table" then
|
||||
table.insert(errors, prefix .. ": 'props' must be an object")
|
||||
end
|
||||
end
|
||||
|
||||
-- Validate layout if present
|
||||
if component.layout then
|
||||
if type(component.layout) ~= "table" then
|
||||
table.insert(errors, prefix .. ": 'layout' must be an object")
|
||||
else
|
||||
local layout_errors = M.validate_layout(component.layout, prefix .. ".layout")
|
||||
for _, err in ipairs(layout_errors) do
|
||||
table.insert(errors, err)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Validate scripts if present
|
||||
if component.scripts then
|
||||
if type(component.scripts) ~= "table" then
|
||||
table.insert(errors, prefix .. ": 'scripts' must be an object")
|
||||
end
|
||||
end
|
||||
|
||||
-- Validate bindings if present
|
||||
if component.bindings then
|
||||
if type(component.bindings) ~= "table" then
|
||||
table.insert(errors, prefix .. ": 'bindings' must be an object")
|
||||
else
|
||||
if component.bindings.dbal ~= nil and type(component.bindings.dbal) ~= "boolean" then
|
||||
table.insert(errors, prefix .. ": 'bindings.dbal' must be a boolean")
|
||||
end
|
||||
if component.bindings.browser ~= nil and type(component.bindings.browser) ~= "boolean" then
|
||||
table.insert(errors, prefix .. ": 'bindings.browser' must be a boolean")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return errors
|
||||
end
|
||||
|
||||
-- Validate layout structure recursively
|
||||
function M.validate_layout(layout, path)
|
||||
local errors = {}
|
||||
|
||||
if not layout.type then
|
||||
table.insert(errors, path .. ": Missing required field 'type'")
|
||||
elseif type(layout.type) ~= "string" then
|
||||
table.insert(errors, path .. ": 'type' must be a string")
|
||||
end
|
||||
|
||||
if layout.props and type(layout.props) ~= "table" then
|
||||
table.insert(errors, path .. ": 'props' must be an object")
|
||||
end
|
||||
|
||||
if layout.children then
|
||||
if type(layout.children) ~= "table" then
|
||||
table.insert(errors, path .. ": 'children' must be an array")
|
||||
else
|
||||
for i, child in ipairs(layout.children) do
|
||||
if type(child) == "table" then
|
||||
local child_errors = M.validate_layout(child, path .. ".children[" .. i .. "]")
|
||||
for _, err in ipairs(child_errors) do
|
||||
table.insert(errors, err)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return errors
|
||||
end
|
||||
|
||||
-- Validate components.json (array of components)
|
||||
function M.validate_components(components)
|
||||
local errors = {}
|
||||
|
||||
if type(components) ~= "table" then
|
||||
table.insert(errors, "components.json must be an array")
|
||||
return false, errors
|
||||
end
|
||||
|
||||
-- Validate each component
|
||||
for i, component in ipairs(components) do
|
||||
local comp_errors = M.validate_component(component, i)
|
||||
for _, err in ipairs(comp_errors) do
|
||||
table.insert(errors, err)
|
||||
end
|
||||
end
|
||||
|
||||
return #errors == 0, errors
|
||||
end
|
||||
--- Validate components.json (array of components)
|
||||
---@param components Component[] Array of component definitions
|
||||
---@return boolean valid Whether all components are valid
|
||||
---@return string[] errors List of validation errors
|
||||
M.validate_components = validate_components
|
||||
|
||||
return M
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
-- Schema Validator initialization
|
||||
--- Schema Validator initialization
|
||||
--- Package validator entry point providing validation utilities
|
||||
---@module init
|
||||
|
||||
local validate = require("validate")
|
||||
|
||||
---@class PackageValidator
|
||||
local M = {}
|
||||
|
||||
-- Initialize the validator
|
||||
--- Initialize the validator
|
||||
---@return { name: string, version: string, description: string } info Validator info
|
||||
function M.init()
|
||||
return {
|
||||
name = "Schema Validator",
|
||||
@@ -12,18 +17,26 @@ function M.init()
|
||||
}
|
||||
end
|
||||
|
||||
-- Main validation entry point
|
||||
--- Main validation entry point
|
||||
---@param package_id string Package identifier to validate
|
||||
---@return ValidationResult results Validation results
|
||||
function M.validate_package(package_id)
|
||||
local package_path = "packages/" .. package_id .. "/seed"
|
||||
return validate.validate_package(package_path)
|
||||
end
|
||||
|
||||
-- Quick validation for metadata
|
||||
--- Quick validation for metadata
|
||||
---@param metadata Metadata Metadata to validate
|
||||
---@return boolean valid Whether valid
|
||||
---@return string[] errors List of errors
|
||||
function M.validate_metadata(metadata)
|
||||
return validate.validate_metadata_only(metadata)
|
||||
end
|
||||
|
||||
-- Quick validation for components
|
||||
--- Quick validation for components
|
||||
---@param components Component[] Components to validate
|
||||
---@return boolean valid Whether valid
|
||||
---@return string[] errors List of errors
|
||||
function M.validate_components(components)
|
||||
return validate.validate_components_only(components)
|
||||
end
|
||||
|
||||
@@ -1,146 +1,24 @@
|
||||
-- Lua file validation
|
||||
--- Lua file validation facade
|
||||
--- Re-exports Lua validation functions for backward compatibility
|
||||
---@module lua_validator
|
||||
|
||||
local validate_lua_syntax = require("validate_lua_syntax")
|
||||
local validate_lua_structure = require("validate_lua_structure")
|
||||
local validate_test_file = require("validate_test_file")
|
||||
local validate_script_exports = require("validate_script_exports")
|
||||
local validate_package_lua_files = require("validate_package_lua_files")
|
||||
local check_lua_quality = require("check_lua_quality")
|
||||
local validate_lua_requires = require("validate_lua_requires")
|
||||
|
||||
---@class LuaValidator
|
||||
local M = {}
|
||||
|
||||
-- Check if Lua file has valid syntax
|
||||
function M.validate_lua_syntax(filepath, content)
|
||||
local errors = {}
|
||||
|
||||
-- Try to load the Lua content
|
||||
local func, err = loadstring(content)
|
||||
|
||||
if not func then
|
||||
table.insert(errors, filepath .. ": Syntax error - " .. (err or "unknown error"))
|
||||
return false, errors
|
||||
end
|
||||
|
||||
return true, errors
|
||||
end
|
||||
|
||||
-- Check if Lua file follows common patterns
|
||||
function M.validate_lua_structure(filepath, content)
|
||||
local warnings = {}
|
||||
|
||||
-- Check for module pattern
|
||||
if not string.match(content, "local%s+M%s*=%s*{}") and
|
||||
not string.match(content, "local%s+[%w_]+%s*=%s*{}") then
|
||||
table.insert(warnings, filepath .. ": Missing module pattern (local M = {})")
|
||||
end
|
||||
|
||||
-- Check for return statement
|
||||
if not string.match(content, "return%s+[%w_]+") then
|
||||
table.insert(warnings, filepath .. ": Missing return statement")
|
||||
end
|
||||
|
||||
return warnings
|
||||
end
|
||||
|
||||
-- Validate test file structure
|
||||
function M.validate_test_file(filepath, content)
|
||||
local errors = {}
|
||||
local warnings = {}
|
||||
|
||||
-- Check for describe blocks
|
||||
if not string.match(content, "describe%(") then
|
||||
table.insert(warnings, filepath .. ": Missing describe() blocks")
|
||||
end
|
||||
|
||||
-- Check for it/test blocks
|
||||
if not string.match(content, "it%(") and not string.match(content, "test%(") then
|
||||
table.insert(warnings, filepath .. ": Missing it() or test() blocks")
|
||||
end
|
||||
|
||||
-- Check for expect assertions
|
||||
if not string.match(content, "expect%(") then
|
||||
table.insert(warnings, filepath .. ": Missing expect() assertions")
|
||||
end
|
||||
|
||||
return errors, warnings
|
||||
end
|
||||
|
||||
-- Validate script exports match metadata
|
||||
function M.validate_script_exports(package_path, metadata)
|
||||
local errors = {}
|
||||
local warnings = {}
|
||||
|
||||
if not metadata.exports or not metadata.exports.scripts then
|
||||
return errors, warnings
|
||||
end
|
||||
|
||||
local scripts_path = package_path .. "/scripts"
|
||||
|
||||
-- Check each exported script exists
|
||||
for _, script_name in ipairs(metadata.exports.scripts) do
|
||||
local script_file = scripts_path .. "/" .. script_name .. ".lua"
|
||||
|
||||
-- Check if file exists
|
||||
local file = io.open(script_file, "r")
|
||||
if not file then
|
||||
table.insert(errors, "Exported script not found: " .. script_name .. ".lua")
|
||||
else
|
||||
file:close()
|
||||
end
|
||||
end
|
||||
|
||||
return errors, warnings
|
||||
end
|
||||
|
||||
-- Validate all Lua files in a package
|
||||
function M.validate_package_lua_files(package_path)
|
||||
local results = {
|
||||
valid = true,
|
||||
errors = {},
|
||||
warnings = {}
|
||||
}
|
||||
|
||||
local scripts_path = package_path .. "/scripts"
|
||||
|
||||
-- Find all Lua files
|
||||
local lua_files = {}
|
||||
-- Note: In real implementation, this would recursively find all .lua files
|
||||
-- For now, we'll validate the pattern
|
||||
|
||||
if not file_exists(scripts_path) then
|
||||
table.insert(results.warnings, "No scripts directory found")
|
||||
return results
|
||||
end
|
||||
|
||||
return results
|
||||
end
|
||||
|
||||
-- Check for common Lua anti-patterns
|
||||
function M.check_lua_quality(filepath, content)
|
||||
local warnings = {}
|
||||
|
||||
-- Check for global variables (potential issue)
|
||||
if string.match(content, "[^%w_]function%s+[%w_]+%(") then
|
||||
table.insert(warnings, filepath .. ": Global function definition found (consider local)")
|
||||
end
|
||||
|
||||
-- Check for TODO comments
|
||||
if string.match(content, "TODO") or string.match(content, "FIXME") then
|
||||
table.insert(warnings, filepath .. ": Contains TODO/FIXME comments")
|
||||
end
|
||||
|
||||
-- Check for print statements (should use proper logging)
|
||||
local print_count = select(2, string.gsub(content, "print%(", ""))
|
||||
if print_count > 0 then
|
||||
table.insert(warnings, filepath .. ": Contains " .. print_count .. " print() statements")
|
||||
end
|
||||
|
||||
return warnings
|
||||
end
|
||||
|
||||
-- Validate Lua file dependencies
|
||||
function M.validate_lua_requires(filepath, content)
|
||||
local errors = {}
|
||||
local requires = {}
|
||||
|
||||
-- Extract all require statements
|
||||
for req in string.gmatch(content, 'require%s*%(%s*["\']([^"\']+)["\']%s*%)') do
|
||||
table.insert(requires, req)
|
||||
end
|
||||
|
||||
return requires, errors
|
||||
end
|
||||
M.validate_lua_syntax = validate_lua_syntax
|
||||
M.validate_lua_structure = validate_lua_structure
|
||||
M.validate_test_file = validate_test_file
|
||||
M.validate_script_exports = validate_script_exports
|
||||
M.validate_package_lua_files = validate_package_lua_files
|
||||
M.check_lua_quality = check_lua_quality
|
||||
M.validate_lua_requires = validate_lua_requires
|
||||
|
||||
return M
|
||||
|
||||
@@ -1,126 +1,16 @@
|
||||
-- Metadata JSON schema definitions
|
||||
--- Metadata JSON schema validation facade
|
||||
--- Re-exports validate_metadata for backward compatibility
|
||||
---@module metadata_schema
|
||||
|
||||
local validate_metadata = require("validate_metadata")
|
||||
|
||||
---@class MetadataSchema
|
||||
local M = {}
|
||||
|
||||
-- Validate metadata.json structure
|
||||
function M.validate_metadata(metadata)
|
||||
local errors = {}
|
||||
|
||||
-- Required fields
|
||||
if not metadata.packageId then
|
||||
table.insert(errors, "Missing required field: packageId")
|
||||
elseif type(metadata.packageId) ~= "string" then
|
||||
table.insert(errors, "packageId must be a string")
|
||||
elseif not string.match(metadata.packageId, "^[a-z_]+$") then
|
||||
table.insert(errors, "packageId must contain only lowercase letters and underscores")
|
||||
end
|
||||
|
||||
if not metadata.name then
|
||||
table.insert(errors, "Missing required field: name")
|
||||
elseif type(metadata.name) ~= "string" then
|
||||
table.insert(errors, "name must be a string")
|
||||
end
|
||||
|
||||
if not metadata.version then
|
||||
table.insert(errors, "Missing required field: version")
|
||||
elseif type(metadata.version) ~= "string" then
|
||||
table.insert(errors, "version must be a string")
|
||||
elseif not string.match(metadata.version, "^%d+%.%d+%.%d+$") then
|
||||
table.insert(errors, "version must follow semantic versioning (e.g., 1.0.0)")
|
||||
end
|
||||
|
||||
if not metadata.description then
|
||||
table.insert(errors, "Missing required field: description")
|
||||
elseif type(metadata.description) ~= "string" then
|
||||
table.insert(errors, "description must be a string")
|
||||
end
|
||||
|
||||
if not metadata.author then
|
||||
table.insert(errors, "Missing required field: author")
|
||||
elseif type(metadata.author) ~= "string" then
|
||||
table.insert(errors, "author must be a string")
|
||||
end
|
||||
|
||||
if not metadata.category then
|
||||
table.insert(errors, "Missing required field: category")
|
||||
elseif type(metadata.category) ~= "string" then
|
||||
table.insert(errors, "category must be a string")
|
||||
end
|
||||
|
||||
-- Optional but must be correct type if present
|
||||
if metadata.dependencies then
|
||||
if type(metadata.dependencies) ~= "table" then
|
||||
table.insert(errors, "dependencies must be an array")
|
||||
else
|
||||
for i, dep in ipairs(metadata.dependencies) do
|
||||
if type(dep) ~= "string" then
|
||||
table.insert(errors, "dependencies[" .. i .. "] must be a string")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Validate devDependencies if present
|
||||
if metadata.devDependencies then
|
||||
if type(metadata.devDependencies) ~= "table" then
|
||||
table.insert(errors, "devDependencies must be an array")
|
||||
else
|
||||
for i, dep in ipairs(metadata.devDependencies) do
|
||||
if type(dep) ~= "string" then
|
||||
table.insert(errors, "devDependencies[" .. i .. "] must be a string")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if metadata.exports then
|
||||
if type(metadata.exports) ~= "table" then
|
||||
table.insert(errors, "exports must be an object")
|
||||
else
|
||||
-- Validate exports.components
|
||||
if metadata.exports.components then
|
||||
if type(metadata.exports.components) ~= "table" then
|
||||
table.insert(errors, "exports.components must be an array")
|
||||
end
|
||||
end
|
||||
|
||||
-- Validate exports.scripts
|
||||
if metadata.exports.scripts then
|
||||
if type(metadata.exports.scripts) ~= "table" then
|
||||
table.insert(errors, "exports.scripts must be an array")
|
||||
end
|
||||
end
|
||||
|
||||
-- Validate exports.pages
|
||||
if metadata.exports.pages then
|
||||
if type(metadata.exports.pages) ~= "table" then
|
||||
table.insert(errors, "exports.pages must be an array")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if metadata.minLevel then
|
||||
if type(metadata.minLevel) ~= "number" then
|
||||
table.insert(errors, "minLevel must be a number")
|
||||
elseif metadata.minLevel < 1 or metadata.minLevel > 6 then
|
||||
table.insert(errors, "minLevel must be between 1 and 6")
|
||||
end
|
||||
end
|
||||
|
||||
if metadata.bindings then
|
||||
if type(metadata.bindings) ~= "table" then
|
||||
table.insert(errors, "bindings must be an object")
|
||||
else
|
||||
if metadata.bindings.dbal ~= nil and type(metadata.bindings.dbal) ~= "boolean" then
|
||||
table.insert(errors, "bindings.dbal must be a boolean")
|
||||
end
|
||||
if metadata.bindings.browser ~= nil and type(metadata.bindings.browser) ~= "boolean" then
|
||||
table.insert(errors, "bindings.browser must be a boolean")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return #errors == 0, errors
|
||||
end
|
||||
--- Validate metadata.json structure
|
||||
---@param metadata Metadata The metadata object to validate
|
||||
---@return boolean valid Whether the metadata is valid
|
||||
---@return string[] errors List of validation errors
|
||||
M.validate_metadata = validate_metadata
|
||||
|
||||
return M
|
||||
|
||||
@@ -1,284 +1,32 @@
|
||||
-- Package folder structure validation
|
||||
--- Structure validation facade
|
||||
--- Re-exports structure validation functions for backward compatibility
|
||||
---@module structure_validator
|
||||
|
||||
local structure_config = require("structure_config")
|
||||
local validate_structure = require("validate_structure")
|
||||
local validate_scripts_structure = require("validate_scripts_structure")
|
||||
local validate_static_content = require("validate_static_content")
|
||||
local validate_naming_conventions = require("validate_naming_conventions")
|
||||
local validate_test_structure = require("validate_test_structure")
|
||||
local validate_package_structure = require("validate_package_structure")
|
||||
|
||||
---@class StructureValidator
|
||||
local M = {}
|
||||
|
||||
-- Expected package structure
|
||||
M.REQUIRED_STRUCTURE = {
|
||||
["seed/metadata.json"] = true,
|
||||
["seed/components.json"] = true
|
||||
}
|
||||
-- Structure configuration
|
||||
M.REQUIRED_STRUCTURE = structure_config.REQUIRED
|
||||
M.OPTIONAL_STRUCTURE = structure_config.OPTIONAL
|
||||
|
||||
M.OPTIONAL_STRUCTURE = {
|
||||
["seed/scripts/"] = "directory",
|
||||
["seed/scripts/init.lua"] = "file",
|
||||
["seed/scripts/tests/"] = "directory",
|
||||
["static_content/"] = "directory",
|
||||
["static_content/icon.svg"] = "file",
|
||||
["README.md"] = "file",
|
||||
["examples/"] = "directory"
|
||||
}
|
||||
|
||||
-- Validate basic folder structure
|
||||
function M.validate_structure(package_path)
|
||||
local errors = {}
|
||||
local warnings = {}
|
||||
|
||||
-- Check required files
|
||||
for path, _ in pairs(M.REQUIRED_STRUCTURE) do
|
||||
local full_path = package_path .. "/" .. path
|
||||
local file = io.open(full_path, "r")
|
||||
|
||||
if not file then
|
||||
table.insert(errors, "Required file missing: " .. path)
|
||||
else
|
||||
file:close()
|
||||
end
|
||||
end
|
||||
|
||||
-- Check optional but recommended files
|
||||
for path, type in pairs(M.OPTIONAL_STRUCTURE) do
|
||||
local full_path = package_path .. "/" .. path
|
||||
|
||||
if type == "file" then
|
||||
local file = io.open(full_path, "r")
|
||||
if not file then
|
||||
table.insert(warnings, "Recommended file missing: " .. path)
|
||||
else
|
||||
file:close()
|
||||
end
|
||||
elseif type == "directory" then
|
||||
-- Note: Directory checking would be done with OS-specific commands
|
||||
-- This is a placeholder for the pattern
|
||||
end
|
||||
end
|
||||
|
||||
return errors, warnings
|
||||
end
|
||||
|
||||
-- Validate scripts directory structure
|
||||
function M.validate_scripts_structure(package_path, metadata)
|
||||
local errors = {}
|
||||
local warnings = {}
|
||||
|
||||
local scripts_path = package_path .. "/scripts"
|
||||
|
||||
-- Check if scripts directory exists when exports.scripts is defined
|
||||
if metadata.exports and metadata.exports.scripts and #metadata.exports.scripts > 0 then
|
||||
local dir_exists = false
|
||||
local test_file = io.open(scripts_path .. "/init.lua", "r")
|
||||
if test_file then
|
||||
test_file:close()
|
||||
dir_exists = true
|
||||
end
|
||||
|
||||
if not dir_exists then
|
||||
table.insert(errors, "scripts/ directory required when exports.scripts is defined")
|
||||
end
|
||||
|
||||
-- Check for init.lua
|
||||
local init_file = io.open(scripts_path .. "/init.lua", "r")
|
||||
if not init_file then
|
||||
table.insert(warnings, "scripts/init.lua is recommended as entry point")
|
||||
else
|
||||
init_file:close()
|
||||
end
|
||||
|
||||
-- Check for tests directory if lua_test is a devDependency
|
||||
local has_lua_test = false
|
||||
if metadata.devDependencies then
|
||||
for _, dep in ipairs(metadata.devDependencies) do
|
||||
if dep == "lua_test" then
|
||||
has_lua_test = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if has_lua_test then
|
||||
-- lua_test is a devDependency, so tests are expected
|
||||
local test_init = io.open(scripts_path .. "/tests/metadata.test.lua", "r")
|
||||
if not test_init then
|
||||
table.insert(warnings, "scripts/tests/ directory with test files recommended when lua_test is a devDependency")
|
||||
else
|
||||
test_init:close()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return errors, warnings
|
||||
end
|
||||
|
||||
-- Validate static content structure
|
||||
function M.validate_static_content(package_path, metadata)
|
||||
local errors = {}
|
||||
local warnings = {}
|
||||
|
||||
if metadata.icon then
|
||||
local icon_path = package_path .. "/" .. metadata.icon
|
||||
local icon_file = io.open(icon_path, "r")
|
||||
|
||||
if not icon_file then
|
||||
table.insert(errors, "Icon file not found: " .. metadata.icon)
|
||||
else
|
||||
icon_file:close()
|
||||
end
|
||||
else
|
||||
table.insert(warnings, "No icon defined in metadata")
|
||||
end
|
||||
|
||||
return errors, warnings
|
||||
end
|
||||
|
||||
-- Check for orphaned files (files not referenced in metadata)
|
||||
function M.check_orphaned_files(package_path, metadata)
|
||||
local warnings = {}
|
||||
|
||||
-- This would scan the package directory and check if files are referenced
|
||||
-- Placeholder for the pattern
|
||||
|
||||
return warnings
|
||||
end
|
||||
|
||||
-- Validate naming conventions
|
||||
function M.validate_naming_conventions(package_path, metadata)
|
||||
local errors = {}
|
||||
local warnings = {}
|
||||
|
||||
-- Package directory should match packageId
|
||||
local dir_name = package_path:match("([^/]+)$")
|
||||
if dir_name ~= metadata.packageId then
|
||||
table.insert(errors, "Package directory name '" .. dir_name .. "' does not match packageId '" .. metadata.packageId .. "'")
|
||||
end
|
||||
|
||||
-- Check script file naming
|
||||
if metadata.exports and metadata.exports.scripts then
|
||||
for _, script_name in ipairs(metadata.exports.scripts) do
|
||||
-- Script names should be lowercase with underscores
|
||||
if not string.match(script_name, "^[a-z_]+$") then
|
||||
table.insert(warnings, "Script name '" .. script_name .. "' should use lowercase and underscores only")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Check component naming
|
||||
if metadata.exports and metadata.exports.components then
|
||||
for _, component_name in ipairs(metadata.exports.components) do
|
||||
-- Component names can be PascalCase or snake_case
|
||||
local is_valid = string.match(component_name, "^[A-Z][a-zA-Z]+$") or
|
||||
string.match(component_name, "^[a-z_]+$")
|
||||
if not is_valid then
|
||||
table.insert(warnings, "Component name '" .. component_name .. "' should use PascalCase or snake_case")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return errors, warnings
|
||||
end
|
||||
|
||||
-- Validate test files structure
|
||||
function M.validate_test_structure(package_path, metadata)
|
||||
local errors = {}
|
||||
local warnings = {}
|
||||
|
||||
-- Check if lua_test is a devDependency
|
||||
local has_lua_test = false
|
||||
if metadata.devDependencies then
|
||||
for _, dep in ipairs(metadata.devDependencies) do
|
||||
if dep == "lua_test" then
|
||||
has_lua_test = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not has_lua_test then
|
||||
-- No lua_test, tests are optional
|
||||
return errors, warnings
|
||||
end
|
||||
|
||||
local tests_path = package_path .. "/scripts/tests"
|
||||
|
||||
-- Check for common test files
|
||||
local common_tests = {
|
||||
"metadata.test.lua",
|
||||
"components.test.lua"
|
||||
}
|
||||
|
||||
local found_tests = false
|
||||
for _, test_file in ipairs(common_tests) do
|
||||
local test_path = tests_path .. "/" .. test_file
|
||||
local file = io.open(test_path, "r")
|
||||
if file then
|
||||
file:close()
|
||||
found_tests = true
|
||||
end
|
||||
end
|
||||
|
||||
if not found_tests then
|
||||
table.insert(warnings, "No test files found (metadata.test.lua, components.test.lua) despite lua_test devDependency")
|
||||
end
|
||||
|
||||
return errors, warnings
|
||||
end
|
||||
|
||||
-- Validate complete package structure
|
||||
function M.validate_package_structure(package_path, metadata)
|
||||
local results = {
|
||||
valid = true,
|
||||
errors = {},
|
||||
warnings = {}
|
||||
}
|
||||
|
||||
-- Basic structure
|
||||
local struct_errors, struct_warnings = M.validate_structure(package_path)
|
||||
for _, err in ipairs(struct_errors) do
|
||||
table.insert(results.errors, err)
|
||||
results.valid = false
|
||||
end
|
||||
for _, warn in ipairs(struct_warnings) do
|
||||
table.insert(results.warnings, warn)
|
||||
end
|
||||
|
||||
-- Scripts structure
|
||||
local script_errors, script_warnings = M.validate_scripts_structure(package_path, metadata)
|
||||
for _, err in ipairs(script_errors) do
|
||||
table.insert(results.errors, err)
|
||||
results.valid = false
|
||||
end
|
||||
for _, warn in ipairs(script_warnings) do
|
||||
table.insert(results.warnings, warn)
|
||||
end
|
||||
|
||||
-- Static content
|
||||
local static_errors, static_warnings = M.validate_static_content(package_path, metadata)
|
||||
for _, err in ipairs(static_errors) do
|
||||
table.insert(results.errors, err)
|
||||
results.valid = false
|
||||
end
|
||||
for _, warn in ipairs(static_warnings) do
|
||||
table.insert(results.warnings, warn)
|
||||
end
|
||||
|
||||
-- Naming conventions
|
||||
local naming_errors, naming_warnings = M.validate_naming_conventions(package_path, metadata)
|
||||
for _, err in ipairs(naming_errors) do
|
||||
table.insert(results.errors, err)
|
||||
results.valid = false
|
||||
end
|
||||
for _, warn in ipairs(naming_warnings) do
|
||||
table.insert(results.warnings, warn)
|
||||
end
|
||||
|
||||
-- Test structure (only if lua_test is a devDependency)
|
||||
local test_errors, test_warnings = M.validate_test_structure(package_path, metadata)
|
||||
for _, err in ipairs(test_errors) do
|
||||
table.insert(results.errors, err)
|
||||
results.valid = false
|
||||
end
|
||||
for _, warn in ipairs(test_warnings) do
|
||||
table.insert(results.warnings, warn)
|
||||
end
|
||||
|
||||
return results
|
||||
-- Validation functions
|
||||
M.validate_structure = validate_structure
|
||||
M.validate_scripts_structure = validate_scripts_structure
|
||||
M.validate_static_content = validate_static_content
|
||||
M.check_orphaned_files = function(package_path, metadata)
|
||||
-- Placeholder for orphaned file checking
|
||||
return {}
|
||||
end
|
||||
M.validate_naming_conventions = validate_naming_conventions
|
||||
M.validate_test_structure = validate_test_structure
|
||||
M.validate_package_structure = validate_package_structure
|
||||
|
||||
return M
|
||||
|
||||
@@ -1,132 +1,15 @@
|
||||
-- Main validation orchestrator
|
||||
local metadata_schema = require("metadata_schema")
|
||||
local component_schema = require("component_schema")
|
||||
local structure_validator = require("structure_validator")
|
||||
local lua_validator = require("lua_validator")
|
||||
--- Main validation orchestrator facade
|
||||
--- Re-exports validation functions for backward compatibility
|
||||
---@module validate
|
||||
|
||||
local validate_package_module = require("validate_package")
|
||||
|
||||
---@class Validate
|
||||
local M = {}
|
||||
|
||||
-- Validate a complete package
|
||||
function M.validate_package(package_path, options)
|
||||
options = options or {}
|
||||
local results = {
|
||||
valid = true,
|
||||
errors = {},
|
||||
warnings = {}
|
||||
}
|
||||
|
||||
-- Load and validate metadata.json
|
||||
local metadata_path = package_path .. "/metadata.json"
|
||||
local metadata_content = load_file(metadata_path)
|
||||
|
||||
if not metadata_content then
|
||||
table.insert(results.errors, "Failed to load metadata.json")
|
||||
results.valid = false
|
||||
return results
|
||||
end
|
||||
|
||||
local metadata = parse_json(metadata_content)
|
||||
if not metadata then
|
||||
table.insert(results.errors, "Failed to parse metadata.json")
|
||||
results.valid = false
|
||||
return results
|
||||
end
|
||||
|
||||
-- 1. Validate metadata schema
|
||||
local metadata_valid, metadata_errors = metadata_schema.validate_metadata(metadata)
|
||||
if not metadata_valid then
|
||||
for _, err in ipairs(metadata_errors) do
|
||||
table.insert(results.errors, "metadata.json: " .. err)
|
||||
end
|
||||
results.valid = false
|
||||
end
|
||||
|
||||
-- 2. Validate components.json
|
||||
local components_path = package_path .. "/components.json"
|
||||
local components_content = load_file(components_path)
|
||||
|
||||
if components_content then
|
||||
local components = parse_json(components_content)
|
||||
if components then
|
||||
local components_valid, component_errors = component_schema.validate_components(components)
|
||||
if not components_valid then
|
||||
for _, err in ipairs(component_errors) do
|
||||
table.insert(results.errors, "components.json: " .. err)
|
||||
end
|
||||
results.valid = false
|
||||
end
|
||||
else
|
||||
table.insert(results.errors, "Failed to parse components.json")
|
||||
results.valid = false
|
||||
end
|
||||
else
|
||||
table.insert(results.warnings, "components.json not found (optional)")
|
||||
end
|
||||
|
||||
-- 3. Validate folder structure
|
||||
if not options.skipStructure then
|
||||
local structure_results = structure_validator.validate_package_structure(package_path, metadata)
|
||||
if not structure_results.valid then
|
||||
results.valid = false
|
||||
end
|
||||
for _, err in ipairs(structure_results.errors) do
|
||||
table.insert(results.errors, "Structure: " .. err)
|
||||
end
|
||||
for _, warn in ipairs(structure_results.warnings) do
|
||||
table.insert(results.warnings, "Structure: " .. warn)
|
||||
end
|
||||
end
|
||||
|
||||
-- 4. Validate Lua script exports
|
||||
if not options.skipLua and metadata.exports and metadata.exports.scripts then
|
||||
local script_errors, script_warnings = lua_validator.validate_script_exports(package_path, metadata)
|
||||
for _, err in ipairs(script_errors) do
|
||||
table.insert(results.errors, "Lua: " .. err)
|
||||
results.valid = false
|
||||
end
|
||||
for _, warn in ipairs(script_warnings) do
|
||||
table.insert(results.warnings, "Lua: " .. warn)
|
||||
end
|
||||
end
|
||||
|
||||
return results
|
||||
end
|
||||
|
||||
-- Validate just metadata
|
||||
function M.validate_metadata_only(metadata)
|
||||
return metadata_schema.validate_metadata(metadata)
|
||||
end
|
||||
|
||||
-- Validate just components
|
||||
function M.validate_components_only(components)
|
||||
return component_schema.validate_components(components)
|
||||
end
|
||||
|
||||
-- Format validation results for display
|
||||
function M.format_results(results)
|
||||
local output = {}
|
||||
|
||||
if results.valid then
|
||||
table.insert(output, "✓ Validation passed")
|
||||
else
|
||||
table.insert(output, "✗ Validation failed")
|
||||
end
|
||||
|
||||
if #results.errors > 0 then
|
||||
table.insert(output, "\nErrors:")
|
||||
for _, err in ipairs(results.errors) do
|
||||
table.insert(output, " • " .. err)
|
||||
end
|
||||
end
|
||||
|
||||
if #results.warnings > 0 then
|
||||
table.insert(output, "\nWarnings:")
|
||||
for _, warn in ipairs(results.warnings) do
|
||||
table.insert(output, " • " .. warn)
|
||||
end
|
||||
end
|
||||
|
||||
return table.concat(output, "\n")
|
||||
end
|
||||
M.validate_package = validate_package_module.validate_package
|
||||
M.validate_metadata_only = validate_package_module.validate_metadata_only
|
||||
M.validate_components_only = validate_package_module.validate_components_only
|
||||
M.format_results = validate_package_module.format_results
|
||||
|
||||
return M
|
||||
|
||||
17
packages/quick_guide/seed/scripts/add_step.lua
Normal file
17
packages/quick_guide/seed/scripts/add_step.lua
Normal file
@@ -0,0 +1,17 @@
|
||||
local create_step = require("create_step")
|
||||
|
||||
--- Add a new step to the list
|
||||
---@param steps Step[] Array of steps
|
||||
---@return Step[] Updated steps array
|
||||
---@return Step New step that was added
|
||||
local function add_step(steps)
|
||||
local newStep = create_step()
|
||||
local result = {}
|
||||
for i, step in ipairs(steps) do
|
||||
result[i] = step
|
||||
end
|
||||
result[#result + 1] = newStep
|
||||
return result, newStep
|
||||
end
|
||||
|
||||
return add_step
|
||||
15
packages/quick_guide/seed/scripts/create_step.lua
Normal file
15
packages/quick_guide/seed/scripts/create_step.lua
Normal file
@@ -0,0 +1,15 @@
|
||||
local generate_step_id = require("generate_step_id")
|
||||
|
||||
--- Create a new empty step
|
||||
---@return Step New step with default values
|
||||
local function create_step()
|
||||
return {
|
||||
id = generate_step_id(),
|
||||
title = "New step",
|
||||
description = "Describe what happens in this step.",
|
||||
duration = "1-2 min",
|
||||
mediaUrl = nil
|
||||
}
|
||||
end
|
||||
|
||||
return create_step
|
||||
7
packages/quick_guide/seed/scripts/generate_step_id.lua
Normal file
7
packages/quick_guide/seed/scripts/generate_step_id.lua
Normal file
@@ -0,0 +1,7 @@
|
||||
--- Generate a unique step ID
|
||||
---@return string Unique step identifier
|
||||
local function generate_step_id()
|
||||
return "step_" .. tostring(os.time()) .. "_" .. math.random(1000, 9999)
|
||||
end
|
||||
|
||||
return generate_step_id
|
||||
@@ -0,0 +1,19 @@
|
||||
local is_valid_url = require("is_valid_url")
|
||||
local is_image_url = require("is_image_url")
|
||||
|
||||
--- Handle thumbnail URL change
|
||||
---@param state MediaState Current state
|
||||
---@param newUrl string New thumbnail URL
|
||||
---@return MediaState Updated state
|
||||
local function handle_thumbnail_change(state, newUrl)
|
||||
return {
|
||||
thumbnailUrl = newUrl,
|
||||
videoUrl = state.videoUrl,
|
||||
thumbnailValid = is_valid_url(newUrl),
|
||||
videoValid = state.videoValid,
|
||||
thumbnailIsImage = is_image_url(newUrl),
|
||||
videoIsVideo = state.videoIsVideo
|
||||
}
|
||||
end
|
||||
|
||||
return handle_thumbnail_change
|
||||
19
packages/quick_guide/seed/scripts/handle_video_change.lua
Normal file
19
packages/quick_guide/seed/scripts/handle_video_change.lua
Normal file
@@ -0,0 +1,19 @@
|
||||
local is_valid_url = require("is_valid_url")
|
||||
local is_video_url = require("is_video_url")
|
||||
|
||||
--- Handle video URL change
|
||||
---@param state MediaState Current state
|
||||
---@param newUrl string New video URL
|
||||
---@return MediaState Updated state
|
||||
local function handle_video_change(state, newUrl)
|
||||
return {
|
||||
thumbnailUrl = state.thumbnailUrl,
|
||||
videoUrl = newUrl,
|
||||
thumbnailValid = state.thumbnailValid,
|
||||
videoValid = is_valid_url(newUrl),
|
||||
thumbnailIsImage = state.thumbnailIsImage,
|
||||
videoIsVideo = is_video_url(newUrl)
|
||||
}
|
||||
end
|
||||
|
||||
return handle_video_change
|
||||
@@ -1,9 +1,16 @@
|
||||
-- Quick Guide package initialization
|
||||
|
||||
---@class QuickGuideModule
|
||||
---@field name string Package name
|
||||
---@field version string Package version
|
||||
---@field init fun(): boolean Initialize the module
|
||||
local M = {}
|
||||
|
||||
M.name = "quick_guide"
|
||||
M.version = "1.0.0"
|
||||
|
||||
---Initialize the quick guide module
|
||||
---@return boolean Success status
|
||||
function M.init()
|
||||
log("Quick Guide package initialized")
|
||||
return true
|
||||
|
||||
19
packages/quick_guide/seed/scripts/is_image_url.lua
Normal file
19
packages/quick_guide/seed/scripts/is_image_url.lua
Normal file
@@ -0,0 +1,19 @@
|
||||
local is_valid_url = require("is_valid_url")
|
||||
|
||||
--- Check if URL is an image
|
||||
---@param url? string URL to check
|
||||
---@return boolean Whether URL points to an image
|
||||
local function is_image_url(url)
|
||||
if not is_valid_url(url) then
|
||||
return false
|
||||
end
|
||||
local patterns = { "%.png$", "%.jpg$", "%.jpeg$", "%.gif$", "%.webp$", "%.svg$" }
|
||||
for _, pattern in ipairs(patterns) do
|
||||
if string.match(url:lower(), pattern) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
return is_image_url
|
||||
11
packages/quick_guide/seed/scripts/is_valid_url.lua
Normal file
11
packages/quick_guide/seed/scripts/is_valid_url.lua
Normal file
@@ -0,0 +1,11 @@
|
||||
--- Validate a URL (basic check)
|
||||
---@param url? string URL to validate
|
||||
---@return boolean Whether URL is valid
|
||||
local function is_valid_url(url)
|
||||
if not url or url == "" then
|
||||
return false
|
||||
end
|
||||
return string.match(url, "^https?://") ~= nil
|
||||
end
|
||||
|
||||
return is_valid_url
|
||||
19
packages/quick_guide/seed/scripts/is_video_url.lua
Normal file
19
packages/quick_guide/seed/scripts/is_video_url.lua
Normal file
@@ -0,0 +1,19 @@
|
||||
local is_valid_url = require("is_valid_url")
|
||||
|
||||
--- Check if URL is a video embed
|
||||
---@param url? string URL to check
|
||||
---@return boolean Whether URL points to a video
|
||||
local function is_video_url(url)
|
||||
if not is_valid_url(url) then
|
||||
return false
|
||||
end
|
||||
local patterns = { "youtube%.com", "vimeo%.com", "%.mp4$", "%.webm$" }
|
||||
for _, pattern in ipairs(patterns) do
|
||||
if string.match(url:lower(), pattern) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
return is_video_url
|
||||
@@ -1,77 +1,22 @@
|
||||
-- Media pane logic for quick guides
|
||||
--- Media pane logic facade for quick guides
|
||||
--- Re-exports media functions for backward compatibility
|
||||
---@module media
|
||||
|
||||
local is_valid_url = require("is_valid_url")
|
||||
local is_image_url = require("is_image_url")
|
||||
local is_video_url = require("is_video_url")
|
||||
local prepare_media_state = require("prepare_media_state")
|
||||
local handle_thumbnail_change = require("handle_thumbnail_change")
|
||||
local handle_video_change = require("handle_video_change")
|
||||
|
||||
---@class MediaModule
|
||||
local M = {}
|
||||
|
||||
-- Validate a URL (basic check)
|
||||
function M.isValidUrl(url)
|
||||
if not url or url == "" then
|
||||
return false
|
||||
end
|
||||
return string.match(url, "^https?://") ~= nil
|
||||
end
|
||||
|
||||
-- Check if URL is an image
|
||||
function M.isImageUrl(url)
|
||||
if not M.isValidUrl(url) then
|
||||
return false
|
||||
end
|
||||
local patterns = { "%.png$", "%.jpg$", "%.jpeg$", "%.gif$", "%.webp$", "%.svg$" }
|
||||
for _, pattern in ipairs(patterns) do
|
||||
if string.match(url:lower(), pattern) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Check if URL is a video embed
|
||||
function M.isVideoUrl(url)
|
||||
if not M.isValidUrl(url) then
|
||||
return false
|
||||
end
|
||||
local patterns = { "youtube%.com", "vimeo%.com", "%.mp4$", "%.webm$" }
|
||||
for _, pattern in ipairs(patterns) do
|
||||
if string.match(url:lower(), pattern) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Prepare media state
|
||||
function M.prepareMediaState(props)
|
||||
props = props or {}
|
||||
return {
|
||||
thumbnailUrl = props.thumbnailUrl or "",
|
||||
videoUrl = props.videoUrl or "",
|
||||
thumbnailValid = M.isValidUrl(props.thumbnailUrl),
|
||||
videoValid = M.isValidUrl(props.videoUrl),
|
||||
thumbnailIsImage = M.isImageUrl(props.thumbnailUrl),
|
||||
videoIsVideo = M.isVideoUrl(props.videoUrl)
|
||||
}
|
||||
end
|
||||
|
||||
-- Handle thumbnail change
|
||||
function M.handleThumbnailChange(state, newUrl)
|
||||
return {
|
||||
thumbnailUrl = newUrl,
|
||||
videoUrl = state.videoUrl,
|
||||
thumbnailValid = M.isValidUrl(newUrl),
|
||||
videoValid = state.videoValid,
|
||||
thumbnailIsImage = M.isImageUrl(newUrl),
|
||||
videoIsVideo = state.videoIsVideo
|
||||
}
|
||||
end
|
||||
|
||||
-- Handle video change
|
||||
function M.handleVideoChange(state, newUrl)
|
||||
return {
|
||||
thumbnailUrl = state.thumbnailUrl,
|
||||
videoUrl = newUrl,
|
||||
thumbnailValid = state.thumbnailValid,
|
||||
videoValid = M.isValidUrl(newUrl),
|
||||
thumbnailIsImage = state.thumbnailIsImage,
|
||||
videoIsVideo = M.isVideoUrl(newUrl)
|
||||
}
|
||||
end
|
||||
M.isValidUrl = is_valid_url
|
||||
M.isImageUrl = is_image_url
|
||||
M.isVideoUrl = is_video_url
|
||||
M.prepareMediaState = prepare_media_state
|
||||
M.handleThumbnailChange = handle_thumbnail_change
|
||||
M.handleVideoChange = handle_video_change
|
||||
|
||||
return M
|
||||
|
||||
20
packages/quick_guide/seed/scripts/prepare_media_state.lua
Normal file
20
packages/quick_guide/seed/scripts/prepare_media_state.lua
Normal file
@@ -0,0 +1,20 @@
|
||||
local is_valid_url = require("is_valid_url")
|
||||
local is_image_url = require("is_image_url")
|
||||
local is_video_url = require("is_video_url")
|
||||
|
||||
--- Prepare media state from props
|
||||
---@param props? MediaProps Input props
|
||||
---@return MediaState Initial media state
|
||||
local function prepare_media_state(props)
|
||||
props = props or {}
|
||||
return {
|
||||
thumbnailUrl = props.thumbnailUrl or "",
|
||||
videoUrl = props.videoUrl or "",
|
||||
thumbnailValid = is_valid_url(props.thumbnailUrl),
|
||||
videoValid = is_valid_url(props.videoUrl),
|
||||
thumbnailIsImage = is_image_url(props.thumbnailUrl),
|
||||
videoIsVideo = is_video_url(props.videoUrl)
|
||||
}
|
||||
end
|
||||
|
||||
return prepare_media_state
|
||||
15
packages/quick_guide/seed/scripts/remove_step.lua
Normal file
15
packages/quick_guide/seed/scripts/remove_step.lua
Normal file
@@ -0,0 +1,15 @@
|
||||
--- Remove a step from the list
|
||||
---@param steps Step[] Array of steps
|
||||
---@param stepId string ID of step to remove
|
||||
---@return Step[] Updated steps array
|
||||
local function remove_step(steps, stepId)
|
||||
local result = {}
|
||||
for _, step in ipairs(steps) do
|
||||
if step.id ~= stepId then
|
||||
result[#result + 1] = step
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
return remove_step
|
||||
17
packages/quick_guide/seed/scripts/reset_ordering.lua
Normal file
17
packages/quick_guide/seed/scripts/reset_ordering.lua
Normal file
@@ -0,0 +1,17 @@
|
||||
--- Reset step IDs to sequential order
|
||||
---@param steps Step[] Array of steps
|
||||
---@return Step[] Steps with reset IDs
|
||||
local function reset_ordering(steps)
|
||||
local result = {}
|
||||
for i, step in ipairs(steps) do
|
||||
local resetStep = {}
|
||||
for k, v in pairs(step) do
|
||||
resetStep[k] = v
|
||||
end
|
||||
resetStep.id = "step_" .. tostring(i)
|
||||
result[i] = resetStep
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
return reset_ordering
|
||||
@@ -1,107 +1,26 @@
|
||||
-- Steps editor logic for quick guides
|
||||
--- Steps editor logic facade for quick guides
|
||||
--- Re-exports step functions for backward compatibility
|
||||
---@module steps
|
||||
|
||||
local generate_step_id = require("generate_step_id")
|
||||
local create_step = require("create_step")
|
||||
local update_step = require("update_step")
|
||||
local remove_step = require("remove_step")
|
||||
local add_step = require("add_step")
|
||||
local reset_ordering = require("reset_ordering")
|
||||
local validate_step = require("validate_step")
|
||||
local validate_all_steps = require("validate_all_steps")
|
||||
|
||||
---@class StepsModule
|
||||
local M = {}
|
||||
|
||||
-- Generate a unique step ID
|
||||
function M.generateStepId()
|
||||
return "step_" .. tostring(os.time()) .. "_" .. math.random(1000, 9999)
|
||||
end
|
||||
|
||||
-- Create a new empty step
|
||||
function M.createStep()
|
||||
return {
|
||||
id = M.generateStepId(),
|
||||
title = "New step",
|
||||
description = "Describe what happens in this step.",
|
||||
duration = "1-2 min",
|
||||
mediaUrl = nil
|
||||
}
|
||||
end
|
||||
|
||||
-- Update a step in the list
|
||||
function M.updateStep(steps, stepId, updates)
|
||||
local result = {}
|
||||
for i, step in ipairs(steps) do
|
||||
if step.id == stepId then
|
||||
local updatedStep = {}
|
||||
for k, v in pairs(step) do
|
||||
updatedStep[k] = v
|
||||
end
|
||||
for k, v in pairs(updates) do
|
||||
updatedStep[k] = v
|
||||
end
|
||||
result[i] = updatedStep
|
||||
else
|
||||
result[i] = step
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- Remove a step from the list
|
||||
function M.removeStep(steps, stepId)
|
||||
local result = {}
|
||||
for _, step in ipairs(steps) do
|
||||
if step.id ~= stepId then
|
||||
result[#result + 1] = step
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- Add a new step to the list
|
||||
function M.addStep(steps)
|
||||
local newStep = M.createStep()
|
||||
local result = {}
|
||||
for i, step in ipairs(steps) do
|
||||
result[i] = step
|
||||
end
|
||||
result[#result + 1] = newStep
|
||||
return result, newStep
|
||||
end
|
||||
|
||||
-- Reset step IDs to sequential order
|
||||
function M.resetOrdering(steps)
|
||||
local result = {}
|
||||
for i, step in ipairs(steps) do
|
||||
local resetStep = {}
|
||||
for k, v in pairs(step) do
|
||||
resetStep[k] = v
|
||||
end
|
||||
resetStep.id = "step_" .. tostring(i)
|
||||
result[i] = resetStep
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- Validate a step
|
||||
function M.validateStep(step)
|
||||
local errors = {}
|
||||
|
||||
if not step.title or step.title == "" then
|
||||
errors.title = "Title is required"
|
||||
end
|
||||
|
||||
if not step.description or step.description == "" then
|
||||
errors.description = "Description is required"
|
||||
end
|
||||
|
||||
return { valid = next(errors) == nil, errors = errors }
|
||||
end
|
||||
|
||||
-- Validate all steps
|
||||
function M.validateAllSteps(steps)
|
||||
local allErrors = {}
|
||||
local valid = true
|
||||
|
||||
for i, step in ipairs(steps) do
|
||||
local result = M.validateStep(step)
|
||||
if not result.valid then
|
||||
valid = false
|
||||
allErrors[step.id] = result.errors
|
||||
end
|
||||
end
|
||||
|
||||
return { valid = valid, errors = allErrors }
|
||||
end
|
||||
M.generateStepId = generate_step_id
|
||||
M.createStep = create_step
|
||||
M.updateStep = update_step
|
||||
M.removeStep = remove_step
|
||||
M.addStep = add_step
|
||||
M.resetOrdering = reset_ordering
|
||||
M.validateStep = validate_step
|
||||
M.validateAllSteps = validate_all_steps
|
||||
|
||||
return M
|
||||
|
||||
35
packages/quick_guide/seed/scripts/types.lua
Normal file
35
packages/quick_guide/seed/scripts/types.lua
Normal file
@@ -0,0 +1,35 @@
|
||||
---@meta
|
||||
-- Type definitions for quick_guide package
|
||||
|
||||
---@class Step
|
||||
---@field id string Unique step identifier
|
||||
---@field title string Step title
|
||||
---@field description string Step description
|
||||
---@field duration string Estimated duration
|
||||
---@field mediaUrl? string Optional media URL
|
||||
|
||||
---@class StepValidationErrors
|
||||
---@field title? string Title error message
|
||||
---@field description? string Description error message
|
||||
|
||||
---@class StepValidationResult
|
||||
---@field valid boolean Whether step is valid
|
||||
---@field errors StepValidationErrors Validation errors
|
||||
|
||||
---@class AllStepsValidationResult
|
||||
---@field valid boolean Whether all steps are valid
|
||||
---@field errors table<string, StepValidationErrors> Errors by step ID
|
||||
|
||||
---@class MediaState
|
||||
---@field thumbnailUrl string Thumbnail URL
|
||||
---@field videoUrl string Video URL
|
||||
---@field thumbnailValid boolean Whether thumbnail URL is valid
|
||||
---@field videoValid boolean Whether video URL is valid
|
||||
---@field thumbnailIsImage boolean Whether thumbnail is an image URL
|
||||
---@field videoIsVideo boolean Whether video is a video URL
|
||||
|
||||
---@class MediaProps
|
||||
---@field thumbnailUrl? string Initial thumbnail URL
|
||||
---@field videoUrl? string Initial video URL
|
||||
|
||||
return {}
|
||||
25
packages/quick_guide/seed/scripts/update_step.lua
Normal file
25
packages/quick_guide/seed/scripts/update_step.lua
Normal file
@@ -0,0 +1,25 @@
|
||||
--- Update a step in the list
|
||||
---@param steps Step[] Array of steps
|
||||
---@param stepId string ID of step to update
|
||||
---@param updates table Partial step updates
|
||||
---@return Step[] Updated steps array
|
||||
local function update_step(steps, stepId, updates)
|
||||
local result = {}
|
||||
for i, step in ipairs(steps) do
|
||||
if step.id == stepId then
|
||||
local updatedStep = {}
|
||||
for k, v in pairs(step) do
|
||||
updatedStep[k] = v
|
||||
end
|
||||
for k, v in pairs(updates) do
|
||||
updatedStep[k] = v
|
||||
end
|
||||
result[i] = updatedStep
|
||||
else
|
||||
result[i] = step
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
return update_step
|
||||
21
packages/quick_guide/seed/scripts/validate_all_steps.lua
Normal file
21
packages/quick_guide/seed/scripts/validate_all_steps.lua
Normal file
@@ -0,0 +1,21 @@
|
||||
local validate_step = require("validate_step")
|
||||
|
||||
--- Validate all steps
|
||||
---@param steps Step[] Array of steps to validate
|
||||
---@return AllStepsValidationResult Validation result for all steps
|
||||
local function validate_all_steps(steps)
|
||||
local allErrors = {}
|
||||
local valid = true
|
||||
|
||||
for _, step in ipairs(steps) do
|
||||
local result = validate_step(step)
|
||||
if not result.valid then
|
||||
valid = false
|
||||
allErrors[step.id] = result.errors
|
||||
end
|
||||
end
|
||||
|
||||
return { valid = valid, errors = allErrors }
|
||||
end
|
||||
|
||||
return validate_all_steps
|
||||
18
packages/quick_guide/seed/scripts/validate_step.lua
Normal file
18
packages/quick_guide/seed/scripts/validate_step.lua
Normal file
@@ -0,0 +1,18 @@
|
||||
--- Validate a single step
|
||||
---@param step Step Step to validate
|
||||
---@return StepValidationResult Validation result
|
||||
local function validate_step(step)
|
||||
local errors = {}
|
||||
|
||||
if not step.title or step.title == "" then
|
||||
errors.title = "Title is required"
|
||||
end
|
||||
|
||||
if not step.description or step.description == "" then
|
||||
errors.description = "Description is required"
|
||||
end
|
||||
|
||||
return { valid = next(errors) == nil, errors = errors }
|
||||
end
|
||||
|
||||
return validate_step
|
||||
@@ -1,4 +1,33 @@
|
||||
-- Schema field types
|
||||
|
||||
---@alias FieldType "string" | "integer" | "float" | "boolean" | "date" | "datetime" | "text" | "json"
|
||||
|
||||
---@class FieldOptions
|
||||
---@field nullable? boolean Whether field can be null
|
||||
---@field default? any Default value for field
|
||||
---@field unique? boolean Whether field must be unique
|
||||
|
||||
---@class FieldReference
|
||||
---@field table string Referenced table name
|
||||
---@field field string Referenced field name
|
||||
|
||||
---@class FieldDefinition
|
||||
---@field name string Field name
|
||||
---@field type FieldType Field type
|
||||
---@field nullable boolean Whether field can be null
|
||||
---@field default? any Default value
|
||||
---@field unique boolean Whether field must be unique
|
||||
---@field references? FieldReference Foreign key reference
|
||||
|
||||
---@class FieldsModule
|
||||
---@field STRING string
|
||||
---@field INTEGER string
|
||||
---@field FLOAT string
|
||||
---@field BOOLEAN string
|
||||
---@field DATE string
|
||||
---@field DATETIME string
|
||||
---@field TEXT string
|
||||
---@field JSON string
|
||||
local M = {}
|
||||
|
||||
M.STRING = "string"
|
||||
@@ -10,6 +39,11 @@ M.DATETIME = "datetime"
|
||||
M.TEXT = "text"
|
||||
M.JSON = "json"
|
||||
|
||||
---Define a schema field
|
||||
---@param name string Field name
|
||||
---@param type FieldType Field type
|
||||
---@param options? FieldOptions Field options
|
||||
---@return FieldDefinition
|
||||
function M.define(name, type, options)
|
||||
return {
|
||||
name = name,
|
||||
@@ -20,10 +54,18 @@ function M.define(name, type, options)
|
||||
}
|
||||
end
|
||||
|
||||
---Create a primary key field
|
||||
---@param name string Field name
|
||||
---@return FieldDefinition
|
||||
function M.primary_key(name)
|
||||
return M.define(name, M.INTEGER, { nullable = false, unique = true })
|
||||
end
|
||||
|
||||
---Create a foreign key field
|
||||
---@param name string Field name
|
||||
---@param ref_table string Referenced table name
|
||||
---@param ref_field string Referenced field name
|
||||
---@return FieldDefinition
|
||||
function M.foreign_key(name, ref_table, ref_field)
|
||||
local field = M.define(name, M.INTEGER, { nullable = true })
|
||||
field.references = { table = ref_table, field = ref_field }
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
-- Schema Editor initialization
|
||||
|
||||
---@class SchemaEditorModule
|
||||
---@field name string Package name
|
||||
---@field version string Package version
|
||||
---@field init fun(): SchemaEditorInfo Initialize the module
|
||||
local M = {}
|
||||
|
||||
M.name = "schema_editor"
|
||||
M.version = "1.0.0"
|
||||
|
||||
---@class SchemaEditorInfo
|
||||
---@field name string Package name
|
||||
---@field version string Package version
|
||||
---@field loaded boolean Whether module is loaded
|
||||
|
||||
---Initialize the schema editor module
|
||||
---@return SchemaEditorInfo
|
||||
function M.init()
|
||||
return {
|
||||
name = M.name,
|
||||
|
||||
@@ -1,10 +1,39 @@
|
||||
-- Table relationships
|
||||
|
||||
---@alias RelationType "one_to_one" | "one_to_many" | "many_to_many"
|
||||
|
||||
---@class RelationEndpoint
|
||||
---@field table string Table name
|
||||
---@field field string Field name
|
||||
|
||||
---@class RelationOptions
|
||||
---@field cascade? boolean Whether to cascade deletes
|
||||
|
||||
---@class Relation
|
||||
---@field type RelationType Relation type
|
||||
---@field from_table string Source table name
|
||||
---@field from_field string Source field name
|
||||
---@field to_table string Target table name
|
||||
---@field to_field string Target field name
|
||||
---@field cascade boolean Whether to cascade deletes
|
||||
---@field pivot_table? string Pivot table for many-to-many
|
||||
|
||||
---@class RelationsModule
|
||||
---@field ONE_TO_ONE string
|
||||
---@field ONE_TO_MANY string
|
||||
---@field MANY_TO_MANY string
|
||||
local M = {}
|
||||
|
||||
M.ONE_TO_ONE = "one_to_one"
|
||||
M.ONE_TO_MANY = "one_to_many"
|
||||
M.MANY_TO_MANY = "many_to_many"
|
||||
|
||||
---Define a table relationship
|
||||
---@param type RelationType Relation type
|
||||
---@param from RelationEndpoint Source endpoint
|
||||
---@param to RelationEndpoint Target endpoint
|
||||
---@param options? RelationOptions Relation options
|
||||
---@return Relation
|
||||
function M.define(type, from, to, options)
|
||||
return {
|
||||
type = type,
|
||||
@@ -16,14 +45,27 @@ function M.define(type, from, to, options)
|
||||
}
|
||||
end
|
||||
|
||||
---Create a one-to-one relationship
|
||||
---@param from RelationEndpoint Source endpoint
|
||||
---@param to RelationEndpoint Target endpoint
|
||||
---@return Relation
|
||||
function M.has_one(from, to)
|
||||
return M.define(M.ONE_TO_ONE, from, to)
|
||||
end
|
||||
|
||||
---Create a one-to-many relationship
|
||||
---@param from RelationEndpoint Source endpoint
|
||||
---@param to RelationEndpoint Target endpoint
|
||||
---@return Relation
|
||||
function M.has_many(from, to)
|
||||
return M.define(M.ONE_TO_MANY, from, to)
|
||||
end
|
||||
|
||||
---Create a many-to-many relationship
|
||||
---@param from RelationEndpoint Source endpoint
|
||||
---@param to RelationEndpoint Target endpoint
|
||||
---@param pivot string Pivot table name
|
||||
---@return Relation
|
||||
function M.belongs_to_many(from, to, pivot)
|
||||
local rel = M.define(M.MANY_TO_MANY, from, to)
|
||||
rel.pivot_table = pivot
|
||||
|
||||
@@ -1,6 +1,39 @@
|
||||
-- Table management
|
||||
|
||||
---@class CreateTableAction
|
||||
---@field action "create_table"
|
||||
---@field name string Table name
|
||||
---@field fields FieldDefinition[] Array of field definitions
|
||||
|
||||
---@class AddFieldAction
|
||||
---@field action "add_field"
|
||||
---@field table string Table name
|
||||
---@field field FieldDefinition Field to add
|
||||
|
||||
---@class RemoveFieldAction
|
||||
---@field action "remove_field"
|
||||
---@field table string Table name
|
||||
---@field field string Field name to remove
|
||||
|
||||
---@class TableEditorProps
|
||||
---@field name string Table name
|
||||
---@field fields FieldDefinition[] Array of field definitions
|
||||
|
||||
---@class TableEditorComponent
|
||||
---@field type "table_editor"
|
||||
---@field props TableEditorProps
|
||||
|
||||
---@class TableDefinition
|
||||
---@field name string Table name
|
||||
---@field fields FieldDefinition[] Array of field definitions
|
||||
|
||||
---@class TablesModule
|
||||
local M = {}
|
||||
|
||||
---Create a new table definition
|
||||
---@param name string Table name
|
||||
---@param fields? FieldDefinition[] Array of field definitions
|
||||
---@return CreateTableAction
|
||||
function M.create(name, fields)
|
||||
return {
|
||||
action = "create_table",
|
||||
@@ -9,6 +42,9 @@ function M.create(name, fields)
|
||||
}
|
||||
end
|
||||
|
||||
---Render a table editor component
|
||||
---@param table_def TableDefinition Table definition
|
||||
---@return TableEditorComponent
|
||||
function M.render(table_def)
|
||||
return {
|
||||
type = "table_editor",
|
||||
@@ -19,6 +55,10 @@ function M.render(table_def)
|
||||
}
|
||||
end
|
||||
|
||||
---Create an add field action
|
||||
---@param table_name string Table name
|
||||
---@param field FieldDefinition Field to add
|
||||
---@return AddFieldAction
|
||||
function M.add_field(table_name, field)
|
||||
return {
|
||||
action = "add_field",
|
||||
@@ -27,6 +67,10 @@ function M.add_field(table_name, field)
|
||||
}
|
||||
end
|
||||
|
||||
---Create a remove field action
|
||||
---@param table_name string Table name
|
||||
---@param field_name string Field name to remove
|
||||
---@return RemoveFieldAction
|
||||
function M.remove_field(table_name, field_name)
|
||||
return {
|
||||
action = "remove_field",
|
||||
|
||||
@@ -1,6 +1,37 @@
|
||||
-- Workflow run card utilities
|
||||
|
||||
---@class WorkflowRun
|
||||
---@field workflow_name string Workflow name
|
||||
---@field run_number integer Run number
|
||||
---@field started_at string Start timestamp
|
||||
---@field duration? string Run duration (or nil if running)
|
||||
---@field status string Run status
|
||||
|
||||
---@class CardChild
|
||||
---@field type string Child component type
|
||||
---@field content? string Text content
|
||||
---@field status? string Status for badge
|
||||
|
||||
---@class RunCardProps
|
||||
---@field title string Card title
|
||||
---@field subtitle string Card subtitle
|
||||
|
||||
---@class RunCard
|
||||
---@field type "card"
|
||||
---@field props RunCardProps
|
||||
---@field children CardChild[]
|
||||
|
||||
---@class RunListGrid
|
||||
---@field type "grid"
|
||||
---@field columns integer Number of columns
|
||||
---@field children RunCard[]
|
||||
|
||||
---@class RunModule
|
||||
local M = {}
|
||||
|
||||
---Render a workflow run card
|
||||
---@param run WorkflowRun Workflow run data
|
||||
---@return RunCard
|
||||
function M.render(run)
|
||||
return {
|
||||
type = "card",
|
||||
@@ -16,6 +47,9 @@ function M.render(run)
|
||||
}
|
||||
end
|
||||
|
||||
---Render a list of workflow runs as a grid
|
||||
---@param runs WorkflowRun[] Array of workflow runs
|
||||
---@return RunListGrid
|
||||
function M.render_list(runs)
|
||||
local cards = {}
|
||||
for _, run in ipairs(runs) do
|
||||
|
||||
Reference in New Issue
Block a user