AI & Agent Dev Bug Sandbox logo
AI & Agent Dev Bug Sandbox
Back to Radar

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.

mediumConfidence 78%Next.jsAffected V14.2.18Affected Vcanary

Origin Analysis

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.

Ecosystem Topology