moved folders around

This commit is contained in:
Boki 2025-06-21 18:27:00 -04:00
parent 4f89affc2b
commit 36cb84b343
202 changed files with 1160 additions and 660 deletions

View file

@ -0,0 +1,183 @@
import { z } from 'zod';
/**
* Secret value wrapper to prevent accidental logging
*/
export class SecretValue<T = string> {
private readonly value: T;
private readonly masked: string;
constructor(value: T, mask: string = '***') {
this.value = value;
this.masked = mask;
}
/**
* Get the actual secret value
* @param reason - Required reason for accessing the secret
*/
reveal(reason: string): T {
if (!reason) {
throw new Error('Reason required for revealing secret value');
}
return this.value;
}
/**
* Get masked representation
*/
toString(): string {
return this.masked;
}
/**
* Prevent JSON serialization of actual value
*/
toJSON(): string {
return this.masked;
}
/**
* Check if value matches without revealing it
*/
equals(other: T): boolean {
return this.value === other;
}
/**
* Transform the secret value
*/
map<R>(fn: (value: T) => R, reason: string): SecretValue<R> {
return new SecretValue(fn(this.reveal(reason)));
}
}
/**
* Zod schema for secret values
*/
export const secretSchema = <T extends z.ZodTypeAny>(_schema: T) => {
return z.custom<SecretValue<z.infer<T>>>(
(val) => val instanceof SecretValue,
{
message: 'Expected SecretValue instance',
}
);
};
/**
* Transform string to SecretValue in Zod schema
*/
export const secretStringSchema = z
.string()
.transform((val) => new SecretValue(val));
/**
* Create a secret value
*/
export function secret<T = string>(value: T, mask?: string): SecretValue<T> {
return new SecretValue(value, mask);
}
/**
* Check if a value is a secret
*/
export function isSecret(value: unknown): value is SecretValue {
return value instanceof SecretValue;
}
/**
* Redact secrets from an object
*/
export function redactSecrets<T extends Record<string, any>>(
obj: T,
secretPaths: string[] = []
): T {
const result = { ...obj };
// Redact known secret paths
for (const path of secretPaths) {
const keys = path.split('.');
let current: any = result;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
if (key && current[key] && typeof current[key] === 'object') {
current = current[key];
} else {
break;
}
}
const lastKey = keys[keys.length - 1];
if (current && lastKey && lastKey in current) {
current[lastKey] = '***REDACTED***';
}
}
// Recursively redact SecretValue instances
function redactSecretValues(obj: any): any {
if (obj === null || obj === undefined) {
return obj;
}
if (isSecret(obj)) {
return obj.toString();
}
if (Array.isArray(obj)) {
return obj.map(redactSecretValues);
}
if (typeof obj === 'object') {
const result: any = {};
for (const [key, value] of Object.entries(obj)) {
result[key] = redactSecretValues(value);
}
return result;
}
return obj;
}
return redactSecretValues(result);
}
/**
* Environment variable names that should be treated as secrets
*/
export const COMMON_SECRET_PATTERNS = [
/password/i,
/secret/i,
/key/i,
/token/i,
/credential/i,
/private/i,
/auth/i,
/api[-_]?key/i,
];
/**
* Check if an environment variable name indicates a secret
*/
export function isSecretEnvVar(name: string): boolean {
return COMMON_SECRET_PATTERNS.some(pattern => pattern.test(name));
}
/**
* Wrap environment variables that look like secrets
*/
export function wrapSecretEnvVars(
env: Record<string, string | undefined>
): Record<string, string | SecretValue | undefined> {
const result: Record<string, string | SecretValue | undefined> = {};
for (const [key, value] of Object.entries(env)) {
if (value !== undefined && isSecretEnvVar(key)) {
result[key] = new SecretValue(value, `***${key}***`);
} else {
result[key] = value;
}
}
return result;
}

View file

