From 3ecfbff745d691f11f24557bc9f2e1f2b0a82cae Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Sat, 31 Jan 2026 20:47:05 +0000 Subject: [PATCH] fix: resolve CI test failures in schema-validator - Created missing schema-validator.ts file - Added enum type validation with options support - Added 'yes'/'no' boolean value handling - Fixed port type detection to take priority over number --- src/core/schema-validator.ts | 149 +++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 src/core/schema-validator.ts diff --git a/src/core/schema-validator.ts b/src/core/schema-validator.ts new file mode 100644 index 0000000..6b1f22c --- /dev/null +++ b/src/core/schema-validator.ts @@ -0,0 +1,149 @@ +import { EnvVariableSchema, EnvType, ValidationResult, ValidationError } from './types'; + +function inferTypeFromValue(value: string): EnvType { + if (value === '') { + return 'string'; + } + + const lowerValue = value.toLowerCase(); + if (lowerValue === 'true' || lowerValue === 'false' || value === '1' || value === '0' || lowerValue === 'yes' || lowerValue === 'no') { + return 'boolean'; + } + + const port = Number(value); + if (!isNaN(port) && port >= 1 && port <= 65535 && value.trim() !== '') { + return 'port'; + } + + const num = Number(value); + if (!isNaN(num) && value.trim() !== '') { + return 'number'; + } + + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (emailRegex.test(value)) { + return 'email'; + } + + try { + new URL(value); + return 'url'; + } catch { + // Not a URL + } + + try { + JSON.parse(value); + return 'json'; + } catch { + // Not JSON + } + + return 'string'; +} + +function validateType(value: string, type: EnvType, options?: string[]): void { + switch (type) { + case 'number': + if (isNaN(Number(value))) { + throw new Error(`Expected number, got: ${value}`); + } + break; + case 'boolean': + if (!['true', 'false', '1', '0', 'yes', 'no'].includes(value.toLowerCase())) { + throw new Error(`Expected boolean, got: ${value}`); + } + break; + case 'port': { + const port = Number(value); + if (isNaN(port) || port < 1 || port > 65535) { + throw new Error(`Expected port number, got: ${value}`); + } + break; + } + case 'email': { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(value)) { + throw new Error(`Expected email, got: ${value}`); + } + break; + } + case 'url': + try { + new URL(value); + } catch { + throw new Error(`Expected URL, got: ${value}`); + } + break; + case 'json': + try { + JSON.parse(value); + } catch { + throw new Error(`Expected JSON, got: ${value}`); + } + break; + case 'enum': + if (options && !options.includes(value)) { + throw new Error(`Expected one of: ${options.join(', ')}, got: ${value}`); + } + break; + } +} + +function cleanBooleanValue(value: string): boolean { + const lowerValue = value.toLowerCase(); + if (lowerValue === 'true' || value === '1' || lowerValue === 'yes') { + return true; + } + if (lowerValue === 'false' || value === '0' || lowerValue === 'no') { + return false; + } + return false; +} + +export function validateEnvVariables( + variables: Record, + schema: EnvVariableSchema +): ValidationResult { + const errors: ValidationError[] = []; + const cleanedEnv: Record = {}; + + for (const [key, config] of Object.entries(schema)) { + const value = variables[key]; + + if (config.required !== false && (value === undefined || value === '')) { + errors.push({ + field: key, + message: `Required variable "${key}" is missing` + }); + continue; + } + + if (value !== undefined && value !== '') { + try { + validateType(value, config.type, config.options); + if (config.type === 'boolean') { + cleanedEnv[key] = cleanBooleanValue(value); + } else if (config.type === 'number' || config.type === 'port') { + cleanedEnv[key] = Number(value); + } else { + cleanedEnv[key] = value; + } + } catch (e) { + errors.push({ + field: key, + message: (e as Error).message, + value + }); + } + } + } + + return { + isValid: errors.length === 0, + errors, + cleanedEnv + }; +} + +export { inferTypeFromValue };