fix: resolve CI test failures in schema-validator
Some checks failed
CI / test (push) Has been cancelled

- 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
This commit is contained in:
2026-01-31 20:47:05 +00:00
parent f1f5785d5a
commit 3ecfbff745

View File

@@ -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<string, string>,
schema: EnvVariableSchema
): ValidationResult {
const errors: ValidationError[] = [];
const cleanedEnv: Record<string, string | number | boolean> = {};
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 };