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 fs = require('fs'); 20let parseargs = require('./parseargs'); 21let utils = require('./utils'); 22let Program; 23let usage = require('fs').readFileSync(`${__dirname}/../usage.txt`).toString(); 24let { Task } = require('./task/task'); 25 26function die(msg) { 27 console.log(msg); 28 process.stdout.write('', function () { 29 process.stderr.write('', function () { 30 process.exit(); 31 }); 32 }); 33} 34 35let preempts = { 36 version: function () { 37 die(jake.version); 38 }, 39 help: function () { 40 die(usage); 41 } 42}; 43 44let AVAILABLE_OPTS = [ 45 { full: 'jakefile', 46 abbr: 'f', 47 expectValue: true 48 }, 49 { full: 'quiet', 50 abbr: 'q', 51 expectValue: false 52 }, 53 { full: 'directory', 54 abbr: 'C', 55 expectValue: true 56 }, 57 { full: 'always-make', 58 abbr: 'B', 59 expectValue: false 60 }, 61 { full: 'tasks', 62 abbr: 'T', 63 expectValue: false, 64 allowValue: true 65 }, 66 // Alias t 67 { full: 'tasks', 68 abbr: 't', 69 expectValue: false, 70 allowValue: true 71 }, 72 // Alias ls 73 { full: 'tasks', 74 abbr: 'ls', 75 expectValue: false, 76 allowValue: true 77 }, 78 { full: 'help', 79 abbr: 'h', 80 }, 81 { full: 'version', 82 abbr: 'V', 83 }, 84 // Alias lowercase v 85 { full: 'version', 86 abbr: 'v', 87 }, 88 { full: 'jakelibdir', 89 abbr: 'J', 90 expectValue: true 91 }, 92 { full: 'allow-rejection', 93 abbr: 'ar', 94 expectValue: false 95 } 96]; 97 98Program = function () { 99 this.availableOpts = AVAILABLE_OPTS; 100 this.opts = {}; 101 this.taskNames = null; 102 this.taskArgs = null; 103 this.envVars = null; 104 this.die = die; 105}; 106 107Program.prototype = new (function () { 108 109 this.handleErr = function (err) { 110 if (jake.listeners('error').length !== 0) { 111 jake.emit('error', err); 112 return; 113 } 114 115 if (jake.listeners('error').length) { 116 jake.emit('error', err); 117 return; 118 } 119 120 utils.logger.error('jake aborted.'); 121 if (err.stack) { 122 utils.logger.error(err.stack); 123 } 124 else { 125 utils.logger.error(err.message); 126 } 127 128 process.stdout.write('', function () { 129 process.stderr.write('', function () { 130 jake.errorCode = jake.errorCode || 1; 131 process.exit(jake.errorCode); 132 }); 133 }); 134 }; 135 136 this.parseArgs = function (args) { 137 let result = (new parseargs.Parser(this.availableOpts)).parse(args); 138 this.setOpts(result.opts); 139 this.setTaskNames(result.taskNames); 140 this.setEnvVars(result.envVars); 141 }; 142 143 this.setOpts = function (options) { 144 let opts = options || {}; 145 Object.assign(this.opts, opts); 146 }; 147 148 this.internalOpts = function (options) { 149 this.availableOpts = this.availableOpts.concat(options); 150 }; 151 152 this.autocompletions = function (cur) { 153 let p; let i; let task; 154 let commonPrefix = ''; 155 let matches = []; 156 157 for (p in jake.Task) { 158 task = jake.Task[p]; 159 if ( 160 'fullName' in task 161 && ( 162 // if empty string, program converts to true 163 cur === true || 164 task.fullName.indexOf(cur) === 0 165 ) 166 ) { 167 if (matches.length === 0) { 168 commonPrefix = task.fullName; 169 } 170 else { 171 for (i = commonPrefix.length; i > -1; --i) { 172 commonPrefix = commonPrefix.substr(0, i); 173 if (task.fullName.indexOf(commonPrefix) === 0) { 174 break; 175 } 176 } 177 } 178 matches.push(task.fullName); 179 } 180 } 181 182 if (matches.length > 1 && commonPrefix === cur) { 183 matches.unshift('yes-space'); 184 } 185 else { 186 matches.unshift('no-space'); 187 } 188 189 process.stdout.write(matches.join(' ')); 190 }; 191 192 this.setTaskNames = function (names) { 193 if (names && !Array.isArray(names)) { 194 throw new Error('Task names must be an array'); 195 } 196 this.taskNames = (names && names.length) ? names : ['default']; 197 }; 198 199 this.setEnvVars = function (vars) { 200 this.envVars = vars || null; 201 }; 202 203 this.firstPreemptiveOption = function () { 204 let opts = this.opts; 205 for (let p in opts) { 206 if (preempts[p]) { 207 return preempts[p]; 208 } 209 } 210 return false; 211 }; 212 213 this.init = function (configuration) { 214 let self = this; 215 let config = configuration || {}; 216 if (config.options) { 217 this.setOpts(config.options); 218 } 219 if (config.taskNames) { 220 this.setTaskNames(config.taskNames); 221 } 222 if (config.envVars) { 223 this.setEnvVars(config.envVars); 224 } 225 process.addListener('uncaughtException', function (err) { 226 self.handleErr(err); 227 }); 228 if (!this.opts['allow-rejection']) { 229 process.addListener('unhandledRejection', (reason, promise) => { 230 utils.logger.error('Unhandled rejection at:', promise, 'reason:', reason); 231 self.handleErr(reason); 232 }); 233 } 234 if (this.envVars) { 235 Object.assign(process.env, this.envVars); 236 } 237 }; 238 239 this.run = function () { 240 let rootTask; 241 let taskNames; 242 let dirname; 243 let opts = this.opts; 244 245 if (opts.autocomplete) { 246 return this.autocompletions(opts['autocomplete-cur'], opts['autocomplete-prev']); 247 } 248 // Run with `jake -T`, just show descriptions 249 if (opts.tasks) { 250 return jake.showAllTaskDescriptions(opts.tasks); 251 } 252 253 taskNames = this.taskNames; 254 if (!(Array.isArray(taskNames) && taskNames.length)) { 255 throw new Error('Please pass jake.runTasks an array of task-names'); 256 } 257 258 // Set working dir 259 dirname = opts.directory; 260 if (dirname) { 261 if (fs.existsSync(dirname) && 262 fs.statSync(dirname).isDirectory()) { 263 process.chdir(dirname); 264 } 265 else { 266 throw new Error(dirname + ' is not a valid directory path'); 267 } 268 } 269 270 rootTask = task(Task.ROOT_TASK_NAME, taskNames, function () {}); 271 rootTask._internal = true; 272 273 rootTask.once('complete', function () { 274 jake.emit('complete'); 275 }); 276 jake.emit('start'); 277 rootTask.invoke(); 278 }; 279 280})(); 281 282module.exports.Program = Program; 283