Skip to content

Auto-add file extension when missing#954

Closed
yamadashy wants to merge 1 commit intomainfrom
claude/auto-add-file-extension-01QFVrvJEww2mBfG5Z9FXBSV
Closed

Auto-add file extension when missing#954
yamadashy wants to merge 1 commit intomainfrom
claude/auto-add-file-extension-01QFVrvJEww2mBfG5Z9FXBSV

Conversation

@yamadashy
Copy link
Copy Markdown
Owner

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.

Checklist

  • Run npm run test
  • Run npm run lint

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.
Copilot AI review requested due to automatic review settings November 16, 2025 13:36
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Nov 16, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

The PR enhances the config loading module to automatically append file extensions to output paths based on the selected output style (xml, markdown, plain, json). When a filePath is explicitly provided without an extension, the system determines the appropriate extension from the chosen style and appends it. Comprehensive tests verify this behavior across different styles and configurations.

Changes

Cohort / File(s) Summary
Config extension handling logic
src/config/configLoad.ts
Added RepomixOutputStyle import and styleToExtensionMap constant. Enhanced mergeConfigs function to auto-append file extensions to output.filePath when set without an extension, selecting the extension based on the chosen style.
Extension handling tests
tests/config/configLoad.test.ts
Added test suite verifying extension assignment across default, markdown, plain, and json styles. Included tests for directory paths, existing extensions, CLI overrides, and file config merging behavior.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

  • Verify the styleToExtensionMap mapping is complete and correct for all supported styles
  • Confirm the logic for detecting and appending extensions handles edge cases (dots in filenames, relative/absolute paths)
  • Review test coverage aligns with documented behavior and handles interaction with CLI and file config overrides

Possibly related PRs

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/auto-add-file-extension-01QFVrvJEww2mBfG5Z9FXBSV

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 949284f and 32ea2d4.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (2)
  • src/config/configLoad.ts (3 hunks)
  • tests/config/configLoad.test.ts (1 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @yamadashy, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant enhancement to the output file handling mechanism. It automates the process of adding file extensions to output paths when they are missing, based on the chosen output style. This change streamlines the user workflow by reducing the need for manual extension specification, while intelligently preserving any extensions that are already present. The update aims to make the system more robust and user-friendly for generating various types of output.

Highlights

  • Automatic File Extension Appending: The system now automatically appends the appropriate file extension to output paths if one is not explicitly provided by the user. This improves user experience by ensuring output files are correctly formatted based on the selected style.
  • Style-Based Extension Mapping: A new styleToExtensionMap has been introduced to map output styles (xml, markdown, plain, json) to their corresponding file extensions (.xml, .md, .txt, .json).
  • Backward Compatibility: Existing file extensions in output paths are preserved unchanged, ensuring backward compatibility for users who prefer to specify extensions manually.
  • Comprehensive Testing: Extensive unit tests have been added to cover various scenarios for the new auto-extension feature, including different output styles, existing extensions, and paths with directories.
  • Dependency Cleanup: Several peer: true entries were removed from package-lock.json, likely as part of a dependency update or cleanup.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

}
} else {
// If filePath is explicitly set, check if it has an extension
const currentExtension = path.extname(mergedOutput.filePath);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential Issue: Edge Case with Dotfiles

The current logic uses path.extname() which returns an empty string for files like .gitignore or .env. This means if a user specifies an output path like .myoutput, the system will add an extension making it .myoutput.xml.

Example:

// Current behavior:
path.extname('.myoutput') // returns ''
// Result: '.myoutput.xml'

// Expected behavior: probably preserve '.myoutput' as-is?

Suggested fix:

const currentExtension = path.extname(mergedOutput.filePath);
const basename = path.basename(mergedOutput.filePath);
const isDotfile = basename.startsWith('.') && !basename.includes('.', 1);

if (!currentExtension && !isDotfile) {
  const extensionToAdd = styleToExtensionMap[style];
  mergedOutput.filePath = `${mergedOutput.filePath}${extensionToAdd}`;
  logger.trace('Added file extension to output path:', mergedOutput.filePath);
}

This ensures dotfiles are preserved unchanged.

};

