对于通过require()调用目标文件的命令行工具,我们如何安全地避免本地和全局npm包之间的冲突



全局安装和本地安装的冲突

我正在开发npm命令行工具和程序包https://github.com/ecma-make/ecmake.我遇到了该包的全局安装版本和本地安装版本之间的奇怪冲突。

我可以通过将一个链接到另一个的库来避免这种冲突。然后只有一个库实例,并且没有冲突。现在,我必须考虑用户,他确实在两个地方都安装了包,一次是全局的,以便能够在没有npx前缀的情况下运行命令,一次在本地的,以便在package.json的dev部分中列出库。

如何繁殖

# prepare test fixture
mkdir ecmakeTest
cd ecmakeTest/
npm init -y
# install globally
npm install -g @ecmake/ecmake@0.3.1
npm ls -g @ecmake/ecmake
# install locally
npm install --save-dev @ecmake/ecmake@0.3.1
npm ls @ecmake/ecmake
# init ecmakeCode.js
npx ecmake --init
# run with local lib => shows the expected behaviour
npx ecmake all 
# run with global lib => NoRootTaskError
ecmake all

冲突的起因

堆栈跟踪将我们引导到全局安装中的行:/usr/local/lib/node_modules/@ecmake/ecmake/lib/runner/reader.js:21:13

if (!(root instanceof Task)) {
throw new Reader.NoRootTaskError(this.makefile);
}

发生了什么

使用本地库创建的对象root已根据全局库的类定义进行检查。它们有相同的代码,但它们是相同代码的不同副本。

全局ecmake运行程序需要本地生成文件ecmakeCode.js。这个文件又需要本地库的Task定义。

const root = module.exports = require('@ecmake/ecmake').makeRoot();
root.default
.described('defaults to all')
.awaits(root.all);
[...]

我们可以通过将日志记录指令放入这两个库中来验证这两个库都被调用了。

其他人如何解决这个问题

GulpGrunt导出一个函数,该函数通过注入获取实际依赖关系。虽然依赖注入通常是非常聪明的,但在这种情况下就不那么漂亮了。整个文件都被打包了。我想避免这种包装功能。

请参阅:https://gulpjs.com/docs/en/getting-started/quick-start#create-a-gullfile

请参阅:https://gruntjs.com/getting-started

我已经考虑过的

如果有这样的冲突,跑步者可以先检查一下。万一它可以通过运行子进程将给定给全局ecmake的参数委托给本地npx ecmake

唉,这会减慢跑步者的速度。至少需要一个子流程,可能需要更多子流程来检查情况。

问题

你有解决这一挑战的通用解决方案吗(除了我已经提到的缺点(?

作为我自己答案的第一部分,我确实调查了为什么不可能有规范的解决方案。

没有规范的解决方案

生成文件创建一个数据模型。runner加载并检查这个数据模型,并运行存储在其中的指令。runner和数据模型都需要一个库。有全局npm安装和本地安装。这已经给出了四种可能的组合。

ecmake转轮提供了一个以原始make工具为模型的--base directory选项。其含义是;在做任何事情之前更改到命名目录中";。这为当地图书馆提供了第二个位置。我们是三乘三的组合。

我将列举几种解决方案的策略。综合这些,有几十种或多或少合理的选择。这使得一个规范的答案不太可能。尽管如此,这个示例的基本挑战通常适用于许多命令行工具。

确切答案

准确解决最初问题的答案是那些策略,这些策略将模型和运行器解耦,或者小心,只调用库的一个实例。

附加约束

现实生活并不能准确地回答问题。在ecmake的情况下,对解决方案的搜索带来了额外的约束。我想确保模型运行程序的版本与主版本匹配,生成文件是根据主版本编码的。重大版本更改时应事先避免版本冲突。

因此,生成文件应与package.json中的相应版本捆绑在一起。如果只有全局安装,则应尝试运行,但应发出缺少本地安装的警告。

最新更新