├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── Gruntfile.js ├── LICENSE-MIT ├── README.md ├── package.json ├── tasks └── prompt.js ├── templates ├── grunt-prompt.md └── readme │ ├── changelog.md │ ├── examples.md │ ├── options.md │ ├── overview.md │ ├── results.md │ ├── screenshots.md │ └── tests.md └── test └── prompt.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | tmp 4 | .idea -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "boss": true, 11 | "eqnull": true, 12 | "node": true 13 | } 14 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /templates/ 2 | /test/ 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | 5 | node_js: 6 | - '0.10' 7 | - '0.12' 8 | - 'iojs' 9 | - '4' 10 | - '5' 11 | 12 | script: 13 | - npm run test 14 | 15 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-prompt 3 | * https://github.com/dylang/grunt-prompt 4 | * 5 | * Copyright (c) 2013 Dylan Greene 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | var semver = require('semver'); 12 | var currentVersion = require('./package.json').version; 13 | var chalk = require('chalk'); 14 | var _ = require('lodash'); 15 | 16 | module.exports = function (grunt) { 17 | 18 | 19 | // Project configuration. 20 | grunt.initConfig({ 21 | 22 | pkg: grunt.file.readJSON('package.json'), 23 | 24 | jshint: { 25 | options: { 26 | jshintrc: '.jshintrc', 27 | ignores: [] 28 | }, 29 | task: [ 30 | 'Gruntfile.js', 31 | 'tasks/**/*.js' 32 | ] 33 | }, 34 | 35 | specialVariable: 'a special thing', 36 | 37 | specialFunction: function () { 38 | return 'a dynamic value [' + new Date() + ']'; 39 | }, 40 | 41 | // Configuration to be run (and then tested). 42 | prompt: { 43 | examples: { 44 | options: { 45 | questions: [ 46 | { 47 | config: 'echo.list', 48 | type: 'list', 49 | message: 'Choose an item from a list, returns the value', 50 | choices: [ 51 | { name: chalk.white('White') }, 52 | { name: chalk.grey('Grey') }, 53 | '---', 54 | { name: chalk.blue('Blue'), value: 'blue' }, 55 | { name: chalk.cyan('Cyan') }, 56 | { name: chalk.green('Green') }, 57 | { name: chalk.magenta('Magenta') }, 58 | { name: chalk.red('Red') }, 59 | { name: chalk.yellow('Yellow') }, 60 | ], 61 | default: 'blue', 62 | filter: function(str) { 63 | return chalk.stripColor(str.toLowerCase()); 64 | } 65 | }, 66 | { 67 | config: 'echo.checkbox', 68 | type: 'checkbox', 69 | message: 'Choose multiple items, returns an array of values', 70 | choices: [ 71 | { name: chalk.bold('Bold'), value: 'bold' }, 72 | { name: chalk.italic('Italic') }, 73 | { name: chalk.underline('Underline'), value: 'underline' }, 74 | { name: chalk.inverse('Inverse') }, 75 | { name: chalk.strikethrough('Strikethrough') } 76 | ], 77 | default: [ 78 | 'bold', 'underline' 79 | ], 80 | filter: function(value) { 81 | return _(value) 82 | .map(chalk.stripColor) 83 | .map(function(str){ 84 | return str.toLowerCase(); 85 | }) 86 | .value(); 87 | } 88 | }, 89 | { 90 | config: 'echo.confirm', 91 | type: 'confirm', 92 | message: 'Choose yes or no, returns a boolean' 93 | }, 94 | { 95 | config: 'echo.input', 96 | type: 'input', 97 | message: 'Text input', 98 | validate: function(value) { 99 | if (value === '') { 100 | return 'A value is required.'; 101 | } 102 | return true; 103 | } 104 | }, 105 | { 106 | config: 'echo.password', 107 | type: 'password', 108 | message: 'Password input', 109 | validate: function(value) { 110 | if (value.length < 5) { 111 | return 'Password should be at least 5 characters.'; 112 | } 113 | return true; 114 | } 115 | } 116 | ], 117 | then: function(){ 118 | console.log(chalk.green.bold.underline('Great job!')); 119 | } 120 | } 121 | }, 122 | 123 | test: { 124 | options: { 125 | questions: [ 126 | { 127 | config: 'test', 128 | type: 'input', 129 | message: 'Just press enter, the result should be the default.', 130 | default: 1 131 | } 132 | ], 133 | then: function(results){ 134 | console.log('results from this test', results); 135 | } 136 | } 137 | }, 138 | 139 | mochacli: { 140 | options: { 141 | questions: [ 142 | { 143 | config: 'mochacli.options.reporter', 144 | type: 'list', 145 | message: 'Which Mocha reporter would you like to use?', 146 | default: 'spec', 147 | choices: [ 148 | { name: 'dot' }, 149 | { name: 'spec' }, 150 | { name: 'nyan' }, 151 | { name: 'TAP' }, 152 | { name: 'landing' }, 153 | { name: 'list' }, 154 | { name: 'progress' }, 155 | { name: 'json' }, 156 | { name: 'JSONconv' }, 157 | { name: 'HTMLconv' }, 158 | { name: 'min' }, 159 | { name: 'doc' } 160 | ] 161 | } 162 | ] 163 | } 164 | }, 165 | 166 | bump: { 167 | options: { 168 | questions: [ 169 | { 170 | config: 'bump.increment', 171 | type: 'list', 172 | message: 'Bump version from ' + '<%= pkg.version %>'.cyan + ' to:', 173 | choices: [ 174 | { 175 | value: 'build', 176 | name: 'Build: '.yellow + (currentVersion + '-?').yellow + 177 | ' Unstable, betas, and release candidates.' 178 | }, 179 | { 180 | value: 'patch', 181 | name: 'Patch: '.yellow + semver.inc(currentVersion, 'patch').yellow + 182 | ' Backwards-compatible bug fixes.' 183 | }, 184 | { 185 | value: 'minor', 186 | name: 'Minor: '.yellow + semver.inc(currentVersion, 'minor').yellow + 187 | ' Add functionality in a backwards-compatible manner.' 188 | }, 189 | { 190 | value: 'major', 191 | name: 'Major: '.yellow + semver.inc(currentVersion, 'major').yellow + 192 | ' Incompatible API changes.' 193 | }, 194 | { 195 | value: 'custom', 196 | name: 'Custom: ?.?.?'.yellow + 197 | ' Specify version...' 198 | } 199 | ] 200 | }, 201 | { 202 | config: 'bump.version', 203 | type: 'input', 204 | message: 'What specific version would you like', 205 | when: function (answers) { 206 | return answers['bump.increment'] === 'custom'; 207 | }, 208 | validate: function (value) { 209 | var valid = semver.valid(value) && true; 210 | return valid || 'Must be a valid semver, such as 1.2.3-rc1. See ' + 'http://semver.org/'.blue.underline + ' for more details.'; 211 | } 212 | }, 213 | { 214 | config: 'bump.files', 215 | type: 'checkbox', 216 | message: 'What should get the new version:', 217 | choices: [ 218 | { 219 | value: 'package', 220 | name: 'package.json' + (!grunt.file.isFile('package.json') ? ' file not found, will create one'.grey : ''), 221 | checked: grunt.file.isFile('package.json') 222 | }, 223 | { 224 | value: 'bower', 225 | name: 'bower.json' + (!grunt.file.isFile('bower.json') ? ' file not found, will create one'.grey : ''), 226 | checked: grunt.file.isFile('bower.json') 227 | }, 228 | { 229 | value: 'git', 230 | name: 'git tag', 231 | checked: grunt.file.isDir('.git') 232 | } 233 | ] 234 | } 235 | ] 236 | } 237 | }, 238 | dynamic: { 239 | options: { 240 | questions: [ 241 | { 242 | config: 'echo.dynamic', 243 | type: 'input', 244 | message: function () { 245 | var specialVariable = grunt.config('specialVariable'), 246 | specialFunction = grunt.config('specialFunction'); 247 | 248 | return 'You can use ' + chalk.yellow(specialVariable) + ' and even ' + chalk.red(specialFunction()) + ' in your questions'; 249 | } 250 | }, 251 | ] 252 | } 253 | }, 254 | separator: { 255 | options: { 256 | questions: [ 257 | { 258 | config: 'separator', 259 | type: 'list', 260 | message: 'List of choices with custom Separator', 261 | choices: [ 262 | { separator: chalk.bold.red('HEADING') }, 263 | 'Label 1', 264 | 'Label 2', 265 | '', 266 | {name: 'Label 3'}, 267 | {name: 'Label 4'}, 268 | '---', 269 | {name: 'Label 5'}, 270 | {name: 'Label 6'} 271 | ] 272 | } 273 | ] 274 | } 275 | } 276 | }, 277 | 278 | mochacli: { 279 | src: 'test/**/*.test.js', 280 | options: { 281 | timeout: 10000, 282 | ui: 'bdd', 283 | reporter: 'spec', 284 | require: [ 285 | 'chai' 286 | ] 287 | } 288 | } 289 | }); 290 | 291 | grunt.registerTask('results', 'show results from grunt-prompt', function(subtask){ 292 | _(grunt.config('prompt')) 293 | .pick(subtask || _.constant(true)) 294 | .pluck('options') 295 | .pluck('questions') 296 | .flatten() 297 | .pluck('config') 298 | .each(function(key){ 299 | console.log(key + ':\t', grunt.config(key)); 300 | }); 301 | }); 302 | 303 | // Fake Grunt Bump task 304 | grunt.registerTask('bump', '', function () { 305 | if (grunt.config('bump.increment') === 'custom') { 306 | grunt.log.ok('Bumping version to ' + grunt.config('bump.version').yellow + ':'); 307 | } else { 308 | grunt.log.ok('Bumping up ' + grunt.config('bump.increment').yellow + ' version number.'); 309 | } 310 | 311 | if (_(grunt.config('bump.files')).contains('package')) { 312 | grunt.log.ok('Updating ' + 'package.json'.yellow + '.'); 313 | } 314 | 315 | if (_(grunt.config('bump.files')).contains('bower')) { 316 | if (!grunt.file.isFile('bower.json')) { 317 | grunt.log.ok('Creating ' + 'bower.json'.yellow + '.'); 318 | } 319 | grunt.log.ok('Updating ' + 'bower.json'.yellow + '.'); 320 | } 321 | 322 | if (_(grunt.config('bump.files')).contains('git')) { 323 | grunt.log.ok('Updating ' + 'git tag'.yellow + '.'); 324 | } 325 | }); 326 | 327 | grunt.registerTask('bump', 328 | [ 329 | //'jshint', 330 | 'prompt:bump', 331 | 'bump' 332 | ]); 333 | 334 | grunt.registerTask('test', 335 | [ 336 | 'jshint', 337 | 'prompt:mochacli', 338 | 'mochacli' 339 | ]); 340 | 341 | grunt.registerTask('dynamic', 342 | [ 343 | 'prompt:dynamic', 344 | 'results:dynamic' 345 | ]); 346 | 347 | grunt.registerTask('default', 348 | [ 349 | 'jshint', 350 | 'prompt:examples', 351 | 'results:examples' 352 | ]); 353 | 354 | require('load-grunt-tasks')(grunt); 355 | 356 | grunt.loadTasks('tasks'); 357 | 358 | }; 359 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Dylan Greene 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## grunt-prompt [![Build Status](http://img.shields.io/travis/dylang/grunt-prompt.svg)](https://travis-ci.org/dylang/grunt-prompt) [![grunt-prompt](http://img.shields.io/npm/dm/grunt-prompt.svg)](https://www.npmjs.org/package/grunt-prompt) 2 | 3 | > Interactive prompt for your Grunt config using console checkboxes, text input with filtering, password fields. 4 | 5 | 6 | 7 | | ![grunt-prompt in action](https://f.cloud.github.com/assets/51505/867636/e727abfc-f717-11e2-997e-6b97e24593c3.gif "grunt-prompt in action") | 8 | |:-------------:| 9 | | grunt-prompt in action | 10 | 11 | 12 | 13 | ### Getting Started 14 | 15 | This plugin recommends Grunt `0.4.1` or newer. 16 | 17 | ### Installing 18 | 19 | ```bash 20 | npm install grunt-prompt --save-dev 21 | ``` 22 | 23 | Once that's done, add this line to your project's `Gruntfile.js`: 24 | 25 | ```js 26 | grunt.loadNpmTasks('grunt-prompt'); 27 | ``` 28 | 29 | 30 | 31 | `Grunt-prompt`'s UI is powered by the amazing [Inquirer](https://github.com/SBoudrias/Inquirer.js), a project created by Simon Boudrias. 32 | 33 | | ![grunt-prompt in action](https://f.cloud.github.com/assets/51505/867636/e727abfc-f717-11e2-997e-6b97e24593c3.gif "grunt-prompt in action") | 34 | |:-------------:| 35 | | grunt-prompt in action | 36 | 37 | 38 | ### Overview 39 | In your project's Gruntfile, add a section named `prompt` to the data object passed into `grunt.initConfig()`. 40 | 41 | `grunt-prompt` is a multi-task. This means you can create multiple prompts. 42 | 43 | ```js 44 | grunt.initConfig({ 45 | prompt: { 46 | target: { 47 | options: { 48 | questions: [ 49 | { 50 | config: 'config.name', // arbitrary name or config for any other grunt task 51 | type: '', // list, checkbox, confirm, input, password 52 | message: 'String|function(answers)', // Question to ask the user, function needs to return a string, 53 | default: 'value', // default value if nothing is entered 54 | choices: 'Array|function(answers)', 55 | validate: function(value), // return true if valid, error message if invalid. works only with type:input 56 | filter: function(value), // modify the answer 57 | when: function(answers) // only ask this question when this function returns true 58 | } 59 | ] 60 | } 61 | }, 62 | }, 63 | }) 64 | ``` 65 | 66 | 67 | 68 | #### Options 69 | 70 | ##### config 71 | 72 | Type: `String` _required_ 73 | 74 | This is used for three things: 75 | 76 | * It will set or overwrite the config of other Grunt tasks: `config: 'jshint.allFiles.reporter'` 77 | * The key in the resulting `answers` object: `if (answers['jshint.allFiles.reporter'] === 'custom') {...` 78 | * It can be an arbitrary value read using `grunt.config`: `grunt.config('jshint.allFiles.reporter')` 79 | 80 | ##### type 81 | 82 | Type: `String` _required_ 83 | 84 | Type of question to ask: 85 | 86 | * `list`: use arrow keys to pick one choice. Returns a string. 87 | * `checkbox`: use arrow keys and space bar to pick multiple items. Returns an array. 88 | * `confirm`: Yes/no. Returns a boolean. 89 | * `input`: Free text input. Returns a string. 90 | * `password`: Masked input. Returns a string. 91 | 92 | Here's an example of each type: 93 | 94 | | ![grunt-prompt example](https://f.cloud.github.com/assets/51505/867636/e727abfc-f717-11e2-997e-6b97e24593c3.gif "grunt-prompt example") | 95 | |:-------------:| 96 | | grunt-prompt example | 97 | 98 | The documentation for [Inquiry](https://github.com/SBoudrias/Inquirer.js) has [more details about type](https://github.com/SBoudrias/Inquirer.js#prompts-type) as well as additional typess. 99 | 100 | ##### message 101 | 102 | Type: `String|function(answers):String` _required_ 103 | 104 | The question to ask the user. If it's a function, it needs to return a string. The first parameter of this function will be an array containing all previously supplied answers. This allows you to customize the message based on the results of previous questions. 105 | 106 | Hint: keep it short, users hate to read. 107 | 108 | ##### default 109 | 110 | Type: `String`/`Array`/`Boolean`/'function' _optional_ 111 | 112 | Default value used when the user just hits Enter. *If a `value` field is not provided, the filter value must match the `name` exactly.* 113 | 114 | ##### choices 115 | 116 | For `question types 'list' and 'checkbox'`: Type: `array of hashes` 117 | 118 | * `name` The label that is displayed in the UI. 119 | * `value` _optional_ Value returned. When not used the name is used instead. 120 | * `checked` _optional_ Choose the option by default. _Only for checkbox._ 121 | 122 | ``` 123 | choices: [ 124 | { name: 'jshint', checked: true }, 125 | { name: 'jslint' }, 126 | { name: 'eslint' }, 127 | '---', // puts in a non-selectable separator. Can be a string or '---' for default. 128 | { name: 'I like to live dangerously', value: 'none' } 129 | ] 130 | ``` 131 | 132 | ##### validate 133 | 134 | Type: `function(value)` _optional_ 135 | 136 | Return `true` if it is valid (true `true`, not a truthy value). 137 | Return `string` message if it is not valid. 138 | 139 | ##### filter 140 | 141 | Type: `function(value)` _optional_ 142 | 143 | Use a modified version of the input for the answer. Useful for stripping extra characters, converting strings to integers. 144 | 145 | ##### when 146 | 147 | Type: `function(answers)` _optional_ 148 | 149 | Choose when this question is asked. Perfect for asking questions based on the results of previous questions. 150 | 151 | ###### then 152 | 153 | Type: `function(results, done):Boolean` _optional_ 154 | 155 | Runs after all questions have been asked. 156 | 157 | The ```done``` parameter is optional, and can be used for async operations in your handler. 158 | 159 | When you return ```true``` from this function, the grunt-prompt code will not complete the async, so you are able to do your own async operations and call ```done()``` yourself. 160 | 161 | ``` 162 | config: 163 | prompt: { 164 | demo: { 165 | options: { 166 | questions: [ 167 | .. 168 | ], 169 | then: function(results, done) { 170 | someAsyncFunction(function () { 171 | done(); 172 | }); 173 | return true; 174 | } 175 | } 176 | } 177 | } 178 | 179 | ``` 180 | 181 | 182 | 183 | ### How to use the results in your Gruntfile 184 | 185 | You can also modify how tasks will work by changing options for other tasks. 186 | You do not need to write code to do this, it's all in the `config` var. 187 | 188 | Here we will let the user choose what Mocha reporter to use. 189 | 190 | ```js 191 | config: 192 | prompt: { 193 | mochacli: { 194 | options: { 195 | questions: [ 196 | { 197 | config: 'mochacli.options.reporter' 198 | type: 'list' 199 | message: 'Which Mocha reporter would you like to use?', 200 | default: 'spec' 201 | choices: ['dot', 'spec', 'nyan', 'TAP', 'landing', 'list', 202 | 'progress', 'json', 'JSONconv', 'HTMLconv', 'min', 'doc'] 203 | } 204 | ] 205 | } 206 | } 207 | } 208 | 209 | ``` 210 | 211 | and create a shortcut: 212 | 213 | ``` 214 | grunt.registerTask('test', 215 | [ 216 | 'prompt:mochacli', 217 | 'mochacli' 218 | ]); 219 | 220 | ``` 221 | 222 | And run it: 223 | 224 | ``` 225 | $ grunt test 226 | ``` 227 | 228 | | ![grunt-prompt setting up Mocha](https://f.cloud.github.com/assets/51505/983227/aabe4b6e-084a-11e3-94cd-514371c24059.gif "grunt-prompt setting up Mocha") | 229 | |:-------------:| 230 | | grunt-prompt setting up Mocha | 231 | 232 | ### How can values be accessed from my own code? 233 | 234 | This `config` value is accessible to all other `grunt` tasks via `grunt.config('')`. 235 | 236 | If you had this: 237 | 238 | ```js 239 | config: 'validation' 240 | ``` 241 | 242 | Then later on in your custom task can access it like this: 243 | 244 | ```js 245 | var validation = grunt.config('validation'); 246 | ``` 247 | 248 | 249 | ### Usage Examples 250 | 251 | | ![grunt-prompt with grunt-bump](https://f.cloud.github.com/assets/51505/2119082/78171246-9142-11e3-970a-f64f2002ad4e.png "grunt-prompt with grunt-bump") | 252 | |:-------------:| 253 | | grunt-prompt with grunt-bump | 254 | 255 | This is an example of how `grunt-prompt` for something like [grunt-bump](https://github.com/vojtajina/grunt-bump) which makes it easy to 256 | update your project's version in the `package.json`, `bower.json`, and `git tag`. 257 | 258 | ```js 259 | prompt: { 260 | bump: { 261 | options: { 262 | questions: [ 263 | { 264 | config: 'bump.increment', 265 | type: 'list', 266 | message: 'Bump version from ' + '<%= pkg.version %>' + ' to:', 267 | choices: [ 268 | { 269 | value: 'build', 270 | name: 'Build: '+ (currentVersion + '-?') + ' Unstable, betas, and release candidates.' 271 | }, 272 | { 273 | value: 'patch', 274 | name: 'Patch: ' + semver.inc(currentVersion, 'patch') + ' Backwards-compatible bug fixes.' 275 | }, 276 | { 277 | value: 'minor', 278 | name: 'Minor: ' + semver.inc(currentVersion, 'minor') + ' Add functionality in a backwards-compatible manner.' 279 | }, 280 | { 281 | value: 'major', 282 | name: 'Major: ' + semver.inc(currentVersion, 'major') + ' Incompatible API changes.' 283 | }, 284 | { 285 | value: 'custom', 286 | name: 'Custom: ?.?.? Specify version...' 287 | } 288 | ] 289 | }, 290 | { 291 | config: 'bump.version', 292 | type: 'input', 293 | message: 'What specific version would you like', 294 | when: function (answers) { 295 | return answers['bump.increment'] === 'custom'; 296 | }, 297 | validate: function (value) { 298 | var valid = semver.valid(value); 299 | return !!valid || 'Must be a valid semver, such as 1.2.3-rc1. See http://semver.org/ for more details.'; 300 | } 301 | }, 302 | { 303 | config: 'bump.files', 304 | type: 'checkbox', 305 | message: 'What should get the new version:', 306 | choices: [ 307 | { 308 | value: 'package', 309 | name: 'package.json' + (!grunt.file.isFile('package.json') ? ' not found, will create one' : ''), 310 | checked: grunt.file.isFile('package.json') 311 | }, 312 | { 313 | value: 'bower', 314 | name: 'bower.json' + (!grunt.file.isFile('bower.json') ? ' not found, will create one' : ''), 315 | checked: grunt.file.isFile('bower.json') 316 | }, 317 | { 318 | value: 'git', 319 | name: 'git tag', 320 | checked: grunt.file.isDir('.git') 321 | } 322 | ] 323 | } 324 | ] 325 | } 326 | } 327 | } 328 | ``` 329 | 330 | 331 | 332 | 333 | 334 | 335 | ### Release History 336 | * **1.3.0** - 26 Oct 2014 - Add {{done}} callback for {{then}}. 337 | * **1.2.1** - 4 Oct 2014 - Separator can be '' or { separator: 'any string' }. Fixed it so choices can be strings again. 338 | * **1.2.0** - 4 Oct 2014 - Separator in choices can be a falsey value or string 339 | * **1.1.0** - 4 Mar 2014 - Messages can be functions instead of strings for dynamic questions. 340 | * **1.0.0** - 4 Feb 2014 - Dropping support for Node 0.8. 341 | * **0.2.2** - 4 Feb 2014 - Updated readme to make it auto-generated. 342 | * **0.2.1** - 4 Feb 2014 - Fix bug when using a function to provide choices. 343 | * **0.2.0** - 26 Jan 2014 - Added `then` option which runs after questions. Improved docs. 344 | * **0.1.1** - 27 July 2013 - Some documentation cleanup, better screenshots, new example code in the gruntfile, reomved unused tests. 345 | * **0.1.0** - 18 July 2013 - First version, after an exhausting but fun day with the family at Hershey Park. 346 | 347 | 348 | 349 | 350 | ### About the Author 351 | 352 | Hi! Thanks for checking out this project! My name is **Dylan Greene**. When not overwhelmed with my two young kids I enjoy contributing 353 | to the open source community. I'm also a tech lead at [Opower](http://opower.com). [![@dylang](https://img.shields.io/badge/github-dylang-green.svg)](https://github.com/dylang) [![@dylang](https://img.shields.io/badge/twitter-dylang-blue.svg)](https://twitter.com/dylang) 354 | 355 | Here's some of my other Node projects: 356 | 357 | | Name | Description | npm Downloads | 358 | |---|---|---| 359 | | [`grunt‑notify`](https://github.com/dylang/grunt-notify) | Automatic desktop notifications for Grunt errors and warnings using Growl for OS X or Windows, Mountain Lion and Mavericks Notification Center, and Notify-Send. | [![grunt-notify](https://img.shields.io/npm/dm/grunt-notify.svg?style=flat-square)](https://www.npmjs.org/package/grunt-notify) | 360 | | [`npm‑check`](https://github.com/dylang/npm-check) | Check for outdated, incorrect, and unused dependencies. | [![npm-check](https://img.shields.io/npm/dm/npm-check.svg?style=flat-square)](https://www.npmjs.org/package/npm-check) | 361 | | [`shortid`](https://github.com/dylang/shortid) | Amazingly short non-sequential url-friendly unique id generator. | [![shortid](https://img.shields.io/npm/dm/shortid.svg?style=flat-square)](https://www.npmjs.org/package/shortid) | 362 | | [`rss`](https://github.com/dylang/node-rss) | RSS feed generator. Add RSS feeds to any project. Supports enclosures and GeoRSS. | [![rss](https://img.shields.io/npm/dm/rss.svg?style=flat-square)](https://www.npmjs.org/package/rss) | 363 | | [`xml`](https://github.com/dylang/node-xml) | Fast and simple xml generator. Supports attributes, CDATA, etc. Includes tests and examples. | [![xml](https://img.shields.io/npm/dm/xml.svg?style=flat-square)](https://www.npmjs.org/package/xml) | 364 | | [`changelog`](https://github.com/dylang/changelog) | Command line tool (and Node module) that generates a changelog in color output, markdown, or json for modules in npmjs.org's registry as well as any public github.com repo. | [![changelog](https://img.shields.io/npm/dm/changelog.svg?style=flat-square)](https://www.npmjs.org/package/changelog) | 365 | | [`grunt‑attention`](https://github.com/dylang/grunt-attention) | Display attention-grabbing messages in the terminal | [![grunt-attention](https://img.shields.io/npm/dm/grunt-attention.svg?style=flat-square)](https://www.npmjs.org/package/grunt-attention) | 366 | | [`observatory`](https://github.com/dylang/observatory) | Beautiful UI for showing tasks running on the command line. | [![observatory](https://img.shields.io/npm/dm/observatory.svg?style=flat-square)](https://www.npmjs.org/package/observatory) | 367 | | [`anthology`](https://github.com/dylang/anthology) | Module information and stats for any @npmjs user | [![anthology](https://img.shields.io/npm/dm/anthology.svg?style=flat-square)](https://www.npmjs.org/package/anthology) | 368 | | [`grunt‑cat`](https://github.com/dylang/grunt-cat) | Echo a file to the terminal. Works with text, figlets, ascii art, and full-color ansi. | [![grunt-cat](https://img.shields.io/npm/dm/grunt-cat.svg?style=flat-square)](https://www.npmjs.org/package/grunt-cat) | 369 | 370 | _This list was generated using [anthology](https://github.com/dylang/anthology)._ 371 | 372 | 373 | ### License 374 | Copyright (c) 2015 Dylan Greene, contributors. 375 | 376 | Released under the [MIT license](https://tldrlegal.com/license/mit-license). 377 | 378 | Screenshots are [CC BY-SA](http://creativecommons.org/licenses/by-sa/4.0/) (Attribution-ShareAlike). 379 | 380 | *** 381 | _Generated using [grunt-readme](https://github.com/assemble/grunt-readme) with [grunt-templates-dylang](https://github.com/dylang/grunt-templates-dylang) on Wednesday, November 11, 2015._ 382 | _To make changes to this document look in `/templates/readme/` 383 | 384 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grunt-prompt", 3 | "description": "Interactive prompt for your Grunt config using console checkboxes, text input with filtering, password fields.", 4 | "version": "1.3.2", 5 | "author": { 6 | "name": "Dylan Greene", 7 | "email": "dylang@gmail.com" 8 | }, 9 | "homepage": "https://github.com/dylang/grunt-prompt", 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/dylang/grunt-prompt.git" 13 | }, 14 | "license": "MIT", 15 | "main": "Gruntfile.js", 16 | "scripts": { 17 | "test": "grunt mochacli --stack" 18 | }, 19 | "devDependencies": { 20 | "chai": "^3.4.1", 21 | "chalk": "^1.1.1", 22 | "grunt": "^0.4.1", 23 | "grunt-cli": "^0.1.13", 24 | "grunt-contrib-jshint": "^0.11.3", 25 | "grunt-mocha-cli": "^2.0.0", 26 | "grunt-notify": "^0.4.1", 27 | "grunt-release": "^0.13.0", 28 | "grunt-templates-dylang": "^1.0.12", 29 | "load-grunt-tasks": "^3.3.0", 30 | "semver": "^5.0.3" 31 | }, 32 | "keywords": [ 33 | "gruntplugin", 34 | "inquery", 35 | "prompt", 36 | "gui", 37 | "console", 38 | "terminal", 39 | "UI", 40 | "interactive", 41 | "questions" 42 | ], 43 | "dependencies": { 44 | "inquirer": "^0.11.0", 45 | "lodash": "^3.10.1" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tasks/prompt.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-prompt 3 | * https://github.com/dylang/grunt-prompt 4 | * 5 | * Copyright (c) 2013 Dylan Greene 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | module.exports = function (grunt) { 12 | grunt.registerMultiTask('prompt', 'Interactive command line user prompts.', function () { 13 | 14 | var inquirer = require('inquirer'), 15 | options = this.options(), 16 | _ = require('lodash'); 17 | 18 | var questions = options.questions; 19 | 20 | function addSeparator(choices) { 21 | if (!choices || _.isFunction(choices)) { 22 | return choices; 23 | } 24 | 25 | return choices.map(function(choice){ 26 | if (choice === '---' || !choice || (choice && choice.separator)) { 27 | return new inquirer.Separator(choice && choice.separator); 28 | } 29 | return choice; 30 | }); 31 | } 32 | 33 | 34 | if (questions) { 35 | var done = this.async(); 36 | 37 | questions = questions.map(function(question){ 38 | // config just made more sense than name, but we accept both 39 | question.name = question.config || question.name; 40 | question.choices = addSeparator(question.choices); 41 | return question; 42 | }); 43 | 44 | inquirer.prompt( questions, function( answers ) { 45 | _.forEach(answers, function(answer, configName){ 46 | grunt.config(configName, answer); 47 | }); 48 | var delegateAsync = false; 49 | if (_.isFunction(options.then)) { 50 | delegateAsync = options.then(answers, done); 51 | } 52 | if (!delegateAsync) { 53 | done(); 54 | } 55 | }); 56 | } 57 | }); 58 | }; 59 | -------------------------------------------------------------------------------- /templates/grunt-prompt.md: -------------------------------------------------------------------------------- 1 | `Grunt-prompt`'s UI is powered by the amazing [Inquirer](https://github.com/SBoudrias/Inquirer.js), a project created by Simon Boudrias. 2 | 3 | {%= _.doc('readme/screenshots.md') %} 4 | 5 | {%= _.doc('readme/overview.md') %} 6 | 7 | {%= _.doc('readme/options.md') %} 8 | 9 | {%= _.doc('readme/results.md') %} 10 | 11 | {%= _.doc('readme/examples.md') %} 12 | 13 | {%= _.doc('readme/tests.md') %} 14 | 15 | {%= _.doc('readme/changelog.md') %} -------------------------------------------------------------------------------- /templates/readme/changelog.md: -------------------------------------------------------------------------------- 1 | ## Release History 2 | * **1.3.0** - 26 Oct 2014 - Add {{done}} callback for {{then}}. 3 | * **1.2.1** - 4 Oct 2014 - Separator can be '' or { separator: 'any string' }. Fixed it so choices can be strings again. 4 | * **1.2.0** - 4 Oct 2014 - Separator in choices can be a falsey value or string 5 | * **1.1.0** - 4 Mar 2014 - Messages can be functions instead of strings for dynamic questions. 6 | * **1.0.0** - 4 Feb 2014 - Dropping support for Node 0.8. 7 | * **0.2.2** - 4 Feb 2014 - Updated readme to make it auto-generated. 8 | * **0.2.1** - 4 Feb 2014 - Fix bug when using a function to provide choices. 9 | * **0.2.0** - 26 Jan 2014 - Added `then` option which runs after questions. Improved docs. 10 | * **0.1.1** - 27 July 2013 - Some documentation cleanup, better screenshots, new example code in the gruntfile, reomved unused tests. 11 | * **0.1.0** - 18 July 2013 - First version, after an exhausting but fun day with the family at Hershey Park. 12 | -------------------------------------------------------------------------------- /templates/readme/examples.md: -------------------------------------------------------------------------------- 1 | ## Usage Examples 2 | 3 | {%= screenshot('grunt-prompt with grunt-bump', 'https://f.cloud.github.com/assets/51505/2119082/78171246-9142-11e3-970a-f64f2002ad4e.png') %} 4 | 5 | This is an example of how `grunt-prompt` for something like [grunt-bump](https://github.com/vojtajina/grunt-bump) which makes it easy to 6 | update your project's version in the `package.json`, `bower.json`, and `git tag`. 7 | 8 | ```js 9 | prompt: { 10 | bump: { 11 | options: { 12 | questions: [ 13 | { 14 | config: 'bump.increment', 15 | type: 'list', 16 | message: 'Bump version from ' + '<%= pkg.version %>' + ' to:', 17 | choices: [ 18 | { 19 | value: 'build', 20 | name: 'Build: '+ (currentVersion + '-?') + ' Unstable, betas, and release candidates.' 21 | }, 22 | { 23 | value: 'patch', 24 | name: 'Patch: ' + semver.inc(currentVersion, 'patch') + ' Backwards-compatible bug fixes.' 25 | }, 26 | { 27 | value: 'minor', 28 | name: 'Minor: ' + semver.inc(currentVersion, 'minor') + ' Add functionality in a backwards-compatible manner.' 29 | }, 30 | { 31 | value: 'major', 32 | name: 'Major: ' + semver.inc(currentVersion, 'major') + ' Incompatible API changes.' 33 | }, 34 | { 35 | value: 'custom', 36 | name: 'Custom: ?.?.? Specify version...' 37 | } 38 | ] 39 | }, 40 | { 41 | config: 'bump.version', 42 | type: 'input', 43 | message: 'What specific version would you like', 44 | when: function (answers) { 45 | return answers['bump.increment'] === 'custom'; 46 | }, 47 | validate: function (value) { 48 | var valid = semver.valid(value); 49 | return !!valid || 'Must be a valid semver, such as 1.2.3-rc1. See http://semver.org/ for more details.'; 50 | } 51 | }, 52 | { 53 | config: 'bump.files', 54 | type: 'checkbox', 55 | message: 'What should get the new version:', 56 | choices: [ 57 | { 58 | value: 'package', 59 | name: 'package.json' + (!grunt.file.isFile('package.json') ? ' not found, will create one' : ''), 60 | checked: grunt.file.isFile('package.json') 61 | }, 62 | { 63 | value: 'bower', 64 | name: 'bower.json' + (!grunt.file.isFile('bower.json') ? ' not found, will create one' : ''), 65 | checked: grunt.file.isFile('bower.json') 66 | }, 67 | { 68 | value: 'git', 69 | name: 'git tag', 70 | checked: grunt.file.isDir('.git') 71 | } 72 | ] 73 | } 74 | ] 75 | } 76 | } 77 | } 78 | ``` 79 | -------------------------------------------------------------------------------- /templates/readme/options.md: -------------------------------------------------------------------------------- 1 | ### Options 2 | 3 | #### config 4 | 5 | Type: `String` _required_ 6 | 7 | This is used for three things: 8 | 9 | * It will set or overwrite the config of other Grunt tasks: `config: 'jshint.allFiles.reporter'` 10 | * The key in the resulting `answers` object: `if (answers['jshint.allFiles.reporter'] === 'custom') {...` 11 | * It can be an arbitrary value read using `grunt.config`: `grunt.config('jshint.allFiles.reporter')` 12 | 13 | #### type 14 | 15 | Type: `String` _required_ 16 | 17 | Type of question to ask: 18 | 19 | * `list`: use arrow keys to pick one choice. Returns a string. 20 | * `checkbox`: use arrow keys and space bar to pick multiple items. Returns an array. 21 | * `confirm`: Yes/no. Returns a boolean. 22 | * `input`: Free text input. Returns a string. 23 | * `password`: Masked input. Returns a string. 24 | 25 | Here's an example of each type: 26 | 27 | {%= screenshot('grunt-prompt example', 'https://f.cloud.github.com/assets/51505/867636/e727abfc-f717-11e2-997e-6b97e24593c3.gif') %} 28 | 29 | The documentation for [Inquiry](https://github.com/SBoudrias/Inquirer.js) has [more details about type](https://github.com/SBoudrias/Inquirer.js#prompts-type) as well as additional typess. 30 | 31 | #### message 32 | 33 | Type: `String|function(answers):String` _required_ 34 | 35 | The question to ask the user. If it's a function, it needs to return a string. The first parameter of this function will be an array containing all previously supplied answers. This allows you to customize the message based on the results of previous questions. 36 | 37 | Hint: keep it short, users hate to read. 38 | 39 | #### default 40 | 41 | Type: `String`/`Array`/`Boolean`/'function' _optional_ 42 | 43 | Default value used when the user just hits Enter. *If a `value` field is not provided, the filter value must match the `name` exactly.* 44 | 45 | #### choices 46 | 47 | For `question types 'list' and 'checkbox'`: Type: `array of hashes` 48 | 49 | * `name` The label that is displayed in the UI. 50 | * `value` _optional_ Value returned. When not used the name is used instead. 51 | * `checked` _optional_ Choose the option by default. _Only for checkbox._ 52 | 53 | ``` 54 | choices: [ 55 | { name: 'jshint', checked: true }, 56 | { name: 'jslint' }, 57 | { name: 'eslint' }, 58 | '---', // puts in a non-selectable separator. Can be a string or '---' for default. 59 | { name: 'I like to live dangerously', value: 'none' } 60 | ] 61 | ``` 62 | 63 | #### validate 64 | 65 | Type: `function(value)` _optional_ 66 | 67 | Return `true` if it is valid (true `true`, not a truthy value). 68 | Return `string` message if it is not valid. 69 | 70 | #### filter 71 | 72 | Type: `function(value)` _optional_ 73 | 74 | Use a modified version of the input for the answer. Useful for stripping extra characters, converting strings to integers. 75 | 76 | #### when 77 | 78 | Type: `function(answers)` _optional_ 79 | 80 | Choose when this question is asked. Perfect for asking questions based on the results of previous questions. 81 | 82 | ##### then 83 | 84 | Type: `function(results, done):Boolean` _optional_ 85 | 86 | Runs after all questions have been asked. 87 | 88 | The ```done``` parameter is optional, and can be used for async operations in your handler. 89 | 90 | When you return ```true``` from this function, the grunt-prompt code will not complete the async, so you are able to do your own async operations and call ```done()``` yourself. 91 | 92 | ``` 93 | config: 94 | prompt: { 95 | demo: { 96 | options: { 97 | questions: [ 98 | .. 99 | ], 100 | then: function(results, done) { 101 | someAsyncFunction(function () { 102 | done(); 103 | }); 104 | return true; 105 | } 106 | } 107 | } 108 | } 109 | 110 | ``` 111 | -------------------------------------------------------------------------------- /templates/readme/overview.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | In your project's Gruntfile, add a section named `prompt` to the data object passed into `grunt.initConfig()`. 3 | 4 | `grunt-prompt` is a multi-task. This means you can create multiple prompts. 5 | 6 | ```js 7 | grunt.initConfig({ 8 | prompt: { 9 | target: { 10 | options: { 11 | questions: [ 12 | { 13 | config: 'config.name', // arbitrary name or config for any other grunt task 14 | type: '', // list, checkbox, confirm, input, password 15 | message: 'String|function(answers)', // Question to ask the user, function needs to return a string, 16 | default: 'value', // default value if nothing is entered 17 | choices: 'Array|function(answers)', 18 | validate: function(value), // return true if valid, error message if invalid. works only with type:input 19 | filter: function(value), // modify the answer 20 | when: function(answers) // only ask this question when this function returns true 21 | } 22 | ] 23 | } 24 | }, 25 | }, 26 | }) 27 | ``` 28 | -------------------------------------------------------------------------------- /templates/readme/results.md: -------------------------------------------------------------------------------- 1 | ## How to use the results in your Gruntfile 2 | 3 | You can also modify how tasks will work by changing options for other tasks. 4 | You do not need to write code to do this, it's all in the `config` var. 5 | 6 | Here we will let the user choose what Mocha reporter to use. 7 | 8 | ```js 9 | config: 10 | prompt: { 11 | mochacli: { 12 | options: { 13 | questions: [ 14 | { 15 | config: 'mochacli.options.reporter' 16 | type: 'list' 17 | message: 'Which Mocha reporter would you like to use?', 18 | default: 'spec' 19 | choices: ['dot', 'spec', 'nyan', 'TAP', 'landing', 'list', 20 | 'progress', 'json', 'JSONconv', 'HTMLconv', 'min', 'doc'] 21 | } 22 | ] 23 | } 24 | } 25 | } 26 | 27 | ``` 28 | 29 | and create a shortcut: 30 | 31 | ``` 32 | grunt.registerTask('test', 33 | [ 34 | 'prompt:mochacli', 35 | 'mochacli' 36 | ]); 37 | 38 | ``` 39 | 40 | And run it: 41 | 42 | ``` 43 | $ grunt test 44 | ``` 45 | 46 | {%= screenshot('grunt-prompt setting up Mocha', 'https://f.cloud.github.com/assets/51505/983227/aabe4b6e-084a-11e3-94cd-514371c24059.gif') %} 47 | 48 | ## How can values be accessed from my own code? 49 | 50 | This `config` value is accessible to all other `grunt` tasks via `grunt.config('')`. 51 | 52 | If you had this: 53 | 54 | ```js 55 | config: 'validation' 56 | ``` 57 | 58 | Then later on in your custom task can access it like this: 59 | 60 | ```js 61 | var validation = grunt.config('validation'); 62 | ``` -------------------------------------------------------------------------------- /templates/readme/screenshots.md: -------------------------------------------------------------------------------- 1 | {%= screenshot('grunt-prompt in action', 'https://f.cloud.github.com/assets/51505/867636/e727abfc-f717-11e2-997e-6b97e24593c3.gif') %} -------------------------------------------------------------------------------- /templates/readme/tests.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dylang/grunt-prompt/4d2d001273b78304f9051e8034cd7ca151a1defa/templates/readme/tests.md -------------------------------------------------------------------------------- /test/prompt.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = require('chai').expect; 4 | 5 | describe('grunt-prompt', function(){ 6 | 7 | describe('silly stuff', function(){ 8 | 9 | it('loads', function(){ 10 | var prompt = require('../tasks/prompt'); 11 | console.log('--- The best way to test this is to just run `grunt` or `grunt prompt results`. ----') 12 | expect(prompt).to.exist; 13 | }); 14 | }); 15 | }); --------------------------------------------------------------------------------