diff --git a/lib/Appstrap.js b/lib/Appstrap.js index 5999868..ceefcc9 100644 --- a/lib/Appstrap.js +++ b/lib/Appstrap.js @@ -1,107 +1,66 @@ const express = require('express') const bodyParser = require('body-parser') -const Logger = require('./Logger') -const Handlers = require('./Handlers') -const Fixtures = require('./Fixtures') -const InMemoryState = require('./InMemoryState') const Interactor = require('./Interactor') const Config = require('./Config') +const EventEmitter = require('node:events').EventEmitter +const Logger = require('./Logger') const chokidar = require('chokidar') -const Interceptor = require('./Interceptor') class Appstrap extends express { - constructor ({ - watch = false, - configDir = './.appstrap', - gqlEndpoint, - logger = Logger - } = {}) { + constructor ({ watch = false, repository, gqlEndpoint, logger = Logger } = {}) { super() - this.logger = logger this.use(bodyParser.json()) - this.history = [] - this.gqlEndpoint = gqlEndpoint - // control cached responses + // disable cached responses this.set('etag', false) this.use((req, res, next) => { res.set('Cache-Control', 'no-store'); next() }) - this.use((req, res, next) => { next() }) - - this.configureRoutes = configureRoutes.bind(this) - this.loadConfiguration = loadConfiguration.bind(this) - this.updateConfiguration = updateConfiguration.bind(this) - - this.config = new Config(configDir, this.logger) - this.loadConfiguration() + this.logger = logger + this.events = new EventEmitter() + this.events.on('log', (event) => { this.logger[event.level](event.message) }) + this.events.on('config:ready', (config) => { + this.config = config + this.interactor = new Interactor({ config, events: this.events }) + this.use((req, res, next) => this.interactor.router(req, res, next)) + this.use(configureEndpoints.call(this, config)) + }) + this.config = new Config({ repository, gqlEndpoint, events: this.events }) if (watch) { - this.fileWatcher = chokidar.watch(this.config.absConfigDir, { ignoreInitial: true }) - this.fileWatcher.on('all', () => { this.updateConfiguration() }) + this.fileWatcher = chokidar.watch(repository, { ignoreInitial: true }) + this.fileWatcher.on('all', () => this.events.emit('watcher:change')) } } - - reset () { - this.loadConfiguration() - } } -function loadConfiguration () { - this.handlers = new Handlers(this) - this.fixtures = new Fixtures(this) - this.memoryStore = new InMemoryState(this.config.initialState) - this.interactor = new Interactor(this) - this.configureRoutes() -} - -function updateConfiguration () { - this.config.update() - this.handlers.update() - this.fixtures.update() - this.configureRoutes() -} - -function configureRoutes () { - const routeCollection = new Set() - for (const proxyPath of Object.keys(this.config.proxyMap)) { - routeCollection.add(`ALL:::${proxyPath}`) - } - - // gather GQL if specified - if (this.gqlEndpoint) { - routeCollection.add(`POST:::${this.gqlEndpoint}`) - } - // gather route handlers - const handlers = Array.from(this.handlers.collection.values()) - handlers.forEach(handler => routeCollection.add(`${handler.method.toUpperCase()}:::${handler.path}`)) +function configureEndpoints (config) { + const router = express.Router({}) + for (const [url, endpoint] of config.endpoints.entries()) { + router.all(url, async (req, res, next) => { + const context = { req, res, next, state: config.state } + try { + const payload = await endpoint.execute(context, config.fixtures) + // todo handle manual rejects etc - // gather fixtures that may not have route handlers associated - const fixtures = Array.from(this.fixtures.collection.values()) - fixtures.forEach(fixture => { - // filter out gql handlers, if specified, that route will already be added to the interceptor list - const restHandlers = fixture.handlers.filter(({ path, method }) => typeof path !== 'undefined' && typeof method !== 'undefined') - restHandlers.forEach(({ path, method }) => { - routeCollection.add(`${method.toUpperCase()}:::${path}`) + /* + * TODO: fix this hacky bs + * forwarding a request and maintaining headers is tough without streaming. Because forwardRequest + * handles returning the stream data, payload could be undefined. If we attempt to return again express + * will yell at us. This is temporary until I can find another solution to the request forwarding. + * */ + if (payload) { + res.json(payload) + } + } catch (e) { + res.status(500).send({ message: e.message }) + } }) - }) - - const router = express.Router({}) - const interceptor = new Interceptor(this) - for (const route of routeCollection) { - const [method, path] = route.split(':::') - // if (!router[method]) { return } - router[method.toLowerCase()](path, (req, res, next) => interceptor.intercept(req, res, next, this.memoryStore.state)) } - // default fall-through - router.all('*', (req, res, next) => next()) - this.routes = router - // always ensure a fresh router (for reloads) - const existingRoutesIndex = this._router.stack.findIndex(route => route.name === 'handleRoutes') + // ensure router doesn't exist in the stack already for hot reloads + const existingRoutesIndex = this._router.stack.findIndex(route => route.name === 'router') if (existingRoutesIndex >= 0) { this._router.stack.splice(existingRoutesIndex, 1) } - const handleRoutes = (req, res, next) => this.routes(req, res, next) - this.use(handleRoutes) - this.use((req, res, next) => this.interactor.router(req, res, next)) + return router } module.exports = Appstrap diff --git a/lib/Config.js b/lib/Config.js deleted file mode 100644 index d0b2865..0000000 --- a/lib/Config.js +++ /dev/null @@ -1,30 +0,0 @@ -const path = require('path') -const fs = require('fs') -const glob = require('glob') - -class Config { - constructor (configDir, logger) { - this.absConfigDir = path.resolve(configDir).split(path.sep).join(path.posix.sep) - this.logger = logger - - if (!fs.existsSync(this.absConfigDir)) { - this.logger.error(`no configuration found at ${this.absConfigDir}`) - } - - this.loadFiles() - } - - loadFiles () { - this.files = glob.sync(`${this.absConfigDir}/**/*`, { nodir: true, nosort: true }).reverse() - const initialStateFile = this.files.find(file => file.includes('initialState')) - this.initialState = initialStateFile ? require(initialStateFile) : {} - const proxyFile = this.files.find(file => file.includes('proxy')) - this.proxyMap = proxyFile ? require(proxyFile) : {} - } - - update () { - this.loadFiles() - } -} - -module.exports = exports = Config diff --git a/lib/InMemoryState.js b/lib/Config/State.js similarity index 72% rename from lib/InMemoryState.js rename to lib/Config/State.js index f0affaa..ca1de24 100644 --- a/lib/InMemoryState.js +++ b/lib/Config/State.js @@ -1,4 +1,4 @@ -class InMemoryState { +class State { constructor (initialState = {}) { this._state = initialState } @@ -12,4 +12,4 @@ class InMemoryState { } } -exports = module.exports = InMemoryState +exports = module.exports = State diff --git a/lib/Config/endpoints/Endpoint.js b/lib/Config/endpoints/Endpoint.js new file mode 100644 index 0000000..34355e8 --- /dev/null +++ b/lib/Config/endpoints/Endpoint.js @@ -0,0 +1,87 @@ +const { sleep } = require('../../_helpers') +const { forwardRequest } = require('./forwardRequest') + +const defaultModifiers = { + error: false, + errorCode: 500, + errorMessage: 'appstrap generated error', + latency: false, + latencyMS: 0, + enabled: true, + responseSequence: [], + allowRequestForwarding: true +} + +class Endpoint { + constructor () { + this.enabled = true + this.requestForwardingURL = null + this.modifiers = new Map() + } + + async execute (context, fixtures, fn, modifiers) { + if (!this.enabled || !modifiers.enabled) { return context.next() } + let payload = {} + + if (modifiers.latency) { await sleep(modifiers.latencyMS) } + + if (modifiers.error) { throw new Error(modifiers.errorMessage, { cause: { statusCode: modifiers.errorCode } }) } + + if (modifiers.responseSequence.length > 0) { + + } else { + if (modifiers.allowRequestForwarding && this.requestForwardingURL) { + // todo convert this from streaming to something we can keep all in the same request cycle + await forwardRequest(context, this.requestForwardingURL, fixtures.applyActive.bind(fixtures)) + return + } else { + if (fn) { + payload = fn(context) + } + } + } + + return fixtures.applyActive(context, payload) + } +} + +class RestEndpoint extends Endpoint { + constructor ({ url, methods }) { + super({ url }) + this.endpointType = 'REST' + this.methods = methods + for (const method of Object.keys(methods)) { + this.modifiers.set(method, defaultModifiers) + } + } + + async execute (context, fixtures) { + const httpVerb = context.req.method + if (typeof this.methods[httpVerb] === 'undefined') { return context.next() } + const method = this.methods[httpVerb] + const modifiers = this.modifiers.get(httpVerb) + return super.execute(context, fixtures, method, modifiers) + } +} + +class GraphEndpoint extends Endpoint { + constructor ({ url, operations }) { + super({ url }) + this.endpointType = 'GQL' + this.operations = operations + for (const operation of operations.keys()) { + this.modifiers.set(operation, defaultModifiers) + } + } + + async execute (context, fixtures) { + const operationName = context.req.body.operationName + const modifiers = this.modifiers.has(operationName) ? this.modifiers.get(operationName) : defaultModifiers + return super.execute(context, fixtures, this.operations.get(operationName), modifiers) + } +} + +module.exports = exports = { + RestEndpoint, + GraphEndpoint +} diff --git a/lib/Config/endpoints/forwardRequest.js b/lib/Config/endpoints/forwardRequest.js new file mode 100644 index 0000000..dc26617 --- /dev/null +++ b/lib/Config/endpoints/forwardRequest.js @@ -0,0 +1,33 @@ +const axios = require('axios') + +async function forwardRequest (context, destination, applyFixtures) { + const options = { + responseType: 'stream', + method: context.req.method.toLowerCase(), + url: destination, + headers: context.req.headers, + useCredentials: true + } + if (context.req.body) { + options.data = context.req.body + } + try { + const { status, headers, data } = await axios(options) + let chunks = '' + + data.on('data', (chunk) => (chunks += chunk)) + data.on('end', () => { + context.res.status(status) + context.res.set(headers) + const data = JSON.parse(chunks.toString()) + const updatedData = applyFixtures(context.req, data) + context.res.write(JSON.stringify(updatedData)) + context.res.end() + }) + } catch (e) { + console.error(e) + return {} + } +} + +module.exports = exports = { forwardRequest } diff --git a/lib/Config/endpoints/index.js b/lib/Config/endpoints/index.js new file mode 100644 index 0000000..c77ffb2 --- /dev/null +++ b/lib/Config/endpoints/index.js @@ -0,0 +1,70 @@ +const { validateGqlOperation, validateEndpoint } = require('../validators') +const { loadFile } = require('../../_helpers') +const path = require('path') +const { GraphEndpoint, RestEndpoint } = require('./Endpoint') +const { WARN_NO_GQL_ENDPOINT } = require('../../_errors') + +function getByKey (key, endpointMap) { + if (key.startsWith('/')) { + return ({ key, endpoint: endpointMap.get(key) }) + } + for (const [url, endpoint] of endpointMap.entries()) { + if (endpoint.endpointType === 'GQL') { + if (endpoint.operations.has(key)) { + return ({ key: url, endpoint: endpointMap.get(url) }) + } + } + } + return {} +} + +function initialize ({ files, gqlEndpoint, hostMap, events }) { + const collection = new Map() + + // create graph endpoint + if (gqlEndpoint) { + const operations = new Map() + const gqlOperationFilePaths = files.filter(pathname => pathname.includes('/gql/')) + for (const gqlOperationFilePath of gqlOperationFilePaths) { + const gqlOperation = loadFile(gqlOperationFilePath, validateGqlOperation, events) + if (gqlOperation) { + operations.set(path.basename(gqlOperationFilePath, '.js'), gqlOperation) + } + } + collection.set(gqlEndpoint, new GraphEndpoint({ url: gqlEndpoint, operations })) + } + + if (files.some(file => file.includes('/gql/')) && !gqlEndpoint) { + events.emit('log', { level: 'warn', message: WARN_NO_GQL_ENDPOINT }) + } + + // create rest endpoint(s) + const endpointFilePaths = files.filter(pathname => pathname.includes('/routes/')) + for (const endpointFilePath of endpointFilePaths) { + const endpointDefinition = loadFile(endpointFilePath, validateEndpoint, events) + if (endpointDefinition) { + const url = endpointFilePath.replace(/.*routes\//, '/').replace(/\[(.*?)\]/g, ':$1').replace('.js', '') + const { ...methods } = endpointDefinition + collection.set(url, new RestEndpoint({ url, methods })) + } + } + + // apply host map forwarding rules + for (const [pattern, destination] of Object.entries(hostMap)) { + for (const endpoint of collection.keys()) { + const matcher = new RegExp(pattern.replace('*', '.*')) + if (matcher.test(endpoint)) { + const endpointInstance = collection.get(endpoint) + endpointInstance.requestForwardingURL = destination + collection.set(endpoint, endpointInstance) + } + } + } + + return collection +} + +module.exports = { + initialize, + getByKey +} diff --git a/lib/Fixture.js b/lib/Config/fixtures/Fixture.js similarity index 93% rename from lib/Fixture.js rename to lib/Config/fixtures/Fixture.js index fa237c8..5788363 100644 --- a/lib/Fixture.js +++ b/lib/Config/fixtures/Fixture.js @@ -6,7 +6,7 @@ class Fixture { this.handlers = handlers } - execute (req, resPayload, logger) { + execute (req, resPayload) { try { let fixture if (req.body && req.body.operationName) { @@ -21,7 +21,6 @@ class Fixture { switch (fixture.mode) { case 'replace': return result - case 'deepMerge': case 'mergeDeep': return mergeDeep(resPayload, result) default: // merge diff --git a/lib/Config/fixtures/index.js b/lib/Config/fixtures/index.js new file mode 100644 index 0000000..68d3454 --- /dev/null +++ b/lib/Config/fixtures/index.js @@ -0,0 +1,41 @@ +const Fixture = require('./Fixture') +const { loadFile } = require('../../_helpers') +const { validateFixture } = require('../validators') + +class Fixtures { + constructor ({ files, events }) { + this.files = files + this.collection = new Map() + this.active = new Set() + this.events = events + + const fixturePaths = this.files.filter(file => file.includes('/fixtures/')) + fixturePaths.forEach(fixturePath => { + const fixtureName = fixturePath.replace(/.*fixtures\//, '').replace('.js', '') + this.collection.set(fixtureName, new Fixture({ name: fixtureName, ...loadFile(fixturePath, validateFixture, events) })) + }) + } + + activateFixture (fixtureName) { + if (!this.collection.has(fixtureName)) { + this.events.emit('log', { level: 'warn', message: `Unable to activate fixture ${fixtureName} - not found in collection` }) + return + } + this.active.add(fixtureName) + } + + deactivateFixture (fixtureName) { + this.active.delete(fixtureName) + } + + applyActive ({ req }, payload) { + return this.active.size > 0 + ? Array.from(this.active) + .reduce((response, activeFixture) => + this.collection.get(activeFixture).execute(req, payload) + , payload) + : payload + } +} + +module.exports = Fixtures diff --git a/lib/Config/index.js b/lib/Config/index.js new file mode 100644 index 0000000..42e6713 --- /dev/null +++ b/lib/Config/index.js @@ -0,0 +1,68 @@ +const path = require('path') +const fs = require('fs') +const glob = require('glob') +const { ERR_NO_REPOSITORY_DEFINED, ERR_NO_REPOSITORY_FOUND, ERR_REPOSITORY_EMPTY } = require('../_errors') +const Fixtures = require('./fixtures') +const { loadFile } = require('../_helpers') +const endpoints = require('./endpoints') + +class Config { + constructor (configArgs) { + this.load(configArgs) + configArgs.events.on('watcher:change', () => this.reload(configArgs)) + } + + load ({ repository, gqlEndpoint, events }) { + this.repository = this.resolveRepository(repository) + const files = glob.sync(`${this.repository}/**/*`, { nodir: true, nosort: true }) + this.files = files.filter(filePath => !filePath.includes('.gitkeep')) + if (this.files.length === 0) { throw new Error(ERR_REPOSITORY_EMPTY) } + + const initialStateFile = this.files.find(file => file === `${this.repository}/initialState.js`) + this.state = initialStateFile ? loadFile(initialStateFile) : {} + + const hostMap = this.files.find(file => file === `${this.repository}/hostMap.js`) + this.hostMap = hostMap ? loadFile(hostMap) : {} + + this.fixtures = new Fixtures({ files: this.files, events }) + this.endpoints = endpoints.initialize({ files: this.files, gqlEndpoint, events, hostMap: this.hostMap }) + events.emit('config:ready', this) + } + + reload (configArgs) { + configArgs.events.emit('log', { level: 'info', message: 'A change in your configuration was detected - reloading now.' }) + // todo theres things to fix here + const activatedFixtures = this.fixtures.active + const oldEndpoints = this.endpoints + + this.load(configArgs) + } + + normalizeFilePath (filePath) { + // remove separator prefix + if (path.isAbsolute(filePath)) { filePath = filePath.substring(1) } + + // convert to web url separators ( / ) regardless of OS + return filePath.split(path.sep) + .filter(entry => !!entry) // remove double slashes before re-joining with posix separator + .join(path.posix.sep) + } + + /* + * This method lays the groundwork for supporting remote repositories in the future. For now there will be no additional + * logic around resolving the repository other than on the local filesystem, but this resolver can be expanded to look + * for http urls, git urls, and potentially other sources in the future. + * */ + resolveRepository (repository) { + if (!repository) { + throw new Error(ERR_NO_REPOSITORY_DEFINED) + } + const directory = path.resolve(this.normalizeFilePath(repository)) + if (!fs.existsSync(directory)) { + throw new Error(ERR_NO_REPOSITORY_FOUND(repository)) + } + return directory + } +} + +module.exports = exports = Config diff --git a/lib/Config/validators.js b/lib/Config/validators.js new file mode 100644 index 0000000..b1fcd34 --- /dev/null +++ b/lib/Config/validators.js @@ -0,0 +1,49 @@ +const joi = require('joi') + +async function validateEndpoint (filePath, fileData, events) { + const routeSchema = joi.object({ + GET: joi.function(), + PUT: joi.function(), + POST: joi.function(), + PATCH: joi.function(), + DELETE: joi.function() + }) + const validationResult = routeSchema.validate(fileData) + if (validationResult.error) { + events.emit('log', { level: 'error', message: validationResult.error.message }) + return false + } + return true +} + +async function validateGqlOperation (filePath, fileData, events) { + // todo implement + return true +} + +async function validateFixture (filePath, fileData, events) { + const fixtureSchema = joi.object({ + handlers: joi.array() + .items(joi.object({ + operationName: joi.string(), + mode: joi.string().valid('merge', 'mergeDeep', 'replace').required(), + path: joi.string(), + method: joi.string().uppercase(), + handler: joi.function(), + payload: joi.object() + }).or('payload', 'handler')) + }) + const validationResult = fixtureSchema.validate(fileData) + if (validationResult.error) { + const shortName = filePath.replace(/.*fixtures\//, '').replace('.js', '') + events.emit('log', { level: 'warn', message: `Skipped loading invalid fixture "${shortName}": ${validationResult.error.message}` }) + return false + } + return true +} + +module.exports = ({ + validateEndpoint, + validateGqlOperation, + validateFixture +}) diff --git a/lib/Fixtures.js b/lib/Fixtures.js deleted file mode 100644 index 9dbd6d3..0000000 --- a/lib/Fixtures.js +++ /dev/null @@ -1,56 +0,0 @@ -const Fixture = require('./Fixture') -const { loadFile } = require('./_helpers') - -class Fixtures { - constructor ({ config, logger }) { - this.config = config - this.logger = logger - this.active = new Set() - this.buildCollection() - } - - buildCollection () { - const filePaths = this.config.files.filter(path => path.includes('fixtures/')) - const collection = new Map() - filePaths.forEach(filePath => { - const fixtureName = filePath.replace(/.*fixtures\//, '').replace('.js', '') - collection.set(fixtureName, new Fixture({ name: fixtureName, ...loadFile(filePath) })) - }) - this.collection = collection - } - - activateFixture (fixtureName) { - if (!this.collection.has(fixtureName)) { - this.logger.warn(`Unable to activate fixture ${fixtureName} - not found in collection`) - return - } - this.active.add(fixtureName) - } - - deactivateFixture (fixtureName) { - this.active.delete(fixtureName) - } - - update () { - this.buildCollection() - // Reconcile currently active fixtures. - Array.from(this.active.keys()).forEach(activeFixture => { - this.collection.has(activeFixture) - ? this.activateFixture(activeFixture) - : this.deactivateFixture(activeFixture) - }) - } - - applyFixtures (req, payload) { - if (this.active.size > 0) { - return Array.from(this.active).reduce((response, activeFixture) => { - const fixture = this.collection.get(activeFixture) - response = fixture.execute(req, payload) - return response - }, payload) - } - return payload - } -} - -module.exports = Fixtures diff --git a/lib/GraphQL.js b/lib/GraphQL.js deleted file mode 100644 index 9e28590..0000000 --- a/lib/GraphQL.js +++ /dev/null @@ -1,18 +0,0 @@ -async function handleGqlRequest (req, res, next) { - req.isGraph = true - const { operationName } = req.body - if (typeof operationName === 'undefined') { - throw new Error('no operationName provided') - } - - const handler = this.handlers.pick({ operation: operationName }) - if (handler && handler.enabled) { - await handler.execute(req, res) - } else { - next() - } -} - -module.exports = { - handleGqlRequest -} diff --git a/lib/Handler.js b/lib/Handler.js deleted file mode 100644 index 8ccd150..0000000 --- a/lib/Handler.js +++ /dev/null @@ -1,38 +0,0 @@ -const { sleep } = require('./_helpers') - -class Handler { - constructor ({ operation, filePath, method, fn }) { - this.path = filePath - .replace(/.*routes\//, '/') - .replace(/\[(.*?)\]/g, ':$1') - .replace('.js', '') - this.id = operation || `${this.path}:::${method.toUpperCase()}` - this.fn = fn - this.method = method.toLowerCase() - this.setDefaults() - } - - setDefaults () { - this.error = false - this.errorCode = 500 - this.errorMessage = 'appstrap generated error' - this.latency = false - this.latencyMS = 0 - this.disabled = false - this.responseSequence = [] - } - - async execute (req, res, next, state) { - if (this.disabled) { return next() } - if (this.latency) { await sleep(this.latencyMS) } - if (this.error) { return res.status(this.errorCode).send(this.errorMessage) } - if (this.responseSequence.length > 0) { - const respond = this.responseSequence.shift() - return respond.call(res, req, res) - } else { // use default function handler - return this.fn(req, res, next, state) - } - } -} - -exports = module.exports = Handler diff --git a/lib/Handlers.js b/lib/Handlers.js deleted file mode 100644 index 4878af8..0000000 --- a/lib/Handlers.js +++ /dev/null @@ -1,62 +0,0 @@ -const Handler = require('./Handler') -const { loadFile } = require('./_helpers') -const { match } = require('path-to-regexp') - -class Handlers { - constructor ({ config }) { - this.config = config - this.collection = this.load() - } - - pick ({ path, method, operation }) { - if (operation) { - return this.collection.get(operation) - } - return Array.from(this.collection.values()).find((handler) => { - const matcher = match(handler.path, { decode: decodeURIComponent }) - return matcher(path) !== false && handler.method.toUpperCase() === method.toUpperCase() - }) - } - - load () { - const handlers = new Map() - const filePaths = this.config.files - .filter(fileName => fileName.includes('routes')) - filePaths.forEach(filePath => { - const { operation, ...handlerMeta } = loadFile(filePath) - for (const [method, handlerFn] of Object.entries(handlerMeta)) { - const handler = new Handler({ operation, filePath, method: method.toUpperCase(), fn: handlerFn }) - handlers.set(handler.id, handler) - } - }) - return handlers - } - - update () { - const newCollection = this.load() - const oldCollection = this.collection - - Array.from(newCollection.keys()).forEach(handlerId => { - if (oldCollection.has(handlerId)) { - const { - error, - errorCode, - errorMessage, - latency, - latencyMS, - disabled - } = oldCollection.get(handlerId) - const handler = newCollection.get(handlerId) - handler.error = error - handler.errorCode = errorCode - handler.errorMessage = errorMessage - handler.latency = latency - handler.latencyMS = latencyMS - handler.disabled = disabled - } - }) - this.collection = newCollection - } -} - -module.exports = Handlers diff --git a/lib/Interactor.js b/lib/Interactor.js index 9af0da0..937a7b3 100644 --- a/lib/Interactor.js +++ b/lib/Interactor.js @@ -1,11 +1,14 @@ const { Router } = require('express') +const { ERR_NO_MATCHING_ENDPOINT_UPDATE, ERR_NO_MATCHING_METHOD_UPDATE, ERR_METHOD_REQUIRED_TO_UPDATE } = require('./_errors') +const { getByKey } = require('./Config/endpoints') class Interactor { - constructor ({ handlers, fixtures, memoryStore }) { - this.helpers = generateHelpers() - this.handlers = handlers - this.fixtures = fixtures - this.memoryStore = memoryStore + constructor ({ config, events }) { + this.config = config + this.endpoints = config.endpoints + this.fixtures = config.fixtures + this.state = config.state + this.events = events this.routePrefix = '__interactor' this.router = this.loadInteractorRoutes() } @@ -13,52 +16,101 @@ class Interactor { loadInteractorRoutes () { const router = Router({}) const handlers = [ - { route: 'setHandlerEnabled', method: 'post' }, + { route: 'setEndpointEnabled', method: 'post' }, { route: 'setModifier', method: 'post' }, { route: 'clearModifiers', method: 'get' }, { route: 'getStatus', method: 'get' }, { route: 'activateFixture', method: 'post' }, { route: 'activateFixtures', method: 'post' }, - { route: 'deactivateFixture', method: 'post' }, - { route: 'injectState', method: 'post' }, - { route: 'addResponseSequence', method: 'post' } + { route: 'deactivateFixture', method: 'post' } + // { route: 'injectState', method: 'post' }, + // { route: 'addResponseSequence', method: 'post' } ] handlers.forEach(({ route, method }) => { router[method](`/${this.routePrefix}/${route}`, (req, res) => { - this[route](req.body, res) + try { + res.json(this[route](req.body)) + } catch (e) { + const statusCode = (e.cause && e.cause.statusCode) ? e.cause.statusCode : 500 + res.status(statusCode).json({ message: e.message }) + } }) }) return router } - getStatus (args, res) { - const fixtureGroups = Array.from(this.fixtures.collection.keys()) - const fixtures = fixtureGroups.map(fixture => ({ name: fixture, active: false })) - Array.from(this.fixtures.active.keys()).forEach((activeFixtureName, indexOrder) => { - const indexInGroup = fixtures.findIndex(fixture => fixture.name === activeFixtureName) - fixtures[indexInGroup].active = true - fixtures[indexInGroup].order = indexOrder + 1 + getStatus () { + const activeFixtures = this.fixtures.active + const fixtures = Array.from(this.fixtures.collection).map(([name, fixtureData]) => { + fixtureData.handlers = fixtureData.handlers.map(_handler => { + if (_handler.handler) { + _handler.handler = _handler.handler.toString() + } + return _handler + }) + return ({ + name, + active: activeFixtures.has(name), + order: Array.from(activeFixtures).indexOf(name) + 1, + ...fixtureData + }) + }) + + const endpoints = Array.from(this.endpoints.entries()).map(([url, endpoint]) => { + const entry = { + enabled: endpoint.isEnabled, + requestForwardingURL: endpoint.requestForwardingURL, + modifiers: Array.from(endpoint.modifiers.entries()) + } + if (endpoint.endpointType === 'GQL') { + entry.operations = Array.from(endpoint.operations).map(([operationName, method]) => { + return ([operationName, method.toString()]) + }) + } else { + entry.methods = Object.entries(endpoint.methods).map(([methodName, fn]) => { + return [methodName, fn.toString()] + }) + } + return [url, entry] }) - const status = { + return ({ fixtures, - routes: Array.from(this.handlers.collection.values()), - state: this.memoryStore.state - } - return res ? res.json(status) : status + endpoints: endpoints, + state: this.state + }) } - setHandlerEnabled ({ path, method, operation, enabled }, res) { - const handler = this.handlers.pick({ path, method, operation }) - handler.disabled = !enabled - if (res) { res.sendStatus(200) } + setModifier ({ key, method, ...updatedModifiers }) { + const { endpoint } = getByKey(key, this.endpoints) + if (!endpoint) { + throw new Error(ERR_NO_MATCHING_ENDPOINT_UPDATE, { cause: { statusCode: 404 } }) + } + const isGQL = endpoint.endpointType === 'GQL' + if (!method && !isGQL) { + throw new Error(ERR_METHOD_REQUIRED_TO_UPDATE, { cause: { statusCode: 400 } }) + } + const modifierKey = isGQL ? key : method + if (!endpoint.modifiers.has(modifierKey)) { + throw new Error(ERR_NO_MATCHING_METHOD_UPDATE, { cause: { statusCode: 400 } }) + } + return endpoint.modifiers + .set(modifierKey, { ...endpoint.modifiers.get(modifierKey), ...updatedModifiers }) + .get(modifierKey) } - setModifier ({ path, method, operation, ...args }, res) { - const handler = this.handlers.pick({ path, method, operation }) - Object.keys(args).forEach(arg => { - handler[arg] = args[arg] - }) - if (res) { res.sendStatus(200) } + setEndpointEnabled ({ key, method, enabled }) { + const { key: mappedKey, endpoint } = getByKey(key, this.endpoints) + if (!endpoint) { + throw new Error(ERR_NO_MATCHING_ENDPOINT_UPDATE, { cause: { statusCode: 404 } }) + } + const isGQL = endpoint.endpointType === 'GQL' + if (Boolean(method) || (isGQL && mappedKey !== key)) { + return this.setModifier({ key, method, enabled }) + } else { + endpoint.enabled = enabled + this.config.endpoints.set(mappedKey, endpoint) + return endpoint + } } activateFixture (fixtureName, res) { @@ -78,29 +130,18 @@ class Interactor { if (res) { res.sendStatus(200) } } - injectState (update, res) { - this.memoryStore.state = update - if (res) { res.sendStatus(200) } - } + // injectState (update, res) { + // this.memoryStore.state = update + // if (res) { res.sendStatus(200) } + // } - addResponseSequence (handlerList, res) { - handlerList.forEach(({ path, method, responseSequence }) => { - const handler = this.handlers.pick({ path, method }) - handler.responseSequence = responseSequence - }) - if (res) { res.sendStatus(200) } - } + // addResponseSequence (handlerList, res) { + // handlerList.forEach(({ path, method, responseSequence }) => { + // const handler = this.handlers.pick({ path, method }) + // handler.responseSequence = responseSequence + // }) + // if (res) { res.sendStatus(200) } + // } } exports = module.exports = Interactor - -function generateHelpers () { - function generateHandler (message, status) { - return (req, res) => res.status(status).send(message) - } - - return { - successfulResponse: (message, status = 200) => generateHandler(message, status), - errorResponse: (message, status = 500) => generateHandler(message, status) - } -} diff --git a/lib/Interceptor.js b/lib/Interceptor.js index 27ee416..c5e7817 100644 --- a/lib/Interceptor.js +++ b/lib/Interceptor.js @@ -1,4 +1,3 @@ -const GQL = require('./GraphQL') const axios = require('axios') class Interceptor { diff --git a/lib/Logger.js b/lib/Logger.js index 5ca7c44..18b68b1 100644 --- a/lib/Logger.js +++ b/lib/Logger.js @@ -2,10 +2,10 @@ const chalk = require('chalk') exports = module.exports = { info (message) { - console.log(chalk`[ Appstrap:info ] ${message}`) + console.log(chalk`[ Appstrap{greenBright :trace} ] {greenBright ${message}}`) }, trace (message) { - console.log(chalk`[ Appstrap{greenBright :trace} ] {greenBright ${message}}`) + console.log(chalk`[ Appstrap:info ] ${message}`) }, warn (message) { console.log(chalk`[ Appstrap{yellow :warn} ] {yellow ${message}}`) diff --git a/lib/_errors.js b/lib/_errors.js new file mode 100644 index 0000000..8534c7a --- /dev/null +++ b/lib/_errors.js @@ -0,0 +1,18 @@ +const ERR_NO_REPOSITORY_DEFINED = 'No repository location provided. Please provide a location via the `repository` argument in your instance constructor.' +const ERR_NO_REPOSITORY_FOUND = (directory) => `No configuration found at ${directory}` +const ERR_REPOSITORY_EMPTY = 'No configuration files found in repository. Nothing to do.' +const ERR_NO_MATCHING_ENDPOINT_UPDATE = 'No matching endpoint to update' +const ERR_NO_MATCHING_METHOD_UPDATE = 'No matching method to update' +const ERR_METHOD_REQUIRED_TO_UPDATE = 'This operation must be performed on a single method.' + +const WARN_NO_GQL_ENDPOINT = 'GraphQL files detected but no endpoint provided. No endpoint handlers can be created until gqlEndpoint is specified' + +module.exports = exports = ({ + ERR_NO_REPOSITORY_DEFINED, + ERR_NO_REPOSITORY_FOUND, + ERR_REPOSITORY_EMPTY, + ERR_NO_MATCHING_ENDPOINT_UPDATE, + ERR_NO_MATCHING_METHOD_UPDATE, + ERR_METHOD_REQUIRED_TO_UPDATE, + WARN_NO_GQL_ENDPOINT +}) diff --git a/lib/_helpers.js b/lib/_helpers.js index ee07845..bf14346 100644 --- a/lib/_helpers.js +++ b/lib/_helpers.js @@ -1,9 +1,20 @@ const decache = require('decache') exports = module.exports = { - loadFile: (handlerPath) => { + loadFile: (handlerPath, fileValidator, events) => { try { decache(handlerPath) } catch (e) {} - return require(handlerPath) + try { + const fileData = require(handlerPath) + if (fileValidator) { + const isValid = fileValidator(handlerPath, fileData, events) + return isValid ? fileData : null + } else { + return fileData + } + } catch (e) { + console.error('unable to require file - skipping') + return null + } }, sleep: (duration) => { return new Promise((resolve) => { diff --git a/package-lock.json b/package-lock.json index 6d7d438..ab767d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,14 @@ { "name": "@appstrap/core", - "version": "3.0.2", + "version": "4.0.0-rc.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@appstrap/core", - "version": "3.0.2", + "version": "4.0.0-rc.1", "license": "ISC", "dependencies": { - "@hapi/joi": "^16.1.7", "axios": "^1.6.0", "body-parser": "^1.19.0", "chalk": "^3.0.0", @@ -17,6 +16,7 @@ "decache": "^4.5.1", "express": "^4.17.1", "glob": "^8.0.3", + "joi": "^17.11.0", "lodash.merge": "^4.6.2", "mocha": "^10.2.0" }, @@ -117,52 +117,6 @@ "node": ">=4" } }, - "node_modules/@hapi/address": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", - "integrity": "sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ==", - "deprecated": "Moved to 'npm install @sideway/address'" - }, - "node_modules/@hapi/formula": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-1.2.0.tgz", - "integrity": "sha512-UFbtbGPjstz0eWHb+ga/GM3Z9EzqKXFWIbSOFURU0A/Gku0Bky4bCk9/h//K2Xr3IrCfjFNhMm4jyZ5dbCewGA==", - "deprecated": "Moved to 'npm install @sideway/formula'" - }, - "node_modules/@hapi/hoek": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz", - "integrity": "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow==", - "deprecated": "This version has been deprecated and is no longer supported or maintained" - }, - "node_modules/@hapi/joi": { - "version": "16.1.7", - "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-16.1.7.tgz", - "integrity": "sha512-anaIgnZhNooG3LJLrTFzgGALTiO97zRA1UkvQHm9KxxoSiIzCozB3RCNCpDnfhTJD72QlrHA8nwGmNgpFFCIeg==", - "deprecated": "Switch to 'npm install joi'", - "dependencies": { - "@hapi/address": "^2.1.2", - "@hapi/formula": "^1.2.0", - "@hapi/hoek": "^8.2.4", - "@hapi/pinpoint": "^1.0.2", - "@hapi/topo": "^3.1.3" - } - }, - "node_modules/@hapi/pinpoint": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-1.0.2.tgz", - "integrity": "sha512-dtXC/WkZBfC5vxscazuiJ6iq4j9oNx1SHknmIr8hofarpKUZKmlUVYVIhNVzIEgK5Wrc4GMHL5lZtt1uS2flmQ==", - "deprecated": "Moved to 'npm install @sideway/pinpoint'" - }, - "node_modules/@hapi/topo": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.6.tgz", - "integrity": "sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ==", - "deprecated": "This version has been deprecated and is no longer supported or maintained", - "dependencies": { - "@hapi/hoek": "^8.3.0" - } - }, "node_modules/@jest/types": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", @@ -228,6 +182,29 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/@sideway/address": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", + "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/address/node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" + }, "node_modules/@sinonjs/commons": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.6.0.tgz", @@ -324,12 +301,12 @@ "dev": true }, "node_modules/accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "dependencies": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { "node": ">= 0.6" @@ -506,23 +483,26 @@ } }, "node_modules/body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", "dependencies": { - "bytes": "3.1.0", + "bytes": "3.1.2", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, "node_modules/body-parser/node_modules/debug": { @@ -564,13 +544,26 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" }, "node_modules/bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "engines": { "node": ">= 0.8" } }, + "node_modules/call-bind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "dependencies": { + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsite": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", @@ -791,16 +784,35 @@ } }, "node_modules/content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "dependencies": { - "safe-buffer": "5.1.2" + "safe-buffer": "5.2.1" }, "engines": { "node": ">= 0.6" } }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", @@ -810,9 +822,9 @@ } }, "node_modules/cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", "engines": { "node": ">= 0.6" } @@ -823,9 +835,9 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, "node_modules/cookiejar": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", - "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", "dev": true }, "node_modules/core-util-is": { @@ -851,9 +863,9 @@ } }, "node_modules/cross-spawn/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "bin": { "semver": "bin/semver" @@ -909,6 +921,19 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "node_modules/define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -973,17 +998,21 @@ } }, "node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } }, "node_modules/diff": { "version": "3.5.0", @@ -1018,7 +1047,7 @@ "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/emoji-regex": { "version": "7.0.3", @@ -1029,7 +1058,7 @@ "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "engines": { "node": ">= 0.8" } @@ -1092,7 +1121,7 @@ "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, "node_modules/escape-string-regexp": { "version": "1.0.5", @@ -1522,7 +1551,7 @@ "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "engines": { "node": ">= 0.6" } @@ -1543,37 +1572,38 @@ } }, "node_modules/express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", "dependencies": { - "accepts": "~1.3.7", + "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.0", + "cookie": "0.5.0", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", + "depd": "2.0.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", + "finalhandler": "1.2.0", "fresh": "0.5.2", + "http-errors": "2.0.0", "merge-descriptors": "1.0.1", "methods": "~1.1.2", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", + "proxy-addr": "~2.0.7", + "qs": "6.11.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", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -1600,6 +1630,25 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "node_modules/express/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -1674,16 +1723,16 @@ } }, "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "2.0.1", "unpipe": "~1.0.0" }, "engines": { @@ -1701,7 +1750,7 @@ "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/find-root": { "version": "1.1.0", @@ -1790,9 +1839,9 @@ "dev": true }, "node_modules/forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "engines": { "node": ">= 0.6" } @@ -1800,7 +1849,7 @@ "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "engines": { "node": ">= 0.6" } @@ -1824,10 +1873,12 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/functional-red-black-tree": { "version": "1.0.1", @@ -1843,6 +1894,20 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-intrinsic": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "dependencies": { + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-stdin": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-7.0.0.tgz", @@ -1909,6 +1974,17 @@ "node": ">=4" } }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -1936,11 +2012,46 @@ "node": ">=4" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "dependencies": { + "get-intrinsic": "^1.2.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", - "dev": true, + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dependencies": { + "function-bind": "^1.1.2" + }, "engines": { "node": ">= 0.4" } @@ -1960,25 +2071,20 @@ "dev": true }, "node_modules/http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" }, "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, - "node_modules/http-errors/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -2086,9 +2192,9 @@ } }, "node_modules/ipaddr.js": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", - "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "engines": { "node": ">= 0.10" } @@ -2443,6 +2549,31 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/joi": { + "version": "17.11.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz", + "integrity": "sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==", + "dependencies": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.3", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/joi/node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "node_modules/joi/node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2675,19 +2806,19 @@ } }, "node_modules/mime-db": { - "version": "1.42.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.42.0.tgz", - "integrity": "sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.25", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.25.tgz", - "integrity": "sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dependencies": { - "mime-db": "1.42.0" + "mime-db": "1.52.0" }, "engines": { "node": ">= 0.6" @@ -2980,9 +3111,9 @@ "dev": true }, "node_modules/negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "engines": { "node": ">= 0.6" } @@ -3034,9 +3165,9 @@ } }, "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "bin": { "semver": "bin/semver" @@ -3060,10 +3191,9 @@ } }, "node_modules/object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", - "dev": true, + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3138,9 +3268,9 @@ } }, "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dependencies": { "ee-first": "1.1.1" }, @@ -3547,12 +3677,12 @@ } }, "node_modules/proxy-addr": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", - "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "dependencies": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.9.0" + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" }, "engines": { "node": ">= 0.10" @@ -3573,11 +3703,17 @@ } }, "node_modules/qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, "engines": { "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/randombytes": { @@ -3597,12 +3733,12 @@ } }, "node_modules/raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "dependencies": { - "bytes": "3.1.0", - "http-errors": "1.7.2", + "bytes": "3.1.2", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" }, @@ -3798,32 +3934,32 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" } }, "node_modules/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "dependencies": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "depd": "2.0.0", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "~1.7.2", + "http-errors": "2.0.0", "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", + "ms": "2.1.3", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "engines": { "node": ">= 0.8.0" @@ -3840,12 +3976,12 @@ "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/send/node_modules/ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/serialize-javascript": { "version": "6.0.0", @@ -3856,23 +3992,37 @@ } }, "node_modules/serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "dependencies": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.1" + "send": "0.18.0" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/setprototypeof": { + "node_modules/set-function-length": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "dependencies": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "node_modules/shebang-command": { "version": "1.2.0", @@ -3895,6 +4045,19 @@ "node": ">=0.10.0" } }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -4046,11 +4209,11 @@ } }, "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/string_decoder": { @@ -4177,10 +4340,9 @@ } }, "node_modules/superagent/node_modules/debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "dependencies": { "ms": "^2.1.1" @@ -4295,9 +4457,9 @@ } }, "node_modules/toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "engines": { "node": ">=0.6" } @@ -4365,7 +4527,7 @@ "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "engines": { "node": ">= 0.8" } @@ -4430,9 +4592,9 @@ } }, "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -4736,46 +4898,6 @@ } } }, - "@hapi/address": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", - "integrity": "sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ==" - }, - "@hapi/formula": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-1.2.0.tgz", - "integrity": "sha512-UFbtbGPjstz0eWHb+ga/GM3Z9EzqKXFWIbSOFURU0A/Gku0Bky4bCk9/h//K2Xr3IrCfjFNhMm4jyZ5dbCewGA==" - }, - "@hapi/hoek": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz", - "integrity": "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow==" - }, - "@hapi/joi": { - "version": "16.1.7", - "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-16.1.7.tgz", - "integrity": "sha512-anaIgnZhNooG3LJLrTFzgGALTiO97zRA1UkvQHm9KxxoSiIzCozB3RCNCpDnfhTJD72QlrHA8nwGmNgpFFCIeg==", - "requires": { - "@hapi/address": "^2.1.2", - "@hapi/formula": "^1.2.0", - "@hapi/hoek": "^8.2.4", - "@hapi/pinpoint": "^1.0.2", - "@hapi/topo": "^3.1.3" - } - }, - "@hapi/pinpoint": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-1.0.2.tgz", - "integrity": "sha512-dtXC/WkZBfC5vxscazuiJ6iq4j9oNx1SHknmIr8hofarpKUZKmlUVYVIhNVzIEgK5Wrc4GMHL5lZtt1uS2flmQ==" - }, - "@hapi/topo": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.6.tgz", - "integrity": "sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ==", - "requires": { - "@hapi/hoek": "^8.3.0" - } - }, "@jest/types": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", @@ -4825,6 +4947,31 @@ } } }, + "@sideway/address": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", + "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", + "requires": { + "@hapi/hoek": "^9.0.0" + }, + "dependencies": { + "@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + } + } + }, + "@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" + }, + "@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" + }, "@sinonjs/commons": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.6.0.tgz", @@ -4921,12 +5068,12 @@ "dev": true }, "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" } }, "acorn": { @@ -5063,20 +5210,22 @@ "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==" }, "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", "requires": { - "bytes": "3.1.0", + "bytes": "3.1.2", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "dependencies": { "debug": { @@ -5117,9 +5266,19 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" }, "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "call-bind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "requires": { + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" + } }, "callsite": { "version": "1.0.0", @@ -5292,11 +5451,18 @@ "dev": true }, "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "5.2.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } } }, "content-type": { @@ -5305,9 +5471,9 @@ "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" }, "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" }, "cookie-signature": { "version": "1.0.6", @@ -5315,9 +5481,9 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, "cookiejar": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", - "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", "dev": true }, "core-util-is": { @@ -5340,9 +5506,9 @@ }, "dependencies": { "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true } } @@ -5380,6 +5546,16 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "requires": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + } + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -5431,14 +5607,14 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" }, "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, "diff": { "version": "3.5.0", @@ -5464,7 +5640,7 @@ "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "emoji-regex": { "version": "7.0.3", @@ -5475,7 +5651,7 @@ "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" }, "error-ex": { "version": "1.3.2", @@ -5523,7 +5699,7 @@ "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, "escape-string-regexp": { "version": "1.0.5", @@ -5868,7 +6044,7 @@ "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, "expect": { "version": "27.5.1", @@ -5883,37 +6059,38 @@ } }, "express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", "requires": { - "accepts": "~1.3.7", + "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.0", + "cookie": "0.5.0", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", + "depd": "2.0.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", + "finalhandler": "1.2.0", "fresh": "0.5.2", + "http-errors": "2.0.0", "merge-descriptors": "1.0.1", "methods": "~1.1.2", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", + "proxy-addr": "~2.0.7", + "qs": "6.11.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", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -5936,6 +6113,11 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" } } }, @@ -6001,16 +6183,16 @@ } }, "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "requires": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "2.0.1", "unpipe": "~1.0.0" }, "dependencies": { @@ -6025,7 +6207,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, @@ -6089,14 +6271,14 @@ "dev": true }, "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" }, "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" }, "fs.realpath": { "version": "1.0.0", @@ -6110,10 +6292,9 @@ "optional": true }, "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "functional-red-black-tree": { "version": "1.0.1", @@ -6126,6 +6307,17 @@ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, + "get-intrinsic": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "requires": { + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + }, "get-stdin": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-7.0.0.tgz", @@ -6176,6 +6368,14 @@ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "requires": { + "get-intrinsic": "^1.1.3" + } + }, "graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -6197,11 +6397,31 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "requires": { + "get-intrinsic": "^1.2.2" + } + }, + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" + }, "has-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", - "dev": true + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "requires": { + "function-bind": "^1.1.2" + } }, "he": { "version": "1.2.0", @@ -6215,22 +6435,15 @@ "dev": true }, "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - } + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" } }, "iconv-lite": { @@ -6321,9 +6534,9 @@ } }, "ipaddr.js": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", - "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, "is-arrayish": { "version": "0.2.1", @@ -6585,6 +6798,33 @@ } } }, + "joi": { + "version": "17.11.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz", + "integrity": "sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==", + "requires": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.3", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + }, + "dependencies": { + "@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "requires": { + "@hapi/hoek": "^9.0.0" + } + } + } + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6765,16 +7005,16 @@ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, "mime-db": { - "version": "1.42.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.42.0.tgz", - "integrity": "sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==" + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, "mime-types": { - "version": "2.1.25", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.25.tgz", - "integrity": "sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "requires": { - "mime-db": "1.42.0" + "mime-db": "1.52.0" } }, "mimic-fn": { @@ -6980,9 +7220,9 @@ "dev": true }, "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" }, "nice-try": { "version": "1.0.5", @@ -7033,9 +7273,9 @@ }, "dependencies": { "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true } } @@ -7052,10 +7292,9 @@ "dev": true }, "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", - "dev": true + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==" }, "object-keys": { "version": "1.1.1", @@ -7112,9 +7351,9 @@ } }, "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "requires": { "ee-first": "1.1.1" } @@ -7423,12 +7662,12 @@ } }, "proxy-addr": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", - "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "requires": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.9.0" + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" } }, "proxy-from-env": { @@ -7443,9 +7682,12 @@ "dev": true }, "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "requires": { + "side-channel": "^1.0.4" + } }, "randombytes": { "version": "2.1.0", @@ -7461,12 +7703,12 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", + "bytes": "3.1.2", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } @@ -7619,29 +7861,29 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true }, "send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "requires": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "depd": "2.0.0", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "~1.7.2", + "http-errors": "2.0.0", "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", + "ms": "2.1.3", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "dependencies": { "debug": { @@ -7655,14 +7897,14 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" } } }, @@ -7675,20 +7917,31 @@ } }, "serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.1" + "send": "0.18.0" } }, - "setprototypeof": { + "set-function-length": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "requires": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "shebang-command": { "version": "1.2.0", @@ -7705,6 +7958,16 @@ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, "signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -7839,9 +8102,9 @@ } }, "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, "string_decoder": { "version": "1.1.1", @@ -7940,9 +8203,9 @@ }, "dependencies": { "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "requires": { "ms": "^2.1.1" @@ -8036,9 +8299,9 @@ } }, "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, "tslib": { "version": "1.10.0", @@ -8091,7 +8354,7 @@ "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" }, "uri-js": { "version": "4.2.2", @@ -8144,9 +8407,9 @@ } }, "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true }, "workerpool": { diff --git a/package.json b/package.json index 598bf1f..c8ec768 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@appstrap/core", - "version": "3.0.2", + "version": "4.0.0-rc.1", "main": "index.js", "author": "James LeClair ", "license": "ISC", @@ -17,7 +17,6 @@ "supertest": "^4.0.2" }, "dependencies": { - "@hapi/joi": "^16.1.7", "axios": "^1.6.0", "body-parser": "^1.19.0", "chalk": "^3.0.0", @@ -25,6 +24,7 @@ "decache": "^4.5.1", "express": "^4.17.1", "glob": "^8.0.3", + "joi": "^17.11.0", "lodash.merge": "^4.6.2", "mocha": "^10.2.0" }, diff --git a/test/Configuration.spec.js b/test/Configuration.spec.js index bab6284..044cef8 100644 --- a/test/Configuration.spec.js +++ b/test/Configuration.spec.js @@ -1,16 +1,28 @@ const Appstrap = require('../lib/Appstrap') -const TestLogger = require('./helpers/TestLogger') const expect = require('expect') +const strapDefault = require('./helpers/strapDefault') +const { ERR_NO_REPOSITORY_DEFINED, ERR_NO_REPOSITORY_FOUND } = require('../lib/_errors') describe('Appstrap Configuration', () => { - it('throws a non critical error when incorrect config is provided', () => { - const strap = new Appstrap({ configDir: './test/configs/doesntExist', logger: TestLogger }) - expect(TestLogger.error.called).toBe(true) - expect(strap).toBeDefined() + it('matches the following shape', () => { + const strap = strapDefault() + const expectedKeys = ['files', 'state', 'hostMap', 'repository', 'endpoints', 'fixtures'] + expect(Object.keys(strap.config)).toEqual(expect.arrayContaining(expectedKeys)) }) - it('loads the config from a specified directory', () => { - const strap = new Appstrap({ configDir: './test/configs/default', logger: TestLogger }) - expect(TestLogger.error.called).toBe(false) + it('throws an error when no repository is provided on instance construction', () => { + expect(() => new Appstrap()).toThrow(ERR_NO_REPOSITORY_DEFINED) + }) + it('throws an error when an invalid repository is provided on instance construction', () => { + expect(() => new Appstrap({ repository: 'path/not/found' })).toThrow(ERR_NO_REPOSITORY_FOUND('path/not/found')) + }) + it('sets the config repository to the provided value on instance construction', () => { + const strap = strapDefault() + expect(strap.config.repository.endsWith('test/configs/default')).toBe(true) + }) + it('populates the files array with path entries for the files in the specified config repository', () => { + const strap = strapDefault() expect(strap.config.files.length).toBeGreaterThan(0) }) + it('triggers a reload on handlers when the config watcher signals for reload') + it('triggers a reload on fixtures when the config watcher signals for reload') }) diff --git a/test/Fixtures.spec.js b/test/Fixtures.spec.js index 660c376..5d7b0fd 100644 --- a/test/Fixtures.spec.js +++ b/test/Fixtures.spec.js @@ -2,17 +2,29 @@ const supertest = require('supertest') const strapDefault = require('./helpers/strapDefault') const expect = require('expect') describe('Fixtures', () => { - it('applies a single activated fixture', async () => { - const strap = strapDefault() - strap.interactor.activateFixture('testOne') - const response = await supertest(strap).get('/foo') - expect(response.body.bar).toBeDefined() + describe('Configured', () => { + }) - it('applies multiple activated fixtures in order', async () => { - const strap = strapDefault() - strap.interactor.activateFixtures(['testOne', 'testThree']) - const response = await supertest(strap).get('/foo') - expect(response.body.bar).toBe('bingo') - expect(response.body.zip).toBeDefined() + describe('Applied', () => { + it('applies a fixture to a graphql operation') + it('applies a fixture to a single route when path is /foo', async () => { + const strap = strapDefault() + strap.interactor.activateFixture('testOne') + const response = await supertest(strap).get('/foo') + expect(response.body.bar).toBeDefined() + }) + + // planned future support + it('applies a fixture to a route folder when path is /foo*') + it('applies a fixture to all routes when path is *') + + it('applies a fixture to a parameterized route when path is /foo/:bar') + it('applies multiple fixtures in activation order', async () => { + const strap = strapDefault() + strap.interactor.activateFixtures(['testOne', 'testThree']) + const response = await supertest(strap).get('/foo') + expect(response.body.bar).toBe('bingo') + expect(response.body.zip).toBeDefined() + }) }) }) diff --git a/test/Handler.spec.js b/test/Handler.spec.js deleted file mode 100644 index f3a14a5..0000000 --- a/test/Handler.spec.js +++ /dev/null @@ -1,15 +0,0 @@ -const supertest = require('supertest') -const strapDefault = require('./helpers/strapDefault') -const expect = require('expect') -describe('Handlers', () => { - it('receives a response from a defined handler', async () => { - const strap = strapDefault() - const response = await supertest(strap).get('/foo') - expect(response.body).toBeDefined() - }) - it('receives the defined route parameter successfully', async () => { - const strap = strapDefault() - const response = await supertest(strap).get('/nested/someRouteParam') - expect(response.text).toEqual('someRouteParam') - }) -}) diff --git a/test/Interactor.spec.js b/test/Interactor.spec.js index 5f0f353..7800126 100644 --- a/test/Interactor.spec.js +++ b/test/Interactor.spec.js @@ -1,47 +1,200 @@ const supertest = require('supertest') const strapDefault = require('./helpers/strapDefault') const expect = require('expect') +const joi = require('joi') +const Appstrap = require('../lib/Appstrap') +const { ERR_NO_MATCHING_METHOD_UPDATE, ERR_NO_MATCHING_ENDPOINT_UPDATE } = require('../lib/_errors') +const { getByKey } = require('../lib/Config/endpoints') describe('Interactor', () => { beforeEach(() => { this.strap = strapDefault() }) + describe('getStatus()', () => { + before(() => { + this.validator = joi.object({ + fixtures: joi.array().items(joi.object({ + name: joi.string(), + active: joi.bool(), + order: joi.number(), + handlers: joi.array().items(joi.object({ + path: joi.string(), + mode: joi.string(), + payload: joi.object(), + handler: joi.string(), + method: joi.string(), + operationName: joi.string() + })) + })), + endpoints: joi.array(), + state: joi.object() + }) + }) + describe('direct', () => { + it('gets an object representing the configuration status', () => { + this.strap.interactor.activateFixture('testOne') + const status = this.strap.interactor.getStatus() + const shapeValidationResult = this.validator.validate(status) + expect(shapeValidationResult.value).not.toEqual({}) + expect(shapeValidationResult.error).not.toBeDefined() + }) + }) + describe('via rest', () => { + it('gets an object representing the configuration status', async () => { + const response = await supertest(this.strap).get('/__interactor/getStatus') + const shapeValidationResult = this.validator.validate(response.body) + expect(shapeValidationResult.value).not.toEqual({}) + expect(shapeValidationResult.error).not.toBeDefined() + }) + }) + }) + describe('setEndpointEnabled()', () => { + describe('direct', () => { + it('throws an error when a specified path does not exist', () => { + const enableMissingPath = () => this.strap.interactor.setEndpointEnabled({ key: '/missing' }) + expect(enableMissingPath).toThrow(ERR_NO_MATCHING_ENDPOINT_UPDATE) + }) + it('throws an error when attempting to update a method not supported in route', () => { + const enableMissingMethod = () => this.strap.interactor.setEndpointEnabled({ key: '/foo', method: 'DELETE' }) + expect(enableMissingMethod).toThrow(ERR_NO_MATCHING_METHOD_UPDATE) + }) + it('disables only one method when one is specified by path', () => { + this.strap.interactor.setEndpointEnabled({ key: '/foo', method: 'GET', enabled: false }) + const { endpoint } = getByKey('/foo', this.strap.config.endpoints) + expect(endpoint.enabled).toBe(true) + expect(endpoint.modifiers.get('GET').enabled).toBe(false) + expect(endpoint.modifiers.get('POST').enabled).toBe(true) + }) + it('disables the entire route when no method is specified by path', () => { + this.strap.interactor.setEndpointEnabled({ key: '/foo', enabled: false }) + const { endpoint } = getByKey('/foo', this.strap.config.endpoints) + expect(endpoint.enabled).toBe(false) + }) + it('throws an error when a specified GQL operation does not exist', () => { + const strap = new Appstrap({ repository: 'test/configs/withGQL' }) + const enableMissingOperation = () => strap.interactor.setEndpointEnabled({ key: 'missing' }) + expect(enableMissingOperation).toThrow(ERR_NO_MATCHING_ENDPOINT_UPDATE) + }) + it('disables the GQL operation when specified', () => { + const gqlOperation = 'RunQueryOne' + const strap = new Appstrap({ repository: 'test/configs/withGQL', gqlEndpoint: '/' }) + strap.interactor.setEndpointEnabled({ key: gqlOperation, enabled: false }) + const { endpoint } = getByKey(gqlOperation, strap.config.endpoints) + expect(endpoint.modifiers.get(gqlOperation).enabled).toBe(false) + }) + }) + describe('via rest', () => { + it('throws an error when a specified path does not exist', async () => { + const response = await supertest(this.strap) + .post('/__interactor/setEndpointEnabled') + .send({ key: '/missing', enabled: false }) + expect(response.status).toBe(404) + expect(response.body.message).toBe(ERR_NO_MATCHING_ENDPOINT_UPDATE) + }) + it('disables only one method when one is specified by path', async () => { + const response = await supertest(this.strap) + .post('/__interactor/setEndpointEnabled') + .send({ key: '/foo', method: 'GET', enabled: false }) + expect(response.status).toBe(200) + const { endpoint } = getByKey('/foo', this.strap.config.endpoints) + expect(endpoint.enabled).toBe(true) + expect(endpoint.modifiers.get('GET').enabled).toBe(false) + expect(endpoint.modifiers.get('POST').enabled).toBe(true) + }) + it('disables the entire route when no method is specified by path', async () => { + const response = await supertest(this.strap) + .post('/__interactor/setEndpointEnabled') + .send({ key: '/foo', enabled: false }) + expect(response.status).toBe(200) + const { endpoint } = getByKey('/foo', this.strap.config.endpoints) + expect(endpoint.enabled).toBe(false) + }) + it('throws an error when a specified GQL operation does not exist', async () => { + const strap = new Appstrap({ repository: 'test/configs/withGQL' }) + const response = await supertest(strap) + .post('/__interactor/setEndpointEnabled') + .send({ key: 'missing', enabled: false }) + expect(response.status).toBe(404) + expect(response.body.message).toBe(ERR_NO_MATCHING_ENDPOINT_UPDATE) + }) + it('disables the GQL operation when specified', async () => { + const gqlOperation = 'RunQueryOne' + const strap = new Appstrap({ repository: 'test/configs/withGQL', gqlEndpoint: '/' }) + const response = await supertest(strap) + .post('/__interactor/setEndpointEnabled') + .send({ key: gqlOperation, enabled: false }) + expect(response.status).toBe(200) + const { endpoint } = getByKey(gqlOperation, strap.config.endpoints) + expect(endpoint.modifiers.get(gqlOperation).enabled).toBe(false) + }) + }) + }) + describe('setModifier()', () => { + describe('direct', () => { + it('disables an endpoint', () => { + const strap = new Appstrap({ repository: 'test/configs/withGQL', gqlEndpoint: '/' }) + strap.interactor.setModifier({ key: 'RunQueryOne', enabled: false }) + }) + }) + describe('via rest', () => {}) + }) + describe('activateFixture()', () => { - it('Logs a warning when fixture to activate does not exist in list', async () => { - this.strap.interactor.activateFixture('notFoundFixture') - expect(this.strap.logger.warn.called).toBe(true) - expect(this.strap.logger.warn.calledWith('Unable to activate fixture notFoundFixture - not found in collection')).toBe(true) + describe('direct', () => { + it('Logs a warning when fixture to activate does not exist in list', async () => { + this.strap.interactor.activateFixture('notFoundFixture') + expect(this.strap.logger.warn.called).toBe(true) + expect(this.strap.logger.warn.calledWith('Unable to activate fixture notFoundFixture - not found in collection')).toBe(true) + }) + it('Activates a single fixture with single route only', async () => { + this.strap.interactor.activateFixture('nested/testTwo') + const response = await supertest(this.strap).get('/nested/twice/zip') + expect(response.body.fixture).toBe('added') + expect(this.strap.config.fixtures.active.has('nested/testTwo')).toBe(true) + }) + it('Activates a single fixture with multiple routes') }) - it('Activates a single fixture with state only') - it('Activates a single fixture with state and routes') - it('Activates a single fixture with single route only', async () => { - this.strap.interactor.activateFixture('nested/testTwo') - const response = await supertest(this.strap).get('/nested/twice/zip') - expect(response.body.fixture).toBe('added') - expect(this.strap.fixtures.active.has('nested/testTwo')).toBe(true) + describe('via rest', () => { + }) - it('Activates a single fixture with multiple routes') }) describe('activateFixtures()', () => { - it('activates a list of fixtures all at once', async () => { - this.strap.interactor.activateFixtures(['nested/testTwo', 'testOne']) - expect(this.strap.fixtures.active.size).toBe(2) + describe('direct', () => { + it('activates a list of fixtures all at once in preserved order', async () => { + const fixtureList = ['nested/testTwo', 'testOne'] + this.strap.interactor.activateFixtures(fixtureList) + expect(this.strap.config.fixtures.active.size).toBe(2) + Array.from(this.strap.config.fixtures.active).forEach((fixtureName, index) => { + expect(fixtureName).toEqual(fixtureList[index]) + }) + }) + }) + describe('via rest', () => { + }) }) describe('deactivateFixture()', () => { - describe('direct api', () => { + describe('direct', () => { it('deactivates a single fixture at the front of the fixture queue', async () => { this.strap.interactor.activateFixture('nested/testTwo') - expect(this.strap.fixtures.active.has('nested/testTwo')).toBe(true) + expect(this.strap.config.fixtures.active.has('nested/testTwo')).toBe(true) this.strap.interactor.deactivateFixture('nested/testTwo') - expect(this.strap.fixtures.active.has('nested/testTwo')).toBe(false) + expect(this.strap.config.fixtures.active.has('nested/testTwo')).toBe(false) }) it('deactivates a single fixture in the middle of the fixture queue') it('deactivates a single fixture at the end of the fixture queue') }) - describe('express api', () => { + describe('via rest', () => { }) }) + describe('injectState()', () => { + describe('direct', () => {}) + describe('via rest', () => {}) + }) + describe('addResponseSequence()', () => { + describe('direct', () => {}) + describe('via rest', () => {}) + }) }) diff --git a/test/RequestForwarding.spec.js b/test/RequestForwarding.spec.js new file mode 100644 index 0000000..adb30fb --- /dev/null +++ b/test/RequestForwarding.spec.js @@ -0,0 +1,27 @@ +const Appstrap = require('../lib/Appstrap') +const expect = require('expect') +describe('Request Forwarding', () => { + it('flags all requests for forwarding when a wildcard is provided in the hostMap (*)', () => { + const strap = new Appstrap({ repository: 'test/configs/withRequestForwardingAll' }) + const endpoints = Array.from(strap.config.endpoints.values()) + expect(endpoints.every(endpoint => endpoint.requestForwardingURL === 'http://localhost:4001')).toBe(true) + }) + it('flags endpoints under a sub-route for forwarding when a wildcard is provided under that route in the hostMap (/nested*)', () => { + const strap = new Appstrap({ repository: 'test/configs/withRequestForwarding' }) + const endpoints = Array.from(strap.config.endpoints.entries()) + endpoints.forEach(([endpoint, definition]) => { + endpoint.startsWith('/nested') + ? expect(definition.requestForwardingURL).toEqual('http://localhost:4001') + : expect(definition.requestForwardingURL).not.toEqual('http://localhost:4001') + }) + }) + it('flags a single route for forwarding when specified exact match in hostMap (/foo)', () => { + const strap = new Appstrap({ repository: 'test/configs/withRequestForwarding' }) + const endpoints = Array.from(strap.config.endpoints.entries()) + endpoints.forEach(([endpoint, definition]) => { + endpoint === '/foo' + ? expect(definition.requestForwardingURL).toEqual('http://localhost:4000') + : expect(definition.requestForwardingURL).not.toEqual('http://localhost:4000') + }) + }) +}) diff --git a/test/Responses.spec.js b/test/Responses.spec.js index 14a89ad..7e29803 100644 --- a/test/Responses.spec.js +++ b/test/Responses.spec.js @@ -1,21 +1,39 @@ -const supertest = require('supertest') const strapDefault = require('./helpers/strapDefault') +const supertest = require('supertest') const expect = require('expect') describe('Responses', () => { - it('does something', async () => { - const strap = strapDefault() - const response = await supertest(strap).get('/foo') - expect(response.text).toEqual(JSON.stringify({ hello: 'world', foo: 'bar' })) + describe('No route match', () => { + it('returns a 404 not found', async () => { + const strap = strapDefault() + const response = await supertest(strap).get('/unknownRoute') + expect(response.status).toEqual(404) + }) }) - it('Handles route parameters when specified in folder structure', async () => { - const strap = strapDefault() - const response = await supertest(strap).get('/nested/fizz') - console.log(response.text) + describe('Proxied Request', () => { + it('returns a 404 response when proxy is disabled and no local handler present') + it('proxies a single route to another server when specified /foo') + it('proxies a route folder to another server when specified /foo*') + it('proxies all routes to another server when specified *') + it('proxies a sub route to another server when specified /foo/bar') + it('proxies a sub folder to another server when specified /foo/bar*') + it('proxies a parameterized route to another server when specified /foo/:param') }) - it('Prioritizes defined routes and falls back to route parameter when provided in folder structure', async () => { - const strap = strapDefault() - const response = await supertest(strap).get('/nested/baz') - expect(response.text).toEqual('hello world nested/baz') + describe('Locally Handled Request', () => { + it('returns a 404 response when matched route is disabled') + it('returns a response when matched route has defined proxy route but proxy is disabled') + it('returns a response from a locally defined GQL handler') + it('returns a response from a handler defined when path is /foo', async () => { + const strap = strapDefault() + const response = await supertest(strap).get('/foo') + expect(response.status).toBe(200) + expect(response.body).toEqual({ hello: 'world', foo: 'bar' }) + }) + it('returns a response from a handler defined when path is /foo/[bar] (parameterized)', async () => { + const strap = strapDefault() + const response = await supertest(strap).get('/foo/test123') + expect(response.status).toBe(200) + expect(response.body).toEqual({ parameter: 'test123' }) + }) }) }) diff --git a/test/configs/default/fixtures/testOne.js b/test/configs/default/fixtures/testOne.js index 0cdc1f7..936c687 100644 --- a/test/configs/default/fixtures/testOne.js +++ b/test/configs/default/fixtures/testOne.js @@ -3,6 +3,7 @@ module.exports = { { path: '/foo', method: 'GET', + mode: 'foobar', payload: { bar: 'baz' } diff --git a/test/configs/default/initialState.js b/test/configs/default/initialState.js deleted file mode 100644 index 1af70b3..0000000 --- a/test/configs/default/initialState.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - foo: 'initial' -} diff --git a/test/configs/default/proxy.js b/test/configs/default/proxy.js deleted file mode 100644 index a2a5dd3..0000000 --- a/test/configs/default/proxy.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - '/gql/*': 'http://localhost:4000' -} diff --git a/test/configs/default/routes/foo.js b/test/configs/default/routes/foo.js index 70d8907..e31130b 100644 --- a/test/configs/default/routes/foo.js +++ b/test/configs/default/routes/foo.js @@ -4,3 +4,10 @@ module.exports.GET = () => { foo: 'bar' } } + +module.exports.POST = () => { + return { + post: 'foo', + zip: 'zow' + } +} diff --git a/test/configs/default/routes/foo/[bar].js b/test/configs/default/routes/foo/[bar].js new file mode 100644 index 0000000..6d75293 --- /dev/null +++ b/test/configs/default/routes/foo/[bar].js @@ -0,0 +1,5 @@ +module.exports.GET = ({ req }) => { + return { + parameter: req.params.bar + } +} diff --git a/test/configs/default/routes/nested/[routeParam].js b/test/configs/default/routes/nested/[routeParam].js deleted file mode 100644 index 0cc7c96..0000000 --- a/test/configs/default/routes/nested/[routeParam].js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports.GET = (req) => { - return req.params.routeParam -} diff --git a/test/configs/withGQL/gql/RunQueryOne.js b/test/configs/withGQL/gql/RunQueryOne.js new file mode 100644 index 0000000..cb2fac1 --- /dev/null +++ b/test/configs/withGQL/gql/RunQueryOne.js @@ -0,0 +1,6 @@ +module.exports = function handler () { + return { + gqlResponse: true, + hello: 'world' + } +} diff --git a/test/configs/withRequestForwarding/hostMap.js b/test/configs/withRequestForwarding/hostMap.js new file mode 100644 index 0000000..8aea5ca --- /dev/null +++ b/test/configs/withRequestForwarding/hostMap.js @@ -0,0 +1,4 @@ +module.exports = { + '/foo': 'http://localhost:4000', + '/nested*': 'http://localhost:4001' +} diff --git a/test/configs/withRequestForwarding/routes/bar.js b/test/configs/withRequestForwarding/routes/bar.js new file mode 100644 index 0000000..e31130b --- /dev/null +++ b/test/configs/withRequestForwarding/routes/bar.js @@ -0,0 +1,13 @@ +module.exports.GET = () => { + return { + hello: 'world', + foo: 'bar' + } +} + +module.exports.POST = () => { + return { + post: 'foo', + zip: 'zow' + } +} diff --git a/test/configs/withRequestForwarding/routes/foo.js b/test/configs/withRequestForwarding/routes/foo.js new file mode 100644 index 0000000..e31130b --- /dev/null +++ b/test/configs/withRequestForwarding/routes/foo.js @@ -0,0 +1,13 @@ +module.exports.GET = () => { + return { + hello: 'world', + foo: 'bar' + } +} + +module.exports.POST = () => { + return { + post: 'foo', + zip: 'zow' + } +} diff --git a/test/configs/withRequestForwarding/routes/nested/zip.js b/test/configs/withRequestForwarding/routes/nested/zip.js new file mode 100644 index 0000000..e31130b --- /dev/null +++ b/test/configs/withRequestForwarding/routes/nested/zip.js @@ -0,0 +1,13 @@ +module.exports.GET = () => { + return { + hello: 'world', + foo: 'bar' + } +} + +module.exports.POST = () => { + return { + post: 'foo', + zip: 'zow' + } +} diff --git a/test/configs/withRequestForwarding/routes/nested/zow.js b/test/configs/withRequestForwarding/routes/nested/zow.js new file mode 100644 index 0000000..e31130b --- /dev/null +++ b/test/configs/withRequestForwarding/routes/nested/zow.js @@ -0,0 +1,13 @@ +module.exports.GET = () => { + return { + hello: 'world', + foo: 'bar' + } +} + +module.exports.POST = () => { + return { + post: 'foo', + zip: 'zow' + } +} diff --git a/test/configs/withRequestForwardingAll/hostMap.js b/test/configs/withRequestForwardingAll/hostMap.js new file mode 100644 index 0000000..06c32a9 --- /dev/null +++ b/test/configs/withRequestForwardingAll/hostMap.js @@ -0,0 +1,4 @@ +module.exports = { + '/foo': 'http://localhost:4000', + '*': 'http://localhost:4001' +} diff --git a/test/configs/withRequestForwardingAll/routes/bar.js b/test/configs/withRequestForwardingAll/routes/bar.js new file mode 100644 index 0000000..e31130b --- /dev/null +++ b/test/configs/withRequestForwardingAll/routes/bar.js @@ -0,0 +1,13 @@ +module.exports.GET = () => { + return { + hello: 'world', + foo: 'bar' + } +} + +module.exports.POST = () => { + return { + post: 'foo', + zip: 'zow' + } +} diff --git a/test/configs/withRequestForwardingAll/routes/foo.js b/test/configs/withRequestForwardingAll/routes/foo.js new file mode 100644 index 0000000..e31130b --- /dev/null +++ b/test/configs/withRequestForwardingAll/routes/foo.js @@ -0,0 +1,13 @@ +module.exports.GET = () => { + return { + hello: 'world', + foo: 'bar' + } +} + +module.exports.POST = () => { + return { + post: 'foo', + zip: 'zow' + } +} diff --git a/test/configs/withRequestForwardingAll/routes/nested/zip.js b/test/configs/withRequestForwardingAll/routes/nested/zip.js new file mode 100644 index 0000000..e31130b --- /dev/null +++ b/test/configs/withRequestForwardingAll/routes/nested/zip.js @@ -0,0 +1,13 @@ +module.exports.GET = () => { + return { + hello: 'world', + foo: 'bar' + } +} + +module.exports.POST = () => { + return { + post: 'foo', + zip: 'zow' + } +} diff --git a/test/configs/withRequestForwardingAll/routes/nested/zow.js b/test/configs/withRequestForwardingAll/routes/nested/zow.js new file mode 100644 index 0000000..e31130b --- /dev/null +++ b/test/configs/withRequestForwardingAll/routes/nested/zow.js @@ -0,0 +1,13 @@ +module.exports.GET = () => { + return { + hello: 'world', + foo: 'bar' + } +} + +module.exports.POST = () => { + return { + post: 'foo', + zip: 'zow' + } +} diff --git a/test/helpers/strapDefault.js b/test/helpers/strapDefault.js index debb138..0222f25 100644 --- a/test/helpers/strapDefault.js +++ b/test/helpers/strapDefault.js @@ -2,5 +2,5 @@ const Appstrap = require('../../lib/Appstrap') const testLogger = require('./TestLogger') exports = module.exports = function strapDefault () { - return new Appstrap({ configDir: './test/configs/default', logger: testLogger }) + return new Appstrap({ repository: 'test/configs/default', logger: testLogger }) } diff --git a/wallaby.config.js b/wallaby.config.js index 6feafac..ef1fce2 100644 --- a/wallaby.config.js +++ b/wallaby.config.js @@ -2,9 +2,8 @@ module.exports = function () { return { files: [ { pattern: 'index.js' }, - { pattern: '.appstrap/**/*.js' }, { pattern: 'lib/**/*.js' }, - { pattern: 'test/_configs/assets/*.png', instrument: false }, + { pattern: 'test/configs/**/*', instrument: false }, { pattern: 'test/**/*.js' }, { pattern: 'test/**/*.spec.js', ignore: true } ],