Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![npm](https://img.shields.io/npm/l/repopack.svg?maxAge=1000)](https://github.com/yamadashy/repopack/blob/master/LICENSE.md)
[![node](https://img.shields.io/node/v/repopack.svg?maxAge=1000)](https://www.npmjs.com/package/repopack)

Repopack is a powerful tool that packs your entire repository into a single, AI-friendly file. Perfect for when you need to feed your codebase to Large Language Models (LLMs) or other AI tools.
Repopack is a powerful tool that packs your entire repository into a single, AI-friendly file. Perfect for when you need to feed your codebase to Large Language Models (LLMs) or other AI tools. It now includes a security check feature to detect potentially sensitive information in your files.



Expand All @@ -15,6 +15,7 @@ Repopack is a powerful tool that packs your entire repository into a single, AI-
- **Simple to Use**: Just one command to pack your entire repository.
- **Customizable**: Easily configure what to include or exclude.
- **Git-Aware**: Automatically respects your .gitignore files.
- **Security Check**: Detects potentially sensitive information in your files.



Expand Down Expand Up @@ -91,6 +92,26 @@ npx repopack src



## 🔍 Security Check

Repopack now includes a security check feature that uses SecretLint to detect potentially sensitive information in your files. This feature helps you identify possible security risks before sharing your packed repository.

The security check results will be displayed in the CLI output after the packing process is complete. If any suspicious files are detected, you'll see a list of these files along with a warning message.

Example output:

```
🔍 Security Check:
──────────────────
2 suspicious file(s) detected:
1. src/config.js
2. tests/testData.json

Please review these files for potential sensitive information.
```



## ⚙️ Configuration

Create a `repopack.config.json` file in your project root for custom configurations. Here's an explanation of the configuration options:
Expand All @@ -99,7 +120,7 @@ Create a `repopack.config.json` file in your project root for custom configurati
|--------|-------------|---------|
|`output.filePath`| The name of the output file | `"repopack-output.txt"` |
|`output.headerText`| Custom text to include in the file header |`null`|
|`output.removeComments`| Whether to remove comments from supported file types. Suppurts python | `false` |
|`output.removeComments`| Whether to remove comments from supported file types | `false` |
|`output.topFilesLength`| Number of top files to display in the summary. If set to 0, no summary will be displayed |`5`|
|`ignore.useDefaultPatterns`| Whether to use default ignore patterns |`true`|
|`ignore.customPatterns`| Additional patterns to ignore |`[]`|
Expand Down
54 changes: 51 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
},
"type": "module",
"dependencies": {
"@secretlint/core": "^8.2.4",
"@secretlint/secretlint-rule-preset-recommend": "^8.2.4",
"cli-spinners": "^2.9.2",
"commander": "^7.1.0",
"iconv-lite": "^0.6.3",
Expand All @@ -62,8 +64,8 @@
"devDependencies": {
"@eslint/js": "^9.7.0",
"@types/eslint": "~8.56.10",
"@types/eslint__js": "~8.42.3",
"@types/eslint-config-prettier": "~6.11.3",
"@types/eslint__js": "~8.42.3",
"@types/node": "^20.14.10",
"@types/strip-comments": "^2.0.4",
"@typescript-eslint/eslint-plugin": "^7.16.0",
Expand Down
15 changes: 15 additions & 0 deletions src/cli/cliOutput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,21 @@ export function printSummary(totalFiles: number, totalCharacters: number, output
console.log(`${pc.white(' Output:')} ${pc.white(outputPath)}`);
}

export function printSecurityCheck(suspiciousFiles: string[]) {
console.log(pc.white('🔎 Security Check:'));
console.log(pc.dim('──────────────────'));

if (suspiciousFiles.length === 0) {
console.log(pc.green('✔') + ' ' + pc.white('No suspicious files detected.'));
} else {
console.log(pc.yellow(`${suspiciousFiles.length} suspicious file(s) detected:`));
suspiciousFiles.forEach((file, index) => {
console.log(`${pc.white(`${index + 1}.`)} ${pc.white(file)}`);
});
console.log(pc.yellow('\nPlease review these files for potential sensitive information.'));
}
}

export function printTopFiles(fileCharCounts: Record<string, number>, topFilesLength: number) {
console.log(pc.white(`📈 Top ${topFilesLength} Files by Character Count:`));
console.log(pc.dim('──────────────────────────────────'));
Expand Down
7 changes: 5 additions & 2 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { getVersion } from '../utils/packageJsonUtils.js';
import Spinner from '../utils/spinner.js';
import pc from 'picocolors';
import { handleError } from '../utils/errorHandler.js';
import { printSummary, printTopFiles, printCompletion } from './cliOutput.js';
import { printSummary, printTopFiles, printCompletion, printSecurityCheck } from './cliOutput.js';

interface CliOptions extends OptionValues {
version?: boolean;
Expand Down Expand Up @@ -59,7 +59,7 @@ async function executeAction(directory: string, options: CliOptions) {
spinner.start();

try {
const { totalFiles, totalCharacters, fileCharCounts } = await pack(targetPath, config);
const { totalFiles, totalCharacters, fileCharCounts, suspiciousFiles } = await pack(targetPath, config);
spinner.succeed('Packing completed successfully!');
console.log('');

Expand All @@ -68,6 +68,9 @@ async function executeAction(directory: string, options: CliOptions) {
console.log('');
}

printSecurityCheck(suspiciousFiles);
console.log('');

printSummary(totalFiles, totalCharacters, config.output.filePath);
console.log('');

Expand Down
72 changes: 53 additions & 19 deletions src/core/packager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from '../utils/gitignoreUtils.js';
import { generateOutput as defaultGenerateOutput } from './outputGenerator.js';
import { defaultIgnoreList } from '../utils/defaultIgnore.js';
import { checkFileWithSecretLint, createSecretLintConfig } from '../utils/secretLintUtils.js';

export interface Dependencies {
getGitignorePatterns: typeof defaultGetGitignorePatterns;
Expand All @@ -21,6 +22,7 @@ export interface PackResult {
totalFiles: number;
totalCharacters: number;
fileCharCounts: Record<string, number>;
suspiciousFiles: string[];
}

export async function pack(
Expand All @@ -33,18 +35,24 @@ export async function pack(
generateOutput: defaultGenerateOutput,
},
): Promise<PackResult> {
// Get ignore patterns
const gitignorePatterns = await deps.getGitignorePatterns(rootDir);

const ignorePatterns = getIgnorePatterns(gitignorePatterns, config);
const ignoreFilter = deps.createIgnoreFilter(ignorePatterns);

const packedFiles = await packDirectory(rootDir, '', config, ignoreFilter, deps);
// Get all file paths in the directory
const filePaths = await getFilePaths(rootDir, '', ignoreFilter);

const totalFiles = packedFiles.length;
const totalCharacters = packedFiles.reduce((sum, file) => sum + file.content.length, 0);
// Perform security check
const suspiciousFiles = await performSecurityCheck(filePaths, rootDir);

// Pack files and generate output
const packedFiles = await packFiles(filePaths, rootDir, config, deps);
await deps.generateOutput(rootDir, config, packedFiles);

// Metrics
const totalFiles = packedFiles.length;
const totalCharacters = packedFiles.reduce((sum, file) => sum + file.content.length, 0);
const fileCharCounts: Record<string, number> = {};
packedFiles.forEach((file) => {
fileCharCounts[file.path] = file.content.length;
Expand All @@ -54,6 +62,7 @@ export async function pack(
totalFiles,
totalCharacters,
fileCharCounts,
suspiciousFiles,
};
}

Expand All @@ -68,30 +77,55 @@ function getIgnorePatterns(gitignorePatterns: string[], config: RepopackConfigMe
return ignorePatterns;
}

async function packDirectory(
dir: string,
relativePath: string,
config: RepopackConfigMerged,
ignoreFilter: IgnoreFilter,
deps: Dependencies,
): Promise<{ path: string; content: string }[]> {
async function getFilePaths(dir: string, relativePath: string, ignoreFilter: IgnoreFilter): Promise<string[]> {
const entries = await fs.readdir(dir, { withFileTypes: true });
const packedFiles: { path: string; content: string }[] = [];
const filePaths: string[] = [];

for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
const entryRelativePath = path.join(relativePath, entry.name);

if (!ignoreFilter(entryRelativePath)) continue;

if (entry.isDirectory()) {
const subDirFiles = await packDirectory(fullPath, entryRelativePath, config, ignoreFilter, deps);
packedFiles.push(...subDirFiles);
const subDirPaths = await getFilePaths(path.join(dir, entry.name), entryRelativePath, ignoreFilter);
filePaths.push(...subDirPaths);
} else {
const content = await deps.processFile(fullPath, config);
if (content) {
packedFiles.push({ path: entryRelativePath, content });
}
filePaths.push(entryRelativePath);
}
}

return filePaths;
}

async function performSecurityCheck(filePaths: string[], rootDir: string): Promise<string[]> {
const secretLintConfig = createSecretLintConfig();
const suspiciousFiles: string[] = [];

for (const filePath of filePaths) {
const fullPath = path.join(rootDir, filePath);
const content = await fs.readFile(fullPath, 'utf-8');
const isSuspicious = await checkFileWithSecretLint(fullPath, content, secretLintConfig);
if (isSuspicious) {
suspiciousFiles.push(filePath);
}
}

return suspiciousFiles;
}

async function packFiles(
filePaths: string[],
rootDir: string,
config: RepopackConfigMerged,
deps: Dependencies,
): Promise<{ path: string; content: string }[]> {
const packedFiles: { path: string; content: string }[] = [];

for (const filePath of filePaths) {
const fullPath = path.join(rootDir, filePath);
const content = await deps.processFile(fullPath, config);
if (content) {
packedFiles.push({ path: filePath, content });
}
}

Expand Down
34 changes: 34 additions & 0 deletions src/utils/secretLintUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { SecretLintCoreConfig } from '@secretlint/types';
import { lintSource } from '@secretlint/core';
import { creator } from '@secretlint/secretlint-rule-preset-recommend';

export async function checkFileWithSecretLint(
filePath: string,
content: string,
config: SecretLintCoreConfig,
): Promise<boolean> {
const result = await lintSource({
source: {
filePath: filePath,
content: content,
ext: filePath.split('.').pop() || '',
contentType: 'text',
},
options: {
config: config,
},
});

return result.messages.length > 0;
}

export function createSecretLintConfig(): SecretLintCoreConfig {
return {
rules: [
{
id: '@secretlint/secretlint-rule-preset-recommend',
rule: creator,
},
],
};
}
Loading