【问题标题】:Why does "npm install" rewrite package-lock.json?为什么“npm install”会重写 package-lock.json?
【发布时间】:2017-12-14 18:40:53
【问题描述】:

我刚刚升级到 npm@5。我现在有一个 package-lock.json 文件,其中包含 package.json 中的所有内容。我希望,当我运行npm install 时,将从锁定文件中提取依赖版本以确定应该在我的 node_modules 目录中安装什么。奇怪的是,它实际上最终修改并重写了我的 package-lock.json 文件。

例如,锁定文件的 typescript 指定为版本 2.1.6。然后,在npm install 命令之后,版本更改为2.4.1。这似乎违背了锁定文件的全部目的。

我错过了什么?如何让 npm 真正尊重我的锁定文件?

【问题讨论】:

  • 这不能回答你的问题,所以希望评论是好的,但看看 Yarn。切换对我们来说用了不到一个小时。
  • 同样的问题,但使用了纱线github.com/yarnpkg/yarn/issues/570(很有启发性)
  • 我遇到了同样的问题。当我运行npm install 时,我的package-lock.json 会重新生成。这闻起来像一个 npm 错误。您使用自己的注册表吗?
  • @YvesM。 --no-save 阻止更改锁定文件,但不影响 OP 提到的愚蠢的一级依赖升级。

标签: node.js npm npm-install package-lock.json


【解决方案1】:

更新 3: 正如其他答案所指出的那样,npm 5.7.0 中引入了 npm ci 命令,作为在 CI 上下文中实现快速且可重复构建的另一种方法。请参阅documentationnpm blog 了解更多信息。


更新 2: 更新和澄清文档的问题是GitHub issue #18103


更新 1: 下面描述的行为已在 npm 5.4.2 中得到修复:当前预期的行为在 GitHub issue #17979 中进行了概述。


原始答案:package-lock.json 的行为在 npm 5.1.0 中已更改,如 issue #16866 中所述。从 5.1.0 版本开始,您观察到的行为显然是 npm 所意图的。

这意味着只要在package.json 中找到新版本的依赖项,package.json 就可以覆盖package-lock.json。如果您想有效地固定您的依赖项,您现在必须指定不带前缀的版本,例如,您需要将它们写为1.2.0 而不是~1.2.0^1.2.0。然后package.jsonpackage-lock.json 的组合将产生可重复的构建。需要明确的是:package-lock.json 单独不再锁定根级别的依赖关系!

这个设计决定是否好是有争议的,在 GitHub 上issue #17979 上的这种混淆导致了持续的讨论。 (在我看来,这是一个值得商榷的决定;至少 lock 这个名字不再适用了。)

另外一个注意事项:对于不支持不可变包的注册表也有限制,例如当您直接从 GitHub 而不是 npmjs.org 拉包时。更多解释请见this documentation of package locks

【讨论】:

  • 那么npm update 的黑客是什么? :o 我有同样的感觉 npm install 更新了 deps,但我不想相信它.. 但似乎这是真的.. 无论如何仍然可以选择使用 npm shrinkwrap 锁定 deps,但绝对名称 package-lock 不正确,因为它不会冻结,也不会锁定依赖项..
  • 一团糟!世界上最大的包管理器,但它没有关于它应该如何工作的文档。每个人都在猜测它应该做什么,它变成了一场意见战。讨论是好的,但应该在发布到野外之前进行。在某些时候,需要有人做出最终决定,然后才能实施、记录和发布。 PHP 是由委员会和 ad-hoc 共同设计的,看看结果如何。我不希望看到同样的事情发生在如此重要且被广泛使用的工具上。
  • 那么,使用 package-lock 有什么意义呢?我以为它会在不同的工作空间中创建相同的环境,但事实证明它什么也没做
  • “那么 package.json 和 package-lock.json 的组合将产生可重复的构建。” “package-lock.json”在这里有什么作用?如果不使用版本前缀,单独的“package.json”不是已经产生了可重复的构建吗?
  • @JānisElmeris 我认为 package.json 无法锁定深度依赖...
