From 32ea2d40713e4809ca68e1c36f756ba251fb9619 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Nov 2025 13:00:53 +0000 Subject: [PATCH] feat(config): Add automatic file extension based on output style When users specify an output file path without an extension, the system now automatically appends the appropriate extension based on the selected output style (xml, markdown, plain, or json). This enhancement improves user experience by: - Automatically adding .xml for xml style (default) - Automatically adding .md for markdown style - Automatically adding .txt for plain style - Automatically adding .json for json style If a file extension is already present, it is preserved unchanged to maintain backward compatibility. --- package-lock.json | 8 ----- src/config/configLoad.ts | 18 ++++++++++ tests/config/configLoad.test.ts | 60 +++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7068f9e7a..d5c031163 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1727,7 +1727,6 @@ "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -3575,7 +3574,6 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "license": "MIT", - "peer": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -5465,7 +5463,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -5529,7 +5526,6 @@ "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" @@ -5672,7 +5668,6 @@ "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -5789,7 +5784,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -5803,7 +5797,6 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -6059,7 +6052,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/src/config/configLoad.ts b/src/config/configLoad.ts index 1f6066ae1..2fa0d36b7 100644 --- a/src/config/configLoad.ts +++ b/src/config/configLoad.ts @@ -12,6 +12,7 @@ import { type RepomixConfigCli, type RepomixConfigFile, type RepomixConfigMerged, + type RepomixOutputStyle, repomixConfigFileSchema, repomixConfigMergedSchema, } from './configSchema.js'; @@ -167,6 +168,14 @@ const loadAndValidateConfig = async ( } }; +// Mapping of output styles to their file extensions +const styleToExtensionMap: Record = { + xml: '.xml', + markdown: '.md', + plain: '.txt', + json: '.json', +} as const; + export const mergeConfigs = ( cwd: string, fileConfig: RepomixConfigFile, @@ -205,6 +214,15 @@ export const mergeConfigs = ( mergedOutput.filePath = desiredPath; logger.trace('Adjusted output file path to match style:', mergedOutput.filePath); } + } else { + // If filePath is explicitly set, check if it has an extension + const currentExtension = path.extname(mergedOutput.filePath); + if (!currentExtension) { + // No extension found, add the appropriate extension based on style + const extensionToAdd = styleToExtensionMap[style]; + mergedOutput.filePath = `${mergedOutput.filePath}${extensionToAdd}`; + logger.trace('Added file extension to output path:', mergedOutput.filePath); + } } return mergedOutput; diff --git a/tests/config/configLoad.test.ts b/tests/config/configLoad.test.ts index d864ab096..14f04af6e 100644 --- a/tests/config/configLoad.test.ts +++ b/tests/config/configLoad.test.ts @@ -329,5 +329,65 @@ describe('configLoad', () => { expect(merged.output.filePath).toBe('repomix-output.txt'); expect(merged.output.style).toBe('plain'); }); + + test('should add extension when CLI filePath has no extension (default style)', () => { + const merged = mergeConfigs(process.cwd(), {}, { output: { filePath: 'myoutput' } }); + expect(merged.output.filePath).toBe('myoutput.xml'); + expect(merged.output.style).toBe('xml'); + }); + + test('should add extension when CLI filePath has no extension (markdown style)', () => { + const merged = mergeConfigs(process.cwd(), {}, { output: { filePath: 'myoutput', style: 'markdown' } }); + expect(merged.output.filePath).toBe('myoutput.md'); + expect(merged.output.style).toBe('markdown'); + }); + + test('should add extension when CLI filePath has no extension (plain style)', () => { + const merged = mergeConfigs(process.cwd(), {}, { output: { filePath: 'myoutput', style: 'plain' } }); + expect(merged.output.filePath).toBe('myoutput.txt'); + expect(merged.output.style).toBe('plain'); + }); + + test('should add extension when CLI filePath has no extension (json style)', () => { + const merged = mergeConfigs(process.cwd(), {}, { output: { filePath: 'myoutput', style: 'json' } }); + expect(merged.output.filePath).toBe('myoutput.json'); + expect(merged.output.style).toBe('json'); + }); + + test('should keep extension when CLI filePath already has extension', () => { + const merged = mergeConfigs(process.cwd(), {}, { output: { filePath: 'myoutput.txt', style: 'markdown' } }); + expect(merged.output.filePath).toBe('myoutput.txt'); + expect(merged.output.style).toBe('markdown'); + }); + + test('should add extension when file config filePath has no extension', () => { + const merged = mergeConfigs(process.cwd(), { output: { filePath: 'myoutput', style: 'markdown' } }, {}); + expect(merged.output.filePath).toBe('myoutput.md'); + expect(merged.output.style).toBe('markdown'); + }); + + test('should keep extension when file config filePath already has extension', () => { + const merged = mergeConfigs(process.cwd(), { output: { filePath: 'myoutput.custom', style: 'markdown' } }, {}); + expect(merged.output.filePath).toBe('myoutput.custom'); + expect(merged.output.style).toBe('markdown'); + }); + + test('should add extension for paths with directories when no extension', () => { + const merged = mergeConfigs(process.cwd(), {}, { output: { filePath: 'output/myfile', style: 'json' } }); + expect(merged.output.filePath).toBe('output/myfile.json'); + expect(merged.output.style).toBe('json'); + }); + + test('should keep extension for paths with directories when extension exists', () => { + const merged = mergeConfigs(process.cwd(), {}, { output: { filePath: 'output/myfile.txt', style: 'json' } }); + expect(merged.output.filePath).toBe('output/myfile.txt'); + expect(merged.output.style).toBe('json'); + }); + + test('should add extension when style is changed via CLI but filePath has no extension', () => { + const merged = mergeConfigs(process.cwd(), { output: { filePath: 'myfile' } }, { output: { style: 'markdown' } }); + expect(merged.output.filePath).toBe('myfile.md'); + expect(merged.output.style).toBe('markdown'); + }); }); });