Browse Source

Initial commit

master
Josh Habdas 7 years ago
commit
98df29f650
96 changed files with 2104 additions and 0 deletions
  1. +12
    -0
      .editorconfig
  2. +34
    -0
      .gitignore
  3. +130
    -0
      CHANGELOG.md
  4. +4
    -0
      Jakefile
  5. +21
    -0
      LICENSE
  6. +215
    -0
      README.md
  7. +10
    -0
      app/application.coffee
  8. +1
    -0
      app/application.less
  9. +15
    -0
      app/assets/index.html
  10. +2
    -0
      app/base.less
  11. +15
    -0
      app/controllers/application.coffee
  12. +1
    -0
      app/controllers/base/controller.coffee
  13. +28
    -0
      app/initialize.coffee
  14. +11
    -0
      app/lib/utils.coffee
  15. +4
    -0
      app/lib/view-helper.coffee
  16. +5
    -0
      app/models/base/collection.coffee
  17. +1
    -0
      app/models/base/model.coffee
  18. +16
    -0
      app/models/product.coffee
  19. +19
    -0
      app/models/products.coffee
  20. +12
    -0
      app/models/reactive.coffee
  21. +3
    -0
      app/routes.coffee
  22. +6
    -0
      app/views/base/collection.coffee
  23. +3
    -0
      app/views/base/item.coffee
  24. +21
    -0
      app/views/base/view.coffee
  25. +6
    -0
      app/views/index/index.coffee
  26. +5
    -0
      app/views/index/style.less
  27. +6
    -0
      app/views/index/template.eco
  28. +33
    -0
      app/views/invoice/index.coffee
  29. +5
    -0
      app/views/invoice/style.less
  30. +63
    -0
      app/views/invoice/template.eco
  31. +29
    -0
      bower.json
  32. +26
    -0
      brunch-config.js
  33. +10
    -0
      generators/code-test/generator.json
  34. +1
    -0
      generators/code-test/helpers.js
  35. +1
    -0
      generators/code-test/test.coffee.hbs
  36. +34
    -0
      generators/collection-view/generator.json
  37. +1
    -0
      generators/collection-view/helpers.js
  38. +7
    -0
      generators/collection-view/index.coffee.hbs
  39. +5
    -0
      generators/collection-view/item/index.coffee.hbs
  40. +5
    -0
      generators/collection-view/item/style.less.hbs
  41. +0
    -0
      generators/collection-view/item/template.eco.hbs
  42. +5
    -0
      generators/collection-view/style.less.hbs
  43. +0
    -0
      generators/collection-view/template.eco.hbs
  44. +14
    -0
      generators/collection-view/test.coffee.hbs
  45. +5
    -0
      generators/collection/collection.coffee.hbs
  46. +16
    -0
      generators/collection/generator.json
  47. +1
    -0
      generators/collection/helpers.js
  48. +14
    -0
      generators/collection/test.coffee.hbs
  49. +3
    -0
      generators/controller/controller.coffee.hbs
  50. +14
    -0
      generators/controller/generator.json
  51. +1
    -0
      generators/controller/helpers.js
  52. +11
    -0
      generators/controller/test.coffee.hbs
  53. +15
    -0
      generators/helpers.js
  54. +14
    -0
      generators/model/generator.json
  55. +1
    -0
      generators/model/helpers.js
  56. +3
    -0
      generators/model/model.coffee.hbs
  57. +11
    -0
      generators/model/test.coffee.hbs
  58. +10
    -0
      generators/site-test/generator.json
  59. +1
    -0
      generators/site-test/helpers.js
  60. +10
    -0
      generators/site-test/test.coffee.hbs
  61. +22
    -0
      generators/view/generator.json
  62. +1
    -0
      generators/view/helpers.js
  63. +5
    -0
      generators/view/index.coffee.hbs
  64. +5
    -0
      generators/view/style.less.hbs
  65. +0
    -0
      generators/view/template.eco.hbs
  66. +11
    -0
      generators/view/test.coffee.hbs
  67. +14
    -0
      jakelib/bower.jake
  68. +38
    -0
      jakelib/build.jake
  69. +77
    -0
      jakelib/generator.jake
  70. +117
    -0
      jakelib/lib/index.js
  71. +212
    -0
      jakelib/module.jake
  72. +10
    -0
      jakelib/npm.jake
  73. +133
    -0
      jakelib/test.jake
  74. +25
    -0
      jakelib/unlisted.jake
  75. +29
    -0
      package.json
  76. +27
    -0
      server/index.coffee
  77. +20
    -0
      server/prerender/index.coffee
  78. +15
    -0
      server/prerender/server.js
  79. +8
    -0
      server/routes/index.coffee
  80. +48
    -0
      setup/defaults.js
  81. +63
    -0
      setup/environment.js
  82. +30
    -0
      setup/index.js
  83. +31
    -0
      setup/platform.js
  84. +53
    -0
      setup/util.js
  85. +36
    -0
      test/assets/test/index.html
  86. +7
    -0
      test/code/controllers/application.coffee
  87. +11
    -0
      test/code/models/product.coffee
  88. +14
    -0
      test/code/models/products.coffee
  89. +11
    -0
      test/code/models/reactive.coffee
  90. +7
    -0
      test/code/views/index.coffee
  91. +11
    -0
      test/code/views/invoice.coffee
  92. +22
    -0
      test/karma.conf.js
  93. +5
    -0
      test/mocha.opts
  94. +11
    -0
      test/setup.js
  95. +0
    -0
      test/site/.gitkeep
  96. +0
    -0
      vendor/.gitignore

+ 12
- 0
.editorconfig View File

@ -0,0 +1,12 @@
# For more information visit http://editorconfig.org/
# Top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file.
# Also 2 space indentation.
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2

+ 34
- 0
.gitignore View File

@ -0,0 +1,34 @@
# Numerous always-ignore extensions
*.diff
*.err
*.orig
*.log
*.rej
*.swo
*.swp
*.vi
*~
*.sass-cache
*.sublime*
*.iws
# OS or Editor folders
.DS_Store
.cache
.project
.settings
.tmproj
nbproject
Thumbs.db
# WebStorm / IntelliJ
.idea/tasks.xml
.idea/watcherTasks.xml
.idea/workspace.xml
# Project specific
npm-debug.log
node_modules/
tmp/
public/
bower_components/

+ 130
- 0
CHANGELOG.md View File

