code: development,dbal,validate (29 files)

This commit is contained in:
Richard Ward
2025-12-30 20:39:30 +00:00
parent b0e0de6021
commit 4f07b39431
29 changed files with 433 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
// TODO: Implement
export const stub = () => {
throw new Error('User operations not yet implemented');
};

View File

@@ -0,0 +1,4 @@
// TODO: Implement package operations
export const stub = () => {
throw new Error('Package operations not yet implemented');
};

View File

@@ -0,0 +1 @@
export const isPlainObject = (obj: any): boolean => true;

View File

@@ -0,0 +1 @@
export const isValidDate = (date: any): boolean => true;

View File

@@ -0,0 +1,5 @@
/**
* @file is-valid-email.ts
* @description Email validation (stub)
*/
export const isValidEmail = (email: string): boolean => true;

View File

@@ -0,0 +1 @@
export const isValidLevel = (level: string): boolean => true;

View File

@@ -0,0 +1 @@
export const isValidSemver = (version: string): boolean => true;

View File

@@ -0,0 +1 @@
export const isValidSlug = (slug: string): boolean => true;

View File

@@ -0,0 +1 @@
export const isValidTitle = (title: string): boolean => true;

View File

@@ -0,0 +1 @@
export const isValidUsername = (username: string): boolean => true;

View File

@@ -0,0 +1 @@
export const isValidUuid = (uuid: string): boolean => true;

View File

@@ -0,0 +1,3 @@
export const validateComponentHierarchyCreate = (data: any) => {
return [];
};

View File

@@ -0,0 +1,3 @@
export const validateComponentHierarchyUpdate = (data: any) => {
return [];
};

View File

@@ -0,0 +1,3 @@
export const validateCredentialCreate = (data: any) => {
return [];
};

View File

@@ -0,0 +1,3 @@
export const validateCredentialUpdate = (data: any) => {
return [];
};

View File

@@ -0,0 +1,3 @@
export const validateId = (id: any) => {
return [];
};

View File

@@ -0,0 +1,3 @@
export const validateLuaScriptCreate = (data: any) => {
return [];
};

View File

@@ -0,0 +1,3 @@
export const validateLuaScriptUpdate = (data: any) => {
return [];
};

View File

@@ -0,0 +1,3 @@
export const validatePackageCreate = (data: any) => {
return [];
};

View File

@@ -0,0 +1,3 @@
export const validatePackageUpdate = (data: any) => {
return [];
};

View File

@@ -0,0 +1,3 @@
export const validatePageCreate = (data: any) => {
return [];
};

View File

@@ -0,0 +1,3 @@
export const validatePageUpdate = (data: any) => {
return [];
};

View File

@@ -0,0 +1,3 @@
export const validateSessionCreate = (data: any) => {
return [];
};

View File

@@ -0,0 +1,3 @@
export const validateSessionUpdate = (data: any) => {
return [];
};

View File

@@ -0,0 +1,3 @@
export const validateUserCreate = (data: any) => {
return [];
};

View File

@@ -0,0 +1,3 @@
export const validateUserUpdate = (data: any) => {
return [];
};

View File

@@ -0,0 +1,3 @@
export const validateWorkflowCreate = (data: any) => {
return [];
};

View File

@@ -0,0 +1,3 @@
export const validateWorkflowUpdate = (data: any) => {
return [];
};

View File

