Skip to content

feat(config): support .config/ directory and YAML/TOML formats via unjs/c12#1364

Open
okikio wants to merge 23 commits intoyamadashy:mainfrom
okikio:feat/support-project-level-config-directory
Open

feat(config): support .config/ directory and YAML/TOML formats via unjs/c12#1364
okikio wants to merge 23 commits intoyamadashy:mainfrom
okikio:feat/support-project-level-config-directory

Conversation

@okikio
Copy link
Copy Markdown

@okikio okikio commented Mar 31, 2026

Adds support for the .config/ directory convention and expands config format support by replacing the manual config discovery/loading system with unjs/c12.

Summary

Config discovery now searches .config/ directory. Repomix finds config files in this order:

  1. Explicit --config path (if provided)
  2. Project root: repomix.config.{ext}
  3. .config/ directory: .config/repomix.{ext} or .config/repomix.config.{ext}
  4. Global config directory (fallback)

YAML and TOML formats are now supported alongside the existing TypeScript, JavaScript, and JSON/JSONC/JSON5 formats.

repomix --init is now interactive. Users choose where to place the config file:

  • Project root (repomix.config.json)
  • .config/ directory (config/repomix.json)
  • .config/ directory with full name (.config/repomix.config.json)

Dependency changes:

  • Added c12 — handles config file discovery, loading, and format parsing
  • Removed json5 — c12 uses confbox internally for JSON5/JSONC/YAML/TOML
  • Removed jiti — c12 declares it as an optional peer dep and imports it dynamically

Why c12

c12 is the config loader behind Nuxt and the unjs ecosystem. It handles the hard parts, file discovery across directories, dynamic TS/JS loading via jiti, and multi-format parsing via confbox, while giving control over merge policy, validation, and trust boundaries. This replaces ~100 lines of manual file-existence checks and format-specific loading with a single well-tested dependency. Plus, my primary reason for adding it, is it enables support for project-level .config/ directories, I thought it would be kinda nice.

Scope

This PR focuses on .config/ directory support and the format expansion that comes with c12. Several c12 features are explicitly disabled for now:

  • rcFile (.repomixrc) — deferred
  • packageJson (repomix key in package.json) — deferred
  • extend (config inheritance) — deferred
  • globalRc, envName, dotenv — deferred

Note

I didn't want to go to far ahead so I limited the change to only adding the .config/ directory support, but you might actually want to enable some of the other powerful features unjs/c12 supports.

This pr doesn't change the merge logic (mergeConfigs) because c12's defu merges arrays in reversed order, which doesn't match existing behavior, and I was weary of making too many changes in one pr.

Warning

A slight side-effect change that shouldn't cause any bugs but it is worth keeping in mind, is that by default the preference order for files is slightly tweaked, so unlike before where if the end user had both a repomix.config.js and repomix.config.ts it would prefer the .ts, with c12 it by default prefers the .ts version of the config.

This is mostly a rare occurrence but it can happen so it's worth being aware.

Files changed

Core:

  • configLoad.ts — Rewrote discovery/loading to use c12; merge logic unchanged
  • initAction.ts — Added interactive config location prompt (given that .config/ is now available as a location I thought it best to add an interactive selection menu for determining where to place the repomix.config.ts)
  • cliRun.ts — Updated CLI help text
  • package.json — Dependency changes (c12 added, json5/jiti removed; c12 already uses jiti under the hood)

Tests:

  • configLoad.test.ts — Rewrote loadFileConfig tests to mock c12Load via DI; added config priority ordering tests
  • configLoad.integration.test.ts — Updated DI for dynamic config tests
  • initAction.test.ts — Added .config/ directory tests, fixed mocks for new interactive prompt

Docs (README + english language website docs):

  • Updated config format lists to include YAML/TOML
  • Added .config/ directory to search locations
  • Updated --init and --config descriptions
  • Updated Configuration File Locations sections

Note

A separate pr might be necessary to update all the other languages.

Checklist

  • Run npm run test
  • Run npm run lint

Closes #1365


Open with Devin

@okikio okikio requested a review from yamadashy as a code owner March 31, 2026 04:08
Copilot AI review requested due to automatic review settings March 31, 2026 04:08
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 31, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f1da9fa1-d014-4657-8d32-e2a2650af24c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR transitions Repomix config handling to support interactive initialization with multiple locations and formats. It replaces custom discovery logic with c12 library integration, enabling config file detection in project root and .config/ directories across JSON, TypeScript, JavaScript, YAML, and TOML formats.

Changes

