1
1
'use strict'
2
2
3
- const fs = require ( 'graceful- fs' )
3
+ const fs = require ( '../ fs' )
4
4
const path = require ( 'path' )
5
- const mkdirs = require ( '../mkdirs' ) . mkdirs
6
- const pathExists = require ( '../path-exists' ) . pathExists
7
- const utimesMillis = require ( '../util/utimes' ) . utimesMillis
5
+ const { mkdirs } = require ( '../mkdirs' )
6
+ const { pathExists } = require ( '../path-exists' )
7
+ const { utimesMillis } = require ( '../util/utimes' )
8
8
const stat = require ( '../util/stat' )
9
9
10
- function copy ( src , dest , opts , cb ) {
11
- if ( typeof opts === 'function' && ! cb ) {
12
- cb = opts
13
- opts = { }
14
- } else if ( typeof opts === 'function' ) {
10
+ async function copy ( src , dest , opts = { } ) {
11
+ if ( typeof opts === 'function' ) {
15
12
opts = { filter : opts }
16
13
}
17
14
18
- cb = cb || function ( ) { }
19
- opts = opts || { }
20
-
21
15
opts . clobber = 'clobber' in opts ? ! ! opts . clobber : true // default to true for now
22
16
opts . overwrite = 'overwrite' in opts ? ! ! opts . overwrite : opts . clobber // overwrite falls back to clobber
23
17
@@ -30,209 +24,152 @@ function copy (src, dest, opts, cb) {
30
24
)
31
25
}
32
26
33
- stat . checkPaths ( src , dest , 'copy' , opts , ( err , stats ) => {
34
- if ( err ) return cb ( err )
35
- const { srcStat, destStat } = stats
36
- stat . checkParentPaths ( src , srcStat , dest , 'copy' , err => {
37
- if ( err ) return cb ( err )
38
- runFilter ( src , dest , opts , ( err , include ) => {
39
- if ( err ) return cb ( err )
40
- if ( ! include ) return cb ( )
41
-
42
- checkParentDir ( destStat , src , dest , opts , cb )
43
- } )
44
- } )
45
- } )
46
- }
27
+ const { srcStat, destStat } = await stat . checkPaths ( src , dest , 'copy' , opts )
28
+
29
+ await stat . checkParentPaths ( src , srcStat , dest , 'copy' )
30
+
31
+ const include = await runFilter ( src , dest , opts )
32
+
33
+ if ( ! include ) return
47
34
48
- function checkParentDir ( destStat , src , dest , opts , cb ) {
35
+ // check if the parent of dest exists, and create it if it doesn't exist
49
36
const destParent = path . dirname ( dest )
50
- pathExists ( destParent , ( err , dirExists ) => {
51
- if ( err ) return cb ( err )
52
- if ( dirExists ) return getStats ( destStat , src , dest , opts , cb )
53
- mkdirs ( destParent , err => {
54
- if ( err ) return cb ( err )
55
- return getStats ( destStat , src , dest , opts , cb )
56
- } )
57
- } )
58
- }
37
+ const dirExists = await pathExists ( destParent )
38
+ if ( ! dirExists ) {
39
+ await mkdirs ( destParent )
40
+ }
59
41
60
- function runFilter ( src , dest , opts , cb ) {
61
- if ( ! opts . filter ) return cb ( null , true )
62
- Promise . resolve ( opts . filter ( src , dest ) )
63
- . then ( include => cb ( null , include ) , error => cb ( error ) )
42
+ await getStatsAndPerformCopy ( destStat , src , dest , opts )
64
43
}
65
44
66
- function getStats ( destStat , src , dest , opts , cb ) {
67
- const stat = opts . dereference ? fs . stat : fs . lstat
68
- stat ( src , ( err , srcStat ) => {
69
- if ( err ) return cb ( err )
70
-
71
- if ( srcStat . isDirectory ( ) ) return onDir ( srcStat , destStat , src , dest , opts , cb )
72
- else if ( srcStat . isFile ( ) ||
73
- srcStat . isCharacterDevice ( ) ||
74
- srcStat . isBlockDevice ( ) ) return onFile ( srcStat , destStat , src , dest , opts , cb )
75
- else if ( srcStat . isSymbolicLink ( ) ) return onLink ( destStat , src , dest , opts , cb )
76
- else if ( srcStat . isSocket ( ) ) return cb ( new Error ( `Cannot copy a socket file: ${ src } ` ) )
77
- else if ( srcStat . isFIFO ( ) ) return cb ( new Error ( `Cannot copy a FIFO pipe: ${ src } ` ) )
78
- return cb ( new Error ( `Unknown file: ${ src } ` ) )
79
- } )
45
+ async function runFilter ( src , dest , opts ) {
46
+ if ( ! opts . filter ) return true
47
+ return opts . filter ( src , dest )
80
48
}
81
49
82
- function onFile ( srcStat , destStat , src , dest , opts , cb ) {
83
- if ( ! destStat ) return copyFile ( srcStat , src , dest , opts , cb )
84
- return mayCopyFile ( srcStat , src , dest , opts , cb )
50
+ async function getStatsAndPerformCopy ( destStat , src , dest , opts ) {
51
+ const statFn = opts . dereference ? fs . stat : fs . lstat
52
+ const srcStat = await statFn ( src )
53
+
54
+ if ( srcStat . isDirectory ( ) ) return onDir ( srcStat , destStat , src , dest , opts )
55
+
56
+ if (
57
+ srcStat . isFile ( ) ||
58
+ srcStat . isCharacterDevice ( ) ||
59
+ srcStat . isBlockDevice ( )
60
+ ) return onFile ( srcStat , destStat , src , dest , opts )
61
+
62
+ if ( srcStat . isSymbolicLink ( ) ) return onLink ( destStat , src , dest , opts )
63
+ if ( srcStat . isSocket ( ) ) throw new Error ( `Cannot copy a socket file: ${ src } ` )
64
+ if ( srcStat . isFIFO ( ) ) throw new Error ( `Cannot copy a FIFO pipe: ${ src } ` )
65
+ throw new Error ( `Unknown file: ${ src } ` )
85
66
}
86
67
87
- function mayCopyFile ( srcStat , src , dest , opts , cb ) {
68
+ async function onFile ( srcStat , destStat , src , dest , opts ) {
69
+ if ( ! destStat ) return copyFile ( srcStat , src , dest , opts )
70
+
88
71
if ( opts . overwrite ) {
89
- fs . unlink ( dest , err => {
90
- if ( err ) return cb ( err )
91
- return copyFile ( srcStat , src , dest , opts , cb )
92
- } )
93
- } else if ( opts . errorOnExist ) {
94
- return cb ( new Error ( `'${ dest } ' already exists` ) )
95
- } else return cb ( )
72
+ await fs . unlink ( dest )
73
+ return copyFile ( srcStat , src , dest , opts )
74
+ }
75
+ if ( opts . errorOnExist ) {
76
+ throw new Error ( `'${ dest } ' already exists` )
77
+ }
96
78
}
97
79
98
- function copyFile ( srcStat , src , dest , opts , cb ) {
99
- fs . copyFile ( src , dest , err => {
100
- if ( err ) return cb ( err )
101
- if ( opts . preserveTimestamps ) return handleTimestampsAndMode ( srcStat . mode , src , dest , cb )
102
- return setDestMode ( dest , srcStat . mode , cb )
103
- } )
104
- }
80
+ async function copyFile ( srcStat , src , dest , opts ) {
81
+ await fs . copyFile ( src , dest )
82
+ if ( opts . preserveTimestamps ) {
83
+ // Make sure the file is writable before setting the timestamp
84
+ // otherwise open fails with EPERM when invoked with 'r+'
85
+ // (through utimes call)
86
+ if ( fileIsNotWritable ( srcStat . mode ) ) {
87
+ await makeFileWritable ( dest , srcStat . mode )
88
+ }
105
89
106
- function handleTimestampsAndMode ( srcMode , src , dest , cb ) {
107
- // Make sure the file is writable before setting the timestamp
108
- // otherwise open fails with EPERM when invoked with 'r+'
109
- // (through utimes call)
110
- if ( fileIsNotWritable ( srcMode ) ) {
111
- return makeFileWritable ( dest , srcMode , err => {
112
- if ( err ) return cb ( err )
113
- return setDestTimestampsAndMode ( srcMode , src , dest , cb )
114
- } )
90
+ // Set timestamps and mode correspondingly
91
+
92
+ // Note that The initial srcStat.atime cannot be trusted
93
+ // because it is modified by the read(2) system call
94
+ // (See https://nodejs.org/api/fs.html#fs_stat_time_values)
95
+ const updatedSrcStat = await fs . stat ( src )
96
+ await utimesMillis ( dest , updatedSrcStat . atime , updatedSrcStat . mtime )
115
97
}
116
- return setDestTimestampsAndMode ( srcMode , src , dest , cb )
98
+
99
+ return fs . chmod ( dest , srcStat . mode )
117
100
}
118
101
119
102
function fileIsNotWritable ( srcMode ) {
120
103
return ( srcMode & 0o200 ) === 0
121
104
}
122
105
123
- function makeFileWritable ( dest , srcMode , cb ) {
124
- return setDestMode ( dest , srcMode | 0o200 , cb )
125
- }
126
-
127
- function setDestTimestampsAndMode ( srcMode , src , dest , cb ) {
128
- setDestTimestamps ( src , dest , err => {
129
- if ( err ) return cb ( err )
130
- return setDestMode ( dest , srcMode , cb )
131
- } )
106
+ function makeFileWritable ( dest , srcMode ) {
107
+ return fs . chmod ( dest , srcMode | 0o200 )
132
108
}
133
109
134
- function setDestMode ( dest , srcMode , cb ) {
135
- return fs . chmod ( dest , srcMode , cb )
136
- }
110
+ async function onDir ( srcStat , destStat , src , dest , opts ) {
111
+ // the dest directory might not exist, create it
112
+ if ( ! destStat ) {
113
+ await fs . mkdir ( dest )
114
+ }
137
115
138
- function setDestTimestamps ( src , dest , cb ) {
139
- // The initial srcStat.atime cannot be trusted
140
- // because it is modified by the read(2) system call
141
- // (See https://nodejs.org/api/fs.html#fs_stat_time_values)
142
- fs . stat ( src , ( err , updatedSrcStat ) => {
143
- if ( err ) return cb ( err )
144
- return utimesMillis ( dest , updatedSrcStat . atime , updatedSrcStat . mtime , cb )
145
- } )
146
- }
116
+ // loop through the files in the current directory to copy everything
117
+ for ( const item of await fs . readdir ( src ) ) {
118
+ const srcItem = path . join ( src , item )
119
+ const destItem = path . join ( dest , item )
147
120
148
- function onDir ( srcStat , destStat , src , dest , opts , cb ) {
149
- if ( ! destStat ) return mkDirAndCopy ( srcStat . mode , src , dest , opts , cb )
150
- return copyDir ( src , dest , opts , cb )
151
- }
121
+ // skip the item if it is matches by the filter function
122
+ const include = await runFilter ( srcItem , destItem , opts )
123
+ if ( ! include ) continue
152
124
153
- function mkDirAndCopy ( srcMode , src , dest , opts , cb ) {
154
- fs . mkdir ( dest , err => {
155
- if ( err ) return cb ( err )
156
- copyDir ( src , dest , opts , err => {
157
- if ( err ) return cb ( err )
158
- return setDestMode ( dest , srcMode , cb )
159
- } )
160
- } )
161
- }
125
+ const { destStat } = await stat . checkPaths ( srcItem , destItem , 'copy' , opts )
162
126
163
- function copyDir ( src , dest , opts , cb ) {
164
- fs . readdir ( src , ( err , items ) => {
165
- if ( err ) return cb ( err )
166
- return copyDirItems ( items , src , dest , opts , cb )
167
- } )
168
- }
127
+ // If the item is a copyable file, `getStatsAndPerformCopy` will copy it
128
+ // If the item is a directory, `getStatsAndPerformCopy` will call `onDir` recursively
129
+ await getStatsAndPerformCopy ( destStat , srcItem , destItem , opts )
130
+ }
169
131
170
- function copyDirItems ( items , src , dest , opts , cb ) {
171
- const item = items . pop ( )
172
- if ( ! item ) return cb ( )
173
- return copyDirItem ( items , item , src , dest , opts , cb )
132
+ if ( ! destStat ) {
133
+ await fs . chmod ( dest , srcStat . mode )
134
+ }
174
135
}
175
136
176
- function copyDirItem ( items , item , src , dest , opts , cb ) {
177
- const srcItem = path . join ( src , item )
178
- const destItem = path . join ( dest , item )
179
- runFilter ( srcItem , destItem , opts , ( err , include ) => {
180
- if ( err ) return cb ( err )
181
- if ( ! include ) return copyDirItems ( items , src , dest , opts , cb )
182
-
183
- stat . checkPaths ( srcItem , destItem , 'copy' , opts , ( err , stats ) => {
184
- if ( err ) return cb ( err )
185
- const { destStat } = stats
186
- getStats ( destStat , srcItem , destItem , opts , err => {
187
- if ( err ) return cb ( err )
188
- return copyDirItems ( items , src , dest , opts , cb )
189
- } )
190
- } )
191
- } )
192
- }
137
+ async function onLink ( destStat , src , dest , opts ) {
138
+ let resolvedSrc = await fs . readlink ( src )
139
+ if ( opts . dereference ) {
140
+ resolvedSrc = path . resolve ( process . cwd ( ) , resolvedSrc )
141
+ }
142
+ if ( ! destStat ) {
143
+ return fs . symlink ( resolvedSrc , dest )
144
+ }
193
145
194
- function onLink ( destStat , src , dest , opts , cb ) {
195
- fs . readlink ( src , ( err , resolvedSrc ) => {
196
- if ( err ) return cb ( err )
197
- if ( opts . dereference ) {
198
- resolvedSrc = path . resolve ( process . cwd ( ) , resolvedSrc )
199
- }
146
+ let resolvedDest = null
147
+ try {
148
+ resolvedDest = await fs . readlink ( dest )
149
+ } catch ( e ) {
150
+ // dest exists and is a regular file or directory,
151
+ // Windows may throw UNKNOWN error. If dest already exists,
152
+ // fs throws error anyway, so no need to guard against it here.
153
+ if ( e . code === 'EINVAL' || e . code === 'UNKNOWN' ) return fs . symlink ( resolvedSrc , dest )
154
+ throw e
155
+ }
156
+ if ( opts . dereference ) {
157
+ resolvedDest = path . resolve ( process . cwd ( ) , resolvedDest )
158
+ }
159
+ if ( stat . isSrcSubdir ( resolvedSrc , resolvedDest ) ) {
160
+ throw new Error ( `Cannot copy '${ resolvedSrc } ' to a subdirectory of itself, '${ resolvedDest } '.` )
161
+ }
200
162
201
- if ( ! destStat ) {
202
- return fs . symlink ( resolvedSrc , dest , cb )
203
- } else {
204
- fs . readlink ( dest , ( err , resolvedDest ) => {
205
- if ( err ) {
206
- // dest exists and is a regular file or directory,
207
- // Windows may throw UNKNOWN error. If dest already exists,
208
- // fs throws error anyway, so no need to guard against it here.
209
- if ( err . code === 'EINVAL' || err . code === 'UNKNOWN' ) return fs . symlink ( resolvedSrc , dest , cb )
210
- return cb ( err )
211
- }
212
- if ( opts . dereference ) {
213
- resolvedDest = path . resolve ( process . cwd ( ) , resolvedDest )
214
- }
215
- if ( stat . isSrcSubdir ( resolvedSrc , resolvedDest ) ) {
216
- return cb ( new Error ( `Cannot copy '${ resolvedSrc } ' to a subdirectory of itself, '${ resolvedDest } '.` ) )
217
- }
218
-
219
- // do not copy if src is a subdir of dest since unlinking
220
- // dest in this case would result in removing src contents
221
- // and therefore a broken symlink would be created.
222
- if ( stat . isSrcSubdir ( resolvedDest , resolvedSrc ) ) {
223
- return cb ( new Error ( `Cannot overwrite '${ resolvedDest } ' with '${ resolvedSrc } '.` ) )
224
- }
225
- return copyLink ( resolvedSrc , dest , cb )
226
- } )
227
- }
228
- } )
229
- }
163
+ // do not copy if src is a subdir of dest since unlinking
164
+ // dest in this case would result in removing src contents
165
+ // and therefore a broken symlink would be created.
166
+ if ( stat . isSrcSubdir ( resolvedDest , resolvedSrc ) ) {
167
+ throw new Error ( `Cannot overwrite '${ resolvedDest } ' with '${ resolvedSrc } '.` )
168
+ }
230
169
231
- function copyLink ( resolvedSrc , dest , cb ) {
232
- fs . unlink ( dest , err => {
233
- if ( err ) return cb ( err )
234
- return fs . symlink ( resolvedSrc , dest , cb )
235
- } )
170
+ // copy the link
171
+ await fs . unlink ( dest )
172
+ return fs . symlink ( resolvedSrc , dest )
236
173
}
237
174
238
175
module . exports = copy
0 commit comments