├── .github └── workflows │ └── semgrep.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── lib ├── curl.js ├── errors.js ├── example-data-extractor.js ├── json.js ├── loader.js ├── object-definition.js ├── pointer.js └── transformer.js ├── package.json ├── test ├── curl.js ├── example-data-extractor.js ├── fixtures │ ├── regression.json │ ├── schema1.json │ └── schema2.json ├── mocha.opts ├── object-definition.js ├── pointer.js ├── regression.js └── transformer.js └── yarn.lock /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | 2 | on: 3 | pull_request: {} 4 | workflow_dispatch: {} 5 | push: 6 | branches: 7 | - main 8 | - master 9 | schedule: 10 | - cron: '0 0 * * *' 11 | name: Semgrep config 12 | jobs: 13 | semgrep: 14 | name: semgrep/ci 15 | runs-on: ubuntu-20.04 16 | env: 17 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} 18 | SEMGREP_URL: https://cloudflare.semgrep.dev 19 | SEMGREP_APP_URL: https://cloudflare.semgrep.dev 20 | SEMGREP_VERSION_CHECK_URL: https://cloudflare.semgrep.dev/api/check-version 21 | container: 22 | image: returntocorp/semgrep 23 | steps: 24 | - uses: actions/checkout@v3 25 | - run: semgrep ci 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | coverage 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'stable' 4 | script: 'npm run coverage' 5 | after_success: 6 | - npm install -g codeclimate-test-reporter 7 | - CODECLIMATE_REPO_TOKEN=e75b019bb6609508328989f7b3308215c888dfd780d492a0e1d821e2efd79f6d codeclimate-test-reporter < coverage/lcov.info 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v3.1.1 2 | 3 | * Properly handle multipart form curls with multiple parts. 4 | 5 | ## v3.1.0 6 | 7 | * Support added for "encType" and "mediaType" with raw string examples. 8 | 9 | ## v3.0.0 10 | 11 | * The non-standard usage of "rel": "self" has been changed to a new 12 | extension keyword, "cfRecurse": "". The "" is technically a plain 13 | (not URI fragment encoded) JSON Pointer (specifically the root pointer). 14 | Currently, only the root pointer is supported, keeping parity with 15 | the old "rel": "self" post-"$ref"-resolution recursion. 16 | 17 | * The non-standard "requestHeaders" and "private" fields have been 18 | namespaced as "cfRequestHeaders" and "cfPrivate" respectively. 19 | 20 | * Fields that start with "\_\_" are no longer treated as private. 21 | Use "cfPrivate" to flag private fields. 22 | 23 | * Two additional custom keywords have **not** been changed: 24 | "extraProperties" will get a more extensive reworking in the 25 | nearish future, and "example" becomes "examples" in the draft-06 26 | of JSON Schema so it seems pointlessly disruptive to put a "cf" on it. 27 | 28 | ## v2.0.0 29 | 30 | * `"required"` and `"type"` now behave normally with the LDO's `"schema"` (previously they needed to be outside of `"schema"` at the top of the LDO) 31 | * the nonstandard behavior of `"additionalProperties"` (properties that are not rolled up in the request/response examples) is now implemented as `"extraProperties"` 32 | * Properties named `"ID"` are no longer treated specially (downcased to `"id"`) as this was a workaround for a problem that no longer exists. 33 | 34 | ## v1.2.2 35 | 36 | * Correctly generate curl examples when `"requestHeaders"` is `{}` 37 | 38 | ## v1.2.1 39 | 40 | * Add `which_of` to the top level of `object_definition` to allow distinguishing between use of `"oneOf"` vs `"anyOf"`. This is a bit of a hack but will get fixed more thoroughly in a future major version. 41 | 42 | 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, CloudFlare. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSON Schema Example Loader (DEPRECATED) 2 | 3 | While still used by the likewise-deprecated `doca` package, for the new `@cloudflare/doca` 4 | package, this package has been replaced by `@cloudflare/json-schema-apidoc-loader` in 5 | the [json-schema-tools](https://github.com/cloudflare/json-schema-tools) repository. 6 | It relies on several general-purpose JSON Schema utility packages that are also present 7 | in that repository. 8 | 9 | Unlike `json-schema-example-loader`, the output of `@cloudflare/json-schema-apidoc-loader` is 10 | still a valid JSON Schema, with extensions. 11 | 12 | ------- 13 | 14 | This package is part of the [doca](https://github.com/cloudflare/doca) suite. Please file any issues at the [doca repository](https://github.com/cloudflare/doca/issues) 15 | ## Installation 16 | 17 | ``` 18 | npm install json-schema-example-loader --save 19 | ``` 20 | 21 | ## Description 22 | 23 | Webpack loader that transforms JSON HyperSchema (without $refs) into an updated datastructure that contains examples and simplified definitions that you can use in order to create nice API docs. Some original properties are removed and some new are precomputed and added (see bellow). 24 | 25 | Why is this a webpack loader and not part of the app? 26 | - JSON HyperSchema structure can be quite complex (deeply nested) 27 | - We precompute a flat datastructure that better fits our UI components 28 | - Everything can be nicely preformatted 29 | - **PERFORMANCE** 30 | 31 | Do you have references ($ref) in your schemas? Use [json-schema-loader](https://www.npmjs.com/package/json-schema-loader) first. 32 | 33 | ## Usage 34 | 35 | ```js 36 | var transformedSchema = require('json-schema-example-loader!./schema.json'); 37 | ``` 38 | 39 | Or define it in your `webpack.config.js` 40 | 41 | ```js 42 | module: { 43 | loaders: [{ 44 | test: /\.json$/, 45 | exclude: /node_modules/, 46 | loader: 'json-schema-example-loader' 47 | }] 48 | } 49 | ``` 50 | 51 | ```js 52 | var transformedSchema = require('./schema.json'); 53 | ``` 54 | 55 | ## Example Input: product.json 56 | 57 | ```json 58 | { 59 | "id": "https://api.example.com/product", 60 | "$schema": "http://json-schema.org/draft-04/hyper-schema#", 61 | "title": "Product", 62 | "description": "A product available for sale in a store", 63 | "type": "object", 64 | "definitions": { 65 | "identifier": { 66 | "type": "string", 67 | "description": "Product SKU", 68 | "example": "ABC-123" 69 | }, 70 | "name": { 71 | "type": "string", 72 | "description": "Product's name", 73 | "maxLength": 100 74 | }, 75 | "description": { 76 | "type": "string", 77 | "description": "Product's description", 78 | "maxLength": 2000 79 | } 80 | }, 81 | "required": [ 82 | "id", 83 | "name", 84 | "description" 85 | ], 86 | "properties": { 87 | "id": { 88 | "type": "string", 89 | "description": "Product SKU", 90 | "example": "ABC-123" 91 | }, 92 | "name": { 93 | "type": "string", 94 | "description": "Product's name", 95 | "maxLength": 100 96 | }, 97 | "description": { 98 | "type": "string", 99 | "description": "Product's description", 100 | "maxLength": 2000 101 | } 102 | }, 103 | "links": [ 104 | { 105 | "title": "Available products", 106 | "description": "Get all available product for the store", 107 | "rel": "instances", 108 | "href": "/products", 109 | "method": "GET", 110 | "schema": { 111 | "type": "object", 112 | "properties": { 113 | "page": { 114 | "type": "integer", 115 | "description": "Current page of products", 116 | "example": 1, 117 | "default": 1 118 | } 119 | } 120 | }, 121 | "targetSchema": { 122 | "type": "array", 123 | "items": { 124 | "rel": "self" 125 | } 126 | } 127 | }, 128 | { 129 | "title": "Product info", 130 | "description": "Get a single product", 131 | "rel": "self", 132 | "href": "/products/{#/definitions/identifier}", 133 | "method": "GET", 134 | "targetSchema": { 135 | "rel": "self" 136 | } 137 | } 138 | ] 139 | } 140 | ``` 141 | 142 | ## Example Output 143 | 144 | ```json 145 | { 146 | "id": "https://api.example.com/product", 147 | "title": "Product", 148 | "description": "A product available for sale in a store", 149 | "type": "object", 150 | "links": [ 151 | { 152 | "title": "Available products", 153 | "description": "Get all available product for the store", 154 | "rel": "instances", 155 | "href": "/products", 156 | "method": "GET", 157 | "html_id": "product-available-products", 158 | "uri": "/products", 159 | "curl": "curl -X GET \"/products?page=1\" \\\n", 160 | "parameters": { 161 | "_formatter": {}, 162 | "all_props": { 163 | "page": { 164 | "type": "integer", 165 | "example": "1", 166 | "description": "Current page of products", 167 | "default": 1 168 | } 169 | }, 170 | "required_props": [], 171 | "optional_props": [ 172 | "page" 173 | ], 174 | "objects": [], 175 | "example": "{\n \"page\": 1\n}" 176 | }, 177 | "response": "{}" 178 | }, 179 | { 180 | "title": "Product info", 181 | "description": "Get a single product", 182 | "rel": "self", 183 | "href": "/products/{#/definitions/identifier}", 184 | "method": "GET", 185 | "html_id": "product-product-info", 186 | "uri": "/products/:identifier", 187 | "curl": "curl -X GET \"/products/ABC-123\" \\\n", 188 | "response": "{\n \"id\": \"ABC-123\"\n}" 189 | } 190 | ], 191 | "html_id": "product", 192 | "object_definition": { 193 | "_formatter": {}, 194 | "all_props": { 195 | "id": { 196 | "type": "string", 197 | "example": "\"ABC-123\"", 198 | "description": "Product SKU" 199 | }, 200 | "name": { 201 | "type": "string", 202 | "description": "Product's name", 203 | "maxLength": 100 204 | }, 205 | "description": { 206 | "type": "string", 207 | "description": "Product's description", 208 | "maxLength": 2000 209 | } 210 | }, 211 | "required_props": [ 212 | "id", 213 | "name", 214 | "description" 215 | ], 216 | "optional_props": [], 217 | "objects": [], 218 | "example": "{\n \"id\": \"ABC-123\"\n}", 219 | "title": "Product", 220 | "description": "A product available for sale in a store" 221 | } 222 | } 223 | ``` 224 | 225 | ## Transformations made 226 | 227 | As you can see, some properties are missing and some are added/updated. Removed properties are typically used in order to compute new properties and they are not needed anymore. Since we want to minimize the ouput as much as possible they are stripped. This happens on two levels: 228 | 229 | - root - schema root 230 | - links - array of links 231 | 232 | ### Removed properties 233 | 234 | **At the root level:** 235 | 236 | - properties 237 | - additionalProperties 238 | - definitions 239 | - allOf 240 | - anyOf 241 | - oneOf 242 | - required 243 | - $schema 244 | 245 | **At the link level:** 246 | 247 | - schema 248 | - targetSchema 249 | 250 | ### New (precomputed) properties 251 | 252 | **At the root level:** 253 | 254 | - html_id : *string* - URL friendly schema id 255 | - object_definition : *object* 256 | - all_props : *object* - all required properties (object where keys = prop names) 257 | - required_props : *array* - list of keys in all_props 258 | - optional_props : *array* - list of keys in all_props 259 | - objects : *array* - nested object_definition (in case when oneOf/anyOf are used) 260 | - which_of : *string* - the name of the property used for objects, if any 261 | - example : *string* - stringified example of the whole schema object 262 | 263 | **At the link level:** 264 | 265 | - html_id : *string* - URL friendly schema + link id 266 | - uri : *string* - link uri (resolved href) 267 | - curl : *string* - curl example 268 | - parameters : *object* 269 | - all_props : *object* - all required properties (object where keys = prop names) 270 | - required_props : *array* - list of keys in all_props 271 | - optional_props : *array* - list of keys in all_props 272 | - objects : *array* - nested parameters (in case when oneOf/anyOf/allOf are used) 273 | - example : *string* - stringified example of request parameters 274 | - response : *string* - response example, based on link/targetSchema 275 | 276 | ### Your custom properties 277 | 278 | All custom properties that you add to the schema root or link object will be preserved. For example, you might want to set a flag "deprecated" to some of the links (endpoints) and write condition in your UI component. 279 | 280 | ## Link curl customization 281 | 282 | Link Curl examples can be further customized (enriched) with baseUrl and optional request headers. This can be done through a query parameter that is accepted by this loader. 283 | 284 | ### Usage 285 | 286 | Notice: It includes json-schema-loader (use matching major version) in a chain. 287 | 288 | ```js 289 | module: { 290 | loaders: [{ 291 | test: /\.json$/, 292 | loader: `json-schema-example?${JSON.stringify(config)}!json-schema`, 293 | }], 294 | }, 295 | ``` 296 | 297 | Where `config.curl.requestHeaders` is a constant following JSON Schema format. 298 | 299 | ```js 300 | const config = { 301 | curl: { 302 | baseUrl: 'https://api.example.com/v1', 303 | requestHeaders: { 304 | required: [ 305 | 'Content-Type', 306 | 'X-Auth-Email', 307 | 'X-Auth-Key', 308 | ], 309 | properties: { 310 | 'X-Auth-Email': { 311 | type: 'string', 312 | description: 'Your email', 313 | example: 'user@example.com', 314 | }, 315 | 'X-Auth-Key': { 316 | type: 'string', 317 | length: 45, 318 | description: 'Your API key', 319 | example: 'c3447eb745079oiu9320b638f5e225cf483cc5cfdda41', 320 | }, 321 | 'Content-Type': { 322 | type: 'string', 323 | enum: [ 324 | 'application/json', 325 | ], 326 | example: 'application/json', 327 | description: 'Content type of the API request', 328 | }, 329 | }, 330 | }, 331 | }, 332 | }; 333 | ``` 334 | 335 | ### Result (product.json) 336 | 337 | ```js 338 | // ... 339 | "links": [ 340 | { 341 | "title": "Available products", 342 | "description": "Get all available product for the store", 343 | "rel": "instances", 344 | "href": "/products", 345 | "method": "GET", 346 | "html_id": "product-available-products", 347 | "uri": "/products", 348 | "curl": "curl -X GET \"https://api.example.com/v1/products?page=1\" \\\n -H \"X-Auth-Email: user@example.com\" \\\n -H \"X-Auth-Key: c3447eb745079oiu9320b638f5e225cf483cc5cfdda41\" \\\n -H \"Content-Type: application/json\"", 349 | "parameters": { 350 | "_formatter": {}, 351 | "all_props": { 352 | "page": { 353 | "type": "integer", 354 | "example": "1", 355 | "description": "Current page of products", 356 | "default": 1 357 | } 358 | }, 359 | "required_props": [], 360 | "optional_props": [ 361 | "page" 362 | ], 363 | "objects": [], 364 | "example": "{\n \"page\": 1\n}" 365 | }, 366 | "response": "{}" 367 | }, 368 | { 369 | "title": "Product info", 370 | "description": "Get a single product", 371 | "rel": "self", 372 | "href": "/products/{#/definitions/identifier}", 373 | "method": "GET", 374 | "html_id": "product-product-info", 375 | "uri": "/products/:identifier", 376 | "curl": "curl -X GET \"https://api.example.com/v1/products/ABC-123\" \\\n -H \"X-Auth-Email: user@example.com\" \\\n -H \"X-Auth-Key: c3447eb745079oiu9320b638f5e225cf483cc5cfdda41\" \\\n -H \"Content-Type: application/json\"", 377 | "response": "{\n \"id\": \"ABC-123\"\n}" 378 | } 379 | ], 380 | // ... 381 | ``` 382 | 383 | 384 | 385 | -------------------------------------------------------------------------------- /lib/curl.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | 5 | module.exports = { 6 | 7 | formatter: require('./json'), 8 | 9 | config: { 10 | HEADER_SEPARATOR: ': ', 11 | NEW_LINE: ' \\\n' 12 | }, 13 | 14 | /** 15 | * Build a cURL string 16 | * 17 | * @param {String} uri 18 | * @param {String} [method=GET] 19 | * @param {Object} [headers] 20 | * @param {Object|Array} [data] 21 | * @param {String} [encType=undefined] 22 | * @returns {String} 23 | */ 24 | generate: function(uri, method, headers, data, encType) { 25 | var config = this.config; 26 | var flags = []; 27 | var str; 28 | 29 | method = method || 'GET'; 30 | 31 | if (data && method.toLowerCase() === 'get') { 32 | uri += this.buildQueryString(data); 33 | } 34 | 35 | str = ['curl', this.buildFlag('X', method.toUpperCase(), 0, ''), '"' + uri + '"'].join(' '); 36 | 37 | if (headers) { 38 | _.each(headers, function(val, header) { 39 | flags.push(this.buildFlag('H', header + config.HEADER_SEPARATOR + val, 5)); 40 | }, this); 41 | } 42 | 43 | if (data && method.toLowerCase() !== 'get') { 44 | if (encType === undefined || encType.match(this.formatter.jsonMediaType)) { 45 | flags.push(this.buildFlag('-data', this.formatData(data), 5, '\'')); 46 | } else if (encType === 'multipart/form-data') { 47 | flags.push.apply(flags, this.formatForms(data, 5, '\'')); 48 | } else { 49 | // The non-JSON, non-multipart data is a raw string. Do not format it. 50 | flags.push(this.buildFlag('-data', data)); 51 | } 52 | } 53 | 54 | if (flags.length) { 55 | return str + config.NEW_LINE + flags.join(config.NEW_LINE); 56 | } 57 | return str; 58 | }, 59 | 60 | /** 61 | * @param {mixed} data 62 | * @returns {String} 63 | */ 64 | formatData: function(data) { 65 | return this.formatter.format(data, null, 0); 66 | }, 67 | 68 | /** 69 | * @param {Object} data 70 | * @param {Number} indents 71 | * @param {String} [quoteType=\"] 72 | * @returns {Array} 73 | */ 74 | formatForms: function(data, indents, quoteType) { 75 | var buildFlag = this.buildFlag; 76 | return Object.keys(data).map(function(key) { 77 | return buildFlag('-form', `${key}=${data[key]}`, indents, quoteType) 78 | }) 79 | }, 80 | 81 | /** 82 | * @param {String} type 83 | * @param {String} value 84 | * @param {Number} indents 85 | * @param {String} [quoteType=\"] 86 | * @returns {String} 87 | */ 88 | buildFlag: function(type, value, indents, quoteType) { 89 | quoteType = !_.isUndefined(quoteType) ? quoteType : '"'; 90 | return [_.repeat(' ', indents) + '-', type, ' ', quoteType, value, quoteType].join(''); 91 | }, 92 | 93 | /** 94 | * 95 | * @param data 96 | * @param {Boolean} [noQueryString=true] 97 | * @returns {String} 98 | */ 99 | buildQueryString: function(data, noQueryString) { 100 | var firstJoin = noQueryString ? '&' : '?'; 101 | return _.reduce(data, function (str, val, key) { 102 | var conn = (str === firstJoin) ? '' : '&'; 103 | return str + conn + key + '=' + val; 104 | }, firstJoin); 105 | } 106 | }; 107 | -------------------------------------------------------------------------------- /lib/errors.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var throwError = function(e, msg) { 4 | var err = new Error(msg + ': ' + e.message); 5 | err.stack = e.stack; 6 | throw err; 7 | }; 8 | 9 | module.exports = { 10 | throwError: throwError, 11 | INVALID_RECURSE_TARGET: 12 | '"cfRecurse" currently only supports "" (the empty string) as a target' 13 | }; 14 | -------------------------------------------------------------------------------- /lib/example-data-extractor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const errors = require('./errors'); 4 | 5 | 6 | var _ = require('lodash'); 7 | /** 8 | * @class ExampleDataExtractor 9 | * @constructor 10 | */ 11 | var ExampleDataExtractor = function() {}; 12 | 13 | /** 14 | * Recursively build an object from a given schema component that is an example 15 | * representation of the object defined by the schema. 16 | * 17 | * @param {Object} component - valid subschema of the root/parent 18 | * @param {Object} root - parent schema used as the base 19 | * @returns {Object} 20 | */ 21 | ExampleDataExtractor.prototype.extract = function(component, root) { 22 | var reduced = {}; 23 | if (!component) { 24 | throw new ReferenceError('No schema received to generate example data'); 25 | } 26 | 27 | // If the schema defines an ID, change scope so all local references as resolved 28 | // relative to the schema with the closest ID 29 | if (component.id) { 30 | root = component; 31 | } 32 | 33 | if (component.allOf) { 34 | // Recursively extend/overwrite the reduced value. 35 | _.reduce(component.allOf, function(accumulator, subschema) { 36 | return _.extend(accumulator, this.extract(subschema, root)); 37 | }, reduced, this); 38 | } else if (component.oneOf) { 39 | // Select the first item to build an example object from 40 | reduced = this.extract(component.oneOf[0], root); 41 | } else if (component.anyOf) { 42 | // Select the first item to build an example object from 43 | reduced = this.extract(component.anyOf[0], root); 44 | } else if (component.cfRecurse !== undefined) { 45 | // Special case where the component is referencing the context schema. 46 | if (component.cfRecurse !== '') { 47 | throw new ReferenceError(errors.RECURSE_TARGET); 48 | } 49 | reduced = this.extract(root, root); 50 | } else if (component.properties) { 51 | reduced = this.mapPropertiesToExamples(component.properties, root); 52 | } else if (component.type && component.type === "array" ) { 53 | var minItems = component.minItems || 1; 54 | var maxItems = component.maxItems || 1; 55 | reduced = []; 56 | _.range(_.random(minItems, maxItems)).forEach(function(i) { 57 | reduced.push( this.extract(component.items, root) ); 58 | }.bind(this)); 59 | 60 | } else if (component.example) { 61 | // If we do not recognize the type of component, see if it has pre-set 62 | // example defined. This is particularly useful for link schemas 63 | // with non-JSON link payloads (e.g. "mediaType" and "encType" keywords), 64 | // as it is the only way to produce a top-level string example. 65 | reduced = component.example; 66 | } 67 | 68 | // Optionally merge in extra properties 69 | // @TODO: Determine if this is the right thing to do 70 | if (_.has(component, 'extraProperties') && _.get(component, 'generator.includeExtraProperties')) { 71 | _.extend(reduced, this.mapPropertiesToExamples(component.extraProperties, root)); 72 | } 73 | 74 | return reduced; 75 | }; 76 | 77 | /** 78 | * Maps a `properties` definition to an object containing example values 79 | * 80 | * `{attribute1: {type: 'string', example: 'example value'}}` -> 81 | * `{attribute1: 'example value'}` 82 | * 83 | * @param {Object} props - Properties definition object 84 | * @param {Object} schema - Root schema containing the properties 85 | * @returns {*} 86 | */ 87 | ExampleDataExtractor.prototype.mapPropertiesToExamples = function(props, schema) { 88 | return _.transform(props, function(properties, propConfig, propName) { 89 | // Allow opt-ing out of generating example data 90 | if (propConfig.cfPrivate) { 91 | return properties; 92 | } 93 | 94 | var example = this.getExampleDataFromItem(propConfig); 95 | 96 | if (propConfig.cfRecurse !== undefined) { 97 | if (propConfig.cfRecurse !== '') { 98 | throw new ReferenceError(errors.RECURSE_TARGET); 99 | } 100 | example = this.extract(schema, schema); 101 | } else if (propConfig.type === 'array' && propConfig.items && !example) { 102 | if (propConfig.items.example) { 103 | example = [propConfig.items.example]; 104 | } else { 105 | example = [this.extract(propConfig.items, schema)]; 106 | } 107 | } else if (propConfig.id && !example) { 108 | example = this.extract(propConfig, propConfig); 109 | } else if (propConfig.properties) { 110 | example = this.mapPropertiesToExamples(propConfig.properties, schema); 111 | } else if (propConfig.oneOf || propConfig.anyOf) { 112 | example = this.extract(propConfig, schema); 113 | } else if (propConfig.allOf) { 114 | example = _.reduce(propConfig.allOf, function(accumulator, item) { 115 | return _.extend(accumulator, this.extract(item, schema)); 116 | }, example || {}, this); 117 | } 118 | properties[propName] = example; 119 | }, {}, this); 120 | }; 121 | 122 | /** 123 | * @param {Object} reference 124 | * @returns {String} 125 | */ 126 | ExampleDataExtractor.prototype.getExampleDataFromItem = function(reference) { 127 | if (!_.isPlainObject(reference)) { 128 | return 'unknown'; 129 | } 130 | return _.has(reference, 'example') ? reference.example : reference.default; 131 | }; 132 | 133 | /** 134 | * @module lib/example-data-extractor 135 | * @type {ExampleDataExtractor} 136 | */ 137 | module.exports = new ExampleDataExtractor(); 138 | -------------------------------------------------------------------------------- /lib/json.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | // JSON stringify parameters 5 | var DEFAULTS = { 6 | replacer: undefined, 7 | space: 2 8 | }; 9 | 10 | /** 11 | * @class JSONFormatter 12 | * @module lib/formatters/json 13 | * @type {{format: Function}} 14 | * @type {{jsonMediaType: RegExp}} 15 | */ 16 | module.exports = { 17 | /** 18 | * @param {mixed} data 19 | * @param {Function} [replacer] 20 | * @param {Number} [space] 21 | * @returns {string} 22 | */ 23 | format: function(data, replacer, space) { 24 | replacer = !_.isUndefined(replacer) ? replacer : DEFAULTS.replacer; 25 | space = !_.isUndefined(space) ? space : DEFAULTS.space; 26 | return JSON.stringify(data, replacer, space); 27 | }, 28 | jsonMediaType: /^application\/(.*\+)?json$/ 29 | }; 30 | -------------------------------------------------------------------------------- /lib/loader.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var loaderUtils = require('loader-utils'); 3 | var transformer = require('./transformer'); 4 | 5 | module.exports = function(contentString, contentJs) { 6 | this.cacheable && this.cacheable(); 7 | var options = loaderUtils.parseQuery(this.query); 8 | var transformeredSchema = transformer.transformSchema(contentJs, options); 9 | var json = JSON.stringify(transformeredSchema, null, 2); 10 | this.value = [transformeredSchema]; 11 | return 'module.exports = ' + json + ';'; 12 | } 13 | -------------------------------------------------------------------------------- /lib/object-definition.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var exampleExtractor = require('./example-data-extractor'); 4 | var JSONformatter = require('./json'); 5 | var _ = require('lodash'); 6 | 7 | /** 8 | * @param {Object} object 9 | * @param {Object} options 10 | * @param {Object} [options.formatter=JSONFormatter]- something that implements `.format(data)` 11 | * @constructor 12 | */ 13 | var ObjectDefinition = function(object, options) { 14 | options = options || {}; 15 | this._formatter = options.formatter || JSONformatter; 16 | _.extend(this, this.build(object)); 17 | }; 18 | 19 | /** 20 | * The entrance method for building a full object definition 21 | * 22 | * @param {Object} object 23 | * @returns {{ 24 | * all_props: {}, 25 | * required_props: Array, 26 | * optional_props: Array, 27 | * objects: Array, 28 | * which_of: string, 29 | * example: string 30 | * }} 31 | */ 32 | ObjectDefinition.prototype.build = function(object) { 33 | var required = object.required || []; 34 | var self = { 35 | // A map of properties defined by the object, if oneOf/anyOf is not defined 36 | all_props: {}, 37 | // All required properties 38 | required_props: [], 39 | // Anything that isn't required, keys in all_props 40 | optional_props: [], 41 | // Nested definition objects for oneOf/anyOf cases, keys in all_props 42 | objects: [], 43 | // Stringified example of the object 44 | example: '' 45 | } 46 | 47 | if (object.type === 'array') { 48 | object = object.items; 49 | } 50 | 51 | if (_.isArray(object.allOf)) { 52 | _.each(object.allOf, function(schema) { 53 | // Deep extend all properties 54 | _.merge(self, this.build(schema), function(a, b) { 55 | if (_.isArray(a)) { 56 | return a.concat(b); 57 | } 58 | }); 59 | }, this); 60 | 61 | } else if (_.isArray(object.oneOf) || _.isArray(object.anyOf)) { 62 | var objects = object.oneOf || object.anyOf; 63 | self.objects = _.map(objects, this.build, this); 64 | self.which_of = object.oneOf ? 'oneOf' : 'anyOf' 65 | 66 | } else if (_.isPlainObject(object.properties)) { 67 | _.extend(self.all_props, this.defineProperties(object.properties)); 68 | 69 | if (_.isPlainObject(object.extraProperties)) { 70 | _.extend(self.all_props, this.defineProperties(object.extraProperties)); 71 | } 72 | } 73 | 74 | // Allow oneOf/anyOf/allOf reference to also include extra properties 75 | if (_.isPlainObject(object.extraProperties)) { 76 | var addtlProps = this.defineProperties(object.extraProperties); 77 | _.each(self.objects, function(obj) { 78 | _.extend(obj.all_props, addtlProps); 79 | }); 80 | } 81 | 82 | self.title = object.title; 83 | self.description = object.description; 84 | self.required_props = required; 85 | self.optional_props = _.difference(_.keys(self.all_props), required); 86 | 87 | try { 88 | self.example = this._formatter.format(exampleExtractor.extract(object)); 89 | } catch (e) { 90 | throw new Error('Error preparing data for object: ' + JSON.stringify(object)); 91 | } 92 | 93 | return self; 94 | }; 95 | 96 | /** 97 | * Expects to receive an object of properties, where the key is the property name 98 | * and the value is the definition of the property 99 | * 100 | * @param {Object} properties 101 | * @returns {Object} 102 | */ 103 | ObjectDefinition.prototype.defineProperties = function(properties) { 104 | return _.mapValues(properties, this.defineProperty, this); 105 | }; 106 | 107 | /** 108 | * Clean up the definition by generating an example value (stringified), 109 | * and following other schema directives. 110 | * 111 | * @param {Object} property 112 | * @returns {Object} 113 | */ 114 | ObjectDefinition.prototype.defineProperty = function(property) { 115 | var definition = {}; 116 | definition.type = property.type; 117 | 118 | // Stringify the example 119 | definition.example = this.getExampleFromProperty(property); 120 | 121 | // If a definition is pointed to another schema that is an `allOf` reference, 122 | // resolve it so the statements below will catch `definition.properties` 123 | if (property.allOf) { 124 | definition.properties = this.build(property).all_props; 125 | 126 | // If an attribute can be multiple types, store each parameter object 127 | // under its appropriate type 128 | } else if (property.oneOf || property.anyOf) { 129 | var key = property.oneOf ? 'oneOf' : 'anyOf'; 130 | definition[key] = _.map(property.oneOf || property.anyOf, this.build, this); 131 | 132 | // If the property value is an object and has its own properties, 133 | // make them available to the definition 134 | } else if (property.properties) { 135 | definition.properties = this.defineProperties(property.properties); 136 | } 137 | 138 | return _.defaults(definition, property); 139 | }; 140 | 141 | /** 142 | * 143 | * @param property 144 | * @return {String} 145 | */ 146 | ObjectDefinition.prototype.getExampleFromProperty = function(property) { 147 | var extracted = exampleExtractor.mapPropertiesToExamples({ 148 | prop: property 149 | }); 150 | // Stringify the example 151 | return this._formatter.format(extracted.prop); 152 | } 153 | 154 | /** 155 | * @class ObjectDefinition 156 | * @module lib/object-definition 157 | * @type {Function} 158 | */ 159 | module.exports = ObjectDefinition; 160 | -------------------------------------------------------------------------------- /lib/pointer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * JSON Pointer implementation 3 | * Modified from: Alexey Kuzmin 4 | * see: http://tools.ietf.org/html/rfc6901 5 | * 6 | * @module lib/pointer 7 | */ 8 | 'use strict'; 9 | 10 | var _ = require('lodash'); 11 | var pointer = {}; 12 | // A list of special characters and their escape sequences. 13 | // Special characters will be unescaped in order they are listed. 14 | // Section 3 of spec. 15 | var specialChars = [ 16 | ['/', '~1'], 17 | ['~', '~0'] 18 | ]; 19 | // Token separator in JSON pointer string. Section 3 of spec. 20 | var tokenSeparator = '/'; 21 | // Validates a pointer string. 22 | var validPointerRegex = /(\/[^\/]*)+/; 23 | // Possible errors during parsing 24 | var ErrorMessage = { 25 | HYPHEN_IS_NOT_SUPPORTED_IN_ARRAY_CONTEXT: 'Implementation does not support "-" token for arrays.', 26 | INVALID_DOCUMENT: 'JSON document is not valid.', 27 | INVALID_DOCUMENT_TYPE: 'JSON document must be a string or object.', 28 | INVALID_POINTER: 'Pointer is not valid.', 29 | NON_NUMBER_TOKEN_IN_ARRAY_CONTEXT: 'Non-number tokens cannot be used in array context.', 30 | TOKEN_WITH_LEADING_ZERO_IN_ARRAY_CONTEXT: 'Token with leading zero cannot be used in array context.' 31 | }; 32 | 33 | /** 34 | * Returns function that takes JSON Pointer as single argument 35 | * and evaluates it in given |target| context. 36 | * Returned function throws an exception if pointer is not valid 37 | * or any error occurs during evaluation. 38 | * 39 | * @param {Object} target Evaluation target. 40 | * @returns {Function} 41 | */ 42 | function createPointerEvaluator(target) { 43 | // Use cache to store already received values. 44 | var cache = {}; 45 | 46 | return function(pointer) { 47 | if (!isValidJSONPointer(pointer)) { 48 | // If it's not, an exception will be thrown. 49 | throw new ReferenceError(ErrorMessage.INVALID_POINTER); 50 | } 51 | 52 | // First, look up in the cache. 53 | if (cache.hasOwnProperty(pointer)) { 54 | // If cache entry exists, return it's value. 55 | return cache[pointer]; 56 | } 57 | 58 | // Now, when all arguments are valid, we can start evaluation. 59 | // First of all, let's convert JSON pointer string to tokens list. 60 | var tokensList = parsePointer(pointer); 61 | var token; 62 | var value = target; 63 | 64 | // Evaluation will be continued till tokens list is not empty 65 | // and returned value is not an undefined. 66 | while (!_.isUndefined(value) && !_.isUndefined(token = tokensList.pop())) { 67 | // Evaluate the token in current context. 68 | // `getValue()` might throw an exception, but we won't handle it. 69 | value = getValue(value, token); 70 | } 71 | 72 | // Pointer evaluation is done, save value in the cache and return it. 73 | cache[pointer] = value; 74 | return value; 75 | }; 76 | } 77 | 78 | 79 | /** 80 | * Validates JSON pointer string. 81 | * 82 | * @param pointer 83 | * @returns {boolean} 84 | */ 85 | function isValidJSONPointer(pointer) { 86 | if (!_.isString(pointer)) { 87 | // If it's not a string, it obviously is not valid. 88 | return false; 89 | } 90 | 91 | // If it is string and is an empty string, it's valid. 92 | if ('' === pointer) { 93 | return true; 94 | } 95 | 96 | // If it is non-empty string, it must match spec defined format. 97 | // Check Section 3 of specification for concrete syntax. 98 | return validPointerRegex.test(pointer); 99 | } 100 | 101 | 102 | /** 103 | * Returns tokens list for given |pointer|. List is reversed, e.g. 104 | * '/simple/path' -> ['path', 'simple'] 105 | * 106 | * @param {String} pointer JSON pointer string. 107 | * @returns {Array} List of tokens. 108 | */ 109 | function parsePointer(pointer) { 110 | // Let's split pointer string by tokens' separator character. 111 | // Also we will reverse resulting array to simplify it's further usage. 112 | var tokens = pointer.split(tokenSeparator).reverse(); 113 | // Last item in resulting array is always an empty string, we don't need it. 114 | tokens.pop(); 115 | // Now tokens' array is ready to use 116 | return tokens; 117 | } 118 | 119 | 120 | /** 121 | * Decodes all escape sequences in given |rawReferenceToken|. 122 | * 123 | * @param {String} rawReferenceToken 124 | * @returns {string} Unescaped reference token. 125 | */ 126 | function unescapeReferenceToken(rawReferenceToken) { 127 | // Unescapes reference token. See Section 3 of specification. 128 | var referenceToken = rawReferenceToken; 129 | var character; 130 | var escapeSequence; 131 | var replaceRegExp; 132 | 133 | // Order of unescaping does matter. 134 | // That's why an array is used here and not hash. 135 | specialChars.forEach(function(pair) { 136 | character = pair[0]; 137 | escapeSequence = pair[1]; 138 | replaceRegExp = new RegExp(escapeSequence, 'g'); 139 | referenceToken = referenceToken.replace(replaceRegExp, character); 140 | }); 141 | 142 | return referenceToken; 143 | } 144 | 145 | 146 | /** 147 | * Returns value pointed by |token| in evaluation |context|. 148 | * Throws an exception if any error occurs. 149 | * 150 | * @param {Array|Object} context Current evaluation context. 151 | * @param {String} token Unescaped reference token. 152 | * @returns {*} Some value or undefined if value if not found. 153 | */ 154 | function getValue(context, token) { 155 | // Reference token evaluation. See Section 4 of spec. 156 | // First of all we should unescape all special characters in token. 157 | token = unescapeReferenceToken(token); 158 | // Further actions depend of context of evaluation. 159 | 160 | // In array context there are more strict requirements for token value. 161 | if (_.isArray(context)) { 162 | if ('-' === token) { 163 | // Token cannot be a "-" character, 164 | // it has no sense in current implementation. 165 | throw new SyntaxError(ErrorMessage.HYPHEN_IS_NOT_SUPPORTED_IN_ARRAY_CONTEXT); 166 | } 167 | if (_.isNaN(+token)) { 168 | // Token cannot be non-number. 169 | throw new ReferenceError(ErrorMessage.NON_NUMBER_TOKEN_IN_ARRAY_CONTEXT); 170 | } 171 | if (token.length > 1 && '0' === token[0]) { 172 | // Token cannot be non-zero number with leading zero. 173 | throw new ReferenceError(ErrorMessage.TOKEN_WITH_LEADING_ZERO_IN_ARRAY_CONTEXT); 174 | } 175 | // If all conditions are met, simply return element 176 | // with token's value index. 177 | // It might be undefined, but it's ok. 178 | return context[token]; 179 | } 180 | 181 | if (_.isPlainObject(context)) { 182 | // In object context we can simply return element w/ key equal to token. 183 | // It might be undefined, but it's ok. 184 | return context[token]; 185 | } 186 | 187 | // If context is not an array or an object, 188 | // token evaluation is not possible. 189 | // This is the expected situation and so we won't throw an error, 190 | // undefined value is perfectly suitable here. 191 | return; 192 | } 193 | 194 | /** 195 | * Returns target object's value pointed by pointer, returns undefined 196 | * if |pointer| points to non-existing value. 197 | * If pointer is not provided, validates first argument and returns 198 | * evaluator function that takes pointer as argument. 199 | * 200 | * @param {Object} target 201 | * @param {string} [pointer] 202 | * @returns {*} pointer JSON Pointer string 203 | */ 204 | function getPointedValue(target, pointer) { 205 | // If not object, an exception will be thrown. 206 | if (!_.isPlainObject(target)) { 207 | throw new ReferenceError(ErrorMessage.INVALID_DOCUMENT_TYPE); 208 | } 209 | 210 | // target is already parsed, create an evaluator for it. 211 | var evaluator = createPointerEvaluator(target); 212 | 213 | // If no pointer was provided, return evaluator function. 214 | if (_.isUndefined(pointer)) { 215 | return evaluator; 216 | } else { 217 | return evaluator(pointer); 218 | } 219 | } 220 | 221 | /** 222 | * 223 | * @module lib/pointer 224 | */ 225 | module.exports = { 226 | get: getPointedValue 227 | }; 228 | -------------------------------------------------------------------------------- /lib/transformer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var path = require('path'); 5 | var curl = require('./curl'); 6 | var JSONformatter = require('./json'); 7 | var exampleExtractor = require('./example-data-extractor'); 8 | var ObjectDefinition = require('./object-definition'); 9 | var errors = require('./errors'); 10 | var pointer = require('./pointer'); 11 | 12 | /** 13 | * Extend a schema with composed data for rendering in a template 14 | * 15 | * @param {Object} schema 16 | * @param {Object} options 17 | * @return {Object} 18 | */ 19 | var transformSchema = function(schema, options) { 20 | options = options || {}; 21 | return _.omit( 22 | _.extend(schema, { 23 | // HTML-ready identifier 24 | html_id: _sanitizeHTMLID(schema.title), 25 | // Links are the available HTTP endpoints to interact with the object(s) 26 | links: transformLinks(schema, schema.links || [], options), 27 | // Object definition. Provides name, type, description, example, etc. for the schema. 28 | object_definition: generateObjectDefinition(schema) 29 | }), [ 30 | 'properties', 31 | 'definitions', 32 | 'allOf', 33 | 'anyOf', 34 | 'oneOf', 35 | 'required', 36 | '$schema' 37 | ] 38 | ); 39 | }; 40 | 41 | /** 42 | * Prepare a string to serve as an HTML id attribute 43 | * 44 | * @param {String} str 45 | * @return {String} 46 | */ 47 | var _sanitizeHTMLID = function(str) { 48 | return (str || '').toString().toLowerCase().replace(/[#\'\(\) ]+/gi, '-'); 49 | }; 50 | 51 | /** 52 | * @param {Object} schema 53 | * @param {Array} links 54 | * @param {Object} options 55 | * @return {Array} 56 | */ 57 | var transformLinks = function(schema, links, options) { 58 | return _.map(links, function(link) { 59 | return transformLink(schema, link, options); 60 | }); 61 | }; 62 | 63 | /** 64 | * Add additional metadata to the link object for API documentation 65 | * 66 | * @param {Object} schema 67 | * @param {Array} link 68 | * @param {Object} options 69 | */ 70 | var transformLink = function(schema, link, options) { 71 | try { 72 | var response; 73 | if (link.targetSchema) { 74 | response = generateExample(link.targetSchema, schema); 75 | 76 | // We only want to format JSON example data, which is the default. 77 | // The analogous request example logic is in the curl module. 78 | if (link.mediaType === undefined || 79 | link.mediaType.match(JSONformatter.jsonMediaType)) { 80 | response = formatData(response); 81 | } 82 | } 83 | 84 | return _.omit( 85 | _.extend(link, { 86 | html_id: _sanitizeHTMLID(schema.title + '-' + link.title), 87 | uri: buildHref(link.href, schema), 88 | curl: buildCurl(link, schema, options), 89 | parameters: link.schema ? formatLinkParameters(link.schema, schema) : undefined, 90 | response: response 91 | }), 92 | ['schema', 'targetSchema'] 93 | ); 94 | } catch (e) { 95 | errors.throwError('Error building link for ' + schema.id, e); 96 | } 97 | }; 98 | 99 | /** 100 | * Note: Only supports resolving references relative to the given schema 101 | * 102 | * @param {String} href 103 | * @param {Object} schema 104 | * @param {Boolean} [withExampleData=false] 105 | * @return {String} 106 | */ 107 | var buildHref = function(href, schema, withExampleData) { 108 | if (!href) return undefined; 109 | // This will pull out all {/schema/pointers} 110 | var pattern = /((?:{(?:#?(\/[\w\/]+))})+)+/g; 111 | var matches = href.match(pattern); 112 | 113 | try { 114 | return _.reduce(matches, function (str, match) { 115 | // Remove the brackets so we can find the definition 116 | var stripped = match.replace(/[{}]/g, ''); 117 | // Resolve the reference within the schema 118 | var definition = pointer.get(schema, stripped.substring(1)); 119 | if (!definition) { 120 | errors.throwError(e, 'Could not resolve href: ' + href); 121 | } 122 | // Replace the match with either example data or the last component of the pointer 123 | var replacement = withExampleData ? exampleExtractor.getExampleDataFromItem(definition) : ':' + path.basename(stripped); 124 | // /my/{#/pointer} -> /my/example_value OR /my/:pointer 125 | return str.replace(match, replacement); 126 | }, href); 127 | } catch (e) { 128 | errors.throwError(e, 'Could not build href: ' + href); 129 | } 130 | }; 131 | 132 | /** 133 | * Generates a cURL string containing example data for 134 | * a link of a given schema. 135 | * 136 | * @param {Object} link 137 | * @param {Object} schema 138 | * @param {Object} options 139 | * @returns {String} 140 | */ 141 | var buildCurl = function (link, schema, options) { 142 | if (!link.href) return undefined; 143 | var headers = {}; 144 | 145 | var baseUrl = _.get(options, 'curl.baseUrl') || ''; 146 | var uri = baseUrl + buildHref(link.href, schema, true); 147 | 148 | if (_.get(options, 'curl.requestHeaders')) { 149 | headers = exampleExtractor.extract(_.get(options, 'curl.requestHeaders'), schema); 150 | } 151 | if (link.cfRequestHeaders) { 152 | headers = exampleExtractor.extract(link.cfRequestHeaders, schema); 153 | } 154 | 155 | var data = link.schemaExampleData; 156 | 157 | if (link.schema) { 158 | data = generateExample(link.schema, schema); 159 | } 160 | // @TODO: Make this better 161 | curl.formatter = JSONformatter; 162 | return curl.generate(uri, link.method, headers, data, link.encType); 163 | }; 164 | 165 | /** 166 | * @param {*} data 167 | * @return {String} 168 | */ 169 | var formatData = function(data) { 170 | return JSONformatter.format(data); 171 | } 172 | 173 | /** 174 | * Recursively build an object from a given schema component that is an example 175 | * representation of the object defined by the schema. 176 | * 177 | * @param {Object} component - valid subschema of the root/parent 178 | * @param {Object} root - parent schema used as the base 179 | * @param {Object} [options] - options for generating example representations of a schema 180 | * @returns {Object} 181 | */ 182 | var generateExample = function(component, root) { 183 | return exampleExtractor.extract(component, root); 184 | }; 185 | 186 | /** 187 | * Loop over each properties in the inputs, assigning to either 188 | * a required or optional list. 189 | * 190 | * @param {Object} schema - Link inputs 191 | * @returns {ObjectDefinition} 192 | */ 193 | var formatLinkParameters = function(schema, root) { 194 | var baseSchema = root; 195 | if (schema.cfRecurse === undefined) { 196 | baseSchema = schema; 197 | } else if (schema.cfRecurse !== '') { 198 | throw new ReferenceError(errors.RECURSE_TARGET); 199 | } 200 | return generateObjectDefinition(baseSchema); 201 | }; 202 | 203 | /** 204 | * 205 | * @param schema 206 | * @returns {ObjectDefinition} 207 | */ 208 | var generateObjectDefinition = function(schema) { 209 | return new ObjectDefinition(schema, { 210 | formatter: JSONformatter 211 | }); 212 | }; 213 | 214 | /** 215 | * @module lib/transformer 216 | * @type {Function} 217 | */ 218 | module.exports = { 219 | transformSchema: transformSchema, 220 | _sanitizeHTMLID: _sanitizeHTMLID, 221 | transformLinks: transformLinks, 222 | transformLink: transformLink, 223 | buildHref: buildHref, 224 | buildCurl: buildCurl, 225 | formatData: formatData, 226 | generateExample: generateExample, 227 | formatLinkParameters: formatLinkParameters, 228 | generateObjectDefinition: generateObjectDefinition 229 | } 230 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json-schema-example-loader", 3 | "version": "3.1.1", 4 | "description": "Webpack loader that transforms JSON HyperSchema (without $refs) into examples.", 5 | "main": "lib/loader.js", 6 | "scripts": { 7 | "test": "mocha \"test/**/*.js\"", 8 | "coverage": "istanbul cover _mocha -- \"test/**/*.js\" --ui bdd -R spec -t 5000" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/cloudflare/json-schema-example-loader.git" 13 | }, 14 | "keywords": [ 15 | "json", 16 | "schema", 17 | "hyperschema", 18 | "loader", 19 | "webpack", 20 | "example" 21 | ], 22 | "author": "Vojtech Miksu ", 23 | "license": "BSD-3-Clause", 24 | "bugs": { 25 | "url": "https://github.com/cloudflare/json-schema-example-loader/issues" 26 | }, 27 | "homepage": "https://github.com/cloudflare/json-schema-example-loader#readme", 28 | "dependencies": { 29 | "loader-utils": "^0.2.15", 30 | "lodash": "^3.10.0" 31 | }, 32 | "devDependencies": { 33 | "chai": "^3.0.0", 34 | "glob": "^7.0.5", 35 | "istanbul": "^0.3.15", 36 | "mocha": "^2.2.5", 37 | "sinon": "^1.15.4", 38 | "sinon-chai": "^2.8.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test/curl.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* globals: describe, it */ 3 | 4 | var expect = require('chai').expect; 5 | var curl = require('../lib/curl'); 6 | 7 | /** @name describe @function */ 8 | /** @name it @function */ 9 | /** @name before @function */ 10 | /** @name after @function */ 11 | /** @name beforeEach @function */ 12 | /** @name afterEach @function */ 13 | 14 | describe('cURL Helper', function() { 15 | describe('#generate', function() { 16 | it('should return a string ending in the url', function() { 17 | var url = 'https://api.example.com/url'; 18 | var curlString = curl.generate(url); 19 | expect(curlString).to.be.a('string'); 20 | expect(curlString.endsWith(url + '"')).to.be.true; 21 | }); 22 | 23 | it('should include the HTTP method', function() { 24 | expect(curl.generate('https://api.example.com/url', 'POST')).to.contain('POST'); 25 | }); 26 | 27 | it('should add headers', function() { 28 | var str = curl.generate('https://api.example.com/url', 'POST', { 29 | 'My-Header': 'some value' 30 | }); 31 | expect(str).to.contain('My-Header: some value'); 32 | }); 33 | 34 | it('should include request body data for undefined encType', function() { 35 | var str = curl.generate('https://api.example.com/url', 'POST', null, { 36 | my_key: 'my value' 37 | }); 38 | 39 | expect(str).to.contain('--data \'{"my_key":"my value"}\''); 40 | }); 41 | 42 | it('should add data as a query string for GET', function() { 43 | var str = curl.generate('https://api.example.com/url', 'GET', null, { 44 | key1: 'value1', 45 | key2: 'value2' 46 | }); 47 | 48 | expect(str).to.contain('"https://api.example.com/url?key1=value1&key2=value2"'); 49 | }); 50 | 51 | it('should add form for encType mutlipart', function() { 52 | var str = curl.generate('https://api.example.com/url', 'POST', null, { 53 | file: '@value' 54 | }, 'multipart/form-data'); 55 | 56 | expect(str).to.contain('--form \'file=@value\''); 57 | }); 58 | 59 | it('should add multiple forms for encType mutlipart', function() { 60 | var str = curl.generate('https://api.example.com/url', 'POST', null, { 61 | value: 'someValue', 62 | metadata: '{"someMetadataKey": "someMetadataValue"}' 63 | }, 'multipart/form-data'); 64 | 65 | expect(str).to.contain('--form \'value=someValue\''); 66 | expect(str).to.contain('--form \'metadata={"someMetadataKey": "someMetadataValue"}\''); 67 | }); 68 | }); 69 | 70 | describe('#buildFlag', function() { 71 | it('should allow wrapping the value in nothing', function() { 72 | expect(curl.buildFlag('X', 'GET', 0, '')).to.equal('-X GET'); 73 | }); 74 | 75 | it('should wrap the value in double quotes by default', function() { 76 | expect(curl.buildFlag('H', 'value', 0)).to.equal('-H "value"'); 77 | }); 78 | 79 | it('should wrap the value in the specified type', function() { 80 | expect(curl.buildFlag('H', 'value', 0, '\'')).to.equal('-H \'value\''); 81 | }); 82 | 83 | it('should prepend extra 5 spaces', function() { 84 | expect(curl.buildFlag('H', 'value', 5)).to.equal(' -H "value"'); 85 | }); 86 | }); 87 | 88 | describe('#buildQueryString', function() { 89 | it('should build an HTTP query string from an object', function() { 90 | expect(curl.buildQueryString({ 91 | key1: 'value1', 92 | key2: 'value2' 93 | })).to.equal('?key1=value1&key2=value2'); 94 | }); 95 | 96 | it('should include a question mark by default', function() { 97 | expect(curl.buildQueryString({ 98 | key1: 'value1', 99 | key2: 'value2' 100 | })).to.contain('?'); 101 | }); 102 | 103 | it('should allow building without a question mark', function() { 104 | expect(curl.buildQueryString({ 105 | key1: 'value1', 106 | key2: 'value2' 107 | }, true)).to.not.contain('?'); 108 | }); 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /test/example-data-extractor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* globals: describe, it */ 3 | 4 | var expect = require('chai').expect; 5 | var extractor = require('../lib/example-data-extractor'); 6 | var schema1 = require('./fixtures/schema1.json'); 7 | var schema2 = require('./fixtures/schema2.json'); 8 | var _ = require('lodash'); 9 | 10 | /** @name describe @function */ 11 | /** @name it @function */ 12 | /** @name before @function */ 13 | /** @name after @function */ 14 | /** @name beforeEach @function */ 15 | /** @name afterEach @function */ 16 | 17 | describe('Example Data Extractor', function() { 18 | // @TODO Figure out a better way isolate these tests 19 | before(function() { 20 | this.schema1 = _.cloneDeep(schema1); 21 | this.schema2 = _.cloneDeep(schema2); 22 | }); 23 | 24 | describe('#getExampleDataFromItem', function() { 25 | it('should return "unknown" if the reference is not an object', function() { 26 | expect(extractor.getExampleDataFromItem([1])).to.equal('unknown'); 27 | }); 28 | 29 | it('should return the value found in the "example" attribute', function() { 30 | expect(extractor.getExampleDataFromItem({ 31 | example: 'my value' 32 | })).to.equal('my value'); 33 | }); 34 | 35 | it('should return the value found in the "default" attribute if "example" is not defined', function() { 36 | expect(extractor.getExampleDataFromItem({ 37 | default: 'my value' 38 | })).to.equal('my value'); 39 | }); 40 | }); 41 | 42 | describe('#mapPropertiesToExamples', function() { 43 | beforeEach(function() { 44 | this.example = extractor.mapPropertiesToExamples(this.schema1.properties, this.schema1); 45 | // Makes tests easier to write 46 | this.properties = _.keys(this.schema1.properties); 47 | }); 48 | 49 | it('should build example values from the given property definitions', function() { 50 | expect(this.example).to.be.an('object'); 51 | expect(this.example).to.have.keys(this.properties); 52 | expect(this.example.foo, 'internal reference').to.equal('bar'); 53 | expect(this.example.baz, 'external reference').to.equal('boo'); 54 | expect(this.example.boo, 'oneOf reference').to.have.property('attribute_one').that.equals('One'); 55 | }); 56 | 57 | it('should merge allOf objects together', function() { 58 | expect(this.example.composite).to.be.an('object'); 59 | expect(this.example.composite).to.have.keys(['attribute_one', 'attribute_two']); 60 | expect(this.example.composite).to.have.property('attribute_one').that.equals('One'); 61 | expect(this.example.composite).to.have.property('attribute_two').that.equals(2); 62 | }); 63 | 64 | it('should resolve cfRecurse references', function() { 65 | var obj = extractor.mapPropertiesToExamples({ 66 | key: { 67 | cfRecurse: '' 68 | } 69 | }, this.schema1); 70 | expect(obj).to.be.an('object'); 71 | expect(obj.key).to.contain.keys(this.properties); 72 | }); 73 | 74 | it('should follow nested schema references', function() { 75 | expect(this.example.nested_object).to.have.keys(_.keys(this.schema2.properties)); 76 | }); 77 | 78 | it('should NOT lowercase ID property references', function() { 79 | expect(this.example).to.contain.key('ID'); 80 | }); 81 | 82 | it('should handle lowercase id properties', function() { 83 | expect(this.example).to.contain.key('id'); 84 | }); 85 | 86 | it('should resolve the first oneOf reference', function() { 87 | expect(this.example.boo).to.have.property('attribute_one').that.equals('One'); 88 | }); 89 | 90 | it('should resolve the first anyOf reference', function() { 91 | expect(this.example.option).to.have.property('attribute_two').that.equals(2); 92 | }); 93 | 94 | it('should resolve array references', function() { 95 | expect(this.example.array_prop).to.be.an('array'); 96 | }); 97 | }); 98 | 99 | describe('#extract', function() { 100 | beforeEach(function() { 101 | this.example = extractor.extract(this.schema1, this.schema1); 102 | }); 103 | 104 | it('should return an example that is the defined type', function() { 105 | expect(this.example).to.be.an(this.schema1.type); 106 | }); 107 | 108 | it('should merge allOf references together', function() { 109 | expect(this.example).to.have.property('composite').that.has.keys(['attribute_one', 'attribute_two']); 110 | }); 111 | 112 | it('should use the first item in oneOf references', function() { 113 | expect(this.example).to.have.property('boo').that.has.key('attribute_one'); 114 | }); 115 | 116 | it('should use the first item in anyOf references', function() { 117 | expect(this.example).to.have.property('option').that.has.key('attribute_two'); 118 | }); 119 | 120 | it('should resolve cfRecurse', function() { 121 | expect(extractor.extract({ 122 | key: { 123 | cfRecurse: '' 124 | } 125 | }, this.schema1)).to.be.an('object'); 126 | }); 127 | 128 | it('should include extra properties', function() { 129 | expect(this.example).to.have.property('plus_one').that.is.not.empty; 130 | }); 131 | }); 132 | }); 133 | -------------------------------------------------------------------------------- /test/fixtures/regression.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "/fixtures/regression", 3 | "$schema": "http://json-schema.org/draft-04/schema#", 4 | "type": "object", 5 | "title": "Regression Test Schema", 6 | "links": [ 7 | { 8 | "title": "Test bugs: required outside of schema", 9 | "href": "/fixtures/regressions", 10 | "method": "POST", 11 | "required": [ 12 | "foo", 13 | "baz" 14 | ], 15 | "schema": { 16 | "required": ["boo"], 17 | "properties": { 18 | "foo": {"type": "boolean"}, 19 | "baz": {"type": "boolean"}, 20 | "boo": {"type": "boolean"} 21 | } 22 | } 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /test/fixtures/schema1.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "/fixtures/foo", 3 | "$schema": "http://json-schema.org/draft-04/schema#", 4 | "type": "object", 5 | "title": "Foo Object", 6 | "description": "The foo object is bar", 7 | "definitions": { 8 | "identifier": { 9 | "type": "number", 10 | "description": "Foo ID", 11 | "example": 123 12 | }, 13 | "foo_prop": { 14 | "type": "string", 15 | "description": "Foo property", 16 | "example": "bar" 17 | }, 18 | "baz_prop": { 19 | "type": "string", 20 | "description": "Baz property", 21 | "example": "boo" 22 | }, 23 | "object_one": { 24 | "type": "object", 25 | "description": "Object 1", 26 | "properties": { 27 | "attribute_one": { 28 | "type": "string", 29 | "description": "Attribute 1", 30 | "example": "One" 31 | } 32 | } 33 | }, 34 | "object_two": { 35 | "type": "object", 36 | "description": "Object 2", 37 | "properties": { 38 | "attribute_two": { 39 | "type": "number", 40 | "description": "Attribute 2", 41 | "example": 2 42 | } 43 | } 44 | } 45 | }, 46 | "properties": { 47 | "id": { 48 | "type": "number", 49 | "description": "Foo ID", 50 | "example": 123 51 | }, 52 | "ID": { 53 | "type": "string", 54 | "description": "tests that ID is no longer a special case", 55 | "example": "something" 56 | }, 57 | "foo": { 58 | "type": "string", 59 | "description": "Foo property", 60 | "example": "bar" 61 | }, 62 | "baz": { 63 | "type": "string", 64 | "description": "Baz property", 65 | "example": "boo" 66 | }, 67 | "boo": { 68 | "type": "object", 69 | "oneOf": [ 70 | { 71 | "type": "object", 72 | "description": "Object 1", 73 | "properties": { 74 | "attribute_one": { 75 | "type": "string", 76 | "description": "Attribute 1", 77 | "example": "One" 78 | } 79 | } 80 | }, 81 | { 82 | "type": "object", 83 | "description": "Object 2", 84 | "properties": { 85 | "attribute_two": { 86 | "type": "number", 87 | "description": "Attribute 2", 88 | "example": 2 89 | } 90 | } 91 | } 92 | ] 93 | }, 94 | "option": { 95 | "type": "object", 96 | "anyOf": [ 97 | { 98 | "type": "object", 99 | "description": "Object 2", 100 | "properties": { 101 | "attribute_two": { 102 | "type": "number", 103 | "description": "Attribute 2", 104 | "example": 2 105 | } 106 | } 107 | }, 108 | { 109 | "type": "object", 110 | "description": "Object 1", 111 | "properties": { 112 | "attribute_one": { 113 | "type": "string", 114 | "description": "Attribute 1", 115 | "example": "One" 116 | } 117 | } 118 | } 119 | ] 120 | }, 121 | "composite": { 122 | "allOf": [ 123 | { 124 | "type": "object", 125 | "description": "Object 1", 126 | "properties": { 127 | "attribute_one": { 128 | "type": "string", 129 | "description": "Attribute 1", 130 | "example": "One" 131 | } 132 | } 133 | }, 134 | { 135 | "type": "object", 136 | "description": "Object 2", 137 | "properties": { 138 | "attribute_two": { 139 | "type": "number", 140 | "description": "Attribute 2", 141 | "example": 2 142 | } 143 | } 144 | } 145 | ] 146 | }, 147 | "nested_object": { 148 | "id": "/fixtures/baz", 149 | "$schema": "http://json-schema.org/draft-04/schema#", 150 | "definitions": { 151 | "identifier": { 152 | "type": "number", 153 | "description": "Baz ID", 154 | "example": 456 155 | }, 156 | "baz_prop": { 157 | "type": "string", 158 | "description": "Baz property", 159 | "example": "boo" 160 | }, 161 | "foo_prop": { 162 | "type": "string", 163 | "description": "Foo property", 164 | "example": "bar" 165 | } 166 | }, 167 | "properties": { 168 | "baz": { 169 | "type": "string", 170 | "description": "Baz property", 171 | "example": "boo" 172 | }, 173 | "foo": { 174 | "type": "string", 175 | "description": "Foo property", 176 | "example": "bar" 177 | } 178 | }, 179 | "links": [ 180 | { 181 | "title": "Get all bazzes", 182 | "href": "/fixtures/bazzes", 183 | "method": "GET", 184 | "schema": { 185 | "type": "object", 186 | "description": "Queriable properties", 187 | "properties": { 188 | "foo": { 189 | "type": "string", 190 | "description": "Baz property", 191 | "example": "boo" 192 | } 193 | } 194 | }, 195 | "targetSchema": { 196 | "cfRecurse": "" 197 | } 198 | }, 199 | { 200 | "title": "Get a single baz", 201 | "href": "/fixtures/bazzes/{#/definitions/identifier}", 202 | "method": "GET", 203 | "targetSchema": { 204 | "cfRecurse": "" 205 | } 206 | } 207 | ] 208 | }, 209 | "array_prop": { 210 | "type": "array", 211 | "description": "Some array property description", 212 | "items": { 213 | "type": "string", 214 | "description": "Foo property", 215 | "example": "bar" 216 | } 217 | } 218 | }, 219 | "extraProperties": { 220 | "plus_one": { 221 | "type": "string", 222 | "description": "Foo property", 223 | "example": "bar" 224 | } 225 | }, 226 | "generator": { 227 | "includeExtraProperties": true 228 | }, 229 | "links": [ 230 | { 231 | "title": "Get all foos", 232 | "href": "/fixtures/foos", 233 | "method": "GET", 234 | "schema": { 235 | "type": "object", 236 | "description": "Queriable properties", 237 | "properties": { 238 | "foo": { 239 | "type": "string", 240 | "description": "Foo property", 241 | "example": "bar" 242 | } 243 | } 244 | }, 245 | "targetSchema": { 246 | "cfRecurse": "" 247 | } 248 | }, 249 | { 250 | "title": "Get a single foo", 251 | "href": "/fixtures/foos/{#/definitions/identifier}", 252 | "method": "GET", 253 | "targetSchema": { 254 | "cfRecurse": "" 255 | } 256 | }, 257 | { 258 | "title": "Create a foo", 259 | "href": "/fixtures/foos", 260 | "method": "POST", 261 | "schema": { 262 | "type": "object", 263 | "required": [ 264 | "foo", 265 | "baz" 266 | ], 267 | "properties": { 268 | "foo": "#/definitions/foo_prop", 269 | "baz": "#/definitions/baz_prop", 270 | "boo": { 271 | "oneOf": [ 272 | { 273 | "type": "object", 274 | "description": "Object 1", 275 | "properties": { 276 | "attribute_one": { 277 | "type": "string", 278 | "description": "Attribute 1", 279 | "example": "One" 280 | } 281 | } 282 | }, 283 | { 284 | "type": "object", 285 | "description": "Object 2", 286 | "properties": { 287 | "attribute_two": { 288 | "type": "number", 289 | "description": "Attribute 2", 290 | "example": 2 291 | } 292 | } 293 | } 294 | ] 295 | } 296 | } 297 | }, 298 | "targetSchema": { 299 | "cfRecurse": "" 300 | } 301 | }, 302 | { 303 | "title": "Get many foos", 304 | "href": "/fixtures/foos", 305 | "method": "GET", 306 | "schema": { 307 | "type": "object", 308 | "description": "Queriable properties", 309 | "properties": { 310 | "foo": { 311 | "$ref": "#/definitions/foo_prop" 312 | } 313 | } 314 | }, 315 | "targetSchema": { 316 | "type": "array", 317 | "minItems": 2, 318 | "maxItems": 5, 319 | "items": { 320 | "cfRecurse": "" 321 | } 322 | } 323 | } 324 | ] 325 | } 326 | -------------------------------------------------------------------------------- /test/fixtures/schema2.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "/fixtures/baz", 3 | "$schema": "http://json-schema.org/draft-04/schema#", 4 | "definitions": { 5 | "identifier": { 6 | "type": "number", 7 | "description": "Baz ID", 8 | "example": 456 9 | }, 10 | "baz_prop": { 11 | "type": "string", 12 | "description": "Baz property", 13 | "example": "boo" 14 | }, 15 | "foo_prop": { 16 | "type": "string", 17 | "description": "Foo property", 18 | "example": "bar" 19 | } 20 | }, 21 | "properties": { 22 | "baz": { 23 | "type": "string", 24 | "description": "Baz property", 25 | "example": "boo" 26 | }, 27 | "foo": { 28 | "type": "string", 29 | "description": "Foo property", 30 | "example": "bar" 31 | } 32 | }, 33 | "links": [ 34 | { 35 | "title": "Get all bazzes", 36 | "href": "/fixtures/bazzes", 37 | "method": "GET", 38 | "schema": { 39 | "type": "object", 40 | "description": "Queriable properties", 41 | "properties": { 42 | "foo": { 43 | "type": "string", 44 | "description": "Baz property", 45 | "example": "boo" 46 | } 47 | } 48 | }, 49 | "targetSchema": { 50 | "cfRecurse": "" 51 | } 52 | }, 53 | { 54 | "title": "Get a single baz", 55 | "href": "/fixtures/bazzes/{#/definitions/identifier}", 56 | "method": "GET", 57 | "targetSchema": { 58 | "cfRecurse": "" 59 | } 60 | }, 61 | { 62 | "title": "Do a thing with a script", 63 | "href": "/fixtures/scripts", 64 | "method": "POST", 65 | "encType": "application/javascript", 66 | "schema": { 67 | "example": "hello('world');" 68 | }, 69 | "mediaType": "application/javascript", 70 | "targetSchema": { 71 | "example": "hello('back');" 72 | } 73 | } 74 | ] 75 | } 76 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --reporter spec 2 | -------------------------------------------------------------------------------- /test/object-definition.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* globals: describe, it */ 3 | 4 | var sinon = require('sinon'); 5 | var chai = require('chai'); 6 | var expect = chai.expect; 7 | var ObjectDefinition = require('../lib/object-definition'); 8 | var schema1 = require('./fixtures/schema1.json'); 9 | var schema2 = require('./fixtures/schema2.json'); 10 | var _ = require('lodash'); 11 | 12 | chai.use(require('sinon-chai')); 13 | 14 | /** @name describe @function */ 15 | /** @name it @function */ 16 | /** @name before @function */ 17 | /** @name after @function */ 18 | /** @name beforeEach @function */ 19 | /** @name afterEach @function */ 20 | 21 | describe('Object Definition', function() { 22 | // @TODO Figure out a better way isolate these tests 23 | before(function() { 24 | this.schema1 = _.cloneDeep(schema1); 25 | this.schema2 = _.cloneDeep(schema2); 26 | }); 27 | 28 | beforeEach(function() { 29 | this.definitionObjectKeys = ['title', 'description', 'all_props', 'required_props', 'optional_props', 'objects', 'example']; 30 | this.definition = new ObjectDefinition(this.schema1); 31 | this.linkParameters = new ObjectDefinition(this.schema1.links[2].schema); 32 | }); 33 | 34 | describe('#constructor', function() { 35 | it('should assign a formatter if one is not provided', function() { 36 | expect(this.definition).to.have.property('_formatter').that.is.not.undefined; 37 | }); 38 | 39 | it('should use the provided formatter', function() { 40 | var formatter = { 41 | format: _.identity 42 | }; 43 | this.definition = new ObjectDefinition(this.schema1, { 44 | formatter: formatter 45 | }); 46 | expect(this.definition).to.have.property('_formatter').that.equals(formatter); 47 | }); 48 | 49 | it('should call build on instantiation', function() { 50 | var spy = sinon.spy(ObjectDefinition.prototype, 'build'); 51 | this.definition = new ObjectDefinition(this.schema1); 52 | expect(spy).to.have.been.called; 53 | spy.restore(); 54 | }); 55 | 56 | it('should merge results of build into itself', function() { 57 | expect(this.definition).to.contain.keys(this.definitionObjectKeys); 58 | }); 59 | }); 60 | 61 | describe('#build', function() { 62 | beforeEach(function() { 63 | this.allOfSchema = { 64 | id: '/someid', 65 | allOf: [ 66 | this.schema1.definitions.object_one, 67 | this.schema1.definitions.object_two 68 | ] 69 | }; 70 | }); 71 | 72 | it('should return an object with the correct attributes', function() { 73 | expect(this.definition.build(this.schema1)).to.contain.keys(this.definitionObjectKeys); 74 | }); 75 | 76 | it('should return required properites when defined', function() { 77 | expect(this.linkParameters.required_props).to.have.members(['foo', 'baz']); 78 | }); 79 | 80 | it('should include all properties found', function() { 81 | expect(this.linkParameters.all_props).to.have.keys(['foo', 'baz', 'boo']); 82 | }); 83 | 84 | it('should only include optional properties', function() { 85 | expect(this.linkParameters.optional_props).to.have.members(['boo']); 86 | expect(this.linkParameters.optional_props).to.not.have.members(['foo', 'baz']); 87 | }); 88 | 89 | it('should merge allOf references together', function() { 90 | var result = this.definition.build(this.allOfSchema); 91 | expect(result).to.have.property('all_props').that.has.keys(['attribute_one', 'attribute_two']); 92 | }); 93 | 94 | it('should build an array of definition objects for oneOf references', function() { 95 | var schema = { 96 | oneOf: [ 97 | this.schema1.definitions.object_one, 98 | this.schema1.definitions.object_two 99 | ] 100 | }; 101 | var result = this.definition.build(schema); 102 | expect(result).to.have.property('objects').that.is.an('array'); 103 | expect(result.objects).to.have.length(2); 104 | expect(result.objects[0], 'first object').to.have.keys(this.definitionObjectKeys); 105 | expect(result.objects[1], 'second object').to.have.keys(this.definitionObjectKeys); 106 | expect(result.which_of).to.equal('oneOf'); 107 | }); 108 | 109 | it('should build an array of definition objects for anyOf references', function() { 110 | var schema = { 111 | anyOf: [ 112 | this.schema1.definitions.object_one, 113 | this.schema1.definitions.object_two 114 | ] 115 | }; 116 | var result = this.definition.build(schema); 117 | expect(result).to.have.property('objects').that.is.an('array'); 118 | expect(result.objects).to.have.length(2); 119 | expect(result.objects[0], 'first object').to.have.keys(this.definitionObjectKeys); 120 | expect(result.objects[1], 'second object').to.have.keys(this.definitionObjectKeys); 121 | expect(result.which_of).to.equal('anyOf'); 122 | }); 123 | 124 | it('should include extra properties in all props when defined', function() { 125 | expect(this.definition.all_props).to.contain.key('plus_one'); 126 | }); 127 | 128 | it('should build an example', function() { 129 | expect(this.definition.example).to.be.a('string').with.length.above(2); 130 | expect(this.definition.example).to.contain('id'); 131 | expect(this.definition.example).to.contain('foo'); 132 | expect(this.definition.example).to.contain('baz'); 133 | expect(this.definition.example).to.contain('boo'); 134 | expect(this.definition.example).to.contain('option'); 135 | expect(this.definition.example).to.contain('composite'); 136 | expect(this.definition.example).to.contain('nested_object'); 137 | expect(this.definition.example).to.contain('array_prop'); 138 | expect(this.definition.example).to.contain('plus_one'); 139 | }); 140 | }); 141 | 142 | describe('#defineProperties', function() { 143 | it('should return an object with the same keys', function() { 144 | expect(this.definition.defineProperties(this.schema1.properties)).to.be.an('object').with.keys(_.keys(this.schema1.properties)); 145 | }); 146 | }); 147 | 148 | describe('#defineProperty', function() { 149 | it('should return an object', function() { 150 | expect(this.definition.defineProperty({})).to.be.an('object'); 151 | }); 152 | 153 | it('should always include a type defined by the property', function() { 154 | expect(this.definition.defineProperty({ 155 | type: 'string' 156 | })).to.have.property('type').that.equals('string'); 157 | 158 | expect(this.definition.defineProperty({ 159 | type: ['string', 'null'] 160 | })).to.have.property('type').that.eql(['string', 'null']); 161 | }); 162 | 163 | it('should derive a type from type definition even if enum is of a different type', function() { 164 | expect(this.definition.defineProperty({ 165 | type: ['string', 'null'], 166 | enum: [1, 2, 3] 167 | })).to.have.property('type').that.eql(['string', 'null']); 168 | 169 | expect(this.definition.defineProperty({ 170 | type: ['integer', 'null'], 171 | enum: ['a', 'b', 'c'] 172 | })).to.have.property('type').that.eql(['integer', 'null']); 173 | }); 174 | 175 | it('should define an example', function() { 176 | expect(this.definition.defineProperty({ 177 | type: 'string', 178 | example: 'abc' 179 | })).to.have.property('example').that.equals('"abc"'); 180 | }); 181 | 182 | it('should use the default when no example is defined', function() { 183 | expect(this.definition.defineProperty({ 184 | type: 'string', 185 | default: 'abc' 186 | })).to.have.property('example').that.equals('"abc"'); 187 | }); 188 | 189 | it('should merge allOf references to build a properties list', function() { 190 | expect(this.definition.defineProperty(this.schema1.properties.composite)).to.have.property('properties').that.has.keys(['attribute_one', 'attribute_two']); 191 | }); 192 | 193 | it('should map object definitions to oneOf references', function() { 194 | expect(this.definition.defineProperty(this.schema1.properties.boo)).to.have.property('oneOf').that.has.length(2); 195 | }); 196 | 197 | it('should map object definitions to anyOf references', function() { 198 | expect(this.definition.defineProperty(this.schema1.properties.option)).to.have.property('anyOf').that.has.length(2); 199 | }); 200 | 201 | it('should define deep properties', function() { 202 | expect(this.definition.defineProperty(this.schema1)).to.have.property('properties').that.has.keys(_.keys(this.schema1.properties)); 203 | }); 204 | 205 | it('should include any arbitrary attributes defined on the property', function() { 206 | expect(this.definition.defineProperty({ 207 | type: 'string', 208 | example: 'abc', 209 | my_prop: 123 210 | })).to.have.property('my_prop').that.equals(123); 211 | }); 212 | }); 213 | 214 | describe('#getExampleFromProperty', function() { 215 | it('return a string', function() { 216 | expect(this.definition.getExampleFromProperty({ 217 | example: 'abc' 218 | }), 'string').to.be.a('string'); 219 | 220 | expect(this.definition.getExampleFromProperty({ 221 | example: 213 222 | }), 'number').to.be.a('string'); 223 | 224 | expect(this.definition.getExampleFromProperty({ 225 | example: { 226 | a: 1 227 | } 228 | }), 'object').to.be.a('string'); 229 | 230 | expect(this.definition.getExampleFromProperty({ 231 | example: false 232 | }), 'false').to.be.a('string'); 233 | 234 | expect(this.definition.getExampleFromProperty({ 235 | example: true 236 | }), 'true').to.be.a('string'); 237 | }); 238 | 239 | it('should resolve an example if a valid schema is detected', function() { 240 | var example = this.definition.getExampleFromProperty(this.schema1); 241 | expect(example).to.contain('{'); 242 | expect(example).to.contain('foo'); 243 | expect(example).to.contain('baz'); 244 | expect(example).to.contain('option'); 245 | }); 246 | 247 | it('should detect if the property is an array', function() { 248 | var example = this.definition.getExampleFromProperty(this.schema1.properties.array_prop); 249 | expect(example).to.not.equal('[]'); 250 | expect(example).to.contain('['); 251 | expect(example).to.contain(']'); 252 | }); 253 | }); 254 | }); 255 | -------------------------------------------------------------------------------- /test/pointer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* globals: describe, it */ 3 | 4 | var expect = require('chai').expect; 5 | var pointer = require('../lib/pointer'); 6 | 7 | /** @name describe @function */ 8 | /** @name it @function */ 9 | /** @name before @function */ 10 | /** @name after @function */ 11 | /** @name beforeEach @function */ 12 | /** @name afterEach @function */ 13 | 14 | describe('Pointer', function() { 15 | 16 | beforeEach(function() { 17 | this.target = { 18 | a: { 19 | b: { 20 | c: 1 21 | } 22 | }, 23 | d: 2, 24 | e: { 25 | f: [3, 4], 26 | g: [{ 27 | h: true 28 | }, { 29 | i: false 30 | }, 5] 31 | }, 32 | 'j/': 6, 33 | 'k~': 7 34 | }; 35 | }); 36 | 37 | describe('#errors', function() { 38 | it('should throw if resolving a token that is only a hyphen when in the context of an array', function() { 39 | expect(function() { 40 | return pointer.get(this.target, '/e/f/-'); 41 | }.bind(this)).to.throw(SyntaxError); 42 | }); 43 | 44 | it('should throw an error if the pointer is referencing a non-numeric index in the context of an array', function() { 45 | expect(function() { 46 | return pointer.get(this.target, '/e/f/nope'); 47 | }.bind(this)).to.throw(ReferenceError); 48 | }); 49 | 50 | it('should throw an error if the pointer is using leading zeros in an array context', function() { 51 | expect(function() { 52 | return pointer.get(this.target, '/e/f/00'); 53 | }.bind(this)).to.throw(ReferenceError); 54 | }); 55 | 56 | it('should throw an error if the pointer is not a string', function() { 57 | expect(function() { 58 | return pointer.get(this.target, true); 59 | }.bind(this), 'boolean').to.throw(ReferenceError); 60 | 61 | expect(function() { 62 | return pointer.get(this.target, {d: 2}); 63 | }.bind(this), 'object').to.throw(ReferenceError); 64 | 65 | expect(function() { 66 | return pointer.get(this.target, 0); 67 | }.bind(this), 'number').to.throw(ReferenceError); 68 | }); 69 | 70 | it('should throw an error if the target is not an object', function() { 71 | expect(function() { 72 | return pointer.get(function() { 73 | return {a: 1} 74 | }, '/a'); 75 | }.bind(this), 'function').to.throw(ReferenceError); 76 | 77 | expect(function() { 78 | return pointer.get(undefined, '/a'); 79 | }.bind(this), 'undefined').to.throw(ReferenceError); 80 | 81 | expect(function() { 82 | return pointer.get(true, '/a'); 83 | }.bind(this), 'boolean').to.throw(ReferenceError); 84 | 85 | expect(function() { 86 | return pointer.get(1, '/0'); 87 | }.bind(this), 'number').to.throw(ReferenceError); 88 | 89 | expect(function() { 90 | return pointer.get([1], '/0'); 91 | }.bind(this), 'array').to.throw(ReferenceError); 92 | }); 93 | }); 94 | 95 | describe('#resolving', function() { 96 | it('should return undefined if the reference is not found', function() { 97 | expect(pointer.get(this.target, '/not/a/reference')).to.be.undefined; 98 | }); 99 | 100 | it('should return an evaluator function when no pointer is provided', function() { 101 | expect(pointer.get(this.target), 'no pointer').to.be.a('function'); 102 | expect(pointer.get(this.target)('/not/a/reference'), 'bad reference').to.be.undefined; 103 | expect(pointer.get(this.target)('/a'), '/a reference').to.be.an('object'); 104 | }); 105 | 106 | it('should return a resolved object reference', function() { 107 | expect(pointer.get(this.target, '/a/b')).to.eql({c: 1}); 108 | expect(pointer.get(this.target, '/d')).to.equal(2); 109 | expect(pointer.get(this.target, '/e/f')).to.eql([3, 4]); 110 | }); 111 | 112 | it('should return a resolved array reference', function() { 113 | expect(pointer.get(this.target, '/e/f/0'), 'index 0').to.equal(3); 114 | expect(pointer.get(this.target, '/e/f/1'), 'index 1').to.equal(4); 115 | }); 116 | 117 | it('should return resolved object references within arrays', function() { 118 | expect(pointer.get(this.target, '/e/g/0/h'), '0 - h').to.be.true; 119 | expect(pointer.get(this.target, '/e/g/0/i'), '0 - i').to.be.undefined; 120 | expect(pointer.get(this.target, '/e/g/1/h'), '1 - h').to.be.undefined; 121 | expect(pointer.get(this.target, '/e/g/1/i'), '1 - i').to.be.false; 122 | }); 123 | 124 | it('should unescape special character sequences in the pointer before resolving', function() { 125 | expect(pointer.get(this.target, '/j~1'), 'forward slash escape').to.equal(6); 126 | expect(pointer.get(this.target, '/k~0'), 'tilda escape').to.equal(7); 127 | }); 128 | }); 129 | }); 130 | -------------------------------------------------------------------------------- /test/regression.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* globals: describe, it */ 3 | 4 | var expect = require('chai').expect; 5 | var transformer = require('../lib/transformer'); 6 | var regression = require('./fixtures/regression.json'); 7 | var ObjectDefinition = require('../lib/object-definition'); 8 | var _ = require('lodash'); 9 | 10 | /** @name describe @function */ 11 | /** @name it @function */ 12 | /** @name before @function */ 13 | /** @name after @function */ 14 | /** @name beforeEach @function */ 15 | /** @name afterEach @function */ 16 | 17 | describe('Schema Transformer Regression', function() { 18 | // @TODO Figure out a better way isolate these tests 19 | before(function() { 20 | this.regression = _.cloneDeep(regression); 21 | }); 22 | 23 | describe('#transformLinkRegression', function() { 24 | beforeEach(function() { 25 | this.link = transformer.transformLink(this.regression, this.regression.links[0]); 26 | }); 27 | 28 | it('should not copy a link "required" into parameters', function() { 29 | expect(this.link.parameters).to.not.have.property('required'); 30 | }); 31 | 32 | it('should have the required property from inside schema', function() { 33 | expect(this.link.parameters.required_props).to.have.members(['boo']); 34 | expect(this.link.parameters.required_props).to.not.have.members(['foo', 'baz']); 35 | }); 36 | 37 | it('should have optional properties based on the inner required', function() { 38 | expect(this.link.parameters.optional_props).to.have.members(['foo', 'baz']); 39 | expect(this.link.parameters.optional_props).to.not.have.members(['boo']); 40 | }); 41 | 42 | it('should still see all parameters', function() { 43 | expect(this.link.parameters.all_props).to.have.keys(['foo', 'baz', 'boo']); 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test/transformer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* globals: describe, it */ 3 | 4 | var expect = require('chai').expect; 5 | var transformer = require('../lib/transformer'); 6 | var schema1 = require('./fixtures/schema1.json'); 7 | var schema2 = require('./fixtures/schema2.json'); 8 | var ObjectDefinition = require('../lib/object-definition'); 9 | var _ = require('lodash'); 10 | 11 | /** @name describe @function */ 12 | /** @name it @function */ 13 | /** @name before @function */ 14 | /** @name after @function */ 15 | /** @name beforeEach @function */ 16 | /** @name afterEach @function */ 17 | 18 | describe('Schema Transformer', function() { 19 | // @TODO Figure out a better way isolate these tests 20 | before(function() { 21 | this.schema1 = _.cloneDeep(schema1); 22 | this.schema2 = _.cloneDeep(schema2); 23 | }); 24 | 25 | describe('#transformLinks', function() { 26 | it('should return an array', function() { 27 | expect(transformer.transformLinks(this.schema1, this.schema1.links)).to.be.an('array'); 28 | }); 29 | }); 30 | 31 | describe('#transformLink', function() { 32 | beforeEach(function() { 33 | this.link = transformer.transformLink(this.schema1, this.schema1.links[0]); 34 | }); 35 | 36 | it('should contain an html ID', function() { 37 | expect(this.link).to.have.property('html_id').that.is.a('string'); 38 | }); 39 | 40 | it('should have a URI', function() { 41 | expect(this.link).to.have.property('uri').that.is.a('string'); 42 | }); 43 | 44 | it('should have a curl', function() { 45 | expect(this.link).to.have.property('curl').that.is.a('string'); 46 | }); 47 | 48 | it('should have a formatted response', function() { 49 | expect(this.link).to.have.property('response').that.is.a('string'); 50 | }); 51 | 52 | it('should have input parameters', function() { 53 | expect(this.link).to.have.property('parameters').that.is.an('object'); 54 | expect(this.link).to.have.property('parameters').that.is.an.instanceOf(ObjectDefinition); 55 | }); 56 | 57 | it('should handle non-JSON media types', function() { 58 | var mediaLink = transformer.transformLink(this.schema2, this.schema2.links[2]); 59 | 60 | expect(mediaLink.curl).to.match(/\n--data \"hello\('world'\);\"$/); 61 | expect(mediaLink.response).to.equal("hello('back');"); 62 | }); 63 | }); 64 | 65 | describe('#buildHref', function() { 66 | it('should replace references with placeholders', function() { 67 | expect(transformer.buildHref(this.schema1.links[1].href, this.schema1)).to.equal('/fixtures/foos/:identifier'); 68 | }); 69 | 70 | it('should replace references with example data', function() { 71 | expect(transformer.buildHref(this.schema1.links[1].href, this.schema1, true)).to.equal('/fixtures/foos/123'); 72 | }); 73 | 74 | it('should throw an error if it cannot resolve a reference', function() { 75 | expect(_.bind(function() { 76 | transformer.buildHref('/foo/bar/{#/not/a/place}', this.schema1) 77 | }, this)).to.throw(Error); 78 | }); 79 | }); 80 | 81 | describe('#buildCurl', function() { 82 | it('should return a string', function() { 83 | expect(transformer.buildCurl(this.schema1.links[1], this.schema1)).to.be.a('string'); 84 | }); 85 | 86 | it('should have a curl in it', function() { 87 | expect(transformer.buildCurl(this.schema1.links[1], this.schema1)).to.contain('curl'); 88 | }); 89 | }); 90 | 91 | describe('#formatData', function() { 92 | it('should return a string', function() { 93 | expect(transformer.formatData(123), 'number').to.be.a('string'); 94 | expect(transformer.formatData('abc'), 'string').to.be.a('string'); 95 | expect(transformer.formatData({ 96 | a: 1, 97 | b: [0,2] 98 | }), 'object').to.be.a('string'); 99 | expect(transformer.formatData([1,2,3,4]), 'array').to.be.a('string'); 100 | expect(transformer.formatData(false), 'boolean').to.be.a('string'); 101 | }); 102 | }); 103 | 104 | describe('#generateExample', function() { 105 | beforeEach(function() { 106 | this.example = transformer.generateExample(this.schema1.links[0].schema, this.schema1); 107 | }); 108 | 109 | it('should return an object', function() { 110 | expect(this.example).to.be.an('object'); 111 | }); 112 | 113 | it('should fill attribute definitions with example values', function() { 114 | expect(this.example).to.have.property('foo').that.equals('bar'); 115 | }); 116 | 117 | it('should build an example for the whole object', function() { 118 | this.example = transformer.generateExample(this.schema1, this.schema1); 119 | expect(this.example).to.be.an('object'); 120 | expect(this.example.id).to.equal(123); 121 | expect(this.example.foo).to.equal('bar'); 122 | expect(this.example.baz).to.equal('boo'); 123 | expect(this.example.boo).to.eql({ 124 | attribute_one: 'One' 125 | }); 126 | expect(this.example.composite).to.eql({ 127 | attribute_one: 'One', 128 | attribute_two: 2 129 | }); 130 | expect(this.example.nested_object).to.not.be.empty; 131 | }); 132 | 133 | it('should handle cfRecurse references', function() { 134 | var data = transformer.generateExample(this.schema1.links[0].targetSchema, this.schema1); 135 | expect(data).to.be.an('object'); 136 | expect(data).to.deep.equal({ 137 | id: 123, 138 | ID: 'something', 139 | foo: 'bar', 140 | baz: 'boo', 141 | array_prop: ['bar'], 142 | boo: { 143 | attribute_one: 'One' 144 | }, 145 | nested_object: { 146 | baz: 'boo', 147 | foo: 'bar' 148 | }, 149 | composite: { 150 | attribute_one: 'One', 151 | attribute_two: 2 152 | }, 153 | option: { 154 | attribute_two: 2 155 | }, 156 | plus_one: 'bar' 157 | }); 158 | }); 159 | 160 | it('should handle cfRecurse references as an array', function() { 161 | var data = transformer.generateExample(this.schema1.links[3].targetSchema, this.schema1); 162 | expect(data).to.be.an('array'); 163 | expect(data.length).to.be.gte(2); 164 | expect(data.length).to.be.lte(5); 165 | expect(data[0]).to.deep.equal({ 166 | id: 123, 167 | ID: 'something', 168 | foo: 'bar', 169 | baz: 'boo', 170 | array_prop: ['bar'], 171 | boo: { 172 | attribute_one: 'One' 173 | }, 174 | nested_object: { 175 | baz: 'boo', 176 | foo: 'bar' 177 | }, 178 | composite: { 179 | attribute_one: 'One', 180 | attribute_two: 2 181 | }, 182 | option: { 183 | attribute_two: 2 184 | }, 185 | plus_one: 'bar' 186 | }); 187 | }); 188 | }); 189 | }); 190 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | abbrev@1, abbrev@1.0.x: 6 | version "1.0.9" 7 | resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" 8 | 9 | align-text@^0.1.1, align-text@^0.1.3: 10 | version "0.1.4" 11 | resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" 12 | dependencies: 13 | kind-of "^3.0.2" 14 | longest "^1.0.1" 15 | repeat-string "^1.5.2" 16 | 17 | amdefine@>=0.0.4: 18 | version "1.0.1" 19 | resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" 20 | 21 | argparse@^1.0.7: 22 | version "1.0.9" 23 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" 24 | dependencies: 25 | sprintf-js "~1.0.2" 26 | 27 | assertion-error@^1.0.1: 28 | version "1.0.2" 29 | resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c" 30 | 31 | async@1.x, async@^1.4.0: 32 | version "1.5.2" 33 | resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" 34 | 35 | async@~0.2.6: 36 | version "0.2.10" 37 | resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" 38 | 39 | balanced-match@^0.4.1: 40 | version "0.4.2" 41 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" 42 | 43 | big.js@^3.1.3: 44 | version "3.1.3" 45 | resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.1.3.tgz#4cada2193652eb3ca9ec8e55c9015669c9806978" 46 | 47 | brace-expansion@^1.0.0: 48 | version "1.1.6" 49 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.6.tgz#7197d7eaa9b87e648390ea61fc66c84427420df9" 50 | dependencies: 51 | balanced-match "^0.4.1" 52 | concat-map "0.0.1" 53 | 54 | camelcase@^1.0.2: 55 | version "1.2.1" 56 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" 57 | 58 | center-align@^0.1.1: 59 | version "0.1.3" 60 | resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" 61 | dependencies: 62 | align-text "^0.1.3" 63 | lazy-cache "^1.0.3" 64 | 65 | chai@^3.0.0: 66 | version "3.5.0" 67 | resolved "https://registry.yarnpkg.com/chai/-/chai-3.5.0.tgz#4d02637b067fe958bdbfdd3a40ec56fef7373247" 68 | dependencies: 69 | assertion-error "^1.0.1" 70 | deep-eql "^0.1.3" 71 | type-detect "^1.0.0" 72 | 73 | cliui@^2.1.0: 74 | version "2.1.0" 75 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" 76 | dependencies: 77 | center-align "^0.1.1" 78 | right-align "^0.1.1" 79 | wordwrap "0.0.2" 80 | 81 | commander@0.6.1: 82 | version "0.6.1" 83 | resolved "https://registry.yarnpkg.com/commander/-/commander-0.6.1.tgz#fa68a14f6a945d54dbbe50d8cdb3320e9e3b1a06" 84 | 85 | commander@2.3.0: 86 | version "2.3.0" 87 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.3.0.tgz#fd430e889832ec353b9acd1de217c11cb3eef873" 88 | 89 | concat-map@0.0.1: 90 | version "0.0.1" 91 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 92 | 93 | debug@2.2.0: 94 | version "2.2.0" 95 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" 96 | dependencies: 97 | ms "0.7.1" 98 | 99 | decamelize@^1.0.0: 100 | version "1.2.0" 101 | resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" 102 | 103 | deep-eql@^0.1.3: 104 | version "0.1.3" 105 | resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-0.1.3.tgz#ef558acab8de25206cd713906d74e56930eb69f2" 106 | dependencies: 107 | type-detect "0.1.1" 108 | 109 | deep-is@~0.1.2: 110 | version "0.1.3" 111 | resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" 112 | 113 | diff@1.4.0: 114 | version "1.4.0" 115 | resolved "https://registry.yarnpkg.com/diff/-/diff-1.4.0.tgz#7f28d2eb9ee7b15a97efd89ce63dcfdaa3ccbabf" 116 | 117 | emojis-list@^2.0.0: 118 | version "2.1.0" 119 | resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" 120 | 121 | escape-string-regexp@1.0.2: 122 | version "1.0.2" 123 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz#4dbc2fe674e71949caf3fb2695ce7f2dc1d9a8d1" 124 | 125 | escodegen@1.7.x: 126 | version "1.7.1" 127 | resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.7.1.tgz#30ecfcf66ca98dc67cd2fd162abeb6eafa8ce6fc" 128 | dependencies: 129 | esprima "^1.2.2" 130 | estraverse "^1.9.1" 131 | esutils "^2.0.2" 132 | optionator "^0.5.0" 133 | optionalDependencies: 134 | source-map "~0.2.0" 135 | 136 | esprima@2.5.x: 137 | version "2.5.0" 138 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.5.0.tgz#f387a46fd344c1b1a39baf8c20bfb43b6d0058cc" 139 | 140 | esprima@^1.2.2: 141 | version "1.2.5" 142 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-1.2.5.tgz#0993502feaf668138325756f30f9a51feeec11e9" 143 | 144 | esprima@^2.6.0: 145 | version "2.7.3" 146 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" 147 | 148 | estraverse@^1.9.1: 149 | version "1.9.3" 150 | resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44" 151 | 152 | esutils@^2.0.2: 153 | version "2.0.2" 154 | resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" 155 | 156 | fast-levenshtein@~1.0.0: 157 | version "1.0.7" 158 | resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-1.0.7.tgz#0178dcdee023b92905193af0959e8a7639cfdcb9" 159 | 160 | fileset@0.2.x: 161 | version "0.2.1" 162 | resolved "https://registry.yarnpkg.com/fileset/-/fileset-0.2.1.tgz#588ef8973c6623b2a76df465105696b96aac8067" 163 | dependencies: 164 | glob "5.x" 165 | minimatch "2.x" 166 | 167 | formatio@1.1.1: 168 | version "1.1.1" 169 | resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.1.1.tgz#5ed3ccd636551097383465d996199100e86161e9" 170 | dependencies: 171 | samsam "~1.1" 172 | 173 | fs.realpath@^1.0.0: 174 | version "1.0.0" 175 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 176 | 177 | glob@3.2.11: 178 | version "3.2.11" 179 | resolved "https://registry.yarnpkg.com/glob/-/glob-3.2.11.tgz#4a973f635b9190f715d10987d5c00fd2815ebe3d" 180 | dependencies: 181 | inherits "2" 182 | minimatch "0.3" 183 | 184 | glob@5.x: 185 | version "5.0.15" 186 | resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" 187 | dependencies: 188 | inflight "^1.0.4" 189 | inherits "2" 190 | minimatch "2 || 3" 191 | once "^1.3.0" 192 | path-is-absolute "^1.0.0" 193 | 194 | glob@^7.0.5: 195 | version "7.1.1" 196 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" 197 | dependencies: 198 | fs.realpath "^1.0.0" 199 | inflight "^1.0.4" 200 | inherits "2" 201 | minimatch "^3.0.2" 202 | once "^1.3.0" 203 | path-is-absolute "^1.0.0" 204 | 205 | growl@1.9.2: 206 | version "1.9.2" 207 | resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" 208 | 209 | handlebars@^4.0.1: 210 | version "4.0.6" 211 | resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.6.tgz#2ce4484850537f9c97a8026d5399b935c4ed4ed7" 212 | dependencies: 213 | async "^1.4.0" 214 | optimist "^0.6.1" 215 | source-map "^0.4.4" 216 | optionalDependencies: 217 | uglify-js "^2.6" 218 | 219 | has-flag@^1.0.0: 220 | version "1.0.0" 221 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" 222 | 223 | inflight@^1.0.4: 224 | version "1.0.6" 225 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 226 | dependencies: 227 | once "^1.3.0" 228 | wrappy "1" 229 | 230 | inherits@2: 231 | version "2.0.3" 232 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 233 | 234 | inherits@2.0.1: 235 | version "2.0.1" 236 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" 237 | 238 | is-buffer@^1.0.2: 239 | version "1.1.4" 240 | resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.4.tgz#cfc86ccd5dc5a52fa80489111c6920c457e2d98b" 241 | 242 | isexe@^1.1.1: 243 | version "1.1.2" 244 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-1.1.2.tgz#36f3e22e60750920f5e7241a476a8c6a42275ad0" 245 | 246 | istanbul@^0.3.15: 247 | version "0.3.22" 248 | resolved "https://registry.yarnpkg.com/istanbul/-/istanbul-0.3.22.tgz#3e164d85021fe19c985d1f0e7ef0c3e22d012eb6" 249 | dependencies: 250 | abbrev "1.0.x" 251 | async "1.x" 252 | escodegen "1.7.x" 253 | esprima "2.5.x" 254 | fileset "0.2.x" 255 | handlebars "^4.0.1" 256 | js-yaml "3.x" 257 | mkdirp "0.5.x" 258 | nopt "3.x" 259 | once "1.x" 260 | resolve "1.1.x" 261 | supports-color "^3.1.0" 262 | which "^1.1.1" 263 | wordwrap "^1.0.0" 264 | 265 | jade@0.26.3: 266 | version "0.26.3" 267 | resolved "https://registry.yarnpkg.com/jade/-/jade-0.26.3.tgz#8f10d7977d8d79f2f6ff862a81b0513ccb25686c" 268 | dependencies: 269 | commander "0.6.1" 270 | mkdirp "0.3.0" 271 | 272 | js-yaml@3.x: 273 | version "3.7.0" 274 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80" 275 | dependencies: 276 | argparse "^1.0.7" 277 | esprima "^2.6.0" 278 | 279 | json5@^0.5.0: 280 | version "0.5.1" 281 | resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" 282 | 283 | kind-of@^3.0.2: 284 | version "3.1.0" 285 | resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.1.0.tgz#475d698a5e49ff5e53d14e3e732429dc8bf4cf47" 286 | dependencies: 287 | is-buffer "^1.0.2" 288 | 289 | lazy-cache@^1.0.3: 290 | version "1.0.4" 291 | resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" 292 | 293 | levn@~0.2.5: 294 | version "0.2.5" 295 | resolved "https://registry.yarnpkg.com/levn/-/levn-0.2.5.tgz#ba8d339d0ca4a610e3a3f145b9caf48807155054" 296 | dependencies: 297 | prelude-ls "~1.1.0" 298 | type-check "~0.3.1" 299 | 300 | loader-utils@^0.2.15: 301 | version "0.2.16" 302 | resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.16.tgz#f08632066ed8282835dff88dfb52704765adee6d" 303 | dependencies: 304 | big.js "^3.1.3" 305 | emojis-list "^2.0.0" 306 | json5 "^0.5.0" 307 | object-assign "^4.0.1" 308 | 309 | lodash@^3.10.0: 310 | version "3.10.1" 311 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" 312 | 313 | lolex@1.3.2: 314 | version "1.3.2" 315 | resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.3.2.tgz#7c3da62ffcb30f0f5a80a2566ca24e45d8a01f31" 316 | 317 | longest@^1.0.1: 318 | version "1.0.1" 319 | resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" 320 | 321 | lru-cache@2: 322 | version "2.7.3" 323 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" 324 | 325 | minimatch@0.3: 326 | version "0.3.0" 327 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.3.0.tgz#275d8edaac4f1bb3326472089e7949c8394699dd" 328 | dependencies: 329 | lru-cache "2" 330 | sigmund "~1.0.0" 331 | 332 | "minimatch@2 || 3", minimatch@^3.0.2: 333 | version "3.0.3" 334 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774" 335 | dependencies: 336 | brace-expansion "^1.0.0" 337 | 338 | minimatch@2.x: 339 | version "2.0.10" 340 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-2.0.10.tgz#8d087c39c6b38c001b97fca7ce6d0e1e80afbac7" 341 | dependencies: 342 | brace-expansion "^1.0.0" 343 | 344 | minimist@0.0.8, minimist@~0.0.1: 345 | version "0.0.8" 346 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 347 | 348 | mkdirp@0.3.0: 349 | version "0.3.0" 350 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e" 351 | 352 | mkdirp@0.5.1, mkdirp@0.5.x: 353 | version "0.5.1" 354 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 355 | dependencies: 356 | minimist "0.0.8" 357 | 358 | mocha@^2.2.5: 359 | version "2.5.3" 360 | resolved "https://registry.yarnpkg.com/mocha/-/mocha-2.5.3.tgz#161be5bdeb496771eb9b35745050b622b5aefc58" 361 | dependencies: 362 | commander "2.3.0" 363 | debug "2.2.0" 364 | diff "1.4.0" 365 | escape-string-regexp "1.0.2" 366 | glob "3.2.11" 367 | growl "1.9.2" 368 | jade "0.26.3" 369 | mkdirp "0.5.1" 370 | supports-color "1.2.0" 371 | to-iso-string "0.0.2" 372 | 373 | ms@0.7.1: 374 | version "0.7.1" 375 | resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" 376 | 377 | nopt@3.x: 378 | version "3.0.6" 379 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" 380 | dependencies: 381 | abbrev "1" 382 | 383 | object-assign@^4.0.1: 384 | version "4.1.1" 385 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 386 | 387 | once@1.x, once@^1.3.0: 388 | version "1.4.0" 389 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 390 | dependencies: 391 | wrappy "1" 392 | 393 | optimist@^0.6.1: 394 | version "0.6.1" 395 | resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" 396 | dependencies: 397 | minimist "~0.0.1" 398 | wordwrap "~0.0.2" 399 | 400 | optionator@^0.5.0: 401 | version "0.5.0" 402 | resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.5.0.tgz#b75a8995a2d417df25b6e4e3862f50aa88651368" 403 | dependencies: 404 | deep-is "~0.1.2" 405 | fast-levenshtein "~1.0.0" 406 | levn "~0.2.5" 407 | prelude-ls "~1.1.1" 408 | type-check "~0.3.1" 409 | wordwrap "~0.0.2" 410 | 411 | path-is-absolute@^1.0.0: 412 | version "1.0.1" 413 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 414 | 415 | prelude-ls@~1.1.0, prelude-ls@~1.1.1, prelude-ls@~1.1.2: 416 | version "1.1.2" 417 | resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" 418 | 419 | repeat-string@^1.5.2: 420 | version "1.6.1" 421 | resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" 422 | 423 | resolve@1.1.x: 424 | version "1.1.7" 425 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" 426 | 427 | right-align@^0.1.1: 428 | version "0.1.3" 429 | resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" 430 | dependencies: 431 | align-text "^0.1.1" 432 | 433 | samsam@1.1.2, samsam@~1.1: 434 | version "1.1.2" 435 | resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.2.tgz#bec11fdc83a9fda063401210e40176c3024d1567" 436 | 437 | sigmund@~1.0.0: 438 | version "1.0.1" 439 | resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" 440 | 441 | sinon-chai@^2.8.0: 442 | version "2.8.0" 443 | resolved "https://registry.yarnpkg.com/sinon-chai/-/sinon-chai-2.8.0.tgz#432a9bbfd51a6fc00798f4d2526a829c060687ac" 444 | 445 | sinon@^1.15.4: 446 | version "1.17.7" 447 | resolved "https://registry.yarnpkg.com/sinon/-/sinon-1.17.7.tgz#4542a4f49ba0c45c05eb2e9dd9d203e2b8efe0bf" 448 | dependencies: 449 | formatio "1.1.1" 450 | lolex "1.3.2" 451 | samsam "1.1.2" 452 | util ">=0.10.3 <1" 453 | 454 | source-map@^0.4.4: 455 | version "0.4.4" 456 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" 457 | dependencies: 458 | amdefine ">=0.0.4" 459 | 460 | source-map@~0.2.0: 461 | version "0.2.0" 462 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d" 463 | dependencies: 464 | amdefine ">=0.0.4" 465 | 466 | source-map@~0.5.1: 467 | version "0.5.6" 468 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" 469 | 470 | sprintf-js@~1.0.2: 471 | version "1.0.3" 472 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 473 | 474 | supports-color@1.2.0: 475 | version "1.2.0" 476 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-1.2.0.tgz#ff1ed1e61169d06b3cf2d588e188b18d8847e17e" 477 | 478 | supports-color@^3.1.0: 479 | version "3.2.3" 480 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" 481 | dependencies: 482 | has-flag "^1.0.0" 483 | 484 | to-iso-string@0.0.2: 485 | version "0.0.2" 486 | resolved "https://registry.yarnpkg.com/to-iso-string/-/to-iso-string-0.0.2.tgz#4dc19e664dfccbe25bd8db508b00c6da158255d1" 487 | 488 | type-check@~0.3.1: 489 | version "0.3.2" 490 | resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" 491 | dependencies: 492 | prelude-ls "~1.1.2" 493 | 494 | type-detect@0.1.1: 495 | version "0.1.1" 496 | resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-0.1.1.tgz#0ba5ec2a885640e470ea4e8505971900dac58822" 497 | 498 | type-detect@^1.0.0: 499 | version "1.0.0" 500 | resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2" 501 | 502 | uglify-js@^2.6: 503 | version "2.7.5" 504 | resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.7.5.tgz#4612c0c7baaee2ba7c487de4904ae122079f2ca8" 505 | dependencies: 506 | async "~0.2.6" 507 | source-map "~0.5.1" 508 | uglify-to-browserify "~1.0.0" 509 | yargs "~3.10.0" 510 | 511 | uglify-to-browserify@~1.0.0: 512 | version "1.0.2" 513 | resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" 514 | 515 | "util@>=0.10.3 <1": 516 | version "0.10.3" 517 | resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" 518 | dependencies: 519 | inherits "2.0.1" 520 | 521 | which@^1.1.1: 522 | version "1.2.12" 523 | resolved "https://registry.yarnpkg.com/which/-/which-1.2.12.tgz#de67b5e450269f194909ef23ece4ebe416fa1192" 524 | dependencies: 525 | isexe "^1.1.1" 526 | 527 | window-size@0.1.0: 528 | version "0.1.0" 529 | resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" 530 | 531 | wordwrap@0.0.2: 532 | version "0.0.2" 533 | resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" 534 | 535 | wordwrap@^1.0.0: 536 | version "1.0.0" 537 | resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" 538 | 539 | wordwrap@~0.0.2: 540 | version "0.0.3" 541 | resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" 542 | 543 | wrappy@1: 544 | version "1.0.2" 545 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 546 | 547 | yargs@~3.10.0: 548 | version "3.10.0" 549 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" 550 | dependencies: 551 | camelcase "^1.0.2" 552 | cliui "^2.1.0" 553 | decamelize "^1.0.0" 554 | window-size "0.1.0" 555 | --------------------------------------------------------------------------------