mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 22:04:56 +00:00
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:
@@ -7,6 +7,9 @@
|
||||
"author": "MetaBuilder",
|
||||
"category": "admin",
|
||||
"dependencies": [],
|
||||
"devDependencies": [
|
||||
"package_validator"
|
||||
],
|
||||
"exports": {
|
||||
"components": [
|
||||
"AuditLogViewer",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
380
packages/package_validator/README.md
Normal file
380
packages/package_validator/README.md
Normal 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.
|
||||
75
packages/package_validator/examples/github-actions.yml
Normal file
75
packages/package_validator/examples/github-actions.yml
Normal 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.'
|
||||
})
|
||||
27
packages/package_validator/examples/package.json.example
Normal file
27
packages/package_validator/examples/package.json.example
Normal 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"
|
||||
]
|
||||
}
|
||||
}
|
||||
45
packages/package_validator/examples/pre-commit-hook.sh
Normal file
45
packages/package_validator/examples/pre-commit-hook.sh
Normal 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
|
||||
61
packages/package_validator/examples/validate-all.sh
Normal file
61
packages/package_validator/examples/validate-all.sh
Normal 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
|
||||
94
packages/package_validator/examples/validate_audit_log.lua
Normal file
94
packages/package_validator/examples/validate_audit_log.lua
Normal 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
|
||||
1
packages/package_validator/seed/components.json
Normal file
1
packages/package_validator/seed/components.json
Normal file
@@ -0,0 +1 @@
|
||||
[]
|
||||
22
packages/package_validator/seed/metadata.json
Normal file
22
packages/package_validator/seed/metadata.json
Normal 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
|
||||
}
|
||||
172
packages/package_validator/seed/scripts/cli.lua
Normal file
172
packages/package_validator/seed/scripts/cli.lua
Normal 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
|
||||
126
packages/package_validator/seed/scripts/component_schema.lua
Normal file
126
packages/package_validator/seed/scripts/component_schema.lua
Normal 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
|
||||
31
packages/package_validator/seed/scripts/init.lua
Normal file
31
packages/package_validator/seed/scripts/init.lua
Normal 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
|
||||
146
packages/package_validator/seed/scripts/lua_validator.lua
Normal file
146
packages/package_validator/seed/scripts/lua_validator.lua
Normal 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
|
||||
126
packages/package_validator/seed/scripts/metadata_schema.lua
Normal file
126
packages/package_validator/seed/scripts/metadata_schema.lua
Normal 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
|
||||
215
packages/package_validator/seed/scripts/structure_validator.lua
Normal file
215
packages/package_validator/seed/scripts/structure_validator.lua
Normal 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
|
||||
149
packages/package_validator/seed/scripts/tests/component.test.lua
Normal file
149
packages/package_validator/seed/scripts/tests/component.test.lua
Normal 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)
|
||||
@@ -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)
|
||||
117
packages/package_validator/seed/scripts/tests/metadata.test.lua
Normal file
117
packages/package_validator/seed/scripts/tests/metadata.test.lua
Normal 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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
132
packages/package_validator/seed/scripts/validate.lua
Normal file
132
packages/package_validator/seed/scripts/validate.lua
Normal 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
|
||||
6
packages/package_validator/static_content/icon.svg
Normal file
6
packages/package_validator/static_content/icon.svg
Normal 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 |
Reference in New Issue
Block a user