E5E9 Feature/local repo support by ddreamboy · Pull Request #9 · addyosmani/git2txt · GitHub
[go: up one dir, main page]

Skip to content
Open
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
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Convert GitHub repositories to text files with ease. This CLI tool downloads a r
## Features

- 📥 Download any public GitHub repository
- 🔒 Process local Git repositories (including private ones)
- 📝 Convert repository contents to a single text file
- ⚡ Automatic binary file exclusion
- 🔧 Configurable file size threshold
Expand All @@ -31,6 +32,9 @@ git2txt username/repository

# SSH format
git2txt git@github.com:username/repository

# Local repository path (requires --local flag)
git2txt /path/to/local/repo --local
```

### URL Format Support
Expand All @@ -42,13 +46,15 @@ The tool accepts these GitHub repository URL formats:
- SSH URLs: `git@github.com:username/repository`
- URLs with or without `.git` suffix
- URLs with or without trailing slashes
- Local paths with `--local` flag: `/path/to/repo` or `./relative/path`

### Options

```
--output, -o Specify output file path (default: repo-name.txt)
--threshold, -t Set file size threshold in MB (default: 0.1)
--include-all Include all files regardless of size or type
--local Process local repository path
--debug Enable debug mode with verbose logging
--help Show help
--version Show version
Expand Down Expand Up @@ -78,6 +84,15 @@ git2txt username/repository --include-all

# Enable debug output
git2txt username/repository --debug

# Using local repository path
git2txt /path/to/local/repo --local

# Local repository with custom output
git2txt ./my-repo --local --output=output.txt

# Local repository with custom threshold
git2txt ./my-repo --local --threshold=2
```

## Default Behavior
Expand Down Expand Up @@ -115,4 +130,4 @@ Contributions are welcome! Please see our [Contributing Guide](CONTRIBUTING.md)

## License

MIT
MIT
113 changes: 74 additions & 39 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,24 @@ import { promisify } from 'util';

const execAsync = promisify(exec);

// CLI help text with usage instructions and examples
// Обновленный текст помощи с информацией о локальных репозиториях
const helpText = `
${chalk.bold('Usage')}
$ git2txt <repository-url>
$ git2txt <repository-url-or-path>

${chalk.bold('Options')}
--output, -o Specify output file path
--threshold, -t Set file size threshold in MB (default: 0.5)
--include-all Include all files regardless of size or type
--local Process local repository path
--debug Enable debug mode with verbose logging
--help Show help
--version Show version

${chalk.bold('Examples')}
$ git2txt https://github.com/username/repository
$ git2txt https://github.com/username/repository --output=output.txt
$ git2txt /path/to/local/repo --local
$ git2txt ./my-repo --local --output=output.txt
`;

/**
Expand Down Expand Up @@ -80,6 +82,10 @@ export const cli = meow(helpText, {
type: 'boolean',
default: false
},
local: {
type: 'boolean',
default: false
},
debug: {
type: 'boolean',
default: false
Expand Down Expand Up @@ -120,22 +126,48 @@ function normalizeGitHubUrl(url) {
}

/**
* Validates the command line input
* @param {string[]} input - Command line arguments
* @returns {Promise<string>} Validated repository URL
* @throws {Error} If input is missing or invalid
* Проверяет существование локального репозитория
* @param {string} path - Путь к локальному репозиторию
* @returns {Promise<boolean>} Существует ли репозиторий
*/
export async function validateInput(input) {
async function validateLocalRepo(repoPath) {
try {
const stats = await fs.stat(repoPath);
const gitPath = path.join(repoPath, '.git');
const gitStats = await fs.stat(gitPath);

return stats.isDirectory() && gitStats.isDirectory();
} catch (error) {
return false;
}
}

/**
* Валидирует входные данные с учетом локальных репозиториев
* @param {string[]} input - Аргументы командной строки
* @param {Object} flags - Флаги CLI
* @returns {Promise<Object>} Объект с валидированными данными
*/
export async function validateInput(input, flags) {
if (!input || input.length === 0) {
throw new Error('Repository URL is required');
throw new Error('Repository URL or path is required');
}

const url = input[0];
if (!url.includes('github.com') && !url.match(/^[\w-]+\/[\w-]+$/)) {
throw new Error('Only GitHub repositories are supported');
const source = input[0];

if (flags.local) {
const isValidRepo = await validateLocalRepo(source);
if (!isValidRepo) {
throw new Error('Invalid local repository path. Make sure it\'s a Git repository.');
}
return { type: 'local', path: source };
}

if (!source.includes('github.com') && !source.match(/^[\w-]+\/[\w-]+$/)) {
throw new Error('Only GitHub repositories or local paths (with --local flag) are supported');
}

return url;
return { type: 'remote', url: source };
}

/**
Expand Down Expand Up @@ -340,37 +372,40 @@ export async function cleanup(directory) {
* @returns {Promise<void>}
*/
export async function main() {
let tempDir;
try {
const url = await validateInput(cli.input);
if (process.env.NODE_ENV !== 'test') {
const result = await downloadRepository(url);
tempDir = result.tempDir;

const outputPath = cli.flags.output || `${result.repoName}.txt`;
const content = await processFiles(tempDir, {
threshold: cli.flags.threshold,
includeAll: cli.flags.includeAll
});

if (!content) {
throw new Error('No content was generated from the repository');
}
const result = await validateInput(cli.input, cli.flags);
let targetDir;
let repoName;

await writeOutput(content, outputPath);
}
} catch (error) {
if (process.env.NODE_ENV === 'test') {
throw error;
if (result.type === 'local') {
targetDir = result.path;
repoName = path.basename(result.path);
} else {
console.error(chalk.red('\nAn unexpected error occurred:'));
console.error(error.message || error);
exit(1);
const downloadResult = await downloadRepository(result.url);
targetDir = downloadResult.tempDir;
repoName = downloadResult.repoName;
}

const outputPath = cli.flags.output || `${repoName}.txt`;
const content = await processFiles(targetDir, {
threshold: cli.flags.threshold,
includeAll: cli.flags.includeAll
});

if (!content) {
throw new Error('No content was generated from the repository');
}
} finally {
if (tempDir) {
await cleanup(tempDir);

await writeOutput(content, outputPath);

// Очищаем временные файлы только для удаленных репозиториев
if (result.type === 'remote') {
await cleanup(targetDir);
}
} catch (error) {
console.error(chalk.red('\nAn error occurred:'));
console.error(error.message || error);
process.exit(1);
}
}

Expand Down
0