Skip to main content
Glama
MIT License
27,120
19,746
  • Linux
  • Apple
configLoad.test.ts10.9 kB
import type { Stats } from 'node:fs'; import * as fs from 'node:fs/promises'; import path from 'node:path'; import process from 'node:process'; import { beforeEach, describe, expect, test, vi } from 'vitest'; import { loadFileConfig, mergeConfigs } from '../../src/config/configLoad.js'; import type { RepomixConfigCli, RepomixConfigFile } from '../../src/config/configSchema.js'; import { getGlobalDirectory } from '../../src/config/globalDirectory.js'; import { RepomixConfigValidationError } from '../../src/shared/errorHandle.js'; import { logger } from '../../src/shared/logger.js'; vi.mock('node:fs/promises'); vi.mock('../../src/shared/logger', () => ({ logger: { trace: vi.fn(), note: vi.fn(), log: vi.fn(), }, })); vi.mock('../../src/config/globalDirectory', () => ({ getGlobalDirectory: vi.fn(), })); describe('configLoad', () => { beforeEach(() => { vi.resetAllMocks(); process.env = {}; }); describe('loadFileConfig', () => { test('should load and parse a valid local config file', async () => { const mockConfig = { output: { filePath: 'test-output.txt' }, ignore: { useDefaultPatterns: true }, }; vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(mockConfig)); vi.mocked(fs.stat).mockResolvedValue({ isFile: () => true } as Stats); const result = await loadFileConfig(process.cwd(), 'test-config.json'); expect(result).toEqual(mockConfig); }); test('should throw RepomixConfigValidationError for invalid config', async () => { const invalidConfig = { output: { filePath: 123, style: 'invalid' }, // Invalid filePath type and invalid style ignore: { useDefaultPatterns: 'not a boolean' }, // Invalid type }; vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(invalidConfig)); vi.mocked(fs.stat).mockResolvedValue({ isFile: () => true } as Stats); await expect(loadFileConfig(process.cwd(), 'test-config.json')).rejects.toThrow(RepomixConfigValidationError); }); test('should load global config when local config is not found', async () => { const mockGlobalConfig = { output: { filePath: 'global-output.txt' }, ignore: { useDefaultPatterns: false }, }; vi.mocked(getGlobalDirectory).mockReturnValue('/global/repomix'); vi.mocked(fs.stat) .mockRejectedValueOnce(new Error('File not found')) // Local repomix.config.ts .mockRejectedValueOnce(new Error('File not found')) // Local repomix.config.mts .mockRejectedValueOnce(new Error('File not found')) // Local repomix.config.cts .mockRejectedValueOnce(new Error('File not found')) // Local repomix.config.js .mockRejectedValueOnce(new Error('File not found')) // Local repomix.config.mjs .mockRejectedValueOnce(new Error('File not found')) // Local repomix.config.cjs .mockRejectedValueOnce(new Error('File not found')) // Local repomix.config.json5 .mockRejectedValueOnce(new Error('File not found')) // Local repomix.config.jsonc .mockRejectedValueOnce(new Error('File not found')) // Local repomix.config.json .mockRejectedValueOnce(new Error('File not found')) // Global repomix.config.ts .mockRejectedValueOnce(new Error('File not found')) // Global repomix.config.mts .mockRejectedValueOnce(new Error('File not found')) // Global repomix.config.cts .mockRejectedValueOnce(new Error('File not found')) // Global repomix.config.js .mockRejectedValueOnce(new Error('File not found')) // Global repomix.config.mjs .mockRejectedValueOnce(new Error('File not found')) // Global repomix.config.cjs .mockResolvedValueOnce({ isFile: () => true } as Stats); // Global repomix.config.json5 vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(mockGlobalConfig)); const result = await loadFileConfig(process.cwd(), null); expect(result).toEqual(mockGlobalConfig); expect(fs.readFile).toHaveBeenCalledWith(path.join('/global/repomix', 'repomix.config.json5'), 'utf-8'); }); test('should return an empty object if no config file is found', async () => { const loggerSpy = vi.spyOn(logger, 'log').mockImplementation(vi.fn()); vi.mocked(getGlobalDirectory).mockReturnValue('/global/repomix'); vi.mocked(fs.stat).mockRejectedValue(new Error('File not found')); const result = await loadFileConfig(process.cwd(), null); expect(result).toEqual({}); expect(loggerSpy).toHaveBeenCalledWith(expect.stringContaining('No custom config found')); expect(loggerSpy).toHaveBeenCalledWith(expect.stringContaining('repomix.config.json5')); expect(loggerSpy).toHaveBeenCalledWith(expect.stringContaining('repomix.config.jsonc')); expect(loggerSpy).toHaveBeenCalledWith(expect.stringContaining('repomix.config.json')); }); test('should throw an error for invalid JSON', async () => { vi.mocked(fs.readFile).mockResolvedValue('invalid json'); vi.mocked(fs.stat).mockResolvedValue({ isFile: () => true } as Stats); await expect(loadFileConfig(process.cwd(), 'test-config.json')).rejects.toThrow('Invalid syntax'); }); test('should parse config file with comments', async () => { const configWithComments = `{ // Output configuration "output": { "filePath": "test-output.txt" }, /* Ignore configuration */ "ignore": { "useGitignore": true // Use .gitignore file } }`; vi.mocked(fs.readFile).mockResolvedValue(configWithComments); vi.mocked(fs.stat).mockResolvedValue({ isFile: () => true } as Stats); const result = await loadFileConfig(process.cwd(), 'test-config.json'); expect(result).toEqual({ output: { filePath: 'test-output.txt' }, ignore: { useGitignore: true }, }); }); test('should parse config file with JSON5 features', async () => { const configWithJSON5Features = `{ // Output configuration output: { filePath: 'test-output.txt', style: 'plain', }, /* Ignore configuration */ ignore: { useGitignore: true, // Use .gitignore file customPatterns: [ '*.log', '*.tmp', '*.temp', // Trailing comma ], }, }`; vi.mocked(fs.readFile).mockResolvedValue(configWithJSON5Features); vi.mocked(fs.stat).mockResolvedValue({ isFile: () => true } as Stats); const result = await loadFileConfig(process.cwd(), 'test-config.json'); expect(result).toEqual({ output: { filePath: 'test-output.txt', style: 'plain' }, ignore: { useGitignore: true, customPatterns: ['*.log', '*.tmp', '*.temp'], }, }); }); test('should load .jsonc config file with priority order', async () => { const mockConfig = { output: { filePath: 'jsonc-output.txt' }, ignore: { useDefaultPatterns: true }, }; vi.mocked(fs.stat) .mockRejectedValueOnce(new Error('File not found')) // repomix.config.ts .mockRejectedValueOnce(new Error('File not found')) // repomix.config.mts .mockRejectedValueOnce(new Error('File not found')) // repomix.config.cts .mockRejectedValueOnce(new Error('File not found')) // repomix.config.js .mockRejectedValueOnce(new Error('File not found')) // repomix.config.mjs .mockRejectedValueOnce(new Error('File not found')) // repomix.config.cjs .mockRejectedValueOnce(new Error('File not found')) // repomix.config.json5 .mockResolvedValueOnce({ isFile: () => true } as Stats); // repomix.config.jsonc vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(mockConfig)); const result = await loadFileConfig(process.cwd(), null); expect(result).toEqual(mockConfig); expect(fs.readFile).toHaveBeenCalledWith(path.resolve(process.cwd(), 'repomix.config.jsonc'), 'utf-8'); }); test('should prioritize .json5 over .jsonc and .json', async () => { const mockConfig = { output: { filePath: 'json5-output.txt' }, ignore: { useDefaultPatterns: true }, }; vi.mocked(fs.stat) .mockRejectedValueOnce(new Error('File not found')) // repomix.config.ts .mockRejectedValueOnce(new Error('File not found')) // repomix.config.mts .mockRejectedValueOnce(new Error('File not found')) // repomix.config.cts .mockRejectedValueOnce(new Error('File not found')) // repomix.config.js .mockRejectedValueOnce(new Error('File not found')) // repomix.config.mjs .mockRejectedValueOnce(new Error('File not found')) // repomix.config.cjs .mockResolvedValueOnce({ isFile: () => true } as Stats); // repomix.config.json5 exists vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(mockConfig)); const result = await loadFileConfig(process.cwd(), null); expect(result).toEqual(mockConfig); expect(fs.readFile).toHaveBeenCalledWith(path.resolve(process.cwd(), 'repomix.config.json5'), 'utf-8'); // Should not check for .jsonc or .json since .json5 was found expect(fs.stat).toHaveBeenCalledTimes(7); }); test('should throw RepomixError when specific config file does not exist', async () => { const nonExistentConfigPath = 'non-existent-config.json'; vi.mocked(fs.stat).mockRejectedValue(new Error('File not found')); await expect(loadFileConfig(process.cwd(), nonExistentConfigPath)).rejects.toThrow( `Config file not found at ${nonExistentConfigPath}`, ); }); }); describe('mergeConfigs', () => { test('should correctly merge configs', () => { const fileConfig: RepomixConfigFile = { output: { filePath: 'file-output.txt' }, ignore: { useDefaultPatterns: true, customPatterns: ['file-ignore'] }, }; const cliConfig: RepomixConfigCli = { output: { filePath: 'cli-output.txt' }, ignore: { customPatterns: ['cli-ignore'] }, }; const result = mergeConfigs(process.cwd(), fileConfig, cliConfig); expect(result.output.filePath).toBe('cli-output.txt'); expect(result.ignore.useDefaultPatterns).toBe(true); expect(result.ignore.customPatterns).toContain('file-ignore'); expect(result.ignore.customPatterns).toContain('cli-ignore'); }); test('should throw RepomixConfigValidationError for invalid merged config', () => { const fileConfig: RepomixConfigFile = { output: { filePath: 'file-output.txt', style: 'plain' }, }; const cliConfig: RepomixConfigCli = { // @ts-expect-error output: { style: 'invalid' }, // Invalid style }; expect(() => mergeConfigs(process.cwd(), fileConfig, cliConfig)).toThrow(RepomixConfigValidationError); }); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/yamadashy/repomix'

If you have feedback or need assistance with the MCP directory API, please join our Discord server