package-lock.json缺失hasInstallScript引发的错误

昨天有个前端项目代码一直npm install 失败,提示找不到预编译包,直接调用node-gyp进行build。以前发生类似问题,如node-sass安装失败,只要配置国内镜像地址,直接拉取到编译后的包。但是这次包本来就在package中了……

错误表象

Error
npm ERR! command sh -c node-gyp rebuild
npm ERR! gyp info it worked if it ends with ok
npm ERR! gyp info using node-gyp@7.1.2

一般看到这个错误,肯定想到是不是没有配置国内源。例如node-sass下载不了,可以在项目的根目录下的.npmrc 文件配置:

sass_binary_site=https://npmmirror.com/mirrors/node-sass

因为在node-sass的install前,会先尝试从github上下载二进制包,如果下载不到就执行编译。国内github连接超时,使得下载失败,进入编译流程。所以配置国内镜像,能下载到包就可以解决这个问题。有时已经配置了国内源,但还会失败?原因有可能是执行了npm i -f,-f 参数会强制编译。

解决方法

但这次错误比较特殊,最后发现是 package-lock.json中缺失hasInstallScript配置项,导致npm在install的时候,不管三七二十一,直接就执行了node-gyp rebuild,跳过了检测脚本。
只要删除掉package-lock.json文件,重新生成就可以了(node v16, npm v8)。

过程分析

hasInstallScript

首先发现问题后,直接对比package-lock文件,发现npm包配置缺少了hasInstallScript信息。

image.png

Github上也有相关的Issue:[BUG] package.json has "lockfileVersion": 2 but is missing "hasInstallScript": true · Issue #2606 · npm/cli (github.com)
具体原因可能是用了不同版本node npm,hasInstallScript 是lockfileVersion:2 后加入的,旧版本的npm生成可能会有不兼容的情况。

npm rebuild

那为什么没有hasInstallScript,就一定rebuild,而不执行install 或者 preInstall 中写的脚本呢?这就需要看一下npm代码了, 搜索hasInstallScript关键字,在 cli/workspaces/arborist/lib/arborist/rebuild.js at v8.3.1 · npm/cli · GitHub ,在分析package-lock文件时,如果有配置hasInstallScript,才会去读取package.json文件,拿到preinstall, install, postinstall, prepare,显然,没有配置就会跳过。

    const { package: pkg, hasInstallScript } = node.target
    const { gypfile, bin, scripts = {} } = pkg
 
    const { preinstall, install, postinstall, prepare } = scripts
    const anyScript = preinstall || install || postinstall || prepare
    if (!refreshed && !anyScript && (hasInstallScript || this[_oldMeta])) {
      set.add(node)
      const pkg = await rpj(node.path + '/package.json').catch(() => ({}))
      set.delete(node)

      const { scripts = {} } = pkg
      node.package.scripts = scripts
      return this[_addToBuildSet](node, set, true)
    }

接着往下看

    const isGyp = gypfile !== false &&
      !install &&
      !preinstall &&
      await isNodeGypPackage(node.path)

    if (bin || preinstall || install || postinstall || prepare || isGyp) {
      if (bin) {
        await this[_checkBins](node)
      }
      if (isGyp) {
        scripts.install = defaultGypInstallScript
        node.package.scripts = scripts
      }
      set.add(node)
    }

没有hasInstallScript,那就看一下是否是个NodeGyp包,isNodeGypPackage函数实际是检查npm包里是否有binding.gyp,当然文件肯定在的,isGyp = true。这个包就被设置成install执行默认build脚本,也就是 scripts: { install: 'node-gyp rebuild' }
以下是分析后的node信息:

{
  gypfile: undefined,
  isGyp: true,
  bin: undefined,
  preinstall: undefined,
  install: undefined,
  postinstall: undefined,
  prepare: undefined,
  pkg: {
    version: '0.1.28',
    resolved: 'https://registry.npmmirror.com/deasync/-/deasync-0.1.28.tgz',
    integrity: 'sha512-QqLF6inIDwiATrfROIyQtwOQxjZuek13WRYZ7donU5wJPLoP67MnYxA6QtqdvdBy2mMqv5m3UefBVdJjvevOYg==',
    dev: true,
    dependencies: { bindings: '^1.5.0', 'node-addon-api': '^1.7.1' },
    engines: { node: '>=0.11.0' },
    name: 'deasync',
    _id: 'deasync@0.1.28',
    scripts: { install: 'node-gyp rebuild' }
  }
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容