diff --git a/packages/audit_log/seed/metadata.json b/packages/audit_log/seed/metadata.json index 5a90fb9eb..8dcfe57ff 100644 --- a/packages/audit_log/seed/metadata.json +++ b/packages/audit_log/seed/metadata.json @@ -7,6 +7,9 @@ "author": "MetaBuilder", "category": "admin", "dependencies": [], + "devDependencies": [ + "package_validator" + ], "exports": { "components": [ "AuditLogViewer", diff --git a/packages/index.json b/packages/index.json index d27b7675c..31a1ec214 100644 --- a/packages/index.json +++ b/packages/index.json @@ -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 } ] } \ No newline at end of file diff --git a/packages/package_validator/README.md b/packages/package_validator/README.md new file mode 100644 index 000000000..cca7b064d --- /dev/null +++ b/packages/package_validator/README.md @@ -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. diff --git a/packages/package_validator/examples/github-actions.yml b/packages/package_validator/examples/github-actions.yml new file mode 100644 index 000000000..66056a9c0 --- /dev/null +++ b/packages/package_validator/examples/github-actions.yml @@ -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.' + }) diff --git a/packages/package_validator/examples/package.json.example b/packages/package_validator/examples/package.json.example new file mode 100644 index 000000000..af83bd193 --- /dev/null +++ b/packages/package_validator/examples/package.json.example @@ -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" + ] + } +} diff --git a/packages/package_validator/examples/pre-commit-hook.sh b/packages/package_validator/examples/pre-commit-hook.sh new file mode 100644 index 000000000..50d30b189 --- /dev/null +++ b/packages/package_validator/examples/pre-commit-hook.sh @@ -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 diff --git a/packages/package_validator/examples/validate-all.sh b/packages/package_validator/examples/validate-all.sh new file mode 100644 index 000000000..bfb98ebaa --- /dev/null +++ b/packages/package_validator/examples/validate-all.sh @@ -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 diff --git a/packages/package_validator/examples/validate_audit_log.lua b/packages/package_validator/examples/validate_audit_log.lua new file mode 100644 index 000000000..4a1a0c9a3 --- /dev/null +++ b/packages/package_validator/examples/validate_audit_log.lua @@ -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 diff --git a/packages/package_validator/seed/components.json b/packages/package_validator/seed/components.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/packages/package_validator/seed/components.json @@ -0,0 +1 @@ +[] diff --git a/packages/package_validator/seed/metadata.json b/packages/package_validator/seed/metadata.json new file mode 100644 index 000000000..c31fc1ae1 --- /dev/null +++ b/packages/package_validator/seed/metadata.json @@ -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 +} diff --git a/packages/package_validator/seed/scripts/cli.lua b/packages/package_validator/seed/scripts/cli.lua new file mode 100644 index 000000000..b5bd55443 --- /dev/null +++ b/packages/package_validator/seed/scripts/cli.lua @@ -0,0 +1,172 @@ +-- CLI tool for validating packages +-- Usage: lua cli.lua [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 [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 diff --git a/packages/package_validator/seed/scripts/component_schema.lua b/packages/package_validator/seed/scripts/component_schema.lua new file mode 100644 index 000000000..c5c678ac7 --- /dev/null +++ b/packages/package_validator/seed/scripts/component_schema.lua @@ -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 diff --git a/packages/package_validator/seed/scripts/init.lua b/packages/package_validator/seed/scripts/init.lua new file mode 100644 index 000000000..da6cb2ca7 --- /dev/null +++ b/packages/package_validator/seed/scripts/init.lua @@ -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 diff --git a/packages/package_validator/seed/scripts/lua_validator.lua b/packages/package_validator/seed/scripts/lua_validator.lua new file mode 100644 index 000000000..e68c5bd36 --- /dev/null +++ b/packages/package_validator/seed/scripts/lua_validator.lua @@ -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 diff --git a/packages/package_validator/seed/scripts/metadata_schema.lua b/packages/package_validator/seed/scripts/metadata_schema.lua new file mode 100644 index 000000000..c28e01a33 --- /dev/null +++ b/packages/package_validator/seed/scripts/metadata_schema.lua @@ -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 diff --git a/packages/package_validator/seed/scripts/structure_validator.lua b/packages/package_validator/seed/scripts/structure_validator.lua new file mode 100644 index 000000000..3a94b5c19 --- /dev/null +++ b/packages/package_validator/seed/scripts/structure_validator.lua @@ -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 diff --git a/packages/package_validator/seed/scripts/tests/component.test.lua b/packages/package_validator/seed/scripts/tests/component.test.lua new file mode 100644 index 000000000..8251513b4 --- /dev/null +++ b/packages/package_validator/seed/scripts/tests/component.test.lua @@ -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) diff --git a/packages/package_validator/seed/scripts/tests/lua_validator.test.lua b/packages/package_validator/seed/scripts/tests/lua_validator.test.lua new file mode 100644 index 000000000..84dbbe3d0 --- /dev/null +++ b/packages/package_validator/seed/scripts/tests/lua_validator.test.lua @@ -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) diff --git a/packages/package_validator/seed/scripts/tests/metadata.test.lua b/packages/package_validator/seed/scripts/tests/metadata.test.lua new file mode 100644 index 000000000..f3c1c0198 --- /dev/null +++ b/packages/package_validator/seed/scripts/tests/metadata.test.lua @@ -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) diff --git a/packages/package_validator/seed/scripts/tests/structure_validator.test.lua b/packages/package_validator/seed/scripts/tests/structure_validator.test.lua new file mode 100644 index 000000000..dafdf870d --- /dev/null +++ b/packages/package_validator/seed/scripts/tests/structure_validator.test.lua @@ -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) diff --git a/packages/package_validator/seed/scripts/tests/validate.test.lua b/packages/package_validator/seed/scripts/tests/validate.test.lua new file mode 100644 index 000000000..836da9367 --- /dev/null +++ b/packages/package_validator/seed/scripts/tests/validate.test.lua @@ -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) diff --git a/packages/package_validator/seed/scripts/validate.lua b/packages/package_validator/seed/scripts/validate.lua new file mode 100644 index 000000000..3d6d8c0e7 --- /dev/null +++ b/packages/package_validator/seed/scripts/validate.lua @@ -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 diff --git a/packages/package_validator/static_content/icon.svg b/packages/package_validator/static_content/icon.svg new file mode 100644 index 000000000..c1f713ea5 --- /dev/null +++ b/packages/package_validator/static_content/icon.svg @@ -0,0 +1,6 @@ + + + + + +