ESLint Migration Codemod Uses Npx Instead Of Pnpm On Pnpm Projects
The @next/codemod next-lint-to-eslint-cli transform hardcodes npx to run @eslint/migrate-config, which is incorrect for pnpm projects and can lead to package manager mismatch, lockfile pollution, or failures in strict environments.
The migration script invokes `npx @eslint/migrate-config` without detecting the project's package manager. In pnpm projects, this bypasses pnpm's dependency resolution and may use npm, causing inconsistencies or requiring npm to be installed.
1. Create a Next.js 14 app with pnpm and ESLint enabled.
2. Run pnpm install.
3. Ensure git working tree is clean.
4. Run `pnpx @next/codemod@canary next-lint-to-eslint-cli . --force`.
5. Observe log: Running "npx @eslint/migrate-config ..." instead of a pnpm dlx command.
Fixing Code Block
import fs from 'fs';
import path from 'path';
import { execa } from 'execa';
type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun';
function detectPackageManager(projectDir) {
if (fs.existsSync(path.join(projectDir, 'pnpm-lock.yaml'))) return 'pnpm';
if (fs.existsSync(path.join(projectDir, 'yarn.lock'))) return 'yarn';
if (fs.existsSync(path.join(projectDir, 'bun.lockb'))) return 'bun';
if (fs.existsSync(path.join(projectDir, 'package-lock.json'))) return 'npm';
return 'npm';
}
function buildMigrationCommand(pm, configPath) {
switch (pm) {
case 'pnpm':
return ['pnpm', 'dlx', '@eslint/migrate-config', configPath];
case 'yarn':
return ['yarn', 'dlx', '@eslint/migrate-config', configPath];
case 'bun':
return ['bun', 'x', '@eslint/migrate-config', configPath];
default:
return ['npx', '--yes', '@eslint/migrate-config', configPath];
}
}
export async function runEslintConfigMigration(projectDir) {
const pm = detectPackageManager(projectDir);
const configPath = path.join(projectDir, '.eslintrc.json');
const command = buildMigrationCommand(pm, configPath);
console.log(`Running ${command.join(' ')} to convert legacy config...`);
await execa(command[0], command.slice(1), { stdio: 'inherit', cwd: projectDir });
}
Replaces the hardcoded npx command with a package-manager-aware command builder. It detects pnpm, yarn, bun, or npm via lockfiles and uses the appropriate dlx/x/exec invocation. This prevents npm from being used in pnpm projects and respects the user's chosen package manager.
Edge Case Audit
Lockfile detection may misidentify the package manager if multiple lockfiles exist or if the project uses a custom packageManager field. Yarn 1 does not support `yarn dlx`; for Yarn classic, fallback to `npx` or `yarn global add` may be needed. pnpm versions before 6.13 lack `dlx`; ensure user has supported pnpm. If the migration tool itself is invoked with a different package manager than the project, the detection based on cwd may still mismatch. Rollback: revert to the original hardcoded `npx @eslint/migrate-config` if this change causes unexpected behavior; or pin the migration tool version.