feat: Add comprehensive validation for package structure, metadata, and components

- Implemented component schema validation in `component_schema.lua`.
- Created metadata schema validation in `metadata_schema.lua`.
- Developed structure validation logic in `structure_validator.lua`.
- Introduced Lua file validation functions in `lua_validator.lua`.
- Added integration tests for validation logic in `validate.test.lua`.
- Created unit tests for component, metadata, and structure validation.
- Added SVG icon for package representation.
- Established a main validation orchestrator in `validate.lua` to coordinate the validation process.
This commit is contained in:
2025-12-30 02:19:28 +00:00
parent 2f61892e5a
commit 459182b655
23 changed files with 2239 additions and 0 deletions

View File

@@ -7,6 +7,9 @@
"author": "MetaBuilder",
"category": "admin",
"dependencies": [],
"devDependencies": [
"package_validator"
],
"exports": {
"components": [
"AuditLogViewer",

View File

@@ -767,6 +767,28 @@
]
},
"minLevel": 5
},
{
"packageId": "package_validator",
"name": "Package Validator",
"version": "1.0.0",
"description": "Validates package structure including JSON schemas, Lua files, and folder organization",
"icon": "static_content/icon.svg",
"author": "MetaBuilder",
"category": "tools",
"dependencies": [],
"exports": {
"components": [],
"scripts": [
"validate",
"metadata_schema",
"component_schema",
"lua_validator",
"structure_validator",
"cli"
]
},
"minLevel": 5
}
]
}

View File