@ -0,0 +1,130 @@
# Changelog
### 0.10.0 (February 5, 2014)
- Update Davy
- Add back coffee-script module
- Update BTC
- Do not install Prerender packages by default
- Autodetect if packages are available
- Add tasks to install/uninstall Prerender packages
- Add PhantomJS as a dependency (could be used for Prerender)
- Do not install code/site testing-related packages by default
- Add tasks to install/uninstall code/site testing-related packages
- Update Sinon Chai
- Fix bug in `npm:clean`
### 0.9.0 (February 4, 2014)
- Update Chaplin
- Update console-polyfill
- Update BTC
- Add `npm:clean` task
- Clean up package.json
- Update jQuery
- Update normalize.css
- Update Chai
- Add structure to `server` directory
- Add Prerender support (server and middleware)
- Update karma-chai-plugins
#### 0.8.3 (January 15, 2014)
- Update BTC
- Fix watch in `test:code`
- Update Mocha
- Update Bluebird
- Update Nodemon
#### 0.8.2 (December 21, 2013)
- Update BTC
- Update Mocha
- Update WebDriverJS
#### 0.8.1 (December 14, 2013)
- Fix consistency in generators
- Update BTC
- Allow multiple names to be specified per scaffold
- Move default Jake task to Jakefile
- Include server to customize and add services
- Update packages
### 0.8.0 (December 12, 2013)
- Update dependencies (Chaplin, Lo-Dash, Exoskeleton)
- Reference Bower for Less Hat
- Update BTC
- Fix Windows support
- Internal fix to bring back spawn options
- Add specific WebStorm and IntelliJ files to gitignore
- Stop worrying about absolute paths
- Mark execute promises as cancellable
- Move auto-reload-brunch to devDependencies
- Allow project's bower to be run if root (or root-like)
- Update selenium-webdriver
- Update dependencies
- Added `bower:clean` task
- Changed behavior of `gen`/`del` tasks
#### 0.7.2 (November 14, 2013)
- Flesh out test generators a bit
- Update BTC
#### 0.7.1 (November 9, 2013)
- Fix issue with exoskeleton task
- Update from BTC
### 0.7.0 (November 9, 2013)
- Remove Bootstrap (use Bower or manually set up)
- Add LESS Hat
- Update from BTC
### 0.6.0 (November 5, 2013)
- Update from BTC
- Update Bootstrap to 3.0.1
- Update Rivets
- Ensure Bootstrap default theme is included
- Added Exoskeleton, Lo-Dash, and Davy as modules
- Added some code to handle either Backbone or Exoskeleton
- Remove CoffeeLint (check out EditorConfig)
- Remove Underscore.String by default
- Remove `redirectTo` function in views (use `Chaplin.helpers`)
#### 0.5.1 (September 27, 2013)
- Make Rivets and Underscore.string optional
- General cleanup
- Bugfixes
### 0.5.0 (September 26, 2013)
- Update from BTC
- Update Bootstrap to v3
- Remove Clearless in favor of Bootstrap's mixins
- Add Rivets for binding
### 0.4.0 (August 19, 2013)
- Update from BTC
- Refactor to reflect Brunch and BWC
- Remove Stickit and Transit (use Bower)
### 0.3.0 (June 6, 2013)
- Integrate BTC Bootstrap
- Update from BWC
- Underscore -> Lo-Dash
- Add Transit
- Update jQuery
#### 0.2.3 (May 20, 2013)
- Bugfix in base collection view.
#### 0.2.2 (May 19, 2013)
- Update Brunch Toolchain.
- Update Chaplin, jQuery, and Font Awesome.
#### 0.2.1 (April 15, 2013)
- Update Brunch Toolchain.
- Update Chaplin.
### 0.2.0 (March 31, 2013)
- Update Backbone and Chaplin.
- Add test generator.
- Have test modules be automatically included again.
- Skeleton cleanup.
### 0.1.0 (March 28, 2013)
- Initial release.

+ 4
- 0
Jakefile View File

@ -0,0 +1,4 @@
// Set default task to list available tasks
task('default', function() {
jake.run('-T');
});

+ 21
- 0
LICENSE View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Juan Placencia
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

+ 215
- 0
README.md View File

