Skip to main content
Glama
MIT License
27,120
19,780
  • Linux
  • Apple
remoteAction.ts•7.37 kB
import * as fs from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; import pc from 'picocolors'; import { execGitShallowClone } from '../../core/git/gitCommand.js'; import { downloadGitHubArchive, isArchiveDownloadSupported } from '../../core/git/gitHubArchive.js'; import { getRemoteRefs } from '../../core/git/gitRemoteHandle.js'; import { isGitHubRepository, parseGitHubRepoInfo, parseRemoteValue } from '../../core/git/gitRemoteParse.js'; import { isGitInstalled } from '../../core/git/gitRepositoryHandle.js'; import { RepomixError } from '../../shared/errorHandle.js'; import { logger } from '../../shared/logger.js'; import { Spinner } from '../cliSpinner.js'; import type { CliOptions } from '../types.js'; import { type DefaultActionRunnerResult, runDefaultAction } from './defaultAction.js'; export const runRemoteAction = async ( repoUrl: string, cliOptions: CliOptions, deps = { isGitInstalled, execGitShallowClone, getRemoteRefs, runDefaultAction, downloadGitHubArchive, isGitHubRepository, parseGitHubRepoInfo, isArchiveDownloadSupported, }, ): Promise<DefaultActionRunnerResult> => { let tempDirPath = await createTempDirectory(); let result: DefaultActionRunnerResult; let downloadMethod: 'archive' | 'git' = 'git'; try { // Check if this is a GitHub repository and archive download is supported const githubRepoInfo = deps.parseGitHubRepoInfo(repoUrl); const shouldTryArchive = githubRepoInfo && deps.isArchiveDownloadSupported(githubRepoInfo); if (shouldTryArchive) { // Try GitHub archive download first const spinner = new Spinner('Downloading repository archive...', cliOptions); try { spinner.start(); // Override ref with CLI option if provided const repoInfoWithBranch = { ...githubRepoInfo, ref: cliOptions.remoteBranch ?? githubRepoInfo.ref, }; await deps.downloadGitHubArchive( repoInfoWithBranch, tempDirPath, { timeout: 60000, // 1 minute timeout for large repos retries: 2, }, (progress) => { if (progress.percentage !== null) { spinner.update(`Downloading repository archive... (${progress.percentage}%)`); } else { // Show downloaded bytes when percentage is not available const downloadedMB = (progress.downloaded / 1024 / 1024).toFixed(1); spinner.update(`Downloading repository archive... (${downloadedMB} MB)`); } }, ); downloadMethod = 'archive'; spinner.succeed('Repository archive downloaded successfully!'); logger.log(''); } catch (archiveError) { spinner.fail('Archive download failed, trying git clone...'); logger.trace('Archive download error:', (archiveError as Error).message); // Clear the temp directory for git clone attempt await cleanupTempDirectory(tempDirPath); tempDirPath = await createTempDirectory(); // Fall back to git clone await performGitClone(repoUrl, tempDirPath, cliOptions, deps); downloadMethod = 'git'; } } else { // Use git clone directly await performGitClone(repoUrl, tempDirPath, cliOptions, deps); downloadMethod = 'git'; } // Run the default action on the downloaded/cloned repository result = await deps.runDefaultAction([tempDirPath], tempDirPath, cliOptions); // Copy output file only when not in stdout mode // In stdout mode, output is written directly to stdout without creating a file, // so attempting to copy a non-existent file would cause an error and exit code 1 if (!cliOptions.stdout) { await copyOutputToCurrentDirectory(tempDirPath, process.cwd(), result.config.output.filePath); } logger.trace(`Repository obtained via ${downloadMethod} method`); } finally { // Cleanup the temporary directory await cleanupTempDirectory(tempDirPath); } return result; }; /** * Performs git clone operation with spinner and error handling */ const performGitClone = async ( repoUrl: string, tempDirPath: string, cliOptions: CliOptions, deps: { isGitInstalled: typeof isGitInstalled; getRemoteRefs: typeof getRemoteRefs; execGitShallowClone: typeof execGitShallowClone; }, ): Promise<void> => { // Check if git is installed only when we actually need to use git if (!(await deps.isGitInstalled())) { throw new RepomixError('Git is not installed or not in the system PATH.'); } // Get remote refs let refs: string[] = []; try { refs = await deps.getRemoteRefs(parseRemoteValue(repoUrl).repoUrl); logger.trace(`Retrieved ${refs.length} refs from remote repository`); } catch (error) { logger.trace('Failed to get remote refs, proceeding without them:', (error as Error).message); } // Parse the remote URL with the refs information const parsedFields = parseRemoteValue(repoUrl, refs); const spinner = new Spinner('Cloning repository...', cliOptions); try { spinner.start(); // Clone the repository await cloneRepository(parsedFields.repoUrl, tempDirPath, cliOptions.remoteBranch || parsedFields.remoteBranch, { execGitShallowClone: deps.execGitShallowClone, }); spinner.succeed('Repository cloned successfully!'); logger.log(''); } catch (error) { spinner.fail('Error during repository cloning. cleanup...'); throw error; } }; export const createTempDirectory = async (): Promise<string> => { const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'repomix-')); logger.trace(`Created temporary directory. (path: ${pc.dim(tempDir)})`); return tempDir; }; export const cloneRepository = async ( url: string, directory: string, remoteBranch?: string, deps = { execGitShallowClone, }, ): Promise<void> => { logger.log(`Clone repository: ${url} to temporary directory. ${pc.dim(`path: ${directory}`)}`); logger.log(''); try { await deps.execGitShallowClone(url, directory, remoteBranch); } catch (error) { throw new RepomixError(`Failed to clone repository: ${(error as Error).message}`); } }; export const cleanupTempDirectory = async (directory: string): Promise<void> => { logger.trace(`Cleaning up temporary directory: ${directory}`); await fs.rm(directory, { recursive: true, force: true }); }; export const copyOutputToCurrentDirectory = async ( sourceDir: string, targetDir: string, outputFileName: string, ): Promise<void> => { const sourcePath = path.resolve(sourceDir, outputFileName); const targetPath = path.resolve(targetDir, outputFileName); // Skip copy if source and target are the same // This can happen when an absolute path is specified for the output file if (sourcePath === targetPath) { logger.trace(`Source and target are the same (${sourcePath}), skipping copy`); return; } try { logger.trace(`Copying output file from: ${sourcePath} to: ${targetPath}`); // Create target directory if it doesn't exist await fs.mkdir(path.dirname(targetPath), { recursive: true }); await fs.copyFile(sourcePath, targetPath); } catch (error) { throw new RepomixError(`Failed to copy output file: ${(error as Error).message}`); } };

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/yamadashy/repomix'

If you have feedback or need assistance with the MCP directory API, please join our Discord server