【解决方案2】:

我发现将有一个新版本的 npm 5.7.1 使用新命令 npm ci,它将仅从 package-lock.json 安装

新的 npm ci 命令仅从您的锁定文件安装。如果你的 package.json 和你的 lock-file 不同步,就会报错。

它的工作原理是丢弃你的 node_modules 并从头开始重新创建它。

除了保证您只会获得锁定文件中的内容之外,当您不从 node_modules 开始时,它也比 npm install 快得多(2x-10x!)。

顾名思义,我们预计它将为持续集成环境带来巨大的好处。我们还希望通过 git 标签进行生产部署的人们会看到重大收获。

【讨论】:

  • 如果存在锁定文件,这应该是默认行为。
  • 所以他们改变了 npm i 的工作方式,只是在几个月后将其作为 npm ci 重新引入?
  • 我还是一头雾水。文档说“确保你有一个包锁和最新的安装:npm install,然后在该项目中运行命令npm cinpm install 不会覆盖 package-lock.json 文件吗?
  • AFAIK: @adiga - 从版本 5.4 开始,npm 更改锁定文件 如果需要这样做,以满足 packages.json 中的规范。所以如果包过去说thatpackage: 1,而锁说..: 1.0.4,开发人员可以编辑说thatpackage: 2——这将强制更改锁文件,因为1.0.4与新指定的范围不兼容。如果不更改packages.json,将保持锁定在确切版本,直到删除锁定文件。 [如果没有保持锁定,并且没有更改 packages.json,请提交错误报告。]
  • 花了我一整天的时间。我花了一整天的时间在这个基本问题上 :( :(
【解决方案3】:

简答:

  • npm install 仅在满足 package.json 要求时才尊重 package-lock.json。
  • 如果不满足这些要求,则会更新包并覆盖包锁。
  • 如果您希望安装失败而不是在发生这种情况时覆盖包锁定,请使用npm ci

这是一个可以解释事情的场景(已通过 NPM 6.3.0 验证)

你在 package.json 中声明一个依赖,比如:

"depA": "^1.0.0"

然后,npm install 将生成一个 package-lock.json,其中包含:

"depA": "1.0.0"

几天后,“depA”的更新次要版本发布,比如“1.1.0”,那么以下情况成立:

npm ci       # respects only package-lock.json and installs 1.0.0

npm install  # also, respects the package-lock version and keeps 1.0.0 installed 
             # (i.e. when package-lock.json exists, it overrules package.json)

接下来,您手动将 package.json 更新为:

"depA": "^1.1.0"

然后重新运行:

npm ci      # will try to honor package-lock which says 1.0.0
            # but that does not satisfy package.json requirement of "^1.1.0" 
            # so it would throw an error 

npm install # installs "1.1.0" (as required by the updated package.json)
            # also rewrites package-lock.json version to "1.1.0"
            # (i.e. when package.json is modified, it overrules the package-lock.json)

【讨论】:

  • 这确实是“锁定”文件的预期行为。显然,旧版本的 NPM 并非如此。
  • 那么 npm 是如何跟踪 package.json 的最后更新的呢?将 package.json 和 package-lock.json 移动到另一台计算机时会发生什么?新机上的 npm 是如何知道 package.lock 是原来的还是已经更新了,来决定是否需要更新 package-lock.json?
  • @LahiruChandima 它并没有真正跟踪更新。 npm install 将使用来自 package-lock.json 的锁定版本,除非它不满足 package.json 在这种情况下它会安装 package.json 并相应地重建 package-lock.json。如果您更改了package.json,使现有的包锁仍然满足更新后的package.json,它将继续使用package-lock
  • 如果你在 node_modules 中已经有一个满足 package.json 要求的模块,那么npm install 什么都不做,不管 package-lock.json。即使有可用的更新与 package.json 中指定的 semver 匹配,我们也必须显式更新包。至少这是我多年来的经验。
  • @ToolmakerSteve 我也对@carlin.scott 报告的行为持怀疑态度,但我只是对其进行了测试,事实上他是正确的。如果node_modules 中的版本满足package.json 中的范围,并且没有package-lock.json 文件,则npm 在运行npm install 时不会更新模块。我想这很好,因为您可以使用npm update(或最新的npm-check)来更新依赖项,并且对于仅向package.json 添加一个条目并且不希望不相关的包更新自己的情况,这种行为会更快到满足 sem-ver 范围的最新版本。
【解决方案4】:

使用新引入的

npm ci

npm ci 承诺给大型团队带来最大的好处。让开发人员能够在包锁定上“签字”可以促进大型团队之间更有效的协作,并且能够准确安装锁定文件中的内容有可能每月节省数十甚至数百小时的开发时间,从而解放团队花费更多的时间来构建和运送令人惊叹的东西。

Introducing npm ci for faster, more reliable builds

【讨论】:

  • 这对我来说似乎是正确的?其他人可以确认吗?
  • @phouse512 这是正确的。我们几乎使用npm ci,并且仅在更新或安装新软件包时使用npm install
  • 最近的 cmets 等。这就是我要回答的问题。太糟糕了,他们无法解决可怕的混乱,但如果新的福音是“npm ci”,那很好。我可以适应。
  • 太糟糕了,它 总是 删除现有的 node_modules 目录并在本地重建,即使这是一个空但重要的符号链接。 :(
  • @ToolmakerSteve 不要屏住呼吸!我认为删除目录的内容比删除目录要慢得多。您必须枚举内容,然后向操作系统发出一系列删除命令,而不仅仅是一个删除命令。由于之前在 npm 上解决的性能问题以及使用 npm ci 的改进,我预计他们将非常不愿意引入任何可能降低相当不常见用例的性能的东西。您可能想查看pnpm.js.org,尽管它使用硬链接来减少磁盘使用量。
【解决方案5】:

使用npm ci 命令代替npm install

“ci”代表“持续集成”。

它将基于 package-lock.json 文件而不是宽松的 package.json 文件依赖安装项目依赖。

它将为您的队友生成相同的构建,而且速度也快得多。

您可以在这篇博文中了解更多信息: https://blog.npmjs.org/post/171556855892/introducing-npm-ci-for-faster-more-reliable

【讨论】:

  • ci 指的是“持续集成”,如宣布命令的文档和博客文章中所述:blog.npmjs.org/post/171556855892/…
  • 谢谢乔。我已经用正确的名称更新了我的答案并链接到了博客文章。 ?(对于那些阅读本文的人,之前我说过它代表“干净安装”)
  • "而且它也快得多" - 它将删除 node_modules 文件夹并从头开始重新创建它。真的快很多吗? npm install 是否也会删除 node_modules 文件夹?
  • 我认为速度来自npm,不需要计算要下载什么包。把它想象成npm install 在运行时必须解决所有的包依赖关系。 npm ci 只是“获取这些确切模块”的购物清单。
  • 我遇到了这个问题,我只是尝试了npm ci 而不是npm i,实际上它甚至不允许我执行该任务。它告诉我我需要先运行npm i :(
【解决方案6】:

这个问题似乎在 npm v5.4.2 中得到修复

https://github.com/npm/npm/issues/17979

(向下滚动到线程中的最后一条评论)

更新

实际上已在 5.6.0 中修复。 5.4.2 中存在一个跨平台错误,导致该问题仍然存在。

https://github.com/npm/npm/issues/18712

更新 2

在这里查看我的答案: https://stackoverflow.com/a/53680257/1611058

npm ci 是您现在安装现有项目时应该使用的命令。

【讨论】:

  • 我使用的是 5.4.2,当npm i 时,它仍然导致我的 package-lock.json 被修改。例如,当我在不支持fsevents 的机器上npm i 时删除模块fsevents,然后在支持npm i 的机器上再次添加该模块。
  • 那么您应该在 npm GitHub 存储库中提出一个新问题来解释这一点。如果按照他们所说的方式不起作用,那么他们会将其视为迫切需要修复的高优先级错误。
  • @hrdwdmrbl 在与 Mac OS X 贡献者合作时,我看到了相同的 fseventspackage-lock.jsonnpm@5.5。如果你还没有打开一个问题,我会的。
  • @hrdwdmrbl 在我留下评论并忘记回到 SO 更新我的评论后,我发现(以及相关问题的长线程)。谢谢你让我回来。一切都很好。
【解决方案7】:

将来,您将能够使用--from-lock-file(或类似的)标志从package-lock.json 安装only,而无需修改它。

这对于可重复构建很重要的 CI 等环境很有用。

请参阅https://github.com/npm/npm/issues/18286 以跟踪该功能。

【讨论】:

  • 我对此表示怀疑。如果不同操作系统的依赖关系不同,如何强制安装不起作用的东西?
  • @YevgeniyAfanasyev 而不是那个标志,它被实现为npm ci,它也可以处理你的问题。
【解决方案8】:

也许你应该使用这样的东西

npm ci

而不是使用npm install 如果您不想更改软件包的版本。

根据官方文档,npm installnpm ci 都安装了项目所需的依赖项。

主要区别在于,npm install 确实安装了以packge.json 为参考的软件包。在npm ci 的情况下,它确实安装了以package-lock.json 作为参考的软件包,确保每次安装准确的软件包。

【讨论】:

    【解决方案9】:

    你可能有类似的东西:

    "typescript":"~2.1.6"
    

    在您的 package.json 中,npm 更新到最新的次要版本,在您的情况下为 2.4.1

    编辑:来自 OP 的问题

    但这并不能解释为什么“npm install”会更改锁定文件。锁定文件不是要创建可重现的构建吗?如果是这样的话, 无论 semver 值如何,它仍应使用相同的 2.1.6 版本。

    答案:

    这旨在锁定您的完整依赖关系树。假设typescript v2.4.1 需要widget ~v1.0.0。当你 npm 安装它 抓住widget v1.0.0。稍后在您的开发人员(或 CI 构建)上 执行 npm 安装并获得 typescript v2.4.1widget 已经 更新为widget v1.0.1。现在你的节点模块不同步了。这 是package-lock.json 阻止的。

    或更笼统地说:

    举个例子,考虑

    A 包:

    { “名称”:“A”,“版本”:“0.1.0”,“依赖项”:{ “B”:“

    包B:

    { “名称”:“B”,“版本”:“0.0.1”,“依赖项”:{ "C": "

    和包C:

    {“名称”:“C”,“版本”:“0.0.1”}

    如果只有这些版本 注册表中可用的 A、B 和 C,然后是正常的 npm install A 将安装:

    A@0.1.0 -- B@0.0.1 -- C@0.0.1

    但是,如果发布了 B@0.0.2,则将安装新的 npm install A:

    A@0.1.0 -- B@0.0.2 -- C@0.0.1 假设新版本没有修改 B 的依赖。当然,新版本的 B 可以包括一个新的 C 的版本和任意数量的新依赖项。如果这些变化是 不可取,A 的作者可以指定对 B@0.0.1 的依赖。 但是,如果 A 的作者和 B 的作者不是同一个人,则有 A的作者没有办法说他或她不想加入 当 B 完全没有改变时,新发布的 C 版本。


    OP 问题 2:让我看看我是否理解正确。你是什​​么 说的是lock文件指定了secondary的版本 依赖,但还是依赖package.json的模糊匹配 确定顶级依赖关系。准确吗?

    Answer: No. package-lock 锁定整个包树,包括 package.json 中描述的根包。如果typescript 被锁定 在您的package-lock.json 中的2.4.1,它应该保持这种状态,直到它 改变了。让我们说明天typescript 发布版本2.4.2。 如果我检查你的分支并运行npm install,npm 将尊重 锁定文件并安装2.4.1

    更多关于package-lock.json:

    package-lock.json 会自动为 npm 修改 node_modules 树或 package.json 的任何操作生成。它描述了生成的确切树,以便后续安装能够生成相同的树,而不管中间依赖项更新如何。

    此文件旨在提交到源存储库中,并用于各种用途:

    描述依赖关系树的单一表示,以保证团队成员、部署和持续集成安装完全相同的依赖关系。

    为用户提供一种“时间旅行”到 node_modules 先前状态的工具,而无需提交目录本身。

    通过可读的源代码控制差异来促进对树更改的更大可见性。

    并通过允许 npm 跳过以前安装的包的重复元数据解析来优化安装过程。

    https://docs.npmjs.com/files/package-lock.json

    【讨论】:

    • 但这并不能解释为什么“npm install”会更改锁定文件。锁定文件不是要创建可重现的构建吗?如果是这样,无论 semver 值如何,它仍应使用相同的 2.1.6 版本。
    • 这就是我要说的。我的包锁定文件说 typescript@2.1.6 但是当我运行 npm install 时,该条目被替换为 typescript@2.4.1。
    • 我也遇到过同样的问题。在我们的 CI/CD 中,package-lock.json 被拉下,然后我们运行 npm install,但 package-lock.json 文件被修改,我们必须执行重置才能拉下下一个更改。
    • 我不明白。如果后续安装仍可能进行升级,这怎么是“锁定”文件?!
    • 我认为他们开始的想法是将这个文件作为“信息”和“锁定”,然后决定它只是一个“信息”文件。更好的名称是“package-info.json”。我很想有一个“npm install -lock”,它将从“package-lock.json”安装并忽略“package.json”
    【解决方案10】:

    编辑:“lock”这个名字很棘手,它的 NPM 试图赶上 Yarn。它不是任何锁定的文件。 package.json 是一个用户固定文件,一旦“安装”将生成 node_modules 文件夹树,然后该树将写入package-lock.json。所以你看,反过来 - 依赖版本将一如既往地从package.json 中提取,package-lock.json 应该被称为package-tree.json

    (希望这让我的答案更清楚,经过这么多的反对)


    一个简单的答案:package.json 像往常一样拥有你的依赖项,而 package-lock.json 是“一个精确的,更重要的是可重现的 node_modules 树”(取自 npm docs itself)。

    至于棘手的名字,它的 NPM 试图赶上 Yarn。

    【讨论】:

    • 因为如果你运行 npm install 会更新 package-lock。
    【解决方案11】:

    在他们的 github 页面上有一个未解决的问题:https://github.com/npm/npm/issues/18712

    当开发人员使用不同的操作系统时,此问题最为严重。

    【讨论】:

    • 包锁中的重写是有意的,问题不是由此造成的
    【解决方案12】:

    npm install 检测对 package.json 文件所做的任何更改以相应地反映依赖项列表。

    例如。如果用户添加或删除了新的依赖项,则构建将下载或删除本地计算机中的依赖项。我们可以将其与 java 中的 .m2 存储库进行比较,其中 maven 不断跟踪 pom.xml 文件以更新依赖关系。

    package-lock.json 是内部进程在运行时使用的 package.json 的副本,唯一的区别是 package-lock.json 对用户是只读的。

    【讨论】:

    • 这个答案与接受的答案有何不同?
    • 因为有些部分是不正确的。
    猜你喜欢
    • 2020-07-16
    • 2022-01-02
    • 2019-09-27
    • 1970-01-01
    • 2017-12-31
    • 2019-03-31
    • 2020-12-29
    • 2018-11-20
    • 1970-01-01
    相关资源
    最近更新 更多