├── .editorconfig ├── .gitignore ├── .jshintrc ├── .npmignore ├── README.md ├── lib ├── generate-route.js ├── index.js ├── schema.json ├── store │ ├── factory.js │ ├── factory │ │ ├── attr.js │ │ ├── many.js │ │ └── one.js │ ├── namespace.js │ ├── props.js │ ├── serializer.js │ └── store.js └── utils │ ├── between.js │ └── pluralize.js ├── package.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.js] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.hbs] 21 | insert_final_newline = false 22 | indent_style = space 23 | indent_size = 2 24 | 25 | [*.css] 26 | indent_style = space 27 | indent_size = 2 28 | 29 | [*.html] 30 | indent_style = space 31 | indent_size = 2 32 | 33 | [*.{diff,md}] 34 | trim_trailing_whitespace = false 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle 2 | bundle 3 | tmp/ 4 | tests/source/ 5 | dist/ 6 | .idea 7 | *.iml 8 | 9 | benchmarks/results/*.json 10 | 11 | .DS_Store 12 | .project 13 | 14 | .github-upload-token 15 | *.swp 16 | 17 | tests/ember-data-tests.js 18 | *gem 19 | 20 | docs/build/ 21 | docs/node_modules/ 22 | 23 | node_modules/ 24 | bower_components/ 25 | .metadata_never_index 26 | npm-debug.log 27 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true 3 | } 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /bower_components 2 | /config/ember-try.js 3 | /dist 4 | /node-tests 5 | /tests 6 | /tmp 7 | /benchmarks 8 | /bin 9 | /docs 10 | 11 | **/.gitkeep 12 | .appveyor.yml 13 | .bowerrc 14 | .editorconfig 15 | .ember-cli 16 | .gitignore 17 | .eslintrc.js 18 | .travis.yml 19 | .watchmanconfig 20 | bower.json 21 | ember-cli-build.js 22 | testem.js 23 | 24 | *.gem 25 | *.gemspec 26 | **/*.rb 27 | node-tests/ 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JsonAPI Mock Server 2 | 3 | A simple json-api validated mock server. 4 | 5 | ## Installation 6 | 7 | ```cli 8 | yarn add json-api-mock-server 9 | ``` 10 | 11 | ## Setup 12 | 13 | ```js 14 | var mountEndpoints = require('json-api-mock-server'); 15 | 16 | mountEndpoints(app, config); 17 | ``` 18 | 19 | `app` is expected to be an express application instance. 20 | 21 | `json-api-mock-server` expects to find: 22 | 23 | - models at `/server/models/*.js` 24 | - scenarios at `/server/scenarios/*.js` 25 | 26 | It will gracefully warn when these aren't found. 27 | 28 | ### Config Settings (with defaults) 29 | 30 | ```js 31 | { 32 | logApiRequests: true, 33 | logApiResponses: true, 34 | serializer: null, 35 | scenario: 'default' 36 | apiNamespace: 'api' 37 | } 38 | ``` 39 | 40 | ### Use with ember-cli 41 | 42 | In `server/index.js` (add this file if not present): 43 | 44 | ```js 45 | /*jshint node:true*/ 46 | var mountEndpoints = require('json-api-mock-server'); 47 | var config = { 48 | logApiRequests: true, 49 | logApiResponses: true, 50 | serializer: null, 51 | scenario: 'default' 52 | apiNamespace: 'api' 53 | }; 54 | 55 | module.exports = function(app) { 56 | mountEndpoints(app, config); 57 | }; 58 | ``` 59 | 60 | You will put the `models` and `scenarios` directories inside of 61 | this `server/` directory. 62 | 63 | ## Models 64 | 65 | Creating a simple model (no relationships): example `server/models/foo.js` 66 | 67 | ```js 68 | var faker = require('faker'); 69 | var props = require('json-api-mock-server/lib/store/props'); 70 | var between = require('json-api-mock-server/lib/utils/between'); 71 | var attr = props.attr; 72 | 73 | module.exports = { 74 | title: attr('string', { defaultValue: function() { return faker.lorem.words(between(3, 5)); }}), 75 | bar: one('bar', { inverse: 'foo', defaultValue: false }), 76 | }; 77 | ``` 78 | 79 | Creating a model `foo`with a one-to-(one|none|many) relationship with 80 | another model `bar` 81 | 82 | ```js 83 | var faker = require('faker'); 84 | var props = require('json-api-mock-server/lib/store/props'); 85 | var between = require('json-api-mock-server/lib/utils/between'); 86 | var attr = props.attr; 87 | var one = props.one; 88 | 89 | module.exports = { 90 | title: attr('string', { defaultValue: function() { return faker.lorem.words(between(3, 5)); }}), 91 | bar: one('bar', { inverse: 'foo', defaultValue: false }), 92 | }; 93 | ``` 94 | 95 | - Omit `inverse` or set it to a false-y value for `one-to-none` behavior. 96 | - setting `defaultValue` to `true` will cause a related model to be created 97 | while seeding the database. Setting it to `false` will cause there 98 | to be no related model for this record. 99 | - `defaultValue` can also be a function that returns `true` or `false`. 100 | 101 | 102 | Creating a model `foo`with a many-to-(one|none|many) relationship with 103 | another model `bar` 104 | 105 | ```js 106 | var faker = require('faker'); 107 | var props = require('json-api-mock-server/lib/store/props'); 108 | var between = require('json-api-mock-server/lib/utils/between'); 109 | var attr = props.attr; 110 | var many = props.many; 111 | 112 | module.exports = { 113 | title: attr('string', { defaultValue: function() { return faker.lorem.words(between(3, 5)); }}), 114 | bars: many('bar', { inverse: 'foo', defaultValue: function() { return between(0, 3); }), 115 | }; 116 | ``` 117 | 118 | - `defaultValue` can be numeric 119 | - the number you set `defaultValue` to or which `defaultValue()` returns 120 | represents the total number of relationships to seed for the related model. 121 | e.g. returning `4` will cause `4` `bar` records to be instantiated. 122 | 123 | ## Scenarios 124 | 125 | Example `server/scenarios/default.js` 126 | 127 | ```js 128 | module.exports = function(store) { 129 | store.seed('user', 10); 130 | store.seed('contacts', 200); 131 | }; 132 | ``` 133 | 134 | - the count passed to `seed` is a minimum to create, additional instances 135 | may be created during the seeding of the relationships of other record types 136 | e.g. if `user` has `contacts` and `defaultValue` on model `user` is 5, 137 | the total number of contacts generated is `5 * 10 + 200 = 250`. 138 | 139 | -------------------------------------------------------------------------------- /lib/generate-route.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | var bodyParser = require('body-parser'); 3 | var pluralize = require('./utils/pluralize'); 4 | var chalk = require('chalk'); 5 | var Validator = require('jsonapi-validator').Validator; 6 | var Schema = require('./schema.json'); 7 | var validator = new Validator(Schema); 8 | var path = require('path'); 9 | 10 | function colorBGForMethod(method) { 11 | switch (method) { 12 | case 'DELETE': 13 | return 'bgRed'; 14 | case 'PUT': 15 | return 'bgYellow'; 16 | case 'PATCH': 17 | return 'bgMagenta'; 18 | case 'POST': 19 | return 'bgBlue'; 20 | case 'GET': 21 | return 'bgGreen'; 22 | default: 23 | return 'bgBlack'; 24 | } 25 | } 26 | 27 | function colorBGForStatus(method) { 28 | switch (method) { 29 | case 206: 30 | case 200: 31 | return 'bgGreen'; 32 | case 404: 33 | case 405: 34 | case 403: 35 | case 406: 36 | return 'bgRed'; 37 | case 204: 38 | return 'bgYellow'; 39 | case 202: 40 | return 'bgMagenta'; 41 | case 201: 42 | return 'bgBlue'; 43 | default: 44 | return 'bgCyan'; 45 | } 46 | } 47 | 48 | function stringifyValidationError(e) { 49 | return chalk.cyan(e.keyword) + ' ' + chalk.white(e.message); 50 | } 51 | 52 | function validateJsonApi(json) { 53 | // will throw error if our payloads are wrong 54 | try { 55 | validator.validate(json); 56 | } catch (e) { 57 | console.log(chalk.red('Invalid json-api response detected')); 58 | console.log('original payload'); 59 | console.log(chalk.grey(JSON.stringify(json, null, 2))); 60 | console.log(chalk.yellow('\n\nJSON API VALIDATION ERRORS' + 61 | '\n===========================\n')); 62 | 63 | for (var i = 0; i < e.errors.length; i++) { 64 | console.log('\t' + (i + 1) + ')\t' + stringifyValidationError(e.errors[i])); 65 | } 66 | console.log('\n'); 67 | } 68 | 69 | return json; 70 | } 71 | 72 | function logApiRequest(shouldLog, req) { 73 | if (shouldLog) { 74 | console.log( 75 | '\tMock Request\t' + 76 | chalk[colorBGForMethod(req.method)](chalk.bold(chalk.white(' ' + req.method + ' '))) + ' ' + 77 | chalk.white(req.baseUrl) + 78 | chalk.yellow(req._parsedUrl.search || '') 79 | ); 80 | } 81 | } 82 | 83 | function logApiResponse(shouldLog, res, payload) { 84 | if (shouldLog) { 85 | var response; 86 | if (payload) { 87 | var totalRecords = payload.data instanceof Array ? payload.data.length : 1; 88 | var includedRecords = payload.included ? payload.included.length : 0; 89 | response = chalk.cyan( 90 | ' ' + totalRecords + ' primary records, ' + includedRecords + ' included records' 91 | ); 92 | } else { 93 | response = chalk.yellow('Request Returned an Empty Response'); 94 | } 95 | 96 | console.log( 97 | '\tMock Response\t' + 98 | chalk[colorBGForStatus(res.statusCode)]( 99 | chalk.bold(chalk.white(' ' + res.statusCode + ' ' + res.statusMessage + ' ')) 100 | ) + response 101 | ); 102 | } 103 | } 104 | 105 | 106 | module.exports = function(app, modelName) { 107 | var express = require('express'); 108 | var router = express.Router(); 109 | var shouldLogRequest = app.store._config.logApiRequests; 110 | var shouldLogResponse = app.store._config.logApiResponses; 111 | 112 | var logRequest = function(req) { 113 | logApiRequest(shouldLogRequest, req); 114 | }; 115 | var logResponse = function(res, payload) { 116 | logApiResponse(shouldLogResponse, res, payload); 117 | }; 118 | 119 | function respond(res, responseData, statusCode) { 120 | if (responseData && !(statusCode >= 500)) { 121 | validateJsonApi(responseData); 122 | } 123 | 124 | res.status(statusCode); 125 | res.send(responseData); 126 | 127 | logResponse(res, responseData); 128 | } 129 | 130 | router.get('/', function(req, res) { 131 | logRequest(req, modelName); 132 | var responseData; 133 | try { 134 | responseData = app.store.query(modelName, req.query); 135 | respond(res, responseData, responseData && responseData.data ? 200 : 404); 136 | } catch (e) { 137 | respond(res, e.message, 500); 138 | } 139 | }); 140 | 141 | router.post('/', function(req, res) { 142 | logRequest(req, modelName); 143 | var responseData; 144 | try { 145 | responseData = app.store.createRecord(modelName, req.body); 146 | respond(res, responseData, responseData && responseData.data ? 201 : 204); 147 | } catch (e) { 148 | respond(res, e.message, 500); 149 | } 150 | }); 151 | 152 | router.get('/:id', function(req, res) { 153 | logRequest(req, modelName); 154 | var responseData; 155 | try { 156 | responseData = app.store.findRecord(modelName, req.params.id, req.query); 157 | respond(res, responseData, responseData && responseData.data ? 200 : 404); 158 | } catch (e) { 159 | respond(res, e.message, 500); 160 | } 161 | }); 162 | 163 | router.put('/:id', function(req, res) { 164 | logRequest(req, modelName); 165 | var responseData; 166 | try { 167 | responseData = app.store.updateRecord(modelName, req.params.id, req.body); 168 | respond(res, responseData, responseData && responseData.data ? 201 : 204); 169 | } catch (e) { 170 | respond(res, e.message, 500); 171 | } 172 | }); 173 | 174 | router.delete('/:id', function(req, res) { 175 | logRequest(req, modelName); 176 | var responseData; 177 | try { 178 | responseData = app.store.deleteRecord(modelName, req.params.id); 179 | respond(res, responseData, responseData && responseData.data ? 201 : 204); 180 | } catch (e) { 181 | respond(res, e.message, 500); 182 | } 183 | }); 184 | 185 | var apiPath = path.join('/', app.store.apiNamespace, pluralize(modelName)); 186 | app.use(apiPath, bodyParser.json()); 187 | app.use(apiPath, router); 188 | }; 189 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /* jshint node:true */ 2 | 3 | var Store = require('./store/store'); 4 | var route = require('./generate-route'); 5 | var globSync = require('glob').sync; 6 | var path = require('path'); 7 | 8 | module.exports = function mountEndpoints(app, config) { 9 | var dirPrefix = path.resolve('./server'); 10 | var scenarios = globSync(path.join(dirPrefix, './scenarios/**/*.js')); 11 | var models = globSync(path.join(dirPrefix, './models/**/*.js')); 12 | var serializers = globSync(path.join(dirPrefix, './serializers/**/*.js')); 13 | 14 | config = config || {}; 15 | config._defs = { 16 | scenarios: scenarios, 17 | models: models, 18 | serializers: serializers 19 | }; 20 | 21 | app.store = new Store(config || {}); 22 | app.store.namespaces.forEach(function(name) { 23 | route(app, name); 24 | }); 25 | }; 26 | 27 | 28 | -------------------------------------------------------------------------------- /lib/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "title": "JSON API Schema", 4 | "description": "This is a schema for responses in the JSON API format. For more, see http://jsonapi.org", 5 | "oneOf": [ 6 | { 7 | "$ref": "#/definitions/success" 8 | }, 9 | { 10 | "$ref": "#/definitions/failure" 11 | }, 12 | { 13 | "$ref": "#/definitions/info" 14 | } 15 | ], 16 | 17 | "definitions": { 18 | "success": { 19 | "type": "object", 20 | "required": [ 21 | "data" 22 | ], 23 | "properties": { 24 | "data": { 25 | "$ref": "#/definitions/data" 26 | }, 27 | "included": { 28 | "description": "To reduce the number of HTTP requests, servers **MAY** allow responses that include related resources along with the requested primary resources. Such responses are called \"compound documents\".", 29 | "type": "array", 30 | "items": { 31 | "$ref": "#/definitions/resource" 32 | }, 33 | "uniqueItems": true 34 | }, 35 | "meta": { 36 | "$ref": "#/definitions/meta" 37 | }, 38 | "links": { 39 | "description": "Link members related to the primary data.", 40 | "allOf": [ 41 | { 42 | "$ref": "#/definitions/links" 43 | }, 44 | { 45 | "$ref": "#/definitions/pagination" 46 | } 47 | ] 48 | }, 49 | "jsonapi": { 50 | "$ref": "#/definitions/jsonapi" 51 | } 52 | }, 53 | "additionalProperties": false 54 | }, 55 | "failure": { 56 | "type": "object", 57 | "required": [ 58 | "errors" 59 | ], 60 | "properties": { 61 | "errors": { 62 | "type": "array", 63 | "items": { 64 | "$ref": "#/definitions/error" 65 | }, 66 | "uniqueItems": true 67 | }, 68 | "meta": { 69 | "$ref": "#/definitions/meta" 70 | }, 71 | "jsonapi": { 72 | "$ref": "#/definitions/jsonapi" 73 | } 74 | }, 75 | "additionalProperties": false 76 | }, 77 | "info": { 78 | "type": "object", 79 | "required": [ 80 | "meta" 81 | ], 82 | "properties": { 83 | "meta": { 84 | "$ref": "#/definitions/meta" 85 | }, 86 | "links": { 87 | "$ref": "#/definitions/links" 88 | }, 89 | "jsonapi": { 90 | "$ref": "#/definitions/jsonapi" 91 | } 92 | }, 93 | "additionalProperties": false 94 | }, 95 | 96 | "meta": { 97 | "description": "Non-standard meta-information that can not be represented as an attribute or relationship.", 98 | "type": "object", 99 | "additionalProperties": true 100 | }, 101 | "data": { 102 | "description": "The document's \"primary data\" is a representation of the resource or collection of resources targeted by a request.", 103 | "oneOf": [ 104 | { 105 | "$ref": "#/definitions/resource" 106 | }, 107 | { 108 | "description": "An array of resource objects, an array of resource identifier objects, or an empty array ([]), for requests that target resource collections.", 109 | "type": "array", 110 | "items": { 111 | "$ref": "#/definitions/resource" 112 | }, 113 | "uniqueItems": true 114 | }, 115 | { 116 | "description": "null if the request is one that might correspond to a single resource, but doesn't currently.", 117 | "type": "null" 118 | } 119 | ] 120 | }, 121 | "resource": { 122 | "description": "\"Resource objects\" appear in a JSON API document to represent resources.", 123 | "type": "object", 124 | "required": [ 125 | "type", 126 | "id" 127 | ], 128 | "properties": { 129 | "type": { 130 | "type": "string" 131 | }, 132 | "id": { 133 | "type": "string" 134 | }, 135 | "attributes": { 136 | "$ref": "#/definitions/attributes" 137 | }, 138 | "relationships": { 139 | "$ref": "#/definitions/relationships" 140 | }, 141 | "links": { 142 | "$ref": "#/definitions/links" 143 | }, 144 | "meta": { 145 | "$ref": "#/definitions/meta" 146 | } 147 | }, 148 | "additionalProperties": false 149 | }, 150 | 151 | "links": { 152 | "description": "A resource object **MAY** contain references to other resource objects (\"relationships\"). Relationships may be to-one or to-many. Relationships can be specified by including a member in a resource's links object.", 153 | "type": "object", 154 | "properties": { 155 | "self": { 156 | "description": "A `self` member, whose value is a URL for the relationship itself (a \"relationship URL\"). This URL allows the client to directly manipulate the relationship. For example, it would allow a client to remove an `author` from an `article` without deleting the people resource itself.", 157 | "type": "string", 158 | "format": "uri" 159 | }, 160 | "related": { 161 | "$ref": "#/definitions/link" 162 | } 163 | }, 164 | "additionalProperties": true 165 | }, 166 | "link": { 167 | "description": "A link **MUST** be represented as either: a string containing the link's URL or a link object.", 168 | "oneOf": [ 169 | { 170 | "description": "A string containing the link's URL.", 171 | "type": "string", 172 | "format": "uri" 173 | }, 174 | { 175 | "type": "object", 176 | "required": [ 177 | "href" 178 | ], 179 | "properties": { 180 | "href": { 181 | "description": "A string containing the link's URL.", 182 | "type": "string", 183 | "format": "uri" 184 | }, 185 | "meta": { 186 | "$ref": "#/definitions/meta" 187 | } 188 | } 189 | } 190 | ] 191 | }, 192 | 193 | "attributes": { 194 | "description": "Members of the attributes object (\"attributes\") represent information about the resource object in which it's defined.", 195 | "type": "object", 196 | "patternProperties": { 197 | "^(?!relationships$|links$)\\w[-\\w_]*$": { 198 | "description": "Attributes may contain any valid JSON value." 199 | } 200 | }, 201 | "additionalProperties": false 202 | }, 203 | 204 | "relationships": { 205 | "description": "Members of the relationships object (\"relationships\") represent references from the resource object in which it's defined to other resource objects.", 206 | "type": "object", 207 | "patternProperties": { 208 | "^\\w[-\\w_]*$": { 209 | "properties": { 210 | "links": { 211 | "$ref": "#/definitions/links" 212 | }, 213 | "data": { 214 | "description": "Member, whose value represents \"resource linkage\".", 215 | "oneOf": [ 216 | { 217 | "$ref": "#/definitions/relationshipToOne" 218 | }, 219 | { 220 | "$ref": "#/definitions/relationshipToMany" 221 | } 222 | ] 223 | }, 224 | "meta": { 225 | "$ref": "#/definitions/meta" 226 | } 227 | }, 228 | "additionalProperties": false 229 | } 230 | }, 231 | "additionalProperties": false 232 | }, 233 | "relationshipToOne": { 234 | "description": "References to other resource objects in a to-one (\"relationship\"). Relationships can be specified by including a member in a resource's links object.", 235 | "anyOf": [ 236 | { 237 | "$ref": "#/definitions/empty" 238 | }, 239 | { 240 | "$ref": "#/definitions/linkage" 241 | } 242 | ] 243 | }, 244 | "relationshipToMany": { 245 | "description": "An array of objects each containing \"type\" and \"id\" members for to-many relationships.", 246 | "type": "array", 247 | "items": { 248 | "$ref": "#/definitions/linkage" 249 | }, 250 | "uniqueItems": true 251 | }, 252 | "empty": { 253 | "description": "Describes an empty to-one relationship.", 254 | "type": "null" 255 | }, 256 | "linkage": { 257 | "description": "The \"type\" and \"id\" to non-empty members.", 258 | "type": "object", 259 | "required": [ 260 | "type", 261 | "id" 262 | ], 263 | "properties": { 264 | "type": { 265 | "type": "string" 266 | }, 267 | "id": { 268 | "type": "string" 269 | }, 270 | "meta": { 271 | "$ref": "#/definitions/meta" 272 | } 273 | }, 274 | "additionalProperties": false 275 | }, 276 | "pagination": { 277 | "type": "object", 278 | "properties": { 279 | "first": { 280 | "description": "The first page of data", 281 | "oneOf": [ 282 | { "type": "string", "format": "uri" }, 283 | { "type": "null" } 284 | ] 285 | }, 286 | "last": { 287 | "description": "The last page of data", 288 | "oneOf": [ 289 | { "type": "string", "format": "uri" }, 290 | { "type": "null" } 291 | ] 292 | }, 293 | "prev": { 294 | "description": "The previous page of data", 295 | "oneOf": [ 296 | { "type": "string", "format": "uri" }, 297 | { "type": "null" } 298 | ] 299 | }, 300 | "next": { 301 | "description": "The next page of data", 302 | "oneOf": [ 303 | { "type": "string", "format": "uri" }, 304 | { "type": "null" } 305 | ] 306 | } 307 | } 308 | }, 309 | 310 | "jsonapi": { 311 | "description": "An object describing the server's implementation", 312 | "type": "object", 313 | "properties": { 314 | "version": { 315 | "type": "string" 316 | }, 317 | "meta": { 318 | "$ref": "#/definitions/meta" 319 | } 320 | }, 321 | "additionalProperties": false 322 | }, 323 | 324 | "error": { 325 | "type": "object", 326 | "properties": { 327 | "id": { 328 | "description": "A unique identifier for this particular occurrence of the problem.", 329 | "type": "string" 330 | }, 331 | "links": { 332 | "$ref": "#/definitions/links" 333 | }, 334 | "status": { 335 | "description": "The HTTP status code applicable to this problem, expressed as a string value.", 336 | "type": "string" 337 | }, 338 | "code": { 339 | "description": "An application-specific error code, expressed as a string value.", 340 | "type": "string" 341 | }, 342 | "title": { 343 | "description": "A short, human-readable summary of the problem. It **SHOULD NOT** change from occurrence to occurrence of the problem, except for purposes of localization.", 344 | "type": "string" 345 | }, 346 | "detail": { 347 | "description": "A human-readable explanation specific to this occurrence of the problem.", 348 | "type": "string" 349 | }, 350 | "source": { 351 | "type": "object", 352 | "properties": { 353 | "pointer": { 354 | "description": "A JSON Pointer [RFC6901] to the associated entity in the request document [e.g. \"/data\" for a primary data object, or \"/data/attributes/title\" for a specific attribute].", 355 | "type": "string" 356 | }, 357 | "parameter": { 358 | "description": "A string indicating which query parameter caused the error.", 359 | "type": "string" 360 | } 361 | } 362 | }, 363 | "meta": { 364 | "$ref": "#/definitions/meta" 365 | } 366 | }, 367 | "additionalProperties": false 368 | } 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /lib/store/factory.js: -------------------------------------------------------------------------------- 1 | var Many = require('./factory/many'); 2 | var One = require('./factory/one'); 3 | var Attr = require('./factory/attr'); 4 | 5 | function inspect(v) { 6 | if (v instanceof Array) { 7 | if (!v.length) { 8 | return '[]'; 9 | } 10 | 11 | return '[\'' + v.map(function(v) { return v.id; }).join('\', \'') + '\']'; 12 | } 13 | return v && v.toString ? v.toString() : String(v); 14 | } 15 | 16 | function Record(data) { 17 | this.__deleted = false; 18 | this.relationships = {}; 19 | this.attributes = {}; 20 | this.id = data.id; 21 | this.type = data.type; 22 | } 23 | 24 | Record.prototype.toString = function() { 25 | return 'RECORD#' + this.type + ':' + this.id; 26 | }; 27 | 28 | function Factory(name, options, store, namespace) { 29 | this.relationships = {}; 30 | this.attributes = {}; 31 | this._nextId = 1; 32 | this.type = name; 33 | this.name = name; 34 | this.store = store; 35 | this.namespace = namespace; 36 | 37 | var _factory = this; 38 | 39 | Object.keys(options).forEach(function(key) { 40 | var option = options[key]; 41 | 42 | if (option instanceof Attr) { 43 | _factory.attributes[key] = option; 44 | } else if (option instanceof One || option instanceof Many) { 45 | _factory.relationships[key] = option; 46 | } 47 | }); 48 | } 49 | 50 | Factory.prototype.generate = function generate(data) { 51 | data = data || {}; 52 | 53 | if (data.id && Number(data.id) < this._nextId) { 54 | throw new Error('Attempting to recreate an existing record!'); 55 | } else if (data.id) { 56 | this._nextId = data.id + 1; 57 | } 58 | 59 | var record = new Record({ 60 | id: String(data.id || this._nextId++), 61 | type: this.type 62 | }); 63 | 64 | // console.log('Seeded: ' + this.type + '#' + record.id); 65 | 66 | // eagerly push record for relationship building 67 | this.namespace.pushRecord(record); 68 | 69 | var i; 70 | var keys = Object.keys(this.attributes); 71 | var key; 72 | var attr; 73 | 74 | // populate attributes 75 | for (i = 0; i < keys.length; i++) { 76 | key = keys[i]; 77 | attr = this.attributes[key]; 78 | 79 | record.attributes[key] = data[key] !== undefined ? data[key] : attr.defaultValue(); 80 | } 81 | 82 | // populate relationships 83 | keys = Object.keys(this.relationships); 84 | for (i = 0; i < keys.length; i++) { 85 | key = keys[i]; 86 | attr = this.relationships[key]; 87 | attr.parent = record; 88 | attr.parentType = this.name; 89 | 90 | var value = data[key] !== undefined ? data[key] : attr.defaultValue(); 91 | 92 | if (value) { 93 | /* 94 | console.log( 95 | '\tlink: ' + this.name + ':' + record.id + 96 | (attr instanceof One ? ' -> ' : ' => ') + key + ':' + inspect(value)); 97 | */ 98 | var reference = attr.reference(value); 99 | // console.log('\t\tlinked:', reference.info()); 100 | record.relationships[key] = reference; 101 | } 102 | } 103 | 104 | return record; 105 | }; 106 | 107 | module.exports = Factory; 108 | -------------------------------------------------------------------------------- /lib/store/factory/attr.js: -------------------------------------------------------------------------------- 1 | function Attr(type, options) { 2 | this.type = type; 3 | this.options = options; 4 | } 5 | 6 | Attr.prototype.defaultValue = function defaultValue() { 7 | return (typeof this.options.defaultValue === 'function') ? 8 | this.options.defaultValue() : this.options.defaultValue; 9 | }; 10 | 11 | module.exports = Attr; 12 | -------------------------------------------------------------------------------- /lib/store/factory/many.js: -------------------------------------------------------------------------------- 1 | function Many(type, options) { 2 | this.type = type; 3 | this.options = options; 4 | } 5 | 6 | function RecordArray() { 7 | var arr = []; 8 | arr.push.apply(arr, arguments); 9 | arr.__proto__ = RecordArray.prototype; 10 | return arr; 11 | } 12 | RecordArray.prototype = new Array(); 13 | RecordArray.prototype.relationship = null; 14 | RecordArray.prototype.namespace = null; 15 | 16 | RecordArray.prototype.fetch = function fetch() { 17 | for (var i = 0; i < this.length; i++) { 18 | this[i] = typeof this[i] === 'string' ? this.namespace.findReference(this[i]) : this[i]; 19 | 20 | // self clean 21 | if (!this[i] || this[i].__deleted) { 22 | this.splice(i, 1); 23 | i -=1; 24 | } 25 | } 26 | 27 | return this; 28 | }; 29 | 30 | RecordArray.prototype.info = function info() { 31 | var _ra = this; 32 | 33 | return this.map(function(item) { 34 | return { 35 | id: item.id ? item.id : item, 36 | type: _ra.relationship.type 37 | }; 38 | }); 39 | }; 40 | 41 | 42 | Many.prototype.reference = function(values) { 43 | if (!values) { 44 | return null; 45 | } 46 | 47 | if (!(values instanceof Array)) { 48 | values = [values]; 49 | } 50 | 51 | var namespace = this.store.namespaceFor(this.type); 52 | var records = new RecordArray(); 53 | 54 | records.relationship = this; 55 | records.namespace = namespace; 56 | 57 | for (var i = 0; i < values.length; i++) { 58 | records.push(values[i]); 59 | } 60 | 61 | return records.fetch(); 62 | }; 63 | 64 | Many.prototype.defaultValue = function defaultValue() { 65 | 66 | var val = (typeof this.options.defaultValue === 'function') ? 67 | this.options.defaultValue() : this.options.defaultValue; 68 | 69 | // console.log('defaultValue ' + this.parentType + '#many(' + this.type + ')', val); 70 | if (typeof val === 'number') { 71 | var namespace = this.store.namespaceFor(this.type); 72 | 73 | if (this.options.inverse) { 74 | var def = {}; 75 | def[this.options.inverse] = this.parent.id; 76 | val = namespace.seed(val, def); 77 | } else { 78 | val = namespace.seed(val); 79 | } 80 | } 81 | 82 | // console.log('seeding many of ' + this.type + ': ', val); 83 | return val; 84 | }; 85 | 86 | Many.prototype.store = null; 87 | 88 | module.exports = Many; 89 | -------------------------------------------------------------------------------- /lib/store/factory/one.js: -------------------------------------------------------------------------------- 1 | function One(type, options) { 2 | this.type = type; 3 | this.options = options; 4 | } 5 | 6 | function Reference(num) { 7 | this.value = num; 8 | } 9 | 10 | Reference.prototype.relationship = null; 11 | Reference.prototype.namespace = null; 12 | 13 | Reference.prototype.toJSON = function toJSON() { 14 | return this.value; 15 | }; 16 | 17 | Reference.prototype.fetch = function fetch() { 18 | this.value = (this.value && typeof this.value === 'string') ? 19 | this.namespace.findReference(this.value) : this.value; 20 | 21 | // self clean 22 | if (!this.value || this.value.__deleted) { 23 | this.value = null; 24 | } 25 | 26 | return this; 27 | }; 28 | 29 | Reference.prototype.info = function info() { 30 | return { id: this.value.id || this.value, type: this.relationship.type }; 31 | }; 32 | 33 | 34 | One.prototype.reference = function(value) { 35 | if (!value) { 36 | return null; 37 | } 38 | 39 | var namespace = this.store.namespaceFor(this.type); 40 | var reference = new Reference(value); 41 | 42 | reference.relationship = this; 43 | reference.namespace = namespace; 44 | 45 | return reference.fetch(); 46 | }; 47 | 48 | 49 | 50 | One.prototype.defaultValue = function defaultValue() { 51 | 52 | var val = (typeof this.options.defaultValue === 'function') ? 53 | this.options.defaultValue() : this.options.defaultValue; 54 | 55 | if (val === true) { 56 | var namespace = this.store.namespaceFor(this.type); 57 | 58 | if (this.options.inverse) { 59 | var def = {}; 60 | def[this.options.inverse] = this.parent.id; 61 | val = namespace.seed(1, def)[0]; 62 | } else { 63 | val = namespace.seed(1); 64 | } 65 | 66 | } else if (val === false) { 67 | val = undefined; 68 | } 69 | 70 | if (val) { 71 | // console.log('defaultValue ' + this.parent.type + ':' + this.parent.id + '#one(' + this.type + ':' + val.id + ')'); 72 | } 73 | return val; 74 | }; 75 | 76 | 77 | 78 | One.prototype.store = null; 79 | 80 | module.exports = One; 81 | -------------------------------------------------------------------------------- /lib/store/namespace.js: -------------------------------------------------------------------------------- 1 | var Factory = require('./factory'); 2 | var assign = require('object-assign'); 3 | 4 | function Namespace(name, model, serializer) { 5 | this._recordMap = {}; 6 | this._records = []; 7 | this._nextID = 0; 8 | this._schema = model; 9 | this.serializer = serializer; 10 | this.factory = new Factory(name, model, this.store, this); 11 | this._name = name; 12 | this._type = name; 13 | } 14 | 15 | Namespace.prototype.clone = function clone(records) { 16 | return records.map(function(record) { 17 | return assign({}, record); 18 | }); 19 | }; 20 | 21 | Namespace.prototype.findRecord = function findRecord(id) { 22 | var record = this._recordMap[id]; 23 | 24 | if (!record || record.__deleted) { 25 | throw new Error(404); 26 | } 27 | 28 | return this.serializer.serializeRecord(record, query); 29 | }; 30 | 31 | Namespace.prototype.createRecord = function createRecord(data) { 32 | var record = {}; 33 | 34 | if (!data) { 35 | throw new Error(500); 36 | } 37 | 38 | var values = this.serializer.normalizeOne(data); 39 | 40 | assign(record, this._schema(), values, { id: this._nextID++ }); 41 | this._records.push(record); 42 | this._recordMap[record.id] = record; 43 | 44 | return this.serializer.serializeRecord(record); 45 | }; 46 | 47 | Namespace.prototype.findAll = function findAll(query) { 48 | return this.serializer.serializeMany(this._records.filter(function(record) { 49 | return !record.__deleted; 50 | }), query); 51 | }; 52 | 53 | Namespace.prototype.query = function query(query) { 54 | var records = this._records.filter(function(record) { 55 | return !record.__deleted; 56 | }); 57 | 58 | var page = query.page ? parseInt(query.page, 10): 0; 59 | var limit = query.limit ? parseInt(query.limit, 10) : 0; 60 | 61 | if (limit === 0) { 62 | return this.findAll(query); 63 | } 64 | 65 | var startingIndex = page * limit; 66 | var results = records.slice(startingIndex, startingIndex + limit); 67 | 68 | return this.serializer.serializeMany(results, query); 69 | }; 70 | 71 | Namespace.prototype.deleteRecord = function deleteRecord(id) { 72 | var record = this._recordMap[id]; 73 | 74 | if (!record || record.__deleted) { 75 | throw new Error(500); 76 | } 77 | 78 | record.__deleted = true; 79 | }; 80 | 81 | Namespace.prototype.updateRecord = function updateRecord(id, data) { 82 | var record = this._recordMap[id]; 83 | 84 | if (!data || !record || record.__deleted) { 85 | throw new Error(500); 86 | } 87 | 88 | var values = this.serializer.normalizeOne(data); 89 | 90 | assign(record, values); 91 | 92 | return this.serializer.serializeRecord(record); 93 | }; 94 | 95 | Namespace.prototype.pushRecord = function pushRecord(record) { 96 | this._recordMap[record.id] = record; 97 | this._records.push(record); 98 | }; 99 | 100 | Namespace.prototype.findReference = function findReference(id) { 101 | var record = this._recordMap[id]; 102 | 103 | if (!record) { 104 | record = this.seed(1, { id: id })[0]; 105 | } 106 | 107 | // console.log('Reference#' + this._type + ':' + id, record); 108 | 109 | return record; 110 | }; 111 | 112 | Namespace.prototype.seed = function seed(number, options) { 113 | options = options || {}; 114 | var records = []; 115 | 116 | for (var i = 0; i < number; i++) { 117 | records.push(this.factory.generate(options)); 118 | } 119 | 120 | return records; 121 | }; 122 | 123 | module.exports = Namespace; 124 | -------------------------------------------------------------------------------- /lib/store/props.js: -------------------------------------------------------------------------------- 1 | var Attr = require('./factory/attr'); 2 | var One = require('./factory/one'); 3 | var Many = require('./factory/many'); 4 | 5 | module.exports = { 6 | 7 | attr: function(type, options) { 8 | return new Attr(type, options); 9 | }, 10 | 11 | many: function(name, options) { 12 | return new Many(name, options); 13 | }, 14 | 15 | one: function(name, options) { 16 | return new One(name, options); 17 | } 18 | 19 | }; 20 | -------------------------------------------------------------------------------- /lib/store/serializer.js: -------------------------------------------------------------------------------- 1 | var assign = require('object-assign'); 2 | // var chalk = require('chalk'); 3 | 4 | function Serializer() {} 5 | 6 | Serializer.prototype.maxSerializationDepth = 4; 7 | 8 | Serializer.prototype.serializeRecord = function serializeRecord(record, query) { 9 | return this._serializeOne(record); 10 | }; 11 | 12 | Serializer.prototype._serializeOne = function serializeOne(record, included, depth) { 13 | if (!depth && depth !== 0) { 14 | depth = 0; 15 | } 16 | 17 | var ret = {}; 18 | var seen = []; 19 | 20 | if (depth < this.maxSerializationDepth) { 21 | if (!included) { 22 | included = ret.included = []; 23 | } 24 | } 25 | 26 | ret.data = this._serializeRecord(record, included, ++depth, seen); 27 | 28 | return ret; 29 | }; 30 | 31 | Serializer.prototype._serializeRecord = function(record, included, depth, seen) { 32 | if (seen.indexOf(record) !== -1) { 33 | throw new Error('Circular whoops!'); 34 | } 35 | seen.push(record); 36 | var data = { 37 | id: record.id, 38 | type: record.type, 39 | attributes: assign({}, record.attributes) 40 | }; 41 | 42 | if (record.relationships) { 43 | this._serializeIncludes(record, data, depth < this.maxSerializationDepth ? included : false, seen); 44 | } 45 | 46 | // console.log('serialized => ' + record.type + ':' + record.id); 47 | return data; 48 | }; 49 | 50 | Serializer.prototype._serializeIncludes = function serializeIncludes(record, hash, included, seen) { 51 | var keys = Object.keys(record.relationships); 52 | var _serializer = this; 53 | 54 | if (keys.length) { 55 | hash.relationships = {}; 56 | } 57 | 58 | keys.forEach(function(key) { 59 | var rel = record.relationships[key]; 60 | 61 | if (rel) { 62 | // console.log('serializing relationship', key, rel.info()); 63 | 64 | rel.fetch(); 65 | 66 | hash.relationships[key] = { data: rel.info() }; 67 | // console.log('serialized ' + key, hash.relationships[key], hash.type + '#' + hash.id); 68 | 69 | if (included) { 70 | if (rel instanceof Array) { 71 | // console.log('serializing many', rel.info(), rel.length, key); 72 | for (var i = 0; i < rel.length; i++) { 73 | if (seen.indexOf(rel[i]) === -1) { 74 | var v = _serializer._serializeRecord(rel[i], included, false, seen); 75 | // console.log('pushing include', v.type, v.id); 76 | included.push(v); 77 | } 78 | } 79 | } else { 80 | if (seen.indexOf(rel.value) === -1) { 81 | var v = _serializer._serializeRecord(rel.value, included, false, seen); 82 | // console.log('pushing include', v.type, v.id); 83 | included.push(v); 84 | } 85 | } 86 | } 87 | } 88 | }); 89 | 90 | }; 91 | 92 | Serializer.prototype.normalizeRecord = function normalizeRecord(record) { 93 | return this._normalizeOne(record); 94 | }; 95 | 96 | Serializer.prototype.normalizeMany = function normalizeRecord(record) { 97 | throw new Error('whoops, this method was not implemented!'); 98 | }; 99 | 100 | Serializer.prototype._normalizeOne = function normalizeOne(record) { 101 | throw new Error('whoops, this method was not implemented!'); 102 | }; 103 | 104 | Serializer.prototype._stripIncludes = function _stripIncludes(ret, query) { 105 | var allowedIncludes = query && query.included ? query.included.split(',') : []; 106 | 107 | if (!allowedIncludes.length) { 108 | delete ret.included; 109 | } else { 110 | var finalIncludes = []; 111 | var seen = {}; 112 | 113 | for (var i = 0, inc = ret.included, l = inc.length; i < l; i++) { 114 | var r = inc[i]; 115 | seen[r.type] = seen[r.type]|| {}; 116 | if (seen[r.type][r.id]) { 117 | continue; 118 | } 119 | seen[r.type][r.id] = true; 120 | 121 | if (allowedIncludes.indexOf(r.type) !== -1) { 122 | finalIncludes.push(r); 123 | } 124 | } 125 | ret.included = finalIncludes; 126 | } 127 | }; 128 | 129 | Serializer.prototype.serializeMany = function serializeMany(records, query) { 130 | var _this = this; 131 | var ret = { 132 | data: [], 133 | included: [] 134 | }; 135 | 136 | records.forEach(function(record) { 137 | var serialized = _this._serializeOne(record); 138 | 139 | ret.data.push(serialized.data); 140 | ret.included = ret.included.concat(serialized.included); 141 | }); 142 | 143 | this._stripIncludes(ret, query); 144 | 145 | return ret; 146 | }; 147 | 148 | module.exports = Serializer; 149 | -------------------------------------------------------------------------------- /lib/store/store.js: -------------------------------------------------------------------------------- 1 | /* jshint node:true */ 2 | var Serializer = require('./serializer'); 3 | var Namespace = require('./namespace'); 4 | var Attr = require('./factory/attr'); 5 | var One = require('./factory/one'); 6 | var Many = require('./factory/many'); 7 | var path = require('path'); 8 | var chalk = require('chalk'); 9 | 10 | function softAssert(message, test) { 11 | if (!test) { 12 | console.log( 13 | chalk.bgRed(chalk.white(chalk.bold(' Error '))) + 14 | chalk.white(' JSON-API-MOCK-SERVER ') + 15 | chalk.yellow(message) 16 | ); 17 | 18 | var trace = new Error(message); 19 | console.log('\n\n', chalk.red(trace)); 20 | } 21 | } 22 | 23 | function warn(message, test) { 24 | if (!test) { 25 | console.log( 26 | chalk.bgYellow(chalk.white(chalk.bold(' Warning '))) + 27 | chalk.white(' JSON-API-MOCK-SERVER ') + 28 | chalk.yellow(message) 29 | ); 30 | } 31 | } 32 | 33 | function normalizeConfig(config) { 34 | if (!config.logApiRequests && config.logApiRequests !== false) { 35 | config.logApiRequests = true; 36 | } 37 | if (!config.logApiResponses && config.logApiResponses !== false) { 38 | config.logApiResponses = true; 39 | } 40 | 41 | return config; 42 | } 43 | 44 | function Store(config) { 45 | this.data = {}; 46 | this.namespaces = []; 47 | this._config = normalizeConfig(config); 48 | this._models = {}; 49 | this._scenarios = {}; 50 | this._serializers = {}; 51 | this.apiNamespace = config.apiNamespace || 'api'; 52 | 53 | // inject all the things 54 | Attr.prototype.store = this; 55 | One.prototype.store = this; 56 | Many.prototype.store = this; 57 | Namespace.prototype.store = this; 58 | 59 | var _store = this; 60 | var serializers = config._defs.serializers; 61 | // setup serializer 62 | if (serializers.length) { 63 | serializers.forEach(function(serializer) { 64 | var name = path.parse(serializer).name; 65 | _store._serializers[name] = serializer; 66 | }); 67 | } 68 | 69 | if (config.serializer) { 70 | softAssert("You specified a serializer in your config but no serializers were found.", serializers.length); 71 | var serializerPath = this._serializers[config.serializer]; 72 | 73 | softAssert("You specified a serializer in your config but that serializer was not found.", serializerPath); 74 | var UserSerializer = require(serializerPath); 75 | this.serializer = new UserSerializer(); 76 | } else { 77 | this.serializer = new Serializer(); 78 | } 79 | 80 | // setup models 81 | var models = config._defs.models; 82 | warn("Cannot mount endpoints, you have no models.", models.length); 83 | models.forEach(function(model) { 84 | var name = path.parse(model).name; 85 | _store._models[name] = model; 86 | 87 | _store.registerNamespace(name, require(model)); 88 | }); 89 | 90 | // setup scenarios 91 | var scenarios = config._defs.scenarios; 92 | scenarios.forEach(function(scenario) { 93 | var name = path.parse(scenario).name; 94 | _store._scenarios[name] = scenario; 95 | }); 96 | 97 | // seed 98 | var scenario; 99 | if (config.scenario) { 100 | scenario = this._scenarios[config.scenario]; 101 | if (models.length) { 102 | softAssert("You specified a scenario but it was not found.", scenario); 103 | } else { 104 | warn("You specified a scenario but it was not found.", scenario); 105 | } 106 | } else { 107 | scenario = this._scenarios['default']; 108 | if (models.length) { 109 | softAssert("You failed to specify a scenario and there is no default scenario to fallback to.", scenario); 110 | } else { 111 | warn("You failed to specify a scenario and there is no default scenario to fallback to.", scenario); 112 | } 113 | } 114 | 115 | if (scenario) { 116 | var scPath = path.join(scenario); 117 | require(scPath)(this); 118 | } 119 | } 120 | 121 | Store.prototype.registerNamespace = function registerNamespace(namespace, model) { 122 | this.namespaces.push(namespace); 123 | this.data[namespace] = new Namespace(namespace, model, this.serializer); 124 | }; 125 | 126 | Store.prototype.namespaceFor = function namespaceFor(namespace) { 127 | return this.data[namespace]; 128 | }; 129 | 130 | Store.prototype.findRecord = function findRecord(namespace, id) { 131 | return this.namespaceFor(namespace).findRecord(id); 132 | }; 133 | 134 | Store.prototype.createRecord = function createRecord(namespace, data) { 135 | return this.namespaceFor(namespace).createRecord(data); 136 | }; 137 | 138 | Store.prototype.findAll = function findAll(namespace) { 139 | return this.namespaceFor(namespace).findAll(); 140 | }; 141 | 142 | Store.prototype.query = function query(namespace, query) { 143 | return this.namespaceFor(namespace).query(query); 144 | }; 145 | 146 | Store.prototype.deleteRecord = function deleteRecord(namespace, id) { 147 | this.namespaceFor(namespace).deleteRecord(id); 148 | }; 149 | 150 | Store.prototype.updateRecord = function updateRecord(namespace, id, data) { 151 | this.namespaceFor(namespace).updateRecord(id, data); 152 | }; 153 | 154 | Store.prototype.seed = function seed(namespace, number) { 155 | return this.namespaceFor(namespace).seed(number); 156 | }; 157 | 158 | module.exports = Store; 159 | -------------------------------------------------------------------------------- /lib/utils/between.js: -------------------------------------------------------------------------------- 1 | module.exports = function between(min, max) { 2 | return Math.floor(Math.random() * (max + 1 - min) + min); 3 | }; 4 | -------------------------------------------------------------------------------- /lib/utils/pluralize.js: -------------------------------------------------------------------------------- 1 | function lastChar(s) { 2 | return s[s.length - 1]; 3 | } 4 | module.exports = function(str) { 5 | switch (lastChar(str)) { 6 | case 'x': 7 | case 'i': 8 | return str + 'es'; 9 | case 'y': 10 | return str.substr(0, str.length - 1) + 'ies'; 11 | default: 12 | return str + 's'; 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json-api-mock-server", 3 | "version": "0.1.2", 4 | "description": "A JSON-API mock server.", 5 | "repository": "git://github.com/runspired/json-api-mock-server.git", 6 | "directories": {}, 7 | "scripts": { 8 | "start": "node server" 9 | }, 10 | "engines": { 11 | "node": ">= 0.12.0" 12 | }, 13 | "author": "", 14 | "license": "MIT", 15 | "main": "./lib/index.js", 16 | "dependencies": { 17 | "body-parser": "^1.15.2", 18 | "chalk": "^1.1.1", 19 | "express": "^4.14.0", 20 | "glob": "^7.1.1", 21 | "jsonapi-validator": "^2.1.1", 22 | "object-assign": "^4.1.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | accepts@~1.3.3: 4 | version "1.3.3" 5 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca" 6 | dependencies: 7 | mime-types "~2.1.11" 8 | negotiator "0.6.1" 9 | 10 | ajv@^4.1.3: 11 | version "4.8.2" 12 | resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.8.2.tgz#65486936ca36fea39a1504332a78bebd5d447bdc" 13 | dependencies: 14 | co "^4.6.0" 15 | json-stable-stringify "^1.0.1" 16 | 17 | ansi-regex@^2.0.0: 18 | version "2.0.0" 19 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.0.0.tgz#c5061b6e0ef8a81775e50f5d66151bf6bf371107" 20 | 21 | ansi-styles@^2.2.1: 22 | version "2.2.1" 23 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" 24 | 25 | array-flatten@1.1.1: 26 | version "1.1.1" 27 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 28 | 29 | balanced-match@^0.4.1: 30 | version "0.4.2" 31 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" 32 | 33 | body-parser: 34 | version "1.15.2" 35 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.15.2.tgz#d7578cf4f1d11d5f6ea804cef35dc7a7ff6dae67" 36 | dependencies: 37 | bytes "2.4.0" 38 | content-type "~1.0.2" 39 | debug "~2.2.0" 40 | depd "~1.1.0" 41 | http-errors "~1.5.0" 42 | iconv-lite "0.4.13" 43 | on-finished "~2.3.0" 44 | qs "6.2.0" 45 | raw-body "~2.1.7" 46 | type-is "~1.6.13" 47 | 48 | brace-expansion@^1.0.0: 49 | version "1.1.6" 50 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.6.tgz#7197d7eaa9b87e648390ea61fc66c84427420df9" 51 | dependencies: 52 | balanced-match "^0.4.1" 53 | concat-map "0.0.1" 54 | 55 | builtin-modules@^1.0.0: 56 | version "1.1.1" 57 | resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" 58 | 59 | bytes@2.4.0: 60 | version "2.4.0" 61 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.4.0.tgz#7d97196f9d5baf7f6935e25985549edd2a6c2339" 62 | 63 | camelcase@^3.0.0: 64 | version "3.0.0" 65 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" 66 | 67 | chalk@^1.1.1: 68 | version "1.1.3" 69 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" 70 | dependencies: 71 | ansi-styles "^2.2.1" 72 | escape-string-regexp "^1.0.2" 73 | has-ansi "^2.0.0" 74 | strip-ansi "^3.0.0" 75 | supports-color "^2.0.0" 76 | 77 | cliui@^3.2.0: 78 | version "3.2.0" 79 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" 80 | dependencies: 81 | string-width "^1.0.1" 82 | strip-ansi "^3.0.1" 83 | wrap-ansi "^2.0.0" 84 | 85 | co@^4.6.0: 86 | version "4.6.0" 87 | resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" 88 | 89 | code-point-at@^1.0.0: 90 | version "1.0.1" 91 | resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.0.1.tgz#1104cd34f9b5b45d3eba88f1babc1924e1ce35fb" 92 | dependencies: 93 | number-is-nan "^1.0.0" 94 | 95 | concat-map@0.0.1: 96 | version "0.0.1" 97 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 98 | 99 | content-disposition@0.5.1: 100 | version "0.5.1" 101 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.1.tgz#87476c6a67c8daa87e32e87616df883ba7fb071b" 102 | 103 | content-type@~1.0.2: 104 | version "1.0.2" 105 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed" 106 | 107 | cookie-signature@1.0.6: 108 | version "1.0.6" 109 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 110 | 111 | cookie@0.3.1: 112 | version "0.3.1" 113 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" 114 | 115 | debug@~2.2.0: 116 | version "2.2.0" 117 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" 118 | dependencies: 119 | ms "0.7.1" 120 | 121 | decamelize@^1.1.1: 122 | version "1.2.0" 123 | resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" 124 | 125 | depd@~1.1.0: 126 | version "1.1.0" 127 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3" 128 | 129 | destroy@~1.0.4: 130 | version "1.0.4" 131 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" 132 | 133 | ee-first@1.1.1: 134 | version "1.1.1" 135 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 136 | 137 | encodeurl@~1.0.1: 138 | version "1.0.1" 139 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" 140 | 141 | error-ex@^1.2.0: 142 | version "1.3.0" 143 | resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.0.tgz#e67b43f3e82c96ea3a584ffee0b9fc3325d802d9" 144 | dependencies: 145 | is-arrayish "^0.2.1" 146 | 147 | escape-html@~1.0.3: 148 | version "1.0.3" 149 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 150 | 151 | escape-string-regexp@^1.0.2: 152 | version "1.0.5" 153 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 154 | 155 | etag@~1.7.0: 156 | version "1.7.0" 157 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.7.0.tgz#03d30b5f67dd6e632d2945d30d6652731a34d5d8" 158 | 159 | express@^4.14.0: 160 | version "4.14.0" 161 | resolved "https://registry.yarnpkg.com/express/-/express-4.14.0.tgz#c1ee3f42cdc891fb3dc650a8922d51ec847d0d66" 162 | dependencies: 163 | accepts "~1.3.3" 164 | array-flatten "1.1.1" 165 | content-disposition "0.5.1" 166 | content-type "~1.0.2" 167 | cookie "0.3.1" 168 | cookie-signature "1.0.6" 169 | debug "~2.2.0" 170 | depd "~1.1.0" 171 | encodeurl "~1.0.1" 172 | escape-html "~1.0.3" 173 | etag "~1.7.0" 174 | finalhandler "0.5.0" 175 | fresh "0.3.0" 176 | merge-descriptors "1.0.1" 177 | methods "~1.1.2" 178 | on-finished "~2.3.0" 179 | parseurl "~1.3.1" 180 | path-to-regexp "0.1.7" 181 | proxy-addr "~1.1.2" 182 | qs "6.2.0" 183 | range-parser "~1.2.0" 184 | send "0.14.1" 185 | serve-static "~1.11.1" 186 | type-is "~1.6.13" 187 | utils-merge "1.0.0" 188 | vary "~1.1.0" 189 | 190 | finalhandler@0.5.0: 191 | version "0.5.0" 192 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-0.5.0.tgz#e9508abece9b6dba871a6942a1d7911b91911ac7" 193 | dependencies: 194 | debug "~2.2.0" 195 | escape-html "~1.0.3" 196 | on-finished "~2.3.0" 197 | statuses "~1.3.0" 198 | unpipe "~1.0.0" 199 | 200 | find-up@^1.0.0: 201 | version "1.1.2" 202 | resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" 203 | dependencies: 204 | path-exists "^2.0.0" 205 | pinkie-promise "^2.0.0" 206 | 207 | forwarded@~0.1.0: 208 | version "0.1.0" 209 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.0.tgz#19ef9874c4ae1c297bcf078fde63a09b66a84363" 210 | 211 | fresh@0.3.0: 212 | version "0.3.0" 213 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.3.0.tgz#651f838e22424e7566de161d8358caa199f83d4f" 214 | 215 | fs.realpath@^1.0.0: 216 | version "1.0.0" 217 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 218 | 219 | get-caller-file@^1.0.1: 220 | version "1.0.2" 221 | resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" 222 | 223 | glob: 224 | version "7.1.1" 225 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" 226 | dependencies: 227 | fs.realpath "^1.0.0" 228 | inflight "^1.0.4" 229 | inherits "2" 230 | minimatch "^3.0.2" 231 | once "^1.3.0" 232 | path-is-absolute "^1.0.0" 233 | 234 | graceful-fs@^4.1.2: 235 | version "4.1.9" 236 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.9.tgz#baacba37d19d11f9d146d3578bc99958c3787e29" 237 | 238 | has-ansi@^2.0.0: 239 | version "2.0.0" 240 | resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" 241 | dependencies: 242 | ansi-regex "^2.0.0" 243 | 244 | hosted-git-info@^2.1.4: 245 | version "2.1.5" 246 | resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.1.5.tgz#0ba81d90da2e25ab34a332e6ec77936e1598118b" 247 | 248 | http-errors@~1.5.0: 249 | version "1.5.0" 250 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.5.0.tgz#b1cb3d8260fd8e2386cad3189045943372d48211" 251 | dependencies: 252 | inherits "2.0.1" 253 | setprototypeof "1.0.1" 254 | statuses ">= 1.3.0 < 2" 255 | 256 | iconv-lite@0.4.13: 257 | version "0.4.13" 258 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2" 259 | 260 | inflight@^1.0.4: 261 | version "1.0.6" 262 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 263 | dependencies: 264 | once "^1.3.0" 265 | wrappy "1" 266 | 267 | inherits@2: 268 | version "2.0.3" 269 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 270 | 271 | inherits@2.0.1: 272 | version "2.0.1" 273 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" 274 | 275 | invert-kv@^1.0.0: 276 | version "1.0.0" 277 | resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" 278 | 279 | ipaddr.js@1.1.1: 280 | version "1.1.1" 281 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.1.1.tgz#c791d95f52b29c1247d5df80ada39b8a73647230" 282 | 283 | is-arrayish@^0.2.1: 284 | version "0.2.1" 285 | resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" 286 | 287 | is-builtin-module@^1.0.0: 288 | version "1.0.0" 289 | resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" 290 | dependencies: 291 | builtin-modules "^1.0.0" 292 | 293 | is-fullwidth-code-point@^1.0.0: 294 | version "1.0.0" 295 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" 296 | dependencies: 297 | number-is-nan "^1.0.0" 298 | 299 | is-utf8@^0.2.0: 300 | version "0.2.1" 301 | resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" 302 | 303 | json-stable-stringify@^1.0.1: 304 | version "1.0.1" 305 | resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" 306 | dependencies: 307 | jsonify "~0.0.0" 308 | 309 | jsonapi-validator@^2.1.1: 310 | version "2.2.0" 311 | resolved "https://registry.yarnpkg.com/jsonapi-validator/-/jsonapi-validator-2.2.0.tgz#07f9bdb6d118c1e728fa77280605fa9b527f8c66" 312 | dependencies: 313 | ajv "^4.1.3" 314 | yargs "^5.0.0" 315 | 316 | jsonify@~0.0.0: 317 | version "0.0.0" 318 | resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" 319 | 320 | lcid@^1.0.0: 321 | version "1.0.0" 322 | resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" 323 | dependencies: 324 | invert-kv "^1.0.0" 325 | 326 | load-json-file@^1.0.0: 327 | version "1.1.0" 328 | resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" 329 | dependencies: 330 | graceful-fs "^4.1.2" 331 | parse-json "^2.2.0" 332 | pify "^2.0.0" 333 | pinkie-promise "^2.0.0" 334 | strip-bom "^2.0.0" 335 | 336 | lodash.assign@^4.1.0, lodash.assign@^4.2.0: 337 | version "4.2.0" 338 | resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" 339 | 340 | media-typer@0.3.0: 341 | version "0.3.0" 342 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 343 | 344 | merge-descriptors@1.0.1: 345 | version "1.0.1" 346 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 347 | 348 | methods@~1.1.2: 349 | version "1.1.2" 350 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 351 | 352 | mime-db@~1.24.0: 353 | version "1.24.0" 354 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.24.0.tgz#e2d13f939f0016c6e4e9ad25a8652f126c467f0c" 355 | 356 | mime-types@~2.1.11: 357 | version "2.1.12" 358 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.12.tgz#152ba256777020dd4663f54c2e7bc26381e71729" 359 | dependencies: 360 | mime-db "~1.24.0" 361 | 362 | mime@1.3.4: 363 | version "1.3.4" 364 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" 365 | 366 | minimatch@^3.0.2: 367 | version "3.0.3" 368 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774" 369 | dependencies: 370 | brace-expansion "^1.0.0" 371 | 372 | ms@0.7.1: 373 | version "0.7.1" 374 | resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" 375 | 376 | negotiator@0.6.1: 377 | version "0.6.1" 378 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" 379 | 380 | normalize-package-data@^2.3.2: 381 | version "2.3.5" 382 | resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.3.5.tgz#8d924f142960e1777e7ffe170543631cc7cb02df" 383 | dependencies: 384 | hosted-git-info "^2.1.4" 385 | is-builtin-module "^1.0.0" 386 | semver "2 || 3 || 4 || 5" 387 | validate-npm-package-license "^3.0.1" 388 | 389 | number-is-nan@^1.0.0: 390 | version "1.0.1" 391 | resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" 392 | 393 | object-assign: 394 | version "4.1.0" 395 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0" 396 | 397 | on-finished@~2.3.0: 398 | version "2.3.0" 399 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" 400 | dependencies: 401 | ee-first "1.1.1" 402 | 403 | once@^1.3.0: 404 | version "1.4.0" 405 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 406 | dependencies: 407 | wrappy "1" 408 | 409 | os-locale@^1.4.0: 410 | version "1.4.0" 411 | resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" 412 | dependencies: 413 | lcid "^1.0.0" 414 | 415 | parse-json@^2.2.0: 416 | version "2.2.0" 417 | resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" 418 | dependencies: 419 | error-ex "^1.2.0" 420 | 421 | parseurl@~1.3.1: 422 | version "1.3.1" 423 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56" 424 | 425 | path-exists@^2.0.0: 426 | version "2.1.0" 427 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" 428 | dependencies: 429 | pinkie-promise "^2.0.0" 430 | 431 | path-is-absolute@^1.0.0: 432 | version "1.0.1" 433 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 434 | 435 | path-to-regexp@0.1.7: 436 | version "0.1.7" 437 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 438 | 439 | path-type@^1.0.0: 440 | version "1.1.0" 441 | resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" 442 | dependencies: 443 | graceful-fs "^4.1.2" 444 | pify "^2.0.0" 445 | pinkie-promise "^2.0.0" 446 | 447 | pify@^2.0.0: 448 | version "2.3.0" 449 | resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" 450 | 451 | pinkie-promise@^2.0.0: 452 | version "2.0.1" 453 | resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" 454 | dependencies: 455 | pinkie "^2.0.0" 456 | 457 | pinkie@^2.0.0: 458 | version "2.0.4" 459 | resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" 460 | 461 | proxy-addr@~1.1.2: 462 | version "1.1.2" 463 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.2.tgz#b4cc5f22610d9535824c123aef9d3cf73c40ba37" 464 | dependencies: 465 | forwarded "~0.1.0" 466 | ipaddr.js "1.1.1" 467 | 468 | qs@6.2.0: 469 | version "6.2.0" 470 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.0.tgz#3b7848c03c2dece69a9522b0fae8c4126d745f3b" 471 | 472 | range-parser@~1.2.0: 473 | version "1.2.0" 474 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" 475 | 476 | raw-body@~2.1.7: 477 | version "2.1.7" 478 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.1.7.tgz#adfeace2e4fb3098058014d08c072dcc59758774" 479 | dependencies: 480 | bytes "2.4.0" 481 | iconv-lite "0.4.13" 482 | unpipe "1.0.0" 483 | 484 | read-pkg-up@^1.0.1: 485 | version "1.0.1" 486 | resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" 487 | dependencies: 488 | find-up "^1.0.0" 489 | read-pkg "^1.0.0" 490 | 491 | read-pkg@^1.0.0: 492 | version "1.1.0" 493 | resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" 494 | dependencies: 495 | load-json-file "^1.0.0" 496 | normalize-package-data "^2.3.2" 497 | path-type "^1.0.0" 498 | 499 | require-directory@^2.1.1: 500 | version "2.1.1" 501 | resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" 502 | 503 | require-main-filename@^1.0.1: 504 | version "1.0.1" 505 | resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" 506 | 507 | "semver@2 || 3 || 4 || 5": 508 | version "5.3.0" 509 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" 510 | 511 | send@0.14.1: 512 | version "0.14.1" 513 | resolved "https://registry.yarnpkg.com/send/-/send-0.14.1.tgz#a954984325392f51532a7760760e459598c89f7a" 514 | dependencies: 515 | debug "~2.2.0" 516 | depd "~1.1.0" 517 | destroy "~1.0.4" 518 | encodeurl "~1.0.1" 519 | escape-html "~1.0.3" 520 | etag "~1.7.0" 521 | fresh "0.3.0" 522 | http-errors "~1.5.0" 523 | mime "1.3.4" 524 | ms "0.7.1" 525 | on-finished "~2.3.0" 526 | range-parser "~1.2.0" 527 | statuses "~1.3.0" 528 | 529 | serve-static@~1.11.1: 530 | version "1.11.1" 531 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.11.1.tgz#d6cce7693505f733c759de57befc1af76c0f0805" 532 | dependencies: 533 | encodeurl "~1.0.1" 534 | escape-html "~1.0.3" 535 | parseurl "~1.3.1" 536 | send "0.14.1" 537 | 538 | set-blocking@^2.0.0: 539 | version "2.0.0" 540 | resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" 541 | 542 | setprototypeof@1.0.1: 543 | version "1.0.1" 544 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.1.tgz#52009b27888c4dc48f591949c0a8275834c1ca7e" 545 | 546 | spdx-correct@~1.0.0: 547 | version "1.0.2" 548 | resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" 549 | dependencies: 550 | spdx-license-ids "^1.0.2" 551 | 552 | spdx-expression-parse@~1.0.0: 553 | version "1.0.4" 554 | resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c" 555 | 556 | spdx-license-ids@^1.0.2: 557 | version "1.2.2" 558 | resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" 559 | 560 | "statuses@>= 1.3.0 < 2", statuses@~1.3.0: 561 | version "1.3.0" 562 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.0.tgz#8e55758cb20e7682c1f4fce8dcab30bf01d1e07a" 563 | 564 | string-width@^1.0.1, string-width@^1.0.2: 565 | version "1.0.2" 566 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" 567 | dependencies: 568 | code-point-at "^1.0.0" 569 | is-fullwidth-code-point "^1.0.0" 570 | strip-ansi "^3.0.0" 571 | 572 | strip-ansi@^3.0.0, strip-ansi@^3.0.1: 573 | version "3.0.1" 574 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" 575 | dependencies: 576 | ansi-regex "^2.0.0" 577 | 578 | strip-bom@^2.0.0: 579 | version "2.0.0" 580 | resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" 581 | dependencies: 582 | is-utf8 "^0.2.0" 583 | 584 | supports-color@^2.0.0: 585 | version "2.0.0" 586 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" 587 | 588 | type-is@~1.6.13: 589 | version "1.6.13" 590 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.13.tgz#6e83ba7bc30cd33a7bb0b7fb00737a2085bf9d08" 591 | dependencies: 592 | media-typer "0.3.0" 593 | mime-types "~2.1.11" 594 | 595 | unpipe@~1.0.0, unpipe@1.0.0: 596 | version "1.0.0" 597 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 598 | 599 | utils-merge@1.0.0: 600 | version "1.0.0" 601 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8" 602 | 603 | validate-npm-package-license@^3.0.1: 604 | version "3.0.1" 605 | resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" 606 | dependencies: 607 | spdx-correct "~1.0.0" 608 | spdx-expression-parse "~1.0.0" 609 | 610 | vary@~1.1.0: 611 | version "1.1.0" 612 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.0.tgz#e1e5affbbd16ae768dd2674394b9ad3022653140" 613 | 614 | which-module@^1.0.0: 615 | version "1.0.0" 616 | resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" 617 | 618 | window-size@^0.2.0: 619 | version "0.2.0" 620 | resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075" 621 | 622 | wrap-ansi@^2.0.0: 623 | version "2.0.0" 624 | resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.0.0.tgz#7d30f8f873f9a5bbc3a64dabc8d177e071ae426f" 625 | dependencies: 626 | string-width "^1.0.1" 627 | 628 | wrappy@1: 629 | version "1.0.2" 630 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 631 | 632 | y18n@^3.2.1: 633 | version "3.2.1" 634 | resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" 635 | 636 | yargs-parser@^3.2.0: 637 | version "3.2.0" 638 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-3.2.0.tgz#5081355d19d9d0c8c5d81ada908cb4e6d186664f" 639 | dependencies: 640 | camelcase "^3.0.0" 641 | lodash.assign "^4.1.0" 642 | 643 | yargs@^5.0.0: 644 | version "5.0.0" 645 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-5.0.0.tgz#3355144977d05757dbb86d6e38ec056123b3a66e" 646 | dependencies: 647 | cliui "^3.2.0" 648 | decamelize "^1.1.1" 649 | get-caller-file "^1.0.1" 650 | lodash.assign "^4.2.0" 651 | os-locale "^1.4.0" 652 | read-pkg-up "^1.0.1" 653 | require-directory "^2.1.1" 654 | require-main-filename "^1.0.1" 655 | set-blocking "^2.0.0" 656 | string-width "^1.0.2" 657 | which-module "^1.0.0" 658 | window-size "^0.2.0" 659 | y18n "^3.2.1" 660 | yargs-parser "^3.2.0" 661 | 662 | --------------------------------------------------------------------------------