├── .gitattributes ├── .gitignore ├── .jshintrc ├── .travis.yml ├── AUTHORS ├── CHANGELOG ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE-MIT ├── README.md ├── appveyor.yml ├── docs ├── copy-examples.md ├── copy-options.md ├── copy-overview.md └── overview.md ├── package.json ├── tasks └── copy.js └── test ├── copy_test.js ├── expected ├── copy_test_files │ ├── test.js │ └── test2.js ├── copy_test_flatten │ ├── one.js │ ├── test.js │ ├── test2.js │ ├── test_process.js │ └── two.js ├── copy_test_mix │ ├── folder_one │ │ └── one.js │ ├── folder_two │ │ └── two.js │ ├── test.js │ ├── test2.js │ └── time_folder │ │ ├── sub_folder │ │ └── sub_sub_folder │ │ │ └── test.js │ │ ├── test.js │ │ └── test_process.js ├── copy_test_noexpandWild │ └── test │ │ └── fixtures │ │ ├── test.js │ │ └── test2.js ├── copy_test_v0.1.0 │ └── folder_one │ │ └── one.js └── single.js └── fixtures ├── .hidden ├── beep.wav ├── folder_one └── one.js ├── folder_two └── two.js ├── test.js ├── test2.js └── time_folder ├── sub_folder └── sub_sub_folder │ └── test.js ├── test.js └── test_process.js /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | tmp -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "boss": true, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "eqnull": true, 6 | "immed": true, 7 | "latedef": true, 8 | "newcap": true, 9 | "noarg": true, 10 | "node": true, 11 | "sub": true, 12 | "undef": true, 13 | "unused": true 14 | } 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | 5 | node_js: 6 | - "8" 7 | - "12" 8 | 9 | matrix: 10 | fast_finish: true 11 | 12 | cache: 13 | directories: 14 | - node_modules 15 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Chris Talkington (http://christalkington.com/) 2 | Tyler Kellen (http://goingslowly.com/) 3 | Kyle Robinson Young (http://twitter.com/shamakry) 4 | Nathan Bleigh (http://www.nathanbleigh.com) 5 | Eric Clemmons (http://ericclemmons.github.com) -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | v1.0.0: 2 | date: 2016-03-04 3 | changes: 4 | - Bump devDependencies. 5 | - Add example of using relative path. 6 | - Point main to task and remove peerDeps. 7 | v0.8.2: 8 | date: 2015-10-19 9 | changes: 10 | - Fix expand-less copies with multiple files. 11 | v0.8.1: 12 | date: 2015-08-20 13 | changes: 14 | - Update `chalk` dependency. 15 | v0.8.0: 16 | date: 2015-02-20 17 | changes: 18 | - Performance improvements. 19 | - The `mode` option now also applies to directories. 20 | - Fix path issue on Windows. 21 | v0.7.0: 22 | date: 2014-10-15 23 | changes: 24 | - Add timestamp option to disable preserving timestamp when copying. 25 | v0.6.0: 26 | date: 2014-09-17 27 | changes: 28 | - Update chalk dependency and other devDependencies. 29 | - Preserve file timestamp when copying. 30 | v0.5.0: 31 | date: 2013-12-23 32 | changes: 33 | - If an encoding is specified, overwrite `grunt.file.defaultEncoding`. 34 | - Rename `processContent`/`processContentExclude` to `process`/`noProcess` to match Grunt API. 35 | - '`mode` option to copy existing or set file permissions.' 36 | v0.4.1: 37 | date: 2013-03-26 38 | changes: 39 | - Output summary by default ("Copied N files, created M folders"). Individual transaction output available via `--verbose`. 40 | v0.4.0: 41 | date: 2013-02-15 42 | changes: 43 | - First official release for Grunt 0.4.0. 44 | v0.4.0rc7: 45 | date: 2013-01-23 46 | changes: 47 | - Updating grunt/gruntplugin dependencies to rc7. 48 | - Changing in-development grunt/gruntplugin dependency versions from tilde version ranges to specific versions. 49 | v0.4.0rc5: 50 | date: 2013-01-14 51 | changes: 52 | - Updating to work with grunt v0.4.0rc5. 53 | - Conversion to grunt v0.4 conventions. 54 | - Replace `basePath` with `cwd`. 55 | - Empty directory support. 56 | v0.3.2: 57 | date: 2012-10-18 58 | changes: 59 | - Pass `copyOptions` on single file copy. 60 | v0.3.1: 61 | date: 2012-10-12 62 | changes: 63 | - Rename grunt-contrib-lib dep to grunt-lib-contrib. 64 | v0.3.0: 65 | date: 2012-09-24 66 | changes: 67 | - General cleanup and consolidation. 68 | - Global options depreciated. 69 | v0.2.4: 70 | date: 2012-09-18 71 | changes: 72 | - No valid source check. 73 | v0.2.3: 74 | date: 2012-09-17 75 | changes: 76 | - '`path.sep` fallback for Node.js <= 0.7.9.' 77 | v0.2.2: 78 | date: 2012-09-17 79 | changes: 80 | - Single file copy support. 81 | - Test refactoring. 82 | v0.2.0: 83 | date: 2012-09-07 84 | changes: 85 | - Refactored from grunt-contrib into individual repo. 86 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Please see the [Contributing to grunt](http://gruntjs.com/contributing) guide for information on contributing to this project. 2 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-contrib-copy 3 | * http://gruntjs.com/ 4 | * 5 | * Copyright (c) 2016 Chris Talkington, contributors 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | module.exports = function(grunt) { 10 | 'use strict'; 11 | 12 | // Make an empty dir for testing as git doesn't track empty folders. 13 | grunt.file.mkdir('test/fixtures/empty_folder'); 14 | grunt.file.mkdir('test/expected/copy_test_mix/empty_folder'); 15 | 16 | // Project configuration. 17 | grunt.initConfig({ 18 | jshint: { 19 | all: [ 20 | 'Gruntfile.js', 21 | 'tasks/*.js', 22 | '<%= nodeunit.tests %>' 23 | ], 24 | options: { 25 | jshintrc: '.jshintrc' 26 | } 27 | }, 28 | 29 | // Before generating any new files, remove any previously-created files. 30 | clean: { 31 | test: ['tmp'] 32 | }, 33 | 34 | testVars: { 35 | name: 'grunt-contrib-copy', 36 | version: '0.1.0', 37 | match: 'folder_one/*' 38 | }, 39 | 40 | // Configuration to be run (and then tested). 41 | copy: { 42 | main: { 43 | files: [ 44 | { expand: true, cwd: 'test/fixtures', src: ['*.js'], dest: 'tmp/copy_test_files/' }, 45 | { expand: true, cwd: 'test/fixtures', src: ['**', '!*.wav'], dest: 'tmp/copy_test_mix/' }, 46 | { expand: true, cwd: 'test/fixtures', src: ['<%= testVars.match %>'], dest: 'tmp/copy_test_v<%= testVars.version %>/' } 47 | ] 48 | }, 49 | 50 | noexpandWild: { 51 | files: [ 52 | { src: 'test/fixtures/*.js', dest: 'tmp/copy_test_noexpandWild/' } 53 | ] 54 | }, 55 | 56 | flatten: { 57 | files: [ 58 | { expand: true, flatten: true, filter: 'isFile', src: ['test/fixtures/**', '!**/*.wav'], dest: 'tmp/copy_test_flatten/' } 59 | ] 60 | }, 61 | 62 | single: { 63 | files: [ 64 | { src: ['test/fixtures/test.js'], dest: 'tmp/single.js' } 65 | ] 66 | }, 67 | 68 | verbose: { 69 | files: [ 70 | { expand: true, src: ['test/fixtures/**'], dest: 'tmp/copy_test_verbose/' } 71 | ] 72 | }, 73 | 74 | mode: { 75 | options: { 76 | mode: '0444' 77 | }, 78 | src: ['test/fixtures/test2.js'], 79 | dest: 'tmp/mode.js' 80 | }, 81 | 82 | modeDir: { 83 | options: { 84 | mode: '0777' 85 | }, 86 | files: [{ 87 | expand: true, 88 | cwd: 'test/fixtures/', 89 | src: ['time_folder/**'], 90 | dest: 'tmp/copy_test_modeDir/' 91 | }] 92 | }, 93 | 94 | process: { 95 | options: { 96 | noProcess: ['test/fixtures/beep.wav'], 97 | process: function (content) { 98 | return content + '/* comment */'; 99 | } 100 | }, 101 | files: [{ expand: true, cwd: 'test/fixtures', src: ['test2.js', 'beep.wav'], dest: 'tmp/process/' }] 102 | }, 103 | 104 | timestamp: { 105 | options: { 106 | process: function (content, srcpath) { 107 | if (srcpath === 'test/fixtures/time_folder/test_process.js') { 108 | return 'with process and file contents were changed'; 109 | } else { 110 | return content; 111 | } 112 | }, 113 | timestamp: true 114 | }, 115 | files: [ 116 | { expand: true, cwd: 'test/fixtures/time_folder/', src: ['**'], dest: 'tmp/copy_test_timestamp/' }, 117 | { src: 'test/fixtures/time_folder/test.js', dest: 'tmp/copy_test_timestamp/test1.js' } 118 | ] 119 | } 120 | }, 121 | 122 | // Unit tests. 123 | nodeunit: { 124 | tests: ['test/*_test.js'] 125 | } 126 | }); 127 | 128 | // Actually load this plugin's task(s). 129 | grunt.loadTasks('tasks'); 130 | 131 | // These plugins provide necessary tasks. 132 | grunt.loadNpmTasks('grunt-contrib-jshint'); 133 | grunt.loadNpmTasks('grunt-contrib-clean'); 134 | grunt.loadNpmTasks('grunt-contrib-nodeunit'); 135 | grunt.loadNpmTasks('grunt-contrib-internal'); 136 | 137 | // Whenever the "test" task is run, first clean the "tmp" dir, then run this 138 | // plugin's task(s), then test the result. 139 | grunt.registerTask('test', ['jshint', 'clean', 'copy', 'nodeunit']); 140 | 141 | // By default, lint and run all tests. 142 | grunt.registerTask('default', ['test', 'build-contrib']); 143 | }; 144 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Chris Talkington, contributors 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # grunt-contrib-copy v1.0.0 [![Build Status: Linux](https://travis-ci.org/gruntjs/grunt-contrib-copy.svg?branch=master)](https://travis-ci.org/gruntjs/grunt-contrib-copy) [![Build Status: Windows](https://ci.appveyor.com/api/projects/status/fe6l517l01ys2y86/branch/master?svg=true)](https://ci.appveyor.com/project/gruntjs/grunt-contrib-copy/branch/master) 2 | 3 | > Copy files and folders 4 | 5 | 6 | 7 | ## Getting Started 8 | 9 | If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command: 10 | 11 | ```shell 12 | npm install grunt-contrib-copy --save-dev 13 | ``` 14 | 15 | Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript: 16 | 17 | ```js 18 | grunt.loadNpmTasks('grunt-contrib-copy'); 19 | ``` 20 | 21 | *This plugin was designed to work with Grunt 0.4.x. If you're still using grunt v0.3.x it's strongly recommended that [you upgrade](http://gruntjs.com/upgrading-from-0.3-to-0.4), but in case you can't please use [v0.3.2](https://github.com/gruntjs/grunt-contrib-copy/tree/grunt-0.3-stable).* 22 | 23 | 24 | 25 | ## Copy task 26 | _Run this task with the `grunt copy` command._ 27 | 28 | Task targets, files and options may be specified according to the grunt [Configuring tasks](http://gruntjs.com/configuring-tasks) guide. 29 | ### Options 30 | 31 | #### process 32 | Type: `Function(content, srcpath)` 33 | 34 | This option is passed to `grunt.file.copy` as an advanced way to control the file contents that are copied. 35 | 36 | *`processContent` has been renamed to `process` and the option name will be removed in the future.* 37 | 38 | #### noProcess 39 | Type: `String` 40 | 41 | This option is passed to `grunt.file.copy` as an advanced way to control which file contents are processed. 42 | 43 | *`processContentExclude` has been renamed to `noProcess` and the option name will be removed in the future.* 44 | 45 | #### encoding 46 | Type: `String` 47 | Default: `grunt.file.defaultEncoding` 48 | 49 | The file encoding to copy files with. 50 | 51 | #### mode 52 | Type: `Boolean` or `String` 53 | Default: `false` 54 | 55 | Whether to copy or set the destination file and directory permissions. 56 | Set to `true` to copy the existing file and directories permissions. 57 | Or set to the mode, i.e.: `0644`, that copied files will be set to. 58 | 59 | #### timestamp 60 | Type: `Boolean` 61 | Default: `false` 62 | 63 | Whether to preserve the timestamp attributes(`atime` and `mtime`) when copying files. Set to `true` to preserve files timestamp. But timestamp will *not* be preserved when the file contents or name are changed during copying. 64 | 65 | ### Usage Examples 66 | 67 | ```js 68 | copy: { 69 | main: { 70 | files: [ 71 | // includes files within path 72 | {expand: true, src: ['path/*'], dest: 'dest/', filter: 'isFile'}, 73 | 74 | // includes files within path and its sub-directories 75 | {expand: true, src: ['path/**'], dest: 'dest/'}, 76 | 77 | // makes all src relative to cwd 78 | {expand: true, cwd: 'path/', src: ['**'], dest: 'dest/'}, 79 | 80 | // flattens results to a single level 81 | {expand: true, flatten: true, src: ['path/**'], dest: 'dest/', filter: 'isFile'}, 82 | ], 83 | }, 84 | }, 85 | ``` 86 | 87 | This task supports all the file mapping format Grunt supports. Please read [Globbing patterns](http://gruntjs.com/configuring-tasks#globbing-patterns) and [Building the files object dynamically](http://gruntjs.com/configuring-tasks#building-the-files-object-dynamically) for additional details. 88 | 89 | Here are some additional examples, given the following file tree: 90 | ```shell 91 | $ tree -I node_modules 92 | . 93 | ├── Gruntfile.js 94 | └── src 95 | ├── a 96 | └── subdir 97 | └── b 98 | 99 | 2 directories, 3 files 100 | ``` 101 | 102 | **Copy a single file tree:** 103 | ```js 104 | copy: { 105 | main: { 106 | expand: true, 107 | src: 'src/*', 108 | dest: 'dest/', 109 | }, 110 | }, 111 | ``` 112 | 113 | ```shell 114 | $ grunt copy 115 | Running "copy:main" (copy) task 116 | Created 1 directories, copied 1 files 117 | 118 | Done, without errors. 119 | $ tree -I node_modules 120 | . 121 | ├── Gruntfile.js 122 | ├── dest 123 | │   └── src 124 | │   ├── a 125 | │   └── subdir 126 | └── src 127 | ├── a 128 | └── subdir 129 | └── b 130 | 131 | 5 directories, 4 files 132 | ``` 133 | 134 | **Copying without full path:** 135 | ```js 136 | copy: { 137 | main: { 138 | expand: true, 139 | cwd: 'src', 140 | src: '**', 141 | dest: 'dest/', 142 | }, 143 | }, 144 | ``` 145 | 146 | ```shell 147 | $ grunt copy 148 | Running "copy:main" (copy) task 149 | Created 2 directories, copied 2 files 150 | 151 | Done, without errors. 152 | $ tree -I node_modules 153 | . 154 | ├── Gruntfile.js 155 | ├── dest 156 | │ ├── a 157 | │ └── subdir 158 | │ └── b 159 | └── src 160 | ├── a 161 | └── subdir 162 | └── b 163 | 164 | 5 directories, 5 files 165 | ``` 166 | 167 | **Flattening the filepath output:** 168 | 169 | ```js 170 | copy: { 171 | main: { 172 | expand: true, 173 | cwd: 'src/', 174 | src: '**', 175 | dest: 'dest/', 176 | flatten: true, 177 | filter: 'isFile', 178 | }, 179 | }, 180 | ``` 181 | 182 | ```shell 183 | $ grunt copy 184 | Running "copy:main" (copy) task 185 | Copied 2 files 186 | 187 | Done, without errors. 188 | $ tree -I node_modules 189 | . 190 | ├── Gruntfile.js 191 | ├── dest 192 | │   ├── a 193 | │   └── b 194 | └── src 195 | ├── a 196 | └── subdir 197 | └── b 198 | 199 | 3 directories, 5 files 200 | ``` 201 | 202 | 203 | **Copy and modify a file:** 204 | 205 | To change the contents of a file as it is copied, set an `options.process` function as follows: 206 | 207 | ```js 208 | copy: { 209 | main: { 210 | src: 'src/a', 211 | dest: 'src/a.bak', 212 | options: { 213 | process: function (content, srcpath) { 214 | return content.replace(/[sad ]/g, '_'); 215 | }, 216 | }, 217 | }, 218 | }, 219 | ``` 220 | 221 | Here all occurrences of the letters "s", "a" and "d", as well as all spaces, will be changed to underlines in "a.bak". Of course, you are not limited to just using regex replacements. 222 | 223 | To process all files in a directory, the `process` function is used in exactly the same way. 224 | 225 | NOTE: If `process` is not working, be aware it was called `processContent` in v0.4.1 and earlier. 226 | 227 | 228 | ##### Troubleshooting 229 | 230 | By default, if a file or directory is not found it is quietly ignored. If the file should exist, and non-existence generate an error, then add `nonull:true`. For instance, this Gruntfile.js entry: 231 | 232 | ```js 233 | copy: { 234 | main: { 235 | nonull: true, 236 | src: 'not-there', 237 | dest: 'create-me', 238 | }, 239 | }, 240 | ``` 241 | 242 | gives this output: 243 | 244 | ```shell 245 | $ grunt copy 246 | Running "copy:main" (copy) task 247 | Warning: Unable to read "not-there" file (Error code: ENOENT). Use --force to continue. 248 | 249 | Aborted due to warnings. 250 | ``` 251 | 252 | 253 | 254 | ## Release History 255 | 256 | * 2016-03-04   v1.0.0   Bump devDependencies. Add example of using relative path. Point main to task and remove peerDeps. 257 | * 2015-10-19   v0.8.2   Fix expand-less copies with multiple files. 258 | * 2015-08-20   v0.8.1   Update `chalk` dependency. 259 | * 2015-02-20   v0.8.0   Performance improvements. The `mode` option now also applies to directories. Fix path issue on Windows. 260 | * 2014-10-15   v0.7.0   Add timestamp option to disable preserving timestamp when copying. 261 | * 2014-09-17   v0.6.0   Update chalk dependency and other devDependencies. Preserve file timestamp when copying. 262 | * 2013-12-23   v0.5.0   If an encoding is specified, overwrite `grunt.file.defaultEncoding`. Rename `processContent`/`processContentExclude` to `process`/`noProcess` to match Grunt API. `mode` option to copy existing or set file permissions. 263 | * 2013-03-26   v0.4.1   Output summary by default ("Copied N files, created M folders"). Individual transaction output available via `--verbose`. 264 | * 2013-02-15   v0.4.0   First official release for Grunt 0.4.0. 265 | * 2013-01-23   v0.4.0rc7   Updating grunt/gruntplugin dependencies to rc7. Changing in-development grunt/gruntplugin dependency versions from tilde version ranges to specific versions. 266 | * 2013-01-14   v0.4.0rc5   Updating to work with grunt v0.4.0rc5. Conversion to grunt v0.4 conventions. Replace `basePath` with `cwd`. Empty directory support. 267 | * 2012-10-18   v0.3.2   Pass `copyOptions` on single file copy. 268 | * 2012-10-12   v0.3.1   Rename grunt-contrib-lib dep to grunt-lib-contrib. 269 | * 2012-09-24   v0.3.0   General cleanup and consolidation. Global options depreciated. 270 | * 2012-09-18   v0.2.4   No valid source check. 271 | * 2012-09-17   v0.2.3   `path.sep` fallback for Node.js <= 0.7.9. 272 | * 2012-09-17   v0.2.2   Single file copy support. Test refactoring. 273 | * 2012-09-07   v0.2.0   Refactored from grunt-contrib into individual repo. 274 | 275 | --- 276 | 277 | Task submitted by [Chris Talkington](http://christalkington.com/) 278 | 279 | *This file was generated on Thu Apr 07 2016 15:11:09.* 280 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | clone_depth: 10 2 | 3 | version: "{build}" 4 | 5 | # What combinations to test 6 | environment: 7 | matrix: 8 | - nodejs_version: "4" 9 | platform: x64 10 | - nodejs_version: "4" 11 | platform: x86 12 | - nodejs_version: "6" 13 | platform: x86 14 | - nodejs_version: "8" 15 | platform: x86 16 | 17 | install: 18 | - ps: Install-Product node $env:nodejs_version $env:platform 19 | - npm install 20 | 21 | test_script: 22 | # Output useful info for debugging 23 | - node --version && npm --version 24 | # We test multiple Windows shells because of prior stdout buffering issues 25 | # filed against Grunt. https://github.com/joyent/node/issues/3584 26 | - ps: "npm test # PowerShell" # Pass comment to PS for easier debugging 27 | - cmd: npm test 28 | 29 | build: off 30 | 31 | matrix: 32 | fast_finish: true 33 | 34 | cache: 35 | - node_modules -> package.json 36 | -------------------------------------------------------------------------------- /docs/copy-examples.md: -------------------------------------------------------------------------------- 1 | # Usage Examples 2 | 3 | ```js 4 | copy: { 5 | main: { 6 | files: [ 7 | // includes files within path 8 | {expand: true, src: ['path/*'], dest: 'dest/', filter: 'isFile'}, 9 | 10 | // includes files within path and its sub-directories 11 | {expand: true, src: ['path/**'], dest: 'dest/'}, 12 | 13 | // makes all src relative to cwd 14 | {expand: true, cwd: 'path/', src: ['**'], dest: 'dest/'}, 15 | 16 | // flattens results to a single level 17 | {expand: true, flatten: true, src: ['path/**'], dest: 'dest/', filter: 'isFile'}, 18 | ], 19 | }, 20 | }, 21 | ``` 22 | 23 | This task supports all the file mapping format Grunt supports. Please read [Globbing patterns](http://gruntjs.com/configuring-tasks#globbing-patterns) and [Building the files object dynamically](http://gruntjs.com/configuring-tasks#building-the-files-object-dynamically) for additional details. 24 | 25 | Here are some additional examples, given the following file tree: 26 | ```shell 27 | $ tree -I node_modules 28 | . 29 | ├── Gruntfile.js 30 | └── src 31 | ├── a 32 | └── subdir 33 | └── b 34 | 35 | 2 directories, 3 files 36 | ``` 37 | 38 | **Copy a single file tree:** 39 | ```js 40 | copy: { 41 | main: { 42 | expand: true, 43 | src: 'src/*', 44 | dest: 'dest/', 45 | }, 46 | }, 47 | ``` 48 | 49 | ```shell 50 | $ grunt copy 51 | Running "copy:main" (copy) task 52 | Created 1 directories, copied 1 files 53 | 54 | Done, without errors. 55 | $ tree -I node_modules 56 | . 57 | ├── Gruntfile.js 58 | ├── dest 59 | │   └── src 60 | │   ├── a 61 | │   └── subdir 62 | └── src 63 | ├── a 64 | └── subdir 65 | └── b 66 | 67 | 5 directories, 4 files 68 | ``` 69 | 70 | **Copying without full path:** 71 | ```js 72 | copy: { 73 | main: { 74 | expand: true, 75 | cwd: 'src', 76 | src: '**', 77 | dest: 'dest/', 78 | }, 79 | }, 80 | ``` 81 | 82 | ```shell 83 | $ grunt copy 84 | Running "copy:main" (copy) task 85 | Created 2 directories, copied 2 files 86 | 87 | Done, without errors. 88 | $ tree -I node_modules 89 | . 90 | ├── Gruntfile.js 91 | ├── dest 92 | │ ├── a 93 | │ └── subdir 94 | │ └── b 95 | └── src 96 | ├── a 97 | └── subdir 98 | └── b 99 | 100 | 5 directories, 5 files 101 | ``` 102 | 103 | **Flattening the filepath output:** 104 | 105 | ```js 106 | copy: { 107 | main: { 108 | expand: true, 109 | cwd: 'src/', 110 | src: '**', 111 | dest: 'dest/', 112 | flatten: true, 113 | filter: 'isFile', 114 | }, 115 | }, 116 | ``` 117 | 118 | ```shell 119 | $ grunt copy 120 | Running "copy:main" (copy) task 121 | Copied 2 files 122 | 123 | Done, without errors. 124 | $ tree -I node_modules 125 | . 126 | ├── Gruntfile.js 127 | ├── dest 128 | │   ├── a 129 | │   └── b 130 | └── src 131 | ├── a 132 | └── subdir 133 | └── b 134 | 135 | 3 directories, 5 files 136 | ``` 137 | 138 | 139 | **Copy and modify a file:** 140 | 141 | To change the contents of a file as it is copied, set an `options.process` function as follows: 142 | 143 | ```js 144 | copy: { 145 | main: { 146 | src: 'src/a', 147 | dest: 'src/a.bak', 148 | options: { 149 | process: function (content, srcpath) { 150 | return content.replace(/[sad ]/g, '_'); 151 | }, 152 | }, 153 | }, 154 | }, 155 | ``` 156 | 157 | Here all occurrences of the letters "s", "a" and "d", as well as all spaces, will be changed to underlines in "a.bak". Of course, you are not limited to just using regex replacements. 158 | 159 | To process all files in a directory, the `process` function is used in exactly the same way. 160 | 161 | NOTE: If `process` is not working, be aware it was called `processContent` in v0.4.1 and earlier. 162 | 163 | 164 | ### Troubleshooting 165 | 166 | By default, if a file or directory is not found it is quietly ignored. If the file should exist, and non-existence generates an error, then add `nonull:true`. For instance, this Gruntfile.js entry: 167 | 168 | ```js 169 | copy: { 170 | main: { 171 | nonull: true, 172 | src: 'not-there', 173 | dest: 'create-me', 174 | }, 175 | }, 176 | ``` 177 | 178 | gives this output: 179 | 180 | ```shell 181 | $ grunt copy 182 | Running "copy:main" (copy) task 183 | Warning: Unable to read "not-there" file (Error code: ENOENT). Use --force to continue. 184 | 185 | Aborted due to warnings. 186 | ``` 187 | 188 | -------------------------------------------------------------------------------- /docs/copy-options.md: -------------------------------------------------------------------------------- 1 | # Options 2 | 3 | ## process 4 | Type: `Function(content, srcpath)` 5 | 6 | This option is passed to `grunt.file.copy` as an advanced way to control the file contents that are copied. 7 | 8 | *`processContent` has been renamed to `process` and the option name will be removed in the future.* 9 | 10 | ## noProcess 11 | Type: `String` 12 | 13 | This option is passed to `grunt.file.copy` as an advanced way to control which file contents are processed. 14 | 15 | *`processContentExclude` has been renamed to `noProcess` and the option name will be removed in the future.* 16 | 17 | ## encoding 18 | Type: `String` 19 | Default: `grunt.file.defaultEncoding` 20 | 21 | The file encoding to copy files with. 22 | 23 | ## mode 24 | Type: `Boolean` or `String` 25 | Default: `false` 26 | 27 | Whether to copy or set the destination file and directory permissions. 28 | Set to `true` to copy the existing file and directories permissions. 29 | Or set to the mode, i.e.: `0644`, that copied files will be set to. 30 | 31 | ## timestamp 32 | Type: `Boolean` 33 | Default: `false` 34 | 35 | Whether to preserve the timestamp attributes(`atime` and `mtime`) when copying files. Set to `true` to preserve files timestamp. But timestamp will *not* be preserved when the file contents or name are changed during copying. 36 | -------------------------------------------------------------------------------- /docs/copy-overview.md: -------------------------------------------------------------------------------- 1 | Task targets, files and options may be specified according to the grunt [Configuring tasks](http://gruntjs.com/configuring-tasks) guide. -------------------------------------------------------------------------------- /docs/overview.md: -------------------------------------------------------------------------------- 1 | *This plugin was designed to work with Grunt 0.4.x. If you're still using grunt v0.3.x it's strongly recommended that [you upgrade](http://gruntjs.com/upgrading-from-0.3-to-0.4), but in case you can't please use [v0.3.2](https://github.com/gruntjs/grunt-contrib-copy/tree/grunt-0.3-stable).* 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grunt-contrib-copy", 3 | "description": "Copy files and folders", 4 | "version": "1.0.0", 5 | "author": { 6 | "name": "Grunt Team", 7 | "url": "http://gruntjs.com/" 8 | }, 9 | "repository": "gruntjs/grunt-contrib-copy", 10 | "license": "MIT", 11 | "engines": { 12 | "node": ">=0.10.0" 13 | }, 14 | "main": "tasks/copy.js", 15 | "scripts": { 16 | "test": "grunt test" 17 | }, 18 | "dependencies": { 19 | "chalk": "^1.1.1", 20 | "file-sync-cmp": "^0.1.0" 21 | }, 22 | "devDependencies": { 23 | "grunt": "^1.0.0", 24 | "grunt-contrib-clean": "^1.0.0", 25 | "grunt-contrib-internal": "^1.1.0", 26 | "grunt-contrib-jshint": "^1.0.0", 27 | "grunt-contrib-nodeunit": "^1.0.0" 28 | }, 29 | "keywords": [ 30 | "gruntplugin" 31 | ], 32 | "files": [ 33 | "tasks" 34 | ], 35 | "appveyor_id": "fe6l517l01ys2y86" 36 | } 37 | -------------------------------------------------------------------------------- /tasks/copy.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-contrib-copy 3 | * http://gruntjs.com/ 4 | * 5 | * Copyright (c) 2016 Chris Talkington, contributors 6 | * Licensed under the MIT license. 7 | * https://github.com/gruntjs/grunt-contrib-copy/blob/master/LICENSE-MIT 8 | */ 9 | 10 | 'use strict'; 11 | 12 | module.exports = function(grunt) { 13 | var path = require('path'); 14 | var fs = require('fs'); 15 | var chalk = require('chalk'); 16 | var fileSyncCmp = require('file-sync-cmp'); 17 | var isWindows = process.platform === 'win32'; 18 | 19 | grunt.registerMultiTask('copy', 'Copy files.', function() { 20 | 21 | var options = this.options({ 22 | encoding: grunt.file.defaultEncoding, 23 | // processContent/processContentExclude deprecated renamed to process/noProcess 24 | processContent: false, 25 | processContentExclude: [], 26 | timestamp: false, 27 | mode: false 28 | }); 29 | 30 | var copyOptions = { 31 | encoding: options.encoding, 32 | process: options.process || options.processContent, 33 | noProcess: options.noProcess || options.processContentExclude 34 | }; 35 | 36 | var detectDestType = function(dest) { 37 | if (grunt.util._.endsWith(dest, '/')) { 38 | return 'directory'; 39 | } else { 40 | return 'file'; 41 | } 42 | }; 43 | 44 | var unixifyPath = function(filepath) { 45 | if (isWindows) { 46 | return filepath.replace(/\\/g, '/'); 47 | } else { 48 | return filepath; 49 | } 50 | }; 51 | 52 | var syncTimestamp = function (src, dest) { 53 | var stat = fs.lstatSync(src); 54 | if (path.basename(src) !== path.basename(dest)) { 55 | return; 56 | } 57 | 58 | if (stat.isFile() && !fileSyncCmp.equalFiles(src, dest)) { 59 | return; 60 | } 61 | 62 | var fd = fs.openSync(dest, isWindows ? 'r+' : 'r'); 63 | fs.futimesSync(fd, stat.atime, stat.mtime); 64 | fs.closeSync(fd); 65 | }; 66 | 67 | var isExpandedPair; 68 | var dirs = {}; 69 | var tally = { 70 | dirs: 0, 71 | files: 0 72 | }; 73 | 74 | this.files.forEach(function(filePair) { 75 | isExpandedPair = filePair.orig.expand || false; 76 | 77 | filePair.src.forEach(function(src) { 78 | src = unixifyPath(src); 79 | var dest = unixifyPath(filePair.dest); 80 | 81 | if (detectDestType(dest) === 'directory') { 82 | dest = isExpandedPair ? dest : path.join(dest, src); 83 | } 84 | 85 | if (grunt.file.isDir(src)) { 86 | grunt.verbose.writeln('Creating ' + chalk.cyan(dest)); 87 | grunt.file.mkdir(dest); 88 | if (options.mode !== false) { 89 | fs.chmodSync(dest, (options.mode === true) ? fs.lstatSync(src).mode : options.mode); 90 | } 91 | 92 | if (options.timestamp) { 93 | dirs[dest] = src; 94 | } 95 | 96 | tally.dirs++; 97 | } else { 98 | grunt.verbose.writeln('Copying ' + chalk.cyan(src) + ' -> ' + chalk.cyan(dest)); 99 | grunt.file.copy(src, dest, copyOptions); 100 | if (options.timestamp !== false) { 101 | syncTimestamp(src, dest); 102 | } 103 | if (options.mode !== false) { 104 | fs.chmodSync(dest, (options.mode === true) ? fs.lstatSync(src).mode : options.mode); 105 | } 106 | tally.files++; 107 | } 108 | }); 109 | }); 110 | 111 | if (options.timestamp) { 112 | Object.keys(dirs).sort(function (a, b) { 113 | return b.length - a.length; 114 | }).forEach(function (dest) { 115 | syncTimestamp(dirs[dest], dest); 116 | }); 117 | } 118 | 119 | if (tally.dirs) { 120 | grunt.log.write('Created ' + chalk.cyan(tally.dirs.toString()) + (tally.dirs === 1 ? ' directory' : ' directories')); 121 | } 122 | 123 | if (tally.files) { 124 | grunt.log.write((tally.dirs ? ', copied ' : 'Copied ') + chalk.cyan(tally.files.toString()) + (tally.files === 1 ? ' file' : ' files')); 125 | } 126 | 127 | grunt.log.writeln(); 128 | }); 129 | 130 | }; 131 | -------------------------------------------------------------------------------- /test/copy_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var grunt = require('grunt'); 4 | var fs = require('fs'); 5 | var isWindows = process.platform === 'win32'; 6 | 7 | exports.copy = { 8 | main: function(test) { 9 | test.expect(3); 10 | 11 | var actual = fs.readdirSync('tmp/copy_test_files').sort(); 12 | var expected = fs.readdirSync('test/expected/copy_test_files').sort(); 13 | test.deepEqual(expected, actual, 'should copy several files'); 14 | 15 | actual = fs.readdirSync('tmp/copy_test_mix').sort(); 16 | expected = fs.readdirSync('test/expected/copy_test_mix').sort(); 17 | test.deepEqual(expected, actual, 'should copy a mix of folders and files'); 18 | 19 | actual = fs.readdirSync('tmp/copy_test_v0.1.0').sort(); 20 | expected = fs.readdirSync('test/expected/copy_test_v0.1.0').sort(); 21 | test.deepEqual(expected, actual, 'should parse both dest and src templates'); 22 | 23 | test.done(); 24 | }, 25 | 26 | noexpandWild: function(test) { 27 | test.expect(3); 28 | 29 | ['/', '/test/', '/test/fixtures/'].forEach(function(subpath, i) { 30 | var actual = fs.readdirSync('tmp/copy_test_noexpandWild' + subpath).sort(); 31 | var expected = fs.readdirSync('test/expected/copy_test_noexpandWild' + subpath).sort(); 32 | test.deepEqual(expected, actual, 'should copy file structure at level ' + i); 33 | }); 34 | 35 | test.done(); 36 | }, 37 | 38 | flatten: function(test) { 39 | test.expect(1); 40 | 41 | var actual = fs.readdirSync('tmp/copy_test_flatten').sort(); 42 | var expected = fs.readdirSync('test/expected/copy_test_flatten').sort(); 43 | test.deepEqual(expected, actual, 'should create a flat structure'); 44 | 45 | test.done(); 46 | }, 47 | 48 | single: function(test) { 49 | test.expect(1); 50 | 51 | var actual = grunt.file.read('tmp/single.js'); 52 | var expected = grunt.file.read('test/expected/single.js'); 53 | test.equal(expected, actual, 'should allow for single file copy'); 54 | 55 | test.done(); 56 | }, 57 | 58 | mode: function(test) { 59 | test.expect(1); 60 | 61 | test.equal(fs.lstatSync('tmp/mode.js').mode.toString(8).slice(-3), '444'); 62 | 63 | test.done(); 64 | }, 65 | 66 | modeDir: function(test) { 67 | test.expect(2); 68 | // on Windows DIRs do not have 'executable' flag, see 69 | // https://github.com/nodejs/node/blob/master/deps/uv/src/win/fs.c#L1064 70 | var expectedMode = isWindows ? '666' : '777'; 71 | test.equal(fs.lstatSync('tmp/copy_test_modeDir/time_folder').mode.toString(8).slice(-3), expectedMode); 72 | test.equal(fs.lstatSync('tmp/copy_test_modeDir/time_folder/sub_folder').mode.toString(8).slice(-3), expectedMode); 73 | test.done(); 74 | }, 75 | 76 | process: function(test) { 77 | test.expect(2); 78 | test.equal(fs.lstatSync('tmp/process/beep.wav').size, fs.lstatSync('test/fixtures/beep.wav').size); 79 | test.notEqual(fs.lstatSync('tmp/process/test2.js').size, fs.lstatSync('test/fixtures/test2.js').size); 80 | 81 | test.done(); 82 | }, 83 | 84 | timestampEqual: function(test) { 85 | if (isWindows) { 86 | // Known Issue: this test will not pass on Windows due to a bug in node.js 87 | // https://github.com/nodejs/node/issues/2069 88 | test.done(); 89 | return; 90 | } 91 | test.expect(2); 92 | test.equal(fs.lstatSync('tmp/copy_test_timestamp/sub_folder').mtime.getTime(), fs.lstatSync('test/fixtures/time_folder/sub_folder').mtime.getTime()); 93 | test.equal(fs.lstatSync('tmp/copy_test_timestamp/test.js').mtime.getTime(), fs.lstatSync('test/fixtures/time_folder/test.js').mtime.getTime()); 94 | test.done(); 95 | }, 96 | 97 | timestampChanged: function(test) { 98 | test.expect(2); 99 | test.notEqual(fs.lstatSync('tmp/copy_test_timestamp/test1.js').mtime.getTime(), fs.lstatSync('test/fixtures/time_folder/test.js').mtime.getTime()); 100 | test.notEqual(fs.lstatSync('tmp/copy_test_timestamp/test_process.js').mtime.getTime(), fs.lstatSync('test/fixtures/time_folder/test_process.js').mtime.getTime()); 101 | test.done(); 102 | } 103 | }; 104 | -------------------------------------------------------------------------------- /test/expected/copy_test_files/test.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){}); -------------------------------------------------------------------------------- /test/expected/copy_test_files/test2.js: -------------------------------------------------------------------------------- 1 | console.log('hello'); -------------------------------------------------------------------------------- /test/expected/copy_test_flatten/one.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){$.noConflict();}); -------------------------------------------------------------------------------- /test/expected/copy_test_flatten/test.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){}); -------------------------------------------------------------------------------- /test/expected/copy_test_flatten/test2.js: -------------------------------------------------------------------------------- 1 | console.log('hello'); -------------------------------------------------------------------------------- /test/expected/copy_test_flatten/test_process.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){}); -------------------------------------------------------------------------------- /test/expected/copy_test_flatten/two.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){jQuery}); -------------------------------------------------------------------------------- /test/expected/copy_test_mix/folder_one/one.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){$.noConflict();}); -------------------------------------------------------------------------------- /test/expected/copy_test_mix/folder_two/two.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){jQuery}); -------------------------------------------------------------------------------- /test/expected/copy_test_mix/test.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){}); -------------------------------------------------------------------------------- /test/expected/copy_test_mix/test2.js: -------------------------------------------------------------------------------- 1 | console.log('hello'); -------------------------------------------------------------------------------- /test/expected/copy_test_mix/time_folder/sub_folder/sub_sub_folder/test.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){}); -------------------------------------------------------------------------------- /test/expected/copy_test_mix/time_folder/test.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){}); -------------------------------------------------------------------------------- /test/expected/copy_test_mix/time_folder/test_process.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){}); -------------------------------------------------------------------------------- /test/expected/copy_test_noexpandWild/test/fixtures/test.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){}); -------------------------------------------------------------------------------- /test/expected/copy_test_noexpandWild/test/fixtures/test2.js: -------------------------------------------------------------------------------- 1 | console.log('hello'); -------------------------------------------------------------------------------- /test/expected/copy_test_v0.1.0/folder_one/one.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){$.noConflict();}); -------------------------------------------------------------------------------- /test/expected/single.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){}); -------------------------------------------------------------------------------- /test/fixtures/.hidden: -------------------------------------------------------------------------------- 1 | #This is a hidden file!!! -------------------------------------------------------------------------------- /test/fixtures/beep.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gruntjs/grunt-contrib-copy/1a02e2cd2f25bea561c27e8198068e9c952d4ca2/test/fixtures/beep.wav -------------------------------------------------------------------------------- /test/fixtures/folder_one/one.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){$.noConflict();}); -------------------------------------------------------------------------------- /test/fixtures/folder_two/two.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){jQuery}); -------------------------------------------------------------------------------- /test/fixtures/test.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){}); -------------------------------------------------------------------------------- /test/fixtures/test2.js: -------------------------------------------------------------------------------- 1 | console.log('hello'); -------------------------------------------------------------------------------- /test/fixtures/time_folder/sub_folder/sub_sub_folder/test.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){}); -------------------------------------------------------------------------------- /test/fixtures/time_folder/test.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){}); -------------------------------------------------------------------------------- /test/fixtures/time_folder/test_process.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){}); --------------------------------------------------------------------------------