11 | Add a <%= resourceName %>
12 | <% } %>
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ~
2 | build/
3 | builds/
4 | [Bb]in
5 | */bin/*
6 | [Dd]ebug*/
7 | obj/*
8 | [Rr]elease*/
9 | _ReSharper*/
10 | zip/
11 | [Tt]est[Rr]esult
12 | [Bb]uild[Ll]og.*
13 | *.swp
14 | *.userprefs
15 | *.obj
16 | *.pdb
17 | *.user
18 | *.aps
19 | *.pch
20 | *.vspscc
21 | BuildLogs/*
22 | packages/*
23 | *.vssscc
24 | *.pidb
25 | *_i.c
26 | *_p.c
27 | *.ncb
28 | *.suo
29 | *.tlb
30 | *.tlh
31 | *.bak
32 | *.cache
33 | *.ilk
34 | *.log
35 | *.lib
36 | *.sbr
37 | *.scc
38 | *.zip
39 | .idea/
40 | *.[Rr]e[Ss]harper
41 | *.[Pp]ublish.xml
42 | *-results.xml
43 | *.InstallLog
44 | [Ll]ogs/
45 | packages/
46 | *.dll
47 | node_modules/
48 |
--------------------------------------------------------------------------------
/app/templates/root/src/app/app.spec.js:
--------------------------------------------------------------------------------
1 | describe('AppController', function () {
2 | describe('isCurrentUrl', function () {
3 | var AppCtrl, $location, $scope;
4 |
5 | beforeEach(module('<%= projectName %>'));
6 |
7 | beforeEach(inject(function ($controller, _$location_, $rootScope) {
8 | $location = _$location_;
9 | $scope = $rootScope.$new();
10 | AppCtrl = $controller('AppController', { $location: $location, $scope: $scope });
11 | }));
12 |
13 | it('should pass a dummy test', inject(function () {
14 | expect(AppCtrl).toBeTruthy();
15 | }));
16 | });
17 | });
18 |
19 |
--------------------------------------------------------------------------------
/app/templates/root/_bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "<%= projectName %>",
3 | "version": "0.0.1",
4 | "_comment": "Any dependencies added here require a manual edit to vendor_files in /Gruntfile.js and (??? verify the following) /build/karma-unit.js (e.g 'vendor/angular-resource/angular-resource.js')",
5 | "devDependencies": {
6 | "angular": "1.3.15",
7 | <% if (includeAngularResource) {%>"angular-resource": "1.3.15",<% } %>
8 | "angular-mocks": "1.3.15",
9 | "bootstrap": "~3.3",
10 | "angular-bootstrap": "~0.12.0",
11 | "angular-ui-router": "~0.2.13"
12 | },
13 | "dependencies": {},
14 | "resolutions": {
15 | "angular": "1.3.15"
16 | }
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/module/templates/_moduleAdd.js:
--------------------------------------------------------------------------------
1 | (function(module) {
2 |
3 | module.controller('Add<%= resourceName %>Controller', function ($window, <%= resourceName %>) {
4 | var model = this;
5 | model.<%= resourceInstance %> = new <%= resourceName %>();
6 | model.save = save;
7 |
8 | init();
9 |
10 | function init() {
11 |
12 | }
13 |
14 | function save() {
15 | model.<%= resourceInstance %>.$save()
16 | .then(function (data) {
17 | $window.location = '#/<%= lowerModuleName %>';
18 | })
19 | .catch(function (error) {
20 | console.log(error);
21 | })
22 | .finally();
23 | }
24 | });
25 |
26 | }(angular.module("<%= projectName %>.<%= camelModuleName %>")));
27 |
--------------------------------------------------------------------------------
/module/templates/directives/_moduleForm.js:
--------------------------------------------------------------------------------
1 | (function(module) {
2 | module.directive('<%= resourceInstance %>Form', function() {
3 | var linker = function(scope, element, attrs) {
4 | // do DOM Manipulation here
5 | };
6 | return {
7 | restrict: 'A',
8 | templateUrl: '<%= lowerModuleName %>/directives/<%= resourceInstance %>Form.tpl.html',
9 | link: linker,
10 | controller: '<%= resourceName %>FormController as model',
11 | bindToController: true,
12 | scope: {
13 | <%= resourceInstance %>: '=<%= resourceInstance %>Form',
14 | lookups: '=<%= resourceInstance %>Lookups'
15 | }
16 | };
17 | });
18 |
19 | module.controller('<%= resourceName %>FormController', function() {
20 | var model = this;
21 |
22 | });
23 | })(angular.module('<%= projectName %>.<%= camelModuleName %>'));
--------------------------------------------------------------------------------
/app/templates/root/src/common/README.md:
--------------------------------------------------------------------------------
1 | # The `src/common/` Directory
2 |
3 | The `src/common/` directory houses internal and third-party re-usable
4 | components. Essentially, this folder is for everything that isn't completely
5 | specific to this application.
6 |
7 | Each component resides in its own directory that may then be structured any way
8 | the developer desires. The build system will read all `*.js` files that do not
9 | end in `.spec.js` as source files to be included in the final build, all
10 | `*.spec.js` files as unit tests to be executed, and all `*.tpl.html` files as
11 | templates to compiled into the `$templateCache`. There is currently no way to
12 | handle components that do not meet this pattern.
13 |
14 | ```
15 | src/
16 | |- common/
17 | | |- someComponent/
18 | ```
19 |
20 | Every component contained here should be drag-and-drop reusable in any other
21 | project; they should depend on no other components that aren't similarly
22 | drag-and-drop reusable.
23 |
--------------------------------------------------------------------------------
/test/test-creation.js:
--------------------------------------------------------------------------------
1 | /*global describe, beforeEach, it */
2 | 'use strict';
3 | var path = require('path');
4 | var helpers = require('yeoman-generator').test;
5 |
6 | describe('ngbp generator', function () {
7 | beforeEach(function (done) {
8 | helpers.testDirectory(path.join(__dirname, 'temp'), function (err) {
9 | if (err) {
10 | return done(err);
11 | }
12 |
13 | this.app = helpers.createGenerator('ngbp:app', [
14 | '../../app'
15 | ]);
16 | done();
17 | }.bind(this));
18 | });
19 |
20 | it('creates expected files', function (done) {
21 | var expected = [
22 | // add files you expect to exist here.
23 | '.jshintrc',
24 | '.editorconfig'
25 | ];
26 |
27 | helpers.mockPrompt(this.app, {
28 | 'someOption': true
29 | });
30 | this.app.options['skip-install'] = true;
31 | this.app.run({}, function () {
32 | helpers.assertFile(expected);
33 | done();
34 | });
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/app/templates/root/src/app/home/home.module.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Each module has a .module.js file. This file contains the angular module declaration -
3 | * angular.module("moduleName", []);
4 | * The build system ensures that all the *.module.js files get included prior to any other .js files, which
5 | * ensures that all module declarations occur before any module references.
6 | */
7 | (function(module) {
8 |
9 | module.config(function ($stateProvider) {
10 | $stateProvider.state('home', {
11 | url: '/home',
12 | views: {
13 | "main": {
14 | controller: 'HomeController as model',
15 | templateUrl: 'home/home.tpl.html'
16 | }
17 | },
18 | data:{ pageTitle: 'Home' }
19 | });
20 | });
21 |
22 | // The name of the module, followed by its dependencies (at the bottom to facilitate enclosure)
23 | }(angular.module("<%= projectName %>.home", [
24 | 'ui.router'
25 | ])));
26 |
--------------------------------------------------------------------------------
/module/templates/_module.js:
--------------------------------------------------------------------------------
1 | (function(module) {
2 | <% if (includeRest) { %>
3 | module.controller('<%= capitalModuleName %>Controller', function (<%= resourceName %>) {
4 | var model = this;
5 | model.loading = false;
6 | model.<%= camelModuleName %> = [];
7 |
8 | init();
9 |
10 | function init() {
11 | model.<%= camelModuleName %> = get<%= capitalModuleName %>();
12 | }
13 |
14 | function get<%= capitalModuleName %>() {
15 | model.loading = true;
16 | <%= resourceName %>.query().$promise.then(function(response) {
17 | model.<%= camelModuleName %> = response;
18 | model.loading = false;
19 | });
20 | }
21 | });
22 | <% } else { %>
23 | module.controller('<%= capitalModuleName %>Controller', function () {
24 | var model = this;
25 |
26 | init();
27 |
28 | function init() {
29 |
30 | }
31 | });
32 | <% } %>
33 | }(angular.module("<%= projectName %>.<%= camelModuleName %>")));
--------------------------------------------------------------------------------
/app/templates/root/_package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "<%= projectName %>",
3 | "version": "0.0.1",
4 | "author": "<%= author %>",
5 | "test": "<%= projectName %>",
6 | "devDependencies": {
7 | "grunt": "~0.4.2",
8 | "grunt-coffeelint": "0.0.10",
9 | "grunt-contrib-clean": "^0.5.0",
10 | "grunt-contrib-coffee": "^0.10.1",
11 | "grunt-contrib-concat": "^0.3.0",
12 | "grunt-contrib-copy": "^0.5.0",
13 | "grunt-contrib-jshint": "^0.9.2",
14 | "grunt-contrib-less": "~0.11.0",
15 | "grunt-contrib-nodeunit": "~0.2.0",
16 | "grunt-contrib-uglify": "~0.2.7",
17 | "grunt-contrib-watch": "^0.6.1",
18 | "grunt-express": "^1.2.1",
19 | "grunt-html2js": "^0.2.4",
20 | "grunt-karma": "^0.8.2",
21 | "grunt-ng-annotate": "^0.3.2",
22 | "karma": "^0.12.31",
23 | "karma-chrome-launcher": "^0.1.3",
24 | "karma-coffee-preprocessor": "^0.2.1",
25 | "karma-firefox-launcher": "^0.1.3",
26 | "karma-jasmine": "^0.1.5",
27 | "karma-phantomjs-launcher": "^0.1.3",
28 | "lodash": "^2.4.1"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/module/templates/_moduleEdit.js:
--------------------------------------------------------------------------------
1 | (function(module) {
2 |
3 | module.controller('Edit<%= resourceName %>Controller', function ($state, $window, <%= resourceName %>) {
4 | var model = this;
5 | model.<%= resourceInstance %> = {};
6 | model.loading = false;
7 | model.update = update;
8 |
9 | init();
10 |
11 | function init() {
12 | model.<%= resourceInstance %> = get<%= resourceName %>();
13 | }
14 |
15 | function get<%= resourceName %>() {
16 | model.loading = true;
17 | <%= resourceName %>.get({ id: $state.params.id }).$promise
18 | .then(function(response) {
19 | model.<%= resourceInstance %> = response;
20 | model.loading = false;
21 | });
22 | }
23 |
24 | function update() {
25 | model.<%= resourceInstance %>.$update()
26 | .then(function(response) {
27 | $window.location = '#/<%= lowerModuleName %>';
28 | });
29 | }
30 | });
31 |
32 | }(angular.module("<%= projectName %>.<%= camelModuleName %>")));
33 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "generator-ngbp",
3 | "version": "0.2.3",
4 | "description": "Yeoman generator based on the ngBoilerplate kickstarter, a best-practice boilerplate for scalable Angular projects built on a highly modular, folder-by-feature structure. Now with easily switchable mocking via $httpBackend and RESTful resource scaffolding.",
5 | "keywords": [
6 | "yeoman-generator",
7 | "angular",
8 | "angularjs",
9 | "boilerplate",
10 | "ngboilerplate",
11 | "scaffold",
12 | "enterprise",
13 | "framework",
14 | "module",
15 | "client",
16 | "grunt",
17 | "karma",
18 | "express",
19 | "app"
20 | ],
21 | "license": "MIT",
22 | "main": "app/index.js",
23 | "repository": "thardy/generator-ngbp",
24 | "author": {
25 | "name": "Tim Hardy",
26 | "email": "",
27 | "url": "https://github.com/thardy"
28 | },
29 | "engines": {
30 | "node": ">=0.10.0"
31 | },
32 | "scripts": {
33 | "test": "mocha"
34 | },
35 | "dependencies": {
36 | "chalk": "~0.4.0",
37 | "inflected": "^1.1.6",
38 | "lodash": "^3.7.0",
39 | "touch": "0.0.3",
40 | "yeoman-generator": "~0.16.0",
41 | "yo": ">=1.0.0"
42 | },
43 | "devDependencies": {
44 | "mocha": "*"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/app/templates/root/src/_index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | <%% styles.forEach( function ( file ) { %>
10 | <%% }); %>
11 |
12 | <%% scripts.forEach( function ( file ) { %>
13 | <%% }); %>
14 |
15 |
16 |
17 |
27 |
28 |
29 |
30 |
31 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/app/templates/root/src/less/main.less:
--------------------------------------------------------------------------------
1 | /**
2 | * This is the main application stylesheet. It should include or import all
3 | * stylesheets used throughout the application as this is the only stylesheet in
4 | * the Grunt configuration that is automatically processed.
5 | */
6 |
7 |
8 | /**
9 | * First, we include the Twitter Bootstrap LESS files. Only the ones used in the
10 | * project should be imported as the rest are just wasting space.
11 | */
12 |
13 | @import '../../vendor/bootstrap/less/bootstrap.less';
14 |
15 | /**
16 | * This is our main variables file. We must include it last so we can overwrite any variable
17 | * definitions in our imported stylesheets.
18 | */
19 |
20 | @import 'variables.less';
21 |
22 |
23 | /**
24 | * Typography
25 | */
26 |
27 | @font-face {
28 | font-family: 'Roboto';
29 | font-style: normal;
30 | font-weight: 400;
31 | src: local('Roboto Regular'), local('Roboto-Regular'), url(fonts/Roboto-Regular.woff) format('woff');
32 | }
33 |
34 |
35 | /**
36 | * Now that all app-wide styles have been applied, we can load the styles for
37 | * all the submodules and components we are using.
38 | *
39 | * TODO: In a later version of this boilerplate, I'd like to automate this.
40 | */
41 |
42 | @import '../app/home/home.less';
43 |
44 |
--------------------------------------------------------------------------------
/app/templates/root/src/app/home/_home.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | # Each section of the site has its own module. It probably also has
3 | # submodules, though this boilerplate is too simple to demonstrate it. Within
4 | # 'src/app/home', however, could exist several additional folders representing
5 | # additional modules that would then be listed as dependencies of this one.
6 | # For example, a 'note' section could have the submodules 'note.create',
7 | # 'note.delete', 'note.edit', etc.
8 | #
9 | # Regardless, so long as dependencies are managed correctly, the build process
10 | # will automatically take take of the rest.
11 | ###
12 | do (module=angular.module "<%= projectName %>.home") ->
13 | # As you add controllers to a module and they grow in size, feel free to
14 | # place them in their own files. Let each module grow organically, adding
15 | # appropriate organization and sub-folders as needed.
16 | module.controller 'HomeController', () ->
17 | model = this
18 | model.someVar = 'blue'
19 | model.someList = ['one', 'two', 'three']
20 | model.someFunctionUsedByTheHomePage = ->
21 | alert('Congratulations')
22 |
23 | init = ->
24 | # A definitive place to put everything that needs to run when the
25 | # controller starts. Avoid writing any code outside of this function
26 | # that executes immediately.
27 |
28 | init()
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/app/templates/root/src/less/README.md:
--------------------------------------------------------------------------------
1 | # The `src/less` Directory
2 |
3 | This folder is actually fairly self-explanatory: it contains your LESS/CSS files to be compiled during the build.
4 | The only important thing to note is that *only* `main.less` will be processed during the build, meaning that all
5 | other stylesheets must be *imported* into that one.
6 |
7 | This should operate somewhat like the routing; the `main.less` file contains all of the site-wide styles, while
8 | any styles that are route-specific should be imported into here from LESS files kept alongside the JavaScript
9 | and HTML sources of that component. For example, the `home` section of the site has some custom styles, which
10 | are imported like so:
11 |
12 | ```css
13 | @import '../app/home/home.less';
14 | ```
15 |
16 | The same principal, though not demonstrated in the code, would also apply to reusable components. CSS or LESS
17 | files from external components would also be imported. If, for example, we had a Twitter feed directive with
18 | an accompanying template and style, we would similarly import it:
19 |
20 | ```css
21 | @import '../common/twitterFeed/twitterFeedDirective.less';
22 | ```
23 |
24 | Using this decentralized approach for all our code (JavaScript, HTML, and CSS) creates a framework where a
25 | component's directory can be dragged and dropped into *any other project* and it will "just work".
26 |
27 | I would like to eventually automate the importing during the build so that manually importing it here would no
28 | longer be required, but more thought must be put in to whether this is the best approach.
29 |
--------------------------------------------------------------------------------
/app/templates/root/src/_README.md:
--------------------------------------------------------------------------------
1 | # The `src` Directory
2 |
3 | ## Overview
4 |
5 | The `src/` directory contains all code used in the application along with all
6 | tests of such code.
7 |
8 | ```
9 | src/
10 | |- app/
11 | | |- about/
12 | | |- home/
13 | | |- app.js
14 | | |- app.spec.js
15 | |- assets/
16 | |- common/
17 | |- less/
18 | | |- main.less
19 | | |- variables.less
20 | |- index.html
21 | ```
22 |
23 | - `src/app/` - application-specific code, i.e. code not likely to be reused in
24 | another application. [Read more »](app/README.md)
25 | - `src/assets/` - static files like fonts and images.
26 | [Read more »](assets/README.md)
27 | - `src/common/` - third-party libraries or components likely to be reused in
28 | another application. [Read more »](common/README.md)
29 | - `src/less/` - LESS CSS files. [Read more »](less/README.md)
30 | - `src/index.html` - this is the HTML document of the single-page application.
31 | See below.
32 |
33 | See each directory for a detailed explanation.
34 |
35 | ## `index.html`
36 |
37 | The `index.html` file is the HTML document of the single-page application (SPA)
38 | that should contain all markup that applies to everything in the app, such as
39 | the header and footer. It declares with `ngApp` that this is `<%= projectName %>`,
40 | specifies the main `AppController` controller, and contains the `ngView` directive
41 | into which route templates are placed.
42 |
43 | Unlike any other HTML document (e.g. the templates), `index.html` is compiled as
44 | a Grunt template, so variables from `Gruntfile.js` and `package.json` can be
45 | referenced from within it. Changing `name` in `package.json` from
46 | "<%= projectName %>" will rename the resultant CSS and JavaScript placed in `build/`,
47 | so this HTML references them by variable for convenience.
48 |
--------------------------------------------------------------------------------
/app/templates/root/src/app/home/_home.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Each section of the site has its own module. It probably also has
3 | * submodules, though this boilerplate is too simple to demonstrate it. Within
4 | * 'src/app/home', however, could exist several additional folders representing
5 | * additional modules that would then be listed as dependencies of this one.
6 | * For example, a 'note' section could have the submodules 'note.create',
7 | * 'note.delete', 'note.edit', etc.
8 | *
9 | * Regardless, so long as dependencies are managed correctly, the build process
10 | * will automatically take take of the rest.
11 | */
12 | (function(module) {
13 |
14 | // As you add controllers to a module and they grow in size, feel free to place them in their own files.
15 | // Let each module grow organically, adding appropriate organization and sub-folders as needed.
16 | module.controller('HomeController', function () {
17 | // The top section of a controller should be lean and make it easy to see the "signature" of the controller
18 | // at a glance. All function definitions should be contained lower down.
19 | var model = this;
20 | model.someVar = 'blue';
21 | model.someList = ['one', 'two', 'three'];
22 | model.someFunctionUsedByTheHomePage = someFunctionUsedByTheHomePage;
23 |
24 | init();
25 |
26 | function init() {
27 | // A definitive place to put everything that needs to run when the controller starts. Avoid
28 | // writing any code outside of this function that executes immediately.
29 | }
30 |
31 | function someFunctionUsedByTheHomePage() {
32 | alert('Congratulations');
33 | }
34 |
35 | });
36 |
37 | // The name of the module, followed by its dependencies (at the bottom to facilitate enclosure)
38 | }(angular.module("<%= projectName %>.home")));
--------------------------------------------------------------------------------
/module/templates/_module.module.js:
--------------------------------------------------------------------------------
1 | (function(module) {
2 | <% if (includeRest) { %>
3 | module.config(function ($stateProvider) {
4 | $stateProvider
5 | .state('<%= routeFriendlyName %>', {
6 | url: '/<%= path %>',
7 | views: {
8 | 'main@': {
9 | controller: '<%= capitalModuleName %>Controller as model',
10 | templateUrl: '<%= path %>/<%= filePrefix %>.tpl.html'
11 | }
12 | },
13 | data:{ pageTitle: '<%= capitalModuleName %>' }
14 | })
15 | .state('add<%= resourceName %>', {
16 | url: '/<%= path %>/add-<%= singularKebabModuleName %>',
17 | views: {
18 | 'main@': {
19 | controller: 'Add<%= resourceName %>Controller as model',
20 | templateUrl: '<%= path %>/add<%= resourceName %>.tpl.html'
21 | }
22 | },
23 | data:{ pageTitle: 'Add <%= resourceName %>' }
24 | })
25 | .state('edit<%= resourceName %>', {
26 | url: '/<%= path %>/edit-<%= singularKebabModuleName %>/{id}',
27 | views: {
28 | 'main@': {
29 | controller: 'Edit<%= resourceName %>Controller as model',
30 | templateUrl: '<%= path %>/edit<%= resourceName %>.tpl.html'
31 | }
32 | },
33 | data:{ pageTitle: 'Edit <%= resourceName %>' }
34 | });
35 | });
36 | <% } else { %>
37 | module.config(function ($stateProvider) {
38 | $stateProvider.state('<%= routeFriendlyName %>', {
39 | url: '/<%= kebabModuleName %>',
40 | views: {
41 | "main": {
42 | controller: '<%= capitalModuleName %>Controller as model',
43 | templateUrl: '<%= path %>/<%= kebabModuleName %>.tpl.html'
44 | }
45 | },
46 | data:{ pageTitle: '<%= capitalModuleName %>' }
47 | });
48 | });
49 | <% } %>
50 | }(angular.module('<%= projectName %>.<%= camelModuleName %>', [
51 | 'ui.router'<% if (includeRest) { %>,
52 | 'ngResource'<% } %>
53 | ])));
54 |
--------------------------------------------------------------------------------
/app/templates/root/karma/karma-unit.tpl.js:
--------------------------------------------------------------------------------
1 | module.exports = function ( karma ) {
2 | karma.set({
3 | /**
4 | * From where to look for files, starting with the location of this file.
5 | */
6 | basePath: '../',
7 |
8 | /**
9 | * This is the list of file patterns to load into the browser during testing.
10 | */
11 | files: [
12 | <% scripts.forEach( function ( file ) { %>'<%= file %>',
13 | <% }); %>
14 | 'src/**/*.module.js',
15 | 'src/**/*.js',
16 | 'src/**/*.module.coffee',
17 | 'src/**/*.coffee',
18 | ],
19 | exclude: [
20 | 'src/assets/**/*.js'
21 | ],
22 | frameworks: [ 'jasmine' ],
23 | plugins: [ 'karma-jasmine', 'karma-firefox-launcher', 'karma-chrome-launcher', 'karma-phantomjs-launcher', 'karma-coffee-preprocessor' ],
24 | preprocessors: {
25 | '**/*.coffee': 'coffee',
26 | },
27 |
28 | /**
29 | * How to report, by default.
30 | */
31 | reporters: 'dots',
32 |
33 | /**
34 | * On which port should the browser connect, on which port is the test runner
35 | * operating, and what is the URL path for the browser to use.
36 | */
37 | port: 9018,
38 | runnerPort: 9100,
39 | urlRoot: '/',
40 |
41 | /**
42 | * Disable file watching by default.
43 | */
44 | autoWatch: false,
45 |
46 | /**
47 | * The list of browsers to launch to test on. This includes only "Firefox" by
48 | * default, but other browser names include:
49 | * Chrome, ChromeCanary, Firefox, Opera, Safari, PhantomJS
50 | *
51 | * Note that you can also use the executable name of the browser, like "chromium"
52 | * or "firefox", but that these vary based on your operating system.
53 | *
54 | * You may also leave this blank and manually navigate your browser to
55 | * http://localhost:9018/ when you're running tests. The window/tab can be left
56 | * open and the tests will automatically occur there during the build. This has
57 | * the aesthetic advantage of not launching a browser every time you save.
58 | */
59 | browsers: [
60 | 'Firefox'
61 | ]
62 | });
63 | };
64 |
65 |
--------------------------------------------------------------------------------
/app/templates/root/src/app/README.md:
--------------------------------------------------------------------------------
1 | # The `src/app` Directory
2 |
3 | ## Overview
4 |
5 | ```
6 | src/
7 | |- app/
8 | | |- home/
9 | | |- about/
10 | | |- app.js
11 | | |- app.spec.js
12 | ```
13 |
14 | The `src/app` directory contains all code specific to this application. Apart
15 | from `app.js` and its accompanying tests (discussed below), this directory is
16 | filled with subdirectories corresponding to high-level sections of the
17 | application, often corresponding to top-level routes. Each directory can have as
18 | many subdirectories as it needs, and the build system will understand what to
19 | do. For example, a top-level route might be "products", which would be a folder
20 | within the `src/app` directory that conceptually corresponds to the top-level
21 | route `/products`, though this is in no way enforced. Products may then have
22 | subdirectories for "create", "view", "search", etc. The "view" submodule may
23 | then define a route of `/products/:id`, ad infinitum.
24 |
25 | As `ngBoilerplate` is quite minimal, take a look at the two provided submodules
26 | to gain a better understanding of how these are used as well as to get a
27 | glimpse of how powerful this simple construct can be.
28 |
29 | ## `app.js`
30 |
31 | This is our main app configuration file. It kickstarts the whole process by
32 | requiring all the modules from `src/app` that we need. We must load these now to
33 | ensure the routes are loaded. If as in our "products" example there are
34 | subroutes, we only require the top-level module, and allow the submodules to
35 | require their own submodules.
36 |
37 | As a matter of course, we also require the template modules that are generated
38 | during the build.
39 |
40 | However, the modules from `src/common` should be required by the app
41 | submodules that need them to ensure proper dependency handling. These are
42 | app-wide dependencies that are required to assemble your app.
43 |
44 | ```js
45 | angular.module( '<%= projectName %>', [
46 | 'templates-app',
47 | 'templates-common',
48 | '<%= projectName %>.home',
49 | '<%= projectName %>.about'
50 | 'ui.state',
51 | 'ui.route'
52 | ])
53 | ```
54 |
55 | With app modules broken down in this way, all routing is performed by the
56 | submodules we include, as that is where our app's functionality is really
57 | defined. So all we need to do in `app.js` is specify a default route to follow,
58 | which route of course is defined in a submodule. In this case, our `home` module
59 | is where we want to start, which has a defined route for `/home` in
60 | `src/app/home/home.js`.
61 |
62 | ```js
63 | .config( function myAppConfig ( $stateProvider, $urlRouterProvider ) {
64 | $urlRouterProvider.otherwise( '/home' );
65 | })
66 | ```
67 |
68 | Use the main applications run method to execute any code after services
69 | have been instantiated.
70 |
71 | ```js
72 | .run( function () {
73 | })
74 | ```
75 |
76 | And then we define our main application controller. This is a good place for logic
77 | not specific to the template or route, such as menu logic or page title wiring.
78 |
79 | ```js
80 | .controller( 'AppCtrl', function AppCtrl ( $scope, $location ) {
81 | $scope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){
82 | if ( angular.isDefined( toState.data.pageTitle ) ) {
83 | $scope.pageTitle = toState.data.pageTitle + ' | <%= projectName %>' ;
84 | }
85 | });
86 | })
87 | ```
88 |
89 | ### Testing
90 |
91 | One of the design philosophies of `ngBoilerplate` is that tests should exist
92 | alongside the code they test and that the build system should be smart enough to
93 | know the difference and react accordingly. As such, the unit test for `app.js`
94 | is `app.spec.js`, though it is quite minimal.
95 |
--------------------------------------------------------------------------------
/app/templates/root/src/app/_mockApp.js:
--------------------------------------------------------------------------------
1 | (function(module) {
2 | module.run(function($httpBackend, $filter, $location) {
3 |
4 | // Intercept only the calls we want to mock
5 |
6 | // EXAMPLE CRUD INTERCEPTION OF A RESOURCE NAMED "Products".
7 | // Feel free to refactor this out into other files, but heed the following advice...
8 | // I wouldn't build automated tests on this or even try to maintain this for all features going forward because
9 | // it can become a nightmare. Once you get a feature working, I recommend simply re-purposing/overwriting this
10 | // code for the next feature. I've come to believe it's best used for rapid prototyping one feature at a time,
11 | // not for maintaining for all features.
12 |
13 | // *** Products *****************************************************************************************
14 | // I use objects because writing json in javascript is a pain and converting via $filter is easy.
15 | // This array will act as our fake database.
16 | var products = [
17 | { id: 1, name: 'Doohicky', description: 'It is what it is.' },
18 | { id: 2, name: 'Thingamajig', description: 'One of those things...' },
19 | { id: 3, name: 'Widget', description: 'A smaller version of the thingamajig.' }
20 | ];
21 |
22 | var indentSpacing = 2;
23 |
24 | // Products GET with id - these tend to be greedy so the more specific GET with parm HAS to be listed
25 | // before the generic GET all or the GET all will suck up the GET with parm requests.
26 | $httpBackend.whenGET(/api\/products\/[\d]+/).respond(function(method, url, data) {
27 | var path = $location.path(); // returns something like "/products/edit-product/1", so the id will be after the third slash
28 | var pathArray = path.split('/');
29 | var productId = pathArray[3];
30 | return [200, $filter('json')(getById(products, productId), indentSpacing), { 'Cache-control': 'no-cache' }];
31 | });
32 |
33 | // Products GET all
34 | $httpBackend.whenGET(/api\/products/).respond(function(method, url, data) {
35 | return [200, $filter('json')(products, indentSpacing), { 'Cache-control': 'no-cache' }];
36 | });
37 |
38 | // Products POST
39 | $httpBackend.whenPOST(/api\/products/).respond(function(method, url, data) {
40 | // Add to our products array, which is acting like a fake database
41 | var newProduct = angular.fromJson(data);
42 | newProduct.id = products.push(newProduct); // data should be the json product that was posted to us,
43 | // and the new length of products should make a fine fake Id
44 | return [200, newProduct, {}]; // respond expects us to return array containing status, data, headers
45 | });
46 |
47 | // Products PUT
48 | $httpBackend.whenPUT(/api\/products\/[\d]+/).respond(function(method, url, data) {
49 | // Update our products array, which is acting like a fake database
50 | var updatedProduct = angular.fromJson(data);
51 | var index = products.indexOf(getById(products, updatedProduct.id));
52 | products[index] = updatedProduct;
53 | return [201, updatedProduct, {}]; // respond expects us to return array containing status, data, headers
54 | });
55 | // ***************************************************************************************************
56 |
57 |
58 | // Now let everything else, everything not specified above, pass through to their real http calls
59 | $httpBackend.whenGET(/.+/).passThrough();
60 | $httpBackend.whenPOST(/.+/).passThrough();
61 | $httpBackend.whenPUT(/.+/).passThrough();
62 |
63 |
64 | // Helper functions
65 | function getById(array, id) {
66 | var len = array.length;
67 | for (i = 0; i < len; i++) {
68 | if (+array[i].id == +id) {
69 | return array[i];
70 | }
71 | }
72 | return null;
73 | }
74 | });
75 |
76 | // Your normal app is listed as a dependency, so all your functionality is there. We've basically just extended it
77 | // with this http interception code.
78 | })(angular.module('mockApp', ['<%=projectName%>', 'ngMockE2E']));
79 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # generator-ngbp
2 |
3 | > Yeoman Generator based on the popular ngBoilerplate AngularJS kickstarter. ngBoilerplate is a best-practice boilerplate for scalable Angular projects built on a highly modular, folder-by-feature structure. You work in vertical slices on a daily basis (view, controller, service, etc), so why not organize your projects to optimize your workflow, maximize discoverability, and get copy-paste module reuse for free?
4 |
5 | ## Latest Updates
6 | (4/22/15) Added some pretty hefty improvements - easily switchable mocking with $httpBackend and scaffolding of RESTful resources. See below. Also updated to Angular 1.3.15 and hardcoded the bower version to avoid issues. As always, please let me know of any issues.
7 |
8 | (12/29/14) Many best-practice improvements, including use of "Controller as model" syntax, cleaner controllers, plus separate \*.module.js file for module declarations and
9 | grunt build support for those files.
10 | Coffeescript is close but broken with the *.module.coffee being included twice in resultant js file. I could use some help from anyone who works
11 | in coffeescript regularly, especially coffeescript grunt builds.
12 |
13 | ## Quick Start
14 |
15 | Install generator-ngbp from npm, run:
16 |
17 | ```
18 | $ npm install -g generator-ngbp
19 | ```
20 |
21 | Create a new directory for your project and cd into it:
22 |
23 | ```
24 | $ mkdir my-new-project
25 | $ cd my-new-project
26 | ```
27 |
28 | Initiate the generator:
29 |
30 | ```
31 | $ yo ngbp
32 | ```
33 | ### Mocking
34 | You can now switch in and out of mocking mode by simply running ```grunt watchmock``` vs ```grunt watch```. Switching between the two will handle all the necessary configuration within your SPA. Running in mock mode will use $httpBackend to intercept any external http calls you manually configure and return whatever results you want.
35 |
36 | See mockApp.js for a full CRUD example (*actually, Delete isn't in yet*), and combine with the new RESTful module generator for a full, working, end-to-end example - great for prototyping data-driven UX with fully functional controllers and api-calling angular services without having to actually build the api first. Simply switch back to ```grunt watch``` and your code will now be making real external calls to a real (if it exists) api. This is my preferred mocking implementation because your code is written to actually make the http call, and the interception is handled by angular. When you are ready to hit a real api, you have no code changes to make.
37 |
38 | ### Sub-Generators
39 |
40 | There's only one subgenerator at the moment
41 | ngbp:module
42 |
43 | To create a new module...
44 |
45 | ```
46 | $ yo ngbp:module "moduleName"
47 | ```
48 |
49 | You can specify the root folder of the module via prompt - default is "app".
50 |
51 | If you specify a module name with a dot (e.g. account.profile), you will be prompted to confirm that you want to create a sub module. Choosing y (default), a new 'profile' module will be created in account folder.
52 |
53 | You will be prompted whether you want your module to wrap a RESTful resource. The default is no.
54 |
55 | You have to authorize the overwrite of app.js when the subgenerator adds a dependency for your new module (the default is Y, so you can just hit enter at the prompt).
56 |
57 | ##### RESTful Scaffolding
58 | If you choose yes to wrap a RESTful resource, you will get fully scaffolded RESTful controllers, views, and a service for CRUD support, using ngResource under the covers - so don't forget to answer 'y' when asked if you want angular-resource when you generate your app, or just add it yourself. The naming convention is currently setup to handle straightforward pluralized module names, like "products", and the scaffolding assumes an api hanging off whatever domain your spa is running on (e.g. http://mydomain.com/api/products). Try the following...
59 | ```
60 | yo ngbp:module "products"
61 | ```
62 | ...type ```y``` when asked if you want to wrap a RESTful resource, then switch your ```grunt watch``` over to ```grunt watchmock``` to see a fully-working example. Navigate to http://localhost:9000/#/products to view (there's no navigation scaffolding at this point, but the routing is there). The mocked results for "products" are handled in a heavily-commented mockApp.js, which you can edit to mock whatever you want.
63 |
64 | There's also still a bug with grunt watch that doesn't always see new files in new folders - https://github.com/gruntjs/grunt-contrib-watch/issues/70. Stopping and
65 | re-running grunt watch will always work though.
66 |
67 | ### ngBoilerplate Tips
68 |
69 | When adding bower modules, always install with
70 | ```
71 | $ bower install some-bower-module --save-dev
72 | ```
73 | Then manually edit the vendor_files.js variable in Gruntfile.js to add the full path to the js files you need from the vendor folder.
74 | This grunt variable is what is used to create the script tags in the header of your index.html in the build folder (dev site).
75 | When you run "grunt compile", this same variable is used to add the vendor files to the single, minified js file in the bin folder (prod site).
76 |
77 | ### More Info
78 |
79 | To learn more about ngBoilerplate, [click here](https://github.com/ngbp/ngbp)
80 |
81 |
82 |
83 | ## License
84 |
85 | MIT
86 |
--------------------------------------------------------------------------------
/app/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var util = require('util');
3 | var path = require('path');
4 | var yeoman = require('yeoman-generator');
5 | var chalk = require('chalk');
6 |
7 |
8 | var NgbpGenerator = yeoman.generators.Base.extend({
9 | init: function () {
10 | this.pkg = require('../package.json');
11 |
12 | this.on('end', function () {
13 | if (!this.options['skip-install']) {
14 | this.installDependencies({
15 | callback: function () {
16 | // Emit a new event - dependencies installed
17 | this.emit('dependenciesInstalled');
18 | }.bind(this)
19 | });
20 | }
21 | });
22 |
23 | // Now we can bind to the dependencies installed event
24 | this.on('dependenciesInstalled', function() {
25 | //this.spawnCommand('grunt', ['build']);
26 | this.log(chalk.green(
27 | '\nYou\'re good to go!!!!\n' +
28 | 'Simply running ') + chalk.cyan.bold("grunt watch") + chalk.green(' will do the following:\n' +
29 | ' - Build everything (concat, create js templates of html, etc) and place it into a "build" folder\n' +
30 | ' - Run all your tests\n' +
31 | ' - Watch your files for changes to do the above without any intervention\n' +
32 | ' - Launch express server to host your app at http://localhost:9000/index.html\n' +
33 | ' - Setup LiveReload so you immediately see changes in your browser (you still have to enable LiveReload on your browser)\n'));
34 | });
35 | },
36 |
37 | askFor: function () {
38 | var done = this.async();
39 |
40 | // have Yeoman greet the user
41 | this.log(this.yeoman);
42 |
43 | this.log(chalk.magenta('You\'re using the ngbp (AngularBoilerplate) generator, a best-practice boilerplate\n for any scale Angular project built on a highly modular, folder-by-feature structure.'));
44 |
45 | var prompts = [
46 | {
47 | name: 'projectName',
48 | message: 'What do you want to name your project?\n e.g. angular.module(\'exactlyWhatYouTypeBelow\', [])\n ?',
49 | default: 'myProject'
50 | },
51 | {
52 | name: 'author',
53 | message: 'What is the author or company name?\n Used for copyright\'s in html, banners in code, and author prop in package.json\n ?',
54 | default: 'Somebody Special'
55 | },
56 | {
57 | type: 'confirm',
58 | name: 'useCoffeescript',
59 | message: 'Would you like to use Coffeescript?',
60 | default: false
61 | },
62 | {
63 | type: 'confirm',
64 | name: 'includeAngularResource',
65 | message: 'Do you want to include angular-resource, helpful for calling RESTful apis?',
66 | default: true
67 | },
68 | ];
69 |
70 | this.prompt(prompts, function (props) {
71 | this.projectName = props.projectName;
72 | this.author = props.author;
73 | this.useCoffeescript = props.useCoffeescript;
74 | this.includeAngularResource = props.includeAngularResource;
75 |
76 | done();
77 | }.bind(this));
78 | },
79 |
80 | config: function() {
81 | this.config.set('projectName', this.projectName);
82 | this.config.set('useCoffeescript', this.useCoffeescript);
83 | this.config.save();
84 | },
85 |
86 | _processDirectory: function (source, destination) {
87 | var root = this.isPathAbsolute(source) ? source : path.join(this.sourceRoot(), source);
88 | var files = this.expandFiles('**', { dot: true, cwd: root });
89 | var useCoffeescript = this.config.get('useCoffeescript');
90 |
91 | for (var i = 0; i < files.length; i++) {
92 | var f = files[i];
93 | var fExt = f.split('.').pop().toLowerCase();
94 | var fIsSource = path.dirname(f).split('/').shift() == 'src';
95 | var isExcluded = false;
96 | if (fIsSource) {
97 | if ((useCoffeescript && fExt == 'js') || (!useCoffeescript && fExt == 'coffee')) {isExcluded = true;}
98 | }
99 | var src = path.join(root, f);
100 | if (!isExcluded) {
101 | if (path.basename(f).indexOf('_') == 0) {
102 | var dest = path.join(destination, path.dirname(f), path.basename(f).replace(/^_/, ''));
103 | this.template(src, dest);
104 | }
105 | else {
106 | var dest = path.join(destination, f);
107 | this.copy(src, dest);
108 | }
109 | }
110 | }
111 | },
112 |
113 | app: function () {
114 | this._processDirectory('root', '');
115 | // this.mkdir('app');
116 | // this.mkdir('app/templates');
117 | //
118 | // this.copy('_package.json', 'package.json');
119 | // this.copy('_bower.json', 'bower.json');
120 | }
121 |
122 | });
123 |
124 | module.exports = NgbpGenerator;
--------------------------------------------------------------------------------
/module/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var util = require('util');
3 | var path = require('path');
4 | var touch = require('touch');
5 | var yeoman = require('yeoman-generator');
6 | var inflector = require('inflected');
7 | var _ = require('lodash');
8 |
9 |
10 | var ModuleGenerator = yeoman.generators.NamedBase.extend({
11 | init: function () {
12 | console.log('Creating the module - ' + this.name);
13 | },
14 |
15 | askFor: function () {
16 | var done = this.async();
17 | var prompts = [];
18 | // confirm with user if this module is a sub module (dot notation)
19 | if (this.name.indexOf('.') !== -1) {
20 | var parent = this.name.substr(0, this.name.lastIndexOf('.'));
21 | prompts.push({
22 | name: 'isSubmodule',
23 | message: 'Looks like ' + this.name + ' is a submodule, do you want to create it in ' + parent + ' module?',
24 | default: 'y'
25 | });
26 | }
27 | prompts.push({
28 | name: 'rootFolder',
29 | message: 'Where do you want to place this module - what is the root folder?',
30 | default: 'app'
31 | });
32 |
33 | prompts.push({
34 | type: 'confirm',
35 | name: 'includeRest',
36 | message: "Do you want this module to wrap a REST-ful resource? (we'll include a service to call an Api, basic controllers, and views)",
37 | default: false
38 | });
39 |
40 | this.prompt(prompts, function (props) {
41 | this.rootFolder = props.rootFolder;
42 | this.isSubmodule = props.isSubmodule == 'y';
43 | this.includeRest = props.includeRest;
44 |
45 | done();
46 | }.bind(this));
47 | },
48 |
49 | files: function () {
50 | var modulePath;
51 | this.projectName = this.config.get('projectName');
52 | var self = this;
53 | var capitalModuleName = [];
54 | // controller name cannot have a dot or dash in it and must be unique in the app
55 | this.name.split(/[\.-]/).forEach(function (value) {
56 | capitalModuleName.push(self._.capitalize(value));
57 | });
58 |
59 | this.capitalModuleName = capitalModuleName.join('');
60 | this.routeFriendlyName = this.name.replace('.', '-');
61 | this.camelModuleName = _.camelCase(this.capitalModuleName, false);
62 | this.resourceInstance = inflector.singularize(this.camelModuleName);
63 | this.resourceName = this._.capitalize(this.resourceInstance);
64 | this.kebabModuleName = _.kebabCase(this.name);
65 | this.singularKebabModuleName = _.kebabCase(inflector.singularize(this.name));
66 |
67 | if (this.isSubmodule) {
68 | this.lowerModuleName = this.name.toLowerCase().replace('.', '/');
69 | this.filePrefix = this.name.substr(this.name.lastIndexOf('.') + 1);
70 | this.path = this.name.replace(/\./g, '/')
71 | modulePath = path.join('src', this.rootFolder, this.path);
72 |
73 | this.resourceInstance = inflector.singularize(_.camelCase(this._.capitalize(this.filePrefix), false));
74 | this.resourceName = this._.capitalize(this.resourceInstance);
75 | this.singularKebabModuleName = _.kebabCase(inflector.singularize(this.filePrefix));
76 |
77 | } else {
78 | this.lowerModuleName = this.name.toLowerCase();
79 | this.filePrefix = this.name;
80 | this.path = this.name;
81 | modulePath = path.join('src', this.rootFolder, this.name);
82 | }
83 | this.mkdir(modulePath);
84 |
85 | if (this.config.get('useCoffeescript')) {
86 | this.template('_module.module.coffee', path.join(modulePath, this.filePrefix + '.module.coffee'));
87 | this.template('_module.coffee', path.join(modulePath, this.filePrefix + '.coffee'));
88 | this.template('_moduleSpec.coffee', path.join(modulePath, this.filePrefix + '.spec.coffee'));
89 |
90 | if (this.includeRest) {
91 | // if you're reading this, you should probably add the coffee files for the REST implementation
92 | }
93 | } else {
94 | this.template('_module.module.js', path.join(modulePath, this.filePrefix + '.module.js'));
95 | this.template('_module.js', path.join(modulePath, this.filePrefix + '.js'));
96 | this.template('_moduleSpec.js', path.join(modulePath, this.filePrefix + '.spec.js'));
97 |
98 |
99 | if (this.includeRest) {
100 | this.template('_moduleAdd.js', path.join(modulePath, 'add' + this.resourceName + '.js'));
101 | this.template('_moduleAdd.tpl.html', path.join(modulePath, 'add' + this.resourceName + '.tpl.html'));
102 | this.template('_moduleEdit.js', path.join(modulePath, 'edit' + this.resourceName + '.js'));
103 | this.template('_moduleEdit.tpl.html', path.join(modulePath, 'edit' + this.resourceName + '.tpl.html'));
104 | this.template('_moduleService.js', path.join(modulePath, this.resourceInstance + 'Service.js'));
105 |
106 | var directivesDir = path.join(modulePath, 'directives');
107 | this.mkdir(directivesDir);
108 | this.template(path.join('directives', '_moduleForm.js'), path.join(directivesDir, this.resourceInstance + 'Form.js'));
109 | this.template(path.join('directives', '_moduleForm.tpl.html'), path.join(directivesDir, this.resourceInstance + 'Form.tpl.html'));
110 | }
111 | }
112 | this.template('_moduleList.tpl.html', path.join(modulePath, this.filePrefix + '.tpl.html'));
113 | this.template('_module.less', path.join(modulePath, this.filePrefix + '.less'));
114 |
115 | this._addModuleToAppJs(this.projectName, this.camelModuleName, this.lowerModuleName);
116 |
117 | // if (this.includeRestfulService) {
118 | // // Add RESTful service stuff here
119 | // }
120 | },
121 |
122 | touchIndexHtml: function () {
123 | // Touch the index.html file to force the index grunt task to rebuild it (that task adds the new module to the scripts)
124 | var indexHtmlFilePath = 'src/index.html';
125 | touch(indexHtmlFilePath, {
126 | mtime: true
127 | });
128 | },
129 |
130 | _addModuleToAppJs: function app(projectName, camelModuleName, lowerModuleName) {
131 | var hook = '])));',
132 | path = 'src/app/app.js',
133 | insert = " '" + projectName + "." + camelModuleName + "',\n";
134 |
135 | if (this.config.get('useCoffeescript')) {
136 | hook = "'templates-app',";
137 | path = 'src/app/app.coffee';
138 | insert = "'" + projectName + "." + camelModuleName + "',\n ";
139 | }
140 |
141 | var file = this.readFileAsString(path);
142 |
143 | if (file.indexOf(insert) === -1) {
144 | this.write(path, file.replace(hook, insert + hook));
145 | }
146 | }
147 |
148 | });
149 |
150 | module.exports = ModuleGenerator;
151 |
--------------------------------------------------------------------------------
/app/templates/root/_Gruntfile.js:
--------------------------------------------------------------------------------
1 | var taskName = '';
2 | module.exports = function(grunt) {
3 |
4 | var _ = require('lodash');
5 |
6 | // Load required Grunt tasks. These are installed based on the versions listed
7 | // * in 'package.json' when you do 'npm install' in this directory.
8 | grunt.loadNpmTasks('grunt-contrib-clean');
9 | grunt.loadNpmTasks('grunt-contrib-copy');
10 | grunt.loadNpmTasks('grunt-contrib-jshint');
11 | grunt.loadNpmTasks('grunt-contrib-concat');
12 | grunt.loadNpmTasks('grunt-contrib-watch');
13 | grunt.loadNpmTasks('grunt-contrib-uglify');
14 | grunt.loadNpmTasks('grunt-contrib-coffee');
15 | grunt.loadNpmTasks('grunt-contrib-less');
16 | grunt.loadNpmTasks('grunt-coffeelint');
17 | grunt.loadNpmTasks('grunt-karma');
18 | grunt.loadNpmTasks('grunt-ng-annotate');
19 | grunt.loadNpmTasks('grunt-html2js');
20 | grunt.loadNpmTasks('grunt-express');
21 |
22 | /** ********************************************************************************* */
23 | /** **************************** File Config **************************************** */
24 | var fileConfig = {
25 | build_dir: 'build',
26 | compile_dir: 'bin',
27 |
28 | /**
29 | * This is a collection of file patterns for our app code (the
30 | * stuff in 'src/'). These paths are used in the configuration of
31 | * build tasks. 'js' is all project javascript, except tests.
32 | * 'commonTemplates' contains our reusable components' ('src/common')
33 | * template HTML files, while 'appTemplates' contains the templates for
34 | * our app's code. 'html' is just our main HTML file. 'less' is our main
35 | * stylesheet, and 'unit' contains our app's unit tests.
36 | */
37 | app_files: {
38 | js: [ './src/**/*.module.js', 'src/**/*.js', '!src/**/*.spec.js', '!src/assets/**/*.js' ],
39 | jsunit: [ 'src/**/*.spec.js' ],
40 |
41 | coffee: [ './src/**/*.module.coffee', 'src/**/*.coffee', '!src/**/*.spec.coffee' ],
42 | coffeeunit: [ 'src/**/*.spec.coffee' ],
43 |
44 | appTemplates: [ 'src/app/**/*.tpl.html' ],
45 | commonTemplates: [ 'src/common/**/*.tpl.html' ],
46 |
47 | html: [ 'src/index.html' ],
48 | less: 'src/less/main.less'
49 | },
50 |
51 | /**
52 | * This is a collection of files used during testing only.
53 | */
54 | test_files: {
55 | js: [
56 | 'vendor/angular-mocks/angular-mocks.js'
57 | ]
58 | },
59 |
60 | /**
61 | * This is the same as 'app_files', except it contains patterns that
62 | * reference vendor code ('vendor/') that we need to place into the build
63 | * process somewhere. While the 'app_files' property ensures all
64 | * standardized files are collected for compilation, it is the user's job
65 | * to ensure non-standardized (i.e. vendor-related) files are handled
66 | * appropriately in 'vendor_files.js'.
67 | *
68 | * The 'vendor_files.js' property holds files to be automatically
69 | * concatenated and minified with our project source files.
70 | *
71 | * The 'vendor_files.css' property holds any CSS files to be automatically
72 | * included in our app.
73 | *
74 | * The 'vendor_files.assets' property holds any assets to be copied along
75 | * with our app's assets. This structure is flattened, so it is not
76 | * recommended that you use wildcards.
77 | */
78 | vendor_files: {
79 | js: [
80 | 'vendor/angular/angular.js',
81 | <% if (includeAngularResource) {%>'vendor/angular-resource/angular-resource.js',<% } %>
82 | 'vendor/angular-bootstrap/ui-bootstrap-tpls.min.js',
83 | 'vendor/placeholders/angular-placeholders-0.0.1-SNAPSHOT.min.js',
84 | 'vendor/angular-ui-router/release/angular-ui-router.js',
85 | 'vendor/angular-ui-utils/modules/route/route.js'
86 | ],
87 | css: [
88 | ],
89 | assets: [
90 | ]
91 | }
92 | };
93 |
94 | /** ********************************************************************************* */
95 | /** **************************** Task Config **************************************** */
96 | var taskConfig = {
97 | pkg: grunt.file.readJSON("package.json"),
98 |
99 | /**
100 | * The banner is the comment that is placed at the top of our compiled
101 | * source files. It is first processed as a Grunt template, where the 'less than percent equals'
102 | * pairs are evaluated based on this very configuration object.
103 | */
104 | meta: {
105 | banner:
106 | '/**\n' +
107 | ' * <%%= pkg.name %> - v<%%= pkg.version %> - <%%= grunt.template.today("yyyy-mm-dd") %>\n' +
108 | ' *\n' +
109 | ' * Copyright (c) <%%= grunt.template.today("yyyy") %> <%%= pkg.author %>\n' +
110 | ' */\n'
111 | },
112 |
113 | /**
114 | * The directories to delete when 'grunt clean' is executed.
115 | */
116 | clean: {
117 | all: [
118 | '<%%= build_dir %>',
119 | '<%%= compile_dir %>'
120 | ],
121 | vendor: [
122 | '<%%= build_dir %>/vendor/'
123 | ],
124 | index: [ '<%%= build_dir %>/index.html' ]
125 | },
126 |
127 | /**
128 | * The 'copy' task just copies files from A to B. We use it here to copy
129 | * our project assets (images, fonts, etc.) and javascripts into
130 | * 'build_dir', and then to copy the assets to 'compile_dir'.
131 | */
132 | copy: {
133 | build_app_assets: {
134 | files: [
135 | {
136 | src: [ '**' ],
137 | dest: '<%%= build_dir %>/assets/',
138 | cwd: 'src/assets',
139 | expand: true
140 | }
141 | ]
142 | },
143 | build_vendor_assets: {
144 | files: [
145 | {
146 | src: [ '<%%= vendor_files.assets %>' ],
147 | dest: '<%%= build_dir %>/assets/',
148 | cwd: '.',
149 | expand: true,
150 | flatten: true
151 | }
152 | ]
153 | },
154 | build_appjs: {
155 | files: [
156 | {
157 | src: [ '<%%= app_files.js %>' ],
158 | dest: '<%%= build_dir %>/',
159 | cwd: '.',
160 | expand: true
161 | }
162 | ]
163 | },
164 | build_vendorjs: {
165 | files: [
166 | {
167 | src: [ '<%%= vendor_files.js %>' ],
168 | dest: '<%%= build_dir %>/',
169 | cwd: '.',
170 | expand: true
171 | }
172 | ]
173 | },
174 | buildmock_vendorjs: {
175 | files: [
176 | {
177 | src: [ '<%%= vendor_files.js %>', '<%%= test_files.js %>' ],
178 | dest: '<%%= build_dir %>/',
179 | cwd: '.',
180 | expand: true
181 | }
182 | ]
183 | },
184 | build_vendorcss: {
185 | files: [
186 | {
187 | src: [ '<%%= vendor_files.css %>' ],
188 | dest: '<%%= build_dir %>/',
189 | cwd: '.',
190 | expand: true
191 | }
192 | ]
193 | },
194 | compile_assets: {
195 | files: [
196 | {
197 | src: [ '**' ],
198 | dest: '<%%= compile_dir %>/assets',
199 | cwd: '<%%= build_dir %>/assets',
200 | expand: true
201 | }
202 | ]
203 | }
204 | },
205 |
206 | /**
207 | * 'grunt concat' concatenates multiple source files into a single file.
208 | */
209 | concat: {
210 | // The 'build_css' target concatenates compiled CSS and vendor CSS together.
211 | build_css: {
212 | src: [
213 | '<%%= vendor_files.css %>',
214 | '<%%= build_dir %>/assets/<%%= pkg.name %>-<%%= pkg.version %>.css'
215 | ],
216 | dest: '<%%= build_dir %>/assets/<%%= pkg.name %>-<%%= pkg.version %>.css'
217 | },
218 | // The 'compile_js' target concatenates app and vendor js code together.
219 | compile_js: {
220 | options: {
221 | banner: '<%%= meta.banner %>'
222 | },
223 | src: [
224 | '<%%= vendor_files.js %>',
225 | 'module.prefix',
226 | '<%%= build_dir %>/src/**/*.module.js',
227 | '<%%= build_dir %>/src/**/*.js',
228 | '<%%= html2js.app.dest %>',
229 | '<%%= html2js.common.dest %>',
230 | 'module.suffix'
231 | ],
232 | dest: '<%%= compile_dir %>/assets/<%%= pkg.name %>-<%%= pkg.version %>.js'
233 | }
234 | },
235 |
236 | /**
237 | * 'grunt coffee' compiles the CoffeeScript sources. To work well with the
238 | * rest of the build, we have a separate compilation task for sources and
239 | * specs so they can go to different places. For example, we need the
240 | * sources to live with the rest of the copied JavaScript so we can include
241 | * it in the final build, but we don't want to include our specs there.
242 | */
243 | coffee: {
244 | source: {
245 | options: {
246 | bare: true
247 | },
248 | expand: true,
249 | cwd: '.',
250 | src: [ '<%%= app_files.coffee %>' ],
251 | dest: '<%%= build_dir %>',
252 | ext: '.js'
253 | }
254 | },
255 |
256 | /**
257 | * 'ng-annotate' annotates the sources for safe minification. That is, it allows us
258 | * to code without the array syntax.
259 | */
260 | ngAnnotate: {
261 | options: {
262 | singleQuotes: true
263 | },
264 | build: {
265 | files:[
266 | {
267 | src: [ '<%%= app_files.js %>' ],
268 | cwd: '<%%= build_dir %>',
269 | dest: '<%%= build_dir %>',
270 | expand: true
271 | },
272 | ]
273 | },
274 | },
275 |
276 | /**
277 | * Minify the sources!
278 | */
279 | uglify: {
280 | compile: {
281 | options: {
282 | banner: '<%%= meta.banner %>'
283 | },
284 | files: {
285 | '<%%= concat.compile_js.dest %>': '<%%= concat.compile_js.dest %>'
286 | }
287 | }
288 | },
289 |
290 | /**
291 | * `grunt-contrib-less` handles our LESS compilation and uglification automatically.
292 | * Only our 'main.less' file is included in compilation; all other files
293 | * must be imported from this file.
294 | */
295 | less: {
296 | build: {
297 | files: {
298 | '<%%= build_dir %>/assets/<%%= pkg.name %>-<%%= pkg.version %>.css': '<%%= app_files.less %>'
299 | }
300 | },
301 | compile: {
302 | files: {
303 | '<%%= build_dir %>/assets/<%%= pkg.name %>-<%%= pkg.version %>.css': '<%%= app_files.less %>'
304 | },
305 | options: {
306 | cleancss: true,
307 | compress: true
308 | }
309 | }
310 | },
311 |
312 | /**
313 | * 'jshint' defines the rules of our linter as well as which files we
314 | * should check. This file, all javascript sources, and all our unit tests
315 | * are linted based on the policies listed in 'options'. But we can also
316 | * specify exclusionary patterns by prefixing them with an exclamation
317 | * point (!); this is useful when code comes from a third party but is
318 | * nonetheless inside 'src/'.
319 | */
320 | jshint: {
321 | src: [
322 | '<%%= app_files.js %>'
323 | ],
324 | test: [
325 | '<%%= app_files.jsunit %>'
326 | ],
327 | gruntfile: [
328 | 'Gruntfile.js'
329 | ],
330 | options: {
331 | curly: true,
332 | immed: true,
333 | newcap: true,
334 | noarg: true,
335 | sub: true,
336 | boss: true,
337 | eqnull: true,
338 | globals: {}
339 | },
340 | },
341 |
342 | /**
343 | * 'coffeelint' does the same as 'jshint', but for CoffeeScript.
344 | * CoffeeScript is not the default in ngBoilerplate, so we're just using
345 | * the defaults here.
346 | */
347 | coffeelint: {
348 | src: {
349 | files: {
350 | src: [ '<%%= app_files.coffee %>' ]
351 | }
352 | },
353 | test: {
354 | files: {
355 | src: [ '<%%= app_files.coffeeunit %>' ]
356 | }
357 | }
358 | },
359 |
360 | /**
361 | * HTML2JS is a Grunt plugin that takes all of your template files and
362 | * places them into JavaScript files as strings that are added to
363 | * AngularJS's template cache. This means that the templates too become
364 | * part of the initial payload as one JavaScript file. Neat!
365 | */
366 | html2js: {
367 | // These are the templates from 'src/app'.
368 | app: {
369 | options: {
370 | base: 'src/app'
371 | },
372 | src: [ '<%%= app_files.appTemplates %>' ],
373 | dest: '<%%= build_dir %>/templates-app.js'
374 | },
375 |
376 | // These are the templates from 'src/common'.
377 | common: {
378 | options: {
379 | base: 'src/common'
380 | },
381 | src: [ '<%%= app_files.commonTemplates %>' ],
382 | dest: '<%%= build_dir %>/templates-common.js'
383 | }
384 | },
385 |
386 | /**
387 | * The 'index' task compiles the 'index.html' file as a Grunt template. CSS
388 | * and JS files co-exist here but they get split apart later.
389 | */
390 | index: {
391 |
392 | /**
393 | * During development, we don't want to have wait for compilation,
394 | * concatenation, minification, etc. So to avoid these steps, we simply
395 | * add all script files directly to the '' of 'index.html'. The
396 | * 'src' property contains the list of included files.
397 | */
398 | build: {
399 | appName: '<%= projectName %>',
400 | dir: '<%%= build_dir %>',
401 | src: [
402 | '<%%= vendor_files.js %>',
403 | '<%%= build_dir %>/src/**/*.module.js',
404 | '<%%= build_dir %>/src/**/*.js',
405 | '<%%= html2js.common.dest %>',
406 | '<%%= html2js.app.dest %>',
407 | '<%%= vendor_files.css %>',
408 | '<%%= build_dir %>/assets/<%%= pkg.name %>-<%%= pkg.version %>.css'
409 | ]
410 | },
411 | /**
412 | * Identical to above, but with test_files included.
413 | * Good for using a mocked backend like $httpBackend.
414 | */
415 | mock: {
416 | appName: 'mockApp',
417 | dir: '<%%= build_dir %>',
418 | src: [
419 | '<%%= vendor_files.js %>',
420 | '<%%= test_files.js %>',
421 | '<%%= build_dir %>/src/**/*.module.js',
422 | '<%%= build_dir %>/src/**/*.js',
423 | '<%%= html2js.common.dest %>',
424 | '<%%= html2js.app.dest %>',
425 | '<%%= vendor_files.css %>',
426 | '<%%= build_dir %>/assets/<%%= pkg.name %>-<%%= pkg.version %>.css'
427 | ]
428 | },
429 |
430 | /**
431 | * When it is time to have a completely compiled application, we can
432 | * alter the above to include only a single JavaScript and a single CSS
433 | * file. Now we're back!
434 | */
435 | compile: {
436 | appName: '<%= projectName %>',
437 | dir: '<%%= compile_dir %>',
438 | src: [
439 | '<%%= concat.compile_js.dest %>',
440 | '<%%= build_dir %>/assets/<%%= pkg.name %>-<%%= pkg.version %>.css'
441 | ]
442 | }
443 | },
444 |
445 | express: {
446 | devServer: {
447 | options: {
448 | port: 9000,
449 | hostname: 'localhost',
450 | serverreload: false,
451 | bases: 'build',
452 | livereload: true
453 | }
454 | }
455 | },
456 |
457 | /**
458 | * The Karma configurations.
459 | */
460 | karma: {
461 | options: {
462 | configFile: '<%%= build_dir %>/karma-unit.js'
463 | },
464 | unit: {
465 | runnerPort: 9019,
466 | background: true
467 | },
468 | continuous: {
469 | singleRun: true
470 | }
471 | },
472 |
473 | /**
474 | * This task compiles the karma template so that changes to its file array
475 | * don't have to be managed manually.
476 | */
477 | karmaconfig: {
478 | unit: {
479 | dir: '<%%= build_dir %>',
480 | src: [
481 | '<%%= vendor_files.js %>',
482 | '<%%= html2js.app.dest %>',
483 | '<%%= html2js.common.dest %>',
484 | '<%%= test_files.js %>'
485 | ]
486 | }
487 | },
488 |
489 | /**
490 | * And for rapid development, we have a watch set up that checks to see if
491 | * any of the files listed below change, and then to execute the listed
492 | * tasks when they do. This just saves us from having to type "grunt" into
493 | * the command-line every time we want to see what we're working on; we can
494 | * instead just leave "grunt watch" running in a background terminal. Set it
495 | * and forget it, as Ron Popeil used to tell us.
496 | *
497 | * But we don't need the same thing to happen for all the files.
498 | */
499 | delta: {
500 | /**
501 | * By default, we want the Live Reload to work for all tasks; this is
502 | * overridden in some tasks (like this file) where browser resources are
503 | * unaffected. It runs by default on port 35729, which your browser
504 | * plugin should auto-detect.
505 | */
506 | options: {
507 | livereload: true
508 | },
509 |
510 | /**
511 | * When the Gruntfile changes, we just want to lint it. In fact, when
512 | * your Gruntfile changes, it will automatically be reloaded!
513 | * We also want to copy vendor files and rebuild index.html in case
514 | * vendor_files.js was altered (list of 3rd party vendor files installed by bower)
515 | */
516 | gruntfile: {
517 | files: 'Gruntfile.js',
518 | tasks: [ 'jshint:gruntfile', 'clean:vendor', 'copy:build_vendorjs', 'copy:build_vendorcss', 'index:build' ],
519 | options: {
520 | livereload: false
521 | }
522 | },
523 |
524 | /**
525 | * When our JavaScript source files change, we want to run lint them and
526 | * run our unit tests.
527 | */
528 | jssrc: {
529 | files: [
530 | '<%%= app_files.js %>'
531 | ],
532 | tasks: [ 'jshint:src', 'karma:unit:run', 'copy:build_appjs', 'index:build' ]
533 | },
534 |
535 | /**
536 | * When our CoffeeScript source files change, we want to run lint them and
537 | * run our unit tests.
538 | */
539 | coffeesrc: {
540 | files: [
541 | '<%%= app_files.coffee %>'
542 | ],
543 | tasks: [ 'coffeelint:src', 'coffee:source', 'karma:unit:run', 'copy:build_appjs', 'index:build' ]
544 | },
545 |
546 | /**
547 | * When assets are changed, copy them. Note that this will *not* copy new
548 | * files, so this is probably not very useful.
549 | */
550 | assets: {
551 | files: [
552 | 'src/assets/**/*'
553 | ],
554 | tasks: [ 'copy:build_app_assets' ]
555 | },
556 |
557 | /**
558 | * When index.html changes, we need to compile it.
559 | */
560 | html: {
561 | files: [ '<%%= app_files.html %>' ],
562 | tasks: [ 'index:build' ]
563 | },
564 |
565 | /**
566 | * When our templates change, we only rewrite the template cache.
567 | */
568 | tpls: {
569 | files: [
570 | '<%%= app_files.appTemplates %>',
571 | '<%%= app_files.commonTemplates %>'
572 | ],
573 | tasks: [ 'html2js' ]
574 | },
575 |
576 | /**
577 | * When the CSS files change, we need to compile and minify them.
578 | */
579 | less: {
580 | files: [ 'src/**/*.less' ],
581 | tasks: [ 'less:build' ]
582 | },
583 |
584 | /**
585 | * When a JavaScript unit test file changes, we only want to lint it and
586 | * run the unit tests. We don't want to do any live reloading.
587 | */
588 | jsunit: {
589 | files: [
590 | '<%%= app_files.jsunit %>'
591 | ],
592 | tasks: [ 'jshint:test', 'karma:unit:run' ],
593 | options: {
594 | livereload: false
595 | }
596 | },
597 |
598 | /**
599 | * When a CoffeeScript unit test file changes, we only want to lint it and
600 | * run the unit tests. We don't want to do any live reloading.
601 | */
602 | coffeeunit: {
603 | files: [
604 | '<%%= app_files.coffeeunit %>'
605 | ],
606 | tasks: [ 'coffeelint:test', 'karma:unit:run' ],
607 | options: {
608 | livereload: false
609 | }
610 | }
611 | }
612 | };
613 |
614 |
615 | /** ********************************************************************************* */
616 | /** **************************** Project Configuration ****************************** */
617 | // The following chooses some watch tasks based on whether we're running in mock mode or not.
618 | // Our watch (delta above) needs to run a different index task and copyVendorJs task
619 | // in several places if "grunt watchmock" is run.
620 | taskName = grunt.cli.tasks[0]; // the name of the task from the command line (e.g. "grunt watch" => "watch")
621 | var indexTask = taskName === 'watchmock' ? 'index:mock' : 'index:build';
622 | var copyVendorJsTask = taskName === 'watchmock' ? 'copy:buildmock_vendorjs' : 'copy:build_vendorjs';
623 | taskConfig.delta.gruntfile.tasks = [ 'jshint:gruntfile', 'clean:vendor', copyVendorJsTask, 'copy:build_vendorcss', indexTask ];
624 | taskConfig.delta.jssrc.tasks = [ 'jshint:src', 'copy:build_appjs', indexTask ];
625 | taskConfig.delta.coffeesrc.tasks = [ 'coffeelint:src', 'coffee:source', 'karma:unit:run', 'copy:build_appjs', indexTask ];
626 | taskConfig.delta.html.tasks = [ indexTask ];
627 |
628 | // Take the big config objects we defined above, combine them, and feed them into grunt
629 | grunt.initConfig(_.assign(taskConfig, fileConfig));
630 |
631 | // In order to make it safe to just compile or copy *only* what was changed,
632 | // we need to ensure we are starting from a clean, fresh build. So we rename
633 | // the 'watch' task to 'delta' (that's why the configuration var above is
634 | // 'delta') and then add a new task called 'watch' that does a clean build
635 | // before watching for changes.
636 | grunt.renameTask('watch', 'delta');
637 | grunt.registerTask('watch', [ 'build', 'karma:unit', 'express', 'delta' ]);
638 | // watchmock is just like watch, but includes testing resources for using $httpBackend
639 | grunt.registerTask('watchmock', [ 'buildmock', 'karma:unit', 'express', 'delta' ]);
640 |
641 | // The default task is to build and compile.
642 | grunt.registerTask('default', [ 'build', 'compile' ]);
643 |
644 | // The 'build' task gets your app ready to run for development and testing.
645 | grunt.registerTask('build', [
646 | 'clean:all', 'html2js', 'jshint', 'coffeelint', 'coffee', 'less:build',
647 | 'concat:build_css', 'copy:build_app_assets', 'copy:build_vendor_assets',
648 | 'copy:build_appjs', 'copy:build_vendorjs', 'copy:build_vendorcss', 'ngAnnotate:build', 'index:build', 'karmaconfig',
649 | 'karma:continuous'
650 | ]);
651 |
652 | // just like build, but includes testing resources for using $httpBackend and switches to mock application in index.html
653 | grunt.registerTask('buildmock', [
654 | 'clean:all', 'html2js', 'jshint', 'coffeelint', 'coffee', 'less:build',
655 | 'concat:build_css', 'copy:build_app_assets', 'copy:build_vendor_assets',
656 | 'copy:build_appjs', 'copy:buildmock_vendorjs', 'copy:build_vendorcss', 'ngAnnotate:build', 'index:mock', 'karmaconfig',
657 | 'karma:continuous'
658 | ]);
659 |
660 | // The 'compile' task gets your app ready for deployment by concatenating and minifying your code.
661 | // Note - compile builds off of the build dir (look at concat:compile_js), so run grunt build before grunt compile
662 | grunt.registerTask('compile', [
663 | 'less:compile', 'concat:build_css', 'copy:compile_assets', 'concat:compile_js', 'uglify', 'index:compile'
664 | ]);
665 |
666 | // The debug task is just like watch without any of the testing.
667 | // This is good for debugging issues that cause 'grunt watch' to fail due to test errors.
668 | // Just 'grunt debug' and debug your app within a browser, then go back to grunt watch once you've
669 | // fixed the problem. You'll usually see the source of the problem right away in the console.
670 | grunt.registerTask('debug', [
671 | 'clean:all', 'html2js', 'jshint', 'less:build',
672 | 'concat:build_css', 'copy:build_app_assets', 'copy:build_vendor_assets',
673 | 'copy:build_appjs', 'copy:build_vendorjs', 'copy:build_vendorcss', 'ngAnnotate:build', 'index:build', 'express', 'delta'
674 | ]);
675 |
676 | // A utility function to get all app JavaScript sources.
677 | function filterForJS (files) {
678 | return files.filter(function (file) {
679 | return file.match(/\.js$/);
680 | });
681 | }
682 |
683 | // A utility function to get all app CSS sources.
684 | function filterForCSS (files) {
685 | return files.filter( function (file) {
686 | return file.match(/\.css$/);
687 | });
688 | }
689 |
690 | // The index.html template includes the stylesheet and javascript sources
691 | // based on dynamic names calculated in this Gruntfile. This task assembles
692 | // the list into variables for the template to use and then runs the
693 | // compilation.
694 | grunt.registerMultiTask('index', 'Process index.html template', function () {
695 | var dirRE = new RegExp('^(' + grunt.config('build_dir') + '|' + grunt.config('compile_dir') + ')\/', 'g');
696 |
697 | // this.fileSrc comes from either build:src, compile:src, or karmaconfig:src in the index config defined above
698 | // see - http://gruntjs.com/api/inside-tasks#this.filessrc for documentation
699 | var jsFiles = filterForJS(this.filesSrc).map(function (file) {
700 | return file.replace(dirRE, '');
701 | });
702 | var cssFiles = filterForCSS(this.filesSrc).map(function (file) {
703 | return file.replace(dirRE, '');
704 | });
705 |
706 | var app = this.data.appName;
707 |
708 | // this.data.dir comes from either build:dir, compile:dir, or karmaconfig:dir in the index config defined above
709 | // see - http://gruntjs.com/api/inside-tasks#this.data for documentation
710 | grunt.file.copy('src/index.html', this.data.dir + '/index.html', {
711 | process: function (contents, path) {
712 | // These are the variables looped over in our index.html exposed as "scripts", "styles", and "version"
713 | return grunt.template.process(contents, {
714 | data: {
715 | appName: app,
716 | scripts: jsFiles,
717 | styles: cssFiles,
718 | version: grunt.config('pkg.version'),
719 | author: grunt.config('pkg.author'),
720 | date: grunt.template.today("yyyy")
721 | }
722 | });
723 | }
724 | });
725 | });
726 |
727 | // In order to avoid having to specify manually the files needed for karma to
728 | // run, we use grunt to manage the list for us. The 'karma/*' files are
729 | // compiled as grunt templates for use by Karma. Yay!
730 | grunt.registerMultiTask('karmaconfig', 'Process karma config templates', function () {
731 | var jsFiles = filterForJS(this.filesSrc);
732 |
733 | grunt.file.copy('karma/karma-unit.tpl.js', grunt.config('build_dir') + '/karma-unit.js', {
734 | process: function (contents, path) {
735 | // This is the variable looped over in the karma template of our index.html exposed as "scripts"
736 | return grunt.template.process(contents, {
737 | data: {
738 | scripts: jsFiles
739 | }
740 | });
741 | }
742 | });
743 | });
744 |
745 | };
746 |
--------------------------------------------------------------------------------