@@ -0,0 +1,380 @@
# Package Validator
A comprehensive Lua-based validation system for MetaBuilder packages. Validates JSON schemas, Lua files, folder structure, and naming conventions.
## Features
- **Metadata Validation**: Validates `metadata.json` files for correct structure and required fields
- **Component Validation**: Validates `components.json` files with nested layout structures
- **Lua File Validation**: Checks Lua syntax, module patterns, and code quality
- **Folder Structure Validation**: Ensures proper package organization and file placement
- **Naming Convention Validation**: Validates package, script, and component naming
- **Comprehensive Error Reporting**: Provides detailed error messages and warnings
- **Test Coverage**: Includes comprehensive test suite for all validation rules
## Package Structure
```
package_validator/
├── seed/
│ ├── metadata.json # Package metadata
│ ├── components.json # Component definitions (empty for this package)
│ └── scripts/
│ ├── init.lua # Main entry point
│ ├── validate.lua # Validation orchestrator
│ ├── metadata_schema.lua # Metadata validation rules
│ ├── component_schema.lua # Component validation rules
│ ├── lua_validator.lua # Lua file validation
│ ├── structure_validator.lua # Folder structure validation
│ └── tests/
│ ├── metadata.test.lua
│ ├── component.test.lua
│ ├── validate.test.lua
│ ├── lua_validator.test.lua
│ └── structure_validator.test.lua
├── static_content/
│ └── icon.svg
└── examples/
└── validate_audit_log.lua
```
## Usage
### Validate a Complete Package
```lua
local package_validator = require("init")
-- Validate a package by ID (runs all validators)
local results = package_validator.validate_package("audit_log")
-- Check if validation passed
if results.valid then
print("Package is valid!")
else
print("Validation failed:")
for _, error in ipairs(results.errors) do
print(" - " .. error)
end
end
```
### Validate with Options
```lua
local package_validator = require("init")
-- Skip specific validators
local results = package_validator.validate_package("audit_log", {
skipStructure = true, -- Skip folder structure validation
skipLua = true -- Skip Lua file validation
})
```
### Validate Metadata Only
```lua
local package_validator = require("init")
local metadata = {
packageId = "my_package",
name = "My Package",
version = "1.0.0",
description = "A test package",
author = "MetaBuilder",
category = "ui"
}
local valid, errors = package_validator.validate_metadata(metadata)
```
### Validate Components Only
```lua
local package_validator = require("init")
local components = {
{
id = "my_component",
type = "MyComponent",
layout = {
type = "Box",
children = {}
}
}
}
local valid, errors = package_validator.validate_components(components)
```
### Validate Lua Files
```lua
local lua_validator = require("lua_validator")
-- Check syntax
local lua_code = [[
local M = {}
function M.test()
return true
end
return M
]]
local valid, errors = lua_validator.validate_lua_syntax("test.lua", lua_code)
-- Check structure and patterns
local warnings = lua_validator.validate_lua_structure("test.lua", lua_code)
-- Check code quality
local quality_warnings = lua_validator.check_lua_quality("test.lua", lua_code)
```
## Validation Rules
### Metadata (metadata.json)
**Required Fields:**
- `packageId` (string, lowercase and underscores only)
- `name` (string)
- `version` (string, semantic versioning: X.Y.Z)
- `description` (string)
- `author` (string)
- `category` (string)
**Optional Fields:**
- `dependencies` (array of strings)
- `devDependencies` (array of strings) - Development-only dependencies like `package_validator`
- `exports` (object with `components`, `scripts`, `pages` arrays)
- `minLevel` (number, 1-6)
- `bindings` (object with `dbal`, `browser` booleans)
- `icon` (string)
- `tags` (array)
- `requiredHooks` (array)
### Components (components.json)
Must be an array of component objects.
**Required Fields per Component:**
- `id` (string)
- `type` (string)
**Optional Fields:**
- `name` (string)
- `description` (string)
- `props` (object)
- `layout` (object with recursive structure)
- `scripts` (object)
- `bindings` (object)
- `requiredHooks` (array)
**Layout Structure:**
- `type` (string, required)
- `props` (object)
- `children` (array of nested layouts)
### Folder Structure
**Required Files:**
- `seed/metadata.json`
- `seed/components.json`
**Recommended Files:**
- `seed/scripts/` (directory)
- `seed/scripts/init.lua` (if exports.scripts is defined)
- `seed/scripts/tests/` (directory for test files)
- `static_content/icon.svg`
- `README.md`
- `examples/` (directory)
### Lua Files
**Module Pattern:**
```lua
local M = {}
-- Your code here
return M
```
**Test File Pattern:**
```lua
describe("Test Suite", function()
it("should pass", function()
expect(value).toBe(expected)
end)
end)
```
**Quality Checks:**
- Avoid global functions (use `local function`)
- Minimize print statements
- Clean up TODO/FIXME comments before release
- Use proper module patterns
### Naming Conventions
- **Package ID**: lowercase with underscores (e.g., `package_validator`)
- **Directory name**: Must match packageId
- **Script names**: lowercase with underscores (e.g., `validate.lua`)
- **Component names**: PascalCase or snake_case (e.g., `TestComponent` or `test_component`)
## Testing
The package includes comprehensive test coverage:
```bash
# Run all tests
lua test_runner.lua packages/package_validator/seed/scripts/tests/
# Run specific test file
lua test_runner.lua packages/package_validator/seed/scripts/tests/metadata.test.lua
```
## Error Messages
The validator provides clear, categorized error messages:
```
✗ Validation failed
Errors:
• metadata.json: packageId must contain only lowercase letters and underscores
• metadata.json: version must follow semantic versioning (e.g., 1.0.0)
• Structure: Package directory name 'wrong_name' does not match packageId 'test_package'
• Lua: Exported script not found: init.lua
• components.json: components[0]: Missing required field 'type'
Warnings:
• Structure: Recommended file missing: README.md
• Structure: scripts/tests/ directory recommended for test files
• Lua: test.lua: Contains 3 print() statements
• Lua: validate.lua: Contains TODO/FIXME comments
```
## Using as a Dev Dependency
Add `package_validator` to your package's `devDependencies` to enable validation during development:
```json
{
"packageId": "your_package",
"name": "Your Package",
"version": "1.0.0",
"dependencies": [],
"devDependencies": [
"package_validator"
]
}
```
### CLI Usage
Run validation from the command line:
```bash
# Basic validation
lua packages/package_validator/seed/scripts/cli.lua your_package
# Verbose output
lua packages/package_validator/seed/scripts/cli.lua your_package --verbose
# Skip specific validators
lua packages/package_validator/seed/scripts/cli.lua your_package --skip-structure --skip-lua
# JSON output (for CI/CD)
lua packages/package_validator/seed/scripts/cli.lua your_package --json
```
**CLI Exit Codes:**
- `0` - Validation passed
- `1` - Validation failed
- `2` - Invalid arguments or package not found
### Pre-commit Hook
Install the pre-commit hook to validate packages before committing:
```bash
# Copy the example hook
cp packages/package_validator/examples/pre-commit-hook.sh .git/hooks/pre-commit
# Make it executable
chmod +x .git/hooks/pre-commit
```
### GitHub Actions
Add automated validation to your CI/CD pipeline:
```yaml
# .github/workflows/validate-packages.yml
name: Validate Packages
on:
push:
paths: ['packages/**']
pull_request:
paths: ['packages/**']
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: leafo/gh-actions-lua@v10
- run: lua packages/package_validator/seed/scripts/cli.lua your_package
```
See [examples/github-actions.yml](examples/github-actions.yml) for a complete workflow.
### NPM Scripts
Integrate with your package.json scripts:
```json
{
"scripts": {
"validate": "lua packages/package_validator/seed/scripts/cli.lua your_package",
"precommit": "npm run validate",
"test": "npm run validate && npm run test:unit"
}
}
```
See [examples/package.json.example](examples/package.json.example) for more integration patterns.
## Integration
This package can be integrated into:
- **Build pipelines** for pre-deployment validation
- **Development tools** for real-time validation
- **Package generators** for ensuring correct output
- **Documentation generators** for validating examples
- **CI/CD workflows** for automated quality checks
- **Pre-commit hooks** for preventing invalid commits
- **NPM scripts** for validation during development
## Example Output
Running the validator on a package:
```lua
local package_validator = require("init")
local validate = require("validate")
local results = package_validator.validate_package("audit_log")
print(validate.format_results(results))
```
Output:
```
✓ Validation passed
Warnings:
• Structure: Recommended file missing: examples/
```
## License
Part of the MetaBuilder system.

