@@ -142,7 +142,7 @@ function downloadAndExtractAsync(version) {
142
142
childNames . forEach ( childName => {
143
143
let oldPath = path . join ( extractedDirPath , childName ) ;
144
144
let newPath = path . join ( targetDir , childName ) ;
145
- fs . renameSync ( oldPath , newPath ) ;
145
+ renameWithRetry ( oldPath , newPath ) ;
146
146
} ) ;
147
147
148
148
// Remove the now-empty directory.
@@ -215,6 +215,38 @@ function remove(version) {
215
215
return result ;
216
216
}
217
217
218
+ /**
219
+ * Synchronously renames a file or directory, retrying to handle
220
+ * occasional 'EPERM' or 'EACCS' errors.
221
+ */
222
+ function renameWithRetry ( from , to ) {
223
+ // Drived/simplified from https://github.com/isaacs/node-graceful-fs/pull/119
224
+ let backoff = 0 ;
225
+ const backoffUntil = Date . now ( ) + 5000 ;
226
+ function tryRename ( ) {
227
+ try {
228
+ fs . renameSync ( from , to ) ;
229
+ } catch ( e ) {
230
+ if ( ! isWindows ) {
231
+ // The retry with backoff is only applicable to Windows.
232
+ throw e ;
233
+ } else if ( ( e . code === 'EACCS' || e . code === 'EPERM' ) && Date . now ( ) < backoffUntil ) {
234
+ if ( backoff < 100 ) {
235
+ backoff += 10 ;
236
+ }
237
+ const waitUntil = Date . now ( ) + backoff ;
238
+ while ( Date . now ( ) < waitUntil ) { }
239
+ tryRename ( ) ;
240
+ } else if ( backoff > 0 && e . code === 'ENOENT' ) {
241
+ // The source no longer exists; assume it was renamed.
242
+ } else {
243
+ throw e ;
244
+ }
245
+ }
246
+ }
247
+ tryRename ( ) ;
248
+ }
249
+
218
250
/**
219
251
* Creates a hierarchy of directories as necessary.
220
252
*/
@@ -286,10 +318,9 @@ async function fixNpmCmdShimsAsync(targetDir) {
286
318
if ( ! isWindows ) return ;
287
319
288
320
try {
289
- // This assumes the npm version carried with the node installation
290
- // includes a `cmd-shim` module. Currently true for at least npm >= 3.
291
321
const cmdShimPath = path . join (
292
322
targetDir , 'node_modules' , 'npm' , 'node_modules' , 'cmd-shim' ) ;
323
+ fs . statSync ( cmdShimPath ) ;
293
324
const cmdShim = require ( cmdShimPath ) ;
294
325
295
326
// Enumerate .cmd files in the target directory and fix if they are shims.
@@ -317,6 +348,12 @@ async function fixNpmCmdShimsAsync(targetDir) {
317
348
} ) ;
318
349
}
319
350
} catch ( e ) {
351
+ if ( e . code === 'ENOENT' ) {
352
+ // Currently all npm >= 3 include the cmd-shim module, but maybe
353
+ // someday it won't? Also it does not exist with test mocking.
354
+ return ;
355
+ }
356
+
320
357
// Not a fatal error. Most things still work if the shims are not fixed.
321
358
// The only problem may be that the global npm package cannot be upgraded.
322
359
console . warn ( 'Warning: Failed to fix npm cmd shims: ' + e . message ) ;
@@ -326,4 +363,5 @@ async function fixNpmCmdShimsAsync(targetDir) {
326
363
module . exports = {
327
364
addAsync,
328
365
remove,
366
+ renameWithRetry,
329
367
} ;
0 commit comments