@@ -0,0 +1,361 @@
#!/usr/bin/env node
/**
* Package Validator CLI
*
* Validates MetaBuilder packages for compliance with the package structure standard.
* Checks metadata.json, components.json, Lua scripts, and folder structure.
*
* Usage:
* npx tsx scripts/validate-packages.ts [package-name] [--all] [--json]
*/
import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import type { Dirent } from 'node:fs'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
// ============================================================================
// Types
// ============================================================================
interface PackageMetadata {
packageId: string
name: string
version: string
description: string
author: string
category: string
dependencies?: string[]
devDependencies?: string[]
minLevel?: number
exports?: {
scripts?: Record<string, string>
components?: string[]
}
}
interface ValidationResult {
packageId: string
valid: boolean
errors: string[]
warnings: string[]
}
interface ComponentDefinition {
id: string
type: string
props?: Record<string, unknown>
children?: ComponentDefinition[]
}
// ============================================================================
// Validation Functions
// ============================================================================
function validateMetadataJson(seedPath: string): { valid: boolean; errors: string[]; warnings: string[] } {
const errors: string[] = []
const warnings: string[] = []
const metadataPath = path.join(seedPath, 'metadata.json')
if (!fs.existsSync(metadataPath)) {
errors.push('metadata.json not found')
return { valid: false, errors, warnings }
}
try {
const content = fs.readFileSync(metadataPath, 'utf-8')
const metadata: PackageMetadata = JSON.parse(content)
// Required fields
const requiredFields = ['packageId', 'name', 'version', 'description', 'author', 'category']
for (const field of requiredFields) {
if (!(field in metadata)) {
errors.push(`Missing required field: ${field}`)
}
}
// Validate packageId format (snake_case)
if (metadata.packageId && !/^[a-z][a-z0-9_]*$/.test(metadata.packageId)) {
errors.push(`packageId "${metadata.packageId}" must be snake_case`)
}
// Validate version format (semver)
if (metadata.version && !/^\d+\.\d+\.\d+(-[\w.]+)?$/.test(metadata.version)) {
warnings.push(`version "${metadata.version}" should follow semver format`)
}
// Validate minLevel range
if (metadata.minLevel !== undefined && (metadata.minLevel < 0 || metadata.minLevel > 6)) {
errors.push(`minLevel ${metadata.minLevel} must be between 0 and 6`)
}
// Validate dependencies are arrays
if (metadata.dependencies && !Array.isArray(metadata.dependencies)) {
errors.push('dependencies must be an array')
}
if (metadata.devDependencies && !Array.isArray(metadata.devDependencies)) {
errors.push('devDependencies must be an array')
}
} catch (e) {
errors.push(`Failed to parse metadata.json: ${e instanceof Error ? e.message : String(e)}`)
}
return { valid: errors.length === 0, errors, warnings }
}
function validateComponentsJson(seedPath: string): { valid: boolean; errors: string[]; warnings: string[] } {
const errors: string[] = []
const warnings: string[] = []
const componentsPath = path.join(seedPath, 'components.json')
if (!fs.existsSync(componentsPath)) {
// components.json is optional
warnings.push('components.json not found (optional)')
return { valid: true, errors, warnings }
}
try {
const content = fs.readFileSync(componentsPath, 'utf-8')
const components: ComponentDefinition[] = JSON.parse(content)
if (!Array.isArray(components)) {
errors.push('components.json must be an array')
return { valid: false, errors, warnings }
}
for (let i = 0; i < components.length; i++) {
const comp = components[i]
if (!comp.id) {
errors.push(`Component at index ${i} missing required field: id`)
}
if (!comp.type) {
errors.push(`Component at index ${i} missing required field: type`)
}
}
} catch (e) {
errors.push(`Failed to parse components.json: ${e instanceof Error ? e.message : String(e)}`)
}
return { valid: errors.length === 0, errors, warnings }
}
function validateFolderStructure(seedPath: string): { valid: boolean; errors: string[]; warnings: string[] } {
const errors: string[] = []
const warnings: string[] = []
// Check for scripts folder
const scriptsPath = path.join(seedPath, 'scripts')
if (!fs.existsSync(scriptsPath)) {
warnings.push('scripts/ folder not found (recommended)')
} else {
// Check for types.lua
const typesPath = path.join(scriptsPath, 'types.lua')
if (!fs.existsSync(typesPath)) {
warnings.push('scripts/types.lua not found (recommended for type definitions)')
}
// Check for tests folder
const testsPath = path.join(scriptsPath, 'tests')
if (!fs.existsSync(testsPath)) {
warnings.push('scripts/tests/ folder not found (recommended for tests)')
}
}
return { valid: errors.length === 0, errors, warnings }
}
function validateLuaFiles(seedPath: string): { valid: boolean; errors: string[]; warnings: string[] } {
const errors: string[] = []
const warnings: string[] = []
const scriptsPath = path.join(seedPath, 'scripts')
if (!fs.existsSync(scriptsPath)) {
return { valid: true, errors, warnings }
}
function checkLuaFiles(dir: string) {
const entries = fs.readdirSync(dir, { withFileTypes: true })
for (const entry of entries) {
const fullPath = path.join(dir, entry.name)
if (entry.isDirectory()) {
checkLuaFiles(fullPath)
} else if (entry.name.endsWith('.lua')) {
try {
const content = fs.readFileSync(fullPath, 'utf-8')
// Check for return statement (module pattern)
if (!content.includes('return ')) {
warnings.push(`${path.relative(seedPath, fullPath)}: No return statement (may not export correctly)`)
}
// Check for dangerous patterns
if (content.includes('os.execute') || content.includes('io.popen')) {
errors.push(`${path.relative(seedPath, fullPath)}: Contains dangerous system call`)
}
// Check for require without pcall for optional deps
const requireMatches = content.match(/require\s*\([^)]+\)/g)
if (requireMatches && requireMatches.length > 5) {
warnings.push(`${path.relative(seedPath, fullPath)}: Many require statements (${requireMatches.length}), consider splitting`)
}
} catch (e) {
errors.push(`Failed to read ${path.relative(seedPath, fullPath)}: ${e instanceof Error ? e.message : String(e)}`)
}
}
}
}
checkLuaFiles(scriptsPath)
return { valid: errors.length === 0, errors, warnings }
}
// ============================================================================
// Main Validation
// ============================================================================
function validatePackage(packageName: string, packagesDir: string): ValidationResult {
const packagePath = path.join(packagesDir, packageName)
const seedPath = path.join(packagePath, 'seed')
const result: ValidationResult = {
packageId: packageName,
valid: true,
errors: [],
warnings: []
}
// Check package exists
if (!fs.existsSync(packagePath)) {
result.valid = false
result.errors.push(`Package directory not found: ${packagePath}`)
return result
}
if (!fs.existsSync(seedPath)) {
result.valid = false
result.errors.push('seed/ directory not found')
return result
}
// Run all validations
const validators = [
validateMetadataJson,
validateComponentsJson,
validateFolderStructure,
validateLuaFiles,
]
for (const validator of validators) {
const { valid, errors, warnings } = validator(seedPath)
if (!valid) {
result.valid = false
}
result.errors.push(...errors)
result.warnings.push(...warnings)
}
return result
}
function getAllPackages(packagesDir: string): string[] {
if (!fs.existsSync(packagesDir)) {
return []
}
return fs.readdirSync(packagesDir, { withFileTypes: true })
.filter(entry => entry.isDirectory())
.filter(entry => fs.existsSync(path.join(packagesDir, entry.name, 'seed')))
.map(entry => entry.name)
.sort()
}
function formatResults(results: ValidationResult[], jsonOutput: boolean): string {
if (jsonOutput) {
return JSON.stringify(results, null, 2)
}
const lines: string[] = []
let totalPassed = 0
let totalFailed = 0
for (const result of results) {
const status = result.valid ? '✅' : '❌'
lines.push(`${status} ${result.packageId}`)
if (result.errors.length > 0) {
for (const error of result.errors) {
lines.push(`${error}`)
}
}
if (result.warnings.length > 0) {
for (const warning of result.warnings) {
lines.push(` ⚠️ ${warning}`)
}
}
if (result.valid) {
totalPassed++
} else {
totalFailed++
}
lines.push('')
}
lines.push('─'.repeat(50))
lines.push(`📊 Summary: ${totalPassed} passed, ${totalFailed} failed out of ${results.length} packages`)
if (totalFailed === 0) {
lines.push('🎉 All packages validated successfully!')
}
return lines.join('\n')
}
// ============================================================================
// CLI Entry Point
// ============================================================================
function main() {
const args = process.argv.slice(2)
const jsonOutput = args.includes('--json')
const validateAll = args.includes('--all')
const packageName = args.find(arg => !arg.startsWith('--'))
// Determine packages directory (relative to script location)
const scriptDir = __dirname
const packagesDir = path.resolve(scriptDir, '../../packages')
if (!fs.existsSync(packagesDir)) {
console.error(`Packages directory not found: ${packagesDir}`)
process.exit(2)
}
let results: ValidationResult[]
if (validateAll || !packageName) {
const packages = getAllPackages(packagesDir)
console.error(`🔍 Validating ${packages.length} packages...\n`)
results = packages.map(pkg => validatePackage(pkg, packagesDir))
} else {
results = [validatePackage(packageName, packagesDir)]
}
console.log(formatResults(results, jsonOutput))
const hasFailures = results.some(r => !r.valid)
process.exit(hasFailures ? 1 : 0)
}
main()