View File

@@ -0,0 +1,75 @@
# GitHub Actions workflow for validating packages
# Place this in .github/workflows/validate-packages.yml
name: Validate Packages
on:
push:
branches: [ main, develop ]
paths:
- 'packages/**'
pull_request:
branches: [ main, develop ]
paths:
- 'packages/**'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Lua
uses: leafo/gh-actions-lua@v10
with:
luaVersion: "5.4"
- name: Get modified packages
id: changed-packages
run: |
if [ "${{ github.event_name }}" == "pull_request" ]; then
PACKAGES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | grep '^packages/' | cut -d'/' -f2 | sort -u)
else
PACKAGES=$(git diff --name-only HEAD~1 | grep '^packages/' | cut -d'/' -f2 | sort -u)
fi
echo "packages=$PACKAGES" >> $GITHUB_OUTPUT
echo "Modified packages: $PACKAGES"
- name: Validate packages
run: |
FAILED=0
PACKAGES="${{ steps.changed-packages.outputs.packages }}"
if [ -z "$PACKAGES" ]; then
echo "No packages modified"
exit 0
fi
for PACKAGE in $PACKAGES; do
echo "::group::Validating $PACKAGE"
if lua packages/package_validator/seed/scripts/cli.lua "$PACKAGE" --verbose; then
echo "✅ $PACKAGE passed validation"
else
echo "❌ $PACKAGE failed validation"
FAILED=1
fi
echo "::endgroup::"
done
exit $FAILED
- name: Comment on PR (if failed)
if: failure() && github.event_name == 'pull_request'
uses: actions/github-script@v6
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '❌ Package validation failed. Please check the action logs for details.'
})

View File

@@ -0,0 +1,27 @@
{
"name": "metabuilder-package-example",
"version": "1.0.0",
"description": "Example showing how to integrate package_validator in npm scripts",
"scripts": {
"validate": "lua packages/package_validator/seed/scripts/cli.lua",
"validate:audit-log": "lua packages/package_validator/seed/scripts/cli.lua audit_log",
"validate:all": "bash scripts/validate-all-packages.sh",
"precommit": "npm run validate:modified",
"validate:modified": "bash packages/package_validator/examples/pre-commit-hook.sh",
"test": "npm run validate:all && npm run test:unit"
},
"devDependencies": {
"husky": "^8.0.0",
"lint-staged": "^13.0.0"
},
"husky": {
"hooks": {
"pre-commit": "npm run precommit"
}
},
"lint-staged": {
"packages/*/seed/**/*": [
"npm run validate:modified"
]
}
}

View File

