iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🆚

Fixing the Issue Where VSCode Jest Extension Cannot Find Node.js Managed by asdf

に公開

I encountered an issue where the VSCode Jest extension (vscode-jest) sometimes fails to call Node.js managed by asdf. In this post, I will share the results of a deep dive into the cause and its solution.

TL;DR

  • The VSCode Jest extension calls Node.js from a non-interactive and non-login /bin/sh shell.
  • Since asdf initialization code (such as setting up the PATH) is often written in ~/.bashrc or ~/.zshrc, the Jest extension cannot call Node.js managed by asdf.
  • There are several workarounds, but setting the Jest execution shell to a login shell via jest.shell in .vscode/settings.json is probably the easiest. Probably.
    .vscode/settings.json
    "jest.shell": {
      "path": "/usr/bin/env",
      "args": ["zsh", "--login"]
    },
    

About the VSCode Jest extension: vscode-jest

VSCode has a useful extension for running Jest[1] tests.

https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest

It is called vscode-jest, and it allows you to run Jest tests and view test results directly within VSCode.

What happened

One day, when I opened a project using Node.js in VSCode, vscode-jest threw an error.

> Test run exited at 2023/6/7 18:31:05 <

env: node: No such file or directory

[error] failed to retrieve test file list. TestExplorer might show incomplete test items

[info] jest process failed to start, most likely due to env or project configuration issues, please see: https://github.com/jest-community/vscode-jest/blob/master/README.md#jest-failed-to-run

env: node: No such file or directory

[error] Jest process "watch-tests" ended unexpectedly

[info] jest process failed to start, most likely due to env or project configuration issues, please see: https://github.com/jest-community/vscode-jest/blob/master/README.md#jest-failed-to-run

Two errors appeared: one stating that the test file list could not be retrieved, and another indicating that watch-tests (which monitors files and runs tests automatically) failed. Both errors included env: node: No such file or directory, suggesting they share the same cause.

What was strange was that I had been able to run tests with the Jest extension without any special configuration until then. At first, I couldn't figure out what was different from usual.

Conditions under which it occurs

After trying several things, I realized that the error occurred when I was not using the code command.

Starting VSCode without using the code command means, for example, opening it from the list of recently opened directories by right-clicking VSCode in the Dock, or from the list of recently opened directories in the menu bar.


Opening from the Dock


Opening from the menu bar

In fact, I often open VSCode from the Dock, and I found that's when the error occurs.

The Cause

While searching around, I found the following page written by a volunteer.

https://scrapbox.io/pnly/RESOLVED%3A_vscode-jest_`env:_node:_No_such_file_or_directory`

The contents of this page and the comments on a vscode-jest issue explain everything, but I'll break it down.

https://github.com/jest-community/vscode-jest/issues/741#issuecomment-921222851

vscode-jest does not read ~/.bashrc and similar files

Apparently, when vscode-jest calls Jest, it calls Node.js—and consequently Jest—from a non-interactive and non-login /bin/sh shell.

Consequently, vscode-jest doesn't read environment variables exported in files like ~/.bashrc.

Where is the path set for a non-login /bin/sh?

To begin with, where is the path set for a non-login /bin/sh?

First, let's check the environment variables with env -i /bin/sh -c 'env'.

Environment variables of a non-interactive & non-login shell
❯ env -i /bin/sh -c 'env'
PWD=<path of execution directory>
SHLVL=1
_=/usr/bin/env

Oh, $PATH is missing. Is it defined at all?

We can check with env -i /bin/sh -c 'echo $PATH'.

$PATH of a non-interactive & non-login shell
❯ env -i /bin/sh -c 'echo $PATH'
/usr/gnu/bin:/usr/local/bin:/bin:/usr/bin:.

Even though PATH wasn't defined in env, the contents of $PATH were displayed. This is because PATH is registered as a default shell variable[2].

This is mentioned in man bash.

Excerpt from man bash
PATH   The search path for commands.  It is a colon-separated list of directories in which the shell looks for commands (see COMMAND EXECUTION below).  A zero-length (null) directory name in the value
      of PATH indicates the current directory.  A null directory name may appear as two adjacent colons, or as an initial or trailing colon.  The default path is system-dependent, and is set by the
      administrator who installs bash.  A common value is ``/usr/gnu/bin:/usr/local/bin:/usr/ucb:/bin:/usr/bin''.

