UNPKG

repomix

Version:

A tool to pack repository contents to single file for AI consumption

299 lines (298 loc) 11.1 kB
import path from 'node:path'; import * as v from 'valibot'; import { loadFileConfig, mergeConfigs } from '../../config/configLoad.js'; import { repomixConfigCliSchema, } from '../../config/configSchema.js'; import { readFilePathsFromStdin } from '../../core/file/fileStdin.js'; import { pack } from '../../core/packager.js'; import { generateDefaultSkillName } from '../../core/skill/skillUtils.js'; import { RepomixError, rethrowValidationErrorIfSchemaError } from '../../shared/errorHandle.js'; import { logger } from '../../shared/logger.js'; import { splitPatterns } from '../../shared/patternUtils.js'; import { reportResults } from '../cliReport.js'; import { Spinner } from '../cliSpinner.js'; import { promptSkillLocation, resolveAndPrepareSkillDir } from '../prompts/skillPrompts.js'; import { runMigrationAction } from './migrationAction.js'; export const runDefaultAction = async (directories, cwd, cliOptions, progressCallback) => { logger.trace('Loaded CLI options:', cliOptions); await runMigrationAction(cwd); const fileConfig = await loadFileConfig(cwd, cliOptions.config ?? null, { skipLocalConfig: cliOptions.skipLocalConfig, }); logger.trace('Loaded file config:', fileConfig); const cliConfig = buildCliConfig(cliOptions); logger.trace('CLI config:', cliConfig); const config = mergeConfigs(cwd, fileConfig, cliConfig); logger.trace('Merged config:', config); validateConflictingOptions(config); if (cliOptions.skillOutput && config.skillGenerate === undefined) { throw new RepomixError('--skill-output can only be used with --skill-generate'); } if (cliOptions.force && config.skillGenerate === undefined) { throw new RepomixError('--force can only be used with --skill-generate'); } if (cliOptions.skillOutput !== undefined && !cliOptions.skillOutput.trim()) { throw new RepomixError('--skill-output path cannot be empty'); } if (config.skillGenerate !== undefined) { cliOptions.skillName ??= typeof config.skillGenerate === 'string' ? config.skillGenerate : generateDefaultSkillName(directories.map((d) => path.resolve(cwd, d))); if (cliOptions.skillOutput && !cliOptions.skillDir) { cliOptions.skillDir = await resolveAndPrepareSkillDir(cliOptions.skillOutput, cwd, cliOptions.force ?? false); } else if (!cliOptions.skillDir) { const promptResult = await promptSkillLocation(cliOptions.skillName, cwd); cliOptions.skillDir = promptResult.skillDir; } } let stdinFilePaths; if (cliOptions.stdin) { const firstDir = directories[0] ?? '.'; if (directories.length > 1 || firstDir !== '.') { throw new RepomixError('When using --stdin, do not specify directory arguments. File paths will be read from stdin.'); } const stdinResult = await readFilePathsFromStdin(cwd); stdinFilePaths = stdinResult.filePaths; logger.trace(`Read ${stdinFilePaths.length} file paths from stdin`); } const spinner = new Spinner('Initializing...', cliOptions); spinner.start(); let packResult; try { const { skillName, skillDir, skillProjectName, skillSourceUrl } = cliOptions; const packOptions = { skillName, skillDir, skillProjectName, skillSourceUrl }; const targetPaths = stdinFilePaths ? [cwd] : directories.map((directory) => path.resolve(cwd, directory)); const handleProgress = (message) => { spinner.update(message); if (progressCallback) { try { Promise.resolve(progressCallback(message)).catch((error) => { logger.trace('progressCallback error:', error); }); } catch (error) { logger.trace('progressCallback error:', error); } } }; packResult = await pack(targetPaths, config, handleProgress, {}, stdinFilePaths, packOptions); spinner.succeed('Packing completed successfully!'); } catch (error) { spinner.fail('Error during packing'); throw error; } reportResults(cwd, packResult, config, cliOptions); return { packResult, config, }; }; export const buildCliConfig = (options) => { const cliConfig = {}; if (options.output) { cliConfig.output = { filePath: options.output }; } if (options.include) { cliConfig.include = splitPatterns(options.include); } if (options.ignore) { cliConfig.ignore = { customPatterns: splitPatterns(options.ignore) }; } if (options.gitignore === false) { cliConfig.ignore = { ...cliConfig.ignore, useGitignore: options.gitignore }; } if (options.dotIgnore === false) { cliConfig.ignore = { ...cliConfig.ignore, useDotIgnore: options.dotIgnore }; } if (options.defaultPatterns === false) { cliConfig.ignore = { ...cliConfig.ignore, useDefaultPatterns: options.defaultPatterns, }; } if (options.topFilesLen !== undefined) { cliConfig.output = { ...cliConfig.output, topFilesLength: options.topFilesLen, }; } if (options.outputShowLineNumbers !== undefined) { cliConfig.output = { ...cliConfig.output, showLineNumbers: options.outputShowLineNumbers, }; } if (options.copy) { cliConfig.output = { ...cliConfig.output, copyToClipboard: options.copy }; } if (options.style) { cliConfig.output = { ...cliConfig.output, style: options.style.toLowerCase(), }; } if (options.parsableStyle !== undefined) { cliConfig.output = { ...cliConfig.output, parsableStyle: options.parsableStyle, }; } if (options.stdout) { cliConfig.output = { ...cliConfig.output, stdout: true, }; } if (options.securityCheck === false) { cliConfig.security = { enableSecurityCheck: options.securityCheck }; } if (options.fileSummary === false) { cliConfig.output = { ...cliConfig.output, fileSummary: false, }; } if (options.directoryStructure === false) { cliConfig.output = { ...cliConfig.output, directoryStructure: false, }; } if (options.files === false) { cliConfig.output = { ...cliConfig.output, files: false, }; } if (options.removeComments !== undefined) { cliConfig.output = { ...cliConfig.output, removeComments: options.removeComments, }; } if (options.removeEmptyLines !== undefined) { cliConfig.output = { ...cliConfig.output, removeEmptyLines: options.removeEmptyLines, }; } if (options.truncateBase64 !== undefined) { cliConfig.output = { ...cliConfig.output, truncateBase64: options.truncateBase64, }; } if (options.headerText !== undefined) { cliConfig.output = { ...cliConfig.output, headerText: options.headerText }; } if (options.compress !== undefined) { cliConfig.output = { ...cliConfig.output, compress: options.compress }; } if (options.tokenCountEncoding) { cliConfig.tokenCount = { encoding: options.tokenCountEncoding }; } if (options.instructionFilePath) { cliConfig.output = { ...cliConfig.output, instructionFilePath: options.instructionFilePath, }; } if (options.includeEmptyDirectories) { cliConfig.output = { ...cliConfig.output, includeEmptyDirectories: options.includeEmptyDirectories, }; } if (options.includeFullDirectoryStructure) { cliConfig.output = { ...cliConfig.output, includeFullDirectoryStructure: options.includeFullDirectoryStructure, }; } if (options.splitOutput !== undefined) { cliConfig.output = { ...cliConfig.output, splitOutput: options.splitOutput, }; } if (options.gitSortByChanges === false) { cliConfig.output = { ...cliConfig.output, git: { ...cliConfig.output?.git, sortByChanges: false, }, }; } if (options.includeDiffs) { cliConfig.output = { ...cliConfig.output, git: { ...cliConfig.output?.git, includeDiffs: true, }, }; } if (options.includeLogs || options.includeLogsCount !== undefined) { const gitLogConfig = { ...cliConfig.output?.git, ...(options.includeLogs && { includeLogs: true }), ...(options.includeLogsCount !== undefined && { includeLogsCount: options.includeLogsCount }), }; cliConfig.output = { ...cliConfig.output, git: gitLogConfig, }; } if (options.tokenCountTree !== undefined) { cliConfig.output = { ...cliConfig.output, tokenCountTree: options.tokenCountTree, }; } if (options.skillGenerate !== undefined) { cliConfig.skillGenerate = options.skillGenerate; } try { return v.parse(repomixConfigCliSchema, cliConfig); } catch (error) { rethrowValidationErrorIfSchemaError(error, 'Invalid cli arguments'); throw error; } }; const validateConflictingOptions = (config) => { const isStdoutMode = config.output.stdout || config.output.filePath === '-'; const options = { splitOutput: { enabled: config.output.splitOutput !== undefined, name: '--split-output', }, skillGenerate: { enabled: config.skillGenerate !== undefined, name: '--skill-generate', }, stdout: { enabled: isStdoutMode, name: '--stdout', }, copy: { enabled: config.output.copyToClipboard, name: '--copy', }, }; const conflicts = [ ['splitOutput', 'stdout', 'Split output requires writing to filesystem.'], ['splitOutput', 'skillGenerate', 'Skill output is a directory.'], ['splitOutput', 'copy', 'Split output generates multiple files.'], ['skillGenerate', 'stdout', 'Skill output requires writing to filesystem.'], ['skillGenerate', 'copy', 'Skill output is a directory and cannot be copied to clipboard.'], ]; for (const [optionA, optionB, message] of conflicts) { if (options[optionA].enabled && options[optionB].enabled) { throw new RepomixError(`${options[optionA].name} cannot be used with ${options[optionB].name}. ${message}`); } } };