@@ -0,0 +1,45 @@
#!/bin/bash
# Pre-commit hook to validate package structure
# Place this in .git/hooks/pre-commit and make it executable
# Get the list of modified packages
MODIFIED_PACKAGES=$(git diff --cached --name-only | grep '^packages/' | cut -d'/' -f2 | sort -u)
if [ -z "$MODIFIED_PACKAGES" ]; then
echo "No package files modified, skipping validation"
exit 0
fi
echo "🔍 Validating modified packages..."
FAILED_PACKAGES=""
TOTAL_PACKAGES=0
for PACKAGE in $MODIFIED_PACKAGES; do
TOTAL_PACKAGES=$((TOTAL_PACKAGES + 1))
echo ""
echo "Validating package: $PACKAGE"
# Run the validator
lua packages/package_validator/seed/scripts/cli.lua "$PACKAGE"
if [ $? -ne 0 ]; then
FAILED_PACKAGES="$FAILED_PACKAGES $PACKAGE"
fi
done
echo ""
echo "========================================"
if [ -z "$FAILED_PACKAGES" ]; then
echo "✅ All $TOTAL_PACKAGES package(s) passed validation!"
exit 0
else
echo "❌ Validation failed for package(s):$FAILED_PACKAGES"
echo ""
echo "Please fix the validation errors before committing."
echo "You can run validation manually with:"
for PACKAGE in $FAILED_PACKAGES; do
echo " lua packages/package_validator/seed/scripts/cli.lua $PACKAGE --verbose"
done
exit 1
fi

View File

@@ -0,0 +1,61 @@
#!/bin/bash
# Validate all packages in the packages directory
echo "🔍 Validating all packages..."
echo ""
PACKAGES_DIR="packages"
VALIDATOR_CLI="packages/package_validator/seed/scripts/cli.lua"
# Check if validator exists
if [ ! -f "$VALIDATOR_CLI" ]; then
echo "❌ Error: Package validator not found at $VALIDATOR_CLI"
exit 2
fi
# Get all package directories (exclude package_validator itself to avoid circular checks during initial validation)
PACKAGES=$(find "$PACKAGES_DIR" -maxdepth 1 -mindepth 1 -type d -exec basename {} \; | sort)
TOTAL=0
PASSED=0
FAILED=0
FAILED_PACKAGES=""
for PACKAGE in $PACKAGES; do
TOTAL=$((TOTAL + 1))
echo "──────────────────────────────────────"
echo "📦 Package: $PACKAGE"
echo ""
if lua "$VALIDATOR_CLI" "$PACKAGE"; then
PASSED=$((PASSED + 1))
echo ""
else
FAILED=$((FAILED + 1))
FAILED_PACKAGES="$FAILED_PACKAGES
- $PACKAGE"
echo ""
fi
done
echo "=========================================="
echo ""
echo "📊 Validation Summary:"
echo " Total packages: $TOTAL"
echo " ✅ Passed: $PASSED"
echo " ❌ Failed: $FAILED"
echo ""
if [ $FAILED -eq 0 ]; then
echo "🎉 All packages validated successfully!"
exit 0
else
echo "⚠️ Failed packages:$FAILED_PACKAGES"
echo ""
echo "Run with --verbose for detailed error information:"
for PACKAGE in $FAILED_PACKAGES; do
echo " lua $VALIDATOR_CLI $(echo $PACKAGE | sed 's/^ - //') --verbose"
done
exit 1
fi

View File

