├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── bin └── yang-express ├── config ├── default.yaml └── example-petstore.yaml ├── example └── example-petstore.yang ├── index.js ├── lib ├── discover.js ├── openapi.js └── restjson.js ├── package.json ├── swagger.yaml ├── yang-web-store.js ├── yang-web-store.yang └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # Users Environment Variables 40 | .lock-wscript 41 | 42 | *~ 43 | 44 | # ignore generated *.js files inside dist 45 | dist/* 46 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git* 2 | test* 3 | *~ 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # yang-express 2 | 3 | YANG model-driven Express framework 4 | 5 | Minimalistic web framework leveraging powerful YANG schema expressions 6 | according to [RFC 6020](http://tools.ietf.org/html/rfc6020). Generates 7 | dynamic model-driven interfaces with flexible plugin system. 8 | 9 | [![NPM Version][npm-image]][npm-url] 10 | [![NPM Downloads][downloads-image]][downloads-url] 11 | 12 | ## Installation 13 | 14 | ```bash 15 | $ npm install -g yang-express 16 | ``` 17 | The preferred installation is *global* for easy access to the 18 | `yang-express` utility but can also be used as a dependency module to 19 | enable YANG model-driven express app as part of your project. 20 | 21 | For development/testing, clone from repo and initialize: 22 | 23 | ```bash 24 | $ git clone https://github.com/corenova/yang-express 25 | $ cd yang-express 26 | $ npm install 27 | ``` 28 | 29 | ## Features 30 | 31 | * Robust model-driven routing 32 | * Hotplug runtime models 33 | * [Dynamic interface generators](#dynamic-interface-generators) 34 | * Hierarchical (deeply nested) data tree 35 | * Adaptive validations 36 | * Flexibe RPCs and notifications 37 | 38 | ## Quick Start 39 | 40 | ```bash 41 | $ yang-express example/example-petstore.yang 42 | ``` 43 | 44 | The above example will import the `example-petstore` YANG module and 45 | start an instance of `yang-express` listening on port 5000 with 46 | `restjson` feature enabled. 47 | 48 | ``` 49 | Usage: yang-express [options] modules... 50 | 51 | Options: 52 | -p, --port Run yang-express on (default: 5000) 53 | -f, --feature Enable one or more features: (restjson, openapi, etc.) 54 | ``` 55 | 56 | You can run `yang-express` inside your own project and it will 57 | dynamically import one or more `modules` and route them using the 58 | `feature` plugins specified. 59 | 60 | You can also use it as a library module: 61 | 62 | ```coffeescript 63 | require 'yang-js' 64 | opts = 65 | port: 5000 66 | feature: [ 'restjson', 'openapi' ] 67 | modules: [ 'ietf-yang-library' ] 68 | express = require('yang-express').eval() 69 | express.in('run') 70 | .invoke opts 71 | .then (res) -> console.log "running" 72 | .catch (err) -> console.error err 73 | ``` 74 | 75 | For more information on programmatic usage, be sure to take a look at 76 | the References listed below. 77 | 78 | ## References 79 | 80 | This module is a YANG model-driven data module, which is essentially a 81 | composition of the [YANG Schema](./yang-express.yang) and 82 | [Control Binding](./src/yang-exress.coffee). It is designed to model 83 | middleware routing runtime configuration and can be utilized with or 84 | without an actual [Express](http://expressjs.com) instance. 85 | 86 | - [Apiary Documentation](http://docs.yangexpress.apiary.io) 87 | - [Using YANG with JavaScript](http://github.com/corenova/yang-js) 88 | - [Using Model API](http://github.com/corenova/yang-js#model-instance) 89 | 90 | ## Examples 91 | 92 | **PetStore** is a simple example based on the provided spec sample in the 93 | [OpenAPI Specification 2.0](http://github.com/OAI/OpenAPPI-Specification) 94 | project. 95 | 96 | ```bash 97 | $ npm run example:petstore 98 | ``` 99 | 100 | When the `yang-express` app runs, it will auto-generate the data model 101 | using the [example-petstore.yang](./example/example-petstore.yang) 102 | schema and dynamically route the following endpoints utilizing the 103 | [restjson](./src/feature/restjson.coffee) dynamic interface 104 | generator: 105 | 106 | endpoint | methods | description 107 | --- | --- | --- 108 | /pet | **CRUMDO** | operate on the pet collection 109 | /pet/:id | **RUMDO** | operate on a specific pet 110 | /pet/:id/:leaf | **RUMDO** | operate on a pet's attribute 111 | /pet/:leaf | **RUMDO** | bulk operate attributes* 112 | 113 | This example runs using the 114 | [sample data](./config/example-petstore.yaml) found inside the 115 | `config` directory. 116 | 117 | ### CRUMDO 118 | 119 | - C: CREATE (POST) 120 | - R: READ (GET) 121 | - U: UPDATE (PUT) 122 | - M: MODIFY (PATCH) 123 | - D: DELETE 124 | - O: OPTIONS 125 | 126 | Alternative API endpoints can be fully-qualified `/petstore:pet/...` 127 | as well as prefix-qualified `/ps:pet/...`. This is the suggested 128 | convention when using multiple models that may have namespace 129 | conflict (if mounted together at '/'). 130 | 131 | **Note**: Bulk operation on all matching attributes can be used to set a new 132 | value for every matching attribute in the collection. 133 | 134 | ## Dynamic Interface Generators 135 | 136 | name | description 137 | --- | --- 138 | [restjson](./src/feature/restjson.coffee) | REST/JSON API 139 | [openapi](./src/feature/openapi.coffee) | OpenAPI/Swagger 2.0 spec 140 | [websocket](./src/feature/websocket.coffee) | [socket.io](http://socket.io) 141 | 142 | ## Tests 143 | 144 | To run the test suite, first install the dependencies, then run `npm 145 | test`: 146 | ``` 147 | $ npm install 148 | $ npm test 149 | ``` 150 | 151 | ## License 152 | [Apache 2.0](LICENSE) 153 | 154 | This software is brought to you by 155 | [Corenova Technologies](http://www.corenova.com). We'd love to hear 156 | your feedback. Please feel free to reach me at 157 | anytime with questions, suggestions, etc. 158 | 159 | [npm-image]: https://img.shields.io/npm/v/yang-express.svg 160 | [npm-url]: https://npmjs.org/package/yang-express 161 | [downloads-image]: https://img.shields.io/npm/dt/yang-express.svg 162 | [downloads-url]: https://npmjs.org/package/yang-express 163 | -------------------------------------------------------------------------------- /bin/yang-express: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | var argv = require('minimist')(process.argv.slice(2), { 5 | boolean: [ 'help' ], 6 | alias: { 7 | help: 'h', 8 | config: 'c', 9 | port: 'p', 10 | router: 'r', 11 | }, 12 | string: [ 'config', 'feature' ] 13 | }); 14 | 15 | if (argv.h === true) { 16 | var help; 17 | help = " Usage: yang-express [options] modules...\n\n"; 18 | help += " Options:\n"; 19 | help += " -c, --config Use to retrieve configuration data (default: uses 'config' directory)\n"; 20 | help += " -r, --router Enable one or more routers: (restjson, openapi, etc.)\n"; 21 | help += " -p, --port Run yang-express on \n"; 22 | console.info(help); 23 | process.exit(); 24 | } 25 | 26 | var config = require('config'); 27 | if (argv.config) { 28 | var path = require('path'); 29 | config = config.util.parseFile(path.resolve(argv.config)); 30 | } 31 | 32 | const port = argv.port 33 | const routers = [].concat(argv.router) 34 | const modules = [].concat(argv._) 35 | 36 | var Yang = require('yang-js'); 37 | var schema = require('../yang-web-store'); 38 | var schemas = modules.map(name => Yang.import(name)) 39 | var store = new Yang.Store({ name: 'express' }).use(schema, ...schemas).set(config); 40 | 41 | store.in('/web:server').merge({ 42 | port, routers, modules 43 | }) 44 | 45 | store.in('/web:listen').do() 46 | .catch(function (err) { 47 | console.error(err); 48 | }); 49 | -------------------------------------------------------------------------------- /config/default.yaml: -------------------------------------------------------------------------------- 1 | yang-openapi:info: 2 | title: YANG-EXPRESS 3 | description: | 4 | Provides YANG model-driven middleware routing based on Express.js 5 | web framework. 6 | 7 | Minimalistic web framework leveraging powerful YANG schema expressions 8 | according to [RFC 6020](http://tools.ietf.org/html/rfc6020). Generates 9 | dynamic model-driven interfaces with flexible plugin system. 10 | 11 | This output was generated using 12 | [yang-swagger](http://github.com/corenova/yang-swagger). 13 | version: '1.0' 14 | contact: 15 | name: Peter K. Lee 16 | url: https://github.com/corenova/yang-express 17 | email: peter@corenova.com 18 | license: 19 | name: 'Apache-2.0' 20 | -------------------------------------------------------------------------------- /config/example-petstore.yaml: -------------------------------------------------------------------------------- 1 | yang-openapi:info: 2 | title: PetStore 3 | description: | 4 | Example of YANG model-driven PetStore implementation 5 | 6 | This output was generated using 7 | [yang-swagger](http://github.com/corenova/yang-swagger). 8 | version: '1.0' 9 | contact: 10 | name: Peter K. Lee 11 | url: https://github.com/corenova/yang-express 12 | email: peter@corenova.com 13 | license: 14 | name: 'Apache-2.0' 15 | 16 | example-petstore:pet: 17 | - id: 1 18 | name: happy 19 | tag: friendly 20 | - id: 2 21 | name: boba 22 | tag: hyper 23 | -------------------------------------------------------------------------------- /example/example-petstore.yang: -------------------------------------------------------------------------------- 1 | module example-petstore { 2 | prefix ps; 3 | namespace "urn:ietf:params:xml:ns:yang:example-petstore"; 4 | description "Yang Petstore"; 5 | grouping Pet { 6 | leaf id { type uint64; mandatory true; } 7 | leaf name { type string; mandatory true; } 8 | leaf tag { type string; } 9 | } 10 | list pet { key "id"; uses Pet; } 11 | rpc upload { 12 | input { 13 | leaf file { 14 | type binary; 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const debug = require('debug')('yang:express'); 2 | 3 | const discover = require('./lib/discover'); 4 | const restjson = require('./lib/restjson'); 5 | 6 | module.exports = { 7 | discover, restjson, 8 | }; 9 | -------------------------------------------------------------------------------- /lib/discover.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ modules = [], store }) => (req, res, next) => { 2 | res.locals.modules = modules; 3 | if (req.path === '/') { 4 | res.locals = { model: store, match: store }; 5 | return next(); 6 | } 7 | 8 | for (const name of modules) { 9 | const model = store.access(name); 10 | if (!model) continue; 11 | const match = model.in(req.path); 12 | if (match) { 13 | model.context.logDebug(`discover: found '${name}' model for ${req.path}`); 14 | res.locals = { model, match }; 15 | break; 16 | } 17 | } 18 | return next('route'); 19 | }; 20 | -------------------------------------------------------------------------------- /lib/openapi.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const debug = require('debug')('yang:express:openapi') 4 | 5 | module.exports = (ctx) => { 6 | const Router = ctx.use('express').Router(); 7 | const mimes = [ 'openapi+yaml', 'openapi+json', 'yaml', 'json' ] 8 | 9 | router.route('/openapi.spec') 10 | .all((req, res, next) => { 11 | if (req.app.enabled('openapi') && 12 | req.accepts(mimes) && 13 | res.locals.modules) { 14 | next() 15 | } else { 16 | next('route') 17 | } 18 | }) 19 | .get((req, res, next) => { 20 | const { modules } = res.locals 21 | ctx.at('/openapi:transform').push({ modules }) 22 | .then(output => { 23 | const format = req.accepts('yaml') ? 'yaml' : 'json' 24 | return output.spec.serialize({ format }) 25 | }) 26 | .then(output => res.send(output.data)) 27 | .catch((err) => next(err)) 28 | }) 29 | return router 30 | }; 31 | -------------------------------------------------------------------------------- /lib/restjson.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const debug = require('debug')('yang:express:restjson'); 4 | const Router = require('express').Router; 5 | const bodyparser = require('body-parser'); 6 | const multipart = require('multer'); 7 | const autoreap = require('multer-autoreap'); 8 | 9 | const UPLOAD_DEST = '/tmp' 10 | 11 | const restjson = (opts = {}) => { 12 | const { 13 | passport = false, 14 | limit = '1mb', 15 | } = opts; 16 | const router = Router(); 17 | const bp = bodyparser; 18 | const mp = multipart({ dest: UPLOAD_DEST }); 19 | 20 | // middleware generator for common REST operations 21 | const transact = (callback) => { 22 | if (!callback) 23 | throw new Error('must specify callback function for transact') 24 | 25 | return async (req, res, next) => { 26 | const { match } = res.locals; 27 | const opts = passport ? { user: req.user } : {} 28 | try { 29 | if (Array.isArray(match)) { 30 | const props = await Promise.all(match.map(m => callback(m.context.with(opts), req.body))); 31 | res.send(props.map(prop => prop.toJSON(true))); 32 | } else { 33 | const prop = await callback(match.context.with(opts), req.body); 34 | res.send(prop.toJSON(true)); 35 | } 36 | } catch (err) { 37 | next(err); 38 | } 39 | } 40 | } 41 | 42 | // setup JSON body parser 43 | router.use(bp.urlencoded({ extended: true, limit }), 44 | bp.json({ strict: true, type:'*/json', limit })); 45 | 46 | router.route('*') 47 | .all((req, res, next) => { 48 | if (req.app.enabled('restjson') && 49 | req.accepts('json') && 50 | res.locals.match) { 51 | debug(`handling ${req.method} on ${req.path} using '${res.locals.model.name}' model`) 52 | next() 53 | } else { 54 | debug(`skipping ${req.path}`) 55 | next('route') 56 | } 57 | }) 58 | .options((req, res, next) => { 59 | const { match } = res.locals 60 | if (Array.isArray(match)) next('route'); 61 | else res.send(match.inspect()); 62 | }) 63 | .post(mp.any(), autoreap, (req, res, next) => { 64 | const { match } = res.locals 65 | if (Array.isArray(match)) return next('route'); 66 | let ctx = match.context; 67 | if (passport) { 68 | ctx = ctx.with({ user: req.user }); 69 | } 70 | switch (match.kind) { 71 | case 'action': 72 | case 'rpc': 73 | if (req.files) { 74 | res.on('autoreap', (file) => ctx.logDebug(`auto-reaped: ${file}`)); 75 | req.body = req.files.reduce((a,b) => { 76 | a[b.fieldname] = b.path; 77 | return a; 78 | },{}); 79 | } 80 | ctx.push(req.body) 81 | .then(out => res.send(out)) 82 | .catch(err => next(err)) 83 | break; 84 | case 'list': 85 | ctx.with({ createOnly: true }).push(req.body) 86 | .then(prop => res.status(201).send({ [prop.name]: prop.delta })) 87 | .catch(err => next(err)) 88 | break; 89 | default: 90 | res.status(400).end() 91 | } 92 | }) 93 | .get(transact((ctx) => ctx)) 94 | .put(transact((ctx, data) => ctx.push(data))) // replicate same behavior as PATCH 95 | //.put(transact((ctx, data) => ctx.with({ replace: true }).push(data))) 96 | .patch(transact((ctx, data) => ctx.push(data))) 97 | .delete(transact((ctx) => ctx.push(null))) 98 | 99 | // setup default error handler 100 | router.use((err, req, res, next) => { 101 | if (err instanceof Error) { 102 | let { name, message, context } = err 103 | const error = { name, message, context: `${context}` } 104 | res.status(500).send({ error }) 105 | } else next() 106 | }) 107 | return router 108 | } 109 | 110 | module.exports = restjson 111 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yang-express", 3 | "version": "2.1.9", 4 | "description": "YANG model-driven Express framework", 5 | "keywords": [ 6 | "yang", 7 | "express", 8 | "model", 9 | "schema", 10 | "adaptive", 11 | "validate", 12 | "object", 13 | "middleware", 14 | "route", 15 | "router", 16 | "rfc6020" 17 | ], 18 | "author": "Peter Lee ", 19 | "homepage": "https://github.com/corenova/yang-express", 20 | "license": "Apache-2.0", 21 | "repository": "corenova/yang-express", 22 | "main": "./index.js", 23 | "bin": { 24 | "yang-express": "./bin/yang-express" 25 | }, 26 | "preferGlobal": true, 27 | "yang": { 28 | "search": [ 29 | ".", 30 | "yang-js", 31 | "yang-swagger" 32 | ], 33 | "resolve": { 34 | "example-petstore": "./example/example-petstore.yang", 35 | "yang-web-store": "./yang-web-store.js" 36 | } 37 | }, 38 | "dependencies": { 39 | "body-parser": "^1.15.2", 40 | "config": "^1.21.0", 41 | "debug": "^4.1.1", 42 | "express": "^4.14.0", 43 | "js-yaml": "^3.13.1", 44 | "multer": "^1.4.1", 45 | "multer-autoreap": "^1.0.3" 46 | }, 47 | "devDependencies": { 48 | "mocha": "^5.2.0", 49 | "rimraf": "^2.5.2", 50 | "should": "~3.1.3", 51 | "yang-js": "^0.24.46", 52 | "yang-swagger": "^2.3.3" 53 | }, 54 | "peerDependencies": { 55 | "yang-js": "^0.24.0", 56 | "yang-swagger": "^2.3.0" 57 | }, 58 | "scripts": { 59 | "clean": "rimraf dist/*", 60 | "prepare:dist": "yarn clean -s && mkdir -p dist", 61 | "prepare:api": "yang-swagger -f yaml -o swagger.yaml yang-web-store", 62 | "prepare": "yarn prepare:dist", 63 | "prepublishOnly": "yarn prepare:api", 64 | "test": "mocha", 65 | "start": "bin/yang-express -r restjson yang-web-store", 66 | "example:petstore": "NODE_ENV=example NODE_APP_INSTANCE=petstore bin/yang-express --router restjson example-petstore" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /swagger.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | title: YANG-EXPRESS 4 | description: | 5 | Provides YANG model-driven middleware routing based on Express.js 6 | web framework. 7 | 8 | Minimalistic web framework leveraging powerful YANG schema expressions 9 | according to [RFC 6020](http://tools.ietf.org/html/rfc6020). Generates 10 | dynamic model-driven interfaces with flexible plugin system. 11 | 12 | This output was generated using 13 | [yang-swagger](http://github.com/corenova/yang-swagger). 14 | version: '1.0' 15 | contact: 16 | name: Peter K. Lee 17 | url: 'https://github.com/corenova/yang-express' 18 | email: peter@corenova.com 19 | license: 20 | name: Apache-2.0 21 | consumes: 22 | - application/json 23 | produces: 24 | - application/json 25 | response: [] 26 | tags: 27 | - name: yang-web-store 28 | description: Operations related to yang-web-store 29 | paths: 30 | '/yang-web-store:server': 31 | get: 32 | tags: 33 | - yang-web-store 34 | summary: View detail on server 35 | deprecated: false 36 | security: [] 37 | parameters: [] 38 | responses: 39 | '200': 40 | description: Expected response of server 41 | schema: 42 | allOf: [] 43 | oneOf: [] 44 | type: object 45 | properties: 46 | port: 47 | default: '5000' 48 | type: integer 49 | format: uint16 50 | hostname: 51 | type: string 52 | format: string 53 | modules: 54 | type: array 55 | items: 56 | type: string 57 | format: string 58 | routers: 59 | type: array 60 | items: 61 | type: string 62 | format: identityref 63 | put: 64 | tags: 65 | - yang-web-store 66 | summary: Update details on server 67 | deprecated: false 68 | security: [] 69 | parameters: 70 | - name: server 71 | in: body 72 | required: false 73 | schema: 74 | allOf: [] 75 | oneOf: [] 76 | type: object 77 | properties: 78 | port: 79 | default: '5000' 80 | type: integer 81 | format: uint16 82 | hostname: 83 | type: string 84 | format: string 85 | modules: 86 | type: array 87 | items: 88 | type: string 89 | format: string 90 | routers: 91 | type: array 92 | items: 93 | type: string 94 | format: identityref 95 | responses: 96 | '200': 97 | description: Expected response of server 98 | schema: 99 | allOf: [] 100 | oneOf: [] 101 | type: object 102 | properties: 103 | port: 104 | default: '5000' 105 | type: integer 106 | format: uint16 107 | hostname: 108 | type: string 109 | format: string 110 | modules: 111 | type: array 112 | items: 113 | type: string 114 | format: string 115 | routers: 116 | type: array 117 | items: 118 | type: string 119 | format: identityref 120 | patch: 121 | tags: 122 | - yang-web-store 123 | summary: Merge details on server 124 | deprecated: false 125 | security: [] 126 | parameters: 127 | - name: server 128 | in: body 129 | required: false 130 | schema: 131 | allOf: [] 132 | oneOf: [] 133 | type: object 134 | properties: 135 | port: 136 | default: '5000' 137 | type: integer 138 | format: uint16 139 | hostname: 140 | type: string 141 | format: string 142 | modules: 143 | type: array 144 | items: 145 | type: string 146 | format: string 147 | routers: 148 | type: array 149 | items: 150 | type: string 151 | format: identityref 152 | responses: 153 | '200': 154 | description: Expected response of server 155 | schema: 156 | allOf: [] 157 | oneOf: [] 158 | type: object 159 | properties: 160 | port: 161 | default: '5000' 162 | type: integer 163 | format: uint16 164 | hostname: 165 | type: string 166 | format: string 167 | modules: 168 | type: array 169 | items: 170 | type: string 171 | format: string 172 | routers: 173 | type: array 174 | items: 175 | type: string 176 | format: identityref 177 | delete: 178 | tags: 179 | - yang-web-store 180 | summary: Delete server from yang-web-store 181 | deprecated: false 182 | security: [] 183 | parameters: [] 184 | responses: 185 | '204': 186 | description: Expected response for delete 187 | parameters: [] 188 | definitions: {} 189 | -------------------------------------------------------------------------------- /yang-web-store.js: -------------------------------------------------------------------------------- 1 | /* 2 | YANG-EXPRESS (web server) middleware router module 3 | This YANG model-driven module enables dynamic web server middleware 4 | interface generation such as [restjson](restjson.coffee) and 5 | [openapi](openapi.coffee). 6 | 7 | It utilizes the [express](http://expressjs.com) web server framework 8 | to dynamically instanticate the web server and makes itself 9 | available for higher-order features to utilize it for associating 10 | additional routing endpoints. 11 | */ 12 | 13 | require('yang-js'); 14 | 15 | module.exports = require('./yang-web-store.yang').bind({ 16 | 17 | 'feature(express)': require('express'), 18 | 'feature(body-parser)': require('body-parser'), 19 | 'feature(multipart)': require('multer'), 20 | 'feature(discover)': require('./lib/discover'), 21 | 'feature(restjson)': require('./lib/restjson'), 22 | 'feature(openapi)': require('./lib/openapi'), 23 | 24 | '/server/hostname': { 25 | get: (ctx) => ctx.use('os').hostname() 26 | }, 27 | 28 | 'rpc(listen)': (ctx, input) => { 29 | const express = ctx.use('express'); 30 | const discover = ctx.use('discover'); 31 | const { app=express() } = input; 32 | const server = ctx.get('/server'); 33 | const { routers=[], modules=[], port } = server; 34 | ctx.logInfo(`listen on ${port} with %o routers for %o modules`, routers, modules); 35 | 36 | app.use(discover({ modules, store: ctx.store })); 37 | for (let routerName of routers) { 38 | const router = ctx.use(routerName); 39 | if (!router) continue 40 | app.use(router()); 41 | app.enable(routerName); 42 | ctx.logDebug(`enabled ${routerName} router`) 43 | } 44 | app.set('json spaces', 2) 45 | return { 46 | instance: app.listen(port) 47 | } 48 | } 49 | }) 50 | -------------------------------------------------------------------------------- /yang-web-store.yang: -------------------------------------------------------------------------------- 1 | module yang-web-store { 2 | namespace "urn:ietf:params:xml:ns:yang:yang-web-store"; 3 | prefix web; 4 | yang-version 1.1; 5 | 6 | import ietf-inet-types { prefix inet; } 7 | import ietf-yang-library { prefix yanglib; } 8 | import yang-openapi { prefix openapi; } 9 | 10 | organization 11 | "Corenova Technologies, Inc."; 12 | contact 13 | "Peter K. Lee "; 14 | description 15 | "This module provides YANG model-driven middleware routing"; 16 | 17 | revision 2018-11-12 { 18 | description 19 | "Update to ES6."; 20 | } 21 | revision 2016-09-14 { 22 | description 23 | "Initial revision."; 24 | } 25 | 26 | /* 27 | * Features 28 | */ 29 | feature express { 30 | description 31 | "Express.js web server framework capability. 32 | 33 | This feature component is the primary instance powering 34 | additional features provided by the yang-express module. 35 | 36 | It utilizes the Express.js web server framework to dynamically 37 | instanticate the web server and makes itself available for 38 | remote management."; 39 | reference "http://expressjs.com"; 40 | } 41 | feature router { 42 | if-feature express; 43 | } 44 | feature body-parser { 45 | if-feature express; 46 | } 47 | feature multipart { 48 | if-feature express; 49 | } 50 | feature discover { 51 | if-feature router; 52 | } 53 | feature restjson { 54 | if-feature router; 55 | if-feature body-parser; 56 | if-feature multipart; 57 | description "YANG model-driven REST/JSON middleware router"; 58 | } 59 | feature openapi { 60 | if-feature restjson; 61 | description "YANG model-driven openapi/swagger 2.0 spec generator"; 62 | } 63 | feature os { 64 | description "Operating System runtime variables"; 65 | } 66 | /* 67 | * Identities 68 | */ 69 | identity router; 70 | identity restjson { base router; } 71 | identity openapi { base router; } 72 | /* 73 | * Groupings 74 | */ 75 | /* 76 | * Configuration data nodes 77 | */ 78 | container server { 79 | leaf port { 80 | type uint16; 81 | default 5000; 82 | } 83 | leaf hostname { 84 | if-feature os; 85 | type string; 86 | config false; 87 | } 88 | leaf-list modules { 89 | // hack for now 90 | type string; 91 | // type leafref { 92 | // path '/yanglib:modules-state/module/name'; 93 | // } 94 | } 95 | leaf-list routers { 96 | type identityref { 97 | base router; 98 | } 99 | } 100 | } 101 | /* 102 | * Remote Procedure Calls 103 | */ 104 | rpc listen { 105 | if-feature express; 106 | description 107 | "Starts an instance of yang-express based on provided parameters."; 108 | input { 109 | anydata app { 110 | description 111 | "optional instance of express application"; 112 | } 113 | } 114 | output { 115 | anydata instance; 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | accepts@~1.3.7: 6 | version "1.3.7" 7 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" 8 | integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== 9 | dependencies: 10 | mime-types "~2.1.24" 11 | negotiator "0.6.2" 12 | 13 | append-field@^1.0.0: 14 | version "1.0.0" 15 | resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56" 16 | integrity sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY= 17 | 18 | argparse@^1.0.7: 19 | version "1.0.10" 20 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" 21 | integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== 22 | dependencies: 23 | sprintf-js "~1.0.2" 24 | 25 | array-flatten@1.1.1: 26 | version "1.1.1" 27 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 28 | integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= 29 | 30 | balanced-match@^1.0.0: 31 | version "1.0.0" 32 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 33 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 34 | 35 | body-parser@1.19.0, body-parser@^1.15.2: 36 | version "1.19.0" 37 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" 38 | integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== 39 | dependencies: 40 | bytes "3.1.0" 41 | content-type "~1.0.4" 42 | debug "2.6.9" 43 | depd "~1.1.2" 44 | http-errors "1.7.2" 45 | iconv-lite "0.4.24" 46 | on-finished "~2.3.0" 47 | qs "6.7.0" 48 | raw-body "2.4.0" 49 | type-is "~1.6.17" 50 | 51 | brace-expansion@^1.1.7: 52 | version "1.1.11" 53 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 54 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 55 | dependencies: 56 | balanced-match "^1.0.0" 57 | concat-map "0.0.1" 58 | 59 | browser-stdout@1.3.1: 60 | version "1.3.1" 61 | resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" 62 | integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== 63 | 64 | buffer-from@^1.0.0: 65 | version "1.1.1" 66 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" 67 | integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== 68 | 69 | busboy@^0.2.11: 70 | version "0.2.14" 71 | resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.2.14.tgz#6c2a622efcf47c57bbbe1e2a9c37ad36c7925453" 72 | integrity sha1-bCpiLvz0fFe7vh4qnDetNseSVFM= 73 | dependencies: 74 | dicer "0.2.5" 75 | readable-stream "1.1.x" 76 | 77 | bytes@3.1.0: 78 | version "3.1.0" 79 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" 80 | integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== 81 | 82 | co@^4.6.0: 83 | version "4.6.0" 84 | resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" 85 | integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= 86 | 87 | commander@2.15.1: 88 | version "2.15.1" 89 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" 90 | integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag== 91 | 92 | "comparse@>= 0.9.x", comparse@^0.9.3: 93 | version "0.9.3" 94 | resolved "https://registry.yarnpkg.com/comparse/-/comparse-0.9.3.tgz#2ce73a4895e0ee1b333db11689c2f55c0ff9a962" 95 | integrity sha1-LOc6SJXg7hszPbEWicL1XA/5qWI= 96 | 97 | concat-map@0.0.1: 98 | version "0.0.1" 99 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 100 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 101 | 102 | concat-stream@^1.5.2: 103 | version "1.6.2" 104 | resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" 105 | integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== 106 | dependencies: 107 | buffer-from "^1.0.0" 108 | inherits "^2.0.3" 109 | readable-stream "^2.2.2" 110 | typedarray "^0.0.6" 111 | 112 | config@^1.21.0: 113 | version "1.31.0" 114 | resolved "https://registry.yarnpkg.com/config/-/config-1.31.0.tgz#ab08aeba6536015d220cd0afe14b3e0501082542" 115 | integrity sha512-Ep/l9Rd1J9IPueztJfpbOqVzuKHQh4ZODMNt9xqTYdBBNRXbV4oTu34kCkkfdRVcDq0ohtpaeXGgb+c0LQxFRA== 116 | dependencies: 117 | json5 "^1.0.1" 118 | 119 | config@^3.0.1: 120 | version "3.3.1" 121 | resolved "https://registry.yarnpkg.com/config/-/config-3.3.1.tgz#b6a70e2908a43b98ed20be7e367edf0cc8ed5a19" 122 | integrity sha512-+2/KaaaAzdwUBE3jgZON11L1ggLLhpf2FsGrfqYFHZW22ySGv/HqYIXrBwKKvn+XZh1UBUjHwAcrfsSkSygT+Q== 123 | dependencies: 124 | json5 "^2.1.1" 125 | 126 | content-disposition@0.5.3: 127 | version "0.5.3" 128 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" 129 | integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== 130 | dependencies: 131 | safe-buffer "5.1.2" 132 | 133 | content-type@~1.0.4: 134 | version "1.0.4" 135 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" 136 | integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== 137 | 138 | cookie-signature@1.0.6: 139 | version "1.0.6" 140 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 141 | integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= 142 | 143 | cookie@0.4.0: 144 | version "0.4.0" 145 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" 146 | integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== 147 | 148 | core-util-is@~1.0.0: 149 | version "1.0.2" 150 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 151 | integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= 152 | 153 | debug@2.6.9: 154 | version "2.6.9" 155 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 156 | integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== 157 | dependencies: 158 | ms "2.0.0" 159 | 160 | debug@3.1.0: 161 | version "3.1.0" 162 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" 163 | integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== 164 | dependencies: 165 | ms "2.0.0" 166 | 167 | debug@^3.1.0: 168 | version "3.2.7" 169 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" 170 | integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== 171 | dependencies: 172 | ms "^2.1.1" 173 | 174 | debug@^4.1.1: 175 | version "4.1.1" 176 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" 177 | integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== 178 | dependencies: 179 | ms "^2.1.1" 180 | 181 | delegates@^1.0.0: 182 | version "1.0.0" 183 | resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" 184 | integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= 185 | 186 | depd@~1.1.2: 187 | version "1.1.2" 188 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" 189 | integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= 190 | 191 | destroy@~1.0.4: 192 | version "1.0.4" 193 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" 194 | integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= 195 | 196 | dicer@0.2.5: 197 | version "0.2.5" 198 | resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.2.5.tgz#5996c086bb33218c812c090bddc09cd12facb70f" 199 | integrity sha1-WZbAhrszIYyBLAkL3cCc0S+stw8= 200 | dependencies: 201 | readable-stream "1.1.x" 202 | streamsearch "0.1.2" 203 | 204 | diff@3.5.0: 205 | version "3.5.0" 206 | resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" 207 | integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== 208 | 209 | ee-first@1.1.1: 210 | version "1.1.1" 211 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 212 | integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= 213 | 214 | encodeurl@~1.0.2: 215 | version "1.0.2" 216 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" 217 | integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= 218 | 219 | es6-object-assign@^1.1.0: 220 | version "1.1.0" 221 | resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c" 222 | integrity sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw= 223 | 224 | escape-html@~1.0.3: 225 | version "1.0.3" 226 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 227 | integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= 228 | 229 | escape-string-regexp@1.0.5: 230 | version "1.0.5" 231 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 232 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= 233 | 234 | esprima@^4.0.0: 235 | version "4.0.1" 236 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" 237 | integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== 238 | 239 | etag@~1.8.1: 240 | version "1.8.1" 241 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" 242 | integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= 243 | 244 | express@^4.14.0: 245 | version "4.17.1" 246 | resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" 247 | integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== 248 | dependencies: 249 | accepts "~1.3.7" 250 | array-flatten "1.1.1" 251 | body-parser "1.19.0" 252 | content-disposition "0.5.3" 253 | content-type "~1.0.4" 254 | cookie "0.4.0" 255 | cookie-signature "1.0.6" 256 | debug "2.6.9" 257 | depd "~1.1.2" 258 | encodeurl "~1.0.2" 259 | escape-html "~1.0.3" 260 | etag "~1.8.1" 261 | finalhandler "~1.1.2" 262 | fresh "0.5.2" 263 | merge-descriptors "1.0.1" 264 | methods "~1.1.2" 265 | on-finished "~2.3.0" 266 | parseurl "~1.3.3" 267 | path-to-regexp "0.1.7" 268 | proxy-addr "~2.0.5" 269 | qs "6.7.0" 270 | range-parser "~1.2.1" 271 | safe-buffer "5.1.2" 272 | send "0.17.1" 273 | serve-static "1.14.1" 274 | setprototypeof "1.1.1" 275 | statuses "~1.5.0" 276 | type-is "~1.6.18" 277 | utils-merge "1.0.1" 278 | vary "~1.1.2" 279 | 280 | finalhandler@~1.1.2: 281 | version "1.1.2" 282 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" 283 | integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== 284 | dependencies: 285 | debug "2.6.9" 286 | encodeurl "~1.0.2" 287 | escape-html "~1.0.3" 288 | on-finished "~2.3.0" 289 | parseurl "~1.3.3" 290 | statuses "~1.5.0" 291 | unpipe "~1.0.0" 292 | 293 | forwarded@~0.1.2: 294 | version "0.1.2" 295 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" 296 | integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= 297 | 298 | fresh@0.5.2: 299 | version "0.5.2" 300 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" 301 | integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= 302 | 303 | fs.realpath@^1.0.0: 304 | version "1.0.0" 305 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 306 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 307 | 308 | glob@7.1.2: 309 | version "7.1.2" 310 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" 311 | integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ== 312 | dependencies: 313 | fs.realpath "^1.0.0" 314 | inflight "^1.0.4" 315 | inherits "2" 316 | minimatch "^3.0.4" 317 | once "^1.3.0" 318 | path-is-absolute "^1.0.0" 319 | 320 | glob@^7.1.3: 321 | version "7.1.6" 322 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" 323 | integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== 324 | dependencies: 325 | fs.realpath "^1.0.0" 326 | inflight "^1.0.4" 327 | inherits "2" 328 | minimatch "^3.0.4" 329 | once "^1.3.0" 330 | path-is-absolute "^1.0.0" 331 | 332 | growl@1.10.5: 333 | version "1.10.5" 334 | resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" 335 | integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== 336 | 337 | has-flag@^3.0.0: 338 | version "3.0.0" 339 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 340 | integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= 341 | 342 | he@1.1.1: 343 | version "1.1.1" 344 | resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" 345 | integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0= 346 | 347 | http-errors@1.7.2: 348 | version "1.7.2" 349 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" 350 | integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== 351 | dependencies: 352 | depd "~1.1.2" 353 | inherits "2.0.3" 354 | setprototypeof "1.1.1" 355 | statuses ">= 1.5.0 < 2" 356 | toidentifier "1.0.0" 357 | 358 | http-errors@~1.7.2: 359 | version "1.7.3" 360 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" 361 | integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== 362 | dependencies: 363 | depd "~1.1.2" 364 | inherits "2.0.4" 365 | setprototypeof "1.1.1" 366 | statuses ">= 1.5.0 < 2" 367 | toidentifier "1.0.0" 368 | 369 | iconv-lite@0.4.24: 370 | version "0.4.24" 371 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" 372 | integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== 373 | dependencies: 374 | safer-buffer ">= 2.1.2 < 3" 375 | 376 | indent-string@^2.1.0: 377 | version "2.1.0" 378 | resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" 379 | integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA= 380 | dependencies: 381 | repeating "^2.0.0" 382 | 383 | inflight@^1.0.4: 384 | version "1.0.6" 385 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 386 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 387 | dependencies: 388 | once "^1.3.0" 389 | wrappy "1" 390 | 391 | inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: 392 | version "2.0.4" 393 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 394 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 395 | 396 | inherits@2.0.3: 397 | version "2.0.3" 398 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 399 | integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= 400 | 401 | ipaddr.js@1.9.1: 402 | version "1.9.1" 403 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" 404 | integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== 405 | 406 | is-finite@^1.0.0: 407 | version "1.1.0" 408 | resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3" 409 | integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w== 410 | 411 | isarray@0.0.1: 412 | version "0.0.1" 413 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" 414 | integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= 415 | 416 | isarray@~1.0.0: 417 | version "1.0.0" 418 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 419 | integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= 420 | 421 | js-yaml@^3.13.1: 422 | version "3.14.0" 423 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" 424 | integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== 425 | dependencies: 426 | argparse "^1.0.7" 427 | esprima "^4.0.0" 428 | 429 | json5@^1.0.1: 430 | version "1.0.1" 431 | resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" 432 | integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== 433 | dependencies: 434 | minimist "^1.2.0" 435 | 436 | json5@^2.1.1: 437 | version "2.1.3" 438 | resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" 439 | integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== 440 | dependencies: 441 | minimist "^1.2.5" 442 | 443 | media-typer@0.3.0: 444 | version "0.3.0" 445 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 446 | integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= 447 | 448 | merge-descriptors@1.0.1: 449 | version "1.0.1" 450 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 451 | integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= 452 | 453 | methods@~1.1.2: 454 | version "1.1.2" 455 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 456 | integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= 457 | 458 | mime-db@1.44.0: 459 | version "1.44.0" 460 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" 461 | integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== 462 | 463 | mime-types@~2.1.24: 464 | version "2.1.27" 465 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" 466 | integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== 467 | dependencies: 468 | mime-db "1.44.0" 469 | 470 | mime@1.6.0: 471 | version "1.6.0" 472 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" 473 | integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== 474 | 475 | minimatch@3.0.4, minimatch@^3.0.4: 476 | version "3.0.4" 477 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 478 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 479 | dependencies: 480 | brace-expansion "^1.1.7" 481 | 482 | minimist@0.0.8: 483 | version "0.0.8" 484 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 485 | integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= 486 | 487 | minimist@^1.2.0, minimist@^1.2.5: 488 | version "1.2.5" 489 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" 490 | integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== 491 | 492 | mkdirp@0.5.1: 493 | version "0.5.1" 494 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 495 | integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= 496 | dependencies: 497 | minimist "0.0.8" 498 | 499 | mkdirp@^0.5.1: 500 | version "0.5.5" 501 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" 502 | integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== 503 | dependencies: 504 | minimist "^1.2.5" 505 | 506 | mocha@^5.2.0: 507 | version "5.2.0" 508 | resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.2.0.tgz#6d8ae508f59167f940f2b5b3c4a612ae50c90ae6" 509 | integrity sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ== 510 | dependencies: 511 | browser-stdout "1.3.1" 512 | commander "2.15.1" 513 | debug "3.1.0" 514 | diff "3.5.0" 515 | escape-string-regexp "1.0.5" 516 | glob "7.1.2" 517 | growl "1.10.5" 518 | he "1.1.1" 519 | minimatch "3.0.4" 520 | mkdirp "0.5.1" 521 | supports-color "5.4.0" 522 | 523 | ms@2.0.0: 524 | version "2.0.0" 525 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 526 | integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= 527 | 528 | ms@2.1.1: 529 | version "2.1.1" 530 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" 531 | integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== 532 | 533 | ms@^2.1.1: 534 | version "2.1.2" 535 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 536 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 537 | 538 | multer-autoreap@^1.0.3: 539 | version "1.0.3" 540 | resolved "https://registry.yarnpkg.com/multer-autoreap/-/multer-autoreap-1.0.3.tgz#a50aaeb713fa9407ac940807f6c112c6ce9df280" 541 | integrity sha512-g0wISfylN2bchQglyAgQTIHoiLUcYQTXKmQh+fKJpheGay9aDqHmcMYRwWRNJ+tK95j9/NZ5QNFkqRytrgw34g== 542 | dependencies: 543 | debug "^3.1.0" 544 | es6-object-assign "^1.1.0" 545 | on-finished "^2.3.0" 546 | 547 | multer@^1.4.1: 548 | version "1.4.2" 549 | resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.2.tgz#2f1f4d12dbaeeba74cb37e623f234bf4d3d2057a" 550 | integrity sha512-xY8pX7V+ybyUpbYMxtjM9KAiD9ixtg5/JkeKUTD6xilfDv0vzzOFcCp4Ljb1UU3tSOM3VTZtKo63OmzOrGi3Cg== 551 | dependencies: 552 | append-field "^1.0.0" 553 | busboy "^0.2.11" 554 | concat-stream "^1.5.2" 555 | mkdirp "^0.5.1" 556 | object-assign "^4.1.1" 557 | on-finished "^2.3.0" 558 | type-is "^1.6.4" 559 | xtend "^4.0.0" 560 | 561 | negotiator@0.6.2: 562 | version "0.6.2" 563 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" 564 | integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== 565 | 566 | object-assign@^4.1.1: 567 | version "4.1.1" 568 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 569 | integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= 570 | 571 | on-finished@^2.3.0, on-finished@~2.3.0: 572 | version "2.3.0" 573 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" 574 | integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= 575 | dependencies: 576 | ee-first "1.1.1" 577 | 578 | once@^1.3.0: 579 | version "1.4.0" 580 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 581 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 582 | dependencies: 583 | wrappy "1" 584 | 585 | parseurl@~1.3.3: 586 | version "1.3.3" 587 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" 588 | integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== 589 | 590 | path-is-absolute@^1.0.0: 591 | version "1.0.1" 592 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 593 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 594 | 595 | path-to-regexp@0.1.7: 596 | version "0.1.7" 597 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 598 | integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= 599 | 600 | process-nextick-args@~2.0.0: 601 | version "2.0.1" 602 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" 603 | integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== 604 | 605 | proxy-addr@~2.0.5: 606 | version "2.0.6" 607 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" 608 | integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw== 609 | dependencies: 610 | forwarded "~0.1.2" 611 | ipaddr.js "1.9.1" 612 | 613 | qs@6.7.0: 614 | version "6.7.0" 615 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" 616 | integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== 617 | 618 | range-parser@~1.2.1: 619 | version "1.2.1" 620 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" 621 | integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== 622 | 623 | raw-body@2.4.0: 624 | version "2.4.0" 625 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" 626 | integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== 627 | dependencies: 628 | bytes "3.1.0" 629 | http-errors "1.7.2" 630 | iconv-lite "0.4.24" 631 | unpipe "1.0.0" 632 | 633 | readable-stream@1.1.x: 634 | version "1.1.14" 635 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" 636 | integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= 637 | dependencies: 638 | core-util-is "~1.0.0" 639 | inherits "~2.0.1" 640 | isarray "0.0.1" 641 | string_decoder "~0.10.x" 642 | 643 | readable-stream@^2.2.2: 644 | version "2.3.7" 645 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" 646 | integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== 647 | dependencies: 648 | core-util-is "~1.0.0" 649 | inherits "~2.0.3" 650 | isarray "~1.0.0" 651 | process-nextick-args "~2.0.0" 652 | safe-buffer "~5.1.1" 653 | string_decoder "~1.1.1" 654 | util-deprecate "~1.0.1" 655 | 656 | repeating@^2.0.0: 657 | version "2.0.1" 658 | resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" 659 | integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= 660 | dependencies: 661 | is-finite "^1.0.0" 662 | 663 | rimraf@^2.5.2: 664 | version "2.7.1" 665 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" 666 | integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== 667 | dependencies: 668 | glob "^7.1.3" 669 | 670 | safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: 671 | version "5.1.2" 672 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 673 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== 674 | 675 | "safer-buffer@>= 2.1.2 < 3": 676 | version "2.1.2" 677 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 678 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== 679 | 680 | send@0.17.1: 681 | version "0.17.1" 682 | resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" 683 | integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== 684 | dependencies: 685 | debug "2.6.9" 686 | depd "~1.1.2" 687 | destroy "~1.0.4" 688 | encodeurl "~1.0.2" 689 | escape-html "~1.0.3" 690 | etag "~1.8.1" 691 | fresh "0.5.2" 692 | http-errors "~1.7.2" 693 | mime "1.6.0" 694 | ms "2.1.1" 695 | on-finished "~2.3.0" 696 | range-parser "~1.2.1" 697 | statuses "~1.5.0" 698 | 699 | serve-static@1.14.1: 700 | version "1.14.1" 701 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" 702 | integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== 703 | dependencies: 704 | encodeurl "~1.0.2" 705 | escape-html "~1.0.3" 706 | parseurl "~1.3.3" 707 | send "0.17.1" 708 | 709 | setprototypeof@1.1.1: 710 | version "1.1.1" 711 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" 712 | integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== 713 | 714 | should@~3.1.3: 715 | version "3.1.4" 716 | resolved "https://registry.yarnpkg.com/should/-/should-3.1.4.tgz#ac280c6e5fc9d35c77d68b95ef1bc60bd554a731" 717 | integrity sha1-rCgMbl/J01x31ouV7xvGC9VUpzE= 718 | 719 | sprintf-js@~1.0.2: 720 | version "1.0.3" 721 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 722 | integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= 723 | 724 | stacktrace-parser@^0.1.4: 725 | version "0.1.10" 726 | resolved "https://registry.yarnpkg.com/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz#29fb0cae4e0d0b85155879402857a1639eb6051a" 727 | integrity sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg== 728 | dependencies: 729 | type-fest "^0.7.1" 730 | 731 | "statuses@>= 1.5.0 < 2", statuses@~1.5.0: 732 | version "1.5.0" 733 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" 734 | integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= 735 | 736 | streamsearch@0.1.2: 737 | version "0.1.2" 738 | resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" 739 | integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= 740 | 741 | string_decoder@~0.10.x: 742 | version "0.10.31" 743 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" 744 | integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= 745 | 746 | string_decoder@~1.1.1: 747 | version "1.1.1" 748 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" 749 | integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== 750 | dependencies: 751 | safe-buffer "~5.1.0" 752 | 753 | supports-color@5.4.0: 754 | version "5.4.0" 755 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" 756 | integrity sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w== 757 | dependencies: 758 | has-flag "^3.0.0" 759 | 760 | toidentifier@1.0.0: 761 | version "1.0.0" 762 | resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" 763 | integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== 764 | 765 | traverse@^0.6.6: 766 | version "0.6.6" 767 | resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137" 768 | integrity sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc= 769 | 770 | type-fest@^0.7.1: 771 | version "0.7.1" 772 | resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48" 773 | integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg== 774 | 775 | type-is@^1.6.4, type-is@~1.6.17, type-is@~1.6.18: 776 | version "1.6.18" 777 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" 778 | integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== 779 | dependencies: 780 | media-typer "0.3.0" 781 | mime-types "~2.1.24" 782 | 783 | typedarray@^0.0.6: 784 | version "0.0.6" 785 | resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" 786 | integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= 787 | 788 | unpipe@1.0.0, unpipe@~1.0.0: 789 | version "1.0.0" 790 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 791 | integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= 792 | 793 | util-deprecate@~1.0.1: 794 | version "1.0.2" 795 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 796 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= 797 | 798 | utils-merge@1.0.1: 799 | version "1.0.1" 800 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" 801 | integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= 802 | 803 | vary@~1.1.2: 804 | version "1.1.2" 805 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" 806 | integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= 807 | 808 | wrappy@1: 809 | version "1.0.2" 810 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 811 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 812 | 813 | xparse@^1.0.0: 814 | version "1.0.0" 815 | resolved "https://registry.yarnpkg.com/xparse/-/xparse-1.0.0.tgz#4775a4bae747c8263ce51af9cef95ee169ac2efd" 816 | integrity sha1-R3WkuudHyCY85Rr5zvle4WmsLv0= 817 | dependencies: 818 | comparse "^0.9.3" 819 | 820 | xtend@^4.0.0: 821 | version "4.0.2" 822 | resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" 823 | integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== 824 | 825 | yang-js@^0.24.28: 826 | version "0.24.28" 827 | resolved "https://registry.yarnpkg.com/yang-js/-/yang-js-0.24.28.tgz#5b4a5e4a077353f785263caea8899bacf1f25a0c" 828 | integrity sha512-90/UgvmKA0E00fwTMIBZY+J6xztn79s9xfVeQ+FEgak0X1td7m/lUYUxbslGZHsIwo0+cftmEQ54HX/OE0n60A== 829 | dependencies: 830 | co "^4.6.0" 831 | debug "^4.1.1" 832 | delegates "^1.0.0" 833 | indent-string "^2.1.0" 834 | stacktrace-parser "^0.1.4" 835 | xparse "^1.0.0" 836 | yang-parser "^0.2.1" 837 | 838 | yang-js@^0.24.46: 839 | version "0.24.46" 840 | resolved "https://registry.yarnpkg.com/yang-js/-/yang-js-0.24.46.tgz#973ecc89ac18c7490a448dd0049681c2ae7e1415" 841 | integrity sha512-Cv+VS4uVrKXgvphw6z6akQAaiiOjIUEMGLxRS6WMTlJZdjZDKvYWCEqVeuGI21IA2s+WRC5gJc/ofKVIjU2nKA== 842 | dependencies: 843 | debug "^4.1.1" 844 | delegates "^1.0.0" 845 | indent-string "^2.1.0" 846 | stacktrace-parser "^0.1.4" 847 | xparse "^1.0.0" 848 | yang-parser "^0.2.1" 849 | 850 | yang-parser@^0.2.1: 851 | version "0.2.1" 852 | resolved "https://registry.yarnpkg.com/yang-parser/-/yang-parser-0.2.1.tgz#b937ca78d887b81559c71c608fe9d850ebb7a77a" 853 | integrity sha1-uTfKeNiHuBVZxxxgj+nYUOu3p3o= 854 | dependencies: 855 | comparse ">= 0.9.x" 856 | 857 | yang-swagger@^2.3.3: 858 | version "2.3.3" 859 | resolved "https://registry.yarnpkg.com/yang-swagger/-/yang-swagger-2.3.3.tgz#2485580709744cf4b68cee7fe2cc48feef471f90" 860 | integrity sha512-Io9NxmRSO+/TN8JpKiD56AvqsJFs8RpPjv4KXerPLJFORHYQ7uWv5EvT8+i7nCw1M1x099F0gpOrzWRqXFUuEg== 861 | dependencies: 862 | config "^3.0.1" 863 | debug "^4.1.1" 864 | js-yaml "^3.13.1" 865 | minimist "^1.2.0" 866 | traverse "^0.6.6" 867 | yang-js "^0.24.28" 868 | --------------------------------------------------------------------------------