├── .gitattributes ├── .travis.yml ├── .gitignore ├── .jshintrc ├── gulpfile.js ├── merger.js ├── CONTRIBUTING.md ├── license ├── package.json ├── CHANGELOG.md ├── SAMPLES.md ├── readme.md ├── test ├── pipeHelper.js ├── optionsSpec.js └── pipeSpec.js └── index.js /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | db.json 3 | *.json 4 | package-lock.json 5 | !package.json 6 | !sample/* 7 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "curly": true, 6 | "immed": true, 7 | "newcap": true, 8 | "noarg": true, 9 | "undef": true, 10 | "unused": "vars", 11 | "strict": true, 12 | "mocha": true 13 | } -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | var jshint = require('gulp-jshint'); 5 | var gulpMocha = require('gulp-mocha'); 6 | 7 | gulp.task('jshint', function(){ 8 | gulp.src(['*.js', 'test/*.js']) 9 | .pipe(jshint('.jshintrc')) 10 | .pipe(jshint.reporter('jshint-stylish')); 11 | }); 12 | 13 | gulp.task('test', function(){ 14 | return gulp.src('test/*.js', {read: false}) 15 | .pipe(gulpMocha({reporter: 'spec'})); 16 | }); 17 | 18 | gulp.task('default', ['jshint','test']); 19 | -------------------------------------------------------------------------------- /merger.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _ = require("lodash"); 4 | 5 | var Merger = function(options){ 6 | var customizer = function(objValue, srcValue) { 7 | if (_.isArray(objValue)) { 8 | var byIdComparison = objValue.length > 0 && objValue[0].hasOwnProperty(options.id); 9 | return byIdComparison ? _.uniqBy(srcValue.concat(objValue), function(x){ return x[options.id]; }) : _.uniq(srcValue.concat(objValue)); 10 | } 11 | }; 12 | 13 | this.merge = function(source, target){ 14 | _.mergeWith(source, target || {}, customizer); 15 | return source; 16 | }; 17 | }; 18 | 19 | module.exports = Merger; -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Notes for contributors 2 | 3 | Feel free to modify the code of gulp-json-srv and make a PR to improve it. 4 | 5 | Here are some suggestions and rules that you should follow when doing a PR 6 | * Do PR against develop branch. It's more convenient to me since i'm doing all work here and then merging/publishing new version. 7 | * Ensure that all checks have passed successfully (you may see status of Travis build on your PR page). 8 | * If you can, you should write tests for your new functinality. If you can't, you should try this at least:) Tests allows us to ensure all working in right way. 9 | * to be continued... 10 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Nikita Ivanov 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gulp-json-srv", 3 | "version": "1.2.0", 4 | "description": "Wrapper for json-server for easy launching with Gulp", 5 | "license": "MIT", 6 | "repository": "https://github.com/GrafGenerator/gulp-json-server.git", 7 | "author": { 8 | "name": "Nikita Ivanov", 9 | "email": "grafgenerator@gmail.com" 10 | }, 11 | "contributors": [ 12 | "Raul Dinegri ", 13 | "borowskt ", 14 | "mijamo ", 15 | "Nathan Mauro ", 16 | "Nicolas Husson " 17 | ], 18 | "engines": { 19 | "node": ">=0.10.0" 20 | }, 21 | "scripts": { 22 | "jshint": "gulp jshint", 23 | "test": "./node_modules/.bin/mocha --compilers js:babel-core/register test/*Spec.js" 24 | }, 25 | "files": [ 26 | "index.js", 27 | "merger.js", 28 | "test" 29 | ], 30 | "keywords": [ 31 | "gulpplugin", 32 | "json-server", 33 | "json", 34 | "fake", 35 | "prototyping", 36 | "test", 37 | "testing", 38 | "mock", 39 | "mocking", 40 | "REST", 41 | "API", 42 | "server" 43 | ], 44 | "dependencies": { 45 | "body-parser": "^1.15.2", 46 | "chalk": "^2.3.0", 47 | "fs": "0.0.2", 48 | "json-server": "^0.12.1", 49 | "lodash": "^4.15.0", 50 | "loglevel": "^1.4.1", 51 | "plugin-error": "^1.0.0", 52 | "server-destroy": "^1.0.1", 53 | "through2": "^2.0.1" 54 | }, 55 | "devDependencies": { 56 | "babel-core": "^6.25.0", 57 | "gulp": "^3.9.0", 58 | "gulp-jshint": "^2.1.0", 59 | "gulp-mocha": "^5.0.0", 60 | "jshint": "^2.9.5", 61 | "jshint-stylish": "^2.0.1", 62 | "mocha": "^5.0.0", 63 | "supertest": "^3.0.0" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Release notes 2 | 3 | ### v1.1.0 - rework logging 4 | * Added 'verbosity' option to control overall log level and json-server's messages about accessed routes. 5 | * Removed 'debug' option. 6 | 7 | ### v1.0.1 - breaking changes 8 | * Fixing issue with missing files in the npm package. 9 | 10 | ### v1.0.0 - breaking changes 11 | * Whole plugin redesigned to meet the requirements of gulp plugin guidelines. 12 | * Added ability to pipe files to the plugin. 13 | * 'data' and 'deferredStart' removed. 14 | * 'debug', 'cumulative' and 'cumulativeSession' options added. 15 | * Plugin do not take objects as input anymore, start() and reload() methods removed, the only way to pass data to plugin is via pipeline. 16 | 17 | ### v0.2.0 18 | * Added ability to add rewrite rules to static files. 19 | * Added ability to use custom routes (reflect json-server changes) 20 | * Use body-parser to parse req.body (allows to use req.body f.e. with custom routes). 21 | 22 | ### v0.1.1 23 | * Forced hotfix because previous not properly published to NPM because of bug in `npm pack`. 24 | 25 | ### v0.1.0 26 | * Added `static` option to serve static files using json-server. 27 | * Starting to use semantic versioning. 28 | 29 | ### v0.0.7 30 | * Fixed typo in server reloading sample and updated sample itself. 31 | 32 | ### v0.0.6 33 | * Added reloading functionality. Now DB could be easily reloaded either from file or from object. 34 | * Added ability to kill the server. 35 | * Added deferredStart option, allowing to define server instance, but start it later. 36 | 37 | ### v0.0.5 38 | * The `id` key, used to match objects in collections now could be changed using `id` parameter in options. Useful to simulate other DBs, for example MongoDB's `_id`. 39 | 40 | ### v0.0.4 41 | * Added ability to change server's base URL. 42 | * Added ability to use rewrite rules. 43 | 44 | ### v0.0.0 - v0.0.3 45 | Basic version of plugin with ability to start json-server from specified file or object, on specific port. 46 | 47 | 48 | ## License 49 | 50 | MIT © 2016 Nikita Ivanov 51 | -------------------------------------------------------------------------------- /SAMPLES.md: -------------------------------------------------------------------------------- 1 | ## Usage 2 | ### Server with default options 3 | ```js 4 | var gulp = require("gulp"); 5 | var jsonServer = require("gulp-json-srv"); 6 | 7 | var server = jsonServer.create(); 8 | 9 | gulp.task("start", function(){ 10 | return gulp.src("data.json") 11 | .pipe(server.pipe()); 12 | }); 13 | ``` 14 | 15 | ### Server with custom options 16 | ```js 17 | var gulp = require("gulp"); 18 | var jsonServer = require("gulp-json-srv"); 19 | 20 | var server = jsonServer.create({ 21 | port: 25000, 22 | id: '_id', 23 | baseUrl: '/api', 24 | rewriteRules: { 25 | '/': '/api/', 26 | '/blog/:resource/:id/show': '/api/:resource/:id' 27 | }, 28 | customRoutes: { 29 | '/big_post': { 30 | method: 'get', 31 | handler: function(req, res) { 32 | return res.json({id: 1, title: 'Big post'}); 33 | } 34 | } 35 | }, 36 | static: './static', 37 | cumulative: true, 38 | cumulativeSession: false, 39 | verbosity: { 40 | level: "debug", 41 | urlTracing: false 42 | } 43 | }); 44 | 45 | gulp.task("start", function(){ 46 | return gulp.src("data.json") 47 | .pipe(server.pipe()); 48 | }); 49 | ``` 50 | 51 | ### Watching files and reloading server 52 | ```js 53 | var gulp = require("gulp"); 54 | var jsonServer = require("gulp-json-srv"); 55 | 56 | var server = jsonServer.create(); 57 | 58 | gulp.task("db", function(){ 59 | return gulp.src("data.json") 60 | .pipe(server.pipe()); 61 | }); 62 | 63 | gulp.task("watch", function () { 64 | gulp.watch(["data.json"], ["db"]); 65 | }); 66 | 67 | gulp.task("default", ["db", "watch"]); 68 | ``` 69 | 70 | ### Start server from object 71 | This functionality now available by using [gulp-file](https://github.com/alexmingoia/gulp-file) plugin. 72 | 73 | ```js 74 | var gulp = require("gulp"); 75 | var file = require("gulp-file"); 76 | var jsonServer = require("gulp-json-srv"); 77 | 78 | var server = jsonServer.create(); 79 | 80 | var db = { 81 | posts: [ 82 | {id: 1, title: "title 1"} 83 | ] 84 | }; 85 | 86 | gulp.task("start", function () { 87 | return file("this-is-actually-in-memory.js", JSON.stringify(db), { src: true }) 88 | .pipe(server.pipe()); 89 | }); 90 | ``` 91 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # gulp-json-server [![Build Status](https://travis-ci.org/GrafGenerator/gulp-json-server.svg?branch=master)](https://travis-ci.org/GrafGenerator/gulp-json-server) [![npm version](https://badge.fury.io/js/gulp-json-srv.svg)](https://badge.fury.io/js/gulp-json-srv) 2 | 3 | Wrapper for [json-server](https://github.com/typicode/json-server). 4 | 5 | **Important!** API versions >1.0.0 are incompatible with ones <1.0.0, see this page for new API, and [examples of usage](SAMPLES.md). 6 | 7 | ## Install 8 | 9 | ``` 10 | $ npm install --save-dev gulp-json-srv 11 | ``` 12 | 13 | 14 | ## Usage 15 | ```js 16 | var gulp = require("gulp"); 17 | var jsonServer = require("gulp-json-srv"); 18 | 19 | var server = jsonServer.create(); 20 | 21 | gulp.task("start", function(){ 22 | return gulp.src("data.json") 23 | .pipe(server.pipe()); 24 | }); 25 | ``` 26 | 27 | See [samples](SAMPLES.md) for more information about usage of plugin. 28 | 29 | 30 | ## API 31 | 32 | ### Options 33 | 34 | | Options | Default value | Description | 35 | |:---|:---|:---| 36 | |`baseUrl`|`null`|The base URL for server API.| 37 | |`bodyParserJson`|`null`|Allows override of default `bodyParser.json()` method so it is possible to pass in custom options. e.g. `bodyParser.json({limit: '10mb', expanded: false})`. 38 | |`cumulative`|`false`|Controls when to merge files from different `pipe()` calls (i.e. two pipelines execution.)| 39 | |`cumulativeSession`|`true`|Controls when to merge files in one `pipe()` call (i.e. one pipeline execution.). If not, then only last file passed to plugin will form the DB state.| 40 | |`customRoutes`|`null`|A key-value pairs of custom routes that should be applied to server. Each value should be the object with `method` and `handler` properties, describing HTTP method and handler of custom route respectively.| 41 | |`id`|`"id"`|Identity property name of objects. Changing this allows to imitate MongoDB's `_id` f.e.| 42 | |`port`|`3000`|Port number on which json-server will listen.| 43 | |`rewriteRules`|`null`|A key-value pairs of rewrite rules that should be applied to server.| 44 | |`static`|`null`|If specified and not null, sets the static files folder and lets json-server serve static files from that folder.| 45 | |`verbosity`|`{`
    `level:"error",`
    `urlTracing:true`
`}`|Should be either `string` or `object`. In case value is object, specifies log `level` (see [loglevel](https://www.npmjs.com/package/loglevel) for details), and `urlTracing` flag, which allows to control messages about accessed routes. In case value is string, specifies only log level, and urlTracing is considered to be `true`.| 46 | 47 | **Important:** Note that `cumulative` and `cumulativeSession` options could be specified in `options` object, passed to `pipe()` method and they will override one set at server level. 48 | 49 | ### Methods 50 | | Method | Description | 51 | |---|---| 52 | |`kill(callback)`|Immediately stops the server and closes all opened connections. If `callback` is provided, it will be called once server stopped.| 53 | |`pipe(options)`|Provides stream trasformation for gulp pipeline. Passing `'options'` to this method allows to override options, set at server level (currently `cumulative` and `cumulativeSession` are overridable). | 54 | 55 | ## Links 56 | 57 | * [SAMPLES.md](SAMPLES.md) - more examples of usage 58 | * [CHANGELOG.md](CHANGELOG.md) 59 | * [CONTRIBUTING.md](CONTRIBUTING.md) - info for contributors 60 | 61 | ## License 62 | 63 | MIT © 2016 Nikita Ivanov 64 | -------------------------------------------------------------------------------- /test/pipeHelper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var jsonServer = require('../.'); 4 | var request = require('supertest'); 5 | 6 | var pipeHelper = function(url, done, options){ 7 | this.url = url; 8 | this.done = done; 9 | this.options = options || {}; 10 | 11 | var ActionName = { 12 | Request: "request", 13 | PipeContent: "pipeContent" 14 | }; 15 | 16 | this.actions = []; 17 | this.lastActionIndex = -1; 18 | 19 | var addAction = function(actionName, fn){ 20 | this.actions.push({ 21 | name: actionName, 22 | info: fn 23 | }); 24 | }.bind(this); 25 | 26 | this.request = function(fn){ 27 | addAction(ActionName.Request, fn); 28 | this.lastActionIndex = this.actions.length - 1; 29 | return this; 30 | }; 31 | 32 | this.pipeContent = function(content, options){ 33 | var contents = []; 34 | 35 | if(content instanceof Array){ 36 | content.forEach(function(c) { 37 | contents.push(c); 38 | }, this); 39 | } 40 | else{ 41 | contents.push(content); 42 | } 43 | 44 | addAction(ActionName.PipeContent, { 45 | contents: contents, 46 | options: options 47 | }); 48 | 49 | return this; 50 | }; 51 | 52 | var chainedRun = function(server, url, actions, currentIndex, done){ 53 | var action = actions[currentIndex]; 54 | var isLastAction = currentIndex === this.lastActionIndex; 55 | 56 | var r = request(url || server.instance); 57 | 58 | switch(action.name){ 59 | case ActionName.PipeContent: 60 | var pipe = server.pipe(action.info.options || {}); 61 | var contents = action.info.contents; 62 | 63 | contents.forEach(function(c) { 64 | pipe.write({ 65 | isNull: function(){return false;}, 66 | isStream: function(){return false;}, 67 | contents: JSON.stringify(c) 68 | }); 69 | }, this); 70 | 71 | chainedRun(server, url, actions, currentIndex + 1, done); 72 | 73 | break; 74 | 75 | case ActionName.Request: 76 | var r2 = action.info(r); 77 | 78 | r2.end(function(err, res){ 79 | if(err) { 80 | server.kill(function(){ 81 | return done(err); 82 | }); 83 | return; 84 | } 85 | 86 | if(isLastAction){ 87 | server.kill(function(){ done(); }); 88 | } 89 | else { 90 | chainedRun(server, url, actions, currentIndex + 1, done); 91 | } 92 | }); 93 | break; 94 | 95 | default: 96 | throw "Unknown test action type." 97 | } 98 | 99 | 100 | }.bind(this); 101 | 102 | this.go = function(){ 103 | var server = jsonServer.create(this.options); 104 | 105 | if(this.actions.length === 0){ 106 | throw "No actions specified in test." 107 | } 108 | 109 | if(this.lastActionIndex === -1){ 110 | throw "No request actions specified in test." 111 | } 112 | 113 | chainedRun(server, this.url, this.actions, 0, this.done); 114 | }; 115 | }; 116 | 117 | module.exports = pipeHelper; -------------------------------------------------------------------------------- /test/optionsSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var chalk = require('chalk'); 5 | var PipeHelper = require('./pipeHelper'); 6 | 7 | 8 | /* ===== sample data ===== */ 9 | var post1 = { id: 1, title: "json-server", author: "typicode" }; 10 | var post2 = { id: 2, title: "gulp-json-srv", author: "grafgenerator" }; 11 | var post3 = { id: 3, title: "gulp-json-srv@1.0.0", author: "grafgenerator" }; 12 | var mongopost = { _id: 1, title: "json-server", author: "typicode" }; 13 | 14 | var db = { 15 | posts: [ 16 | post1, 17 | post2, 18 | post3 19 | ] 20 | }; 21 | 22 | var mongodb = { 23 | posts: [ 24 | mongopost 25 | ] 26 | }; 27 | 28 | var makeCopy = function(input){ 29 | return JSON.parse(JSON.stringify(input)); 30 | }; 31 | 32 | var makeOptions = function(options){ 33 | return _.extend({debug: true}, options); 34 | }; 35 | 36 | 37 | 38 | 39 | 40 | 41 | describe('#start()', function(){ 42 | beforeEach(function(){ 43 | console.log(); 44 | console.log(); 45 | console.log(); 46 | console.log(); 47 | console.log(chalk.blue("=======================================================")); 48 | }); 49 | 50 | 51 | 52 | it('should start server on port 3000 by default', function(done){ 53 | var helper = new PipeHelper('http://localhost:3000', done, makeOptions()); 54 | 55 | helper 56 | .pipeContent(makeCopy(db)) 57 | .request(function(req){ 58 | return req 59 | .get('/posts/1') 60 | .expect(200, makeCopy(post1)); 61 | }); 62 | 63 | helper.go(); 64 | }); 65 | 66 | it('should start server on specific port', function(done){ 67 | var helper = new PipeHelper('http://localhost:3001', done, makeOptions({ 68 | port: 3001 69 | })); 70 | 71 | helper 72 | .pipeContent(makeCopy(db)) 73 | .request(function(req){ 74 | return req 75 | .get('/posts/1') 76 | .expect(200, makeCopy(post1)); 77 | }); 78 | 79 | helper.go(); 80 | }); 81 | 82 | it('should be able to change base API URL', function(done){ 83 | var helper = new PipeHelper('http://localhost:3000', done, makeOptions({ 84 | baseUrl: '/api' 85 | })); 86 | 87 | helper 88 | .pipeContent(makeCopy(db)) 89 | .request(function(req){ 90 | return req 91 | .get('/api/posts/1') 92 | .expect(200, makeCopy(post1)); 93 | }); 94 | 95 | helper.go(); 96 | }); 97 | 98 | it('should not be available on root after changing base API URL', function(done){ 99 | var helper = new PipeHelper('http://localhost:3000', done, makeOptions({ 100 | baseUrl: '/api' 101 | })); 102 | 103 | helper 104 | .pipeContent(makeCopy(db)) 105 | .request(function(req){ 106 | return req 107 | .get('/posts/1') 108 | .expect(404); 109 | }); 110 | 111 | helper.go(); 112 | }); 113 | 114 | it('should be able to use rewrite rules', function(done){ 115 | var helper = new PipeHelper('http://localhost:3000', done, makeOptions({ 116 | rewriteRules: { 117 | '/api/': '/', 118 | '/blog/:resource/:id/show': '/:resource/:id' 119 | } 120 | })); 121 | 122 | helper 123 | .pipeContent(makeCopy(db)) 124 | .request(function(req){ 125 | return req 126 | .get('/blog/posts/1/show') 127 | .expect(200, makeCopy(post1)); 128 | }); 129 | 130 | helper.go(); 131 | }); 132 | 133 | it('should get a post with mongodb\'s _id' , function(done){ 134 | var helper = new PipeHelper('http://localhost:3000', done, makeOptions({ 135 | id: '_id' 136 | })); 137 | 138 | helper 139 | .pipeContent(makeCopy(mongodb)) 140 | .request(function(req){ 141 | return req 142 | .get('/posts/1') 143 | .expect(200, makeCopy(mongopost)); 144 | }); 145 | 146 | helper.go(); 147 | }); 148 | 149 | it('should return 404 for mongopost if default id option' , function(done){ 150 | var helper = new PipeHelper('http://localhost:3000', done, makeOptions()); 151 | 152 | helper 153 | .pipeContent(makeCopy(mongodb)) 154 | .request(function(req){ 155 | return req 156 | .get('/posts/1') 157 | .expect(404); 158 | }); 159 | 160 | helper.go(); 161 | }); 162 | 163 | it('should be able to use custom routes', function(done){ 164 | var helper = new PipeHelper('http://localhost:3000', done, makeOptions({ 165 | customRoutes: { 166 | '/big_post': { 167 | method: 'get', 168 | handler: function(req, res) { 169 | return res.json({id: 1, title: 'Big post'}); 170 | } 171 | } 172 | } 173 | })); 174 | 175 | helper 176 | .pipeContent(makeCopy(db)) 177 | .request(function(req){ 178 | return req 179 | .get('/big_post') 180 | .expect(200, {id: 1, title: 'Big post'}); 181 | }); 182 | 183 | helper.go(); 184 | }); 185 | }); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var jsonServer = require('json-server'); 5 | var Merger = require('./merger'); 6 | var through = require('through2'); 7 | var PluginError = require('plugin-error'); 8 | var log = require('loglevel'); 9 | var chalk = require('chalk'); 10 | var bodyParser = require('body-parser'); 11 | var enableDestroy = require('server-destroy'); 12 | 13 | var GulpJsonServer = function(options){ 14 | this.server = null; 15 | this.instance = null; 16 | this.router = null; 17 | this.serverStarted = false; 18 | 19 | this.options = { 20 | port: 3000, 21 | rewriteRules: null, 22 | customRoutes: null, 23 | baseUrl: null, 24 | id: 'id', 25 | static: null, 26 | cumulative: false, 27 | cumulativeSession: true, 28 | verbosity: { 29 | level: "error", 30 | urlTracing: true 31 | }, 32 | bodyParserJson: null 33 | }; 34 | 35 | var self = this; 36 | 37 | var prepareOptions = function(inputOptions){ 38 | _.merge(this.options, inputOptions || {}); 39 | 40 | if(typeof this.options.verbosity === "string"){ 41 | var verbosityLevel = this.options.verbosity; 42 | 43 | this.options.verbosity = { 44 | level: verbosityLevel, 45 | urlTracing: true 46 | } 47 | } 48 | 49 | log.setLevel(this.options.verbosity.level); 50 | }.bind(this); 51 | 52 | prepareOptions(options); 53 | 54 | var start = function (data) { 55 | if(this.serverStarted){ 56 | log.debug(chalk.yellow('server already started')); 57 | return this.instance; 58 | } 59 | 60 | log.info(chalk.green("starting server")); 61 | 62 | var server = jsonServer.create(); 63 | 64 | server.use(this.options.bodyParserJson || bodyParser.json()); 65 | server.use(bodyParser.urlencoded({ extended: true })); 66 | 67 | if(this.options.rewriteRules){ 68 | server.use(jsonServer.rewriter(this.options.rewriteRules)); 69 | } 70 | 71 | var defaultsOpts = { }; 72 | 73 | if (!this.options.verbosity.urlTracing) { 74 | defaultsOpts.logger = false; 75 | } 76 | 77 | if (this.options.static) { 78 | defaultsOpts.static = this.options.static; 79 | } 80 | 81 | server.use(jsonServer.defaults(defaultsOpts)); 82 | 83 | if(this.options.customRoutes){ 84 | for(var path in this.options.customRoutes) { 85 | var customRoute = this.options.customRoutes[path]; 86 | server[customRoute.method.toLocaleLowerCase()](path, customRoute.handler); 87 | } 88 | } 89 | 90 | var router = jsonServer.router(data || this.options.data); 91 | if(this.options.baseUrl) { 92 | server.use(this.options.baseUrl, router); 93 | } 94 | else{ 95 | server.use(router); 96 | } 97 | 98 | if(this.options.id){ 99 | var newId = this.options.id; 100 | router.db._.mixin({ 101 | __id: function(){ 102 | return newId; 103 | } 104 | }); 105 | } 106 | 107 | this.server = server; 108 | this.router = router; 109 | this.instance = server.listen(this.options.port); 110 | 111 | enableDestroy(this.instance); 112 | 113 | this.serverStarted = true; 114 | 115 | return this.instance; 116 | }.bind(this); 117 | 118 | var reload = function(data){ 119 | if(typeof data === 'undefined'){ 120 | log.debug(chalk.yellow('nothing to reload, quit')); 121 | return; 122 | } 123 | 124 | if(this.options.debug){ 125 | log.debug(chalk.green("reloading data:")); 126 | log.debug(JSON.stringify(data)); 127 | log.debug(chalk.yellow("destroying server...")); 128 | } 129 | 130 | this.kill(function(){ 131 | log.debug(chalk.yellow("server destroyed")); 132 | }); 133 | start(data); 134 | }.bind(this); 135 | 136 | 137 | 138 | this.kill = function(callback){ 139 | if(this.instance){ 140 | this.instance.destroy(callback); 141 | this.serverStarted = false; 142 | } 143 | }; 144 | 145 | this.pipe = function(options){ 146 | var isCumulative = options && typeof(options.cumulative) !== "undefined" ? options.cumulative : self.options.cumulative; 147 | var isCumulativeSession = options && typeof(options.cumulativeSession) !== "undefined" ? options.cumulativeSession : self.options.cumulativeSession; 148 | 149 | // HACK json-server to get its db object if needed 150 | var aggregatorObject = self.serverStarted && isCumulative ? self.router.db.getState() || {} : {}; 151 | 152 | log.trace(chalk.red("server started: " + this.serverStarted)); 153 | log.trace(chalk.red("cumulative: " + this.options.cumulative)); 154 | log.trace(chalk.red("aggregator object:")); 155 | log.trace(JSON.stringify(aggregatorObject)); 156 | 157 | return through.obj(function (file, enc, cb) { 158 | if (file.isNull()) { 159 | cb(null, file); 160 | return; 161 | } 162 | 163 | if (file.isStream()) { 164 | cb(new PluginError('gulp-json-srv', 'Streaming not supported')); 165 | return; 166 | } 167 | 168 | try { 169 | var appendedObject = JSON.parse(file.contents.toString()); 170 | log.debug(chalk.green('file data:')); 171 | log.debug(JSON.stringify(appendedObject)); 172 | 173 | if(isCumulativeSession){ 174 | aggregatorObject = new Merger(self.options).merge(aggregatorObject, appendedObject || {}); 175 | log.debug(chalk.green("combine DB data in session")); 176 | } 177 | else{ 178 | aggregatorObject = appendedObject || {}; 179 | log.debug(chalk.green("override DB data in session (cumulativeSession=false)")); 180 | } 181 | 182 | log.trace(chalk.red("pipe reloading data:")); 183 | log.trace(JSON.stringify(aggregatorObject)); 184 | 185 | reload(aggregatorObject); 186 | 187 | this.push(file); 188 | } catch (err) { 189 | this.emit('error', new PluginError('gulp-json-srv', err)); 190 | } 191 | 192 | cb(); 193 | }); 194 | }; 195 | }; 196 | 197 | module.exports = { 198 | create: function(options){ 199 | return new GulpJsonServer(options); 200 | } 201 | }; 202 | -------------------------------------------------------------------------------- /test/pipeSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var chalk = require('chalk'); 5 | var PipeHelper = require('./pipeHelper'); 6 | 7 | 8 | /* ===== sample data ===== */ 9 | var post1 = { id: 1, title: "json-server", author: "typicode" }; 10 | var post2 = { id: 2, title: "gulp-json-srv", author: "grafgenerator" }; 11 | var post3 = { id: 3, title: "gulp-json-srv@1.0.0", author: "grafgenerator" }; 12 | var post4 = { id: 4, title: "gulp-json-srv@beta", author: "grafgenerator" }; 13 | 14 | var dbBigger = { 15 | posts: [ 16 | post1, 17 | post2, 18 | post3 19 | ] 20 | }; 21 | 22 | var dbLesser = { 23 | posts: [post1, post4] 24 | }; 25 | 26 | var makeCopy = function(input){ 27 | return JSON.parse(JSON.stringify(input)); 28 | }; 29 | 30 | var makeOptions = function(options){ 31 | return _.extend({debug: true}, options); 32 | }; 33 | /* ===== sample data ===== */ 34 | 35 | 36 | 37 | 38 | describe('#pipe()', function(){ 39 | beforeEach(function(){ 40 | console.log(); 41 | console.log(); 42 | console.log(); 43 | console.log(); 44 | console.log(chalk.blue("=======================================================")); 45 | }); 46 | 47 | it('should load file content when it\'s piped', function(done){ 48 | var helper = new PipeHelper('http://localhost:3000', done, makeOptions()); 49 | 50 | helper 51 | .pipeContent(makeCopy(dbBigger)) 52 | .request(function(req){ 53 | return req 54 | .get('/posts/1') 55 | .expect(200, makeCopy(post1)); 56 | }); 57 | 58 | helper.go(); 59 | }); 60 | 61 | it('should drop previous state when cumulative=false', function(done){ 62 | var helper = new PipeHelper('http://localhost:3000', done, makeOptions({ 63 | cumulative: false 64 | })); 65 | 66 | helper 67 | .pipeContent(makeCopy(dbBigger)) 68 | .request(function(req){ 69 | return req 70 | .get('/posts/2') 71 | .expect(200, makeCopy(post2)); 72 | }) 73 | .pipeContent(makeCopy(dbLesser)) 74 | .request(function(req){ 75 | return req 76 | .get('/posts/2') 77 | .expect(404); 78 | }) 79 | .request(function(req){ 80 | return req 81 | .get('/posts/4') 82 | .expect(200, makeCopy(post4)); 83 | }); 84 | 85 | helper.go(); 86 | }); 87 | 88 | it('should combine previous state with new when cumulative=true', function(done){ 89 | var helper = new PipeHelper('http://localhost:3000', done, makeOptions({ 90 | cumulative: true 91 | })); 92 | 93 | helper 94 | .pipeContent(makeCopy(dbBigger)) 95 | .request(function(req){ 96 | return req 97 | .get('/posts/2') 98 | .expect(200, makeCopy(post2)); 99 | }) 100 | .pipeContent(makeCopy(dbLesser)) 101 | .request(function(req){ 102 | return req 103 | .get('/posts/2') 104 | .expect(200, makeCopy(post2)); 105 | }) 106 | .request(function(req){ 107 | return req 108 | .get('/posts/4') 109 | .expect(200, makeCopy(post4)); 110 | }); 111 | 112 | helper.go(); 113 | }); 114 | 115 | it('should combine input in one pipe session when cumulativeSession=true', function(done){ 116 | var helper = new PipeHelper('http://localhost:3000', done, makeOptions({ 117 | cumulativeSession: true 118 | })); 119 | 120 | helper 121 | .pipeContent([makeCopy(dbBigger), makeCopy(dbLesser)]) 122 | .request(function(req){ 123 | return req 124 | .get('/posts/2') 125 | .expect(200, makeCopy(post2)); 126 | }) 127 | .request(function(req){ 128 | return req 129 | .get('/posts/4') 130 | .expect(200, makeCopy(post4)); 131 | }); 132 | 133 | helper.go(); 134 | }); 135 | 136 | it('should take last occurence of property in one pipe session when cumulativeSession=true', function(done){ 137 | var helper = new PipeHelper('http://localhost:3000', done, makeOptions({ 138 | cumulativeSession: true 139 | })); 140 | var differentPost = { id: 1, title: "different", author: "anyone" }; 141 | 142 | helper 143 | .pipeContent([makeCopy(dbBigger), makeCopy({ posts: [ differentPost]})]) 144 | .request(function(req){ 145 | return req 146 | .get('/posts/1') 147 | .expect(200, makeCopy(differentPost)); 148 | }); 149 | 150 | helper.go(); 151 | }); 152 | 153 | it('should take last one input in one pipe session when cumulative input=false', function(done){ 154 | var helper = new PipeHelper('http://localhost:3000', done, makeOptions({ 155 | cumulativeSession: false 156 | })); 157 | 158 | helper 159 | .pipeContent([makeCopy(dbBigger), makeCopy(dbLesser)]) 160 | .request(function(req){ 161 | return req 162 | .get('/posts/2') 163 | .expect(404); 164 | }) 165 | .request(function(req){ 166 | return req 167 | .get('/posts/4') 168 | .expect(200, makeCopy(post4)); 169 | }); 170 | 171 | helper.go(); 172 | }); 173 | 174 | it('should override cumulative option set at plugin level', function(done){ 175 | var helper = new PipeHelper('http://localhost:3000', done, makeOptions({ 176 | cumulative: false 177 | })); 178 | 179 | helper 180 | .pipeContent(makeCopy(dbBigger)) 181 | .request(function(req){ 182 | return req 183 | .get('/posts/2') 184 | .expect(200, makeCopy(post2)); 185 | }) 186 | .pipeContent(makeCopy(dbLesser), { cumulative: true }) 187 | .request(function(req){ 188 | return req 189 | .get('/posts/2') 190 | .expect(200, makeCopy(post2)); 191 | }) 192 | .request(function(req){ 193 | return req 194 | .get('/posts/4') 195 | .expect(200, makeCopy(post4)); 196 | }); 197 | 198 | helper.go(); 199 | }); 200 | 201 | it('should override cumulativeSession option set at plugin level', function(done){ 202 | var helper = new PipeHelper('http://localhost:3000', done, makeOptions({ 203 | cumulativeSession: true 204 | })); 205 | 206 | helper 207 | .pipeContent([makeCopy(dbBigger), makeCopy(dbLesser)], { cumulativeSession: false}) 208 | .request(function(req){ 209 | return req 210 | .get('/posts/2') 211 | .expect(404); 212 | }) 213 | .request(function(req){ 214 | return req 215 | .get('/posts/4') 216 | .expect(200, makeCopy(post4)); 217 | }); 218 | 219 | helper.go(); 220 | }); 221 | }); 222 | --------------------------------------------------------------------------------