@ -0,0 +1,215 @@
# Chapless Brunch 0.10.0
[<img src="https://david-dm.org/jupl/chapless-brunch.png"/>](https://david-dm.org/jupl/chapless-brunch)
[<img src="https://david-dm.org/jupl/chapless-brunch/dev-status.png"/>](https://david-dm.org/jupl/chapless-brunch#info=devDependencies)
## Introduction
Chapless Brunch is a skeleton for building web applications, specifically single-page applications. It is a modification of [Brunch with Chaplin](https://github.com/paulmillr/brunch-with-chaplin). This skeleton leverages [node](http://nodejs.org), [Brunch](http://brunch.io), [Scaffolt](https://github.com/paulmillr/scaffolt), [Bower](http://bower.io/), [Jake](https://github.com/mde/jake), and [PhantomJS](http://phantomjs.org/) to provide cross-platform tasks in a simple package. [LESS Hat](http://lesshat.com/) mixins are included. [EditorConfig](http://editorconfig.org/) is also provided to help with consistency. [Prerender](https://prerender.io/) can be easily enabled/configured for search engine crawling.
For a mobile/Cordova friendly variant, check out [this skeleton](https://github.com/jupl/chapless-brunch/tree/cordova).
## File Structure
├── app # App is built here. Look at Brunch for more info.
│   ├── assets # Static files that are just copied
│   ├── controllers # Chaplin controllers
│   ├── lib # Chaplin utilities and helpers
│   ├── models # Chaplin models and collections
│   ├── views # Chaplin views and collection views
│   ├── application.coffee # Chaplin application definition
│   ├── application.less # Application/page styling definition
│   ├── base.less # LESS variables and mixins for the application
│   ├── initialize.coffee # Chaplin views and collection views
│   └── routes.coffee # Route definitions for Chaplin
├── bower_components # Packages installed by Bower
├── generators # Generators used by Scaffolt
├── jakelib # Unified set of tasks for development
├── public # Generated final product
├── server # Server configuration
│   ├── prerender # Configuration for Prerender server/middleware
│   └── routes # Custom routes/services/proxies/etc. (server-side)
├── setup # Add configuration options to brunch-config
├── test # Test-related files
│   ├── assets # Static assets to run code tests manually
│   ├── code # Code-based tests for Karma/manual
│   ├── site # Site-based tests for Mocha and WebDriverJS
│   ├── karma.conf.js # Karma configuration for code tests
│   ├── mocha.opts # Default options for site tests
│   └── setup.js # Configuration for site tests
├── vendor # 3rd party JS/CSS libraries
├── .editorconfig # EditorConfig definition file for coding styles
├── bower.json # Listing for Bower dependencies to download
├── brunch-config.js # Brunch app build configuration
└── package.json # Project dependencies and configuration
## Requirements
- [node.js](http://nodejs.org)
- [Jake](https://github.com/mde/jake#installing-with-npm) (required for development)
## Setup
1. Install node.js.
2. If using Windows and leveraging Bower, install [Git](http://git-scm.com/download/win).
3. If doing development, install Jake.
4. Open a terminal window and navigate to the project directory.
5. Execute the command `npm install` to install all package dependencies.
## Notes
If you want to just run Brunch without using Jake tasks, just use either `web:dev` or `web:prod` for the environment. (ex: `brunch watch --server --env web:prod`)
One-line commands are provided for convenience as well for those that want to start running things as quickly as possible by installing depedencies automatically. Use `npm start` to download non-development packages and run the `server:prod` task. Use `npm test` to download all packages and run the `test:all` task.
Prerender is not enabled by default.
- To enable/disable Prerender see tasks `add:prerender`/`rem:prerender`.
- By default it is configured to use a local Prerender server that is set up.
- To modify the local Prerender server see `server/prerender/server.js`.
- To modify Prerender middleware see `server/prerender/index.js`.
- For more information visit their [website](https://prerender.io/).
## Task List
While Brunch/Scaffolt/etc. can be used, Jake commands are provided for a simple and consistent interface. These tasks can be executed using `jake`. (`jake [task]`) These are the following available tasks provided out of the box:
### NPM
#### `npm:clean`
Remove downloaded Node modules. This is useful if you want to reinstall dependencies. (ex. updated/corrupted package(s)) Remember that you need to call `npm install` to install dependencies.
### Bower
#### `bower:install`
Download and preinstall any Bower dependencies in advance. You can run this if you want to download Bower dependencies in advance.
#### `bower:clean`
Remove downloaded Bower dependencies. This is useful if you want to reinstall dependencies. (ex. updated/corrupted package(s))
### Extras
These commands add additional features/items to the project that are not included by default.
#### `add:testing` / `rem:testing`
Add/remove packages to test. See below for more details on code/site testing packages.
#### `add:codetesting` / `rem:codetesting`
Add/remove packages to test browser code. Packages include Mocha/Chai/Sinon/etc. for Bower and Karma-related packages for NPM.
#### `add:sitetesting` / `rem:sitetesting`
Add/remove packages to test site features. Packages include Mocha, Chai, WebDriverJS, etc. for NPM.
#### `add:prerender` / `rem:prerender`
Add/remove [Prerender](https://prerender.io/) to handle search crawling in JavaScript heavy applications. See the "Notes" section above for more information.
#### `add:jquery` / `rem:jquery`
Add/remove the ubiquitous library [jQuery](http://jquery.com/) to/from the project.
#### `add:normalize` / `rem:normalize`
Add/remove [normalize.css](http://necolas.github.io/normalize.css/) to ensure a consistent starting point in styling between different browsers.
#### `add:lodash` / `rem:lodash`
Add/remove [Lo-Dash](http://lodash.com/) to/from the project.
#### `add:rivets` / `rem:rivets`
Add/remove [Rivets.js](http://rivetsjs.com/) to/from the project for binding models and views. No additional configuration is needed if added. To reference a model from a view with rivets use the `model` object. To access model properties from Rivets by default use `:`. (ex: `model:name` equates to `model.get('name')`)
#### `add:exoskeleton` / `rem:exoskeleton`
Add/remove [Exoskeleton](http://exosjs.com/) to/from the project for a more lightweight Backbone. Note that this removes/restores [classic Backbone](http://backbonejs.org/), jQuery, and Lo-Dash. You can use other tasks to add/remove jQuery and Lo-Dash again.
#### `add:davy` / `rem:davy`
Add/remove [Davy](https://github.com/lvivski/davy) to/from the project for simple and lightweight Promise support. Add this if you are using Exoskeleton and want support for promises.
### Scaffolding
Scaffolding commands are available in the form of `gen` and `del`. (syntax ex: `jake gen codetest=user`) Multiple scaffolds can be specified in a single command, as well as separating names with commas. (ex: `jake gen codetest=test1,test2 sitetest=test3`) Unit test files are automatically generated for Chaplin items. For Chaplin views, a template and stylesheet is also provided in addition to the code file.
#### `gen` / `del`
List available scaffolds.
#### `gen model=[name]` / `del model=[name]`
Generate/destroy a Chaplin model.
#### `gen collection=[name]` / `del collection=[name]`
Generate/destroy a Chaplin collection. Generating a Chaplin collection will also generate its corresponding model. Specify the name in singular form, as collection will automatically be pluralized.
#### `gen view=[name]` / `del view=[name]`
Generate/destroy a Chaplin view.
#### `gen collectionview=[name]` / `del collectionview=[name]`
Generate/destroy a Chaplin collection view. Generating a Chaplin collection view will also generate the individual item view.
#### `gen controller=[name]` / `del controller=[name]`
Generate/destroy a [Chaplin controller](http://docs.chaplinjs.org/chaplin.controller.html).
#### `gen codetest=[name]` / `del codetest=[name]`
Generate/destroy a test file with the given test name for testing code. (ex: unit testing)
#### `gen sitetest=[name]` / `del sitetest=[name]`
Generate/destroy a test file with the given test name for testing the site. (ex: functional testing)
### Testing
To enable testing, required packages must be added. Use `add:testing`/`add:codetesting`/`add:sitetesting` tasks to install dependencies via Bower/npm. Tests leverage [Mocha](http://visionmedia.github.io/mocha/), [Mocha as Promised](https://github.com/domenic/mocha-as-promised), and [Chai](http://chaijs.com/). Code and site testing is provided. Code testing adds [Sinon](http://sinonjs.org/) and [Sinon-Chai](https://github.com/domenic/sinon-chai).
#### `test:all [codereporter=progress] [sitereporter=spec] [browsers=[browsers]]`
Run all tests listed below once. For more information on reporters see below.
#### `test:code [reporter=progress] [watch=false] [browsers=[browsers]]`
Run code-based tests (ex. unit tests) using Karma. Karma is preconfigured to run with [PhantomJS](http://phantomjs.org/). A Karma reporter can be specified with the `reporter` option. You can also override the browsers to run with with the `browsers` option. (ex: `browsers=Chrome,Firefox,Safari`) If you run this task with `watch=true` Karma will auto-run on file changes. Otherwise by default Karma runs once. You can also run the server while watching files with `watch=server`. In addition, if you run a build (see below) with the `dev` environment the tests are included with a reporter under `test` to run in browsers. (ex. visit `http://locahost:[port]/test`)
#### `test:site [reporter=spec] [watch=false]`
Run site-based tests (ex. system tests) using Mocha, PhantomJS, and WebDriverJS. A Brunch server is started up temporarily to interact with the site. A Mocha reporter can be specified with the `reporter` option. If you run this task with `watch=true` Mocha will auto-run on file changes with [nodemon](http://remy.github.io/nodemon/). Otherwise by default Mocha runs once. The global method `getDriver` is provided to get a setup and built driver. WebDriverJS' use of Promises can be combined with Mocha as Promised to handle asynchronous behavior easily. ex:
```coffeescript
describe 'Sample', ->
before ->
@driver = getDriver()
it 'Has a proper title', ->
@driver.get('http://localhost:3333').then =>
@driver.getTitle()
.then (title) ->
expect(title).to.equal('Chapless Brunch')
after ->
@driver.quit()
```
### Building
These commands are used to assemble the application, generating the necessary JS/CSS and adding assets. Use `dev` mode to keep readable JS/CSS, plus include source maps as well as tests under the `test` folder. Use `prod` mode to minify/uglify JS/CSS as well as omit source maps and tests. If any Bower dependencies have not been downloaded yet, Bower will first download them.
#### `build:[mode]`
Assemble the application once.
#### `watch:[mode]`
Assemble the application and continue to watch for changes. Rebuild every time a change is detected.
#### `server:[mode]`
Assemble the application and continue to watch for changes. Rebuild every time a change is detected. Also, the application is served locally to open with a browser. [Prerender](https://prerender.io/) server and middleware is set up if available. This build uses the `web` environment.
## Libraries
### Core
- [Brunch Toolchain](https://github.com/jupl/brunch-toolchain) 0.8.1
### Languages
- [CoffeeScript](http://coffeescript.org/)
- [Eco](https://github.com/sstephenson/eco)
- [LESS](http://lesscss.org)
### Framework
- [Chaplin](http://chaplinjs.org)
- [Backbone](http://backbonejs.org)
- [Exoskeleton](http://exosjs.com/)
### Utilities
- [jQuery](http://jquery.com/)
- [LESS Hat](http://lesshat.com/)
- [Lo-Dash](http://lodash.com/)
- [Rivets.js](http://rivetsjs.com/)
- [Davy](https://github.com/lvivski/davy)

+ 10
- 0
app/application.coffee View File

@ -0,0 +1,10 @@
routes = require('routes')
utils = require('lib/utils')
defaultOptions = {routes, controllerSuffix: ''}
module.exports = class Application extends Chaplin.Application
title: 'Rivets'
constructor: (options) ->
super utils.extend({}, defaultOptions, options)

+ 1
- 0
app/application.less View File

@ -0,0 +1 @@
@import 'app/base';

+ 15
- 0
app/assets/index.html View File

@ -0,0 +1,15 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<title>Rivets</title>
<link rel="stylesheet" href="/stylesheets/app.css">
<script src="/javascripts/vendor.js"></script>
<script src="/javascripts/app.js"></script>
<script>require('initialize');</script>
</head>
<body></body>
</html>

+ 2
- 0
app/base.less View File

@ -0,0 +1,2 @@
@import 'bower_components/lesshat/build/lesshat.less';

+ 15
- 0
app/controllers/application.coffee View File

@ -0,0 +1,15 @@
Controller = require('controllers/base/controller')
IndexView = require('views/index')
Reactive = require('models/reactive')
Products = require('models/products')
InvoiceView = require('views/invoice/index')
module.exports = class ApplicationController extends Controller
index: ->
@model = new Reactive
@view = new IndexView {@model}
invoice: ->
@collection = new Products
@view = new InvoiceView {@collection}

+ 1
- 0
app/controllers/base/controller.coffee View File

@ -0,0 +1 @@
module.exports = class Controller extends Chaplin.Controller

+ 28
- 0
app/initialize.coffee View File

@ -0,0 +1,28 @@
initialize = ->
# Add Davy promises if available and we are using Exoskeleton
if Backbone.Deferred and Davy?
Backbone.Deferred = ->
new Davy
# Set up Rivets if available
rivets?.adapters[':'] =
subscribe: (obj, keypath, callback) ->
obj.on("change:#{keypath}", callback)
unsubscribe: (obj, keypath, callback) ->
obj.off("change:#{keypath}", callback)
read: (obj, keypath) ->
obj.get(keypath)
publish: (obj, keypath, value) ->
obj.set(keypath, value)
# Start application
Application = require('application')
new Application
# Initialize the application on DOM ready event.
# Use jQuery if available. Otherwise use native.
if $?
$(document).ready(initialize)
else
document.addEventListener('DOMContentLoaded', initialize)

+ 11
- 0
app/lib/utils.coffee View File

@ -0,0 +1,11 @@
# Application-specific utilities
# ------------------------------
# Delegate to Chaplins utils module
module.exports = utils = Chaplin.utils.beget(Chaplin.utils)
# Add extend so no need to worry if using Backbone or Exoskeleton
utils.extend = if _? then _.extend else Backbone.utils.extend
# utils.extend utils,
# someMethod: ->

+ 4
- 0
app/lib/view-helper.coffee View File

@ -0,0 +1,4 @@
utils = require('./utils')
# Application-specific view helpers (Lo-Dash mixins, Rivets formatters, etc.)
# ---------------------------------------------------------------------------

+ 5
- 0
app/models/base/collection.coffee View File

@ -0,0 +1,5 @@
Model = require('./model')
module.exports = class Collection extends Chaplin.Collection
# Use the project base model by default
model: Model

+ 1
- 0
app/models/base/model.coffee View File

@ -0,0 +1 @@
module.exports = class Model extends Chaplin.Model

+ 16
- 0
app/models/product.coffee View File

@ -0,0 +1,16 @@
Model = require('models/base/model')
module.exports = class Product extends Model
defaults:
price: 0
quantity: 1
subtotal: 0
initialize: ->
@on 'change:price change:quantity', @setSubtotal
@on 'change:item', @setPrice
# Override default Backbone method since no sync is needed
destroy: =>
@collection.remove @

+ 19
- 0
app/models/products.coffee View File

@ -0,0 +1,19 @@
Collection = require('models/base/collection')
Product = require('./product')
module.exports = class Products extends Collection
model: Product
initialize: ->
@on 'change', @_calculateTotals
@add [
{price: 0, quantity: 1, subtotal: 0}
{price: 0, quantity: 1, subtotal: 0}
]
window.debug = @
_calculateTotals: (product) ->
product.set 'subtotal', 1 * product.get('quantity') * 1 * product.get('price')
@total = @reduce (sum, product) ->
sum + product.get('subtotal')
, 0

+ 12
- 0
app/models/reactive.coffee View File

@ -0,0 +1,12 @@
Model = require('models/base/model')
module.exports = class Reactive extends Model
defaults:
name: 'One-way data binding'
color: 'grey'
enabled: false
initialize: ->
@on 'change:name', => @set 'computed', @get('name') + ' with computed property'
window.debug = @

+ 3
- 0
app/routes.coffee View File

@ -0,0 +1,3 @@
module.exports = (match) ->
match '', 'application#index'
match 'invoice', 'application#invoice'

+ 6
- 0
app/views/base/collection.coffee View File

@ -0,0 +1,6 @@
View = require('./view')
module.exports = class CollectionView extends Chaplin.CollectionView
# This class doesnt inherit from the application-specific View class,
# so we need to borrow the methods from the View prototype:
getTemplateFunction: View::getTemplateFunction

+ 3
- 0
app/views/base/item.coffee View File

@ -0,0 +1,3 @@
View = require('./view')
module.exports = class ItemView extends View

+ 21
- 0
app/views/base/view.coffee View File

@ -0,0 +1,21 @@
require('lib/view-helper') # Just load the view helpers, no return value
module.exports = class View extends Chaplin.View
autoRender: yes
# Precompiled templates function initializer.
getTemplateFunction: ->
@template
render: ->
super
return unless @model
if @_rivets
@_rivets.build()
else
@_rivets = rivets?.bind(@el, {@model})
dispose: ->
@_rivets?.unbind()
delete @_rivets
super

+ 6
- 0
app/views/index/index.coffee View File

@ -0,0 +1,6 @@
View = require('views/base/view')
module.exports = class IndexView extends View
className: 'index-view'
container: 'body'
template: require('./template')

+ 5
- 0
app/views/index/style.less View File

@ -0,0 +1,5 @@
@import 'app/base';
.index-view {
padding-left: 2rem
}

+ 6
- 0
app/views/index/template.eco View File

@ -0,0 +1,6 @@
<h1>Reactive Programming with Rivets</h1>
<p>{model:name}</p>
<p>{model:computed}</p>
<p class="lead text-muted">Easy right? Now try out <a href="/invoice">an example using two-way bindings</a> and formatters.</p>

+ 33
- 0
app/views/invoice/index.coffee View File

@ -0,0 +1,33 @@
View = require('views/base/view')
module.exports = class InvoiceView extends View
className: 'invoice-view'
container: 'body'
template: require('./template')
events:
'click .js-AddBtn': '_addBtnOnClick'
listen:
'all collection': '_updateTotal'
initialize: ->
# Rivets Formatter to input in dollars and store as cents
rivets?.formatters.currency =
read: (value) ->
(value / Math.pow(10, 2)).toFixed(2)
publish: (value) ->
Math.round(parseFloat(value) * Math.pow(10, 2))
# Chaplin method, called when View is attached
attach: ->
super
rivets?.bind @$('.js-Product'), {products: @collection}
_addBtnOnClick: (evt) ->
@collection.add new @collection.model
_updateTotal: (evt) ->
@$('.js-Total').val do =>
value = @collection.reduce (sum, product) ->
sum + product.get('subtotal')
, 0
(value / Math.pow(10, 2)).toFixed(2)

+ 5
- 0
app/views/invoice/style.less View File

@ -0,0 +1,5 @@
@import 'app/base';
.invoice-view {
}

+ 63
- 0
app/views/invoice/template.eco View File

@ -0,0 +1,63 @@
<div class="container">
<header>
<h1>Invoice <small>Product</small></h1>
</header>
<form class="form-inline" role="form">
<div class="table-responsive">
<table class="table">
<caption class="sr-only">Enter product invoice data.</caption>
<tbody>
<tr rv-each-product="products.models" class="js-Product">
<td>
<select class="form-control" rv-value="product.item">
<option value="0"></option>
<option value="50">Skirt</option>
<option value="120">Boots</option>
<option value="30.5">Pants</option>
</select>
</td>
<td>
<input class="js-Price form-control text-right" rv-value="product:price | currency" type="number" min="0.00" step="0.01">
</td>
<td>
<input class="js-Quantity form-control text-right" rv-value="product:quantity" type="number" min="1" step="1">
</td>
<td>
<input class="js-Subtotal form-control text-right" rv-value="product:subtotal | currency" placeholder="0.00" disabled>
</td>
<td>
<button class="btn btn-link" type="button" rv-on-click="product.destroy">Remove</button>
</td>
</tr>
</tbody>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>Quantity</th>
<th colspan="2">Subtotal</th>
</tr>
</thead>
<tfoot>
<tr>
<td>
<button type="button" class="js-AddBtn btn btn-primary" autofocus>Add product</button>
</td>
<td colspan="2">&nbsp;</td>
<td>
<label for="total" class="sr-only">Total</label>
<input id="total" class="js-Total form-control text-right" placeholder="0.00" disabled>
</td>
<td>&nbsp;</td>
</tr>
</tfoot>
</table>
</div>
</form>
<footer>
<p>
Created by <a href="http://www.twitter.com/jhabdas">Josh Habdas</a>.
Inspired by Boris Barroso's <a href="https://github.com/boriscy/backbone-rivetsjs">backbone-rivetsjs</a>.
</p>
</footer>
</div>

+ 29
- 0
bower.json View File

@ -0,0 +1,29 @@
{
"name": "package-name",
"ignore": [
"**/.*",
"node_modules",
"bower_components"
],
"dependencies": {
"chaplin": "~1.0.0",
"console-polyfill": "~0.1.1",
"jquery": "~2.1.0",
"lesshat": "~2.0.12",
"lodash": "~2.4.1",
"rivets": "~0.6.4",
"bootstrap": "~3.1.0"
},
"overrides": {
"backbone": {
"dependencies": {
"lodash": "*",
"jquery": "*"
},
"main": "backbone.js"
},
"rivets": {
"main": "dist/rivets.js"
}
}
}

+ 26
- 0
brunch-config.js View File

@ -0,0 +1,26 @@
var setup = require('./setup');
exports.config = setup({
server: {
path: 'server'
},
files: {
javascripts: {
joinTo: {
'javascripts/app.js': /^app/,
'javascripts/vendor.js': /^(vendor|bower_components)/
}
},
stylesheets: {
joinTo: {
'stylesheets/app.css': /^(app|vendor|bower_components)/
}
},
templates: {
joinTo: 'javascripts/app.js'
}
}
});

+ 10
- 0
generators/code-test/generator.json View File

@ -0,0 +1,10 @@
{
"description": "test file for code testing",
"files": [
{
"from": "test.coffee.hbs",
"to": "test/code/{{#dasherize}}{{name}}{{/dasherize}}.coffee"
}
],
"dependencies": []
}

+ 1
- 0
generators/code-test/helpers.js View File

@ -0,0 +1 @@
module.exports = require('../helpers');

+ 1
- 0
generators/code-test/test.coffee.hbs View File

@ -0,0 +1 @@
describe '{{#humanize}}{{name}}{{/humanize}}', ->

+ 34
- 0
generators/collection-view/generator.json View File

@ -0,0 +1,34 @@
{
"description": "Chaplin collection view and unit test",
"files": [
{
"from": "index.coffee.hbs",
"to": "app/views/{{#dasherize}}{{name}}{{/dasherize}}/index.coffee"
},
{
"from": "style.less.hbs",
"to": "app/views/{{#dasherize}}{{name}}{{/dasherize}}/style.less"
},
{
"from": "template.eco.hbs",
"to": "app/views/{{#dasherize}}{{name}}{{/dasherize}}/template.eco"
},
{
"from": "item/index.coffee.hbs",
"to": "app/views/{{#dasherize}}{{name}}{{/dasherize}}/item/index.coffee"
},
{
"from": "item/style.less.hbs",
"to": "app/views/{{#dasherize}}{{name}}{{/dasherize}}/item/style.less"
},
{
"from": "item/template.eco.hbs",
"to": "app/views/{{#dasherize}}{{name}}{{/dasherize}}/item/template.eco"
},
{
"from": "test.coffee.hbs",
"to": "test/code/views/{{#dasherize}}{{name}}{{/dasherize}}.coffee"
}
],
"dependencies": []
}

+ 1
- 0
generators/collection-view/helpers.js View File

@ -0,0 +1 @@
module.exports = require('../helpers');

+ 7
- 0
generators/collection-view/index.coffee.hbs View File

@ -0,0 +1,7 @@
CollectionView = require('views/base/collection')
{{#camelize}}{{name}}{{/camelize}}ItemView = require('./item')
module.exports = class {{#camelize}}{{name}}{{/camelize}}View extends CollectionView
className: '{{#dasherize}}{{name}}{{/dasherize}}-view'
template: require('./template')
itemView: {{#camelize}}{{name}}{{/camelize}}ItemView

+ 5
- 0
generators/collection-view/item/index.coffee.hbs View File

@ -0,0 +1,5 @@
ItemView = require('views/base/item')
module.exports = class {{#camelize}}{{name}}{{/camelize}}ItemView extends ItemView
className: '{{#dasherize}}{{name}}{{/dasherize}}-item-view'
template: require('./template')

+ 5
- 0
generators/collection-view/item/style.less.hbs View File

@ -0,0 +1,5 @@
@import "app/base";
.{{#dasherize}}{{name}}{{/dasherize}}-item-view {
}

+ 0
- 0
generators/collection-view/item/template.eco.hbs View File


+ 5
- 0
generators/collection-view/style.less.hbs View File

@ -0,0 +1,5 @@
@import "app/base";
.{{#dasherize}}{{name}}{{/dasherize}}-view {
}

+ 0
- 0
generators/collection-view/template.eco.hbs View File


+ 14
- 0
generators/collection-view/test.coffee.hbs View File

@ -0,0 +1,14 @@
{{#camelize}}{{name}}{{/camelize}}View = require('views/{{#dasherize}}{{name}}{{/dasherize}}')
{{#camelize}}{{name}}{{/camelize}}ItemView = require('views/{{#dasherize}}{{name}}{{/dasherize}}/item')
describe '{{#camelize}}{{name}}{{/camelize}}View', ->
beforeEach ->
@{{#lowercamelize}}{{name}}{{/lowercamelize}}View = new {{#camelize}}{{name}}{{/camelize}}View
@{{#lowercamelize}}{{name}}{{/lowercamelize}}ItemView = new {{#camelize}}{{name}}{{/camelize}}ItemView
afterEach ->
@{{#lowercamelize}}{{name}}{{/lowercamelize}}View.dispose()
@{{#lowercamelize}}{{name}}{{/lowercamelize}}ItemView.dispose()

+ 5
- 0
generators/collection/collection.coffee.hbs View File

@ -0,0 +1,5 @@
Collection = require('models/base/collection')
{{#camelize}}{{name}}{{/camelize}} = require('./{{#dasherize}}{{name}}{{/dasherize}}')
module.exports = class {{#camelize}}{{pluralName}}{{/camelize}} extends Collection
model: {{#camelize}}{{name}}{{/camelize}}

+ 16
- 0
generators/collection/generator.json View File

@ -0,0 +1,16 @@
{
"description": "Chaplin collection and unit test",
"files": [
{
"from": "collection.coffee.hbs",
"to": "app/models/{{#dasherize}}{{pluralName}}{{/dasherize}}.coffee"
},
{
"from": "test.coffee.hbs",
"to": "test/code/models/{{#dasherize}}{{pluralName}}{{/dasherize}}.coffee"
}
],
"dependencies": [
{ "name": "model", "params": "{{name}}" }
]
}

+ 1
- 0
generators/collection/helpers.js View File

@ -0,0 +1 @@
module.exports = require('../helpers');

+ 14
- 0
generators/collection/test.coffee.hbs View File

@ -0,0 +1,14 @@
{{#camelize}}{{name}}{{/camelize}} = require('models/{{#dasherize}}{{name}}{{/dasherize}}')
{{#camelize}}{{pluralName}}{{/camelize}} = require('models/{{#dasherize}}{{pluralName}}{{/dasherize}}')
describe '{{#camelize}}{{pluralName}}{{/camelize}}', ->
beforeEach ->
@{{#lowercamelize}}{{name}}{{/lowercamelize}} = new {{#camelize}}{{name}}{{/camelize}}
@{{#lowercamelize}}{{pluralName}}{{/lowercamelize}} = new {{#camelize}}{{pluralName}}{{/camelize}}
afterEach ->
@{{#lowercamelize}}{{name}}{{/lowercamelize}}.dispose()
@{{#lowercamelize}}{{pluralName}}{{/lowercamelize}}.dispose()

+ 3
- 0
generators/controller/controller.coffee.hbs View File

@ -0,0 +1,3 @@
Controller = require('controllers/base/controller')
module.exports = class {{#camelize}}{{name}}{{/camelize}}Controller extends Controller

+ 14
- 0
generators/controller/generator.json View File

@ -0,0 +1,14 @@
{
"description": "Chaplin controller and unit test",
"files": [
{
"from": "controller.coffee.hbs",
"to": "app/controllers/{{#dasherize}}{{name}}{{/dasherize}}.coffee"
},
{
"from": "test.coffee.hbs",
"to": "test/code/controllers/{{#dasherize}}{{name}}{{/dasherize}}.coffee"
}
],
"dependencies": []
}

+ 1
- 0
generators/controller/helpers.js View File

@ -0,0 +1 @@
module.exports = require('../helpers');

+ 11
- 0
generators/controller/test.coffee.hbs View File

@ -0,0 +1,11 @@
{{#camelize}}{{name}}{{/camelize}}Controller = require('controllers/{{#dasherize}}{{name}}{{/dasherize}}')
describe '{{#camelize}}{{name}}{{/camelize}}Controller', ->
beforeEach ->
@{{#lowercamelize}}{{name}}{{/lowercamelize}}Controller = new {{#camelize}}{{name}}{{/camelize}}Controller
afterEach ->
@{{#lowercamelize}}{{name}}{{/lowercamelize}}Controller.dispose()

+ 15
- 0
generators/helpers.js View File

@ -0,0 +1,15 @@
require('sugar');
module.exports = function(Handlebars) {
Handlebars.registerHelper('humanize', function(options) {
return new Handlebars.SafeString(options.fn(this).underscore().humanize());
});
Handlebars.registerHelper('dasherize', function(options) {
return new Handlebars.SafeString(options.fn(this).dasherize());
});
Handlebars.registerHelper('lowercamelize', function(options) {
return new Handlebars.SafeString(options.fn(this).camelize(false));
});
};

+ 14
- 0
generators/model/generator.json View File

@ -0,0 +1,14 @@
{
"description": "Chaplin view and unit test",
"files": [
{
"from": "model.coffee.hbs",
"to": "app/models/{{#dasherize}}{{name}}{{/dasherize}}.coffee"
},
{
"from": "test.coffee.hbs",
"to": "test/code/models/{{#dasherize}}{{name}}{{/dasherize}}.coffee"
}
],
"dependencies": []
}

+ 1
- 0
generators/model/helpers.js View File

@ -0,0 +1 @@
module.exports = require('../helpers');

+ 3
- 0
generators/model/model.coffee.hbs View File

@ -0,0 +1,3 @@
Model = require('models/base/model')
module.exports = class {{#camelize}}{{name}}{{/camelize}} extends Model

+ 11
- 0
generators/model/test.coffee.hbs View File

@ -0,0 +1,11 @@
{{#camelize}}{{name}}{{/camelize}} = require('models/{{#dasherize}}{{name}}{{/dasherize}}')
describe '{{#camelize}}{{name}}{{/camelize}}', ->
beforeEach ->
@{{#lowercamelize}}{{name}}{{/lowercamelize}} = new {{#camelize}}{{name}}{{/camelize}}
afterEach ->
@{{#lowercamelize}}{{name}}{{/lowercamelize}}.dispose()

+ 10
- 0
generators/site-test/generator.json View File

@ -0,0 +1,10 @@
{
"description": "test file for site testing",
"files": [
{
"from": "test.coffee.hbs",
"to": "test/site/{{#dasherize}}{{name}}{{/dasherize}}.coffee"
}
],
"dependencies": []
}

+ 1
- 0
generators/site-test/helpers.js View File

@ -0,0 +1 @@
module.exports = require('../helpers');

+ 10
- 0
generators/site-test/test.coffee.hbs View File

@ -0,0 +1,10 @@
describe '{{#humanize}}{{name}}{{/humanize}}', ->
before ->
@driver = getDriver()
it '...', ->
@driver.get('http://localhost:3333')
after ->
@driver.quit()

+ 22
- 0
generators/view/generator.json View File

@ -0,0 +1,22 @@
{
"description": "Chaplin view and unit test",
"files": [
{
"from": "index.coffee.hbs",
"to": "app/views/{{name}}/index.coffee"
},
{
"from": "style.less.hbs",
"to": "app/views/{{name}}/style.less"
},
{
"from": "template.eco.hbs",
"to": "app/views/{{name}}/template.eco"
},
{
"from": "test.coffee.hbs",
"to": "test/code/views/{{name}}.coffee"
}
],
"dependencies": []
}

+ 1
- 0
generators/view/helpers.js View File

@ -0,0 +1 @@
module.exports = require('../helpers');

+ 5
- 0
generators/view/index.coffee.hbs View File

@ -0,0 +1,5 @@
View = require('views/base/view')
module.exports = class {{#camelize}}{{name}}{{/camelize}}View extends View
className: '{{name}}-view'
template: require('./template')

+ 5
- 0
generators/view/style.less.hbs View File

@ -0,0 +1,5 @@
@import 'app/base';
.{{name}}-view {
}

+ 0
- 0
generators/view/template.eco.hbs View File


+ 11
- 0
generators/view/test.coffee.hbs View File

@ -0,0 +1,11 @@
{{#camelize}}{{name}}{{/camelize}}View = require('views/{{name}}')
describe '{{#camelize}}{{name}}{{/camelize}}View', ->
beforeEach ->
@{{#lowercamelize}}{{name}}{{/lowercamelize}}View = new {{#camelize}}{{name}}{{/camelize}}View
afterEach ->
@{{#lowercamelize}}{{name}}{{/lowercamelize}}View.dispose()

+ 14
- 0
jakelib/bower.jake View File

@ -0,0 +1,14 @@
// Bower related tasks
var bower = require('./lib').npmBin('bower');
namespace('bower', function() {
desc('Download and install Bower components');
task('install', function() {
return bower.execute('install', '--allow-root');
});
desc('Clear Bower components');
task('clean', function() {
jake.rmRf('bower_components');
});
});

+ 38
- 0
jakelib/build.jake View File

@ -0,0 +1,38 @@
// Brunch build tasks
var brunch = require('./lib').npmBin('brunch');
namespace('build', function() {
desc('Build project for development');
task('dev', ['bower:install', 'clean:web'], function() {
return brunch.execute('build', '--env', 'web:dev');
});
desc('Build project for production');
task('prod', ['bower:install', 'clean:web'], function() {
return brunch.execute('build', '--env', 'web:prod');
});
});
namespace('watch', function() {
desc('Build project for development and rebuild on changes');
task('dev', ['bower:install', 'clean:web'], function() {
return brunch.execute('watch', '--env', 'web:dev');
});
desc('Build project for production and rebuild on changes');
task('prod', ['bower:install', 'clean:web'], function() {
return brunch.execute('watch', '--env', 'web:prod');
});
});
namespace('server', function() {
desc('Build project for development, rebuild on changes, and host locally');
task('dev', ['bower:install', 'clean:web'], function() {
return brunch.execute('watch', '--server', '--env', 'web:dev');
});
desc('Build project for production, rebuild on changes, and host locally');
task('prod', ['bower:install', 'clean:web'], function() {
return brunch.execute('watch', '--server', '--env', 'web:prod');
});
});

+ 77
- 0
jakelib/generator.jake View File

@ -0,0 +1,77 @@
// Scaffolt non-module generator tasks
require('sugar');
var generators = require('./lib').generators;
var Promise = require('bluebird');
// Iterate over non-module generators for creating tasks that scaffold
desc('Scaffold item(s), or list available scaffolds')
task('gen', function() {
var promises = [];
// Iterate over all available generators.
generators.forEach(function(generator) {
var type = generator.name;
var names = process.env[generator.task];
if(!generator.isModule && names) {
names.split(',').forEach(function(name) {
promises.push(new Promise(function(resolve) {
validate(type, name);
jake.Task['scaffold:gen']
.addListener('complete', resolve)
.invoke(type, name);
}));
});
}
});
// Check if promises have been made. If not, list available generators.
if(promises.length) {
return Promise.all(promises);
}
else {
listGenerators();
}
});
// Iterate over non-module generators for creating tasks that undo a scaffold
desc('Delete scaffolded item(s), or list available scaffolds')
task('del', function() {
var promises = [];
// Iterate over all available generators.
generators.forEach(function(generator) {
var type = generator.name;
var names = process.env[generator.task];
if(!generator.isModule && names) {
names.split(',').forEach(function(name) {
promises.push(new Promise(function(resolve) {
validate(type, name);
jake.Task['scaffold:del']
.addListener('complete', resolve)
.invoke(type, name);
}));
});
}
});
// Check if promises have been made. If not, list available generators.
if(promises.length) {
return Promise.all(promises);
}
else {
listGenerators();
}
});
function listGenerators() {
console.log('Available scaffolds:');
generators.forEach(function(generator) {
console.log(generator.task + ' - ' + generator.description.humanize());
});
}
function validate(generator, name) {
if((generator === 'view' || generator === 'collection-view') && name.dasherize() === 'base') {
fail('name parameter cannot be "base"');
}
}

+ 117
- 0
jakelib/lib/index.js View File

@ -0,0 +1,117 @@
require('sugar');
var fs = require('fs');
var os = require('os');
var path = require('path');
var Promise = require('bluebird');
var spawn = require('child_process').spawn;
var cwd = process.cwd();
var slice = Array.prototype.slice;
/**
* Return a function that will execute a console command
* @param {String} command Command to execute
* @return {Object} Object with two properties:
* execute - Function that runs the node module
* command. Arguments passed to the function will
* be added to the module command. It returns a
* promise.
* options - Options to pass through, since
* execute relies on node's spawn.
*/
exports.bin = function(command) {
return {
options: {
stdio: 'inherit'
},
execute: function() {
var args = slice.call(arguments, 0);
return execute.apply(null, [command, this.options].concat(args));
}
};
}
/**
* List of available generators from Scaffolt. Each element has the following
* properties:
* name Generator name that is to be passed to Scaffolt
* task Same as name, but its name is formatted to be friendly with
* Jake task names
* description Description of generator. If one is not defined in Scaffolt,
* then make an educated guess with the name.
* @type {Array}
*/
exports.generators = fs.readdirSync('generators').filter(function(generator) {
var generatorFile = path.resolve('generators', generator, 'generator.json');
return fs.existsSync(generatorFile);
})
.map(function(generator) {
var generatorFile = path.resolve('generators', generator, 'generator.json');
var json = require(generatorFile);
return {
task: generator.dasherize().replace(/-/g, ''),
name: generator,
description: json.description || generator.spacify()
}
});
/**
* Return a function that will execute the a node module command
* @param {String} moduleName Name of node module installed locally
* @return {Object} Object with execute command and options object.
* For more information see bin command above.
*/
exports.npmBin = function(moduleName) {
var command = path.resolve('node_modules', '.bin', moduleName);
// Tack on '.cmd' for Windows platform
if(os.platform() === 'win32') {
command += '.cmd';
}
return exports.bin(command);
}
/**
* Wraps spawn command in a promise.
* @param {String} command Path to command to execute in the shell
* @param {Object} options Options to pass through via the spawn command
* @param {Array} args If given argument is an array, it is assumed
* that it is a list of arguments to be passed.
* @param {...String} Params for command that would be space separated
* @return {Promise} Bluebird promise that resolves when command is
* completed. Errors or aborts will cause
* rejection.
*/
function execute(command, options, args) {
var child;
// Check if arguments have been given as an array
if(!Object.isArray(args)) {
args = slice.call(arguments, 2);
}
// Run spawn and leverage promises
return new Promise(function(resolve, reject) {
// Catch for Ctrl-C
process.on('SIGINT', reject);
// Execute and check for errors when process finishes
child = spawn(command, args, options);
child.on('exit', function(code) {
if(!code) {
resolve();
}
else {
reject();
}
});
child.on('error', reject);
})
.cancellable()
.finally(function() {
if(child) {
child.kill();
}
});
};

+ 212
- 0
jakelib/module.jake View File

@ -0,0 +1,212 @@
// Tasks to add modules to the project that are not included by default.
// This is usually either Bower packages or module-based Scaffolt generators.
var generators = require('./lib').generators;
var jsonfile = require('jsonfile');
var npm = require('./lib').bin('npm');
namespace('add', function() {
desc('Add support for testing (code/site)');
task('testing', ['add:codetesting', 'add:sitetesting']);
desc('Add support for code testing');
task('codetesting', function() {
editBower(function() {
this.dependencies.chai = '~1.9.0';
this.dependencies.mocha = '~1.17.1';
this.dependencies.sinon = 'http://sinonjs.org/releases/sinon-1.7.3.js';
this.dependencies['sinon-chai'] = '~2.5.0';
this.overrides.mocha = {
"main": [
"mocha.css",
"mocha.js"
]
};
this.overrides['sinon-chai'] = {
"main": "lib/sinon-chai.js",
"dependencies": {
"sinon": "*",
"chai": "*"
}
};
});
editPackage(function() {
this.devDependencies.karma = '~0.10.9';
this.devDependencies['karma-chai-plugins'] = '~0.2.0';
this.devDependencies['karma-mocha'] = '~0.1.1';
});
return npm.execute('install');
});
desc('Add support for site testing');
task('sitetesting', function() {
editPackage(function() {
this.devDependencies.chai = '~1.9.0';
this.devDependencies.mocha = '~1.17.1';
this.devDependencies['mocha-as-promised'] = '~2.0.0';
this.devDependencies.nodemon = '~1.0.14';
this.devDependencies['selenium-webdriver'] = '~2.39.0';
});
return npm.execute('install');
});
desc('Add Prerender');
task('prerender', function() {
editPackage(function() {
this.dependencies.prerender = '~2.0.1';
this.dependencies['prerender-node'] = '~0.1.15';
});
return npm.execute('install');
});
desc('Add jQuery');
task('jquery', function() {
editBower(function() {
this.dependencies.jquery = '~2.1.0';
});
});
desc('Add normalize.css');
task('normalize', function() {
editBower(function() {
this.dependencies['normalize-css'] = '~3.0.0';
});
});
desc('Add Lo-Dash');
task('lodash', function() {
editBower(function() {
this.dependencies.lodash = '~2.4.1';
});
});
desc('Add Rivets for better view/model data binding');
task('rivets', function() {
editBower(function() {
this.dependencies.rivets = '~0.6.4';
this.overrides.rivets = {
main: 'dist/rivets.js'
};
});
});
desc('Add Exoskeleton (replaces Backbone, removes jQuery and Lodash)');
task('exoskeleton', ['rem:jquery', 'rem:lodash'], function() {
editBower(function() {
this.dependencies.exoskeleton = '~0.6.1';
this.overrides.chaplin = {
dependencies: {
exoskeleton: '*'
}
};
delete this.overrides.backbone;
});
});
desc('Add Davy for promise support (useful with Exoskeleton)');
task('davy', function() {
editBower(function() {
this.dependencies.davy = '~0.1.0';
});
});
});
namespace('rem', function() {
desc('Remove support for testing (code/site)');
task('testing', ['rem:codetesting', 'rem:sitetesting']);
desc('Remove support for code testing');
task('codetesting', function() {
editBower(function() {
delete this.dependencies.chai;
delete this.dependencies.mocha;
delete this.dependencies.sinon;
delete this.dependencies['sinon-chai'];
delete this.overrides.mocha;
delete this.overrides['sinon-chai'];
});
return npm.execute('uninstall',
'karma',
'karma-chai-plugins',
'karma-mocha',
'--save-dev');
});
desc('Remove support for site testing');
task('sitetesting', function() {
return npm.execute('uninstall',
'chai',
'mocha',
'mocha-as-promised',
'nodemon',
'selenium-webdriver',
'--save-dev');
});
desc('Remove Prerender');
task('prerender', function() {
return npm.execute('uninstall', 'prerender', 'prerender-node', '--save');
});
desc('Remove jQuery');
task('jquery', function() {
editBower(function() {
delete this.dependencies.jquery;
});
});
desc('Remove normalize.css');
task('normalize', function() {
editBower(function() {
delete this.dependencies['normalize-css'];
});
});
desc('Remove Lo-Dash');
task('lodash', function() {
editBower(function() {
delete this.dependencies.lodash;
});
});
desc('Remove Rivets');
task('rivets', function() {
editBower(function() {
delete this.dependencies.rivets;
delete this.overrides.rivets;
});
});
desc('Remove Exoskeleton (restores classic Backbone, jQuery, and Lo-Dash)');
task('exoskeleton', ['add:jquery', 'add:lodash'], function() {
editBower(function() {
this.overrides.backbone = {
dependencies: {
lodash: '*',
jquery: '*'
},
main: 'backbone.js'
};
delete this.dependencies.exoskeleton;
delete this.overrides.chaplin;
});
});
desc('Remove Davy');
task('davy', function() {
editBower(function() {
delete this.dependencies.davy;
});
});
});
function editBower(callback) {
var json = jsonfile.readFileSync('bower.json');
callback.call(json);
jsonfile.writeFileSync('bower.json', json);
}
function editPackage(callback) {
var json = jsonfile.readFileSync('package.json');
callback.call(json);
jsonfile.writeFileSync('package.json', json);
}

+ 10
- 0
jakelib/npm.jake View File

@ -0,0 +1,10 @@
// NPM related tasks
var npm = require('./lib').bin('npm');
namespace('npm', function() {
desc('Clear Node packages');
task('clean', function() {
jake.rmRf('node_modules');
return npm.execute('cache', 'clean');
});
});

+ 133
- 0
jakelib/test.jake View File

@ -0,0 +1,133 @@
// Test-related tasks
var brunch = require('./lib').npmBin('brunch');
var config = require('../brunch-config').config.overrides['web:dev'];
var karma = require('./lib').npmBin('karma');
var mocha = require('./lib').npmBin('mocha');
var nodemon = require('./lib').npmBin('nodemon');
var path = require('path');
var phantomjs = require('./lib').npmBin('phantomjs');
var Promise = require('bluebird');
namespace('test', function() {
desc('Run all tests');
task('all', function() {
process.env.watch = null;
return new Promise(function(resolve) {
console.log('\nCode testing\n------------');
process.env.reporter = process.env.codereporter;
jake.Task['test:code'].addListener('complete', function() {
console.log('\nSite testing\n------------')
process.env.reporter = process.env.sitereporter;
jake.Task['test:site'].addListener('complete', resolve).execute();
}).execute();
});
});
desc('Run code-based tests using Karma');
task('code', ['bower:install', 'clean:web'], function() {
var args = ['start', path.resolve('test', 'karma.conf.js')];
// Check for reporter
if(process.env.reporter) {
args.push('--reporters');
args.push(process.env.reporter);
}
// Set browsers options if available
if(process.env.browsers) {
args.push('--browsers');
args.push(process.env.browsers);
}
// Default behavior is to run tests once
if(process.env.watch !== 'true' && process.env.watch !== 'server') {
return new Promise(function(resolve) {
jake.Task['build:dev'].addListener('complete', function() {
args.push('--single-run');
resolve(karma.execute(args));
})
.execute();
});
}
// Also tests can be run continuously
else {
if(process.env.watch === 'server') {
var server = brunch.execute('watch', '--server', '--env', 'web:dev');
}
else {
var server = brunch.execute('watch', '--env', 'web:dev');
}
return new Promise(function(resolve, reject) {
server.catch(reject);
var id = setInterval(function() {
try { // Check if public folder is not empty
var publicReady = !!jake.readdirR(config.paths.public).length
}
catch(e) {
}
if(typeof publicReady !== 'undefined' && publicReady) {
clearInterval(id);
args.push('--no-single-run');
karma.execute(args).then(resolve, reject);
}
}, 1000);
})
.finally(function() {
if(!server.isFulfilled()) {
server.cancel();
}
});
}
});
desc('Run site-based tests using Mocha and WebDriverJS');
task('site', ['bower:install', 'clean:web'], function() {
var phantom = phantomjs.execute('--webdriver=4444');
var server = brunch.execute('watch', '--server', '--env', 'web:prod');
var args = [];
// Check for reporter
if(process.env.reporter) {
args.push('--reporter');
args.push(process.env.reporter);
}
return new Promise(function(resolve, reject) {
phantom.catch(reject);
server.catch(reject);
var id = setInterval(function() {
// Check if public folder is not empty
try {
</