一个奇怪的问题 ? VSCode 插件报错偶现 npm/node not found 问题排查&解决 – 运行时和内置终端 $PATH 不匹配

前言

大家好,我是祯民。今天我想给大家分享一个 vscode 插件开发中比较难排查的偶现问题,报错信息看上去很像一个普通的本地环境问题,但实际并不简单。全文会被分为背景排查解决三个模块,希望可以给遇到类似问题的同学一点启发,下面我们直接进入正题。

背景

前段时间我写了一个 duplication-lint 的 vscode 插件,可以做到代码重复场景防劣化的 lint,当编码阶段有工作空间维度的重复代码时,会警告报错,并提供一些修复方式,比如 AI 修复等,类似下面的效果。

一个奇怪的问题 ? VSCode 插件报错偶现 npm/node not found 问题排查&解决 - 运行时和内置终端 $PATH 不匹配
在给业务方同学去使用的时候,有一些同学会偶现下面的报错。

一个奇怪的问题 ? VSCode 插件报错偶现 npm/node not found 问题排查&解决 - 运行时和内置终端 $PATH 不匹配
因为依赖的一些特殊历史原因,部分逻辑非常规的调包,而是插件的逻辑中直接使用 node 去执行一个可执行文件,如果没有的话,会走 npm 安装。在逻辑中的体现为,使用 exec 去执行终端命令调起。

一个奇怪的问题 ? VSCode 插件报错偶现 npm/node not found 问题排查&解决 - 运行时和内置终端 $PATH 不匹配

当然这个问题我早期并没有放在心上,一个是这个报错信息太明显了,就是内置环境找不到全局的命令(比如 npm 或者 node),另一方面又是小概率事件,很少的一部分用户复现,而且还不是稳定复现。我当时感觉这个问题大概率就是用户本地环境没装好,或者基础命令并不是全局安装。

直到年后回来,我重启了一下电脑,发现我也复现了这个问题(中途我没有进行任何操作)。这次是 node not found 的报错,但估计也是 exec 位置处引起的。

一个奇怪的问题 ? VSCode 插件报错偶现 npm/node not found 问题排查&解决 - 运行时和内置终端 $PATH 不匹配

其实开发过程中我也有复现过类似问题,但通常是一闪而过,或者 reload window 就可以解决,vscode 插件运行时是魔改后的 node 运行时,现在回顾起来这样未排查就武断凭 node 开发经验下结果的确是太自负了。所以我决定这次彻底搞清楚到底是什么原因导致的~

一个奇怪的问题 ? VSCode 插件报错偶现 npm/node not found 问题排查&解决 - 运行时和内置终端 $PATH 不匹配

问题排查

猜测1:node 可执行文件是否存在

第一个猜测是针对本地环境的,也是对于这个报错最直接的常见问题。当然在终端试验过后,本地终端环境都是正常的。

一个奇怪的问题 ? VSCode 插件报错偶现 npm/node not found 问题排查&解决 - 运行时和内置终端 $PATH 不匹配

猜测2:exec 命令是否可执行

既然本地环境没问题,难道是我的 exec 命令本身就跑不起来?但这个可能性很小,毕竟报错信息是 node 这种全局命令找不到,而不是脚本的报错。但出于死马当活马医的心态,我还是决定尝试一下排查。

一个奇怪的问题 ? VSCode 插件报错偶现 npm/node not found 问题排查&解决 - 运行时和内置终端 $PATH 不匹配
当然结果也是没问题的,而且快得飞起…

猜测3:cwd 是否异常

排查了 node 本地环境和 exec 的可能后,我的第一反应是,也许是在某些场景下,逻辑中注入了异常的 cwd,导致执行目录出来了问题。

一个奇怪的问题 ? VSCode 插件报错偶现 npm/node not found 问题排查&解决 - 运行时和内置终端 $PATH 不匹配
验证后仍然正常…

猜测4:仅限 node ?还是所有的全局命令失效

到这里其实我的心态已经有点崩了,我在想既然直接执行 exec 跑 node 找不到命令,那么就说明至少在当时的执行环境下 node 不是一个全局命令。既然如此,我也许可以用 which node 拿到 node 可执行文件所在的文件路径,直接执行可执行文件。

一个奇怪的问题 ? VSCode 插件报错偶现 npm/node not found 问题排查&解决 - 运行时和内置终端 $PATH 不匹配

这个想法看上去很靠谱,但依然失败了

一个奇怪的问题 ? VSCode 插件报错偶现 npm/node not found 问题排查&解决 - 运行时和内置终端 $PATH 不匹配

看来不仅是 node 挂了,所有的全局命令全部都不行了。不过 which 这个命令挂了倒的确给了我一个启发,which 命令是用来查找并显示在 $PATH 环境变量所定义的目录列表中是否存在指定命令的可执行文件。
难道当前执行环境 $PATH 本身就是错的?

猜测5:vscode内置shell与系统终端不匹配?

在讲这个猜测前,这里我们先简单温习一下$PATH 是什么?

$PATH 是一个在 Unix-like 操作系统(包括 Linux、macOS 等)和 Windows 操作系统中的环境变量。它是一个字符串,包含了多个由冒号(Unix-like 系统)或分号(Windows 系统)分隔的目录路径列表。
通过配置 $PATH,用户可以轻松地执行位于系统不同目录下的命令,而无需每次都输入完整的路径。这对于频繁使用的命令或自定义工具尤其有用。

