{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import fs from 'fs'\nimport path from 'path'\n\ntype EnvSchema = {\n  name: string\n  constraints: {\n    optional: boolean\n    number: boolean\n  }\n}[]\n\ntype EnvVariable = {\n  name: string\n  value: string | undefined\n}\n\ntype EnvVariables = EnvVariable[]\n\ntype dotenvErrorsCode = 'empty' | 'invalid_type' | 'file_not_found' | 'missing' | 'duplicate' | 'not_in_schema'\n\ntype dotenvErrors = {\n  code?: dotenvErrorsCode\n  variable?: string\n  expected?: string\n}\n\ntype dotenvResult = {\n  errors: dotenvErrors[]\n  success: boolean\n}\nexport const validateEnv = (config?: {\n  dotenvPath?: string\n  schemaPath?: string\n}): dotenvResult => {\n  const appRoot = process.cwd()\n  let envFile: string\n  let envSchemaFile: string\n\n  try {\n    envFile = fs.readFileSync(\n      path.join(appRoot, config?.dotenvPath ?? '.env'),\n      'utf8'\n    )\n    envSchemaFile = fs.readFileSync(\n      path.join(appRoot, config?.schemaPath ?? '.env.schema'),\n      'utf8'\n    )\n  } catch (error) {\n    return {\n      errors: [\n        {\n          code: 'file_not_found',\n        },\n      ],\n      success: false,\n    }\n  }\n\n  const envVariables: EnvVariables = extractEnvVariables(envFile)\n  const envSchema = extractEnvSchema(envSchemaFile)\n\n  const duplicateErrors = checkDuplicates(envVariables)\n  const undeclaredErrors = checkUndeclared(envVariables, envSchema)\n  const constraintErrors = checkConstraints(envVariables, envSchema)\n\n  const allErrors = [...duplicateErrors, ...undeclaredErrors, ...constraintErrors]\n\n  return {\n    errors: allErrors,\n    success: allErrors.length === 0,\n  }\n}\n\nconst extractEnvVariables = (dotenvFile: string) => {\n  const envVariables: EnvVariables = []\n  for (const line of dotenvFile.split('\\n')) {\n    const trimmedLine = line.trim()\n    // Ignore empty lines and comments\n    if (!trimmedLine || trimmedLine.startsWith('#')) {\n      continue\n    }\n    if (line.includes('=')) {\n      const [name, value] = line.split('=')\n      envVariables.push({ name, value })\n    }\n  }\n  return envVariables\n}\n\nconst extractEnvSchema = (schemaFile: string) => {\n  const schema: EnvSchema = []\n  for (const line of schemaFile.split('\\n')) {\n    if (line.includes('=')) {\n      const name = line.split('=')[0]\n      const optional = line.includes('#optional')\n      const number = line.includes('#number')\n      schema.push({ name, constraints: { optional, number } })\n    }\n  }\n  return schema\n}\n\nconst checkDuplicates = (envVariables: EnvVariables) => {\n  const errors: dotenvErrors[] = []\n  const seen = new Set<string>()\n  const duplicates = new Set<string>()\n\n  for (const { name } of envVariables) {\n    if (seen.has(name)) {\n      duplicates.add(name)\n    }\n    seen.add(name)\n  }\n\n  for (const name of duplicates) {\n    errors.push({\n      variable: name,\n      code: 'duplicate',\n    })\n  }\n\n  return errors\n}\n\nconst checkUndeclared = (envVariables: EnvVariables, envSchema: EnvSchema) => {\n  const errors: dotenvErrors[] = []\n  const schemaVariables = new Set(envSchema.map((s) => s.name))\n\n  for (const { name } of envVariables) {\n    if (!schemaVariables.has(name)) {\n      errors.push({\n        variable: name,\n        code: 'not_in_schema',\n      })\n    }\n  }\n\n  return errors\n}\n\nconst checkConstraints = (envVariables: EnvVariables, envSchema: EnvSchema) => {\n  const errors: dotenvErrors[] = []\n  for (const { name, constraints } of envSchema) {\n    const envVariable = envVariables.find((v) => v.name === name)\n    if (!envVariable && !constraints.optional) {\n      errors.push({\n        variable: name,\n        code: 'missing',\n      })\n    } else if (envVariable) {\n      if (constraints.number) {\n        const result = isNumber(envVariable)\n        if (result !== true) {\n          errors.push(result)\n        }\n      }\n      if (!constraints.optional) {\n        const result = isRequired(envVariable)\n        if (result !== true) {\n          errors.push(result)\n        }\n      }\n    }\n  }\n  return errors\n}\n\nconst isRequired = (envVariable: EnvVariable): dotenvErrors | true => {\n  if (!envVariable.value) {\n    return {\n      variable: envVariable.name,\n      code: 'empty',\n    }\n  }\n  return true\n}\n\nconst isNumber = (envVariable: EnvVariable): dotenvErrors | true => {\n  if (envVariable.value && isNaN(Number(envVariable.value))) {\n    return {\n      variable: envVariable.name,\n      code: 'invalid_type',\n      expected: 'number',\n    }\n  }\n  return true\n}\n\nexport default {\n  validateEnv,\n}\n"],"mappings":"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","names":["fs","path"]}