diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index 10472884a7b2e..6ff49b76e23db 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -532,6 +532,7 @@ graph LR; npmcli-arborist-->bin-links; npmcli-arborist-->cacache; npmcli-arborist-->common-ancestor-path; + npmcli-arborist-->gar-promise-retry["@gar/promise-retry"]; npmcli-arborist-->hosted-git-info; npmcli-arborist-->isaacs-string-locale-compare["@isaacs/string-locale-compare"]; npmcli-arborist-->json-stringify-nice; diff --git a/package-lock.json b/package-lock.json index 1d41c0e575c87..3b95fb9245223 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14440,6 +14440,7 @@ "version": "9.4.0", "license": "ISC", "dependencies": { + "@gar/promise-retry": "^1.0.0", "@isaacs/string-locale-compare": "^1.1.0", "@npmcli/fs": "^5.0.0", "@npmcli/installed-package-contents": "^4.0.0", diff --git a/workspaces/arborist/lib/arborist/rebuild.js b/workspaces/arborist/lib/arborist/rebuild.js index 317cfc1df8a72..d4cce1ac02776 100644 --- a/workspaces/arborist/lib/arborist/rebuild.js +++ b/workspaces/arborist/lib/arborist/rebuild.js @@ -9,6 +9,7 @@ const runScript = require('@npmcli/run-script') const { callLimit: promiseCallLimit } = require('promise-call-limit') const { depth: dfwalk } = require('treeverse') const { isNodeGypPackage, defaultGypInstallScript } = require('@npmcli/node-gyp') +const { promiseRetry } = require('@gar/promise-retry') const { log, time } = require('proc-log') const { resolve } = require('node:path') @@ -381,13 +382,20 @@ module.exports = cls => class Builder extends cls { const timeEnd = time.start(`build:link:${node.location}`) - const p = binLinks({ + // On Windows, antivirus/indexer can transiently lock files, causing EPERM/EACCES/EBUSY on the rename inside write-file-atomic (used by bin-links/fix-bin.js), so, retry with backoff. + const p = promiseRetry((retry) => binLinks({ pkg: node.package, path: node.path, top: !!(node.isTop || node.globalTop), force: this.options.force, global: !!node.globalTop, - }) + }).catch(/* istanbul ignore next - Windows-only transient antivirus locks */ err => { + if (process.platform === 'win32' && + (err.code === 'EPERM' || err.code === 'EACCES' || err.code === 'EBUSY')) { + return retry(err) + } + throw err + }), { retries: 5, minTimeout: 500 }) await (this.#doHandleOptionalFailure ? this[_handleOptionalFailure](node, p) diff --git a/workspaces/arborist/package.json b/workspaces/arborist/package.json index 3b49201fe966b..ea0c5262103c7 100644 --- a/workspaces/arborist/package.json +++ b/workspaces/arborist/package.json @@ -3,6 +3,7 @@ "version": "9.4.0", "description": "Manage node_modules trees", "dependencies": { + "@gar/promise-retry": "^1.0.0", "@isaacs/string-locale-compare": "^1.1.0", "@npmcli/fs": "^5.0.0", "@npmcli/installed-package-contents": "^4.0.0",