├── .gitattributes ├── .gitignore ├── .npmignore ├── LICENSE.txt ├── README.markdown ├── bin ├── kudusync └── kudusync.js ├── build.cmd ├── ext └── fsx_win32.node ├── lib ├── DirectoryInfo.ts ├── Ensure.ts ├── FileInfo.ts ├── FileInfoBase.ts ├── FileUtils.ts ├── Header.ts ├── Main.ts ├── Manifest.ts └── Utils.ts ├── npmpublish.cmd ├── package.json ├── test ├── attemptTests.js ├── functionalTests.js └── testTarget.js └── typings ├── commander.d.ts ├── minimatch.d.ts ├── node.d.ts └── q.d.ts /.gitattributes: -------------------------------------------------------------------------------- 1 | *.doc diff=astextplain 2 | *.DOC diff=astextplain 3 | *.docx diff=astextplain 4 | *.DOCX diff=astextplain 5 | *.dot diff=astextplain 6 | *.DOT diff=astextplain 7 | *.pdf diff=astextplain 8 | *.PDF diff=astextplain 9 | *.rtf diff=astextplain 10 | *.RTF diff=astextplain 11 | 12 | *.jpg binary 13 | *.png binary 14 | *.gif binary 15 | 16 | *.cs text=auto diff=csharp 17 | *.vb text=auto 18 | *.c text=auto 19 | *.cpp text=auto 20 | *.cxx text=auto 21 | *.h text=auto 22 | *.hxx text=auto 23 | *.py text=auto 24 | *.rb text=auto 25 | *.java text=auto 26 | *.html text=auto 27 | *.htm text=auto 28 | *.css text=auto 29 | *.scss text=auto 30 | *.sass text=auto 31 | *.less text=auto 32 | *.js text=auto 33 | *.lisp text=auto 34 | *.clj text=auto 35 | *.sql text=auto 36 | *.php text=auto 37 | *.lua text=auto 38 | *.m text=auto 39 | *.asm text=auto 40 | *.erl text=auto 41 | *.fs text=auto 42 | *.fsx text=auto 43 | *.hs text=auto 44 | 45 | *.csproj text=auto 46 | *.vbproj text=auto 47 | *.fsproj text=auto 48 | *.dbproj text=auto 49 | *.sln text=auto eol=crlf 50 | 51 | bin/* eol=lf 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.suo 2 | *.cache 3 | *.docstates 4 | *.dgml 5 | _ReSharper.* 6 | *.csproj.user 7 | *[Rr]e[Ss]harper.user 8 | _ReSharper.*/ 9 | node_packages/* 10 | node_modules/* 11 | lib/*.js 12 | *.log 13 | *.map 14 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # git # 2 | .git* 3 | 4 | # Node # 5 | node_modules/ 6 | 7 | lib/*.js 8 | build.cmd 9 | *.map 10 | npmpublish.cmd 11 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | ### KuduSync 2 | 3 | Tool for syncing files for deployment, will only copy changed files and delete files that don't exist in the destination but only if they were part of the previous deployment. 4 | 5 | This is the node.js version of [KuduSync.NET](https://github.com/projectkudu/KuduSync.NET). 6 | 7 | Install from npm - `npm install -g kudusync` 8 | 9 | 10 | ### Usage 11 | 12 | kudusync `-f [source path] -t [destination path] -n [path to next manifest path] -p [path to current manifest path] -i ` 13 | 14 | The tool will sync files from the `[source path]` to the `[destination path]` using the manifest file in `[path to current manifest]` to help determine what was added/removed, and will write the new manifest file at `[path to current manifest]`. 15 | Paths in `` will be ignored in the process 16 | 17 | 18 | ### License 19 | 20 | [Apache License 2.0](https://github.com/projectkudu/kudu/blob/master/LICENSE.txt) 21 | 22 | 23 | ### Questions? 24 | 25 | You can use the [forum](http://social.msdn.microsoft.com/Forums/en-US/azuregit/threads), chat on [JabbR](https://jabbr.net/#/rooms/kudu), or open issues in this repository. 26 | 27 | This project is under the benevolent umbrella of the [.NET Foundation](http://www.dotnetfoundation.org/). 28 | -------------------------------------------------------------------------------- /bin/kudusync: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Copyright (c) Microsoft. All rights reserved. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | 19 | var ks = require('./kudusync.js'); 20 | ks.main(); 21 | -------------------------------------------------------------------------------- /bin/kudusync.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var pathUtil = require('path'); 3 | var Q = require('q'); 4 | var minimatch = require('minimatch'); 5 | var log = console.log; 6 | if(!fs.existsSync) { 7 | fs.existsSync = pathUtil.existsSync; 8 | } 9 | var Ensure; 10 | (function (Ensure) { 11 | function argNotNull(arg, argName) { 12 | if(arg === null || arg === undefined) { 13 | throw new Error("The argument '" + argName + "' is null"); 14 | } 15 | } 16 | Ensure.argNotNull = argNotNull; 17 | })(Ensure || (Ensure = {})); 18 | 19 | var Utils; 20 | (function (Utils) { 21 | Utils.DefaultRetries = 3; 22 | Utils.DefaultDelayBeforeRetry = 250; 23 | function attempt(action, ignoreError, retries, delayBeforeRetry) { 24 | if (typeof retries === "undefined") { retries = Utils.DefaultRetries; } 25 | if (typeof delayBeforeRetry === "undefined") { delayBeforeRetry = Utils.DefaultDelayBeforeRetry; } 26 | Ensure.argNotNull(action, "action"); 27 | var currentTry = 1; 28 | var retryAction = function () { 29 | return action().then(Q.resolve, function (err) { 30 | if(ignoreError && err && err.code && err.code == ignoreError) { 31 | Q.resolve; 32 | } else { 33 | if(retries >= currentTry++) { 34 | return Q.delay(Q.fcall(retryAction), delayBeforeRetry); 35 | } else { 36 | return Q.reject(err); 37 | } 38 | } 39 | }); 40 | }; 41 | return retryAction(); 42 | } 43 | Utils.attempt = attempt; 44 | function map(source, action) { 45 | var results = []; 46 | for(var i = 0; i < source.length; i++) { 47 | results.push(action(source[i], i)); 48 | } 49 | return results; 50 | } 51 | Utils.map = map; 52 | function serialize() { 53 | var source = []; 54 | for (var _i = 0; _i < (arguments.length - 0); _i++) { 55 | source[_i] = arguments[_i + 0]; 56 | } 57 | var result = Q.resolve(); 58 | for(var i = 0; i < source.length; i++) { 59 | result = result.then(source[i]); 60 | } 61 | return result; 62 | } 63 | Utils.serialize = serialize; 64 | function mapSerialized(source, action) { 65 | var result = Q.resolve(); 66 | for(var i = 0; i < source.length; i++) { 67 | var func = { 68 | source: source[i], 69 | index: i, 70 | action: function () { 71 | var self = this; 72 | return function () { 73 | return action(self.source, self.index); 74 | } 75 | } 76 | }; 77 | result = result.then(func.action()); 78 | } 79 | return result; 80 | } 81 | Utils.mapSerialized = mapSerialized; 82 | function mapParallelized(maxParallel, source, action) { 83 | var parallelOperations = []; 84 | var result = Q.resolve(); 85 | for(var i = 0; i < source.length; i++) { 86 | var singleOperation = { 87 | source: source[i], 88 | index: i, 89 | action: function () { 90 | return action(this.source, this.index); 91 | } 92 | }; 93 | parallelOperations.push(singleOperation); 94 | if((i % maxParallel) == (maxParallel - 1) || i == (source.length - 1)) { 95 | var complexOperation = { 96 | parallelOperations: parallelOperations, 97 | action: function () { 98 | var self = this; 99 | return function () { 100 | var promises = []; 101 | for(var j = 0; j < self.parallelOperations.length; j++) { 102 | promises.push(self.parallelOperations[j].action()); 103 | } 104 | return Q.all(promises); 105 | } 106 | } 107 | }; 108 | result = result.then(complexOperation.action()); 109 | parallelOperations = []; 110 | } 111 | } 112 | return result; 113 | } 114 | Utils.mapParallelized = mapParallelized; 115 | })(Utils || (Utils = {})); 116 | 117 | exports.Utils = Utils; 118 | var FileInfoBase = (function () { 119 | function FileInfoBase(path, rootPath) { 120 | Ensure.argNotNull(path, "path"); 121 | this._path = path; 122 | this._rootPath = rootPath; 123 | this._name = pathUtil.relative(pathUtil.dirname(path), path); 124 | } 125 | FileInfoBase.prototype.name = function () { 126 | return this._name; 127 | }; 128 | FileInfoBase.prototype.path = function () { 129 | return this._path; 130 | }; 131 | FileInfoBase.prototype.rootPath = function () { 132 | return this._rootPath; 133 | }; 134 | FileInfoBase.prototype.relativePath = function () { 135 | return pathUtil.relative(this._rootPath, this._path); 136 | }; 137 | FileInfoBase.prototype.exists = function () { 138 | if(!this._exists) { 139 | this._exists = fs.existsSync(this.path()); 140 | } 141 | return this._exists; 142 | }; 143 | FileInfoBase.prototype.setExists = function (val) { 144 | this._exists = val; 145 | }; 146 | return FileInfoBase; 147 | })(); 148 | var __extends = this.__extends || function (d, b) { 149 | function __() { this.constructor = d; } 150 | __.prototype = b.prototype; 151 | d.prototype = new __(); 152 | } 153 | var FileInfo = (function (_super) { 154 | __extends(FileInfo, _super); 155 | function FileInfo(path, rootPath, size, modifiedTime) { 156 | _super.call(this, path, rootPath); 157 | Ensure.argNotNull(size, "size"); 158 | Ensure.argNotNull(modifiedTime, "modifiedTime"); 159 | this._size = size; 160 | this._modifiedTime = new Date(modifiedTime); 161 | } 162 | FileInfo.prototype.modifiedTime = function () { 163 | return this._modifiedTime; 164 | }; 165 | FileInfo.prototype.size = function () { 166 | return this._size; 167 | }; 168 | FileInfo.prototype.equals = function (otherFile) { 169 | if(this.modifiedTime() == null || otherFile.modifiedTime() == null) { 170 | return false; 171 | } 172 | return this.modifiedTime().getTime() === otherFile.modifiedTime().getTime() && this.size() === otherFile.size(); 173 | }; 174 | return FileInfo; 175 | })(FileInfoBase); 176 | var listDir = null; 177 | try { 178 | listDir = require("../ext/fsx_win32").listDir; 179 | console.log("Using fsx_win32"); 180 | } catch (e) { 181 | } 182 | if(listDir == null) { 183 | listDir = function (path) { 184 | var files = fs.readdirSync(path); 185 | return Utils.map(files, function (fileName) { 186 | var filePath = pathUtil.join(path, fileName); 187 | var stat = fs.statSync(filePath); 188 | if(stat.isDirectory()) { 189 | var result = { 190 | fileName: fileName, 191 | isDirectory: true 192 | }; 193 | return result; 194 | } else { 195 | var result = { 196 | fileName: fileName, 197 | size: stat.size, 198 | modifiedTime: stat.mtime 199 | }; 200 | return result; 201 | } 202 | }); 203 | }; 204 | } 205 | var DirectoryInfo = (function (_super) { 206 | __extends(DirectoryInfo, _super); 207 | function DirectoryInfo(path, rootPath) { 208 | _super.call(this, path, rootPath); 209 | this._filesMapping = []; 210 | this._subDirectoriesMapping = []; 211 | this._filesList = []; 212 | this._subDirectoriesList = []; 213 | this._initialized = false; 214 | } 215 | DirectoryInfo.prototype.ensureCreated = function () { 216 | var _this = this; 217 | if(!this.exists()) { 218 | var promise = this.parent().ensureCreated(); 219 | promise = promise.then(function () { 220 | return Utils.attempt(function () { 221 | return Q.nfcall(fs.mkdir, _this.path()); 222 | }); 223 | }); 224 | promise = promise.then(function () { 225 | _this.setExists(true); 226 | _this._initialized = true; 227 | }); 228 | return promise; 229 | } 230 | return Q.resolve(); 231 | }; 232 | DirectoryInfo.prototype.parent = function () { 233 | return new DirectoryInfo(pathUtil.dirname(this.path()), this.rootPath()); 234 | }; 235 | DirectoryInfo.prototype.initializeFilesAndSubDirectoriesLists = function () { 236 | if(!this._initialized && this.exists()) { 237 | return this.updateFilesAndSubDirectoriesLists(); 238 | } 239 | return Q.resolve(); 240 | }; 241 | DirectoryInfo.prototype.updateFilesAndSubDirectoriesLists = function () { 242 | var _this = this; 243 | var filesMapping = new Array(); 244 | var filesList = new Array(); 245 | var subDirectoriesMapping = new Array(); 246 | var subDirectoriesList = new Array(); 247 | if(this.exists()) { 248 | return Utils.attempt(function () { 249 | try { 250 | var files = listDir(_this.path()); 251 | files.forEach(function (file) { 252 | var path = pathUtil.join(_this.path(), file.fileName); 253 | if(file.fileName !== "." && file.fileName !== "..") { 254 | if(file.isDirectory) { 255 | var directoryInfo = new DirectoryInfo(path, _this.rootPath()); 256 | directoryInfo.setExists(true); 257 | subDirectoriesMapping[file.fileName.toUpperCase()] = directoryInfo; 258 | subDirectoriesList.push(directoryInfo); 259 | } else { 260 | var fileInfo = new FileInfo(path, _this.rootPath(), file.size, file.modifiedTime); 261 | filesMapping[file.fileName.toUpperCase()] = fileInfo; 262 | filesList.push(fileInfo); 263 | } 264 | } 265 | }); 266 | _this._filesMapping = filesMapping; 267 | _this._subDirectoriesMapping = subDirectoriesMapping; 268 | _this._filesList = filesList; 269 | _this._subDirectoriesList = subDirectoriesList; 270 | _this._initialized = true; 271 | return Q.resolve(); 272 | } catch (err) { 273 | return Q.reject(err); 274 | } 275 | }); 276 | } 277 | return Q.resolve(); 278 | }; 279 | DirectoryInfo.prototype.getFile = function (fileName) { 280 | Ensure.argNotNull(fileName, "fileName"); 281 | return this._filesMapping[fileName.toUpperCase()]; 282 | }; 283 | DirectoryInfo.prototype.getSubDirectory = function (subDirectoryName) { 284 | Ensure.argNotNull(subDirectoryName, "subDirectoryName"); 285 | return this._subDirectoriesMapping[subDirectoryName.toUpperCase()]; 286 | }; 287 | DirectoryInfo.prototype.filesList = function () { 288 | return this._filesList; 289 | }; 290 | DirectoryInfo.prototype.subDirectoriesList = function () { 291 | return this._subDirectoriesList; 292 | }; 293 | DirectoryInfo.prototype.isSubdirectoryOf = function (potentialParentDirectory) { 294 | if(potentialParentDirectory == null || this.path() == null || potentialParentDirectory.path() == null) { 295 | return false; 296 | } 297 | var thisPath = pathUtil.resolve(this.path()); 298 | var potentialParentDirectoryPath = pathUtil.resolve(potentialParentDirectory.path()); 299 | if(thisPath.toUpperCase().indexOf(potentialParentDirectoryPath.toUpperCase()) == 0) { 300 | var pathPart = thisPath.substr(potentialParentDirectoryPath.length); 301 | return pathPart.indexOf('/') >= 0 || pathPart.indexOf('\\') >= 0; 302 | } 303 | return false; 304 | }; 305 | return DirectoryInfo; 306 | })(FileInfoBase); 307 | var nodePath = require("path"); 308 | var Manifest = (function () { 309 | function Manifest() { 310 | this._files = new Array(); 311 | } 312 | Manifest.load = function load(manifestPath) { 313 | var manifest = new Manifest(); 314 | if(manifestPath == null) { 315 | return Q.resolve(manifest); 316 | } 317 | return Q.nfcall(fs.readFile, manifestPath, 'utf8').then(function (content) { 318 | var filePaths = content.split("\n"); 319 | var files = new Array(); 320 | filePaths.forEach(function (filePath) { 321 | var file = filePath.trim(); 322 | if(file != "") { 323 | files[file] = file; 324 | } 325 | }); 326 | manifest._files = files; 327 | return Q.resolve(manifest); 328 | }, function (err) { 329 | if(err.errno == 34 || err.errno == -4058) { 330 | return Q.resolve(manifest); 331 | } else { 332 | return Q.reject(err); 333 | } 334 | }); 335 | } 336 | Manifest.save = function save(manifest, manifestPath) { 337 | Ensure.argNotNull(manifest, "manifest"); 338 | Ensure.argNotNull(manifestPath, "manifestPath"); 339 | var manifestFileContent = ""; 340 | var filesForOutput = new Array(); 341 | var i = 0; 342 | for(var file in manifest._files) { 343 | filesForOutput[i] = file; 344 | i++; 345 | } 346 | var manifestFileContent = filesForOutput.join("\n"); 347 | return Q.nfcall(fs.writeFile, manifestPath, manifestFileContent, 'utf8'); 348 | } 349 | Manifest.prototype.isPathInManifest = function (path, rootPath, targetSubFolder) { 350 | Ensure.argNotNull(path, "path"); 351 | Ensure.argNotNull(rootPath, "rootPath"); 352 | var relativePath = pathUtil.relative(rootPath, path); 353 | relativePath = (targetSubFolder ? nodePath.join(targetSubFolder, relativePath) : relativePath); 354 | return this._files[relativePath] != null; 355 | }; 356 | Manifest.prototype.addFileToManifest = function (path, rootPath, targetSubFolder) { 357 | Ensure.argNotNull(path, "path"); 358 | Ensure.argNotNull(rootPath, "rootPath"); 359 | var relativePath = pathUtil.relative(rootPath, path); 360 | relativePath = (targetSubFolder ? nodePath.join(targetSubFolder, relativePath) : relativePath); 361 | this._files[relativePath] = relativePath; 362 | }; 363 | return Manifest; 364 | })(); 365 | function kuduSync(fromPath, toPath, targetSubFolder, nextManifestPath, previousManifestPath, ignoreManifest, ignore, whatIf) { 366 | Ensure.argNotNull(fromPath, "fromPath"); 367 | Ensure.argNotNull(toPath, "toPath"); 368 | Ensure.argNotNull(nextManifestPath, "nextManifestPath"); 369 | if(ignoreManifest) { 370 | previousManifestPath = null; 371 | } 372 | var from = new DirectoryInfo(fromPath, fromPath); 373 | var to = new DirectoryInfo(toPath, toPath); 374 | if(!from.exists()) { 375 | return Q.reject(new Error("From directory doesn't exist")); 376 | } 377 | if(from.isSubdirectoryOf(to) || to.isSubdirectoryOf(from)) { 378 | return Q.reject(new Error("Source and destination directories cannot be sub-directories of each other")); 379 | } 380 | var nextManifest = new Manifest(); 381 | var ignoreList = parseIgnoreList(ignore); 382 | log("Kudu sync from: '" + from.path() + "' to: '" + to.path() + "'"); 383 | return Manifest.load(previousManifestPath).then(function (manifest) { 384 | return kuduSyncDirectory(from, to, from.path(), to.path(), targetSubFolder, manifest, nextManifest, ignoreManifest, ignoreList, whatIf); 385 | }).then(function () { 386 | if(!whatIf) { 387 | return Manifest.save(nextManifest, nextManifestPath); 388 | } 389 | }); 390 | } 391 | exports.kuduSync = kuduSync; 392 | function parseIgnoreList(ignore) { 393 | if(!ignore) { 394 | return null; 395 | } 396 | return ignore.split(";"); 397 | } 398 | function shouldIgnore(path, rootPath, ignoreList) { 399 | if(!ignoreList) { 400 | return false; 401 | } 402 | var relativePath = pathUtil.relative(rootPath, path); 403 | for(var i = 0; i < ignoreList.length; i++) { 404 | var ignore = ignoreList[i]; 405 | if(minimatch(relativePath, ignore, { 406 | matchBase: true, 407 | nocase: true 408 | })) { 409 | log("Ignoring: " + relativePath); 410 | return true; 411 | } 412 | } 413 | return false; 414 | } 415 | function copyFile(fromFile, toFilePath, whatIf) { 416 | Ensure.argNotNull(fromFile, "fromFile"); 417 | Ensure.argNotNull(toFilePath, "toFilePath"); 418 | log("Copying file: '" + fromFile.relativePath() + "'"); 419 | if(!whatIf) { 420 | return Utils.attempt(function () { 421 | var promise = copyFileInternal(fromFile, toFilePath); 422 | promise = promise.then(function () { 423 | return Q.nfcall(fs.utimes, toFilePath, new Date(), fromFile.modifiedTime()); 424 | }, null); 425 | return promise; 426 | }); 427 | } 428 | return Q.resolve(); 429 | } 430 | function copyFileInternal(fromFile, toFilePath) { 431 | var deffered = Q.defer(); 432 | try { 433 | var readStream = fs.createReadStream(fromFile.path()); 434 | var writeStream = fs.createWriteStream(toFilePath); 435 | readStream.pipe(writeStream); 436 | readStream.on("error", deffered.reject); 437 | writeStream.on("error", deffered.reject); 438 | writeStream.on("close", deffered.resolve); 439 | } catch (err) { 440 | deffered.reject(err); 441 | } 442 | return deffered.promise; 443 | } 444 | function deleteFile(file, manifest, rootPath, targetSubFolder, ignoreManifest, whatIf) { 445 | Ensure.argNotNull(file, "file"); 446 | var path = file.path(); 447 | if(ignoreManifest || manifest.isPathInManifest(file.path(), rootPath, targetSubFolder)) { 448 | log("Deleting file: '" + file.relativePath() + "'"); 449 | if(!whatIf) { 450 | return Utils.attempt(function () { 451 | return Q.nfcall(fs.unlink, path); 452 | }, "ENOENT"); 453 | } 454 | } 455 | return Q.resolve(); 456 | } 457 | function deleteDirectoryRecursive(directory, manifest, rootPath, targetSubFolder, ignoreManifest, whatIf) { 458 | Ensure.argNotNull(directory, "directory"); 459 | var path = directory.path(); 460 | var relativePath = directory.relativePath(); 461 | if(!ignoreManifest && !manifest.isPathInManifest(path, rootPath, targetSubFolder)) { 462 | return Q.resolve(); 463 | } 464 | return Utils.serialize(function () { 465 | return directory.initializeFilesAndSubDirectoriesLists(); 466 | }, function () { 467 | return Utils.mapSerialized(directory.filesList(), function (file) { 468 | return deleteFile(file, manifest, rootPath, targetSubFolder, ignoreManifest, whatIf); 469 | }); 470 | }, function () { 471 | return Utils.mapSerialized(directory.subDirectoriesList(), function (subDir) { 472 | return deleteDirectoryRecursive(subDir, manifest, rootPath, targetSubFolder, ignoreManifest, whatIf); 473 | }); 474 | }, function () { 475 | return directory.updateFilesAndSubDirectoriesLists(); 476 | }, function () { 477 | var filesCount = directory.filesList().length + directory.subDirectoriesList().length; 478 | if(filesCount > 0) { 479 | return Q.resolve(); 480 | } 481 | log("Deleting directory: '" + relativePath + "'"); 482 | if(!whatIf) { 483 | return Utils.attempt(function () { 484 | return Q.nfcall(fs.rmdir, path); 485 | }, "ENOENT"); 486 | } 487 | return Q.resolve(); 488 | }); 489 | } 490 | function kuduSyncDirectory(from, to, fromRootPath, toRootPath, targetSubFolder, manifest, outManifest, ignoreManifest, ignoreList, whatIf) { 491 | Ensure.argNotNull(from, "from"); 492 | Ensure.argNotNull(to, "to"); 493 | Ensure.argNotNull(fromRootPath, "fromRootPath"); 494 | Ensure.argNotNull(toRootPath, "toRootPath"); 495 | Ensure.argNotNull(manifest, "manifest"); 496 | Ensure.argNotNull(outManifest, "outManifest"); 497 | try { 498 | if(shouldIgnore(from.path(), fromRootPath, ignoreList)) { 499 | return Q.resolve(); 500 | } 501 | if(!pathUtil.relative(from.path(), toRootPath)) { 502 | return Q.resolve(); 503 | } 504 | if(from.path() != fromRootPath) { 505 | outManifest.addFileToManifest(from.path(), fromRootPath, targetSubFolder); 506 | } 507 | return Utils.serialize(function () { 508 | if(!whatIf) { 509 | return to.ensureCreated(); 510 | } 511 | return Q.resolve(); 512 | }, function () { 513 | return to.initializeFilesAndSubDirectoriesLists(); 514 | }, function () { 515 | return from.initializeFilesAndSubDirectoriesLists(); 516 | }, function () { 517 | return Utils.mapParallelized(5, from.filesList(), function (fromFile) { 518 | if(shouldIgnore(fromFile.path(), fromRootPath, ignoreList)) { 519 | return Q.resolve(); 520 | } 521 | outManifest.addFileToManifest(fromFile.path(), fromRootPath, targetSubFolder); 522 | var toFile = to.getFile(fromFile.name()); 523 | if(toFile == null || !fromFile.equals(toFile)) { 524 | return copyFile(fromFile, pathUtil.join(to.path(), fromFile.name()), whatIf); 525 | } 526 | return Q.resolve(); 527 | }); 528 | }, function () { 529 | return Utils.mapSerialized(to.filesList(), function (toFile) { 530 | if(shouldIgnore(toFile.path(), toRootPath, ignoreList)) { 531 | return Q.resolve(); 532 | } 533 | if(!from.getFile(toFile.name())) { 534 | return deleteFile(toFile, manifest, toRootPath, targetSubFolder, ignoreManifest, whatIf); 535 | } 536 | return Q.resolve(); 537 | }); 538 | }, function () { 539 | return Utils.mapSerialized(to.subDirectoriesList(), function (toSubDirectory) { 540 | if(!from.getSubDirectory(toSubDirectory.name())) { 541 | return deleteDirectoryRecursive(toSubDirectory, manifest, toRootPath, targetSubFolder, ignoreManifest, whatIf); 542 | } 543 | return Q.resolve(); 544 | }); 545 | }, function () { 546 | return Utils.mapSerialized(from.subDirectoriesList(), function (fromSubDirectory) { 547 | var toSubDirectory = new DirectoryInfo(pathUtil.join(to.path(), fromSubDirectory.name()), toRootPath); 548 | return kuduSyncDirectory(fromSubDirectory, toSubDirectory, fromRootPath, toRootPath, targetSubFolder, manifest, outManifest, ignoreManifest, ignoreList, whatIf); 549 | }); 550 | }); 551 | } catch (err) { 552 | return Q.reject(err); 553 | } 554 | } 555 | function main() { 556 | var commander = require("commander"); 557 | var package = require("../package.json"); 558 | var path = require("path"); 559 | commander.version(package.version).usage("[options]").option("-f, --fromDir ", "Source directory to sync").option("-t, --toDir ", "Destination directory to sync").option("-s, --targetSubFolder ", "A relative sub folder in the destination to create and copy files to").option("-n, --nextManifest ", "Next manifest file path").option("-p, --previousManifest [manifest file path]", "Previous manifest file path").option("-x, --ignoreManifest", "Disables the processing of the manifest file").option("-i, --ignore [patterns]", "List of files/directories to ignore and not sync, delimited by ;").option("-q, --quiet", "No logging").option("-v, --verbose [maxLines]", "Verbose logging with maximum number of output lines").option("-w, --whatIf", "Only log without actual copy/remove of files").option("--perf", "Print out the time it took to complete KuduSync operation").parse(process.argv); 560 | var commanderValues = commander; 561 | var fromDir = commanderValues.fromDir; 562 | var toDir = commanderValues.toDir; 563 | var targetSubFolder = commanderValues.targetSubFolder; 564 | var previousManifest = commanderValues.previousManifest; 565 | var nextManifest = commanderValues.nextManifest; 566 | var ignoreManifest = commanderValues.ignoreManifest; 567 | var ignore = commanderValues.ignore; 568 | var quiet = commanderValues.quiet; 569 | var verbose = commanderValues.verbose; 570 | var whatIf = commanderValues.whatIf; 571 | var perf = commanderValues.perf; 572 | if(quiet && verbose) { 573 | console.log("Error: Cannot use --quiet and --verbose arguments together"); 574 | process.exit(1); 575 | return; 576 | } 577 | if(!fromDir || !toDir || !nextManifest) { 578 | console.log("Error: Missing required argument"); 579 | commander.help(); 580 | process.exit(1); 581 | return; 582 | } 583 | if(quiet) { 584 | log = function () { 585 | }; 586 | } 587 | if(targetSubFolder) { 588 | toDir = path.join(toDir, targetSubFolder); 589 | } 590 | var counter = 0; 591 | var nextLogTime = null; 592 | if(verbose && verbose > 0) { 593 | log = function (msg) { 594 | var updateLogTime = false; 595 | if(counter < verbose) { 596 | console.log(msg); 597 | } else { 598 | if(counter == verbose) { 599 | console.log("Omitting next output lines..."); 600 | updateLogTime = true; 601 | } else { 602 | if(new Date().getTime() >= nextLogTime.getTime()) { 603 | console.log("Processed " + (counter - 1) + " files..."); 604 | updateLogTime = true; 605 | } 606 | } 607 | } 608 | if(updateLogTime) { 609 | var currentDate = new Date(); 610 | nextLogTime = new Date(currentDate.getTime() + 20000); 611 | } 612 | counter++; 613 | }; 614 | } 615 | var start = new Date(); 616 | kuduSync(fromDir, toDir, targetSubFolder, nextManifest, previousManifest, ignoreManifest, ignore, whatIf).then(function () { 617 | if(perf) { 618 | var stop = new Date(); 619 | console.log("Operation took " + ((stop.getTime() - start.getTime()) / 1000) + " seconds"); 620 | } 621 | process.exit(0); 622 | }, function (err) { 623 | if(err) { 624 | console.log("" + err); 625 | } 626 | process.exit(1); 627 | }); 628 | } 629 | exports.main = main; 630 | -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | call npm install 4 | 5 | echo Building JavaScript from TypeScript files 6 | call npm run-script build 7 | -------------------------------------------------------------------------------- /ext/fsx_win32.node: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectkudu/KuduSync/0f863015f6c660313620f587c3de52a90a30ceea/ext/fsx_win32.node -------------------------------------------------------------------------------- /lib/DirectoryInfo.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | var listDir: (path: string) => any[] = null; 4 | 5 | try { 6 | listDir = require("../ext/fsx_win32").listDir; 7 | console.log("Using fsx_win32"); 8 | } 9 | catch (e) { 10 | } 11 | 12 | if (listDir == null) { 13 | listDir = function (path: string) { 14 | var files = fs.readdirSync(path); 15 | return Utils.map(files, (fileName) => { 16 | var filePath = pathUtil.join(path, fileName); 17 | var stat = fs.statSync(filePath); 18 | 19 | if (stat.isDirectory()) { 20 | var result: any = { 21 | fileName: fileName, 22 | isDirectory: true 23 | }; 24 | return result; 25 | } 26 | else { 27 | var result: any = { 28 | fileName: fileName, 29 | size: stat.size, 30 | modifiedTime: stat.mtime 31 | }; 32 | return result; 33 | } 34 | }); 35 | } 36 | } 37 | 38 | class DirectoryInfo extends FileInfoBase { 39 | private _filesMapping: FileInfo[]; 40 | private _subDirectoriesMapping: DirectoryInfo[]; 41 | private _filesList: FileInfo[]; 42 | private _subDirectoriesList: DirectoryInfo[]; 43 | private _initialized: bool; 44 | 45 | constructor(path: string, rootPath: string) { 46 | super(path, rootPath); 47 | 48 | this._filesMapping = []; 49 | this._subDirectoriesMapping = []; 50 | this._filesList = []; 51 | this._subDirectoriesList = []; 52 | this._initialized = false; 53 | } 54 | 55 | ensureCreated(): Promise { 56 | if (!this.exists()) { 57 | var promise = this.parent().ensureCreated(); 58 | 59 | promise = promise.then(() => { 60 | return Utils.attempt(() => Q.nfcall(fs.mkdir, this.path())); 61 | }); 62 | 63 | promise = promise.then(() => { 64 | this.setExists(true); 65 | this._initialized = true; 66 | }); 67 | 68 | return promise; 69 | } 70 | 71 | return Q.resolve(); 72 | } 73 | 74 | parent(): DirectoryInfo { 75 | return new DirectoryInfo(pathUtil.dirname(this.path()), this.rootPath()); 76 | } 77 | 78 | initializeFilesAndSubDirectoriesLists(): Promise { 79 | if (!this._initialized && this.exists()) { 80 | return this.updateFilesAndSubDirectoriesLists(); 81 | } 82 | 83 | return Q.resolve(); 84 | } 85 | 86 | updateFilesAndSubDirectoriesLists(): Promise { 87 | var filesMapping = new FileInfo[]; 88 | var filesList = new FileInfo[]; 89 | var subDirectoriesMapping = new DirectoryInfo[]; 90 | var subDirectoriesList = new DirectoryInfo[]; 91 | 92 | if (this.exists()) { 93 | return Utils.attempt(() => { 94 | try { 95 | var files = listDir(this.path()); 96 | files.forEach((file: any) => { 97 | var path = pathUtil.join(this.path(), file.fileName); 98 | 99 | if (file.fileName !== "." && file.fileName !== "..") { 100 | if (file.isDirectory) { 101 | // Store both as mapping as an array 102 | var directoryInfo = new DirectoryInfo(path, this.rootPath()); 103 | directoryInfo.setExists(true); 104 | subDirectoriesMapping[file.fileName.toUpperCase()] = directoryInfo; 105 | subDirectoriesList.push(directoryInfo); 106 | } else { 107 | // Store both as mapping as an array 108 | var fileInfo = new FileInfo(path, this.rootPath(), file.size, file.modifiedTime); 109 | filesMapping[file.fileName.toUpperCase()] = fileInfo; 110 | filesList.push(fileInfo); 111 | } 112 | } 113 | }); 114 | 115 | this._filesMapping = filesMapping; 116 | this._subDirectoriesMapping = subDirectoriesMapping; 117 | this._filesList = filesList; 118 | this._subDirectoriesList = subDirectoriesList; 119 | 120 | this._initialized = true; 121 | 122 | return Q.resolve(); 123 | } 124 | catch (err) { 125 | return Q.reject(err); 126 | } 127 | }); 128 | } 129 | 130 | return Q.resolve(); 131 | } 132 | 133 | getFile(fileName: string): FileInfo { 134 | Ensure.argNotNull(fileName, "fileName"); 135 | 136 | return this._filesMapping[fileName.toUpperCase()]; 137 | } 138 | 139 | getSubDirectory(subDirectoryName: string): DirectoryInfo { 140 | Ensure.argNotNull(subDirectoryName, "subDirectoryName"); 141 | 142 | return this._subDirectoriesMapping[subDirectoryName.toUpperCase()]; 143 | } 144 | 145 | filesList() { 146 | return this._filesList; 147 | } 148 | 149 | subDirectoriesList() { 150 | return this._subDirectoriesList; 151 | } 152 | 153 | isSubdirectoryOf(potentialParentDirectory: DirectoryInfo): bool { 154 | if (potentialParentDirectory == null || this.path() == null || potentialParentDirectory.path() == null) { 155 | return false; 156 | } 157 | 158 | var thisPath = pathUtil.resolve(this.path()); 159 | var potentialParentDirectoryPath = pathUtil.resolve(potentialParentDirectory.path()); 160 | 161 | if (thisPath.toUpperCase().indexOf(potentialParentDirectoryPath.toUpperCase()) == 0) { 162 | var pathPart = thisPath.substr(potentialParentDirectoryPath.length); 163 | return pathPart.indexOf('/') >= 0 || pathPart.indexOf('\\') >= 0; 164 | } 165 | 166 | return false; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /lib/Ensure.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | module Ensure { 4 | export function argNotNull(arg, argName: string) { 5 | if (arg === null || arg === undefined) { 6 | throw new Error("The argument '" + argName + "' is null"); 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/FileInfo.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | class FileInfo extends FileInfoBase { 4 | private _size; 5 | private _modifiedTime: Date; 6 | 7 | constructor (path: string, rootPath: string, size: any, modifiedTime: any) { 8 | super(path, rootPath); 9 | Ensure.argNotNull(size, "size"); 10 | Ensure.argNotNull(modifiedTime, "modifiedTime"); 11 | 12 | this._size = size; 13 | this._modifiedTime = new Date(modifiedTime); 14 | } 15 | 16 | modifiedTime() { 17 | return this._modifiedTime; 18 | } 19 | 20 | size() { 21 | return this._size; 22 | } 23 | 24 | equals(otherFile: FileInfo): bool { 25 | if (this.modifiedTime() == null || otherFile.modifiedTime() == null) { 26 | return false; 27 | } 28 | 29 | return this.modifiedTime().getTime() === otherFile.modifiedTime().getTime() && 30 | this.size() === otherFile.size(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/FileInfoBase.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | class FileInfoBase { 4 | private _name: string; 5 | private _path: string; 6 | private _rootPath: string; 7 | private _exists: bool; 8 | 9 | constructor (path: string, rootPath: string) { 10 | Ensure.argNotNull(path, "path"); 11 | 12 | this._path = path; 13 | this._rootPath = rootPath; 14 | this._name = pathUtil.relative(pathUtil.dirname(path), path); 15 | } 16 | 17 | name(): string { 18 | return this._name; 19 | } 20 | 21 | path(): string { 22 | return this._path; 23 | } 24 | 25 | rootPath(): string { 26 | return this._rootPath; 27 | } 28 | 29 | relativePath(): string { 30 | return pathUtil.relative(this._rootPath, this._path); 31 | } 32 | 33 | exists(): bool { 34 | if (!this._exists) { 35 | this._exists = fs.existsSync(this.path()); 36 | } 37 | return this._exists; 38 | } 39 | 40 | setExists(val: bool) { 41 | this._exists = val; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/FileUtils.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | function kuduSync(fromPath: string, toPath: string, targetSubFolder: string, nextManifestPath: string, previousManifestPath: string, ignoreManifest: bool, ignore: string, whatIf: bool) : Promise { 5 | Ensure.argNotNull(fromPath, "fromPath"); 6 | Ensure.argNotNull(toPath, "toPath"); 7 | Ensure.argNotNull(nextManifestPath, "nextManifestPath"); 8 | 9 | if (ignoreManifest) { 10 | previousManifestPath = null; 11 | } 12 | 13 | var from = new DirectoryInfo(fromPath, fromPath); 14 | var to = new DirectoryInfo(toPath, toPath); 15 | 16 | if (!from.exists()) { 17 | return Q.reject(new Error("From directory doesn't exist")); 18 | } 19 | 20 | if (from.isSubdirectoryOf(to) || to.isSubdirectoryOf(from)) { 21 | return Q.reject(new Error("Source and destination directories cannot be sub-directories of each other")); 22 | } 23 | 24 | var nextManifest = new Manifest(); 25 | 26 | var ignoreList = parseIgnoreList(ignore); 27 | 28 | log("Kudu sync from: '" + from.path() + "' to: '" + to.path() + "'"); 29 | 30 | return Manifest.load(previousManifestPath) 31 | .then((manifest) => kuduSyncDirectory(from, to, from.path(), to.path(), targetSubFolder, manifest, nextManifest, ignoreManifest, ignoreList, whatIf)) 32 | .then(() => { 33 | if (!whatIf) { 34 | return Manifest.save(nextManifest, nextManifestPath); 35 | } 36 | }); 37 | } 38 | 39 | exports.kuduSync = kuduSync; 40 | 41 | function parseIgnoreList(ignore: string): string[] { 42 | if (!ignore) { 43 | return null; 44 | } 45 | 46 | return ignore.split(";"); 47 | } 48 | 49 | function shouldIgnore(path: string, rootPath: string, ignoreList: string[]): bool { 50 | if (!ignoreList) { 51 | return false; 52 | } 53 | 54 | var relativePath = pathUtil.relative(rootPath, path); 55 | 56 | for (var i = 0; i < ignoreList.length; i++) { 57 | var ignore = ignoreList[i]; 58 | if (minimatch(relativePath, ignore, { matchBase: true, nocase: true })) { 59 | log("Ignoring: " + relativePath); 60 | return true; 61 | } 62 | } 63 | 64 | return false; 65 | } 66 | 67 | function copyFile(fromFile: FileInfo, toFilePath: string, whatIf: bool) : Promise { 68 | Ensure.argNotNull(fromFile, "fromFile"); 69 | Ensure.argNotNull(toFilePath, "toFilePath"); 70 | 71 | log("Copying file: '" + fromFile.relativePath() + "'"); 72 | 73 | if (!whatIf) { 74 | return Utils.attempt(() => { 75 | var promise = copyFileInternal(fromFile, toFilePath); 76 | promise = promise.then(function () { 77 | return Q.nfcall(fs.utimes, toFilePath, new Date(), fromFile.modifiedTime()); 78 | }, null); 79 | 80 | return promise; 81 | }); 82 | } 83 | 84 | return Q.resolve(); 85 | } 86 | 87 | function copyFileInternal(fromFile: FileInfo, toFilePath: string): Promise { 88 | var deffered = Q.defer(); 89 | try { 90 | var readStream = fs.createReadStream(fromFile.path()); 91 | var writeStream = fs.createWriteStream(toFilePath); 92 | readStream.pipe(writeStream); 93 | readStream.on("error", deffered.reject); 94 | writeStream.on("error", deffered.reject); 95 | writeStream.on("close", deffered.resolve); 96 | } 97 | catch (err) { 98 | deffered.reject(err); 99 | } 100 | 101 | return deffered.promise; 102 | } 103 | 104 | function deleteFile(file: FileInfo, manifest: Manifest, rootPath: string, targetSubFolder: string, ignoreManifest: bool, whatIf: bool) : Promise { 105 | Ensure.argNotNull(file, "file"); 106 | 107 | var path = file.path(); 108 | 109 | // Remove file only if it was in previous manifest or if manifest is to be ignored 110 | if (ignoreManifest || manifest.isPathInManifest(file.path(), rootPath, targetSubFolder)) { 111 | log("Deleting file: '" + file.relativePath() + "'"); 112 | 113 | if (!whatIf) { 114 | return Utils.attempt(() => Q.nfcall(fs.unlink, path), "ENOENT"); 115 | } 116 | } 117 | 118 | return Q.resolve(); 119 | } 120 | 121 | function deleteDirectoryRecursive(directory: DirectoryInfo, manifest: Manifest, rootPath: string, targetSubFolder: string, ignoreManifest: bool, whatIf: bool) { 122 | Ensure.argNotNull(directory, "directory"); 123 | 124 | var path = directory.path(); 125 | var relativePath = directory.relativePath(); 126 | 127 | // Remove directory only if it was in previous manifest or if manifest is to be ignored 128 | if (!ignoreManifest && !manifest.isPathInManifest(path, rootPath, targetSubFolder)) { 129 | return Q.resolve(); 130 | } 131 | 132 | return Utils.serialize( 133 | () => { 134 | return directory.initializeFilesAndSubDirectoriesLists(); 135 | }, 136 | 137 | () => { 138 | return Utils.mapSerialized(directory.filesList(), (file: FileInfo) => deleteFile(file, manifest, rootPath, targetSubFolder, ignoreManifest, whatIf)); 139 | }, 140 | 141 | () => { 142 | return Utils.mapSerialized(directory.subDirectoriesList(), (subDir) => deleteDirectoryRecursive(subDir, manifest, rootPath, targetSubFolder, ignoreManifest, whatIf)); 143 | }, 144 | 145 | () => { 146 | return directory.updateFilesAndSubDirectoriesLists(); 147 | }, 148 | 149 | () => { 150 | var filesCount = directory.filesList().length + directory.subDirectoriesList().length; 151 | if (filesCount > 0) { 152 | return Q.resolve(); 153 | } 154 | 155 | // Delete current directory 156 | log("Deleting directory: '" + relativePath + "'"); 157 | if (!whatIf) { 158 | return Utils.attempt(() => Q.nfcall(fs.rmdir, path), "ENOENT"); 159 | } 160 | return Q.resolve(); 161 | }); 162 | } 163 | 164 | function kuduSyncDirectory(from: DirectoryInfo, to: DirectoryInfo, fromRootPath: string, toRootPath: string, targetSubFolder: string, manifest: Manifest, outManifest: Manifest, ignoreManifest: bool, ignoreList: string[], whatIf: bool) { 165 | Ensure.argNotNull(from, "from"); 166 | Ensure.argNotNull(to, "to"); 167 | Ensure.argNotNull(fromRootPath, "fromRootPath"); 168 | Ensure.argNotNull(toRootPath, "toRootPath"); 169 | Ensure.argNotNull(manifest, "manifest"); 170 | Ensure.argNotNull(outManifest, "outManifest"); 171 | 172 | try { 173 | if (shouldIgnore(from.path(), fromRootPath, ignoreList)) { 174 | // Ignore directories in ignore list 175 | return Q.resolve(); 176 | } 177 | 178 | if (!pathUtil.relative(from.path(), toRootPath)) { 179 | // No need to copy the destination path itself (if contained within the source directory) 180 | return Q.resolve(); 181 | } 182 | 183 | if (from.path() != fromRootPath) { 184 | outManifest.addFileToManifest(from.path(), fromRootPath, targetSubFolder); 185 | } 186 | 187 | // Do the following actions one after the other (serialized) 188 | return Utils.serialize( 189 | () => { 190 | if (!whatIf) { 191 | return to.ensureCreated(); 192 | } 193 | return Q.resolve(); 194 | }, 195 | 196 | () => { 197 | return to.initializeFilesAndSubDirectoriesLists(); 198 | }, 199 | 200 | () => { 201 | return from.initializeFilesAndSubDirectoriesLists(); 202 | }, 203 | 204 | () => { 205 | // Copy files 206 | return Utils.mapParallelized( 207 | 5, 208 | from.filesList(), 209 | (fromFile: FileInfo) => { 210 | if (shouldIgnore(fromFile.path(), fromRootPath, ignoreList)) { 211 | // Ignore files in ignore list 212 | return Q.resolve(); 213 | } 214 | 215 | outManifest.addFileToManifest(fromFile.path(), fromRootPath, targetSubFolder); 216 | 217 | // if the file exists in the destination then only copy it again if it's 218 | // last write time is different than the same file in the source (only if it changed) 219 | var toFile = to.getFile(fromFile.name()); 220 | 221 | if (toFile == null || !fromFile.equals(toFile)) { 222 | return copyFile(fromFile, pathUtil.join(to.path(), fromFile.name()), whatIf); 223 | } 224 | 225 | return Q.resolve(); 226 | } 227 | ); 228 | }, 229 | 230 | () => { 231 | // If the file doesn't exist in the source, only delete if: 232 | // 1. We have no previous directory 233 | // 2. We have a previous directory and the file exists there 234 | return Utils.mapSerialized( 235 | to.filesList(), 236 | (toFile: FileInfo) => { 237 | if (shouldIgnore(toFile.path(), toRootPath, ignoreList)) { 238 | // Ignore files in ignore list 239 | return Q.resolve(); 240 | } 241 | 242 | if (!from.getFile(toFile.name())) { 243 | return deleteFile(toFile, manifest, toRootPath, targetSubFolder, ignoreManifest, whatIf); 244 | } 245 | return Q.resolve(); 246 | } 247 | ); 248 | }, 249 | 250 | () => { 251 | return Utils.mapSerialized( 252 | to.subDirectoriesList(), 253 | (toSubDirectory: DirectoryInfo) => { 254 | // If the file doesn't exist in the source, only delete if: 255 | // 1. We have no previous directory 256 | // 2. We have a previous directory and the file exists there 257 | if (!from.getSubDirectory(toSubDirectory.name())) { 258 | return deleteDirectoryRecursive(toSubDirectory, manifest, toRootPath, targetSubFolder, ignoreManifest, whatIf); 259 | } 260 | return Q.resolve(); 261 | } 262 | ); 263 | }, 264 | 265 | () => { 266 | // Copy directories 267 | return Utils.mapSerialized( 268 | from.subDirectoriesList(), 269 | (fromSubDirectory: DirectoryInfo) => { 270 | var toSubDirectory = new DirectoryInfo(pathUtil.join(to.path(), fromSubDirectory.name()), toRootPath); 271 | return kuduSyncDirectory( 272 | fromSubDirectory, 273 | toSubDirectory, 274 | fromRootPath, 275 | toRootPath, 276 | targetSubFolder, 277 | manifest, 278 | outManifest, 279 | ignoreManifest, 280 | ignoreList, 281 | whatIf); 282 | } 283 | ); 284 | }); 285 | } 286 | catch (err) { 287 | return Q.reject(err); 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /lib/Header.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | var fs = require('fs'); 6 | var pathUtil = require('path'); 7 | var Q = require('q'); 8 | var minimatch = require('minimatch'); 9 | 10 | var log: any = console.log; 11 | 12 | // Workaround to support both APIs whether in node 0.6.* or 0.8.0 and higher. 13 | if (!fs.existsSync) fs.existsSync = pathUtil.existsSync; 14 | -------------------------------------------------------------------------------- /lib/Main.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | function main() { 5 | var commander: Commander = require("commander"); 6 | var package = require("../package.json"); 7 | var path = require("path"); 8 | 9 | commander 10 | .version(package.version) 11 | .usage("[options]") 12 | .option("-f, --fromDir ", "Source directory to sync") 13 | .option("-t, --toDir ", "Destination directory to sync") 14 | .option("-s, --targetSubFolder ", "A relative sub folder in the destination to create and copy files to") 15 | .option("-n, --nextManifest ", "Next manifest file path") 16 | .option("-p, --previousManifest [manifest file path]", "Previous manifest file path") 17 | .option("-x, --ignoreManifest", "Disables the processing of the manifest file") 18 | .option("-i, --ignore [patterns]", "List of files/directories to ignore and not sync, delimited by ;") 19 | .option("-q, --quiet", "No logging") 20 | .option("-v, --verbose [maxLines]", "Verbose logging with maximum number of output lines") 21 | .option("-w, --whatIf", "Only log without actual copy/remove of files") 22 | .option("--perf", "Print out the time it took to complete KuduSync operation") 23 | .parse(process.argv); 24 | 25 | var commanderValues: any = commander; 26 | var fromDir = commanderValues.fromDir; 27 | var toDir = commanderValues.toDir; 28 | var targetSubFolder = commanderValues.targetSubFolder; 29 | var previousManifest = commanderValues.previousManifest; 30 | var nextManifest = commanderValues.nextManifest; 31 | var ignoreManifest = commanderValues.ignoreManifest; 32 | var ignore = commanderValues.ignore; 33 | var quiet = commanderValues.quiet; 34 | var verbose = commanderValues.verbose; 35 | var whatIf = commanderValues.whatIf; 36 | var perf = commanderValues.perf; 37 | 38 | if (quiet && verbose) { 39 | console.log("Error: Cannot use --quiet and --verbose arguments together"); 40 | 41 | // Exit with an error code 42 | process.exit(1); 43 | 44 | return; 45 | } 46 | 47 | if (!fromDir || !toDir || !nextManifest) { 48 | console.log("Error: Missing required argument"); 49 | commander.help(); 50 | 51 | // Exit with an error code 52 | process.exit(1); 53 | 54 | return; 55 | } 56 | 57 | if (quiet) { 58 | // Change log to be no op 59 | log = () => { }; 60 | } 61 | 62 | if (targetSubFolder) { 63 | toDir = path.join(toDir, targetSubFolder); 64 | } 65 | 66 | var counter = 0; 67 | var nextLogTime: Date = null; 68 | if (verbose && verbose > 0) { 69 | log = (msg) => { 70 | var updateLogTime: bool = false; 71 | 72 | if (counter < verbose) { 73 | console.log(msg); 74 | } 75 | else if (counter == verbose) { 76 | console.log("Omitting next output lines..."); 77 | updateLogTime = true; 78 | } 79 | else { 80 | if (new Date().getTime() >= nextLogTime.getTime()) { 81 | console.log("Processed " + (counter - 1) + " files..."); 82 | updateLogTime = true; 83 | } 84 | } 85 | 86 | if (updateLogTime) { 87 | var currentDate = new Date(); 88 | nextLogTime = new Date(currentDate.getTime() + 20000); 89 | } 90 | 91 | counter++; 92 | }; 93 | } 94 | 95 | var start = new Date(); 96 | kuduSync( 97 | fromDir, 98 | toDir, 99 | targetSubFolder, 100 | nextManifest, 101 | previousManifest, 102 | ignoreManifest, 103 | ignore, 104 | whatIf).then( 105 | () => { 106 | if (perf) { 107 | var stop = new Date(); 108 | console.log("Operation took " + ((stop.getTime() - start.getTime()) / 1000) + " seconds"); 109 | } 110 | process.exit(0); 111 | }, 112 | function (err?) { 113 | if (err) { 114 | // Errors should always be logged 115 | console.log("" + err); 116 | } 117 | 118 | // Exit with an error code 119 | process.exit(1); 120 | }); 121 | } 122 | 123 | exports.main = main; 124 | 125 | -------------------------------------------------------------------------------- /lib/Manifest.ts: -------------------------------------------------------------------------------- 1 | /// 2 | var nodePath = require("path"); 3 | 4 | class Manifest { 5 | 6 | private _files: string[]; 7 | 8 | constructor () { 9 | this._files = new string[]; 10 | } 11 | 12 | static load(manifestPath: string) { 13 | var manifest = new Manifest(); 14 | 15 | if (manifestPath == null) { 16 | return Q.resolve(manifest); 17 | } 18 | 19 | return Q.nfcall(fs.readFile, manifestPath, 'utf8').then( 20 | function(content?) { 21 | var filePaths = content.split("\n"); 22 | var files = new string[]; 23 | filePaths.forEach( 24 | function (filePath) { 25 | var file = filePath.trim(); 26 | if (file != "") { 27 | files[file] = file; 28 | } 29 | } 30 | ); 31 | manifest._files = files; 32 | return Q.resolve(manifest); 33 | }, 34 | function(err?) { 35 | // If failed on file not found (34/-4058), return an empty manifest 36 | if (err.errno == 34 || err.errno == -4058) { 37 | return Q.resolve(manifest); 38 | } 39 | else { 40 | return Q.reject(err); 41 | } 42 | }); 43 | } 44 | 45 | static save(manifest: Manifest, manifestPath: string) { 46 | Ensure.argNotNull(manifest, "manifest"); 47 | Ensure.argNotNull(manifestPath, "manifestPath"); 48 | 49 | var manifestFileContent = ""; 50 | var filesForOutput = new string[]; 51 | 52 | var i = 0; 53 | for (var file in manifest._files) { 54 | filesForOutput[i] = file; 55 | i++ 56 | } 57 | 58 | var manifestFileContent = filesForOutput.join("\n"); 59 | return Q.nfcall(fs.writeFile, manifestPath, manifestFileContent, 'utf8'); 60 | } 61 | 62 | isPathInManifest(path: string, rootPath: string, targetSubFolder: string) { 63 | Ensure.argNotNull(path, "path"); 64 | Ensure.argNotNull(rootPath, "rootPath"); 65 | 66 | var relativePath = pathUtil.relative(rootPath, path); 67 | relativePath = (targetSubFolder 68 | ? nodePath.join(targetSubFolder, relativePath) 69 | : relativePath); 70 | return this._files[relativePath] != null; 71 | } 72 | 73 | addFileToManifest(path: string, rootPath: string, targetSubFolder: string) { 74 | Ensure.argNotNull(path, "path"); 75 | Ensure.argNotNull(rootPath, "rootPath"); 76 | 77 | var relativePath = pathUtil.relative(rootPath, path); 78 | relativePath = (targetSubFolder 79 | ? nodePath.join(targetSubFolder, relativePath) 80 | : relativePath); 81 | this._files[relativePath] = relativePath; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/Utils.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | module Utils { 4 | private DefaultRetries: number = 3; 5 | private DefaultDelayBeforeRetry: number = 250; // 250 ms 6 | 7 | export function attempt(action: () => Promise, ignoreError: string = "", retries: number = DefaultRetries, delayBeforeRetry: number = DefaultDelayBeforeRetry) : Promise { 8 | Ensure.argNotNull(action, "action"); 9 | var currentTry = 1; 10 | 11 | var retryAction = () => { 12 | return action().then( 13 | Q.resolve, 14 | function(err?) { 15 | if (ignoreError && err && err.code && err.code == ignoreError) { 16 | Q.resolve; 17 | } 18 | else if (retries >= currentTry++) { 19 | return Q.delay(Q.fcall(retryAction), delayBeforeRetry); 20 | } 21 | else { 22 | return Q.reject(err); 23 | } 24 | }); 25 | }; 26 | return retryAction(); 27 | } 28 | 29 | export function map(source: any[], action: (element: any, index: Number) => any) : any[] { 30 | var results = []; 31 | for (var i = 0; i < source.length; i++) { 32 | results.push(action(source[i], i)); 33 | } 34 | 35 | return results; 36 | } 37 | 38 | export function serialize(...source: {(): Promise; }[]) : Promise { 39 | var result = Q.resolve(); 40 | for (var i = 0; i < source.length; i++) { 41 | result = result.then(source[i]); 42 | } 43 | return result; 44 | } 45 | 46 | export function mapSerialized(source: any[], action: (element: any, index: Number) => Promise) : Promise { 47 | var result = Q.resolve(); 48 | for (var i = 0; i < source.length; i++) { 49 | var func: any = { 50 | source: source[i], 51 | index: i, 52 | action: function () { 53 | var self = this; 54 | return function () { 55 | return action(self.source, self.index); 56 | } 57 | } 58 | }; 59 | 60 | result = result.then(func.action()); 61 | } 62 | 63 | return result; 64 | } 65 | 66 | export function mapParallelized(maxParallel: number, source: any[], action: (element: any, index: number) => Promise) : Promise { 67 | var parallelOperations = []; 68 | var result = Q.resolve(); 69 | 70 | for (var i = 0; i < source.length; i++) { 71 | var singleOperation: any = { 72 | source: source[i], 73 | index: i, 74 | action: function () { 75 | return action(this.source, this.index); 76 | } 77 | }; 78 | 79 | parallelOperations.push(singleOperation); 80 | 81 | // Create a complex operation to run once reached maxParallel number of operations 82 | // Or this is the last operation 83 | if ((i % maxParallel) == (maxParallel - 1) || i == (source.length - 1)) { 84 | var complexOperation: any = { 85 | parallelOperations: parallelOperations, 86 | action: function () { 87 | var self = this; 88 | return function () { 89 | var promises = []; 90 | 91 | for (var j = 0; j < self.parallelOperations.length; j++) { 92 | promises.push(self.parallelOperations[j].action()); 93 | } 94 | 95 | return Q.all(promises); 96 | } 97 | } 98 | }; 99 | 100 | result = result.then(complexOperation.action()); 101 | parallelOperations = []; 102 | } 103 | } 104 | 105 | return result; 106 | } 107 | } 108 | exports.Utils = Utils; 109 | -------------------------------------------------------------------------------- /npmpublish.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | setlocal 4 | 5 | set GIT_STATUS_RETURN_VALUE= 6 | 7 | echo Make sure no outstanding files to commit 8 | FOR /F "tokens=*" %%i IN ('git status -z') DO ( 9 | set GIT_STATUS_RETURN_VALUE=%%i 10 | ) 11 | 12 | if NOT "%GIT_STATUS_RETURN_VALUE%" == "" ( 13 | git status 14 | goto error 15 | ) 16 | 17 | echo Building KuduSync 18 | call build.cmd 19 | 20 | echo Testing KuduSync 21 | call npm test 22 | IF %ERRORLEVEL% NEQ 0 goto error 23 | 24 | echo Fixing bin\kudusync.js line endings 25 | git commit bin/kudusync.js -m "Auto-generated file" 26 | 27 | del bin\kudusync.js 28 | IF %ERRORLEVEL% NEQ 0 goto error 29 | 30 | git checkout bin/kudusync.js 31 | IF %ERRORLEVEL% NEQ 0 goto error 32 | 33 | echo Testing KuduSync again 34 | call npm test 35 | IF %ERRORLEVEL% NEQ 0 goto error 36 | 37 | echo Incrementing KuduSync version 38 | call npm version patch 39 | IF %ERRORLEVEL% NEQ 0 goto error 40 | 41 | echo Trying to install KuduSync 42 | call npm install . -g 43 | IF %ERRORLEVEL% NEQ 0 goto error 44 | 45 | echo Publishing KuduSync 46 | call npm publish 47 | IF %ERRORLEVEL% NEQ 0 goto error 48 | 49 | echo Trying to install KuduSync from npm registry 50 | call npm install kudusync -g 51 | IF %ERRORLEVEL% NEQ 0 goto error 52 | 53 | goto end 54 | 55 | :error 56 | echo Publishing KuduSync failed 57 | exit /b 1 58 | 59 | :end 60 | echo Published successfully 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kudusync", 3 | "author": ".NET Foundation", 4 | "version": "0.4.0", 5 | "description": "Tool for syncing files for deployment, will only copy changed files and delete files that doesn't exists in the destination but only if they were part of the previous deployment.", 6 | "tags": [ 7 | "azure", 8 | "deployment" 9 | ], 10 | "keywords": [ 11 | "node", 12 | "azure", 13 | "deployment" 14 | ], 15 | "main": "./bin/kudusync.js", 16 | "engines": { 17 | "node": ">= 0.6.20" 18 | }, 19 | "scripts": { 20 | "build": "tsc --module node --out bin/kudusync.js lib/Main.ts", 21 | "test": "mocha -u tdd test -R spec --slow 3000 --timeout 6000" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/projectkudu/KuduSync.git" 26 | }, 27 | "homepage": "http://github.com/projectkudu/KuduSync", 28 | "bugs": { 29 | "url": "http://github.com/projectkudu/KuduSync/issues" 30 | }, 31 | "bin": { 32 | "kudusync": "./bin/kudusync" 33 | }, 34 | "licenses": [ 35 | { 36 | "type": "Apache", 37 | "url": "http://www.apache.org/licenses/LICENSE-2.0" 38 | } 39 | ], 40 | "readmeFilename": "README.markdown", 41 | "devDependencies": { 42 | "typescript": "0.8.0", 43 | "mocha": "2.1.0", 44 | "should": "4.6.0" 45 | }, 46 | "dependencies": { 47 | "q": "1.1.2", 48 | "minimatch": "2.0.1", 49 | "commander": "2.6.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/attemptTests.js: -------------------------------------------------------------------------------- 1 | // Functional tests using mocha and should. 2 | 3 | // The tested module 4 | var ks = require("../bin/kudusync.js"); 5 | 6 | var should = require("should"); 7 | var Q = require("q"); 8 | 9 | var attempts = 0; 10 | 11 | // Tests Suite 12 | suite('Attempt Function Tests', function () { 13 | test('Attempt without failing action should be called once', function (done) { 14 | ks.Utils.attempt(function () { 15 | attempts.should.equal(0); 16 | attempts++; 17 | return Q.resolve(); 18 | }, null, 3, 10).then(function () { 19 | attempts.should.equal(1); 20 | done(); 21 | }); 22 | }); 23 | 24 | test('Attempt with failing action should be called once', function (done) { 25 | ks.Utils.attempt(function () { 26 | // Do nothing, success 27 | attempts++; 28 | 29 | if (attempts == 1) { 30 | return Q.reject(new Error("error")); 31 | } 32 | 33 | return Q.resolve(); 34 | }, null, 3, 10).then(function () { 35 | attempts.should.equal(2); 36 | done(); 37 | }); 38 | }); 39 | 40 | test('Attempt should retry for failing actions', function (done) { 41 | ks.Utils.attempt(function () { 42 | // Do nothing, success 43 | attempts++; 44 | 45 | if (attempts <= 2) { 46 | return Q.reject(new Error("error")); 47 | } 48 | 49 | return Q.resolve(); 50 | }, null, 3, 10).then(function () { 51 | attempts.should.equal(3); 52 | done(); 53 | }); 54 | }); 55 | 56 | test('Attempt should retry for failing actions', function (done) { 57 | ks.Utils.attempt(function () { 58 | // Do nothing, success 59 | attempts++; 60 | 61 | if (attempts <= 3) { 62 | return Q.reject(new Error("error")); 63 | } 64 | 65 | return Q.resolve(); 66 | }, null, 3, 10).then(function () { 67 | attempts.should.equal(4); 68 | done(); 69 | }); 70 | }); 71 | 72 | test('Attempt with failing fails after retries', function (done) { 73 | ks.Utils.attempt(function () { 74 | // Do nothing, success 75 | attempts++; 76 | 77 | if (attempts <= 4) { 78 | return Q.reject(new Error("error")); 79 | } 80 | 81 | return Q.resolve(); 82 | }, null, 3, 10).fail(function (err) { 83 | err.should.be.ok; 84 | err.message.should.equal("error"); 85 | attempts.should.equal(4); 86 | done(); 87 | }); 88 | }); 89 | 90 | setup(function () { 91 | attempts = 0; 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /test/functionalTests.js: -------------------------------------------------------------------------------- 1 | // Functional tests using mocha and should. 2 | 3 | var should = require("should"); 4 | var fs = require("fs"); 5 | var pathUtil = require("path"); 6 | 7 | var exec = require("child_process").exec; 8 | 9 | // Globals 10 | var baseTestTempDir = "temp"; 11 | var fromDir = "from"; 12 | var toDir = "to"; 13 | var testDirBase = "test"; 14 | var testDirIndex = 0; 15 | var testDir = ""; 16 | 17 | // Supply way to be able to replace the test target 18 | var testTarget = require("./testTarget"); 19 | 20 | // Tests Suite 21 | suite('Kudu Sync Functional Tests', function () { 22 | var testBase = test; 23 | test = function (testName, testFunc) { 24 | // Ignore tests that are on the ignore tests map 25 | if (testTarget.ignoredTestsMap[testName] === true) { 26 | return; 27 | } 28 | 29 | testBase(testName, testFunc); 30 | }; 31 | 32 | test('Single file should be sync\'d', function (done) { 33 | var testedFiles = ["file1"]; 34 | runKuduSyncTestScenario(testedFiles, testedFiles, null, done); // Files to create, Files to expect 35 | }); 36 | 37 | test('Single file with digit for file name should be sync\'d', function (done) { 38 | var testedFiles = ["5"]; 39 | runKuduSyncTestScenario(testedFiles, testedFiles, null, done); // Files to create, Files to expect 40 | }); 41 | 42 | test('Several files should be sync\'d', function (done) { 43 | var testedFiles = ["file1", "file2", "file3"]; 44 | runKuduSyncTestScenario(testedFiles, testedFiles, null, done); 45 | }); 46 | 47 | test('Several files and sub-directories should be sync\'d', function (done) { 48 | var testedFiles = ["file1", "file2", "dir1/file3", "dir1/dir2/dir3/file4", "dir1/dir2/dir3/file5", "dir2/file6.txt"]; 49 | runKuduSyncTestScenario(testedFiles, testedFiles, null, done); 50 | }); 51 | 52 | test('Single file updated should be sync\'d', function (done) { 53 | runKuduSyncTestScenario(["file1.bin"], ["file1.bin"], null, function () { 54 | runKuduSyncTestScenario(["file1.bin"], ["file1.bin"], null, done); 55 | }); 56 | }); 57 | 58 | test('Several files updated should be sync\'d', function (done) { 59 | runKuduSyncTestScenario(["file1.bin", "file2", "dir1/file3", "dir1/dir2/dir3/file4"], ["file1.bin", "file2", "dir1/file3", "dir1/dir2/dir3/file4"], null, function () { 60 | runKuduSyncTestScenario(["file2", "dir1/file3", "dir1/dir2/dir3/file5", "dir2/file6.txt"], ["file1.bin", "file2", "dir1/file3", "dir1/dir2/dir3/file4", "dir1/dir2/dir3/file5", "dir2/file6.txt"], null, done); 61 | }); 62 | }); 63 | 64 | test('Single file created then removed should be sync\'d', function (done) { 65 | runKuduSyncTestScenario(["file1"], ["file1"], null, function () { 66 | runKuduSyncTestScenario(["-file1"], ["-file1"], null, done); 67 | }); 68 | }); 69 | 70 | test('Several files some created some removed should be sync\'d', function (done) { 71 | runKuduSyncTestScenario(["file1.bin", "file2", "dir1/file3", "dir1/dir2/dir3/file4"], ["file1.bin", "file2", "dir1/file3", "dir1/dir2/dir3/file4"], null, function () { 72 | runKuduSyncTestScenario(["file2", "-dir1/file3", "-dir1/dir2/dir3/file4"], ["file1.bin", "file2", "-dir1/file3", "-dir1/dir2/dir3/file4"], null, done); 73 | }); 74 | }); 75 | 76 | test('Single file created then file created only in destination, new file should remain', function (done) { 77 | runKuduSyncTestScenario(["file1"], ["file1"], null, function () { 78 | // Generating a file only in the destination directory, this shouldn't be removed 79 | generateToFile("tofile"); 80 | 81 | runKuduSyncTestScenario([], ["file1", "tofile"], null, done); 82 | }); 83 | }); 84 | 85 | test('Single file created then file created only in destination, new file should be removed with -x', function (done) { 86 | runKuduSyncTestScenario(["file1"], ["file1"], null, function (err) { 87 | if (err) { 88 | return done(err); 89 | } 90 | 91 | // Generating a file only in the destination directory, this should be removed with -x 92 | generateToFile("tofile"); 93 | 94 | runKuduSyncTestScenario([], ["file1", "-tofile"], null, done, false /* whatIf */, true /* ignoreManifest */); 95 | }); 96 | }); 97 | 98 | test('Directory should not be removed if not empty', function (done) { 99 | runKuduSyncTestScenario(["file1", "dir1/file2"], ["file1", "dir1/file2"], null, function () { 100 | // Generating a file only in the destination directory, this shouldn't be removed 101 | generateToFile("dir1/file3"); 102 | 103 | runKuduSyncTestScenario(["-dir1/file2", "-dir1"], ["file1", "-dir1/file2", "dir1/file3"], null, done); 104 | }); 105 | }); 106 | 107 | test('Deep directory should not be removed if not empty', function (done) { 108 | runKuduSyncTestScenario(["file1", "dir1/file2", "dir1/dir2/file3", "dir1/dir3/file4", "dir1/dir2/dir4/file5"], ["file1", "dir1/file2", "dir1/dir2/file3", "dir1/dir3/file4", "dir1/dir2/dir4/file5"], null, function () { 109 | // Generating a file only in the destination directory, this shouldn't be removed 110 | generateToFile("dir1/dir2/file6"); 111 | 112 | runKuduSyncTestScenario(["-dir1"], ["file1", "-dir1/file2", "-dir1/dir2/file3", "-dir1/dir3/file4", "-dir1/dir2/dir4/file5", "dir1/dir2/file6", "-dir1/dir3", "-dir1/dir2/dir4"], null, done); 113 | }); 114 | }); 115 | 116 | test('Several files created then file created only in destination, new file should remain', function (done) { 117 | runKuduSyncTestScenario(["file1", "dir1/file2"], ["file1", "dir1/file2"], null, function () { 118 | // Generating files only in the destination directory, those files shouldn't be removed 119 | generateToFile("dir1/dir2/tofile1"); 120 | generateToFile("dir1/dir2/tofile2"); 121 | 122 | runKuduSyncTestScenario(["-file1"], ["-file1", "dir1/file2", "dir1/dir2/tofile1", "dir1/dir2/tofile2"], null, done); 123 | }); 124 | }); 125 | 126 | test('Several files created then file created only in destination, new files should be removed with -x', function (done) { 127 | runKuduSyncTestScenario(["file1", "dir1/file2"], ["file1", "dir1/file2"], null, function () { 128 | // Generating files only in the destination directory, those files should be removed with -x 129 | generateToFile("dir1/dir2/tofile1"); 130 | generateToFile("dir1/dir2/tofile2"); 131 | 132 | runKuduSyncTestScenario(["-file1"], ["-file1", "dir1/file2", "-dir1/dir2/tofile1", "-dir1/dir2/tofile2"], null, done, false /* whatIf */, true /* ignoreManifest */); 133 | }); 134 | }); 135 | 136 | test('File created then removed (resulting in empty manifest) then added while target has an extra file which should stay', function (done) { 137 | runKuduSyncTestScenario(["file1"], ["file1"], null, function () { 138 | // Generating files only in the destination directory, those files shouldn't be removed 139 | generateToFile("tofile2"); 140 | 141 | runKuduSyncTestScenario(["-file1"], ["-file1", "tofile2"], null, function () { 142 | runKuduSyncTestScenario(["file1"], ["file1", "tofile2"], null, done); 143 | }); 144 | }); 145 | }); 146 | 147 | test('No previous manifest will not clean target directory', function (done) { 148 | runKuduSyncTestScenario(["file1"], ["file1"], null, function () { 149 | // Generating files only in the destination directory, those files shouldn't be removed 150 | generateToFile("tofile2"); 151 | 152 | runKuduSyncTestScenario(["-file1"], ["-file1", "tofile2"], null, function () { 153 | removeManifestFile(); 154 | runKuduSyncTestScenario(["file1"], ["file1", "tofile2"], null, done); 155 | }); 156 | }); 157 | }); 158 | 159 | test('Several files should not be sync\'d with whatIf flag set to true', function (done) { 160 | runKuduSyncTestScenario(["file1", "file2", "file3"], [], null, done, /*whatIf*/true); 161 | }); 162 | 163 | test('Several files and directories should not be sync\'d with whatIf flag set to true', function (done) { 164 | runKuduSyncTestScenario(["file1", "file2", "dir1/dir2/file3"], [], null, done, /*whatIf*/true); 165 | }); 166 | 167 | test('Ignore files (file2) should not copy them', function (done) { 168 | var testedFiles = ["file1", "file2", "file3"]; 169 | var ignore = "file2"; 170 | var expectedFiles = ["file1", "-file2", "file3"]; 171 | runKuduSyncTestScenario(testedFiles, expectedFiles, ignore, done); 172 | }); 173 | 174 | test('Ignore files (file*) should not copy them', function (done) { 175 | var testedFiles = ["file1", "file2", "file3", "bin/more/file4", "bin/more/gile5"]; 176 | var ignore = "file*"; 177 | var expectedFiles = ["-file1", "-file2", "-file3", "-bin/more/file4", "bin/more/gile5"]; 178 | runKuduSyncTestScenario(testedFiles, expectedFiles, ignore, done); 179 | }); 180 | 181 | test('Ignore files (bin/file*) should not copy them', function (done) { 182 | var testedFiles = ["file1", "bin/file2", "bin/gile3", "bin/more/file4"]; 183 | var ignore = "bin/file*"; 184 | var expectedFiles = ["file1", "-bin/file2", "bin/gile3", "bin/more/file4"]; 185 | runKuduSyncTestScenario(testedFiles, expectedFiles, ignore, done); 186 | }); 187 | 188 | test('Ignore files (bin/**) should not copy them', function (done) { 189 | var testedFiles = ["file1", "bin/file2", "bin/gile3", "bin/more/file4"]; 190 | var ignore = "bin/**"; 191 | var expectedFiles = ["file1", "-bin/file2", "-bin/gile3", "-bin/more/file4", "bin"]; 192 | runKuduSyncTestScenario(testedFiles, expectedFiles, ignore, done); 193 | }); 194 | 195 | test('Ignore files (bin) should not copy them', function (done) { 196 | var testedFiles = ["file1", "bin/file2", "bin/gile3", "bin/more/file4"]; 197 | var ignore = "bin"; 198 | var expectedFiles = ["file1", "-bin/file2", "-bin/gile3", "-bin/more/file4"]; 199 | runKuduSyncTestScenario(testedFiles, expectedFiles, ignore, done); 200 | }); 201 | 202 | test('Ignore files (file1) should not copy them', function (done) { 203 | var testedFiles = ["file1", "bin/file2", "bin/gile3", "bin/more/file4"]; 204 | var ignore = "file1"; 205 | var expectedFiles = ["-file1", "bin/file2", "bin/gile3", "bin/more/file4"]; 206 | runKuduSyncTestScenario(testedFiles, expectedFiles, ignore, done); 207 | }); 208 | 209 | test('Ignore files (file1;file2) should not copy them', function (done) { 210 | var testedFiles = ["file1", "bin/file2", "bin/file3", "bin/more/file4"]; 211 | var ignore = "file1;file2"; 212 | var expectedFiles = ["-file1", "-bin/file2", "bin/file3", "bin/more/file4"]; 213 | runKuduSyncTestScenario(testedFiles, expectedFiles, ignore, done); 214 | }); 215 | 216 | test('Ignore files (file1;bin/file3) should not copy them', function (done) { 217 | var testedFiles = ["file1", "bin/file2", "bin/file3", "bin/more/file4", "file5"]; 218 | var ignore = "file1;bin/file3"; 219 | var expectedFiles = ["-file1", "bin/file2", "-bin/file3", "bin/more/file4", "file5"]; 220 | runKuduSyncTestScenario(testedFiles, expectedFiles, ignore, done); 221 | }); 222 | 223 | test('Ignore files (file1;bin/*) should not copy them', function (done) { 224 | var testedFiles = ["file1", "bin/file2", "bin/file3", "bin/more/file4", "file5"]; 225 | var ignore = "file1;bin/*"; 226 | var expectedFiles = ["-file1", "-bin/file2", "-bin/file3", "-bin/more/file4", "file5"]; 227 | runKuduSyncTestScenario(testedFiles, expectedFiles, ignore, done); 228 | }); 229 | 230 | test('Ignore files added after first sync', function (done) { 231 | runKuduSyncTestScenario(["file1", "dir1/file2"], ["file1", "dir1/file2"], null, function () { 232 | var testedFiles = ["-file1", "file3", "file4"]; 233 | var ignore = "file3"; 234 | var expectedFiles = ["-file1", "dir1/file2", "-file3", "file4"]; 235 | runKuduSyncTestScenario(testedFiles, expectedFiles, ignore, done); 236 | }); 237 | }); 238 | 239 | test('Ignore files removed after first sync', function (done) { 240 | runKuduSyncTestScenario(["file1", "dir1/file2"], ["file1", "dir1/file2"], null, function () { 241 | var testedFiles = ["-file1", "file3", "file4"]; 242 | var ignore = "file1"; 243 | var expectedFiles = ["file1", "dir1/file2", "file3", "file4"]; 244 | runKuduSyncTestScenario(testedFiles, expectedFiles, ignore, done); 245 | }); 246 | }); 247 | 248 | test('Ignore directories added after first sync', function (done) { 249 | runKuduSyncTestScenario(["file1", "dir1/file2"], ["file1", "dir1/file2"], null, function () { 250 | var testedFiles = ["-file1", "dir1/file3", "file4"]; 251 | var ignore = "dir1"; 252 | var expectedFiles = ["-file1", "dir1/file2", "-dir1/file3", "file4"]; 253 | runKuduSyncTestScenario(testedFiles, expectedFiles, ignore, done); 254 | }); 255 | }); 256 | 257 | test('Ignore directories removed after first sync', function (done) { 258 | runKuduSyncTestScenario(["file1", "dir1/file2"], ["file1", "dir1/file2"], null, function () { 259 | var testedFiles = ["-file1", "-dir1/file2", "dir1/file3", "file4"]; 260 | var ignore = "dir1"; 261 | var expectedFiles = ["-file1", "dir1/file2", "-dir1/file3", "file4"]; 262 | runKuduSyncTestScenario(testedFiles, expectedFiles, ignore, done); 263 | }); 264 | }); 265 | 266 | test('Clean before sync when it\'s the first sync (manifest is empty)', function (done) { 267 | generateToFile("dir1/dir2/tofile3"); 268 | var testedFiles = ["file4", "file5", "file6"]; 269 | var expectedFiles = ["file4", "file5", "file6", "dir1/dir2/tofile3"]; 270 | runKuduSyncTestScenario(testedFiles, expectedFiles, null, done); 271 | }); 272 | 273 | test('From directory doesn\'t exist should fail with an error', function (done) { 274 | var from = pathUtil.join(baseTestTempDir, testDir, 'doesntexist'); 275 | var to = pathUtil.join(baseTestTempDir, testDir, 'to'); 276 | 277 | runKuduSyncTestExpectError(from, to, done); 278 | }); 279 | 280 | test('KuduSync should fail when target is subdirectory of source', function (done) { 281 | var from = pathUtil.join(baseTestTempDir, testDir, fromDir); 282 | var to = pathUtil.join(from, 'subdirectory'); 283 | 284 | ensurePathExists(from); 285 | ensurePathExists(to); 286 | 287 | runKuduSyncTestExpectError(from, to, done); 288 | }); 289 | 290 | test('KuduSync should fail when source is subdirectory of target', function (done) { 291 | var to = pathUtil.join(baseTestTempDir, testDir, fromDir); 292 | var from = pathUtil.join(to, 'subdirectory'); 293 | 294 | ensurePathExists(to); 295 | ensurePathExists(from); 296 | 297 | runKuduSyncTestExpectError(from, to, done); 298 | }); 299 | 300 | setup(function () { 301 | // Setting a different test directory per test. 302 | incrementTestDir(); 303 | removePath(testDir); 304 | console.log(); 305 | }); 306 | 307 | teardown(function () { 308 | // Cleaning up after each test 309 | removePath(baseTestTempDir); 310 | }); 311 | }); 312 | 313 | // The scenario: 314 | // 1. Create/update or remove files from updatedFiles on the source path 315 | // 2. Run the kudu sync function 316 | // 3. Verify expectedFiles exist (or not exist) in the destination path 317 | function runKuduSyncTestScenario(updatedFiles, expectedFiles, ignore, callback, whatIf, ignoreManifest) { 318 | generateFromFiles(updatedFiles); 319 | 320 | runKuduSync("manifest1", "manifest1", ignoreManifest, ignore, whatIf, function (err) { 321 | if (err) { 322 | callback(err); 323 | return; 324 | } 325 | 326 | // Small timeout to make sure files were persisted in file system. 327 | setTimeout(function () { 328 | try { 329 | testFilesShouldBeEqual(expectedFiles); 330 | callback(); 331 | } 332 | catch (err) { 333 | callback(err); 334 | } 335 | }, 100); 336 | }); 337 | } 338 | 339 | function runKuduSyncTestExpectError(from, to, callback) { 340 | var prevManifestPath = pathUtil.join(baseTestTempDir, testDir, 'manifest'); 341 | var nextManifestPath = pathUtil.join(baseTestTempDir, testDir, 'manifest'); 342 | 343 | var command = testTarget.cmd + " -f " + from + " -t " + to + " -n " + nextManifestPath + " -p " + prevManifestPath; 344 | console.log("command: " + command); 345 | 346 | var child = exec(command, 347 | function (error, stdout, stderr) { 348 | if (stdout !== '') { 349 | console.log('---------stdout: ---------\n' + stdout); 350 | } 351 | if (stderr !== '') { 352 | console.log('---------stderr: ---------\n' + stderr); 353 | } 354 | if (error !== null) { 355 | console.log('---------exec error: ---------\n[' + error + ']'); 356 | } 357 | should.exist(error); 358 | callback(); 359 | }); 360 | 361 | child.stderr.pipe(process.stderr); 362 | } 363 | 364 | function removeManifestFile() { 365 | var manifestPath = pathUtil.join(baseTestTempDir, testDir, "manifest1"); 366 | tryRemoveFile(manifestPath); 367 | } 368 | 369 | function incrementTestDir() { 370 | testDirIndex++; 371 | testDir = testDirBase + testDirIndex; 372 | } 373 | 374 | function testFilesShouldBeEqual(files) { 375 | for (var index in files) { 376 | var file = files[index]; 377 | 378 | // Find whether to verify file is there or not 379 | if (file.indexOf("-") == 0) { 380 | file = file.substring(1); 381 | var fileFullPath = pathUtil.join(baseTestTempDir, testDir, toDir, file); 382 | fs.existsSync(fileFullPath).should.equal(false, "File exists: " + fileFullPath); 383 | } 384 | else { 385 | testFileShouldBeEqual(file); 386 | } 387 | } 388 | } 389 | 390 | function testFileShouldBeEqual(file) { 391 | var from = pathUtil.join(baseTestTempDir, testDir, fromDir); 392 | var to = pathUtil.join(baseTestTempDir, testDir, toDir); 393 | 394 | filesShouldBeEqual(from, to, file); 395 | } 396 | 397 | function runKuduSync(prevManifestFile, nextManifestFile, ignoreManifest, ignore, whatIf, callback) { 398 | var from = pathUtil.join(baseTestTempDir, testDir, fromDir); 399 | var to = pathUtil.join(baseTestTempDir, testDir, toDir); 400 | var prevManifestPath = pathUtil.join(baseTestTempDir, testDir, prevManifestFile); 401 | var nextManifestPath = pathUtil.join(baseTestTempDir, testDir, nextManifestFile); 402 | 403 | var command = testTarget.cmd + " -f " + from + " -t " + to + " -n " + nextManifestPath + " -p " + prevManifestPath; 404 | 405 | if (ignoreManifest) { 406 | command += " -x"; 407 | } 408 | 409 | if (ignore) { 410 | command += " -i \"" + ignore + "\""; 411 | } 412 | 413 | if (whatIf) { 414 | command += " -w"; 415 | } 416 | 417 | console.log("command: " + command); 418 | exec(command, 419 | function (error, stdout, stderr) { 420 | if (stdout !== '') { 421 | console.log('---------stdout: ---------\n' + stdout); 422 | } 423 | if (stderr !== '') { 424 | console.log('---------stderr: ---------\n' + stderr); 425 | } 426 | if (error !== null) { 427 | console.log('---------exec error: ---------\n[' + error + ']'); 428 | } 429 | callback(error); 430 | }); 431 | } 432 | 433 | function generateFromFiles(files) { 434 | for (var index in files) { 435 | var file = files[index]; 436 | // to find casing issues on windows 437 | file = /^win/.test(process.platform) ? file.toUpperCase() : file; 438 | generateFromFile(file); 439 | } 440 | } 441 | 442 | function generateFromFile(fileName) { 443 | var isRemove = false; 444 | // Find whether to create/update or remove file 445 | if (fileName.indexOf("-") == 0) { 446 | fileName = fileName.substring(1); 447 | isRemove = true; 448 | } 449 | 450 | var filePath = pathUtil.join(baseTestTempDir, testDir, fromDir, fileName); 451 | 452 | if (isRemove) { 453 | removePath(filePath); 454 | return ""; 455 | } 456 | else { 457 | return generateFile(filePath); 458 | } 459 | } 460 | 461 | function generateToFile(fileName) { 462 | var filePath = pathUtil.join(baseTestTempDir, testDir, toDir, fileName); 463 | return generateFile(filePath); 464 | } 465 | 466 | function filesShouldBeEqual(fromPath, toPath, fileName) { 467 | fromPath = pathUtil.join(fromPath, fileName); 468 | toPath = pathUtil.join(toPath, fileName); 469 | 470 | // Only validate content if the from file exists 471 | var expectedContent = null; 472 | if (fs.existsSync(fromPath)) { 473 | var stat = tryGetFileStat(fromPath); 474 | if (!stat.isDirectory()) { 475 | expectedContent = fs.readFileSync(fromPath, 'utf8'); 476 | } 477 | } 478 | 479 | fileShouldExist(toPath, expectedContent); 480 | } 481 | 482 | function fileShouldExist(path, expectedContent) { 483 | fs.existsSync(path).should.equal(true, "File doesn't exist: " + path); 484 | 485 | // Validate content if received it 486 | if (expectedContent != null) { 487 | var content = fs.readFileSync(path, 'utf8'); 488 | expectedContent.should.equal(content); 489 | } 490 | } 491 | 492 | function generateFile(path, content) { 493 | if (content == null) { 494 | content = randomString(); 495 | } 496 | ensurePathExists(pathUtil.dirname(path)); 497 | fs.writeFileSync(path, content, 'utf8'); 498 | return content; 499 | } 500 | 501 | function removePath(path) { 502 | var stat = tryGetFileStat(path); 503 | if (stat) { 504 | if (!stat.isDirectory()) { 505 | tryRemoveFile(path); 506 | } 507 | else { 508 | var files = fs.readdirSync(path); 509 | for (var index in files) { 510 | var file = files[index]; 511 | var filePath = pathUtil.join(path, file); 512 | removePath(filePath); 513 | } 514 | 515 | tryRemoveDir(path); 516 | } 517 | } 518 | } 519 | 520 | function tryGetFileStat(path) { 521 | try { 522 | return fs.statSync(path); 523 | } 524 | catch (e) { 525 | if (e.errno == 34 || e.errno == -4058) { 526 | // Return null if path doesn't exist 527 | return null; 528 | } 529 | 530 | throw e; 531 | } 532 | } 533 | 534 | function tryRemoveFile(path) { 535 | try { 536 | fs.unlinkSync(path); 537 | } 538 | catch (e) { 539 | console.log(e); 540 | } 541 | } 542 | 543 | function tryRemoveDir(path) { 544 | try { 545 | fs.rmdirSync(path); 546 | } 547 | catch (e) { 548 | } 549 | } 550 | 551 | function ensurePathExists(path) { 552 | if (!fs.existsSync(path)) { 553 | ensurePathExists(pathUtil.dirname(path)); 554 | fs.mkdirSync(path); 555 | } 556 | } 557 | 558 | // Create a random string, more chance for /n and space. 559 | function randomString() { 560 | var length = Math.floor(Math.random() * 1024) + 100; 561 | 562 | var text = ""; 563 | var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZ \n abcdefghijklmnopqrstuvwxyz \n 0123456789 \n \t"; 564 | 565 | for (var i = 0; i < length; i++) 566 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 567 | 568 | return text; 569 | } 570 | -------------------------------------------------------------------------------- /test/testTarget.js: -------------------------------------------------------------------------------- 1 | var pathUtil = require("path"); 2 | 3 | var cmd = "node " + pathUtil.join(__dirname, "..", "bin", "kudusync"); 4 | var ignoredTestsMap = {}; 5 | 6 | exports.cmd = cmd; 7 | exports.ignoredTestsMap = ignoredTestsMap; 8 | -------------------------------------------------------------------------------- /typings/commander.d.ts: -------------------------------------------------------------------------------- 1 | declare interface Commander { 2 | prompt(msg: string, fn: Function); 3 | password(msg: string, fn: Function); 4 | confirm(msg: string, fn: Function); 5 | choose(list, fn: Function); 6 | version(versionStr: string): Commander; 7 | option(args: string, msg: string, fn?: Function): Commander; 8 | parse(argv: string[]); 9 | usage(msg: string); 10 | help(); 11 | } 12 | -------------------------------------------------------------------------------- /typings/minimatch.d.ts: -------------------------------------------------------------------------------- 1 | declare function minimatch(path: string, pattern: string, options: any); 2 | -------------------------------------------------------------------------------- /typings/node.d.ts: -------------------------------------------------------------------------------- 1 | /************************************************ 2 | * * 3 | * Node.js v0.8.8 API * 4 | * * 5 | ************************************************/ 6 | 7 | /************************************************ 8 | * * 9 | * GLOBAL * 10 | * * 11 | ************************************************/ 12 | declare var process: NodeProcess; 13 | declare var global: any; 14 | 15 | declare var console: { 16 | log(...data: any[]): void; 17 | info(...data: any[]): void; 18 | error(...data: any[]): void; 19 | warn(...data: any[]): void; 20 | dir(obj: any): void; 21 | timeEnd(label: string): void; 22 | trace(label: string): void; 23 | assert(expression: any, ...message: string[]): void; 24 | } 25 | 26 | declare var __filename: string; 27 | declare var __dirname: string; 28 | 29 | declare function setTimeout(callback: () => void , ms: number): any; 30 | declare function clearTimeout(timeoutId: any); 31 | declare function setInterval(callback: () => void , ms: number): any; 32 | declare function clearInterval(intervalId: any); 33 | 34 | declare var require: { 35 | (id: string): any; 36 | resolve(): string; 37 | cache: any; 38 | extensions: any; 39 | } 40 | 41 | declare var module: { 42 | exports: any; 43 | require(id: string): any; 44 | id: string; 45 | filename: string; 46 | loaded: bool; 47 | parent: any; 48 | children: any[]; 49 | } 50 | 51 | // Same as module.exports 52 | declare var exports: any; 53 | declare var SlowBuffer: { 54 | new (str: string, encoding?: string): NodeBuffer; 55 | new (size: number): NodeBuffer; 56 | new (array: any[]): NodeBuffer; 57 | prototype: NodeBuffer; 58 | isBuffer(obj: any): bool; 59 | byteLength(string: string, encoding?: string): number; 60 | concat(list: NodeBuffer[], totalLength?: number): NodeBuffer; 61 | }; 62 | declare var Buffer: { 63 | new (str: string, encoding?: string): NodeBuffer; 64 | new (size: number): NodeBuffer; 65 | new (array: any[]): NodeBuffer; 66 | prototype: NodeBuffer; 67 | isBuffer(obj: any): bool; 68 | byteLength(string: string, encoding?: string): number; 69 | concat(list: NodeBuffer[], totalLength?: number): NodeBuffer; 70 | } 71 | 72 | /************************************************ 73 | * * 74 | * INTERFACES * 75 | * * 76 | ************************************************/ 77 | 78 | interface FileStat { 79 | dev: number; 80 | mode: number; 81 | nlink: number; 82 | uid: number; 83 | gid: number; 84 | rdev: number; 85 | ino: number; 86 | size: number; 87 | atime: Date; 88 | mtime: Date; 89 | ctime: Date; 90 | } 91 | 92 | interface EventEmitter { 93 | addListener(event: string, listener: Function); 94 | on(event: string, listener: Function); 95 | once(event: string, listener: Function): void; 96 | removeListener(event: string, listener: Function): void; 97 | removeAllListener(event: string): void; 98 | setMaxListeners(n: number): void; 99 | listeners(event: string): { Function; }[]; 100 | emit(event: string, arg1?: any, arg2?: any): void; 101 | } 102 | 103 | interface WritableStream extends EventEmitter { 104 | writable: bool; 105 | write(str: string, encoding?: string, fd?: string): bool; 106 | write(buffer: NodeBuffer): bool; 107 | end(): void; 108 | end(str: string, enconding: string): void; 109 | end(buffer: NodeBuffer): void; 110 | destroy(): void; 111 | destroySoon(): void; 112 | } 113 | 114 | interface ReadableStream extends EventEmitter { 115 | readable: bool; 116 | setEncoding(encoding: string): void; 117 | pause(): void; 118 | resume(): void; 119 | destroy(): void; 120 | pipe(destination: WritableStream, options?: { end?: bool; }): void; 121 | } 122 | 123 | interface NodeProcess extends EventEmitter { 124 | stdout: WritableStream; 125 | stderr: WritableStream; 126 | stdin: ReadableStream; 127 | argv: string[]; 128 | execPath: string; 129 | abort(): void; 130 | chdir(directory: string): void; 131 | cwd(): void; 132 | env: any; 133 | exit(code?: number): void; 134 | getgid(): number; 135 | setgid(id: number): void; 136 | getuid(): number; 137 | setuid(id: number): void; 138 | version: string; 139 | versions: { http_parser: string; node: string; v8: string; ares: string; uv: string; zlib: string; openssl: string; }; 140 | config: { 141 | target_defaults: { 142 | cflags: any[]; 143 | default_configuration: string; 144 | defines: string[]; 145 | include_dirs: string[]; 146 | libraries: string[]; 147 | }; 148 | variables: { 149 | clang: number; 150 | host_arch: string; 151 | node_install_npm: bool; 152 | node_install_waf: bool; 153 | node_prefix: string; 154 | node_shared_openssl: bool; 155 | node_shared_v8: bool; 156 | node_shared_zlib: bool; 157 | node_use_dtrace: bool; 158 | node_use_etw: bool; 159 | node_use_openssl: bool; 160 | target_arch: string; 161 | v8_no_strict_aliasing: number; 162 | v8_use_snapshot: bool; 163 | visibility: string; 164 | }; 165 | }; 166 | kill(pid: number, signal?: string): void; 167 | pid: number; 168 | title: string; 169 | arch: string; 170 | platform: string; 171 | memoryUsage(): { rss: number; heapTotal; number; heapUsed: number; }; 172 | nextTick(callback: Function): void; 173 | umask(mask?: number): number; 174 | uptime(): number; 175 | hrtime(): number[]; 176 | } 177 | 178 | // Buffer class 179 | interface NodeBuffer { 180 | [index: number]: number; 181 | write(string: string, offset?: number, length?: number, encoding?: string): number; 182 | toString(encoding: string, start: number, end: number): string; 183 | length: number; 184 | copy(targetBuffer: NodeBuffer, targetStart?: number, sourceStart?: number, sourceEnd?: number): void; 185 | slice(start?: number, end?: number): NodeBuffer; 186 | readUInt8(offset: number, noAsset?: bool): number; 187 | readUInt16LE(offset: number, noAssert?: bool): number; 188 | readUInt16BE(offset: number, noAssert?: bool): number; 189 | readUInt32LE(offset: number, noAssert?: bool): number; 190 | readUInt32BE(offset: number, noAssert?: bool): number; 191 | readInt8(offset: number, noAssert?: bool): number; 192 | readInt16LE(offset: number, noAssert?: bool): number; 193 | readInt16BE(offset: number, noAssert?: bool): number; 194 | readInt32LE(offset: number, noAssert?: bool): number; 195 | readInt32BE(offset: number, noAssert?: bool): number; 196 | readFloatLE(offset: number, noAssert?: bool): number; 197 | readFloatBE(offset: number, noAssert?: bool): number; 198 | readDoubleLE(offset: number, noAssert?: bool): number; 199 | readDoubleBE(offset: number, noAssert?: bool): number; 200 | writeUInt8(value: number, offset: number, noAssert?: bool): void; 201 | writeUInt16LE(value: number, offset: number, noAssert?: bool): void; 202 | writeUInt16BE(value: number, offset: number, noAssert?: bool): void; 203 | writeUInt32LE(value: number, offset: number, noAssert?: bool): void; 204 | writeUInt32BE(value: number, offset: number, noAssert?: bool): void; 205 | writeInt8(value: number, offset: number, noAssert?: bool): void; 206 | writeInt16LE(value: number, offset: number, noAssert?: bool): void; 207 | writeInt16BE(value: number, offset: number, noAssert?: bool): void; 208 | writeInt32LE(value: number, offset: number, noAssert?: bool): void; 209 | writeInt32BE(value: number, offset: number, noAssert?: bool): void; 210 | writeFloatLE(value: number, offset: number, noAssert?: bool): void; 211 | writeFloatBE(value: number, offset: number, noAssert?: bool): void; 212 | writeDoubleLE(value: number, offset: number, noAssert?: bool): void; 213 | writeDoubleBE(value: number, offset: number, noAssert?: bool): void; 214 | fill(value: any, offset?: number, end?: number): void; 215 | INSPECT_MAX_BYTES: number; 216 | } 217 | 218 | /************************************************ 219 | * * 220 | * MODULES * 221 | * * 222 | ************************************************/ 223 | declare module "querystring" { 224 | export function stringify(obj: any, sep?: string, eq?: string): string; 225 | export function parse(str: string, sep?: string, eq?: string, options?: { maxKeys?: number; }): any; 226 | export function escape(): any; 227 | export function unescape(): any; 228 | } 229 | 230 | declare module "events" { 231 | export interface NodeEventEmitter { 232 | addListener(event: string, listener: Function); 233 | on(event: string, listener: Function): any; 234 | once(event: string, listener: Function): void; 235 | removeListener(event: string, listener: Function): void; 236 | removeAllListener(event: string): void; 237 | setMaxListeners(n: number): void; 238 | listeners(event: string): { Function; }[]; 239 | emit(event: string, arg1?: any, arg2?: any): void; 240 | } 241 | 242 | export var EventEmitter: NodeEventEmitter; 243 | } 244 | 245 | declare module "http" { 246 | import events = module("events"); 247 | import net = module("net"); 248 | import stream = module("stream"); 249 | 250 | export interface Server extends events.NodeEventEmitter { 251 | listen(port: number, hostname?: string, backlog?: number, callback?: Function): void; 252 | listen(path: string, callback?: Function): void; 253 | listen(handle: any, listeningListener?: Function): void; 254 | close(cb?: any): void; 255 | maxHeadersCount: number; 256 | } 257 | export interface ServerRequest extends events.NodeEventEmitter, stream.ReadableStream { 258 | method: string; 259 | url: string; 260 | headers: string; 261 | trailers: string; 262 | httpVersion: string; 263 | setEncoding(encoding?: string): void; 264 | pause(): void; 265 | resume(): void; 266 | connection: net.NodeSocket; 267 | } 268 | export interface ServerResponse extends events.NodeEventEmitter, stream.WritableStream { 269 | // Extended base methods 270 | write(str: string, encoding?: string, fd?: string): bool; 271 | write(buffer: NodeBuffer): bool; 272 | 273 | writeContinue(): void; 274 | writeHead(statusCode: number, reasonPhrase?: string, headers?: any): void; 275 | writeHead(statusCode: number, headers?: any): void; 276 | statusCode: number; 277 | setHeader(name: string, value: string): void; 278 | sendDate: bool; 279 | getHeader(name: string): string; 280 | removeHeader(name: string): void; 281 | write(chunk: any, encoding?: string): any; 282 | addTrailers(headers: any): void; 283 | end(data?: any, encoding?: string): void; 284 | } 285 | export interface ClientRequest extends events.NodeEventEmitter, stream.WritableStream { 286 | // Extended base methods 287 | write(str: string, encoding?: string, fd?: string): bool; 288 | write(buffer: NodeBuffer): bool; 289 | 290 | write(chunk: any, encoding?: string): void; 291 | end(data?: any, encoding?: string): void; 292 | abort(): void; 293 | setTimeout(timeout: number, callback?: Function): void; 294 | setNoDelay(noDelay?: Function): void; 295 | setSocketKeepAlive(enable?: bool, initialDelay?: number): void; 296 | } 297 | export interface ClientResponse extends events.NodeEventEmitter, stream.ReadableStream { 298 | statusCode: number; 299 | httpVersion: string; 300 | headers: any; 301 | trailers: any; 302 | setEncoding(encoding?: string): void; 303 | pause(): void; 304 | resume(): void; 305 | } 306 | export interface Agent { maxSockets: number; sockets: any; requests: any; } 307 | 308 | export var STATUS_CODES; 309 | export function createServer(requestListener?: (request: ServerRequest, response: ServerResponse) =>void ): Server; 310 | export function createClient(port?: number, host?: string): any; 311 | export function request(options: any, callback?: Function): ClientRequest; 312 | export function get(options: any, callback?: Function): ClientRequest; 313 | export var globalAgent: Agent; 314 | } 315 | 316 | declare module "cluster" { 317 | import child_process = module("child_process"); 318 | 319 | export interface ClusterSettings { 320 | exec: string; 321 | args: string[]; 322 | silent: bool; 323 | } 324 | export interface Worker { 325 | id: string; 326 | process: child_process; 327 | suicide: bool; 328 | send(message: any, sendHandle?: any): void; 329 | destroy(): void; 330 | disconnect(): void; 331 | } 332 | 333 | 334 | export var settings: ClusterSettings; 335 | export var isMaster: bool; 336 | export var isWorker: bool; 337 | export function setupMaster(settings?: ClusterSettings): void; 338 | export function fork(env?: any): Worker; 339 | export function disconnect(callback?: Function): void; 340 | export var workers: any; 341 | 342 | // Event emitter 343 | export function addListener(event: string, listener: Function): void; 344 | export function on(event: string, listener: Function): any; 345 | export function once(event: string, listener: Function): void; 346 | export function removeListener(event: string, listener: Function): void; 347 | export function removeAllListener(event: string): void; 348 | export function setMaxListeners(n: number): void; 349 | export function listeners(event: string): { Function; }[]; 350 | export function emit(event: string, arg1?: any, arg2?: any): void; 351 | } 352 | 353 | declare module "zlib" { 354 | import stream = module("stream"); 355 | export interface ZlibOptions { chunkSize?: number; windowBits?: number; level?: number; memLevel?: number; strategy?: number; dictionary?: any; } 356 | 357 | export interface Gzip extends stream.ReadWriteStream { } 358 | export interface Gunzip extends stream.ReadWriteStream { } 359 | export interface Deflate extends stream.ReadWriteStream { } 360 | export interface Inflate extends stream.ReadWriteStream { } 361 | export interface DeflateRaw extends stream.ReadWriteStream { } 362 | export interface InflateRaw extends stream.ReadWriteStream { } 363 | export interface Unzip extends stream.ReadWriteStream { } 364 | 365 | export function createGzip(options: ZlibOptions): Gzip; 366 | export function createGunzip(options: ZlibOptions): Gunzip; 367 | export function createDeflate(options: ZlibOptions): Deflate; 368 | export function createInflate(options: ZlibOptions): Inflate; 369 | export function createDeflateRaw(options: ZlibOptions): DeflateRaw; 370 | export function createInflateRaw(options: ZlibOptions): InflateRaw; 371 | export function createUnzip(options: ZlibOptions): Unzip; 372 | 373 | export function deflate(buf: NodeBuffer, callback: (error: Error, result) =>void ): void; 374 | export function deflateRaw(buf: NodeBuffer, callback: (error: Error, result) =>void ): void; 375 | export function gzip(buf: NodeBuffer, callback: (error: Error, result) =>void ): void; 376 | export function gunzip(buf: NodeBuffer, callback: (error: Error, result) =>void ): void; 377 | export function inflate(buf: NodeBuffer, callback: (error: Error, result) =>void ): void; 378 | export function inflateRaw(buf: NodeBuffer, callback: (error: Error, result) =>void ): void; 379 | export function unzip(buf: NodeBuffer, callback: (error: Error, result) =>void ): void; 380 | 381 | // Constants 382 | export var Z_NO_FLUSH: number; 383 | export var Z_PARTIAL_FLUSH: number; 384 | export var Z_SYNC_FLUSH: number; 385 | export var Z_FULL_FLUSH: number; 386 | export var Z_FINISH: number; 387 | export var Z_BLOCK: number; 388 | export var Z_TREES: number; 389 | export var Z_OK: number; 390 | export var Z_STREAM_END: number; 391 | export var Z_NEED_DICT: number; 392 | export var Z_ERRNO: number; 393 | export var Z_STREAM_ERROR: number; 394 | export var Z_DATA_ERROR: number; 395 | export var Z_MEM_ERROR: number; 396 | export var Z_BUF_ERROR: number; 397 | export var Z_VERSION_ERROR: number; 398 | export var Z_NO_COMPRESSION: number; 399 | export var Z_BEST_SPEED: number; 400 | export var Z_BEST_COMPRESSION: number; 401 | export var Z_DEFAULT_COMPRESSION: number; 402 | export var Z_FILTERED: number; 403 | export var Z_HUFFMAN_ONLY: number; 404 | export var Z_RLE: number; 405 | export var Z_FIXED: number; 406 | export var Z_DEFAULT_STRATEGY: number; 407 | export var Z_BINARY: number; 408 | export var Z_TEXT: number; 409 | export var Z_ASCII: number; 410 | export var Z_UNKNOWN: number; 411 | export var Z_DEFLATED: number; 412 | export var Z_NULL: number; 413 | } 414 | 415 | declare module "os" { 416 | export function tmpDir(): string; 417 | export function hostname(): string; 418 | export function type(): string; 419 | export function platform(): string; 420 | export function arch(): string; 421 | export function release(): string; 422 | export function uptime(): number; 423 | export function loadavg(): number[]; 424 | export function totalmem(): number; 425 | export function freemem(): number; 426 | export function cpus(): { model: string; speed: number; times: { user: number; nice: number; sys: number; idle: number; irq: number; }; }[]; 427 | export function networkInterfaces(): any; 428 | export var EOL: string; 429 | } 430 | 431 | declare module "https" { 432 | import tls = module("tls"); 433 | import events = module("events"); 434 | import http = module("http"); 435 | 436 | export interface ServerOptions { 437 | pfx?: any; 438 | key?: any; 439 | passphrase?: string; 440 | cert?: any; 441 | ca?: any; 442 | crl?: any; 443 | ciphers?: string; 444 | honorCipherOrder?: bool; 445 | requestCert?: bool; 446 | rejectUnauthorized?: bool; 447 | NPNProtocols?: any; 448 | SNICallback?: (servername: string) => any; 449 | } 450 | 451 | export interface RequestOptions { 452 | host?: string; 453 | hostname?: string; 454 | port?: number; 455 | path?: string; 456 | method?: string; 457 | headers?: any; 458 | auth?: string; 459 | agent?: any; 460 | pfx?: any; 461 | key?: any; 462 | passphrase?: string; 463 | cert?: any; 464 | ca?: any; 465 | ciphers?: string; 466 | rejectUnauthorized?: bool; 467 | } 468 | 469 | export interface NodeAgent { 470 | maxSockets: number; 471 | sockets: any; 472 | requests: any; 473 | } 474 | export var Agent: { 475 | new (options?: RequestOptions): NodeAgent; 476 | }; 477 | export interface Server extends tls.Server { } 478 | export function createServer(options: ServerOptions, requestListener?: Function): Server; 479 | export function request(options: RequestOptions, callback?: (res: events.NodeEventEmitter) =>void ): http.ClientRequest; 480 | export function get(options: RequestOptions, callback?: (res: events.NodeEventEmitter) =>void ): http.ClientRequest; 481 | export var globalAgent: NodeAgent; 482 | } 483 | 484 | declare module "punycode" { 485 | export function decode(string: string): string; 486 | export function encode(string: string): string; 487 | export function toUnicode(domain: string): string; 488 | export function toASCII(domain: string): string; 489 | export var ucs2: ucs2; 490 | interface ucs2 { 491 | decode(string: string): string; 492 | encode(codePoints: number[]): string; 493 | } 494 | export var version; 495 | } 496 | 497 | declare module "repl" { 498 | import stream = module("stream"); 499 | import events = module("events"); 500 | 501 | export interface ReplOptions { 502 | prompt?: string; 503 | input?: stream.ReadableStream; 504 | output?: stream.WritableStream; 505 | terminal?: bool; 506 | eval?: Function; 507 | useColors?: bool; 508 | useGlobal?: bool; 509 | ignoreUndefined?: bool; 510 | writer?: Function; 511 | } 512 | export function start(options: ReplOptions): events.NodeEventEmitter; 513 | } 514 | 515 | declare module "readline" { 516 | import events = module("events"); 517 | import stream = module("stream"); 518 | 519 | export interface ReadLine extends events.NodeEventEmitter { 520 | setPrompt(prompt: string, length: number): void; 521 | prompt(preserveCursor?: bool): void; 522 | question(query: string, callback: Function): void; 523 | pause(): void; 524 | resume(): void; 525 | close(): void; 526 | write(data: any, key?: any): void; 527 | } 528 | export interface ReadLineOptions { 529 | input: stream.ReadableStream; 530 | output: stream.WritableStream; 531 | completer?: Function; 532 | terminal?: bool; 533 | } 534 | export function createInterface(options: ReadLineOptions): ReadLine; 535 | } 536 | 537 | declare module "vm" { 538 | export interface Context { } 539 | export interface Script { 540 | runInThisContext(): void; 541 | runInNewContext(sandbox?: Context): void; 542 | } 543 | export function runInThisContext(code: string, filename?: string): void; 544 | export function runInNewContext(code: string, sandbox?: Context, filename?: string): void; 545 | export function runInContext(code: string, context: Context, filename?: string): void; 546 | export function createContext(initSandbox?: Context): Context; 547 | export function createScript(code: string, filename?: string): Script; 548 | } 549 | 550 | declare module "child_process" { 551 | import events = module("events"); 552 | import stream = module("stream"); 553 | 554 | export interface ChildProcess extends events.NodeEventEmitter { 555 | stdin: stream.WritableStream; 556 | stdout: stream.ReadableStream; 557 | stderr: stream.ReadableStream; 558 | pid: number; 559 | kill(signal?: string): void; 560 | send(message: any, sendHandle: any): void; 561 | disconnect(): void; 562 | } 563 | 564 | export function spawn(command: string, args?: string[], options?: { 565 | cwd?: string; 566 | stdio?: any; 567 | custom?: any; 568 | env?: any; 569 | detached?: bool; 570 | }): ChildProcess; 571 | export function exec(command: string, options: { 572 | cwd?: string; 573 | stdio?: any; 574 | customFds?: any; 575 | env?: any; 576 | encoding?: string; 577 | timeout?: number; 578 | maxBuffer?: number; 579 | killSignal?: string; 580 | }, callback: (error: Error, stdout: NodeBuffer, stderr: NodeBuffer) =>void ): ChildProcess; 581 | export function exec(command: string, callback: (error: Error, stdout: NodeBuffer, stderr: NodeBuffer) =>void ): ChildProcess; 582 | export function execFile(file: string, args: string[], options: { 583 | cwd?: string; 584 | stdio?: any; 585 | customFds?: any; 586 | env?: any; 587 | encoding?: string; 588 | timeout?: number; 589 | maxBuffer?: string; 590 | killSignal?: string; 591 | }, callback: (error: Error, stdout: NodeBuffer, stderr: NodeBuffer) =>void ): ChildProcess; 592 | export function fork(modulePath: string, args?: string[], options?: { 593 | cwd?: string; 594 | env?: any; 595 | encoding?: string; 596 | }): ChildProcess; 597 | } 598 | 599 | declare module "url" { 600 | export interface Url { 601 | href: string; 602 | protocol: string; 603 | auth: string; 604 | hostname: string; 605 | port: string; 606 | host: string; 607 | pathname: string; 608 | search: string; 609 | query: string; 610 | slashes: bool; 611 | } 612 | 613 | export function parse(urlStr: string, parseQueryString? , slashesDenoteHost? ): Url; 614 | export function format(url: Url): string; 615 | export function resolve(from: string, to: string): string; 616 | } 617 | 618 | declare module "dns" { 619 | export function lookup(domain: string, family: number, callback: (err: Error, address: string, family: number) =>void ): string; 620 | export function lookup(domain: string, callback: (err: Error, address: string, family: number) =>void ): string; 621 | export function resolve(domain: string, rrtype: string, callback: (err: Error, addresses: string[]) =>void ): string[]; 622 | export function resolve(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; 623 | export function resolve4(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; 624 | export function resolve6(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; 625 | export function resolveMx(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; 626 | export function resolveTxt(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; 627 | export function resolveSrv(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; 628 | export function resolveNs(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; 629 | export function resolveCname(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; 630 | export function reverse(ip: string, callback: (err: Error, domains: string[]) =>void ): string[]; 631 | } 632 | 633 | declare module "net" { 634 | import stream = module("stream"); 635 | 636 | export interface NodeSocket extends stream.ReadWriteStream { 637 | // Extended base methods 638 | write(str: string, encoding?: string, fd?: string): bool; 639 | write(buffer: NodeBuffer): bool; 640 | 641 | connect(port: number, host?: string, connectionListener?: Function): void; 642 | connect(path: string, connectionListener?: Function): void; 643 | bufferSize: number; 644 | setEncoding(encoding?: string): void; 645 | write(data: any, encoding?: string, callback?: Function): void; 646 | end(data?: any, encoding?: string): void; 647 | destroy(): void; 648 | pause(): void; 649 | resume(): void; 650 | setTimeout(timeout: number, callback?: Function); void; 651 | setNoDelay(noDelay?: bool): void; 652 | setKeepAlive(enable?: bool, initialDelay?: number): void; 653 | address(): { port: number; family: string; address: string; }; 654 | remoteAddress: string; 655 | remotePort: number; 656 | bytesRead: number; 657 | bytesWritten: number; 658 | } 659 | 660 | export var Socket: { 661 | new (options?: { fd?: string; type?: string; allowHalfOpen?: bool; }): NodeSocket; 662 | }; 663 | 664 | export interface Server extends NodeSocket { 665 | listen(port: number, host?: string, backlog?: number, listeningListener?: Function): void; 666 | listen(path: string, listeningListener?: Function): void; 667 | listen(handle: any, listeningListener?: Function): void; 668 | close(callback?: Function): void; 669 | address(): { port: number; family: string; address: string; }; 670 | maxConnections: number; 671 | connections: number; 672 | } 673 | export function createServer(connectionListener?: (socket: NodeSocket) =>void ): Server; 674 | export function createServer(options?: { allowHalfOpen?: bool; }, connectionListener?: (socket: NodeSocket) =>void ): Server; 675 | export function connect(options: { allowHalfOpen?: bool; }, connectionListener?: Function): void; 676 | export function connect(port: number, host?: string, connectionListener?: Function): void; 677 | export function connect(path: string, connectionListener?: Function): void; 678 | export function createConnection(options: { allowHalfOpen?: bool; }, connectionListener?: Function): void; 679 | export function createConnection(port: number, host?: string, connectionListener?: Function): void; 680 | export function createConnection(path: string, connectionListener?: Function): void; 681 | export function isIP(input: string): number; 682 | export function isIPv4(input: string): bool; 683 | export function isIPv6(input: string): bool; 684 | } 685 | 686 | declare module "dgram" { 687 | import events = module("events"); 688 | 689 | export function createSocket(type: string, callback?: Function): Socket; 690 | 691 | interface Socket extends events.NodeEventEmitter { 692 | send(buf: NodeBuffer, offset: number, length: number, port: number, address: string, callback?: Function): void; 693 | bind(port: number, address?: string): void; 694 | close(): void; 695 | address: { address: string; family: string; port: number; }; 696 | setBroadcast(flag: bool): void; 697 | setMulticastTTL(ttl: number): void; 698 | setMulticastLoopback(flag: bool): void; 699 | addMembership(multicastAddress: string, multicastInterface?: string): void; 700 | dropMembership(multicastAddress: string, multicastInterface?: string): void; 701 | } 702 | } 703 | 704 | declare module "fs" { 705 | import stream = module("stream"); 706 | 707 | interface Stats { 708 | isFile(): bool; 709 | isDirectory(): bool; 710 | isBlockDevice(): bool; 711 | isCharacterDevice(): bool; 712 | isSymbolicLink(): bool; 713 | isFIFO(): bool; 714 | isSocket(): bool; 715 | dev: number; 716 | ino: number; 717 | mode: number; 718 | nlink: number; 719 | uid: number; 720 | gid: number; 721 | rdev: number; 722 | size: number; 723 | blksize: number; 724 | blocks: number; 725 | atime: Date; 726 | mtime: Date; 727 | ctime: Date; 728 | } 729 | 730 | interface FSWatcher { 731 | close(): void; 732 | } 733 | 734 | export interface ReadStream extends stream.ReadableStream { } 735 | export interface WriteStream extends stream.WritableStream { } 736 | 737 | export function rename(oldPath: string, newPath: string, callback?: Function): void; 738 | export function renameSync(oldPath: string, newPath: string): void; 739 | export function truncate(fd: string, len: number, callback?: Function): void; 740 | export function truncateSync(fd: string, len: number): void; 741 | export function chown(path: string, uid: number, gid: number, callback?: Function): void; 742 | export function chownSync(path: string, uid: number, gid: number): void; 743 | export function fchown(fd: string, uid: number, gid: number, callback?: Function): void; 744 | export function fchownSync(fd: string, uid: number, gid: number): void; 745 | export function lchown(path: string, uid: number, gid: number, callback?: Function): void; 746 | export function lchownSync(path: string, uid: number, gid: number): void; 747 | export function chmod(path: string, mode: string, callback?: Function): void; 748 | export function chmodSync(path: string, mode: string): void; 749 | export function fchmod(fd: string, mode: string, callback?: Function): void; 750 | export function fchmodSync(fd: string, mode: string): void; 751 | export function lchmod(path: string, mode: string, callback?: Function): void; 752 | export function lchmodSync(path: string, mode: string): void; 753 | export function stat(path: string, callback?: (err: Error, stats: Stats) =>any): Stats; 754 | export function lstat(path: string, callback?: (err: Error, stats: Stats) =>any): Stats; 755 | export function fstat(fd: string, callback?: (err: Error, stats: Stats) =>any): Stats; 756 | export function statSync(path: string): Stats; 757 | export function lstatSync(path: string): Stats; 758 | export function fstatSync(fd: string): Stats; 759 | export function link(srcpath: string, dstpath: string, callback?: Function): void; 760 | export function linkSync(srcpath: string, dstpath: string): void; 761 | export function symlink(srcpath: string, dstpath: string, type?: string, callback?: Function): void; 762 | export function symlinkSync(srcpath: string, dstpath: string, type?: string): void; 763 | export function readlink(path: string, callback?: (err: Error, linkString: string) =>any): void; 764 | export function realpath(path: string, callback?: (err: Error, resolvedPath: string) =>any): void; 765 | export function realpath(path: string, cache: string, callback: (err: Error, resolvedPath: string) =>any): void; 766 | export function realpathSync(path: string, cache?: string): void; 767 | export function unlink(path: string, callback?: Function): void; 768 | export function unlinkSync(path: string): void; 769 | export function rmdir(path: string, callback?: Function): void; 770 | export function rmdirSync(path: string): void; 771 | export function mkdir(path: string, mode?: string, callback?: Function): void; 772 | export function mkdirSync(path: string, mode?: string): void; 773 | export function readdir(path: string, callback?: (err: Error, files: string[]) => void): void; 774 | export function readdirSync(path: string): string[]; 775 | export function close(fd: string, callback?: Function): void; 776 | export function closeSync(fd: string): void; 777 | export function open(path: string, flags: string, mode?: string, callback?: (err: Error, fd: string) =>any): void; 778 | export function openSync(path: string, flags: string, mode?: string): void; 779 | export function utimes(path: string, atime: number, mtime: number, callback?: Function): void; 780 | export function utimesSync(path: string, atime: number, mtime: number): void; 781 | export function futimes(fd: string, atime: number, mtime: number, callback?: Function): void; 782 | export function futimesSync(fd: string, atime: number, mtime: number): void; 783 | export function fsync(fd: string, callback?: Function): void; 784 | export function fsyncSync(fd: string): void; 785 | export function write(fd: string, buffer: NodeBuffer, offset: number, length: number, position: number, callback?: (err: Error, written: number, buffer: NodeBuffer) =>any): void; 786 | export function writeSync(fd: string, buffer: NodeBuffer, offset: number, length: number, position: number): void; 787 | export function read(fd: string, buffer: NodeBuffer, offset: number, length: number, position: number, callback?: (err: Error, bytesRead: number, buffer: NodeBuffer) => void): void; 788 | export function readSync(fd: string, buffer: NodeBuffer, offset: number, length: number, position: number): any[]; 789 | export function readFile(filename: string, encoding: string, callback: (err: Error, data: NodeBuffer) => void ): void; 790 | export function readFile(filename: string, callback: (err: Error, data: NodeBuffer) => void ): void; 791 | export function readFileSync(filename: string): NodeBuffer; 792 | export function readFileSync(filename: string, encoding: string): String; 793 | export function writeFile(filename: string, data: any, encoding?: string, callback?: Function): void; 794 | export function writeFileSync(filename: string, data: any, encoding?: string): void; 795 | export function appendFile(filename: string, data: any, encoding?: string, callback?: Function): void; 796 | export function appendFileSync(filename: string, data: any, encoding?: string): void; 797 | export function watchFile(filename: string, listener: { curr: Stats; prev: Stats; }): void; 798 | export function watchFile(filename: string, options: { persistent?: bool; interval?: number; }, listener: { curr: Stats; prev: Stats; }): void; 799 | export function unwatchFile(filename: string, listener?: Stats): void; 800 | export function watch(filename: string, options?: { persistent?: bool; }, listener?: (event: string, filename: string) =>any): FSWatcher; 801 | export function exists(path: string, callback?: (exists: bool) =>void ): void; 802 | export function existsSync(path: string): bool; 803 | export function createReadStream(path: string, options?: { 804 | flags?: string; 805 | encoding?: string; 806 | fd?: string; 807 | mode?: number; 808 | bufferSize?: number; 809 | }): ReadStream; 810 | export function createWriteStream(path: string, options?: { 811 | flags?: string; 812 | encoding?: string; 813 | string?: string; 814 | }): WriteStream; 815 | } 816 | 817 | declare module "path" { 818 | export function normalize(p: string): string; 819 | export function join(...paths: any[]): string; 820 | export function resolve(from: string, to: string): string; 821 | export function resolve(from: string, from2: string, to: string): string; 822 | export function resolve(from: string, from2: string, from3: string, to: string): string; 823 | export function resolve(from: string, from2: string, from3: string, from4: string, to: string): string; 824 | export function resolve(from: string, from2: string, from3: string, from4: string, from5: string, to: string): string; 825 | export function relative(from: string, to: string): string; 826 | export function dirname(p: string): string; 827 | export function basename(p: string, ext?: string): string; 828 | export function extname(p: string): string; 829 | export var sep: string; 830 | } 831 | 832 | declare module "string_decoder" { 833 | export interface NodeStringDecoder { 834 | write(buffer: NodeBuffer): string; 835 | detectIncompleteChar(buffer: NodeBuffer): number; 836 | } 837 | export var StringDecoder: { 838 | new (encoding: string): NodeStringDecoder; 839 | }; 840 | } 841 | 842 | declare module "tls" { 843 | import crypto = module("crypto"); 844 | import net = module("net"); 845 | import stream = module("stream"); 846 | 847 | var CLIENT_RENEG_LIMIT: number; 848 | var CLIENT_RENEG_WINDOW: number; 849 | 850 | export interface TlsOptions { 851 | pfx?: any; //string or buffer 852 | key?: any; //string or buffer 853 | passphrase?: string; 854 | cert?: any; 855 | ca?: any; //string or buffer 856 | crl?: any; //string or string array 857 | ciphers?: string; 858 | honorCipherOrder?: any; 859 | requestCert?: bool; 860 | rejectUnauthorized?: bool; 861 | NPNProtocols?: any; //array or Buffer; 862 | SNICallback?: (servername: string) => any; 863 | } 864 | 865 | export interface ConnectionOptions { 866 | host?: string; 867 | port?: number; 868 | socket?: net.NodeSocket; 869 | pfx?: any; //string | Buffer 870 | key?: any; //string | Buffer 871 | passphrase?: string; 872 | cert?: any; //string | Buffer 873 | ca?: any; //Array of string | Buffer 874 | rejectUnauthorized?: bool; 875 | NPNProtocols?: any; //Array of string | Buffer 876 | servername?: string; 877 | } 878 | 879 | export interface Server extends net.Server { 880 | // Extended base methods 881 | listen(port: number, host?: string, backlog?: number, listeningListener?: Function): void; 882 | listen(path: string, listeningListener?: Function): void; 883 | listen(handle: any, listeningListener?: Function): void; 884 | 885 | listen(port: number, host?: string, callback?: Function): void; 886 | close(): void; 887 | address(): { port: number; family: string; address: string; }; 888 | addContext(hostName: string, credentials: { 889 | key: string; 890 | cert: string; 891 | ca: string; 892 | }): void; 893 | maxConnections: number; 894 | connections: number; 895 | } 896 | 897 | export interface ClearTextStream extends stream.ReadWriteStream { 898 | authorized: bool; 899 | authorizationError: Error; 900 | getPeerCertificate(): any; 901 | getCipher: { 902 | name: string; 903 | version: string; 904 | }; 905 | address: { 906 | port: number; 907 | family: string; 908 | address: string; 909 | }; 910 | remoteAddress: string; 911 | remotePort: number; 912 | } 913 | 914 | export interface SecurePair { 915 | encrypted: any; 916 | cleartext: any; 917 | } 918 | 919 | export function createServer(options: TlsOptions, secureConnectionListener?: (cleartextStream: ClearTextStream) =>void ): Server; 920 | export function connect(options: TlsOptions, secureConnectionListener?: () =>void ): ClearTextStream; 921 | export function connect(port: number, host?: string, options?: ConnectionOptions, secureConnectListener?: () =>void ): ClearTextStream; 922 | export function connect(port: number, options?: ConnectionOptions, secureConnectListener?: () =>void ): ClearTextStream; 923 | export function createSecurePair(credentials?: crypto.Credentials, isServer?: bool, requestCert?: bool, rejectUnauthorized?: bool): SecurePair; 924 | } 925 | 926 | declare module "crypto" { 927 | export interface CredentialDetails { 928 | pfx: string; 929 | key: string; 930 | passphrase: string; 931 | cert: string; 932 | ca: any; //string | string array 933 | crl: any; //string | string array 934 | ciphers: string; 935 | } 936 | export interface Credentials { context?: any; } 937 | export function createCredentials(details: CredentialDetails): Credentials; 938 | export function createHash(algorithm: string): Hash; 939 | interface Hash { 940 | update(data: any, input_encoding?: string): void; 941 | digest(encoding?: string): void; 942 | createHmac(algorithm: string, key: string): Hmac; 943 | } 944 | interface Hmac { 945 | update(data: any): void; 946 | digest(encoding?: string): void; 947 | } 948 | export function createCipher(algorithm: string, password: any): Cipher; 949 | export function createCipheriv(algorithm: string, key: any, iv: any): Cipher; 950 | interface Cipher { 951 | update(data: any, input_encoding?: string, output_encoding?: string): string; 952 | final(output_encoding?: string): string; 953 | setAutoPadding(auto_padding: bool): void; 954 | createDecipher(algorithm: string, password: any): Decipher; 955 | createDecipheriv(algorithm: string, key: any, iv: any): Decipher; 956 | } 957 | interface Decipher { 958 | update(data: any, input_encoding?: string, output_encoding?: string): void; 959 | final(output_encoding?: string): string; 960 | setAutoPadding(auto_padding: bool): void; 961 | } 962 | export function createSign(algorithm: string): Signer; 963 | interface Signer { 964 | update(data: any): void; 965 | sign(private_key: string, output_format: string): string; 966 | } 967 | export function createVerify(algorith: string): Verify; 968 | interface Verify { 969 | update(data: any): void; 970 | verify(object: string, signature: string, signature_format?: string): bool; 971 | } 972 | export function createDiffieHellman(prime_length: number): DiffieHellman; 973 | export function createDiffieHellman(prime: number, encoding?: string): DiffieHellman; 974 | interface DiffieHellman { 975 | generateKeys(encoding?: string): string; 976 | computeSecret(other_public_key: string, input_encoding?: string, output_encoding?: string): string; 977 | getPrime(encoding?: string): string; 978 | getGenerator(encoding: string): string; 979 | getPublicKey(encoding?: string): string; 980 | getPrivateKey(encoding?: string): string; 981 | setPublicKey(public_key: string, encoding?: string): void; 982 | setPrivateKey(public_key: string, encoding?: string): void; 983 | } 984 | export function getDiffieHellman(group_name: string): DiffieHellman; 985 | export function pbkdf2(password: string, salt: string, iterations: number, keylen: number, callback: (err: Error, derivedKey: string) => any): void; 986 | export function randomBytes(size: number, callback?: (err: Error, buf: NodeBuffer) =>void ); 987 | } 988 | 989 | declare module "stream" { 990 | import events = module("events"); 991 | 992 | export interface WritableStream extends events.NodeEventEmitter { 993 | writable: bool; 994 | write(str: string, encoding?: string, fd?: string): bool; 995 | write(buffer: NodeBuffer): bool; 996 | end(): void; 997 | end(str: string, enconding: string): void; 998 | end(buffer: NodeBuffer): void; 999 | destroy(): void; 1000 | destroySoon(): void; 1001 | } 1002 | 1003 | export interface ReadableStream extends events.NodeEventEmitter { 1004 | readable: bool; 1005 | setEncoding(encoding: string): void; 1006 | pause(): void; 1007 | resume(): void; 1008 | destroy(): void; 1009 | pipe(destination: WritableStream, options?: { end?: bool; }): void; 1010 | } 1011 | 1012 | export interface ReadWriteStream extends ReadableStream, WritableStream { } 1013 | } 1014 | 1015 | declare module "util" { 1016 | export function format(format: any, ...param: any[]): string; 1017 | export function debug(string: string): void; 1018 | export function error(...param: any[]): void; 1019 | export function puts(...param: any[]): void; 1020 | export function print(...param: any[]): void; 1021 | export function log(string: string): void; 1022 | export function inspect(object: any, showHidden?: bool, depth?: number, color?: bool): void; 1023 | export function isArray(object: any): bool; 1024 | export function isRegExp(object: any): bool; 1025 | export function isDate(object: any): bool; 1026 | export function isError(object: any): bool; 1027 | export function inherits(constructor: any, superConstructor: any): void; 1028 | } 1029 | 1030 | declare module "assert" { 1031 | export function fail(actual: any, expected: any, message: string, operator: string): void; 1032 | export function assert(value: any, message: string): void; 1033 | export function ok(value: any, message?: string): void; 1034 | export function equal(actual: any, expected: any, message?: string): void; 1035 | export function notEqual(actual: any, expected: any, message?: string): void; 1036 | export function deepEqual(actual: any, expected: any, message?: string): void; 1037 | export function notDeepEqual(acutal: any, expected: any, message?: string): void; 1038 | export function strictEqual(actual: any, expected: any, message?: string): void; 1039 | export function notStrictEqual(actual: any, expected: any, message?: string): void; 1040 | export function throws(block: any, error?: any, messsage?: string): void; 1041 | export function doesNotThrow(block: any, error?: any, messsage?: string): void; 1042 | export function ifError(value: any): void; 1043 | } 1044 | 1045 | declare module "tty" { 1046 | import net = module("net"); 1047 | 1048 | export function isatty(fd: string): bool; 1049 | export interface ReadStream extends net.NodeSocket { 1050 | isRaw: bool; 1051 | setRawMode(mode: bool): void; 1052 | } 1053 | export interface WriteStream extends net.NodeSocket { 1054 | columns: number; 1055 | rows: number; 1056 | } 1057 | } 1058 | 1059 | declare module "domain" { 1060 | import events = module("events"); 1061 | 1062 | export interface Domain extends events.NodeEventEmitter { } 1063 | 1064 | export function create(): Domain; 1065 | export function run(fn: Function): void; 1066 | export function add(emitter: events.NodeEventEmitter): void; 1067 | export function remove(emitter: events.NodeEventEmitter): void; 1068 | export function bind(cb: (er: Error, data: any) =>any): any; 1069 | export function intercept(cb: (data: any) => any): any; 1070 | export function dispose(): void; 1071 | } -------------------------------------------------------------------------------- /typings/q.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Interface for the Q deferred, part of callbacks 3 | */ 4 | interface Promise { 5 | fin(callback: (e?: any) => void): Promise; 6 | fail(callback: (e?: any) => any): Promise; 7 | then(fulfilled_opt: (v: any) => any, rejected_opt?: (e?: any) => any): Promise; 8 | then(fulfilled_opt: () => any, rejected_opt?: (e?: any) => any): Promise; 9 | } 10 | 11 | interface Deferred { 12 | promise: Promise; 13 | reject(arg?: any): void; 14 | resolve(arg?: any): void; 15 | node(): { (err: any): void; }; 16 | } 17 | 18 | interface QStatic { 19 | defer(): Deferred; 20 | when(value: any, fulfilled_opt: (v?: any) => void, rejected_opt?: (e?: any) => void): Promise; 21 | resolve(value?: any): Promise; 22 | reject(value?: any): Promise; 23 | delay(action: Function, delay: Number): Promise; 24 | all(...promises: any[]): Promise; 25 | nfcall(nodeFunction: Function, ...args: any[]): Promise; 26 | fcall(nodeFunction: Function, thisp?: any, ...args: any[]); 27 | } 28 | 29 | declare var Q: QStatic; 30 | --------------------------------------------------------------------------------