1/* 2 * Jake JavaScript build tool 3 * Copyright 2112 Matthew Eernisse (mde@fleegix.org) 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 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 19let path = require('path'); 20let fs = require('fs'); 21let exec = require('child_process').exec; 22let FileList = require('filelist').FileList; 23 24/** 25 @name jake 26 @namespace jake 27*/ 28/** 29 @name jake.PackageTask 30 @constructor 31 @description Instantiating a PackageTask creates a number of Jake 32 Tasks that make packaging and distributing your software easy. 33 34 @param {String} name The name of the project 35 @param {String} version The current project version (will be 36 appended to the project-name in the package-archive 37 @param {Function} definition Defines the contents of the package, 38 and format of the package-archive. Will be executed on the instantiated 39 PackageTask (i.e., 'this', will be the PackageTask instance), 40 to set the various instance-propertiess. 41 42 @example 43 let t = new jake.PackageTask('rous', 'v' + version, function () { 44 let files = [ 45 'Capfile' 46 , 'Jakefile' 47 , 'README.md' 48 , 'package.json' 49 , 'app/*' 50 , 'bin/*' 51 , 'config/*' 52 , 'lib/*' 53 , 'node_modules/*' 54 ]; 55 this.packageFiles.include(files); 56 this.packageFiles.exclude('node_modules/foobar'); 57 this.needTarGz = true; 58 }); 59 60 */ 61let PackageTask = function () { 62 let args = Array.prototype.slice.call(arguments); 63 let name = args.shift(); 64 let version = args.shift(); 65 let definition = args.pop(); 66 let prereqs = args.pop() || []; // Optional 67 68 prereqs = [].concat(prereqs); // Accept string or list 69 70 /** 71 @name jake.PackageTask#name 72 @public 73 @type {String} 74 @description The name of the project 75 */ 76 this.name = name; 77 /** 78 @name jake.PackageTask#version 79 @public 80 @type {String} 81 @description The project version-string 82 */ 83 this.version = version; 84 /** 85 @name jake.PackageTask#prereqs 86 @public 87 @type {Array} 88 @description Tasks to run before packaging 89 */ 90 this.prereqs = prereqs; 91 /** 92 @name jake.PackageTask#packageDir 93 @public 94 @type {String='pkg'} 95 @description The directory-name to use for packaging the software 96 */ 97 this.packageDir = 'pkg'; 98 /** 99 @name jake.PackageTask#packageFiles 100 @public 101 @type {jake.FileList} 102 @description The list of files and directories to include in the 103 package-archive 104 */ 105 this.packageFiles = new FileList(); 106 /** 107 @name jake.PackageTask#needTar 108 @public 109 @type {Boolean=false} 110 @description If set to true, uses the `tar` utility to create 111 a gzip .tgz archive of the package 112 */ 113 this.needTar = false; 114 /** 115 @name jake.PackageTask#needTarGz 116 @public 117 @type {Boolean=false} 118 @description If set to true, uses the `tar` utility to create 119 a gzip .tar.gz archive of the package 120 */ 121 this.needTarGz = false; 122 /** 123 @name jake.PackageTask#needTarBz2 124 @public 125 @type {Boolean=false} 126 @description If set to true, uses the `tar` utility to create 127 a bzip2 .bz2 archive of the package 128 */ 129 this.needTarBz2 = false; 130 /** 131 @name jake.PackageTask#needJar 132 @public 133 @type {Boolean=false} 134 @description If set to true, uses the `jar` utility to create 135 a .jar archive of the package 136 */ 137 this.needJar = false; 138 /** 139 @name jake.PackageTask#needZip 140 @public 141 @type {Boolean=false} 142 @description If set to true, uses the `zip` utility to create 143 a .zip archive of the package 144 */ 145 this.needZip = false; 146 /** 147 @name jake.PackageTask#manifestFile 148 @public 149 @type {String=null} 150 @description Can be set to point the `jar` utility at a manifest 151 file to use in a .jar archive. If unset, one will be automatically 152 created by the `jar` utility. This path should be relative to the 153 root of the package directory (this.packageDir above, likely 'pkg') 154 */ 155 this.manifestFile = null; 156 /** 157 @name jake.PackageTask#tarCommand 158 @public 159 @type {String='tar'} 160 @description The shell-command to use for creating tar archives. 161 */ 162 this.tarCommand = 'tar'; 163 /** 164 @name jake.PackageTask#jarCommand 165 @public 166 @type {String='jar'} 167 @description The shell-command to use for creating jar archives. 168 */ 169 this.jarCommand = 'jar'; 170 /** 171 @name jake.PackageTask#zipCommand 172 @public 173 @type {String='zip'} 174 @description The shell-command to use for creating zip archives. 175 */ 176 this.zipCommand = 'zip'; 177 /** 178 @name jake.PackageTask#archiveNoBaseDir 179 @public 180 @type {Boolean=false} 181 @description Simple option for performing the archive on the 182 contents of the directory instead of the directory itself 183 */ 184 this.archiveNoBaseDir = false; 185 /** 186 @name jake.PackageTask#archiveChangeDir 187 @public 188 @type {String=null} 189 @description Equivalent to the '-C' command for the `tar` and `jar` 190 commands. ("Change to this directory before adding files.") 191 */ 192 this.archiveChangeDir = null; 193 /** 194 @name jake.PackageTask#archiveContentDir 195 @public 196 @type {String=null} 197 @description Specifies the files and directories to include in the 198 package-archive. If unset, this will default to the main package 199 directory -- i.e., name + version. 200 */ 201 this.archiveContentDir = null; 202 203 if (typeof definition == 'function') { 204 definition.call(this); 205 } 206 this.define(); 207}; 208 209PackageTask.prototype = new (function () { 210 211 let _compressOpts = { 212 Tar: { 213 ext: '.tgz', 214 flags: 'czf', 215 cmd: 'tar' 216 }, 217 TarGz: { 218 ext: '.tar.gz', 219 flags: 'czf', 220 cmd: 'tar' 221 }, 222 TarBz2: { 223 ext: '.tar.bz2', 224 flags: 'cjf', 225 cmd: 'tar' 226 }, 227 Jar: { 228 ext: '.jar', 229 flags: 'cf', 230 cmd: 'jar' 231 }, 232 Zip: { 233 ext: '.zip', 234 flags: 'qr', 235 cmd: 'zip' 236 } 237 }; 238 239 this.define = function () { 240 let self = this; 241 let packageDirPath = this.packageDirPath(); 242 let compressTaskArr = []; 243 244 desc('Build the package for distribution'); 245 task('package', self.prereqs.concat(['clobberPackage', 'buildPackage'])); 246 // Backward-compat alias 247 task('repackage', ['package']); 248 249 task('clobberPackage', function () { 250 jake.rmRf(self.packageDir, {silent: true}); 251 }); 252 253 desc('Remove the package'); 254 task('clobber', ['clobberPackage']); 255 256 let doCommand = function (p) { 257 let filename = path.resolve(self.packageDir + '/' + self.packageName() + 258 _compressOpts[p].ext); 259 if (process.platform == 'win32') { 260 // Windows full path may have drive letter, which is going to cause 261 // namespace problems, so strip it. 262 if (filename.length > 2 && filename[1] == ':') { 263 filename = filename.substr(2); 264 } 265 } 266 compressTaskArr.push(filename); 267 268 file(filename, [packageDirPath], function () { 269 let cmd; 270 let opts = _compressOpts[p]; 271 // Directory to move to when doing the compression-task 272 // Changes in the case of zip for emulating -C option 273 let chdir = self.packageDir; 274 // Save the current dir so it's possible to pop back up 275 // after compressing 276 let currDir = process.cwd(); 277 let archiveChangeDir; 278 let archiveContentDir; 279 280 if (self.archiveNoBaseDir) { 281 archiveChangeDir = self.packageName(); 282 archiveContentDir = '.'; 283 } 284 else { 285 archiveChangeDir = self.archiveChangeDir; 286 archiveContentDir = self.archiveContentDir; 287 } 288 289 cmd = self[opts.cmd + 'Command']; 290 cmd += ' -' + opts.flags; 291 if (opts.cmd == 'jar' && self.manifestFile) { 292 cmd += 'm'; 293 } 294 295 // The name of the archive to create -- use full path 296 // so compression can be performed from a different dir 297 // if needed 298 cmd += ' ' + filename; 299 300 if (opts.cmd == 'jar' && self.manifestFile) { 301 cmd += ' ' + self.manifestFile; 302 } 303 304 // Where to perform the compression -- -C option isn't 305 // supported in zip, so actually do process.chdir for this 306 if (archiveChangeDir) { 307 if (opts.cmd == 'zip') { 308 chdir = path.join(chdir, archiveChangeDir); 309 } 310 else { 311 cmd += ' -C ' + archiveChangeDir; 312 } 313 } 314 315 // Where to get the archive content 316 if (archiveContentDir) { 317 cmd += ' ' + archiveContentDir; 318 } 319 else { 320 cmd += ' ' + self.packageName(); 321 } 322 323 // Move into the desired dir (usually packageDir) to compress 324 // Return back up to the current dir after the exec 325 process.chdir(chdir); 326 327 exec(cmd, function (err, stdout, stderr) { 328 if (err) { throw err; } 329 330 // Return back up to the starting directory (see above, 331 // before exec) 332 process.chdir(currDir); 333 334 complete(); 335 }); 336 }, {async: true}); 337 }; 338 339 for (let p in _compressOpts) { 340 if (this['need' + p]) { 341 doCommand(p); 342 } 343 } 344 345 task('buildPackage', compressTaskArr, function () {}); 346 347 directory(this.packageDir); 348 349 file(packageDirPath, this.packageFiles, function () { 350 jake.mkdirP(packageDirPath); 351 let fileList = []; 352 self.packageFiles.forEach(function (name) { 353 let f = path.join(self.packageDirPath(), name); 354 let fDir = path.dirname(f); 355 jake.mkdirP(fDir, {silent: true}); 356 357 // Add both files and directories 358 fileList.push({ 359 from: name, 360 to: f 361 }); 362 }); 363 let _copyFile = function () { 364 let file = fileList.pop(); 365 let stat; 366 if (file) { 367 stat = fs.statSync(file.from); 368 // Target is a directory, just create it 369 if (stat.isDirectory()) { 370 jake.mkdirP(file.to, {silent: true}); 371 _copyFile(); 372 } 373 // Otherwise copy the file 374 else { 375 jake.cpR(file.from, file.to, {silent: true}); 376 _copyFile(); 377 } 378 } 379 else { 380 complete(); 381 } 382 }; 383 _copyFile(); 384 }, {async: true}); 385 386 387 }; 388 389 this.packageName = function () { 390 if (this.version) { 391 return this.name + '-' + this.version; 392 } 393 else { 394 return this.name; 395 } 396 }; 397 398 this.packageDirPath = function () { 399 return this.packageDir + '/' + this.packageName(); 400 }; 401 402})(); 403 404jake.PackageTask = PackageTask; 405exports.PackageTask = PackageTask; 406 407