diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 4095e3e4..8fe7ba8c 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -43,5 +43,12 @@ jobs: DEBUG: openapi-cop:* CI: true + - if: startsWith(matrix.node-version, '12') + name: Test Docker image + run: npm test:docker + env: + DEBUG: openapi-cop:* + CI: true + - name: Analyze dependencies run: npm run test:deps diff --git a/.mocharc.json b/.mocharc.json new file mode 100644 index 00000000..9c9607ca --- /dev/null +++ b/.mocharc.json @@ -0,0 +1,11 @@ +{ + "bail": true, + "timeout": 60000, + "exit": true, + "spec": [ + "build/test/*.test.js" + ], + "require": [ + "dotenv/config" + ] +} diff --git a/.nvmrc b/.nvmrc index f599e28b..48082f72 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -10 +12 diff --git a/docker/Dockerfile b/docker/Dockerfile index cc18665d..5d66c63d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,22 +1,23 @@ -FROM node:10 AS builder +FROM node:12 AS builder WORKDIR /app COPY . . -RUN npm ci --unsafe-perm +RUN npm ci --no-optional --unsafe-perm RUN npm run compile -FROM node:10 AS main +FROM node:12 AS main ENV TARGET "" ENV FILE "" ENV DEFAULT_FORBID_ADDITIONAL_PROPERTIES "" ENV SILENT "" ENV VERBOSE "" +ENV CLI_ARGUMENTS "" ENV NODE_ENV "production" WORKDIR /openapi-cop-docker COPY package.json . COPY package-lock.json . -RUN npm ci --omit=dev +RUN npm ci --omit=dev --no-optional COPY --from=builder /app/build . COPY docker/entrypoint.bash . diff --git a/docker/README.md b/docker/README.md index c14c174e..ab133eed 100644 --- a/docker/README.md +++ b/docker/README.md @@ -32,6 +32,7 @@ same [openapi-cop CLI flags](https://github.com/EXXETA/openapi-cop#cli-usage): are not allowed. - `SILENT`: When set, the proxy will forward response bodies unchanged and only set validation headers. - `VERBOSE`: When set, activates verbose output. +- `CLI_ARGUMENTS`: Sets all the arguments at once and overrides any other option given. - `NODE_ENV` (default: "production"): When set to "development", stack traces will also be logged. ### Example diff --git a/docker/entrypoint.bash b/docker/entrypoint.bash index 4250954b..97fcb9c4 100644 --- a/docker/entrypoint.bash +++ b/docker/entrypoint.bash @@ -22,4 +22,8 @@ if [ -n "$VERBOSE" ]; then cli_args="${cli_args}--verbose " fi +if [ -n "$CLI_ARGUMENTS" ]; then + cli_args="$CLI_ARGUMENTS" +fi + node src/cli --host 0.0.0.0 $cli_args diff --git a/mock-server/.eslintignore b/mock-server/.eslintignore new file mode 100644 index 00000000..fc0bf8e8 --- /dev/null +++ b/mock-server/.eslintignore @@ -0,0 +1,2 @@ +build +types diff --git a/mock-server/.eslintrc.json b/mock-server/.eslintrc.json new file mode 100755 index 00000000..7c76cf5f --- /dev/null +++ b/mock-server/.eslintrc.json @@ -0,0 +1,37 @@ +{ + "root": true, + "env": { + "node": true, + "jest": true + }, + "extends": [ + "eslint:recommended" + ], + "overrides": [ + { + "files": [ + "*.ts" + ], + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint" + ], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ], + "rules": { + "@typescript-eslint/no-explicit-any": 0, + "@typescript-eslint/no-var-requires": 0, + "@typescript-eslint/explicit-module-boundary-types": [ + "warn", + { + "allowArgumentsExplicitlyTypedAsAny": true + } + ] + } + } + ] +} + diff --git a/mock-server/.prettierrc.json b/mock-server/.prettierrc.json new file mode 100755 index 00000000..f3089ed3 --- /dev/null +++ b/mock-server/.prettierrc.json @@ -0,0 +1,5 @@ +{ + "trailingComma": "all", + "semi": true, + "singleQuote": true +} diff --git a/mock-server/package-lock.json b/mock-server/package-lock.json index efeb3c26..d72120c1 100644 --- a/mock-server/package-lock.json +++ b/mock-server/package-lock.json @@ -67,27 +67,27 @@ } }, "@babel/compat-data": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.0.tgz", - "integrity": "sha512-y5rqgTTPTmaF5e2nVhOxw+Ur9HDJLsWb6U/KpgUzRZEdPfE6VOubXBKLdbcUTijzRptednSBDQbYZBOSqJxpJw==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.5.tgz", + "integrity": "sha512-KZXo2t10+/jxmkhNXc7pZTqRvSOIvVv/+lJwHS+B2rErwOyjuVRh60yVpb7liQ1U5t7lLJ1bz+t8tSypUZdm0g==", "dev": true }, "@babel/core": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.0.tgz", - "integrity": "sha512-reM4+U7B9ss148rh2n1Qs9ASS+w94irYXga7c2jaQv9RVzpS7Mv1a9rnYYwuDa45G+DkORt9g6An2k/V4d9LbQ==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.5.tgz", + "integrity": "sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==", "dev": true, "requires": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.0", - "@babel/helper-compilation-targets": "^7.19.0", - "@babel/helper-module-transforms": "^7.19.0", - "@babel/helpers": "^7.19.0", - "@babel/parser": "^7.19.0", + "@babel/generator": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-module-transforms": "^7.20.2", + "@babel/helpers": "^7.20.5", + "@babel/parser": "^7.20.5", "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.0", - "@babel/types": "^7.19.0", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -96,9 +96,9 @@ }, "dependencies": { "@babel/parser": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.0.tgz", - "integrity": "sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz", + "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==", "dev": true }, "semver": { @@ -110,25 +110,25 @@ } }, "@babel/generator": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.0.tgz", - "integrity": "sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz", + "integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==", "dev": true, "requires": { - "@babel/types": "^7.19.0", + "@babel/types": "^7.20.5", "@jridgewell/gen-mapping": "^0.3.2", "jsesc": "^2.5.1" } }, "@babel/helper-compilation-targets": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.0.tgz", - "integrity": "sha512-Ai5bNWXIvwDvWM7njqsG3feMlL9hCVQsPYXodsZyLwshYkZVJt59Gftau4VrE8S9IT9asd2uSP1hG6wCNw+sXA==", + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz", + "integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==", "dev": true, "requires": { - "@babel/compat-data": "^7.19.0", + "@babel/compat-data": "^7.20.0", "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.20.2", + "browserslist": "^4.21.3", "semver": "^6.3.0" }, "dependencies": { @@ -175,34 +175,42 @@ } }, "@babel/helper-module-transforms": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz", - "integrity": "sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==", + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz", + "integrity": "sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==", "dev": true, "requires": { "@babel/helper-environment-visitor": "^7.18.9", "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.0", - "@babel/types": "^7.19.0" + "@babel/traverse": "^7.20.1", + "@babel/types": "^7.20.2" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true + } } }, "@babel/helper-plugin-utils": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", - "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==", + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", "dev": true }, "@babel/helper-simple-access": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", - "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.20.2" } }, "@babel/helper-split-export-declaration": { @@ -215,9 +223,9 @@ } }, "@babel/helper-string-parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", - "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", "dev": true }, "@babel/helper-validator-identifier": { @@ -233,14 +241,14 @@ "dev": true }, "@babel/helpers": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.19.0.tgz", - "integrity": "sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg==", + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.6.tgz", + "integrity": "sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==", "dev": true, "requires": { "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.0", - "@babel/types": "^7.19.0" + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5" } }, "@babel/highlight": { @@ -421,12 +429,12 @@ } }, "@babel/plugin-syntax-typescript": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz", - "integrity": "sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==", + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", + "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.19.0" } }, "@babel/template": { @@ -441,48 +449,56 @@ }, "dependencies": { "@babel/parser": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.0.tgz", - "integrity": "sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz", + "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==", "dev": true } } }, "@babel/traverse": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.0.tgz", - "integrity": "sha512-4pKpFRDh+utd2mbRC8JLnlsMUii3PMHjpL6a0SZ4NMZy7YFP9aXORxEhdMVOc9CpWtDF09IkciQLEhK7Ml7gRA==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz", + "integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==", "dev": true, "requires": { "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.0", + "@babel/generator": "^7.20.5", "@babel/helper-environment-visitor": "^7.18.9", "@babel/helper-function-name": "^7.19.0", "@babel/helper-hoist-variables": "^7.18.6", "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.19.0", - "@babel/types": "^7.19.0", + "@babel/parser": "^7.20.5", + "@babel/types": "^7.20.5", "debug": "^4.1.0", "globals": "^11.1.0" }, "dependencies": { "@babel/parser": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.0.tgz", - "integrity": "sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz", + "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==", "dev": true } } }, "@babel/types": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz", - "integrity": "sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz", + "integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==", "dev": true, "requires": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true + } } }, "@bcoe/v8-coverage": { @@ -811,13 +827,13 @@ "dev": true }, "@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", "dev": true, "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" } }, "@jsdevtools/ono": { @@ -873,9 +889,9 @@ "dev": true }, "@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", + "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", "dev": true, "requires": { "type-detect": "4.0.8" @@ -897,9 +913,9 @@ "dev": true }, "@types/babel__core": { - "version": "7.1.19", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", - "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", + "version": "7.1.20", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.20.tgz", + "integrity": "sha512-PVb6Bg2QuscZ30FvOU7z4guG6c926D9YRvOxEaelzndpMsvP+YM74Q/dAFASpg2l6+XLalxSGxcq/lrgYWZtyQ==", "dev": true, "requires": { "@babel/parser": "^7.1.0", @@ -929,9 +945,9 @@ } }, "@types/babel__traverse": { - "version": "7.18.1", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.1.tgz", - "integrity": "sha512-FSdLaZh2UxaMuLp9lixWaHq/golWTRWOnRsAXzDTDSDOQLuZb1nsdCt6pJSPWSEQt2eFZ2YVk3oYhn+1kLMeMA==", + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", + "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", "dev": true, "requires": { "@babel/types": "^7.3.0" @@ -1106,6 +1122,12 @@ "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", "dev": true }, + "@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true + }, "@types/serve-static": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", @@ -1168,18 +1190,18 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz", - "integrity": "sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.41.0.tgz", + "integrity": "sha512-DXUS22Y57/LAFSg3x7Vi6RNAuLpTXwxB9S2nIA7msBb/Zt8p7XqMwdpdc1IU7CkOQUPgAqR5fWvxuKCbneKGmA==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "4.33.0", - "@typescript-eslint/scope-manager": "4.33.0", - "debug": "^4.3.1", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", - "regexpp": "^3.1.0", - "semver": "^7.3.5", + "@typescript-eslint/scope-manager": "5.41.0", + "@typescript-eslint/type-utils": "5.41.0", + "@typescript-eslint/utils": "5.41.0", + "debug": "^4.3.4", + "ignore": "^5.2.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", "tsutils": "^3.21.0" } }, @@ -1195,95 +1217,174 @@ "@typescript-eslint/typescript-estree": "4.33.0", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0" + }, + "dependencies": { + "@typescript-eslint/scope-manager": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz", + "integrity": "sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.33.0", + "@typescript-eslint/visitor-keys": "4.33.0" + } + }, + "@typescript-eslint/types": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.33.0.tgz", + "integrity": "sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz", + "integrity": "sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.33.0", + "@typescript-eslint/visitor-keys": "4.33.0", + "debug": "^4.3.1", + "globby": "^11.0.3", + "is-glob": "^4.0.1", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz", + "integrity": "sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.33.0", + "eslint-visitor-keys": "^2.0.0" + } + } } }, "@typescript-eslint/parser": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.33.0.tgz", - "integrity": "sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.41.0.tgz", + "integrity": "sha512-HQVfix4+RL5YRWZboMD1pUfFN8MpRH4laziWkkAzyO1fvNOY/uinZcvo3QiFJVS/siNHupV8E5+xSwQZrl6PZA==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "4.33.0", - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/typescript-estree": "4.33.0", - "debug": "^4.3.1" + "@typescript-eslint/scope-manager": "5.41.0", + "@typescript-eslint/types": "5.41.0", + "@typescript-eslint/typescript-estree": "5.41.0", + "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz", - "integrity": "sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.41.0.tgz", + "integrity": "sha512-xOxPJCnuktUkY2xoEZBKXO5DBCugFzjrVndKdUnyQr3+9aDWZReKq9MhaoVnbL+maVwWJu/N0SEtrtEUNb62QQ==", "dev": true, "requires": { - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/visitor-keys": "4.33.0" + "@typescript-eslint/types": "5.41.0", + "@typescript-eslint/visitor-keys": "5.41.0" + } + }, + "@typescript-eslint/type-utils": { + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.41.0.tgz", + "integrity": "sha512-L30HNvIG6A1Q0R58e4hu4h+fZqaO909UcnnPbwKiN6Rc3BUEx6ez2wgN7aC0cBfcAjZfwkzE+E2PQQ9nEuoqfA==", + "dev": true, + "requires": { + "@typescript-eslint/typescript-estree": "5.41.0", + "@typescript-eslint/utils": "5.41.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" } }, "@typescript-eslint/types": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.33.0.tgz", - "integrity": "sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.41.0.tgz", + "integrity": "sha512-5BejraMXMC+2UjefDvrH0Fo/eLwZRV6859SXRg+FgbhA0R0l6lDqDGAQYhKbXhPN2ofk2kY5sgGyLNL907UXpA==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz", - "integrity": "sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.41.0.tgz", + "integrity": "sha512-SlzFYRwFSvswzDSQ/zPkIWcHv8O5y42YUskko9c4ki+fV6HATsTODUPbRbcGDFYP86gaJL5xohUEytvyNNcXWg==", "dev": true, "requires": { - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/visitor-keys": "4.33.0", - "debug": "^4.3.1", - "globby": "^11.0.3", - "is-glob": "^4.0.1", - "semver": "^7.3.5", + "@typescript-eslint/types": "5.41.0", + "@typescript-eslint/visitor-keys": "5.41.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", "tsutils": "^3.21.0" } }, + "@typescript-eslint/utils": { + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.41.0.tgz", + "integrity": "sha512-QlvfwaN9jaMga9EBazQ+5DDx/4sAdqDkcs05AsQHMaopluVCUyu1bTRUVKzXbgjDlrRAQrYVoi/sXJ9fmG+KLQ==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.41.0", + "@typescript-eslint/types": "5.41.0", + "@typescript-eslint/typescript-estree": "5.41.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0", + "semver": "^7.3.7" + } + }, "@typescript-eslint/visitor-keys": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz", - "integrity": "sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.41.0.tgz", + "integrity": "sha512-vilqeHj267v8uzzakbm13HkPMl7cbYpKVjgFWZPIOHIJHZtinvypUhJ5xBXfWYg4eFKqztbMMpOgFpT9Gfx4fw==", "dev": true, "requires": { - "@typescript-eslint/types": "4.33.0", - "eslint-visitor-keys": "^2.0.0" + "@typescript-eslint/types": "5.41.0", + "eslint-visitor-keys": "^3.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + } } }, "@vue/compiler-core": { - "version": "3.2.39", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.39.tgz", - "integrity": "sha512-mf/36OWXqWn0wsC40nwRRGheR/qoID+lZXbIuLnr4/AngM0ov8Xvv8GHunC0rKRIkh60bTqydlqTeBo49rlbqw==", + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.45.tgz", + "integrity": "sha512-rcMj7H+PYe5wBV3iYeUgbCglC+pbpN8hBLTJvRiK2eKQiWqu+fG9F+8sW99JdL4LQi7Re178UOxn09puSXvn4A==", "dev": true, "requires": { "@babel/parser": "^7.16.4", - "@vue/shared": "3.2.39", + "@vue/shared": "3.2.45", "estree-walker": "^2.0.2", "source-map": "^0.6.1" } }, "@vue/compiler-dom": { - "version": "3.2.39", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.39.tgz", - "integrity": "sha512-HMFI25Be1C8vLEEv1hgEO1dWwG9QQ8LTTPmCkblVJY/O3OvWx6r1+zsox5mKPMGvqYEZa6l8j+xgOfUspgo7hw==", + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.45.tgz", + "integrity": "sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==", "dev": true, "requires": { - "@vue/compiler-core": "3.2.39", - "@vue/shared": "3.2.39" + "@vue/compiler-core": "3.2.45", + "@vue/shared": "3.2.45" } }, "@vue/compiler-sfc": { - "version": "3.2.39", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.39.tgz", - "integrity": "sha512-fqAQgFs1/BxTUZkd0Vakn3teKUt//J3c420BgnYgEOoVdTwYpBTSXCMJ88GOBCylmUBbtquGPli9tVs7LzsWIA==", + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.45.tgz", + "integrity": "sha512-1jXDuWah1ggsnSAOGsec8cFjT/K6TMZ0sPL3o3d84Ft2AYZi2jWJgRMjw4iaK0rBfA89L5gw427H4n1RZQBu6Q==", "dev": true, "requires": { "@babel/parser": "^7.16.4", - "@vue/compiler-core": "3.2.39", - "@vue/compiler-dom": "3.2.39", - "@vue/compiler-ssr": "3.2.39", - "@vue/reactivity-transform": "3.2.39", - "@vue/shared": "3.2.39", + "@vue/compiler-core": "3.2.45", + "@vue/compiler-dom": "3.2.45", + "@vue/compiler-ssr": "3.2.45", + "@vue/reactivity-transform": "3.2.45", + "@vue/shared": "3.2.45", "estree-walker": "^2.0.2", "magic-string": "^0.25.7", "postcss": "^8.1.10", @@ -1291,32 +1392,32 @@ } }, "@vue/compiler-ssr": { - "version": "3.2.39", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.39.tgz", - "integrity": "sha512-EoGCJ6lincKOZGW+0Ky4WOKsSmqL7hp1ZYgen8M7u/mlvvEQUaO9tKKOy7K43M9U2aA3tPv0TuYYQFrEbK2eFQ==", + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.45.tgz", + "integrity": "sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==", "dev": true, "requires": { - "@vue/compiler-dom": "3.2.39", - "@vue/shared": "3.2.39" + "@vue/compiler-dom": "3.2.45", + "@vue/shared": "3.2.45" } }, "@vue/reactivity-transform": { - "version": "3.2.39", - "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.39.tgz", - "integrity": "sha512-HGuWu864zStiWs9wBC6JYOP1E00UjMdDWIG5W+FpUx28hV3uz9ODOKVNm/vdOy/Pvzg8+OcANxAVC85WFBbl3A==", + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.45.tgz", + "integrity": "sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ==", "dev": true, "requires": { "@babel/parser": "^7.16.4", - "@vue/compiler-core": "3.2.39", - "@vue/shared": "3.2.39", + "@vue/compiler-core": "3.2.45", + "@vue/shared": "3.2.45", "estree-walker": "^2.0.2", "magic-string": "^0.25.7" } }, "@vue/shared": { - "version": "3.2.39", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.39.tgz", - "integrity": "sha512-D3dl2ZB9qE6mTuWPk9RlhDeP1dgNRUKC3NJxji74A4yL8M2MwlhLKUC/49WHjrNzSPug58fWx/yFbaTzGAQSBw==", + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.45.tgz", + "integrity": "sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==", "dev": true }, "abab": { @@ -1642,15 +1743,15 @@ "dev": true }, "browserslist": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz", - "integrity": "sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==", + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001370", - "electron-to-chromium": "^1.4.202", + "caniuse-lite": "^1.0.30001400", + "electron-to-chromium": "^1.4.251", "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.5" + "update-browserslist-db": "^1.0.9" } }, "bs-logger": { @@ -1676,12 +1777,6 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha512-wxXCdllwGhI2kCC0MnvTGYTMvnVZTvqgypkiTI8Pa5tcz2i6VqsqwYGgqwXji+4RgCzms6EajE4IxiUH6HH8nQ==", - "dev": true - }, "bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -1733,9 +1828,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001393", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001393.tgz", - "integrity": "sha512-N/od11RX+Gsk+1qY/jbPa0R6zJupEa0lxeBG598EbrtblxVCTJsQwbRBm6+V+rxpc5lHKdsXb9RY83cZIPLseA==", + "version": "1.0.30001439", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001439.tgz", + "integrity": "sha512-1MgUzEkoMO6gKfXflStpYgZDlFM7M/ck/bgfVCACO5vnAf0fXoNVHdWtqGU+MYca+4bL9Z5bpOVmR33cWW9G2A==", "dev": true }, "chalk": { @@ -1774,6 +1869,12 @@ "readdirp": "~3.6.0" } }, + "ci-info": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.0.tgz", + "integrity": "sha512-2CpRNYmImPx+RXKLq6jko/L07phmS9I02TyqkcNU20GCF/GgaWvc58hPtjxDX8lPpkdwc9sNh72V9k00S7ezog==", + "dev": true + }, "cjs-module-lexer": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", @@ -1872,13 +1973,10 @@ "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" }, "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true }, "cookie": { "version": "0.4.0", @@ -1891,9 +1989,9 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, "cosmiconfig": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", - "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", "dev": true, "requires": { "@types/parse-json": "^4.0.0", @@ -1963,9 +2061,9 @@ "dev": true }, "decamelize-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", - "integrity": "sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", "dev": true, "requires": { "decamelize": "^1.1.0", @@ -1981,9 +2079,9 @@ } }, "decimal.js": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.0.tgz", - "integrity": "sha512-Nv6ENEzyPQ6AItkGwLE2PGKinZZ9g59vSh2BeH6NqPu0OTKZ5ruJsVqh/orbAnqXc9pBbgXAIrc2EyaCj8NpGg==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", "dev": true }, "dedent": { @@ -2063,12 +2161,6 @@ "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, "diff-sequences": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", @@ -2116,9 +2208,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "electron-to-chromium": { - "version": "1.4.244", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.244.tgz", - "integrity": "sha512-E21saXLt2eTDaTxgUtiJtBUqanF9A32wZasAwDZ8gvrqXoxrBrbwtDCx7c/PQTLp81wj4X0OLDeoGQg7eMo3+w==", + "version": "1.4.284", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", + "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==", "dev": true }, "emittery": { @@ -2677,18 +2769,18 @@ "dev": true }, "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", + "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==", "dev": true, "requires": { "reusify": "^1.0.4" } }, "fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "dev": true, "requires": { "bser": "2.1.1" @@ -2956,6 +3048,77 @@ "prettier": "^2.1.2", "rimraf": "^3.0.2", "write-file-atomic": "^3.0.3" + }, + "dependencies": { + "@typescript-eslint/eslint-plugin": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz", + "integrity": "sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "4.33.0", + "@typescript-eslint/scope-manager": "4.33.0", + "debug": "^4.3.1", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.1.8", + "regexpp": "^3.1.0", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/parser": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.33.0.tgz", + "integrity": "sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "4.33.0", + "@typescript-eslint/types": "4.33.0", + "@typescript-eslint/typescript-estree": "4.33.0", + "debug": "^4.3.1" + } + }, + "@typescript-eslint/scope-manager": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz", + "integrity": "sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.33.0", + "@typescript-eslint/visitor-keys": "4.33.0" + } + }, + "@typescript-eslint/types": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.33.0.tgz", + "integrity": "sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz", + "integrity": "sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.33.0", + "@typescript-eslint/visitor-keys": "4.33.0", + "debug": "^4.3.1", + "globby": "^11.0.3", + "is-glob": "^4.0.1", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz", + "integrity": "sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.33.0", + "eslint-visitor-keys": "^2.0.0" + } + } } }, "hard-rejection": { @@ -3170,9 +3333,9 @@ } }, "is-core-module": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", - "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", "dev": true, "requires": { "has": "^1.0.3" @@ -3244,9 +3407,9 @@ "dev": true }, "istanbul-lib-instrument": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", - "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "dev": true, "requires": { "@babel/core": "^7.12.3", @@ -3397,14 +3560,6 @@ "pretty-format": "^27.5.1", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "ci-info": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.2.tgz", - "integrity": "sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==", - "dev": true - } } }, "jest-diff": { @@ -3572,9 +3727,9 @@ } }, "jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true }, "jest-regex-util": { @@ -3723,14 +3878,6 @@ "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", "picomatch": "^2.2.3" - }, - "dependencies": { - "ci-info": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.2.tgz", - "integrity": "sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==", - "dev": true - } } }, "jest-validate": { @@ -3785,9 +3932,9 @@ } }, "joi": { - "version": "17.6.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.6.0.tgz", - "integrity": "sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw==", + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.7.0.tgz", + "integrity": "sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==", "dev": true, "requires": { "@hapi/hoek": "^9.0.0", @@ -3848,9 +3995,9 @@ }, "dependencies": { "acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", "dev": true } } @@ -3887,9 +4034,9 @@ "dev": true }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.2.tgz", + "integrity": "sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ==", "dev": true }, "kind-of": { @@ -4127,9 +4274,9 @@ } }, "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", "dev": true }, "minimist-options": { @@ -4151,15 +4298,6 @@ } } }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "requires": { - "minimist": "^1.2.6" - } - }, "mock-json-schema": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/mock-json-schema/-/mock-json-schema-1.1.1.tgz", @@ -4254,9 +4392,9 @@ "dev": true }, "node-releases": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", - "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.7.tgz", + "integrity": "sha512-EJ3rzxL9pTWPjk5arA0s0dgXpnyiAbJDE6wHT62g7VsgrgQgmmZ+Ru++M1BFofncWja+Pnn3rEr3fieRySAdKQ==", "dev": true }, "normalize-package-data": { @@ -4516,9 +4654,9 @@ } }, "postcss": { - "version": "8.4.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", - "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", + "version": "8.4.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.20.tgz", + "integrity": "sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==", "dev": true, "requires": { "nanoid": "^3.3.4", @@ -4533,9 +4671,9 @@ "dev": true }, "prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.1.tgz", + "integrity": "sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==", "dev": true }, "prettier-linter-helpers": { @@ -4611,12 +4749,13 @@ } }, "query-ast": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/query-ast/-/query-ast-1.0.4.tgz", - "integrity": "sha512-KFJFSvODCBjIH5HbHvITj9EEZKYUU6VX0T5CuB1ayvjUoUaZkKMi6eeby5Tf8DMukyZHlJQOE1+f3vevKUe6eg==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/query-ast/-/query-ast-1.0.5.tgz", + "integrity": "sha512-JK+1ma4YDuLjvKKcz9JZ70G+CM9qEOs/l1cZzstMMfwKUabTJ9sud5jvDGrUNuv03yKUgs82bLkHXJkDyhRmBw==", "dev": true, "requires": { - "invariant": "2.2.4" + "invariant": "2.2.4", + "lodash": "^4.17.21" } }, "querystringify": { @@ -4870,9 +5009,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sass": { - "version": "1.54.9", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.54.9.tgz", - "integrity": "sha512-xb1hjASzEH+0L0WI9oFjqhRi51t/gagWnxLiwUNMltA0Ab6jIDkAacgKiGYKM9Jhy109osM7woEEai6SXeJo5Q==", + "version": "1.56.2", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.56.2.tgz", + "integrity": "sha512-ciEJhnyCRwzlBCB+h5cCPM6ie/6f8HrhZMQOf5vlU60Y1bI1rx5Zb0vlDZvaycHsg/MqFfF1Eq2eokAa32iw8w==", "dev": true, "requires": { "chokidar": ">=3.0.0 <4.0.0", @@ -4890,12 +5029,13 @@ } }, "scss-parser": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/scss-parser/-/scss-parser-1.0.5.tgz", - "integrity": "sha512-RZOtvCmCnwkDo7kdcYBi807Y5EoTIxJ34AgEgJNDmOH1jl0/xG0FyYZFbH6Ga3Iwu7q8LSdxJ4C5UkzNXjQxKQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/scss-parser/-/scss-parser-1.0.6.tgz", + "integrity": "sha512-SH3TaoaJFzfAtqs3eG1j5IuHJkeEW5rKUPIjIN+ZorLAyJLHItQGnsgwHk76v25GtLtpT9IqfAcqK4vFWdiw+w==", "dev": true, "requires": { - "invariant": "2.2.4" + "invariant": "2.2.4", + "lodash": "4.17.21" } }, "semver": { @@ -5097,9 +5237,9 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, "stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, "requires": { "escape-string-regexp": "^2.0.0" @@ -5365,100 +5505,6 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, - "tslint": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", - "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "builtin-modules": "^1.1.1", - "chalk": "^2.3.0", - "commander": "^2.12.1", - "diff": "^4.0.1", - "glob": "^7.1.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.3", - "resolve": "^1.3.2", - "semver": "^5.3.0", - "tslib": "^1.13.0", - "tsutils": "^2.29.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "tsutils": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", - "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - } - } - }, "tsutils": { "version": "3.21.0", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", @@ -5508,9 +5554,9 @@ } }, "typescript": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.5.tgz", - "integrity": "sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ==", + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", "dev": true }, "universalify": { @@ -5525,9 +5571,9 @@ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" }, "update-browserslist-db": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.7.tgz", - "integrity": "sha512-iN/XYesmZ2RmmWAiI4Z5rq0YqSiv0brj9Ce9CfhNE4xIW2h+MFxcgkxIzZ+ShkFPUkjU3gQ+3oypadD3RAMtrg==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", "dev": true, "requires": { "escalade": "^3.1.1", @@ -5638,24 +5684,24 @@ } }, "follow-redirects": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", - "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", "dev": true }, "rxjs": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.6.tgz", - "integrity": "sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", + "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", "dev": true, "requires": { "tslib": "^2.1.0" } }, "tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", "dev": true } } diff --git a/mock-server/package.json b/mock-server/package.json index 12bf0394..08010bc9 100755 --- a/mock-server/package.json +++ b/mock-server/package.json @@ -54,13 +54,15 @@ "@types/swagger-parser": "4.0.3", "@types/wait-on": "5.3.1", "@types/which": "2.0.1", + "@typescript-eslint/eslint-plugin": "5.41.0", + "@typescript-eslint/parser": "5.41.0", "axios": "0.19.2", "depcheck": "1.4.3", + "eslint": "7.32.0", "gts": "3.1.1", "jest": "27.5.1", "ts-jest": "27.1.5", - "tslint": "6.1.3", - "typescript": "4.0.5", + "typescript": "4.8.4", "wait-on": "6.0.1" } } diff --git a/mock-server/src/app.ts b/mock-server/src/app.ts index 6996175f..2a4268ab 100755 --- a/mock-server/src/app.ts +++ b/mock-server/src/app.ts @@ -5,125 +5,143 @@ import 'source-map-support/register'; import * as errorHandler from 'errorhandler'; import * as express from 'express'; -import {Request, Response} from 'express'; +import { Request, Response } from 'express'; import * as refParser from 'json-schema-ref-parser'; import * as morgan from 'morgan'; -import OpenAPIBackend, {Request as OpenAPIRequest} from 'openapi-backend'; -import {OpenAPIV3} from 'openapi-types'; +import OpenAPIBackend, { Request as OpenAPIRequest } from 'openapi-backend'; +import { OpenAPIV3 } from 'openapi-types'; import * as path from 'path'; import * as http from 'http'; -import {readJsonOrYamlSync} from './util'; +import { readJsonOrYamlSync } from './util'; -export async function buildApp(apiDocFile: string): - Promise { - const apiDocRaw = readJsonOrYamlSync(apiDocFile); - const apiDoc = await refParser.dereference(apiDocFile, apiDocRaw, {}); +export async function buildApp( + apiDocFile: string, +): Promise { + const apiDocRaw = readJsonOrYamlSync(apiDocFile); + const apiDoc = await refParser.dereference(apiDocFile, apiDocRaw, {}); - const api = buildApi(apiDocFile, apiDoc); - await api.init(); - debug(`Loaded API definition for ${path.basename(apiDocFile)} ("${ - api.document.info.title}", version: ${api.document.info.version})`); + const api = buildApi(apiDocFile, apiDoc); + await api.init(); + debug( + `Loaded API definition for ${path.basename(apiDocFile)} ("${ + api.document.info.title + }", version: ${api.document.info.version})`, + ); - return buildExpressApp(api); + return buildExpressApp(api); } /** Configures and build the OpenAPIBackend express middleware. */ export function buildApi(apiDocFile: string, apiDoc: any): OpenAPIBackend { - return new OpenAPIBackend({ - definition: apiDoc as OpenAPIV3.Document, - strict: true, - validate: false, - ajvOpts: {unknownFormats: ['int32', 'int64', 'float', 'double']}, - handlers: { - validationFail: async (c, _req: Request, res: Response) => { - if (!c) return; - res.setHeader('openapi-cop-openapi-file', apiDocFile); - res.status(400).json({validation: c.validation}); - }, - notFound: async (c, _req: Request, res: Response) => { - if (!c) return; - // c.operationId is not defined, but c.operation is - if (c.operation) { - debug( - 'Cannot mock operation without an "operationId". Responding with 404.'); - } - res.setHeader('openapi-cop-openapi-file', apiDocFile); - res.status(404).json({error: 'not found'}); - }, - notImplemented: async (c, _req: Request, res: Response) => { - if (!c) return; - res.setHeader('openapi-cop-openapi-file', apiDocFile); - - if (!c.operation || !c.operation.operationId) { - debug('Cannot mock operation without an "operationId"'); - return res.status(404).json({error: 'not found'}); - } - const {status, mock} = - c.api.mockResponseForOperation(c.operation.operationId); - - return res.status(status).json(mock); - } - } - }); + return new OpenAPIBackend({ + definition: apiDoc as OpenAPIV3.Document, + strict: true, + validate: false, + ajvOpts: { unknownFormats: ['int32', 'int64', 'float', 'double'] }, + handlers: { + validationFail: async (c, _req: Request, res: Response) => { + if (!c) return; + res.setHeader('openapi-cop-openapi-file', apiDocFile); + res.status(400).json({ validation: c.validation }); + }, + notFound: async (c, _req: Request, res: Response) => { + if (!c) return; + // c.operationId is not defined, but c.operation is + if (c.operation) { + debug( + 'Cannot mock operation without an "operationId". Responding with 404.', + ); + } + res.setHeader('openapi-cop-openapi-file', apiDocFile); + res.status(404).json({ error: 'not found' }); + }, + notImplemented: async (c, _req: Request, res: Response) => { + if (!c) return; + res.setHeader('openapi-cop-openapi-file', apiDocFile); + + if (!c.operation || !c.operation.operationId) { + debug('Cannot mock operation without an "operationId"'); + return res.status(404).json({ error: 'not found' }); + } + const { status, mock } = c.api.mockResponseForOperation( + c.operation.operationId, + ); + + return res.status(status).json(mock); + }, + }, + }); } /** * Creates an express app and attaches a OpenAPIBackend middleware instance to * it. */ -export async function buildExpressApp(api: OpenAPIBackend): - Promise { - const app: express.Application = express(); - app.use(express.json()); - - if (debugMod.enabled('openapi-cop:mock')) { - // Logging of the form "openapi-cop:mock METHOD /url 123B (50ms)" - app.use(morgan((tokens, req, res) => { - return [ - chalk.bold(' openapi-cop:mock'), tokens.method(req, res), - tokens.url(req, res), tokens.status(req, res), - tokens.res(req, res, 'content-length') + 'B', - '(' + tokens['response-time'](req, res), 'ms)' - ].join(' '); - })); - } - - // Attach OpenAPI backend - app.use( - (req, res) => api.handleRequest(req as OpenAPIRequest, req, res)); - - app.use( - // tslint:disable-next-line:no-any - (err: any, _req: Request, res: Response) => { - console.error(err.stack); - res.status(500).send('Server error'); - }); - - // Display full error stack traces - if (process.env.NODE_ENV === 'development') { - app.use(errorHandler()); - } - - return app; +export async function buildExpressApp( + api: OpenAPIBackend, +): Promise { + const app: express.Application = express(); + app.use(express.json()); + + if (debugMod.enabled('openapi-cop:mock')) { + // Logging of the form "openapi-cop:mock METHOD /url 123B (50ms)" + app.use( + morgan((tokens, req, res) => { + return [ + chalk.bold(' openapi-cop:mock'), + tokens.method(req, res), + tokens.url(req, res), + tokens.status(req, res), + tokens.res(req, res, 'content-length') + 'B', + '(' + tokens['response-time'](req, res), + 'ms)', + ].join(' '); + }), + ); + } + + // Attach OpenAPI backend + app.use((req, res) => api.handleRequest(req as OpenAPIRequest, req, res)); + + app.use( + // tslint:disable-next-line:no-any + (err: any, _req: Request, res: Response) => { + console.error(err.stack); + res.status(500).send('Server error'); + }, + ); + + // Display full error stack traces + if (process.env.NODE_ENV === 'development') { + app.use(errorHandler()); + } + + return app; +} + +export type MockOptions = BaseMockOptions & ExtendedMockOptions; + +export interface BaseMockOptions { + port: string | number; +} + +export interface ExtendedMockOptions { + apiDocFile: string; } /** Builds the app and runs it on the given port. */ -export async function runApp( - port: string|number, apiDocFile: string): Promise { - try { - const app = await buildApp(apiDocFile); - let server: http.Server; - return new Promise(resolve => { - server = app.listen(port, () => { - resolve(); - }); - }) - .then(() => { - return server; - }); - } catch (e) { - console.error(`Failed to run mock server:\n${e.message}`); - return Promise.reject(); - } +export async function runApp({ port, apiDocFile }: MockOptions): Promise { + try { + const app = await buildApp(apiDocFile); + let server: http.Server; + return new Promise((resolve) => { + server = app.listen(port, () => { + resolve(server); + }); + }); + } catch (error) { + console.error('Failed to run mock server', error); + return Promise.reject(); + } } diff --git a/mock-server/src/cli.ts b/mock-server/src/cli.ts index e8746407..ecbfb4ef 100644 --- a/mock-server/src/cli.ts +++ b/mock-server/src/cli.ts @@ -1,27 +1,28 @@ #!/usr/bin/env node const debugMod = require('debug'); const debug = debugMod('openapi-cop:mock'); -debug.log = console.log.bind(console); // output to stdout +debug.log = console.log.bind(console); // output to stdout import chalk = require('chalk'); import * as chokidar from 'chokidar'; import * as program from 'commander'; import * as http from 'http'; import * as path from 'path'; -import {runApp} from './app'; +import { runApp } from './app'; // Rename for consistent display of package name in help output process.argv[1] = path.join(process.argv[1], 'openapi-cop-mock-server'); -program // - .option('-f, --file ', 'path to the OpenAPI document') - .option('-p, --port ', 'port number on which to run server', 8889) - .option( - '-w, --watch [watchLocation]', - 'watch for changes in a file/directory (falls back to the OpenAPI file) and restart server accordingly') - .option('-v, --verbose', 'show verbose output') - .version('0.0.1') - .parse(process.argv); +program // + .option('-f, --file ', 'path to the OpenAPI document') + .option('-p, --port ', 'port number on which to run server', 8889) + .option( + '-w, --watch [watchLocation]', + 'watch for changes in a file/directory (falls back to the OpenAPI file) and restart server accordingly', + ) + .option('-v, --verbose', 'show verbose output') + .version('0.0.1') + .parse(process.argv); let server: http.Server; @@ -43,15 +44,17 @@ if (!program.file) { async function start(restart = false) { const apiDocFile = path.resolve(program.file); try { - server = await runApp(program.port, apiDocFile); + server = await runApp({ port: program.port, apiDocFile }); } catch (e) { process.exit(); } if (!restart) { console.log( - chalk.blue(`openapi-cop mock server is running at http://localhost:${ - program.port}`)); + chalk.blue( + `openapi-cop mock server is running at http://localhost:${program.port}`, + ), + ); } else { console.log(chalk.hex('#eeeeee')('Restarted mock server')); } @@ -63,13 +66,16 @@ start(); // Watch for file changes and restart server if (program.watch) { const watchLocation = - typeof program.watch !== 'boolean' ? program.watch : program.file; - const watcher = chokidar.watch(watchLocation, {persistent: true}); + typeof program.watch !== 'boolean' ? program.watch : program.file; + const watcher = chokidar.watch(watchLocation, { persistent: true }); console.log(chalk.blue(`Watching changes in '${watchLocation}'`)); - watcher.on('change', path => { - console.log(chalk.hex('#eeeeee')( - `Detected change in file ${path}. Restarting server...`)); + watcher.on('change', (path) => { + console.log( + chalk.hex('#eeeeee')( + `Detected change in file ${path}. Restarting server...`, + ), + ); server.close(() => { start(true); }); diff --git a/mock-server/tsconfig.json b/mock-server/tsconfig.json index 2d926853..75d5cc72 100755 --- a/mock-server/tsconfig.json +++ b/mock-server/tsconfig.json @@ -3,18 +3,22 @@ "compilerOptions": { "baseUrl": ".", "paths": { - "*": ["types/*"] + "*": [ + "types/*" + ] }, "rootDir": ".", "outDir": "build", "strict": true, "moduleResolution": "node", "resolveJsonModule": true, - "typeRoots": ["./node_modules/@types"] + "typeRoots": [ + "./node_modules/@types" + ] }, "declaration": true, "include": [ "src/*.ts", - "test/*.ts", + "test/*.ts" ] } diff --git a/mock-server/tslint.json b/mock-server/tslint.json deleted file mode 100755 index 305de38c..00000000 --- a/mock-server/tslint.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "gts/tslint.json", - "linterOptions": { - "exclude": ["node_modules/**"] - }, - "rules": { - "no-any": false, - "quotemark": [true, "single", "avoid-escape", "avoid-template"] - } -} diff --git a/package-lock.json b/package-lock.json index f73597a0..13a56f69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,11 +15,6 @@ "js-yaml": "^4.1.0" }, "dependencies": { - "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" - }, "js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -76,9 +71,9 @@ "integrity": "sha512-11oi4zYorsgvg5yBarZplAqbpev5HkuVNPlZaPTknPDzAynq+lnJdXAmruGWP0s+dNYZS7bjM+xrTpJw7184Fg==" }, "validator": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", - "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==" + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.9.0.tgz", + "integrity": "sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA==" }, "z-schema": { "version": "4.2.4", @@ -103,9 +98,9 @@ } }, "@babel/helper-validator-identifier": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", "dev": true }, "@babel/highlight": { @@ -178,12 +173,12 @@ } }, "@babel/runtime-corejs3": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.19.0.tgz", - "integrity": "sha512-JyXXoCu1N8GLuKc2ii8y5RGma5FMpFeO2nAQIe0Yzrbq+rQnN+sFj47auLblR5ka6aHNGPDgv8G/iI2Grb0ldQ==", + "version": "7.20.13", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.20.13.tgz", + "integrity": "sha512-p39/6rmY9uvlzRiLZBIB3G9/EBr66LBMcYm7fIDeSBNdRjF2AGD3rFZucUyAgGHC2N+7DdLvVi33uTjSE44FIw==", "requires": { - "core-js-pure": "^3.20.2", - "regenerator-runtime": "^0.13.4" + "core-js-pure": "^3.25.1", + "regenerator-runtime": "^0.13.11" } }, "@cloudflare/json-schema-walker": { @@ -318,9 +313,9 @@ } }, "@sideway/formula": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz", - "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==" + "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", @@ -334,6 +329,13 @@ "requires": { "@types/connect": "*", "@types/node": "*" + }, + "dependencies": { + "@types/node": { + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==" + } } }, "@types/caseless": { @@ -354,6 +356,13 @@ "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", "requires": { "@types/node": "*" + }, + "dependencies": { + "@types/node": { + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==" + } } }, "@types/content-type": { @@ -383,13 +392,20 @@ } }, "@types/express-serve-static-core": { - "version": "4.17.30", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.30.tgz", - "integrity": "sha512-gstzbTWro2/nFed1WXtf+TtrpwxH7Ggs4RLYTLbeVgIkUQOI3WG/JKjgeOU1zXDvezllupjrf8OPIdvTbIaVOQ==", + "version": "4.17.33", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz", + "integrity": "sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==", "requires": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*" + }, + "dependencies": { + "@types/node": { + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==" + } } }, "@types/js-yaml": { @@ -401,13 +417,12 @@ "@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" }, "@types/lodash": { - "version": "4.14.184", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.184.tgz", - "integrity": "sha512-RoZphVtHbxPZizt4IcILciSWiC6dcn+eZ8oX9IWEYfDMcocdd42f7NPI6fQj+6zI8y4E0L7gu2pcZKLGTRaV9Q==", + "version": "4.14.191", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz", + "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==", "dev": true }, "@types/lodash.merge": { @@ -445,7 +460,8 @@ "@types/node": { "version": "10.17.60", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==" + "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", + "dev": true }, "@types/normalize-package-data": { "version": "2.4.1", @@ -498,9 +514,9 @@ } }, "@types/semver": { - "version": "7.3.12", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.12.tgz", - "integrity": "sha512-WwA1MW0++RfXmCr12xeYOOC5baSC9mSb0ZqCquFzKhcoF4TvHu5MKOuXsncgZcpVFhB1pXd5hZmM0ryAoCp12A==", + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", "dev": true }, "@types/serve-static": { @@ -510,6 +526,13 @@ "requires": { "@types/mime": "*", "@types/node": "*" + }, + "dependencies": { + "@types/node": { + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==" + } } }, "@types/swagger-parser": { @@ -897,9 +920,9 @@ } }, "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -946,9 +969,9 @@ "optional": true }, "apib2swagger": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/apib2swagger/-/apib2swagger-1.15.0.tgz", - "integrity": "sha512-zhKPRwXLPwdNrHzCMe+Y8/EmP88urqCrGvpe3Akmt+XoUj4rgeBI1PNxMabVoIxAJIiP9RkYkPD1sD0N68XDng==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/apib2swagger/-/apib2swagger-1.16.1.tgz", + "integrity": "sha512-yNq5Eq/654WJ1K43f6W2dPmTfTFqrKQzFLZdnxSHsI8V0xW84NNH5MeFCmp3Ioocl9E8wztzmqo6FWhV62gqMQ==", "optional": true, "requires": { "apib-include-directive": "^0.1.0", @@ -1035,9 +1058,9 @@ "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==" }, "aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" }, "axios": { "version": "0.19.2", @@ -1207,9 +1230,9 @@ } }, "call-me-maybe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", - "integrity": "sha512-wCyFsDQkKPwwF8BDwOiWNx/9K45L/hvggQiDbve+viMNMQnWhrlYIuBk09offfwCRtCO9P6XwUttufzU11WCVw==" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==" }, "callsites": { "version": "3.1.0", @@ -1430,9 +1453,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", @@ -1440,9 +1463,9 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, "cookiejar": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", - "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==" + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==" }, "core-js": { "version": "2.6.12", @@ -1450,9 +1473,9 @@ "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==" }, "core-js-pure": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.25.1.tgz", - "integrity": "sha512-7Fr74bliUDdeJCBMxkkIuQ4xfxn/SwrVg+HkJUAoNEXVqYLv55l6Af0dJ5Lq2YBUW9yKqSkLXaS5SYPK6MGa/A==" + "version": "3.27.2", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.27.2.tgz", + "integrity": "sha512-Cf2jqAbXgWH3VVzjyaaFkY1EBazxugUepGymDoeteyYr9ByX51kD2jdHZlsEF/xnJMyN3Prua7mQuzwMg6Zc9A==" }, "core-util-is": { "version": "1.0.3", @@ -1518,9 +1541,9 @@ "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==" }, "decamelize-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", - "integrity": "sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", "dev": true, "requires": { "decamelize": "^1.1.0", @@ -1578,9 +1601,9 @@ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, "dezalgo": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", - "integrity": "sha512-K7i4zNfT2kgQz3GylDw40ot9GAE47sFZ9EXHFSPP6zONLgH6kWXE0KWJchkbQJLBkRazq4APwZ4OwiFFlT95OQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", "requires": { "asap": "^2.0.0", "wrappy": "1" @@ -1618,6 +1641,12 @@ "esutils": "^2.0.2" } }, + "dotenv": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", + "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", + "dev": true + }, "drafter.js": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/drafter.js/-/drafter.js-2.6.7.tgz", @@ -1902,9 +1931,9 @@ "dev": true }, "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -2199,11 +2228,6 @@ "vary": "~1.1.2" }, "dependencies": { - "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" - }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -2291,9 +2315,9 @@ "dev": true }, "fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", @@ -2324,9 +2348,9 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", "dev": true, "requires": { "reusify": "^1.0.4" @@ -2451,9 +2475,9 @@ "dev": true }, "follow-redirects": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", - "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==" + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" }, "forever-agent": { "version": "0.6.1", @@ -2490,21 +2514,14 @@ } }, "formidable": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz", - "integrity": "sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz", + "integrity": "sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==", "requires": { - "dezalgo": "1.0.3", - "hexoid": "1.0.0", - "once": "1.4.0", - "qs": "6.9.3" - }, - "dependencies": { - "qs": { - "version": "6.9.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz", - "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==" - } + "dezalgo": "^1.0.4", + "hexoid": "^1.0.0", + "once": "^1.4.0", + "qs": "^6.11.0" } }, "forwarded": { @@ -2571,9 +2588,9 @@ "dev": true }, "get-intrinsic": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", - "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -2617,9 +2634,9 @@ } }, "globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -3008,9 +3025,9 @@ } }, "ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true }, "import-fresh": { @@ -3151,9 +3168,9 @@ } }, "is-core-module": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", - "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", "requires": { "has": "^1.0.3" } @@ -3301,9 +3318,9 @@ "optional": true }, "joi": { - "version": "17.6.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.6.0.tgz", - "integrity": "sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw==", + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.7.0.tgz", + "integrity": "sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==", "requires": { "@hapi/hoek": "^9.0.0", "@hapi/topo": "^5.0.0", @@ -3385,9 +3402,9 @@ "optional": true }, "json-schema-faker": { - "version": "0.5.0-rcv.44", - "resolved": "https://registry.npmjs.org/json-schema-faker/-/json-schema-faker-0.5.0-rcv.44.tgz", - "integrity": "sha512-MbDxYFsPXTVMawW1Y6zEU7QhfwsT+ZJ2d+LI8n57Y8+Xw1Cdx1hITgsFTLNOJ1lDMHZqWeXGGgMbc1hW0BGisg==", + "version": "0.5.0-rcv.46", + "resolved": "https://registry.npmjs.org/json-schema-faker/-/json-schema-faker-0.5.0-rcv.46.tgz", + "integrity": "sha512-Q+sGrxptZfezwm7M9W9VmHT9E8s5fWPCaRC4J2zUjb3CmDsxokiCBdHdS/psu91Tafc/ITv+GtIztGzUVT2zIg==", "requires": { "json-schema-ref-parser": "^6.1.0", "jsonpath-plus": "^5.1.0" @@ -3439,9 +3456,9 @@ "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "jsonpath": { @@ -3572,9 +3589,9 @@ } }, "loupe": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", - "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", "dev": true, "requires": { "get-func-name": "^2.0.0" @@ -3897,9 +3914,9 @@ } }, "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" }, "minimist-options": { "version": "4.1.0", @@ -4349,9 +4366,9 @@ "optional": true }, "object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" }, "on-finished": { "version": "2.4.1", @@ -4416,6 +4433,11 @@ "qs": "^6.9.3" }, "dependencies": { + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" + }, "openapi-types": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-7.2.3.tgz", @@ -4627,9 +4649,9 @@ "optional": true }, "prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz", + "integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==", "dev": true }, "prettier-linter-helpers": { @@ -4674,9 +4696,9 @@ "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" }, "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==" }, "q": { "version": "0.9.7", @@ -4912,9 +4934,9 @@ } }, "regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, "regexpp": { "version": "3.2.0", @@ -5048,9 +5070,9 @@ } }, "rxjs": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.6.tgz", - "integrity": "sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", + "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", "requires": { "tslib": "^2.1.0" } @@ -5453,14 +5475,6 @@ "semver": "^7.3.7" }, "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } - }, "form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -5479,11 +5493,6 @@ "yallist": "^4.0.0" } }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -5495,9 +5504,9 @@ } }, "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "requires": { "lru-cache": "^6.0.0" } @@ -5542,11 +5551,6 @@ "url": "~0.11.0" }, "dependencies": { - "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" - }, "is-plain-object": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", @@ -5663,9 +5667,9 @@ } }, "table": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", - "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", + "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", "dev": true, "requires": { "ajv": "^8.0.1", @@ -5676,9 +5680,9 @@ }, "dependencies": { "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -5782,9 +5786,9 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, "traverse": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", - "integrity": "sha512-kdf4JKs8lbARxWdp7RKdNzoJBhGUcIalSYibuGyHJbmk40pOysQ0+QPvlkCOICOivDWU2IJo2rkrxyTK2AH4fw==" + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.7.tgz", + "integrity": "sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg==" }, "trim-newlines": { "version": "3.0.1", @@ -5793,9 +5797,9 @@ "dev": true }, "tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" }, "tsutils": { "version": "3.21.0", @@ -5873,9 +5877,9 @@ } }, "typescript": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.5.tgz", - "integrity": "sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ==", + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", "dev": true }, "underscore": { diff --git a/package.json b/package.json index 8d27767e..7bdeb703 100755 --- a/package.json +++ b/package.json @@ -34,8 +34,9 @@ "dev-start": "npm run compile && node build/src/cli --port 8888 --target \"http://localhost:8889\" --file", "dev-start-along-mock": "(export DEBUG=openapi-cop:* || set DEBUG=openapi-cop:*) && npm run compile && node build/test/scripts/spawn", "pretest": "npm run compile", - "test": "mocha --bail --timeout 60000 --exit build/test/*.test.js", - "test:docker": "bash ./test/docker/run-docker-test.bash", + "pretest:docker": "npm run pretest && bash ./test/docker/build-docker-test.bash", + "test": "DOTENV_CONFIG_PATH=./test/config.env mocha", + "test:docker": "DOTENV_CONFIG_PATH=./test/docker/config.env mocha --timeout 300000", "test:deps": "npx depcheck --ignores=\"@types/*\"", "posttest": "npm run check", "package": "npx yarn pack --filename openapi-cop.tgz" @@ -80,11 +81,12 @@ "@typescript-eslint/parser": "5.41.0", "axios": "0.19.2", "chai": "4.3.6", + "dotenv": "^16.0.3", "eslint": "7.32.0", "find-process": "1.4.7", "gts": "3.1.1", "mocha": "9.2.2", "@exxeta/openapi-cop-mock-server": "1.0.1", - "typescript": "4.0.5" + "typescript": "4.8.4" } } diff --git a/src/app.ts b/src/app.ts index 909d688b..9f19da75 100755 --- a/src/app.ts +++ b/src/app.ts @@ -1,17 +1,18 @@ +import chalk = require('chalk'); + const debug = require('debug')('openapi-cop:proxy'); debug.log = console.log.bind(console); // output to stdout -import chalk = require('chalk'); import * as express from 'express'; -import {Request, Response} from 'express'; +import { Request, Response } from 'express'; import * as http from 'http'; -import {Operation} from 'openapi-backend'; +import { Operation } from 'openapi-backend'; import * as path from 'path'; import * as crypto from 'crypto'; import * as validUrl from 'valid-url'; import * as rp from 'request-promise-native'; import * as errors from 'request-promise-native/errors'; -import {ValidationResults} from '../types/validation'; +import { ValidationResults } from '../types/validation'; import { convertToOpenApiV3, copyHeaders, @@ -23,8 +24,8 @@ import { setValidationHeader, toOasRequest, } from './util'; -import {dereference, hasErrors, resolve, Validator} from './validation'; -import {URL} from "url"; +import { dereference, hasErrors, resolve, Validator } from './validation'; +import { URL } from 'url'; interface BuildOptions { targetUrl: string; @@ -40,10 +41,15 @@ const defaults: BuildOptions = { silent: false, }; -interface ProxyOptions { - port: number; +export type ProxyOptions = BaseProxyOptions & ExtendedProxyOptions; + +export interface BaseProxyOptions { + port: string | number; host: string; targetUrl: string; +} + +export interface ExtendedProxyOptions { apiDocPath: string; defaultForbidAdditionalProperties?: boolean; silent?: boolean; @@ -55,7 +61,7 @@ interface ProxyOptions { export async function buildApp( options: BuildOptions, ): Promise { - const {targetUrl, apiDocPath, defaultForbidAdditionalProperties, silent} = { + const { targetUrl, apiDocPath, defaultForbidAdditionalProperties, silent } = { ...defaults, ...options, }; @@ -69,11 +75,11 @@ export async function buildApp( console.log( chalk.blue( 'Validating against ' + - chalk.bold( - `${path.basename(apiDocPath)} ("${rawApiDoc.info.title}", version: ${ - rawApiDoc.info.version - })`, - ), + chalk.bold( + `${path.basename(apiDocPath)} ("${rawApiDoc.info.title}", version: ${ + rawApiDoc.info.version + })`, + ), ), ); @@ -85,12 +91,16 @@ export async function buildApp( ); } - const apiDoc = await prepareApiDocument(rawApiDoc, apiDocPath, defaultForbidAdditionalProperties); + const apiDoc = await prepareApiDocument( + rawApiDoc, + apiDocPath, + defaultForbidAdditionalProperties, + ); const oasValidator: Validator = new Validator(apiDoc); // Consume raw request body - app.use(express.raw({type: '*/*'})); + app.use(express.raw({ type: '*/*' })); // Global route handler app.all('*', (req: Request, res: Response) => { @@ -146,17 +156,18 @@ export async function buildApp( statusCode, ); - validationResults.responseHeaders = oasValidator.validateResponseHeaders( - serverResponse.headers, - operation as Operation, - statusCode, - ); + validationResults.responseHeaders = + oasValidator.validateResponseHeaders( + serverResponse.headers, + operation as Operation, + statusCode, + ); copyHeaders(serverResponse, res); setValidationHeader(res, validationResults); debug( `Validation results [${oasRequest.method} ${oasRequest.path}] ` + - JSON.stringify(validationResults, null, 2), + JSON.stringify(validationResults, null, 2), ); if (silent || !hasErrors(validationResults)) { @@ -187,7 +198,7 @@ export async function buildApp( setValidationHeader(res, validationResults); debug( `Validation results [${oasRequest.method} ${oasRequest.path}] ` + - JSON.stringify(validationResults, null, 2), + JSON.stringify(validationResults, null, 2), ); if (!reason.response && reason instanceof errors.RequestError) { @@ -232,13 +243,13 @@ export async function buildApp( * the server response untouched */ export async function runProxy({ - port, - host, - targetUrl, - apiDocPath, - defaultForbidAdditionalProperties = false, - silent = false, - }: ProxyOptions): Promise { + port, + host, + targetUrl, + apiDocPath, + defaultForbidAdditionalProperties = false, + silent = false, +}: ProxyOptions): Promise { try { const app = await buildApp({ targetUrl, @@ -247,8 +258,8 @@ export async function runProxy({ silent, }); let server: http.Server; - return new Promise(resolve => { - server = app.listen(port, host, () => { + return new Promise((resolve) => { + server = app.listen(+port, host, () => { resolve(server); }); }); @@ -258,18 +269,22 @@ export async function runProxy({ } } -async function prepareApiDocument(rawApiDoc: any, apiDocPath: string, defaultForbidAdditionalProperties: boolean | undefined): Promise { +async function prepareApiDocument( + rawApiDoc: any, + apiDocPath: string, + defaultForbidAdditionalProperties: boolean | undefined, +): Promise { const apiDocConv = await convertToOpenApiV3(rawApiDoc, apiDocPath).catch( - err => { + (err) => { throw new Error(`Could not convert document to OpenAPI v3: ${err}`); }, ); - const apiDocDeref = await dereference(apiDocConv, apiDocPath).catch(err => { + const apiDocDeref = await dereference(apiDocConv, apiDocPath).catch((err) => { throw new Error(`Reference resolution error: ${err}`); }); - let apiDoc = await resolve(apiDocDeref, apiDocPath).catch(err => { + let apiDoc = await resolve(apiDocDeref, apiDocPath).catch((err) => { throw new Error(`Reference resolution error: ${err}`); }); @@ -284,11 +299,28 @@ async function prepareApiDocument(rawApiDoc: any, apiDocPath: string, defaultFor } // Ensure every operation has a operationId (required by openapi-backend validator, but not by OpenAPI v3 schema). [Issue #4] - if (traversalPath.length === 3 - && traversalPath[0] === 'paths' - && ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'].includes(traversalPath[2])) { + if ( + traversalPath.length === 3 && + traversalPath[0] === 'paths' && + [ + 'get', + 'put', + 'post', + 'delete', + 'options', + 'head', + 'patch', + 'trace', + ].includes(traversalPath[2]) + ) { if (obj.operationId === undefined) { - obj.operationId = 'generatedOperationId_' + traversalPath[1].slice(1) + '_' + traversalPath[2] + '_' + crypto.randomBytes(3).toString('hex'); + obj.operationId = + 'generatedOperationId_' + + traversalPath[1].slice(1) + + '_' + + traversalPath[2] + + '_' + + crypto.randomBytes(3).toString('hex'); } } diff --git a/src/cli.ts b/src/cli.ts index 09049064..fedd33be 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -9,7 +9,7 @@ import * as chokidar from 'chokidar'; import * as program from 'commander'; import * as http from 'http'; import * as path from 'path'; -const npmPackage = require('../../package.json'); +import * as npmPackage from '../package.json'; import { runProxy } from './app'; diff --git a/src/util.ts b/src/util.ts index dfaf9650..602fefba 100755 --- a/src/util.ts +++ b/src/util.ts @@ -8,7 +8,6 @@ import * as yaml from 'js-yaml'; import { Request as OasRequest } from 'openapi-backend'; import * as path from 'path'; import * as qs from 'qs'; -import * as waitOn from 'wait-on'; import { ResponseParsingError } from '../types/errors'; import { ValidationResults } from '../types/validation'; import * as rp from 'request-promise-native'; @@ -65,7 +64,7 @@ export function readFileSync(filePath: string): any { } export async function fetchAndReadFile(uri: string): Promise { - return rp(uri).then(responseBody => parseJsonOrYaml(uri, responseBody)); + return rp(uri).then((responseBody) => parseJsonOrYaml(uri, responseBody)); } /** @@ -209,19 +208,6 @@ export function setSourceRequestHeader( res.setHeader('openapi-cop-source-request', JSON.stringify(oasRequest)); } -/** Closes the server and waits until the port is again free. */ -export async function closeServer(server: http.Server): Promise { - const port = (server.address() as any).port; - await new Promise((resolve, reject) => { - server.close(err => { - if (err) return reject(err); - resolve(); - }); - }); - - await waitOn({ resources: [`http://localhost:${port}`], reverse: true }); -} - /** * Recursively maps a nested object (JSON) given a mapping function. Maps in * depth-first order. If it finds an array it applies the mapping function @@ -231,7 +217,11 @@ export async function closeServer(server: http.Server): Promise { * @param fn Mapping function that returns the new value. * @param traversalPath internal parameter used to track the current traversal path */ -export function mapWalkObject(obj: any, fn: (currentObj: any, traversalPath: Array) => any, traversalPath: Array = []): any { +export function mapWalkObject( + obj: any, + fn: (currentObj: any, traversalPath: Array) => any, + traversalPath: Array = [], +): any { let objCopy = Object.assign({}, obj); for (const key in obj) { if (!Object.prototype.hasOwnProperty.call(obj, key)) continue; @@ -251,3 +241,26 @@ export function mapWalkObject(obj: any, fn: (currentObj: any, traversalPath: Arr objCopy = fn(objCopy, traversalPath); return objCopy; } + +export interface CliFlags { + flag: string; + value?: string | boolean; +} + +/** + * Receives a list of optional CLI flags and maps them to a list of strings. + * Flags are not included if their value is not a string or equal to `true` (boolean flag). + * + * @param flags + */ +export function buildCliArguments(flags: Array): Array { + return flags + .filter(({ value }) => typeof value === 'string' || value === true) + .flatMap(({ flag, value }) => { + const args = [flag]; + if (typeof value === 'string') { + args.push(value); + } + return args; + }); +} diff --git a/test/01.unit.test.ts b/test/01.unit.test.ts index fe1635b3..f096bbf4 100755 --- a/test/01.unit.test.ts +++ b/test/01.unit.test.ts @@ -4,7 +4,7 @@ import { readJsonOrYamlSync } from '../src/util'; import { validateDocument } from '../src/validation'; import { SCHEMAS_DIR } from './config'; -import { readDirFilesSync } from './util/io'; +import { getFileName, readDirFilesSync } from './util/io'; import { assertThrowsAsync } from './util/testing'; // tslint:disable: only-arrow-functions @@ -13,10 +13,7 @@ describe('Loading and validation of OpenAPI schemas', function() { const schemaDir = path.join(SCHEMAS_DIR, 'v3'); process.chdir(schemaDir); for (const filePath of readDirFilesSync(schemaDir)) { - const fileName = path - .normalize(path.basename(filePath)) - .replace(/\\/g, '/'); - it(`should be able to load valid openapi v3 schemas: ${fileName}`, async function() { + it(`should be able to load valid openapi v3 schemas: ${getFileName(filePath)}`, async function() { const apiDoc = readJsonOrYamlSync(filePath); await validateDocument(apiDoc); }); @@ -27,10 +24,7 @@ describe('Loading and validation of OpenAPI schemas', function() { const schemaDir = path.join(SCHEMAS_DIR, 'v2'); process.chdir(schemaDir); for (const filePath of readDirFilesSync(schemaDir)) { - const fileName = path - .normalize(path.basename(filePath)) - .replace(/\\/g, '/'); - it(`should be able to load valid openapi v2 schemas: ${fileName}`, async function() { + it(`should be able to load valid openapi v2 schemas: ${getFileName(filePath)}`, async function() { const apiDoc = readJsonOrYamlSync(filePath); await validateDocument(apiDoc); }); @@ -41,10 +35,7 @@ describe('Loading and validation of OpenAPI schemas', function() { const schemaDir = path.join(SCHEMAS_DIR, 'invalid'); process.chdir(schemaDir); for (const filePath of readDirFilesSync(schemaDir)) { - const fileName = path - .normalize(path.basename(filePath)) - .replace(/\\/g, '/'); - it(`should fail to load invalid openapi schemas: ${fileName}`, async function() { + it(`should fail to load invalid openapi schemas: ${getFileName(filePath)}`, async function() { const apiDoc = readJsonOrYamlSync(filePath); await assertThrowsAsync( async () => validateDocument(apiDoc), diff --git a/test/02.integration.test.ts b/test/02.integration.test.ts index ecb06b62..8bc9757c 100755 --- a/test/02.integration.test.ts +++ b/test/02.integration.test.ts @@ -1,75 +1,105 @@ // tslint:disable: only-arrow-functions -import {assert} from 'chai'; +import { assert } from 'chai'; import * as path from 'path'; -import {INVALID_TEST_REQUESTS, STRICTLY_INVALID_TEST_REQUESTS,} from './test-requests/invalid-requests'; -import {INVALID_RESPONSES, STRICTLY_INVALID_RESPONSES,} from './test-responses/invalid-responses'; -import {killProcesses} from './util/process'; -import {testRequestForEachFile, testRequestForEachFileWithServers,} from './util/testing'; -import {DEFAULT_OPENAPI_FILE, PROXY_PORT, SCHEMAS_DIR, TARGET_SERVER_PORT,} from './config'; -import {ChildProcess} from 'child_process'; -import {Readable} from 'stream'; -import axios, {AxiosRequestConfig} from 'axios'; -import {runProxy} from '../src/app'; -import {closeServer} from '../src/util'; -import {STRICTLY_VALID_TEST_REQUESTS, VALID_TEST_REQUESTS} from './test-requests/valid-requests'; -import {spawnProxyServer} from './util/server'; -import findProcess = require('find-process'); +import { + INVALID_TEST_REQUESTS, + STRICTLY_INVALID_TEST_REQUESTS, +} from './test-requests/invalid-requests'; +import { + INVALID_RESPONSES, + STRICTLY_INVALID_RESPONSES, +} from './test-responses/invalid-responses'; +import { + AssertionFunction, + testRequestsForApiDoc, + testRequestsForEachApiDoc, + testResponsesForEachApiDoc, +} from './util/testing'; +import { + DEFAULT_OPENAPI_FILE, + PROXY_PORT, + SCHEMAS_DIR, + SERVER_RUNTIME, + TARGET_SERVER_PORT, +} from './config'; +import axios, { AxiosRequestConfig } from 'axios'; +import { + STRICTLY_VALID_TEST_REQUESTS, + VALID_TEST_REQUESTS, +} from './test-requests/valid-requests'; +import { + DockerServerOrchestrator, + NodeHttpServerOrchestrator, + ServerOrchestrator, +} from './util/server-orchestrator'; +import { URL } from 'url'; +import { Server } from 'http'; +import { ChildProcess } from 'child_process'; -describe('integration.test.js', function () { +let serverOrchestrator: ServerOrchestrator; +if (SERVER_RUNTIME === 'docker') { + serverOrchestrator = new DockerServerOrchestrator( + new URL(`http://0.0.0.0:${PROXY_PORT}`), + new URL(`http://0.0.0.0:${TARGET_SERVER_PORT}`), + ); +} else { + serverOrchestrator = new NodeHttpServerOrchestrator( + new URL(`http://localhost:${PROXY_PORT}`), + new URL(`http://localhost:${TARGET_SERVER_PORT}`), + ); +} + +describe('integration.test.js', function() { this.slow(1000 * 15); // 15 seconds const contentType = 'application/json'; const clients = { proxy: axios.create({ baseURL: `http://localhost:${PROXY_PORT}`, - headers: {'content-type': contentType}, + headers: { 'content-type': contentType }, validateStatus: () => true, }), target: axios.create({ baseURL: `http://localhost:${TARGET_SERVER_PORT}`, - headers: {'content-type': contentType}, + headers: { 'content-type': contentType }, validateStatus: () => true, }), }; - before(async function () { - // Kill active processes listening on any of the given ports - const pid1 = await findProcess('port', PROXY_PORT); - const pid2 = await findProcess('port', TARGET_SERVER_PORT); - - await killProcesses([ - ...pid1.filter(p => p.cmd.indexOf('node') !== -1).map(p => p.pid), - ...pid2.filter(p => p.cmd.indexOf('node') !== -1).map(p => p.pid), - ]); + before(function() { + console.log('Killing existing processes...'); + return serverOrchestrator.kill(); }); - describe('OpenAPI v3', function () { + describe('OpenAPI v3', function() { const schemasDirV3 = path.join(SCHEMAS_DIR, 'v3'); - describe('Invariance tests', function () { - testRequestForEachFile({ + describe('Invariance tests', function() { + testRequestsForEachApiDoc({ testTitle: 'should return the same status and response bodies as the target server in silent mode', - dir: schemasDirV3, - testRequests: VALID_TEST_REQUESTS.v3, - client: clients, - silent: true, - callback(proxyRes, targetRes) { + apiDocDirectory: schemasDirV3, + testRequestMap: VALID_TEST_REQUESTS.v3, + clients, + serverOrchestrator, + proxyOptions: { silent: true }, + test: (proxyRes, targetRes) => { assert.deepStrictEqual(proxyRes.data, targetRes.data); assert.equal(proxyRes.status, targetRes.status); }, }); - testRequestForEachFile({ + testRequestsForEachApiDoc({ testTitle: 'should return the same headers as the target server except from the openapi-cop headers in silent mode', - dir: schemasDirV3, - testRequests: VALID_TEST_REQUESTS.v3, - client: clients, - silent: true, - callback(proxyRes, targetRes) { + apiDocDirectory: schemasDirV3, + testRequestMap: VALID_TEST_REQUESTS.v3, + clients, + serverOrchestrator, + proxyOptions: { silent: true }, + test: (proxyRes, targetRes) => { assert.property(proxyRes.headers, 'openapi-cop-validation-result'); assert.property(proxyRes.headers, 'openapi-cop-source-request'); delete proxyRes.headers['openapi-cop-validation-result']; @@ -87,53 +117,53 @@ describe('integration.test.js', function () { }); }); - it('should return the source request object inside the response header', async function () { - console.log('Starting proxy server...'); - const server = await runProxy({ - port: PROXY_PORT, - host: 'localhost', - targetUrl: `http://localhost:${TARGET_SERVER_PORT}`, + { + let headerTestRequest: AxiosRequestConfig; + testRequestsForApiDoc({ + testTitle: + 'should return the source request object inside the response header', apiDocPath: DEFAULT_OPENAPI_FILE, - defaultForbidAdditionalProperties: false, - }); - - const originalRequest: AxiosRequestConfig = { - method: 'GET', - url: '/pets', - data: JSON.stringify({search: 'something'}), - }; - - const proxyResponse = await clients.proxy.request(originalRequest); - - const openapiCopRequest = JSON.parse( - proxyResponse.headers['openapi-cop-source-request'], - ); + testRequests: [ + (headerTestRequest = { + method: 'GET', + url: '/pets', + data: JSON.stringify({ search: 'something' }), + }), + ], + clients, + serverOrchestrator, + proxyOptions: { silent: true }, + test: (proxyResponse) => { + const openapiCopRequest = JSON.parse( + proxyResponse.headers['openapi-cop-source-request'], + ); - assert.deepStrictEqual(openapiCopRequest, { - method: originalRequest.method, - path: originalRequest.url, - body: JSON.parse(originalRequest.data), - query: {}, - headers: { - accept: 'application/json, text/plain, */*', - connection: 'close', - 'content-length': '22', - 'content-type': 'application/json', - host: 'localhost:8888', - 'user-agent': 'axios/0.19.2', + assert.deepStrictEqual(openapiCopRequest, { + method: headerTestRequest.method, + path: headerTestRequest.url, + body: JSON.parse(headerTestRequest.data), + query: {}, + headers: { + accept: 'application/json, text/plain, */*', + connection: 'close', + 'content-length': '22', + 'content-type': 'application/json', + host: 'localhost:8888', + 'user-agent': 'axios/0.19.2', + }, + }); }, }); + } - await closeServer(server); - }); - - testRequestForEachFile({ + testRequestsForEachApiDoc({ testTitle: 'should respond with validation headers that are ValidationResult', - dir: schemasDirV3, - testRequests: VALID_TEST_REQUESTS.v3, - client: clients, - callback(proxyRes) { + apiDocDirectory: schemasDirV3, + testRequestMap: VALID_TEST_REQUESTS.v3, + clients, + serverOrchestrator, + test: (proxyRes) => { const validationResults = JSON.parse( proxyRes.headers['openapi-cop-validation-result'], ); @@ -169,7 +199,7 @@ describe('integration.test.js', function () { ]; validationResults[k]['errors'].forEach((err: any) => { assert( - Object.keys(err).every(k => validKeys.includes(k)), // all keys are valid keys + Object.keys(err).every((k) => validKeys.includes(k)), // all keys are valid keys 'validation error elements should conform with Ajv.ValidationError', ); }); @@ -178,196 +208,146 @@ describe('integration.test.js', function () { }, }); - it('should fail when target server is not available', async function () { + it('should fail when target server is not available', async function() { this.timeout(10000); - console.log('Starting proxy server...'); - const ps: ChildProcess = await spawnProxyServer( - PROXY_PORT, - TARGET_SERVER_PORT, - DEFAULT_OPENAPI_FILE, - ); - - console.log('Reading output'); + await serverOrchestrator.withProxy({ + proxyOptions: { apiDocPath: DEFAULT_OPENAPI_FILE }, + task: async () => { + const proxyResponse = await clients.proxy.request({ + method: 'GET', + url: '/pets', + }); + assert.equal(proxyResponse.status, 500); + assert.isTrue(proxyResponse.data.includes('ECONNREFUSED')); - let output = ''; - (ps.stdout as Readable).on('data', (data: Buffer) => { - output += data; + const validationResults = JSON.parse( + proxyResponse.headers['openapi-cop-validation-result'], + ); - if (data.toString().includes('Proxying client request')) { - setTimeout(() => { - ps.kill(); - }, 1500); - } + assert.hasAllKeys(validationResults, ['request']); + assert.doesNotHaveAnyKeys(validationResults, ['response']); + }, }); + }); - clients.proxy.request({method: 'GET', url: '/pets'}); + const assertDoesNotHaveValidationErrors: AssertionFunction = (proxyRes) => { + const validationResults = JSON.parse( + proxyRes.headers['openapi-cop-validation-result'], + ); + const reqValidationResults = validationResults['request']; + assert.isTrue(reqValidationResults['valid']); + assert.isNull(reqValidationResults['errors']); + }; - return new Promise((resolve) => { - ps.on('exit', (code: number) => { - assert.notEqual(code, -1); - assert.isTrue( - output.includes('Validation results'), - 'should yield validation results', - ); - assert.isTrue( - output.includes('Target server is unreachable'), - 'should show failure to communicate with the target server', - ); - resolve(); - }); - }); - }); + const assertHasRequestValidationError: AssertionFunction = ( + proxyResponse, + targetResponse, + fileName, + expectedError, + ) => { + if (!expectedError) { + throw new Error( + 'Bad test: "expectedError" property should be set for test requests that check for validation errors', + ); + } + const validationResults = JSON.parse( + proxyResponse.headers['openapi-cop-validation-result'], + ); + const reqValidationResults = validationResults['request']; + assert.isNotTrue(reqValidationResults['valid']); + assert.isNotNull(reqValidationResults['errors']); + assert.isArray(reqValidationResults['errors']); + assert.lengthOf(reqValidationResults['errors'], 1); + for (const k of Object.keys(expectedError)) { + assert.deepEqual(reqValidationResults['errors'][0][k], expectedError[k]); + } + }; - testRequestForEachFile({ - testTitle: 'should NOT return any validation errors for valid REQuests', - dir: schemasDirV3, - testRequests: VALID_TEST_REQUESTS.v3, - client: clients, - callback(proxyRes) { - const validationResults = JSON.parse( - proxyRes.headers['openapi-cop-validation-result'], + const assertHasResponseValidationErrors: AssertionFunction = ( + proxyRes, + targetRes, + fileName, + expectedError, + ) => { + if (!expectedError) { + throw new Error( + 'Bad test: "expectedError" property should be set for test requests that check for validation errors', ); - const reqValidationResults = validationResults['request']; - assert.isTrue(reqValidationResults['valid']); - assert.isNull(reqValidationResults['errors']); - }, + } + const validationResults = JSON.parse( + proxyRes.headers['openapi-cop-validation-result'], + ); + const resValidationResults = validationResults['response']; + assert.isNotTrue(resValidationResults['valid'], 'Response should be invalid'); + assert.isNotNull( + resValidationResults['errors'], + 'There should be at least one error present', + ); + assert.isArray(resValidationResults['errors']); + for (const k of Object.keys(expectedError)) { + assert.deepEqual(resValidationResults['errors'][0][k], expectedError[k]); + } + }; + + testRequestsForEachApiDoc({ + testTitle: 'should NOT return any validation errors for valid requests', + apiDocDirectory: schemasDirV3, + testRequestMap: VALID_TEST_REQUESTS.v3, + clients, + serverOrchestrator, + test: assertDoesNotHaveValidationErrors, }); - testRequestForEachFile({ + testRequestsForEachApiDoc({ testTitle: - 'should NOT return any validation errors for strictly valid REQuests', - dir: schemasDirV3, - testRequests: STRICTLY_VALID_TEST_REQUESTS.v3, - client: clients, - callback(proxyRes) { - const validationResults = JSON.parse( - proxyRes.headers['openapi-cop-validation-result'], - ); - const reqValidationResults = validationResults['request']; - assert.isTrue(reqValidationResults['valid']); - assert.isNull(reqValidationResults['errors']); - }, + 'should NOT return any validation errors for strictly valid requests', + apiDocDirectory: schemasDirV3, + testRequestMap: STRICTLY_VALID_TEST_REQUESTS.v3, + clients, + serverOrchestrator, + test: assertDoesNotHaveValidationErrors, }); - testRequestForEachFile({ - testTitle: 'should return correct validation errors for invalid REQuests', - dir: schemasDirV3, - testRequests: INVALID_TEST_REQUESTS.v3, - client: clients, - callback(proxyRes, _targetRes, fileName, requestObject) { - const validationResults = JSON.parse( - proxyRes.headers['openapi-cop-validation-result'], - ); - const reqValidationResults = validationResults['request']; - assert.isNotTrue(reqValidationResults['valid']); - assert.isNotNull(reqValidationResults['errors']); - assert.isArray(reqValidationResults['errors']); - assert.lengthOf(reqValidationResults['errors'], 1); - assert.isDefined( - requestObject.expectedError, - '"expectedError" property should be set for test requests that check for validation errors', - ); - for (const k of Object.keys(requestObject.expectedError)) { - assert.deepEqual( - reqValidationResults['errors'][0][k], - requestObject.expectedError[k], - ); - } - }, + testRequestsForEachApiDoc({ + testTitle: 'should return correct validation errors for invalid requests', + apiDocDirectory: schemasDirV3, + testRequestMap: INVALID_TEST_REQUESTS.v3, + clients, + serverOrchestrator, + test: assertHasRequestValidationError, }); - testRequestForEachFileWithServers({ + testResponsesForEachApiDoc({ testTitle: - 'should return correct validation errors for invalid RESponses', - dir: schemasDirV3, - testServers: INVALID_RESPONSES.v3, + 'should return correct validation errors for invalid responses', + apiDocDirectory: schemasDirV3, + testResponses: INVALID_RESPONSES.v3, client: clients, - callback(proxyRes, targetRes, fileName, expectedError) { - assert.isDefined( - expectedError, - '"expectedError" property should be set for test requests that check for validation errors', - ); - const validationResults = JSON.parse( - proxyRes.headers['openapi-cop-validation-result'], - ); - const resValidationResults = validationResults['response']; - assert.isNotTrue( - resValidationResults['valid'], - 'Response should be invalid', - ); - assert.isNotNull( - resValidationResults['errors'], - 'There should be at least one error present', - ); - assert.isArray(resValidationResults['errors']); - for (const k of Object.keys(expectedError)) { - assert.deepEqual( - resValidationResults['errors'][0][k], - expectedError[k], - ); - } - }, + serverOrchestrator, + test: assertHasResponseValidationErrors, }); - testRequestForEachFile({ + testRequestsForEachApiDoc({ testTitle: - 'should return correct validation errors for strictly invalid REQuests', - dir: schemasDirV3, - testRequests: STRICTLY_INVALID_TEST_REQUESTS.v3, - client: clients, - defaultForbidAdditionalProperties: true, - callback(proxyRes, _targetRes, fileName, requestObject) { - const validationResults = JSON.parse( - proxyRes.headers['openapi-cop-validation-result'], - ); - const reqValidationResults = validationResults['request']; - assert.isNotTrue(reqValidationResults['valid']); - assert.isNotNull(reqValidationResults['errors']); - assert.isArray(reqValidationResults['errors']); - assert.lengthOf(reqValidationResults['errors'], 1); - - for (const k of Object.keys(requestObject.expectedError)) { - assert.deepEqual( - reqValidationResults['errors'][0][k], - requestObject.expectedError[k], - ); - } - }, + 'should return correct validation errors for strictly invalid requests', + apiDocDirectory: schemasDirV3, + testRequestMap: STRICTLY_INVALID_TEST_REQUESTS.v3, + clients, + serverOrchestrator, + proxyOptions: { defaultForbidAdditionalProperties: true }, + test: assertHasRequestValidationError, }); - testRequestForEachFileWithServers({ + testResponsesForEachApiDoc({ testTitle: - 'should return correct validation errors for strictly invalid RESponses', - dir: schemasDirV3, - testServers: STRICTLY_INVALID_RESPONSES.v3, + 'should return correct validation errors for strictly invalid responses', + apiDocDirectory: schemasDirV3, + testResponses: STRICTLY_INVALID_RESPONSES.v3, client: clients, - defaultForbidAdditionalProperties: true, - callback(proxyRes, targetRes, fileName, expectedError) { - assert.isDefined( - expectedError, - '"expectedError" property should be set for test requests that check for validation errors', - ); - const validationResults = JSON.parse( - proxyRes.headers['openapi-cop-validation-result'], - ); - const resValidationResults = validationResults['response']; - assert.isNotTrue( - resValidationResults['valid'], - 'Response should be invalid', - ); - assert.isNotNull( - resValidationResults['errors'], - 'There should be at least one error present', - ); - assert.isArray(resValidationResults['errors']); - for (const k of Object.keys(expectedError)) { - assert.deepEqual( - resValidationResults['errors'][0][k], - expectedError[k], - ); - } - }, + serverOrchestrator, + proxyOptions: { defaultForbidAdditionalProperties: true }, + test: assertHasResponseValidationErrors, }); }); }); diff --git a/test/config.env b/test/config.env new file mode 100644 index 00000000..0e59687f --- /dev/null +++ b/test/config.env @@ -0,0 +1,3 @@ +PROXY_PORT=8888 +TARGET_SERVER_PORT=8889 +SERVER_RUNTIME=node diff --git a/test/config.ts b/test/config.ts index 97ba6ad0..b60c925d 100755 --- a/test/config.ts +++ b/test/config.ts @@ -1,11 +1,12 @@ import * as path from 'path'; +export const PROXY_PORT = Number(process.env.PROXY_PORT); +export const TARGET_SERVER_PORT = Number(process.env.TARGET_SERVER_PORT); +export const SERVER_RUNTIME: 'docker' | 'node' | string | undefined = process.env.SERVER_RUNTIME; + export const MOCK_SERVER_DIR = path.resolve(__dirname, '../../mock-server/'); export const SCHEMAS_DIR = path.resolve(__dirname, '../../test/schemas/'); -export const PROXY_PORT = 8888; -export const TARGET_SERVER_PORT = 8889; - export const DEFAULT_OPENAPI_FILE = path.join( SCHEMAS_DIR, 'v3/7-petstore.yaml', diff --git a/test/docker/build-docker-test.bash b/test/docker/build-docker-test.bash new file mode 100755 index 00000000..a7fac2bd --- /dev/null +++ b/test/docker/build-docker-test.bash @@ -0,0 +1,3 @@ +#!/bin/bash + +docker build . -f docker/Dockerfile -t lxlu/openapi-cop:test --label test diff --git a/test/docker/config.env b/test/docker/config.env new file mode 100644 index 00000000..8c8cd262 --- /dev/null +++ b/test/docker/config.env @@ -0,0 +1,3 @@ +PROXY_PORT=8888 +TARGET_SERVER_PORT=8889 +SERVER_RUNTIME=docker diff --git a/test/docker/entrypoint.bash b/test/docker/entrypoint.bash deleted file mode 100755 index a2288686..00000000 --- a/test/docker/entrypoint.bash +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash - -cd /data - -echo "Cleaning, then installing..." -(cd mock && npm run clean && npm install) -npm run clean && npm install - -echo "Running tests..." -DEBUG=openapi-cop:* npm test diff --git a/test/docker/kill-docker-test.bash b/test/docker/kill-docker-test.bash new file mode 100755 index 00000000..7a131671 --- /dev/null +++ b/test/docker/kill-docker-test.bash @@ -0,0 +1,3 @@ +#!/bin/bash + +docker kill $(docker ps -q --filter ancestor=lxlu/openapi-cop:test) diff --git a/test/docker/run-docker-test.bash b/test/docker/run-docker-test.bash index 85640ba3..967e3088 100755 --- a/test/docker/run-docker-test.bash +++ b/test/docker/run-docker-test.bash @@ -1,14 +1,13 @@ -#!/usr/bin/env bash +#!/bin/bash -read -r -p "Enter node version [10|12]: " userInput -NODE_VERSION=$(echo $userInput | sed 's/v//g') -echo "Using node version $NODE_VERSION" +cliArguments=$1 +schemasDir=$(readlink -f "$(dirname $0)/../schemas") -MSYS_NO_PATHCONV=1 -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +containerId=$(docker run -d --network="host" \ + -v "$schemasDir:/schemas" \ + --env "CLI_ARGUMENTS=$cliArguments" \ + --env "DEBUG=openapi-cop:*" \ + --env "CI=true" \ + lxlu/openapi-cop:test) -docker run --rm -it \ - -v "$DIR/../..":/data \ - -v "$DIR/entrypoint.bash":/entrypoint.bash \ - --user "$(id -u):$(id -g)" \ - node:$NODE_VERSION bash 'entrypoint.bash' +echo "Proxy container: ${containerId:0:8}" diff --git a/test/scripts/spawn.ts b/test/scripts/spawn.ts deleted file mode 100755 index 9f2677b0..00000000 --- a/test/scripts/spawn.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Utility script for spawning a openapi-cop instance and a mock server in parallel - * that are both based on a given OpenAPI document. - * - * node ./spawn [openapi-path] - * - * openapi-path (optional): path to the OpenAPI file on which both the proxy server - * and the mock server will be based on. If the file path is relative, it is relative to the project base. - * Defaults to a hard-coded file (see below). - * - * This script is called/aliased by `npm run dev-start-along-mock`. - */ - -import * as path from 'path'; - -import {PROXY_PORT, TARGET_SERVER_PORT} from '../config'; -import {spawnProxyWithMockServer} from '../util/server'; - -const apiDocRelativePath = process.argv[2] ? process.argv[2] : 'test/schemas/v3/3-parameters.yaml'; -const apiDocPath = path.resolve(__dirname, '../../..', apiDocRelativePath); - -spawnProxyWithMockServer(PROXY_PORT, TARGET_SERVER_PORT, apiDocPath, {stdio: 'inherit'}); diff --git a/test/test-requests/invalid-requests.ts b/test/test-requests/invalid-requests.ts index 58194074..e780b3e3 100755 --- a/test/test-requests/invalid-requests.ts +++ b/test/test-requests/invalid-requests.ts @@ -2,9 +2,9 @@ * NOTE: To enable tests for a specific OpenAPI file, add the file name * as a key of the object and add at least one TestRequestConfig to the array. */ -import {TestRequests} from '../../types/test-requests'; +import {TestRequestMap} from 'test-request-map'; -export const INVALID_TEST_REQUESTS: { [dir: string]: TestRequests } = { +export const INVALID_TEST_REQUESTS: { [dir: string]: TestRequestMap } = { v3: { '2-path.yaml': [ { @@ -38,7 +38,7 @@ export const INVALID_TEST_REQUESTS: { [dir: string]: TestRequests } = { }, }; -export const STRICTLY_INVALID_TEST_REQUESTS: { [dir: string]: TestRequests } = { +export const STRICTLY_INVALID_TEST_REQUESTS: { [dir: string]: TestRequestMap } = { v3: { '2-path.yaml': [ { diff --git a/test/test-requests/valid-requests.ts b/test/test-requests/valid-requests.ts index 0acd5993..3254b45b 100755 --- a/test/test-requests/valid-requests.ts +++ b/test/test-requests/valid-requests.ts @@ -2,9 +2,9 @@ * NOTE: To enable tests for a specific OpenAPI file, add the file name * as a key of the object and add at least one `TestRequestConfig` to the array. */ -import {TestRequests} from '../../types/test-requests'; +import {TestRequestMap} from 'test-request-map'; -export const VALID_TEST_REQUESTS: { [dir: string]: TestRequests } = { +export const VALID_TEST_REQUESTS: { [dir: string]: TestRequestMap } = { v3: { '2-path.yaml': [ { @@ -49,7 +49,7 @@ export const VALID_TEST_REQUESTS: { [dir: string]: TestRequests } = { }, }; -export const STRICTLY_VALID_TEST_REQUESTS: { [dir: string]: TestRequests } = { +export const STRICTLY_VALID_TEST_REQUESTS: { [dir: string]: TestRequestMap } = { v3: { '3-parameters.yaml': [ { diff --git a/test/test-responses/invalid-responses.ts b/test/test-responses/invalid-responses.ts index fb9add23..d030aafd 100755 --- a/test/test-responses/invalid-responses.ts +++ b/test/test-responses/invalid-responses.ts @@ -1,23 +1,6 @@ -import * as express from 'express'; -import {Request, Response} from 'express'; - -import {TARGET_SERVER_PORT} from '../config'; -import {TestResponses} from '../../types/test-requests'; - -/** - * Utility function to create a server that responds to only one given path/method. - */ -function responderTo( - method: string, - path: string, - routeHandler: express.RequestHandler, -) { - return async () => { - const app: express.Application = express(); - ((app as any)[method] as express.IRouterMatcher)(path, routeHandler); - return app.listen(TARGET_SERVER_PORT); - }; -} +import { Request, Response } from 'express'; +import { TestResponses } from 'test-request-map'; +import { responderTo } from '../util/server'; /** * For every OpenAPI file path, an HTTP server is provided along with valid @@ -35,18 +18,18 @@ export const INVALID_RESPONSES: { request: { method: 'POST', url: '/echo', - data: JSON.stringify({input: 'ECHO!'}), + data: JSON.stringify({ input: 'ECHO!' }), }, - runServer: responderTo( + serverFactory: responderTo( 'post', '/echo', (_req: Request, res: Response) => { - res.status(200).json({itseMe: 'Mario!'}); + res.status(200).json({ itseMe: 'Mario!' }); }, ), expectedError: { keyword: 'required', - params: {missingProperty: 'output'}, + params: { missingProperty: 'output' }, }, }, ], @@ -58,16 +41,16 @@ export const INVALID_RESPONSES: { request: { method: 'POST', url: '/echo', - data: JSON.stringify({input: 'ECHO!'}), + data: JSON.stringify({ input: 'ECHO!' }), }, - runServer: responderTo( + serverFactory: responderTo( 'post', '/echo', (_req: Request, res: Response) => { - res.status(400).json({error: {name: 666, message: 42}}); + res.status(400).json({ error: { name: 666, message: 42 } }); }, ), - expectedError: {keyword: 'type'}, + expectedError: { keyword: 'type' }, }, ], '5-external-refs.yaml': [ @@ -75,29 +58,29 @@ export const INVALID_RESPONSES: { request: { method: 'POST', url: '/echo', - data: JSON.stringify({input: 'ECHO!'}), + data: JSON.stringify({ input: 'ECHO!' }), }, - runServer: responderTo( + serverFactory: responderTo( 'post', '/echo', (_req: Request, res: Response) => { - res.status(400).json({error: {name: 666, message: 42}}); + res.status(400).json({ error: { name: 666, message: 42 } }); }, ), - expectedError: {keyword: 'type'}, + expectedError: { keyword: 'type' }, }, ], '6-examples.yaml': [ { - request: {method: 'GET', url: '/pets'}, - runServer: responderTo( + request: { method: 'GET', url: '/pets' }, + serverFactory: responderTo( 'get', '/pets', (_req: Request, res: Response) => { - res.status(200).json([{id: 12, name: 'Figaro'}, 'rofl', 'lol']); + res.status(200).json([{ id: 12, name: 'Figaro' }, 'rofl', 'lol']); }, ), - expectedError: {keyword: 'type', message: 'should be object'}, + expectedError: { keyword: 'type', message: 'should be object' }, }, ], '7-petstore.yaml': [ @@ -118,18 +101,18 @@ export const STRICTLY_INVALID_RESPONSES: { request: { method: 'POST', url: '/echo', - data: JSON.stringify({input: 'ECHO!'}), + data: JSON.stringify({ input: 'ECHO!' }), }, - runServer: responderTo( + serverFactory: responderTo( 'post', '/echo', (_req: Request, res: Response) => { res .status(200) - .json({output: 'The cake is a lie', forrest: 'Gump'}); + .json({ output: 'The cake is a lie', forrest: 'Gump' }); }, ), - expectedError: {keyword: 'additionalProperties'}, + expectedError: { keyword: 'additionalProperties' }, }, ], }, diff --git a/test/util/io.ts b/test/util/io.ts index af076b41..aadc739d 100755 --- a/test/util/io.ts +++ b/test/util/io.ts @@ -1,6 +1,10 @@ import * as fs from 'fs'; import * as path from 'path'; +export function getFileName(filePath: string): string { + return path.normalize(path.basename(filePath)).replace(/\\/g, '/'); +} + /** * Synchronously lists all top-level files in a directory. */ diff --git a/test/util/process.ts b/test/util/process.ts index 514f5fda..9ef73064 100755 --- a/test/util/process.ts +++ b/test/util/process.ts @@ -1,42 +1,36 @@ import { exec as _exec } from 'child_process'; import * as util from 'util'; -const exec = util.promisify(_exec); +import findProcess = require('find-process'); -/** Kills all processes that are listening on a given port. */ -export async function killOnPort(port: number | string): Promise { - if (!port) { - return Promise.reject(new Error('Invalid argument provided for port')); - } +const exec = util.promisify(_exec); - if (process.platform === 'win32') { - return exec( - `netstat -ano | grep :${port} | grep :LISTEN | awk '{print $5}' | uniq | xargs -n1 -I{} tskill {} /A /V`, - ); - } else { - return exec( - `lsof -i tcp:${port} | grep LISTEN | awk '{print $2}' | uniq | xargs -n1 kill -9`, - ); - } +export async function killNodeProcesses(ports: Array): Promise { + const processResults = await Promise.all( + ports.map((port) => findProcess('port', port)), + ); + return killProcesses( + processResults.flatMap((results) => + results.flatMap((process) => + process.cmd.includes('node') ? process.pid : [], + ), + ), + ); } /** Kills many processes by their PIDs. */ export function killProcesses(pids: number[]): Promise> { - return Promise.all(pids.map(pid => killProcess(pid))); + return Promise.all(pids.map((pid) => killProcess(pid))); } /** Kills a process given its PID. */ export async function killProcess(pid: number): Promise { const isWin = /^win/.test(process.platform); if (!pid) return Promise.resolve(); - try { - if (!isWin) { - await exec('kill -9 ' + pid); - } else { - await exec('taskkill /pid ' + pid + ' /f /t'); - } - } catch (ex) { - if (!/not found/.test(ex.stderr)) { - console.log('Failed to kill child process:', ex); - } + + const command = !isWin ? `kill -9 ${pid}` : `taskkill /pid ${pid} /f /t`; + const { stderr } = await exec(command); + + if (stderr && !/not found/.test(stderr)) { + console.log('Failed to kill child process:', stderr); } } diff --git a/test/util/server-orchestrator.ts b/test/util/server-orchestrator.ts new file mode 100644 index 00000000..8e7596ae --- /dev/null +++ b/test/util/server-orchestrator.ts @@ -0,0 +1,209 @@ +import { killNodeProcesses } from './process'; +import { + BaseProxyOptions, + ExtendedProxyOptions, + ProxyOptions, + runProxy as runProxyServer, +} from '../../src/app'; +import { + BaseMockOptions, + MockOptions, + runApp as runMockServer, +} from '@exxeta/openapi-cop-mock-server'; +import { Server } from 'http'; +import { URL } from 'url'; +import { ChildProcess } from 'child_process'; +import { + closeServer, + killDockerProxyServer, + spawnDockerProxyServer, +} from './server'; + +export enum ServerRole { + Proxy = 'proxy', + MockTarget = 'mock', +} + +export abstract class ServerOrchestrator { + protected servers: { + [ServerRole.Proxy]?: S; + [ServerRole.MockTarget]?: S; + } = {}; + + constructor(public readonly proxyUrl: URL, public readonly targetUrl: URL) {} + + get proxyOptions(): BaseProxyOptions { + return { + port: this.proxyUrl.port, + host: this.proxyUrl.hostname, + targetUrl: this.targetUrl.toString(), + }; + } + + get mockOptions(): BaseMockOptions { + return { + port: this.targetUrl.port, + }; + } + + get options(): { + [ServerRole.Proxy]: BaseProxyOptions; + [ServerRole.MockTarget]: BaseMockOptions; + } { + return { + proxy: this.proxyOptions, + mock: this.mockOptions, + }; + } + + public abstract start( + server: ServerRole, + options: ProxyOptions | MockOptions, + ): Promise; + + public abstract stop(server: ServerRole): Promise; + + public abstract kill(): Promise; + + async startAll(proxyOptions: ExtendedProxyOptions): Promise> { + console.log('Starting servers...'); + return Promise.all([ + this.start(ServerRole.Proxy, { + ...this.proxyOptions, + ...proxyOptions, + }), + this.start(ServerRole.MockTarget, { + ...this.mockOptions, + apiDocFile: proxyOptions?.apiDocPath, + }), + ]); + } + + async stopAll(): Promise { + console.log('Shutting down servers...'); + if (!this.servers) { + return; + } + await Promise.all([ + this.stop(ServerRole.Proxy), + this.stop(ServerRole.MockTarget), + ]); + } + + /** + * Starts the servers, performs a task and finally shuts the servers down. + * + * @param task A function to be executed after the servers are up. Afterwards the servers are shut down again. + * @param proxyOptions Temporal proxy options to set only for the execution of this task. + */ + public async withServers({ + task, + proxyOptions, + }: { + task: () => Promise; + proxyOptions: ExtendedProxyOptions; + }): Promise { + await this.startAll(proxyOptions); + console.log('Started both servers!'); + await task(); + await this.stopAll(); + } + + public async withProxy({ + task, + proxyOptions, + }: { + task: () => Promise; + proxyOptions: ExtendedProxyOptions; + }): Promise { + await this.start(ServerRole.Proxy, { + ...this.proxyOptions, + ...proxyOptions, + }); + await task(); + await this.stop(ServerRole.Proxy); + } +} + +/** + * This orchestrator starts Express servers directly on the main process. + */ +export class NodeHttpServerOrchestrator extends ServerOrchestrator { + async start( + serverRole: ServerRole, + options: ProxyOptions | MockOptions, + ): Promise { + switch (serverRole) { + case ServerRole.Proxy: { + this.servers[serverRole] = await runProxyServer( + options as ProxyOptions, + ); + break; + } + case ServerRole.MockTarget: { + this.servers[serverRole] = await runMockServer(options as MockOptions); + break; + } + } + + return this.servers[serverRole] as Server; + } + + async stop(serverRole: ServerRole): Promise { + const server = this.servers[serverRole]; + if (!server) { + return; + } + await closeServer(server, this.options[serverRole].port); + } + + kill(): Promise { + return killNodeProcesses([this.proxyOptions.port, this.mockOptions.port]); + } +} + +export class DockerServerOrchestrator extends ServerOrchestrator< + Server | ChildProcess +> { + private mockServerOrchestrator = new NodeHttpServerOrchestrator( + this.proxyUrl, + this.targetUrl, + ); + + async start( + serverRole: ServerRole, + options: ProxyOptions | MockOptions, + ): Promise { + if (serverRole === ServerRole.Proxy) { + console.log('Starting docker proxy server...'); + this.servers[serverRole] = await spawnDockerProxyServer( + options as ProxyOptions, + { detached: true, stdio: 'inherit' }, + true, + ); + console.log('Spawned docker proxy server!'); + return this.servers[serverRole] as ChildProcess; + } else { + this.servers[serverRole] = await this.mockServerOrchestrator.start( + ServerRole.MockTarget, + options, + ); + return this.servers[serverRole] as Server; + } + } + + async stop(serverRole: ServerRole): Promise { + if (serverRole === ServerRole.Proxy) { + await killDockerProxyServer(this.proxyOptions); + } else { + await this.mockServerOrchestrator.stop(ServerRole.MockTarget); + } + } + + async kill(): Promise { + await Promise.all([ + killNodeProcesses([this.mockOptions.port]), + killDockerProxyServer(this.proxyOptions), + ]); + } +} diff --git a/test/util/server.ts b/test/util/server.ts index 30eb43f0..da9d84b4 100644 --- a/test/util/server.ts +++ b/test/util/server.ts @@ -1,44 +1,40 @@ -import {runProxy as runProxyApp} from '../../src/app'; -import {MOCK_SERVER_DIR, PROXY_PORT, TARGET_SERVER_PORT} from '../config'; -import {runApp as runMockApp} from '@exxeta/openapi-cop-mock-server'; -import {closeServer} from '../../src/util'; -import {ChildProcess, spawn} from 'child_process'; +import { BaseProxyOptions, ProxyOptions } from '../../src/app'; +import { MOCK_SERVER_DIR, SCHEMAS_DIR, TARGET_SERVER_PORT } from '../config'; +import { BaseMockOptions, MockOptions } from '@exxeta/openapi-cop-mock-server'; +import { buildCliArguments, CliFlags } from '../../src/util'; +import { ChildProcess, execFile, spawn, SpawnOptions } from 'child_process'; import * as waitOn from 'wait-on'; import debug from 'debug'; +import * as path from 'path'; +import * as http from 'http'; +import { Server } from 'http'; +import * as express from 'express'; -/** - * Executes a function within the context of a proxy and a mock server. - * Resources are created before execution and cleaned up thereafter. - */ -export async function withServers({ - apiDocPath, - callback, - defaultForbidAdditionalProperties, - silent, - }: { - apiDocPath: string; - callback: () => Promise; - defaultForbidAdditionalProperties: boolean; - silent: boolean; -}): Promise { - console.log('Starting servers...'); - const servers = { - proxy: await runProxyApp({ - port: PROXY_PORT, - host: 'localhost', - targetUrl: `http://localhost:${TARGET_SERVER_PORT}`, - apiDocPath, - defaultForbidAdditionalProperties, - silent, - }), - mock: await runMockApp(TARGET_SERVER_PORT, apiDocPath), - }; +function toProxyCliOptions(proxyOptions: ProxyOptions, isVerbose = false) { + const args: Array = [ + { flag: '--host', value: proxyOptions.host }, + { flag: '--port', value: proxyOptions.port.toString() }, + { flag: '--target', value: proxyOptions.targetUrl }, + { flag: '--file', value: proxyOptions.apiDocPath }, + { + flag: '--default-forbid-additional-properties', + value: proxyOptions.defaultForbidAdditionalProperties, + }, + { flag: '--silent', value: proxyOptions.silent }, + { flag: '--verbose', value: isVerbose }, + ]; + + return buildCliArguments(args); +} - console.log('Running test...'); - await callback(); +function toMockCliOptions(mockOptions: MockOptions, isVerbose = false) { + const args: Array = [ + { flag: '--port', value: mockOptions.port.toString() }, + { flag: '--file', value: mockOptions.apiDocFile }, + { flag: '--verbose', value: isVerbose }, + ]; - console.log('Shutting down servers...'); - await Promise.all([closeServer(servers.proxy), closeServer(servers.mock)]); + return buildCliArguments(args); } /** @@ -48,28 +44,25 @@ export async function withServers({ * The `options` can be used to override the `child_process.spawn` options. */ export async function spawnProxyServer( - proxyPort: number, - targetPort: number, - apiDocFile: string, - options: any = {}, -): Promise { + proxyOptions: ProxyOptions, // NOTE: for debugging use the options {detached: true, stdio: 'inherit'} + spawnOptions: SpawnOptions = {}, + isVerbose = false, +): Promise { const cp = spawn( 'node', - [ - '../../src/cli.js', - '--port', - proxyPort.toString(), - '--target', - `http://localhost:${targetPort}`, - '--file', - apiDocFile, - '--verbose', - ], - {cwd: __dirname, stdio: 'pipe', detached: false, ...options}, + ['../../src/cli.js', ...toProxyCliOptions(proxyOptions, isVerbose)], + { + cwd: __dirname, + stdio: 'pipe', + detached: false, + ...spawnOptions, + }, ); - await waitOn({resources: [`tcp:localhost:${proxyPort}`]}); + await waitOn({ + resources: [`tcp:${proxyOptions.host}:${proxyOptions.port}`], + }); return cp; } @@ -81,30 +74,23 @@ export async function spawnProxyServer( * The `options` can be used to override the `child_process.spawn` options. */ export async function spawnMockServer( - port: number, - apiDocFile: string, - options: any = {}, -): Promise { + mockOptions: MockOptions, // NOTE: for debugging use the options {detached: true, stdio: 'inherit'} + spawnOptions: SpawnOptions = {}, + isVerbose = false, +): Promise { const cp = spawn( 'node', - [ - './build/src/cli.js', - '--port', - port.toString(), - '--file', - apiDocFile, - '--verbose', - ], + ['./build/src/cli.js', ...toMockCliOptions(mockOptions, isVerbose)], { cwd: MOCK_SERVER_DIR, stdio: debug.enabled('openapi-cop:mock') ? 'inherit' : 'ignore', detached: false, - ...options, + ...spawnOptions, }, ); - await waitOn({resources: [`tcp:localhost:${port}`]}); + await waitOn({ resources: [`tcp:${mockOptions.port}`] }); return cp; } @@ -113,13 +99,106 @@ export async function spawnMockServer( * Convenience function to spawn a proxy server along a mock server. */ export async function spawnProxyWithMockServer( - proxyPort: number, - targetPort: number, - apiDocFile: string, - options: any = {}, -): Promise<{ proxy: ChildProcess; target: ChildProcess; }> { + proxyOptions: ProxyOptions, + mockOptions: MockOptions, + spawnOptions: SpawnOptions = {}, +): Promise<{ proxy: ChildProcess; target: ChildProcess }> { return { - proxy: await spawnProxyServer(proxyPort, targetPort, apiDocFile, options), - target: await spawnMockServer(targetPort, apiDocFile, options), + proxy: await spawnProxyServer(proxyOptions, spawnOptions), + target: await spawnMockServer(mockOptions, spawnOptions), + }; +} + +export async function spawnDockerProxyServer( + proxyOptions: ProxyOptions, + // NOTE: for debugging use the options {detached: true, stdio: 'inherit'} + spawnOptions: SpawnOptions = {}, + isVerbose = false, +): Promise { + const dockerProxyOptions = { + ...proxyOptions, + apiDocPath: path.join( + '/schemas', + path.relative(SCHEMAS_DIR, proxyOptions.apiDocPath), + ), }; + + const cp = spawn( + 'test/docker/run-docker-test.bash', + [toProxyCliOptions(dockerProxyOptions, isVerbose).join(' ')], + { + cwd: process.env.PWD, + stdio: 'pipe', + detached: false, + ...spawnOptions, + }, + ); + + await waitOn({ + resources: [`tcp:${proxyOptions.host}:${proxyOptions.port}`], + timeout: 5000, + }); + + return cp; +} + +export async function killDockerProxyServer( + proxyOptions: BaseProxyOptions, +): Promise { + const cp = execFile('test/docker/kill-docker-test.bash', { + cwd: process.env.PWD, + }); + + await waitOn({ + resources: [`tcp:${proxyOptions.host}:${proxyOptions.port}`], + reverse: true, + }); + + return cp; +} + +/** + * Utility function to create a server that responds to only one given path/method. + */ +export function responderTo( + method: string, + path: string, + routeHandler: express.RequestHandler, +): (port: number | string) => http.Server { + return (port: number | string) => { + const app: express.Application = express(); + ((app as any)[method] as express.IRouterMatcher)(path, routeHandler); + return app.listen(port); + }; +} + +export async function withServer({ + serverFactory, + port, + task, +}: { + serverFactory: (port: number | string) => Server; + port: number | string, + task: () => Promise; +}): Promise { + const server = await serverFactory(port); + await task(); + await closeServer(server, port); +} + +/** Closes the server and waits until the port is again free. */ +export async function closeServer( + server: http.Server, + port: number | string, +): Promise { + if (server.address()) { + await new Promise((resolve, reject) => { + server.close((err) => { + if (err) return reject(err); + resolve(); + }); + }); + } + + await waitOn({ resources: [`tcp:${port}`], reverse: true }); } diff --git a/test/util/testing.ts b/test/util/testing.ts index da9133a8..dd027903 100755 --- a/test/util/testing.ts +++ b/test/util/testing.ts @@ -1,14 +1,11 @@ -import {TestRequestConfig, TestRequests, TestResponses} from '../../types/test-requests'; +import { TestRequest, TestRequestMap, TestResponses } from 'test-request-map'; import * as assert from 'assert'; -import {AxiosInstance, AxiosRequestConfig, AxiosResponse} from 'axios'; -import * as path from 'path'; - -import {PROXY_PORT, TARGET_SERVER_PORT} from '../config'; -import {runProxy as runProxyApp} from '../../src/app'; -import {closeServer} from '../../src/util'; -import {readDirFilesSync} from './io'; -import {withServers} from './server'; +import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; +import { ExtendedProxyOptions } from '../../src/app'; +import { getFileName, readDirFilesSync } from './io'; import * as chalk from 'chalk'; +import { ServerOrchestrator } from './server-orchestrator'; +import { withServer } from './server'; /** * Formats a request in a compact way, i.e. METHOD /url {...} @@ -26,8 +23,13 @@ export function formatRequest(req: AxiosRequestConfig): string { } } -export async function assertThrowsAsync(fn: () => Promise, regExp: RegExp): Promise { - let f = () => { return }; +export async function assertThrowsAsync( + fn: () => Promise, + regExp: RegExp, +): Promise { + let f = () => { + return; + }; try { await fn(); } catch (e) { @@ -39,98 +41,101 @@ export async function assertThrowsAsync(fn: () => Promise, regExp: RegExp) } } +export type AssertionFunction = ( + proxyResponse: AxiosResponse, + targetResponse: AxiosResponse, + fileName: string, + expectedError: any, +) => void; + /** * For each OpenAPI file in a given directory, it boots a proxy and a mock * server and runs the provided test requests. It then executes the callback * function that contains the test code. */ -export function testRequestForEachFile({ +export function testRequestsForEachApiDoc(options: { + testTitle: string; + apiDocDirectory: string; + testRequestMap: TestRequestMap; + clients: { proxy: AxiosInstance; target: AxiosInstance }; + serverOrchestrator: ServerOrchestrator; + proxyOptions?: Partial; + test: AssertionFunction; +}): void { + for (const apiDocPath of readDirFilesSync(options.apiDocDirectory)) { + testRequestsForApiDoc({ + ...options, + apiDocPath, + testRequests: options.testRequestMap[getFileName(apiDocPath)] ?? [], + }); + } +} + +export function testRequestsForApiDoc({ testTitle, - dir, + apiDocPath, testRequests, - client, - callback, - defaultForbidAdditionalProperties = false, - silent = false, + clients, + serverOrchestrator, + proxyOptions, + test, }: { testTitle: string; - dir: string; - testRequests: TestRequests; - client: { proxy: AxiosInstance; target: AxiosInstance }; - callback: ( - proxyRes: AxiosResponse, - targetRes: AxiosResponse, - fileName: string, - requestObject: TestRequestConfig, - ) => void; - defaultForbidAdditionalProperties?: boolean; - silent?: boolean; + apiDocPath: string; + testRequests: Array; + clients: { proxy: AxiosInstance; target: AxiosInstance }; + serverOrchestrator: ServerOrchestrator; + proxyOptions?: Partial; + test: AssertionFunction; }): void { - // tslint:disable:only-arrow-functions - for (const p of readDirFilesSync(dir)) { - const fileName = path.normalize(path.basename(p)).replace(/\\/g, '/'); - it(`${testTitle}: ${fileName}`, async function() { - // Skip if no test requests exist for the OpenAPI definition - if (!(fileName in testRequests)) { - console.log( - chalk.keyword('orange')( - `Skipping '${fileName}' due to missing test requests.`, - ), - ); - return; - } + const fileName = getFileName(apiDocPath); + it(`${testTitle}: ${fileName}`, async function () { + // Skip if no test requests exist for the OpenAPI definition + if (testRequests.length === 0) { + console.log( + chalk.keyword('orange')( + `Skipping '${fileName}' due to missing test requests.`, + ), + ); + return; + } - await withServers({ - apiDocPath: p, - defaultForbidAdditionalProperties, - silent, - async callback() { - // Perform all test requests on both servers yield responses - // to compare - for (const req of testRequests[fileName]) { - console.log(`Sending request ${formatRequest(req)}`); - const targetRes = await client.target(req); - const proxyRes = await client.proxy(req); - callback(proxyRes, targetRes, fileName, req); - } - }, - }); + await serverOrchestrator.withServers({ + proxyOptions: { ...proxyOptions, apiDocPath }, + task: async () => { + for (const request of testRequests) { + console.log(`Sending request ${formatRequest(request)}`); + const targetResponse = await clients.target(request); + const proxyResponse = await clients.proxy(request); + test(proxyResponse, targetResponse, fileName, request.expectedError); + } + }, }); - } + }); } -/** - * For each OpenAPI file in a given directory, it boots a proxy server, along - * with a test target server and runs the provided test requests. It then - * executes the callback function that contains the test code. - */ -export function testRequestForEachFileWithServers({ +export function testResponsesForEachApiDoc({ testTitle, - dir, - testServers, + apiDocDirectory, + testResponses, client, - callback, - defaultForbidAdditionalProperties = false, + serverOrchestrator, + proxyOptions, + test, }: { testTitle: string; - dir: string; - testServers: TestResponses; + apiDocDirectory: string; + testResponses: TestResponses; client: { proxy: AxiosInstance; target: AxiosInstance }; - defaultForbidAdditionalProperties?: boolean; - callback: ( - proxyRes: AxiosResponse, - targetRes: AxiosResponse, - fileName: string, - expectedError: any, - ) => void; + serverOrchestrator: ServerOrchestrator; + proxyOptions?: Partial; + test: AssertionFunction; }): void { - // tslint:disable:only-arrow-functions - for (const apiDocFile of readDirFilesSync(dir)) { - const fileName = path - .normalize(path.basename(apiDocFile)) - .replace(/\\/g, '/'); - it(`${testTitle}: ${fileName}`, async function() { - if (!(fileName in testServers)) { + for (const apiDocPath of readDirFilesSync(apiDocDirectory)) { + const fileName = getFileName(apiDocPath); + it(`${testTitle}: ${fileName}`, async function () { + const testData = testResponses[fileName]; + if (!testData?.length) { console.log( chalk.keyword('orange')( `Skipping '${fileName}' due to missing test responses.`, @@ -139,40 +144,23 @@ export function testRequestForEachFileWithServers({ return; } - if (testServers[fileName].length === 0) { - // When no tests are present in the array, this is interpreted as an - // intentional skip - return; - } - - console.log('Starting proxy server...'); - const proxyServer = await runProxyApp({ - port: PROXY_PORT, - host: 'localhost', - targetUrl: `http://localhost:${TARGET_SERVER_PORT}`, - apiDocPath: apiDocFile, - defaultForbidAdditionalProperties, + await serverOrchestrator.withProxy({ + proxyOptions: { ...proxyOptions, apiDocPath }, + task: async () => { + for (const { request, serverFactory, expectedError } of testData) { + await withServer({ + serverFactory, + port: serverOrchestrator.targetUrl.port, + task: async () => { + console.log(`Sending request ${formatRequest(request)}`); + const targetRes = await client.target(request); + const proxyRes = await client.proxy(request); + test(proxyRes, targetRes, apiDocPath, expectedError); + }, + }); + } + }, }); - - console.log('Running test...'); - for (const { request, runServer, expectedError } of testServers[ - fileName - ]) { - console.log('Starting some mock server...'); - const mockServer = await runServer(); - - console.log(`Sending request ${formatRequest(request)}`); - const targetRes = await client.target(request); - const proxyRes = await client.proxy(request); - - callback(proxyRes, targetRes, fileName, expectedError); - - console.log('Shutting down mock server...'); - await closeServer(mockServer); - } - - console.log('Shutting down proxy server...'); - await closeServer(proxyServer); }); } } diff --git a/tsconfig.json b/tsconfig.json index 5fe49ead..45d564b5 100755 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,8 +10,8 @@ }, "strict": true, "typeRoots": ["./node_modules/@types"], - "lib": ["es2018"], - "target": "es2018" + "lib": ["es2019"], + "target": "es2019" }, "include": ["src/**/*.ts", "test/**/*.ts", "types/**/*.ts"] } diff --git a/types/test-request-map.ts b/types/test-request-map.ts new file mode 100644 index 00000000..4e86a754 --- /dev/null +++ b/types/test-request-map.ts @@ -0,0 +1,19 @@ +import {AxiosRequestConfig} from 'axios'; +import * as http from 'http'; + + +export interface TestRequestMap { + [fileName: string]: Array; +} + +export type TestRequest = AxiosRequestConfig & { expectedError?: any }; + +export interface TestResponses { + [fileName: string]: TestResponseConfig; +} + +export type TestResponseConfig = Array<{ + request: TestRequest; + serverFactory: (port: number | string) => http.Server; + expectedError: any; +}>; diff --git a/types/test-requests.ts b/types/test-requests.ts deleted file mode 100644 index dd27f990..00000000 --- a/types/test-requests.ts +++ /dev/null @@ -1,19 +0,0 @@ -import {AxiosRequestConfig} from 'axios'; -import * as http from 'http'; - - -export interface TestRequests { - [fileName: string]: TestRequestConfig[]; -} - -export type TestRequestConfig = AxiosRequestConfig & { expectedError?: any }; - -export interface TestResponses { - [fileName: string]: TestResponseConfig; -} - -export type TestResponseConfig = Array<{ - request: TestRequestConfig; - runServer: () => Promise; - expectedError: any; -}>;