diff --git a/.changeset/gentle-sheep-feel.md b/.changeset/gentle-sheep-feel.md new file mode 100644 index 0000000..0b515c7 --- /dev/null +++ b/.changeset/gentle-sheep-feel.md @@ -0,0 +1,5 @@ +--- +'dotenv-diff': minor +--- + +added --list-all flag to view all environment variables found in codebase diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9254a8c..4767420 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -56,4 +56,5 @@ jobs: commit: 'chore(release): publish to npm' title: 'chore(release): publish to npm' env: - GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} \ No newline at end of file + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} + NPM_CONFIG_PROVENANCE: true \ No newline at end of file diff --git a/.github/workflows/test-coverage.yml b/.github/workflows/test-coverage.yml index 835a198..db225dc 100644 --- a/.github/workflows/test-coverage.yml +++ b/.github/workflows/test-coverage.yml @@ -33,14 +33,39 @@ jobs: run: pip install diff-cover - name: Run coverage - run: pnpm run coverage + run: pnpm --filter dotenv-diff run coverage - - name: Fix lcov paths + - name: Locate and normalize coverage files + id: coverage_files run: | - if [ -f "coverage/lcov.info" ]; then - sed -i "s|^SF:|SF:|g" coverage/lcov.info + LCOV_FILE="coverage/lcov.info" + SUMMARY_FILE="coverage/coverage-summary.json" + + if [ -f "packages/cli/coverage/lcov.info" ]; then + LCOV_FILE="packages/cli/coverage/lcov.info" + fi + + if [ -f "packages/cli/coverage/coverage-summary.json" ]; then + SUMMARY_FILE="packages/cli/coverage/coverage-summary.json" + fi + + if [ ! -f "$LCOV_FILE" ]; then + echo "Missing lcov file at $LCOV_FILE" + exit 1 fi + if [ ! -f "$SUMMARY_FILE" ]; then + echo "Missing coverage summary at $SUMMARY_FILE" + exit 1 + fi + + # Normalize source-file paths for monolith layout so diff-cover can + # map coverage entries to changed files under packages/cli. + sed -i -E 's|^SF:src/|SF:packages/cli/src/|; s|^SF:src\\|SF:packages/cli/src/|; s|^SF:packages\\cli\\src\\|SF:packages/cli/src/|' "$LCOV_FILE" + + echo "lcov_file=$LCOV_FILE" >> $GITHUB_OUTPUT + echo "summary_file=$SUMMARY_FILE" >> $GITHUB_OUTPUT + - name: Run diff-cover analysis id: diff_cover run: | @@ -51,11 +76,11 @@ jobs: all_passed=true overall_coverage="N/A" - if [ -f "coverage/coverage-summary.json" ]; then - overall_coverage=$(jq -r '.total.lines.pct' "coverage/coverage-summary.json") + if [ -f "${{ steps.coverage_files.outputs.summary_file }}" ]; then + overall_coverage=$(jq -r '.total.lines.pct' "${{ steps.coverage_files.outputs.summary_file }}") fi - diff-cover coverage/lcov.info \ + diff-cover "${{ steps.coverage_files.outputs.lcov_file }}" \ --compare-branch=origin/${{ github.event.pull_request.base.ref }} \ --diff-range-notation=... \ --json-report diff-coverage.json \ diff --git a/docs/configuration_and_flags.md b/docs/configuration_and_flags.md index bc40281..8ff4730 100644 --- a/docs/configuration_and_flags.md +++ b/docs/configuration_and_flags.md @@ -28,6 +28,7 @@ CLI flags always take precedence over configuration file values. ### Display Options +- [--list-all](#--list-all) - [--show-unused](#--show-unused) - [--no-show-unused](#--no-show-unused) - [--show-stats](#--show-stats) @@ -437,6 +438,30 @@ If you later want to scan files from one of the default excluded paths, use `--i ## Display Options +### `--list-all` + +Scans the codebase and prints all unique environment variable names found. + +This is useful when you want a quick overview of every environment variable your project references. + +The list is sorted alphabetically and deduplicated across all usages. + +Example usage: + +```bash +dotenv-diff --list-all +``` + +Usage in the configuration file: + +```json +{ + "listAll": true +} +``` + +--- + ### `--show-unused` List variables that are defined in `.env` but not used in the codebase (enabled by default). diff --git a/package.json b/package.json index 95cc303..37fcc15 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "@types/prompts": "^2.4.9", "@typescript-eslint/eslint-plugin": "^8.59.1", "@typescript-eslint/parser": "^8.59.1", - "@vitest/coverage-v8": "4.1.4", + "@vitest/coverage-v8": "4.1.5", "eslint": "^10.2.1", "husky": "^9.1.7", "lint-staged": "^16.4.0", diff --git a/packages/cli/src/cli/program.ts b/packages/cli/src/cli/program.ts index 1a3a910..615887a 100644 --- a/packages/cli/src/cli/program.ts +++ b/packages/cli/src/cli/program.ts @@ -92,5 +92,9 @@ export function createProgram() { 'Disable inconsistent naming pattern warnings', ) .option('--init', 'Create a sample dotenv-diff.config.json file') + .option( + '--list-all', + 'List all unique environment variable keys found in codebase', + ) ); } diff --git a/packages/cli/src/cli/run.ts b/packages/cli/src/cli/run.ts index e131547..93d5b35 100644 --- a/packages/cli/src/cli/run.ts +++ b/packages/cli/src/cli/run.ts @@ -87,6 +87,7 @@ async function runScanMode(opts: Options): Promise { uppercaseKeys: opts.uppercaseKeys, expireWarnings: opts.expireWarnings, inconsistentNamingWarnings: opts.inconsistentNamingWarnings, + listAll: opts.listAll, }); return exitWithError; diff --git a/packages/cli/src/commands/scanUsage.ts b/packages/cli/src/commands/scanUsage.ts index 7117174..914387a 100644 --- a/packages/cli/src/commands/scanUsage.ts +++ b/packages/cli/src/commands/scanUsage.ts @@ -136,7 +136,11 @@ export async function scanUsage(opts: ScanUsageOptions): Promise { // JSON output if (opts.json) { - const jsonOutput = scanJsonOutput(scanResult, comparedAgainst); + const jsonOutput = scanJsonOutput( + scanResult, + comparedAgainst, + opts.listAll ?? false, + ); console.log(JSON.stringify(jsonOutput, null, 2)); // Check for high severity secrets diff --git a/packages/cli/src/config/options.ts b/packages/cli/src/config/options.ts index d729f32..473cb66 100644 --- a/packages/cli/src/config/options.ts +++ b/packages/cli/src/config/options.ts @@ -56,6 +56,7 @@ export function normalizeOptions(raw: RawOptions): Options { const uppercaseKeys = raw.uppercaseKeys !== false; const expireWarnings = raw.expireWarnings !== false; const inconsistentNamingWarnings = raw.inconsistentNamingWarnings !== false; + const listAll = toBool(raw.listAll); const cwd = process.cwd(); const envFlag = @@ -96,6 +97,7 @@ export function normalizeOptions(raw: RawOptions): Options { uppercaseKeys, expireWarnings, inconsistentNamingWarnings, + listAll, }; } diff --git a/packages/cli/src/config/types.ts b/packages/cli/src/config/types.ts index 17241cf..9251294 100644 --- a/packages/cli/src/config/types.ts +++ b/packages/cli/src/config/types.ts @@ -93,6 +93,7 @@ export interface RawOptions { uppercaseKeys?: boolean; expireWarnings?: boolean; inconsistentNamingWarnings?: boolean; + listAll?: boolean; } /** @@ -135,6 +136,7 @@ export interface Options { uppercaseKeys: boolean; expireWarnings: boolean; inconsistentNamingWarnings: boolean; + listAll: boolean; } export type EnvPatternName = 'process.env' | 'import.meta.env' | 'sveltekit'; @@ -228,6 +230,7 @@ export interface ScanUsageOptions extends ScanOptions { uppercaseKeys?: boolean; expireWarnings?: boolean; inconsistentNamingWarnings?: boolean; + listAll?: boolean; } /** diff --git a/packages/cli/src/services/printScanResult.ts b/packages/cli/src/services/printScanResult.ts index fc645df..7060953 100644 --- a/packages/cli/src/services/printScanResult.ts +++ b/packages/cli/src/services/printScanResult.ts @@ -27,6 +27,7 @@ import { computeHealthScore } from '../core/scan/computeHealthScore.js'; import { printHealthScore } from '../ui/scan/printHealthScore.js'; import { printExpireWarnings } from '../ui/scan/printExpireWarnings.js'; import { printInconsistentNamingWarning } from '../ui/scan/printInconsistentNamingWarning.js'; +import { printListAll } from '../ui/scan/printListAll.js'; /** * Prints the scan result to the console. @@ -46,6 +47,10 @@ export function printScanResult( // Determine if output should be in JSON format const isJson = opts.json; + if (opts.listAll) { + printListAll(scanResult.used); + } + printHeader(comparedAgainst); // Show stats if requested @@ -102,7 +107,7 @@ export function printScanResult( printSecrets(scanResult.secrets, opts.strict); } // Console log usage warning - if (scanResult.logged) { + if (scanResult.logged?.length) { printConsolelogWarning(scanResult.logged, opts.strict); } diff --git a/packages/cli/src/ui/scan/printListAll.ts b/packages/cli/src/ui/scan/printListAll.ts new file mode 100644 index 0000000..48a55cf --- /dev/null +++ b/packages/cli/src/ui/scan/printListAll.ts @@ -0,0 +1,27 @@ +import type { EnvUsage } from '../../config/types.js'; +import { accent, dim, header, divider } from '../theme.js'; + +/** + * Prints all unique environment variable names found in the codebase scan. + * @param usages - All environment variable usages found during the scan. + */ +export function printListAll(usages: EnvUsage[]): void { + const uniqueVars = [...new Set(usages.map((u) => u.variable))].sort(); + + if (uniqueVars.length === 0) { + console.log(dim('\nNo environment variables found in codebase.\n')); + return; + } + + console.log( + `\n${accent('▸')} ${header('Environment variables found in codebase')}`, + ); + console.log(`${divider}`); + + for (const key of uniqueVars) { + console.log(`${accent(key)}`); + } + + console.log(`${divider}`); + console.log(dim(`${uniqueVars.length} unique variable(s)\n`)); +} diff --git a/packages/cli/src/ui/scan/scanJsonOutput.ts b/packages/cli/src/ui/scan/scanJsonOutput.ts index 79640bb..a8b8a05 100644 --- a/packages/cli/src/ui/scan/scanJsonOutput.ts +++ b/packages/cli/src/ui/scan/scanJsonOutput.ts @@ -17,6 +17,7 @@ import { normalizePath } from '../../core/helpers/normalizePath.js'; */ interface ScanJsonOutput { stats?: ScanStats; + listAll?: string[]; missing?: Array<{ variable: string; usages: Array<{ @@ -64,9 +65,16 @@ interface ScanJsonOutput { export function scanJsonOutput( scanResult: ScanResult, comparedAgainst: string, + listAll: boolean = false, ): ScanJsonOutput { const output: ScanJsonOutput = {}; + if (listAll) { + output.listAll = [ + ...new Set(scanResult.used.map((usage) => usage.variable)), + ].sort(); + } + // Add comparison info if we compared against a file if (comparedAgainst) { output.comparedAgainst = comparedAgainst; diff --git a/packages/cli/test/unit/cli/run.test.ts b/packages/cli/test/unit/cli/run.test.ts index af19023..a24134f 100644 --- a/packages/cli/test/unit/cli/run.test.ts +++ b/packages/cli/test/unit/cli/run.test.ts @@ -86,6 +86,7 @@ function createBaseOptions(overrides: Partial = {}): Options { uppercaseKeys: true, expireWarnings: true, inconsistentNamingWarnings: true, + listAll: false, ...overrides, }; } diff --git a/packages/cli/test/unit/services/printScanResult.test.ts b/packages/cli/test/unit/services/printScanResult.test.ts index 4445974..2d5b8a4 100644 --- a/packages/cli/test/unit/services/printScanResult.test.ts +++ b/packages/cli/test/unit/services/printScanResult.test.ts @@ -68,6 +68,10 @@ vi.mock('../../../src/ui/scan/printInconsistentNamingWarning.js', () => ({ printInconsistentNamingWarning: vi.fn(), })); +vi.mock('../../../src/ui/scan/printListAll.js', () => ({ + printListAll: vi.fn(), +})); + vi.mock('../../../src/core/scan/computeHealthScore.js', () => ({ computeHealthScore: vi.fn(() => 100), })); @@ -95,10 +99,20 @@ import { printUnused } from '../../../src/ui/scan/printUnused.js'; import { printFrameworkWarnings } from '../../../src/ui/scan/printFrameworkWarnings.js'; import { printUppercaseWarning } from '../../../src/ui/scan/printUppercaseWarning.js'; import { printInconsistentNamingWarning } from '../../../src/ui/scan/printInconsistentNamingWarning.js'; +import { printListAll } from '../../../src/ui/scan/printListAll.js'; import { printExampleWarnings } from '../../../src/ui/scan/printExampleWarnings.js'; import { printSecrets } from '../../../src/ui/scan/printSecrets.js'; import { printExpireWarnings } from '../../../src/ui/scan/printExpireWarnings.js'; import { printConsolelogWarning } from '../../../src/ui/scan/printConsolelogWarning.js'; +import type { SecretFinding } from '../../../src/core/security/secretDetectors.js'; +import type { + FrameworkWarning, + UppercaseWarning, + InconsistentNamingWarning, + ExampleSecretWarning, + ExpireWarning, +} from '../../../src/config/types.js'; +import { GITIGNORE_ISSUES } from '../../../src/config/constants.js'; describe('printScanResult', () => { const baseScanResult: ScanResult = { @@ -142,10 +156,18 @@ describe('printScanResult', () => { }); it('returns exitWithError true when high severity secret exists', () => { + const secret: SecretFinding = { + file: 'test.ts', + line: 1, + kind: 'pattern', + message: 'test', + snippet: 'test', + severity: 'high', + }; const result = printScanResult( { ...baseScanResult, - secrets: [{ severity: 'high' } as any], + secrets: [secret], }, baseOpts, '.env', @@ -186,8 +208,8 @@ describe('printScanResult', () => { it('prints gitignore warning when env file is not ignored', () => { vi.mocked(checkGitignoreStatus).mockReturnValue({ - reason: 'not-ignored', - } as any); + reason: GITIGNORE_ISSUES.NOT_IGNORED, + }); printScanResult(baseScanResult, baseOpts, '.env'); @@ -198,8 +220,8 @@ describe('printScanResult', () => { it('returns exitWithError true in strict mode when gitignore issue exists', () => { vi.mocked(checkGitignoreStatus).mockReturnValue({ - reason: 'not-ignored', - } as any); + reason: GITIGNORE_ISSUES.NOT_IGNORED, + }); const result = printScanResult( baseScanResult, @@ -211,10 +233,17 @@ describe('printScanResult', () => { }); it('prints framework warnings when present', () => { + const warning: FrameworkWarning = { + variable: 'API_KEY', + reason: 'exposed', + file: 'app.ts', + line: 1, + framework: 'nextjs', + }; printScanResult( { ...baseScanResult, - frameworkWarnings: [{ type: 'nextjs', message: 'warn' } as any], + frameworkWarnings: [warning], }, baseOpts, '.env', @@ -224,10 +253,14 @@ describe('printScanResult', () => { }); it('prints uppercase warnings when present', () => { + const warning: UppercaseWarning = { + key: 'Api_Key', + suggestion: 'API_KEY', + }; printScanResult( { ...baseScanResult, - uppercaseWarnings: [{ key: 'Api_Key', expected: 'API_KEY' } as any], + uppercaseWarnings: [warning], }, baseOpts, '.env', @@ -237,10 +270,15 @@ describe('printScanResult', () => { }); it('prints inconsistent naming warnings when present', () => { + const warning: InconsistentNamingWarning = { + key1: 'API_KEY', + key2: 'APIKEY', + suggestion: 'API_KEY', + }; printScanResult( { ...baseScanResult, - inconsistentNamingWarnings: [{ key: 'MY-KEY' } as any], + inconsistentNamingWarnings: [warning], }, baseOpts, '.env', @@ -250,10 +288,16 @@ describe('printScanResult', () => { }); it('prints example warnings when present', () => { + const warning: ExampleSecretWarning = { + key: 'SECRET_VAR', + value: 'suspicious_value', + reason: 'Entropy', + severity: 'low', + }; printScanResult( { ...baseScanResult, - exampleWarnings: [{ severity: 'low' } as any], + exampleWarnings: [warning], }, baseOpts, '.env', @@ -263,10 +307,18 @@ describe('printScanResult', () => { }); it('prints secrets when secrets flag is enabled', () => { + const secret: SecretFinding = { + file: 'test.ts', + line: 1, + kind: 'pattern', + message: 'test', + snippet: 'test', + severity: 'low', + }; printScanResult( { ...baseScanResult, - secrets: [{ severity: 'low' } as any], + secrets: [secret], }, { ...baseOpts, secrets: true }, '.env', @@ -276,10 +328,15 @@ describe('printScanResult', () => { }); it('prints expiration warnings when present', () => { + const warning: ExpireWarning = { + key: 'TOKEN', + date: '2026-12-31', + daysLeft: 5, + }; printScanResult( { ...baseScanResult, - expireWarnings: [{ key: 'TOKEN', daysLeft: 5 } as any], + expireWarnings: [warning], }, baseOpts, '.env', @@ -289,10 +346,16 @@ describe('printScanResult', () => { }); it('returns exitWithError true when high severity example warning exists', () => { + const warning: ExampleSecretWarning = { + key: 'DB_PASSWORD', + value: 'password123', + reason: 'Pattern', + severity: 'high', + }; const result = printScanResult( { ...baseScanResult, - exampleWarnings: [{ severity: 'high' } as any], + exampleWarnings: [warning], }, baseOpts, '.env', @@ -302,10 +365,15 @@ describe('printScanResult', () => { }); it('returns exitWithError true when expiration warning is urgent (<=7 days)', () => { + const warning: ExpireWarning = { + key: 'TOKEN', + date: '2026-03-10', + daysLeft: 7, + }; const result = printScanResult( { ...baseScanResult, - expireWarnings: [{ key: 'TOKEN', daysLeft: 7 } as any], + expireWarnings: [warning], }, baseOpts, '.env', @@ -333,7 +401,7 @@ describe('printScanResult', () => { printScanResult( { ...baseScanResult, - duplicates: undefined as any, + duplicates: {}, }, baseOpts, '', @@ -354,7 +422,7 @@ describe('printScanResult', () => { printScanResult( { ...baseScanResult, - logged: undefined as any, + logged: [], }, baseOpts, '.env', @@ -363,11 +431,38 @@ describe('printScanResult', () => { expect(printConsolelogWarning).not.toHaveBeenCalled(); }); + it('calls printConsolelogWarning when logged has items', () => { + const loggedUsages = [ + { + variable: 'SECRET_KEY', + file: 'src/logger.ts', + line: 42, + column: 5, + pattern: 'process.env' as const, + context: 'console.log(process.env.SECRET_KEY)', + }, + ]; + + printScanResult( + { + ...baseScanResult, + logged: loggedUsages, + }, + baseOpts, + '.env', + ); + + expect(printConsolelogWarning).toHaveBeenCalledWith( + loggedUsages, + undefined, + ); + }); + it('keeps exitWithError false when secrets is undefined and no strict violations', () => { const result = printScanResult( { ...baseScanResult, - secrets: undefined as any, + secrets: [], }, baseOpts, '.env', @@ -430,4 +525,26 @@ describe('printScanResult', () => { false, ); }); + + it('calls printListAll and returns early when listAll is true', () => { + const usages = [ + { + variable: 'API_KEY', + file: 'src/index.ts', + line: 1, + column: 1, + pattern: 'process.env' as const, + context: 'process.env.API_KEY', + }, + ]; + + const result = printScanResult( + { ...baseScanResult, used: usages }, + { ...baseOpts, listAll: true }, + '.env', + ); + + expect(printListAll).toHaveBeenCalledWith(usages); + expect(result.exitWithError).toBe(false); + }); }); diff --git a/packages/cli/test/unit/ui/scan/printListAll.test.ts b/packages/cli/test/unit/ui/scan/printListAll.test.ts new file mode 100644 index 0000000..4ac3b39 --- /dev/null +++ b/packages/cli/test/unit/ui/scan/printListAll.test.ts @@ -0,0 +1,95 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { printListAll } from '../../../../src/ui/scan/printListAll.js'; +import type { EnvUsage } from '../../../../src/config/types.js'; + +const makeUsage = (variable: string): EnvUsage => ({ + variable, + file: 'src/index.ts', + line: 1, + column: 1, + pattern: 'process.env', + context: `process.env.${variable}`, +}); + +describe('printListAll', () => { + let logSpy: ReturnType; + + beforeEach(() => { + logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('prints empty message when usages is an empty array', () => { + printListAll([]); + + expect(logSpy).toHaveBeenCalledTimes(1); + expect(logSpy).toHaveBeenCalledWith( + expect.stringContaining('No environment variables found in codebase.'), + ); + }); + + it('prints header, each unique variable, and summary for a single usage', () => { + printListAll([makeUsage('API_KEY')]); + + const calls = logSpy.mock.calls.map((call: [string]) => call[0]); + + expect( + calls.some((c: string) => + c?.includes('Environment variables found in codebase'), + ), + ).toBe(true); + expect(calls.some((c: string) => c?.includes('API_KEY'))).toBe(true); + expect(calls.some((c: string) => c?.includes('1 unique variable(s)'))).toBe( + true, + ); + }); + + it('deduplicates variables with multiple usages', () => { + printListAll([ + makeUsage('DB_URL'), + makeUsage('DB_URL'), + makeUsage('DB_URL'), + ]); + + const calls = logSpy.mock.calls.map((call: [string]) => call[0]); + const dbUrlCalls = calls.filter((c: string) => c?.includes('DB_URL')); + + expect(dbUrlCalls).toHaveLength(1); + expect(calls.some((c: string) => c?.includes('1 unique variable(s)'))).toBe( + true, + ); + }); + + it('sorts variables alphabetically', () => { + printListAll([makeUsage('ZEBRA'), makeUsage('ALPHA'), makeUsage('MIDDLE')]); + + const calls = logSpy.mock.calls.map((call: [string]) => call[0]); + const varLines = calls.filter( + (c: string) => + c?.includes('ALPHA') || c?.includes('MIDDLE') || c?.includes('ZEBRA'), + ); + + expect(varLines[0]).toContain('ALPHA'); + expect(varLines[1]).toContain('MIDDLE'); + expect(varLines[2]).toContain('ZEBRA'); + }); + + it('shows correct count for multiple unique variables', () => { + printListAll([makeUsage('FOO'), makeUsage('BAR'), makeUsage('BAZ')]); + + const calls = logSpy.mock.calls.map((call: [string]) => call[0]); + expect(calls.some((c: string) => c?.includes('3 unique variable(s)'))).toBe( + true, + ); + }); + + it('prints divider before and after the variable list', () => { + printListAll([makeUsage('TOKEN')]); + + // header + divider + variable + divider + summary = 5 calls + expect(logSpy).toHaveBeenCalledTimes(5); + }); +}); diff --git a/packages/cli/test/unit/ui/scan/scanJsonOutput.test.ts b/packages/cli/test/unit/ui/scan/scanJsonOutput.test.ts index 44417b8..ae61789 100644 --- a/packages/cli/test/unit/ui/scan/scanJsonOutput.test.ts +++ b/packages/cli/test/unit/ui/scan/scanJsonOutput.test.ts @@ -313,4 +313,58 @@ describe('scanJsonOutput', () => { expect(result.healthScore).toBeDefined(); expect(typeof result.healthScore).toBe('number'); }); + + it('includes listAllVariables when listAll is true', () => { + const scanResult = makeScanResult({ + used: [ + { + variable: 'Z_VAR', + file: 'src\\a.ts', + line: 1, + column: 0, + pattern: 'process.env', + context: 'process.env.Z_VAR', + }, + { + variable: 'A_VAR', + file: 'src\\b.ts', + line: 2, + column: 0, + pattern: 'process.env', + context: 'process.env.A_VAR', + }, + { + variable: 'A_VAR', + file: 'src\\c.ts', + line: 3, + column: 0, + pattern: 'process.env', + context: 'process.env.A_VAR', + }, + ], + }); + + const result = scanJsonOutput(scanResult, '', true); + + expect(result.listAll).toEqual(['A_VAR', 'Z_VAR']); + }); + + it('omits listAllVariables when listAll is false', () => { + const scanResult = makeScanResult({ + used: [ + { + variable: 'ONLY_VAR', + file: 'src\\a.ts', + line: 1, + column: 0, + pattern: 'process.env', + context: 'process.env.ONLY_VAR', + }, + ], + }); + + const result = scanJsonOutput(scanResult, '', false); + + expect(result.listAll).toBeUndefined(); + }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9d12a30..8e62ab6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,8 +33,8 @@ importers: specifier: ^8.59.1 version: 8.59.1(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) '@vitest/coverage-v8': - specifier: 4.1.4 - version: 4.1.4(vitest@4.1.5) + specifier: 4.1.5 + version: 4.1.5(vitest@4.1.5) eslint: specifier: ^10.2.1 version: 10.2.1(jiti@2.6.1) @@ -55,7 +55,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.5 - version: 4.1.5(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4)(vite@7.3.1(@types/node@25.6.0)(jiti@2.6.1)(yaml@2.8.3)) + version: 4.1.5(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5)(vite@7.3.1(@types/node@25.6.0)(jiti@2.6.1)(yaml@2.8.3)) packages/@repo/eslint-config: devDependencies: @@ -664,11 +664,11 @@ packages: resolution: {integrity: sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@vitest/coverage-v8@4.1.4': - resolution: {integrity: sha512-x7FptB5oDruxNPDNY2+S8tCh0pcq7ymCe1gTHcsp733jYjrJl8V1gMUlVysuCD9Kz46Xz9t1akkv08dPcYDs1w==} + '@vitest/coverage-v8@4.1.5': + resolution: {integrity: sha512-38C0/Ddb7HcRG0Z4/DUem8x57d2p9jYgp18mkaYswEOQBGsI1CG4f/hjm0ZCeaJfWhSZ4k7jgs29V1Zom7Ki9A==} peerDependencies: - '@vitest/browser': 4.1.4 - vitest: 4.1.4 + '@vitest/browser': 4.1.5 + vitest: 4.1.5 peerDependenciesMeta: '@vitest/browser': optional: true @@ -687,9 +687,6 @@ packages: vite: optional: true - '@vitest/pretty-format@4.1.4': - resolution: {integrity: sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==} - '@vitest/pretty-format@4.1.5': resolution: {integrity: sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==} @@ -702,9 +699,6 @@ packages: '@vitest/spy@4.1.5': resolution: {integrity: sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==} - '@vitest/utils@4.1.4': - resolution: {integrity: sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==} - '@vitest/utils@4.1.5': resolution: {integrity: sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==} @@ -2080,10 +2074,10 @@ snapshots: '@typescript-eslint/types': 8.59.1 eslint-visitor-keys: 5.0.1 - '@vitest/coverage-v8@4.1.4(vitest@4.1.5)': + '@vitest/coverage-v8@4.1.5(vitest@4.1.5)': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.1.4 + '@vitest/utils': 4.1.5 ast-v8-to-istanbul: 1.0.0 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 @@ -2092,7 +2086,7 @@ snapshots: obug: 2.1.1 std-env: 4.1.0 tinyrainbow: 3.1.0 - vitest: 4.1.5(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4)(vite@7.3.1(@types/node@25.6.0)(jiti@2.6.1)(yaml@2.8.3)) + vitest: 4.1.5(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5)(vite@7.3.1(@types/node@25.6.0)(jiti@2.6.1)(yaml@2.8.3)) '@vitest/expect@4.1.5': dependencies: @@ -2111,10 +2105,6 @@ snapshots: optionalDependencies: vite: 7.3.1(@types/node@25.6.0)(jiti@2.6.1)(yaml@2.8.3) - '@vitest/pretty-format@4.1.4': - dependencies: - tinyrainbow: 3.1.0 - '@vitest/pretty-format@4.1.5': dependencies: tinyrainbow: 3.1.0 @@ -2133,12 +2123,6 @@ snapshots: '@vitest/spy@4.1.5': {} - '@vitest/utils@4.1.4': - dependencies: - '@vitest/pretty-format': 4.1.4 - convert-source-map: 2.0.0 - tinyrainbow: 3.1.0 - '@vitest/utils@4.1.5': dependencies: '@vitest/pretty-format': 4.1.5 @@ -2862,7 +2846,7 @@ snapshots: jiti: 2.6.1 yaml: 2.8.3 - vitest@4.1.5(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4)(vite@7.3.1(@types/node@25.6.0)(jiti@2.6.1)(yaml@2.8.3)): + vitest@4.1.5(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5)(vite@7.3.1(@types/node@25.6.0)(jiti@2.6.1)(yaml@2.8.3)): dependencies: '@vitest/expect': 4.1.5 '@vitest/mocker': 4.1.5(vite@7.3.1(@types/node@25.6.0)(jiti@2.6.1)(yaml@2.8.3)) @@ -2886,7 +2870,7 @@ snapshots: why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 25.6.0 - '@vitest/coverage-v8': 4.1.4(vitest@4.1.5) + '@vitest/coverage-v8': 4.1.5(vitest@4.1.5) transitivePeerDependencies: - msw