├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── bin └── americano ├── coffeelint.json ├── main.coffee ├── package.json └── tests ├── fixtures └── installed-plugin-test │ ├── main.coffee │ └── package.json ├── node_modules ├── installed-plugin-test │ ├── main.coffee │ └── package.json └── myplugins │ └── package.json ├── server ├── config.coffee └── controllers │ └── routes.coffee └── tests.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | node_modules 3 | *.swp 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | coffeelint.json 2 | *.coffee 3 | todo 4 | .git* 5 | .project 6 | .travis.yml 7 | tests 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | matrix: 4 | fast_finish: true 5 | allow_failures: 6 | - node_js: "5" 7 | node_js: 8 | - "0.10" 9 | - "0.12" 10 | - "4" 11 | - "5" 12 | env: 13 | global: 14 | - NODE_ENV=test 15 | - CXX=g++-4.8 16 | addons: 17 | apt: 18 | sources: 19 | - ubuntu-toolchain-r-test 20 | packages: 21 | - gcc-4.8 22 | - g++-4.8 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2012 Cozy Cloud (contact@cozycloud.cc)) 2 | 3 | This project is free software released under the MIT/X11 license: 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Americano 2 | 3 | [ExpressJS](http://expressjs.com/) 4 | is an awesome tool to build small web application. But once you start 5 | using it, you discover that writing the configuration and the routes 6 | often leads to ugly code. To solve that, Americano acts as a 7 | wrapper around Express and make it more opinionated about how to write routes 8 | and configuration. See in the following how it make things cleaner. 9 | 10 | ## When to use Americano? 11 | 12 | Americano is: 13 | 14 | * a tool to quickstart small web applications or web modules. 15 | * oriented to build single page-app and REST API. 16 | 17 | Americano is not: 18 | 19 | * a full-featured framework for making big web applications. 20 | 21 | NB: Americano inherits all the [ExpressJS 22 | features](http://expressjs.com/guide.html) 23 | 24 | ## Getting started 25 | 26 | 27 | ### Binary 28 | 29 | There is a binary provided with Americano to start quickly your project: 30 | 31 | #### Install 32 | 33 | npm install americano -g 34 | 35 | #### Usage 36 | 37 | americano blog 38 | 39 | #### Output 40 | 41 | create: blog 42 | create: blog/package.json 43 | create: blog/server.js 44 | create: blog/README.md 45 | create: blog/client/public 46 | create: blog/server/models 47 | create: blog/server/controllers 48 | create: blog/server/controllers/routes.js 49 | create: blog/server/controllers/index.js 50 | create: blog/server/config.js 51 | 52 | install dependencies: 53 | $ cd blog && npm install 54 | 55 | Run your application: 56 | $ npm start 57 | 58 | #### Coffeescript Usage 59 | 60 | americano --coffee blog 61 | 62 | ### Handmade 63 | 64 | To write an Americano application you need to add it as a dependency of your 65 | package.json file. 66 | 67 | npm install americano --save 68 | 69 | Then you must to create your main file: 70 | 71 | ```javascript 72 | // ./server.js 73 | var americano = require('americano'); 74 | 75 | var port = process.env.PORT || 3000; 76 | americano.start({name: 'yourapp', port: port}, function(err, app, server) { 77 | // Do something when everything is properly started. 78 | }); 79 | ``` 80 | 81 | 82 | ## Configuration 83 | 84 | Americano requires a config file located at the 85 | root of your project, let's add it: 86 | 87 | ```javascript 88 | // ./server/config.js 89 | var americano = require('americano'); 90 | 91 | module.exports = { 92 | common: { 93 | use: [ 94 | americano.bodyParser(), 95 | americano.methodOverride(), 96 | americano.static(__dirname + '/../client/public', { 97 | maxAge: 86400000 98 | }) 99 | ] 100 | useAfter: [ 101 | americano.errorHandler({ 102 | dumpExceptions: true, 103 | showStack: true 104 | }) 105 | ] 106 | }, 107 | development: { 108 | use: [ 109 | americano.logger('dev') 110 | ], 111 | set: { 112 | debug: 'on' 113 | } 114 | }, 115 | production: [ 116 | americano.logger('short') 117 | ] 118 | }; 119 | ``` 120 | 121 | 122 | ## Routes 123 | 124 | Once configuration is done, Americano will ask for your routes to be described 125 | in a single file following this syntax: 126 | 127 | 128 | ```javascript 129 | // ./server/controllers/routes.coffee 130 | var posts = require('./posts'); 131 | var comments = require('./comments'); 132 | 133 | module.exports = { 134 | 'posts': { 135 | get: posts.all, 136 | post: posts.create 137 | }, 138 | 'posts/:id': { 139 | get: posts.show, 140 | put: posts.modify, 141 | del: [posts.verifyToken, posts.destroy] 142 | }, 143 | 'posts/:id/comments': { 144 | get: comments.fromPost 145 | }, 146 | 'comments': { 147 | get: comments.all 148 | } 149 | }; 150 | ``` 151 | 152 | ## Controllers 153 | 154 | Your controllers can be written as usual, they are ExpressJS controlllers. 155 | 156 | ## Final thoughts 157 | 158 | You're done! Just run `node server.js` and you have your configured 159 | Express web server up and running! 160 | 161 | By the way this is how your single-page app looks like with Americano: 162 | 163 | 164 | your-blog/ 165 | server.js 166 | server/ 167 | config.js 168 | controllers/ 169 | routes.js 170 | posts.js 171 | comments.js 172 | models/ 173 | post.js 174 | comment.js 175 | client/ 176 | ... front-end stuff ... 177 | 178 | ## Plugins 179 | 180 | Americano allows to use plugins that shares its philosophy of making cleaner 181 | and more straightforward things. 182 | 183 | Actually there is only one plugin, feel free to add yours: 184 | 185 | * [cozy-db](https://github.com/cozy/cozy-db/blob/master/DOCINDEX.md#use-with-americano): 186 | a plugin to make [Cozy](https://cozy.io) application faster. 187 | 188 | ## What about contributions? 189 | 190 | Here is what I would like to do next: 191 | 192 | * make a plugin for socket-io 193 | * make a plugin for mongoose 194 | * make a plugin for sqlite 195 | * make a plugin for cozy-realtime-adapter 196 | 197 | I didn't start any development yet, so you're welcome to participate! 198 | -------------------------------------------------------------------------------- /bin/americano: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var exec = require('child_process').exec 4 | , program = require('commander') 5 | , mkdirp = require('mkdirp') 6 | , pkg = require('../package.json') 7 | , version = pkg.version 8 | , os = require('os') 9 | , fs = require('fs'); 10 | 11 | // CLI 12 | 13 | program 14 | .version(version) 15 | .usage('[options] [dir]') 16 | .option('-c, --coffee', 'Generate Javascript files instead of Coffee files.') 17 | .parse(process.argv); 18 | 19 | // Path 20 | 21 | var path = program.args.shift() || '.'; 22 | 23 | // end-of-line code 24 | 25 | var eol = os.EOL 26 | 27 | /** 28 | * Templates 29 | */ 30 | var routes = [ 31 | "index = require './index'", 32 | "module.exports =", 33 | " '':", 34 | " get: index.index" 35 | ].join(eol); 36 | 37 | var routesJs = [ 38 | "var index = require('./index');", 39 | "module.exports = {", 40 | " '': { get: index.index }", 41 | "};", 42 | ].join(eol); 43 | 44 | var app = [ 45 | "americano = require('americano')", 46 | "", 47 | "port = process.env.PORT || 3000", 48 | "americano.start name: '', port: port" 49 | ].join(eol); 50 | 51 | var appJs = [ 52 | "var americano = require('americano');", 53 | "", 54 | "var port = process.env.PORT || 3000;", 55 | "americano.start({name: '', port: port});" 56 | ].join(eol); 57 | 58 | var index = [ 59 | "module.exports.index = (req, res, next) ->", 60 | " res.send 'Hello'" 61 | ].join(eol); 62 | 63 | var indexJs = [ 64 | "module.exports.index = function (req, res, next) {", 65 | " res.send('Hello');", 66 | "};" 67 | ].join(eol); 68 | 69 | var configuration = [ 70 | "americano = require 'americano'", 71 | "", 72 | "module.exports =", 73 | " common:", 74 | " use: [", 75 | " americano.bodyParser()", 76 | " americano.methodOverride()", 77 | " americano.static __dirname + '/../client/public',", 78 | " maxAge: 86400000", 79 | " ]", 80 | " useAfter: [", 81 | " americano.errorHandler", 82 | " dumpExceptions: true", 83 | " showStack: true", 84 | " ]", 85 | " development: [", 86 | " americano.logger 'dev'", 87 | " ]", 88 | " production: [", 89 | " americano.logger 'short'", 90 | " ]" 91 | ].join(eol); 92 | 93 | var configurationJs = [ 94 | "var americano = require('americano');", 95 | "", 96 | "module.exports = {", 97 | " common: {", 98 | " use: [", 99 | " americano.bodyParser(),", 100 | " americano.methodOverride(),", 101 | " americano.static(__dirname + '/../client/public', {", 102 | " maxAge: 86400000", 103 | " })", 104 | " ],", 105 | " useAfter: [", 106 | " americano.errorHandler({", 107 | " dumpExceptions: true,", 108 | " showStack: true", 109 | " }),", 110 | " ]", 111 | " },", 112 | " development: [", 113 | " americano.logger('dev')", 114 | " ],", 115 | " production: [", 116 | " americano.logger('short')", 117 | " ]", 118 | "};" 119 | ].join(eol); 120 | 121 | 122 | // Generate application 123 | 124 | (function createApplication(path) { 125 | createApplicationAt(path); 126 | })(path); 127 | 128 | /** 129 | * Create application at the given directory `path`. 130 | * 131 | * @param {String} path 132 | */ 133 | 134 | function createApplicationAt(path) { 135 | console.log(); 136 | process.on('exit', function(){ 137 | console.log(); 138 | console.log(' install dependencies:'); 139 | console.log(' $ cd %s && npm install', path); 140 | console.log(); 141 | console.log(' Run your application:'); 142 | console.log(' $ npm start'); 143 | console.log(); 144 | }); 145 | 146 | mkdir(path, function(){ 147 | var extension = '.coffee'; 148 | var scripts = { start: 'coffee server.coffee' }; 149 | if (!program.coffee) { 150 | routes = routesJs; 151 | index = indexJs; 152 | configuration = configurationJs; 153 | app = appJs; 154 | extension = '.js'; 155 | scripts = { start: 'node server.js' } 156 | }; 157 | mkdir(path + '/client/public'); 158 | mkdir(path + '/server/models'); 159 | mkdir(path + '/server/controllers', function() { 160 | write(path + '/server/controllers/routes' + extension, routes); 161 | write(path + '/server/controllers/index' + extension, index); 162 | write(path + '/server/config' + extension, configuration); 163 | }); 164 | 165 | var pkg = { 166 | name: path, 167 | version: '0.0.1', 168 | scripts: scripts, 169 | dependencies: { americano: version } 170 | } 171 | write(path + '/package.json', JSON.stringify(pkg, null, 2)); 172 | write(path + '/server' + extension, app); 173 | write(path + '/README.md', ''); 174 | }); 175 | } 176 | 177 | /** 178 | * Check if the given directory `path` is empty. 179 | * 180 | * @param {String} path 181 | * @param {Function} fn 182 | */ 183 | 184 | function emptyDirectory(path, fn) { 185 | fs.readdir(path, function(err, files){ 186 | if (err && 'ENOENT' != err.code) throw err; 187 | fn(!files || !files.length); 188 | }); 189 | } 190 | 191 | /** 192 | * echo str > path. 193 | * 194 | * @param {String} path 195 | * @param {String} str 196 | */ 197 | 198 | function write(path, str) { 199 | fs.writeFile(path, str); 200 | console.log(' \x1b[36mcreate\x1b[0m: ' + path); 201 | } 202 | 203 | /** 204 | * Mkdir -p. 205 | * 206 | * @param {String} path 207 | * @param {Function} fn 208 | */ 209 | 210 | function mkdir(path, fn) { 211 | mkdirp(path, 0755, function(err){ 212 | if (err) throw err; 213 | console.log(' \033[36mcreate\033[0m: ' + path); 214 | fn && fn(); 215 | }); 216 | } 217 | 218 | /** 219 | * Exit with the given `str`. 220 | * 221 | * @param {String} str 222 | */ 223 | 224 | function abort(str) { 225 | console.error(str); 226 | process.exit(1); 227 | } 228 | -------------------------------------------------------------------------------- /coffeelint.json: -------------------------------------------------------------------------------- 1 | { 2 | "indentation" : { 3 | "value" : 4, 4 | "level" : "error" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /main.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | # Unique file describing all the configuration steps done by americano. 3 | ### 4 | 5 | express = require 'express' 6 | fs = require 'fs' 7 | path = require 'path' 8 | log = require('printit') 9 | date: true 10 | prefix: 'americano' 11 | morgan = require 'morgan' 12 | logFormat = \ # We don't like the default logging. 13 | '[:date] - :method :url - :status - ' + \ 14 | ':response-time ms - :res[content-length]' 15 | morgan.format 'short', logFormat 16 | 17 | 18 | # americano wraps express 19 | module.exports = americano = express 20 | 21 | 22 | # Function to put some express modules by defaults. 23 | _bundleMiddleware = (name, moduleName) -> 24 | Object.defineProperty americano, name, value: require moduleName 25 | 26 | # Re-bundle middlewares. They are not included by default in Express but 27 | # for building rest APIs they are very useful. 28 | _bundleMiddleware 'bodyParser', 'body-parser' 29 | _bundleMiddleware 'methodOverride', 'method-override' 30 | _bundleMiddleware 'errorHandler', 'errorhandler' 31 | _bundleMiddleware 'logger', 'morgan' 32 | 33 | 34 | # Use to collect middlewares to apply after routes are set 35 | afterMiddlewares = [] 36 | 37 | # Default configuration, used if no configuration file is found. 38 | config = 39 | common: 40 | use: [ 41 | americano.bodyParser() 42 | americano.methodOverride() 43 | americano.static __dirname + '/../../client/public', 44 | maxAge: 86400000 45 | ] 46 | useAfter: [ 47 | americano.errorHandler 48 | dumpExceptions: true 49 | showStack: true 50 | ] 51 | development: [ 52 | americano.logger 'dev' 53 | ] 54 | production: [ 55 | americano.logger 'short' 56 | ] 57 | 58 | 59 | # Load configuration file then load configuration for each environment. 60 | americano._configure = (options, app) -> 61 | try 62 | config = require path.join options.root, "server", "config" 63 | catch err 64 | log.error err.stack or err 65 | log.warn "Can't load config file, use default one instead" 66 | 67 | for env, middlewares of config 68 | americano._configureEnv app, env, middlewares 69 | 70 | 71 | # Load express/connect middlewares found in the configuration file. 72 | # If set or engine properties are written they are applied too. 73 | # beforeStart and afterStat method are also set on given application. 74 | americano._configureEnv = (app, env, middlewares) -> 75 | 76 | if env is 'common' or env is app.get 'env' 77 | if middlewares instanceof Array 78 | for middleware in middlewares 79 | middleware = [middleware] unless middleware instanceof Array 80 | app.use.apply app, middleware 81 | else 82 | for method, elements of middlewares 83 | if method in ['beforeStart', 'afterStart'] 84 | app[method] = elements 85 | else if method is 'use' 86 | app[method] element for element in elements 87 | else if method is 'useAfter' 88 | afterMiddlewares.push element for element in elements 89 | else 90 | for key, value of elements 91 | app[method].apply app, [key, value] 92 | app.get key 93 | 94 | 95 | # Apply middlewares to apply after routes are set. 96 | americano._configureAfter = (app) -> 97 | app.use middleware for middleware in afterMiddlewares 98 | afterMiddlewares = [] 99 | 100 | 101 | # Load all routes found in the routes file. 102 | americano._loadRoutes = (options, app) -> 103 | try 104 | rPath = path.join options.root, "server", "controllers", "routes" 105 | routes = require rPath 106 | catch err 107 | log.error err.stack or err 108 | log.warn "Route configuration file is missing, make " + \ 109 | "sure routes.(coffee|js) is located at the root of" + \ 110 | " the controllers folder." 111 | log.warn "No routes loaded" 112 | for reqpath, controllers of routes 113 | for verb, controller of controllers 114 | americano._loadRoute app, reqpath, verb, controller 115 | 116 | log.info "Routes loaded." unless options.silent 117 | 118 | 119 | # Load given route in the Express app. 120 | americano._loadRoute = (app, reqpath, verb, controller) -> 121 | try 122 | if verb is "param" 123 | app.param reqpath, controller 124 | else 125 | if controller instanceof Array 126 | app[verb].apply app, ["/#{reqpath}"].concat controller 127 | else 128 | app[verb] "/#{reqpath}", controller 129 | catch err 130 | log.error "Can't load controller for route #{verb}: #{reqpath}" 131 | log.raw err.stack or err 132 | process.exit 1 133 | 134 | 135 | # Load given plugin by requiring it and running it as a function. 136 | americano._loadPlugin = (options, app, plugin, callback) -> 137 | log.info "add plugin: #{plugin}" unless options.silent 138 | 139 | # Enable absolute path for plugins 140 | if plugin.indexOf('/') is -1 141 | # if the plugin's path isn't absolute, we let node looking for it. 142 | pluginPath = plugin 143 | else 144 | # otherwise it looks for the plugin from the root folder. 145 | pluginPath = path.join options.root, plugin 146 | 147 | try 148 | plugin = require pluginPath 149 | # merge plugin's properties into the americano instance. 150 | americano extends plugin 151 | 152 | # run the plugin initializer. 153 | americano.configure options, app, callback 154 | catch err 155 | callback err 156 | 157 | 158 | # Load plugins one by one then call given callback. 159 | americano._loadPlugins = (options, app, callback) -> 160 | pluginList = config.plugins 161 | 162 | _loadPluginList = (list) -> 163 | if list.length > 0 164 | plugin = list.pop() 165 | 166 | americano._loadPlugin options, app, plugin, (err) -> 167 | if err 168 | log.error "#{plugin} failed to load." 169 | log.raw err 170 | else 171 | log.info "#{plugin} loaded." unless options.silent 172 | _loadPluginList list 173 | else 174 | callback() 175 | 176 | if pluginList?.length > 0 177 | _loadPluginList pluginList 178 | else 179 | callback() 180 | 181 | 182 | # Listen for http (or https) connections 183 | americano._listen = (app, options, callback) -> 184 | if options.tls 185 | server = require('https').createServer options.tls, app 186 | server.listen options.port, options.host, (err) -> 187 | callback err, server 188 | else if options.socket 189 | server = app.listen options.socket, (err) -> 190 | callback err, server 191 | else 192 | server = app.listen options.port, options.host, (err) -> 193 | callback err, server 194 | 195 | 196 | # Set the express application: configure the app, load routes and plugins. 197 | americano._new = (options, callback) -> 198 | app = americano() 199 | americano._configure options, app 200 | americano._loadPlugins options, app, (err) -> 201 | return callback err if err 202 | 203 | americano._loadRoutes options, app 204 | americano._configureAfter app 205 | callback null, app 206 | 207 | 208 | # Clean options, configure the application then starts the server. 209 | americano.start = (options, callback) -> 210 | process.env.NODE_ENV = 'development' unless process.env.NODE_ENV? 211 | options.port ?= 3000 212 | options.name ?= "Americano" 213 | options.host ?= "127.0.0.1" 214 | options.root ?= process.cwd() 215 | options.tls ?= false 216 | options.socket ?= false 217 | 218 | americano._new options, (err, app) -> 219 | return callback? err if err 220 | 221 | app.beforeStart ?= (cb) -> cb() 222 | app.beforeStart (err) -> 223 | return callback? err if err 224 | 225 | americano._listen app, options, (err, server) -> 226 | return callback? err if err 227 | 228 | app.afterStart? app, server 229 | unless options.silent 230 | log.info """ 231 | Configuration for #{process.env.NODE_ENV} loaded. 232 | #{options.name} server is listening on port #{options.port}... 233 | """ 234 | 235 | callback? null, app, server 236 | 237 | 238 | # Clean options, configure the application then returns app via a callback. 239 | # Useful to generate the express app for given module based on americano. 240 | # In that gase 241 | americano.newApp = (options, callback) -> 242 | americano._new options, (err, app) -> 243 | return callback? err if err 244 | 245 | unless options.silent 246 | log.info "Configuration for #{process.env.NODE_ENV} loaded." 247 | 248 | callback? null, app 249 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "americano", 3 | "description": "Wrapper for Express that makes its configuration clean and easy.", 4 | "version": "0.4.5", 5 | "author": "Cozy Cloud (https://cozy.io)", 6 | "license": "MIT", 7 | "keywords": [ 8 | "framework", 9 | "web", 10 | "api", 11 | "rest", 12 | "express" 13 | ], 14 | "bin": { 15 | "americano": "./bin/americano" 16 | }, 17 | "bugs": { 18 | "url": "https://github.com/cozy/americano/issues" 19 | }, 20 | "contributors": [ 21 | "Frank Rousseau", 22 | "Joseph Silvestre", 23 | "Romain Foucault", 24 | "Benjamin Bouvier" 25 | ], 26 | "engines": { 27 | "node": "*" 28 | }, 29 | "main": "./main.js", 30 | "dependencies": { 31 | "body-parser": "1.17.1", 32 | "commander": "2.9.0", 33 | "errorhandler": "1.5.0", 34 | "express": "4.15.2", 35 | "method-override": "2.3.7", 36 | "mkdirp": "0.5.1", 37 | "morgan": "1.8.1", 38 | "printit": "0.1.20" 39 | }, 40 | "devDependencies": { 41 | "chai": "*", 42 | "coffee-script": "1.9.0", 43 | "coffeelint": "1.8.1", 44 | "mocha": "*", 45 | "request-json": "0.4.10" 46 | }, 47 | "scripts": { 48 | "prepublish": "coffee -cb main.coffee", 49 | "lint": "./node_modules/coffeelint/bin/coffeelint -f coffeelint.json main.coffee", 50 | "test": "cd tests/ && NODE_ENV=test mocha tests.coffee --reporter spec --compilers coffee:coffee-script/register --colors", 51 | "build": "./node_modules/coffee-script/bin/coffee -cb main.coffee" 52 | }, 53 | "repository": { 54 | "type": "git", 55 | "url": "git://github.com/cozy/americano.git" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/fixtures/installed-plugin-test/main.coffee: -------------------------------------------------------------------------------- 1 | plugin = {} 2 | 3 | plugin.configure = (options, app, callback) -> 4 | app.data = options.data 5 | callback() if callback? 6 | 7 | plugin.getModel = -> 8 | return 42 9 | 10 | module.exports = plugin 11 | -------------------------------------------------------------------------------- /tests/fixtures/installed-plugin-test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "installed-plugin-test", 3 | "version": "0.0.1", 4 | "description": "A test plugin", 5 | "engines": { 6 | "node": "*" 7 | }, 8 | "main": "./main.coffee", 9 | "dependencies": {} 10 | } 11 | -------------------------------------------------------------------------------- /tests/node_modules/installed-plugin-test/main.coffee: -------------------------------------------------------------------------------- 1 | plugin = {} 2 | 3 | plugin.configure = (options, app, callback) -> 4 | app.data = options.data 5 | callback() if callback? 6 | 7 | plugin.getModel = -> 8 | return 42 9 | 10 | module.exports = plugin 11 | -------------------------------------------------------------------------------- /tests/node_modules/installed-plugin-test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "installed-plugin-test", 3 | "version": "0.0.1", 4 | "description": "A test plugin", 5 | "engines": { 6 | "node": "*" 7 | }, 8 | "main": "./main.coffee", 9 | "dependencies": {} 10 | } 11 | -------------------------------------------------------------------------------- /tests/node_modules/myplugins/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "myplugins", 3 | "version": "0.1.4", 4 | "description": "Americano helpers to build Cozy applications faster", 5 | "main": "./main.js", 6 | "dependencies": { 7 | }, 8 | "devDependencies": { 9 | }, 10 | "optionalDependencies": {}, 11 | "scripts": { 12 | "prepublish": "cake build", 13 | "test": "cake tests" 14 | }, 15 | "keywords": [ 16 | "plugin", 17 | "cozy", 18 | "americano" 19 | ], 20 | "licenses": [ 21 | { 22 | "type": "MIT", 23 | "url": "https://github.com/mycozycloud/americano-cozy/blob/master/LICENSE" 24 | } 25 | ], 26 | "bugs": { 27 | "url": "https://github.com/mycozycloud/americano-cozy/issues" 28 | }, 29 | "author": "Frank R", 30 | "contributors": [], 31 | "engines": { 32 | "node": "*" 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "https://github.com/mycozycloud/americano-cozy.git" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/server/config.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | common: 3 | useAfter: [ 4 | (err, req, res, next) -> 5 | res.status(500).send message: err.message 6 | ] 7 | beforeStart: (callback) -> 8 | @set 'before', 'good' 9 | callback() 10 | afterStart: (app, server) -> 11 | @set 'after', 'still good' 12 | -------------------------------------------------------------------------------- /tests/server/controllers/routes.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | # routes must not be empty, otherwise Express crashes 3 | # see https://github.com/visionmedia/express/issues/2159 4 | 'fakeroute': get: (req, res) -> 5 | 6 | 'test-error': 7 | post: (req, res, next) -> 8 | next new Error 'test_error' 9 | -------------------------------------------------------------------------------- /tests/tests.coffee: -------------------------------------------------------------------------------- 1 | expect = require('chai').expect 2 | request = require 'request-json' 3 | exec = require('child_process').exec 4 | 5 | americano = require '../main' 6 | 7 | 8 | if process.env.NODE_ENV isnt 'test' 9 | console.log "Tests should be run with NODE_ENV=test" 10 | process.exit 1 11 | 12 | # Configuration 13 | describe '_configureEnv', -> 14 | it 'should add given middlewares to given app and environment', (done) -> 15 | middlewares = [americano.bodyParser()] 16 | americano.start root: __dirname, (err, app, server) -> 17 | client = request.newClient 'http://localhost:3000/' 18 | 19 | americano._configureEnv app, 'development', middlewares 20 | app.post '/test-1/', (req, res) -> 21 | expect(req.body).to.be.undefined 22 | res.status(200).send ok: true 23 | client.post 'test-1/', name: 'name_test', (err, res, body) -> 24 | expect(err).to.be.null 25 | 26 | americano._configureEnv app, 'test', middlewares 27 | app.post '/test-2/', (req, res) -> 28 | expect(req.body.name).to.be.equal 'name_test' 29 | res.sendStatus 200 30 | data = name: 'name_test' 31 | client.post 'test-2/', data, (err, res, body) -> 32 | expect(err).to.be.null 33 | server.close() 34 | done() 35 | , false 36 | 37 | 38 | describe '_configureEnv with object', -> 39 | it 'should apply given configuration to Express app', (done) -> 40 | client = request.newClient 'http://localhost:3000/' 41 | middlewares = 42 | use: [americano.bodyParser()] 43 | set: 44 | mydata: 'ok' 45 | 46 | americano.start root: __dirname, (err, app, server) -> 47 | americano._configureEnv app, 'common', middlewares 48 | expect(app.get 'mydata').to.equal 'ok' 49 | app.post '/test-1/', (req, res) -> 50 | expect(req.body.name).to.be.equal 'name_test' 51 | res.status(200).send ok: true 52 | 53 | client.post 'test-1/', name: 'name_test', (err, res, body) -> 54 | expect(err).to.be.null 55 | expect(body.ok).to.be.true 56 | server.close() 57 | done() 58 | 59 | 60 | describe '_configureEnv with beforeStart and afterStart', -> 61 | it 'should run given methods before and after application starts', (done) -> 62 | americano.start root: __dirname, (err, app, server) -> 63 | expect(app.get 'before').to.be.equal 'good' 64 | expect(app.get 'after').to.be.equal 'still good' 65 | server.close() 66 | done() 67 | 68 | 69 | describe '_configureEnv with useAfter', -> 70 | 71 | before (done) => 72 | # Everythin is done via config files 73 | americano.start root: __dirname, (err, app, server) => 74 | @server = server 75 | @app = app 76 | done() 77 | 78 | after => 79 | @server.close() 80 | 81 | it 'should run error middleware set after routes', (done) => 82 | client = request.newClient 'http://localhost:3000/' 83 | client.post 'test-error/', name: 'name_test', (err, res, body) -> 84 | expect(body.message).to.be.equal 'test_error' 85 | done() 86 | 87 | 88 | # Routes 89 | describe '_loadRoute', -> 90 | it 'should add route to given app', (done) -> 91 | americano.start root: __dirname, (err, app, server) -> 92 | client = request.newClient 'http://localhost:3000/' 93 | client.get 'test/', (err, res, body) -> 94 | expect(err).not.to.be.null 95 | msg = 'test ok' 96 | americano._loadRoute app, 'test/', 'get', (req, res) -> 97 | res.send msg: msg 98 | client.get 'test/', (err, res, body) -> 99 | expect(body.msg).to.equal msg 100 | server.close() 101 | done() 102 | 103 | it 'should add several controllers for given route to app', (done) -> 104 | americano.start root: __dirname, (err, app, server) -> 105 | client = request.newClient 'http://localhost:3000/' 106 | 107 | client.get 'test/', (err, res, body) -> 108 | expect(err).not.to.be.null 109 | 110 | msg = 'test ok' 111 | msg2 = 'test array ok' 112 | americano._loadRoute app, 'test/', 'get', [ 113 | (req, res, next) -> 114 | req.mytest = msg2 115 | next() 116 | (req, res) -> res.send msg: msg, msg2: req.mytest 117 | ] 118 | client.get 'test/', (err, res, body) -> 119 | expect(err).to.be.null 120 | expect(body.msg).to.equal msg 121 | expect(body.msg2).to.equal msg2 122 | server.close() 123 | done() 124 | 125 | it 'should support param routes', (done) -> 126 | americano.start root: __dirname, (err, app, server) -> 127 | client = request.newClient 'http://localhost:3000/' 128 | 129 | americano._loadRoute app, 'testid', 'param', (req, res, next, id) -> 130 | req.doubledid = id * 2 131 | next() 132 | 133 | americano._loadRoute app, 'test/:testid', 'get', (req, res) -> 134 | res.send doubled: req.doubledid 135 | 136 | client.get 'test/12', (err, res, body) -> 137 | expect(body.doubled).to.equal 24 138 | server.close() 139 | done() 140 | 141 | # Plugins 142 | describe '_loadPlugin', -> 143 | before (done) -> 144 | command = "cp -r ./fixtures/installed-plugin-test ../node_modules" 145 | exec command, done 146 | 147 | it 'should add plugin to given app when path is absolute', (done) -> 148 | americano.start root: __dirname, (err, app, server) -> 149 | pluginPath = 'node_modules/installed-plugin-test/main' 150 | options = root: __dirname, data: 'test' 151 | americano._loadPlugin options, app, pluginPath, (err) -> 152 | expect(err).not.to.exist 153 | expect(app.data).to.equal options.data 154 | expect(americano.getModel()).to.equal 42 155 | server.close done 156 | 157 | it 'should add plugin to given app when path is relative', (done) -> 158 | americano.start root: __dirname, (err, app, server) -> 159 | americano._loadPlugin root: __dirname, app, 'installed-plugin-test', (err) -> 160 | expect(err).not.to.exist 161 | expect(americano.getModel()).to.equal 42 162 | server.close done 163 | 164 | # Create new server 165 | --------------------------------------------------------------------------------