add-change.js (8170B)
1 var ChangeCreator = require('./change-creator').ChangeCreator; 2 3 /** 4 * The CLI class to add a changelog entry. 5 */ 6 function AddChangeCli() { 7 this._changeCreator = new ChangeCreator(); 8 this._maxRetries = 2; 9 this._retryCount = 0; 10 } 11 12 AddChangeCli.prototype = { 13 /** 14 * Prints a string to stdout. 15 * @param {string} message - text to print. 16 */ 17 print: function print(message) { 18 process.stdout.write(message); 19 }, 20 21 /** 22 * Prints the CLI intro message. 23 */ 24 showIntro: function showIntro() { 25 var intro = '\n'; 26 intro += 'This utility will walk you through creating a changelog entry.\n\n'; 27 intro += 'A changelog entry requires:\n'; 28 intro += '\t- type: Type should be one of: feature, bugfix.\n'; 29 intro += '\t- category: This can be a service identifier (e.g. "s3"), or something like: Paginator.\n'; 30 intro += '\t- description: A brief description of the change.\n'; 31 intro += '\t You can also include a github style reference such as "#111".\n\n' 32 intro += 'Please run this script before submitting a pull request.\n\n'; 33 intro += 'Press ^C at any time to quit.\n'; 34 this.print(intro); 35 }, 36 37 /** 38 * Gets a string from stdin and returns a promise resolved with the string. 39 * Note: stdin is read when the user presses 'Enter'. 40 * Returns a promise that is resolved with the trimmed user input. 41 */ 42 retrieveInputAsync: function retrieveInput() { 43 return new Promise(function(resolve, reject) { 44 function getData() { 45 var chunk = process.stdin.read(); 46 if (chunk !== null) { 47 // Remove self from stdin and call callback 48 process.stdin.removeListener('readable', getData); 49 resolve(chunk.trim()); 50 } 51 } 52 process.stdin.setEncoding('utf8'); 53 // start listening for input 54 process.stdin.on('readable', getData); 55 }); 56 }, 57 58 /** 59 * Prompts the user to enter a type. 60 * Will also process the user input. 61 * Returns a promise. 62 */ 63 promptType: function promptType() { 64 var changeCreator = this._changeCreator; 65 var existingType = changeCreator.getChangeType(); 66 this.print('\nValid types are "feature" or "bugfix"\n'); 67 this.print('type: ' + (existingType ? '(' + existingType + ') ' : '')); 68 return this.retrieveInputAsync() 69 .then(this.processType.bind(this)); 70 }, 71 72 /** 73 * Prompts the user to enter a category. 74 * Will also process the user input. 75 * Returns a promise. 76 */ 77 promptCategory: function promptCategory() { 78 var changeCreator = this._changeCreator; 79 var existingCategory = changeCreator.getChangeCategory(); 80 this.print('\nCategory can be a service identifier or something like: Paginator\n'); 81 this.print('category: ' + (existingCategory ? '(' + existingCategory + ') ' : '')); 82 return this.retrieveInputAsync() 83 .then(this.processCategory.bind(this)); 84 }, 85 86 /** 87 * Prompts the user to enter a description. 88 * Will also process the user input. 89 * Returns a promise. 90 */ 91 promptDescription: function promptDescription() { 92 var changeCreator = this._changeCreator; 93 var existingDescription = changeCreator.getChangeDescription(); 94 this.print('\nA brief description of your change.\n'); 95 this.print('description: ' + (existingDescription ? '(' + existingDescription + ') ' : '')); 96 return this.retrieveInputAsync() 97 .then(this.processDescription.bind(this)); 98 }, 99 100 /** 101 * Handles processing of `type` based on user input. 102 * If validation of `type` fails, the prompt will be shown again up to 3 times. 103 * Returns a promise. 104 */ 105 processType: function processType(type) { 106 var changeCreator = this._changeCreator; 107 var type = type.toLowerCase(); 108 // validate 109 try { 110 if (type) { 111 changeCreator.setChangeType(type); 112 } 113 changeCreator.validateChangeType(type); 114 } catch (err) { 115 // Log the error 116 this.print(err.message + '\n'); 117 // re-prompt if we still have retries 118 if (this._retryCount < this._maxRetries) { 119 this._retryCount++; 120 return this.promptType(); 121 } 122 //otherwise, just exit 123 return Promise.reject(); 124 } 125 // reset retry count 126 this._retryCount = 0; 127 return Promise.resolve(); 128 }, 129 130 /** 131 * Handles processing of `category` based on user input. 132 * If validation of `category` fails, the prompt will be shown again up to 3 times. 133 * Returns a promise. 134 */ 135 processCategory: function processCategory(category) { 136 var changeCreator = this._changeCreator; 137 // validate 138 try { 139 if (category) { 140 changeCreator.setChangeCategory(category); 141 } 142 changeCreator.validateChangeCategory(category); 143 } catch (err) { 144 // Log the error 145 this.print(err.message + '\n'); 146 // re-prompt if we still have retries 147 if (this._retryCount < this._maxRetries) { 148 this._retryCount++; 149 return this.promptCategory(); 150 } 151 //otherwise, just exit 152 return Promise.reject(); 153 } 154 // reset retry count 155 this._retryCount = 0; 156 return Promise.resolve(); 157 }, 158 159 /** 160 * Handles processing of `description` based on user input. 161 * If validation of `description` fails, the prompt will be shown again up to 3 times. 162 * Returns a promise. 163 */ 164 processDescription: function processDescription(description) { 165 var changeCreator = this._changeCreator; 166 // validate 167 try { 168 if (description) { 169 changeCreator.setChangeDescription(description); 170 } 171 changeCreator.validateChangeDescription(description); 172 } catch (err) { 173 // Log the error 174 this.print(err.message + '\n'); 175 // re-prompt if we still have retries 176 if (this._retryCount < this._maxRetries) { 177 this._retryCount++; 178 return this.promptDescription(); 179 } 180 //otherwise, just exit 181 return Promise.reject(); 182 } 183 // reset retry count 184 this._retryCount = 0; 185 return Promise.resolve(); 186 }, 187 188 /** 189 * Prompts the user for all inputs. 190 * Returns a promise. 191 */ 192 promptInputs: function promptInputs() { 193 var self = this; 194 return this.promptType() 195 .then(this.promptCategory.bind(this)) 196 .then(this.promptDescription.bind(this)) 197 .catch(function(err) { 198 self.print(err.message); 199 }); 200 }, 201 202 /** 203 * Writes the changelog entry to a JSON file. 204 * Returns a promise that is resolved with the output filename. 205 */ 206 writeChangeEntry: function writeChangeEntry() { 207 var self = this; 208 return new Promise(function(resolve, reject) { 209 var changeCreator = self._changeCreator; 210 changeCreator.writeChanges(function(err, data) { 211 if (err) { 212 return reject(err); 213 } 214 self.print('\nFile created at ' + data.file + '\n'); 215 return resolve(data); 216 }); 217 }); 218 } 219 }; 220 221 // Run the CLI program 222 var cli = new AddChangeCli(); 223 cli.showIntro(); 224 cli.promptInputs() 225 .then(cli.writeChangeEntry.bind(cli)) 226 .then(function() { 227 // CLI done with its work, exit successfully. 228 setTimeout(function() { 229 process.exit(0) 230 }, 0); 231 }) 232 .catch(function(err) { 233 cli.print(err.message); 234 cli.print('\nExiting...\n'); 235 setTimeout(function() { 236 // CLI failed, exit with an error 237 process.exit(1); 238 }, 0); 239 });