The default path is system-dependent, and is set by the administrator who installs bash. A common value is ``/usr/gnu/bin:/usr/local/bin:/usr/ucb:/bin:/usr/bin''.

Apparently, it is system-dependent (decided by the person who installed it). In the case of macOS (darwin?), it might be set by their developers.

At least in the /bin/sh of my environment, the following locations were specified:

  • /usr/gnu/bin
  • /usr/local/bin
  • /bin
  • /usr/bin
  • .

Therefore, the Node.js binary (node) must exist in one of these locations.

In my environment, the path to Node.js was not set in a non-login /bin/sh

For example, when installing Node.js with Homebrew, it is eventually placed in /opt/homebrew/bin/.
Usually, /opt/homebrew is not in the path (it's not included in $PATH), so you need to run /opt/homebrew/bin/brew shellenv in ~/.bashrc or similar to set the path to /opt/homebrew/bin/ [3].

In my case, I manage Node.js with asdf[4].

❯ where node
/Users/<username>/.asdf/shims/node
/opt/homebrew/bin/node  # Note: Node.js was also installed via Homebrew

Binaries managed by asdf are placed in ~/.asdf/shims, so just like with Homebrew, you need to set the path. Since I configure my path via ~/.zshrc, the path was not set in /bin/sh (because it's set in .zshrc, the path isn't set regardless of whether it's login or non-login).

This was the cause.

Solution

The solution was also described in the page I mentioned earlier.

https://scrapbox.io/pnly/RESOLVED%3A_vscode-jest_`env:_node:_No_such_file_or_directory`

In the author's case, they bypassed the issue by setting their usual shell (Zsh) in jest.shell and setting the path in .zshenv.

vscode-jest has a setting called jest.shell, where you can set the shell used to call Jest (defaults to /bin/sh if nothing is set).

Additionally, in the case of Zsh, the ~/.zshenv file is read in all cases ((non-)login, (non-)interactive)[6]. Therefore, by setting the path in ~/.zshenv, the path to Node.js will be set when Zsh calls Jest.

However, modifying ~/.zshenv affects things like Zsh shell script execution, so I'd prefer not to rewrite it if possible.

As a way to avoid this, the page also mentioned that a change to allow customizing the shell used for running Jest might be coming. It seemed unreleased at that time, but it appeared to have been released already, so this time I tried running Jest as a login shell using --login.

Actual configuration

.vscode/settings.json
"jest.shell": {
  "path": "/usr/bin/env",
  "args": ["zsh", "--login"]
},

So, the actual configuration is as shown above.
In my case, I call Zsh via env and use the --login option to start it as a login shell.

By starting as a login shell, ~/.zprofile and ~/.zlogin are read.
Setting the path to Node.js in those files allows you to avoid modifying ~/.zshenv.

Adding this configuration to the .vscode/settings.json of the target repository resolved the issue mentioned at the beginning.

Note that depending on the situation, this might take slightly longer than starting /bin/sh.
In my case, I didn't notice any particular issues regarding speed.

Conclusion

What started as a mystery with the VSCode Jest extension's behavior led me to find a workaround quickly, but I was curious about the underlying cause and decided to investigate.

I'm glad I could prevent the issue from recurring.
It was a great learning experience regarding the differences in shell modes and the loading order of configuration files.

If I've written anything incorrect or if you have a better solution, please let me know!

脚注
  1. A JavaScript testing framework. https://jestjs.io ↩︎

  2. This behavior likely varies depending on the shell. ↩︎

  3. This instruction appears after installing Homebrew. https://github.com/Homebrew/install/blob/716a1d024f32890ef75ea82c18a769abc24e9475/install.sh#L1010-L1025 ↩︎

  4. Roughly speaking, it's a version management tool for binaries. You can use different versions of binaries for each directory. ↩︎

  5. The mechanism of inheriting environment variables is likely common knowledge, but I looked for a page listing the specifications. I couldn't find one, so if anyone knows, please let me know. ↩︎

  6. For details, see STARTUP/SHUTDOWN FILES in man zsh. https://linux.die.net/man/1/zsh#STARTUP/SHUTDOWN FILES ↩︎

GitHubで編集を提案

Discussion