env-doctor

API Reference

Use env-doctor programmatically in your Node.js applications.

Installation

npm install @theaccessibleteam/env-doctor

Quick Start

import { analyze, loadConfig } from '@theaccessibleteam/env-doctor';

const { config } = await loadConfig();
const result = await analyze({ config });

console.log(`Found ${result.issues.length} issues`);

Core Functions

analyze(options)

Main analysis function that orchestrates all scanners and analyzers.

import { analyze } from '@theaccessibleteam/env-doctor';

const result = await analyze({
  config: {
    envFiles: ['.env'],
    include: ['src/**/*.ts'],
    framework: 'nextjs'
  },
  verbose: true
});

Parameters

Parameter Type Description
options.config EnvDoctorConfig Configuration object
options.verbose boolean Enable debug logging

Returns

Promise<AnalysisResult>

interface AnalysisResult {
  issues: Issue[];
  definedVariables: EnvVariable[];
  usedVariables: EnvUsage[];
  templateVariables?: EnvVariable[];
  framework: string;
  stats: ScanStats;
}

loadConfig(configPath?, rootDir?)

Load configuration from file or use defaults.

import { loadConfig } from '@theaccessibleteam/env-doctor';

// Load from default locations
const { config, configPath } = await loadConfig();

// Load from specific file
const { config } = await loadConfig('./custom-config.js');

// Load with custom root directory
const { config } = await loadConfig(undefined, '/path/to/project');

Returns

{
  config: EnvDoctorConfig;
  configPath?: string;  // Path if config was found
}

Scanners

parseEnvFile(filePath, rootDir?)

Parse a single .env file.

import { parseEnvFile } from '@theaccessibleteam/env-doctor';

const result = await parseEnvFile('.env', process.cwd());

for (const variable of result.variables) {
  console.log(`${variable.name}=${variable.isSecret ? '[REDACTED]' : variable.value}`);
}

Returns

interface ParseResult {
  variables: EnvVariable[];
  errors: Array<{ line: number; message: string }>;
}

interface EnvVariable {
  name: string;
  value: string;
  line: number;
  file: string;
  isSecret?: boolean;
}

scanCode(options)

Scan source files for process.env usage.

import { scanCode } from '@theaccessibleteam/env-doctor';

const result = await scanCode({
  rootDir: process.cwd(),
  include: ['src/**/*.ts'],
  exclude: ['node_modules'],
  framework: 'nextjs'
});

console.log(`Found ${result.usages.length} env usages`);

Returns

interface CodeScanResult {
  usages: EnvUsage[];
  errors: Array<{ file: string; message: string }>;
  filesScanned: number;
}

interface EnvUsage {
  name: string;
  file: string;
  line: number;
  column: number;
  accessPattern: 'direct' | 'bracket' | 'destructure' | 'dynamic';
  inferredType?: 'string' | 'number' | 'boolean' | 'json' | 'array';
  isClientSide?: boolean;
}

scanGitHistory(options)

Scan git history for leaked secrets.

import { scanGitHistory } from '@theaccessibleteam/env-doctor';

const { results, error } = await scanGitHistory({
  rootDir: process.cwd(),
  depth: 100
});

if (results.length > 0) {
  console.warn('Found secrets in git history!');
}

Analyzers

analyzeMissing(options)

Find variables used but not defined.

import { analyzeMissing } from '@theaccessibleteam/env-doctor';

const issues = analyzeMissing({
  definedVariables: envVars,
  usedVariables: codeUsages,
  config
});

analyzeUnused(options)

Find variables defined but not used.

import { analyzeUnused } from '@theaccessibleteam/env-doctor';

const issues = analyzeUnused({
  definedVariables: envVars,
  usedVariables: codeUsages,
  config,
  framework: 'nextjs'
});

analyzeTypeMismatch(options)

Find type mismatches between usage and values.

import { analyzeTypeMismatch } from '@theaccessibleteam/env-doctor';

const issues = analyzeTypeMismatch({
  definedVariables: envVars,
  usedVariables: codeUsages,
  config
});

analyzeSecrets(options)

Detect exposed secrets.

import { analyzeSecrets } from '@theaccessibleteam/env-doctor';

const issues = analyzeSecrets({
  variables: envVars,
  customPatterns: [/^MY_SECRET/],
  ignorePatterns: ['TEST_*']
});

analyzeSyncDrift(options)

Check sync between env and template files.

import { analyzeSyncDrift } from '@theaccessibleteam/env-doctor';

const result = analyzeSyncDrift({
  envVariables: envVars,
  templateVariables: templateVars,
  templateFile: '.env.example'
});

console.log(`In sync: ${result.inSync}`);

Reporters

reportToConsole(result, options?)

Output results to console with formatting.

import { analyze, reportToConsole } from '@theaccessibleteam/env-doctor';

const result = await analyze({ config });
reportToConsole(result, { verbose: true });

reportToJSON(result)

Get results as formatted JSON string.

import { analyze, reportToJSON } from '@theaccessibleteam/env-doctor';

const result = await analyze({ config });
const json = reportToJSON(result);
fs.writeFileSync('report.json', json);

reportToSARIF(result)

Get results in SARIF format for GitHub.

import { analyze, reportToSARIF } from '@theaccessibleteam/env-doctor';

const result = await analyze({ config });
const sarif = reportToSARIF(result);
fs.writeFileSync('results.sarif', sarif);

Framework Detection

detectFramework(rootDir)

Auto-detect the framework used.

import { detectFramework } from '@theaccessibleteam/env-doctor';

const framework = await detectFramework(process.cwd());
// Returns: 'nextjs' | 'vite' | 'cra' | 'node'

getFrameworkInfo(framework)

Get detailed framework information.

import { getFrameworkInfo } from '@theaccessibleteam/env-doctor';

const info = getFrameworkInfo('nextjs');
// {
//   name: 'nextjs',
//   displayName: 'Next.js',
//   clientPrefix: ['NEXT_PUBLIC_'],
//   ...
// }

Types

EnvDoctorConfig

interface EnvDoctorConfig {
  envFiles: string[];
  templateFile?: string;
  include: string[];
  exclude: string[];
  framework: 'auto' | 'nextjs' | 'vite' | 'cra' | 'node';
  variables: Record<string, VariableRule>;
  ignore: string[];
  strict?: boolean;
  secretPatterns?: RegExp[];
  root?: string;
}

VariableRule

interface VariableRule {
  required?: boolean;
  secret?: boolean;
  type?: 'string' | 'number' | 'boolean' | 'json' | 'url' | 'email';
  pattern?: RegExp;
  default?: string | number | boolean;
  enum?: string[];
  description?: string;
}

Issue

interface Issue {
  type: IssueType;
  severity: 'error' | 'warning' | 'info';
  variable: string;
  message: string;
  location?: SourceLocation;
  fix?: string;
  context?: Record<string, unknown>;
}

type IssueType = 
  | 'missing'
  | 'unused'
  | 'type-mismatch'
  | 'sync-drift'
  | 'secret-exposed'
  | 'invalid-value'
  | 'dynamic-access';