@ -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 |
@ -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/ |
@ -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. |
@ -0,0 +1,4 @@ | |||
// Set default task to list available tasks | |||
task('default', function() { | |||
jake.run('-T'); | |||
}); |
@ -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. |
@ -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) |
@ -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) |
@ -0,0 +1 @@ | |||
@import 'app/base'; |
@ -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> |
@ -0,0 +1,2 @@ | |||
@import 'bower_components/lesshat/build/lesshat.less'; | |||
@ -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} |
@ -0,0 +1 @@ | |||
module.exports = class Controller extends Chaplin.Controller |
@ -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) |
@ -0,0 +1,11 @@ | |||
# Application-specific utilities | |||
# ------------------------------ | |||
# Delegate to Chaplinโs 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: -> |
@ -0,0 +1,4 @@ | |||
utils = require('./utils') | |||
# Application-specific view helpers (Lo-Dash mixins, Rivets formatters, etc.) | |||
# --------------------------------------------------------------------------- |
@ -0,0 +1,5 @@ | |||
Model = require('./model') | |||
module.exports = class Collection extends Chaplin.Collection | |||
# Use the project base model by default | |||
model: Model |
@ -0,0 +1 @@ | |||
module.exports = class Model extends Chaplin.Model |
@ -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 @ |
@ -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 |
@ -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 = @ |
@ -0,0 +1,3 @@ | |||
module.exports = (match) -> | |||
match '', 'application#index' | |||
match 'invoice', 'application#invoice' |
@ -0,0 +1,6 @@ | |||
View = require('./view') | |||
module.exports = class CollectionView extends Chaplin.CollectionView | |||
# This class doesnโt inherit from the application-specific View class, | |||
# so we need to borrow the methods from the View prototype: | |||
getTemplateFunction: View::getTemplateFunction |
@ -0,0 +1,3 @@ | |||
View = require('./view') | |||
module.exports = class ItemView extends View |
@ -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 |
@ -0,0 +1,6 @@ | |||
View = require('views/base/view') | |||
module.exports = class IndexView extends View | |||
className: 'index-view' | |||
container: 'body' | |||
template: require('./template') |
@ -0,0 +1,5 @@ | |||
@import 'app/base'; | |||
.index-view { | |||
padding-left: 2rem | |||
} |
@ -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> |
@ -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) |
@ -0,0 +1,5 @@ | |||
@import 'app/base'; | |||
.invoice-view { | |||
} |
@ -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"> </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> </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> |
@ -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" | |||
} | |||
} | |||
} |
@ -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' | |||
} | |||
} | |||
}); |
@ -0,0 +1,10 @@ | |||
{ | |||
"description": "test file for code testing", | |||
"files": [ | |||
{ | |||
"from": "test.coffee.hbs", | |||
"to": "test/code/{{#dasherize}}{{name}}{{/dasherize}}.coffee" | |||
} | |||
], | |||
"dependencies": [] | |||
} |
@ -0,0 +1 @@ | |||
module.exports = require('../helpers'); |
@ -0,0 +1 @@ | |||
describe '{{#humanize}}{{name}}{{/humanize}}', -> |
@ -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": [] | |||
} |
@ -0,0 +1 @@ | |||
module.exports = require('../helpers'); |
@ -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 |
@ -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') |
@ -0,0 +1,5 @@ | |||
@import "app/base"; | |||
.{{#dasherize}}{{name}}{{/dasherize}}-item-view { | |||
} |
@ -0,0 +1,5 @@ | |||
@import "app/base"; | |||
.{{#dasherize}}{{name}}{{/dasherize}}-view { | |||
} |
@ -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() |
@ -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}} |
@ -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}}" } | |||
] | |||
} |
@ -0,0 +1 @@ | |||
module.exports = require('../helpers'); |
@ -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() |
@ -0,0 +1,3 @@ | |||
Controller = require('controllers/base/controller') | |||
module.exports = class {{#camelize}}{{name}}{{/camelize}}Controller extends Controller |
@ -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": [] | |||
} |
@ -0,0 +1 @@ | |||
module.exports = require('../helpers'); |
@ -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() |
@ -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)); | |||
}); | |||
}; |
@ -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": [] | |||
} |
@ -0,0 +1 @@ | |||
module.exports = require('../helpers'); |
@ -0,0 +1,3 @@ | |||
Model = require('models/base/model') | |||
module.exports = class {{#camelize}}{{name}}{{/camelize}} extends Model |
@ -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() |
@ -0,0 +1,10 @@ | |||
{ | |||
"description": "test file for site testing", | |||
"files": [ | |||
{ | |||
"from": "test.coffee.hbs", | |||
"to": "test/site/{{#dasherize}}{{name}}{{/dasherize}}.coffee" | |||
} | |||
], | |||
"dependencies": [] | |||
} |
@ -0,0 +1 @@ | |||
module.exports = require('../helpers'); |
@ -0,0 +1,10 @@ | |||
describe '{{#humanize}}{{name}}{{/humanize}}', -> | |||
before -> | |||
@driver = getDriver() | |||
it '...', -> | |||
@driver.get('http://localhost:3333') | |||
after -> | |||
@driver.quit() |
@ -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": [] | |||
} |
@ -0,0 +1 @@ | |||
module.exports = require('../helpers'); |
@ -0,0 +1,5 @@ | |||
View = require('views/base/view') | |||
module.exports = class {{#camelize}}{{name}}{{/camelize}}View extends View | |||
className: '{{name}}-view' | |||
template: require('./template') |
@ -0,0 +1,5 @@ | |||
@import 'app/base'; | |||
.{{name}}-view { | |||
} |
@ -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() |
@ -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'); | |||
}); | |||
}); |
@ -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'); | |||
}); | |||
}); |
@ -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"'); | |||
} | |||
} |
@ -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(); | |||
} | |||
}); | |||
}; |
@ -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); | |||
} |
@ -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'); | |||
}); | |||
}); |
@ -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 { | |||