Cohort / File(s) Summary
Dependencies & Library Migration
package.json
Removed jiti and json5; added c12 (^4.0.0-beta.4) to support multi-format config discovery.
Config Loading Refactor
src/config/configLoad.ts
Replaced bespoke config discovery and file-format handling with c12-based loading. Introduced loadConfigFromC12 and validateConfig helpers; updated --config resolution and local/global discovery to delegate to c12.loadConfig; adjusted error handling for new library behavior.
Config Initialization
src/cli/actions/initAction.ts
Added interactive prompts.select for config location selection (project root vs .config/ directory); replaced hardcoded repomix.config.json messaging with dynamic configFileName derived from user choice; consolidated config path resolution logic.
CLI Help Text
src/cli/cliRun.ts, README.md
Updated help text for --config to document support for multiple formats (.json, .ts, .js, .yaml, .toml); updated --init to describe interactive config creation with location/format selection.
Configuration Documentation
website/client/src/en/guide/command-line-options.md, website/client/src/en/guide/configuration.md, website/client/src/en/index.md
Updated CLI options documentation and configuration guide to reflect multi-format support, interactive --init flow, and consolidated search order (explicit path, project root, .config/, global directory fallback).
Test Updates
tests/cli/actions/initAction.test.ts, tests/config/configLoad.test.ts, tests/config/configLoad.integration.test.ts
Added prompts.select mocking for location selection; expanded cancellation coverage; refactored config loading tests from fs-based behavior to c12Load injection; added precedence and discovery assertion tests.

Sequence Diagram

sequenceDiagram
    actor User
    participant CLI Init
    participant Prompts
    participant Config System
    participant c12 Discovery

    User->>CLI Init: Run `repomix --init`
    CLI Init->>Prompts: Show location options
    Prompts->>User: Select root or .config/
    User->>Prompts: Choose location & format
    Prompts->>CLI Init: Return selection
    CLI Init->>Config System: createConfigFile(location, format)
    Config System->>Config System: Resolve configPath & configFileName
    Config System->>User: Prompt for overwrite (if exists)
    User->>Config System: Confirm/skip
    Config System->>Config System: Write config file
    
    Note over User,Config System: Later: Normal config load flow
    User->>CLI Init: Run repomix (load config)
    CLI Init->>Config System: loadFileConfig()
    Config System->>c12 Discovery: Search locations in order
    c12 Discovery->>c12 Discovery: Check --config path
    c12 Discovery->>c12 Discovery: Check project root
    c12 Discovery->>c12 Discovery: Check .config/ directory
    c12 Discovery->>c12 Discovery: Check global directory
    c12 Discovery->>Config System: Return matched file + parsed config
    Config System->>CLI Init: Resolved configuration
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • yamadashy
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and specifically describes the main changes: adding .config/ directory support and expanding config formats (YAML/TOML) via the c12 dependency.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering the changes, rationale, dependency updates, scope, and files affected with detailed explanations.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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[bot]

This comment was marked as resolved.

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

okikio added 20 commits April 3, 2026 02:46
Signed-off-by: Okiki Ojo <hey@okikio.dev>
…gs from the project level `.config/` directory

Signed-off-by: Okiki Ojo <hey@okikio.dev>
…tions

Signed-off-by: Okiki Ojo <hey@okikio.dev>
…rompts

Signed-off-by: Okiki Ojo <hey@okikio.dev>
…andling and improve mock implementation

Signed-off-by: Okiki Ojo <hey@okikio.dev>
…supported formats

Signed-off-by: Okiki Ojo <hey@okikio.dev>
…tation for clarity and interactivity

Signed-off-by: Okiki Ojo <hey@okikio.dev>
Signed-off-by: Okiki Ojo <hey@okikio.dev>
…Config is true

Move the skipLocalConfig guard before the c12 call instead of after it.
The previous flow called c12 (which executes JS/TS config files) and
then discarded the result, still running arbitrary code from untrusted
remote repositories.

Now c12 is never invoked for local discovery when skipLocalConfig is
true. A general informational note is logged instead, pointing the user
to --remote-trust-config.

Also add SyntaxError branches to all three catch blocks (explicit,
local, global) so config parse errors surface clearly instead of
falling through to the generic "Error loading config" message.

Signed-off-by: Okiki Ojo <hey@okikio.dev>
The init wizard now prompts for config file format (JSON, YAML, TOML,
TypeScript, JavaScript) after the location prompt. The filename
extension and serialization are driven by the chosen format.

TS output uses defineConfig from repomix for type-safe autocomplete.
TS and JS formats strip $schema since it is JSON-specific.
YAML and TOML serialization uses confbox.

Location prompt hints now show wildcard extensions (repomix.config.*)
instead of hardcoded .json.

Signed-off-by: Okiki Ojo <hey@okikio.dev>
confbox provides YAML and TOML serialization for the new --init format
selection. jiti is an optional peer dependency of c12 required at
runtime for loading TS/JS config files.

