repomix
Version:
A tool to pack repository contents to single file for AI consumption
1,823 lines (1,630 loc) • 1.1 MB
text/xml
This file is a merged representation of the entire codebase, combined into a single document by Repomix.
<file_summary>
This section contains a summary of this file.
<purpose>
This file contains a packed representation of the entire repository's contents.
It is designed to be easily consumable by AI systems for analysis, code review,
or other automated processes.
</purpose>
<file_format>
The content is organized as follows:
1. This summary section
2. Repository information
3. Directory structure
4. Repository files, each consisting of:
- File path as an attribute
- Full contents of the file
</file_format>
<usage_guidelines>
- This file should be treated as read-only. Any changes should be made to the
original repository files, not this packed version.
- When processing this file, use the file path to distinguish
between different files in the repository.
- Be aware that this file may contain sensitive information. Handle it with
the same level of security as you would the original repository.
- Pay special attention to the Repository Description. These contain important context and guidelines specific to this project.
- Pay special attention to the Repository Instruction. These contain important context and guidelines specific to this project.
</usage_guidelines>
<notes>
- Some files may have been excluded based on .gitignore rules and Repomix's configuration
- Binary files are not included in this packed representation. Please refer to the Repository Structure section for a complete list of file paths, including binary files
- Files matching patterns in .gitignore are excluded
- Files matching default ignore patterns are excluded
- Files are sorted by Git change count (files with more changes are at the bottom)
</notes>
<additional_info>
<user_provided_header>
This repository contains the source code for the Repomix tool.
Repomix is designed to pack repository contents into a single file,
making it easier for AI systems to analyze and process the codebase.
Key Features:
- Configurable ignore patterns
- Custom header text support
- Efficient file processing and packing
Please refer to the README.md file for more detailed information on usage and configuration.
</user_provided_header>
</additional_info>
</file_summary>
<directory_structure>
.devcontainer/
devcontainer.json
.github/
workflows/
ci.yml
codeql.yml
docker.yml
homebrew.yml
CODEOWNERS
copilot-instructions.md
dependabot.yml
FUNDING.yml
pull_request_template.md
renovate.json5
.memo/
memo.md
.repomix/
bin/
repomix.cjs
src/
cli/
actions/
defaultAction.ts
initAction.ts
mcpAction.ts
migrationAction.ts
remoteAction.ts
versionAction.ts
cliPrint.ts
cliRun.ts
cliSpinner.ts
types.ts
config/
configLoad.ts
configSchema.ts
defaultIgnore.ts
globalDirectory.ts
core/
file/
workers/
fileCollectWorker.ts
fileProcessWorker.ts
fileCollect.ts
fileManipulate.ts
filePathSort.ts
fileProcess.ts
fileSearch.ts
fileTreeGenerate.ts
fileTypes.ts
gitCommand.ts
packageJsonParse.ts
permissionCheck.ts
metrics/
workers/
fileMetricsWorker.ts
outputMetricsWorker.ts
types.ts
calculateAllFileMetrics.ts
calculateMetrics.ts
calculateOutputMetrics.ts
output/
outputStyles/
markdownStyle.ts
plainStyle.ts
xmlStyle.ts
outputGenerate.ts
outputGeneratorTypes.ts
outputSort.ts
outputStyleDecorate.ts
packager/
copyToClipboardIfEnabled.ts
writeOutputToDisk.ts
security/
workers/
securityCheckWorker.ts
filterOutUntrustedFiles.ts
securityCheck.ts
validateFileSafety.ts
tokenCount/
tokenCount.ts
treeSitter/
parseStrategies/
CssParseStrategy.ts
DefaultParseStrategy.ts
GoParseStrategy.ts
ParseStrategy.ts
PythonParseStrategy.ts
TypeScriptParseStrategy.ts
VueParseStrategy.ts
queries/
queryC.ts
queryCpp.ts
queryCSharp.ts
queryCss.ts
queryGo.ts
queryJava.ts
queryJavascript.ts
queryPhp.ts
queryPython.ts
queryRuby.ts
queryRust.ts
querySolidity.ts
querySwift.ts
queryTypescript.ts
queryVue.ts
README.md
ext2Lang.ts
lang2Query.ts
languageParser.ts
loadLanguage.ts
parseFile.ts
packager.ts
mcp/
prompts/
packRemoteRepositoryPrompts.ts
tools/
fileSystemReadDirectoryTool.ts
fileSystemReadFileTool.ts
mcpToolRuntime.ts
packCodebaseTool.ts
packRemoteRepositoryTool.ts
readRepomixOutputTool.ts
mcpServer.ts
shared/
constants.ts
errorHandle.ts
logger.ts
processConcurrency.ts
types.ts
index.ts
test-css/
tests/
cli/
actions/
defaultAction.test.ts
initAction.test.ts
mcpAction.test.ts
migrationAction.test.ts
remoteAction.test.ts
versionAction.test.ts
cliPrint.test.ts
cliRun.test.ts
config/
configLoad.test.ts
configSchema.test.ts
globalDirectory.test.ts
core/
file/
fileCollect.test.ts
fileManipulate.test.ts
filePathSort.test.ts
fileProcess.test.ts
fileSearch.test.ts
gitCommand.test.ts
packageJsonParse.test.ts
permissionCheck.test.ts
metrics/
calculateAllFileMetrics.test.ts
calculateMetrics.test.ts
calculateOutputMetrics.test.ts
output/
outputStyles/
markdownStyle.test.ts
plainStyle.test.ts
xmlStyle.test.ts
outputGenerate.test.ts
outputSort.test.ts
outputStyleDecorate.test.ts
packager/
copyToClipboardIfEnabled.test.ts
writeOutputToDisk.test.ts
security/
workers/
securityCheckWorker.test.ts
filterOutUntrustedFiles.test.ts
securityCheck.test.ts
validateFileSafety.test.ts
tokenCount/
tokenCount.test.ts
treeSitter/
LanguageParser.test.ts
loadLanguage.test.ts
parseFile.c.test.ts
parseFile.comments.test.ts
parseFile.cpp.test.ts
parseFile.csharp.test.ts
parseFile.css.test.ts
parseFile.go.test.ts
parseFile.java.test.ts
parseFile.javascript.test.ts
parseFile.php.test.ts
parseFile.python.test.ts
parseFile.ruby.test.ts
parseFile.rust.test.ts
parseFile.solidity.test.ts
parseFile.swift.test.ts
parseFile.test.ts
parseFile.typescript.test.ts
parseFile.vue.test.ts
packager.test.ts
integration-tests/
packager.test.ts
mcp/
tools/
fileSystemReadDirectoryTool.test.ts
fileSystemReadFileTool.test.ts
packCodebaseTool.test.ts
mcpServer.test.ts
shared/
logger.test.ts
processConcurrency.test.ts
testing/
testUtils.ts
website/
client/
.vitepress/
config/
configDe.ts
configEnUs.ts
configEs.ts
configFr.ts
configJa.ts
configKo.ts
configPtBr.ts
configShard.ts
configZhCn.ts
theme/
component.d.ts
custom.css
index.ts
style.css
config.ts
components/
api/
client.ts
Home/
Hero.vue
PackButton.vue
TryIt.vue
TryItFileUpload.vue
TryItFolderUpload.vue
TryItPackOptions.vue
TryItResult.vue
TryItResultContent.vue
TryItResultErrorContent.vue
TryItUrlInput.vue
utils/
analytics.ts
requestHandlers.ts
resultViewer.ts
validation.ts
Home.vue
src/
de/
guide/
development/
index.md
setup.md
tips/
best-practices.md
code-compress.md
command-line-options.md
comment-removal.md
configuration.md
custom-instructions.md
index.md
installation.md
mcp-server.md
output.md
prompt-examples.md
remote-repository-processing.md
security.md
usage.md
index.md
en/
guide/
development/
index.md
setup.md
tips/
best-practices.md
code-compress.md
command-line-options.md
comment-removal.md
configuration.md
custom-instructions.md
index.md
installation.md
mcp-server.md
output.md
prompt-examples.md
remote-repository-processing.md
security.md
usage.md
index.md
es/
guide/
development/
index.md
setup.md
tips/
best-practices.md
code-compress.md
command-line-options.md
comment-removal.md
configuration.md
custom-instructions.md
index.md
installation.md
mcp-server.md
output.md
prompt-examples.md
remote-repository-processing.md
security.md
usage.md
index.md
fr/
guide/
development/
index.md
setup.md
tips/
best-practices.md
code-compress.md
command-line-options.md
comment-removal.md
configuration.md
custom-instructions.md
index.md
installation.md
mcp-server.md
output.md
prompt-examples.md
remote-repository-processing.md
security.md
usage.md
index.md
ja/
guide/
development/
index.md
setup.md
tips/
best-practices.md
code-compress.md
command-line-options.md
comment-removal.md
configuration.md
custom-instructions.md
index.md
installation.md
mcp-server.md
output.md
prompt-examples.md
remote-repository-processing.md
security.md
usage.md
index.md
ko/
guide/
development/
index.md
setup.md
tips/
best-practices.md
code-compress.md
command-line-options.md
comment-removal.md
configuration.md
custom-instructions.md
index.md
installation.md
mcp-server.md
output.md
prompt-examples.md
remote-repository-processing.md
security.md
usage.md
index.md
pt-br/
guide/
development/
index.md
setup.md
tips/
best-practices.md
code-compress.md
command-line-options.md
comment-removal.md
configuration.md
custom-instructions.md
index.md
installation.md
mcp-server.md
output.md
prompt-examples.md
remote-repository-processing.md
security.md
usage.md
index.md
public/
images/
repomix-logo.svg
zh-cn/
guide/
development/
index.md
setup.md
tips/
best-practices.md
code-compress.md
command-line-options.md
comment-removal.md
configuration.md
custom-instructions.md
index.md
installation.md
mcp-server.md
output.md
prompt-examples.md
remote-repository-processing.md
security.md
usage.md
index.md
.gitignore
.tool-versions
Dockerfile
package.json
tsconfig.json
tsconfig.node.json
server/
src/
schemas/
request.ts
utils/
cache.ts
errorHandler.ts
fileUtils.ts
logger.ts
network.ts
processConcurrency.ts
rateLimit.ts
sharedInstance.ts
time.ts
validation.ts
constants.ts
index.ts
processZipFile.ts
remoteRepo.ts
types.ts
.dockerignore
.gitignore
cloudbuild.yaml
Dockerfile
package.json
tsconfig.json
compose.yml
README.md
.clinerules
.codecov.yml
.cursorrules
.dockerignore
.editorconfig
.gitignore
.node-version
.npmignore
.repomixignore
.secretlintrc.json
.tool-versions
biome.json
CLAUDE.md
CODE_OF_CONDUCT.md
CONTRIBUTING.md
Dockerfile
LICENSE
llms-install.md
package.json
README.md
repomix-instruction.md
repomix.config.json
SECURITY.md
tsconfig.build.json
tsconfig.json
typos.toml
vitest.config.ts
</directory_structure>
<files>
This section contains the contents of the repository's files.
<file path=".devcontainer/devcontainer.json">
{
"name": "Repomix",
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bullseye",
"runArgs": ["--name", "repomix-devcontainer"],
"postCreateCommand": "npm install"
}
</file>
<file path=".github/workflows/ci.yml">
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_dispatch:
jobs:
lint-biome:
name: Lint Biome
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .tool-versions
cache: npm
- run: npm ci
- run: npm run lint-biome && git diff --exit-code
lint-ts:
name: Lint TypeScript
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .tool-versions
cache: npm
- run: npm ci
- run: npm run lint-ts
lint-secretlint:
name: Lint Secretlint
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .tool-versions
cache: npm
- run: npm ci
- run: npm run lint-secretlint
lint-action:
name: Lint GitHub Actions
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker://rhysd/actionlint:latest
with:
args: "-color"
check-typos:
name: Check typos
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: crate-ci/typos@master
test:
name: Test
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [18.0.0, 18.x, 19.x, 20.x, 21.x, 22.x, 23.x]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run test --reporter=verbose
env:
CI_OS: ${{ runner.os }}
test-coverage:
name: Test coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .tool-versions
cache: npm
- run: npm ci
- run: npm run test-coverage -- --reporter=verbose
env:
CI_OS: ${{ runner.os }}
- uses: actions/upload-artifact@v4
with:
name: test-coverage
path: coverage/
- uses: codecov/codecov-action@v5
with:
fail_ci_if_error: true
directory: ./coverage
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
build-and-run:
name: Build and run
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [18.0.0, 18.x, 19.x, 20.x, 21.x, 22.x, 23.x]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run build
- name: Install only production dependencies
run: npm ci --omit=dev
- run: node bin/repomix.cjs
- run: node bin/repomix.cjs --version
- run: node bin/repomix.cjs --help
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: repomix-output-${{ matrix.os }}-${{ matrix.node-version }}.txt
path: repomix-output.txt
</file>
<file path=".github/workflows/codeql.yml">
name: "CodeQL"
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
- cron: '25 11 * * 0'
jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
permissions:
security-events: write
packages: read
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
- language: javascript-typescript
build-mode: none
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
- if: matrix.build-mode == 'manual'
shell: bash
run: |
echo 'If you are using a "manual" build mode for one or more of the' \
'languages you are analyzing, replace this with the commands to build' \
'your code, for example:'
echo ' make bootstrap'
echo ' make release'
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"
</file>
<file path=".github/workflows/docker.yml">
name: Docker
on:
push:
branches:
- "main"
paths-ignore:
- "**.md"
- LICENSE
pull_request:
branches:
- "*"
paths:
- "Dockerfile"
- ".dockerignore"
- ".github/workflows/docker.yml"
workflow_dispatch:
release:
types: [published, edited]
permissions:
contents: read
packages: write
jobs:
build-and-publish-image:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/yamadashy/repomix
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable=${{ github.event_name == 'release' }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Publish Docker Image
uses: docker/build-push-action@v6
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64,linux/arm/v7
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
</file>
<file path=".github/workflows/homebrew.yml">
name: Homebrew
on:
release:
types:
- created
jobs:
homebrew:
runs-on: macos-latest
steps:
- name: Set up Homebrew
uses: Homebrew/actions/setup-homebrew@master
with:
test-bot: false
- name: Configure Git user
uses: Homebrew/actions/git-user-config@master
- name: Bump packages
uses: Homebrew/actions/bump-packages@master
with:
token: ${{ secrets.COMMITTER_TOKEN }}
formulae: repomix
</file>
<file path=".github/dependabot.yml">
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
</file>
<file path=".github/FUNDING.yml">
github: yamadashy
</file>
<file path=".github/pull_request_template.md">
<!-- Please include a summary of the changes -->
## Checklist
- [ ] Run `npm run test`
- [ ] Run `npm run lint`
</file>
<file path=".github/renovate.json5">
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
"schedule:weekly",
'group:allNonMajor'
],
"rangeStrategy": "bump",
"dependencyDashboard": false,
"labels": ["dependencies", "renovate"],
"packageRules": [
{
matchDepTypes: ['peerDependencies'],
enabled: false,
},
],
"ignoreDeps": [
"node",
]
}
</file>
<file path=".memo/memo.md">
</file>
<file path="bin/repomix.cjs">
#!/usr/bin/env node
const nodeVersion = process.versions.node;
const [major] = nodeVersion.split('.').map(Number);
const EXIT_CODES = {
SUCCESS: 0,
ERROR: 1,
};
if (major < 16) {
console.error(`Repomix requires Node.js version 16 or higher. Current version: ${nodeVersion}\n`);
process.exit(EXIT_CODES.ERROR);
}
function setupErrorHandlers() {
process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
process.exit(EXIT_CODES.ERROR);
});
process.on('unhandledRejection', (reason) => {
console.error('Unhandled Promise Rejection:', reason);
process.exit(EXIT_CODES.ERROR);
});
function shutdown() {
process.exit(EXIT_CODES.SUCCESS);
}
process.on('SIGINT', () => {
console.log('\nReceived SIGINT. Shutting down...');
shutdown();
});
process.on('SIGTERM', shutdown);
}
(async () => {
try {
setupErrorHandlers();
const { run } = await import('../lib/cli/cliRun.js');
await run();
} catch (error) {
if (error instanceof Error) {
console.error('Fatal Error:', {
name: error.name,
message: error.message,
stack: error.stack,
});
} else {
console.error('Fatal Error:', error);
}
process.exit(EXIT_CODES.ERROR);
}
})();
</file>
<file path="src/cli/actions/migrationAction.ts">
import * as fs from 'node:fs/promises';
import path from 'node:path';
import * as prompts from '@clack/prompts';
import pc from 'picocolors';
import { getGlobalDirectory } from '../../config/globalDirectory.js';
import { logger } from '../../shared/logger.js';
interface MigrationPaths {
oldConfigPath: string;
newConfigPath: string;
oldIgnorePath: string;
newIgnorePath: string;
oldInstructionPath: string;
newInstructionPath: string;
oldOutputPaths: string[];
newOutputPaths: string[];
oldGlobalConfigPath: string;
newGlobalConfigPath: string;
}
interface MigrationResult {
configMigrated: boolean;
ignoreMigrated: boolean;
instructionMigrated: boolean;
outputFilesMigrated: string[];
globalConfigMigrated: boolean;
error?: Error;
}
/**
* Check if a file exists at the given path
*/
const fileExists = async (filePath: string): Promise<boolean> => {
try {
await fs.access(filePath);
return true;
} catch {
return false;
}
};
/**
* Replace all occurrences of 'repopack' with 'repomix' in a string
*/
const replaceRepopackString = (content: string): string => {
return content.replace(/repopack/g, 'repomix').replace(/Repopack/g, 'Repomix');
};
/**
* Update file content by replacing 'repopack' with 'repomix'
*/
const updateFileContent = async (filePath: string): Promise<boolean> => {
const content = await fs.readFile(filePath, 'utf8');
const updatedContent = replaceRepopackString(content);
// Check if content needs to be updated
if (content !== updatedContent) {
await fs.writeFile(filePath, updatedContent, 'utf8');
const relativePath = path.relative(process.cwd(), filePath);
logger.log(`Updated repopack references in ${pc.cyan(relativePath)}`);
return true;
}
return false;
};
/**
* Parse JSON content, update instructionFilePath if exists
*/
const updateInstructionPath = (content: string): string => {
try {
const config = JSON.parse(content);
if (config.output?.instructionFilePath) {
config.output.instructionFilePath = config.output.instructionFilePath.replace('repopack', 'repomix');
}
// Also update output.filePath if it exists
if (config.output?.filePath) {
config.output.filePath = config.output.filePath.replace('repopack', 'repomix');
}
return JSON.stringify(config, null, 2);
} catch {
return content;
}
};
/**
* Get output file paths pairs
*/
const getOutputFilePaths = (rootDir: string): { oldPaths: string[]; newPaths: string[] } => {
const extensions = ['.txt', '.xml', '.md'];
const oldPaths = extensions.map((ext) => path.join(rootDir, `repopack-output${ext}`));
const newPaths = extensions.map((ext) => path.join(rootDir, `repomix-output${ext}`));
return { oldPaths, newPaths };
};
/**
* Migrate a single file from old path to new path
*/
const migrateFile = async (
oldPath: string,
newPath: string,
description: string,
isConfig = false,
): Promise<boolean> => {
if (!(await fileExists(oldPath))) {
return false;
}
const exists = await fileExists(newPath);
if (exists) {
const shouldOverwrite = await prompts.confirm({
message: `${description} already exists at ${newPath}. Do you want to overwrite it?`,
});
if (prompts.isCancel(shouldOverwrite) || !shouldOverwrite) {
logger.info(`Skipping migration of ${description}`);
return false;
}
}
try {
// Read and update content
let content = await fs.readFile(oldPath, 'utf8');
content = replaceRepopackString(content);
// For config files, also update instructionFilePath and output.filePath
if (isConfig) {
content = updateInstructionPath(content);
}
// Ensure the target directory exists
await fs.mkdir(path.dirname(newPath), { recursive: true });
// Write to new file
await fs.writeFile(newPath, content, 'utf8');
// Remove old file
await fs.unlink(oldPath);
const relativeOldPath = path.relative(process.cwd(), oldPath);
const relativeNewPath = path.relative(process.cwd(), newPath);
logger.log(`Renamed ${description} from ${relativeOldPath} to ${relativeNewPath}`);
return true;
} catch (error) {
logger.error(`Failed to migrate ${description}:`, error);
return false;
}
};
/**
* Update content of gitignore and repomixignore files
*/
const updateIgnoreFiles = async (rootDir: string): Promise<void> => {
const gitignorePath = path.join(rootDir, '.gitignore');
const repomixignorePath = path.join(rootDir, '.repomixignore');
if (await fileExists(gitignorePath)) {
const updated = await updateFileContent(gitignorePath);
if (!updated) {
logger.debug('No changes needed in .gitignore');
}
}
if (await fileExists(repomixignorePath)) {
const updated = await updateFileContent(repomixignorePath);
if (!updated) {
logger.debug('No changes needed in .repomixignore');
}
}
};
/**
* Get all migration related file paths
*/
const getMigrationPaths = (rootDir: string): MigrationPaths => {
const { oldPaths: oldOutputPaths, newPaths: newOutputPaths } = getOutputFilePaths(rootDir);
const oldGlobalDirectory = path.join(process.env.HOME || '', '.config', 'repopack');
const newGlobalDirectory = getGlobalDirectory();
return {
oldConfigPath: path.join(rootDir, 'repopack.config.json'),
newConfigPath: path.join(rootDir, 'repomix.config.json'),
oldIgnorePath: path.join(rootDir, '.repopackignore'),
newIgnorePath: path.join(rootDir, '.repomixignore'),
oldInstructionPath: path.join(rootDir, 'repopack-instruction.md'),
newInstructionPath: path.join(rootDir, 'repomix-instruction.md'),
oldOutputPaths,
newOutputPaths,
oldGlobalConfigPath: path.join(oldGlobalDirectory, 'repopack.config.json'),
newGlobalConfigPath: path.join(newGlobalDirectory, 'repomix.config.json'),
};
};
/**
* Migrate output files
*/
const migrateOutputFiles = async (oldPaths: string[], newPaths: string[]): Promise<string[]> => {
const migratedFiles: string[] = [];
for (let i = 0; i < oldPaths.length; i++) {
const oldPath = oldPaths[i];
const newPath = newPaths[i];
const ext = path.extname(oldPath);
if (await migrateFile(oldPath, newPath, `Output file (${ext})`)) {
migratedFiles.push(newPath);
}
}
return migratedFiles;
};
export const runMigrationAction = async (rootDir: string): Promise<MigrationResult> => {
const result: MigrationResult = {
configMigrated: false,
ignoreMigrated: false,
instructionMigrated: false,
outputFilesMigrated: [],
globalConfigMigrated: false,
};
try {
const paths = getMigrationPaths(rootDir);
// Check if migration is needed
const hasOldConfig = await fileExists(paths.oldConfigPath);
const hasOldIgnore = await fileExists(paths.oldIgnorePath);
const hasOldInstruction = await fileExists(paths.oldInstructionPath);
const hasOldGlobalConfig = await fileExists(paths.oldGlobalConfigPath);
const hasOldOutput = await Promise.all(paths.oldOutputPaths.map(fileExists)).then((results) =>
results.some((exists) => exists),
);
if (!hasOldConfig && !hasOldIgnore && !hasOldInstruction && !hasOldOutput && !hasOldGlobalConfig) {
logger.debug('No Repopack files found to migrate.');
return result;
}
// Show migration notice based on what needs to be migrated
let migrationMessage = `Found ${pc.green('Repopack')} `;
const items = [];
if (hasOldConfig || hasOldIgnore || hasOldInstruction || hasOldOutput) items.push('local configuration');
if (hasOldGlobalConfig) items.push('global configuration');
migrationMessage += `${items.join(' and ')}. Would you like to migrate to ${pc.green('Repomix')}?`;
// Confirm migration with user
const shouldMigrate = await prompts.confirm({
message: migrationMessage,
});
if (prompts.isCancel(shouldMigrate) || !shouldMigrate) {
logger.info('Migration cancelled.');
return result;
}
// Show migration notice
logger.info(pc.cyan('\nMigrating from Repopack to Repomix...'));
logger.log('');
// Migrate config file
if (hasOldConfig) {
result.configMigrated = await migrateFile(paths.oldConfigPath, paths.newConfigPath, 'Configuration file', true);
}
// Migrate global config file
if (hasOldGlobalConfig) {
result.globalConfigMigrated = await migrateFile(
paths.oldGlobalConfigPath,
paths.newGlobalConfigPath,
'Global configuration file',
true,
);
}
// Migrate ignore file
if (hasOldIgnore) {
result.ignoreMigrated = await migrateFile(paths.oldIgnorePath, paths.newIgnorePath, 'Ignore file');
}
// Migrate instruction file
if (hasOldInstruction) {
result.instructionMigrated = await migrateFile(
paths.oldInstructionPath,
paths.newInstructionPath,
'Instruction file',
);
}
// Migrate output files
if (hasOldOutput) {
result.outputFilesMigrated = await migrateOutputFiles(paths.oldOutputPaths, paths.newOutputPaths);
}
// Update content in gitignore and repomixignore
await updateIgnoreFiles(rootDir);
// Show success message
if (
result.configMigrated ||
result.ignoreMigrated ||
result.instructionMigrated ||
result.outputFilesMigrated.length > 0 ||
result.globalConfigMigrated
) {
logger.log('');
logger.success('✔ Migration completed successfully!');
logger.log('');
logger.info(
'You can now use Repomix commands as usual. The old Repopack files have been migrated to the new format.',
);
logger.log('');
}
return result;
} catch (error) {
if (error instanceof Error) {
result.error = error;
} else {
result.error = new Error(String(error));
}
logger.error('An error occurred during migration:', error);
return result;
}
};
</file>
<file path="src/cli/actions/versionAction.ts">
import { getVersion } from '../../core/file/packageJsonParse.js';
import { logger } from '../../shared/logger.js';
export const runVersionAction = async (): Promise<void> => {
const version = await getVersion();
logger.log(version);
};
</file>
<file path="src/cli/cliPrint.ts">
import path from 'node:path';
import pc from 'picocolors';
import type { RepomixConfigMerged } from '../config/configSchema.js';
import type { SuspiciousFileResult } from '../core/security/securityCheck.js';
import { logger } from '../shared/logger.js';
export const printSummary = (
totalFiles: number,
totalCharacters: number,
totalTokens: number,
outputPath: string,
suspiciousFilesResults: SuspiciousFileResult[],
config: RepomixConfigMerged,
) => {
let securityCheckMessage = '';
if (config.security.enableSecurityCheck) {
if (suspiciousFilesResults.length > 0) {
securityCheckMessage = pc.yellow(
`${suspiciousFilesResults.length.toLocaleString()} suspicious file(s) detected and excluded`,
);
} else {
securityCheckMessage = pc.white('✔ No suspicious files detected');
}
} else {
securityCheckMessage = pc.dim('Security check disabled');
}
logger.log(pc.white('📊 Pack Summary:'));
logger.log(pc.dim('────────────────'));
logger.log(`${pc.white(' Total Files:')} ${pc.white(totalFiles.toLocaleString())} files`);
logger.log(`${pc.white(' Total Chars:')} ${pc.white(totalCharacters.toLocaleString())} chars`);
logger.log(`${pc.white(' Total Tokens:')} ${pc.white(totalTokens.toLocaleString())} tokens`);
logger.log(`${pc.white(' Output:')} ${pc.white(outputPath)}`);
logger.log(`${pc.white(' Security:')} ${pc.white(securityCheckMessage)}`);
};
export const printSecurityCheck = (
rootDir: string,
suspiciousFilesResults: SuspiciousFileResult[],
config: RepomixConfigMerged,
) => {
if (!config.security.enableSecurityCheck) {
return;
}
logger.log(pc.white('🔎 Security Check:'));
logger.log(pc.dim('──────────────────'));
if (suspiciousFilesResults.length === 0) {
logger.log(`${pc.green('✔')} ${pc.white('No suspicious files detected.')}`);
} else {
logger.log(pc.yellow(`${suspiciousFilesResults.length} suspicious file(s) detected and excluded from the output:`));
suspiciousFilesResults.forEach((suspiciousFilesResult, index) => {
const relativeFilePath = path.relative(rootDir, suspiciousFilesResult.filePath);
logger.log(`${pc.white(`${index + 1}.`)} ${pc.white(relativeFilePath)}`);
logger.log(pc.dim(` - ${suspiciousFilesResult.messages.join('\n - ')}`));
});
logger.log(pc.yellow('\nThese files have been excluded from the output for security reasons.'));
logger.log(pc.yellow('Please review these files for potential sensitive information.'));
}
};
export const printTopFiles = (
fileCharCounts: Record<string, number>,
fileTokenCounts: Record<string, number>,
topFilesLength: number,
) => {
const topFilesLengthStrLen = topFilesLength.toString().length;
logger.log(pc.white(`📈 Top ${topFilesLength} Files by Character Count and Token Count:`));
logger.log(pc.dim(`─────────────────────────────────────────────────${'─'.repeat(topFilesLengthStrLen)}`));
const topFiles = Object.entries(fileCharCounts)
.sort((a, b) => b[1] - a[1])
.slice(0, topFilesLength);
topFiles.forEach(([filePath, charCount], index) => {
const tokenCount = fileTokenCounts[filePath];
const indexString = `${index + 1}.`.padEnd(3, ' ');
logger.log(
`${pc.white(`${indexString}`)} ${pc.white(filePath)} ${pc.dim(`(${charCount.toLocaleString()} chars, ${tokenCount.toLocaleString()} tokens)`)}`,
);
});
};
export const printCompletion = () => {
logger.log(pc.green('🎉 All Done!'));
logger.log(pc.white('Your repository has been successfully packed.'));
logger.log('');
logger.log(`💡 Repomix is now available in your browser! Try it at ${pc.underline('https://repomix.com')}`);
};
</file>
<file path="src/cli/cliSpinner.ts">
import cliSpinners from 'cli-spinners';
import logUpdate from 'log-update';
import pc from 'picocolors';
import type { CliOptions } from './types.js';
export class Spinner {
private spinner = cliSpinners.dots;
private message: string;
private currentFrame = 0;
private interval: ReturnType<typeof setInterval> | null = null;
private readonly isQuiet: boolean;
constructor(message: string, cliOptions: CliOptions) {
this.message = message;
// If the user has specified the verbose flag, don't show the spinner
this.isQuiet = cliOptions.quiet || cliOptions.verbose || false;
}
start(): void {
if (this.isQuiet) {
return;
}
const frames = this.spinner.frames;
const framesLength = frames.length;
this.interval = setInterval(() => {
this.currentFrame++;
const frame = frames[this.currentFrame % framesLength];
logUpdate(`${pc.cyan(frame)} ${this.message}`);
}, this.spinner.interval);
}
update(message: string): void {
if (this.isQuiet) {
return;
}
this.message = message;
}
stop(finalMessage: string): void {
if (this.isQuiet) {
return;
}
if (this.interval) {
clearInterval(this.interval);
this.interval = null;
}
logUpdate(finalMessage);
logUpdate.done();
}
succeed(message: string): void {
if (this.isQuiet) {
return;
}
this.stop(`${pc.green('✔')} ${message}`);
}
fail(message: string): void {
if (this.isQuiet) {
return;
}
this.stop(`${pc.red('✖')} ${message}`);
}
}
</file>
<file path="src/config/configLoad.ts">
import * as fs from 'node:fs/promises';
import path from 'node:path';
import JSON5 from 'json5';
import pc from 'picocolors';
import { RepomixError, rethrowValidationErrorIfZodError } from '../shared/errorHandle.js';
import { logger } from '../shared/logger.js';
import {
type RepomixConfigCli,
type RepomixConfigFile,
type RepomixConfigMerged,
defaultConfig,
defaultFilePathMap,
repomixConfigFileSchema,
repomixConfigMergedSchema,
} from './configSchema.js';
import { getGlobalDirectory } from './globalDirectory.js';
const defaultConfigPath = 'repomix.config.json';
const getGlobalConfigPath = () => {
return path.join(getGlobalDirectory(), 'repomix.config.json');
};
export const loadFileConfig = async (rootDir: string, argConfigPath: string | null): Promise<RepomixConfigFile> => {
let useDefaultConfig = false;
let configPath = argConfigPath;
if (!configPath) {
useDefaultConfig = true;
configPath = defaultConfigPath;
}
const fullPath = path.resolve(rootDir, configPath);
logger.trace('Loading local config from:', fullPath);
// Check local file existence
const isLocalFileExists = await fs
.stat(fullPath)
.then((stats) => stats.isFile())
.catch(() => false);
if (isLocalFileExists) {
return await loadAndValidateConfig(fullPath);
}
if (useDefaultConfig) {
// Try to load global config
const globalConfigPath = getGlobalConfigPath();
logger.trace('Loading global config from:', globalConfigPath);
const isGlobalFileExists = await fs
.stat(globalConfigPath)
.then((stats) => stats.isFile())
.catch(() => false);
if (isGlobalFileExists) {
return await loadAndValidateConfig(globalConfigPath);
}
logger.log(
pc.dim(
`No custom config found at ${configPath} or global config at ${globalConfigPath}.\nYou can add a config file for additional settings. Please check https://github.com/yamadashy/repomix for more information.`,
),
);
return {};
}
throw new RepomixError(`Config file not found at ${configPath}`);
};
const loadAndValidateConfig = async (filePath: string): Promise<RepomixConfigFile> => {
try {
const fileContent = await fs.readFile(filePath, 'utf-8');
const config = JSON5.parse(fileContent);
return repomixConfigFileSchema.parse(config);
} catch (error) {
rethrowValidationErrorIfZodError(error, 'Invalid config schema');
if (error instanceof SyntaxError) {
throw new RepomixError(`Invalid JSON5 in config file ${filePath}: ${error.message}`);
}
if (error instanceof Error) {
throw new RepomixError(`Error loading config from ${filePath}: ${error.message}`);
}
throw new RepomixError(`Error loading config from ${filePath}`);
}
};
export const mergeConfigs = (
cwd: string,
fileConfig: RepomixConfigFile,
cliConfig: RepomixConfigCli,
): RepomixConfigMerged => {
logger.trace('Default config:', defaultConfig);
const baseConfig = defaultConfig;
// If the output file path is not provided in the config file or CLI, use the default file path for the style
if (cliConfig.output?.filePath == null && fileConfig.output?.filePath == null) {
const style = cliConfig.output?.style || fileConfig.output?.style || baseConfig.output.style;
baseConfig.output.filePath = defaultFilePathMap[style];
logger.trace('Default output file path is set to:', baseConfig.output.filePath);
}
const mergedConfig = {
cwd,
output: {
...baseConfig.output,
...fileConfig.output,
...cliConfig.output,
},
include: [...(baseConfig.include || []), ...(fileConfig.include || []), ...(cliConfig.include || [])],
ignore: {
...baseConfig.ignore,
...fileConfig.ignore,
...cliConfig.ignore,
customPatterns: [
...(baseConfig.ignore.customPatterns || []),
...(fileConfig.ignore?.customPatterns || []),
...(cliConfig.ignore?.customPatterns || []),
],
},
security: {
...baseConfig.security,
...fileConfig.security,
...cliConfig.security,
},
};
try {
return repomixConfigMergedSchema.parse(mergedConfig);
} catch (error) {
rethrowValidationErrorIfZodError(error, 'Invalid merged config');
throw error;
}
};
</file>
<file path="src/config/globalDirectory.ts">
import os from 'node:os';
import path from 'node:path';
export const getGlobalDirectory = () => {
if (process.platform === 'win32') {
const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
return path.join(localAppData, 'Repomix');
}
if (process.env.XDG_CONFIG_HOME) {
return path.join(process.env.XDG_CONFIG_HOME, 'repomix');
}
return path.join(os.homedir(), '.config', 'repomix');
};
</file>
<file path="src/core/file/workers/fileCollectWorker.ts">
import * as fs from 'node:fs/promises';
import path from 'node:path';
import iconv from 'iconv-lite';
import { isBinary } from 'istextorbinary';
import jschardet from 'jschardet';
import pc from 'picocolors';
import { logger } from '../../../shared/logger.js';
// Maximum file size to process (50MB)
// This prevents out-of-memory errors when processing very large files
export const MAX_FILE_SIZE = 50 * 1024 * 1024;
export interface FileCollectTask {
filePath: string;
rootDir: string;
}
export default async ({ filePath, rootDir }: FileCollectTask) => {
const fullPath = path.resolve(rootDir, filePath);
const content = await readRawFile(fullPath);
if (content) {
return {
path: filePath,
content,
};
}
return null;
};
const readRawFile = async (filePath: string): Promise<string | null> => {
try {
const stats = await fs.stat(filePath);
if (stats.size > MAX_FILE_SIZE) {
const sizeMB = (stats.size / 1024 / 1024).toFixed(1);
logger.log('');
logger.log('⚠️ Large File Warning:');
logger.log('──────────────────────');
logger.log(`File exceeds size limit: ${sizeMB}MB > ${MAX_FILE_SIZE / 1024 / 1024}MB (${filePath})`);
logger.log(pc.dim('Add this file to .repomixignore if you want to exclude it permanently'));
logger.log('');
return null;
}
if (isBinary(filePath)) {
logger.debug(`Skipping binary file: ${filePath}`);
return null;
}
logger.trace(`Reading file: ${filePath}`);
const buffer = await fs.readFile(filePath);
if (isBinary(null, buffer)) {
logger.debug(`Skipping binary file (content check): ${filePath}`);
return null;
}
const encoding = jschardet.detect(buffer).encoding || 'utf-8';
const content = iconv.decode(buffer, encoding);
return content;
} catch (error) {
logger.warn(`Failed to read file: ${filePath}`, error);
return null;
}
};
</file>
<file path="src/core/file/workers/fileProcessWorker.ts">
import type { RepomixConfigMerged } from '../../../config/configSchema.js';
import { logger } from '../../../shared/logger.js';
import { parseFile } from '../../treeSitter/parseFile.js';
import { getFileManipulator } from '../fileManipulate.js';
import type { ProcessedFile, RawFile } from '../fileTypes.js';
export interface FileProcessTask {
rawFile: RawFile;
config: RepomixConfigMerged;
}
export default async ({ rawFile, config }: FileProcessTask): Promise<ProcessedFile> => {
const processedContent = await processContent(rawFile, config);
return {
path: rawFile.path,
content: processedContent,
};
};
export const processContent = async (rawFile: RawFile, config: RepomixConfigMerged) => {
const processStartAt = process.hrtime.bigint();
let processedContent = rawFile.content;
const manipulator = getFileManipulator(rawFile.path);
logger.trace(`Processing file: ${rawFile.path}`);
if (manipulator && config.output.removeComments) {
processedContent = manipulator.removeComments(processedContent);
}
if (config.output.removeEmptyLines && manipulator) {
processedContent = manipulator.removeEmptyLines(processedContent);
}
processedContent = processedContent.trim();
if (config.output.compress) {
try {
const parsedContent = await parseFile(processedContent, rawFile.path, config);
if (parsedContent === undefined) {
logger.trace(`Failed to parse ${rawFile.path} in compressed mode. Using original content.`);
}
processedContent = parsedContent ?? processedContent;
} catch (error: unknown) {
const message = error instanceof Error ? error.message : String(error);
logger.error(`Error parsing ${rawFile.path} in compressed mode: ${message}`);
//re-throw error
throw error;
}
} else if (config.output.showLineNumbers) {
const lines = processedContent.split('\n');
const padding = lines.length.toString().length;
const numberedLines = lines.map((line, i) => `${(i + 1).toString().padStart(padding)}: ${line}`);
processedContent = numberedLines.join('\n');
}
const processEndAt = process.hrtime.bigint();
logger.trace(`Processed file: ${rawFile.path}. Took: ${(Number(processEndAt - processStartAt) / 1e6).toFixed(2)}ms`);
return processedContent;
};
</file>
<file path="src/core/file/fileCollect.ts">
import pc from 'picocolors';
import { logger } from '../../shared/logger.js';
import { initPiscina } from '../../shared/processConcurrency.js';
import type { RepomixProgressCallback } from '../../shared/types.js';
import type { RawFile } from './fileTypes.js';
import type { FileCollectTask } from './workers/fileCollectWorker.js';
const initTaskRunner = (numOfTasks: number) => {
const pool = initPiscina(numOfTasks, new URL('./workers/fileCollectWorker.js', import.meta.url).href);
return (task: FileCollectTask) => pool.run(task);
};
export const collectFiles = async (
filePaths: string[],
rootDir: string,
progressCallback: RepomixProgressCallback = () => {},
deps = {
initTaskRunner,
},
): Promise<RawFile[]> => {
const runTask = deps.initTaskRunner(filePaths.length);
const tasks = filePaths.map(
(filePath) =>
({
filePath,
rootDir,
}) satisfies FileCollectTask,
);
try {
const startTime = process.hrtime.bigint();
logger.trace(`Starting file collection for ${filePaths.length} files using worker pool`);
let completedTasks = 0;
const totalTasks = tasks.length;
const results = await Promise.all(
tasks.map((task) =>
runTask(task).then((result) => {
completedTasks++;
progressCallback(`Collect file... (${completedTasks}/${totalTasks}) ${pc.dim(task.filePath)}`);
logger.trace(`Collect files... (${completedTasks}/${totalTasks}) ${task.filePath}`);
return result;
}),
),
);
const endTime = process.hrtime.bigint();
const duration = Number(endTime - startTime) / 1e6;
logger.trace(`File collection completed in ${duration.toFixed(2)}ms`);
return results.filter((file): file is RawFile => file !== null);
} catch (error) {
logger.error('Error during file collection:', error);
throw error;
}
};
</file>
<file path="src/core/file/fileProcess.ts">
import pc from 'picocolors';
import type { RepomixConfigMerged } from '../../config/configSchema.js';
import { logger } from '../../shared/logger.js';
import { initPiscina } from '../../shared/processConcurrency.js';
import type { RepomixProgressCallback } from '../../shared/types.js';
import { type FileManipulator, getFileManipulator } from './fileManipulate.js';
import type { ProcessedFile, RawFile } from './fileTypes.js';
import type { FileProcessTask } from './workers/fileProcessWorker.js';
type GetFileManipulator = (filePath: string) => FileManipulator | nu