UNPKG

repomix

Version:

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

205 lines (204 loc) 8.5 kB
import * as fs from 'node:fs/promises'; import path from 'node:path'; import pc from 'picocolors'; import { getGlobalDirectory } from '../../config/globalDirectory.js'; import { logger } from '../../shared/logger.js'; const fileExists = async (filePath) => { try { await fs.access(filePath); return true; } catch { return false; } }; const replaceRepopackString = (content) => { return content.replace(/repopack/g, 'repomix').replace(/Repopack/g, 'Repomix'); }; const updateFileContent = async (filePath) => { const content = await fs.readFile(filePath, 'utf8'); const updatedContent = replaceRepopackString(content); 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; }; const updateInstructionPath = (content) => { try { const config = JSON.parse(content); if (config.output?.instructionFilePath) { config.output.instructionFilePath = config.output.instructionFilePath.replace('repopack', 'repomix'); } if (config.output?.filePath) { config.output.filePath = config.output.filePath.replace('repopack', 'repomix'); } return JSON.stringify(config, null, 2); } catch { return content; } }; const getOutputFilePaths = (rootDir) => { 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 }; }; const migrateFile = async (oldPath, newPath, description, isConfig = false) => { if (!(await fileExists(oldPath))) { return false; } const exists = await fileExists(newPath); if (exists) { const p = await import('@clack/prompts'); const shouldOverwrite = await p.confirm({ message: `${description} already exists at ${newPath}. Do you want to overwrite it?`, }); if (p.isCancel(shouldOverwrite) || !shouldOverwrite) { logger.info(`Skipping migration of ${description}`); return false; } } try { let content = await fs.readFile(oldPath, 'utf8'); content = replaceRepopackString(content); if (isConfig) { content = updateInstructionPath(content); } await fs.mkdir(path.dirname(newPath), { recursive: true }); await fs.writeFile(newPath, content, 'utf8'); 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; } }; const updateIgnoreFiles = async (rootDir) => { 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'); } } }; const getMigrationPaths = (rootDir) => { 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'), }; }; const migrateOutputFiles = async (oldPaths, newPaths) => { const migratedFiles = []; 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) => { const result = { configMigrated: false, ignoreMigrated: false, instructionMigrated: false, outputFilesMigrated: [], globalConfigMigrated: false, }; try { const paths = getMigrationPaths(rootDir); 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; } 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')}?`; const p = await import('@clack/prompts'); const shouldMigrate = await p.confirm({ message: migrationMessage, }); if (p.isCancel(shouldMigrate) || !shouldMigrate) { logger.info('Migration cancelled.'); return result; } logger.info(pc.cyan('\nMigrating from Repopack to Repomix...')); logger.log(''); if (hasOldConfig) { result.configMigrated = await migrateFile(paths.oldConfigPath, paths.newConfigPath, 'Configuration file', true); } if (hasOldGlobalConfig) { result.globalConfigMigrated = await migrateFile(paths.oldGlobalConfigPath, paths.newGlobalConfigPath, 'Global configuration file', true); } if (hasOldIgnore) { result.ignoreMigrated = await migrateFile(paths.oldIgnorePath, paths.newIgnorePath, 'Ignore file'); } if (hasOldInstruction) { result.instructionMigrated = await migrateFile(paths.oldInstructionPath, paths.newInstructionPath, 'Instruction file'); } if (hasOldOutput) { result.outputFilesMigrated = await migrateOutputFiles(paths.oldOutputPaths, paths.newOutputPaths); } await updateIgnoreFiles(rootDir); 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; } };