copy-sync.js
  1  'use strict'
  2  
  3  const fs = require('graceful-fs')
  4  const path = require('path')
  5  const mkdirsSync = require('../mkdirs').mkdirsSync
  6  const utimesMillisSync = require('../util/utimes').utimesMillisSync
  7  const stat = require('../util/stat')
  8  
  9  function copySync (src, dest, opts) {
 10    if (typeof opts === 'function') {
 11      opts = { filter: opts }
 12    }
 13  
 14    opts = opts || {}
 15    opts.clobber = 'clobber' in opts ? !!opts.clobber : true // default to true for now
 16    opts.overwrite = 'overwrite' in opts ? !!opts.overwrite : opts.clobber // overwrite falls back to clobber
 17  
 18    // Warn about using preserveTimestamps on 32-bit node
 19    if (opts.preserveTimestamps && process.arch === 'ia32') {
 20      console.warn(`fs-extra: Using the preserveTimestamps option in 32-bit node is not recommended;\n
 21      see https://github.com/jprichardson/node-fs-extra/issues/269`)
 22    }
 23  
 24    const { srcStat, destStat } = stat.checkPathsSync(src, dest, 'copy')
 25    stat.checkParentPathsSync(src, srcStat, dest, 'copy')
 26    return handleFilterAndCopy(destStat, src, dest, opts)
 27  }
 28  
 29  function handleFilterAndCopy (destStat, src, dest, opts) {
 30    if (opts.filter && !opts.filter(src, dest)) return
 31    const destParent = path.dirname(dest)
 32    if (!fs.existsSync(destParent)) mkdirsSync(destParent)
 33    return startCopy(destStat, src, dest, opts)
 34  }
 35  
 36  function startCopy (destStat, src, dest, opts) {
 37    if (opts.filter && !opts.filter(src, dest)) return
 38    return getStats(destStat, src, dest, opts)
 39  }
 40  
 41  function getStats (destStat, src, dest, opts) {
 42    const statSync = opts.dereference ? fs.statSync : fs.lstatSync
 43    const srcStat = statSync(src)
 44  
 45    if (srcStat.isDirectory()) return onDir(srcStat, destStat, src, dest, opts)
 46    else if (srcStat.isFile() ||
 47             srcStat.isCharacterDevice() ||
 48             srcStat.isBlockDevice()) return onFile(srcStat, destStat, src, dest, opts)
 49    else if (srcStat.isSymbolicLink()) return onLink(destStat, src, dest, opts)
 50  }
 51  
 52  function onFile (srcStat, destStat, src, dest, opts) {
 53    if (!destStat) return copyFile(srcStat, src, dest, opts)
 54    return mayCopyFile(srcStat, src, dest, opts)
 55  }
 56  
 57  function mayCopyFile (srcStat, src, dest, opts) {
 58    if (opts.overwrite) {
 59      fs.unlinkSync(dest)
 60      return copyFile(srcStat, src, dest, opts)
 61    } else if (opts.errorOnExist) {
 62      throw new Error(`'${dest}' already exists`)
 63    }
 64  }
 65  
 66  function copyFile (srcStat, src, dest, opts) {
 67    fs.copyFileSync(src, dest)
 68    if (opts.preserveTimestamps) handleTimestamps(srcStat.mode, src, dest)
 69    return setDestMode(dest, srcStat.mode)
 70  }
 71  
 72  function handleTimestamps (srcMode, src, dest) {
 73    // Make sure the file is writable before setting the timestamp
 74    // otherwise open fails with EPERM when invoked with 'r+'
 75    // (through utimes call)
 76    if (fileIsNotWritable(srcMode)) makeFileWritable(dest, srcMode)
 77    return setDestTimestamps(src, dest)
 78  }
 79  
 80  function fileIsNotWritable (srcMode) {
 81    return (srcMode & 0o200) === 0
 82  }
 83  
 84  function makeFileWritable (dest, srcMode) {
 85    return setDestMode(dest, srcMode | 0o200)
 86  }
 87  
 88  function setDestMode (dest, srcMode) {
 89    return fs.chmodSync(dest, srcMode)
 90  }
 91  
 92  function setDestTimestamps (src, dest) {
 93    // The initial srcStat.atime cannot be trusted
 94    // because it is modified by the read(2) system call
 95    // (See https://nodejs.org/api/fs.html#fs_stat_time_values)
 96    const updatedSrcStat = fs.statSync(src)
 97    return utimesMillisSync(dest, updatedSrcStat.atime, updatedSrcStat.mtime)
 98  }
 99  
100  function onDir (srcStat, destStat, src, dest, opts) {
101    if (!destStat) return mkDirAndCopy(srcStat.mode, src, dest, opts)
102    if (destStat && !destStat.isDirectory()) {
103      throw new Error(`Cannot overwrite non-directory '${dest}' with directory '${src}'.`)
104    }
105    return copyDir(src, dest, opts)
106  }
107  
108  function mkDirAndCopy (srcMode, src, dest, opts) {
109    fs.mkdirSync(dest)
110    copyDir(src, dest, opts)
111    return setDestMode(dest, srcMode)
112  }
113  
114  function copyDir (src, dest, opts) {
115    fs.readdirSync(src).forEach(item => copyDirItem(item, src, dest, opts))
116  }
117  
118  function copyDirItem (item, src, dest, opts) {
119    const srcItem = path.join(src, item)
120    const destItem = path.join(dest, item)
121    const { destStat } = stat.checkPathsSync(srcItem, destItem, 'copy')
122    return startCopy(destStat, srcItem, destItem, opts)
123  }
124  
125  function onLink (destStat, src, dest, opts) {
126    let resolvedSrc = fs.readlinkSync(src)
127    if (opts.dereference) {
128      resolvedSrc = path.resolve(process.cwd(), resolvedSrc)
129    }
130  
131    if (!destStat) {
132      return fs.symlinkSync(resolvedSrc, dest)
133    } else {
134      let resolvedDest
135      try {
136        resolvedDest = fs.readlinkSync(dest)
137      } catch (err) {
138        // dest exists and is a regular file or directory,
139        // Windows may throw UNKNOWN error. If dest already exists,
140        // fs throws error anyway, so no need to guard against it here.
141        if (err.code === 'EINVAL' || err.code === 'UNKNOWN') return fs.symlinkSync(resolvedSrc, dest)
142        throw err
143      }
144      if (opts.dereference) {
145        resolvedDest = path.resolve(process.cwd(), resolvedDest)
146      }
147      if (stat.isSrcSubdir(resolvedSrc, resolvedDest)) {
148        throw new Error(`Cannot copy '${resolvedSrc}' to a subdirectory of itself, '${resolvedDest}'.`)
149      }
150  
151      // prevent copy if src is a subdir of dest since unlinking
152      // dest in this case would result in removing src contents
153      // and therefore a broken symlink would be created.
154      if (fs.statSync(dest).isDirectory() && stat.isSrcSubdir(resolvedDest, resolvedSrc)) {
155        throw new Error(`Cannot overwrite '${resolvedDest}' with '${resolvedSrc}'.`)
156      }
157      return copyLink(resolvedSrc, dest)
158    }
159  }
160  
161  function copyLink (resolvedSrc, dest) {
162    fs.unlinkSync(dest)
163    return fs.symlinkSync(resolvedSrc, dest)
164  }
165  
166  module.exports = copySync