Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/cli/src/cli/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export async function run(program: Command): Promise<void> {
// Normalize merged options
const opts = normalizeOptions(mergedRawOptions);

// set up global config (e.g. chalk for noColor)
setupGlobalConfig(opts);

// Handle --explain flag
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/compare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type {
} from '../config/types.js';
import { parseAndFilterEnv } from '../core/compare/parseAndFilterEnv.js';
import { updateTotals } from '../core/compare/updateTotals.js';
import { applyFixes } from '../core/fixEnv.js';
import { applyFixes } from '../services/fixEnv.js';
import { printFixTips } from '../ui/shared/printFixTips.js';
import { printStats } from '../ui/compare/printStats.js';
import { printDuplicates } from '../ui/shared/printDuplicates.js';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fs from 'fs';
import path from 'path';
import { isEnvIgnoredByGit, isGitRepo, findGitRoot } from '../services/git.js';
import { isEnvIgnoredByGit, isGitRepo, findGitRoot } from './git.js';
import { DEFAULT_GITIGNORE_ENV_PATTERNS } from '../config/constants.js';
import type { FixResult } from '../config/types.js';

Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/services/processComparisonFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { parseEnvFile } from './parseEnvFile.js';
import { filterIgnoredKeys } from '../core/helpers/filterIgnoredKeys.js';
import { compareWithEnvFiles } from '../core/scan/compareScan.js';
import { findDuplicateKeys } from '../core/duplicates.js';
import { applyFixes } from '../core/fixEnv.js';
import { applyFixes } from './fixEnv.js';
import { toUpperSnakeCase } from '../core/helpers/toUpperSnakeCase.js';
import { resolveFromCwd } from '../core/helpers/resolveFromCwd.js';
import { detectEnvExpirations } from './detectEnvExpirations.js';
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/test/unit/commands/compare.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ vi.mock('../../../src/core/compare/updateTotals.js', () => ({
updateTotals: vi.fn(() => false),
}));

vi.mock('../../../src/core/fixEnv.js', () => ({
vi.mock('../../../src/services/fixEnv.js', () => ({
applyFixes: vi.fn(),
}));

Expand Down Expand Up @@ -84,7 +84,7 @@ import { checkGitignoreStatus } from '../../../src/services/git.js';
import { findDuplicateKeys } from '../../../src/core/duplicates.js';
import { filterIgnoredKeys } from '../../../src/core/helpers/filterIgnoredKeys.js';
import { updateTotals } from '../../../src/core/compare/updateTotals.js';
import { applyFixes } from '../../../src/core/fixEnv.js';
import { applyFixes } from '../../../src/services/fixEnv.js';
import { printFixTips } from '../../../src/ui/shared/printFixTips.js';
import { printStats } from '../../../src/ui/compare/printStats.js';
import { printDuplicates } from '../../../src/ui/shared/printDuplicates.js';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest';
import { filterIgnoredKeys } from '../../../src/core/helpers/filterIgnoredKeys.js';
import { filterIgnoredKeys } from '../../../../src/core/helpers/filterIgnoredKeys.js';

describe('filterIgnoredKeys', () => {
it('filters exact and regex matches', () => {
Expand Down
90 changes: 90 additions & 0 deletions packages/cli/test/unit/core/helpers/findConfigFile.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import fs from 'fs';
import path from 'path';
import os from 'os';
import { findConfigFile } from '../../../../src/core/helpers/findConfigFile.js';

describe('findConfigFile', () => {
let tmpDir: string;

beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'find-config-test-'));
});

afterEach(() => {
fs.rmSync(tmpDir, { recursive: true, force: true });
});

it('returns the path when the config file exists in the given directory', () => {
const file = 'dotenv-diff.config.json';
fs.writeFileSync(path.join(tmpDir, file), '{}');

const result = findConfigFile(tmpDir, file);

expect(result).toBe(path.resolve(tmpDir, file));
});

it('finds the config file in a parent directory', () => {
const file = 'dotenv-diff.config.json';
fs.writeFileSync(path.join(tmpDir, file), '{}');

const child = path.join(tmpDir, 'subdir');
fs.mkdirSync(child);

const result = findConfigFile(child, file);

expect(result).toBe(path.resolve(tmpDir, file));
});

it('finds the config file multiple levels up', () => {
const file = 'dotenv-diff.config.json';
fs.writeFileSync(path.join(tmpDir, file), '{}');

const deep = path.join(tmpDir, 'a', 'b', 'c');
fs.mkdirSync(deep, { recursive: true });

const result = findConfigFile(deep, file);

expect(result).toBe(path.resolve(tmpDir, file));
});

it('returns null when the config file does not exist anywhere in the tree', () => {
const child = path.join(tmpDir, 'subdir');
fs.mkdirSync(child);

const result = findConfigFile(child, 'nonexistent.config.json');

expect(result).toBeNull();
});

it('prefers the config file in the starting directory over a parent one', () => {
const file = 'dotenv-diff.config.json';
fs.writeFileSync(path.join(tmpDir, file), '{"root":true}');

const child = path.join(tmpDir, 'subdir');
fs.mkdirSync(child);
fs.writeFileSync(path.join(child, file), '{"child":true}');

const result = findConfigFile(child, file);

expect(result).toBe(path.resolve(child, file));
});

it('works with different file names', () => {
const file = 'dotenv-diff.baseline.json';
fs.writeFileSync(path.join(tmpDir, file), '{}');

const result = findConfigFile(tmpDir, file);

expect(result).toBe(path.resolve(tmpDir, file));
});

it('returns null when starting from a directory with no ancestors containing the file', () => {
// Use a real path that definitely won't have the file
const result = findConfigFile(
os.tmpdir(),
'__nonexistent_config_xyz__.json',
);
expect(result).toBeNull();
});
});
42 changes: 42 additions & 0 deletions packages/cli/test/unit/core/helpers/isLikelyMinified.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { describe, it, expect } from 'vitest';
import { isLikelyMinified } from '../../../../src/core/helpers/isLikelyMinified.js';

describe('isLikelyMinified', () => {
it('returns false for empty string', () => {
expect(isLikelyMinified('')).toBe(false);
});

it('returns false for short single-line content', () => {
expect(isLikelyMinified('const x = 1;')).toBe(false);
});

it('returns false for multi-line content where all lines are short', () => {
const content = 'const x = 1;\nconst y = 2;\nconst z = 3;';
expect(isLikelyMinified(content)).toBe(false);
});

it('returns true when a line exceeds 500 characters', () => {
const longLine = 'a'.repeat(501);
expect(isLikelyMinified(longLine)).toBe(true);
});

it('returns false when a line is exactly 500 characters', () => {
const line = 'a'.repeat(500);
expect(isLikelyMinified(line)).toBe(false);
});

it('returns true when only one line in a multi-line string exceeds 500 characters', () => {
const content = 'short line\n' + 'a'.repeat(501) + '\nanother short line';
expect(isLikelyMinified(content)).toBe(true);
});

it('handles Windows-style line endings (CRLF)', () => {
const content = 'short\r\n' + 'a'.repeat(501) + '\r\nshort';
expect(isLikelyMinified(content)).toBe(true);
});

it('returns false for Windows-style line endings where all lines are short', () => {
const content = 'const x = 1;\r\nconst y = 2;\r\n';
expect(isLikelyMinified(content)).toBe(false);
});
});
40 changes: 40 additions & 0 deletions packages/cli/test/unit/core/helpers/normalizePath.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { describe, it, expect } from 'vitest';
import { normalizePath } from '../../../../src/core/helpers/normalizePath.js';

describe('normalizePath', () => {
it('returns forward-slash paths unchanged', () => {
expect(normalizePath('src/core/helpers/normalizePath.ts')).toBe(
'src/core/helpers/normalizePath.ts',
);
});

it('converts backslashes to forward slashes', () => {
expect(normalizePath('src\\core\\helpers\\normalizePath.ts')).toBe(
'src/core/helpers/normalizePath.ts',
);
});

it('converts mixed slashes to forward slashes', () => {
expect(normalizePath('src/core\\helpers/file.ts')).toBe(
'src/core/helpers/file.ts',
);
});

it('handles Windows absolute paths', () => {
expect(normalizePath('C:\\Users\\user\\project\\file.ts')).toBe(
'C:/Users/user/project/file.ts',
);
});

it('returns empty string unchanged', () => {
expect(normalizePath('')).toBe('');
});

it('handles a single backslash', () => {
expect(normalizePath('\\')).toBe('/');
});

it('handles paths with no slashes', () => {
expect(normalizePath('file.ts')).toBe('file.ts');
});
});
Loading