简单来说,就是存放全局命令的根目录,全局命令的可执行文件路径需要是 PATH 的子集。PATH 不同,那就不得不怀疑一下,vscode 的内置 shell 是不是设置得不对了。

一个奇怪的问题 ? VSCode 插件报错偶现 npm/node not found 问题排查&解决 - 运行时和内置终端 $PATH 不匹配

一个奇怪的问题 ? VSCode 插件报错偶现 npm/node not found 问题排查&解决 - 运行时和内置终端 $PATH 不匹配

我系统中默认使用的终端就是 zsh,看上去仍然是正确的~

猜测6:vscode 内置终端 和 vscode 插件运行时 $PATH 不同?

到这里只剩下最后一个原因了,那就是 vscode 插件运行时的 $PATH 和 vscode 内置终端的 $PATH 并不相同。vscode 插件运行时的 $PATH 继承于启动 vscode 的父进程环境,当然我们设置过默认终端,所以理论上说这两者应该是相同的。

但这里的确也没有其他的可能性了,我们先按这个思路来排查,毕竟也是一个偶现的小概率事件。

我们先看 vscode 内置终端中 $PATH是什么。

一个奇怪的问题 ? VSCode 插件报错偶现 npm/node not found 问题排查&解决 - 运行时和内置终端 $PATH 不匹配

这个是符合预期的,也就是它会先在 /Users/bytedance/.local/bin 下找所有的可执行文件,找不到再依次降级,我所有的个人文件都在这里面,所以是可以找到的。

这个通过前面猜测推断,也肯定是正常的,不然不可能都能正常执行。我们接着来看vscode 插件运行时的 $PATH,这个我们需要使用脚本输出,因为它是独立的一个进程。

一个奇怪的问题 ? VSCode 插件报错偶现 npm/node not found 问题排查&解决 - 运行时和内置终端 $PATH 不匹配

的确不一样,vscode 插件运行时中的 $PATH 中并没有包含任何全局路径。所以我们也就定位到了原因,vscode 插件运行时在某些情况下并没有顺利继承默认终端中的环境变量 $PATH,而是使用了类Unix操作系统(如Linux、macOS)中的默认命令行路径。(不排除是 vscode 中配置的兜底,在某些场景下继承默认终端变量异常时,就使用系统默认 $PATH)

解决方案

既然定位到了原因,我们就可以开始考虑怎么解决了。我们只需要使得 vscode 插件运行时能顺利继承终端中的环境变量,这个问题就可以迎刃而解了。

方案1:code 启动,自动继承终端的环境变量

第一个方案是,使用 code 命令启动 vscode,这时候 vscode 将会自动继承启动终端的环境变量。还不知道什么是 code 命令的同学可以网上搜下,简单来说就是 vscode 提供的一个全局命令,可以通过 code 直接打开 vscode。

一个奇怪的问题 ? VSCode 插件报错偶现 npm/node not found 问题排查&解决 - 运行时和内置终端 $PATH 不匹配

方案2:exec 取 terminal.integrated.env 作为优先级更高的兜底

terminal.integrated.env 是VSCode中用来配置集成终端环境变量的一个设置选项。这个设置项允许你为不同操作系统(环境)自定义集成终端的环境变量。

macOS 的配置示例如下

"terminal.integrated.env.osx": { 
    "PATH": "${env:PATH}:/Users/user/custom/path" 
}

我们可以配置对应的环境变量到配置项中,然后在执行 child_process 模块的时候,通过 vscode API 取对应的环境变量配置项作为 env 参数的兜底,从而达到在运行时封闭进程内同步外部进程环境变量的效果。

方案3:换依赖调用,而非 exec

这个是我最后用的方案,不再使用 exec,而通过依赖包调用。当然这个不算从根源解决,只是为了让整个流程更开箱即用,逃避了问题而已,并不是所有的场景都有比较合适的方式替代的。

方案4:插件开发阶段,可配置 launch.json path 变量手动注入环境变量

上面的三种方案都是针对用户使用阶段,出现类似问题的解决方案。如果是在插件的开发阶段,我们也可以通过在 launch.json 中配置 path 变量手动注入环境变量。这样通过该 launch.json 启动的环境将强制被配置的环境变量覆盖。

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Run Extension",
            "type": "extensionHost",
            "request": "launch",
            "args": [
                "--extensionDevelopmentPath=${workspaceFolder}"
            ],
            "outFiles": [
                "${workspaceFolder}/dist/**/*.js"
            ],
            "path": "/Users/bytedance/.local/bin"
        }
    ]
}

小结

到这里本文的课程就结束了,一个看似简单的偶现环境问题,实际也暗藏着不少知识点。相信经过这次学习,对大家都有或多或少的启发,在以后遇到类似全局命令失效的问题时,大家也可以从终端环境、cwd、$PATH展开,针对问题的实际情况深入排查。

感谢大家的时间,有问题欢迎在评论区交流讨论。

原文链接:https://juejin.cn/post/7337896254544674870 作者:祯民

(0)
上一篇 2024年2月22日 下午4:37
下一篇 2024年2月22日 下午4:48

相关推荐

发表回复

登录后才能评论