cli icon indicating copy to clipboard operation
cli copied to clipboard

[BUG] Yarn workspaces w/ Symlinks Breaks npx Invocations

Open brianlenz opened this issue 1 year ago • 2 comments

Is there an existing issue for this?

  • [X] I have searched the existing issues

This issue exists in the latest npm version

  • [X] I am using the latest npm

Current Behavior

I've had issues invoking a couple of different commands due to the existence of symbolic links in our monorepo using Yarn workspaces. Specifically, I've gotten the error with npx @react-native-vector-icons/codemod and npx @react-native-community/cli config.

I had originally thought it was a bug in @react-native-community/cli and filed a bug there, but then I had the same issue with @react-native-vector-icons/codemod and realized it might be an npx bug, which is why I'm reporting the issue here now.

Expected Behavior

The existence of symbolic links in a monorepo should not break npx commands.

Steps To Reproduce

  1. Clone this git repo: https://github.com/brianlenz/rncli-symlink-bug
  2. Run npx @react-native-community/cli config in the root of the repo or in testsymlinkproj.
  3. Observe error:
Exit prior to config file resolving
cause
Could not read package.json: Error: ENOTDIR: not a directory, open '.../testsymlinkmonorepo/scripts/script-a/src/index.js/package.json'

or

npm error code ENOTDIR
npm error syscall open
npm error path .../testsymlinkmonorepo/scripts/script-a/src/index.js/package.json
npm error errno -20
npm error Could not read package.json: Error: ENOTDIR: not a directory, open '.../testsymlinkmonorepo/scripts/script-a/src/index.js/package.json'
npm error A complete log of this run can be found in: /Users/brian/.npm/_logs/2024-10-14T18_48_40_227Z-debug-0.log

The log detail:

0 verbose cli /opt/homebrew/Cellar/node/22.9.0_1/bin/node /opt/homebrew/lib/node_modules/npm/bin/npm-cli.js
1 info using [email protected]
2 info using [email protected]
3 silly config load:file:/opt/homebrew/lib/node_modules/npm/npmrc
4 silly config load:file:.../testsymlinkmonorepo/.npmrc
5 silly config load:file:/Users/brian/.npmrc
6 silly config load:file:/opt/homebrew/etc/npmrc
7 verbose title npm exec @react-native-community/cli config
8 verbose argv "exec" "--" "@react-native-community/cli" "config"
9 verbose logfile logs-max:10 dir:/Users/brian/.npm/_logs/2024-10-14T18_48_40_227Z-
10 verbose logfile /Users/brian/.npm/_logs/2024-10-14T18_48_40_227Z-debug-0.log
11 silly logfile start cleaning logs, removing 1 files
12 silly logfile done cleaning log files
13 silly packumentCache heap:4345298944 maxSize:1086324736 maxEntrySize:543162368
14 verbose stack Error: Could not read package.json: Error: ENOTDIR: not a directory, open '.../testsymlinkmonorepo/scripts/script-a/src/index.js/package.json'
14 verbose stack     at async open (node:internal/fs/promises:638:25)
14 verbose stack     at async readFile (node:internal/fs/promises:1238:14)
14 verbose stack     at async read (/opt/homebrew/lib/node_modules/npm/node_modules/@npmcli/package-json/lib/read-package.js:9:18)
14 verbose stack     at async PackageJson.load (/opt/homebrew/lib/node_modules/npm/node_modules/@npmcli/package-json/lib/index.js:130:31)
14 verbose stack     at async PackageJson.normalize (/opt/homebrew/lib/node_modules/npm/node_modules/@npmcli/package-json/lib/index.js:116:5)
14 verbose stack     at async mapWorkspaces (/opt/homebrew/lib/node_modules/npm/node_modules/@npmcli/map-workspaces/lib/index.js:135:13)
14 verbose stack     at async [setWorkspaces] (/opt/homebrew/lib/node_modules/npm/node_modules/@npmcli/arborist/lib/arborist/index.js:208:24)
14 verbose stack     at async #loadActual (/opt/homebrew/lib/node_modules/npm/node_modules/@npmcli/arborist/lib/arborist/load-actual.js:172:5)
14 verbose stack     at async exec (/opt/homebrew/lib/node_modules/npm/node_modules/libnpmexec/lib/index.js:175:21)
14 verbose stack     at async Npm.exec (/opt/homebrew/lib/node_modules/npm/lib/npm.js:207:9)
15 error code ENOTDIR
16 error syscall open
17 error path .../testsymlinkmonorepo/scripts/script-a/src/index.js/package.json
18 error errno -20
19 error Could not read package.json: Error: ENOTDIR: not a directory, open '.../testsymlinkmonorepo/scripts/script-a/src/index.js/package.json'
20 verbose cwd .../testsymlinkmonorepo
21 verbose os Darwin 24.0.0
22 verbose node v22.9.0
23 verbose npm  v10.9.0
24 verbose exit -20
25 verbose code -20
26 error A complete log of this run can be found in: /Users/brian/.npm/_logs/2024-10-14T18_48_40_227Z-debug-0.log