@@ -0,0 +1,94 @@
-- Example: Validate the audit_log package
-- This demonstrates how to use the package_validator package
local package_validator = require("init")
local validate = require("validate")
print("=== Package Validator Demo ===\n")
-- Example 1: Validate a complete package
print("1. Validating audit_log package...")
local results = package_validator.validate_package("audit_log")
print(validate.format_results(results))
print()
-- Example 2: Validate just metadata
print("2. Validating metadata only...")
local metadata = {
packageId = "package_validator",
name = "Package Validator",
version = "1.0.0",
description = "Validates JSON schemas for package metadata, components, and configuration files",
icon = "static_content/icon.svg",
author = "MetaBuilder",
category = "tools",
dependencies = {},
exports = {
components = {},
scripts = {"validate", "metadata_schema", "component_schema"}
},
minLevel = 5
}
local valid, errors = package_validator.validate_metadata(metadata)
if valid then
print("✓ Metadata is valid")
else
print("✗ Metadata validation failed:")
for _, err in ipairs(errors) do
print("" .. err)
end
end
print()
-- Example 3: Validate components
print("3. Validating components...")
local components = {
{
id = "audit_stats_cards",
type = "audit_stats_cards",
name = "Audit Stats Cards",
description = "Grid of stat cards showing audit log summary",
props = {
stats = {
total = 0,
successful = 0,
failed = 0,
rateLimit = 0
}
},
layout = {
type = "Grid",
props = { cols = 4, gap = 4 }
}
}
}
local valid, errors = package_validator.validate_components(components)
if valid then
print("✓ Components are valid")
else
print("✗ Component validation failed:")
for _, err in ipairs(errors) do
print("" .. err)
end
end
print()
-- Example 4: Demonstrate validation failure
print("4. Demonstrating validation failure...")
local invalid_metadata = {
packageId = "Invalid-Package", -- Invalid: contains uppercase and hyphen
name = "Test",
version = "1.0", -- Invalid: not semantic versioning
description = "Test package",
author = "Test",
category = "test",
minLevel = 10 -- Invalid: out of range
}
local valid, errors = package_validator.validate_metadata(invalid_metadata)
print("✗ Expected validation failures:")
for _, err in ipairs(errors) do
print("" .. err)
end

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,22 @@
{
"packageId": "package_validator",
"name": "Package Validator",
"version": "1.0.0",
"description": "Validates package structure including JSON schemas, Lua files, and folder organization",
"icon": "static_content/icon.svg",
"author": "MetaBuilder",
"category": "tools",
"dependencies": [],
"exports": {
"components": [],
"scripts": [
"validate",
"metadata_schema",
"component_schema",
"lua_validator",
"structure_validator",
"cli"
]
},
"minLevel": 5
}

View File

@@ -0,0 +1,172 @@
-- CLI tool for validating packages
-- Usage: lua cli.lua <package_name> [options]
local validate = require("validate")
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
}
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
function M.run(args)
local options = M.parse_args(args)
if not options.package_name then
print("Error: Package name is required")
print("Use --help for usage information")
os.exit(2)
end
-- Construct package path
local package_path = "packages/" .. options.package_name .. "/seed"
-- Check if package exists
local metadata_file = io.open(package_path .. "/metadata.json", "r")
if not metadata_file then
print("Error: Package '" .. options.package_name .. "' not found")
os.exit(2)
end
metadata_file:close()
-- Validate package
local results = validate.validate_package(package_path, {
skipStructure = options.skipStructure,
skipLua = options.skipLua
})
-- Output results
if options.json_output then
M.output_json(results)
else
M.output_human(results, options.verbose)
end
-- Exit with appropriate code
if results.valid then
os.exit(0)
else
os.exit(1)
end
end
return M
-- If run directly, execute main
if arg then
M.run(arg)
end

View File

@@ -0,0 +1,126 @@
-- Component JSON schema definitions
local M = {}
-- Validate a single component structure
function M.validate_component(component, index)
local errors = {}
local prefix = index and ("components[" .. index .. "]") or "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
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
return M

View File

@@ -0,0 +1,31 @@
-- Schema Validator initialization
local validate = require("validate")
local M = {}
-- Initialize the validator
function M.init()
return {
name = "Schema Validator",
version = "1.0.0",
description = "Validates package JSON schemas"
}
end
-- Main validation entry point
function M.validate_package(package_id)
local package_path = "packages/" .. package_id .. "/seed"
return validate.validate_package(package_path)
end
-- Quick validation for metadata
function M.validate_metadata(metadata)
return validate.validate_metadata_only(metadata)
end
-- Quick validation for components
function M.validate_components(components)
return validate.validate_components_only(components)
end
return M

View File

@@ -0,0 +1,146 @@
-- Lua file validation
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
return M

View File

@@ -0,0 +1,126 @@
-- Metadata JSON schema definitions
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
return M

View File

@@ -0,0 +1,215 @@
-- Package folder structure validation
local M = {}
-- Expected package structure
M.REQUIRED_STRUCTURE = {
["seed/metadata.json"] = true,
["seed/components.json"] = true
}
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
local test_init = io.open(scripts_path .. "/tests/metadata.test.lua", "r")
if not test_init then
table.insert(warnings, "scripts/tests/ directory recommended for test files")
else
test_init:close()
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 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
return results
end
return M

