diff --git a/dbal/development/src/core/entities/operations/core/user$file b/dbal/development/src/core/entities/operations/core/user$file new file mode 100644 index 000000000..f317cfe13 --- /dev/null +++ b/dbal/development/src/core/entities/operations/core/user$file @@ -0,0 +1,4 @@ +// TODO: Implement +export const stub = () => { + throw new Error('User operations not yet implemented'); +}; diff --git a/dbal/development/src/core/entities/operations/system/package$file b/dbal/development/src/core/entities/operations/system/package$file new file mode 100644 index 000000000..d91c8b02e --- /dev/null +++ b/dbal/development/src/core/entities/operations/system/package$file @@ -0,0 +1,4 @@ +// TODO: Implement package operations +export const stub = () => { + throw new Error('Package operations not yet implemented'); +}; diff --git a/dbal/development/src/core/foundation/validation/is-plain-object.ts b/dbal/development/src/core/foundation/validation/is-plain-object.ts new file mode 100644 index 000000000..aa355d73a --- /dev/null +++ b/dbal/development/src/core/foundation/validation/is-plain-object.ts @@ -0,0 +1 @@ +export const isPlainObject = (obj: any): boolean => true; diff --git a/dbal/development/src/core/foundation/validation/is-valid-date.ts b/dbal/development/src/core/foundation/validation/is-valid-date.ts new file mode 100644 index 000000000..d271972f8 --- /dev/null +++ b/dbal/development/src/core/foundation/validation/is-valid-date.ts @@ -0,0 +1 @@ +export const isValidDate = (date: any): boolean => true; diff --git a/dbal/development/src/core/foundation/validation/is-valid-email.ts b/dbal/development/src/core/foundation/validation/is-valid-email.ts new file mode 100644 index 000000000..9ac3f3378 --- /dev/null +++ b/dbal/development/src/core/foundation/validation/is-valid-email.ts @@ -0,0 +1,5 @@ +/** + * @file is-valid-email.ts + * @description Email validation (stub) + */ +export const isValidEmail = (email: string): boolean => true; diff --git a/dbal/development/src/core/foundation/validation/is-valid-level.ts b/dbal/development/src/core/foundation/validation/is-valid-level.ts new file mode 100644 index 000000000..d3774117e --- /dev/null +++ b/dbal/development/src/core/foundation/validation/is-valid-level.ts @@ -0,0 +1 @@ +export const isValidLevel = (level: string): boolean => true; diff --git a/dbal/development/src/core/foundation/validation/is-valid-semver.ts b/dbal/development/src/core/foundation/validation/is-valid-semver.ts new file mode 100644 index 000000000..d6b867af2 --- /dev/null +++ b/dbal/development/src/core/foundation/validation/is-valid-semver.ts @@ -0,0 +1 @@ +export const isValidSemver = (version: string): boolean => true; diff --git a/dbal/development/src/core/foundation/validation/is-valid-slug.ts b/dbal/development/src/core/foundation/validation/is-valid-slug.ts new file mode 100644 index 000000000..1714efb7b --- /dev/null +++ b/dbal/development/src/core/foundation/validation/is-valid-slug.ts @@ -0,0 +1 @@ +export const isValidSlug = (slug: string): boolean => true; diff --git a/dbal/development/src/core/foundation/validation/is-valid-title.ts b/dbal/development/src/core/foundation/validation/is-valid-title.ts new file mode 100644 index 000000000..68ad18d76 --- /dev/null +++ b/dbal/development/src/core/foundation/validation/is-valid-title.ts @@ -0,0 +1 @@ +export const isValidTitle = (title: string): boolean => true; diff --git a/dbal/development/src/core/foundation/validation/is-valid-username.ts b/dbal/development/src/core/foundation/validation/is-valid-username.ts new file mode 100644 index 000000000..197535981 --- /dev/null +++ b/dbal/development/src/core/foundation/validation/is-valid-username.ts @@ -0,0 +1 @@ +export const isValidUsername = (username: string): boolean => true; diff --git a/dbal/development/src/core/foundation/validation/is-valid-uuid.ts b/dbal/development/src/core/foundation/validation/is-valid-uuid.ts new file mode 100644 index 000000000..db23f28b9 --- /dev/null +++ b/dbal/development/src/core/foundation/validation/is-valid-uuid.ts @@ -0,0 +1 @@ +export const isValidUuid = (uuid: string): boolean => true; diff --git a/dbal/development/src/core/foundation/validation/validate-component-hierarchy-create.ts b/dbal/development/src/core/foundation/validation/validate-component-hierarchy-create.ts new file mode 100644 index 000000000..0d2c35719 --- /dev/null +++ b/dbal/development/src/core/foundation/validation/validate-component-hierarchy-create.ts @@ -0,0 +1,3 @@ +export const validateComponentHierarchyCreate = (data: any) => { + return []; +}; diff --git a/dbal/development/src/core/foundation/validation/validate-component-hierarchy-update.ts b/dbal/development/src/core/foundation/validation/validate-component-hierarchy-update.ts new file mode 100644 index 000000000..7fe9353b1 --- /dev/null +++ b/dbal/development/src/core/foundation/validation/validate-component-hierarchy-update.ts @@ -0,0 +1,3 @@ +export const validateComponentHierarchyUpdate = (data: any) => { + return []; +}; diff --git a/dbal/development/src/core/foundation/validation/validate-credential-create.ts b/dbal/development/src/core/foundation/validation/validate-credential-create.ts new file mode 100644 index 000000000..514093c75 --- /dev/null +++ b/dbal/development/src/core/foundation/validation/validate-credential-create.ts @@ -0,0 +1,3 @@ +export const validateCredentialCreate = (data: any) => { + return []; +}; diff --git a/dbal/development/src/core/foundation/validation/validate-credential-update.ts b/dbal/development/src/core/foundation/validation/validate-credential-update.ts new file mode 100644 index 000000000..6166ce0e8 --- /dev/null +++ b/dbal/development/src/core/foundation/validation/validate-credential-update.ts @@ -0,0 +1,3 @@ +export const validateCredentialUpdate = (data: any) => { + return []; +}; diff --git a/dbal/development/src/core/foundation/validation/validate-id.ts b/dbal/development/src/core/foundation/validation/validate-id.ts new file mode 100644 index 000000000..179ed8997 --- /dev/null +++ b/dbal/development/src/core/foundation/validation/validate-id.ts @@ -0,0 +1,3 @@ +export const validateId = (id: any) => { + return []; +}; diff --git a/dbal/development/src/core/foundation/validation/validate-lua-script-create.ts b/dbal/development/src/core/foundation/validation/validate-lua-script-create.ts new file mode 100644 index 000000000..d409bae5d --- /dev/null +++ b/dbal/development/src/core/foundation/validation/validate-lua-script-create.ts @@ -0,0 +1,3 @@ +export const validateLuaScriptCreate = (data: any) => { + return []; +}; diff --git a/dbal/development/src/core/foundation/validation/validate-lua-script-update.ts b/dbal/development/src/core/foundation/validation/validate-lua-script-update.ts new file mode 100644 index 000000000..a12823dbb --- /dev/null +++ b/dbal/development/src/core/foundation/validation/validate-lua-script-update.ts @@ -0,0 +1,3 @@ +export const validateLuaScriptUpdate = (data: any) => { + return []; +}; diff --git a/dbal/development/src/core/foundation/validation/validate-package-create.ts b/dbal/development/src/core/foundation/validation/validate-package-create.ts new file mode 100644 index 000000000..a505cc522 --- /dev/null +++ b/dbal/development/src/core/foundation/validation/validate-package-create.ts @@ -0,0 +1,3 @@ +export const validatePackageCreate = (data: any) => { + return []; +}; diff --git a/dbal/development/src/core/foundation/validation/validate-package-update.ts b/dbal/development/src/core/foundation/validation/validate-package-update.ts new file mode 100644 index 000000000..2362a7390 --- /dev/null +++ b/dbal/development/src/core/foundation/validation/validate-package-update.ts @@ -0,0 +1,3 @@ +export const validatePackageUpdate = (data: any) => { + return []; +}; diff --git a/dbal/development/src/core/foundation/validation/validate-page-create.ts b/dbal/development/src/core/foundation/validation/validate-page-create.ts new file mode 100644 index 000000000..abe1cea20 --- /dev/null +++ b/dbal/development/src/core/foundation/validation/validate-page-create.ts @@ -0,0 +1,3 @@ +export const validatePageCreate = (data: any) => { + return []; +}; diff --git a/dbal/development/src/core/foundation/validation/validate-page-update.ts b/dbal/development/src/core/foundation/validation/validate-page-update.ts new file mode 100644 index 000000000..8a14535cc --- /dev/null +++ b/dbal/development/src/core/foundation/validation/validate-page-update.ts @@ -0,0 +1,3 @@ +export const validatePageUpdate = (data: any) => { + return []; +}; diff --git a/dbal/development/src/core/foundation/validation/validate-session-create.ts b/dbal/development/src/core/foundation/validation/validate-session-create.ts new file mode 100644 index 000000000..1042aae86 --- /dev/null +++ b/dbal/development/src/core/foundation/validation/validate-session-create.ts @@ -0,0 +1,3 @@ +export const validateSessionCreate = (data: any) => { + return []; +}; diff --git a/dbal/development/src/core/foundation/validation/validate-session-update.ts b/dbal/development/src/core/foundation/validation/validate-session-update.ts new file mode 100644 index 000000000..78a19da51 --- /dev/null +++ b/dbal/development/src/core/foundation/validation/validate-session-update.ts @@ -0,0 +1,3 @@ +export const validateSessionUpdate = (data: any) => { + return []; +}; diff --git a/dbal/development/src/core/foundation/validation/validate-user-create.ts b/dbal/development/src/core/foundation/validation/validate-user-create.ts new file mode 100644 index 000000000..493dcf34c --- /dev/null +++ b/dbal/development/src/core/foundation/validation/validate-user-create.ts @@ -0,0 +1,3 @@ +export const validateUserCreate = (data: any) => { + return []; +}; diff --git a/dbal/development/src/core/foundation/validation/validate-user-update.ts b/dbal/development/src/core/foundation/validation/validate-user-update.ts new file mode 100644 index 000000000..7e330f156 --- /dev/null +++ b/dbal/development/src/core/foundation/validation/validate-user-update.ts @@ -0,0 +1,3 @@ +export const validateUserUpdate = (data: any) => { + return []; +}; diff --git a/dbal/development/src/core/foundation/validation/validate-workflow-create.ts b/dbal/development/src/core/foundation/validation/validate-workflow-create.ts new file mode 100644 index 000000000..4ea46050e --- /dev/null +++ b/dbal/development/src/core/foundation/validation/validate-workflow-create.ts @@ -0,0 +1,3 @@ +export const validateWorkflowCreate = (data: any) => { + return []; +}; diff --git a/dbal/development/src/core/foundation/validation/validate-workflow-update.ts b/dbal/development/src/core/foundation/validation/validate-workflow-update.ts new file mode 100644 index 000000000..a40f58ce2 --- /dev/null +++ b/dbal/development/src/core/foundation/validation/validate-workflow-update.ts @@ -0,0 +1,3 @@ +export const validateWorkflowUpdate = (data: any) => { + return []; +}; diff --git a/frontends/nextjs/scripts/validate-packages.ts b/frontends/nextjs/scripts/validate-packages.ts new file mode 100644 index 000000000..0f6a41f07 --- /dev/null +++ b/frontends/nextjs/scripts/validate-packages.ts @@ -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 + components?: string[] + } +} + +interface ValidationResult { + packageId: string + valid: boolean + errors: string[] + warnings: string[] +} + +interface ComponentDefinition { + id: string + type: string + props?: Record + 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()