pnpm: ESlint plugins are conflicted each other migrating to pnpm

pnpm version: 6.32.9

Code to reproduce the issue:

I tried to reproduce error using CRA, but couldn’t figure out exact condition 😢

Expected behavior:

ESLint plugins don’t conflict

Actual behavior:

Plugin ā€œ@typescript-eslintā€ was conflicted between ā€œ.eslintrc.jsonā€ and ā€œ.eslintrc.json Ā» eslint-config-react-app#overrides[0]ā€. ERROR in Plugin ā€œ@typescript-eslintā€ was conflicted between ā€œ.eslintrc.jsonā€ and ā€œ.eslintrc.json Ā» eslint-config-react-app#overrides[0]ā€.

The reason why I posted this issue on pnpm is that eslint didn’t conflict when we were using npm. This error was occurred migrating from npm to pnpm. I tried lots of experiments.

  • Remove node_modules and install from scratch -> Failed
  • Remove all eslint packages and re-install from scratch -> Failed

From this situation, I cannot no longer guess what is the real problem? I think installation mechanism of pnpm is related to this issue. Is there other reason that you think? Thanks for your reading.

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 8
  • Comments: 17

Most upvoted comments

I had a similar issue:

[Info  - 11:59:46 AM] ESLint server is starting
[Info  - 11:59:46 AM] ESLint server running in node v16.13.0
[Info  - 11:59:46 AM] ESLint server is running.
[Info  - 11:59:47 AM] ESLint library loaded from: /home/atablash/voltiso/node_modules/.pnpm/eslint@8.18.0/node_modules/eslint/lib/api.js
[Error - 11:59:48 AM] ESLint stack trace:
[Error - 11:59:48 AM] Error: Plugin "prettier" was conflicted between ".eslintrc.js" and ".eslintrc.js Ā» plugin:github/recommended".
    at mergePlugins (/home/atablash/voltiso/node_modules/.pnpm/@eslint+eslintrc@1.3.0/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:598:19)
    at createConfig (/home/atablash/voltiso/node_modules/.pnpm/@eslint+eslintrc@1.3.0/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:701:9)
    at ConfigArray.extractConfig (/home/atablash/voltiso/node_modules/.pnpm/@eslint+eslintrc@1.3.0/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:877:33)
    at CLIEngine.isPathIgnored (/home/atablash/voltiso/node_modules/.pnpm/eslint@8.18.0/node_modules/eslint/lib/cli-engine/cli-engine.js:990:18)
    at CLIEngine.executeOnText (/home/atablash/voltiso/node_modules/.pnpm/eslint@8.18.0/node_modules/eslint/lib/cli-engine/cli-engine.js:899:38)
    at ESLint.lintText (/home/atablash/voltiso/node_modules/.pnpm/eslint@8.18.0/node_modules/eslint/lib/eslint/eslint.js:592:23)
    at /home/atablash/.vscode-server/extensions/dbaeumer.vscode-eslint-2.2.2/server/out/eslintServer.js:1:180347
    at Ce (/home/atablash/.vscode-server/extensions/dbaeumer.vscode-eslint-2.2.2/server/out/eslintServer.js:1:186295)
    at /home/atablash/.vscode-server/extensions/dbaeumer.vscode-eslint-2.2.2/server/out/eslintServer.js:1:180305
    at /home/atablash/.vscode-server/extensions/dbaeumer.vscode-eslint-2.2.2/server/out/eslintServer.js:1:181582

The problem is I’m using eslint configs that declare plugins as regular non-peer dependencies.

  • BTW. I do it the same way with my config package, because currently PNPM’s auto-install-peers does not seem to work for me like I want - I don’t want to manually install all peer dependencies of the config package in my projects

Using PNPM’s override field in package.json, I force a single version of the problematic package - e.g. esling-plugin-prettier in my case:

	"pnpm": {
		"overrides": {
			"@typescript-eslint/eslint-plugin": "latest",
			"eslint": "latest",
			"prettier": "latest",
			"eslint-plugin-jsonc": "latest",
			"eslint-plugin-unicorn": "latest"
		},

The problem still persisted, because eslint uses file path to figure out if the plugin is the same package. I think it didn’t even resolve symlinks using fs.realpath.

So I made a patch-package patch to fix this. But resolving symlinks still didn’t help: even though the eslint-plugin-prettier was the exact same version and hardlinked to the same place on disk, PNPM can put the same package in different ā€œnode_modules contextsā€ depending on the set of dependencies it’s used together with.

So the final patch checks is the file is hardlinked to the same place on disk… not sure if this is 100% correct, because of these different ā€œnode_modules contextsā€ but it works for me.

The patch generated using @milahu/patch-package-with-pnpm-support:

# generated by patch-package 6.4.10
#
# command:
#   npx patch-package @eslint/eslintrc
#
# declared package:
#   @eslint/eslintrc: 1.3.0 || *
#
diff --git a/node_modules/@eslint/eslintrc/dist/eslintrc.cjs b/node_modules/@eslint/eslintrc/dist/eslintrc.cjs
index 8b07ba1..31c2c80 100644
--- a/node_modules/@eslint/eslintrc/dist/eslintrc.cjs
+++ b/node_modules/@eslint/eslintrc/dist/eslintrc.cjs
@@ -563,12 +563,24 @@ class PluginConflictError extends Error {
      * @param {{filePath:string, importerName:string}[]} plugins The resolved plugins.
      */
     constructor(pluginId, plugins) {
-        super(`Plugin "${pluginId}" was conflicted between ${plugins.map(p => `"${p.importerName}"`).join(" and ")}.`);
+        super(`Plugin "${pluginId}" was conflicted between ${plugins.map(p => `"${p.importerName}"`).join(" and ")}. Files not linking to the same place on disk: ${plugins.map(p => p.filePath).join(' and ')}`);
         this.messageTemplate = "plugin-conflict";
         this.messageData = { pluginId, plugins };
     }
 }
 
+function isSameFile(pathA, pathB) {
+  try {
+    const statA = fs.statSync(pathA)
+    const statB = fs.statSync(pathB)
+    if(typeof statA?.ino !== 'number' || typeof statB?.ino !== 'number') return false
+    else return statA.ino === statB.ino
+  } catch(error) {
+    console.error(error)
+    return false
+  }
+}
+
 /**
  * Merge plugins.
  * `target`'s definition is prior to `source`'s.
@@ -594,7 +606,7 @@ function mergePlugins(target, source) {
                 throw sourceValue.error;
             }
             target[key] = sourceValue;
-        } else if (sourceValue.filePath !== targetValue.filePath) {
+        } else if (!isSameFile(sourceValue.filePath, targetValue.filePath)) {
             throw new PluginConflictError(key, [
                 {
                     filePath: targetValue.filePath,

I’m not sure about performance of the synchronous fs.statSync calls - but it seems ok for my use case. šŸ˜Ž

Also, I’m using Ubuntu WSL - I’m not exactly sure how cross-platform node’s stat implemetation is - is it ā€œpolyfilledā€ for Windows or not.

Fixed with: .npmrc

auto-install-peers = true

@baeharam I faced a similar issue, after upgrading to pnpm 7. I resolved it by adding eslint and eslint-config-react-app to devDependencies as described here.