Environment

  • npm: 10.9.0
  • Node.js: 22.9.0
  • OS Name: macOS
  • System Model Name: Macbook Pro
  • npm config:
; "builtin" config from /opt/homebrew/lib/node_modules/npm/npmrc

prefix = "/opt/homebrew"

; "user" config from /Users/brian/.npmrc

//registry.npmjs.org/:_authToken = (protected)

; node bin location = /opt/homebrew/Cellar/node/22.9.0_1/bin/node
; node version = v22.9.0
; npm local prefix = /Users/brian
; npm version = 10.9.0
; cwd = /Users/brian
; HOME = /Users/brian
; Run `npm config ls -l` to show all defaults.

brianlenz avatar Oct 14 '24 18:10 brianlenz

in your workspace definition of package.json


"workspaces": [
    "scripts/*",       // instead of **, could you try with * 
    "testsymlinkproj"
  ]
  

milaninfy avatar Oct 16 '24 13:10 milaninfy

@milaninfy sorry for the delay! Yes, it appears that changing that workspace config to /* instead of /** works around the issue. Unfortunately, this isn't a viable solution for us, as we have deeply nested projects in the scripts directory in our monorepo that we need to use Yarn workspaces (thus the ** is a necessity). But hopefully that information is helpful to trace and fix the bug! 🙏

brianlenz avatar Oct 17 '24 20:10 brianlenz

I don't think it's a bug yet 😄 ** recursively finds all folders as workspaces. I believe that's not the case with your project, as I understand it's deeply nested but not all folders are workspaces.

milaninfy avatar Oct 21 '24 16:10 milaninfy

@milaninfy the configuration works perfectly from a Yarn workspaces standpoint, so I don't think that's the issue at all. Why is npx treating a symbolic link to a file as a folder (and thus a new workspace)? It's just a file, but npx seems to be looking for a package.json file within a file (which obviously doesn't make any sense). To be clear, just look at this path: .../testsymlinkmonorepo/scripts/script-a/src/index.js/package.json. Of course this package.json doesn't exist because index.js is a symbolic link to a file 😁

Please check out this repo that reproduces the issue, and you can see how simple the use case is: https://github.com/brianlenz/rncli-symlink-bug

Based on the stack trace, it looks like it's probably a bug in https://github.com/npm/map-workspaces.

One way to address this is here: https://github.com/npm/map-workspaces/blob/main/lib/index.js#L137

-      if (err.code === 'ENOENT') {
+      if (err.code === 'ENOENT' || err.code === 'ENOTDIR') {

This solves the problem. Is there anything problematic about this approach?

brianlenz avatar Oct 21 '24 16:10 brianlenz

I went ahead and opened a PR here for further consideration / discussion 🙏

https://github.com/npm/map-workspaces/pull/176

brianlenz avatar Oct 21 '24 16:10 brianlenz