├── app ├── templates │ ├── gitignore │ ├── test │ │ ├── data │ │ │ ├── sample-a.json │ │ │ └── sample-b.json │ │ └── _spec.js │ ├── editorconfig │ ├── eslintrc │ ├── _package.json │ ├── _README.md │ ├── _Gruntfile.js │ └── tasks │ │ └── _task.js └── index.js ├── assets └── cli.png ├── .gitignore ├── README.md ├── package.json └── LICENSE /app/templates/gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | tmp 4 | -------------------------------------------------------------------------------- /app/templates/test/data/sample-a.json: -------------------------------------------------------------------------------- 1 | { 2 | "foo" : "moo", 3 | "bar" : [1, 2, 3] 4 | } 5 | -------------------------------------------------------------------------------- /assets/cli.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hirokith/generator-grunt-awesome-plugin/master/assets/cli.png -------------------------------------------------------------------------------- /app/templates/test/data/sample-b.json: -------------------------------------------------------------------------------- 1 | { 2 | "noz" : { 3 | "yuu" : "gork", 4 | "boo" : false 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /app/templates/editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /app/templates/eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "indent": [ 4 | 2, 5 | 4 6 | ], 7 | "quotes": [ 8 | 2, 9 | "single" 10 | ], 11 | "linebreak-style": [ 12 | 2, 13 | "unix" 14 | ], 15 | "semi": [ 16 | 2, 17 | "always" 18 | ] 19 | }, 20 | "env": { 21 | "node": true 22 | }, 23 | "extends": "eslint:recommended" 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # generator-grunt-awesome-plugin 2 | A Yeoman generator for Grunt plugin, the awesome way 3 | 4 | ## Install 5 | 6 | ### Yeoman 7 | 8 | ```sh 9 | npm install -g yo 10 | ``` 11 | 12 | ### This generator 13 | 14 | ```sh 15 | npm install -g generator-grunt-awesome-plugin 16 | ``` 17 | 18 | ## Create a brand new Grunt plugin 19 | 20 | ```sh 21 | mkdir grunt-moo 22 | cd grunt-moo 23 | yo grunt-awesome-plugin 24 | ``` 25 | 26 | ![Generate a plugin](https://raw.githubusercontent.com/krampstudio/generator-grunt-awesome-plugin/master/assets/cli.png) 27 | 28 | ## History 29 | 30 | * [0.1.0](https://github.com/krampstudio/generator-grunt-awesome-plugin/releases/tag/v0.1.0) : Initial version 31 | 32 | ## License 33 | 34 | Bertrand Chevrier - 2016 - [MIT](https://spdx.org/licenses/MIT) 35 | 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-grunt-awesome-plugin", 3 | "version": "0.1.0", 4 | "description": "A Yeoman generator for Grunt plugin, the awesome way", 5 | "files": [ 6 | "app" 7 | ], 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/krampstudio/generator-grunt-awesome-plugin.git" 11 | }, 12 | "keywords": [ 13 | "yeoman-generator", 14 | "yeoman", 15 | "generator", 16 | "grunt", 17 | "gruntplugin", 18 | "yo", 19 | "grunt" 20 | ], 21 | "author": "Bertrand Chevrier ", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/krampstudio/generator-grunt-awesome-plugin/issues" 25 | }, 26 | "homepage": "https://github.com/krampstudio/generator-grunt-awesome-plugin#readme", 27 | "dependencies": { 28 | "yeoman-generator": "^0.23.3" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/templates/_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%=name%>", 3 | "version": "<%=version%>", 4 | "description": "<%=description%>", 5 | "author": "<%=authorName%> <<%=authorEmail%>>", 6 | "keywords": <%-keywords%>, 7 | "license": "<%=license%>", 8 | "main": "Gruntfile.js", 9 | "devDependencies": { 10 | "chai": "^3.5.0", 11 | "mocha": "^2.5.3", 12 | "grunt": "^<%=gruntVersion%>", 13 | "grunt-contrib-clean": "^1.0.0", 14 | "grunt-contrib-watch": "^1.0.0", 15 | "grunt-eslint": "^18.0.0", 16 | "grunt-mocha-test": "^0.12.7" 17 | }, 18 | "scripts": { 19 | "test": "grunt test" 20 | }, 21 | <% if(github) { %> 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/<%=githubName%>/<%=name%>.git" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/<%=githubName%>/<%=name%>/issues" 28 | }, 29 | "homepage": "https://github.com/<%=githubName%>/<%=name%>#readme" 30 | <% } %> 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Bertrand CHEVRIER 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/templates/_README.md: -------------------------------------------------------------------------------- 1 | # <%=name%> 2 | 3 | > <%=description%> 4 | 5 | ## Getting Started 6 | This plugin requires Grunt. 7 | 8 | If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command: 9 | 10 | ```shell 11 | npm install <%=name%> --save-dev 12 | ``` 13 | 14 | Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript: 15 | 16 | ```js 17 | grunt.loadNpmTasks('<%=name%>'); 18 | ``` 19 | 20 | ## The "<%=taskName%>" task 21 | 22 | ### Overview 23 | In your project's Gruntfile, add a section named `<%=taskName%>` to the data object passed into `grunt.initConfig()`. 24 | 25 | ```js 26 | grunt.initConfig({ 27 | foodfact: { 28 | options: { 29 | foo : true 30 | }, 31 | files: { 32 | 'dest.json' : ['*.json'] 33 | } 34 | }, 35 | }) 36 | ``` 37 | 38 | ## Test 39 | 40 | ```sh 41 | npm test 42 | ``` 43 | 44 | ## Contributing 45 | 46 | In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/). 47 | 48 | ## Release History 49 | 50 | * _<%=version%>_ initial release 51 | 52 | ## License 53 | 54 | See [<%=license%>](https://spdx.org/licenses/<%=license%>) 55 | -------------------------------------------------------------------------------- /app/templates/_Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | grunt.initConfig({ 4 | 5 | eslint: { 6 | all: [ 7 | 'Gruntfile.js', 8 | 'tasks/*.js' 9 | ] 10 | }, 11 | 12 | '<%=taskName%>': { 13 | test: { 14 | options: { 15 | foo : true 16 | }, 17 | files: { 18 | 'test/data/out/dest.json' : ['test/data/*.json'] 19 | } 20 | } 21 | }, 22 | 23 | clean : { 24 | options: { 25 | force : true 26 | }, 27 | test: ['test/data/out/*'] 28 | }, 29 | 30 | mochaTest: { 31 | options: { 32 | reporter: 'spec' 33 | }, 34 | test: { 35 | src: ['test/<%=taskName%>_spec.js'] 36 | } 37 | }, 38 | 39 | watch : { 40 | test: { 41 | files : ['tasks/*.js', 'test/*_spec.js'], 42 | tasks: ['test'], 43 | options: { 44 | debounceDelay: 2000 45 | } 46 | } 47 | } 48 | }); 49 | 50 | grunt.loadNpmTasks('grunt-eslint'); 51 | grunt.loadNpmTasks('grunt-contrib-clean'); 52 | grunt.loadNpmTasks('grunt-contrib-watch'); 53 | grunt.loadNpmTasks('grunt-mocha-test'); 54 | 55 | grunt.loadTasks('tasks'); 56 | 57 | 58 | grunt.registerTask('test', 'Run tests', [ 59 | 'clean:test', 60 | '<%=taskName%>:test', 61 | 'mochaTest:test' 62 | ]); 63 | 64 | grunt.registerTask('devtest', ['watch:test']); 65 | }; 66 | 67 | -------------------------------------------------------------------------------- /app/templates/tasks/_task.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Extend a source object with a destination object 3 | * @param {Object} source - the reference will be modified 4 | * @param {Object} dest - the object to assign to the source 5 | * @returns {Object} the source reference 6 | */ 7 | var extend = function extend(source, dest){ 8 | if(typeof Object.assign === 'function'){ 9 | return Object.assign(source, dest); 10 | } else { 11 | return require('util')._extend(source, dest); 12 | } 13 | }; 14 | 15 | 16 | module.exports = function(grunt) { 17 | 18 | /** 19 | * Register the Grunt task <%=taskName%> 20 | */ 21 | grunt.registerMultiTask('<%=taskName%>', '<%=description%>', function() { 22 | var options = this.options(); 23 | var done = this.async(); 24 | var count = 0; 25 | 26 | // Placeholder task 27 | // merge everything into the destination 28 | // the options and the source files JSON 29 | 30 | this.files.forEach(function(file){ 31 | 32 | var content = extend({}, options); 33 | var dest = file.dest; 34 | 35 | grunt.log.debug("base content %j", content); 36 | 37 | file.src.forEach(function(source){ 38 | 39 | grunt.log.debug("adding %s", source); 40 | extend(content, grunt.file.readJSON(source)); 41 | grunt.log.debug("content is now %j", content); 42 | }); 43 | 44 | grunt.log.debug("writing to %s", dest); 45 | 46 | grunt.file.write(dest, JSON.stringify(content)); 47 | 48 | if(grunt.file.exists(dest)){ 49 | 50 | grunt.verbose.write("%s created", dest); 51 | count++; 52 | } else { 53 | grunt.fail.warn('Unable to write %s', dest); 54 | } 55 | }); 56 | if(count > 0){ 57 | grunt.log.ok( "%s %s created", count, (count > 1 ? 'files' : 'file')); 58 | } 59 | }); 60 | }; 61 | 62 | -------------------------------------------------------------------------------- /app/templates/test/_spec.js: -------------------------------------------------------------------------------- 1 | var describe = require('mocha').describe; 2 | var it = require('mocha').it; 3 | var expect = require('chai').expect; 4 | var grunt = require('grunt'); 5 | 6 | 7 | var dataDir = 'test/data/'; 8 | var destFile = dataDir + 'out/dest.json'; 9 | var sampleA = dataDir + 'sample-a.json'; 10 | var sampleB = dataDir + 'sample-b.json'; 11 | 12 | describe('<%=taskName%>', function(){ 13 | 14 | describe('test target', function(){ 15 | 16 | it('should have created the dest file', function(){ 17 | expect(grunt.file.exists(destFile)).to.be.equal(true); 18 | expect(grunt.file.read(destFile).length).to.be.above(0); 19 | }); 20 | 21 | it('should have merged the options', function(){ 22 | 23 | var content = grunt.file.readJSON(destFile); 24 | 25 | expect(content).to.be.an('object'); 26 | expect(content).to.include.keys('foo'); 27 | expect(content.foo).to.be.equal(true); 28 | }); 29 | 30 | it('should have merged the sample-a file', function(){ 31 | var aContent; 32 | var content = grunt.file.readJSON(destFile); 33 | 34 | expect(grunt.file.exists(sampleA).to.be.equal(true); 35 | expect(grunt.file.read(sampleA).length).to.be.above(0); 36 | 37 | aContent = grunt.file.readJSON(sampleA); 38 | 39 | expect(aContent).to.be.an('object'); 40 | expect(aContent).to.include.keys('boo'); 41 | expect(aContent.boo).to.be.equal('moo'); 42 | expect(aContent).to.include.keys('bar'); 43 | expect(aContent.bar).to.be.equal([1, 2, 3]); 44 | 45 | expect(content).to.include.keys('boo'); 46 | expect(content.boo).to.be.equal(aContent.boo); 47 | 48 | expect(content).to.include.keys('bar'); 49 | expect(content.bar).to.be.equal(aContent.bar); 50 | }); 51 | 52 | it('should have merged the sample-b file', function(){ 53 | var bContent; 54 | var content = grunt.file.readJSON(destFile); 55 | 56 | expect(grunt.file.exists(sampleA).to.be.equal(true); 57 | expect(grunt.file.read(sampleA).length).to.be.above(0); 58 | 59 | bContent = grunt.file.readJSON(sampleA); 60 | 61 | expect(bContent).to.be.an('object'); 62 | expect(bContent).to.include.keys('noz'); 63 | expect(bContent.noz).to.be.an('object'); 64 | 65 | expect(content).to.include.keys('noz'); 66 | 67 | expect(content.noz).to.be.deep.equal(bContent.noz); 68 | }); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The Grunt plugin generator 3 | * @author Bertrand Chevrier 4 | * @license MIT 5 | */ 6 | 7 | var generators = require('yeoman-generator'); 8 | 9 | /** 10 | * Normalize the name from "Grunt Moo" to "grunt-moo" 11 | */ 12 | var normalizeName = function normalizeName(name) { 13 | return name.replace(/\s/g, '-').toLowerCase(); 14 | }; 15 | 16 | /** 17 | * @see http://yeoman.io 18 | */ 19 | module.exports = generators.Base.extend({ 20 | 21 | constructor: function() { 22 | generators.Base.apply(this, arguments); 23 | }, 24 | 25 | /** 26 | * Prompt step, ask the user for details 27 | * @see https://github.com/SBoudrias/Inquirer.js for the prompt function syntax 28 | */ 29 | prompting: { 30 | 31 | //all properties usefull to the package.json 32 | packageProps: function packageProps() { 33 | var self = this; 34 | return this.prompt([{ 35 | type: 'input', 36 | name: 'name', 37 | message: 'Your Grunt plugin name', 38 | default: normalizeName(this.appname), 39 | validate: function(input) { 40 | if (/^grunt-contrib/i.test(input)) { 41 | return "Hum, grunt-contrib plugins names are reserved for the Grunt core team, sorry"; 42 | } 43 | if (/^grunt-contrib/i.test(input)) { 44 | return "By convention Grunt plugins starts with 'grunt-'"; 45 | } 46 | return true; 47 | }, 48 | filter: normalizeName 49 | }, { 50 | type: 'input', 51 | name: 'description', 52 | message: 'The plugin description', 53 | default: 'The best Grunt plugin' 54 | }, { 55 | type: 'input', 56 | name: 'version', 57 | message: 'The plugin version', 58 | default: '0.1.0' 59 | }, { 60 | type: 'list', 61 | name: 'gruntVersion', 62 | message: 'The version of Grunt', 63 | default: '1.0.0', 64 | choices: ['1.0.0', '0.4.5'] 65 | }, { 66 | type: 'input', 67 | name: 'keywords', 68 | message: 'Plugin keywords', 69 | filter: function(input) { 70 | var keywords = ['gruntplugin'].concat(input.split(/[\s,]+/g)); 71 | return JSON.stringify(keywords); 72 | } 73 | }, { 74 | type: 'list', 75 | name: 'license', 76 | message: 'License', 77 | default: 'MIT', 78 | choices: ['MIT', 'GPL-3.0', 'GPL-2.0', 'AGPL-3.0', 'LGPL-3.0', 'Apache-2.0', 'BSD-2-Clause', 'BSD-3-Clause', 'MPL-2.0', 'Unlicense', 'Other'] 79 | }, { 80 | type: 'input', 81 | name: 'authorName', 82 | message: 'Your name', 83 | default: this.user.git.name(), 84 | }, { 85 | type: 'input', 86 | name: 'authorEmail', 87 | message: 'Your email', 88 | default: this.user.git.email(), 89 | }, { 90 | type: 'boolean', 91 | name: 'github', 92 | message: 'Is your plugin hosted on Github ?', 93 | }]).then(function(answers) { 94 | self.props = answers; 95 | self.props.taskName = self.props.name.replace(/^grunt-/i, ''); 96 | }); 97 | }, 98 | 99 | //specific to the repository, need to know if we use github before 100 | repoProps: function repoProps() { 101 | var self = this; 102 | return this.prompt([{ 103 | type: 'input', 104 | name: 'githubName', 105 | message: 'Your github pseudo ?', 106 | when: this.props.github 107 | }]).then(function(answers) { 108 | self.props.githubName = answers.githubName 109 | }); 110 | } 111 | }, 112 | 113 | /** 114 | * Create the file sytem, by either copying or using templates 115 | * @see http://www.embeddedjs.com/ for the template syntax 116 | */ 117 | writing: { 118 | packageJSON: function packageJSON() { 119 | this.fs.copyTpl( 120 | this.templatePath('_package.json'), 121 | this.destinationPath('package.json'), 122 | this.props 123 | ); 124 | }, 125 | git: function git() { 126 | this.fs.copy( 127 | this.templatePath('gitignore'), 128 | this.destinationPath('.gitignore') 129 | ); 130 | }, 131 | editorconfig: function editorconfig() { 132 | this.fs.copy( 133 | this.templatePath('editorconfig'), 134 | this.destinationPath('.editorconfig') 135 | ); 136 | }, 137 | eslintrc: function eslintrc() { 138 | this.fs.copy( 139 | this.templatePath('eslintrc'), 140 | this.destinationPath('.eslintrc') 141 | ); 142 | }, 143 | grunttask: function grunttask() { 144 | this.fs.copyTpl( 145 | this.templatePath('tasks/_task.js'), 146 | this.destinationPath('tasks/' + this.props.taskName + '.js'), 147 | this.props 148 | ); 149 | }, 150 | gruntfile: function gruntfile() { 151 | this.fs.copyTpl( 152 | this.templatePath('_Gruntfile.js'), 153 | this.destinationPath('Gruntfile.js'), 154 | this.props 155 | ); 156 | }, 157 | tests: function tests() { 158 | this.fs.copy( 159 | this.templatePath('test/data/sample-a.json'), 160 | this.destinationPath('test/data/sample-a.json') 161 | ); 162 | this.fs.copy( 163 | this.templatePath('test/data/sample-b.json'), 164 | this.destinationPath('test/data/sample-b.json') 165 | ); 166 | this.fs.copyTpl( 167 | this.templatePath('test/_spec.js'), 168 | this.destinationPath('test/' + this.props.taskName + '_spec.js'), 169 | this.props 170 | ); 171 | }, 172 | readme : function readme(){ 173 | this.fs.copyTpl( 174 | this.templatePath('_README.md'), 175 | this.destinationPath('README.md'), 176 | this.props 177 | ); 178 | } 179 | }, 180 | 181 | /** 182 | * Install and setup step 183 | */ 184 | install: function install() { 185 | this.npmInstall(); 186 | 187 | if (this.props.gruntVersion !== '1.0.0') { 188 | this.log('Do not forget to run : npm install -g grunt-cli'); 189 | } 190 | 191 | this.log('Here we go! Your plugin is ready to use!'); 192 | } 193 | }); 194 | 195 | --------------------------------------------------------------------------------