// Mapping of output styles to their file extensions
const styleToExtensionMap: Record<RepomixOutputStyle, string> = {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Quality: Consider Making Map More Maintainable

The styleToExtensionMap duplicates information from defaultFilePathMap. Consider deriving one from the other to maintain a single source of truth.

Suggested approach:

// Derive extensions from defaultFilePathMap
const styleToExtensionMap: Record<RepomixOutputStyle, string> = Object.fromEntries(
  Object.entries(defaultFilePathMap).map(([style, filePath]) => [
    style,
    path.extname(filePath)
  ])
) as Record<RepomixOutputStyle, string>;

This ensures consistency and reduces maintenance burden when adding new output styles.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a helpful user experience improvement by automatically adding the correct file extension to the output file path when it's missing. The implementation is clean and the logic in mergeConfigs correctly handles the new feature. The addition of comprehensive tests is also great to see, ensuring the change is robust. I have a couple of minor suggestions to improve code clarity and test maintainability, but overall this is a solid contribution.

Comment on lines +172 to +177
const styleToExtensionMap: Record<RepomixOutputStyle, string> = {
xml: '.xml',
markdown: '.md',
plain: '.txt',
json: '.json',
} as const;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While the current implementation is correct, the type assertion as const combined with an explicit type annotation Record<RepomixOutputStyle, string> is a bit redundant. You can simplify this by letting TypeScript infer the type from the as const assertion. This makes the code more concise and still ensures type safety and immutability.

Suggested change
const styleToExtensionMap: Record<RepomixOutputStyle, string> = {
xml: '.xml',
markdown: '.md',
plain: '.txt',
json: '.json',
} as const;
const styleToExtensionMap = {
xml: '.xml',
markdown: '.md',
plain: '.txt',
json: '.json',
} as const;

Comment on lines +333 to +355
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');
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

These tests are great for ensuring correctness! To improve maintainability and reduce code duplication, you could parameterize these related test cases using test.each. This will make it easier to add or modify tests for different output styles in the future.

    test.each([
      { description: 'default style', config: { output: { filePath: 'myoutput' } }, expectedStyle: 'xml', expectedExt: '.xml' },
      { description: 'markdown style', config: { output: { filePath: 'myoutput', style: 'markdown' } }, expectedStyle: 'markdown', expectedExt: '.md' },
      { description: 'plain style', config: { output: { filePath: 'myoutput', style: 'plain' } }, expectedStyle: 'plain', expectedExt: '.txt' },
      { description: 'json style', config: { output: { filePath: 'myoutput', style: 'json' } }, expectedStyle: 'json', expectedExt: '.json' },
    ])('should add extension when CLI filePath has no extension ($description)', ({ config, expectedStyle, expectedExt }) => {
      const merged = mergeConfigs(process.cwd(), {}, config);
      expect(merged.output.filePath).toBe(`myoutput${expectedExt}`);
      expect(merged.output.style).toBe(expectedStyle);
    });

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Nov 16, 2025

Code Review Summary

Thank you for this enhancement! The automatic file extension feature is a great UX improvement. I've reviewed the implementation and identified several issues that should be addressed before merging.

🔴 Critical Issues

1. Dotfile Edge Case (Security/Data Loss Risk)

Issue: The current implementation doesn't handle dotfiles correctly. Files starting with a dot (like .myoutput) will have extensions added, resulting in .myoutput.xml instead of preserving the original name.

Impact: Users who intentionally use dotfile naming conventions will get unexpected behavior.

Test case to add:

test('should preserve dotfile names without adding extension', () => {
  const merged = mergeConfigs(process.cwd(), {}, { output: { filePath: '.myoutput', style: 'markdown' } });
  expect(merged.output.filePath).toBe('.myoutput'); // Currently fails - becomes '.myoutput.md'
});

See inline comment for suggested fix.

2. Multiple Extension Edge Case

Issue: Files with multiple dots (like archive.tar.gz) are handled correctly by path.extname() (returns .gz), but the behavior might be unexpected for users.

Test cases to add:

test('should handle files with multiple dots', () => {
  const merged = mergeConfigs(process.cwd(), {}, { output: { filePath: 'output.tar' } });
  expect(merged.output.filePath).toBe('output.tar'); // Has extension, should be preserved
});

test('should handle compound extensions', () => {
  const merged = mergeConfigs(process.cwd(), {}, { output: { filePath: 'output.backup', style: 'json' } });
  expect(merged.output.filePath).toBe('output.backup'); // Has extension, should be preserved
});

🟡 Medium Priority Issues

3. Code Duplication - DRY Principle Violation

The styleToExtensionMap duplicates information from defaultFilePathMap. This creates maintenance burden when adding new output styles.

See inline comment for suggested refactor to derive one from the other.

4. Missing Test Coverage

Additional edge cases to test:

// Windows path handling
test('should handle Windows-style paths', () => {
  const merged = mergeConfigs(process.cwd(), {}, { output: { filePath: 'C:\\output\\myfile', style: 'json' } });
  expect(merged.output.filePath).toBe('C:\\output\\myfile.json');
});

// Trailing dots
test('should handle filenames ending with dot', () => {
  const merged = mergeConfigs(process.cwd(), {}, { output: { filePath: 'myfile.' } });
  // What should happen here? Currently returns '.xml' which is unexpected
});

// Empty extension handling
test('should handle paths with trailing slash', () => {
  const merged = mergeConfigs(process.cwd(), {}, { output: { filePath: 'output/', style: 'json' } });
  // This would create 'output/.json' which is invalid
});

🟢 Minor Issues

5. Code Organization

The logic in lines 207-226 could be refactored for better readability by separating the two distinct code paths more clearly. See inline comment for suggested structure.

✅ Strengths

  • Excellent test coverage for the main use cases
  • Backward compatibility is preserved by checking for existing extensions
  • Clear logging helps with debugging
  • Good use of type safety with the Record<RepomixOutputStyle, string> type
  • Follows project conventions for config merging

📋 Premortem Analysis - Potential Failure Scenarios

Deployment Risks
  1. User Scripts Breaking: Users with automation scripts expecting exact filenames (without extensions) will experience breaking changes

    • Mitigation: Add release notes warning and consider a deprecation period
  2. CI/CD Integration Issues: Users who parse output filenames in CI pipelines might fail

    • Mitigation: Document the new behavior clearly
  3. Cross-platform Path Issues: Windows vs Unix path handling with path.extname()

    • Mitigation: Add Windows-specific tests (currently missing)
  4. Filesystem Limitations: Some filesystems have restrictions on filenames starting with dots

    • Mitigation: Handle dotfile edge case properly
Integration Risks
  1. Config File Migration: Users with existing config files specifying paths without extensions will see behavior changes

    • Severity: Low (it's actually fixing an inconsistency)
    • Mitigation: Add to migration guide
  2. API Consumer Impact: If Repomix is used programmatically, consumers expecting specific output paths will need updates

    • Mitigation: Consider this a minor version bump, document in changelog

🔧 Recommended Actions

Before merging:

  1. ✅ Fix dotfile edge case (critical)
  2. ✅ Add tests for dotfiles, trailing dots, and Windows paths
  3. ⚠️ Consider refactoring to derive styleToExtensionMap from defaultFilePathMap
  4. ✅ Run npm run test and npm run lint (checklist items)
  5. ⚠️ Add entry to CHANGELOG.md for this feature
  6. ⚠️ Update documentation if output file behavior is documented anywhere

Nice to have:

  • Consider adding validation for invalid filename patterns (trailing slashes, etc.)
  • Add integration test with actual file writing to ensure cross-platform compatibility

🎯 Overall Assessment

Status: Needs Changes ⚠️

The core implementation is solid and the test coverage is comprehensive for the happy path. However, the dotfile edge case is a critical issue that could lead to user confusion. Once the edge cases are addressed, this will be a valuable enhancement to the project.

Great work on maintaining backward compatibility and adding thorough unit tests! 🎉

@codecov
Copy link
Copy Markdown

codecov bot commented Nov 16, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.70%. Comparing base (949284f) to head (32ea2d4).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #954      +/-   ##
==========================================
+ Coverage   89.68%   89.70%   +0.01%     
==========================================
  Files         111      111              
  Lines        7843     7856      +13     
  Branches     1504     1506       +2     
==========================================
+ Hits         7034     7047      +13     
  Misses        809      809              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR enhances the user experience by automatically appending appropriate file extensions when users specify an output file path without one. The system now adds .xml, .md, .txt, or .json extensions based on the selected output style (xml, markdown, plain, or json respectively), while preserving existing extensions to maintain backward compatibility.

Key changes:

  • Added automatic extension detection and appending logic in config merging
  • Created a styleToExtensionMap to map output styles to their corresponding file extensions
  • Added comprehensive test coverage for various scenarios including paths with directories, file/CLI config sources, and style changes

Reviewed Changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/config/configLoad.ts Added styleToExtensionMap and logic to automatically append extensions when filePath lacks one
tests/config/configLoad.test.ts Added 11 new test cases covering extension auto-addition for all styles, existing extensions, directory paths, and config source combinations
package-lock.json Removed "peer": true fields from several dependencies (unrelated cleanup)

Comment on lines +219 to +225
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);
}
Copy link

Copilot AI Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Files starting with a dot (like .gitignore or .env) will be treated as having an extension by path.extname(). For example, path.extname('.config') returns '.config', meaning the extension will not be added when it should be. Consider checking if the filename starts with a dot and has no other extension, or use a more robust check like path.basename(mergedOutput.filePath).indexOf('.') === 0.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants