From 6ec0f455fa676c1ad23e76fab0a13567f158337d Mon Sep 17 00:00:00 2001 From: Adam Miller Date: Fri, 3 Jan 2020 15:18:30 -0500 Subject: [PATCH] feat: Typescript conversion, refactor. --- dist/index.d.ts | 9 + dist/index.js | 230 ++++++++++ index.js | 232 ---------- package.json | 19 +- src/index.ts | 248 +++++++++++ tsconfig.json | 48 +++ yarn.lock | 1099 +++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 1649 insertions(+), 236 deletions(-) create mode 100644 dist/index.d.ts create mode 100644 dist/index.js delete mode 100644 index.js create mode 100644 src/index.ts create mode 100644 tsconfig.json create mode 100644 yarn.lock diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000..a1b26f4 --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,9 @@ +import * as Express from 'express'; +export interface IApiHandler { + ALL?: Express.RequestHandler; + GET?: Express.RequestHandler; + POST?: Express.RequestHandler; + PUT?: Express.RequestHandler; + DELETE?: Express.RequestHandler; +} +export default function api(express: any, apiPath?: string): Express.Express; diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..5450894 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,230 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const path = require("path"); +const walk = require("walk"); +const chalk = require("chalk"); +const DEFAULT_API_DIR = path.join(process.cwd(), 'api'); +// If no API endpoint takes the bait, return the properly formatted 400 error +// for the media requested. JSON for ajax, just the http status code for all others +function apiNotFound(req, res, next) { + if (typeof req !== 'object') + return; + res.status(400); + if (req.xhr) { + console.error(chalk.red('✘ Error routing to API path '), chalk.red(req.path)); + return res.json({ code: 404, status: 'error', message: 'Method Not Implemented' }); + } + return next(); +} +const evalAPI = function (func) { + return function (req, res, next) { + return __awaiter(this, void 0, void 0, function* () { + // Evaluate API function + try { + const result = yield func(req, res, next); + if (typeof result === 'object') { + return res.status((result.code || 200)).json(result); + } + console.error('✘ API endpoint returned something other than JSON or a Promise:', result, func); + return res.status(500).json({ status: 'error', message: 'Invalid Response' }); + } + catch (err) { + // If internal API flag is present, just return the result to the next handler + if (req._internalAPI) + throw err; + console.error('✘ API promise rejected and returning non 200 response:', err); + return res.status((err.code || 500)).json(err); + } + }); + }; +}; +function hasValidMethod(handler) { + return typeof handler === 'object' && + (typeof handler.ALL === 'function' + || typeof handler.GET === 'function' + || typeof handler.POST === 'function' + || typeof handler.PUT === 'function' + || typeof handler.DELETE === 'function'); +} +function loadAPI(router, filePath, apiPath) { + let methods = ''; + try { + const handler = require(filePath); + // If handler is a function, register it as a get callback + if (typeof handler === 'function' && (methods += ' GET')) + router.get(apiPath, evalAPI(handler)); + // If handler is an object with any valid http method, register them + else if (hasValidMethod(handler)) { + if (typeof handler.ALL === 'function' && (methods += ' ALL')) + router.all(apiPath, evalAPI(handler.ALL)); + if (typeof handler.GET === 'function' && (methods += ' GET')) + router.get(apiPath, evalAPI(handler.GET)); + if (typeof handler.POST === 'function' && (methods += ' POST')) + router.post(apiPath, evalAPI(handler.POST)); + if (typeof handler.PUT === 'function' && (methods += ' PUT')) + router.put(apiPath, evalAPI(handler.PUT)); + if (typeof handler.DELETE === 'function' && (methods += ' DELETE')) + router.delete(apiPath, evalAPI(handler.DELETE)); + } + // Otherwise, this is an invalid export. Error. + else { + return console.error(chalk.bold.red(' ✘ Error in API:'), chalk.bold.black(apiPath), chalk.gray(' - no valid HTTP method exported')); + } + console.log(chalk.green(' • Registered:'), (apiPath ? apiPath : '/'), chalk.yellow('(' + methods.trim() + ')')); + } + catch (err) { + // If require() failed, error + console.error(chalk.bold.red(' ✘ Error in API:'), chalk.bold.black(apiPath), chalk.gray(' - error in the API file')); + console.error(' ', chalk.underline(filePath)); + console.error(' ', err.toString().replace(/(\r\n|\r|\n)/gm, '$1 ')); + } +} +function discoverAPI(router, apiDir) { + var queue = [], options = { + listeners: { + file: function (root, fileStats, next) { + // Ignore hidden files + if (fileStats.name[0] === '.') + return next(); + // Construct both the absolute file path, and public facing API path + var filePath = path.join(root, fileStats.name), apiPath = filePath.replace(apiDir, '').replace(/\/index.js$/, '').replace(/.js$/, ''); + // Push them to our queue. This later sorted in order of route precedence. + queue.push({ apiPath, filePath }); + // Process next file + next(); + }, + end: function () { + // Sort queue in reverse alphabetical order. + // Has the nice side effect of ordering by route precedence + queue.sort(function (file1, file2) { + return (file1.apiPath > file2.apiPath) ? 1 : -1; + }); + // For each API item in the queue, load it into our router + while (queue.length) { + const file = queue.pop(); // TODO: When ES6 is common in node, make let + loadAPI(router, file.filePath, file.apiPath); + } + // When we have loaded all of our API endpoints, register our catchall route + router.all('*', apiNotFound); + } + } + }; + try { + walk.walkSync(apiDir, options); + } + catch (e) { + console.error(chalk.bold.red('✘ Error reading API directory: '), e); + } +} +class ApiQuery { + constructor(router, method, req, res, path, body) { + this.router = router; + this.path = path; + this.req = Object.create(req, { + url: { writable: true, configurable: true, value: this.path }, + method: { writable: true, configurable: true, value: method }, + ip: { writable: true, configurable: true, value: '127.0.0.1' }, + body: { writable: true, configurable: true, value: (body || {}) }, + query: { writable: true, configurable: true, value: {} }, + params: { writable: true, configurable: true, value: {} }, + originalUrl: { writable: true, configurable: true, value: undefined }, + _internalAPI: { writable: true, configurable: true, value: true } + }); + this.res = res; + return this; + } + then(callback, errCallback) { + return __awaiter(this, void 0, void 0, function* () { + if (typeof this.path !== 'string') + return console.error('✘ API call must be provided a path!'); + if (!errCallback) + throw 'YOU MUST PROVIDE AN ERROR CALLBACK FOR INTERNAL API CALLS'; + // Handle + try { + yield this.router.handle(this.req, this.res, (result) => __awaiter(this, void 0, void 0, function* () { + try { + const data = yield result; + if (typeof data === 'object') { + // If the response is an error, call the error callback. + if (data && data.status === 'error') { + console.error(chalk.bold.red('✘ Internal API promise failed:'), result.message); + callback(data); + } + // Otherwise, call the success response. + return callback(data); + } + // If the response is not an object, panic. + console.error(chalk.bold.red('✘ Internal API returned with invalid response:'), result); + return errCallback(new Error('Internal API returned with invalid response'), { status: 'error', message: 'Invalid Response' }); + } + catch (err) { + console.error(chalk.bold.red('✘ Internal API promise rejected:'), err); + return errCallback(err, { status: 'error', message: 'Server Error' }); + } + })); + } + catch (err) { + console.error(chalk.bold.red('✘ Internal API promise rejected:'), err); + return errCallback(err, { status: 'error', message: 'Server Error' }); + } + }); + } +} +// Register function must be called at the begining of your app.js file. +// Creates a new express Router using the parent application's version of express +// And adds a middleware that attaches a new instance of the api query function +// to each request's locals object. +function api(express, apiPath = DEFAULT_API_DIR) { + const setupRouter = express(); + const router = express.Router(); + // Hacky. Force the parent router to attach the locals.api interface at the begining of each request + setupRouter.on('mount', function (parent) { + parent.use((req, res, next) => { + res.locals.api = { + get: function GET_factory(path, body) { + return new ApiQuery(router, 'GET', req, res, path, body); + }, + post: function POST_factory(path, body) { + return new ApiQuery(router, 'POST', req, res, path, body); + }, + put: function PUT_factory(path, body) { + return new ApiQuery(router, 'PUT', req, res, path, body); + }, + delete: function DELETE_factory(path, body) { + return new ApiQuery(router, 'DELETE', req, res, path, body); + } + }; + next(); + }); + parent._router.stack.splice(2, 0, parent._router.stack.pop()); + }); + // If this is not an ajax request, and request is for an asset that accepts html, + // then this must be a first time render - just send our base page down. + setupRouter.use((req, res, next) => { + if (typeof req === 'object' && !req.xhr && req.accepts(['*/*', 'text/html']) === 'text/html') { + return res.sendfile(path.join(apiPath, '/index.html'), {}, function (err) { + if (err) + res.status((err) ? err.status : 500); + else + res.status(200); + }); + } + next(); + }); + console.log(chalk.bold.green('• Discovering API:')); + discoverAPI(router, apiPath); + setupRouter.use(router); + console.log(chalk.bold.green('✔ API Discovery Complete')); + return setupRouter; +} +exports.default = api; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7QUFBQSw2QkFBNkI7QUFHN0IsNkJBQTZCO0FBQzdCLCtCQUErQjtBQUUvQixNQUFNLGVBQWUsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLEVBQUUsRUFBRSxLQUFLLENBQUMsQ0FBQztBQVl4RCw2RUFBNkU7QUFDN0UsbUZBQW1GO0FBQ25GLFNBQVMsV0FBVyxDQUFDLEdBQW9CLEVBQUUsR0FBcUIsRUFBRSxJQUEwQjtJQUMxRixJQUFHLE9BQU8sR0FBRyxLQUFLLFFBQVE7UUFBRSxPQUFPO0lBQ25DLEdBQUcsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDaEIsSUFBRyxHQUFHLENBQUMsR0FBRyxFQUFDO1FBQ1QsT0FBTyxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLDhCQUE4QixDQUFDLEVBQUUsS0FBSyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztRQUM5RSxPQUFPLEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBQyxJQUFJLEVBQUUsR0FBRyxFQUFFLE1BQU0sRUFBRSxPQUFPLEVBQUUsT0FBTyxFQUFFLHdCQUF3QixFQUFDLENBQUMsQ0FBQztLQUNsRjtJQUNELE9BQU8sSUFBSSxFQUFFLENBQUM7QUFDaEIsQ0FBQztBQUVELE1BQU0sT0FBTyxHQUFHLFVBQVMsSUFBNEI7SUFDbkQsT0FBTyxVQUFlLEdBQW9CLEVBQUUsR0FBcUIsRUFBRSxJQUEwQjs7WUFFM0Ysd0JBQXdCO1lBQ3hCLElBQUk7Z0JBQ0YsTUFBTSxNQUFNLEdBQUcsTUFBTSxJQUFJLENBQUMsR0FBRyxFQUFFLEdBQUcsRUFBRSxJQUFJLENBQUMsQ0FBQztnQkFDMUMsSUFBRyxPQUFPLE1BQU0sS0FBSyxRQUFRLEVBQUM7b0JBQzVCLE9BQU8s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QUFDO0lBQzFELE9BQU8sV0FBVyxDQUFDO0FBQ3JCLENBQUM7QUEzQ0Qsc0JBMkNDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0ICogYXMgcGF0aCBmcm9tICdwYXRoJztcblxuaW1wb3J0ICogYXMgRXhwcmVzcyBmcm9tICdleHByZXNzJztcbmltcG9ydCAqIGFzIHdhbGsgZnJvbSAnd2Fsayc7XG5pbXBvcnQgKiBhcyBjaGFsayBmcm9tICdjaGFsayc7XG5cbmNvbnN0IERFRkFVTFRfQVBJX0RJUiA9IHBhdGguam9pbihwcm9jZXNzLmN3ZCgpLCAnYXBpJyk7XG5cbnR5cGUgTWV0aG9kID0gJ0FMTCcgfCAnR0VUJyB8ICdQT1NUJyB8ICdQVVQnIHwgJ0RFTEVURSc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgSUFwaUhhbmRsZXIge1xuICBBTEw/OiBFeHByZXNzLlJlcXVlc3RIYW5kbGVyO1xuICBHRVQ/OiBFeHByZXNzLlJlcXVlc3RIYW5kbGVyO1xuICBQT1NUPzogRXhwcmVzcy5SZXF1ZXN0SGFuZGxlcjtcbiAgUFVUPzogRXhwcmVzcy5SZXF1ZXN0SGFuZGxlcjtcbiAgREVMRVRFPzogRXhwcmVzcy5SZXF1ZXN0SGFuZGxlcjtcbn1cblxuLy8gSWYgbm8gQVBJIGVuZHBvaW50IHRha2VzIHRoZSBiYWl0LCByZXR1cm4gdGhlIHByb3Blcmx5IGZvcm1hdHRlZCA0MDAgZXJyb3Jcbi8vIGZvciB0aGUgbWVkaWEgcmVxdWVzdGVkLiBKU09OIGZvciBhamF4LCBqdXN0IHRoZSBodHRwIHN0YXR1cyBjb2RlIGZvciBhbGwgb3RoZXJzXG5mdW5jdGlvbiBhcGlOb3RGb3VuZChyZXE6IEV4cHJlc3MuUmVxdWVzdCwgcmVzOiBFeHByZXNzLlJlc3BvbnNlLCBuZXh0OiBFeHByZXNzLk5leHRGdW5jdGlvbikge1xuICBpZih0eXBlb2YgcmVxICE9PSAnb2JqZWN0JykgcmV0dXJuO1xuICByZXMuc3RhdHVzKDQwMCk7XG4gIGlmKHJlcS54aHIpe1xuICAgIGNvbnNvbGUuZXJyb3IoY2hhbGsucmVkKCfinJggRXJyb3Igcm91dGluZyB0byBBUEkgcGF0aCAnKSwgY2hhbGsucmVkKHJlcS5wYXRoKSk7XG4gICAgcmV0dXJuIHJlcy5qc29uKHtjb2RlOiA0MDQsIHN0YXR1czogJ2Vycm9yJywgbWVzc2FnZTogJ01ldGhvZCBOb3QgSW1wbGVtZW50ZWQnfSk7XG4gIH1cbiAgcmV0dXJuIG5leHQoKTtcbn1cblxuY29uc3QgZXZhbEFQSSA9IGZ1bmN0aW9uKGZ1bmM6IEV4cHJlc3MuUmVxdWVzdEhhbmRsZXIpIHtcbiAgcmV0dXJuIGFzeW5jIGZ1bmN0aW9uKHJlcTogRXhwcmVzcy5SZXF1ZXN0LCByZXM6IEV4cHJlc3MuUmVzcG9uc2UsIG5leHQ6IEV4cHJlc3MuTmV4dEZ1bmN0aW9uKSB7XG5cbiAgICAvLyBFdmFsdWF0ZSBBUEkgZnVuY3Rpb25cbiAgICB0cnkge1xuICAgICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgZnVuYyhyZXEsIHJlcywgbmV4dCk7XG4gICAgICBpZih0eXBlb2YgcmVzdWx0ID09PSAnb2JqZWN0Jyl7XG4gICAgICAgIHJldHVybiByZXMuc3RhdHVzKChyZXN1bHQuY29kZSB8fCAyMDApKS5qc29uKHJlc3VsdCk7XG4gICAgICB9XG5cbiAgICAgIGNvbnNvbGUuZXJyb3IoJ+KcmCBBUEkgZW5kcG9pbnQgcmV0dXJuZWQgc29tZXRoaW5nIG90aGVyIHRoYW4gSlNPTiBvciBhIFByb21pc2U6JywgcmVzdWx0LCBmdW5jKTtcbiAgICAgIHJldHVybiByZXMuc3RhdHVzKDUwMCkuanNvbih7c3RhdHVzOiAnZXJyb3InLCBtZXNzYWdlOiAnSW52YWxpZCBSZXNwb25zZSd9KTtcblxuICAgIH0gY2F0Y2goZXJyKSB7XG4gICAgICAvLyBJZiBpbnRlcm5hbCBBUEkgZmxhZyBpcyBwcmVzZW50LCBqdXN0IHJldHVybiB0aGUgcmVzdWx0IHRvIHRoZSBuZXh0IGhhbmRsZXJcbiAgICAgIGlmKChyZXEgYXMgYW55KS5faW50ZXJuYWxBUEkpIHRocm93IGVycjtcblxuICAgICAgY29uc29sZS5lcnJvcign4pyYIEFQSSBwcm9taXNlIHJlamVjdGVkIGFuZCByZXR1cm5pbmcgbm9uIDIwMCByZXNwb25zZTonLCBlcnIpO1xuICAgICAgcmV0dXJuIHJlcy5zdGF0dXMoKGVyci5jb2RlIHx8IDUwMCkpLmpzb24oZXJyKTtcbiAgICB9XG4gIH1cbiB9O1xuXG5mdW5jdGlvbiBoYXNWYWxpZE1ldGhvZChoYW5kbGVyOiBJQXBpSGFuZGxlcil7XG4gIHJldHVybiB0eXBlb2YgaGFuZGxlciA9PT0gJ29iamVjdCcgJiZcbiAgICAgKCAgdHlwZW9mIGhhbmRsZXIuQUxMID09PSAnZnVuY3Rpb24nXG4gICAgICAgIHx8IHR5cGVvZiBoYW5kbGVyLkdFVCA9PT0gJ2Z1bmN0aW9uJ1xuICAgICAgICB8fCB0eXBlb2YgaGFuZGxlci5QT1NUID09PSAnZnVuY3Rpb24nXG4gICAgICAgIHx8IHR5cGVvZiBoYW5kbGVyLlBVVCA9PT0gJ2Z1bmN0aW9uJ1xuICAgICAgICB8fCB0eXBlb2YgaGFuZGxlci5ERUxFVEUgPT09ICdmdW5jdGlvbicpXG59XG5cbmZ1bmN0aW9uIGxvYWRBUEkocm91dGVyOiBFeHByZXNzLlJvdXRlciwgZmlsZVBhdGg6IHN0cmluZywgYXBpUGF0aDogc3RyaW5nKXtcbiAgbGV0IG1ldGhvZHMgPSAnJztcbiAgdHJ5IHtcbiAgICAgY29uc3QgaGFuZGxlciA9IHJlcXVpcmUoZmlsZVBhdGgpIGFzIElBcGlIYW5kbGVyO1xuICAgICAvLyBJZiBoYW5kbGVyIGlzIGEgZnVuY3Rpb24sIHJlZ2lzdGVyIGl0IGFzIGEgZ2V0IGNhbGxiYWNrXG4gICAgIGlmKHR5cGVvZiBoYW5kbGVyID09PSAnZnVuY3Rpb24nICYmIChtZXRob2RzICs9ICcgR0VUJykpIHJvdXRlci5nZXQoYXBpUGF0aCwgZXZhbEFQSShoYW5kbGVyKSk7XG4gICAgIC8vIElmIGhhbmRsZXIgaXMgYW4gb2JqZWN0IHdpdGggYW55IHZhbGlkIGh0dHAgbWV0aG9kLCByZWdpc3RlciB0aGVtXG4gICAgIGVsc2UgaWYoaGFzVmFsaWRNZXRob2QoaGFuZGxlcikpe1xuICAgICAgIGlmKHR5cGVvZiBoYW5kbGVyLkFMTCA9PT0gJ2Z1bmN0aW9uJyAmJiAobWV0aG9kcyArPSAnIEFMTCcpKSByb3V0ZXIuYWxsKGFwaVBhdGgsIGV2YWxBUEkoaGFuZGxlci5BTEwpKTtcbiAgICAgICBpZih0eXBlb2YgaGFuZGxlci5HRVQgPT09ICdmdW5jdGlvbicgJiYgKG1ldGhvZHMgKz0gJyBHRVQnKSkgcm91dGVyLmdldChhcGlQYXRoLCBldmFsQVBJKGhhbmRsZXIuR0VUKSk7XG4gICAgICAgaWYodHlwZW9mIGhhbmRsZXIuUE9TVCA9PT0gJ2Z1bmN0aW9uJyAmJiAobWV0aG9kcyArPSAnIFBPU1QnKSkgcm91dGVyLnBvc3QoYXBpUGF0aCwgZXZhbEFQSShoYW5kbGVyLlBPU1QpKTtcbiAgICAgICBpZih0eXBlb2YgaGFuZGxlci5QVVQgPT09ICdmdW5jdGlvbicgJiYgKG1ldGhvZHMgKz0gJyBQVVQnKSkgcm91dGVyLnB1dChhcGlQYXRoLCBldmFsQVBJKGhhbmRsZXIuUFVUKSk7XG4gICAgICAgaWYodHlwZW9mIGhhbmRsZXIuREVMRVRFID09PSAnZnVuY3Rpb24nICYmIChtZXRob2RzICs9ICcgREVMRVRFJykpIHJvdXRlci5kZWxldGUoYXBpUGF0aCwgZXZhbEFQSShoYW5kbGVyLkRFTEVURSkpO1xuICAgICB9XG4gICAgIC8vIE90aGVyd2lzZSwgdGhpcyBpcyBhbiBpbnZhbGlkIGV4cG9ydC4gRXJyb3IuXG4gICAgIGVsc2V7XG4gICAgICAgcmV0dXJuIGNvbnNvbGUuZXJyb3IoY2hhbGsuYm9sZC5yZWQoJyAgIOKcmCBFcnJvciBpbiBBUEk6JyksIGNoYWxrLmJvbGQuYmxhY2soYXBpUGF0aCksIGNoYWxrLmdyYXkoJyAtIG5vIHZhbGlkIEhUVFAgbWV0aG9kIGV4cG9ydGVkJykpO1xuICAgICB9XG4gICAgIGNvbnNvbGUubG9nKGNoYWxrLmdyZWVuKCcgICDigKIgUmVnaXN0ZXJlZDonKSwgKGFwaVBhdGggPyBhcGlQYXRoIDogJy8nKSwgY2hhbGsueWVsbG93KCcoJyttZXRob2RzLnRyaW0oKSsnKScpKTtcbiAgfSBjYXRjaChlcnIpIHtcbiAgICAvLyBJZiByZXF1aXJlKCkgZmFpbGVkLCBlcnJvclxuICAgIGNvbnNvbGUuZXJyb3IoY2hhbGsuYm9sZC5yZWQoJyAgIOKcmCBFcnJvciBpbiBBUEk6JyksIGNoYWxrLmJvbGQuYmxhY2soYXBpUGF0aCksIGNoYWxrLmdyYXkoJyAtIGVycm9yIGluIHRoZSBBUEkgZmlsZScpKTtcbiAgICBjb25zb2xlLmVycm9yKCcgICAgJywgY2hhbGsudW5kZXJsaW5lKGZpbGVQYXRoKSk7XG4gICAgY29uc29sZS5lcnJvcignICAgICcsIGVyci50b1N0cmluZygpLnJlcGxhY2UoLyhcXHJcXG58XFxyfFxcbikvZ20sICckMSAgICAgJykpXG4gIH1cbn1cblxuaW50ZXJmYWNlIFF1ZXVlSXRlbSB7XG4gIGFwaVBhdGg6IHN0cmluZztcbiAgZmlsZVBhdGg6IHN0cmluZztcbn1cblxuZnVuY3Rpb24gZGlzY292ZXJBUEkocm91dGVyOiBFeHByZXNzLlJvdXRlciwgYXBpRGlyOiBzdHJpbmcpe1xuICB2YXIgcXVldWU6IFF1ZXVlSXRlbVtdID0gW10sXG4gICAgICBvcHRpb25zID0ge1xuICAgICAgICBsaXN0ZW5lcnM6IHtcbiAgICAgICAgICBmaWxlOiBmdW5jdGlvbiAocm9vdDogc3RyaW5nLCBmaWxlU3RhdHM6IHdhbGsuV2Fsa1N0YXRzLCBuZXh0OiB3YWxrLldhbGtOZXh0KSB7XG4gICAgICAgICAgICAvLyBJZ25vcmUgaGlkZGVuIGZpbGVzXG4gICAgICAgICAgICBpZihmaWxlU3RhdHMubmFtZVswXSA9PT0gJy4nKSByZXR1cm4gbmV4dCgpO1xuXG4gICAgICAgICAgICAvLyBDb25zdHJ1Y3QgYm90aCB0aGUgYWJzb2x1dGUgZmlsZSBwYXRoLCBhbmQgcHVibGljIGZhY2luZyBBUEkgcGF0aFxuICAgICAgICAgICAgdmFyIGZpbGVQYXRoID0gcGF0aC5qb2luKHJvb3QsIGZpbGVTdGF0cy5uYW1lKSxcbiAgICAgICAgICAgICAgICBhcGlQYXRoID0gZmlsZVBhdGgucmVwbGFjZShhcGlEaXIsICcnKS5yZXBsYWNlKC9cXC9pbmRleC5qcyQvLCAnJykucmVwbGFjZSgvLmpzJC8sICcnKTtcblxuICAgICAgICAgICAgLy8gUHVzaCB0aGVtIHRvIG91ciBxdWV1ZS4gVGhpcyBsYXRlciBzb3J0ZWQgaW4gb3JkZXIgb2Ygcm91dGUgcHJlY2VkZW5jZS5cbiAgICAgICAgICAgIHF1ZXVlLnB1c2goeyBhcGlQYXRoLCBmaWxlUGF0aCB9KTtcblxuICAgICAgICAgICAgLy8gUHJvY2VzcyBuZXh0IGZpbGVcbiAgICAgICAgICAgIG5leHQoKTtcbiAgICAgICAgICB9LFxuXG4gICAgICAgICAgZW5kOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAvLyBTb3J0IHF1ZXVlIGluIHJldmVyc2UgYWxwaGFiZXRpY2FsIG9yZGVyLlxuICAgICAgICAgICAgLy8gSGFzIHRoZSBuaWNlIHNpZGUgZWZmZWN0IG9mIG9yZGVyaW5nIGJ5IHJvdXRlIHByZWNlZGVuY2VcbiAgICAgICAgICAgIHF1ZXVlLnNvcnQoZnVuY3Rpb24oZmlsZTEsIGZpbGUyKXtcbiAgICAgICAgICAgICAgcmV0dXJuIChmaWxlMS5hcGlQYXRoID4gZmlsZTIuYXBpUGF0aCkgPyAxIDogLTE7XG4gICAgICAgICAgICB9KVxuXG4gICAgICAgICAgICAvLyBGb3IgZWFjaCBBUEkgaXRlbSBpbiB0aGUgcXVldWUsIGxvYWQgaXQgaW50byBvdXIgcm91dGVyXG4gICAgICAgICAgICB3aGlsZShxdWV1ZS5sZW5ndGgpe1xuICAgICAgICAgICAgICBjb25zdCBmaWxlID0gcXVldWUucG9wKCkhOyAvLyBUT0RPOiBXaGVuIEVTNiBpcyBjb21tb24gaW4gbm9kZSwgbWFrZSBsZXRcbiAgICAgICAgICAgICAgbG9hZEFQSShyb3V0ZXIsIGZpbGUuZmlsZVBhdGgsIGZpbGUuYXBpUGF0aCk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIC8vIFdoZW4gd2UgaGF2ZSBsb2FkZWQgYWxsIG9mIG91ciBBUEkgZW5kcG9pbnRzLCByZWdpc3RlciBvdXIgY2F0Y2hhbGwgcm91dGVcbiAgICAgICAgICAgIHJvdXRlci5hbGwoJyonLCBhcGlOb3RGb3VuZCk7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9O1xuICB0cnl7XG4gICAgd2Fsay53YWxrU3luYyhhcGlEaXIsIG9wdGlvbnMpO1xuICB9IGNhdGNoKGUpe1xuICAgIGNvbnNvbGUuZXJyb3IoY2hhbGsuYm9sZC5yZWQoJ+KcmCBFcnJvciByZWFkaW5nIEFQSSBkaXJlY3Rvcnk6ICAnKSwgZSk7XG4gIH1cbn1cblxuY2xhc3MgQXBpUXVlcnkge1xuXG4gIHByaXZhdGUgcGF0aDogc3RyaW5nO1xuICBwcml2YXRlIHJvdXRlcjogRXhwcmVzcy5Sb3V0ZXI7XG4gIHByaXZhdGUgcmVxOiBFeHByZXNzLlJlcXVlc3RIYW5kbGVyO1xuICBwcml2YXRlIHJlczogRXhwcmVzcy5SZXNwb25zZTtcblxuICBjb25zdHJ1Y3Rvcihyb3V0ZXI6IEV4cHJlc3MuUm91dGVyLCBtZXRob2Q6IE1ldGhvZCwgcmVxOiBFeHByZXNzLlJlcXVlc3QsIHJlczogRXhwcmVzcy5SZXNwb25zZSwgcGF0aDogc3RyaW5nLCBib2R5OiBhbnkpIHtcbiAgICB0aGlzLnJvdXRlciA9IHJvdXRlcjtcbiAgICB0aGlzLnBhdGggPSBwYXRoO1xuICAgIHRoaXMucmVxID0gT2JqZWN0LmNyZWF0ZShyZXEsIHtcbiAgICAgIHVybDogeyB3cml0YWJsZTogdHJ1ZSwgY29uZmlndXJhYmxlOiB0cnVlLCB2YWx1ZTogdGhpcy5wYXRoIH0sXG4gICAgICBtZXRob2Q6IHsgd3JpdGFibGU6IHRydWUsIGNvbmZpZ3VyYWJsZTogdHJ1ZSwgdmFsdWU6IG1ldGhvZCB9LFxuICAgICAgaXA6IHsgd3JpdGFibGU6IHRydWUsIGNvbmZpZ3VyYWJsZTogdHJ1ZSwgdmFsdWU6ICcxMjcuMC4wLjEnIH0sXG4gICAgICBib2R5OiB7IHdyaXRhYmxlOiB0cnVlLCBjb25maWd1cmFibGU6IHRydWUsIHZhbHVlOiAoYm9keSB8fCB7fSkgfSxcbiAgICAgIHF1ZXJ5OiB7IHdyaXRhYmxlOiB0cnVlLCBjb25maWd1cmFibGU6IHRydWUsIHZhbHVlOiB7fSB9LFxuICAgICAgcGFyYW1zOiB7IHdyaXRhYmxlOiB0cnVlLCBjb25maWd1cmFibGU6IHRydWUsIHZhbHVlOiB7fSB9LFxuICAgICAgb3JpZ2luYWxVcmw6IHsgd3JpdGFibGU6IHRydWUsIGNvbmZpZ3VyYWJsZTogdHJ1ZSwgdmFsdWU6IHVuZGVmaW5lZCB9LFxuICAgICAgX2ludGVybmFsQVBJOiB7IHdyaXRhYmxlOiB0cnVlLCBjb25maWd1cmFibGU6IHRydWUsIHZhbHVlOiB0cnVlfVxuICAgIH0pO1xuICAgIHRoaXMucmVzID0gcmVzO1xuICAgIHJldHVybiB0aGlzO1xuICB9XG5cbiAgYXN5bmMgdGhlbihjYWxsYmFjazogKGRhdGE6IGFueSkgPT4gYW55LCBlcnJDYWxsYmFjazogKGVycjogRXJyb3IsIGRhdGE6IGFueSkgPT4gYW55KTogUHJvbWlzZTxhbnk+IHtcblxuICAgIGlmKHR5cGVvZiB0aGlzLnBhdGggIT09ICdzdHJpbmcnKSByZXR1cm4gY29uc29sZS5lcnJvcign4pyYIEFQSSBjYWxsIG11c3QgYmUgcHJvdmlkZWQgYSBwYXRoIScpO1xuICAgIGlmKCFlcnJDYWxsYmFjaykgdGhyb3cgJ1lPVSBNVVNUIFBST1ZJREUgQU4gRVJST1IgQ0FMTEJBQ0sgRk9SIElOVEVSTkFMIEFQSSBDQUxMUyc7XG5cbiAgICAvLyBIYW5kbGVcbiAgICB0cnkge1xuICAgICAgYXdhaXQgKHRoaXMucm91dGVyIGFzIGFueSkuaGFuZGxlKHRoaXMucmVxLCB0aGlzLnJlcywgYXN5bmMgKHJlc3VsdDogUHJvbWlzZTxhbnk+IHwgYW55KSA9PiB7XG5cbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICBjb25zdCBkYXRhID0gYXdhaXQgcmVzdWx0O1xuICAgICAgICAgIGlmKHR5cGVvZiBkYXRhID09PSAnb2JqZWN0Jykge1xuXG4gICAgICAgICAgICAvLyBJZiB0aGUgcmVzcG9uc2UgaXMgYW4gZXJyb3IsIGNhbGwgdGhlIGVycm9yIGNhbGxiYWNrLlxuICAgICAgICAgICAgaWYoZGF0YSAmJiBkYXRhLnN0YXR1cyA9PT0gJ2Vycm9yJykge1xuICAgICAgICAgICAgICBjb25zb2xlLmVycm9yKGNoYWxrLmJvbGQucmVkKCfinJggSW50ZXJuYWwgQVBJIHByb21pc2UgZmFpbGVkOicpLCByZXN1bHQubWVzc2FnZSk7XG4gICAgICAgICAgICAgIGNhbGxiYWNrKGRhdGEpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvLyBPdGhlcndpc2UsIGNhbGwgdGhlIHN1Y2Nlc3MgcmVzcG9uc2UuXG4gICAgICAgICAgICByZXR1cm4gY2FsbGJhY2soZGF0YSk7XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgLy8gSWYgdGhlIHJlc3BvbnNlIGlzIG5vdCBhbiBvYmplY3QsIHBhbmljLlxuICAgICAgICAgIGNvbnNvbGUuZXJyb3IoY2hhbGsuYm9sZC5yZWQoJ+KcmCBJbnRlcm5hbCBBUEkgcmV0dXJuZWQgd2l0aCBpbnZhbGlkIHJlc3BvbnNlOicpLCByZXN1bHQpO1xuICAgICAgICAgIHJldHVybiBlcnJDYWxsYmFjayhuZXcgRXJyb3IoJ0ludGVybmFsIEFQSSByZXR1cm5lZCB3aXRoIGludmFsaWQgcmVzcG9uc2UnKSwgeyBzdGF0dXM6ICdlcnJvcicsIG1lc3NhZ2U6ICdJbnZhbGlkIFJlc3BvbnNlJyB9KTtcbiAgICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgICAgY29uc29sZS5lcnJvcihjaGFsay5ib2xkLnJlZCgn4pyYIEludGVybmFsIEFQSSBwcm9taXNlIHJlamVjdGVkOicpLCBlcnIpO1xuICAgICAgICAgIHJldHVybiBlcnJDYWxsYmFjayhlcnIsIHsgc3RhdHVzOiAnZXJyb3InLCBtZXNzYWdlOiAnU2VydmVyIEVycm9yJyB9KVxuICAgICAgICB9XG4gICAgICB9KTtcbiAgICB9IGNhdGNoKGVycikge1xuICAgICAgY29uc29sZS5lcnJvcihjaGFsay5ib2xkLnJlZCgn4pyYIEludGVybmFsIEFQSSBwcm9taXNlIHJlamVjdGVkOicpLCBlcnIpO1xuICAgICAgcmV0dXJuIGVyckNhbGxiYWNrKGVyciwgeyBzdGF0dXM6ICdlcnJvcicsIG1lc3NhZ2U6ICdTZXJ2ZXIgRXJyb3InIH0pO1xuICAgIH1cbiAgfVxufVxuXG4vLyBSZWdpc3RlciBmdW5jdGlvbiBtdXN0IGJlIGNhbGxlZCBhdCB0aGUgYmVnaW5pbmcgb2YgeW91ciBhcHAuanMgZmlsZS5cbi8vIENyZWF0ZXMgYSBuZXcgZXhwcmVzcyBSb3V0ZXIgdXNpbmcgdGhlIHBhcmVudCBhcHBsaWNhdGlvbidzIHZlcnNpb24gb2YgZXhwcmVzc1xuLy8gQW5kIGFkZHMgYSBtaWRkbGV3YXJlIHRoYXQgYXR0YWNoZXMgYSBuZXcgaW5zdGFuY2Ugb2YgdGhlIGFwaSBxdWVyeSBmdW5jdGlvblxuLy8gdG8gZWFjaCByZXF1ZXN0J3MgbG9jYWxzIG9iamVjdC5cbmV4cG9ydCBkZWZhdWx0IGZ1bmN0aW9uIGFwaShleHByZXNzOiBhbnksIGFwaVBhdGg6IHN0cmluZyA9IERFRkFVTFRfQVBJX0RJUikge1xuICBjb25zdCBzZXR1cFJvdXRlciA9IGV4cHJlc3MoKSBhcyBFeHByZXNzLkV4cHJlc3M7XG4gIGNvbnN0IHJvdXRlciA9IGV4cHJlc3MuUm91dGVyKCkgYXMgRXhwcmVzcy5Sb3V0ZXI7XG5cbiAgLy8gSGFja3kuIEZvcmNlIHRoZSBwYXJlbnQgcm91dGVyIHRvIGF0dGFjaCB0aGUgbG9jYWxzLmFwaSBpbnRlcmZhY2UgYXQgdGhlIGJlZ2luaW5nIG9mIGVhY2ggcmVxdWVzdFxuICBzZXR1cFJvdXRlci5vbignbW91bnQnLCBmdW5jdGlvbihwYXJlbnQpe1xuICAgIHBhcmVudC51c2UoKHJlcTogRXhwcmVzcy5SZXF1ZXN0LCByZXM6IEV4cHJlc3MuUmVzcG9uc2UsIG5leHQ6IEV4cHJlc3MuTmV4dEZ1bmN0aW9uKSA9PiB7XG4gICAgICByZXMubG9jYWxzLmFwaSA9IHtcbiAgICAgICAgZ2V0OiBmdW5jdGlvbiBHRVRfZmFjdG9yeShwYXRoOiBzdHJpbmcsIGJvZHk6IGFueSl7XG4gICAgICAgICAgcmV0dXJuIG5ldyBBcGlRdWVyeShyb3V0ZXIsICdHRVQnLCByZXEsIHJlcywgcGF0aCwgYm9keSk7XG4gICAgICAgIH0sXG4gICAgICAgIHBvc3Q6IGZ1bmN0aW9uIFBPU1RfZmFjdG9yeShwYXRoOiBzdHJpbmcsIGJvZHk6IGFueSl7XG4gICAgICAgICAgcmV0dXJuIG5ldyBBcGlRdWVyeShyb3V0ZXIsICdQT1NUJywgcmVxLCByZXMsIHBhdGgsIGJvZHkpO1xuICAgICAgICB9LFxuICAgICAgICBwdXQ6IGZ1bmN0aW9uIFBVVF9mYWN0b3J5KHBhdGg6IHN0cmluZywgYm9keTogYW55KXtcbiAgICAgICAgICByZXR1cm4gbmV3IEFwaVF1ZXJ5KHJvdXRlciwgJ1BVVCcsIHJlcSwgcmVzLCBwYXRoLCBib2R5KTtcbiAgICAgICAgfSxcbiAgICAgICAgZGVsZXRlOiBmdW5jdGlvbiBERUxFVEVfZmFjdG9yeShwYXRoOiBzdHJpbmcsIGJvZHk6IGFueSl7XG4gICAgICAgICAgcmV0dXJuIG5ldyBBcGlRdWVyeShyb3V0ZXIsICdERUxFVEUnLCByZXEsIHJlcywgcGF0aCwgYm9keSk7XG4gICAgICAgIH1cbiAgICAgIH07XG4gICAgICBuZXh0KCk7XG4gICAgfSk7XG4gICAgcGFyZW50Ll9yb3V0ZXIuc3RhY2suc3BsaWNlKDIsIDAsIHBhcmVudC5fcm91dGVyLnN0YWNrLnBvcCgpKTtcbiAgfSk7XG5cbiAgLy8gSWYgdGhpcyBpcyBub3QgYW4gYWpheCByZXF1ZXN0LCBhbmQgcmVxdWVzdCBpcyBmb3IgYW4gYXNzZXQgdGhhdCBhY2NlcHRzIGh0bWwsXG4gIC8vIHRoZW4gdGhpcyBtdXN0IGJlIGEgZmlyc3QgdGltZSByZW5kZXIgLSBqdXN0IHNlbmQgb3VyIGJhc2UgcGFnZSBkb3duLlxuICBzZXR1cFJvdXRlci51c2UoKHJlcTogRXhwcmVzcy5SZXF1ZXN0LCByZXM6IEV4cHJlc3MuUmVzcG9uc2UsIG5leHQ6IEV4cHJlc3MuTmV4dEZ1bmN0aW9uKSA9PiB7XG4gICAgaWYodHlwZW9mIHJlcSA9PT0gJ29iamVjdCcgJiYgIXJlcS54aHIgJiYgcmVxLmFjY2VwdHMoWycqLyonLCAndGV4dC9odG1sJ10pID09PSAndGV4dC9odG1sJykge1xuICAgICAgcmV0dXJuIHJlcy5zZW5kZmlsZShwYXRoLmpvaW4oYXBpUGF0aCwgJy9pbmRleC5odG1sJyksIHt9LCBmdW5jdGlvbiAoZXJyOiBhbnkpIHtcbiAgICAgICAgaWYgKGVycikgcmVzLnN0YXR1cygoZXJyKSA/IGVyci5zdGF0dXMgOiA1MDApO1xuICAgICAgICBlbHNlIHJlcy5zdGF0dXMoMjAwKTtcbiAgICAgIH0pO1xuICAgIH1cbiAgICBuZXh0KCk7XG4gIH0pO1xuXG4gIGNvbnNvbGUubG9nKGNoYWxrLmJvbGQuZ3JlZW4oJ+KAoiBEaXNjb3ZlcmluZyBBUEk6JykpO1xuICBkaXNjb3ZlckFQSShyb3V0ZXIsIGFwaVBhdGgpO1xuICBzZXR1cFJvdXRlci51c2Uocm91dGVyKTtcbiAgY29uc29sZS5sb2coY2hhbGsuYm9sZC5ncmVlbign4pyUIEFQSSBEaXNjb3ZlcnkgQ29tcGxldGUnKSk7XG4gIHJldHVybiBzZXR1cFJvdXRlcjtcbn0iXX0= \ No newline at end of file diff --git a/index.js b/index.js deleted file mode 100644 index 7f5ffc9..0000000 --- a/index.js +++ /dev/null @@ -1,232 +0,0 @@ -var Promise = require("bluebird"); -var walk = require('walk'); -var fs = require('fs'); -var path = require('path'); -var colour = require('colour'); - -var rootDir = path.dirname(require.main.filename); -var apiDir = rootDir + "/api"; -var router; - -// If this is not an ajax request, and request is for an asset that accepts html, -// then this must be a first time render - just send our base page down. -function renderBasePage(req, res, next){ - if(typeof req === 'object' && !req.xhr && req.accepts(['*/*', 'text/html']) === 'text/html'){ - return res.sendfile(rootDir + '/index.html', {}, function (err) { - if (err) res.status((err) ? err.status : 500); - else res.status(200); - }); - } - next(); -} - -// If no API endpoint takes the bait, return the properly formatted 400 error -// for the media requested. JSON for ajax, just the http status code for all others -function apiNotFound(req, res, next){ - if(typeof req !== 'object') return; - res.status(400); - if(req.xhr){ - console.error('✘ Error routing to API path '.red, req.path.red); - return res.json({code: 404, status: 'error', message: 'Method Not Implemented'}); - } - next(); -} - -function attachLocalApi(req, res, next){ - res.locals.api = { - get: function GET_factory(path, body){ - return new api_query('get', req, res, path, body); - }, - post: function POST_factory(path, body){ - return new api_query('post', req, res, path, body); - }, - put: function PUT_factory(path, body){ - return new api_query('put', req, res, path, body); - }, - delete: function DELETE_factory(path, body){ - return new api_query('delete', req, res, path, body); - } - }; - next(); -} -attachLocalApi.next = function(){console.log('BLARG?!')} - -var evalAPI = function(func){ - return function(req, res, next){ - - // Evaluate API function - var result = func(req, res); - - // If internal API flag is present, just return the result to the next handler - if(req._internalAPI) return next(result); - - // Otherwise, send back to the browser with the proper response - if(result && typeof result.then === 'function'){ - return result.then(function(result){ - result || (result = {}) - res.status((result.code || 200)).json(result); - }, function(err){ - result || (result = {}) - console.error('✘ API promise rejected and returning non 200 response:', err); - res.status((err.code || 500)).json(err); - }); - } - else if(typeof result === 'object'){ - result || (result = {}) - return res.status((result.code || 200)).json(result); - } - console.error('✘ API endpoint returned something other than JSON or a Promise:', result, func); - return res.status(500).json({status: 'error', message: 'Invalid Response'}); - } - }; - -function hasValidMethod(handler){ - return typeof handler === 'object' && - ( typeof handler.ALL === 'function' - || typeof handler.GET === 'function' - || typeof handler.POST === 'function' - || typeof handler.PUT === 'function' - || typeof handler.DELETE === 'function') -} - -function loadAPI(router, filePath, apiPath){ - var methods = ''; - try { - handler = require(filePath); - // If handler is a function, register it as a get callback - if(typeof handler === 'function' && (methods += ' GET')) router.get(apiPath, evalAPI(handler)); - // If handler is an object with any valid http method, register them - else if(hasValidMethod(handler)){ - if(typeof handler.ALL === 'function' && (methods += ' ALL')) router.all(apiPath, evalAPI(handler.ALL)); - if(typeof handler.GET === 'function' && (methods += ' GET')) router.get(apiPath, evalAPI(handler.GET)); - if(typeof handler.POST === 'function' && (methods += ' POST')) router.post(apiPath, evalAPI(handler.POST)); - if(typeof handler.PUT === 'function' && (methods += ' PUT')) router.put(apiPath, evalAPI(handler.PUT)); - if(typeof handler.DELETE === 'function' && (methods += ' DELETE')) router.delete(apiPath, evalAPI(handler.DELETE)); - } - // Otherwise, this is an invalid export. Error. - else{ - return console.error(' ✘ Error in API:'.red.bold, apiPath.bold.black, (' - no valid HTTP method exported').gray); - } - console.log(' • Registered:'.green, (apiPath ? apiPath : '/'), ('('+methods.trim()+')').yellow); - } catch(err) { - // If require() failed, error - console.error(' ✘ Error in API:'.red.bold, apiPath.bold.black, ' - error in the API file'.gray); - console.error(' ', filePath.underline); - console.error(' ', err.toString().replace(/(\r\n|\r|\n)/gm, '$1 ')) - } -} - -function discoverAPI(router){ - var queue = [], - options = { - listeners: { - file: function (root, fileStats, next) { - // Ignore hidden files - if(fileStats.name[0] === '.') return next(); - - // Construct both the absolute filepath, and public facing API path - var filePath = root + '/' + fileStats.name, - apiPath = filePath.replace(apiDir, '').replace(/\/index.js$/, '').replace(/.js$/, ''); - - // Push them to our queue. This later sorted in order of route precidence. - queue.push({apiPath: apiPath, filePath: filePath}); - - // Process next file - next(); - }, - - end: function () { - // Sort queue in reverse alphabetical order. - // Has the nice side effect of ordering by route precidence - queue.sort(function(file1, file2){ - return (file1.apiPath > file2.apiPath) ? 1 : -1; - }) - - // For each API item in the queue, load it into our router - while(queue.length){ - var file = queue.pop(); // TODO: When ES6 is common in node, make let - loadAPI(router, file.filePath, file.apiPath); - } - - // When we have loaded all of our API endpoints, register our catchall route - router.all('*', apiNotFound); - } - } - }; - try{ - walk.walkSync(apiDir, options); - } catch(e){ - console.error('✘ Error reading API directory: '.red.bold, e); - } -} - -// Register function must be called at the begining of your app.js file. -// Creates a new express Router using the parent application's version of express -// And adds a middleware that attaches a new instance of the api query function -// to each request's locals object. -api = function(express){ - var setupRouter = express(); - router = express.Router(); - - // Hacky. Force the parent router to attach the locals.api interface at the begining of each request - setupRouter.on('mount', function(parent){ - parent.use(attachLocalApi) - parent._router.stack.splice(2, 0, parent._router.stack.pop()); - }); - - // If this is not an ajax request, just send our base page - setupRouter.use(renderBasePage); - - console.log('• Discovering API:'.green.bold); - discoverAPI(router); - setupRouter.use(router); - console.log("✔ API Discovery Complete".green.bold); - return setupRouter; -} - -function api_query(method, req, res, path, body){ - this.path = (typeof path === 'object') ? path.path : path; - this.req = Object.create(req, { - url: { writable: true, configurable: true, value: this.path }, - method: { writable: true, configurable: true, value: method }, - ip: { writable: true, configurable: true, value: '127.0.0.1' }, - body: { writable: true, configurable: true, value: (body || {}) }, - query: { writable: true, configurable: true, value: {} }, - params: { writable: true, configurable: true, value: {} }, - originalUrl: { writable: true, configurable: true, value: undefined }, - _internalAPI: { writable: true, configurable: true, value: true} - }); - this.res = res; - return this; -} - -api_query.prototype.then = function(callback, errCallback){ - - var self = this; - - if(typeof this.path !== 'string') return console.error('✘ API call must be provided a path!'); - if(!errCallback) throw 'YOU MUST PROVIDE AN ERROR CALLBACK FOR INTERNAL API CALLS'; - - // Handle - return new Promise(function(resolve, reject){ - router.handle(self.req, self.res, function(result){ - if(typeof result.then === 'function'){ - return result.then(function(data){ - if(data && data.status === 'error') console.error('✘ Internal API promise failed:'.red.bold, data.message); - resolve(data); - }, function(err){ - console.error('✘ Internal API promise rejected:'.red.bold, err); - reject(err) - }); - } - else if(typeof result === 'object'){ - if(result && result.status === 'error') console.error('✘ Internal API promise failed:'.red.bold, result.message); - return resolve(result); - } - console.error('✘ Internal API returned with invalid response:'.red.bold, result); - reject({status: 'error', message: 'Invalid Response'}); - }); - }).then(callback, errCallback); -} - -module.exports = api; \ No newline at end of file diff --git a/package.json b/package.json index 840383c..4a38704 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,14 @@ { "name": "rebound-api", - "version": "1.0.4", + "version": "2.0.0-beta.0", "description": "An Express middleware for elegant API creation", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "build": "rm -rf dist && tsc --jsx preserve -p tsconfig.json", + "pretest": "yarn run build", + "test": "mocha -G dist/test", + "watch": "watch 'yarn run test' './src' './test' --wait=1", + "start": "node dist/src/index.js" }, "repository": { "type": "git", @@ -24,8 +28,15 @@ }, "homepage": "https://github.com/reboundjs/rebound-api#readme", "dependencies": { - "bluebird": "^2.9.34", - "colour": "^0.7.1", + "chalk": "^3.0.0", "walk": "^2.3.9" + }, + "devDependencies": { + "@types/express": "^4.17.2", + "@types/walk": "^2.3.0", + "express": "^4.17.1", + "mocha": "^6.2.2", + "typescript": "^3.7.4", + "watch": "^1.0.2" } } diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..0dd0f03 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,248 @@ +import * as path from 'path'; + +import * as Express from 'express'; +import * as walk from 'walk'; +import * as chalk from 'chalk'; + +const DEFAULT_API_DIR = path.join(process.cwd(), 'api'); + +type Method = 'ALL' | 'GET' | 'POST' | 'PUT' | 'DELETE'; + +export interface IApiHandler { + ALL?: Express.RequestHandler; + GET?: Express.RequestHandler; + POST?: Express.RequestHandler; + PUT?: Express.RequestHandler; + DELETE?: Express.RequestHandler; +} + +// If no API endpoint takes the bait, return the properly formatted 400 error +// for the media requested. JSON for ajax, just the http status code for all others +function apiNotFound(req: Express.Request, res: Express.Response, next: Express.NextFunction) { + if(typeof req !== 'object') return; + res.status(400); + if(req.xhr){ + console.error(chalk.red('✘ Error routing to API path '), chalk.red(req.path)); + return res.json({code: 404, status: 'error', message: 'Method Not Implemented'}); + } + return next(); +} + +const evalAPI = function(func: Express.RequestHandler) { + return async function(req: Express.Request, res: Express.Response, next: Express.NextFunction) { + + // Evaluate API function + try { + const result = await func(req, res, next); + if(typeof result === 'object'){ + return res.status((result.code || 200)).json(result); + } + + console.error('✘ API endpoint returned something other than JSON or a Promise:', result, func); + return res.status(500).json({status: 'error', message: 'Invalid Response'}); + + } catch(err) { + // If internal API flag is present, just return the result to the next handler + if((req as any)._internalAPI) throw err; + + console.error('✘ API promise rejected and returning non 200 response:', err); + return res.status((err.code || 500)).json(err); + } + } + }; + +function hasValidMethod(handler: IApiHandler){ + return typeof handler === 'object' && + ( typeof handler.ALL === 'function' + || typeof handler.GET === 'function' + || typeof handler.POST === 'function' + || typeof handler.PUT === 'function' + || typeof handler.DELETE === 'function') +} + +function loadAPI(router: Express.Router, filePath: string, apiPath: string){ + let methods = ''; + try { + const handler = require(filePath) as IApiHandler; + // If handler is a function, register it as a get callback + if(typeof handler === 'function' && (methods += ' GET')) router.get(apiPath, evalAPI(handler)); + // If handler is an object with any valid http method, register them + else if(hasValidMethod(handler)){ + if(typeof handler.ALL === 'function' && (methods += ' ALL')) router.all(apiPath, evalAPI(handler.ALL)); + if(typeof handler.GET === 'function' && (methods += ' GET')) router.get(apiPath, evalAPI(handler.GET)); + if(typeof handler.POST === 'function' && (methods += ' POST')) router.post(apiPath, evalAPI(handler.POST)); + if(typeof handler.PUT === 'function' && (methods += ' PUT')) router.put(apiPath, evalAPI(handler.PUT)); + if(typeof handler.DELETE === 'function' && (methods += ' DELETE')) router.delete(apiPath, evalAPI(handler.DELETE)); + } + // Otherwise, this is an invalid export. Error. + else{ + return console.error(chalk.bold.red(' ✘ Error in API:'), chalk.bold.black(apiPath), chalk.gray(' - no valid HTTP method exported')); + } + console.log(chalk.green(' • Registered:'), (apiPath ? apiPath : '/'), chalk.yellow('('+methods.trim()+')')); + } catch(err) { + // If require() failed, error + console.error(chalk.bold.red(' ✘ Error in API:'), chalk.bold.black(apiPath), chalk.gray(' - error in the API file')); + console.error(' ', chalk.underline(filePath)); + console.error(' ', err.toString().replace(/(\r\n|\r|\n)/gm, '$1 ')) + } +} + +interface QueueItem { + apiPath: string; + filePath: string; +} + +function discoverAPI(router: Express.Router, apiDir: string){ + var queue: QueueItem[] = [], + options = { + listeners: { + file: function (root: string, fileStats: walk.WalkStats, next: walk.WalkNext) { + // Ignore hidden files + if(fileStats.name[0] === '.') return next(); + + // Construct both the absolute file path, and public facing API path + var filePath = path.join(root, fileStats.name), + apiPath = filePath.replace(apiDir, '').replace(/\/index.js$/, '').replace(/.js$/, ''); + + // Push them to our queue. This later sorted in order of route precedence. + queue.push({ apiPath, filePath }); + + // Process next file + next(); + }, + + end: function () { + // Sort queue in reverse alphabetical order. + // Has the nice side effect of ordering by route precedence + queue.sort(function(file1, file2){ + return (file1.apiPath > file2.apiPath) ? 1 : -1; + }) + + // For each API item in the queue, load it into our router + while(queue.length){ + const file = queue.pop()!; // TODO: When ES6 is common in node, make let + loadAPI(router, file.filePath, file.apiPath); + } + + // When we have loaded all of our API endpoints, register our catchall route + router.all('*', apiNotFound); + } + } + }; + try{ + walk.walkSync(apiDir, options); + } catch(e){ + console.error(chalk.bold.red('✘ Error reading API directory: '), e); + } +} + +class ApiQuery { + + private path: string; + private router: Express.Router; + private req: Express.RequestHandler; + private res: Express.Response; + + constructor(router: Express.Router, method: Method, req: Express.Request, res: Express.Response, path: string, body: any) { + this.router = router; + this.path = path; + this.req = Object.create(req, { + url: { writable: true, configurable: true, value: this.path }, + method: { writable: true, configurable: true, value: method }, + ip: { writable: true, configurable: true, value: '127.0.0.1' }, + body: { writable: true, configurable: true, value: (body || {}) }, + query: { writable: true, configurable: true, value: {} }, + params: { writable: true, configurable: true, value: {} }, + originalUrl: { writable: true, configurable: true, value: undefined }, + _internalAPI: { writable: true, configurable: true, value: true} + }); + this.res = res; + return this; + } + + async then(callback: (data: any) => any, errCallback: (err: Error, data: any) => any): Promise { + + if(typeof this.path !== 'string') return console.error('✘ API call must be provided a path!'); + if(!errCallback) throw 'YOU MUST PROVIDE AN ERROR CALLBACK FOR INTERNAL API CALLS'; + + // Handle + try { + await (this.router as any).handle(this.req, this.res, async (result: Promise | any) => { + + try { + const data = await result; + if(typeof data === 'object') { + + // If the response is an error, call the error callback. + if(data && data.status === 'error') { + console.error(chalk.bold.red('✘ Internal API promise failed:'), result.message); + callback(data); + } + + // Otherwise, call the success response. + return callback(data); + } + + // If the response is not an object, panic. + console.error(chalk.bold.red('✘ Internal API returned with invalid response:'), result); + return errCallback(new Error('Internal API returned with invalid response'), { status: 'error', message: 'Invalid Response' }); + } catch (err) { + console.error(chalk.bold.red('✘ Internal API promise rejected:'), err); + return errCallback(err, { status: 'error', message: 'Server Error' }) + } + }); + } catch(err) { + console.error(chalk.bold.red('✘ Internal API promise rejected:'), err); + return errCallback(err, { status: 'error', message: 'Server Error' }); + } + } +} + +// Register function must be called at the begining of your app.js file. +// Creates a new express Router using the parent application's version of express +// And adds a middleware that attaches a new instance of the api query function +// to each request's locals object. +export default function api(express: any, apiPath: string = DEFAULT_API_DIR) { + const setupRouter = express() as Express.Express; + const router = express.Router() as Express.Router; + + // Hacky. Force the parent router to attach the locals.api interface at the begining of each request + setupRouter.on('mount', function(parent){ + parent.use((req: Express.Request, res: Express.Response, next: Express.NextFunction) => { + res.locals.api = { + get: function GET_factory(path: string, body: any){ + return new ApiQuery(router, 'GET', req, res, path, body); + }, + post: function POST_factory(path: string, body: any){ + return new ApiQuery(router, 'POST', req, res, path, body); + }, + put: function PUT_factory(path: string, body: any){ + return new ApiQuery(router, 'PUT', req, res, path, body); + }, + delete: function DELETE_factory(path: string, body: any){ + return new ApiQuery(router, 'DELETE', req, res, path, body); + } + }; + next(); + }); + parent._router.stack.splice(2, 0, parent._router.stack.pop()); + }); + + // If this is not an ajax request, and request is for an asset that accepts html, + // then this must be a first time render - just send our base page down. + setupRouter.use((req: Express.Request, res: Express.Response, next: Express.NextFunction) => { + if(typeof req === 'object' && !req.xhr && req.accepts(['*/*', 'text/html']) === 'text/html') { + return res.sendfile(path.join(apiPath, '/index.html'), {}, function (err: any) { + if (err) res.status((err) ? err.status : 500); + else res.status(200); + }); + } + next(); + }); + + console.log(chalk.bold.green('• Discovering API:')); + discoverAPI(router, apiPath); + setupRouter.use(router); + console.log(chalk.bold.green('✔ API Discovery Complete')); + return setupRouter; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..79884ef --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,48 @@ +{ + "include": [ "src/**/*", "test/**/*" ], + "compilerOptions": { + + "outDir": "dist", + "baseUrl": "dist", + "lib": [ "es7", "es2017", "dom" ], + + // JSX Settings for Preact + "jsx": "react", + "jsxFactory": "h", + + // Compilation Configuration + "target": "es2015", + "inlineSources": true, + "inlineSourceMap": true, + "declaration": true, + "allowSyntheticDefaultImports": true, + + // Environment Configuration + "experimentalDecorators": true, + "moduleResolution": "node", + "skipLibCheck": true, + + // Enhance Strictness + "noImplicitAny": true, + "suppressImplicitAnyIndexErrors": true, + "noUnusedParameters": true, + "allowUnreachableCode": false, + "strictNullChecks": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "forceConsistentCasingInFileNames": true, + "noUnusedLocals": true, + "strictFunctionTypes": true, + "alwaysStrict": true, + "strictPropertyInitialization": true, + + // output options + "preserveConstEnums": false, + "newLine": "LF", + "traceResolution": false, + "module": "commonjs", + + }, + + "references": [] +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..d827b9a --- /dev/null +++ b/yarn.lock @@ -0,0 +1,1099 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/body-parser@*": + version "1.17.1" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.17.1.tgz#18fcf61768fb5c30ccc508c21d6fd2e8b3bf7897" + integrity sha512-RoX2EZjMiFMjZh9lmYrwgoP9RTpAjSHiJxdp4oidAQVO02T7HER3xj9UKue5534ULWeqVEkujhWcyvUce+d68w== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/color-name@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" + integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== + +"@types/connect@*": + version "3.4.33" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.33.tgz#31610c901eca573b8713c3330abc6e6b9f588546" + integrity sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A== + dependencies: + "@types/node" "*" + +"@types/express-serve-static-core@*": + version "4.17.1" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.1.tgz#82be64a77211b205641e0209096fd3afb62481d3" + integrity sha512-9e7jj549ZI+RxY21Cl0t8uBnWyb22HzILupyHZjYEVK//5TT/1bZodU+yUbLnPdoYViBBnNWbxp4zYjGV0zUGw== + dependencies: + "@types/node" "*" + "@types/range-parser" "*" + +"@types/express@^4.17.2": + version "4.17.2" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.2.tgz#a0fb7a23d8855bac31bc01d5a58cadd9b2173e6c" + integrity sha512-5mHFNyavtLoJmnusB8OKJ5bshSzw+qkMIBAobLrIM48HJvunFva9mOa6aBwh64lBFyNwBbs0xiEFuj4eU/NjCA== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "*" + "@types/serve-static" "*" + +"@types/mime@*": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d" + integrity sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw== + +"@types/node@*": + version "13.1.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-13.1.2.tgz#fe94285bf5e0782e1a9e5a8c482b1c34465fa385" + integrity sha512-B8emQA1qeKerqd1dmIsQYnXi+mmAzTB7flExjmy5X1aVAKFNNNDubkavwR13kR6JnpeLp3aLoJhwn9trWPAyFQ== + +"@types/range-parser@*": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" + integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== + +"@types/serve-static@*": + version "1.13.3" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.3.tgz#eb7e1c41c4468272557e897e9171ded5e2ded9d1" + integrity sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g== + dependencies: + "@types/express-serve-static-core" "*" + "@types/mime" "*" + +"@types/walk@^2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@types/walk/-/walk-2.3.0.tgz#1bfeb19f6fab63a1a39f929c0f2b4c02134468f8" + integrity sha512-I1w1RJW5kowe7JnekvVTSD/Lek8WK0N/Fz80n9Chnb5jYo+mne4tDthgCAV/Fo1tym9m8W6stfsJQvjRMpOHgw== + dependencies: + "@types/node" "*" + +accepts@~1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" + integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== + dependencies: + mime-types "~2.1.24" + negotiator "0.6.2" + +ansi-colors@3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" + integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw== + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" + integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== + dependencies: + "@types/color-name" "^1.1.1" + color-convert "^2.0.1" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +body-parser@1.19.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" + integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== + dependencies: + bytes "3.1.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.2" + http-errors "1.7.2" + iconv-lite "0.4.24" + on-finished "~2.3.0" + qs "6.7.0" + raw-body "2.4.0" + type-is "~1.6.17" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +browser-stdout@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== + +bytes@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" + integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== + +camelcase@^5.0.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +chalk@^2.0.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +cliui@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" + integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== + dependencies: + string-width "^3.1.0" + strip-ansi "^5.2.0" + wrap-ansi "^5.1.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +content-disposition@0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" + integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== + dependencies: + safe-buffer "5.1.2" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= + +cookie@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" + integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@3.2.6: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + +decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +define-properties@^1.1.2, define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= + +diff@3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + +es-abstract@^1.17.0-next.1: + version "1.17.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.0.tgz#f42a517d0036a5591dbb2c463591dc8bb50309b1" + integrity sha512-yYkE07YF+6SIBmg1MsJ9dlub5L48Ek7X0qz+c/CPCHS9EBXfESorzng4cJQjJW5/pB6vDF41u7F8vUhLVDqIug== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.1.5" + is-regex "^1.0.5" + object-inspect "^1.7.0" + object-keys "^1.1.1" + object.assign "^4.1.0" + string.prototype.trimleft "^2.1.1" + string.prototype.trimright "^2.1.1" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + +exec-sh@^0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.2.tgz#2a5e7ffcbd7d0ba2755bdecb16e5a427dfbdec36" + integrity sha512-FIUCJz1RbuS0FKTdaAafAByGS0CPvU3R0MeHxgtl+djzCc//F8HakL8GzmVNZanasTbTAY/3DRFA0KpVqj/eAw== + dependencies: + merge "^1.2.0" + +express@^4.17.1: + version "4.17.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" + integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== + dependencies: + accepts "~1.3.7" + array-flatten "1.1.1" + body-parser "1.19.0" + content-disposition "0.5.3" + content-type "~1.0.4" + cookie "0.4.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.2" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "~1.1.2" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.5" + qs "6.7.0" + range-parser "~1.2.1" + safe-buffer "5.1.2" + send "0.17.1" + serve-static "1.14.1" + setprototypeof "1.1.1" + statuses "~1.5.0" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +finalhandler@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" + integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.3" + statuses "~1.5.0" + unpipe "~1.0.0" + +find-up@3.0.0, find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +flat@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.0.tgz#090bec8b05e39cba309747f1d588f04dbaf98db2" + integrity sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw== + dependencies: + is-buffer "~2.0.3" + +foreachasync@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/foreachasync/-/foreachasync-3.0.0.tgz#5502987dc8714be3392097f32e0071c9dee07cf6" + integrity sha1-VQKYfchxS+M5IJfzLgBxyd7gfPY= + +forwarded@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" + integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +get-caller-file@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +glob@7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +growl@1.10.5: + version "1.10.5" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" + integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.0, has-symbols@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" + integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +he@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +http-errors@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" + integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +http-errors@~1.7.2: + version "1.7.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" + integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +ipaddr.js@1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65" + integrity sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA== + +is-buffer@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623" + integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A== + +is-callable@^1.1.4, is-callable@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" + integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== + +is-date-object@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" + integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-regex@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" + integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ== + dependencies: + has "^1.0.3" + +is-symbol@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" + integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== + dependencies: + has-symbols "^1.0.1" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +js-yaml@3.13.1: + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +lodash@^4.17.15: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + +log-symbols@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" + integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== + dependencies: + chalk "^2.0.1" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= + +merge@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145" + integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + +mime-db@1.42.0: + version "1.42.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.42.0.tgz#3e252907b4c7adb906597b4b65636272cf9e7bac" + integrity sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ== + +mime-types@~2.1.24: + version "2.1.25" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.25.tgz#39772d46621f93e2a80a856c53b86a62156a6437" + integrity sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg== + dependencies: + mime-db "1.42.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +minimatch@3.0.4, minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + +minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= + +mkdirp@0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + dependencies: + minimist "0.0.8" + +mocha@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-6.2.2.tgz#5d8987e28940caf8957a7d7664b910dc5b2fea20" + integrity sha512-FgDS9Re79yU1xz5d+C4rv1G7QagNGHZ+iXF81hO8zY35YZZcLEsJVfFolfsqKFWunATEvNzMK0r/CwWd/szO9A== + dependencies: + ansi-colors "3.2.3" + browser-stdout "1.3.1" + debug "3.2.6" + diff "3.5.0" + escape-string-regexp "1.0.5" + find-up "3.0.0" + glob "7.1.3" + growl "1.10.5" + he "1.2.0" + js-yaml "3.13.1" + log-symbols "2.2.0" + minimatch "3.0.4" + mkdirp "0.5.1" + ms "2.1.1" + node-environment-flags "1.0.5" + object.assign "4.1.0" + strip-json-comments "2.0.1" + supports-color "6.0.0" + which "1.3.1" + wide-align "1.1.3" + yargs "13.3.0" + yargs-parser "13.1.1" + yargs-unparser "1.6.0" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + +ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +negotiator@0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" + integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== + +node-environment-flags@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.5.tgz#fa930275f5bf5dae188d6192b24b4c8bbac3d76a" + integrity sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ== + dependencies: + object.getownpropertydescriptors "^2.0.3" + semver "^5.7.0" + +object-inspect@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" + integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== + +object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@4.1.0, object.assign@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + +object.getownpropertydescriptors@^2.0.3: + version "2.1.0" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" + integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= + dependencies: + ee-first "1.1.1" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +p-limit@^2.0.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e" + integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ== + dependencies: + p-try "^2.0.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= + +proxy-addr@~2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34" + integrity sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ== + dependencies: + forwarded "~0.1.2" + ipaddr.js "1.9.0" + +qs@6.7.0: + version "6.7.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" + integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" + integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== + dependencies: + bytes "3.1.0" + http-errors "1.7.2" + iconv-lite "0.4.24" + unpipe "1.0.0" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + +safe-buffer@5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^5.7.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +send@0.17.1: + version "0.17.1" + resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" + integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== + dependencies: + debug "2.6.9" + depd "~1.1.2" + destroy "~1.0.4" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.7.2" + mime "1.6.0" + ms "2.1.1" + on-finished "~2.3.0" + range-parser "~1.2.1" + statuses "~1.5.0" + +serve-static@1.14.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" + integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.17.1" + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +setprototypeof@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" + integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +"statuses@>= 1.5.0 < 2", statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + +"string-width@^1.0.2 || 2": + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string-width@^3.0.0, string-width@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string.prototype.trimleft@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74" + integrity sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag== + dependencies: + define-properties "^1.1.3" + function-bind "^1.1.1" + +string.prototype.trimright@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz#440314b15996c866ce8a0341894d45186200c5d9" + integrity sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g== + dependencies: + define-properties "^1.1.3" + function-bind "^1.1.1" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-json-comments@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + +supports-color@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.0.0.tgz#76cfe742cf1f41bb9b1c29ad03068c05b4c0e40a" + integrity sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg== + dependencies: + has-flag "^3.0.0" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" + integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== + dependencies: + has-flag "^4.0.0" + +toidentifier@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" + integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== + +type-is@~1.6.17, type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +typescript@^3.7.4: + version "3.7.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.4.tgz#1743a5ec5fef6a1fa9f3e4708e33c81c73876c19" + integrity sha512-A25xv5XCtarLwXpcDNZzCGvW2D1S3/bACratYBx2sax8PefsFhlYmkQicKHvpYflFS8if4zne5zT5kpJ7pzuvw== + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= + +walk@^2.3.9: + version "2.3.14" + resolved "https://registry.yarnpkg.com/walk/-/walk-2.3.14.tgz#60ec8631cfd23276ae1e7363ce11d626452e1ef3" + integrity sha512-5skcWAUmySj6hkBdH6B6+3ddMjVQYH5Qy9QGbPmN8kVmLteXk+yVXg+yfk1nbX30EYakahLrr8iPcCxJQSCBeg== + dependencies: + foreachasync "^3.0.0" + +watch@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/watch/-/watch-1.0.2.tgz#340a717bde765726fa0aa07d721e0147a551df0c" + integrity sha1-NApxe952Vyb6CqB9ch4BR6VR3ww= + dependencies: + exec-sh "^0.2.0" + minimist "^1.2.0" + +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + +which@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +wide-align@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== + dependencies: + string-width "^1.0.2 || 2" + +wrap-ansi@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" + integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== + dependencies: + ansi-styles "^3.2.0" + string-width "^3.0.0" + strip-ansi "^5.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +y18n@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" + integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== + +yargs-parser@13.1.1, yargs-parser@^13.1.1: + version "13.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0" + integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs-unparser@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f" + integrity sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw== + dependencies: + flat "^4.1.0" + lodash "^4.17.15" + yargs "^13.3.0" + +yargs@13.3.0, yargs@^13.3.0: + version "13.3.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83" + integrity sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA== + dependencies: + cliui "^5.0.0" + find-up "^3.0.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^3.0.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^13.1.1"