deprecopilot
Version:
Automated dependency management with AI-powered codemods
166 lines (165 loc) โข 7.63 kB
JavaScript
import { detectPackageManager, getPackageJson, getCurrentVersion, stubDetectBreaking, findSourceFilesUsing } from '../lib/upgradeUtils.js';
import { runCodemod } from '../lib/codemodEngine.js';
import { logger } from '../lib/logger.js';
import { safeInvokePluginHook } from '../plugins/safeInvokePluginHook.js';
import path from 'path';
export async function fix(flags = {}, context = {}) {
// Only print debug logs if not in JSON mode and not in test environment
if (!flags.json && process.env.NODE_ENV !== 'test') {
console.error('FLAGS:', JSON.stringify(flags));
}
function log(...args) {
if (process.env.NODE_ENV !== 'test' && !flags.json) {
console.log(...args);
}
}
const pkg = await getPackageJson();
const manager = detectPackageManager();
const upgrades = [];
for (const depType of ['dependencies', 'devDependencies', 'peerDependencies']) {
const deps = pkg[depType] || {};
for (const name of Object.keys(deps)) {
const current = await getCurrentVersion(name);
const to = current ? (parseInt(current.split('.')[0]) + 1) + '.0.0' : null;
const safeCurrent = current ?? '';
const safeTo = to ?? '';
if (flags.all || flags.breakingChanges || stubDetectBreaking(name, safeCurrent, safeTo).length) {
upgrades.push({ name, from: safeCurrent, to: safeTo });
}
}
}
const results = [];
let filesTouched = 0;
let filesSkipped = 0;
let previewDiffs = [];
await Promise.all((context.plugins ?? []).map(p => safeInvokePluginHook(p, 'beforeFix', context)));
for (const up of upgrades) {
let files = await findSourceFilesUsing(up.name);
files = files.filter(f => !f.includes('.deprecopilot-preview-'));
files = files.filter(f => f.endsWith('.js'));
if (!flags.json) {
console.error('DEBUG: files found for', up.name, ':', files);
}
let codemodResult = null;
if (flags.dryRun) {
// In dry-run mode, always show what would be done, even if no files found
if (!flags.json) {
console.log(`๐ Would apply codemod for ${up.name} ${up.from} โ ${up.to} to ${files.length} files`);
console.log(`๐ Files found for ${up.name}:`, files);
}
if (files.length > 0) {
codemodResult = { applied: false, files, diff: 'stub-diff' };
}
results.push({ ...up, files, codemodResult });
continue;
}
if (!files.length)
continue;
let codemodPath = undefined;
if (pkg.deprecopilot && pkg.deprecopilot.codemods && pkg.deprecopilot.codemods[up.name]) {
codemodPath = pkg.deprecopilot.codemods[up.name];
}
else {
codemodPath = `codemods/${up.name}-major.js`;
}
codemodPath = path.isAbsolute(codemodPath) ? codemodPath : path.resolve(codemodPath);
try {
if (flags.previewOnly) {
if (!flags.json && process.env.NODE_ENV !== 'test') {
console.error('ENTERING PREVIEWONLY BLOCK');
console.error('DEBUG: codemodPath:', codemodPath);
console.error('DEBUG: files:', files);
console.error('DEBUG: up.name:', up.name);
console.error('DEBUG: up.from:', up.from);
console.error('DEBUG: up.to:', up.to);
}
try {
const diff = await runCodemod({
codemodPath,
files,
llmPromptContext: { fromVersion: up.from, toVersion: up.to, packageName: up.name, changelog: '', codeContext: '' },
ai: flags.ai,
previewOnly: true,
llmProvider: flags.llmProvider,
});
if (!flags.json && process.env.NODE_ENV !== 'test') {
console.error('AFTER RUNCODEMOD:', typeof diff, diff);
}
codemodResult = { applied: false, files, diff };
if (flags.previewOnly) {
if (!flags.json && process.env.NODE_ENV !== 'test') {
console.error('DIFF OUTPUT:', JSON.stringify(codemodResult.diff));
}
previewDiffs.push({ name: up.name, diff });
}
if (!flags.json) {
process.stdout.write(diff + '\n');
}
}
catch (error) {
if (!flags.json && process.env.NODE_ENV !== 'test') {
console.error('DEBUG: Exception in runCodemod:', error);
if (error instanceof Error) {
console.error('DEBUG: Exception stack:', error.stack);
}
}
throw error;
}
}
else {
if (!flags.json && process.env.NODE_ENV !== 'test') {
console.error('ENTERING ELSE BLOCK (not previewOnly)');
}
if (process.env.NODE_ENV === 'test') {
await runCodemod({
codemodPath,
files,
llmPromptContext: { fromVersion: up.from, toVersion: up.to, packageName: up.name, changelog: '', codeContext: '' },
ai: flags.ai,
llmProvider: flags.llmProvider,
});
codemodResult = { applied: true, files };
}
else {
await runCodemod({
codemodPath,
files,
llmPromptContext: { fromVersion: up.from, toVersion: up.to, packageName: up.name, changelog: '', codeContext: '' },
ai: flags.ai,
llmProvider: flags.llmProvider,
});
codemodResult = { applied: true, files };
}
if (!flags.json)
logger.info(`โ ${up.name} โ ${up.to}: codemod applied to ${files.length} files`);
}
filesTouched += files.length;
}
catch (error) {
if (!flags.json && process.env.NODE_ENV !== 'test') {
console.error('DEBUG: Exception caught in fix command:', error);
if (error instanceof Error) {
console.error('DEBUG: Exception stack:', error.stack);
}
}
filesSkipped += files.length;
if (!flags.json)
logger.error(`โ ${files.length} file(s) skipped for ${up.name}`);
}
results.push({ ...up, files, codemodResult });
}
if (flags.json) {
const output = { results, filesTouched, filesSkipped };
if (flags.previewOnly) {
output.preview = { diffs: previewDiffs };
}
process.stdout.write(JSON.stringify(output) + '\n');
}
else {
logger.info(`๐งช Codemods applied to ${filesTouched} files`);
if (filesSkipped)
logger.error(`โ ${filesSkipped} file(s) skipped`);
logger.info('โ
All fixes complete');
}
await Promise.all((context.plugins ?? []).map(p => safeInvokePluginHook(p, 'afterFix', context)));
}