ncp.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. // imported from ncp (this is temporary, will rewrite)
  2. var fs = require('graceful-fs')
  3. var path = require('path')
  4. var utimes = require('../util/utimes')
  5. function ncp (source, dest, options, callback) {
  6. if (!callback) {
  7. callback = options
  8. options = {}
  9. }
  10. var basePath = process.cwd()
  11. var currentPath = path.resolve(basePath, source)
  12. var targetPath = path.resolve(basePath, dest)
  13. var filter = options.filter
  14. var transform = options.transform
  15. var clobber = options.clobber !== false // default true
  16. var dereference = options.dereference
  17. var preserveTimestamps = options.preserveTimestamps === true
  18. var started = 0
  19. var finished = 0
  20. var running = 0
  21. var errored = false
  22. startCopy(currentPath)
  23. function startCopy (source) {
  24. started++
  25. if (filter) {
  26. if (filter instanceof RegExp) {
  27. console.warn('Warning: fs-extra: Passing a RegExp filter is deprecated, use a function')
  28. if (!filter.test(source)) {
  29. return doneOne(true)
  30. }
  31. } else if (typeof filter === 'function') {
  32. if (!filter(source)) {
  33. return doneOne(true)
  34. }
  35. }
  36. }
  37. return getStats(source)
  38. }
  39. function getStats (source) {
  40. var stat = dereference ? fs.stat : fs.lstat
  41. running++
  42. stat(source, function (err, stats) {
  43. if (err) return onError(err)
  44. // We need to get the mode from the stats object and preserve it.
  45. var item = {
  46. name: source,
  47. mode: stats.mode,
  48. mtime: stats.mtime, // modified time
  49. atime: stats.atime, // access time
  50. stats: stats // temporary
  51. }
  52. if (stats.isDirectory()) {
  53. return onDir(item)
  54. } else if (stats.isFile() || stats.isCharacterDevice() || stats.isBlockDevice()) {
  55. return onFile(item)
  56. } else if (stats.isSymbolicLink()) {
  57. // Symlinks don't really need to know about the mode.
  58. return onLink(source)
  59. }
  60. })
  61. }
  62. function onFile (file) {
  63. var target = file.name.replace(currentPath, targetPath.replace('$', '$$$$')) // escapes '$' with '$$'
  64. isWritable(target, function (writable) {
  65. if (writable) {
  66. copyFile(file, target)
  67. } else {
  68. if (clobber) {
  69. rmFile(target, function () {
  70. copyFile(file, target)
  71. })
  72. } else {
  73. var err = new Error('EEXIST: ' + target + ' already exists.')
  74. err.code = 'EEXIST'
  75. err.errno = -17
  76. err.path = target
  77. onError(err)
  78. }
  79. }
  80. })
  81. }
  82. function copyFile (file, target) {
  83. var readStream = fs.createReadStream(file.name)
  84. var writeStream = fs.createWriteStream(target, { mode: file.mode })
  85. readStream.on('error', onError)
  86. writeStream.on('error', onError)
  87. if (transform) {
  88. transform(readStream, writeStream, file)
  89. } else {
  90. writeStream.on('open', function () {
  91. readStream.pipe(writeStream)
  92. })
  93. }
  94. writeStream.once('finish', function () {
  95. fs.chmod(target, file.mode, function (err) {
  96. if (err) return onError(err)
  97. if (preserveTimestamps) {
  98. utimes.utimesMillis(target, file.atime, file.mtime, function (err) {
  99. if (err) return onError(err)
  100. return doneOne()
  101. })
  102. } else {
  103. doneOne()
  104. }
  105. })
  106. })
  107. }
  108. function rmFile (file, done) {
  109. fs.unlink(file, function (err) {
  110. if (err) return onError(err)
  111. return done()
  112. })
  113. }
  114. function onDir (dir) {
  115. var target = dir.name.replace(currentPath, targetPath.replace('$', '$$$$')) // escapes '$' with '$$'
  116. isWritable(target, function (writable) {
  117. if (writable) {
  118. return mkDir(dir, target)
  119. }
  120. copyDir(dir.name)
  121. })
  122. }
  123. function mkDir (dir, target) {
  124. fs.mkdir(target, dir.mode, function (err) {
  125. if (err) return onError(err)
  126. // despite setting mode in fs.mkdir, doesn't seem to work
  127. // so we set it here.
  128. fs.chmod(target, dir.mode, function (err) {
  129. if (err) return onError(err)
  130. copyDir(dir.name)
  131. })
  132. })
  133. }
  134. function copyDir (dir) {
  135. fs.readdir(dir, function (err, items) {
  136. if (err) return onError(err)
  137. items.forEach(function (item) {
  138. startCopy(path.join(dir, item))
  139. })
  140. return doneOne()
  141. })
  142. }
  143. function onLink (link) {
  144. var target = link.replace(currentPath, targetPath)
  145. fs.readlink(link, function (err, resolvedPath) {
  146. if (err) return onError(err)
  147. checkLink(resolvedPath, target)
  148. })
  149. }
  150. function checkLink (resolvedPath, target) {
  151. if (dereference) {
  152. resolvedPath = path.resolve(basePath, resolvedPath)
  153. }
  154. isWritable(target, function (writable) {
  155. if (writable) {
  156. return makeLink(resolvedPath, target)
  157. }
  158. fs.readlink(target, function (err, targetDest) {
  159. if (err) return onError(err)
  160. if (dereference) {
  161. targetDest = path.resolve(basePath, targetDest)
  162. }
  163. if (targetDest === resolvedPath) {
  164. return doneOne()
  165. }
  166. return rmFile(target, function () {
  167. makeLink(resolvedPath, target)
  168. })
  169. })
  170. })
  171. }
  172. function makeLink (linkPath, target) {
  173. fs.symlink(linkPath, target, function (err) {
  174. if (err) return onError(err)
  175. return doneOne()
  176. })
  177. }
  178. function isWritable (path, done) {
  179. fs.lstat(path, function (err) {
  180. if (err) {
  181. if (err.code === 'ENOENT') return done(true)
  182. return done(false)
  183. }
  184. return done(false)
  185. })
  186. }
  187. function onError (err) {
  188. // ensure callback is defined & called only once:
  189. if (!errored && callback !== undefined) {
  190. errored = true
  191. return callback(err)
  192. }
  193. }
  194. function doneOne (skipped) {
  195. if (!skipped) running--
  196. finished++
  197. if ((started === finished) && (running === 0)) {
  198. if (callback !== undefined) {
  199. return callback(null)
  200. }
  201. }
  202. }
  203. }
  204. module.exports = ncp