Signed-off-by: Okiki Ojo <hey@okikio.dev>
…nfig is true

Replace the previous "should log a message when skipping" test with a
stronger assertion: c12Load must never be called with the local cwd
when skipLocalConfig is true. Also assert the security note is still
logged with the --remote-trust-config hint.

Signed-off-by: Okiki Ojo <hey@okikio.dev>
… integration test

Fixes biome lint/style/noNonNullAssertion warnings on options.cwd and
options.configFile by using ?? '' fallbacks.

Signed-off-by: Okiki Ojo <hey@okikio.dev>
…ific cases

All existing tests now mock both location and format prompts via
mockResolvedValueOnce chaining. New tests cover: format cancel, YAML
output (no braces, YAML key syntax), TOML output ([output] section),
TypeScript output (defineConfig, no $schema), JavaScript output
(export default, no $schema), and dotconfig + non-JSON format
filename.

Signed-off-by: Okiki Ojo <hey@okikio.dev>
…loaded

c12's loadConfig always returns a truthy configFile (the resolved
pattern path), even when no actual file exists on disk. Only
_configFile is set when a real file was loaded. The previous check
on configFile meant loadConfigFromC12 never returned null, causing
three failures:

- --config non-existent.json silently succeeded with empty config
- Global config fallback never triggered because local discovery
  always returned a truthy configFile
- The "No custom config found" message never displayed

Switch the emptiness check to _configFile and update all test mocks
to mirror real c12 return shape: always-truthy configFile with
_configFile only set when a file was actually found.

Signed-off-by: Okiki Ojo <hey@okikio.dev>
…handle non-Error throws

decision(config): normalize and compare resolved paths so explicit --config rejects
  discovered fallback files that happen to match a different name
constraint(c12): c12 always returns a truthy configFile even when no real file was loaded;
  _configFile is the reliable provenance signal
learned(config): non-Error values thrown by c12 or its loaders (e.g. jiti) silently fell
  through the catch block — every catch path now needs a String(error) fallback

Signed-off-by: Okiki Ojo <hey@okikio.dev>
intent(init): prevent an attacker-controlled symlink in .config/ or .repomixignore from
  redirecting init writes outside the project directory
decision(init): use fs.lstat before every init write to detect symlinks at the target and
  its parent directory for .config/ locations
decision(init): generate plain `export default` for TS/JS configs instead of defineConfig
  so the output works without a local repomix dependency
rejected(init): defineConfig in generated TS/JS — requires repomix as a dev dependency,
  which breaks npx and global-install workflows
constraint(init): cancel symbol from @clack/prompts must be checked before treating the
  overwrite response as a boolean, otherwise cancel overwrites the file

Signed-off-by: Okiki Ojo <hey@okikio.dev>
…covery, and non-Error throws

intent(config): regression tests for the three new defensive behaviors in loadFileConfig
decision(config): test explicit --config rejection by having the mock return a config
  loaded from a different path than the one requested

Signed-off-by: Okiki Ojo <hey@okikio.dev>
… explicit-path semantics

intent(config): validate config loading against real c12 behavior using temp directories,
  not just mocked return shapes
decision(config): use os.tmpdir with cleanup to avoid polluting the workspace
learned(config): the JS dynamic-config mock was missing _configFile, which caused it to
  silently not match real c12 behavior

Signed-off-by: Okiki Ojo <hey@okikio.dev>
okikio added 3 commits April 3, 2026 02:46
…lain TS/JS export output

intent(init): regression coverage for symlink hardening in config and ignore file writes,
  and for the cancel-symbol bug in ignore overwrite flow
decision(init): mock fs.lstat to return isSymbolicLink for targeted paths and throw ENOENT
  for all others, matching real filesystem behavior

Signed-off-by: Okiki Ojo <hey@okikio.dev>
…at support

intent(docs): align README with the interactive format/location selector now available
  in repomix --init
decision(docs): add a note explaining that generated TS/JS configs use plain export default
  and that users can switch to defineConfig after installing repomix locally

Signed-off-by: Okiki Ojo <hey@okikio.dev>
…t --init

intent(docs): align the English website guide with the new --init format and location
  selection behavior
decision(docs): rename section from "JSON Configuration" to "Interactive Configuration"
  since --init now supports json, yaml, toml, ts, and js

Signed-off-by: Okiki Ojo <hey@okikio.dev>
@okikio okikio force-pushed the feat/support-project-level-config-directory branch from aaeeb29 to 66d626b Compare April 3, 2026 06:46
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.

Support .config/ directory convention and additional config formats (YAML, TOML)

2 participants