├── .editorconfig ├── .gitattributes ├── .gitignore ├── .jshintrc ├── .travis.yml ├── CUSTOMIZING.md ├── LICENSE ├── README.md ├── UPGRADING.md ├── app ├── index.js └── templates │ └── skeleton │ ├── .bowerrc │ ├── .editorconfig │ ├── .gitignore │ ├── .jshintrc │ ├── Gruntfile.js │ ├── app.js │ ├── app.less │ ├── bower.json │ ├── gulpfile.js │ ├── index.html │ └── package.json ├── directive ├── index.js └── templates │ ├── complex │ ├── directive-spec.js │ ├── directive.html │ ├── directive.js │ └── directive.less │ └── simple │ ├── directive-spec.js │ └── directive.js ├── filter ├── index.js └── templates │ ├── filter-spec.js │ └── filter.js ├── modal ├── index.js └── templates │ ├── modal-spec.js │ ├── modal.html │ ├── modal.js │ └── modal.less ├── module ├── index.js └── templates │ ├── module.js │ └── module.less ├── package.json ├── partial ├── index.js └── templates │ ├── partial-spec.js │ ├── partial.html │ ├── partial.js │ └── partial.less ├── service ├── index.js └── templates │ ├── service-spec.js │ └── service.js ├── test ├── test-creation.js └── test-load.js └── utils.js /.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 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | temp/ 3 | .DS_Store -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "es5": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 4, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "white": true 22 | } 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.8' 4 | - '0.10' 5 | -------------------------------------------------------------------------------- /CUSTOMIZING.md: -------------------------------------------------------------------------------- 1 | Customizing the Subgenerators 2 | ------------- 3 | 4 | You may need to customize the subgenerators if you use things like SASS, RequireJS, and more. The subgenerators can be extended and overriden through the `.yo-rc.json` file. The `.yo-rc.json` file can be configured with the following properties: 5 | 6 | #### partialTemplates 7 | Type: `String` 8 | 9 | Directory to load partial template files from. 10 | 11 | #### directiveSimpleTemplates 12 | Type: `String` 13 | 14 | Directory to load directive template files from if the user has chosen to create a directive without an external partial. 15 | 16 | #### directiveComplexTemplates 17 | Type: `String` 18 | 19 | Directory to load directive template files from if the user has chosen to create a directive with an external partial. 20 | 21 | #### serviceTemplates 22 | Type: `String` 23 | 24 | Directory to load service template files from. 25 | 26 | #### filterTemplates 27 | Type: `String` 28 | 29 | Directory to load filter template files from 30 | 31 | #### inject 32 | Type: `Object` 33 | 34 | A map of file extensions where each extension key contains another map containing 3 properties (file, template, marker). The subgenerators use these options to inject script tags and import statements. These options can be extended to add new file types, or override to change the existing injection behavior. The file location that is to be generated can be accessed using the `filename` template variable. 35 | 36 | 37 | ## Example Configuration 38 | 39 | Here is an example configuration that matches the default behavior of the subgenerators: 40 | 41 | ```js 42 | { 43 | "uirouter": false, 44 | "partialDirectory": "partial/", 45 | "directiveDirectory": "directive/", 46 | "serviceDirectory": "service/", 47 | "filterDirectory": "filter/", 48 | "partialTemplates": "templates/partial", 49 | "directiveSimpleTemplates": "templates/simpleDirective", 50 | "directiveComplexTemplates": "templates/complexDirective", 51 | "serviceTemplates": "templates/service", 52 | "filterTemplates": "templates/filter", 53 | "moduleTemplates": "templates/module", 54 | "inject": { 55 | "js": { 56 | "file": "index.html", 57 | "marker": "", 58 | "template": "" 59 | }, 60 | "less": { 61 | "file": "app.less", 62 | "marker": "/* Add Component LESS Above */", 63 | "template": "@import \"<%= filename %>\";" 64 | } 65 | } 66 | } 67 | ``` 68 | 69 | If a given template property is missing, the default templates will be used. 70 | 71 | ## Template Directories 72 | 73 | Each template directory can contain any number of files for a given subgenerator. Each file will be read, run through the template engine, and then saved to the user specified destination. The name of the destination file will be derived from the name of the template file by replacing the type word (partial/directive/filter/service) with the name of the component. In other words, `partial-spec.js` becomes `whatever-spec.js`. 74 | 75 | ### Template variables 76 | 77 | All templates are valid underscore templates using the standard ERB-style delimiters. The templates will also have the following variables: 78 | 79 | * `appname` - name of the Angular app/module name 80 | * `name` - the name of the component entered by the user 81 | * `dir` - the name of the directory where the component will be placed (includes trailing backslash) 82 | * `ctrlname` - (partials only) name of the controller 83 | * `_` - Underscore.js with Underscore.string mixed in. This allows you to use code like `<%= _.camelize(name) %>` in the templates. 84 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2013 Chris Gross 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | | *Newer Modern Generator for Angular and Webpack* | 4 | | ----- | 5 | | _There is a new generator for Angular 1.5, Webpack, npm, SASS, and ES6. Check it out here:_ https://github.com/cgross/generator-cg-angular15 | 6 | 7 | --- 8 | 9 | #generator-cg-angular 10 | 11 | >Yeoman Generator for Enterprise Angular Projects 12 | 13 | This generator follows the [Angular Best Practice Guidelines for Project Structure](https://blog.angularjs.org/2014/02/an-angularjs-style-guide-and-best.html?_escaped_fragment_=). 14 | 15 | Features 16 | 17 | * Provides a directory structure geared towards large Angular projects. 18 | * Each controller, service, filter, and directive are placed in their own file. 19 | * All files related to a conceptual unit are placed together. For example, the controller, HTML, LESS, and unit test for a partial are placed together in the same directory. 20 | * Provides a ready-made Grunt build that produces an extremely optimized distribution. 21 | * Build uses [grunt-ng-annotate](https://github.com/olov/ng-annotate) so you don't have to use the Angular injection syntax for safe minification (i.e. you dont need `$inject` or `(['$scope','$http',...`. 22 | * `grunt serve` task allows you to run a simple development server with watch/livereload enabled. Additionally, JSHint and the appropriate unit tests are run for the changed files. 23 | * Integrates Bower for package management 24 | * Includes Yeoman subgenerators for directives, services, partials, filters, and modules. 25 | * Integrates LESS and includes Bootstrap via the source LESS files allowing you to reuse Bootstrap vars/mixins/etc. 26 | * Easily Testable - Each sub-generator creates a skeleton unit test. Unit tests can be run via `grunt test` and they run automatically during the grunt watch that is active during `grunt serve`. 27 | 28 | Directory Layout 29 | ------------- 30 | All subgenerators prompt the user to specify where to save the new files. Thus you can create any directory structure you desire, including nesting. The generator will create a handful of files in the root of your project including `index.html`, `app.js`, and `app.less`. You determine how the rest of the project will be structured. 31 | 32 | In this example, the user has chosen to group the app into an `admin` folder, a `search` folder, and a `service` folder. 33 | 34 | 35 | app.less ....................... main app-wide styles 36 | app.js ......................... angular module initialization and route setup 37 | index.html ..................... main HTML file 38 | Gruntfile.js ................... Grunt build file 39 | /admin ......................... example admin module folder 40 | admin.js ..................... admin module initialization and route setup 41 | admin.less ................... admin module LESS 42 | /admin-directive1 ............ angular directives folder 43 | admin-directive1.js ........ example simple directive 44 | admin-directive1-spec.js.... example simple directive unit test 45 | /admin-directive2 ............ example complex directive (contains external partial) 46 | admin-directive2.js ........ complex directive javascript 47 | admin-directive2.html ...... complex directive partial 48 | admin-directive2.less ...... complex directive LESS 49 | admin-directive2-spec.js ... complex directive unit test 50 | /admin-partial ............... example partial 51 | admin-partial.html ......... example partial html 52 | admin-partial.js ........... example partial controller 53 | admin-partial.less ......... example partial LESS 54 | admin-partial-spec.js ...... example partial unit test 55 | /search ........................ example search component folder 56 | my-filter.js ................. example filter 57 | my-filter-spec.js ............ example filter unit test 58 | /search-partial .............. example partial 59 | search-partial.html ........ example partial html 60 | search-partial.js .......... example partial controller 61 | search-partial.less ........ example partial LESS 62 | search-partial-spec.js ..... example partial unit test 63 | /service ....................... angular services folder 64 | my-service.js .............. example service 65 | my-service-spec.js ......... example service unit test 66 | my-service2.js ............. example service 67 | my-service2-spec.js ........ example service unit test 68 | /img ........................... images (not created by default but included in /dist if added) 69 | /dist .......................... distributable version of app built using grunt and Gruntfile.js 70 | /bower_component................ 3rd party libraries managed by bower 71 | /node_modules .................. npm managed libraries used by grunt 72 | 73 | Getting Started 74 | ------------- 75 | 76 | Prerequisites: Node, Grunt, Yeoman, and Bower. Once Node is installed, do: 77 | 78 | npm install -g grunt-cli yo bower 79 | 80 | Next, install this generator: 81 | 82 | npm install -g generator-cg-angular 83 | 84 | To create a project: 85 | 86 | mkdir MyNewAwesomeApp 87 | cd MyNewAwesomeApp 88 | yo cg-angular 89 | 90 | Grunt Tasks 91 | ------------- 92 | 93 | Now that the project is created, you have 3 simple Grunt commands available: 94 | 95 | grunt serve #This will run a development server with watch & livereload enabled. 96 | grunt test #Run local unit tests. 97 | grunt build #Places a fully optimized (minified, concatenated, and more) in /dist 98 | 99 | When `grunt serve` is running, any changed javascript files will be linted using JSHint as well as have their appropriate unit tests executed. Only the unit tests that correspond to the changed file will be run. This allows for an efficient test driven workflow. 100 | 101 | Yeoman Subgenerators 102 | ------------- 103 | 104 | There are a set of subgenerators to initialize empty Angular components. Each of these generators will: 105 | 106 | * Create one or more skeleton files (javascript, LESS, html, spec etc) for the component type. 107 | * Update index.html and add the necessary `script` tags. 108 | * Update app.less and add the @import as needed. 109 | * For partials, update the app.js, adding the necessary route call if a route was entered in the generator prompts. 110 | 111 | There are generators for `directive`,`partial`,`service`, `filter`, `module`, and `modal`. 112 | 113 | Running a generator: 114 | 115 | yo cg-angular:directive my-awesome-directive 116 | yo cg-angular:partial my-partial 117 | yo cg-angular:service my-service 118 | yo cg-angular:filter my-filter 119 | yo cg-angular:module my-module 120 | yo cg-angular:modal my-modal 121 | 122 | The name paramater passed (i.e. 'my-awesome-directive') will be used as the file names. The generators will derive appropriate class names from this parameter (ex. 'my-awesome-directive' will convert to a class name of 'MyAwesomeDirective'). Each sub-generator will ask for the folder in which to create the new skeleton files. You may override the default folder for each sub-generator in the `.yo-rc.json` file. 123 | 124 | The modal subgenerator is a convenient shortcut to create partials that work as modals for Bootstrap v3 and Angular-UI-Bootstrap v1.3 (both come preconfigured with this generator). If you choose not to use either of these libraries, simply don't use the modal subgenerator. 125 | 126 | Subgenerators are also customizable. Please read [CUSTOMIZING.md](CUSTOMIZING.md) for details. 127 | 128 | Submodules 129 | ------------- 130 | 131 | Submodules allow you to more explicitly separate parts of your application. Use the `yo cg-angular:module my-module` command and specify a new subdirectory to place the module into. Once you've created a submodule, running other subgenerators will now prompt you to select the module in which to place the new component. 132 | 133 | Preconfigured Libraries 134 | ------------- 135 | 136 | The new app will have a handful of preconfigured libraries included. This includes Angular 1.5, Bootstrap 3, AngularUI Bootstrap, FontAwesome, JQuery, LoDash, LESS, and Momentx. You may of course add to or remove any of these libraries. But the work to integrate them into the app and into the build process has already been done for you. 137 | 138 | Build Process 139 | ------------- 140 | 141 | The project will include a ready-made Grunt build that will: 142 | 143 | * Build all the LESS files into one minified CSS file. 144 | * Uses [grunt-angular-templates](https://github.com/ericclemmons/grunt-angular-templates) to turn all your partials into Javascript. 145 | * Uses [grunt-ng-annotate](https://github.com/olov/ng-annotate) to preprocess all Angular injectable methods and make them minification safe. Thus you don't have to use the array syntax. 146 | * Concatenates and minifies all Javascript into one file. 147 | * Replaces all appropriate script references in `index.html` with the minified CSS and JS files. 148 | * (Optionally) Minifies any images in `/img`. 149 | * Minifies the `index.html`. 150 | * Copies any extra files necessary for a distributable build (ex. Font-Awesome font files, etc). 151 | 152 | The resulting build loads only a few highly compressed files. 153 | 154 | The build process uses [grunt-dom-munger](https://github.com/cgross/grunt-dom-munger) to pull script references from the `index.html`. This means that **your index.html is the single source of truth about what makes up your app**. Adding a new library, new controller, new directive, etc does not require that you update the build file. Also the order of the scripts in your `index.html` will be maintained when they're concatenated. 155 | 156 | Importantly, grunt-dom-munger uses CSS attribute selectors to manage the parsing of the script and link tags. Its very easy to exclude certain scripts or stylesheets from the concatenated files. This is often the case if you're using a CDN. This can also be used to prevent certain development scripts from being included in the final build. 157 | 158 | * To prevent a script or stylesheet from being included in concatenation, put a `data-concat="false"` attribute on the link or script tag. This is currently applied for the `livereload.js` and `less.js` script tags. 159 | 160 | * To prevent a script or link tag from being removed from the finalized `index.html`, use a `data-remove="false"` attribute. 161 | 162 | 163 | Release History 164 | ------------- 165 | * 04/10/2016 - v3.30 - Upgrades to the Bower components including Angular to version 1.5. 166 | * 11/9/2014 - v3.2.0 - Switch from ngmin to ng-annotate. Disabling grunt-contrib-imagemin so Windows users don't encounter its issues. Subgenerators prompt for a name if not entered. Other fixes. 167 | * 7/6/2014 - v3.1.2 - Fix for directive template URLs with backslashes on Windows. 168 | * 6/10/2014 - v3.1.1 - Fix for backslashes being used in injected routes/tags on subgenerators. 169 | * 5/1/2014 - v3.1.0 - New subgenerators for modules and modals. Replaced grunt-contrib-jasmine with grunt-karma. Karma allows us to test against actual browsers other than PhantomJS. 170 | * 3/10/2014 - v3.0.2 - Fix for directive files not being named correctly. Fix for htmlmin from affecting some Bootstrap styles. 171 | * 3/03/2014 - v3.0.0 - All subgenerators now ask the user for a directory enabling any user-defined project structure. Gruntfile has been altered to allow scripts, partials, and LESS files to be located anywhere in the project directory structure. An option to use `angular-ui-router` is now available when initializing a new project. `js/setup.js` and `css/app.less` moved to `app.js` and `app.less`. `grunt server` is now `grunt serve`. Inside `index.html` all user script tags are grouped together instead of split out into groups for services/filters/etc. New ability to customize the subgenerators. 172 | * 2/10/2014 - v2.1.1 - Fix for the directive spec file named with a .less extension. 173 | * 1/06/2014 - v2.1.0 - Nice enhancements for unit testing. Specs are now placed in the same directory as the component they're testing. Additionally, unit tests are now run during `grunt server` allowing for an easy and efficient test-driven workflow. 174 | * 12/30/2013 - v2.0.0 - Big Update. Angular 1.2 and Bootstrap 3. Newer versions of Angular UI, Font Awesome, and JQuery. Lodash was replaced with Underscore. Lots of other small changes. 175 | * 9/06/2013 - V1.0.4 - Fixed templating issue with generated specs for `yo cg-angular:service` subgenerator. 176 | * 8/29/2013 - V1.0.3 - Renamed `/lib` back to `/bower_components` as clarity trumps brevity. Renamed `/bin` to `/dist`. Fixed spelling error in generated directive's js template location. Moved up to later version of `yeoman-generator` dependency to solve "Cannot read bold of undefined" error coming from Yeoman. JSHint options now read from `.jshintrc`. And more small stuff. 177 | * 7/08/2013 - V1.0.2 - Added utf8 charset to index.html. Fix for "EMFile, too many open files" on `grunt watch` by no longer watching the `lib` folder. 178 | * 6/20/2013 - v1.0.1 - Fixed a ton of known issues. Replaced `grunt-regarde` with `grunt-contrib-watch`. Fixed and tweaked the unit test specs and `grunt test`. Fixed issues with the build. Generator is now ready for real use. 179 | * 6/18/2013 - v1.0.0 - Initial release of template as Yeoman generator. 180 | -------------------------------------------------------------------------------- /UPGRADING.md: -------------------------------------------------------------------------------- 1 | Upgrading Projects from v2.x to v3.0 2 | ------------- 3 | 4 | The following guide will describe the steps needed to take a project generated using v2 of the generator and enable it for use with v3.0. 5 | 6 | The new v3.0 generator has moved some files, made small changes how script tags and @import statements are injected, and offers users the ability to create a completely custom directory structure. To convert a v2 project to v3.0, do the following: 7 | 8 | 1. Rename and move `js/setup.js` to `app.js`. Modify the related script tag in `index.html`. 9 | 2. Move `css/app.less` to `app.less`. Modify the related link tag in `index.html`. 10 | 3. In `app.less`, modify the existing @import statements and remove the `../` from the beginning of the @import statements for all partial and directive less files. 11 | 4. In `app.less`, combine the two comment-separated sections for partial and directive less into one section. Modify the bottom comment marker of this section to be `/* Add Component LESS Above */`. 12 | 5. In `index.html`, combine the 4 comment-separated sections for component script tags (for partials, directives, services, filters) into one section. Modify the bottom comment marker of this section to be ``. 13 | 6. Overwrite your existing `Gruntfile.js` with the content from [Gruntfile.js](app/templates/skeleton/Gruntfile.js). Do a find and replace and search for `<%%=` and replace with `<%=`. 14 | 7. Upgrade the `grunt-dom-munger` task by doing `npm install grunt-dom-munger@3.4 --save-dev`. 15 | 8. Create a `.yo-rc.json` file in the project root with the following content: 16 | 17 | ```js 18 | { 19 | "generator-cg-angular": { 20 | "uirouter": false, 21 | "partialDirectory": "partial/", 22 | "directiveDirectory": "directive/", 23 | "serviceDirectory": "service/", 24 | "filterDirectory": "filter/", 25 | "inject": { 26 | "js": { 27 | "file": "index.html", 28 | "marker": "", 29 | "template": "" 30 | }, 31 | "less": { 32 | "file": "app.less", 33 | "marker": "/* Add Component LESS Above */", 34 | "template": "@import \"<%= filename %>\";" 35 | } 36 | } 37 | } 38 | } 39 | ``` 40 | 41 | Upgrading from v3.0 to v3.1 42 | ------------- 43 | 44 | 1. Modify your `.yo-rc.json` file to look like the following. The only changes are in the `less` section. They are the new `relativeToModule` property and the updated `file` value. 45 | 46 | ```js 47 | { 48 | "generator-cg-angular": { 49 | "uirouter": false, 50 | "partialDirectory": "partial/", 51 | "directiveDirectory": "directive/", 52 | "serviceDirectory": "service/", 53 | "filterDirectory": "filter/", 54 | "inject": { 55 | "js": { 56 | "file": "index.html", 57 | "marker": "", 58 | "template": "" 59 | }, 60 | "less": { 61 | "relativeToModule": true, 62 | "file": "<%= module %>.less", 63 | "marker": "/* Add Component LESS Above */", 64 | "template": "@import \"<%= filename %>\";" 65 | } 66 | } 67 | } 68 | } 69 | ``` 70 | 71 | 2. Modify your `app.js` file. The v3.1 generator does not generate the chained calls to `$routeProvider`. Please update the `app.js` so that the contents of the `config` method call look something like: 72 | 73 | ```js 74 | /* Add New Routes Above */ 75 | $routeProvider.otherwise({redirectTo:'/home'}); 76 | ``` 77 | 78 | Notice that there is now no beginning "$routeProvider." above the comment marker. The new generator will inject full statements w/o chaining. For example: 79 | 80 | ```js 81 | $routeProvider.when('route',{templateUrl:'partial.html'}); 82 | $routeProvider.when('route2',{templateUrl:'partial2.html'}); 83 | $routeProvider.when('route3',{templateUrl:'partial3.html'}); 84 | /* Add New Routes Above */ 85 | $routeProvider.otherwise({redirectTo:'/home'}); 86 | ``` 87 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var util = require('util'); 3 | var path = require('path'); 4 | var yeoman = require('yeoman-generator'); 5 | var cgUtils = require('../utils.js'); 6 | 7 | var CgangularGenerator = module.exports = function CgangularGenerator(args, options, config) { 8 | yeoman.generators.Base.apply(this, arguments); 9 | 10 | this.on('end', function () { 11 | this.config.set('partialDirectory','partial/'); 12 | this.config.set('modalDirectory','partial/'); 13 | this.config.set('directiveDirectory','directive/'); 14 | this.config.set('filterDirectory','filter/'); 15 | this.config.set('serviceDirectory','service/'); 16 | var inject = { 17 | js: { 18 | file: 'index.html', 19 | marker: cgUtils.JS_MARKER, 20 | template: '' 21 | }, 22 | less: { 23 | relativeToModule: true, 24 | file: '<%= module %>.less', 25 | marker: cgUtils.LESS_MARKER, 26 | template: '@import "<%= filename %>";' 27 | } 28 | }; 29 | this.config.set('inject',inject); 30 | this.config.save(); 31 | this.installDependencies({ skipInstall: options['skip-install'] }); 32 | }); 33 | 34 | this.pkg = JSON.parse(this.readFileAsString(path.join(__dirname, '../package.json'))); 35 | }; 36 | 37 | util.inherits(CgangularGenerator, yeoman.generators.Base); 38 | 39 | CgangularGenerator.prototype.askFor = function askFor() { 40 | var cb = this.async(); 41 | 42 | var prompts = [{ 43 | name: 'appname', 44 | message: 'What would you like the angular app/module name to be?', 45 | default: path.basename(process.cwd()) 46 | }]; 47 | 48 | this.prompt(prompts, function (props) { 49 | this.appname = props.appname; 50 | cb(); 51 | }.bind(this)); 52 | }; 53 | 54 | CgangularGenerator.prototype.askForUiRouter = function askFor() { 55 | var cb = this.async(); 56 | 57 | var prompts = [{ 58 | name: 'router', 59 | type:'list', 60 | message: 'Which router would you like to use?', 61 | default: 0, 62 | choices: ['Standard Angular Router','Angular UI Router'] 63 | }]; 64 | 65 | this.prompt(prompts, function (props) { 66 | if (props.router === 'Angular UI Router') { 67 | this.uirouter = true; 68 | this.routerJs = 'bower_components/angular-ui-router/release/angular-ui-router.js'; 69 | this.routerModuleName = 'ui.router'; 70 | this.routerViewDirective = 'ui-view'; 71 | } else { 72 | this.uirouter = false; 73 | this.routerJs = 'bower_components/angular-route/angular-route.js'; 74 | this.routerModuleName = 'ngRoute'; 75 | this.routerViewDirective = 'ng-view'; 76 | } 77 | this.config.set('uirouter',this.uirouter); 78 | cb(); 79 | }.bind(this)); 80 | }; 81 | 82 | CgangularGenerator.prototype.app = function app() { 83 | this.directory('skeleton/','./'); 84 | }; 85 | -------------------------------------------------------------------------------- /app/templates/skeleton/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory" : "bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /app/templates/skeleton/.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 | -------------------------------------------------------------------------------- /app/templates/skeleton/.gitignore: -------------------------------------------------------------------------------- 1 | temp/ 2 | dist/ 3 | node_modules/ 4 | _SpecRunner.html 5 | .DS_Store 6 | test-results.xml -------------------------------------------------------------------------------- /app/templates/skeleton/.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 | "browser": true, 13 | "smarttabs": true, 14 | "globals": { 15 | "jQuery": true, 16 | "angular": true, 17 | "console": true, 18 | "$": true, 19 | "_": true, 20 | "moment": true, 21 | "describe": true, 22 | "beforeEach": true, 23 | "module": true, 24 | "inject": true, 25 | "it": true, 26 | "expect": true, 27 | "xdescribe": true, 28 | "xit": true, 29 | "spyOn": true 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/templates/skeleton/Gruntfile.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | var pkg = require('./package.json'); 5 | 6 | //Using exclusion patterns slows down Grunt significantly 7 | //instead of creating a set of patterns like '**/*.js' and '!**/node_modules/**' 8 | //this method is used to create a set of inclusive patterns for all subdirectories 9 | //skipping node_modules, bower_components, dist, and any .dirs 10 | //This enables users to create any directory structure they desire. 11 | var createFolderGlobs = function(fileTypePatterns) { 12 | fileTypePatterns = Array.isArray(fileTypePatterns) ? fileTypePatterns : [fileTypePatterns]; 13 | var ignore = ['node_modules','bower_components','dist','temp']; 14 | var fs = require('fs'); 15 | return fs.readdirSync(process.cwd()) 16 | .map(function(file){ 17 | if (ignore.indexOf(file) !== -1 || 18 | file.indexOf('.') === 0 || 19 | !fs.lstatSync(file).isDirectory()) { 20 | return null; 21 | } else { 22 | return fileTypePatterns.map(function(pattern) { 23 | return file + '/**/' + pattern; 24 | }); 25 | } 26 | }) 27 | .filter(function(patterns){ 28 | return patterns; 29 | }) 30 | .concat(fileTypePatterns); 31 | }; 32 | 33 | module.exports = function (grunt) { 34 | 35 | // load all grunt tasks 36 | require('load-grunt-tasks')(grunt); 37 | 38 | // Project configuration. 39 | grunt.initConfig({ 40 | connect: { 41 | main: { 42 | options: { 43 | port: 9001 44 | } 45 | } 46 | }, 47 | watch: { 48 | main: { 49 | options: { 50 | livereload: true, 51 | livereloadOnError: false, 52 | spawn: false 53 | }, 54 | files: [createFolderGlobs(['*.js','*.less','*.html']),'!_SpecRunner.html','!.grunt'], 55 | tasks: [] //all the tasks are run dynamically during the watch event handler 56 | } 57 | }, 58 | jshint: { 59 | main: { 60 | options: { 61 | jshintrc: '.jshintrc' 62 | }, 63 | src: createFolderGlobs('*.js') 64 | } 65 | }, 66 | clean: { 67 | before:{ 68 | src:['dist','temp'] 69 | }, 70 | after: { 71 | src:['temp'] 72 | } 73 | }, 74 | less: { 75 | production: { 76 | options: { 77 | }, 78 | files: { 79 | 'temp/app.css': 'app.less' 80 | } 81 | } 82 | }, 83 | ngtemplates: { 84 | main: { 85 | options: { 86 | module: pkg.name, 87 | htmlmin:'<%%= htmlmin.main.options %>' 88 | }, 89 | src: [createFolderGlobs('*.html'),'!index.html','!_SpecRunner.html'], 90 | dest: 'temp/templates.js' 91 | } 92 | }, 93 | copy: { 94 | main: { 95 | files: [ 96 | {src: ['img/**'], dest: 'dist/'}, 97 | {src: ['bower_components/font-awesome/fonts/**'], dest: 'dist/',filter:'isFile',expand:true}, 98 | {src: ['bower_components/bootstrap/fonts/**'], dest: 'dist/',filter:'isFile',expand:true} 99 | //{src: ['bower_components/angular-ui-utils/ui-utils-ieshiv.min.js'], dest: 'dist/'}, 100 | //{src: ['bower_components/select2/*.png','bower_components/select2/*.gif'], dest:'dist/css/',flatten:true,expand:true}, 101 | //{src: ['bower_components/angular-mocks/angular-mocks.js'], dest: 'dist/'} 102 | ] 103 | } 104 | }, 105 | dom_munger:{ 106 | read: { 107 | options: { 108 | read:[ 109 | {selector:'script[data-concat!="false"]',attribute:'src',writeto:'appjs'}, 110 | {selector:'link[rel="stylesheet"][data-concat!="false"]',attribute:'href',writeto:'appcss'} 111 | ] 112 | }, 113 | src: 'index.html' 114 | }, 115 | update: { 116 | options: { 117 | remove: ['script[data-remove!="false"]','link[data-remove!="false"]'], 118 | append: [ 119 | {selector:'body',html:''}, 120 | {selector:'head',html:''} 121 | ] 122 | }, 123 | src:'index.html', 124 | dest: 'dist/index.html' 125 | } 126 | }, 127 | cssmin: { 128 | main: { 129 | src:['temp/app.css','<%%= dom_munger.data.appcss %>'], 130 | dest:'dist/app.full.min.css' 131 | } 132 | }, 133 | concat: { 134 | main: { 135 | src: ['<%%= dom_munger.data.appjs %>','<%%= ngtemplates.main.dest %>'], 136 | dest: 'temp/app.full.js' 137 | } 138 | }, 139 | ngAnnotate: { 140 | main: { 141 | src:'temp/app.full.js', 142 | dest: 'temp/app.full.js' 143 | } 144 | }, 145 | uglify: { 146 | main: { 147 | src: 'temp/app.full.js', 148 | dest:'dist/app.full.min.js' 149 | } 150 | }, 151 | htmlmin: { 152 | main: { 153 | options: { 154 | collapseBooleanAttributes: true, 155 | collapseWhitespace: true, 156 | removeAttributeQuotes: true, 157 | removeComments: true, 158 | removeEmptyAttributes: true, 159 | removeScriptTypeAttributes: true, 160 | removeStyleLinkTypeAttributes: true 161 | }, 162 | files: { 163 | 'dist/index.html': 'dist/index.html' 164 | } 165 | } 166 | }, 167 | //Imagemin has issues on Windows. 168 | //To enable imagemin: 169 | // - "npm install grunt-contrib-imagemin" 170 | // - Comment in this section 171 | // - Add the "imagemin" task after the "htmlmin" task in the build task alias 172 | // imagemin: { 173 | // main:{ 174 | // files: [{ 175 | // expand: true, cwd:'dist/', 176 | // src:['**/{*.png,*.jpg}'], 177 | // dest: 'dist/' 178 | // }] 179 | // } 180 | // }, 181 | karma: { 182 | options: { 183 | frameworks: ['jasmine'], 184 | files: [ //this files data is also updated in the watch handler, if updated change there too 185 | '<%%= dom_munger.data.appjs %>', 186 | 'bower_components/angular-mocks/angular-mocks.js', 187 | createFolderGlobs('*-spec.js') 188 | ], 189 | logLevel:'ERROR', 190 | reporters:['mocha'], 191 | autoWatch: false, //watching is handled by grunt-contrib-watch 192 | singleRun: true 193 | }, 194 | all_tests: { 195 | browsers: ['PhantomJS','Chrome','Firefox'] 196 | }, 197 | during_watch: { 198 | browsers: ['PhantomJS'] 199 | }, 200 | } 201 | }); 202 | 203 | grunt.registerTask('build',['jshint','clean:before','less','dom_munger','ngtemplates','cssmin','concat','ngAnnotate','uglify','copy','htmlmin','clean:after']); 204 | grunt.registerTask('serve', ['dom_munger:read','jshint','connect', 'watch']); 205 | grunt.registerTask('test',['dom_munger:read','karma:all_tests']); 206 | 207 | grunt.event.on('watch', function(action, filepath) { 208 | //https://github.com/gruntjs/grunt-contrib-watch/issues/156 209 | 210 | var tasksToRun = []; 211 | 212 | if (filepath.lastIndexOf('.js') !== -1 && filepath.lastIndexOf('.js') === filepath.length - 3) { 213 | 214 | //lint the changed js file 215 | grunt.config('jshint.main.src', filepath); 216 | tasksToRun.push('jshint'); 217 | 218 | //find the appropriate unit test for the changed file 219 | var spec = filepath; 220 | if (filepath.lastIndexOf('-spec.js') === -1 || filepath.lastIndexOf('-spec.js') !== filepath.length - 8) { 221 | spec = filepath.substring(0,filepath.length - 3) + '-spec.js'; 222 | } 223 | 224 | //if the spec exists then lets run it 225 | if (grunt.file.exists(spec)) { 226 | var files = [].concat(grunt.config('dom_munger.data.appjs')); 227 | files.push('bower_components/angular-mocks/angular-mocks.js'); 228 | files.push(spec); 229 | grunt.config('karma.options.files', files); 230 | tasksToRun.push('karma:during_watch'); 231 | } 232 | } 233 | 234 | //if index.html changed, we need to reread the '); 89 | $('head').append(''); 90 | })) 91 | .pipe(htmlmin(htmlminOptions)) 92 | .pipe(gulp.dest('dist/')); 93 | }); 94 | 95 | gulp.task('images', ['clean'], function(){ 96 | return gulp.src('img/**') 97 | .pipe(imagemin()) 98 | .pipe(gulp.dest('dist/')); 99 | }); 100 | 101 | gulp.task('fonts', ['clean'], function(){ 102 | return gulp.src('bower_components/font-awesome/fonts/**') 103 | .pipe(gulp.dest('dist/bower_components/font-awesome/fonts/')); 104 | }); 105 | 106 | gulp.task('jshint', function(){ 107 | gulp.src(['!node_modules/**','!.grunt/**','!dist/**','!bower_components/**','**/*.js']) 108 | .pipe(jshint()) 109 | .pipe(jshint.reporter(stylish)); 110 | }); 111 | 112 | gulp.task('build', ['clean', 'css', 'js', 'indexHtml', 'images', 'fonts']); 113 | 114 | /* 115 | 116 | -specifying clean dependency on each task is ugly 117 | https://github.com/robrich/orchestrator/issues/26 118 | 119 | -gulp-jasmine needs a phantomjs option 120 | https://github.com/sindresorhus/gulp-jasmine/issues/2 121 | 122 | */ 123 | 124 | /* 125 | "gulp-dom-src": "~0.1.0", 126 | "gulp-concat": "~2.1.7", 127 | "gulp-uglify": "~0.2.1", 128 | "gulp-cssmin": "~0.1.3", 129 | "gulp-imagemin": "~0.1.5", 130 | "gulp-less": "~1.2.2", 131 | "gulp-cheerio": "~0.2.0", 132 | "gulp-rename": "~1.2.0", 133 | "gulp-ng-html2js": "~0.1.6", 134 | "gulp-ngmin": "~0.1.2", 135 | "gulp-htmlmin": "~0.1.2", 136 | "gulp-jshint": "~1.5.0", 137 | "gulp-jasmine": "~0.2.0", 138 | "jshint-stylish": "~0.1.5", 139 | "rimraf": "~2.2.6", 140 | "streamqueue": "0.0.5", 141 | "gulp": "~3.5.5" 142 | */ -------------------------------------------------------------------------------- /app/templates/skeleton/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 |
34 |
35 |
> 36 |
37 |
38 |
39 |
40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/templates/skeleton/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= _.camelize(appname) %>", 3 | "version": "0.0.0", 4 | "devDependencies": { 5 | "grunt": "~0.4", 6 | "grunt-angular-templates": "~0.5", 7 | "grunt-browser-output": "0.1.0", 8 | "grunt-contrib-clean": "~0.5", 9 | "grunt-contrib-concat": "~0.3", 10 | "grunt-contrib-connect": "~0.6", 11 | "grunt-contrib-copy": "~0.5", 12 | "grunt-contrib-cssmin": "~0.7", 13 | "grunt-contrib-htmlmin": "~0.1", 14 | "grunt-contrib-jshint": "~0.9", 15 | "grunt-contrib-less": "~0.8", 16 | "grunt-contrib-uglify": "~0.2", 17 | "grunt-contrib-watch": "~0.6", 18 | "grunt-dom-munger": "~3.4", 19 | "grunt-karma": "~0.8.3", 20 | "grunt-ng-annotate": "~0.5", 21 | "karma": "~0.12.6", 22 | "karma-chrome-launcher": "~0.1.3", 23 | "karma-firefox-launcher": "~0.1.3", 24 | "karma-jasmine": "~0.1.5", 25 | "karma-mocha-reporter": "~0.2.5", 26 | "karma-phantomjs-launcher": "~0.1.4", 27 | "load-grunt-tasks": "~0.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /directive/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var util = require('util'); 3 | var yeoman = require('yeoman-generator'); 4 | var path = require('path'); 5 | var cgUtils = require('../utils.js'); 6 | var chalk = require('chalk'); 7 | var _ = require('underscore'); 8 | var fs = require('fs'); 9 | 10 | _.str = require('underscore.string'); 11 | _.mixin(_.str.exports()); 12 | 13 | var DirectiveGenerator = module.exports = function DirectiveGenerator(args, options, config) { 14 | 15 | cgUtils.getNameArg(this,args); 16 | 17 | yeoman.generators.Base.apply(this, arguments); 18 | 19 | }; 20 | 21 | util.inherits(DirectiveGenerator, yeoman.generators.Base); 22 | 23 | DirectiveGenerator.prototype.askFor = function askFor() { 24 | var cb = this.async(); 25 | 26 | var prompts = [{ 27 | type:'confirm', 28 | name: 'needpartial', 29 | message: 'Does this directive need an external html file (i.e. partial)?', 30 | default: true 31 | }]; 32 | 33 | cgUtils.addNamePrompt(this,prompts,'directive'); 34 | 35 | this.prompt(prompts, function (props) { 36 | if (props.name){ 37 | this.name = props.name; 38 | } 39 | this.needpartial = props.needpartial; 40 | cgUtils.askForModuleAndDir('directive',this,this.needpartial,cb); 41 | }.bind(this)); 42 | 43 | }; 44 | 45 | DirectiveGenerator.prototype.files = function files() { 46 | 47 | var configName = 'directiveSimpleTemplates'; 48 | var defaultDir = 'templates/simple'; 49 | if (this.needpartial) { 50 | configName = 'directiveComplexTemplates'; 51 | defaultDir = 'templates/complex'; 52 | } 53 | 54 | this.htmlPath = path.join(this.dir,this.name + '.html').replace(/\\/g,'/');; 55 | 56 | cgUtils.processTemplates(this.name,this.dir,'directive',this,defaultDir,configName,this.module); 57 | 58 | }; -------------------------------------------------------------------------------- /directive/templates/complex/directive-spec.js: -------------------------------------------------------------------------------- 1 | describe('<%= _.camelize(name) %>', function() { 2 | 3 | beforeEach(module('<%= appname %>')); 4 | 5 | var scope,compile; 6 | 7 | beforeEach(inject(function($rootScope,$compile) { 8 | scope = $rootScope.$new(); 9 | compile = $compile; 10 | })); 11 | 12 | it('should ...', function() { 13 | 14 | /* 15 | To test your directive, you need to create some html that would use your directive, 16 | send that through compile() then compare the results. 17 | 18 | var element = compile('
hi
')(scope); 19 | expect(element.text()).toBe('hello, world'); 20 | */ 21 | 22 | }); 23 | }); -------------------------------------------------------------------------------- /directive/templates/complex/directive.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
-------------------------------------------------------------------------------- /directive/templates/complex/directive.js: -------------------------------------------------------------------------------- 1 | angular.module('<%= appname %>').directive('<%= _.camelize(name) %>', function() { 2 | return { 3 | restrict: 'E', 4 | replace: true, 5 | scope: { 6 | 7 | }, 8 | templateUrl: '<%= htmlPath %>', 9 | link: function(scope, element, attrs, fn) { 10 | 11 | 12 | } 13 | }; 14 | }); 15 | -------------------------------------------------------------------------------- /directive/templates/complex/directive.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cgross/generator-cg-angular/050fa5e7ccc308925663a3f516ac2ff902fe22d9/directive/templates/complex/directive.less -------------------------------------------------------------------------------- /directive/templates/simple/directive-spec.js: -------------------------------------------------------------------------------- 1 | describe('<%= _.camelize(name) %>', function() { 2 | 3 | beforeEach(module('<%= appname %>')); 4 | 5 | var scope,compile; 6 | 7 | beforeEach(inject(function($rootScope,$compile) { 8 | scope = $rootScope.$new(); 9 | compile = $compile; 10 | })); 11 | 12 | it('should ...', function() { 13 | 14 | /* 15 | To test your directive, you need to create some html that would use your directive, 16 | send that through compile() then compare the results. 17 | 18 | var element = compile('
hi
')(scope); 19 | expect(element.text()).toBe('hello, world'); 20 | */ 21 | 22 | }); 23 | }); -------------------------------------------------------------------------------- /directive/templates/simple/directive.js: -------------------------------------------------------------------------------- 1 | angular.module('<%= appname %>').directive('<%= _.camelize(name) %>', function() { 2 | return { 3 | restrict: 'A', 4 | link: function(scope, element, attrs, fn) { 5 | 6 | 7 | } 8 | }; 9 | }); -------------------------------------------------------------------------------- /filter/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var util = require('util'); 3 | var yeoman = require('yeoman-generator'); 4 | var path = require('path'); 5 | var cgUtils = require('../utils.js'); 6 | var chalk = require('chalk'); 7 | var _ = require('underscore'); 8 | var fs = require('fs'); 9 | 10 | _.str = require('underscore.string'); 11 | _.mixin(_.str.exports()); 12 | 13 | var FilterGenerator = module.exports = function FilterGenerator(args, options, config) { 14 | 15 | cgUtils.getNameArg(this,args); 16 | 17 | yeoman.generators.Base.apply(this, arguments); 18 | 19 | }; 20 | 21 | util.inherits(FilterGenerator, yeoman.generators.Base); 22 | 23 | FilterGenerator.prototype.askFor = function askFor() { 24 | var cb = this.async(); 25 | 26 | var prompts = []; 27 | 28 | cgUtils.addNamePrompt(this,prompts,'filter'); 29 | 30 | this.prompt(prompts, function (props) { 31 | if (props.name){ 32 | this.name = props.name; 33 | } 34 | cgUtils.askForModuleAndDir('filter',this,false,cb); 35 | }.bind(this)); 36 | 37 | 38 | }; 39 | 40 | FilterGenerator.prototype.files = function files() { 41 | 42 | cgUtils.processTemplates(this.name,this.dir,'filter',this,null,null,this.module); 43 | 44 | }; 45 | -------------------------------------------------------------------------------- /filter/templates/filter-spec.js: -------------------------------------------------------------------------------- 1 | describe('<%= _.camelize(name) %>', function() { 2 | 3 | beforeEach(module('<%= appname %>')); 4 | 5 | it('should ...', inject(function($filter) { 6 | 7 | var filter = $filter('<%= _.camelize(name) %>'); 8 | 9 | expect(filter('input')).toEqual('output'); 10 | 11 | })); 12 | 13 | }); 14 | -------------------------------------------------------------------------------- /filter/templates/filter.js: -------------------------------------------------------------------------------- 1 | angular.module('<%= appname %>').filter('<%= _.camelize(name) %>', function() { 2 | return function(input,arg) { 3 | return 'output'; 4 | }; 5 | }); -------------------------------------------------------------------------------- /modal/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var util = require('util'); 3 | var yeoman = require('yeoman-generator'); 4 | var cgUtils = require('../utils.js'); 5 | var _ = require('underscore'); 6 | _.str = require('underscore.string'); 7 | _.mixin(_.str.exports()); 8 | var chalk = require('chalk'); 9 | var path = require('path'); 10 | 11 | var ModalGenerator = module.exports = function ModalGenerator(args, options, config) { 12 | 13 | cgUtils.getNameArg(this,args); 14 | 15 | yeoman.generators.Base.apply(this, arguments); 16 | 17 | }; 18 | 19 | util.inherits(ModalGenerator, yeoman.generators.Base); 20 | 21 | ModalGenerator.prototype.askFor = function askFor() { 22 | var cb = this.async(); 23 | 24 | var prompts = []; 25 | 26 | cgUtils.addNamePrompt(this,prompts,'modal'); 27 | 28 | this.prompt(prompts, function (props) { 29 | if (props.name){ 30 | this.name = props.name; 31 | } 32 | cgUtils.askForModuleAndDir('modal',this,true,cb); 33 | }.bind(this)); 34 | 35 | }; 36 | 37 | ModalGenerator.prototype.files = function files() { 38 | 39 | this.ctrlname = _.camelize(_.classify(this.name)) + 'Ctrl'; 40 | 41 | cgUtils.processTemplates(this.name,this.dir,'modal',this,null,null,this.module); 42 | 43 | setTimeout((function(){ 44 | 45 | console.log(''); 46 | console.log(' Open this modal by using ' + chalk.bold('angular-ui-bootstrap') + ' module\'s ' + chalk.bold('$uibModal') + ' service:'); 47 | console.log(''); 48 | console.log(' $uibModal.open({'); 49 | console.log(' templateUrl: \'' + path.join(this.dir,this.name + '.html') + '\','); 50 | console.log(' controller: \''+ this.ctrlname +'\''); 51 | console.log(' }).result.then(function(result){'); 52 | console.log(' //do something with the result'); 53 | console.log(' });'); 54 | console.log(''); 55 | 56 | }).bind(this),200); 57 | 58 | }; 59 | -------------------------------------------------------------------------------- /modal/templates/modal-spec.js: -------------------------------------------------------------------------------- 1 | describe('<%= ctrlname %>', function() { 2 | 3 | beforeEach(module('<%= appname %>')); 4 | 5 | var scope,ctrl; 6 | 7 | beforeEach(inject(function($rootScope, $controller) { 8 | scope = $rootScope.$new(); 9 | ctrl = $controller('<%= ctrlname %>', {$scope: scope}); 10 | })); 11 | 12 | it('should ...', inject(function() { 13 | 14 | expect(1).toEqual(1); 15 | 16 | })); 17 | 18 | }); 19 | -------------------------------------------------------------------------------- /modal/templates/modal.html: -------------------------------------------------------------------------------- 1 |
2 | 6 | 9 | 13 |
-------------------------------------------------------------------------------- /modal/templates/modal.js: -------------------------------------------------------------------------------- 1 | angular.module('<%= appname %>').controller('<%= ctrlname %>',function($scope){ 2 | 3 | 4 | }); -------------------------------------------------------------------------------- /modal/templates/modal.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cgross/generator-cg-angular/050fa5e7ccc308925663a3f516ac2ff902fe22d9/modal/templates/modal.less -------------------------------------------------------------------------------- /module/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var util = require('util'); 3 | var yeoman = require('yeoman-generator'); 4 | var path = require('path'); 5 | var cgUtils = require('../utils.js'); 6 | var chalk = require('chalk'); 7 | var _ = require('underscore'); 8 | var fs = require('fs'); 9 | 10 | _.str = require('underscore.string'); 11 | _.mixin(_.str.exports()); 12 | 13 | var ModuleGenerator = module.exports = function ModuleGenerator(args, options, config) { 14 | 15 | cgUtils.getNameArg(this,args); 16 | 17 | yeoman.generators.Base.apply(this, arguments); 18 | 19 | this.uirouter = this.config.get('uirouter'); 20 | this.routerModuleName = this.uirouter ? 'ui.router' : 'ngRoute'; 21 | }; 22 | 23 | util.inherits(ModuleGenerator, yeoman.generators.Base); 24 | 25 | ModuleGenerator.prototype.askFor = function askFor() { 26 | var cb = this.async(); 27 | var that = this; 28 | 29 | var prompts = [ 30 | { 31 | name:'dir', 32 | message:'Where would you like to create the module (must specify a subdirectory)?', 33 | default: function(data){ 34 | return path.join(that.name || data.name,'/'); 35 | }, 36 | validate: function(value) { 37 | value = _.str.trim(value); 38 | if (_.isEmpty(value) || value[0] === '/' || value[0] === '\\') { 39 | return 'Please enter a subdirectory.'; 40 | } 41 | return true; 42 | } 43 | } 44 | ]; 45 | 46 | cgUtils.addNamePrompt(this,prompts,'module'); 47 | 48 | this.prompt(prompts, function (props) { 49 | if (props.name){ 50 | this.name = props.name; 51 | } 52 | this.dir = path.join(props.dir,'/'); 53 | cb(); 54 | }.bind(this)); 55 | }; 56 | 57 | ModuleGenerator.prototype.files = function files() { 58 | 59 | var module = cgUtils.getParentModule(path.join(this.dir,'..')); 60 | module.dependencies.modules.push(_.camelize(this.name)); 61 | module.save(); 62 | this.log.writeln(chalk.green(' updating') + ' %s',path.basename(module.file)); 63 | 64 | cgUtils.processTemplates(this.name,this.dir,'module',this,null,null,module); 65 | 66 | var modules = this.config.get('modules'); 67 | if (!modules) { 68 | modules = []; 69 | } 70 | modules.push({name:_.camelize(this.name),file:path.join(this.dir,this.name + '.js')}); 71 | this.config.set('modules',modules); 72 | this.config.save(); 73 | }; -------------------------------------------------------------------------------- /module/templates/module.js: -------------------------------------------------------------------------------- 1 | angular.module('<%= _.camelize(name) %>', ['ui.bootstrap','ui.utils','<%= routerModuleName %>','ngAnimate']); 2 | <% if (!uirouter) { %> 3 | angular.module('<%= _.camelize(name) %>').config(function($routeProvider) { 4 | 5 | /* Add New Routes Above */ 6 | 7 | }); 8 | <% } %><% if (uirouter) { %> 9 | angular.module('<%= _.camelize(name) %>').config(function($stateProvider) { 10 | 11 | /* Add New States Above */ 12 | 13 | }); 14 | <% } %> 15 | -------------------------------------------------------------------------------- /module/templates/module.less: -------------------------------------------------------------------------------- 1 | /* Component LESS */ 2 | /* Add Component LESS Above */ 3 | 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-cg-angular", 3 | "version": "3.3.0", 4 | "description": "Yeoman Generator for Enterprise Angular projects.", 5 | "keywords": [ 6 | "yeoman-generator", 7 | "angular" 8 | ], 9 | "homepage": "https://github.com/cgross/generator-cg-angular", 10 | "bugs": "https://github.com/cgross/generator-cg-angular/issues", 11 | "author": { 12 | "name": "Chris Gross", 13 | "email": "schtoo@schtoo.com", 14 | "url": "https://github.com/cgross" 15 | }, 16 | "main": "app/index.js", 17 | "repository": { 18 | "type": "git", 19 | "url": "git://github.com/cgross/generator-cg-angular.git" 20 | }, 21 | "scripts": { 22 | "test": "mocha" 23 | }, 24 | "dependencies": { 25 | "underscore": "~1.5", 26 | "underscore.string": "~2.3", 27 | "yeoman-generator": "~0.16", 28 | "chalk": "~0.4.0", 29 | "ng-parse-module": "~0.1.0" 30 | }, 31 | "devDependencies": { 32 | "mocha": "~1.10.0" 33 | }, 34 | "engines": { 35 | "node": ">=0.8.0" 36 | }, 37 | "licenses": [ 38 | { 39 | "type": "MIT" 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /partial/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var util = require('util'); 3 | var yeoman = require('yeoman-generator'); 4 | var path = require('path'); 5 | var fs = require('fs'); 6 | var cgUtils = require('../utils.js'); 7 | var _ = require('underscore'); 8 | var chalk = require('chalk'); 9 | var fs = require('fs'); 10 | var url = require('url'); 11 | 12 | _.str = require('underscore.string'); 13 | _.mixin(_.str.exports()); 14 | 15 | var PartialGenerator = module.exports = function PartialGenerator(args, options, config) { 16 | 17 | cgUtils.getNameArg(this,args); 18 | 19 | yeoman.generators.Base.apply(this, arguments); 20 | 21 | }; 22 | 23 | util.inherits(PartialGenerator, yeoman.generators.Base); 24 | 25 | PartialGenerator.prototype.askFor = function askFor() { 26 | var cb = this.async(); 27 | 28 | var prompts = [ 29 | { 30 | name: 'route', 31 | message: 'Enter your route url (i.e. /mypartial/:id). If you don\'t want a route added for you, leave this empty.' 32 | } 33 | ]; 34 | 35 | cgUtils.addNamePrompt(this,prompts,'partial'); 36 | 37 | this.prompt(prompts, function (props) { 38 | if (props.name){ 39 | this.name = props.name; 40 | } 41 | this.route = url.resolve('',props.route); 42 | cgUtils.askForModuleAndDir('partial',this,true,cb); 43 | }.bind(this)); 44 | }; 45 | 46 | PartialGenerator.prototype.files = function files() { 47 | 48 | this.ctrlname = _.camelize(_.classify(this.name)) + 'Ctrl'; 49 | 50 | cgUtils.processTemplates(this.name,this.dir,'partial',this,null,null,this.module); 51 | 52 | if (this.route && this.route.length > 0){ 53 | var partialUrl = this.dir + this.name + '.html'; 54 | cgUtils.injectRoute(this.module.file,this.config.get('uirouter'),this.name,this.route,partialUrl,this); 55 | } 56 | 57 | }; 58 | -------------------------------------------------------------------------------- /partial/templates/partial-spec.js: -------------------------------------------------------------------------------- 1 | describe('<%= ctrlname %>', function() { 2 | 3 | beforeEach(module('<%= appname %>')); 4 | 5 | var scope,ctrl; 6 | 7 | beforeEach(inject(function($rootScope, $controller) { 8 | scope = $rootScope.$new(); 9 | ctrl = $controller('<%= ctrlname %>', {$scope: scope}); 10 | })); 11 | 12 | it('should ...', inject(function() { 13 | 14 | expect(1).toEqual(1); 15 | 16 | })); 17 | 18 | }); 19 | -------------------------------------------------------------------------------- /partial/templates/partial.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 | -------------------------------------------------------------------------------- /partial/templates/partial.js: -------------------------------------------------------------------------------- 1 | angular.module('<%= appname %>').controller('<%= ctrlname %>',function($scope){ 2 | 3 | 4 | }); -------------------------------------------------------------------------------- /partial/templates/partial.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cgross/generator-cg-angular/050fa5e7ccc308925663a3f516ac2ff902fe22d9/partial/templates/partial.less -------------------------------------------------------------------------------- /service/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var util = require('util'); 3 | var yeoman = require('yeoman-generator'); 4 | var path = require('path'); 5 | var cgUtils = require('../utils.js'); 6 | var chalk = require('chalk'); 7 | var _ = require('underscore'); 8 | var fs = require('fs'); 9 | 10 | _.str = require('underscore.string'); 11 | _.mixin(_.str.exports()); 12 | 13 | var ServiceGenerator = module.exports = function ServiceGenerator(args, options, config) { 14 | 15 | cgUtils.getNameArg(this,args); 16 | 17 | yeoman.generators.Base.apply(this, arguments); 18 | 19 | }; 20 | 21 | util.inherits(ServiceGenerator, yeoman.generators.Base); 22 | 23 | ServiceGenerator.prototype.askFor = function askFor() { 24 | var cb = this.async(); 25 | 26 | var prompts = []; 27 | 28 | cgUtils.addNamePrompt(this,prompts,'service'); 29 | 30 | this.prompt(prompts, function (props) { 31 | if (props.name){ 32 | this.name = props.name; 33 | } 34 | cgUtils.askForModuleAndDir('service',this,false,cb); 35 | }.bind(this)); 36 | 37 | }; 38 | 39 | ServiceGenerator.prototype.files = function files() { 40 | 41 | cgUtils.processTemplates(this.name,this.dir,'service',this,null,null,this.module); 42 | 43 | }; 44 | -------------------------------------------------------------------------------- /service/templates/service-spec.js: -------------------------------------------------------------------------------- 1 | describe('<%= _.camelize(name) %>', function() { 2 | 3 | beforeEach(module('<%= appname %>')); 4 | 5 | it('should ...', inject(function(<%= _.camelize(name) %>) { 6 | 7 | //expect(<%= _.camelize(name) %>.doSomething()).toEqual('something'); 8 | 9 | })); 10 | 11 | }); 12 | -------------------------------------------------------------------------------- /service/templates/service.js: -------------------------------------------------------------------------------- 1 | angular.module('<%= appname %>').factory('<%= _.camelize(name) %>',function() { 2 | 3 | var <%= _.camelize(name) %> = {}; 4 | 5 | return <%= _.camelize(name) %>; 6 | }); -------------------------------------------------------------------------------- /test/test-creation.js: -------------------------------------------------------------------------------- 1 | /*global describe, beforeEach, it*/ 2 | 'use strict'; 3 | 4 | var path = require('path'); 5 | var helpers = require('yeoman-generator').test; 6 | 7 | 8 | describe('cg-angular generator', function () { 9 | beforeEach(function (done) { 10 | helpers.testDirectory(path.join(__dirname, 'temp'), function (err) { 11 | if (err) { 12 | return done(err); 13 | } 14 | 15 | this.app = helpers.createGenerator('cgangular:app', [ 16 | '../../app' 17 | ]); 18 | done(); 19 | }.bind(this)); 20 | }); 21 | 22 | it('creates expected files', function (done) { 23 | var expected = [ 24 | // add files you expect to exist here. 25 | '.jshintrc', 26 | '.editorconfig' 27 | ]; 28 | 29 | helpers.mockPrompt(this.app, { 30 | 'someOption': 'Y' 31 | }); 32 | this.app.options['skip-install'] = true; 33 | this.app.run({}, function () { 34 | helpers.assertFiles(expected); 35 | done(); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/test-load.js: -------------------------------------------------------------------------------- 1 | /*global describe, beforeEach, it*/ 2 | 'use strict'; 3 | 4 | var assert = require('assert'); 5 | 6 | describe('cg-angular generator', function () { 7 | it('can be imported without blowing up', function () { 8 | var app = require('../app'); 9 | assert(app !== undefined); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | var _ = require('underscore'); 4 | var chalk = require('chalk'); 5 | _.str = require('underscore.string'); 6 | _.mixin(_.str.exports()); 7 | var ngParseModule = require('ng-parse-module'); 8 | 9 | 10 | exports.JS_MARKER = ""; 11 | exports.LESS_MARKER = "/* Add Component LESS Above */"; 12 | 13 | exports.ROUTE_MARKER = "/* Add New Routes Above */"; 14 | exports.STATE_MARKER = "/* Add New States Above */"; 15 | 16 | exports.addToFile = function(filename,lineToAdd,beforeMarker){ 17 | try { 18 | var fullPath = path.resolve(process.cwd(),filename); 19 | var fileSrc = fs.readFileSync(fullPath,'utf8'); 20 | 21 | var indexOf = fileSrc.indexOf(beforeMarker); 22 | var lineStart = fileSrc.substring(0,indexOf).lastIndexOf('\n') + 1; 23 | var indent = fileSrc.substring(lineStart,indexOf); 24 | fileSrc = fileSrc.substring(0,indexOf) + lineToAdd + "\n" + indent + fileSrc.substring(indexOf); 25 | 26 | fs.writeFileSync(fullPath,fileSrc); 27 | } catch(e) { 28 | throw e; 29 | } 30 | }; 31 | 32 | exports.processTemplates = function(name,dir,type,that,defaultDir,configName,module){ 33 | 34 | if (!defaultDir) { 35 | defaultDir = 'templates' 36 | } 37 | if (!configName) { 38 | configName = type + 'Templates'; 39 | } 40 | 41 | var templateDirectory = path.join(path.dirname(that.resolved),defaultDir); 42 | if(that.config.get(configName)){ 43 | templateDirectory = path.join(process.cwd(),that.config.get(configName)); 44 | } 45 | _.chain(fs.readdirSync(templateDirectory)) 46 | .filter(function(template){ 47 | return template[0] !== '.'; 48 | }) 49 | .each(function(template){ 50 | var customTemplateName = template.replace(type,name); 51 | var templateFile = path.join(templateDirectory,template); 52 | //create the file 53 | that.template(templateFile,path.join(dir,customTemplateName)); 54 | //inject the file reference into index.html/app.less/etc as appropriate 55 | exports.inject(path.join(dir,customTemplateName),that,module); 56 | }); 57 | }; 58 | 59 | exports.inject = function(filename,that,module) { 60 | //special case to skip unit tests 61 | if (_(filename).endsWith('-spec.js') || 62 | _(filename).endsWith('_spec.js') || 63 | _(filename).endsWith('-test.js') || 64 | _(filename).endsWith('_test.js')) { 65 | return; 66 | } 67 | 68 | var ext = path.extname(filename); 69 | if (ext[0] === '.') { 70 | ext = ext.substring(1); 71 | } 72 | var config = that.config.get('inject')[ext]; 73 | if (config) { 74 | var configFile = _.template(config.file)({module:path.basename(module.file,'.js')}); 75 | var injectFileRef = filename; 76 | if (config.relativeToModule) { 77 | configFile = path.join(path.dirname(module.file),configFile); 78 | injectFileRef = path.relative(path.dirname(module.file),filename); 79 | } 80 | injectFileRef = injectFileRef.replace(/\\/g,'/'); 81 | var lineTemplate = _.template(config.template)({filename:injectFileRef}); 82 | exports.addToFile(configFile,lineTemplate,config.marker); 83 | that.log.writeln(chalk.green(' updating') + ' %s',path.basename(configFile)); 84 | } 85 | }; 86 | 87 | exports.injectRoute = function(moduleFile,uirouter,name,route,routeUrl,that){ 88 | 89 | routeUrl = routeUrl.replace(/\\/g,'/'); 90 | 91 | if (uirouter){ 92 | var code = '$stateProvider.state(\''+name+'\', {\n url: \''+route+'\',\n templateUrl: \''+routeUrl+'\'\n });'; 93 | exports.addToFile(moduleFile,code,exports.STATE_MARKER); 94 | } else { 95 | exports.addToFile(moduleFile,'$routeProvider.when(\''+route+'\',{templateUrl: \''+routeUrl+'\'});',exports.ROUTE_MARKER); 96 | } 97 | 98 | that.log.writeln(chalk.green(' updating') + ' %s',path.basename(moduleFile)); 99 | 100 | }; 101 | 102 | exports.getParentModule = function(dir){ 103 | //starting this dir, find the first module and return parsed results 104 | if (fs.existsSync(dir)) { 105 | var files = fs.readdirSync(dir); 106 | for (var i = 0; i < files.length; i++) { 107 | if (path.extname(files[i]) !== '.js') { 108 | continue; 109 | } 110 | var results = ngParseModule.parse(path.join(dir,files[i])); 111 | if (results) { 112 | return results; 113 | } 114 | } 115 | } 116 | 117 | if (fs.existsSync(path.join(dir,'.yo-rc.json'))) { 118 | //if we're in the root of the project then bail 119 | return; 120 | } 121 | 122 | return exports.getParentModule(path.join(dir,'..')); 123 | }; 124 | 125 | exports.askForModule = function(type,that,cb){ 126 | 127 | var modules = that.config.get('modules'); 128 | var mainModule = ngParseModule.parse('app.js'); 129 | mainModule.primary = true; 130 | 131 | if (!modules || modules.length === 0) { 132 | cb.bind(that)(mainModule); 133 | return; 134 | } 135 | 136 | var choices = _.pluck(modules,'name'); 137 | choices.unshift(mainModule.name + ' (Primary Application Module)'); 138 | 139 | var prompts = [ 140 | { 141 | name:'module', 142 | message:'Which module would you like to place the new ' + type + '?', 143 | type: 'list', 144 | choices: choices, 145 | default: 0 146 | } 147 | ]; 148 | 149 | that.prompt(prompts, function (props) { 150 | 151 | var i = choices.indexOf(props.module); 152 | 153 | var module; 154 | 155 | if (i === 0) { 156 | module = mainModule; 157 | } else { 158 | module = ngParseModule.parse(modules[i-1].file); 159 | } 160 | 161 | cb.bind(that)(module); 162 | }.bind(that)); 163 | 164 | }; 165 | 166 | exports.askForDir = function(type,that,module,ownDir,cb){ 167 | 168 | that.module = module; 169 | that.appname = module.name; 170 | that.dir = path.dirname(module.file); 171 | 172 | var configedDir = that.config.get(type + 'Directory'); 173 | if (!configedDir){ 174 | configedDir = '.'; 175 | } 176 | var defaultDir = path.join(that.dir,configedDir,'/'); 177 | defaultDir = path.relative(process.cwd(),defaultDir); 178 | 179 | if (ownDir) { 180 | defaultDir = path.join(defaultDir,that.name); 181 | } 182 | 183 | defaultDir = path.join(defaultDir,'/'); 184 | 185 | var dirPrompt = [ 186 | { 187 | name:'dir', 188 | message:'Where would you like to create the '+type+' files?', 189 | default: defaultDir, 190 | validate: function(dir){ 191 | if (!module.primary) { 192 | //ensure dir is in module dir or subdir of it 193 | dir = path.resolve(dir); 194 | if (path.relative(that.dir,dir).substring(0,2) === '..') { 195 | return 'Files must be placed inside the module directory or a subdirectory of the module.' 196 | } 197 | } 198 | return true; 199 | } 200 | } 201 | ]; 202 | 203 | var dirPromptCallback = function (props) { 204 | 205 | that.dir = path.join(props.dir,'/'); 206 | var dirToCreate = that.dir; 207 | if (ownDir){ 208 | dirToCreate = path.join(dirToCreate, '..'); 209 | } 210 | 211 | if (!fs.existsSync(dirToCreate)) { 212 | that.prompt([{ 213 | name:'isConfirmed', 214 | type:'confirm', 215 | message:chalk.cyan(dirToCreate) + ' does not exist. Create it?' 216 | }],function(props){ 217 | if (props.isConfirmed){ 218 | cb(); 219 | } else { 220 | that.prompt(dirPrompt,dirPromptCallback); 221 | } 222 | }); 223 | } else if (ownDir && fs.existsSync(that.dir)){ 224 | //if the dir exists and this type of thing generally is inside its own dir, confirm it 225 | that.prompt([{ 226 | name:'isConfirmed', 227 | type:'confirm', 228 | message:chalk.cyan(that.dir) + ' already exists. Components of this type contain multiple files and are typically put inside directories of their own. Continue?' 229 | }],function(props){ 230 | if (props.isConfirmed){ 231 | cb(); 232 | } else { 233 | that.prompt(dirPrompt,dirPromptCallback); 234 | } 235 | }); 236 | } else { 237 | cb(); 238 | } 239 | 240 | }; 241 | 242 | that.prompt(dirPrompt,dirPromptCallback); 243 | 244 | }; 245 | 246 | exports.askForModuleAndDir = function(type,that,ownDir,cb) { 247 | exports.askForModule(type,that,function(module){ 248 | exports.askForDir(type,that,module,ownDir,cb); 249 | }); 250 | }; 251 | 252 | exports.getNameArg = function(that,args){ 253 | if (args.length > 0){ 254 | that.name = args[0]; 255 | } 256 | }; 257 | 258 | exports.addNamePrompt = function(that,prompts,type){ 259 | if (!that.name){ 260 | prompts.splice(0,0,{ 261 | name:'name', 262 | message: 'Enter a name for the ' + type + '.', 263 | validate: function(input){ 264 | return true; 265 | } 266 | }); 267 | } 268 | } 269 | --------------------------------------------------------------------------------