@ -0,0 +1,195 @@
import { z } from 'zod';
export interface ValidationResult {
valid: boolean;
errors?: Array<{
path: string;
message: string;
expected?: string;
received?: string;
}>;
warnings?: Array<{
path: string;
message: string;
}>;
}
/**
* Validate configuration against a schema
*/
export function validateConfig<T>(
config: unknown,
schema: z.ZodSchema<T>
): ValidationResult {
try {
schema.parse(config);
return { valid: true };
} catch (error) {
if (error instanceof z.ZodError) {
const errors = error.errors.map(err => ({
path: err.path.join('.'),
message: err.message,
expected: 'expected' in err ? String(err.expected) : undefined,
received: 'received' in err ? String(err.received) : undefined,
}));
return { valid: false, errors };
}
throw error;
}
}
/**
* Check for deprecated configuration options
*/
export function checkDeprecations(
config: Record<string, unknown>,
deprecations: Record<string, string>
): ValidationResult['warnings'] {
const warnings: ValidationResult['warnings'] = [];
function checkObject(obj: Record<string, unknown>, path: string[] = []): void {
for (const [key, value] of Object.entries(obj)) {
const currentPath = [...path, key];
const pathStr = currentPath.join('.');
if (pathStr in deprecations) {
const deprecationMessage = deprecations[pathStr];
if (deprecationMessage) {
warnings?.push({
path: pathStr,
message: deprecationMessage,
});
}
}
if (value && typeof value === 'object' && !Array.isArray(value)) {
checkObject(value as Record<string, unknown>, currentPath);
}
}
}
checkObject(config);
return warnings;
}
/**
* Check for required environment variables
*/
export function checkRequiredEnvVars(
required: string[]
): ValidationResult {
const errors: ValidationResult['errors'] = [];
for (const envVar of required) {
if (!process.env[envVar]) {
errors.push({
path: `env.${envVar}`,
message: `Required environment variable ${envVar} is not set`,
});
}
}
return {
valid: errors.length === 0,
errors: errors.length > 0 ? errors : undefined,
};
}
/**
* Validate configuration completeness
*/
export function validateCompleteness(
config: Record<string, any>,
required: string[]
): ValidationResult {
const errors: ValidationResult['errors'] = [];
for (const path of required) {
const keys = path.split('.');
let current: any = config;
let found = true;
for (const key of keys) {
if (current && typeof current === 'object' && key in current) {
current = current[key];
} else {
found = false;
break;
}
}
if (!found || current === undefined || current === null) {
errors.push({
path,
message: `Required configuration value is missing`,
});
}
}
return {
valid: errors.length === 0,
errors: errors.length > 0 ? errors : undefined,
};
}
/**
* Format validation result for display
*/
export function formatValidationResult(result: ValidationResult): string {
const lines: string[] = [];
if (result.valid) {
lines.push('✅ Configuration is valid');
} else {
lines.push('❌ Configuration validation failed');
}
if (result.errors && result.errors.length > 0) {
lines.push('\nErrors:');
for (const error of result.errors) {
lines.push(` - ${error.path}: ${error.message}`);
if (error.expected && error.received) {
lines.push(` Expected: ${error.expected}, Received: ${error.received}`);
}
}
}
if (result.warnings && result.warnings.length > 0) {
lines.push('\nWarnings:');
for (const warning of result.warnings) {
lines.push(` - ${warning.path}: ${warning.message}`);
}
}
return lines.join('\n');
}
/**
* Create a strict schema that doesn't allow extra properties
*/
export function createStrictSchema<T extends z.ZodRawShape>(
shape: T
): z.ZodObject<T, 'strict'> {
return z.object(shape).strict();
}
/**
* Merge multiple schemas
*/
export function mergeSchemas<T extends z.ZodSchema[]>(
...schemas: T
): z.ZodIntersection<T[0], T[1]> {
if (schemas.length < 2) {
throw new Error('At least two schemas required for merge');
}
let result = schemas[0]!.and(schemas[1]!);
for (let i = 2; i < schemas.length; i++) {
result = result.and(schemas[i]!) as any;
}
return result as any;
}