View File

@@ -0,0 +1,149 @@
-- Component validation tests for schema_validator package
local component_schema = require("component_schema")
describe("Component Schema Validation", function()
it("should validate a simple component", function()
local valid_component = {
id = "test_component",
type = "TestComponent",
name = "Test Component",
description = "A test component"
}
local errors = component_schema.validate_component(valid_component)
expect(#errors).toBe(0)
end)
it("should fail when component id is missing", function()
local invalid_component = {
type = "TestComponent",
name = "Test Component"
}
local errors = component_schema.validate_component(invalid_component)
expect(#errors).toBeGreaterThan(0)
end)
it("should fail when component type is missing", function()
local invalid_component = {
id = "test_component",
name = "Test Component"
}
local errors = component_schema.validate_component(invalid_component)
expect(#errors).toBeGreaterThan(0)
end)
it("should validate component with props", function()
local valid_component = {
id = "test_component",
type = "TestComponent",
props = {
title = "Test",
count = 5
}
}
local errors = component_schema.validate_component(valid_component)
expect(#errors).toBe(0)
end)
it("should validate component with layout", function()
local valid_component = {
id = "test_component",
type = "TestComponent",
layout = {
type = "Box",
props = { className = "test" },
children = {}
}
}
local errors = component_schema.validate_component(valid_component)
expect(#errors).toBe(0)
end)
it("should validate nested layout structure", function()
local valid_component = {
id = "test_component",
type = "TestComponent",
layout = {
type = "Box",
children = {
{
type = "Card",
children = {
{
type = "CardHeader",
props = { text = "Title" }
}
}
}
}
}
}
local errors = component_schema.validate_component(valid_component)
expect(#errors).toBe(0)
end)
it("should fail when layout type is missing", function()
local invalid_component = {
id = "test_component",
type = "TestComponent",
layout = {
props = { className = "test" }
}
}
local errors = component_schema.validate_component(invalid_component)
expect(#errors).toBeGreaterThan(0)
end)
it("should validate components array", function()
local valid_components = {
{
id = "component_1",
type = "Component1"
},
{
id = "component_2",
type = "Component2"
}
}
local valid, errors = component_schema.validate_components(valid_components)
expect(valid).toBe(true)
expect(#errors).toBe(0)
end)
it("should validate empty components array", function()
local valid_components = {}
local valid, errors = component_schema.validate_components(valid_components)
expect(valid).toBe(true)
expect(#errors).toBe(0)
end)
it("should fail when components is not an array", function()
local invalid_components = "not an array"
local valid, errors = component_schema.validate_components(invalid_components)
expect(valid).toBe(false)
expect(#errors).toBeGreaterThan(0)
end)
it("should validate component with bindings", function()
local valid_component = {
id = "test_component",
type = "TestComponent",
bindings = {
dbal = true,
browser = false
}
}
local errors = component_schema.validate_component(valid_component)
expect(#errors).toBe(0)
end)
end)

View File

@@ -0,0 +1,103 @@
-- Lua validator tests
local lua_validator = require("lua_validator")
describe("Lua Validator", function()
it("should validate correct Lua syntax", function()
local valid_lua = [[
local M = {}
function M.test()
return true
end
return M
]]
local valid, errors = lua_validator.validate_lua_syntax("test.lua", valid_lua)
expect(valid).toBe(true)
expect(#errors).toBe(0)
end)
it("should detect Lua syntax errors", function()
local invalid_lua = [[
local M = {}
function M.test(
-- Missing closing parenthesis
end
return M
]]
local valid, errors = lua_validator.validate_lua_syntax("test.lua", invalid_lua)
expect(valid).toBe(false)
expect(#errors).toBeGreaterThan(0)
end)
it("should warn about missing module pattern", function()
local no_module = [[
function test()
return true
end
]]
local warnings = lua_validator.validate_lua_structure("test.lua", no_module)
expect(#warnings).toBeGreaterThan(0)
end)
it("should warn about missing return statement", function()
local no_return = [[
local M = {}
function M.test()
return true
end
]]
local warnings = lua_validator.validate_lua_structure("test.lua", no_return)
expect(#warnings).toBeGreaterThan(0)
end)
it("should validate test file structure", function()
local valid_test = [[
describe("Test Suite", function()
it("should pass", function()
expect(true).toBe(true)
end)
end)
]]
local errors, warnings = lua_validator.validate_test_file("test.test.lua", valid_test)
expect(#errors).toBe(0)
end)
it("should warn about missing test structure", function()
local no_tests = [[
local M = {}
return M
]]
local errors, warnings = lua_validator.validate_test_file("test.test.lua", no_tests)
expect(#warnings).toBeGreaterThan(0)
end)
it("should extract require statements", function()
local lua_with_requires = [[
local foo = require("foo")
local bar = require("bar")
return {}
]]
local requires, errors = lua_validator.validate_lua_requires("test.lua", lua_with_requires)
expect(#requires).toBe(2)
expect(requires[1]).toBe("foo")
expect(requires[2]).toBe("bar")
end)
it("should check for code quality issues", function()
local code_with_issues = [[
-- TODO: Fix this
function globalFunction()
print("Debug message")
end
]]
local warnings = lua_validator.check_lua_quality("test.lua", code_with_issues)
expect(#warnings).toBeGreaterThan(0)
end)
end)

View File

@@ -0,0 +1,117 @@
-- Metadata validation tests for schema_validator package
local metadata_schema = require("metadata_schema")
describe("Metadata Schema Validation", function()
it("should validate a complete valid metadata", function()
local valid_metadata = {
packageId = "test_package",
name = "Test Package",
version = "1.0.0",
description = "A test package",
author = "MetaBuilder",
category = "test",
dependencies = {},
exports = {
components = {},
scripts = {}
},
minLevel = 1
}
local valid, errors = metadata_schema.validate_metadata(valid_metadata)
expect(valid).toBe(true)
expect(#errors).toBe(0)
end)
it("should fail when packageId is missing", function()
local invalid_metadata = {
name = "Test Package",
version = "1.0.0",
description = "A test package",
author = "MetaBuilder",
category = "test"
}
local valid, errors = metadata_schema.validate_metadata(invalid_metadata)
expect(valid).toBe(false)
expect(#errors).toBeGreaterThan(0)
end)
it("should fail when packageId has uppercase letters", function()
local invalid_metadata = {
packageId = "TestPackage",
name = "Test Package",
version = "1.0.0",
description = "A test package",
author = "MetaBuilder",
category = "test"
}
local valid, errors = metadata_schema.validate_metadata(invalid_metadata)
expect(valid).toBe(false)
end)
it("should fail when version is not semantic", function()
local invalid_metadata = {
packageId = "test_package",
name = "Test Package",
version = "1.0",
description = "A test package",
author = "MetaBuilder",
category = "test"
}
local valid, errors = metadata_schema.validate_metadata(invalid_metadata)
expect(valid).toBe(false)
end)
it("should fail when minLevel is out of range", function()
local invalid_metadata = {
packageId = "test_package",
name = "Test Package",
version = "1.0.0",
description = "A test package",
author = "MetaBuilder",
category = "test",
minLevel = 10
}
local valid, errors = metadata_schema.validate_metadata(invalid_metadata)
expect(valid).toBe(false)
end)
it("should validate optional bindings field", function()
local valid_metadata = {
packageId = "test_package",
name = "Test Package",
version = "1.0.0",
description = "A test package",
author = "MetaBuilder",
category = "test",
bindings = {
dbal = true,
browser = false
}
}
local valid, errors = metadata_schema.validate_metadata(valid_metadata)
expect(valid).toBe(true)
end)
it("should fail when bindings.dbal is not boolean", function()
local invalid_metadata = {
packageId = "test_package",
name = "Test Package",
version = "1.0.0",
description = "A test package",
author = "MetaBuilder",
category = "test",
bindings = {
dbal = "yes"
}
}
local valid, errors = metadata_schema.validate_metadata(invalid_metadata)
expect(valid).toBe(false)
end)
end)

View File

@@ -0,0 +1,115 @@
-- Structure validator tests
local structure_validator = require("structure_validator")
describe("Structure Validator", function()
it("should validate naming conventions for packageId", function()
local metadata = {
packageId = "test_package",
name = "Test Package",
version = "1.0.0",
description = "Test",
author = "Test",
category = "test"
}
local errors, warnings = structure_validator.validate_naming_conventions("packages/test_package", metadata)
expect(#errors).toBe(0)
end)
it("should fail when directory name doesn't match packageId", function()
local metadata = {
packageId = "test_package",
name = "Test Package",
version = "1.0.0",
description = "Test",
author = "Test",
category = "test"
}
local errors, warnings = structure_validator.validate_naming_conventions("packages/wrong_name", metadata)
expect(#errors).toBeGreaterThan(0)
end)
it("should warn about invalid script naming", function()
local metadata = {
packageId = "test_package",
name = "Test Package",
version = "1.0.0",
description = "Test",
author = "Test",
category = "test",
exports = {
scripts = {"ValidScript"} -- Should be lowercase
}
}
local errors, warnings = structure_validator.validate_naming_conventions("packages/test_package", metadata)
expect(#warnings).toBeGreaterThan(0)
end)
it("should accept valid component naming conventions", function()
local metadata = {
packageId = "test_package",
name = "Test Package",
version = "1.0.0",
description = "Test",
author = "Test",
category = "test",
exports = {
components = {"TestComponent", "test_component"}
}
}
local errors, warnings = structure_validator.validate_naming_conventions("packages/test_package", metadata)
-- Component names are valid
expect(#errors).toBe(0)
end)
it("should validate scripts structure when exports.scripts exists", function()
local metadata = {
packageId = "test_package",
name = "Test Package",
version = "1.0.0",
description = "Test",
author = "Test",
category = "test",
exports = {
scripts = {"init", "validate"}
}
}
local errors, warnings = structure_validator.validate_scripts_structure("packages/test_package/seed", metadata)
-- May have warnings about missing files, but structure is validated
expect(errors).toBeTruthy()
end)
it("should validate icon path", function()
local metadata = {
packageId = "test_package",
name = "Test Package",
version = "1.0.0",
description = "Test",
author = "Test",
category = "test",
icon = "static_content/icon.svg"
}
local errors, warnings = structure_validator.validate_static_content("packages/test_package/seed", metadata)
-- Will check if file exists
expect(errors).toBeTruthy()
end)
it("should warn when no icon is defined", function()
local metadata = {
packageId = "test_package",
name = "Test Package",
version = "1.0.0",
description = "Test",
author = "Test",
category = "test"
}
local errors, warnings = structure_validator.validate_static_content("packages/test_package/seed", metadata)
expect(#warnings).toBeGreaterThan(0)
end)
end)

View File

@@ -0,0 +1,71 @@
-- Integration tests for schema validation
local validate = require("validate")
describe("Schema Validator Integration", function()
it("should format validation results for valid package", function()
local results = {
valid = true,
errors = {},
warnings = {}
}
local formatted = validate.format_results(results)
expect(formatted).toContain("✓ Validation passed")
end)
it("should format validation results with errors", function()
local results = {
valid = false,
errors = {
"Missing required field: packageId",
"Invalid version format"
},
warnings = {}
}
local formatted = validate.format_results(results)
expect(formatted).toContain("✗ Validation failed")
expect(formatted).toContain("Errors:")
expect(formatted).toContain("Missing required field: packageId")
end)
it("should format validation results with warnings", function()
local results = {
valid = true,
errors = {},
warnings = {
"components.json not found (optional)"
}
}
local formatted = validate.format_results(results)
expect(formatted).toContain("✓ Validation passed")
expect(formatted).toContain("Warnings:")
end)
it("should validate metadata only", function()
local metadata = {
packageId = "schema_validator",
name = "Schema Validator",
version = "1.0.0",
description = "Validates JSON schemas",
author = "MetaBuilder",
category = "tools"
}
local valid, errors = validate.validate_metadata_only(metadata)
expect(valid).toBe(true)
end)
it("should validate components only", function()
local components = {
{
id = "test_comp",
type = "TestComponent"
}
}
local valid, errors = validate.validate_components_only(components)
expect(valid).toBe(true)
end)
end)

View File

@@ -0,0 +1,132 @@
-- 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")
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
return M

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
<rect width="64" height="64" fill="#4F46E5" rx="8"/>
<path d="M32 12L44 20V36L32 44L20 36V20L32 12Z" fill="none" stroke="white" stroke-width="3"/>
<path d="M26 28L30 32L38 24" fill="none" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="32" cy="32" r="2" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 413 B