From 674f0306d6c56b2ea07e1f4a9e3c299bd9b41a3a Mon Sep 17 00:00:00 2001 From: rddimon Date: Thu, 7 Dec 2023 14:59:08 +0200 Subject: [PATCH 01/10] Fix hosted zone filtering --- .eslintignore | 2 + .eslintrc.json | 41 + package-lock.json | 2693 ++++++++++++++--- package.json | 15 +- src/aws/acm-wrapper.ts | 146 +- src/aws/api-gateway-v1-wrapper.ts | 286 +- src/aws/api-gateway-v2-wrapper.ts | 292 +- src/aws/cloud-formation-wrapper.ts | 258 +- src/aws/route53-wrapper.ts | 260 +- src/aws/s3-wrapper.ts | 52 +- src/globals.ts | 85 +- src/index.ts | 679 +++-- src/logging.ts | 92 +- src/models/api-gateway-map.ts | 10 +- src/models/domain-config.ts | 267 +- src/models/domain-info.ts | 53 +- src/types.ts | 142 +- src/utils.ts | 39 +- test/unit-tests/aws/acm-wrapper.test.ts | 8 +- .../aws/api-gateway-v1-wrapper.test.ts | 4 +- .../aws/api-gateway-v2-wrapper.test.ts | 4 +- .../aws/cloud-formation-wrapper.test.ts | 4 +- test/unit-tests/aws/route53-wrapper.test.ts | 1126 +++---- test/unit-tests/aws/s3-wrapper.test.ts | 4 +- tslint.json | 6 - 25 files changed, 4302 insertions(+), 2266 deletions(-) create mode 100644 .eslintignore create mode 100644 .eslintrc.json delete mode 100644 tslint.json diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..76add878 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +node_modules +dist \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 00000000..adf88e2a --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,41 @@ +{ + "env": { + "browser": true, + "es2021": true, + "commonjs": true, + "node": true, + "mocha": true + }, + "extends": [ + "standard", + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 2020, + "sourceType": "module" + }, + "plugins": [ + "@typescript-eslint" + ], + "rules": { + "quotes": [ + "error", + "double" + ], + "semi": [ + "error", + "always" + ], + "arrow-parens": [ + "error", + "always" + ], + "complexity": [ + "error", + 15 + ], + "guard-for-in": "error" + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 371ab62f..ed699fa6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "serverless-domain-manager", - "version": "7.2.1", + "version": "7.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "serverless-domain-manager", - "version": "7.2.1", + "version": "7.4.0", "license": "MIT", "dependencies": { "@aws-sdk/client-acm": "^3.460.0", @@ -27,10 +27,17 @@ "devDependencies": { "@types/mocha": "^10.0.6", "@types/node": "^20.10.0", + "@types/randomstring": "^1.1.11", "@types/shelljs": "^0.8.15", + "@typescript-eslint/eslint-plugin": "^5.62.0", "aws-sdk-client-mock": "^3.0.0", "chai": "^4.3.10", "chai-spies": "^1.1.0", + "eslint": "^7.32.0", + "eslint-config-standard": "^16.0.3", + "eslint-plugin-import": "^2.29.0", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^5.2.0", "mocha": "^10.2.0", "mocha-param": "^2.0.1", "nyc": "^15.1.0", @@ -39,8 +46,7 @@ "serverless-plugin-split-stacks": "^1.13.0", "shelljs": "^0.8.5", "ts-node": "^10.9.1", - "tslint": "^6.1.3", - "typescript": "^5.2.2" + "typescript": "^5.1.6 && <5.2" }, "engines": { "node": ">=14" @@ -49,6 +55,15 @@ "serverless": "^2.60 || ^3.0.0" } }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@ampproject/remapping": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", @@ -1678,6 +1693,206 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -2926,6 +3141,18 @@ "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", "dev": true }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, "node_modules/@types/keyv": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", @@ -2962,6 +3189,12 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/randomstring": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@types/randomstring/-/randomstring-1.1.11.tgz", + "integrity": "sha512-j3y9mKzGyYN5PHWjRv8Ah/ieZlApRbfSb0rBWVrW9Z+z5N1xjZpbBxgVKbO7WAGfnMpvLG2Gg8gRPxo+f0JWmg==", + "dev": true + }, "node_modules/@types/responselike": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", @@ -2971,6 +3204,12 @@ "@types/node": "*" } }, + "node_modules/@types/semver": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "dev": true + }, "node_modules/@types/shelljs": { "version": "0.8.15", "resolved": "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.8.15.tgz", @@ -2996,91 +3235,400 @@ "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==", "dev": true }, - "node_modules/2-thenable": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/2-thenable/-/2-thenable-1.0.0.tgz", - "integrity": "sha512-HqiDzaLDFCXkcCO/SwoyhRwqYtINFHF7t9BDRq4x90TOKNAJpiqUt9X5lQ08bwxYzc067HUywDjGySpebHcUpw==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", "dev": true, "dependencies": { - "d": "1", - "es5-ext": "^0.10.47" + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "dependencies": { - "event-target-shim": "^5.0.0" + "yallist": "^4.0.0" }, "engines": { - "node": ">=6.5" + "node": ">=10" } }, - "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, "bin": { - "acorn": "bin/acorn" + "semver": "bin/semver.js" }, "engines": { - "node": ">=0.4.0" + "node": ">=10" } }, - "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } + "node_modules/@typescript-eslint/eslint-plugin/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true }, - "node_modules/adm-zip": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", - "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==", + "node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, "engines": { - "node": ">=6.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", "dev": true, "dependencies": { - "debug": "4" + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" }, "engines": { - "node": ">= 6.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", "dev": true, "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" }, "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/2-thenable": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/2-thenable/-/2-thenable-1.0.0.tgz", + "integrity": "sha512-HqiDzaLDFCXkcCO/SwoyhRwqYtINFHF7t9BDRq4x90TOKNAJpiqUt9X5lQ08bwxYzc067HUywDjGySpebHcUpw==", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "^0.10.47" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/adm-zip": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", + "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js": "^4.2.2" }, @@ -3299,6 +3847,38 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", + "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -3308,38 +3888,123 @@ "node": ">=8" } }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "node_modules/array.prototype.findlastindex": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", + "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.2.1" + }, "engines": { - "node": "*" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ast-types": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", - "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, "dependencies": { - "tslib": "^2.0.1" + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" }, "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true - }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", + "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "dev": true + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -3706,18 +4371,28 @@ } }, "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", "dev": true, "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -4573,6 +5248,12 @@ "node": ">=6" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, "node_modules/default-require-extensions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", @@ -4622,6 +5303,20 @@ "timers-ext": "^0.1.7" } }, + "node_modules/define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/define-lazy-prop": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", @@ -4631,6 +5326,23 @@ "node": ">=8" } }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/degenerator": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", @@ -4684,6 +5396,18 @@ "node": ">=8" } }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/dotenv": { "version": "16.3.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", @@ -4736,6 +5460,112 @@ "once": "^1.4.0" } }, + "node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/es-abstract": { + "version": "1.22.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", + "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.2", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.5", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.2", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.12", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "safe-array-concat": "^1.0.1", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.8", + "string.prototype.trimend": "^1.0.7", + "string.prototype.trimstart": "^1.0.7", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", + "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2", + "has-tostringtag": "^1.0.0", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/es5-ext": { "version": "0.10.62", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", @@ -4842,20 +5672,530 @@ "esgenerate": "bin/esgenerate.js" }, "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/eslint": { + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-standard": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-16.0.3.tgz", + "integrity": "sha512-x4fmJL5hGqNJKGHSjnLdgA6U6h1YW/G2dW9fA+cyVur4SK6lyue8+UgNKWlZtUDTXvgKDD/Oa3GQjmB5kjtVvg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peerDependencies": { + "eslint": "^7.12.1", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^4.2.1 || ^5.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "dev": true, + "dependencies": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz", + "integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.14.2" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "dev": true, + "dependencies": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "peerDependencies": { + "eslint": ">=5.16.0" + } + }, + "node_modules/eslint-plugin-node/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-node/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-promise": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.2.0.tgz", + "integrity": "sha512-SftLb1pUG01QYq2A/hGAWfDRXqYD82zE7j7TopDOyNdU+7SvvoXREls/+PRTY17vUXzXnZA/zfnyKgRH6x4JJw==", + "dev": true, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/eslint/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/esniff": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-1.1.0.tgz", + "integrity": "sha512-vmHXOeOt7FJLsqofvFk4WB3ejvcHizCd8toXXwADmYfd02p2QwHRgkUbhYDX54y08nqk818CUTWipgZGlyN07g==", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "^0.10.12" + } + }, + "node_modules/espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "dependencies": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/espree/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" } }, - "node_modules/esniff": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/esniff/-/esniff-1.1.0.tgz", - "integrity": "sha512-vmHXOeOt7FJLsqofvFk4WB3ejvcHizCd8toXXwADmYfd02p2QwHRgkUbhYDX54y08nqk818CUTWipgZGlyN07g==", + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true, - "dependencies": { - "d": "1", - "es5-ext": "^0.10.12" + "engines": { + "node": ">=4" } }, "node_modules/esprima": { @@ -4870,6 +6210,30 @@ "node": ">=4" } }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/essentials": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/essentials/-/essentials-1.2.0.tgz", @@ -4993,6 +6357,18 @@ "node": ">=8.6.0" } }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, "node_modules/fast-safe-stringify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", @@ -5071,6 +6447,18 @@ "node": ">=0.8.0" } }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, "node_modules/file-type": { "version": "16.5.4", "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", @@ -5190,6 +6578,26 @@ "flat": "cli.js" } }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true + }, "node_modules/follow-redirects": { "version": "1.15.3", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", @@ -5370,11 +6778,47 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", "dev": true }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -5403,14 +6847,15 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", "dev": true, "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5449,6 +6894,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-uri": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.2.tgz", @@ -5555,6 +7016,21 @@ "node": ">=4" } }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -5617,6 +7093,12 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, "node_modules/graphlib": { "version": "2.1.8", "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", @@ -5626,16 +7108,13 @@ "lodash": "^4.17.15" } }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-flag": { @@ -5647,6 +7126,30 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", @@ -5690,6 +7193,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -5802,6 +7317,31 @@ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", "dev": true }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -5876,6 +7416,20 @@ "node": ">=8" } }, + "node_modules/internal-slot": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", + "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/interpret": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", @@ -5906,6 +7460,32 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -5918,6 +7498,22 @@ "node": ">=8" } }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -5931,12 +7527,27 @@ } }, "node_modules/is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", "dev": true, "dependencies": { - "has": "^1.0.3" + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6017,6 +7628,18 @@ "integrity": "sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==", "dev": true }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -6026,21 +7649,64 @@ "node": ">=0.12.0" } }, - "node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "dev": true + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", "dev": true, - "engines": { - "node": ">=0.10.0" + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-promise": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", - "dev": true - }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -6053,16 +7719,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", "has-tostringtag": "^1.0.0" }, "engines": { @@ -6072,6 +7734,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.11" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -6090,6 +7782,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -6423,6 +8127,12 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -6552,6 +8262,19 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lie": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", @@ -6618,6 +8341,18 @@ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", "dev": true }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true + }, "node_modules/lodash.union": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", @@ -6981,6 +8716,18 @@ "integrity": "sha512-zkVhZUA3y8mbz652WrL5x0fB0ehrBkulWT3TomAQ9iDtyXZvzKeEA6GPxAItBYeNYl5yngKRX612qHOhvMkDeg==", "dev": true }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "node_modules/ncjsm": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ncjsm/-/ncjsm-4.3.2.tgz", @@ -7361,10 +9108,83 @@ } }, "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", + "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", + "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1" + } + }, + "node_modules/object.values": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", + "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -7410,6 +9230,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/ora": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", @@ -7613,6 +9450,18 @@ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "dev": true }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -7826,6 +9675,15 @@ "node": ">=8" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -7859,6 +9717,15 @@ "node": ">=10.0" } }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/promise-queue": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/promise-queue/-/promise-queue-2.2.5.tgz", @@ -8094,6 +9961,35 @@ "node": ">= 0.10" } }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", + "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "set-function-name": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, "node_modules/release-zalgo": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", @@ -8131,12 +10027,12 @@ "dev": true }, "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, "dependencies": { - "is-core-module": "^2.9.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -8276,6 +10172,30 @@ "tslib": "^2.1.0" } }, + "node_modules/safe-array-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", + "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -8296,6 +10216,20 @@ } ] }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -8528,6 +10462,35 @@ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true }, + "node_modules/set-function-length": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", + "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -8646,6 +10609,23 @@ "node": ">=8" } }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -8809,18 +10789,63 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", + "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", + "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", + "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", "dev": true, "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/strip-ansi": { @@ -8991,6 +11016,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/table": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", + "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/tar": { "version": "6.1.15", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz", @@ -9066,6 +11107,12 @@ "node": "*" } }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, "node_modules/throat": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", @@ -9252,248 +11299,164 @@ "node": ">=0.3.1" } }, - "node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" - }, - "node_modules/tslint": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", - "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", - "deprecated": "TSLint has been deprecated in favor of ESLint. Please see https://github.com/palantir/tslint/issues/4534 for more information.", + "node_modules/tsconfig-paths": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", + "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", "dev": true, "dependencies": { - "@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" - }, - "bin": { - "tslint": "bin/tslint" - }, - "engines": { - "node": ">=4.8.0" - }, - "peerDependencies": { - "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev || >= 4.0.0-dev" + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" } }, - "node_modules/tslint/node_modules/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==", + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "dependencies": { - "color-convert": "^1.9.0" + "minimist": "^1.2.0" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/tslint/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/tslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "bin": { + "json5": "lib/cli.js" } }, - "node_modules/tslint/node_modules/builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha512-wxXCdllwGhI2kCC0MnvTGYTMvnVZTvqgypkiTI8Pa5tcz2i6VqsqwYGgqwXji+4RgCzms6EajE4IxiUH6HH8nQ==", + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "node_modules/tslint/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", "dev": true, "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "tslib": "^1.8.1" }, "engines": { - "node": ">=4" - } - }, - "node_modules/tslint/node_modules/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, - "dependencies": { - "color-name": "1.1.3" + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" } }, - "node_modules/tslint/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, - "node_modules/tslint/node_modules/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==", + "node_modules/type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", "dev": true }, - "node_modules/tslint/node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, "engines": { - "node": ">=0.3.1" + "node": ">= 0.8.0" } }, - "node_modules/tslint/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true, "engines": { - "node": ">=0.8.0" + "node": ">=4" } }, - "node_modules/tslint/node_modules/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==", + "node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/tslint/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "node_modules/typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", "dev": true, "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">= 0.4" } }, - "node_modules/tslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" }, "engines": { - "node": "*" - } - }, - "node_modules/tslint/node_modules/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, - "dependencies": { - "minimist": "^1.2.6" + "node": ">= 0.4" }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/tslint/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/tslint/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", "dev": true, "dependencies": { - "has-flag": "^3.0.0" + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" }, "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/tslint/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/tsutils": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", - "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", "dev": true, "dependencies": { - "tslib": "^1.8.1" + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" }, - "peerDependencies": { - "typescript": ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev" - } - }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", - "dev": true - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/typedarray-to-buffer": { @@ -9506,9 +11469,9 @@ } }, "node_modules/typescript": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", - "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -9518,6 +11481,21 @@ "node": ">=14.17" } }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/unbzip2-stream": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", @@ -9673,6 +11651,12 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/v8-compile-cache": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", + "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==", + "dev": true + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -9728,6 +11712,22 @@ "node": ">= 8" } }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", @@ -9735,17 +11735,16 @@ "dev": true }, "node_modules/which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", + "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "call-bind": "^1.0.4", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" + "has-tostringtag": "^1.0.0" }, "engines": { "node": ">= 0.4" diff --git a/package.json b/package.json index 557e01c4..97ae1611 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serverless-domain-manager", - "version": "7.3.1", + "version": "7.3.2", "engines": { "node": ">=14" }, @@ -33,7 +33,8 @@ "test": "find ./test/unit-tests -name '*.test.ts' | xargs nyc mocha -r ts-node/register --project tsconfig.json --timeout 5000 && nyc report --reporter=text-summary", "test:debug": "NODE_OPTIONS='--inspect-brk' mocha -j 1 -r ts-node/register --project tsconfig.json test/unit-tests/index.test.ts", "integration-test": "npm run integration-basic && npm run integration-deploy", - "lint": "tslint --project . && tslint --project tsconfig.json", + "lint": "eslint src --ext .ts", + "lint:fix": "npm run lint -- --fix", "build": "tsc --project .", "prepare": "npm run build" }, @@ -51,10 +52,17 @@ "devDependencies": { "@types/mocha": "^10.0.6", "@types/node": "^20.10.0", + "@types/randomstring": "^1.1.11", "@types/shelljs": "^0.8.15", "aws-sdk-client-mock": "^3.0.0", "chai": "^4.3.10", "chai-spies": "^1.1.0", + "eslint": "^7.32.0", + "eslint-config-standard": "^16.0.3", + "eslint-plugin-import": "^2.29.0", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^5.2.0", + "@typescript-eslint/eslint-plugin": "^5.62.0", "mocha": "^10.2.0", "mocha-param": "^2.0.1", "nyc": "^15.1.0", @@ -63,8 +71,7 @@ "serverless-plugin-split-stacks": "^1.13.0", "shelljs": "^0.8.5", "ts-node": "^10.9.1", - "tslint": "^6.1.3", - "typescript": "^5.2.2" + "typescript": "^5.1.6 && <5.2" }, "dependencies": { "@aws-sdk/client-acm": "^3.460.0", diff --git a/src/aws/acm-wrapper.ts b/src/aws/acm-wrapper.ts index eddb860a..64127a54 100644 --- a/src/aws/acm-wrapper.ts +++ b/src/aws/acm-wrapper.ts @@ -1,97 +1,97 @@ import { - CertificateStatus, - ACMClient, - CertificateSummary, - ListCertificatesCommand, - ListCertificatesCommandInput, - ListCertificatesCommandOutput + CertificateStatus, + ACMClient, + CertificateSummary, + ListCertificatesCommand, + ListCertificatesCommandInput, + ListCertificatesCommandOutput } from "@aws-sdk/client-acm"; import Globals from "../globals"; import DomainConfig = require("../models/domain-config"); -import {getAWSPagedResults} from "../utils"; +import { getAWSPagedResults } from "../utils"; import Logging from "../logging"; const certStatuses = [ - CertificateStatus.PENDING_VALIDATION, - CertificateStatus.ISSUED, - CertificateStatus.INACTIVE + CertificateStatus.PENDING_VALIDATION, + CertificateStatus.ISSUED, + CertificateStatus.INACTIVE ]; class ACMWrapper { public acm: ACMClient; - constructor(credentials: any, endpointType: string) { - const isEdge = endpointType === Globals.endpointTypes.edge; - this.acm = new ACMClient({ - credentials, - region: isEdge ? Globals.defaultRegion : Globals.getRegion(), - retryStrategy: Globals.getRetryStrategy(), - requestHandler: Globals.getRequestHandler(), - }); + constructor (credentials: any, endpointType: string) { + const isEdge = endpointType === Globals.endpointTypes.edge; + this.acm = new ACMClient([{ + credentials, + region: isEdge ? Globals.defaultRegion : Globals.getRegion(), + retryStrategy: Globals.getRetryStrategy(), + requestHandler: Globals.getRequestHandler() + }]); } - public async getCertArn(domain: DomainConfig): Promise { - let certificateArn; // The arn of the selected certificate - let certificateName = domain.certificateName; // The certificate name + public async getCertArn (domain: DomainConfig): Promise { + let certificateArn; // The arn of the selected certificate + let certificateName = domain.certificateName; // The certificate name - try { - const certificates = await getAWSPagedResults( - this.acm, - "CertificateSummaryList", - "NextToken", - "NextToken", - new ListCertificatesCommand({CertificateStatuses: certStatuses}) - ); - // enhancement idea: weight the choice of cert so longer expires - // and RenewalEligibility = ELIGIBLE is more preferable - if (certificateName) { - certificateArn = this.getCertArnByCertName(certificates, certificateName); - } else { - certificateName = domain.givenDomainName; - certificateArn = ACMWrapper.getCertArnByDomainName(certificates, certificateName); - } - Logging.logInfo(`Found a certificate ARN: '${certificateArn}'`); - } catch (err) { - throw Error(`Could not search certificates in Certificate Manager.\n${err.message}`); + try { + const certificates = await getAWSPagedResults( + this.acm, + "CertificateSummaryList", + "NextToken", + "NextToken", + new ListCertificatesCommand({ CertificateStatuses: certStatuses }) + ); + // enhancement idea: weight the choice of cert so longer expires + // and RenewalEligibility = ELIGIBLE is more preferable + if (certificateName) { + certificateArn = this.getCertArnByCertName(certificates, certificateName); + } else { + certificateName = domain.givenDomainName; + certificateArn = ACMWrapper.getCertArnByDomainName(certificates, certificateName); } - if (certificateArn == null) { - throw Error(`Could not find an in-date certificate for '${certificateName}'.`); - } - return certificateArn; + Logging.logInfo(`Found a certificate ARN: '${certificateArn}'`); + } catch (err) { + throw Error(`Could not search certificates in Certificate Manager.\n${err.message}`); + } + if (certificateArn == null) { + throw Error(`Could not find an in-date certificate for '${certificateName}'.`); + } + return certificateArn; } - private getCertArnByCertName(certificates, certName): string { - const found = certificates.find((c) => c.DomainName === certName); - if (found) { - return found.CertificateArn; - } - return null; + private getCertArnByCertName (certificates, certName): string { + const found = certificates.find((c) => c.DomainName === certName); + if (found) { + return found.CertificateArn; + } + return null; } - private static getCertArnByDomainName(certificates, domainName): string { - // The more specific name will be the longest - let nameLength = 0; - let certificateArn; - for (const currCert of certificates) { - const allDomainsForCert = [ - currCert.DomainName, - ...(currCert.SubjectAlternativeNameSummaries || []), - ]; - for (const currCertDomain of allDomainsForCert) { - let certificateListName = currCertDomain; - // Looks for wild card and take it out when checking - if (certificateListName[0] === "*") { - certificateListName = certificateListName.substring(1); - } - // Looks to see if the name in the list is within the given domain - // Also checks if the name is more specific than previous ones - if (domainName.includes(certificateListName) && certificateListName.length > nameLength) { - nameLength = certificateListName.length; - certificateArn = currCert.CertificateArn; - } - } + private static getCertArnByDomainName (certificates, domainName): string { + // The more specific name will be the longest + let nameLength = 0; + let certificateArn; + for (const currCert of certificates) { + const allDomainsForCert = [ + currCert.DomainName, + ...(currCert.SubjectAlternativeNameSummaries || []) + ]; + for (const currCertDomain of allDomainsForCert) { + let certificateListName = currCertDomain; + // Looks for wild card and take it out when checking + if (certificateListName[0] === "*") { + certificateListName = certificateListName.substring(1); + } + // Looks to see if the name in the list is within the given domain + // Also checks if the name is more specific than previous ones + if (domainName.includes(certificateListName) && certificateListName.length > nameLength) { + nameLength = certificateListName.length; + certificateArn = currCert.CertificateArn; + } } - return certificateArn; + } + return certificateArn; } } diff --git a/src/aws/api-gateway-v1-wrapper.ts b/src/aws/api-gateway-v1-wrapper.ts index dfe3549c..d936b50a 100644 --- a/src/aws/api-gateway-v1-wrapper.ts +++ b/src/aws/api-gateway-v1-wrapper.ts @@ -5,187 +5,187 @@ import DomainConfig = require("../models/domain-config"); import DomainInfo = require("../models/domain-info"); import Globals from "../globals"; import { - APIGatewayClient, - BasePathMapping, - CreateBasePathMappingCommand, - CreateDomainNameCommand, - CreateDomainNameCommandOutput, - DeleteBasePathMappingCommand, - DeleteDomainNameCommand, - GetBasePathMappingsCommand, - GetBasePathMappingsCommandInput, - GetBasePathMappingsCommandOutput, - GetDomainNameCommand, - GetDomainNameCommandOutput, - UpdateBasePathMappingCommand + APIGatewayClient, + BasePathMapping, + CreateBasePathMappingCommand, + CreateDomainNameCommand, + CreateDomainNameCommandOutput, + DeleteBasePathMappingCommand, + DeleteDomainNameCommand, + GetBasePathMappingsCommand, + GetBasePathMappingsCommandInput, + GetBasePathMappingsCommandOutput, + GetDomainNameCommand, + GetDomainNameCommandOutput, + UpdateBasePathMappingCommand } from "@aws-sdk/client-api-gateway"; import ApiGatewayMap = require("../models/api-gateway-map"); import APIGatewayBase = require("../models/apigateway-base"); import Logging from "../logging"; -import {getAWSPagedResults} from "../utils"; +import { getAWSPagedResults } from "../utils"; class APIGatewayV1Wrapper extends APIGatewayBase { public readonly apiGateway: APIGatewayClient; - constructor(credentials?: any) { - super(); - this.apiGateway = new APIGatewayClient({ - credentials, - region: Globals.getRegion(), - retryStrategy: Globals.getRetryStrategy(), - requestHandler: Globals.getRequestHandler(), - }); + constructor (credentials?: any) { + super(); + this.apiGateway = new APIGatewayClient([{ + credentials, + region: Globals.getRegion(), + retryStrategy: Globals.getRetryStrategy(), + requestHandler: Globals.getRequestHandler() + }]); } - public async createCustomDomain(domain: DomainConfig): Promise { - const providerTags = { - ...Globals.serverless.service.provider.stackTags, - ...Globals.serverless.service.provider.tags - }; + public async createCustomDomain (domain: DomainConfig): Promise { + const providerTags = { + ...Globals.serverless.service.provider.stackTags, + ...Globals.serverless.service.provider.tags + }; - const params: any = { - domainName: domain.givenDomainName, - endpointConfiguration: { - types: [domain.endpointType], - }, - securityPolicy: domain.securityPolicy, - tags: providerTags, - }; + const params: any = { + domainName: domain.givenDomainName, + endpointConfiguration: { + types: [domain.endpointType] + }, + securityPolicy: domain.securityPolicy, + tags: providerTags + }; - const isEdgeType = domain.endpointType === Globals.endpointTypes.edge; - if (isEdgeType) { - params.certificateArn = domain.certificateArn; - } else { - params.regionalCertificateArn = domain.certificateArn; + const isEdgeType = domain.endpointType === Globals.endpointTypes.edge; + if (isEdgeType) { + params.certificateArn = domain.certificateArn; + } else { + params.regionalCertificateArn = domain.certificateArn; - if (domain.tlsTruststoreUri) { - params.mutualTlsAuthentication = { - truststoreUri: domain.tlsTruststoreUri - }; + if (domain.tlsTruststoreUri) { + params.mutualTlsAuthentication = { + truststoreUri: domain.tlsTruststoreUri + }; - if (domain.tlsTruststoreVersion) { - params.mutualTlsAuthentication.truststoreVersion = domain.tlsTruststoreVersion; - } - } + if (domain.tlsTruststoreVersion) { + params.mutualTlsAuthentication.truststoreVersion = domain.tlsTruststoreVersion; + } } + } - try { - const domainInfo: CreateDomainNameCommandOutput = await this.apiGateway.send( - new CreateDomainNameCommand(params) - ); - return new DomainInfo(domainInfo); - } catch (err) { - throw new Error( + try { + const domainInfo: CreateDomainNameCommandOutput = await this.apiGateway.send( + new CreateDomainNameCommand(params) + ); + return new DomainInfo(domainInfo); + } catch (err) { + throw new Error( `V1 - Failed to create custom domain '${domain.givenDomainName}':\n${err.message}` - ); - } + ); + } } - public async getCustomDomain(domain: DomainConfig): Promise { - // Make API call - try { - const domainInfo: GetDomainNameCommandOutput = await this.apiGateway.send( - new GetDomainNameCommand({ - domainName: domain.givenDomainName, - }) - ); - return new DomainInfo(domainInfo); - } catch (err) { - if (!err.$metadata || err.$metadata.httpStatusCode !== 404) { - throw new Error( + public async getCustomDomain (domain: DomainConfig): Promise { + // Make API call + try { + const domainInfo: GetDomainNameCommandOutput = await this.apiGateway.send( + new GetDomainNameCommand({ + domainName: domain.givenDomainName + }) + ); + return new DomainInfo(domainInfo); + } catch (err) { + if (!err.$metadata || err.$metadata.httpStatusCode !== 404) { + throw new Error( `V1 - Unable to fetch information about '${domain.givenDomainName}':\n${err.message}` - ); - } - Logging.logWarning(`V1 - '${domain.givenDomainName}' does not exist.`); + ); } + Logging.logWarning(`V1 - '${domain.givenDomainName}' does not exist.`); + } } - public async deleteCustomDomain(domain: DomainConfig): Promise { - // Make API call - try { - await this.apiGateway.send(new DeleteDomainNameCommand({ - domainName: domain.givenDomainName, - })); - } catch (err) { - throw new Error(`V1 - Failed to delete custom domain '${domain.givenDomainName}':\n${err.message}`); - } + public async deleteCustomDomain (domain: DomainConfig): Promise { + // Make API call + try { + await this.apiGateway.send(new DeleteDomainNameCommand({ + domainName: domain.givenDomainName + })); + } catch (err) { + throw new Error(`V1 - Failed to delete custom domain '${domain.givenDomainName}':\n${err.message}`); + } } - public async createBasePathMapping(domain: DomainConfig): Promise { - try { - await this.apiGateway.send(new CreateBasePathMappingCommand({ - basePath: domain.basePath, - domainName: domain.givenDomainName, - restApiId: domain.apiId, - stage: domain.stage, - })); - Logging.logInfo(`V1 - Created API mapping '${domain.basePath}' for '${domain.givenDomainName}'`); - } catch (err) { - throw new Error( + public async createBasePathMapping (domain: DomainConfig): Promise { + try { + await this.apiGateway.send(new CreateBasePathMappingCommand({ + basePath: domain.basePath, + domainName: domain.givenDomainName, + restApiId: domain.apiId, + stage: domain.stage + })); + Logging.logInfo(`V1 - Created API mapping '${domain.basePath}' for '${domain.givenDomainName}'`); + } catch (err) { + throw new Error( `V1 - Make sure the '${domain.givenDomainName}' exists. Unable to create base path mapping for '${domain.givenDomainName}':\n${err.message}` - ); - } + ); + } } - public async getBasePathMappings(domain: DomainConfig): Promise { - try { - const items = await getAWSPagedResults( - this.apiGateway, - "items", - "position", - "position", - new GetBasePathMappingsCommand({ - domainName: domain.givenDomainName, - }) - ); - return items.map((item) => { - return new ApiGatewayMap(item.restApiId, item.basePath, item.stage, null); - } - ); - } catch (err) { - throw new Error( + public async getBasePathMappings (domain: DomainConfig): Promise { + try { + const items = await getAWSPagedResults( + this.apiGateway, + "items", + "position", + "position", + new GetBasePathMappingsCommand({ + domainName: domain.givenDomainName + }) + ); + return items.map((item) => { + return new ApiGatewayMap(item.restApiId, item.basePath, item.stage, null); + } + ); + } catch (err) { + throw new Error( `V1 - Make sure the '${domain.givenDomainName}' exists. Unable to get Base Path Mappings:\n${err.message}` - ); - } + ); + } } - public async updateBasePathMapping(domain: DomainConfig): Promise { - try { - await this.apiGateway.send(new UpdateBasePathMappingCommand({ - basePath: domain.apiMapping.basePath, - domainName: domain.givenDomainName, - patchOperations: [{ - op: "replace", - path: "/basePath", - value: domain.basePath, - }] - } - )); - Logging.logInfo(`V1 - Updated API mapping from '${domain.apiMapping.basePath}' + public async updateBasePathMapping (domain: DomainConfig): Promise { + try { + await this.apiGateway.send(new UpdateBasePathMappingCommand({ + basePath: domain.apiMapping.basePath, + domainName: domain.givenDomainName, + patchOperations: [{ + op: "replace", + path: "/basePath", + value: domain.basePath + }] + } + )); + Logging.logInfo(`V1 - Updated API mapping from '${domain.apiMapping.basePath}' to '${domain.basePath}' for '${domain.givenDomainName}'`); - } catch (err) { - throw new Error( + } catch (err) { + throw new Error( `V1 - Unable to update base path mapping for '${domain.givenDomainName}':\n${err.message}` - ); - } + ); + } } - public async deleteBasePathMapping(domain: DomainConfig): Promise { - try { - await this.apiGateway.send( - new DeleteBasePathMappingCommand({ - basePath: domain.apiMapping.basePath, - domainName: domain.givenDomainName, - }) - ); - Logging.logInfo(`V1 - Removed '${domain.apiMapping.basePath}' base path mapping`); - } catch (err) { - throw new Error( + public async deleteBasePathMapping (domain: DomainConfig): Promise { + try { + await this.apiGateway.send( + new DeleteBasePathMappingCommand({ + basePath: domain.apiMapping.basePath, + domainName: domain.givenDomainName + }) + ); + Logging.logInfo(`V1 - Removed '${domain.apiMapping.basePath}' base path mapping`); + } catch (err) { + throw new Error( `V1 - Unable to remove base path mapping for '${domain.givenDomainName}':\n${err.message}` - ); - } + ); + } } } diff --git a/src/aws/api-gateway-v2-wrapper.ts b/src/aws/api-gateway-v2-wrapper.ts index 18bcba4c..1c42e4ce 100644 --- a/src/aws/api-gateway-v2-wrapper.ts +++ b/src/aws/api-gateway-v2-wrapper.ts @@ -7,214 +7,214 @@ import Globals from "../globals"; import ApiGatewayMap = require("../models/api-gateway-map"); import APIGatewayBase = require("../models/apigateway-base"); import { - ApiGatewayV2Client, - ApiMapping, - CreateApiMappingCommand, - CreateDomainNameCommand, - CreateDomainNameCommandOutput, - DeleteApiMappingCommand, - DeleteDomainNameCommand, - GetApiMappingsCommand, - GetApiMappingsCommandInput, - GetApiMappingsCommandOutput, - GetDomainNameCommand, - GetDomainNameCommandOutput, - UpdateApiMappingCommand + ApiGatewayV2Client, + ApiMapping, + CreateApiMappingCommand, + CreateDomainNameCommand, + CreateDomainNameCommandOutput, + DeleteApiMappingCommand, + DeleteDomainNameCommand, + GetApiMappingsCommand, + GetApiMappingsCommandInput, + GetApiMappingsCommandOutput, + GetDomainNameCommand, + GetDomainNameCommandOutput, + UpdateApiMappingCommand } from "@aws-sdk/client-apigatewayv2"; import Logging from "../logging"; -import {getAWSPagedResults} from "../utils"; +import { getAWSPagedResults } from "../utils"; class APIGatewayV2Wrapper extends APIGatewayBase { public readonly apiGateway: ApiGatewayV2Client; - constructor(credentials?: any) { - super(); - this.apiGateway = new ApiGatewayV2Client({ - credentials, - region: Globals.getRegion(), - retryStrategy: Globals.getRetryStrategy(), - requestHandler: Globals.getRequestHandler(), - }); + constructor (credentials?: any) { + super(); + this.apiGateway = new ApiGatewayV2Client([{ + credentials, + region: Globals.getRegion(), + retryStrategy: Globals.getRetryStrategy(), + requestHandler: Globals.getRequestHandler() + }]); } /** * Creates Custom Domain Name * @param domain: DomainConfig */ - public async createCustomDomain(domain: DomainConfig): Promise { - const providerTags = { - ...Globals.serverless.service.provider.stackTags, - ...Globals.serverless.service.provider.tags - }; + public async createCustomDomain (domain: DomainConfig): Promise { + const providerTags = { + ...Globals.serverless.service.provider.stackTags, + ...Globals.serverless.service.provider.tags + }; - const params: any = { - DomainName: domain.givenDomainName, - DomainNameConfigurations: [{ - CertificateArn: domain.certificateArn, - EndpointType: domain.endpointType, - SecurityPolicy: domain.securityPolicy, - }], - Tags: providerTags - }; + const params: any = { + DomainName: domain.givenDomainName, + DomainNameConfigurations: [{ + CertificateArn: domain.certificateArn, + EndpointType: domain.endpointType, + SecurityPolicy: domain.securityPolicy + }], + Tags: providerTags + }; - const isEdgeType = domain.endpointType === Globals.endpointTypes.edge; - if (!isEdgeType && domain.tlsTruststoreUri) { - params.MutualTlsAuthentication = { - TruststoreUri: domain.tlsTruststoreUri - }; + const isEdgeType = domain.endpointType === Globals.endpointTypes.edge; + if (!isEdgeType && domain.tlsTruststoreUri) { + params.MutualTlsAuthentication = { + TruststoreUri: domain.tlsTruststoreUri + }; - if (domain.tlsTruststoreVersion) { - params.MutualTlsAuthentication.TruststoreVersion = domain.tlsTruststoreVersion; - } + if (domain.tlsTruststoreVersion) { + params.MutualTlsAuthentication.TruststoreVersion = domain.tlsTruststoreVersion; } + } - try { - const domainInfo: CreateDomainNameCommandOutput = await this.apiGateway.send( - new CreateDomainNameCommand(params) - ); - return new DomainInfo(domainInfo); - } catch (err) { - throw new Error( + try { + const domainInfo: CreateDomainNameCommandOutput = await this.apiGateway.send( + new CreateDomainNameCommand(params) + ); + return new DomainInfo(domainInfo); + } catch (err) { + throw new Error( `V2 - Failed to create custom domain '${domain.givenDomainName}':\n${err.message}` - ); - } + ); + } } /** * Get Custom Domain Info * @param domain: DomainConfig */ - public async getCustomDomain(domain: DomainConfig): Promise { - // Make API call - try { - const domainInfo: GetDomainNameCommandOutput = await this.apiGateway.send( - new GetDomainNameCommand({ - DomainName: domain.givenDomainName - }) - ); - return new DomainInfo(domainInfo); - } catch (err) { - if (!err.$metadata || err.$metadata.httpStatusCode !== 404) { - throw new Error( + public async getCustomDomain (domain: DomainConfig): Promise { + // Make API call + try { + const domainInfo: GetDomainNameCommandOutput = await this.apiGateway.send( + new GetDomainNameCommand({ + DomainName: domain.givenDomainName + }) + ); + return new DomainInfo(domainInfo); + } catch (err) { + if (!err.$metadata || err.$metadata.httpStatusCode !== 404) { + throw new Error( `V2 - Unable to fetch information about '${domain.givenDomainName}':\n${err.message}` - ); - } - Logging.logInfo(`V2 - '${domain.givenDomainName}' does not exist.`); + ); } + Logging.logInfo(`V2 - '${domain.givenDomainName}' does not exist.`); + } } /** * Delete Custom Domain Name * @param domain: DomainConfig */ - public async deleteCustomDomain(domain: DomainConfig): Promise { - // Make API call - try { - await this.apiGateway.send( - new DeleteDomainNameCommand({ - DomainName: domain.givenDomainName, - }) - ); - } catch (err) { - throw new Error( + public async deleteCustomDomain (domain: DomainConfig): Promise { + // Make API call + try { + await this.apiGateway.send( + new DeleteDomainNameCommand({ + DomainName: domain.givenDomainName + }) + ); + } catch (err) { + throw new Error( `V2 - Failed to delete custom domain '${domain.givenDomainName}':\n${err.message}` - ); - } + ); + } } /** * Create Base Path Mapping * @param domain: DomainConfig */ - public async createBasePathMapping(domain: DomainConfig): Promise { - if (domain.apiType === Globals.apiTypes.http && domain.stage !== Globals.defaultStage) { - Logging.logWarning( + public async createBasePathMapping (domain: DomainConfig): Promise { + if (domain.apiType === Globals.apiTypes.http && domain.stage !== Globals.defaultStage) { + Logging.logWarning( `Using a HTTP API with a stage name other than '${Globals.defaultStage}'. ` + `HTTP APIs require a stage named '${Globals.defaultStage}'. ` + - 'Please make sure that stage exists in the API Gateway. ' + - 'See https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-stages.html' - ) - } - try { - await this.apiGateway.send( - new CreateApiMappingCommand({ - ApiId: domain.apiId, - ApiMappingKey: domain.basePath, - DomainName: domain.givenDomainName, - Stage: domain.stage, - }) - ); - Logging.logInfo(`V2 - Created API mapping '${domain.basePath}' for '${domain.givenDomainName}'`); - } catch (err) { - throw new Error( + "Please make sure that stage exists in the API Gateway. " + + "See https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-stages.html" + ); + } + try { + await this.apiGateway.send( + new CreateApiMappingCommand({ + ApiId: domain.apiId, + ApiMappingKey: domain.basePath, + DomainName: domain.givenDomainName, + Stage: domain.stage + }) + ); + Logging.logInfo(`V2 - Created API mapping '${domain.basePath}' for '${domain.givenDomainName}'`); + } catch (err) { + throw new Error( `V2 - Unable to create base path mapping for '${domain.givenDomainName}':\n${err.message}` - ); - } + ); + } } /** * Get APi Mapping * @param domain: DomainConfig */ - public async getBasePathMappings(domain: DomainConfig): Promise { - try { - const items = await getAWSPagedResults( - this.apiGateway, - "Items", - "NextToken", - "NextToken", - new GetApiMappingsCommand({ - DomainName: domain.givenDomainName - }) - ); - return items.map( - (item) => new ApiGatewayMap(item.ApiId, item.ApiMappingKey, item.Stage, item.ApiMappingId) - ); - } catch (err) { - throw new Error( + public async getBasePathMappings (domain: DomainConfig): Promise { + try { + const items = await getAWSPagedResults( + this.apiGateway, + "Items", + "NextToken", + "NextToken", + new GetApiMappingsCommand({ + DomainName: domain.givenDomainName + }) + ); + return items.map( + (item) => new ApiGatewayMap(item.ApiId, item.ApiMappingKey, item.Stage, item.ApiMappingId) + ); + } catch (err) { + throw new Error( `V2 - Make sure the '${domain.givenDomainName}' exists. Unable to get API Mappings:\n${err.message}` - ); - } + ); + } } /** * Update APi Mapping * @param domain: DomainConfig */ - public async updateBasePathMapping(domain: DomainConfig): Promise { - try { - await this.apiGateway.send( - new UpdateApiMappingCommand({ - ApiId: domain.apiId, - ApiMappingId: domain.apiMapping.apiMappingId, - ApiMappingKey: domain.basePath, - DomainName: domain.givenDomainName, - Stage: domain.stage, - }) - ); - Logging.logInfo(`V2 - Updated API mapping to '${domain.basePath}' for '${domain.givenDomainName}'`); - } catch (err) { - throw new Error( + public async updateBasePathMapping (domain: DomainConfig): Promise { + try { + await this.apiGateway.send( + new UpdateApiMappingCommand({ + ApiId: domain.apiId, + ApiMappingId: domain.apiMapping.apiMappingId, + ApiMappingKey: domain.basePath, + DomainName: domain.givenDomainName, + Stage: domain.stage + }) + ); + Logging.logInfo(`V2 - Updated API mapping to '${domain.basePath}' for '${domain.givenDomainName}'`); + } catch (err) { + throw new Error( `V2 - Unable to update base path mapping for '${domain.givenDomainName}':\n${err.message}` - ); - } + ); + } } /** * Delete Api Mapping */ - public async deleteBasePathMapping(domain: DomainConfig): Promise { - try { - await this.apiGateway.send(new DeleteApiMappingCommand({ - ApiMappingId: domain.apiMapping.apiMappingId, - DomainName: domain.givenDomainName, - })); - Logging.logInfo(`V2 - Removed API Mapping with id: '${domain.apiMapping.apiMappingId}'`); - } catch (err) { - throw new Error( + public async deleteBasePathMapping (domain: DomainConfig): Promise { + try { + await this.apiGateway.send(new DeleteApiMappingCommand({ + ApiMappingId: domain.apiMapping.apiMappingId, + DomainName: domain.givenDomainName + })); + Logging.logInfo(`V2 - Removed API Mapping with id: '${domain.apiMapping.apiMappingId}'`); + } catch (err) { + throw new Error( `V2 - Unable to remove base path mapping for '${domain.givenDomainName}':\n${err.message}` - ); - } + ); + } } } diff --git a/src/aws/cloud-formation-wrapper.ts b/src/aws/cloud-formation-wrapper.ts index 51ed4e60..216f22f8 100644 --- a/src/aws/cloud-formation-wrapper.ts +++ b/src/aws/cloud-formation-wrapper.ts @@ -23,177 +23,177 @@ class CloudFormationWrapper { public cloudFormation: CloudFormationClient; public stackName: string; - constructor(credentials?: any) { - // for the CloudFormation stack we should use the `base` stage not the plugin custom stage - const defaultStackName = Globals.serverless.service.service + "-" + Globals.getBaseStage(); - this.stackName = Globals.serverless.service.provider.stackName || defaultStackName; - this.cloudFormation = new CloudFormationClient({ - credentials, - region: Globals.getRegion(), - retryStrategy: Globals.getRetryStrategy(), - requestHandler: Globals.getRequestHandler(), - }); + constructor (credentials?: any) { + // for the CloudFormation stack we should use the `base` stage not the plugin custom stage + const defaultStackName = Globals.serverless.service.service + "-" + Globals.getBaseStage(); + this.stackName = Globals.serverless.service.provider.stackName || defaultStackName; + this.cloudFormation = new CloudFormationClient([{ + credentials, + region: Globals.getRegion(), + retryStrategy: Globals.getRetryStrategy(), + requestHandler: Globals.getRequestHandler() + }]); } /** * Get an API id from the existing config or CloudFormation stack resources or outputs */ - public async findApiId(apiType: string): Promise { - const configApiId = await this.getConfigId(apiType); - if (configApiId) { - return configApiId; - } + public async findApiId (apiType: string): Promise { + const configApiId = await this.getConfigId(apiType); + if (configApiId) { + return configApiId; + } - return await this.getStackApiId(apiType); + return await this.getStackApiId(apiType); } /** * Get an API id from the existing config or CloudFormation stack based on provider.apiGateway params */ - private async getConfigId(apiType: string): Promise { - const apiGateway = Globals.serverless.service.provider.apiGateway || {}; - const apiIdKey = Globals.gatewayAPIIdKeys[apiType]; - const apiGatewayValue = apiGateway[apiIdKey]; - - if (apiGatewayValue) { - if (typeof apiGatewayValue === "string") { - return apiGatewayValue; - } - - return await this.getCloudformationId(apiGatewayValue, apiType); + private async getConfigId (apiType: string): Promise { + const apiGateway = Globals.serverless.service.provider.apiGateway || {}; + const apiIdKey = Globals.gatewayAPIIdKeys[apiType]; + const apiGatewayValue = apiGateway[apiIdKey]; + + if (apiGatewayValue) { + if (typeof apiGatewayValue === "string") { + return apiGatewayValue; } - return null; + return await this.getCloudformationId(apiGatewayValue, apiType); + } + + return null; } - private async getCloudformationId(apiGatewayValue: object, apiType: string): Promise { - // in case object and Fn::ImportValue try to get API id from the CloudFormation outputs - const importName = apiGatewayValue[Globals.CFFuncNames.fnImport]; - if (importName) { - const importValues = await this.getImportValues([importName]); - const nameValue = importValues[importName]; - if (!nameValue) { - Logging.logWarning(`CloudFormation ImportValue '${importName}' not found in the outputs`); - } - return nameValue; + private async getCloudformationId (apiGatewayValue: object, apiType: string): Promise { + // in case object and Fn::ImportValue try to get API id from the CloudFormation outputs + const importName = apiGatewayValue[Globals.CFFuncNames.fnImport]; + if (importName) { + const importValues = await this.getImportValues([importName]); + const nameValue = importValues[importName]; + if (!nameValue) { + Logging.logWarning(`CloudFormation ImportValue '${importName}' not found in the outputs`); } + return nameValue; + } - const ref = apiGatewayValue[Globals.CFFuncNames.ref]; - if (ref) { - try { - return await this.getStackApiId(apiType, ref); - } catch (error) { - Logging.logWarning(`Unable to get ref ${ref} value.\n ${error.message}`); - return null; - } + const ref = apiGatewayValue[Globals.CFFuncNames.ref]; + if (ref) { + try { + return await this.getStackApiId(apiType, ref); + } catch (error) { + Logging.logWarning(`Unable to get ref ${ref} value.\n ${error.message}`); + return null; } + } - // log warning not supported restApiId - Logging.logWarning(`Unsupported apiGateway.${apiType} object`); + // log warning not supported restApiId + Logging.logWarning(`Unsupported apiGateway.${apiType} object`); - return null; + return null; } /** * Gets rest API id from CloudFormation stack or nested stack */ - public async getStackApiId(apiType: string, logicalResourceId: string = null): Promise { - if (!logicalResourceId) { - logicalResourceId = Globals.CFResourceIds[apiType]; - } - - let response; - try { - // trying to get information for specified stack name - response = await this.getStack(logicalResourceId, this.stackName); - } catch { - // in case error trying to get information from some of nested stacks - response = await this.getNestedStack(logicalResourceId, this.stackName); - } - - if (!response) { - throw new Error(`Failed to find a stack ${this.stackName}\n`); - } - - const apiId = response.StackResourceDetail.PhysicalResourceId; - if (!apiId) { - throw new Error(`No ApiId associated with CloudFormation stack ${this.stackName}`); - } - - return apiId; + public async getStackApiId (apiType: string, logicalResourceId: string = null): Promise { + if (!logicalResourceId) { + logicalResourceId = Globals.CFResourceIds[apiType]; + } + + let response; + try { + // trying to get information for specified stack name + response = await this.getStack(logicalResourceId, this.stackName); + } catch { + // in case error trying to get information from some of nested stacks + response = await this.getNestedStack(logicalResourceId, this.stackName); + } + + if (!response) { + throw new Error(`Failed to find a stack ${this.stackName}\n`); + } + + const apiId = response.StackResourceDetail.PhysicalResourceId; + if (!apiId) { + throw new Error(`No ApiId associated with CloudFormation stack ${this.stackName}`); + } + + return apiId; } /** * Gets values by names from cloudformation exports */ - public async getImportValues(names: string[]): Promise { - const exports = await getAWSPagedResults( - this.cloudFormation, - "Exports", - "NextToken", - "NextToken", - new ListExportsCommand({}) - ); + public async getImportValues (names: string[]): Promise { + const exports = await getAWSPagedResults( + this.cloudFormation, + "Exports", + "NextToken", + "NextToken", + new ListExportsCommand({}) + ); // filter Exports by names which we need - const filteredExports = exports.filter((item) => names.indexOf(item.Name) !== -1); - // converting a list of unique values to dict - // [{Name: "export-name", Value: "export-value"}, ...] - > {"export-name": "export-value"} - return filteredExports.reduce((prev, current) => ({...prev, [current.Name]: current.Value}), {}); + const filteredExports = exports.filter((item) => names.indexOf(item.Name) !== -1); + // converting a list of unique values to dict + // [{Name: "export-name", Value: "export-value"}, ...] - > {"export-name": "export-value"} + return filteredExports.reduce((prev, current) => ({ ...prev, [current.Name]: current.Value }), {}); } /** * Returns a description of the specified resource in the specified stack. */ - public async getStack(logicalResourceId: string, stackName: string): Promise { - try { - return await this.cloudFormation.send( - new DescribeStackResourceCommand({ - LogicalResourceId: logicalResourceId, - StackName: stackName, - }) - ); - } catch (err) { - throw new Error(`Failed to find CloudFormation resources with an error: ${err.message}\n`); - } + public async getStack (logicalResourceId: string, stackName: string): Promise { + try { + return await this.cloudFormation.send( + new DescribeStackResourceCommand({ + LogicalResourceId: logicalResourceId, + StackName: stackName + }) + ); + } catch (err) { + throw new Error(`Failed to find CloudFormation resources with an error: ${err.message}\n`); + } } /** * Returns a description of the specified resource in the specified nested stack. */ - public async getNestedStack(logicalResourceId: string, stackName: string) { - // get all stacks from the CloudFormation - const stacks = await getAWSPagedResults( - this.cloudFormation, - "Stacks", - "NextToken", - "NextToken", - new DescribeStacksCommand({}) - ); - - // filter stacks by given stackName and check by nested stack RootId - const regex = new RegExp("/" + stackName + "/"); - const filteredStackNames = stacks - .reduce((acc, stack) => { - if (!stack.RootId) { - return acc; - } - const match = stack.RootId.match(regex); - if (match) { - acc.push(stack.StackName); - } - return acc; - }, []); - - for (const name of filteredStackNames) { - try { - // stop the loop and return the stack details in case the first one found - // in case of error continue the looping - return await this.getStack(logicalResourceId, name); - } catch (err) { - Logging.logWarning(err.message); - } + public async getNestedStack (logicalResourceId: string, stackName: string) { + // get all stacks from the CloudFormation + const stacks = await getAWSPagedResults( + this.cloudFormation, + "Stacks", + "NextToken", + "NextToken", + new DescribeStacksCommand({}) + ); + + // filter stacks by given stackName and check by nested stack RootId + const regex = new RegExp("/" + stackName + "/"); + const filteredStackNames = stacks + .reduce((acc, stack) => { + if (!stack.RootId) { + return acc; + } + const match = stack.RootId.match(regex); + if (match) { + acc.push(stack.StackName); + } + return acc; + }, []); + + for (const name of filteredStackNames) { + try { + // stop the loop and return the stack details in case the first one found + // in case of error continue the looping + return await this.getStack(logicalResourceId, name); + } catch (err) { + Logging.logWarning(err.message); } - return null; + } + return null; } } diff --git a/src/aws/route53-wrapper.ts b/src/aws/route53-wrapper.ts index f93f8173..c45bbb97 100644 --- a/src/aws/route53-wrapper.ts +++ b/src/aws/route53-wrapper.ts @@ -2,83 +2,85 @@ import Globals from "../globals"; import DomainConfig = require("../models/domain-config"); import Logging from "../logging"; import { - ChangeResourceRecordSetsCommand, - HostedZone, - ListHostedZonesCommand, - ListHostedZonesCommandInput, - ListHostedZonesCommandOutput, - Route53Client, RRType, ChangeAction + ChangeResourceRecordSetsCommand, + HostedZone, + ListHostedZonesCommand, + ListHostedZonesCommandInput, + ListHostedZonesCommandOutput, + Route53Client, RRType, ChangeAction } from "@aws-sdk/client-route-53"; -import {getAWSPagedResults} from "../utils"; +import { getAWSPagedResults } from "../utils"; class Route53Wrapper { public route53: Route53Client; - constructor(credentials?: any, region?: string) { - // not null and not undefined - if (credentials) { - this.route53 = new Route53Client({ - credentials, - region: region || Globals.getRegion(), - retryStrategy: Globals.getRetryStrategy(), - requestHandler: Globals.getRequestHandler(), - }); - } else { - this.route53 = new Route53Client({ - region: Globals.getRegion(), - retryStrategy: Globals.getRetryStrategy(), - requestHandler: Globals.getRequestHandler(), - }); - } + constructor (credentials?: any, region?: string) { + // not null and not undefined + if (credentials) { + this.route53 = new Route53Client([{ + credentials, + region: region || Globals.getRegion(), + retryStrategy: Globals.getRetryStrategy(), + requestHandler: Globals.getRequestHandler() + }]); + } else { + this.route53 = new Route53Client([{ + region: Globals.getRegion(), + retryStrategy: Globals.getRetryStrategy(), + requestHandler: Globals.getRequestHandler() + }]); + } } /** * Gets Route53 HostedZoneId from user or from AWS */ - public async getRoute53HostedZoneId(domain: DomainConfig, isHostedZonePrivate?: boolean): Promise { - if (domain.hostedZoneId) { - Logging.logInfo(`Selected specific hostedZoneId ${domain.hostedZoneId}`); - return domain.hostedZoneId; - } + public async getRoute53HostedZoneId (domain: DomainConfig, isHostedZonePrivate?: boolean): Promise { + if (domain.hostedZoneId) { + Logging.logInfo(`Selected specific hostedZoneId ${domain.hostedZoneId}`); + return domain.hostedZoneId; + } - const isPrivateDefined = typeof isHostedZonePrivate !== "undefined"; - if (isPrivateDefined) { - const zoneTypeString = isHostedZonePrivate ? "private" : "public"; - Logging.logInfo(`Filtering to only ${zoneTypeString} zones.`); - } + const isPrivateDefined = typeof isHostedZonePrivate !== "undefined"; + if (isPrivateDefined) { + const zoneTypeString = isHostedZonePrivate ? "private" : "public"; + Logging.logInfo(`Filtering to only ${zoneTypeString} zones.`); + } - let hostedZones = []; - try { - hostedZones = await getAWSPagedResults( - this.route53, - "HostedZones", - "Marker", - "NextMarker", - new ListHostedZonesCommand({}) - ); - Logging.logInfo(`Founded hosted zones list: ${hostedZones.map(zone => zone.Name)}.`); - } catch (err) { - throw new Error(`Unable to list hosted zones in Route53.\n${err.message}`); - } + let hostedZones = []; + try { + hostedZones = await getAWSPagedResults( + this.route53, + "HostedZones", + "Marker", + "NextMarker", + new ListHostedZonesCommand({}) + ); + Logging.logInfo(`Founded hosted zones list: ${hostedZones.map((zone) => zone.Name)}.`); + } catch (err) { + throw new Error(`Unable to list hosted zones in Route53.\n${err.message}`); + } - // removing the first part of the domain name, api.test.com => test.com - const domainNameHost = domain.givenDomainName.substring(domain.givenDomainName.indexOf(".") + 1); - const targetHostedZone = hostedZones - .filter((hostedZone) => { - return !isPrivateDefined || isHostedZonePrivate === hostedZone.Config.PrivateZone; - }) - .filter((hostedZone) => { - const hostedZoneName = hostedZone.Name.replace(/\.$/, ""); - return domainNameHost.endsWith(hostedZoneName); - }) - .sort((zone1, zone2) => zone2.Name.length - zone1.Name.length) - .shift(); + // removing the first part of the domain name, api.test.com => test.com + const domainNameHost = domain.givenDomainName.substring(domain.givenDomainName.indexOf(".") + 1); + console.log(domain.givenDomainName); + const targetHostedZone = hostedZones + .filter((hostedZone) => { + return !isPrivateDefined || isHostedZonePrivate === hostedZone.Config.PrivateZone; + }) + .filter((hostedZone) => { + const hostedZoneName = hostedZone.Name.replace(/\.$/, ""); + console.log(hostedZoneName, domain.givenDomainName === hostedZoneName); + return domain.givenDomainName === hostedZoneName || domainNameHost.endsWith(hostedZoneName); + }) + .sort((zone1, zone2) => zone2.Name.length - zone1.Name.length) + .shift(); - if (targetHostedZone) { - return targetHostedZone.Id.replace("/hostedzone/", ""); - } else { - throw new Error(`Could not find hosted zone '${domain.givenDomainName}'`); - } + if (targetHostedZone) { + return targetHostedZone.Id.replace("/hostedzone/", ""); + } else { + throw new Error(`Could not find hosted zone '${domain.givenDomainName}'`); + } } /** @@ -86,80 +88,80 @@ class Route53Wrapper { * @param action: String descriptor of change to be made. Valid actions are ['UPSERT', 'DELETE'] * @param domain: DomainInfo object containing info about custom domain */ - public async changeResourceRecordSet(action: ChangeAction, domain: DomainConfig): Promise { - if (domain.createRoute53Record === false) { - Logging.logInfo(`Skipping ${action === ChangeAction.DELETE ? "removal" : "creation"} of Route53 record.`); - return; - } - // Set up parameters - const route53HostedZoneId = await this.getRoute53HostedZoneId(domain, domain.hostedZonePrivate); - const route53Params = domain.route53Params; - const route53healthCheck = route53Params.healthCheckId ? {HealthCheckId: route53Params.healthCheckId} : {}; - const domainInfo = domain.domainInfo ?? { - domainName: domain.givenDomainName, - hostedZoneId: route53HostedZoneId, - }; + public async changeResourceRecordSet (action: ChangeAction, domain: DomainConfig): Promise { + if (domain.createRoute53Record === false) { + Logging.logInfo(`Skipping ${action === ChangeAction.DELETE ? "removal" : "creation"} of Route53 record.`); + return; + } + // Set up parameters + const route53HostedZoneId = await this.getRoute53HostedZoneId(domain, domain.hostedZonePrivate); + const route53Params = domain.route53Params; + const route53healthCheck = route53Params.healthCheckId ? { HealthCheckId: route53Params.healthCheckId } : {}; + const domainInfo = domain.domainInfo ?? { + domainName: domain.givenDomainName, + hostedZoneId: route53HostedZoneId + }; - let routingOptions = {}; - if (route53Params.routingPolicy === Globals.routingPolicies.latency) { - routingOptions = { - Region: await this.route53.config.region(), - SetIdentifier: route53Params.setIdentifier ?? domainInfo.domainName, - ...route53healthCheck, - }; - } + let routingOptions = {}; + if (route53Params.routingPolicy === Globals.routingPolicies.latency) { + routingOptions = { + Region: await this.route53.config[0].region, + SetIdentifier: route53Params.setIdentifier ?? domainInfo.domainName, + ...route53healthCheck + }; + } - if (route53Params.routingPolicy === Globals.routingPolicies.weighted) { - routingOptions = { - Weight: route53Params.weight, - SetIdentifier: route53Params.setIdentifier ?? domainInfo.domainName, - ...route53healthCheck, - }; - } + if (route53Params.routingPolicy === Globals.routingPolicies.weighted) { + routingOptions = { + Weight: route53Params.weight, + SetIdentifier: route53Params.setIdentifier ?? domainInfo.domainName, + ...route53healthCheck + }; + } - let hostedZoneIds: string[]; - if (domain.splitHorizonDns) { - hostedZoneIds = await Promise.all([ - this.getRoute53HostedZoneId(domain, false), - this.getRoute53HostedZoneId(domain, true), - ]); - } else { - hostedZoneIds = [route53HostedZoneId]; - } + let hostedZoneIds: string[]; + if (domain.splitHorizonDns) { + hostedZoneIds = await Promise.all([ + this.getRoute53HostedZoneId(domain, false), + this.getRoute53HostedZoneId(domain, true) + ]); + } else { + hostedZoneIds = [route53HostedZoneId]; + } - const recordsToCreate = domain.createRoute53IPv6Record ? [RRType.A, RRType.AAAA] : [RRType.A]; - for (const hostedZoneId of hostedZoneIds) { - const changes = recordsToCreate.map((Type) => ({ - Action: action, - ResourceRecordSet: { - AliasTarget: { - DNSName: domainInfo.domainName, - EvaluateTargetHealth: false, - HostedZoneId: domainInfo.hostedZoneId, - }, - Name: domain.givenDomainName, - Type, - ...routingOptions, - }, - })); + const recordsToCreate = domain.createRoute53IPv6Record ? [RRType.A, RRType.AAAA] : [RRType.A]; + for (const hostedZoneId of hostedZoneIds) { + const changes = recordsToCreate.map((Type) => ({ + Action: action, + ResourceRecordSet: { + AliasTarget: { + DNSName: domainInfo.domainName, + EvaluateTargetHealth: false, + HostedZoneId: domainInfo.hostedZoneId + }, + Name: domain.givenDomainName, + Type, + ...routingOptions + } + })); - const params = { - ChangeBatch: { - Changes: changes, - Comment: `Record created by "${Globals.pluginName}"`, - }, - HostedZoneId: hostedZoneId, - }; - // Make API call - try { - await this.route53.send(new ChangeResourceRecordSetsCommand(params)); - } catch (err) { - throw new Error( + const params = { + ChangeBatch: { + Changes: changes, + Comment: `Record created by "${Globals.pluginName}"` + }, + HostedZoneId: hostedZoneId + }; + // Make API call + try { + await this.route53.send(new ChangeResourceRecordSetsCommand(params)); + } catch (err) { + throw new Error( `Failed to ${action} ${recordsToCreate.join(",")} Alias for '${domain.givenDomainName}':\n ${err.message}` - ); - } + ); } + } } } diff --git a/src/aws/s3-wrapper.ts b/src/aws/s3-wrapper.ts index 501a0e5a..dd4e5e40 100644 --- a/src/aws/s3-wrapper.ts +++ b/src/aws/s3-wrapper.ts @@ -1,49 +1,49 @@ import DomainConfig = require("../models/domain-config"); import Logging from "../logging"; -import {HeadObjectCommand, HeadObjectRequest, S3Client} from "@aws-sdk/client-s3"; +import { HeadObjectCommand, HeadObjectRequest, S3Client } from "@aws-sdk/client-s3"; import Globals from "../globals"; class S3Wrapper { public s3: S3Client; - constructor(credentials?: any) { - this.s3 = new S3Client({ - credentials, - region: Globals.getRegion(), - requestHandler: Globals.getRequestHandler(), - }); + constructor (credentials?: any) { + this.s3 = new S3Client([{ + credentials, + region: Globals.getRegion(), + requestHandler: Globals.getRequestHandler() + }]); } /** * * Checks whether the Mutual TLS certificate exists in S3 or not */ - public async assertTlsCertObjectExists(domain: DomainConfig): Promise { - const {Bucket, Key} = this.extractBucketAndKey(domain.tlsTruststoreUri); - const params: HeadObjectRequest = {Bucket, Key}; - - if (domain.tlsTruststoreVersion) { - params.VersionId = domain.tlsTruststoreVersion; + public async assertTlsCertObjectExists (domain: DomainConfig): Promise { + const { Bucket, Key } = S3Wrapper.extractBucketAndKey(domain.tlsTruststoreUri); + const params: HeadObjectRequest = { Bucket, Key }; + + if (domain.tlsTruststoreVersion) { + params.VersionId = domain.tlsTruststoreVersion; + } + + try { + await this.s3.send(new HeadObjectCommand(params)); + } catch (err) { + if (!err.$metadata || err.$metadata.httpStatusCode !== 403) { + throw Error(`Could not head S3 object at ${domain.tlsTruststoreUri}.\n${err.message}`); } - try { - await this.s3.send(new HeadObjectCommand(params)); - } catch (err) { - if (!err.$metadata || err.$metadata.httpStatusCode !== 403) { - throw Error(`Could not head S3 object at ${domain.tlsTruststoreUri}.\n${err.message}`); - } - - Logging.logWarning( + Logging.logWarning( `Forbidden to check the existence of the S3 object ${domain.tlsTruststoreUri} due to\n${err}` - ); - } + ); + } } /** * * Extracts Bucket and Key from the given s3 uri */ - private extractBucketAndKey(uri: string): { Bucket: string; Key: string } { - const {hostname, pathname} = new URL(uri); - return {Bucket: hostname, Key: pathname.substring(1)}; + private static extractBucketAndKey (uri: string): { Bucket: string; Key: string } { + const { hostname, pathname } = new URL(uri); + return { Bucket: hostname, Key: pathname.substring(1) }; } } diff --git a/src/globals.ts b/src/globals.ts index bdcd9404..baded5fa 100644 --- a/src/globals.ts +++ b/src/globals.ts @@ -1,11 +1,10 @@ -import {ServerlessInstance, ServerlessOptions, ServerlessUtils} from "./types"; -import {fromIni} from "@aws-sdk/credential-providers"; -import {ConfiguredRetryStrategy} from "@smithy/util-retry"; +import { ServerlessInstance, ServerlessOptions, ServerlessUtils } from "./types"; +import { fromIni } from "@aws-sdk/credential-providers"; +import { ConfiguredRetryStrategy } from "@smithy/util-retry"; import { NodeHttpHandler } from "@smithy/node-http-handler"; import { ProxyAgent } from "proxy-agent"; export default class Globals { - public static pluginName = "Serverless Domain Manager"; public static serverless: ServerlessInstance; @@ -23,74 +22,74 @@ export default class Globals { public static reservedBasePaths = ["ping", "sping"]; public static endpointTypes = { - edge: "EDGE", - regional: "REGIONAL", + edge: "EDGE", + regional: "REGIONAL" }; public static apiTypes = { - http: "HTTP", - rest: "REST", - websocket: "WEBSOCKET", + http: "HTTP", + rest: "REST", + websocket: "WEBSOCKET" }; public static gatewayAPIIdKeys = { - [Globals.apiTypes.rest]: "restApiId", - [Globals.apiTypes.websocket]: "websocketApiId", + [Globals.apiTypes.rest]: "restApiId", + [Globals.apiTypes.websocket]: "websocketApiId" }; // Cloud Formation Resource Ids public static CFResourceIds = { - [Globals.apiTypes.http]: "HttpApi", - [Globals.apiTypes.rest]: "ApiGatewayRestApi", - [Globals.apiTypes.websocket]: "WebsocketsApi", + [Globals.apiTypes.http]: "HttpApi", + [Globals.apiTypes.rest]: "ApiGatewayRestApi", + [Globals.apiTypes.websocket]: "WebsocketsApi" }; // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html public static CFFuncNames = { - fnImport: "Fn::ImportValue", - ref: "Ref" + fnImport: "Fn::ImportValue", + ref: "Ref" } - /*eslint camelcase: ["error", {allow: ["^tls_"]}]*/ + /* eslint camelcase: ["error", {allow: ["^tls_"]}] */ public static tlsVersions = { - tls_1_0: "TLS_1_0", - tls_1_2: "TLS_1_2", - tls_1_3: "TLS_1_3", + tls_1_0: "TLS_1_0", + tls_1_2: "TLS_1_2", + tls_1_3: "TLS_1_3" }; public static routingPolicies = { - simple: "simple", - latency: "latency", - weighted: "weighted", + simple: "simple", + latency: "latency", + weighted: "weighted" }; - public static getBaseStage() { - return Globals.options.stage || Globals.serverless.service.provider.stage; + public static getBaseStage () { + return Globals.options.stage || Globals.serverless.service.provider.stage; } - public static getRegion() { - const slsRegion = Globals.options.region || Globals.serverless.service.provider.region; - return slsRegion || Globals.currentRegion || Globals.defaultRegion; + public static getRegion () { + const slsRegion = Globals.options.region || Globals.serverless.service.provider.region; + return slsRegion || Globals.currentRegion || Globals.defaultRegion; } - public static async getProfileCreds(profile: string) { - return await fromIni({profile})(); + public static async getProfileCreds (profile: string) { + return await fromIni({ profile })(); } - public static getRetryStrategy(attempts: number = 3, delay: number = 3000, backoff: number = 500) { - return new ConfiguredRetryStrategy( - attempts, // max attempts. - // This example sets the backoff at 500ms plus 3s per attempt. - // https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/modules/_aws_sdk_util_retry.html#aws-sdkutil-retry - (attempt: number) => backoff + attempt * delay // backoff function. - ) + public static getRetryStrategy (attempts: number = 3, delay: number = 3000, backoff: number = 500) { + return new ConfiguredRetryStrategy( + attempts, // max attempts. + // This example sets the backoff at 500ms plus 3s per attempt. + // https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/modules/_aws_sdk_util_retry.html#aws-sdkutil-retry + (attempt: number) => backoff + attempt * delay // backoff function. + ); } - public static getRequestHandler() { - const proxyAgent = new ProxyAgent(); - return new NodeHttpHandler({ - httpAgent: proxyAgent, - httpsAgent: proxyAgent - }); + public static getRequestHandler () { + const proxyAgent = new ProxyAgent(); + return new NodeHttpHandler({ + httpAgent: proxyAgent, + httpsAgent: proxyAgent + }); } } diff --git a/src/index.ts b/src/index.ts index 9b830036..c36bc82a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,18 +6,17 @@ import Route53Wrapper = require("./aws/route53-wrapper"); import S3Wrapper = require("./aws/s3-wrapper"); import DomainConfig = require("./models/domain-config"); import Globals from "./globals"; -import {CustomDomain, ServerlessInstance, ServerlessOptions, ServerlessUtils} from "./types"; -import {sleep} from "./utils"; +import { CustomDomain, ServerlessInstance, ServerlessOptions, ServerlessUtils } from "./types"; +import { sleep } from "./utils"; import APIGatewayV1Wrapper = require("./aws/api-gateway-v1-wrapper"); import APIGatewayV2Wrapper = require("./aws/api-gateway-v2-wrapper"); import APIGatewayBase = require("./models/apigateway-base"); import Logging from "./logging"; -import {loadConfig} from "@smithy/node-config-provider"; -import {NODE_REGION_CONFIG_FILE_OPTIONS, NODE_REGION_CONFIG_OPTIONS} from "@smithy/config-resolver"; -import {ChangeAction} from "@aws-sdk/client-route-53"; +import { loadConfig } from "@smithy/node-config-provider"; +import { NODE_REGION_CONFIG_FILE_OPTIONS, NODE_REGION_CONFIG_OPTIONS } from "@smithy/config-resolver"; +import { ChangeAction } from "@aws-sdk/client-route-53"; class ServerlessCustomDomain { - // AWS SDK resources public apiGatewayV1Wrapper: APIGatewayV1Wrapper; public apiGatewayV2Wrapper: APIGatewayV2Wrapper; @@ -33,459 +32,457 @@ class ServerlessCustomDomain { // Domain Manager specific properties public domains: DomainConfig[] = []; - constructor(serverless: ServerlessInstance, options: ServerlessOptions, v3Utils?: ServerlessUtils) { - this.serverless = serverless; - Globals.serverless = serverless; - - this.options = options; - Globals.options = options; - - if (v3Utils?.log) { - Globals.v3Utils = v3Utils; + constructor (serverless: ServerlessInstance, options: ServerlessOptions, v3Utils?: ServerlessUtils) { + this.serverless = serverless; + Globals.serverless = serverless; + + this.options = options; + Globals.options = options; + + if (v3Utils?.log) { + Globals.v3Utils = v3Utils; + } + + /* eslint camelcase: ["error", {allow: ["create_domain", "delete_domain"]}] */ + this.commands = { + create_domain: { + lifecycleEvents: [ + "create", + "initialize" + ], + usage: "Creates a domain using the domain name defined in the serverless file" + }, + delete_domain: { + lifecycleEvents: [ + "delete", + "initialize" + ], + usage: "Deletes a domain using the domain name defined in the serverless file" } - - /*eslint camelcase: ["error", {allow: ["create_domain", "delete_domain"]}]*/ - this.commands = { - create_domain: { - lifecycleEvents: [ - "create", - "initialize", - ], - usage: "Creates a domain using the domain name defined in the serverless file", - }, - delete_domain: { - lifecycleEvents: [ - "delete", - "initialize", - ], - usage: "Deletes a domain using the domain name defined in the serverless file", - }, - }; - this.hooks = { - "after:deploy:deploy": this.hookWrapper.bind(this, this.setupBasePathMappings), - "after:info:info": this.hookWrapper.bind(this, this.domainSummaries), - "before:deploy:deploy": this.hookWrapper.bind(this, this.createOrGetDomainForCfOutputs), - "before:remove:remove": this.hookWrapper.bind(this, this.removeBasePathMappings), - "create_domain:create": this.hookWrapper.bind(this, this.createDomains), - "delete_domain:delete": this.hookWrapper.bind(this, this.deleteDomains), - }; + }; + this.hooks = { + "after:deploy:deploy": this.hookWrapper.bind(this, this.setupBasePathMappings), + "after:info:info": this.hookWrapper.bind(this, this.domainSummaries), + "before:deploy:deploy": this.hookWrapper.bind(this, this.createOrGetDomainForCfOutputs), + "before:remove:remove": this.hookWrapper.bind(this, this.removeBasePathMappings), + "create_domain:create": this.hookWrapper.bind(this, this.createDomains), + "delete_domain:delete": this.hookWrapper.bind(this, this.deleteDomains) + }; } /** * Wrapper for lifecycle function, initializes variables and checks if enabled. * @param lifecycleFunc lifecycle function that actually does desired action */ - public async hookWrapper(lifecycleFunc: any) { - // check if `customDomain` or `customDomains` config exists - this.validateConfigExists(); - // init config variables - this.initializeVariables(); - // Validate the domain configurations - this.validateDomainConfigs(); - // setup AWS resources - await this.initSLSCredentials(); - await this.initAWSRegion(); - await this.initAWSResources(); - - // start of the legacy AWS SDK V2 creds support - // TODO: remove it in case serverless will add V3 support - const domain = this.domains[0]; - if (domain) { - try { - await this.getApiGateway(domain).getCustomDomain(domain); - } catch (error) { - if (error.message.includes('Could not load credentials from any providers')) { - Globals.credentials = this.serverless.providers.aws.getCredentials(); - await this.initAWSResources(); - } - } + public async hookWrapper (lifecycleFunc: any) { + // check if `customDomain` or `customDomains` config exists + this.validateConfigExists(); + // init config variables + this.initializeVariables(); + // Validate the domain configurations + this.validateDomainConfigs(); + // setup AWS resources + await this.initSLSCredentials(); + await this.initAWSRegion(); + await this.initAWSResources(); + + // start of the legacy AWS SDK V2 creds support + // TODO: remove it in case serverless will add V3 support + const domain = this.domains[0]; + if (domain) { + try { + await this.getApiGateway(domain).getCustomDomain(domain); + } catch (error) { + if (error.message.includes("Could not load credentials from any providers")) { + Globals.credentials = this.serverless.providers.aws.getCredentials(); + await this.initAWSResources(); + } } - // end of the legacy AWS SDK V2 creds support + } + // end of the legacy AWS SDK V2 creds support - return lifecycleFunc.call(this); + return lifecycleFunc.call(this); } /** * Validate if the plugin config exists */ - public validateConfigExists(): void { - // Make sure customDomain configuration exists, stop if not - const config = this.serverless.service.custom; - const domainExists = config && typeof config.customDomain !== "undefined"; - const domainsExists = config && typeof config.customDomains !== "undefined"; - if (typeof config === "undefined" || (!domainExists && !domainsExists)) { - throw new Error(`${Globals.pluginName}: Plugin configuration is missing.`); - } + public validateConfigExists (): void { + // Make sure customDomain configuration exists, stop if not + const config = this.serverless.service.custom; + const domainExists = config && typeof config.customDomain !== "undefined"; + const domainsExists = config && typeof config.customDomains !== "undefined"; + if (typeof config === "undefined" || (!domainExists && !domainsExists)) { + throw new Error(`${Globals.pluginName}: Plugin configuration is missing.`); + } } /** * Goes through custom domain property and initializes local variables and cloudformation template */ - public initializeVariables(): void { - const config = this.serverless.service.custom; - const domainConfig = config.customDomain ? [config.customDomain] : []; - const domainsConfig = config.customDomains || []; - const customDomains: CustomDomain[] = domainConfig.concat(domainsConfig); - - // Loop over the domain configurations and populate the domains array with DomainConfigs - this.domains = []; - customDomains.forEach((domain) => { - // If the key of the item in config is an API type then using per API type domain structure - let isTypeConfigFound = false; - Object.keys(Globals.apiTypes).forEach((apiType) => { - const domainTypeConfig = domain[apiType]; - if (domainTypeConfig) { - domainTypeConfig.apiType = apiType; - this.domains.push(new DomainConfig(domainTypeConfig)); - isTypeConfigFound = true; - } - }); - - if (!isTypeConfigFound) { - this.domains.push(new DomainConfig(domain)); - } + public initializeVariables (): void { + const config = this.serverless.service.custom; + const domainConfig = config.customDomain ? [config.customDomain] : []; + const domainsConfig = config.customDomains || []; + const customDomains: CustomDomain[] = domainConfig.concat(domainsConfig); + + // Loop over the domain configurations and populate the domains array with DomainConfigs + this.domains = []; + customDomains.forEach((domain) => { + // If the key of the item in config is an API type then using per API type domain structure + let isTypeConfigFound = false; + Object.keys(Globals.apiTypes).forEach((apiType) => { + const domainTypeConfig = domain[apiType]; + if (domainTypeConfig) { + domainTypeConfig.apiType = apiType; + this.domains.push(new DomainConfig(domainTypeConfig)); + isTypeConfigFound = true; + } }); - // Filter inactive domains - this.domains = this.domains.filter((domain) => domain.enabled); + if (!isTypeConfigFound) { + this.domains.push(new DomainConfig(domain)); + } + }); + + // Filter inactive domains + this.domains = this.domains.filter((domain) => domain.enabled); } /** * Validates domain configs to make sure they are valid, ie HTTP api cannot be used with EDGE domain */ - public validateDomainConfigs() { - this.domains.forEach((domain) => { - if (domain.allowPathMatching) { - Logging.logWarning(`"allowPathMatching" is set for ${domain.givenDomainName}. + public validateDomainConfigs () { + this.domains.forEach((domain) => { + if (domain.allowPathMatching) { + Logging.logWarning(`"allowPathMatching" is set for ${domain.givenDomainName}. This should only be used when migrating a path to a different API type. e.g. REST to HTTP.`); - } + } - if (domain.apiType === Globals.apiTypes.rest) { - // Currently no validation for REST API types + if (domain.apiType === Globals.apiTypes.rest) { + // Currently no validation for REST API types - } else if (domain.apiType === Globals.apiTypes.http) { - // HTTP APIs do not support edge domains - if (domain.endpointType === Globals.endpointTypes.edge) { - // https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html - throw Error( - "'EDGE' endpointType is not compatible with HTTP APIs\n" + + } else if (domain.apiType === Globals.apiTypes.http) { + // HTTP APIs do not support edge domains + if (domain.endpointType === Globals.endpointTypes.edge) { + // https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html + throw Error( + "'EDGE' endpointType is not compatible with HTTP APIs\n" + "https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html" - ); - } - - } else if (domain.apiType === Globals.apiTypes.websocket) { - // Websocket APIs do not support edge domains - if (domain.endpointType === Globals.endpointTypes.edge) { - throw Error("'EDGE' endpointType is not compatible with WebSocket APIs"); - } - } - }); + ); + } + } else if (domain.apiType === Globals.apiTypes.websocket) { + // Websocket APIs do not support edge domains + if (domain.endpointType === Globals.endpointTypes.edge) { + throw Error("'EDGE' endpointType is not compatible with WebSocket APIs"); + } + } + }); } /** * Init AWS credentials based on sls `provider.profile` */ - public async initSLSCredentials(): Promise { - const slsProfile = Globals.options["aws-profile"] || Globals.serverless.service.provider.profile; - Globals.credentials = slsProfile ? await Globals.getProfileCreds(slsProfile) : null; + public async initSLSCredentials (): Promise { + const slsProfile = Globals.options["aws-profile"] || Globals.serverless.service.provider.profile; + Globals.credentials = slsProfile ? await Globals.getProfileCreds(slsProfile) : null; } /** * Init AWS current region based on Node options */ - public async initAWSRegion(): Promise { - try { - Globals.currentRegion = await loadConfig(NODE_REGION_CONFIG_OPTIONS, NODE_REGION_CONFIG_FILE_OPTIONS)(); - } catch (err) { - Logging.logInfo("Node region was not found."); - } + public async initAWSRegion (): Promise { + try { + Globals.currentRegion = await loadConfig(NODE_REGION_CONFIG_OPTIONS, NODE_REGION_CONFIG_FILE_OPTIONS)(); + } catch (err) { + Logging.logInfo("Node region was not found."); + } } /** * Setup AWS resources */ - public async initAWSResources(): Promise { - this.apiGatewayV1Wrapper = new APIGatewayV1Wrapper(Globals.credentials); - this.apiGatewayV2Wrapper = new APIGatewayV2Wrapper(Globals.credentials); - this.cloudFormationWrapper = new CloudFormationWrapper(Globals.credentials); - this.s3Wrapper = new S3Wrapper(Globals.credentials); + public async initAWSResources (): Promise { + this.apiGatewayV1Wrapper = new APIGatewayV1Wrapper(Globals.credentials); + this.apiGatewayV2Wrapper = new APIGatewayV2Wrapper(Globals.credentials); + this.cloudFormationWrapper = new CloudFormationWrapper(Globals.credentials); + this.s3Wrapper = new S3Wrapper(Globals.credentials); } - public getApiGateway(domain: DomainConfig): APIGatewayBase { - // 1. https://stackoverflow.com/questions/72339224/aws-v1-vs-v2-api-for-listing-apis-on-aws-api-gateway-return-different-data-for-t - // 2. https://aws.amazon.com/blogs/compute/announcing-http-apis-for-amazon-api-gateway/ - // There are currently two API Gateway namespaces for managing API Gateway deployments. - // The API V1 namespace represents REST APIs and API V2 represents WebSocket APIs and the new HTTP APIs. - // You can create an HTTP API by using the AWS Management Console, CLI, APIs, CloudFormation, SDKs, or the Serverless Application Model (SAM). - if (domain.apiType !== Globals.apiTypes.rest) { - return this.apiGatewayV2Wrapper; - } - - // multi-level base path mapping is supported by Gateway V2 - // https://github.com/amplify-education/serverless-domain-manager/issues/558 - // https://aws.amazon.com/blogs/compute/using-multiple-segments-in-amazon-api-gateway-base-path-mapping/ - if (domain.basePath.includes("/")) { - return this.apiGatewayV2Wrapper; - } - - return this.apiGatewayV1Wrapper; + public getApiGateway (domain: DomainConfig): APIGatewayBase { + // 1. https://stackoverflow.com/questions/72339224/aws-v1-vs-v2-api-for-listing-apis-on-aws-api-gateway-return-different-data-for-t + // 2. https://aws.amazon.com/blogs/compute/announcing-http-apis-for-amazon-api-gateway/ + // There are currently two API Gateway namespaces for managing API Gateway deployments. + // The API V1 namespace represents REST APIs and API V2 represents WebSocket APIs and the new HTTP APIs. + // You can create an HTTP API by using the AWS Management Console, CLI, APIs, CloudFormation, SDKs, or the Serverless Application Model (SAM). + if (domain.apiType !== Globals.apiTypes.rest) { + return this.apiGatewayV2Wrapper; + } + + // multi-level base path mapping is supported by Gateway V2 + // https://github.com/amplify-education/serverless-domain-manager/issues/558 + // https://aws.amazon.com/blogs/compute/using-multiple-segments-in-amazon-api-gateway-base-path-mapping/ + if (domain.basePath.includes("/")) { + return this.apiGatewayV2Wrapper; + } + + return this.apiGatewayV1Wrapper; } /** * Lifecycle function to create a domain * Wraps creating a domain and resource record set */ - public async createDomains(): Promise { - await Promise.all(this.domains.map(async (domain) => { - await this.createDomain(domain); - })); + public async createDomains (): Promise { + await Promise.all(this.domains.map(async (domain) => { + await this.createDomain(domain); + })); } /** * Lifecycle function to create a domain * Wraps creating a domain and resource record set */ - public async createDomain(domain: DomainConfig): Promise { - const creationProgress = Globals.v3Utils && Globals.v3Utils.progress.get(`create-${domain.givenDomainName}`); - const route53Creds = domain.route53Profile ? await Globals.getProfileCreds(domain.route53Profile) : Globals.credentials; - - const apiGateway = this.getApiGateway(domain); - const route53 = new Route53Wrapper(route53Creds, domain.route53Region); - const acm = new ACMWrapper(Globals.credentials, domain.endpointType); - - domain.domainInfo = await apiGateway.getCustomDomain(domain); - - try { - if (!domain.domainInfo) { - if (domain.tlsTruststoreUri) { - await this.s3Wrapper.assertTlsCertObjectExists(domain); - } - if (!domain.certificateArn) { - domain.certificateArn = await acm.getCertArn(domain); - } - domain.domainInfo = await apiGateway.createCustomDomain(domain); - Logging.logInfo(`Custom domain '${domain.givenDomainName}' was created. + public async createDomain (domain: DomainConfig): Promise { + const creationProgress = Globals.v3Utils && Globals.v3Utils.progress.get(`create-${domain.givenDomainName}`); + const route53Creds = domain.route53Profile ? await Globals.getProfileCreds(domain.route53Profile) : Globals.credentials; + + const apiGateway = this.getApiGateway(domain); + const route53 = new Route53Wrapper(route53Creds, domain.route53Region); + const acm = new ACMWrapper(Globals.credentials, domain.endpointType); + + domain.domainInfo = await apiGateway.getCustomDomain(domain); + + try { + if (!domain.domainInfo) { + if (domain.tlsTruststoreUri) { + await this.s3Wrapper.assertTlsCertObjectExists(domain); + } + if (!domain.certificateArn) { + domain.certificateArn = await acm.getCertArn(domain); + } + domain.domainInfo = await apiGateway.createCustomDomain(domain); + Logging.logInfo(`Custom domain '${domain.givenDomainName}' was created. New domains may take up to 40 minutes to be initialized.`); - } else { - Logging.logInfo(`Custom domain '${domain.givenDomainName}' already exists.`); - } - Logging.logInfo(`Creating/updating route53 record for '${domain.givenDomainName}'.`); - await route53.changeResourceRecordSet(ChangeAction.UPSERT, domain); - } catch (err) { - throw new Error(`Unable to create domain '${domain.givenDomainName}':\n${err.message}`); - } finally { - if (creationProgress) { - creationProgress.remove(); - } + } else { + Logging.logInfo(`Custom domain '${domain.givenDomainName}' already exists.`); + } + Logging.logInfo(`Creating/updating route53 record for '${domain.givenDomainName}'.`); + await route53.changeResourceRecordSet(ChangeAction.UPSERT, domain); + } catch (err) { + throw new Error(`Unable to create domain '${domain.givenDomainName}':\n${err.message}`); + } finally { + if (creationProgress) { + creationProgress.remove(); } + } } /** * Lifecycle function to delete a domain * Wraps deleting a domain and resource record set */ - public async deleteDomains(): Promise { - await Promise.all(this.domains.map(async (domain) => { - await this.deleteDomain(domain); - })); + public async deleteDomains (): Promise { + await Promise.all(this.domains.map(async (domain) => { + await this.deleteDomain(domain); + })); } /** * Wraps deleting a domain and resource record set */ - public async deleteDomain(domain: DomainConfig): Promise { - const apiGateway = this.getApiGateway(domain); - const route53Creds = domain.route53Profile ? await Globals.getProfileCreds(domain.route53Profile) : null; - const route53 = new Route53Wrapper(route53Creds, domain.route53Region); + public async deleteDomain (domain: DomainConfig): Promise { + const apiGateway = this.getApiGateway(domain); + const route53Creds = domain.route53Profile ? await Globals.getProfileCreds(domain.route53Profile) : null; + const route53 = new Route53Wrapper(route53Creds, domain.route53Region); - domain.domainInfo = await apiGateway.getCustomDomain(domain); - try { - if (domain.domainInfo) { - await apiGateway.deleteCustomDomain(domain); - await route53.changeResourceRecordSet(ChangeAction.DELETE, domain); - domain.domainInfo = null; - Logging.logInfo(`Custom domain ${domain.givenDomainName} was deleted.`); - } else { - Logging.logInfo(`Custom domain ${domain.givenDomainName} does not exist.`); - } - } catch (err) { - throw new Error(`Unable to delete domain '${domain.givenDomainName}':\n${err.message}`); + domain.domainInfo = await apiGateway.getCustomDomain(domain); + try { + if (domain.domainInfo) { + await apiGateway.deleteCustomDomain(domain); + await route53.changeResourceRecordSet(ChangeAction.DELETE, domain); + domain.domainInfo = null; + Logging.logInfo(`Custom domain ${domain.givenDomainName} was deleted.`); + } else { + Logging.logInfo(`Custom domain ${domain.givenDomainName} does not exist.`); } + } catch (err) { + throw new Error(`Unable to delete domain '${domain.givenDomainName}':\n${err.message}`); + } } /** * Lifecycle function to createDomain before deploy and add domain info to the CloudFormation stack's Outputs */ - public async createOrGetDomainForCfOutputs(): Promise { - await Promise.all(this.domains.map(async (domain) => { - if (domain.autoDomain) { - Logging.logInfo("Creating domain name before deploy."); - await this.createDomain(domain); - } + public async createOrGetDomainForCfOutputs (): Promise { + await Promise.all(this.domains.map(async (domain) => { + if (domain.autoDomain) { + Logging.logInfo("Creating domain name before deploy."); + await this.createDomain(domain); + } - const apiGateway = this.getApiGateway(domain); - domain.domainInfo = await apiGateway.getCustomDomain(domain); + const apiGateway = this.getApiGateway(domain); + domain.domainInfo = await apiGateway.getCustomDomain(domain); - if (domain.autoDomain) { - const atLeastOneDoesNotExist = () => this.domains.some((d) => !d.domainInfo); - const maxWaitFor = parseInt(domain.autoDomainWaitFor, 10) || 120; - const pollInterval = 3; - for (let i = 0; i * pollInterval < maxWaitFor && atLeastOneDoesNotExist() === true; i++) { - Logging.logInfo(` + if (domain.autoDomain) { + const atLeastOneDoesNotExist = () => this.domains.some((d) => !d.domainInfo); + const maxWaitFor = parseInt(domain.autoDomainWaitFor, 10) || 120; + const pollInterval = 3; + for (let i = 0; i * pollInterval < maxWaitFor && atLeastOneDoesNotExist() === true; i++) { + Logging.logInfo(` Poll #${i + 1}: polling every ${pollInterval} seconds for domain to exist or until ${maxWaitFor} seconds have elapsed before starting deployment `); - await sleep(pollInterval); - domain.domainInfo = await apiGateway.getCustomDomain(domain); - } - } - this.addOutputs(domain); - })); + await sleep(pollInterval); + domain.domainInfo = await apiGateway.getCustomDomain(domain); + } + } + this.addOutputs(domain); + })); } /** * Lifecycle function to create basepath mapping * Wraps creation of basepath mapping and adds domain name info as output to cloudformation stack */ - public async setupBasePathMappings(): Promise { - await Promise.all(this.domains.map(async (domain) => { - domain.apiId = await this.cloudFormationWrapper.findApiId(domain.apiType); + public async setupBasePathMappings (): Promise { + await Promise.all(this.domains.map(async (domain) => { + domain.apiId = await this.cloudFormationWrapper.findApiId(domain.apiType); - const apiGateway = this.getApiGateway(domain); - const mappings = await apiGateway.getBasePathMappings(domain); - - const filteredMappings = mappings.filter((mapping) => { - return mapping.apiId === domain.apiId || ( - mapping.basePath === domain.basePath && domain.allowPathMatching - ); - }); - domain.apiMapping = filteredMappings ? filteredMappings[0] : null; - domain.domainInfo = await apiGateway.getCustomDomain(domain); + const apiGateway = this.getApiGateway(domain); + const mappings = await apiGateway.getBasePathMappings(domain); - if (!domain.apiMapping) { - await apiGateway.createBasePathMapping(domain); - } else { - await apiGateway.updateBasePathMapping(domain); - } - })).finally(() => { - Logging.printDomainSummary(this.domains); + const filteredMappings = mappings.filter((mapping) => { + return mapping.apiId === domain.apiId || ( + mapping.basePath === domain.basePath && domain.allowPathMatching + ); }); + domain.apiMapping = filteredMappings ? filteredMappings[0] : null; + domain.domainInfo = await apiGateway.getCustomDomain(domain); + + if (!domain.apiMapping) { + await apiGateway.createBasePathMapping(domain); + } else { + await apiGateway.updateBasePathMapping(domain); + } + })).finally(() => { + Logging.printDomainSummary(this.domains); + }); } /** * Lifecycle function to delete basepath mapping * Wraps deletion of basepath mapping */ - public async removeBasePathMappings(): Promise { - await Promise.all(this.domains.map(async (domain) => { - let externalBasePathExists = false; - try { - domain.apiId = await this.cloudFormationWrapper.findApiId(domain.apiType); - // Unable to find the corresponding API, manual clean up will be required - if (!domain.apiId) { - Logging.logInfo(`Unable to find corresponding API for '${domain.givenDomainName}', + public async removeBasePathMappings (): Promise { + await Promise.all(this.domains.map(async (domain) => { + let externalBasePathExists = false; + try { + domain.apiId = await this.cloudFormationWrapper.findApiId(domain.apiType); + // Unable to find the corresponding API, manual clean up will be required + if (!domain.apiId) { + Logging.logInfo(`Unable to find corresponding API for '${domain.givenDomainName}', API Mappings may need to be manually removed.`); - } else { - const apiGateway = this.getApiGateway(domain); - const mappings = await apiGateway.getBasePathMappings(domain); - const filteredMappings = mappings.filter((mapping) => { - return mapping.apiId === domain.apiId || ( - mapping.basePath === domain.basePath && domain.allowPathMatching - ); - }); - if (domain.preserveExternalPathMappings) { - externalBasePathExists = mappings.length > filteredMappings.length; - } - domain.apiMapping = filteredMappings ? filteredMappings[0] : null; - if (domain.apiMapping) { - await apiGateway.deleteBasePathMapping(domain); - } else { - Logging.logWarning( + } else { + const apiGateway = this.getApiGateway(domain); + const mappings = await apiGateway.getBasePathMappings(domain); + const filteredMappings = mappings.filter((mapping) => { + return mapping.apiId === domain.apiId || ( + mapping.basePath === domain.basePath && domain.allowPathMatching + ); + }); + if (domain.preserveExternalPathMappings) { + externalBasePathExists = mappings.length > filteredMappings.length; + } + domain.apiMapping = filteredMappings ? filteredMappings[0] : null; + if (domain.apiMapping) { + await apiGateway.deleteBasePathMapping(domain); + } else { + Logging.logWarning( `Api mapping was not found for '${domain.givenDomainName}'. Skipping base path deletion.` - ); - } - } - } catch (err) { - if (err.message.indexOf("Failed to find CloudFormation") > -1) { - Logging.logWarning(`Unable to find Cloudformation Stack for ${domain.givenDomainName}, + ); + } + } + } catch (err) { + if (err.message.indexOf("Failed to find CloudFormation") > -1) { + Logging.logWarning(`Unable to find Cloudformation Stack for ${domain.givenDomainName}, API Mappings may need to be manually removed.`); - } else { - Logging.logWarning( + } else { + Logging.logWarning( `Unable to remove base path mappings for '${domain.givenDomainName}':\n${err.message}` - ); - } - } + ); + } + } - if (domain.autoDomain === true && !externalBasePathExists) { - Logging.logInfo("Deleting domain name after removing base path mapping."); - await this.deleteDomain(domain); - } - })); + if (domain.autoDomain === true && !externalBasePathExists) { + Logging.logInfo("Deleting domain name after removing base path mapping."); + await this.deleteDomain(domain); + } + })); } /** * Lifecycle function to print domain summary * Wraps printing of all domain manager related info */ - public async domainSummaries(): Promise { - await Promise.all(this.domains.map(async (domain) => { - const apiGateway = this.getApiGateway(domain); - domain.domainInfo = await apiGateway.getCustomDomain(domain); - })).finally(() => { - Logging.printDomainSummary(this.domains); - }); + public async domainSummaries (): Promise { + await Promise.all(this.domains.map(async (domain) => { + const apiGateway = this.getApiGateway(domain); + domain.domainInfo = await apiGateway.getCustomDomain(domain); + })).finally(() => { + Logging.printDomainSummary(this.domains); + }); } /** * Adds the domain name and distribution domain name to the CloudFormation outputs */ - public addOutputs(domain: DomainConfig): void { - const service = this.serverless.service; - if (!service.provider.compiledCloudFormationTemplate.Outputs) { - service.provider.compiledCloudFormationTemplate.Outputs = {}; - } - - // Defaults for REST and backwards compatibility - let distributionDomainNameOutputKey = "DistributionDomainName"; - let domainNameOutputKey = "DomainName"; - let hostedZoneIdOutputKey = "HostedZoneId"; - - if (domain.apiType === Globals.apiTypes.http) { - distributionDomainNameOutputKey += "Http"; - domainNameOutputKey += "Http"; - hostedZoneIdOutputKey += "Http"; - - } else if (domain.apiType === Globals.apiTypes.websocket) { - distributionDomainNameOutputKey += "Websocket"; - domainNameOutputKey += "Websocket"; - hostedZoneIdOutputKey += "Websocket"; + public addOutputs (domain: DomainConfig): void { + const service = this.serverless.service; + if (!service.provider.compiledCloudFormationTemplate.Outputs) { + service.provider.compiledCloudFormationTemplate.Outputs = {}; + } + + // Defaults for REST and backwards compatibility + let distributionDomainNameOutputKey = "DistributionDomainName"; + let domainNameOutputKey = "DomainName"; + let hostedZoneIdOutputKey = "HostedZoneId"; + + if (domain.apiType === Globals.apiTypes.http) { + distributionDomainNameOutputKey += "Http"; + domainNameOutputKey += "Http"; + hostedZoneIdOutputKey += "Http"; + } else if (domain.apiType === Globals.apiTypes.websocket) { + distributionDomainNameOutputKey += "Websocket"; + domainNameOutputKey += "Websocket"; + hostedZoneIdOutputKey += "Websocket"; + } + + // for the CloudFormation stack we should use the `base` stage not the plugin custom stage + // Remove all special characters + const safeStage = Globals.getBaseStage().replace(/[^a-zA-Z\d]/g, ""); + service.provider.compiledCloudFormationTemplate.Outputs[domainNameOutputKey] = { + Value: domain.givenDomainName, + Export: { + Name: `sls-${service.service}-${safeStage}-${domainNameOutputKey}` } - - // for the CloudFormation stack we should use the `base` stage not the plugin custom stage - // Remove all special characters - const safeStage = Globals.getBaseStage().replace(/[^a-zA-Z\d]/g, ""); - service.provider.compiledCloudFormationTemplate.Outputs[domainNameOutputKey] = { - Value: domain.givenDomainName, - Export: { - Name: `sls-${service.service}-${safeStage}-${domainNameOutputKey}`, - }, + }; + + if (domain.domainInfo) { + service.provider.compiledCloudFormationTemplate.Outputs[distributionDomainNameOutputKey] = { + Value: domain.domainInfo.domainName, + Export: { + Name: `sls-${service.service}-${safeStage}-${distributionDomainNameOutputKey}` + } }; - - if (domain.domainInfo) { - service.provider.compiledCloudFormationTemplate.Outputs[distributionDomainNameOutputKey] = { - Value: domain.domainInfo.domainName, - Export: { - Name: `sls-${service.service}-${safeStage}-${distributionDomainNameOutputKey}`, - }, - }; - service.provider.compiledCloudFormationTemplate.Outputs[hostedZoneIdOutputKey] = { - Value: domain.domainInfo.hostedZoneId, - Export: { - Name: `sls-${service.service}-${safeStage}-${hostedZoneIdOutputKey}`, - }, - }; - } + service.provider.compiledCloudFormationTemplate.Outputs[hostedZoneIdOutputKey] = { + Value: domain.domainInfo.hostedZoneId, + Export: { + Name: `sls-${service.service}-${safeStage}-${hostedZoneIdOutputKey}` + } + }; + } } } diff --git a/src/logging.ts b/src/logging.ts index c843b765..7d8e10ec 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -2,66 +2,66 @@ import Globals from "./globals"; import DomainConfig = require("./models/domain-config"); export default class Logging { - public static cliLog(prefix: string, message: string): void { - Globals.serverless.cli.log(`${prefix} ${message}`, Globals.pluginName); - } + public static cliLog (prefix: string, message: string): void { + Globals.serverless.cli.log(`${prefix} ${message}`, Globals.pluginName); + } - /** + /** * Logs error message */ - public static logError(message: string): void { - if (Globals.v3Utils) { - Globals.v3Utils.log.error(message); - } else { - Logging.cliLog("[Error]", message); - } + public static logError (message: string): void { + if (Globals.v3Utils) { + Globals.v3Utils.log.error(message); + } else { + Logging.cliLog("[Error]", message); } + } - /** + /** * Logs info message */ - public static logInfo(message: string): void { - if (Globals.v3Utils) { - Globals.v3Utils.log.verbose(message); - } else { - Logging.cliLog("[Info]", message); - } + public static logInfo (message: string): void { + if (Globals.v3Utils) { + Globals.v3Utils.log.verbose(message); + } else { + Logging.cliLog("[Info]", message); } + } - /** + /** * Logs warning message */ - public static logWarning(message: string): void { - if (Globals.v3Utils) { - Globals.v3Utils.log.warning(message); - } else { - Logging.cliLog("[WARNING]", message); - } + public static logWarning (message: string): void { + if (Globals.v3Utils) { + Globals.v3Utils.log.warning(message); + } else { + Logging.cliLog("[WARNING]", message); } + } - /** + /** * Prints out a summary of all domain manager related info */ - public static printDomainSummary(domains: DomainConfig[]): void { - const summaryList = []; - domains.forEach((domain) => { - if (domain.domainInfo) { - summaryList.push(`Domain Name: ${domain.givenDomainName}`); - summaryList.push(`Target Domain: ${domain.domainInfo.domainName}`); - summaryList.push(`Hosted Zone Id: ${domain.domainInfo.hostedZoneId}`); - } - }); - // don't print summary if summaryList is empty - if (!summaryList.length) { - return; - } - if (Globals.v3Utils) { - Globals.serverless.addServiceOutputSection(Globals.pluginName, summaryList); - } else { - Logging.cliLog("[Summary]", "Distribution Domain Name"); - summaryList.forEach((item) => { - Logging.cliLog("", `${item}`); - }); - } + public static printDomainSummary (domains: DomainConfig[]): void { + const summaryList = []; + domains.forEach((domain) => { + if (domain.domainInfo) { + summaryList.push(`Domain Name: ${domain.givenDomainName}`); + summaryList.push(`Target Domain: ${domain.domainInfo.domainName}`); + summaryList.push(`Hosted Zone Id: ${domain.domainInfo.hostedZoneId}`); + } + }); + // don't print summary if summaryList is empty + if (!summaryList.length) { + return; + } + if (Globals.v3Utils) { + Globals.serverless.addServiceOutputSection(Globals.pluginName, summaryList); + } else { + Logging.cliLog("[Summary]", "Distribution Domain Name"); + summaryList.forEach((item) => { + Logging.cliLog("", `${item}`); + }); } + } } diff --git a/src/models/api-gateway-map.ts b/src/models/api-gateway-map.ts index 2dd10b5f..c19caf4b 100644 --- a/src/models/api-gateway-map.ts +++ b/src/models/api-gateway-map.ts @@ -4,11 +4,11 @@ class ApiGatewayMap { public stage: string; public apiMappingId: string | null; - constructor(apiId: string, basePath: string, stage: string, apiMappingId: string | null) { - this.apiId = apiId; - this.basePath = basePath; - this.stage = stage; - this.apiMappingId = apiMappingId; + constructor (apiId: string, basePath: string, stage: string, apiMappingId: string | null) { + this.apiId = apiId; + this.basePath = basePath; + this.stage = stage; + this.apiMappingId = apiMappingId; } } diff --git a/src/models/domain-config.ts b/src/models/domain-config.ts index 18f68133..6eacdfd6 100644 --- a/src/models/domain-config.ts +++ b/src/models/domain-config.ts @@ -4,157 +4,156 @@ import DomainInfo = require("./domain-info"); import Globals from "../globals"; -import {CustomDomain, Route53Params} from "../types"; -import {evaluateBoolean} from "../utils"; +import { CustomDomain, Route53Params } from "../types"; +import { evaluateBoolean } from "../utils"; import ApiGatewayMap = require("./api-gateway-map"); import Logging from "../logging"; class DomainConfig { - public givenDomainName: string; - public basePath: string | undefined; - public stage: string | undefined; - public certificateName: string | undefined; - public certificateArn: string | undefined; - public createRoute53Record: boolean | undefined; - public createRoute53IPv6Record: boolean | undefined; - public route53Profile: string | undefined; - public route53Region: string | undefined; - public endpointType: string | undefined; - public apiType: string | undefined; - public tlsTruststoreUri: string | undefined; - public tlsTruststoreVersion: string | undefined; - public hostedZoneId: string | undefined; - public hostedZonePrivate: boolean | undefined; - public splitHorizonDns: boolean | undefined; - public enabled: boolean | string | undefined; - public securityPolicy: string | undefined; - public autoDomain: boolean | undefined; - public autoDomainWaitFor: string | undefined; - public route53Params: Route53Params; - public preserveExternalPathMappings: boolean | undefined; - public domainInfo: DomainInfo | undefined; - public apiId: string | undefined; - public apiMapping: ApiGatewayMap; - public allowPathMatching: boolean | false; - - constructor(config: CustomDomain) { - - this.enabled = evaluateBoolean(config.enabled, true); - this.givenDomainName = config.domainName; - this.certificateArn = config.certificateArn; - this.certificateName = config.certificateName; - this.createRoute53Record = evaluateBoolean(config.createRoute53Record, true); - this.createRoute53IPv6Record = evaluateBoolean(config.createRoute53IPv6Record, true); - this.route53Profile = config.route53Profile; - this.route53Region = config.route53Region; - this.hostedZoneId = config.hostedZoneId; - this.hostedZonePrivate = config.hostedZonePrivate; - this.allowPathMatching = config.allowPathMatching; - this.autoDomain = evaluateBoolean(config.autoDomain, false); - this.autoDomainWaitFor = config.autoDomainWaitFor; - this.preserveExternalPathMappings = evaluateBoolean(config.preserveExternalPathMappings, false); - this.basePath = this._getBasePath(config.basePath); - this.apiType = this._getApiType(config.apiType); - // apiType should be defined before stage - this.stage = this._getStage(config.stage, this.apiType); - this.endpointType = this._getEndpointType(config.endpointType); - this.tlsTruststoreUri = this._getTLSTruststoreUri(config.tlsTruststoreUri, this.endpointType); - this.tlsTruststoreVersion = config.tlsTruststoreVersion; - this.securityPolicy = this._getSecurityPolicy(config.securityPolicy); - this.route53Params = this._getRoute53Params(config.route53Params, this.endpointType); - this.splitHorizonDns = !this.hostedZoneId && !this.hostedZonePrivate && evaluateBoolean(config.splitHorizonDns, false); + public givenDomainName: string; + public basePath: string | undefined; + public stage: string | undefined; + public certificateName: string | undefined; + public certificateArn: string | undefined; + public createRoute53Record: boolean | undefined; + public createRoute53IPv6Record: boolean | undefined; + public route53Profile: string | undefined; + public route53Region: string | undefined; + public endpointType: string | undefined; + public apiType: string | undefined; + public tlsTruststoreUri: string | undefined; + public tlsTruststoreVersion: string | undefined; + public hostedZoneId: string | undefined; + public hostedZonePrivate: boolean | undefined; + public splitHorizonDns: boolean | undefined; + public enabled: boolean | string | undefined; + public securityPolicy: string | undefined; + public autoDomain: boolean | undefined; + public autoDomainWaitFor: string | undefined; + public route53Params: Route53Params; + public preserveExternalPathMappings: boolean | undefined; + public domainInfo: DomainInfo | undefined; + public apiId: string | undefined; + public apiMapping: ApiGatewayMap; + public allowPathMatching: boolean | false; + + constructor (config: CustomDomain) { + this.enabled = evaluateBoolean(config.enabled, true); + this.givenDomainName = config.domainName; + this.certificateArn = config.certificateArn; + this.certificateName = config.certificateName; + this.createRoute53Record = evaluateBoolean(config.createRoute53Record, true); + this.createRoute53IPv6Record = evaluateBoolean(config.createRoute53IPv6Record, true); + this.route53Profile = config.route53Profile; + this.route53Region = config.route53Region; + this.hostedZoneId = config.hostedZoneId; + this.hostedZonePrivate = config.hostedZonePrivate; + this.allowPathMatching = config.allowPathMatching; + this.autoDomain = evaluateBoolean(config.autoDomain, false); + this.autoDomainWaitFor = config.autoDomainWaitFor; + this.preserveExternalPathMappings = evaluateBoolean(config.preserveExternalPathMappings, false); + this.basePath = DomainConfig._getBasePath(config.basePath); + this.apiType = DomainConfig._getApiType(config.apiType); + // apiType should be defined before stage + this.stage = DomainConfig._getStage(config.stage, this.apiType); + this.endpointType = DomainConfig._getEndpointType(config.endpointType); + this.tlsTruststoreUri = DomainConfig._getTLSTruststoreUri(config.tlsTruststoreUri, this.endpointType); + this.tlsTruststoreVersion = config.tlsTruststoreVersion; + this.securityPolicy = DomainConfig._getSecurityPolicy(config.securityPolicy); + this.route53Params = DomainConfig._getRoute53Params(config.route53Params, this.endpointType); + this.splitHorizonDns = !this.hostedZoneId && !this.hostedZonePrivate && evaluateBoolean(config.splitHorizonDns, false); + } + + private static _getStage (stage: string, apiType: string) { + if (apiType === Globals.apiTypes.http && !stage) { + return Globals.defaultStage; } + return stage || Globals.getBaseStage(); + } - private _getStage(stage: string, apiType: string) { - if (apiType === Globals.apiTypes.http && !stage) { - return Globals.defaultStage; - } - return stage || Globals.getBaseStage() + private static _getBasePath (basePath: string | undefined) { + if (!basePath || basePath.trim() === "") { + basePath = Globals.defaultBasePath; } - - private _getBasePath(basePath: string | undefined) { - if (!basePath || basePath.trim() === "") { - basePath = Globals.defaultBasePath; - } - if (Globals.reservedBasePaths.indexOf(basePath) !== -1) { - Logging.logWarning( - "The `/ping` and `/sping` paths are reserved for the service health check.\n Please take a look at" + - "https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-known-issues.html" - ); - } - return basePath; + if (Globals.reservedBasePaths.indexOf(basePath) !== -1) { + Logging.logWarning( + "The `/ping` and `/sping` paths are reserved for the service health check.\n Please take a look at" + + "https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-known-issues.html" + ); } - - private _getEndpointType(endpointType: string | undefined) { - const endpointTypeWithDefault = endpointType || Globals.endpointTypes.edge; - const endpointTypeToUse = Globals.endpointTypes[endpointTypeWithDefault.toLowerCase()]; - if (!endpointTypeToUse) { - throw new Error(`${endpointTypeWithDefault} is not supported endpointType, use EDGE or REGIONAL.`); - } - return endpointTypeToUse; + return basePath; + } + + private static _getEndpointType (endpointType: string | undefined) { + const endpointTypeWithDefault = endpointType || Globals.endpointTypes.edge; + const endpointTypeToUse = Globals.endpointTypes[endpointTypeWithDefault.toLowerCase()]; + if (!endpointTypeToUse) { + throw new Error(`${endpointTypeWithDefault} is not supported endpointType, use EDGE or REGIONAL.`); } - - private _getApiType(apiType: string | undefined) { - const apiTypeWithDefault = apiType || Globals.apiTypes.rest; - const apiTypeToUse = Globals.apiTypes[apiTypeWithDefault.toLowerCase()]; - if (!apiTypeToUse) { - throw new Error(`${apiTypeWithDefault} is not supported api type, use REST, HTTP or WEBSOCKET.`); - } - return apiTypeToUse; + return endpointTypeToUse; + } + + private static _getApiType (apiType: string | undefined) { + const apiTypeWithDefault = apiType || Globals.apiTypes.rest; + const apiTypeToUse = Globals.apiTypes[apiTypeWithDefault.toLowerCase()]; + if (!apiTypeToUse) { + throw new Error(`${apiTypeWithDefault} is not supported api type, use REST, HTTP or WEBSOCKET.`); } + return apiTypeToUse; + } + + private static _getTLSTruststoreUri (tlsTruststoreUri: string | undefined, endpointType: string) { + if (tlsTruststoreUri) { + if (endpointType === Globals.endpointTypes.edge) { + throw new Error( + endpointType + " APIs do not support mutual TLS, " + + "remove tlsTruststoreUri or change to a regional API." + ); + } + + const { protocol, pathname } = new URL(tlsTruststoreUri); + + if (protocol !== "s3:" && !pathname.substring(1).includes("/")) { + throw new Error( + `${tlsTruststoreUri} is not a valid s3 uri, try something like s3://bucket-name/key-name.` + ); + } + } + + return tlsTruststoreUri; + } - private _getTLSTruststoreUri(tlsTruststoreUri: string | undefined, endpointType: string) { - if (tlsTruststoreUri) { - if (endpointType === Globals.endpointTypes.edge) { - throw new Error( - endpointType + " APIs do not support mutual TLS, " + - "remove tlsTruststoreUri or change to a regional API." - ); - } - - const {protocol, pathname} = new URL(tlsTruststoreUri); - - if (protocol !== "s3:" && !pathname.substring(1).includes("/")) { - throw new Error( - `${tlsTruststoreUri} is not a valid s3 uri, try something like s3://bucket-name/key-name.` - ); - } - } - - return tlsTruststoreUri; + private static _getSecurityPolicy (securityPolicy: string | undefined) { + const securityPolicyDefault = securityPolicy || Globals.tlsVersions.tls_1_2; + const tlsVersionToUse = Globals.tlsVersions[securityPolicyDefault.toLowerCase()]; + if (!tlsVersionToUse) { + throw new Error(`${securityPolicyDefault} is not a supported securityPolicy, use tls_1_0 or tls_1_2.`); } - private _getSecurityPolicy(securityPolicy: string | undefined) { - const securityPolicyDefault = securityPolicy || Globals.tlsVersions.tls_1_2; - const tlsVersionToUse = Globals.tlsVersions[securityPolicyDefault.toLowerCase()]; - if (!tlsVersionToUse) { - throw new Error(`${securityPolicyDefault} is not a supported securityPolicy, use tls_1_0 or tls_1_2.`); - } + return tlsVersionToUse; + } - return tlsVersionToUse; + private static _getRoute53Params (route53Params: Route53Params | undefined, endpointType: string) { + const routingPolicy = route53Params?.routingPolicy?.toLowerCase() ?? Globals.routingPolicies.simple; + const routingPolicyToUse = Globals.routingPolicies[routingPolicy]; + if (!routingPolicyToUse) { + throw new Error(`${routingPolicy} is not a supported routing policy, use simple, latency, or weighted.`); } - private _getRoute53Params(route53Params: Route53Params | undefined, endpointType: string) { - const routingPolicy = route53Params?.routingPolicy?.toLowerCase() ?? Globals.routingPolicies.simple; - const routingPolicyToUse = Globals.routingPolicies[routingPolicy]; - if (!routingPolicyToUse) { - throw new Error(`${routingPolicy} is not a supported routing policy, use simple, latency, or weighted.`); - } - - if (routingPolicyToUse !== Globals.routingPolicies.simple && endpointType === Globals.endpointTypes.edge) { - throw new Error( - `${routingPolicy} routing is not intended to be used with edge endpoints. ` + - "Use a regional endpoint instead." - ); - } - - return { - routingPolicy: routingPolicyToUse, - setIdentifier: route53Params?.setIdentifier, - weight: route53Params?.weight ?? 200, - healthCheckId: route53Params?.healthCheckId - }; + if (routingPolicyToUse !== Globals.routingPolicies.simple && endpointType === Globals.endpointTypes.edge) { + throw new Error( + `${routingPolicy} routing is not intended to be used with edge endpoints. ` + + "Use a regional endpoint instead." + ); } + + return { + routingPolicy: routingPolicyToUse, + setIdentifier: route53Params?.setIdentifier, + weight: route53Params?.weight ?? 200, + healthCheckId: route53Params?.healthCheckId + }; + } } export = DomainConfig; diff --git a/src/models/domain-info.ts b/src/models/domain-info.ts index 5cdfdfeb..53c69060 100644 --- a/src/models/domain-info.ts +++ b/src/models/domain-info.ts @@ -2,37 +2,36 @@ * Wrapper class for Custom Domain information */ class DomainInfo { + public domainName: string; + public hostedZoneId: string; + public securityPolicy: string; - public domainName: string; - public hostedZoneId: string; - public securityPolicy: string; + /** + * Sometimes, the getDomainName call doesn't return either a distributionHostedZoneId or a regionalHostedZoneId. + * AFAICT, this only happens with edge-optimized endpoints. + * The hostedZoneId for these endpoints is always the one below. + * Docs: https://docs.aws.amazon.com/general/latest/gr/rande.html#apigateway_region + * PR: https://github.com/amplify-education/serverless-domain-manager/pull/171 + */ + private defaultHostedZoneId: string = "Z2FDTNDATAQYW2"; + private defaultSecurityPolicy: string = "TLS_1_2"; - /** - * Sometimes, the getDomainName call doesn't return either a distributionHostedZoneId or a regionalHostedZoneId. - * AFAICT, this only happens with edge-optimized endpoints. - * The hostedZoneId for these endpoints is always the one below. - * Docs: https://docs.aws.amazon.com/general/latest/gr/rande.html#apigateway_region - * PR: https://github.com/amplify-education/serverless-domain-manager/pull/171 - */ - private defaultHostedZoneId: string = "Z2FDTNDATAQYW2"; - private defaultSecurityPolicy: string = "TLS_1_2"; + constructor (data: any) { + this.domainName = data.distributionDomainName || + data.regionalDomainName || + (data.DomainNameConfigurations && data.DomainNameConfigurations[0].ApiGatewayDomainName) || + data.DomainName || + data.domainName; - constructor(data: any) { - this.domainName = data.distributionDomainName - || data.regionalDomainName - || data.DomainNameConfigurations && data.DomainNameConfigurations[0].ApiGatewayDomainName - || data.DomainName - || data.domainName; + this.hostedZoneId = data.distributionHostedZoneId || + data.regionalHostedZoneId || + (data.DomainNameConfigurations && data.DomainNameConfigurations[0].HostedZoneId) || + this.defaultHostedZoneId; - this.hostedZoneId = data.distributionHostedZoneId - || data.regionalHostedZoneId - || data.DomainNameConfigurations && data.DomainNameConfigurations[0].HostedZoneId - || this.defaultHostedZoneId; - - this.securityPolicy = data.securityPolicy - || data.DomainNameConfigurations && data.DomainNameConfigurations[0].SecurityPolicy - || this.defaultSecurityPolicy; - } + this.securityPolicy = data.securityPolicy || + (data.DomainNameConfigurations && data.DomainNameConfigurations[0].SecurityPolicy) || + this.defaultSecurityPolicy; + } } export = DomainInfo; diff --git a/src/types.ts b/src/types.ts index 78352d12..b5c0ae8c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,96 +1,96 @@ export interface Route53Params { - routingPolicy: "simple" | "latency" | "weighted" | undefined; - weight: number | undefined; - setIdentifier: string | undefined; - healthCheckId: string | undefined; + routingPolicy: "simple" | "latency" | "weighted" | undefined; + weight: number | undefined; + setIdentifier: string | undefined; + healthCheckId: string | undefined; } export interface CustomDomain { - domainName: string; - basePath: string | undefined; - stage: string | undefined; - certificateName: string | undefined; - certificateArn: string | undefined; - createRoute53Record: boolean | undefined; - createRoute53IPv6Record: boolean | undefined; - route53Profile: string | undefined; - route53Region: string | undefined; - endpointType: string | undefined; - apiType: string | undefined; - tlsTruststoreUri: string | undefined; - tlsTruststoreVersion: string | undefined; - hostedZoneId: string | undefined; - hostedZonePrivate: boolean | undefined; - splitHorizonDns: boolean | undefined; - enabled: boolean | string | undefined; - securityPolicy: string | undefined; - autoDomain: boolean | undefined; - autoDomainWaitFor: string | undefined; - allowPathMatching: boolean | undefined; - route53Params: Route53Params | undefined; - preserveExternalPathMappings: boolean | undefined; + domainName: string; + basePath: string | undefined; + stage: string | undefined; + certificateName: string | undefined; + certificateArn: string | undefined; + createRoute53Record: boolean | undefined; + createRoute53IPv6Record: boolean | undefined; + route53Profile: string | undefined; + route53Region: string | undefined; + endpointType: string | undefined; + apiType: string | undefined; + tlsTruststoreUri: string | undefined; + tlsTruststoreVersion: string | undefined; + hostedZoneId: string | undefined; + hostedZonePrivate: boolean | undefined; + splitHorizonDns: boolean | undefined; + enabled: boolean | string | undefined; + securityPolicy: string | undefined; + autoDomain: boolean | undefined; + autoDomainWaitFor: string | undefined; + allowPathMatching: boolean | undefined; + route53Params: Route53Params | undefined; + preserveExternalPathMappings: boolean | undefined; } export interface Tags { - [key: string]: string; + [key: string]: string; } export interface ServerlessInstance { - service: { - service: string - provider: { - stage: string - region?: string - profile?: string - stackName: string - compiledCloudFormationTemplate: { - Outputs: any, - }, - apiGateway: { - restApiId: any, - websocketApiId: any, - }, - tags: Tags, - stackTags: Tags, - } - custom: { - customDomain?: CustomDomain, - customDomains?: CustomDomain[], - }, - }; - providers: { - aws: { - getCredentials(), - }, - }; - cli: { - log(str: string, entity?: string) - }; + service: { + service: string + provider: { + stage: string + region?: string + profile?: string + stackName: string + compiledCloudFormationTemplate: { + Outputs: any, + }, + apiGateway: { + restApiId: any, + websocketApiId: any, + }, + tags: Tags, + stackTags: Tags, + } + custom: { + customDomain?: CustomDomain, + customDomains?: CustomDomain[], + }, + }; + providers: { + aws: { + getCredentials (), + }, + }; + cli: { + log (str: string, entity?: string) + }; - addServiceOutputSection?(name: string, data: string[]); + addServiceOutputSection? (name: string, data: string[]); } export interface ServerlessOptions { - stage: string; - region?: string; + stage: string; + region?: string; } interface ServerlessProgress { - update(message: string): void + update (message: string): void - remove(): void + remove (): void } export interface ServerlessProgressFactory { - get(name: string): ServerlessProgress; + get (name: string): ServerlessProgress; } export interface ServerlessUtils { - writeText: (message: string) => void, - log: { - error(message: string): void - verbose(message: string): void - warning(message: string): void - } - progress: ServerlessProgressFactory + writeText: (message: string) => void, + log: { + error (message: string): void + verbose (message: string): void + warning (message: string): void + } + progress: ServerlessProgressFactory } diff --git a/src/utils.ts b/src/utils.ts index 97ace62b..5bbad1ba 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -7,8 +7,8 @@ import Globals from "./globals"; * @param seconds * @returns {Promise} Resolves after given number of seconds. */ -async function sleep(seconds: number) { - return new Promise((resolve) => setTimeout(resolve, 1000 * seconds)); +async function sleep (seconds: number) { + return new Promise((resolve) => setTimeout(resolve, 1000 * seconds)); } /** @@ -22,21 +22,21 @@ async function sleep(seconds: number) { * @param {boolean} defaultValue the default value to return, if config value is undefined * @returns {boolean} the parsed boolean from the config value, or the default value */ -function evaluateBoolean(value: any, defaultValue: boolean): boolean { - if (value === undefined) { - return defaultValue; - } +function evaluateBoolean (value: any, defaultValue: boolean): boolean { + if (value === undefined) { + return defaultValue; + } - const s = value.toString().toLowerCase().trim(); - const trueValues = ["true", "1"]; - const falseValues = ["false", "0"]; - if (trueValues.indexOf(s) >= 0) { - return true; - } - if (falseValues.indexOf(s) >= 0) { - return false; - } - throw new Error(`${Globals.pluginName}: Ambiguous boolean config: "${value}"`); + const s = value.toString().toLowerCase().trim(); + const trueValues = ["true", "1"]; + const falseValues = ["false", "0"]; + if (trueValues.indexOf(s) >= 0) { + return true; + } + if (falseValues.indexOf(s) >= 0) { + return false; + } + throw new Error(`${Globals.pluginName}: Ambiguous boolean config: "${value}"`); } /** @@ -48,7 +48,7 @@ function evaluateBoolean(value: any, defaultValue: boolean): boolean { * @param nextRequestTokenKey - The response key name that has the next paging token value * @param params - Parameters to send in the request */ -async function getAWSPagedResults( +async function getAWSPagedResults ( client: Client, resultsKey: keyof ClientOutputCommand, nextTokenKey: keyof ClientInputCommand, @@ -58,10 +58,7 @@ async function getAWSPagedResults { - it("Initialization edge", async () => { + it("Initialization edge", () => { const acmWrapper = new ACMWrapper(null, Globals.endpointTypes.edge); - const actualResult = await acmWrapper.acm.config.region(); + const actualResult = acmWrapper.acm.config[0].region; expect(actualResult).to.equal(Globals.defaultRegion); }); - it("Initialization regional", async () => { + it("Initialization regional", () => { const acmWrapper = new ACMWrapper(null, Globals.endpointTypes.regional); - const actualResult = await acmWrapper.acm.config.region(); + const actualResult = acmWrapper.acm.config[0].region; expect(actualResult).to.equal(Globals.currentRegion); }); diff --git a/test/unit-tests/aws/api-gateway-v1-wrapper.test.ts b/test/unit-tests/aws/api-gateway-v1-wrapper.test.ts index 55839957..06d27e10 100644 --- a/test/unit-tests/aws/api-gateway-v1-wrapper.test.ts +++ b/test/unit-tests/aws/api-gateway-v1-wrapper.test.ts @@ -20,9 +20,9 @@ describe("API Gateway V1 wrapper checks", () => { consoleOutput.length = 0; }); - it("Initialization", async () => { + it("Initialization", () => { const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); - const actualResult = await apiGatewayV1Wrapper.apiGateway.config.region(); + const actualResult = apiGatewayV1Wrapper.apiGateway.config[0].region; expect(actualResult).to.equal(Globals.currentRegion); }); diff --git a/test/unit-tests/aws/api-gateway-v2-wrapper.test.ts b/test/unit-tests/aws/api-gateway-v2-wrapper.test.ts index bd4fb55e..dbf08f41 100644 --- a/test/unit-tests/aws/api-gateway-v2-wrapper.test.ts +++ b/test/unit-tests/aws/api-gateway-v2-wrapper.test.ts @@ -18,9 +18,9 @@ describe("API Gateway V2 wrapper checks", () => { consoleOutput.length = 0; }); - it("Initialization", async () => { + it("Initialization", () => { const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); - const actualResult = await apiGatewayV2Wrapper.apiGateway.config.region(); + const actualResult = apiGatewayV2Wrapper.apiGateway.config[0].region; expect(actualResult).to.equal(Globals.currentRegion); }); diff --git a/test/unit-tests/aws/cloud-formation-wrapper.test.ts b/test/unit-tests/aws/cloud-formation-wrapper.test.ts index 89541f09..902575f2 100644 --- a/test/unit-tests/aws/cloud-formation-wrapper.test.ts +++ b/test/unit-tests/aws/cloud-formation-wrapper.test.ts @@ -18,9 +18,9 @@ describe("Cloud Formation wrapper checks", () => { Globals.serverless.service.provider.apiGateway.websocketApiId = null; }); - it("Initialization", async () => { + it("Initialization", () => { const cloudFormationWrapper = new CloudFormationWrapper(); - const actualResult = await cloudFormationWrapper.cloudFormation.config.region(); + const actualResult = cloudFormationWrapper.cloudFormation.config[0].region; expect(actualResult).to.equal(Globals.currentRegion); expect(cloudFormationWrapper.stackName).to.equal(Globals.serverless.service.provider.stackName); }); diff --git a/test/unit-tests/aws/route53-wrapper.test.ts b/test/unit-tests/aws/route53-wrapper.test.ts index 0f9f3c4c..0f6268d8 100644 --- a/test/unit-tests/aws/route53-wrapper.test.ts +++ b/test/unit-tests/aws/route53-wrapper.test.ts @@ -1,591 +1,591 @@ -import {consoleOutput, expect, getDomainConfig} from "../base"; +import { consoleOutput, expect, getDomainConfig } from "../base"; import Globals from "../../../src/globals"; import Route53Wrapper = require("../../../src/aws/route53-wrapper"); -import {mockClient} from "aws-sdk-client-mock"; +import { mockClient } from "aws-sdk-client-mock"; import { - ChangeAction, - ChangeResourceRecordSetsCommand, - ListHostedZonesCommand, ResourceRecordSetRegion, - Route53Client, RRType + ChangeAction, + ChangeResourceRecordSetsCommand, + ListHostedZonesCommand, ResourceRecordSetRegion, + Route53Client, RRType } from "@aws-sdk/client-route-53"; import DomainConfig = require("../../../src/models/domain-config"); describe("Route53 wrapper checks", () => { - beforeEach(() => { - consoleOutput.length = 0; - }); - - it("Initialization", async () => { - const route53Wrapper = new Route53Wrapper(); - const actualResult = await route53Wrapper.route53.config.region(); - expect(actualResult).to.equal(Globals.currentRegion); - }); - - it("Initialization profile", async () => { - const credentials = { - accessKeyId: "test_key_id", - secretAccessKey: "test_access_key", - sessionToken: "test_token" - }; - const regionName = "test-region"; - const route53Wrapper = new Route53Wrapper(credentials, regionName); - - const actualRegion = await route53Wrapper.route53.config.region(); - expect(actualRegion).to.equal(regionName); - - const actualCredentials = await route53Wrapper.route53.config.credentials(); - expect(actualCredentials).to.equal(credentials); - }); - - it("get route53 hosted zone id", async () => { - const testId = "test_host_id" - const Route53Mock = mockClient(Route53Client); - Route53Mock.on(ListHostedZonesCommand).resolves({ - HostedZones: [ - { - CallerReference: "1", - Config: {PrivateZone: false}, - Id: testId, - Name: "test_domain", - }, { - CallerReference: "2", - Config: {PrivateZone: false}, - Id: testId, - Name: "dummy_test_domain", - }, { - CallerReference: "3", - Config: {PrivateZone: false}, - Id: testId, - Name: "domain", - } - ] - }); - - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain" - })); - - let actualId = await new Route53Wrapper().getRoute53HostedZoneId(dc); - expect(actualId).to.equal(testId); - - const commandCalls = Route53Mock.commandCalls(ListHostedZonesCommand, {}); - expect(commandCalls.length).to.equal(1); - - dc.hostedZoneId = "test_id" - actualId = await new Route53Wrapper().getRoute53HostedZoneId(dc); - expect(actualId).to.equal(dc.hostedZoneId); - }); - - it("get route53 hosted zone id paginated", async () => { - const testId = "test_host_id" - const Route53Mock = mockClient(Route53Client); - Route53Mock.on(ListHostedZonesCommand).resolvesOnce({ - HostedZones: [ - { - CallerReference: "1", - Config: {PrivateZone: false}, - Id: testId, - Name: "test_domain", - }, { - CallerReference: "2", - Config: {PrivateZone: false}, - Id: testId, - Name: "dummy_test_domain", - }, { - CallerReference: "3", - Config: {PrivateZone: false}, - Id: testId, - Name: "domain", - } - ], - NextMarker: "NextMarker" - }) - .resolvesOnce({ - HostedZones: [ - { - CallerReference: "4", - Config: {PrivateZone: false}, - Id: testId, - Name: "test_domain2", - }, { - CallerReference: "5", - Config: {PrivateZone: false}, - Id: testId, - Name: "dummy_test_domain2", - }, { - CallerReference: "6", - Config: {PrivateZone: false}, - Id: testId, - Name: "domain2", - } - ], - NextMarker: "NextMarker" - }) - .resolves({ - HostedZones: [ - { - CallerReference: "7", - Config: {PrivateZone: false}, - Id: testId, - Name: "test_domain3", - }, { - CallerReference: "8", - Config: {PrivateZone: false}, - Id: testId, - Name: "dummy_test_domain3", - }, { - CallerReference: "9", - Config: {PrivateZone: false}, - Id: testId, - Name: "domain3", - } - ] - }); - - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain" - })); - - let actualId = await new Route53Wrapper().getRoute53HostedZoneId(dc); - expect(actualId).to.equal(testId); - - const commandCalls = Route53Mock.commandCalls(ListHostedZonesCommand, {}); - expect(commandCalls.length).to.equal(3); - - dc.hostedZoneId = "test_id" - actualId = await new Route53Wrapper().getRoute53HostedZoneId(dc); - expect(actualId).to.equal(dc.hostedZoneId); - }); - - it("get route53 hosted zone id public", async () => { - const testId = "test_host_id" - const Route53Mock = mockClient(Route53Client); - Route53Mock.on(ListHostedZonesCommand).resolves({ - HostedZones: [ - { - CallerReference: "", - Config: {PrivateZone: false}, - Id: "no_valid", - Name: "api.test_domain", - }, { - CallerReference: "", - Config: {PrivateZone: true}, - Id: "dummy_host_id", - Name: "test_domain", - }, { - CallerReference: "", - Config: {PrivateZone: false}, - Id: testId, - Name: "test_domain", - }, - ] - }); - - const dc = new DomainConfig(getDomainConfig({ - domainName: "devapi.test_domain" - })); - - const actualId = await new Route53Wrapper().getRoute53HostedZoneId(dc, false); - expect(actualId).to.equal(testId); - - const commandCalls = Route53Mock.commandCalls(ListHostedZonesCommand, {}); - expect(commandCalls.length).to.equal(1); + beforeEach(() => { + consoleOutput.length = 0; + }); + + it("Initialization", () => { + const route53Wrapper = new Route53Wrapper(); + const actualResult = route53Wrapper.route53.config[0].region; + expect(actualResult).to.equal(Globals.currentRegion); + }); + + it("Initialization profile", () => { + const credentials = { + accessKeyId: "test_key_id", + secretAccessKey: "test_access_key", + sessionToken: "test_token" + }; + const regionName = "test-region"; + const route53Wrapper = new Route53Wrapper(credentials, regionName); + + const actualRegion = route53Wrapper.route53.config[0].region; + expect(actualRegion).to.equal(regionName); + + const actualCredentials = route53Wrapper.route53.config[0].credentials; + expect(actualCredentials).to.equal(credentials); + }); + + it("get route53 hosted zone id", async () => { + const testId = "test_host_id" + const Route53Mock = mockClient(Route53Client); + Route53Mock.on(ListHostedZonesCommand).resolves({ + HostedZones: [ + { + CallerReference: "1", + Config: { PrivateZone: false }, + Id: testId, + Name: "test_domain", + }, { + CallerReference: "2", + Config: { PrivateZone: false }, + Id: testId, + Name: "dummy_test_domain", + }, { + CallerReference: "3", + Config: { PrivateZone: false }, + Id: testId, + Name: "domain", + } + ] }); - it("get route53 hosted zone id private", async () => { - const testId = "test_host_id" - const Route53Mock = mockClient(Route53Client); - Route53Mock.on(ListHostedZonesCommand).resolves({ - HostedZones: [ - { - CallerReference: "", - Config: {PrivateZone: false}, - Id: "dummy_host_id", - Name: "test_domain", - }, { - CallerReference: "", - Config: {PrivateZone: true}, - Id: testId, - Name: "test_domain", - } - ] - }); - - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain" - })); - - const actualId = await new Route53Wrapper().getRoute53HostedZoneId(dc, true); - expect(actualId).to.equal(testId); - - const commandCalls = Route53Mock.commandCalls(ListHostedZonesCommand, {}); - expect(commandCalls.length).to.equal(1); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain" + })); + + let actualId = await new Route53Wrapper().getRoute53HostedZoneId(dc); + expect(actualId).to.equal(testId); + + const commandCalls = Route53Mock.commandCalls(ListHostedZonesCommand, {}); + expect(commandCalls.length).to.equal(1); + + dc.hostedZoneId = "test_id" + actualId = await new Route53Wrapper().getRoute53HostedZoneId(dc); + expect(actualId).to.equal(dc.hostedZoneId); + }); + + it("get route53 hosted zone id paginated", async () => { + const testId = "test_host_id" + const Route53Mock = mockClient(Route53Client); + Route53Mock.on(ListHostedZonesCommand).resolvesOnce({ + HostedZones: [ + { + CallerReference: "1", + Config: { PrivateZone: false }, + Id: testId, + Name: "test_domain", + }, { + CallerReference: "2", + Config: { PrivateZone: false }, + Id: testId, + Name: "dummy_test_domain", + }, { + CallerReference: "3", + Config: { PrivateZone: false }, + Id: testId, + Name: "domain", + } + ], + NextMarker: "NextMarker" + }) + .resolvesOnce({ + HostedZones: [ + { + CallerReference: "4", + Config: { PrivateZone: false }, + Id: testId, + Name: "test_domain2", + }, { + CallerReference: "5", + Config: { PrivateZone: false }, + Id: testId, + Name: "dummy_test_domain2", + }, { + CallerReference: "6", + Config: { PrivateZone: false }, + Id: testId, + Name: "domain2", + } + ], + NextMarker: "NextMarker" + }) + .resolves({ + HostedZones: [ + { + CallerReference: "7", + Config: { PrivateZone: false }, + Id: testId, + Name: "test_domain3", + }, { + CallerReference: "8", + Config: { PrivateZone: false }, + Id: testId, + Name: "dummy_test_domain3", + }, { + CallerReference: "9", + Config: { PrivateZone: false }, + Id: testId, + Name: "domain3", + } + ] + }); + + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain" + })); + + let actualId = await new Route53Wrapper().getRoute53HostedZoneId(dc); + expect(actualId).to.equal(testId); + + const commandCalls = Route53Mock.commandCalls(ListHostedZonesCommand, {}); + expect(commandCalls.length).to.equal(3); + + dc.hostedZoneId = "test_id" + actualId = await new Route53Wrapper().getRoute53HostedZoneId(dc); + expect(actualId).to.equal(dc.hostedZoneId); + }); + + it("get route53 hosted zone id public", async () => { + const testId = "test_host_id" + const Route53Mock = mockClient(Route53Client); + Route53Mock.on(ListHostedZonesCommand).resolves({ + HostedZones: [ + { + CallerReference: "", + Config: { PrivateZone: false }, + Id: "no_valid", + Name: "api.test_domain", + }, { + CallerReference: "", + Config: { PrivateZone: false }, + Id: testId, + Name: "devapi.test_domain", + }, { + CallerReference: "", + Config: { PrivateZone: false }, + Id: "dummy_host_id", + Name: "test_domain", + } + ] }); - it("get route53 hosted zone id failure", async () => { - const Route53Mock = mockClient(Route53Client); - Route53Mock.on(ListHostedZonesCommand).rejects(null); - - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain" - })); - - let errored = false; - try { - await new Route53Wrapper().getRoute53HostedZoneId(dc); - } catch (err) { - errored = true; - expect(err.message).to.contains("Unable to list hosted zones in Route53"); + const dc = new DomainConfig(getDomainConfig({ + domainName: "devapi.test_domain" + })); + + const actualId = await new Route53Wrapper().getRoute53HostedZoneId(dc, false); + expect(actualId).to.equal(testId); + + const commandCalls = Route53Mock.commandCalls(ListHostedZonesCommand, {}); + expect(commandCalls.length).to.equal(1); + }); + + it("get route53 hosted zone id private", async () => { + const testId = "test_host_id" + const Route53Mock = mockClient(Route53Client); + Route53Mock.on(ListHostedZonesCommand).resolves({ + HostedZones: [ + { + CallerReference: "", + Config: { PrivateZone: false }, + Id: "dummy_host_id", + Name: "test_domain", + }, { + CallerReference: "", + Config: { PrivateZone: true }, + Id: testId, + Name: "test_domain", } - expect(errored).to.equal(true); + ] }); - it("get route53 hosted zone not found", async () => { - const Route53Mock = mockClient(Route53Client); - Route53Mock.on(ListHostedZonesCommand).resolves({ - HostedZones: [ - { - CallerReference: "1", - Config: {PrivateZone: false}, - Id: "test_host_id", - Name: "test_domain", - } - ] - }); - - const dc = new DomainConfig(getDomainConfig({ - domainName: "dummy_domain" - })); - - let errored = false; - try { - await new Route53Wrapper().getRoute53HostedZoneId(dc); - } catch (err) { - errored = true; - expect(err.message).to.contains("Could not find hosted zone"); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain" + })); + + const actualId = await new Route53Wrapper().getRoute53HostedZoneId(dc, true); + expect(actualId).to.equal(testId); + + const commandCalls = Route53Mock.commandCalls(ListHostedZonesCommand, {}); + expect(commandCalls.length).to.equal(1); + }); + + it("get route53 hosted zone id failure", async () => { + const Route53Mock = mockClient(Route53Client); + Route53Mock.on(ListHostedZonesCommand).rejects(null); + + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain" + })); + + let errored = false; + try { + await new Route53Wrapper().getRoute53HostedZoneId(dc); + } catch (err) { + errored = true; + expect(err.message).to.contains("Unable to list hosted zones in Route53"); + } + expect(errored).to.equal(true); + }); + + it("get route53 hosted zone not found", async () => { + const Route53Mock = mockClient(Route53Client); + Route53Mock.on(ListHostedZonesCommand).resolves({ + HostedZones: [ + { + CallerReference: "1", + Config: { PrivateZone: false }, + Id: "test_host_id", + Name: "test_domain", } - expect(errored).to.equal(true); + ] }); - it("change resource record set skip", async () => { - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - createRoute53Record: false - })); - - const actualResult = await new Route53Wrapper().changeResourceRecordSet(ChangeAction.UPSERT, dc); - expect(actualResult).to.equal(undefined); + const dc = new DomainConfig(getDomainConfig({ + domainName: "dummy_domain" + })); + + let errored = false; + try { + await new Route53Wrapper().getRoute53HostedZoneId(dc); + } catch (err) { + errored = true; + expect(err.message).to.contains("Could not find hosted zone"); + } + expect(errored).to.equal(true); + }); + + it("change resource record set skip", async () => { + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain", + createRoute53Record: false + })); + + const actualResult = await new Route53Wrapper().changeResourceRecordSet(ChangeAction.UPSERT, dc); + expect(actualResult).to.equal(undefined); + }); + + it("change resource record set", async () => { + const Route53Mock = mockClient(Route53Client); + Route53Mock.on(ListHostedZonesCommand).resolves({ + HostedZones: [{ + CallerReference: "", + Config: { PrivateZone: false }, + Id: "test_host_id", + Name: "test_domain", + }], }); - - it("change resource record set", async () => { - const Route53Mock = mockClient(Route53Client); - Route53Mock.on(ListHostedZonesCommand).resolves({ - HostedZones: [{ - CallerReference: "", - Config: {PrivateZone: false}, - Id: "test_host_id", - Name: "test_domain", - }], - }); - Route53Mock.on(ChangeResourceRecordSetsCommand).resolves(null); - - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain" - })); - - await new Route53Wrapper().changeResourceRecordSet(ChangeAction.UPSERT, dc); - - const expectedParams = { - ChangeBatch: { - Changes: [ - { - Action: ChangeAction.UPSERT, - ResourceRecordSet: { - AliasTarget: { - DNSName: "test_domain", - EvaluateTargetHealth: false, - HostedZoneId: "test_host_id", - }, - Name: "test_domain", - Type: RRType.A, - }, - }, - { - Action: ChangeAction.UPSERT, - ResourceRecordSet: { - AliasTarget: { - DNSName: "test_domain", - EvaluateTargetHealth: false, - HostedZoneId: "test_host_id", - }, - Name: "test_domain", - Type: RRType.AAAA, - }, - }, - ], - Comment: `Record created by "${Globals.pluginName}"` + Route53Mock.on(ChangeResourceRecordSetsCommand).resolves(null); + + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain" + })); + + await new Route53Wrapper().changeResourceRecordSet(ChangeAction.UPSERT, dc); + + const expectedParams = { + ChangeBatch: { + Changes: [ + { + Action: ChangeAction.UPSERT, + ResourceRecordSet: { + AliasTarget: { + DNSName: "test_domain", + EvaluateTargetHealth: false, + HostedZoneId: "test_host_id", + }, + Name: "test_domain", + Type: RRType.A, }, - HostedZoneId: "test_host_id", - }; - const commandCalls = Route53Mock.commandCalls(ChangeResourceRecordSetsCommand, expectedParams); - expect(commandCalls.length).to.equal(1); - }); - - it("change resource record set routing policy latency", async () => { - const Route53Mock = mockClient(Route53Client); - Route53Mock.on(ListHostedZonesCommand).resolves({ - HostedZones: [{ - CallerReference: "", - Config: {PrivateZone: false}, - Id: "test_host_id", - Name: "test_domain", - }], - }); - Route53Mock.on(ChangeResourceRecordSetsCommand).resolves(null); - - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - endpointType: Globals.endpointTypes.regional, - route53Params: { - routingPolicy: Globals.routingPolicies.latency - } - })); - - await new Route53Wrapper().changeResourceRecordSet(ChangeAction.UPSERT, dc); - - const expectedParams = { - ChangeBatch: { - Changes: [ - { - Action: ChangeAction.UPSERT, - ResourceRecordSet: { - AliasTarget: { - DNSName: "test_domain", - EvaluateTargetHealth: false, - HostedZoneId: "test_host_id", - }, - Name: "test_domain", - Type: RRType.A, - Region: ResourceRecordSetRegion.us_east_1, - SetIdentifier: 'test_domain' - }, - }, - { - Action: ChangeAction.UPSERT, - ResourceRecordSet: { - AliasTarget: { - DNSName: "test_domain", - EvaluateTargetHealth: false, - HostedZoneId: "test_host_id", - }, - Name: "test_domain", - Type: RRType.AAAA, - Region: ResourceRecordSetRegion.us_east_1, - SetIdentifier: 'test_domain' - }, - }, - ], - Comment: `Record created by "${Globals.pluginName}"` + }, + { + Action: ChangeAction.UPSERT, + ResourceRecordSet: { + AliasTarget: { + DNSName: "test_domain", + EvaluateTargetHealth: false, + HostedZoneId: "test_host_id", + }, + Name: "test_domain", + Type: RRType.AAAA, }, - HostedZoneId: "test_host_id", - }; - const commandCalls = Route53Mock.commandCalls(ChangeResourceRecordSetsCommand, expectedParams); - expect(commandCalls.length).to.equal(1); + }, + ], + Comment: `Record created by "${Globals.pluginName}"` + }, + HostedZoneId: "test_host_id", + }; + const commandCalls = Route53Mock.commandCalls(ChangeResourceRecordSetsCommand, expectedParams); + expect(commandCalls.length).to.equal(1); + }); + + it("change resource record set routing policy latency", async () => { + const Route53Mock = mockClient(Route53Client); + Route53Mock.on(ListHostedZonesCommand).resolves({ + HostedZones: [{ + CallerReference: "", + Config: { PrivateZone: false }, + Id: "test_host_id", + Name: "test_domain", + }], }); - - it("change resource record set routing policy weighted", async () => { - const Route53Mock = mockClient(Route53Client); - Route53Mock.on(ListHostedZonesCommand).resolves({ - HostedZones: [{ - CallerReference: "", - Config: {PrivateZone: false}, - Id: "test_host_id", - Name: "test_domain", - }], - }); - Route53Mock.on(ChangeResourceRecordSetsCommand).resolves(null); - - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - endpointType: Globals.endpointTypes.regional, - route53Params: { - routingPolicy: Globals.routingPolicies.weighted, - weight: 1 - } - })); - - await new Route53Wrapper().changeResourceRecordSet(ChangeAction.UPSERT, dc); - - const expectedParams = { - ChangeBatch: { - Changes: [ - { - Action: ChangeAction.UPSERT, - ResourceRecordSet: { - AliasTarget: { - DNSName: "test_domain", - EvaluateTargetHealth: false, - HostedZoneId: "test_host_id", - }, - Name: "test_domain", - Type: RRType.A, - Weight: 1, - SetIdentifier: 'test_domain' - }, - }, - { - Action: ChangeAction.UPSERT, - ResourceRecordSet: { - AliasTarget: { - DNSName: "test_domain", - EvaluateTargetHealth: false, - HostedZoneId: "test_host_id", - }, - Name: "test_domain", - Type: RRType.AAAA, - Weight: 1, - SetIdentifier: 'test_domain' - }, - }, - ], - Comment: `Record created by "${Globals.pluginName}"` + Route53Mock.on(ChangeResourceRecordSetsCommand).resolves(null); + + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain", + endpointType: Globals.endpointTypes.regional, + route53Params: { + routingPolicy: Globals.routingPolicies.latency + } + })); + + await new Route53Wrapper().changeResourceRecordSet(ChangeAction.UPSERT, dc); + + const expectedParams = { + ChangeBatch: { + Changes: [ + { + Action: ChangeAction.UPSERT, + ResourceRecordSet: { + AliasTarget: { + DNSName: "test_domain", + EvaluateTargetHealth: false, + HostedZoneId: "test_host_id", + }, + Name: "test_domain", + Type: RRType.A, + Region: ResourceRecordSetRegion.us_east_1, + SetIdentifier: 'test_domain' + }, + }, + { + Action: ChangeAction.UPSERT, + ResourceRecordSet: { + AliasTarget: { + DNSName: "test_domain", + EvaluateTargetHealth: false, + HostedZoneId: "test_host_id", + }, + Name: "test_domain", + Type: RRType.AAAA, + Region: ResourceRecordSetRegion.us_east_1, + SetIdentifier: 'test_domain' }, - HostedZoneId: "test_host_id", - }; - const commandCalls = Route53Mock.commandCalls(ChangeResourceRecordSetsCommand, expectedParams); - expect(commandCalls.length).to.equal(1); + }, + ], + Comment: `Record created by "${Globals.pluginName}"` + }, + HostedZoneId: "test_host_id", + }; + const commandCalls = Route53Mock.commandCalls(ChangeResourceRecordSetsCommand, expectedParams); + expect(commandCalls.length).to.equal(1); + }); + + it("change resource record set routing policy weighted", async () => { + const Route53Mock = mockClient(Route53Client); + Route53Mock.on(ListHostedZonesCommand).resolves({ + HostedZones: [{ + CallerReference: "", + Config: { PrivateZone: false }, + Id: "test_host_id", + Name: "test_domain", + }], }); - - it("change resource record set split horizon dns", async () => { - const privateZone = "private_host_id"; - const publicZone = "public_host_id"; - const Route53Mock = mockClient(Route53Client); - Route53Mock.on(ListHostedZonesCommand).resolves({ - HostedZones: [{ - CallerReference: "", - Config: {PrivateZone: false}, - Id: publicZone, - Name: "test_domain", - }, { - CallerReference: "", - Config: {PrivateZone: true}, - Id: privateZone, - Name: "test_domain", - } - ] - }); - Route53Mock.on(ChangeResourceRecordSetsCommand).resolves(null); - - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - endpointType: Globals.endpointTypes.regional, - splitHorizonDns: true - })); - - await new Route53Wrapper().changeResourceRecordSet(ChangeAction.UPSERT, dc); - - const expectedParams1 = { - ChangeBatch: { - Changes: [ - { - Action: ChangeAction.UPSERT, - ResourceRecordSet: { - AliasTarget: { - DNSName: "test_domain", - EvaluateTargetHealth: false, - HostedZoneId: publicZone, - }, - Name: "test_domain", - Type: RRType.A, - }, - }, - { - Action: ChangeAction.UPSERT, - ResourceRecordSet: { - AliasTarget: { - DNSName: "test_domain", - EvaluateTargetHealth: false, - HostedZoneId: publicZone, - }, - Name: "test_domain", - Type: RRType.AAAA, - }, - }, - ], - Comment: `Record created by "${Globals.pluginName}"` + Route53Mock.on(ChangeResourceRecordSetsCommand).resolves(null); + + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain", + endpointType: Globals.endpointTypes.regional, + route53Params: { + routingPolicy: Globals.routingPolicies.weighted, + weight: 1 + } + })); + + await new Route53Wrapper().changeResourceRecordSet(ChangeAction.UPSERT, dc); + + const expectedParams = { + ChangeBatch: { + Changes: [ + { + Action: ChangeAction.UPSERT, + ResourceRecordSet: { + AliasTarget: { + DNSName: "test_domain", + EvaluateTargetHealth: false, + HostedZoneId: "test_host_id", + }, + Name: "test_domain", + Type: RRType.A, + Weight: 1, + SetIdentifier: 'test_domain' }, - HostedZoneId: publicZone, - }; - const commandCalls1 = Route53Mock.commandCalls(ChangeResourceRecordSetsCommand, expectedParams1, true); - expect(commandCalls1.length).to.equal(1); - - const expectedParams2 = { - ChangeBatch: { - Changes: [ - { - Action: ChangeAction.UPSERT, - ResourceRecordSet: { - AliasTarget: { - DNSName: "test_domain", - EvaluateTargetHealth: false, - HostedZoneId: publicZone, - }, - Name: "test_domain", - Type: RRType.A - }, - }, - { - Action: ChangeAction.UPSERT, - ResourceRecordSet: { - AliasTarget: { - DNSName: "test_domain", - EvaluateTargetHealth: false, - HostedZoneId: publicZone, - }, - Name: "test_domain", - Type: RRType.AAAA, - }, - }, - ], - Comment: `Record created by "${Globals.pluginName}"` + }, + { + Action: ChangeAction.UPSERT, + ResourceRecordSet: { + AliasTarget: { + DNSName: "test_domain", + EvaluateTargetHealth: false, + HostedZoneId: "test_host_id", + }, + Name: "test_domain", + Type: RRType.AAAA, + Weight: 1, + SetIdentifier: 'test_domain' }, - HostedZoneId: privateZone, - }; - const commandCalls2 = Route53Mock.commandCalls(ChangeResourceRecordSetsCommand, expectedParams2, true); - expect(commandCalls2.length).to.equal(1); + }, + ], + Comment: `Record created by "${Globals.pluginName}"` + }, + HostedZoneId: "test_host_id", + }; + const commandCalls = Route53Mock.commandCalls(ChangeResourceRecordSetsCommand, expectedParams); + expect(commandCalls.length).to.equal(1); + }); + + it("change resource record set split horizon dns", async () => { + const privateZone = "private_host_id"; + const publicZone = "public_host_id"; + const Route53Mock = mockClient(Route53Client); + Route53Mock.on(ListHostedZonesCommand).resolves({ + HostedZones: [{ + CallerReference: "", + Config: { PrivateZone: false }, + Id: publicZone, + Name: "test_domain", + }, { + CallerReference: "", + Config: { PrivateZone: true }, + Id: privateZone, + Name: "test_domain", + } + ] }); - - it("change resource record set failure", async () => { - const privateZone = "private_host_id"; - const publicZone = "public_host_id"; - const Route53Mock = mockClient(Route53Client); - Route53Mock.on(ListHostedZonesCommand).resolves({ - HostedZones: [{ - CallerReference: "", - Config: {PrivateZone: false}, - Id: publicZone, - Name: "test_domain", - }, { - CallerReference: "", - Config: {PrivateZone: true}, - Id: privateZone, - Name: "test_domain", - }] - }); - Route53Mock.on(ChangeResourceRecordSetsCommand).rejects(null); - - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - endpointType: Globals.endpointTypes.regional - })); - - let errored = false; - try { - await new Route53Wrapper().changeResourceRecordSet("UPSERT", dc); - } catch (err) { - errored = true; - expect(err.message).to.contains("Failed to UPSERT"); - } - expect(errored).to.equal(true); + Route53Mock.on(ChangeResourceRecordSetsCommand).resolves(null); + + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain", + endpointType: Globals.endpointTypes.regional, + splitHorizonDns: true + })); + + await new Route53Wrapper().changeResourceRecordSet(ChangeAction.UPSERT, dc); + + const expectedParams1 = { + ChangeBatch: { + Changes: [ + { + Action: ChangeAction.UPSERT, + ResourceRecordSet: { + AliasTarget: { + DNSName: "test_domain", + EvaluateTargetHealth: false, + HostedZoneId: publicZone, + }, + Name: "test_domain", + Type: RRType.A, + }, + }, + { + Action: ChangeAction.UPSERT, + ResourceRecordSet: { + AliasTarget: { + DNSName: "test_domain", + EvaluateTargetHealth: false, + HostedZoneId: publicZone, + }, + Name: "test_domain", + Type: RRType.AAAA, + }, + }, + ], + Comment: `Record created by "${Globals.pluginName}"` + }, + HostedZoneId: publicZone, + }; + const commandCalls1 = Route53Mock.commandCalls(ChangeResourceRecordSetsCommand, expectedParams1, true); + expect(commandCalls1.length).to.equal(1); + + const expectedParams2 = { + ChangeBatch: { + Changes: [ + { + Action: ChangeAction.UPSERT, + ResourceRecordSet: { + AliasTarget: { + DNSName: "test_domain", + EvaluateTargetHealth: false, + HostedZoneId: publicZone, + }, + Name: "test_domain", + Type: RRType.A + }, + }, + { + Action: ChangeAction.UPSERT, + ResourceRecordSet: { + AliasTarget: { + DNSName: "test_domain", + EvaluateTargetHealth: false, + HostedZoneId: publicZone, + }, + Name: "test_domain", + Type: RRType.AAAA, + }, + }, + ], + Comment: `Record created by "${Globals.pluginName}"` + }, + HostedZoneId: privateZone, + }; + const commandCalls2 = Route53Mock.commandCalls(ChangeResourceRecordSetsCommand, expectedParams2, true); + expect(commandCalls2.length).to.equal(1); + }); + + it("change resource record set failure", async () => { + const privateZone = "private_host_id"; + const publicZone = "public_host_id"; + const Route53Mock = mockClient(Route53Client); + Route53Mock.on(ListHostedZonesCommand).resolves({ + HostedZones: [{ + CallerReference: "", + Config: { PrivateZone: false }, + Id: publicZone, + Name: "test_domain", + }, { + CallerReference: "", + Config: { PrivateZone: true }, + Id: privateZone, + Name: "test_domain", + }] }); + Route53Mock.on(ChangeResourceRecordSetsCommand).rejects(null); + + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain", + endpointType: Globals.endpointTypes.regional + })); + + let errored = false; + try { + await new Route53Wrapper().changeResourceRecordSet("UPSERT", dc); + } catch (err) { + errored = true; + expect(err.message).to.contains("Failed to UPSERT"); + } + expect(errored).to.equal(true); + }); }); diff --git a/test/unit-tests/aws/s3-wrapper.test.ts b/test/unit-tests/aws/s3-wrapper.test.ts index 7fe16a98..98ddcfe9 100644 --- a/test/unit-tests/aws/s3-wrapper.test.ts +++ b/test/unit-tests/aws/s3-wrapper.test.ts @@ -10,9 +10,9 @@ describe("S3 wrapper checks", () => { consoleOutput.length = 0; }); - it("Initialization", async () => { + it("Initialization", () => { const s3Wrapper = new S3Wrapper(); - const actualResult = await s3Wrapper.s3.config.region(); + const actualResult = s3Wrapper.s3.config[0].region; expect(actualResult).to.equal(Globals.currentRegion); }); diff --git a/tslint.json b/tslint.json deleted file mode 100644 index 534cb941..00000000 --- a/tslint.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "tslint:recommended", - "rules": { - "no-console": false - } -} From 39b593496ae87fa473fe9a469476aa9fbc4afc7b Mon Sep 17 00:00:00 2001 From: rddimon Date: Thu, 7 Dec 2023 15:00:58 +0200 Subject: [PATCH 02/10] Add EOL --- .eslintignore | 2 +- .eslintrc.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.eslintignore b/.eslintignore index 76add878..f06235c4 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,2 @@ node_modules -dist \ No newline at end of file +dist diff --git a/.eslintrc.json b/.eslintrc.json index adf88e2a..eada90c2 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -38,4 +38,4 @@ ], "guard-for-in": "error" } -} \ No newline at end of file +} From 54590257b892e996ecd3e84f4d46465353ead683 Mon Sep 17 00:00:00 2001 From: rddimon Date: Thu, 7 Dec 2023 15:05:28 +0200 Subject: [PATCH 03/10] fix node14 check --- CHANGELOG.md | 6 ++++++ package-lock.json | 6 +++--- package.json | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 239a1ac9..09aeef16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [7.3.2] - 2023-12-07 + +### Fixed +- Bugfix for the hosted zone filtering. +- Switched `tslint` to `eslint` + ## [7.3.1] - 2023-12-01 ### Fixed diff --git a/package-lock.json b/package-lock.json index ed699fa6..7971f3da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "serverless-domain-manager", - "version": "7.4.0", + "version": "7.3.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "serverless-domain-manager", - "version": "7.4.0", + "version": "7.3.2", "license": "MIT", "dependencies": { "@aws-sdk/client-acm": "^3.460.0", @@ -30,6 +30,7 @@ "@types/randomstring": "^1.1.11", "@types/shelljs": "^0.8.15", "@typescript-eslint/eslint-plugin": "^5.62.0", + "@typescript-eslint/parser": "^5.62.0", "aws-sdk-client-mock": "^3.0.0", "chai": "^4.3.10", "chai-spies": "^1.1.0", @@ -3307,7 +3308,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", diff --git a/package.json b/package.json index 97ae1611..38bf2aa6 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "eslint-plugin-import": "^2.29.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^5.2.0", + "@typescript-eslint/parser": "^5.62.0", "@typescript-eslint/eslint-plugin": "^5.62.0", "mocha": "^10.2.0", "mocha-param": "^2.0.1", From b832502788ff3254789ab2663c9156880ba0eef8 Mon Sep 17 00:00:00 2001 From: rddimon Date: Thu, 7 Dec 2023 15:09:44 +0200 Subject: [PATCH 04/10] Remove console.log --- src/aws/route53-wrapper.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/aws/route53-wrapper.ts b/src/aws/route53-wrapper.ts index c45bbb97..e703936e 100644 --- a/src/aws/route53-wrapper.ts +++ b/src/aws/route53-wrapper.ts @@ -63,14 +63,12 @@ class Route53Wrapper { // removing the first part of the domain name, api.test.com => test.com const domainNameHost = domain.givenDomainName.substring(domain.givenDomainName.indexOf(".") + 1); - console.log(domain.givenDomainName); const targetHostedZone = hostedZones .filter((hostedZone) => { return !isPrivateDefined || isHostedZonePrivate === hostedZone.Config.PrivateZone; }) .filter((hostedZone) => { const hostedZoneName = hostedZone.Name.replace(/\.$/, ""); - console.log(hostedZoneName, domain.givenDomainName === hostedZoneName); return domain.givenDomainName === hostedZoneName || domainNameHost.endsWith(hostedZoneName); }) .sort((zone1, zone2) => zone2.Name.length - zone1.Name.length) From 1999f1d860917b222ecac78ef7c86cb7df4e995f Mon Sep 17 00:00:00 2001 From: rddimon Date: Thu, 7 Dec 2023 15:36:39 +0200 Subject: [PATCH 05/10] Fix Route53 --- src/aws/route53-wrapper.ts | 9 +- test/integration-tests/apigateway.ts | 84 +- test/integration-tests/base.ts | 20 +- test/integration-tests/basic/basic.test.ts | 312 ++--- test/integration-tests/debug/debug.test.ts | 26 +- test/integration-tests/deploy/deploy.test.ts | 283 +++-- test/integration-tests/test-utilities.ts | 112 +- test/unit-tests/aws/acm-wrapper.test.ts | 324 ++--- .../aws/api-gateway-v1-wrapper.test.ts | 989 +++++++------- .../aws/api-gateway-v2-wrapper.test.ts | 1121 ++++++++-------- .../aws/cloud-formation-wrapper.test.ts | 743 ++++++----- test/unit-tests/aws/route53-wrapper.test.ts | 146 +-- test/unit-tests/aws/s3-wrapper.test.ts | 142 +-- test/unit-tests/base.ts | 184 +-- test/unit-tests/index.test.ts | 1131 ++++++++--------- test/unit-tests/logging.test.ts | 136 +- 16 files changed, 2880 insertions(+), 2882 deletions(-) diff --git a/src/aws/route53-wrapper.ts b/src/aws/route53-wrapper.ts index e703936e..9863c7cd 100644 --- a/src/aws/route53-wrapper.ts +++ b/src/aws/route53-wrapper.ts @@ -13,19 +13,22 @@ import { getAWSPagedResults } from "../utils"; class Route53Wrapper { public route53: Route53Client; + private readonly region: string; constructor (credentials?: any, region?: string) { // not null and not undefined if (credentials) { + this.region = region || Globals.getRegion(); this.route53 = new Route53Client([{ credentials, - region: region || Globals.getRegion(), + region: this.region, retryStrategy: Globals.getRetryStrategy(), requestHandler: Globals.getRequestHandler() }]); } else { + this.region = Globals.getRegion(); this.route53 = new Route53Client([{ - region: Globals.getRegion(), + region: this.region, retryStrategy: Globals.getRetryStrategy(), requestHandler: Globals.getRequestHandler() }]); @@ -103,7 +106,7 @@ class Route53Wrapper { let routingOptions = {}; if (route53Params.routingPolicy === Globals.routingPolicies.latency) { routingOptions = { - Region: await this.route53.config[0].region, + Region: this.region, SetIdentifier: route53Params.setIdentifier ?? domainInfo.domainName, ...route53healthCheck }; diff --git a/test/integration-tests/apigateway.ts b/test/integration-tests/apigateway.ts index a550bb02..2a9b0643 100644 --- a/test/integration-tests/apigateway.ts +++ b/test/integration-tests/apigateway.ts @@ -1,23 +1,23 @@ import { - APIGatewayClient, - CreateRestApiCommand, - CreateRestApiCommandOutput, - DeleteRestApiCommand, - GetBasePathMappingsCommand, - GetBasePathMappingsCommandOutput, GetDomainNameCommand, GetDomainNameCommandOutput, - GetResourcesCommand, - GetResourcesCommandOutput + APIGatewayClient, + CreateRestApiCommand, + CreateRestApiCommandOutput, + DeleteRestApiCommand, + GetBasePathMappingsCommand, + GetBasePathMappingsCommandOutput, GetDomainNameCommand, GetDomainNameCommandOutput, + GetResourcesCommand, + GetResourcesCommandOutput } from "@aws-sdk/client-api-gateway"; import Globals from "../../src/globals"; export default class APIGatewayWrap { private client: APIGatewayClient; - constructor(region: string) { - this.client = new APIGatewayClient({ - region, - retryStrategy: Globals.getRetryStrategy() - }); + constructor (region: string) { + this.client = new APIGatewayClient({ + region, + retryStrategy: Globals.getRetryStrategy() + }); } /** @@ -25,18 +25,18 @@ export default class APIGatewayWrap { * @param {string} restApiName * @return {Object} Contains restApiId and resourceId */ - public async setupApiGatewayResources(restApiName) { - const restAPI: CreateRestApiCommandOutput = await this.client.send( - new CreateRestApiCommand({name: restApiName}) - ) + public async setupApiGatewayResources (restApiName) { + const restAPI: CreateRestApiCommandOutput = await this.client.send( + new CreateRestApiCommand({ name: restApiName }) + ); - const restApiId = restAPI.id; - const resources: GetResourcesCommandOutput = await this.client.send( - new GetResourcesCommand({restApiId}) - ) + const restApiId = restAPI.id; + const resources: GetResourcesCommandOutput = await this.client.send( + new GetResourcesCommand({ restApiId }) + ); - const resourceId = resources.items[0].id; - return {restApiId, resourceId}; + const resourceId = resources.items[0].id; + return { restApiId, resourceId }; } /** @@ -44,10 +44,10 @@ export default class APIGatewayWrap { * @param {string} restApiId * @return {boolean} Returns true if deleted */ - public async deleteApiGatewayResources(restApiId) { - return await this.client.send( - new DeleteRestApiCommand({restApiId}) - ); + public async deleteApiGatewayResources (restApiId) { + return await this.client.send( + new DeleteRestApiCommand({ restApiId }) + ); } /** @@ -55,12 +55,12 @@ export default class APIGatewayWrap { * @param domainName * @returns {Promise} */ - public async getStage(domainName) { - const result: GetBasePathMappingsCommandOutput = await this.client.send( - new GetBasePathMappingsCommand({domainName}) - ) + public async getStage (domainName) { + const result: GetBasePathMappingsCommandOutput = await this.client.send( + new GetBasePathMappingsCommand({ domainName }) + ); - return result.items[0].stage; + return result.items[0].stage; } /** @@ -68,12 +68,12 @@ export default class APIGatewayWrap { * @param domainName * @returns {Promise} */ - public async getBasePath(domainName) { - const result: GetBasePathMappingsCommandOutput = await this.client.send( - new GetBasePathMappingsCommand({domainName}) - ) + public async getBasePath (domainName) { + const result: GetBasePathMappingsCommandOutput = await this.client.send( + new GetBasePathMappingsCommand({ domainName }) + ); - return result.items[0].basePath; + return result.items[0].basePath; } /** @@ -81,11 +81,11 @@ export default class APIGatewayWrap { * @param domainName * @returns {Promise} */ - public async getEndpointType(domainName) { - const result: GetDomainNameCommandOutput = await this.client.send( - new GetDomainNameCommand({domainName}) - ) + public async getEndpointType (domainName) { + const result: GetDomainNameCommandOutput = await this.client.send( + new GetDomainNameCommand({ domainName }) + ); - return result.endpointConfiguration.types[0]; + return result.endpointConfiguration.types[0]; } } diff --git a/test/integration-tests/base.ts b/test/integration-tests/base.ts index ba468d62..1722f43e 100644 --- a/test/integration-tests/base.ts +++ b/test/integration-tests/base.ts @@ -3,9 +3,9 @@ import randomstring = require("randomstring"); // this is set in the each sls configs for the cleanup purpose in case of tests failure const PLUGIN_IDENTIFIER = "sdm"; const RANDOM_STRING = randomstring.generate({ - capitalization: "lowercase", - charset: "alphanumeric", - length: 5, + capitalization: "lowercase", + charset: "alphanumeric", + length: 5 }); const TEMP_DIR = `~/tmp/domain-manager-integration-tests/${RANDOM_STRING}`; const TEST_DOMAIN = process.env.TEST_DOMAIN; @@ -13,16 +13,16 @@ const TEST_DOMAIN = process.env.TEST_DOMAIN; // setting a `RANDOM_STRING` variable to use in each integration test // by this we are going to run unique test each time // and handling case for running tests for the same AWS account at the same time by different runs -process.env.PLUGIN_IDENTIFIER = PLUGIN_IDENTIFIER -process.env.RANDOM_STRING = RANDOM_STRING +process.env.PLUGIN_IDENTIFIER = PLUGIN_IDENTIFIER; +process.env.RANDOM_STRING = RANDOM_STRING; if (!TEST_DOMAIN) { - throw new Error("TEST_DOMAIN environment variable not set"); + throw new Error("TEST_DOMAIN environment variable not set"); } export { - PLUGIN_IDENTIFIER, - RANDOM_STRING, - TEMP_DIR, - TEST_DOMAIN, + PLUGIN_IDENTIFIER, + RANDOM_STRING, + TEMP_DIR, + TEST_DOMAIN }; diff --git a/test/integration-tests/basic/basic.test.ts b/test/integration-tests/basic/basic.test.ts index 8c20efa9..af2ad2e5 100644 --- a/test/integration-tests/basic/basic.test.ts +++ b/test/integration-tests/basic/basic.test.ts @@ -2,10 +2,10 @@ import chai = require("chai"); import "mocha"; import utilities = require("../test-utilities"); import { - PLUGIN_IDENTIFIER, - RANDOM_STRING, - TEMP_DIR, - TEST_DOMAIN, + PLUGIN_IDENTIFIER, + RANDOM_STRING, + TEMP_DIR, + TEST_DOMAIN } from "../base"; import APIGatewayWrap from "../apigateway"; @@ -16,156 +16,156 @@ const TIMEOUT_MINUTES = 15 * 60 * 1000; // 15 minutes in milliseconds const apiGatewayClient = new APIGatewayWrap("us-west-2"); describe("Integration Tests", function () { - this.timeout(TIMEOUT_MINUTES); - - it("Creates a empty basepath mapping", async () => { - const testName = "null-basepath-mapping"; - const configFolder = `${CONFIGS_FOLDER}/${testName}`; - const testURL = `${PLUGIN_IDENTIFIER}-${testName}-${RANDOM_STRING}.${TEST_DOMAIN}`; - // Perform sequence of commands to replicate basepath mapping issue - try { - await utilities.createTempDir(TEMP_DIR, configFolder); - await utilities.slsCreateDomain(TEMP_DIR); - await utilities.slsDeploy(TEMP_DIR); - await utilities.slsDeleteDomain(TEMP_DIR); - await utilities.slsCreateDomain(TEMP_DIR); - await utilities.slsDeploy(TEMP_DIR); - - const basePath = await apiGatewayClient.getBasePath(testURL); - expect(basePath).to.equal("(none)"); - } finally { - await utilities.destroyResources(testName); - } - }); - - it("Delete domain then recreate", async () => { - const testName = "basepath-mapping"; - const configFolder = `${CONFIGS_FOLDER}/${testName}`; - const testURL = `${PLUGIN_IDENTIFIER}-${testName}-${RANDOM_STRING}.${TEST_DOMAIN}`; - // Perform sequence of commands to replicate basepath mapping issue - try { - await utilities.createTempDir(TEMP_DIR, configFolder); - await utilities.slsCreateDomain(TEMP_DIR); - await utilities.slsDeploy(TEMP_DIR); - await utilities.slsDeleteDomain(TEMP_DIR); - await utilities.slsCreateDomain(TEMP_DIR); - await utilities.slsDeploy(TEMP_DIR); - - const basePath = await apiGatewayClient.getBasePath(testURL); - expect(basePath).to.equal("api"); - } finally { - await utilities.destroyResources(testName); - } - }); - - it("Delete domain then remove", async () => { - const testName = "null-basepath-mapping"; - const configFolder = `${CONFIGS_FOLDER}/${testName}`; - const testURL = `${PLUGIN_IDENTIFIER}-${testName}-${RANDOM_STRING}.${TEST_DOMAIN}`; - // Perform sequence of commands to replicate basepath mapping issue - try { - await utilities.createTempDir(TEMP_DIR, configFolder); - await utilities.slsCreateDomain(TEMP_DIR); - await utilities.slsDeploy(TEMP_DIR); - await utilities.slsDeleteDomain(TEMP_DIR); - await utilities.slsRemove(TEMP_DIR); - await utilities.slsCreateDomain(TEMP_DIR); - await utilities.slsDeploy(TEMP_DIR); - - const basePath = await apiGatewayClient.getBasePath(testURL); - expect(basePath).to.equal("(none)"); - } finally { - await utilities.destroyResources(testName); - } - }); - - it("API Gateway with export and import", async () => { - const testExportName = "apigateway-with-export"; - const testImportName = "apigateway-with-import"; - const configExportFolder = `${CONFIGS_FOLDER}/${testExportName}`; - const configImportFolder = `${CONFIGS_FOLDER}/${testImportName}`; - const testExportURL = `${PLUGIN_IDENTIFIER}-${testExportName}-${RANDOM_STRING}.${TEST_DOMAIN}`; - // Perform sequence of commands to replicate basepath mapping issue - try { - await utilities.createTempDir(TEMP_DIR, configExportFolder); - await utilities.slsDeploy(TEMP_DIR); - - await utilities.createTempDir(TEMP_DIR, configImportFolder); - await utilities.slsDeploy(TEMP_DIR); - - const basePath = await apiGatewayClient.getBasePath(testExportURL); - expect(basePath).to.equal("hello-world"); - } finally { - // should destroy the last created config folder ( import config ) - await utilities.destroyResources(testImportName); - // temp dir are empty and we need to update it with export config for the proper cleanup - await utilities.createTempDir(TEMP_DIR, configExportFolder); - await utilities.destroyResources(testExportName); - } - }); - - it("Can use a specified profile for route53", async () => { - const testName = "route53-profile"; - const configFolder = `${CONFIGS_FOLDER}/${testName}`; - const testURL = `${PLUGIN_IDENTIFIER}-${testName}-${RANDOM_STRING}.${TEST_DOMAIN}`; - try { - await utilities.createTempDir(TEMP_DIR, configFolder); - await utilities.slsCreateDomain(TEMP_DIR); - await utilities.slsCreateDomain(TEMP_DIR); - await utilities.slsCreateDomain(TEMP_DIR); - await utilities.slsDeploy(TEMP_DIR); - } finally { - await utilities.destroyResources(testURL); - } - }); - - it("Creates a domain multiple times without failure", async () => { - const testName = "create-domain-idempotent"; - const configFolder = `${CONFIGS_FOLDER}/${testName}`; - try { - await utilities.createTempDir(TEMP_DIR, configFolder); - await utilities.slsCreateDomain(TEMP_DIR); - await utilities.slsCreateDomain(TEMP_DIR); - await utilities.slsCreateDomain(TEMP_DIR); - await utilities.slsDeploy(TEMP_DIR); - } finally { - await utilities.destroyResources(testName); - } - }); - - it("Deploys multiple times without failure", async () => { - const testName = "deploy-idempotent"; - const configFolder = `${CONFIGS_FOLDER}/${testName}`; - try { - await utilities.createTempDir(TEMP_DIR, configFolder); - await utilities.slsCreateDomain(TEMP_DIR); - await utilities.slsDeploy(TEMP_DIR); - await utilities.slsDeploy(TEMP_DIR); - await utilities.slsDeploy(TEMP_DIR); - } finally { - await utilities.destroyResources(testName); - } - }); - - it("Deploy multi domains", async () => { - const testName = "http-api-multiple"; - const configFolder = `${CONFIGS_FOLDER}/${testName}`; - try { - await utilities.createTempDir(TEMP_DIR, configFolder); - await utilities.slsDeploy(TEMP_DIR); - } finally { - await utilities.destroyResources(testName); - } - }); - - it("Mutual TLS", async () => { - const testName = "mutual-tls"; - const configFolder = `${CONFIGS_FOLDER}/${testName}`; - try { - await utilities.createTempDir(TEMP_DIR, configFolder); - await utilities.slsDeploy(TEMP_DIR); - } finally { - await utilities.destroyResources(testName); - } - }); + this.timeout(TIMEOUT_MINUTES); + + it("Creates a empty basepath mapping", async () => { + const testName = "null-basepath-mapping"; + const configFolder = `${CONFIGS_FOLDER}/${testName}`; + const testURL = `${PLUGIN_IDENTIFIER}-${testName}-${RANDOM_STRING}.${TEST_DOMAIN}`; + // Perform sequence of commands to replicate basepath mapping issue + try { + await utilities.createTempDir(TEMP_DIR, configFolder); + await utilities.slsCreateDomain(TEMP_DIR); + await utilities.slsDeploy(TEMP_DIR); + await utilities.slsDeleteDomain(TEMP_DIR); + await utilities.slsCreateDomain(TEMP_DIR); + await utilities.slsDeploy(TEMP_DIR); + + const basePath = await apiGatewayClient.getBasePath(testURL); + expect(basePath).to.equal("(none)"); + } finally { + await utilities.destroyResources(testName); + } + }); + + it("Delete domain then recreate", async () => { + const testName = "basepath-mapping"; + const configFolder = `${CONFIGS_FOLDER}/${testName}`; + const testURL = `${PLUGIN_IDENTIFIER}-${testName}-${RANDOM_STRING}.${TEST_DOMAIN}`; + // Perform sequence of commands to replicate basepath mapping issue + try { + await utilities.createTempDir(TEMP_DIR, configFolder); + await utilities.slsCreateDomain(TEMP_DIR); + await utilities.slsDeploy(TEMP_DIR); + await utilities.slsDeleteDomain(TEMP_DIR); + await utilities.slsCreateDomain(TEMP_DIR); + await utilities.slsDeploy(TEMP_DIR); + + const basePath = await apiGatewayClient.getBasePath(testURL); + expect(basePath).to.equal("api"); + } finally { + await utilities.destroyResources(testName); + } + }); + + it("Delete domain then remove", async () => { + const testName = "null-basepath-mapping"; + const configFolder = `${CONFIGS_FOLDER}/${testName}`; + const testURL = `${PLUGIN_IDENTIFIER}-${testName}-${RANDOM_STRING}.${TEST_DOMAIN}`; + // Perform sequence of commands to replicate basepath mapping issue + try { + await utilities.createTempDir(TEMP_DIR, configFolder); + await utilities.slsCreateDomain(TEMP_DIR); + await utilities.slsDeploy(TEMP_DIR); + await utilities.slsDeleteDomain(TEMP_DIR); + await utilities.slsRemove(TEMP_DIR); + await utilities.slsCreateDomain(TEMP_DIR); + await utilities.slsDeploy(TEMP_DIR); + + const basePath = await apiGatewayClient.getBasePath(testURL); + expect(basePath).to.equal("(none)"); + } finally { + await utilities.destroyResources(testName); + } + }); + + it("API Gateway with export and import", async () => { + const testExportName = "apigateway-with-export"; + const testImportName = "apigateway-with-import"; + const configExportFolder = `${CONFIGS_FOLDER}/${testExportName}`; + const configImportFolder = `${CONFIGS_FOLDER}/${testImportName}`; + const testExportURL = `${PLUGIN_IDENTIFIER}-${testExportName}-${RANDOM_STRING}.${TEST_DOMAIN}`; + // Perform sequence of commands to replicate basepath mapping issue + try { + await utilities.createTempDir(TEMP_DIR, configExportFolder); + await utilities.slsDeploy(TEMP_DIR); + + await utilities.createTempDir(TEMP_DIR, configImportFolder); + await utilities.slsDeploy(TEMP_DIR); + + const basePath = await apiGatewayClient.getBasePath(testExportURL); + expect(basePath).to.equal("hello-world"); + } finally { + // should destroy the last created config folder ( import config ) + await utilities.destroyResources(testImportName); + // temp dir are empty and we need to update it with export config for the proper cleanup + await utilities.createTempDir(TEMP_DIR, configExportFolder); + await utilities.destroyResources(testExportName); + } + }); + + it("Can use a specified profile for route53", async () => { + const testName = "route53-profile"; + const configFolder = `${CONFIGS_FOLDER}/${testName}`; + const testURL = `${PLUGIN_IDENTIFIER}-${testName}-${RANDOM_STRING}.${TEST_DOMAIN}`; + try { + await utilities.createTempDir(TEMP_DIR, configFolder); + await utilities.slsCreateDomain(TEMP_DIR); + await utilities.slsCreateDomain(TEMP_DIR); + await utilities.slsCreateDomain(TEMP_DIR); + await utilities.slsDeploy(TEMP_DIR); + } finally { + await utilities.destroyResources(testURL); + } + }); + + it("Creates a domain multiple times without failure", async () => { + const testName = "create-domain-idempotent"; + const configFolder = `${CONFIGS_FOLDER}/${testName}`; + try { + await utilities.createTempDir(TEMP_DIR, configFolder); + await utilities.slsCreateDomain(TEMP_DIR); + await utilities.slsCreateDomain(TEMP_DIR); + await utilities.slsCreateDomain(TEMP_DIR); + await utilities.slsDeploy(TEMP_DIR); + } finally { + await utilities.destroyResources(testName); + } + }); + + it("Deploys multiple times without failure", async () => { + const testName = "deploy-idempotent"; + const configFolder = `${CONFIGS_FOLDER}/${testName}`; + try { + await utilities.createTempDir(TEMP_DIR, configFolder); + await utilities.slsCreateDomain(TEMP_DIR); + await utilities.slsDeploy(TEMP_DIR); + await utilities.slsDeploy(TEMP_DIR); + await utilities.slsDeploy(TEMP_DIR); + } finally { + await utilities.destroyResources(testName); + } + }); + + it("Deploy multi domains", async () => { + const testName = "http-api-multiple"; + const configFolder = `${CONFIGS_FOLDER}/${testName}`; + try { + await utilities.createTempDir(TEMP_DIR, configFolder); + await utilities.slsDeploy(TEMP_DIR); + } finally { + await utilities.destroyResources(testName); + } + }); + + it("Mutual TLS", async () => { + const testName = "mutual-tls"; + const configFolder = `${CONFIGS_FOLDER}/${testName}`; + try { + await utilities.createTempDir(TEMP_DIR, configFolder); + await utilities.slsDeploy(TEMP_DIR); + } finally { + await utilities.destroyResources(testName); + } + }); }); diff --git a/test/integration-tests/debug/debug.test.ts b/test/integration-tests/debug/debug.test.ts index 29ebda15..0c5d128a 100644 --- a/test/integration-tests/debug/debug.test.ts +++ b/test/integration-tests/debug/debug.test.ts @@ -1,23 +1,23 @@ import "mocha"; import utilities = require("../test-utilities"); -import {TEMP_DIR} from "../base"; +import { TEMP_DIR } from "../base"; const CONFIGS_FOLDER = "debug"; const TIMEOUT_MINUTES = 15 * 60 * 1000; // 15 minutes in milliseconds describe("Integration Tests", function () { - this.timeout(TIMEOUT_MINUTES); + this.timeout(TIMEOUT_MINUTES); - it("Creates pr-example", async () => { - const testName = "pr-example"; - const configFolder = `${CONFIGS_FOLDER}/${testName}`; + it("Creates pr-example", async () => { + const testName = "pr-example"; + const configFolder = `${CONFIGS_FOLDER}/${testName}`; - try { - await utilities.createTempDir(TEMP_DIR, configFolder); - await utilities.slsCreateDomain(TEMP_DIR, true); - await utilities.slsDeploy(TEMP_DIR, true); - } finally { - await utilities.destroyResources(testName); - } - }); + try { + await utilities.createTempDir(TEMP_DIR, configFolder); + await utilities.slsCreateDomain(TEMP_DIR, true); + await utilities.slsDeploy(TEMP_DIR, true); + } finally { + await utilities.destroyResources(testName); + } + }); }); diff --git a/test/integration-tests/deploy/deploy.test.ts b/test/integration-tests/deploy/deploy.test.ts index daa781dc..6cffbe21 100644 --- a/test/integration-tests/deploy/deploy.test.ts +++ b/test/integration-tests/deploy/deploy.test.ts @@ -4,7 +4,7 @@ import itParam = require("mocha-param"); import shell = require("shelljs"); import utilities = require("../test-utilities"); import APIGatewayWrap from "../apigateway"; -import {TEST_DOMAIN, PLUGIN_IDENTIFIER, RANDOM_STRING} from "../base"; +import { TEST_DOMAIN, PLUGIN_IDENTIFIER, RANDOM_STRING } from "../base"; const expect = chai.expect; const CONFIGS_FOLDER = "deploy"; @@ -13,153 +13,154 @@ const TIMEOUT_MINUTES = 10 * 60 * 1000; // 10 minutes in milliseconds const apiGatewayClient = new APIGatewayWrap("us-west-2"); const testCases = [ - { - testBasePath: "(none)", - testDescription: "Creates domain as part of deploy", - testDomain: `${PLUGIN_IDENTIFIER}-auto-domain-${RANDOM_STRING}.${TEST_DOMAIN}`, - testEndpoint: "EDGE", - testFolder: `${CONFIGS_FOLDER}/auto-domain`, - testStage: "test", - }, - { - testBasePath: "(none)", - testDescription: "Enabled with default values", - testDomain: `${PLUGIN_IDENTIFIER}-default-${RANDOM_STRING}.${TEST_DOMAIN}`, - testEndpoint: "EDGE", - testFolder: `${CONFIGS_FOLDER}/default`, - testStage: "test", - }, - { - restApiName: "rest-api-custom", - testBasePath: "(none)", - testDescription: "Enabled with custom api gateway", - testDomain: `${PLUGIN_IDENTIFIER}-custom-apigateway-${RANDOM_STRING}.${TEST_DOMAIN}`, - testEndpoint: "EDGE", - testFolder: `${CONFIGS_FOLDER}/custom-apigateway`, - testStage: "test", - }, - { - testBasePath: "api", - testDescription: "Enabled with custom basepath", - testDomain: `${PLUGIN_IDENTIFIER}-basepath-${RANDOM_STRING}.${TEST_DOMAIN}`, - testEndpoint: "EDGE", - testFolder: `${CONFIGS_FOLDER}/basepath`, - testStage: "test", - }, - { - testBasePath: "(none)", - testDescription: "Enabled with custom stage and empty basepath", - testDomain: `${PLUGIN_IDENTIFIER}-stage-basepath-${RANDOM_STRING}.${TEST_DOMAIN}`, - testEndpoint: "EDGE", - testFolder: `${CONFIGS_FOLDER}/stage-basepath`, - testStage: "test", - }, - { - testBasePath: "api", - testDescription: "Enabled with regional endpoint, custom basePath", - testDomain: `${PLUGIN_IDENTIFIER}-regional-basepath-${RANDOM_STRING}.${TEST_DOMAIN}`, - testEndpoint: "REGIONAL", - testFolder: `${CONFIGS_FOLDER}/regional-basepath`, - testStage: "test", - }, - { - testBasePath: "(none)", - testDescription: "Enabled with regional endpoint, custom stage, empty basepath", - testDomain: `${PLUGIN_IDENTIFIER}-regional-stage-basepath-${RANDOM_STRING}.${TEST_DOMAIN}`, - testEndpoint: "REGIONAL", - testFolder: `${CONFIGS_FOLDER}/regional-stage-basepath`, - testStage: "test", - }, - { - testBasePath: "(none)", - testDescription: "Create Web socket API and domain name", - testDomain: `${PLUGIN_IDENTIFIER}-web-socket-${RANDOM_STRING}.${TEST_DOMAIN}`, - testEndpoint: "REGIONAL", - testFolder: `${CONFIGS_FOLDER}/web-socket`, - testStage: "test", - }, - { - testBasePath: "(none)", - testDescription: "Create HTTP API and domain name", - testDomain: `${PLUGIN_IDENTIFIER}-http-api-${RANDOM_STRING}.${TEST_DOMAIN}`, - testEndpoint: "REGIONAL", - testFolder: `${CONFIGS_FOLDER}/http-api`, - testStage: "$default", - }, - { - testBasePath: "(none)", - testDescription: "Deploy regional domain with TLS 1.0", - testDomain: `${PLUGIN_IDENTIFIER}-regional-tls-1-0-${RANDOM_STRING}.${TEST_DOMAIN}`, - testEndpoint: "REGIONAL", - testFolder: `${CONFIGS_FOLDER}/regional-tls-1-0`, - testStage: "test", - }, - { - testBasePath: "api", - testDescription: "Deploy with nested CloudFormation stack", - testDomain: `${PLUGIN_IDENTIFIER}-basepath-nested-stack-${RANDOM_STRING}.${TEST_DOMAIN}`, - testEndpoint: "EDGE", - testFolder: `${CONFIGS_FOLDER}/basepath-nested-stack`, - testStage: "test", - }, - { - testBasePath: "(none)", - testDescription: "Deploy with latency routing", - testDomain: `${PLUGIN_IDENTIFIER}-route-53-latency-routing-${RANDOM_STRING}.${TEST_DOMAIN}`, - testEndpoint: "REGIONAL", - testFolder: `${CONFIGS_FOLDER}/route-53-latency-routing`, - testStage: "test", - }, - { - testBasePath: "(none)", - testDescription: "Deploy with weighted routing", - testDomain: `${PLUGIN_IDENTIFIER}-route-53-weighted-routing-${RANDOM_STRING}.${TEST_DOMAIN}`, - testEndpoint: "REGIONAL", - testFolder: `${CONFIGS_FOLDER}/route-53-weighted-routing`, - testStage: "test", - }, - { - testBasePath: "(none)", - testDescription: "Deploy with split horizon dns", - testDomain: `${PLUGIN_IDENTIFIER}-split-horizon-dns-${RANDOM_STRING}.${TEST_DOMAIN}`, - testEndpoint: "REGIONAL", - testFolder: `${CONFIGS_FOLDER}/split-horizon-dns`, - testStage: "test", - }, + { + testBasePath: "(none)", + testDescription: "Creates domain as part of deploy", + testDomain: `${PLUGIN_IDENTIFIER}-auto-domain-${RANDOM_STRING}.${TEST_DOMAIN}`, + testEndpoint: "EDGE", + testFolder: `${CONFIGS_FOLDER}/auto-domain`, + testStage: "test" + }, + { + testBasePath: "(none)", + testDescription: "Enabled with default values", + testDomain: `${PLUGIN_IDENTIFIER}-default-${RANDOM_STRING}.${TEST_DOMAIN}`, + testEndpoint: "EDGE", + testFolder: `${CONFIGS_FOLDER}/default`, + testStage: "test" + }, + { + restApiName: "rest-api-custom", + testBasePath: "(none)", + testDescription: "Enabled with custom api gateway", + testDomain: `${PLUGIN_IDENTIFIER}-custom-apigateway-${RANDOM_STRING}.${TEST_DOMAIN}`, + testEndpoint: "EDGE", + testFolder: `${CONFIGS_FOLDER}/custom-apigateway`, + testStage: "test" + }, + { + testBasePath: "api", + testDescription: "Enabled with custom basepath", + testDomain: `${PLUGIN_IDENTIFIER}-basepath-${RANDOM_STRING}.${TEST_DOMAIN}`, + testEndpoint: "EDGE", + testFolder: `${CONFIGS_FOLDER}/basepath`, + testStage: "test" + }, + { + testBasePath: "(none)", + testDescription: "Enabled with custom stage and empty basepath", + testDomain: `${PLUGIN_IDENTIFIER}-stage-basepath-${RANDOM_STRING}.${TEST_DOMAIN}`, + testEndpoint: "EDGE", + testFolder: `${CONFIGS_FOLDER}/stage-basepath`, + testStage: "test" + }, + { + testBasePath: "api", + testDescription: "Enabled with regional endpoint, custom basePath", + testDomain: `${PLUGIN_IDENTIFIER}-regional-basepath-${RANDOM_STRING}.${TEST_DOMAIN}`, + testEndpoint: "REGIONAL", + testFolder: `${CONFIGS_FOLDER}/regional-basepath`, + testStage: "test" + }, + { + testBasePath: "(none)", + testDescription: "Enabled with regional endpoint, custom stage, empty basepath", + testDomain: `${PLUGIN_IDENTIFIER}-regional-stage-basepath-${RANDOM_STRING}.${TEST_DOMAIN}`, + testEndpoint: "REGIONAL", + testFolder: `${CONFIGS_FOLDER}/regional-stage-basepath`, + testStage: "test" + }, + { + testBasePath: "(none)", + testDescription: "Create Web socket API and domain name", + testDomain: `${PLUGIN_IDENTIFIER}-web-socket-${RANDOM_STRING}.${TEST_DOMAIN}`, + testEndpoint: "REGIONAL", + testFolder: `${CONFIGS_FOLDER}/web-socket`, + testStage: "test" + }, + { + testBasePath: "(none)", + testDescription: "Create HTTP API and domain name", + testDomain: `${PLUGIN_IDENTIFIER}-http-api-${RANDOM_STRING}.${TEST_DOMAIN}`, + testEndpoint: "REGIONAL", + testFolder: `${CONFIGS_FOLDER}/http-api`, + testStage: "$default" + }, + { + testBasePath: "(none)", + testDescription: "Deploy regional domain with TLS 1.0", + testDomain: `${PLUGIN_IDENTIFIER}-regional-tls-1-0-${RANDOM_STRING}.${TEST_DOMAIN}`, + testEndpoint: "REGIONAL", + testFolder: `${CONFIGS_FOLDER}/regional-tls-1-0`, + testStage: "test" + }, + { + testBasePath: "api", + testDescription: "Deploy with nested CloudFormation stack", + testDomain: `${PLUGIN_IDENTIFIER}-basepath-nested-stack-${RANDOM_STRING}.${TEST_DOMAIN}`, + testEndpoint: "EDGE", + testFolder: `${CONFIGS_FOLDER}/basepath-nested-stack`, + testStage: "test" + }, + { + testBasePath: "(none)", + testDescription: "Deploy with latency routing", + testDomain: `${PLUGIN_IDENTIFIER}-route-53-latency-routing-${RANDOM_STRING}.${TEST_DOMAIN}`, + testEndpoint: "REGIONAL", + testFolder: `${CONFIGS_FOLDER}/route-53-latency-routing`, + testStage: "test" + }, + { + testBasePath: "(none)", + testDescription: "Deploy with weighted routing", + testDomain: `${PLUGIN_IDENTIFIER}-route-53-weighted-routing-${RANDOM_STRING}.${TEST_DOMAIN}`, + testEndpoint: "REGIONAL", + testFolder: `${CONFIGS_FOLDER}/route-53-weighted-routing`, + testStage: "test" + }, + { + testBasePath: "(none)", + testDescription: "Deploy with split horizon dns", + testDomain: `${PLUGIN_IDENTIFIER}-split-horizon-dns-${RANDOM_STRING}.${TEST_DOMAIN}`, + testEndpoint: "REGIONAL", + testFolder: `${CONFIGS_FOLDER}/split-horizon-dns`, + testStage: "test" + } ]; describe("Integration Tests", function () { - this.timeout(TIMEOUT_MINUTES); + this.timeout(TIMEOUT_MINUTES); - describe("Configuration Tests", () => { - // @ts-ignore - itParam("${value.testDescription}", testCases, async (value) => { - let restApiInfo; - if (value.restApiName) { - restApiInfo = await apiGatewayClient.setupApiGatewayResources(value.restApiName); + describe("Configuration Tests", () => { + // @ts-ignore + // eslint-disable-next-line no-template-curly-in-string + itParam("${value.testDescription}", testCases, async (value) => { + let restApiInfo; + if (value.restApiName) { + restApiInfo = await apiGatewayClient.setupApiGatewayResources(value.restApiName); - shell.env.REST_API_ID = restApiInfo.restApiId; - shell.env.RESOURCE_ID = restApiInfo.resourceId; - } - try { - await utilities.createResources(value.testFolder, value.testDomain); - const stage = await apiGatewayClient.getStage(value.testDomain); - expect(stage).to.equal(value.testStage); + shell.env.REST_API_ID = restApiInfo.restApiId; + shell.env.RESOURCE_ID = restApiInfo.resourceId; + } + try { + await utilities.createResources(value.testFolder, value.testDomain); + const stage = await apiGatewayClient.getStage(value.testDomain); + expect(stage).to.equal(value.testStage); - const basePath = await apiGatewayClient.getBasePath(value.testDomain); - expect(basePath).to.equal(value.testBasePath); + const basePath = await apiGatewayClient.getBasePath(value.testDomain); + expect(basePath).to.equal(value.testBasePath); - const endpoint = await apiGatewayClient.getEndpointType(value.testDomain); - expect(endpoint).to.equal(value.testEndpoint); - } finally { - await utilities.destroyResources(value.testDomain); - if (value.restApiName) { - await apiGatewayClient.deleteApiGatewayResources(restApiInfo.restApiId); + const endpoint = await apiGatewayClient.getEndpointType(value.testDomain); + expect(endpoint).to.equal(value.testEndpoint); + } finally { + await utilities.destroyResources(value.testDomain); + if (value.restApiName) { + await apiGatewayClient.deleteApiGatewayResources(restApiInfo.restApiId); - delete shell.env.REST_API_ID; - delete shell.env.RESOURCE_ID; - } - } - }); + delete shell.env.REST_API_ID; + delete shell.env.RESOURCE_ID; + } + } }); + }); }); diff --git a/test/integration-tests/test-utilities.ts b/test/integration-tests/test-utilities.ts index 216aa590..2da6889b 100644 --- a/test/integration-tests/test-utilities.ts +++ b/test/integration-tests/test-utilities.ts @@ -1,23 +1,23 @@ "use strict"; import shell = require("shelljs"); -import {TEMP_DIR} from "./base"; +import { TEMP_DIR } from "./base"; /** * Executes given shell command. * @param cmd shell command to execute * @returns {Promise} Resolves if successfully executed, else rejects */ -async function exec(cmd) { - console.debug(`\tRunning command: ${cmd}`); - return new Promise((resolve, reject) => { - shell.exec(cmd, {silent: false}, (errCode, stdout, stderr) => { - if (errCode) { - return reject(stderr); - } - return resolve(stdout); - }); +async function exec (cmd) { + console.debug(`\tRunning command: ${cmd}`); + return new Promise((resolve, reject) => { + shell.exec(cmd, { silent: false }, (errCode, stdout, stderr) => { + if (errCode) { + return reject(stderr); + } + return resolve(stdout); }); + }); } /** @@ -25,15 +25,15 @@ async function exec(cmd) { * @param {string} tempDir * @param {string} folderName */ -async function createTempDir(tempDir, folderName) { - await exec(`rm -rf ${tempDir}`); - await exec(`mkdir -p ${tempDir} && cp -R test/integration-tests/${folderName}/. ${tempDir}`); - await exec(`mkdir -p ${tempDir}/node_modules/.bin`); - await exec(`ln -s $(pwd) ${tempDir}/node_modules/`); +async function createTempDir (tempDir, folderName) { + await exec(`rm -rf ${tempDir}`); + await exec(`mkdir -p ${tempDir} && cp -R test/integration-tests/${folderName}/. ${tempDir}`); + await exec(`mkdir -p ${tempDir}/node_modules/.bin`); + await exec(`ln -s $(pwd) ${tempDir}/node_modules/`); - await exec(`ln -s $(pwd)/node_modules/serverless ${tempDir}/node_modules/`); - // we use npx running the local serverless in case not exists the global serverless will be used - await exec(`ln -s $(pwd)/node_modules/serverless/bin/serverless.js ${tempDir}/node_modules/.bin/serverless`); + await exec(`ln -s $(pwd)/node_modules/serverless ${tempDir}/node_modules/`); + // we use npx running the local serverless in case not exists the global serverless will be used + await exec(`ln -s $(pwd)/node_modules/serverless/bin/serverless.js ${tempDir}/node_modules/.bin/serverless`); } /** @@ -42,8 +42,8 @@ async function createTempDir(tempDir, folderName) { * @param debug - enable loging * @returns {Promise} */ -function slsCreateDomain(tempDir, debug: boolean = false) { - return exec(`cd ${tempDir} && npx serverless create_domain` + (debug ? " --verbose" : "")); +function slsCreateDomain (tempDir, debug: boolean = false) { + return exec(`cd ${tempDir} && npx serverless create_domain` + (debug ? " --verbose" : "")); } /** @@ -52,8 +52,8 @@ function slsCreateDomain(tempDir, debug: boolean = false) { * @param debug - enable loging * @returns {Promise} */ -function slsDeploy(tempDir, debug: boolean = false) { - return exec(`cd ${tempDir} && npx serverless deploy` + (debug ? " --verbose" : "")); +function slsDeploy (tempDir, debug: boolean = false) { + return exec(`cd ${tempDir} && npx serverless deploy` + (debug ? " --verbose" : "")); } /** @@ -61,8 +61,8 @@ function slsDeploy(tempDir, debug: boolean = false) { * @param tempDir * @returns {Promise} */ -function slsDeleteDomain(tempDir) { - return exec(`cd ${tempDir} && npx serverless delete_domain`); +function slsDeleteDomain (tempDir) { + return exec(`cd ${tempDir} && npx serverless delete_domain`); } /** @@ -70,8 +70,8 @@ function slsDeleteDomain(tempDir) { * @param tempDir * @returns {Promise} */ -function slsRemove(tempDir) { - return exec(`cd ${tempDir} && npx serverless remove`); +function slsRemove (tempDir) { + return exec(`cd ${tempDir} && npx serverless remove`); } /** @@ -80,16 +80,16 @@ function slsRemove(tempDir) { * @param url * @returns {Promise} Resolves if successfully executed, else rejects */ -async function createResources(folderName, url) { - console.debug(`\tCreating Resources for ${url} \tUsing tmp directory ${TEMP_DIR}`); - try { - await createTempDir(TEMP_DIR, folderName); - await slsCreateDomain(TEMP_DIR); - await slsDeploy(TEMP_DIR); - console.debug("\tResources Created"); - } catch (e) { - console.debug("\tResources Failed to Create"); - } +async function createResources (folderName, url) { + console.debug(`\tCreating Resources for ${url} \tUsing tmp directory ${TEMP_DIR}`); + try { + await createTempDir(TEMP_DIR, folderName); + await slsCreateDomain(TEMP_DIR); + await slsDeploy(TEMP_DIR); + console.debug("\tResources Created"); + } catch (e) { + console.debug("\tResources Failed to Create"); + } } /** @@ -97,27 +97,27 @@ async function createResources(folderName, url) { * @param url * @returns {Promise} Resolves if successfully executed, else rejects */ -async function destroyResources(url?) { - try { - console.log(`\tCleaning Up Resources for ${url}`); - await slsRemove(TEMP_DIR); - console.log("\tslsDeleteDomain"); - await slsDeleteDomain(TEMP_DIR); - console.log("\trm -rf"); - await exec(`rm -rf ${TEMP_DIR}`); - console.log("\tResources Cleaned Up"); - } catch (e) { - console.log(`\tFailed to Clean Up Resources: ${e}`); - } +async function destroyResources (url?) { + try { + console.log(`\tCleaning Up Resources for ${url}`); + await slsRemove(TEMP_DIR); + console.log("\tslsDeleteDomain"); + await slsDeleteDomain(TEMP_DIR); + console.log("\trm -rf"); + await exec(`rm -rf ${TEMP_DIR}`); + console.log("\tResources Cleaned Up"); + } catch (e) { + console.log(`\tFailed to Clean Up Resources: ${e}`); + } } export { - createResources, - createTempDir, - destroyResources, - exec, - slsCreateDomain, - slsDeploy, - slsDeleteDomain, - slsRemove, + createResources, + createTempDir, + destroyResources, + exec, + slsCreateDomain, + slsDeploy, + slsDeleteDomain, + slsRemove }; diff --git a/test/unit-tests/aws/acm-wrapper.test.ts b/test/unit-tests/aws/acm-wrapper.test.ts index 177fd47c..8f34080e 100644 --- a/test/unit-tests/aws/acm-wrapper.test.ts +++ b/test/unit-tests/aws/acm-wrapper.test.ts @@ -1,173 +1,173 @@ import ACMWrapper = require("../../../src/aws/acm-wrapper"); import DomainConfig = require("../../../src/models/domain-config"); import Globals from "../../../src/globals"; -import {expect, getDomainConfig} from "../base"; -import {mockClient} from "aws-sdk-client-mock"; -import {ACMClient, ListCertificatesCommand} from "@aws-sdk/client-acm"; +import { expect, getDomainConfig } from "../base"; +import { mockClient } from "aws-sdk-client-mock"; +import { ACMClient, ListCertificatesCommand } from "@aws-sdk/client-acm"; -const testCertificateArnByName = "test_certificate_name" -const testCertificateArnByDomain = "test_domain_arn" +const testCertificateArnByName = "test_certificate_name"; +const testCertificateArnByDomain = "test_domain_arn"; const certTestData = { - CertificateSummaryList: [ - { - CertificateArn: testCertificateArnByDomain, - DomainName: "test_domain", - }, { - CertificateArn: testCertificateArnByName, - DomainName: "cert_name", - }, { - CertificateArn: "test_dummy_arn", - DomainName: "other_cert_name", - } - ] + CertificateSummaryList: [ + { + CertificateArn: testCertificateArnByDomain, + DomainName: "test_domain" + }, { + CertificateArn: testCertificateArnByName, + DomainName: "cert_name" + }, { + CertificateArn: "test_dummy_arn", + DomainName: "other_cert_name" + } + ] }; describe("ACM Wrapper checks", () => { - it("Initialization edge", () => { - const acmWrapper = new ACMWrapper(null, Globals.endpointTypes.edge); - const actualResult = acmWrapper.acm.config[0].region; - expect(actualResult).to.equal(Globals.defaultRegion); - }); - - it("Initialization regional", () => { - const acmWrapper = new ACMWrapper(null, Globals.endpointTypes.regional); - const actualResult = acmWrapper.acm.config[0].region; - expect(actualResult).to.equal(Globals.currentRegion); - }); - - it("getCertArn by certificate name", async () => { - const ACMCMock = mockClient(ACMClient); - ACMCMock.on(ListCertificatesCommand).resolves(certTestData); - - const acmWrapper = new ACMWrapper(null, Globals.endpointTypes.regional); - const dc = new DomainConfig(getDomainConfig({ - certificateName: "cert_name" - })); - - const actualResult = await acmWrapper.getCertArn(dc); - expect(actualResult).to.equal(testCertificateArnByName); - }); - - it("getCertArn by domain name", async () => { - const ACMCMock = mockClient(ACMClient); - ACMCMock.on(ListCertificatesCommand).resolves(certTestData); - - const acmWrapper = new ACMWrapper(null, Globals.endpointTypes.regional); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - })); - - const actualResult = await acmWrapper.getCertArn(dc); - expect(actualResult).to.equal(testCertificateArnByDomain); - }); - - it("getCertArn by domain name by getting all paginated certificates from AWS", async () => { - const ACMCMock = mockClient(ACMClient); - ACMCMock.on(ListCertificatesCommand) - .resolvesOnce({ - CertificateSummaryList: [ - { - CertificateArn: "test_domain_arn", - DomainName: "test_domain", - Status: "ISSUED", - }, - ], - NextToken: 'NextToken', - }) - .resolves({ - CertificateSummaryList: [ - { - CertificateArn: "test_domain_arn2", - DomainName: "test_domain2", - Status: "ISSUED", - }, - ], - }); - - const acmWrapper = new ACMWrapper(null, Globals.endpointTypes.regional); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - })); - - const actualResult = await acmWrapper.getCertArn(dc); - expect(actualResult).to.equal(testCertificateArnByDomain); - expect(ACMCMock.calls().length).to.equal(2); - }); - - it("empty getCertArn by certificate name", async () => { - const ACMCMock = mockClient(ACMClient); - ACMCMock.on(ListCertificatesCommand).resolves(certTestData); - - const certificateName = "not_existing_certificate" - const acmWrapper = new ACMWrapper(null, Globals.endpointTypes.regional); - const dc = new DomainConfig(getDomainConfig({certificateName})); - - let errored = false; - try { - await acmWrapper.getCertArn(dc); - } catch (err) { - errored = true; - expect(err.message).to.contains(`Could not find an in-date certificate for \'${certificateName}\'`); - } - expect(errored).to.equal(true); - }); - - it("getCertArn with wild card and alternative name summaries", async () => { - const wildCardCertificate = "*.test_domain"; - const ACMCMock = mockClient(ACMClient); - ACMCMock.on(ListCertificatesCommand).resolves({ - CertificateSummaryList: [{ - CertificateArn: testCertificateArnByDomain, - DomainName: "dammy_domain", - SubjectAlternativeNameSummaries: [ - wildCardCertificate - ] - }] - }); - - const acmWrapper = new ACMWrapper(null, Globals.endpointTypes.regional); - const dc = new DomainConfig(getDomainConfig({ - domainName: "sub.test_domain", - })); - - const actualResult = await acmWrapper.getCertArn(dc); - expect(actualResult).to.equal(testCertificateArnByDomain); - }); - - it("empty getCertArn by domain name", async () => { - const ACMCMock = mockClient(ACMClient); - ACMCMock.on(ListCertificatesCommand).resolves(certTestData); - - const domainName = "not_existing_domain" - const acmWrapper = new ACMWrapper(null, Globals.endpointTypes.regional); - const dc = new DomainConfig(getDomainConfig({domainName})); - - let errored = false; - try { - await acmWrapper.getCertArn(dc); - } catch (err) { - errored = true; - expect(err.message).to.contains(`Could not find an in-date certificate for \'${domainName}\'`); - } - expect(errored).to.equal(true); + it("Initialization edge", () => { + const acmWrapper = new ACMWrapper(null, Globals.endpointTypes.edge); + const actualResult = acmWrapper.acm.config[0].region; + expect(actualResult).to.equal(Globals.defaultRegion); + }); + + it("Initialization regional", () => { + const acmWrapper = new ACMWrapper(null, Globals.endpointTypes.regional); + const actualResult = acmWrapper.acm.config[0].region; + expect(actualResult).to.equal(Globals.currentRegion); + }); + + it("getCertArn by certificate name", async () => { + const ACMCMock = mockClient(ACMClient); + ACMCMock.on(ListCertificatesCommand).resolves(certTestData); + + const acmWrapper = new ACMWrapper(null, Globals.endpointTypes.regional); + const dc = new DomainConfig(getDomainConfig({ + certificateName: "cert_name" + })); + + const actualResult = await acmWrapper.getCertArn(dc); + expect(actualResult).to.equal(testCertificateArnByName); + }); + + it("getCertArn by domain name", async () => { + const ACMCMock = mockClient(ACMClient); + ACMCMock.on(ListCertificatesCommand).resolves(certTestData); + + const acmWrapper = new ACMWrapper(null, Globals.endpointTypes.regional); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain" + })); + + const actualResult = await acmWrapper.getCertArn(dc); + expect(actualResult).to.equal(testCertificateArnByDomain); + }); + + it("getCertArn by domain name by getting all paginated certificates from AWS", async () => { + const ACMCMock = mockClient(ACMClient); + ACMCMock.on(ListCertificatesCommand) + .resolvesOnce({ + CertificateSummaryList: [ + { + CertificateArn: "test_domain_arn", + DomainName: "test_domain", + Status: "ISSUED" + } + ], + NextToken: "NextToken" + }) + .resolves({ + CertificateSummaryList: [ + { + CertificateArn: "test_domain_arn2", + DomainName: "test_domain2", + Status: "ISSUED" + } + ] + }); + + const acmWrapper = new ACMWrapper(null, Globals.endpointTypes.regional); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain" + })); + + const actualResult = await acmWrapper.getCertArn(dc); + expect(actualResult).to.equal(testCertificateArnByDomain); + expect(ACMCMock.calls().length).to.equal(2); + }); + + it("empty getCertArn by certificate name", async () => { + const ACMCMock = mockClient(ACMClient); + ACMCMock.on(ListCertificatesCommand).resolves(certTestData); + + const certificateName = "not_existing_certificate"; + const acmWrapper = new ACMWrapper(null, Globals.endpointTypes.regional); + const dc = new DomainConfig(getDomainConfig({ certificateName })); + + let errored = false; + try { + await acmWrapper.getCertArn(dc); + } catch (err) { + errored = true; + expect(err.message).to.contains(`Could not find an in-date certificate for '${certificateName}'`); + } + expect(errored).to.equal(true); + }); + + it("getCertArn with wild card and alternative name summaries", async () => { + const wildCardCertificate = "*.test_domain"; + const ACMCMock = mockClient(ACMClient); + ACMCMock.on(ListCertificatesCommand).resolves({ + CertificateSummaryList: [{ + CertificateArn: testCertificateArnByDomain, + DomainName: "dammy_domain", + SubjectAlternativeNameSummaries: [ + wildCardCertificate + ] + }] }); - it("getCertArn failure", async () => { - const ACMCMock = mockClient(ACMClient); - ACMCMock.on(ListCertificatesCommand).rejects(); - - const acmWrapper = new ACMWrapper(null, Globals.endpointTypes.regional); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - })); - - let errored = false; - try { - await acmWrapper.getCertArn(dc); - } catch (err) { - errored = true; - expect(err.message).to.contains("Could not search certificates in Certificate Manager"); - } - expect(errored).to.equal(true); - }); + const acmWrapper = new ACMWrapper(null, Globals.endpointTypes.regional); + const dc = new DomainConfig(getDomainConfig({ + domainName: "sub.test_domain" + })); + + const actualResult = await acmWrapper.getCertArn(dc); + expect(actualResult).to.equal(testCertificateArnByDomain); + }); + + it("empty getCertArn by domain name", async () => { + const ACMCMock = mockClient(ACMClient); + ACMCMock.on(ListCertificatesCommand).resolves(certTestData); + + const domainName = "not_existing_domain"; + const acmWrapper = new ACMWrapper(null, Globals.endpointTypes.regional); + const dc = new DomainConfig(getDomainConfig({ domainName })); + + let errored = false; + try { + await acmWrapper.getCertArn(dc); + } catch (err) { + errored = true; + expect(err.message).to.contains(`Could not find an in-date certificate for '${domainName}'`); + } + expect(errored).to.equal(true); + }); + + it("getCertArn failure", async () => { + const ACMCMock = mockClient(ACMClient); + ACMCMock.on(ListCertificatesCommand).rejects(); + + const acmWrapper = new ACMWrapper(null, Globals.endpointTypes.regional); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain" + })); + + let errored = false; + try { + await acmWrapper.getCertArn(dc); + } catch (err) { + errored = true; + expect(err.message).to.contains("Could not search certificates in Certificate Manager"); + } + expect(errored).to.equal(true); + }); }); diff --git a/test/unit-tests/aws/api-gateway-v1-wrapper.test.ts b/test/unit-tests/aws/api-gateway-v1-wrapper.test.ts index 06d27e10..80cb8e2b 100644 --- a/test/unit-tests/aws/api-gateway-v1-wrapper.test.ts +++ b/test/unit-tests/aws/api-gateway-v1-wrapper.test.ts @@ -1,541 +1,538 @@ -import {mockClient} from "aws-sdk-client-mock"; +import { mockClient } from "aws-sdk-client-mock"; import { - APIGatewayClient, CreateBasePathMappingCommand, - CreateDomainNameCommand, DeleteBasePathMappingCommand, - DeleteDomainNameCommand, GetBasePathMappingsCommand, - GetDomainNameCommand, PatchOperation, UpdateBasePathMappingCommand, - Op, EndpointType, SecurityPolicy + APIGatewayClient, CreateBasePathMappingCommand, + CreateDomainNameCommand, DeleteBasePathMappingCommand, + DeleteDomainNameCommand, GetBasePathMappingsCommand, + GetDomainNameCommand, UpdateBasePathMappingCommand, + Op, EndpointType, SecurityPolicy } from "@aws-sdk/client-api-gateway"; -import {consoleOutput, expect, getDomainConfig} from "../base"; +import { consoleOutput, expect, getDomainConfig } from "../base"; import Globals from "../../../src/globals"; import DomainConfig = require("../../../src/models/domain-config"); import APIGatewayV1Wrapper = require("../../../src/aws/api-gateway-v1-wrapper"); import DomainInfo = require("../../../src/models/domain-info"); import ApiGatewayMap = require("../../../src/models/api-gateway-map"); -import {Type} from "@aws-sdk/client-s3"; - describe("API Gateway V1 wrapper checks", () => { - beforeEach(() => { - consoleOutput.length = 0; + beforeEach(() => { + consoleOutput.length = 0; + }); + + it("Initialization", () => { + const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); + const actualResult = apiGatewayV1Wrapper.apiGateway.config[0].region; + expect(actualResult).to.equal(Globals.currentRegion); + }); + + describe("Custom domain", () => { + it("create custom domain edge", async () => { + const APIGatewayMock = mockClient(APIGatewayClient); + APIGatewayMock.on(CreateDomainNameCommand).resolves({ + distributionDomainName: "foo", + securityPolicy: "TLS_1_0" + }); + + const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain", + basePath: "test_basepath", + endpointType: Globals.endpointTypes.edge, + securityPolicy: "tls_1_0", + certificateArn: "test_arn" + })); + + const actualResult = await apiGatewayV1Wrapper.createCustomDomain(dc); + const expectedResult = new DomainInfo({ + distributionDomainName: "foo", + securityPolicy: "TLS_1_0" + }); + + expect(actualResult).to.eql(expectedResult); + + const expectedParams = { + domainName: dc.givenDomainName, + endpointConfiguration: { + types: [EndpointType.EDGE] + }, + securityPolicy: SecurityPolicy.TLS_1_0, + tags: { + ...Globals.serverless.service.provider.stackTags, + ...Globals.serverless.service.provider.tags + }, + certificateArn: dc.certificateArn + }; + const commandCalls = APIGatewayMock.commandCalls(CreateDomainNameCommand, expectedParams, true); + + expect(commandCalls.length).to.equal(1); }); - it("Initialization", () => { - const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); - const actualResult = apiGatewayV1Wrapper.apiGateway.config[0].region; - expect(actualResult).to.equal(Globals.currentRegion); + it("create custom domain regional", async () => { + const APIGatewayMock = mockClient(APIGatewayClient); + APIGatewayMock.on(CreateDomainNameCommand).resolves({ + distributionDomainName: "foo", + securityPolicy: "TLS_1_0" + }); + + const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain", + basePath: "test_basepath", + endpointType: Globals.endpointTypes.regional, + securityPolicy: "tls_1_0", + certificateArn: "test_arn" + })); + + const actualResult = await apiGatewayV1Wrapper.createCustomDomain(dc); + const expectedResult = new DomainInfo({ + distributionDomainName: "foo", + securityPolicy: "TLS_1_0" + }); + + expect(actualResult).to.eql(expectedResult); + + const expectedParams = { + domainName: dc.givenDomainName, + endpointConfiguration: { + types: [EndpointType.REGIONAL] + }, + securityPolicy: SecurityPolicy.TLS_1_0, + tags: { + ...Globals.serverless.service.provider.stackTags, + ...Globals.serverless.service.provider.tags + }, + regionalCertificateArn: dc.certificateArn + }; + const commandCalls = APIGatewayMock.commandCalls(CreateDomainNameCommand, expectedParams, true); + + expect(commandCalls.length).to.equal(1); }); - describe("Custom domain", () => { - it("create custom domain edge", async () => { - const APIGatewayMock = mockClient(APIGatewayClient); - APIGatewayMock.on(CreateDomainNameCommand).resolves({ - distributionDomainName: "foo", - securityPolicy: "TLS_1_0" - }); - - const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - basePath: "test_basepath", - endpointType: Globals.endpointTypes.edge, - securityPolicy: "tls_1_0", - certificateArn: "test_arn" - })); - - const actualResult = await apiGatewayV1Wrapper.createCustomDomain(dc); - const expectedResult = new DomainInfo({ - distributionDomainName: "foo", - securityPolicy: "TLS_1_0" - }); - - expect(actualResult).to.eql(expectedResult); - - const expectedParams = { - domainName: dc.givenDomainName, - endpointConfiguration: { - types: [EndpointType.EDGE], - }, - securityPolicy: SecurityPolicy.TLS_1_0, - tags: { - ...Globals.serverless.service.provider.stackTags, - ...Globals.serverless.service.provider.tags, - }, - certificateArn: dc.certificateArn - } - const commandCalls = APIGatewayMock.commandCalls(CreateDomainNameCommand, expectedParams, true); - - expect(commandCalls.length).to.equal(1); - }); - - it("create custom domain regional", async () => { - const APIGatewayMock = mockClient(APIGatewayClient); - APIGatewayMock.on(CreateDomainNameCommand).resolves({ - distributionDomainName: "foo", - securityPolicy: "TLS_1_0" - }); - - const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - basePath: "test_basepath", - endpointType: Globals.endpointTypes.regional, - securityPolicy: "tls_1_0", - certificateArn: "test_arn" - })); - - const actualResult = await apiGatewayV1Wrapper.createCustomDomain(dc); - const expectedResult = new DomainInfo({ - distributionDomainName: "foo", - securityPolicy: "TLS_1_0" - }); - - expect(actualResult).to.eql(expectedResult); - - const expectedParams = { - domainName: dc.givenDomainName, - endpointConfiguration: { - types: [EndpointType.REGIONAL], - }, - securityPolicy: SecurityPolicy.TLS_1_0, - tags: { - ...Globals.serverless.service.provider.stackTags, - ...Globals.serverless.service.provider.tags, - }, - regionalCertificateArn: dc.certificateArn - } - const commandCalls = APIGatewayMock.commandCalls(CreateDomainNameCommand, expectedParams, true); - - expect(commandCalls.length).to.equal(1); - }); - - it("create custom domain with mutual TLS authentication", async () => { - const APIGatewayMock = mockClient(APIGatewayClient); - APIGatewayMock.on(CreateDomainNameCommand).resolves({ - distributionDomainName: "foo", - securityPolicy: "TLS_1_0" - }); - - const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - basePath: "test_basepath", - endpointType: Globals.endpointTypes.regional, - securityPolicy: "tls_1_0", - certificateArn: "test_arn", - tlsTruststoreUri: "s3://bucket-name/key-name", - tlsTruststoreVersion: "test_version" - })); - const actualResult = await apiGatewayV1Wrapper.createCustomDomain(dc); - const expectedResult = new DomainInfo({ - distributionDomainName: "foo", - securityPolicy: "TLS_1_0" - }); - - expect(actualResult).to.eql(expectedResult); - - const expectedParams = { - domainName: dc.givenDomainName, - endpointConfiguration: { - types: [EndpointType.REGIONAL], - }, - securityPolicy: SecurityPolicy.TLS_1_0, - tags: { - ...Globals.serverless.service.provider.stackTags, - ...Globals.serverless.service.provider.tags, - }, - regionalCertificateArn: dc.certificateArn, - mutualTlsAuthentication: { - truststoreUri: dc.tlsTruststoreUri, - truststoreVersion: dc.tlsTruststoreVersion - } - } - const commandCalls = APIGatewayMock.commandCalls(CreateDomainNameCommand, expectedParams, true); - - expect(commandCalls.length).to.equal(1); - }); + it("create custom domain with mutual TLS authentication", async () => { + const APIGatewayMock = mockClient(APIGatewayClient); + APIGatewayMock.on(CreateDomainNameCommand).resolves({ + distributionDomainName: "foo", + securityPolicy: "TLS_1_0" + }); + + const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain", + basePath: "test_basepath", + endpointType: Globals.endpointTypes.regional, + securityPolicy: "tls_1_0", + certificateArn: "test_arn", + tlsTruststoreUri: "s3://bucket-name/key-name", + tlsTruststoreVersion: "test_version" + })); + const actualResult = await apiGatewayV1Wrapper.createCustomDomain(dc); + const expectedResult = new DomainInfo({ + distributionDomainName: "foo", + securityPolicy: "TLS_1_0" + }); + + expect(actualResult).to.eql(expectedResult); + + const expectedParams = { + domainName: dc.givenDomainName, + endpointConfiguration: { + types: [EndpointType.REGIONAL] + }, + securityPolicy: SecurityPolicy.TLS_1_0, + tags: { + ...Globals.serverless.service.provider.stackTags, + ...Globals.serverless.service.provider.tags + }, + regionalCertificateArn: dc.certificateArn, + mutualTlsAuthentication: { + truststoreUri: dc.tlsTruststoreUri, + truststoreVersion: dc.tlsTruststoreVersion + } + }; + const commandCalls = APIGatewayMock.commandCalls(CreateDomainNameCommand, expectedParams, true); + + expect(commandCalls.length).to.equal(1); + }); - it("create custom domain failure", async () => { - const APIGatewayMock = mockClient(APIGatewayClient); - APIGatewayMock.on(CreateDomainNameCommand).rejects(); - - const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - basePath: "test_basepath", - endpointType: Globals.endpointTypes.regional, - securityPolicy: "tls_1_0", - certificateArn: "test_arn", - })); - - let errored = false; - try { - await apiGatewayV1Wrapper.createCustomDomain(dc); - } catch (err) { - errored = true; - expect(err.message).to.contains("V1 - Failed to create custom domain"); - } - expect(errored).to.equal(true); - }); + it("create custom domain failure", async () => { + const APIGatewayMock = mockClient(APIGatewayClient); + APIGatewayMock.on(CreateDomainNameCommand).rejects(); + + const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain", + basePath: "test_basepath", + endpointType: Globals.endpointTypes.regional, + securityPolicy: "tls_1_0", + certificateArn: "test_arn" + })); + + let errored = false; + try { + await apiGatewayV1Wrapper.createCustomDomain(dc); + } catch (err) { + errored = true; + expect(err.message).to.contains("V1 - Failed to create custom domain"); + } + expect(errored).to.equal(true); + }); - it("get custom domain", async () => { - const APIGatewayMock = mockClient(APIGatewayClient); - APIGatewayMock.on(GetDomainNameCommand).resolves({ - domainName: "test_domain", - regionalHostedZoneId: "test_id" - }); + it("get custom domain", async () => { + const APIGatewayMock = mockClient(APIGatewayClient); + APIGatewayMock.on(GetDomainNameCommand).resolves({ + domainName: "test_domain", + regionalHostedZoneId: "test_id" + }); - const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - })); + const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain" + })); - const actualResult = await apiGatewayV1Wrapper.getCustomDomain(dc); - const expectedResult = new DomainInfo({ - domainName: "test_domain", - regionalHostedZoneId: "test_id" - }); + const actualResult = await apiGatewayV1Wrapper.getCustomDomain(dc); + const expectedResult = new DomainInfo({ + domainName: "test_domain", + regionalHostedZoneId: "test_id" + }); - expect(actualResult).to.eql(expectedResult); + expect(actualResult).to.eql(expectedResult); - const expectedParams = { - domainName: dc.givenDomainName, - } - const commandCalls = APIGatewayMock.commandCalls(GetDomainNameCommand, expectedParams, true); + const expectedParams = { + domainName: dc.givenDomainName + }; + const commandCalls = APIGatewayMock.commandCalls(GetDomainNameCommand, expectedParams, true); - expect(commandCalls.length).to.equal(1); - }); + expect(commandCalls.length).to.equal(1); + }); - it("get custom domain not found", async () => { - const APIGatewayMock = mockClient(APIGatewayClient); - APIGatewayMock.on(GetDomainNameCommand).rejects({ - "$metadata": {httpStatusCode: 404} - }); - - const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - })); - - let errored = false; - try { - await apiGatewayV1Wrapper.getCustomDomain(dc); - } catch (err) { - errored = true; - } - expect(errored).to.equal(false); - expect(consoleOutput[0]).to.contains("\'test_domain\' does not exist."); - }); + it("get custom domain not found", async () => { + const APIGatewayMock = mockClient(APIGatewayClient); + APIGatewayMock.on(GetDomainNameCommand).rejects({ + $metadata: { httpStatusCode: 404 } + }); + + const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain" + })); + + let errored = false; + try { + await apiGatewayV1Wrapper.getCustomDomain(dc); + } catch (err) { + errored = true; + } + expect(errored).to.equal(false); + expect(consoleOutput[0]).to.contains("'test_domain' does not exist."); + }); - it("get custom domain failure", async () => { - const APIGatewayMock = mockClient(APIGatewayClient); - APIGatewayMock.on(GetDomainNameCommand).rejects({ - "$metadata": {httpStatusCode: 400} - }); - - const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - })); - - let errored = false; - try { - await apiGatewayV1Wrapper.getCustomDomain(dc); - } catch (err) { - errored = true; - expect(err.message).to.contains("V1 - Unable to fetch information about"); - } - expect(errored).to.equal(true); - }); + it("get custom domain failure", async () => { + const APIGatewayMock = mockClient(APIGatewayClient); + APIGatewayMock.on(GetDomainNameCommand).rejects({ + $metadata: { httpStatusCode: 400 } + }); + + const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain" + })); + + let errored = false; + try { + await apiGatewayV1Wrapper.getCustomDomain(dc); + } catch (err) { + errored = true; + expect(err.message).to.contains("V1 - Unable to fetch information about"); + } + expect(errored).to.equal(true); + }); - it("delete custom domain", async () => { - const APIGatewayMock = mockClient(APIGatewayClient); - APIGatewayMock.on(DeleteDomainNameCommand).resolves(null); + it("delete custom domain", async () => { + const APIGatewayMock = mockClient(APIGatewayClient); + APIGatewayMock.on(DeleteDomainNameCommand).resolves(null); - const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - })); + const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain" + })); - await apiGatewayV1Wrapper.deleteCustomDomain(dc); + await apiGatewayV1Wrapper.deleteCustomDomain(dc); - const expectedParams = { - domainName: dc.givenDomainName, - } - const commandCalls = APIGatewayMock.commandCalls(DeleteDomainNameCommand, expectedParams, true); + const expectedParams = { + domainName: dc.givenDomainName + }; + const commandCalls = APIGatewayMock.commandCalls(DeleteDomainNameCommand, expectedParams, true); - expect(commandCalls.length).to.equal(1); - }); + expect(commandCalls.length).to.equal(1); + }); - it("delete custom domain failure", async () => { - const APIGatewayMock = mockClient(APIGatewayClient); - APIGatewayMock.on(DeleteDomainNameCommand).rejects(); - - const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - })); - - let errored = false; - try { - await apiGatewayV1Wrapper.deleteCustomDomain(dc); - } catch (err) { - errored = true; - expect(err.message).to.contains("V1 - Failed to delete custom domain"); - } - expect(errored).to.equal(true); - }); + it("delete custom domain failure", async () => { + const APIGatewayMock = mockClient(APIGatewayClient); + APIGatewayMock.on(DeleteDomainNameCommand).rejects(); + + const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain" + })); + + let errored = false; + try { + await apiGatewayV1Wrapper.deleteCustomDomain(dc); + } catch (err) { + errored = true; + expect(err.message).to.contains("V1 - Failed to delete custom domain"); + } + expect(errored).to.equal(true); + }); + }); + + describe("Base path", () => { + it("create base path mapping", async () => { + const APIGatewayMock = mockClient(APIGatewayClient); + APIGatewayMock.on(CreateBasePathMappingCommand).resolves(null); + + const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain", + basePath: "test_basepath", + apiId: "test_rest_api_id" + })); + + await apiGatewayV1Wrapper.createBasePathMapping(dc); + + const expectedParams = { + basePath: dc.basePath, + domainName: dc.givenDomainName, + restApiId: dc.apiId, + stage: dc.stage + }; + const commandCalls = APIGatewayMock.commandCalls(CreateBasePathMappingCommand, expectedParams, true); + + expect(commandCalls.length).to.equal(1); + expect(consoleOutput[0]).to.contains("V1 - Created API mapping"); }); - describe("Base path", () => { - it("create base path mapping", async () => { - const APIGatewayMock = mockClient(APIGatewayClient); - APIGatewayMock.on(CreateBasePathMappingCommand).resolves(null); - - const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - basePath: "test_basepath", - apiId: "test_rest_api_id", - })); - - await apiGatewayV1Wrapper.createBasePathMapping(dc); - - const expectedParams = { - basePath: dc.basePath, - domainName: dc.givenDomainName, - restApiId: dc.apiId, - stage: dc.stage, - } - const commandCalls = APIGatewayMock.commandCalls(CreateBasePathMappingCommand, expectedParams, true); + it("create base path mapping failure", async () => { + const APIGatewayMock = mockClient(APIGatewayClient); + APIGatewayMock.on(CreateBasePathMappingCommand).rejects(); + + const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain", + basePath: "test_basepath", + apiId: "test_rest_api_id" + })); + + let errored = false; + try { + await apiGatewayV1Wrapper.createBasePathMapping(dc); + } catch (err) { + errored = true; + expect(err.message).to.contains("Unable to create base path mapping for"); + } + expect(errored).to.equal(true); + }); - expect(commandCalls.length).to.equal(1); - expect(consoleOutput[0]).to.contains("V1 - Created API mapping"); - }); + it("get base path mapping", async () => { + const APIGatewayMock = mockClient(APIGatewayClient); + APIGatewayMock.on(GetBasePathMappingsCommand).resolves({ + items: [{ + restApiId: "test_rest_api_id", + basePath: "test", + stage: "test" + }] + }); + + const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain" + })); + + const actualResult = await apiGatewayV1Wrapper.getBasePathMappings(dc); + const expectedResult = [ + new ApiGatewayMap("test_rest_api_id", "test", "test", null) + ]; + + expect(actualResult).to.eql(expectedResult); + + const expectedParams = { + domainName: dc.givenDomainName + }; + const commandCalls = APIGatewayMock.commandCalls(GetBasePathMappingsCommand, expectedParams, true); + + expect(commandCalls.length).to.equal(1); + }); - it("create base path mapping failure", async () => { - const APIGatewayMock = mockClient(APIGatewayClient); - APIGatewayMock.on(CreateBasePathMappingCommand).rejects(); - - const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - basePath: "test_basepath", - apiId: "test_rest_api_id", - })); - - let errored = false; - try { - await apiGatewayV1Wrapper.createBasePathMapping(dc); - } catch (err) { - errored = true; - expect(err.message).to.contains("Unable to create base path mapping for"); + it("get all base path mappings", async () => { + const APIGatewayCMock = mockClient(APIGatewayClient); + APIGatewayCMock.on(GetBasePathMappingsCommand) + .resolvesOnce({ + items: [ + { + restApiId: "test_rest_api_id", + basePath: "test", + stage: "test" } - expect(errored).to.equal(true); - }); - - it("get base path mapping", async () => { - const APIGatewayMock = mockClient(APIGatewayClient); - APIGatewayMock.on(GetBasePathMappingsCommand).resolves({ - items: [{ - restApiId: "test_rest_api_id", - basePath: "test", - stage: "test" - }] - }); - - const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain" - })); - - const actualResult = await apiGatewayV1Wrapper.getBasePathMappings(dc); - const expectedResult = [ - new ApiGatewayMap("test_rest_api_id", "test", "test", null) - ] - - expect(actualResult).to.eql(expectedResult); - - const expectedParams = { - domainName: dc.givenDomainName, + ], + position: "position" + }) + .resolves({ + items: [ + { + restApiId: "test_rest_api_id2", + basePath: "test2", + stage: "test" } - const commandCalls = APIGatewayMock.commandCalls(GetBasePathMappingsCommand, expectedParams, true); - - expect(commandCalls.length).to.equal(1); + ] }); - it("get all base path mappings", async () => { - const APIGatewayCMock = mockClient(APIGatewayClient); - APIGatewayCMock.on(GetBasePathMappingsCommand) - .resolvesOnce({ - items: [ - { - restApiId: "test_rest_api_id", - basePath: "test", - stage: "test" - }, - ], - position: "position", - }) - .resolves({ - items: [ - { - restApiId: "test_rest_api_id2", - basePath: "test2", - stage: "test", - }, - ], - }); - - const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain" - })); - - const actualResult = await apiGatewayV1Wrapper.getBasePathMappings(dc); - const expectedResult = [ - new ApiGatewayMap("test_rest_api_id", "test", "test", null), - new ApiGatewayMap("test_rest_api_id2", "test2", "test", null), - ] - - expect(actualResult).to.eql(expectedResult); - expect(APIGatewayCMock.calls().length).to.equal(2); - }); + const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain" + })); - it("get base path mapping failure", async () => { - const APIGatewayMock = mockClient(APIGatewayClient); - APIGatewayMock.on(GetBasePathMappingsCommand).rejects(); - - const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain" - })); - - let errored = false; - try { - await apiGatewayV1Wrapper.getBasePathMappings(dc); - } catch (err) { - errored = true; - expect(err.message).to.contains("Unable to get Base Path Mappings"); - } - expect(errored).to.equal(true); - }); + const actualResult = await apiGatewayV1Wrapper.getBasePathMappings(dc); + const expectedResult = [ + new ApiGatewayMap("test_rest_api_id", "test", "test", null), + new ApiGatewayMap("test_rest_api_id2", "test2", "test", null) + ]; - it("update base path mapping", async () => { - const APIGatewayMock = mockClient(APIGatewayClient); - APIGatewayMock.on(UpdateBasePathMappingCommand).resolves(null); - - const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - basePath: "test_basepath", - apiId: "test_rest_api_id", - })); - dc.apiMapping = { - apiId: "old_api_id", - basePath: "old_basepath", - stage: "test", - apiMappingId: null - }; - - await apiGatewayV1Wrapper.updateBasePathMapping(dc); - const a: PatchOperation = null; - const expectedParams = { - basePath: dc.apiMapping.basePath, - domainName: dc.givenDomainName, - patchOperations: [{ - op: Op.replace, - path: "/basePath", - value: dc.basePath, - }] - } - const commandCalls = APIGatewayMock.commandCalls(UpdateBasePathMappingCommand, expectedParams, true); + expect(actualResult).to.eql(expectedResult); + expect(APIGatewayCMock.calls().length).to.equal(2); + }); - expect(commandCalls.length).to.equal(1); - expect(consoleOutput[0]).to.contains("V1 - Updated API mapping from"); - }); + it("get base path mapping failure", async () => { + const APIGatewayMock = mockClient(APIGatewayClient); + APIGatewayMock.on(GetBasePathMappingsCommand).rejects(); + + const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain" + })); + + let errored = false; + try { + await apiGatewayV1Wrapper.getBasePathMappings(dc); + } catch (err) { + errored = true; + expect(err.message).to.contains("Unable to get Base Path Mappings"); + } + expect(errored).to.equal(true); + }); - it("update base path mapping failure", async () => { - const APIGatewayMock = mockClient(APIGatewayClient); - APIGatewayMock.on(UpdateBasePathMappingCommand).rejects(); - - const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - basePath: "test_basepath", - apiId: "test_rest_api_id", - })); - dc.apiMapping = { - apiId: "old_api_id", - basePath: "old_basepath", - stage: "test", - apiMappingId: null - }; - - let errored = false; - try { - await apiGatewayV1Wrapper.updateBasePathMapping(dc); - } catch (err) { - errored = true; - expect(err.message).to.contains("V1 - Unable to update base path mapping for"); - } - expect(errored).to.equal(true); - }); + it("update base path mapping", async () => { + const APIGatewayMock = mockClient(APIGatewayClient); + APIGatewayMock.on(UpdateBasePathMappingCommand).resolves(null); + + const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain", + basePath: "test_basepath", + apiId: "test_rest_api_id" + })); + dc.apiMapping = { + apiId: "old_api_id", + basePath: "old_basepath", + stage: "test", + apiMappingId: null + }; + + await apiGatewayV1Wrapper.updateBasePathMapping(dc); + const expectedParams = { + basePath: dc.apiMapping.basePath, + domainName: dc.givenDomainName, + patchOperations: [{ + op: Op.replace, + path: "/basePath", + value: dc.basePath + }] + }; + const commandCalls = APIGatewayMock.commandCalls(UpdateBasePathMappingCommand, expectedParams, true); + + expect(commandCalls.length).to.equal(1); + expect(consoleOutput[0]).to.contains("V1 - Updated API mapping from"); + }); - it("delete base path mapping", async () => { - const APIGatewayMock = mockClient(APIGatewayClient); - APIGatewayMock.on(DeleteBasePathMappingCommand).resolves(null); - - const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - basePath: "test_basepath", - apiId: "test_rest_api_id", - })); - dc.apiMapping = { - apiId: "old_api_id", - basePath: "old_basepath", - stage: "test", - apiMappingId: null - }; - - await apiGatewayV1Wrapper.deleteBasePathMapping(dc); - - const expectedParams = { - basePath: dc.apiMapping.basePath, - domainName: dc.givenDomainName, - } - const commandCalls = APIGatewayMock.commandCalls(DeleteBasePathMappingCommand, expectedParams, true); + it("update base path mapping failure", async () => { + const APIGatewayMock = mockClient(APIGatewayClient); + APIGatewayMock.on(UpdateBasePathMappingCommand).rejects(); + + const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain", + basePath: "test_basepath", + apiId: "test_rest_api_id" + })); + dc.apiMapping = { + apiId: "old_api_id", + basePath: "old_basepath", + stage: "test", + apiMappingId: null + }; + + let errored = false; + try { + await apiGatewayV1Wrapper.updateBasePathMapping(dc); + } catch (err) { + errored = true; + expect(err.message).to.contains("V1 - Unable to update base path mapping for"); + } + expect(errored).to.equal(true); + }); - expect(commandCalls.length).to.equal(1); - expect(consoleOutput[0]).to.contains("V1 - Removed"); - }); + it("delete base path mapping", async () => { + const APIGatewayMock = mockClient(APIGatewayClient); + APIGatewayMock.on(DeleteBasePathMappingCommand).resolves(null); + + const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain", + basePath: "test_basepath", + apiId: "test_rest_api_id" + })); + dc.apiMapping = { + apiId: "old_api_id", + basePath: "old_basepath", + stage: "test", + apiMappingId: null + }; + + await apiGatewayV1Wrapper.deleteBasePathMapping(dc); + + const expectedParams = { + basePath: dc.apiMapping.basePath, + domainName: dc.givenDomainName + }; + const commandCalls = APIGatewayMock.commandCalls(DeleteBasePathMappingCommand, expectedParams, true); + + expect(commandCalls.length).to.equal(1); + expect(consoleOutput[0]).to.contains("V1 - Removed"); + }); - it("delete base path mapping failure", async () => { - const APIGatewayMock = mockClient(APIGatewayClient); - APIGatewayMock.on(DeleteBasePathMappingCommand).rejects(); - - const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - basePath: "test_basepath", - apiId: "test_rest_api_id", - })); - dc.apiMapping = { - apiId: "old_api_id", - basePath: "old_basepath", - stage: "test", - apiMappingId: null - }; - - let errored = false; - try { - await apiGatewayV1Wrapper.deleteBasePathMapping(dc); - } catch (err) { - errored = true; - expect(err.message).to.contains("V1 - Unable to remove base path mapping for"); - } - expect(errored).to.equal(true); - }); + it("delete base path mapping failure", async () => { + const APIGatewayMock = mockClient(APIGatewayClient); + APIGatewayMock.on(DeleteBasePathMappingCommand).rejects(); + + const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain", + basePath: "test_basepath", + apiId: "test_rest_api_id" + })); + dc.apiMapping = { + apiId: "old_api_id", + basePath: "old_basepath", + stage: "test", + apiMappingId: null + }; + + let errored = false; + try { + await apiGatewayV1Wrapper.deleteBasePathMapping(dc); + } catch (err) { + errored = true; + expect(err.message).to.contains("V1 - Unable to remove base path mapping for"); + } + expect(errored).to.equal(true); }); + }); }); diff --git a/test/unit-tests/aws/api-gateway-v2-wrapper.test.ts b/test/unit-tests/aws/api-gateway-v2-wrapper.test.ts index dbf08f41..68adbdfd 100644 --- a/test/unit-tests/aws/api-gateway-v2-wrapper.test.ts +++ b/test/unit-tests/aws/api-gateway-v2-wrapper.test.ts @@ -1,601 +1,600 @@ -import {mockClient} from "aws-sdk-client-mock"; +import { mockClient } from "aws-sdk-client-mock"; import { - ApiGatewayV2Client, CreateApiMappingCommand, - CreateDomainNameCommand, DeleteApiMappingCommand, - DeleteDomainNameCommand, EndpointType, GetApiMappingsCommand, - GetDomainNameCommand, SecurityPolicy, UpdateApiMappingCommand + ApiGatewayV2Client, CreateApiMappingCommand, + CreateDomainNameCommand, DeleteApiMappingCommand, + DeleteDomainNameCommand, EndpointType, GetApiMappingsCommand, + GetDomainNameCommand, SecurityPolicy, UpdateApiMappingCommand } from "@aws-sdk/client-apigatewayv2"; -import {consoleOutput, expect, getDomainConfig} from "../base"; +import { consoleOutput, expect, getDomainConfig } from "../base"; import Globals from "../../../src/globals"; import DomainConfig = require("../../../src/models/domain-config"); import DomainInfo = require("../../../src/models/domain-info"); import ApiGatewayMap = require("../../../src/models/api-gateway-map"); import APIGatewayV2Wrapper = require("../../../src/aws/api-gateway-v2-wrapper"); - describe("API Gateway V2 wrapper checks", () => { - beforeEach(() => { - consoleOutput.length = 0; + beforeEach(() => { + consoleOutput.length = 0; + }); + + it("Initialization", () => { + const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); + const actualResult = apiGatewayV2Wrapper.apiGateway.config[0].region; + expect(actualResult).to.equal(Globals.currentRegion); + }); + + describe("Custom domain", () => { + it("create custom domain edge", async () => { + const APIGatewayMock = mockClient(ApiGatewayV2Client); + APIGatewayMock.on(CreateDomainNameCommand).resolves({ + DomainName: "foo", + DomainNameConfigurations: [{ SecurityPolicy: "TLS_1_2" }] + }); + + const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain", + basePath: "test_basepath", + endpointType: Globals.endpointTypes.edge, + securityPolicy: "tls_1_0", + certificateArn: "test_arn" + })); + + const actualResult = await apiGatewayV2Wrapper.createCustomDomain(dc); + const expectedResult = new DomainInfo({ + DomainName: "foo", + DomainNameConfigurations: [{ SecurityPolicy: "TLS_1_2" }] + }); + + expect(actualResult).to.eql(expectedResult); + + const expectedParams = { + DomainName: dc.givenDomainName, + DomainNameConfigurations: [ + { + CertificateArn: dc.certificateArn, + EndpointType: EndpointType.EDGE, + SecurityPolicy: SecurityPolicy.TLS_1_0 + } + ], + Tags: { + ...Globals.serverless.service.provider.stackTags, + ...Globals.serverless.service.provider.tags + } + }; + const commandCalls = APIGatewayMock.commandCalls(CreateDomainNameCommand, expectedParams, true); + + expect(commandCalls.length).to.equal(1); }); - it("Initialization", () => { - const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); - const actualResult = apiGatewayV2Wrapper.apiGateway.config[0].region; - expect(actualResult).to.equal(Globals.currentRegion); + it("create custom domain regional", async () => { + const APIGatewayMock = mockClient(ApiGatewayV2Client); + APIGatewayMock.on(CreateDomainNameCommand).resolves({ + DomainName: "foo", + DomainNameConfigurations: [{ SecurityPolicy: "TLS_1_2" }] + }); + + const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain", + basePath: "test_basepath", + endpointType: Globals.endpointTypes.regional, + securityPolicy: "tls_1_0", + certificateArn: "test_arn" + })); + + const actualResult = await apiGatewayV2Wrapper.createCustomDomain(dc); + const expectedResult = new DomainInfo({ + DomainName: "foo", + DomainNameConfigurations: [{ SecurityPolicy: "TLS_1_2" }] + }); + + expect(actualResult).to.eql(expectedResult); + + const expectedParams = { + DomainName: dc.givenDomainName, + DomainNameConfigurations: [ + { + CertificateArn: dc.certificateArn, + EndpointType: EndpointType.REGIONAL, + SecurityPolicy: SecurityPolicy.TLS_1_0 + } + ], + Tags: { + ...Globals.serverless.service.provider.stackTags, + ...Globals.serverless.service.provider.tags + } + }; + const commandCalls = APIGatewayMock.commandCalls(CreateDomainNameCommand, expectedParams, true); + + expect(commandCalls.length).to.equal(1); }); - describe("Custom domain", () => { - it("create custom domain edge", async () => { - const APIGatewayMock = mockClient(ApiGatewayV2Client); - APIGatewayMock.on(CreateDomainNameCommand).resolves({ - DomainName: "foo", - DomainNameConfigurations: [{SecurityPolicy: "TLS_1_2"}] - }); - - const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - basePath: "test_basepath", - endpointType: Globals.endpointTypes.edge, - securityPolicy: "tls_1_0", - certificateArn: "test_arn" - })); - - const actualResult = await apiGatewayV2Wrapper.createCustomDomain(dc); - const expectedResult = new DomainInfo({ - DomainName: "foo", - DomainNameConfigurations: [{SecurityPolicy: "TLS_1_2"}] - }); - - expect(actualResult).to.eql(expectedResult); - - const expectedParams = { - DomainName: dc.givenDomainName, - DomainNameConfigurations: [ - { - CertificateArn: dc.certificateArn, - EndpointType: EndpointType.EDGE, - SecurityPolicy: SecurityPolicy.TLS_1_0 - } - ], - Tags: { - ...Globals.serverless.service.provider.stackTags, - ...Globals.serverless.service.provider.tags, - } - } - const commandCalls = APIGatewayMock.commandCalls(CreateDomainNameCommand, expectedParams, true); - - expect(commandCalls.length).to.equal(1); - }); + it("create custom domain with mutual TLS authentication", async () => { + const APIGatewayMock = mockClient(ApiGatewayV2Client); + APIGatewayMock.on(CreateDomainNameCommand).resolves({ + DomainName: "foo", + DomainNameConfigurations: [{ SecurityPolicy: "TLS_1_2" }] + }); + + const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain", + basePath: "test_basepath", + endpointType: Globals.endpointTypes.regional, + securityPolicy: "tls_1_0", + certificateArn: "test_arn", + tlsTruststoreUri: "s3://bucket-name/key-name", + tlsTruststoreVersion: "test_version" + })); + const actualResult = await apiGatewayV2Wrapper.createCustomDomain(dc); + const expectedResult = new DomainInfo({ + DomainName: "foo", + DomainNameConfigurations: [{ SecurityPolicy: "TLS_1_2" }] + }); + + expect(actualResult).to.eql(expectedResult); + + const expectedParams = { + DomainName: dc.givenDomainName, + DomainNameConfigurations: [ + { + CertificateArn: dc.certificateArn, + EndpointType: EndpointType.REGIONAL, + SecurityPolicy: SecurityPolicy.TLS_1_0 + } + ], + Tags: { + ...Globals.serverless.service.provider.stackTags, + ...Globals.serverless.service.provider.tags + }, + MutualTlsAuthentication: { + TruststoreUri: dc.tlsTruststoreUri, + TruststoreVersion: dc.tlsTruststoreVersion + } + }; + const commandCalls = APIGatewayMock.commandCalls(CreateDomainNameCommand, expectedParams, true); + + expect(commandCalls.length).to.equal(1); + }); - it("create custom domain regional", async () => { - const APIGatewayMock = mockClient(ApiGatewayV2Client); - APIGatewayMock.on(CreateDomainNameCommand).resolves({ - DomainName: "foo", - DomainNameConfigurations: [{SecurityPolicy: "TLS_1_2"}] - }); - - const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - basePath: "test_basepath", - endpointType: Globals.endpointTypes.regional, - securityPolicy: "tls_1_0", - certificateArn: "test_arn" - })); - - const actualResult = await apiGatewayV2Wrapper.createCustomDomain(dc); - const expectedResult = new DomainInfo({ - DomainName: "foo", - DomainNameConfigurations: [{SecurityPolicy: "TLS_1_2"}] - }); - - expect(actualResult).to.eql(expectedResult); - - const expectedParams = { - DomainName: dc.givenDomainName, - DomainNameConfigurations: [ - { - CertificateArn: dc.certificateArn, - EndpointType: EndpointType.REGIONAL, - SecurityPolicy: SecurityPolicy.TLS_1_0 - } - ], - Tags: { - ...Globals.serverless.service.provider.stackTags, - ...Globals.serverless.service.provider.tags, - } - } - const commandCalls = APIGatewayMock.commandCalls(CreateDomainNameCommand, expectedParams, true); - - expect(commandCalls.length).to.equal(1); - }); + it("create custom domain failure", async () => { + const APIGatewayMock = mockClient(ApiGatewayV2Client); + APIGatewayMock.on(CreateDomainNameCommand).rejects(); + + const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain", + basePath: "test_basepath", + endpointType: Globals.endpointTypes.regional, + securityPolicy: "tls_1_0", + certificateArn: "test_arn" + })); + + let errored = false; + try { + await apiGatewayV2Wrapper.createCustomDomain(dc); + } catch (err) { + errored = true; + expect(err.message).to.contains("V2 - Failed to create custom domain"); + } + expect(errored).to.equal(true); + }); - it("create custom domain with mutual TLS authentication", async () => { - const APIGatewayMock = mockClient(ApiGatewayV2Client); - APIGatewayMock.on(CreateDomainNameCommand).resolves({ - DomainName: "foo", - DomainNameConfigurations: [{SecurityPolicy: "TLS_1_2"}] - }); - - const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - basePath: "test_basepath", - endpointType: Globals.endpointTypes.regional, - securityPolicy: "tls_1_0", - certificateArn: "test_arn", - tlsTruststoreUri: "s3://bucket-name/key-name", - tlsTruststoreVersion: "test_version" - })); - const actualResult = await apiGatewayV2Wrapper.createCustomDomain(dc); - const expectedResult = new DomainInfo({ - DomainName: "foo", - DomainNameConfigurations: [{SecurityPolicy: "TLS_1_2"}] - }); - - expect(actualResult).to.eql(expectedResult); - - const expectedParams = { - DomainName: dc.givenDomainName, - DomainNameConfigurations: [ - { - CertificateArn: dc.certificateArn, - EndpointType: EndpointType.REGIONAL, - SecurityPolicy: SecurityPolicy.TLS_1_0 - } - ], - Tags: { - ...Globals.serverless.service.provider.stackTags, - ...Globals.serverless.service.provider.tags, - }, - MutualTlsAuthentication: { - TruststoreUri: dc.tlsTruststoreUri, - TruststoreVersion: dc.tlsTruststoreVersion - } - } - const commandCalls = APIGatewayMock.commandCalls(CreateDomainNameCommand, expectedParams, true); - - expect(commandCalls.length).to.equal(1); - }); + it("get custom domain", async () => { + const APIGatewayMock = mockClient(ApiGatewayV2Client); + APIGatewayMock.on(GetDomainNameCommand).resolves({ + DomainName: "test_domain", + DomainNameConfigurations: [{ SecurityPolicy: "TLS_1_2" }] + }); + + const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain" + })); + + const actualResult = await apiGatewayV2Wrapper.getCustomDomain(dc); + const expectedResult = new DomainInfo({ + domainName: "test_domain", + defaultHostedZoneId: "Z2FDTNDATAQYW2", + defaultSecurityPolicy: "TLS_1_2" + }); + + expect(actualResult).to.eql(expectedResult); + + const expectedParams = { + DomainName: dc.givenDomainName + }; + const commandCalls = APIGatewayMock.commandCalls(GetDomainNameCommand, expectedParams, true); + + expect(commandCalls.length).to.equal(1); + }); - it("create custom domain failure", async () => { - const APIGatewayMock = mockClient(ApiGatewayV2Client); - APIGatewayMock.on(CreateDomainNameCommand).rejects(); - - const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - basePath: "test_basepath", - endpointType: Globals.endpointTypes.regional, - securityPolicy: "tls_1_0", - certificateArn: "test_arn", - })); - - let errored = false; - try { - await apiGatewayV2Wrapper.createCustomDomain(dc); - } catch (err) { - errored = true; - expect(err.message).to.contains("V2 - Failed to create custom domain"); - } - expect(errored).to.equal(true); - }); + it("get custom domain not found", async () => { + const APIGatewayMock = mockClient(ApiGatewayV2Client); + APIGatewayMock.on(GetDomainNameCommand).rejects({ + $metadata: { httpStatusCode: 404 } + }); + + const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain" + })); + + let errored = false; + try { + await apiGatewayV2Wrapper.getCustomDomain(dc); + } catch (err) { + errored = true; + } + expect(errored).to.equal(false); + expect(consoleOutput[0]).to.contains("'test_domain' does not exist."); + }); - it("get custom domain", async () => { - const APIGatewayMock = mockClient(ApiGatewayV2Client); - APIGatewayMock.on(GetDomainNameCommand).resolves({ - DomainName: "test_domain", - DomainNameConfigurations: [{SecurityPolicy: "TLS_1_2"}] - }); - - const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - })); - - const actualResult = await apiGatewayV2Wrapper.getCustomDomain(dc); - const expectedResult = new DomainInfo({ - domainName: "test_domain", - defaultHostedZoneId: "Z2FDTNDATAQYW2", - defaultSecurityPolicy: "TLS_1_2" - }); - - expect(actualResult).to.eql(expectedResult); - - const expectedParams = { - DomainName: dc.givenDomainName, - } - const commandCalls = APIGatewayMock.commandCalls(GetDomainNameCommand, expectedParams, true); - - expect(commandCalls.length).to.equal(1); - }); + it("get custom domain failure", async () => { + const APIGatewayMock = mockClient(ApiGatewayV2Client); + APIGatewayMock.on(GetDomainNameCommand).rejects({ + $metadata: { httpStatusCode: 400 } + }); + + const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain" + })); + + let errored = false; + try { + await apiGatewayV2Wrapper.getCustomDomain(dc); + } catch (err) { + errored = true; + expect(err.message).to.contains("V2 - Unable to fetch information about"); + } + expect(errored).to.equal(true); + }); - it("get custom domain not found", async () => { - const APIGatewayMock = mockClient(ApiGatewayV2Client); - APIGatewayMock.on(GetDomainNameCommand).rejects({ - "$metadata": {httpStatusCode: 404} - }); - - const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - })); - - let errored = false; - try { - await apiGatewayV2Wrapper.getCustomDomain(dc); - } catch (err) { - errored = true; - } - expect(errored).to.equal(false); - expect(consoleOutput[0]).to.contains("\'test_domain\' does not exist."); - }); + it("delete custom domain", async () => { + const APIGatewayMock = mockClient(ApiGatewayV2Client); + APIGatewayMock.on(DeleteDomainNameCommand).resolves(null); - it("get custom domain failure", async () => { - const APIGatewayMock = mockClient(ApiGatewayV2Client); - APIGatewayMock.on(GetDomainNameCommand).rejects({ - "$metadata": {httpStatusCode: 400} - }); - - const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - })); - - let errored = false; - try { - await apiGatewayV2Wrapper.getCustomDomain(dc); - } catch (err) { - errored = true; - expect(err.message).to.contains("V2 - Unable to fetch information about"); - } - expect(errored).to.equal(true); - }); + const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain" + })); - it("delete custom domain", async () => { - const APIGatewayMock = mockClient(ApiGatewayV2Client); - APIGatewayMock.on(DeleteDomainNameCommand).resolves(null); + await apiGatewayV2Wrapper.deleteCustomDomain(dc); - const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - })); + const expectedParams = { + DomainName: dc.givenDomainName + }; + const commandCalls = APIGatewayMock.commandCalls(DeleteDomainNameCommand, expectedParams, true); - await apiGatewayV2Wrapper.deleteCustomDomain(dc); + expect(commandCalls.length).to.equal(1); + }); - const expectedParams = { - DomainName: dc.givenDomainName, - } - const commandCalls = APIGatewayMock.commandCalls(DeleteDomainNameCommand, expectedParams, true); + it("delete custom domain failure", async () => { + const APIGatewayMock = mockClient(ApiGatewayV2Client); + APIGatewayMock.on(DeleteDomainNameCommand).rejects(); + + const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain" + })); + + let errored = false; + try { + await apiGatewayV2Wrapper.deleteCustomDomain(dc); + } catch (err) { + errored = true; + expect(err.message).to.contains("V2 - Failed to delete custom domain"); + } + expect(errored).to.equal(true); + }); + }); + + describe("Base path", () => { + it("create base path mapping", async () => { + const APIGatewayMock = mockClient(ApiGatewayV2Client); + APIGatewayMock.on(CreateApiMappingCommand).resolves(null); + + const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain", + basePath: "test_basepath", + apiId: "test_rest_api_id" + })); + + await apiGatewayV2Wrapper.createBasePathMapping(dc); + + const expectedParams = { + ApiMappingKey: dc.basePath, + DomainName: dc.givenDomainName, + ApiId: dc.apiId, + Stage: dc.stage + }; + const commandCalls = APIGatewayMock.commandCalls(CreateApiMappingCommand, expectedParams, true); + + expect(commandCalls.length).to.equal(1); + expect(consoleOutput[0]).to.contains("V2 - Created API mapping"); + }); - expect(commandCalls.length).to.equal(1); - }); + it("create http base path mapping", async () => { + const APIGatewayMock = mockClient(ApiGatewayV2Client); + APIGatewayMock.on(CreateApiMappingCommand).resolves(null); + + const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain", + basePath: "test_basepath", + apiId: "test_rest_api_id", + apiType: Globals.apiTypes.http + })); + + await apiGatewayV2Wrapper.createBasePathMapping(dc); + + const expectedParams = { + ApiMappingKey: dc.basePath, + DomainName: dc.givenDomainName, + ApiId: dc.apiId, + Stage: dc.stage + }; + const commandCalls = APIGatewayMock.commandCalls(CreateApiMappingCommand, expectedParams, true); + + expect(commandCalls.length).to.equal(1); + expect(consoleOutput[0]).to.contains("V2 - Created API mapping"); + }); - it("delete custom domain failure", async () => { - const APIGatewayMock = mockClient(ApiGatewayV2Client); - APIGatewayMock.on(DeleteDomainNameCommand).rejects(); - - const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - })); - - let errored = false; - try { - await apiGatewayV2Wrapper.deleteCustomDomain(dc); - } catch (err) { - errored = true; - expect(err.message).to.contains("V2 - Failed to delete custom domain"); - } - expect(errored).to.equal(true); - }); + it("create base path mapping failure", async () => { + const APIGatewayMock = mockClient(ApiGatewayV2Client); + APIGatewayMock.on(CreateApiMappingCommand).rejects(); + + const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain", + basePath: "test_basepath", + apiId: "test_rest_api_id" + })); + + let errored = false; + try { + await apiGatewayV2Wrapper.createBasePathMapping(dc); + } catch (err) { + errored = true; + expect(err.message).to.contains("Unable to create base path mapping for"); + } + expect(errored).to.equal(true); }); - describe("Base path", () => { - it("create base path mapping", async () => { - const APIGatewayMock = mockClient(ApiGatewayV2Client); - APIGatewayMock.on(CreateApiMappingCommand).resolves(null); - - const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - basePath: "test_basepath", - apiId: "test_rest_api_id", - })); - - await apiGatewayV2Wrapper.createBasePathMapping(dc); - - const expectedParams = { - ApiMappingKey: dc.basePath, - DomainName: dc.givenDomainName, - ApiId: dc.apiId, - Stage: dc.stage, - } - const commandCalls = APIGatewayMock.commandCalls(CreateApiMappingCommand, expectedParams, true); - - expect(commandCalls.length).to.equal(1); - expect(consoleOutput[0]).to.contains("V2 - Created API mapping"); - }); + it("get base path mapping", async () => { + const APIGatewayMock = mockClient(ApiGatewayV2Client); + APIGatewayMock.on(GetApiMappingsCommand).resolves({ + Items: [{ + ApiId: "test_rest_api_id", + ApiMappingKey: "test", + Stage: "test", + ApiMappingId: "test_id" + }] + }); + + const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain" + })); + + const actualResult = await apiGatewayV2Wrapper.getBasePathMappings(dc); + const expectedResult = [ + new ApiGatewayMap("test_rest_api_id", "test", "test", "test_id") + ]; + + expect(actualResult).to.eql(expectedResult); + + const expectedParams = { + DomainName: dc.givenDomainName + }; + const commandCalls = APIGatewayMock.commandCalls(GetApiMappingsCommand, expectedParams, true); + + expect(commandCalls.length).to.equal(1); + }); - it("create http base path mapping", async () => { - const APIGatewayMock = mockClient(ApiGatewayV2Client); - APIGatewayMock.on(CreateApiMappingCommand).resolves(null); - - const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - basePath: "test_basepath", - apiId: "test_rest_api_id", - apiType: Globals.apiTypes.http - })); - - await apiGatewayV2Wrapper.createBasePathMapping(dc); - - const expectedParams = { - ApiMappingKey: dc.basePath, - DomainName: dc.givenDomainName, - ApiId: dc.apiId, - Stage: dc.stage, - } - const commandCalls = APIGatewayMock.commandCalls(CreateApiMappingCommand, expectedParams, true); - - expect(commandCalls.length).to.equal(1); - expect(consoleOutput[0]).to.contains("V2 - Created API mapping"); + it("get all base path mappings", async () => { + const APIGatewayMock = mockClient(ApiGatewayV2Client); + APIGatewayMock.on(GetApiMappingsCommand).resolvesOnce({ + Items: [{ + ApiId: "test_rest_api_id", + ApiMappingKey: "test", + Stage: "test", + ApiMappingId: "test_id" + }], + NextToken: "NextToken" + }) + .resolves({ + Items: [{ + ApiId: "test_rest_api_id2", + ApiMappingKey: "test2", + Stage: "test", + ApiMappingId: "test_id2" + }] }); - it("create base path mapping failure", async () => { - const APIGatewayMock = mockClient(ApiGatewayV2Client); - APIGatewayMock.on(CreateApiMappingCommand).rejects(); - - const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - basePath: "test_basepath", - apiId: "test_rest_api_id", - })); - - let errored = false; - try { - await apiGatewayV2Wrapper.createBasePathMapping(dc); - } catch (err) { - errored = true; - expect(err.message).to.contains("Unable to create base path mapping for"); - } - expect(errored).to.equal(true); - }); + const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain" + })); - it("get base path mapping", async () => { - const APIGatewayMock = mockClient(ApiGatewayV2Client); - APIGatewayMock.on(GetApiMappingsCommand).resolves({ - Items: [{ - ApiId: "test_rest_api_id", - ApiMappingKey: "test", - Stage: "test", - ApiMappingId: "test_id" - }] - }); - - const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain" - })); - - const actualResult = await apiGatewayV2Wrapper.getBasePathMappings(dc); - const expectedResult = [ - new ApiGatewayMap("test_rest_api_id", "test", "test", "test_id") - ] - - expect(actualResult).to.eql(expectedResult); - - const expectedParams = { - DomainName: dc.givenDomainName, - } - const commandCalls = APIGatewayMock.commandCalls(GetApiMappingsCommand, expectedParams, true); - - expect(commandCalls.length).to.equal(1); - }); + const actualResult = await apiGatewayV2Wrapper.getBasePathMappings(dc); + const expectedResult = [ + new ApiGatewayMap("test_rest_api_id", "test", "test", "test_id"), + new ApiGatewayMap("test_rest_api_id2", "test2", "test", "test_id2") + ]; - it("get all base path mappings", async () => { - const APIGatewayMock = mockClient(ApiGatewayV2Client); - APIGatewayMock.on(GetApiMappingsCommand).resolvesOnce({ - Items: [{ - ApiId: "test_rest_api_id", - ApiMappingKey: "test", - Stage: "test", - ApiMappingId: "test_id" - }], - NextToken: "NextToken" - }) - .resolves({ - Items: [{ - ApiId: "test_rest_api_id2", - ApiMappingKey: "test2", - Stage: "test", - ApiMappingId: "test_id2" - }] - }); - - const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain" - })); - - const actualResult = await apiGatewayV2Wrapper.getBasePathMappings(dc); - const expectedResult = [ - new ApiGatewayMap("test_rest_api_id", "test", "test", "test_id"), - new ApiGatewayMap("test_rest_api_id2", "test2", "test", "test_id2") - ] - - expect(actualResult).to.eql(expectedResult); - expect(APIGatewayMock.calls().length).to.equal(2); - }); + expect(actualResult).to.eql(expectedResult); + expect(APIGatewayMock.calls().length).to.equal(2); + }); - it("get base path mapping failure", async () => { - const APIGatewayMock = mockClient(ApiGatewayV2Client); - APIGatewayMock.on(GetApiMappingsCommand).rejects(); - - const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain" - })); - - let errored = false; - try { - await apiGatewayV2Wrapper.getBasePathMappings(dc); - } catch (err) { - errored = true; - expect(err.message).to.contains("Unable to get API Mappings"); - } - expect(errored).to.equal(true); - }); + it("get base path mapping failure", async () => { + const APIGatewayMock = mockClient(ApiGatewayV2Client); + APIGatewayMock.on(GetApiMappingsCommand).rejects(); + + const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain" + })); + + let errored = false; + try { + await apiGatewayV2Wrapper.getBasePathMappings(dc); + } catch (err) { + errored = true; + expect(err.message).to.contains("Unable to get API Mappings"); + } + expect(errored).to.equal(true); + }); - it("update base path mapping", async () => { - const APIGatewayMock = mockClient(ApiGatewayV2Client); - APIGatewayMock.on(UpdateApiMappingCommand).resolves(null); - - const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - basePath: "test_basepath", - apiId: "test_rest_api_id", - })); - dc.apiMapping = { - apiId: "old_api_id", - basePath: "old_basepath", - stage: "test", - apiMappingId: null - }; - - await apiGatewayV2Wrapper.updateBasePathMapping(dc); - - const expectedParams = { - ApiId: dc.apiId, - ApiMappingId: dc.apiMapping.apiMappingId, - ApiMappingKey: dc.basePath, - DomainName: dc.givenDomainName, - Stage: dc.stage, - } - const commandCalls = APIGatewayMock.commandCalls(UpdateApiMappingCommand, expectedParams, true); - - expect(commandCalls.length).to.equal(1); - expect(consoleOutput[0]).to.contains("V2 - Updated API mapping to"); - }); + it("update base path mapping", async () => { + const APIGatewayMock = mockClient(ApiGatewayV2Client); + APIGatewayMock.on(UpdateApiMappingCommand).resolves(null); + + const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain", + basePath: "test_basepath", + apiId: "test_rest_api_id" + })); + dc.apiMapping = { + apiId: "old_api_id", + basePath: "old_basepath", + stage: "test", + apiMappingId: null + }; + + await apiGatewayV2Wrapper.updateBasePathMapping(dc); + + const expectedParams = { + ApiId: dc.apiId, + ApiMappingId: dc.apiMapping.apiMappingId, + ApiMappingKey: dc.basePath, + DomainName: dc.givenDomainName, + Stage: dc.stage + }; + const commandCalls = APIGatewayMock.commandCalls(UpdateApiMappingCommand, expectedParams, true); + + expect(commandCalls.length).to.equal(1); + expect(consoleOutput[0]).to.contains("V2 - Updated API mapping to"); + }); - it("update http base path mapping", async () => { - const APIGatewayMock = mockClient(ApiGatewayV2Client); - APIGatewayMock.on(UpdateApiMappingCommand).resolves(null); - - const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - basePath: "test_basepath", - apiId: "test_rest_api_id", - apiType: Globals.apiTypes.http - })); - dc.apiMapping = { - apiId: "old_api_id", - basePath: "old_basepath", - stage: "test", - apiMappingId: null - }; - - await apiGatewayV2Wrapper.updateBasePathMapping(dc); - - const expectedParams = { - ApiId: dc.apiId, - ApiMappingId: dc.apiMapping.apiMappingId, - ApiMappingKey: dc.basePath, - DomainName: dc.givenDomainName, - Stage: dc.stage - } - const commandCalls = APIGatewayMock.commandCalls(UpdateApiMappingCommand, expectedParams, true); - - expect(commandCalls.length).to.equal(1); - expect(consoleOutput[0]).to.contains("V2 - Updated API mapping to"); - }); + it("update http base path mapping", async () => { + const APIGatewayMock = mockClient(ApiGatewayV2Client); + APIGatewayMock.on(UpdateApiMappingCommand).resolves(null); + + const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain", + basePath: "test_basepath", + apiId: "test_rest_api_id", + apiType: Globals.apiTypes.http + })); + dc.apiMapping = { + apiId: "old_api_id", + basePath: "old_basepath", + stage: "test", + apiMappingId: null + }; + + await apiGatewayV2Wrapper.updateBasePathMapping(dc); + + const expectedParams = { + ApiId: dc.apiId, + ApiMappingId: dc.apiMapping.apiMappingId, + ApiMappingKey: dc.basePath, + DomainName: dc.givenDomainName, + Stage: dc.stage + }; + const commandCalls = APIGatewayMock.commandCalls(UpdateApiMappingCommand, expectedParams, true); + + expect(commandCalls.length).to.equal(1); + expect(consoleOutput[0]).to.contains("V2 - Updated API mapping to"); + }); - it("update base path mapping failure", async () => { - const APIGatewayMock = mockClient(ApiGatewayV2Client); - APIGatewayMock.on(UpdateApiMappingCommand).rejects(); - - const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - basePath: "test_basepath", - apiId: "test_rest_api_id", - })); - dc.apiMapping = { - apiId: "old_api_id", - basePath: "old_basepath", - stage: "test", - apiMappingId: null - }; - - let errored = false; - try { - await apiGatewayV2Wrapper.updateBasePathMapping(dc); - } catch (err) { - errored = true; - expect(err.message).to.contains("V2 - Unable to update base path mapping for"); - } - expect(errored).to.equal(true); - }); + it("update base path mapping failure", async () => { + const APIGatewayMock = mockClient(ApiGatewayV2Client); + APIGatewayMock.on(UpdateApiMappingCommand).rejects(); + + const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain", + basePath: "test_basepath", + apiId: "test_rest_api_id" + })); + dc.apiMapping = { + apiId: "old_api_id", + basePath: "old_basepath", + stage: "test", + apiMappingId: null + }; + + let errored = false; + try { + await apiGatewayV2Wrapper.updateBasePathMapping(dc); + } catch (err) { + errored = true; + expect(err.message).to.contains("V2 - Unable to update base path mapping for"); + } + expect(errored).to.equal(true); + }); - it("delete base path mapping", async () => { - const APIGatewayMock = mockClient(ApiGatewayV2Client); - APIGatewayMock.on(DeleteApiMappingCommand).resolves(null); - - const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - basePath: "test_basepath", - apiId: "test_rest_api_id", - })); - dc.apiMapping = { - apiId: "old_api_id", - basePath: "old_basepath", - stage: "test", - apiMappingId: "old_api_id" - }; - - await apiGatewayV2Wrapper.deleteBasePathMapping(dc); - - const expectedParams = { - ApiMappingId: dc.apiMapping.apiMappingId, - DomainName: dc.givenDomainName, - } - const commandCalls = APIGatewayMock.commandCalls(DeleteApiMappingCommand, expectedParams, true); - - expect(commandCalls.length).to.equal(1); - expect(consoleOutput[0]).to.contains("V2 - Removed API Mapping with id"); - }); + it("delete base path mapping", async () => { + const APIGatewayMock = mockClient(ApiGatewayV2Client); + APIGatewayMock.on(DeleteApiMappingCommand).resolves(null); + + const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain", + basePath: "test_basepath", + apiId: "test_rest_api_id" + })); + dc.apiMapping = { + apiId: "old_api_id", + basePath: "old_basepath", + stage: "test", + apiMappingId: "old_api_id" + }; + + await apiGatewayV2Wrapper.deleteBasePathMapping(dc); + + const expectedParams = { + ApiMappingId: dc.apiMapping.apiMappingId, + DomainName: dc.givenDomainName + }; + const commandCalls = APIGatewayMock.commandCalls(DeleteApiMappingCommand, expectedParams, true); + + expect(commandCalls.length).to.equal(1); + expect(consoleOutput[0]).to.contains("V2 - Removed API Mapping with id"); + }); - it("delete base path mapping failure", async () => { - const APIGatewayMock = mockClient(ApiGatewayV2Client); - APIGatewayMock.on(DeleteApiMappingCommand).rejects(); - - const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - basePath: "test_basepath", - apiId: "test_rest_api_id", - })); - dc.apiMapping = { - apiId: "old_api_id", - basePath: "old_basepath", - stage: "test", - apiMappingId: null - }; - - let errored = false; - try { - await apiGatewayV2Wrapper.deleteBasePathMapping(dc); - } catch (err) { - errored = true; - expect(err.message).to.contains("V2 - Unable to remove base path mapping for"); - } - expect(errored).to.equal(true); - }); + it("delete base path mapping failure", async () => { + const APIGatewayMock = mockClient(ApiGatewayV2Client); + APIGatewayMock.on(DeleteApiMappingCommand).rejects(); + + const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain", + basePath: "test_basepath", + apiId: "test_rest_api_id" + })); + dc.apiMapping = { + apiId: "old_api_id", + basePath: "old_basepath", + stage: "test", + apiMappingId: null + }; + + let errored = false; + try { + await apiGatewayV2Wrapper.deleteBasePathMapping(dc); + } catch (err) { + errored = true; + expect(err.message).to.contains("V2 - Unable to remove base path mapping for"); + } + expect(errored).to.equal(true); }); + }); }); diff --git a/test/unit-tests/aws/cloud-formation-wrapper.test.ts b/test/unit-tests/aws/cloud-formation-wrapper.test.ts index 902575f2..7d542e3b 100644 --- a/test/unit-tests/aws/cloud-formation-wrapper.test.ts +++ b/test/unit-tests/aws/cloud-formation-wrapper.test.ts @@ -1,397 +1,396 @@ -import {consoleOutput, expect} from "../base"; +import { consoleOutput, expect } from "../base"; import Globals from "../../../src/globals"; import CloudFormationWrapper = require("../../../src/aws/cloud-formation-wrapper"); -import {mockClient} from "aws-sdk-client-mock"; +import { mockClient } from "aws-sdk-client-mock"; import { - CloudFormationClient, - DescribeStackResourceCommand, DescribeStacksCommand, - ListExportsCommand, ResourceStatus, StackStatus + CloudFormationClient, + DescribeStackResourceCommand, DescribeStacksCommand, + ListExportsCommand, ResourceStatus, StackStatus } from "@aws-sdk/client-cloudformation"; describe("Cloud Formation wrapper checks", () => { - beforeEach(() => { - consoleOutput.length = 0; + beforeEach(() => { + consoleOutput.length = 0; + }); + + afterEach(() => { + Globals.serverless.service.provider.apiGateway.restApiId = null; + Globals.serverless.service.provider.apiGateway.websocketApiId = null; + }); + + it("Initialization", () => { + const cloudFormationWrapper = new CloudFormationWrapper(); + const actualResult = cloudFormationWrapper.cloudFormation.config[0].region; + expect(actualResult).to.equal(Globals.currentRegion); + expect(cloudFormationWrapper.stackName).to.equal(Globals.serverless.service.provider.stackName); + }); + + it("findApiId for the rest api type in the sls config", async () => { + Globals.serverless.service.provider.apiGateway.restApiId = "test_api_id"; + + const actualResult = await new CloudFormationWrapper().findApiId(Globals.apiTypes.rest); + expect(actualResult).to.equal(Globals.serverless.service.provider.apiGateway.restApiId); + }); + + it("findApiId for the rest api type via Fn::ImportValue", async () => { + const fnImportValue = "test-value"; + const CloudFormationMock = mockClient(CloudFormationClient); + CloudFormationMock.on(ListExportsCommand).resolves({ + Exports: [ + { Name: "test-name", Value: fnImportValue }, + { Name: "dummy-name", Value: "dummy-value" } + ] }); - afterEach(() => { - Globals.serverless.service.provider.apiGateway.restApiId = null; - Globals.serverless.service.provider.apiGateway.websocketApiId = null; + const cloudFormationWrapper = new CloudFormationWrapper(); + Globals.serverless.service.provider.apiGateway.restApiId = { + [Globals.CFFuncNames.fnImport]: "test-name" + }; + + const actualResult = await cloudFormationWrapper.findApiId(Globals.apiTypes.rest); + expect(actualResult).to.equal(fnImportValue); + + const expectedParams = {}; + const commandCalls = CloudFormationMock.commandCalls(ListExportsCommand, expectedParams, true); + expect(commandCalls.length).to.equal(1); + }); + + it("findApiId for the rest api type via Fn::ImportValue paginated", async () => { + const fnImportValue = "test-value"; + const CloudFormationMock = mockClient(CloudFormationClient); + CloudFormationMock.on(ListExportsCommand).resolvesOnce({ + Exports: [ + { Name: "test-name", Value: fnImportValue }, + { Name: "dummy-name", Value: "dummy-value" } + ], + NextToken: "NextToken" + }) + .resolves({ + Exports: [ + { Name: "test-name2", Value: "test-name2" }, + { Name: "dummy-name2", Value: "dummy-value2" } + ] + }); + + const cloudFormationWrapper = new CloudFormationWrapper(); + Globals.serverless.service.provider.apiGateway.restApiId = { + [Globals.CFFuncNames.fnImport]: "test-name" + }; + + const actualResult = await cloudFormationWrapper.findApiId(Globals.apiTypes.rest); + expect(actualResult).to.equal(fnImportValue); + + const expectedParams = { + NextToken: "NextToken" + }; + const commandCalls = CloudFormationMock.commandCalls(ListExportsCommand, expectedParams, true); + expect(commandCalls.length).to.equal(2); + }); + + it("findApiId for the rest api type via Fn::ImportValue not found", async () => { + const fnImportValue = "test-value"; + const CloudFormationMock = mockClient(CloudFormationClient); + CloudFormationMock.on(ListExportsCommand).resolves({ + Exports: [ + { Name: "test-name", Value: fnImportValue }, + { Name: "dummy-name", Value: "dummy-value" } + ] }); - it("Initialization", () => { - const cloudFormationWrapper = new CloudFormationWrapper(); - const actualResult = cloudFormationWrapper.cloudFormation.config[0].region; - expect(actualResult).to.equal(Globals.currentRegion); - expect(cloudFormationWrapper.stackName).to.equal(Globals.serverless.service.provider.stackName); + Globals.serverless.service.provider.apiGateway.restApiId = { + [Globals.CFFuncNames.fnImport]: "not-existing-name" + }; + + let errored = false; + try { + await new CloudFormationWrapper().findApiId(Globals.apiTypes.rest); + } catch (err) { + errored = true; + expect(err.message).to.contains("Failed to find a stack"); + } + expect(errored).to.equal(true); + + const expectedParams = {}; + const commandCalls = CloudFormationMock.commandCalls(ListExportsCommand, expectedParams, true); + expect(commandCalls.length).to.equal(1); + }); + + it("findApiId for the rest api type via Ref", async () => { + const physicalResourceId = "test_rest_api_id"; + const fnRefName = "test-name"; + const CloudFormationMock = mockClient(CloudFormationClient); + CloudFormationMock.on(DescribeStackResourceCommand).resolves({ + StackResourceDetail: { + LogicalResourceId: fnRefName, + PhysicalResourceId: physicalResourceId, + ResourceType: "", + LastUpdatedTimestamp: null, + ResourceStatus: ResourceStatus.CREATE_COMPLETE + } }); - it("findApiId for the rest api type in the sls config", async () => { - - Globals.serverless.service.provider.apiGateway.restApiId = "test_api_id"; - - const actualResult = await new CloudFormationWrapper().findApiId(Globals.apiTypes.rest) - expect(actualResult).to.equal(Globals.serverless.service.provider.apiGateway.restApiId); - }); - - it("findApiId for the rest api type via Fn::ImportValue", async () => { - const fnImportValue = "test-value"; - const CloudFormationMock = mockClient(CloudFormationClient); - CloudFormationMock.on(ListExportsCommand).resolves({ - Exports: [ - {Name: "test-name", Value: fnImportValue}, - {Name: "dummy-name", Value: "dummy-value"}, - ] - }); - - const cloudFormationWrapper = new CloudFormationWrapper(); - Globals.serverless.service.provider.apiGateway.restApiId = { - [Globals.CFFuncNames.fnImport]: "test-name" - }; - - const actualResult = await cloudFormationWrapper.findApiId(Globals.apiTypes.rest) - expect(actualResult).to.equal(fnImportValue); - - const expectedParams = {}; - const commandCalls = CloudFormationMock.commandCalls(ListExportsCommand, expectedParams, true); - expect(commandCalls.length).to.equal(1); + const cloudFormationWrapper = new CloudFormationWrapper(); + Globals.serverless.service.provider.apiGateway.restApiId = { + [Globals.CFFuncNames.ref]: fnRefName + }; + + const actualResult = await cloudFormationWrapper.findApiId(Globals.apiTypes.rest); + expect(actualResult).to.equal(physicalResourceId); + + const expectedParams = { + LogicalResourceId: fnRefName, + StackName: Globals.serverless.service.provider.stackName + }; + const commandCalls = CloudFormationMock.commandCalls(DescribeStackResourceCommand, expectedParams, true); + expect(commandCalls.length).to.equal(1); + }); + + it("findApiId for the rest api type via Ref not found", async () => { + const fnRefName = "not-existing-name"; + const CloudFormationMock = mockClient(CloudFormationClient); + CloudFormationMock.on(DescribeStackResourceCommand).resolves(null); + + Globals.serverless.service.provider.apiGateway.restApiId = { + [Globals.CFFuncNames.ref]: fnRefName + }; + + let errored = false; + try { + await new CloudFormationWrapper().findApiId(Globals.apiTypes.rest); + } catch (err) { + errored = true; + expect(err.message).to.contains("Failed to find a stack"); + } + expect(errored).to.equal(true); + + const expectedParams = { + LogicalResourceId: fnRefName, + StackName: Globals.serverless.service.provider.stackName + }; + const commandCalls = CloudFormationMock.commandCalls(DescribeStackResourceCommand, expectedParams, true); + expect(commandCalls.length).to.equal(1); + }); + + it("findApiId for the rest api type via not supported func", async () => { + const CloudFormationMock = mockClient(CloudFormationClient); + CloudFormationMock.on(DescribeStackResourceCommand).resolves(null); + + Globals.serverless.service.provider.apiGateway.restApiId = { + "unsupported-func-name": "test-value" + }; + + let errored = false; + try { + await new CloudFormationWrapper().findApiId(Globals.apiTypes.rest); + } catch (err) { + errored = true; + expect(err.message).to.contains("Failed to find a stack"); + } + expect(errored).to.equal(true); + expect(consoleOutput[0]).to.contains("Unsupported apiGateway"); + + const expectedParams = { + LogicalResourceId: Globals.CFResourceIds[Globals.apiTypes.rest], + StackName: Globals.serverless.service.provider.stackName + }; + const commandCalls = CloudFormationMock.commandCalls(DescribeStackResourceCommand, expectedParams, true); + expect(commandCalls.length).to.equal(1); + }); + + it("findApiId for the rest api type", async () => { + const physicalResourceId = "test_rest_api_id"; + const CloudFormationMock = mockClient(CloudFormationClient); + CloudFormationMock.on(DescribeStackResourceCommand).resolves({ + StackResourceDetail: { + LogicalResourceId: "ApiGatewayRestApi", + PhysicalResourceId: physicalResourceId, + ResourceType: "", + LastUpdatedTimestamp: null, + ResourceStatus: ResourceStatus.CREATE_COMPLETE + } }); - it("findApiId for the rest api type via Fn::ImportValue paginated", async () => { - const fnImportValue = "test-value"; - const CloudFormationMock = mockClient(CloudFormationClient); - CloudFormationMock.on(ListExportsCommand).resolvesOnce({ - Exports: [ - {Name: "test-name", Value: fnImportValue}, - {Name: "dummy-name", Value: "dummy-value"}, - ], - NextToken: "NextToken" - }) - .resolves({ - Exports: [ - {Name: "test-name2", Value: "test-name2"}, - {Name: "dummy-name2", Value: "dummy-value2"}, - ] - }); - - const cloudFormationWrapper = new CloudFormationWrapper(); - Globals.serverless.service.provider.apiGateway.restApiId = { - [Globals.CFFuncNames.fnImport]: "test-name" - }; - - const actualResult = await cloudFormationWrapper.findApiId(Globals.apiTypes.rest) - expect(actualResult).to.equal(fnImportValue); - - const expectedParams = { - NextToken: "NextToken" - }; - const commandCalls = CloudFormationMock.commandCalls(ListExportsCommand, expectedParams, true); - expect(commandCalls.length).to.equal(2); + const cloudFormationWrapper = new CloudFormationWrapper(); + const actualResult = await cloudFormationWrapper.findApiId(Globals.apiTypes.rest); + expect(actualResult).to.equal(physicalResourceId); + + const expectedParams = { + LogicalResourceId: Globals.CFResourceIds[Globals.apiTypes.rest], + StackName: Globals.serverless.service.provider.stackName + }; + const commandCalls = CloudFormationMock.commandCalls(DescribeStackResourceCommand, expectedParams, true); + expect(commandCalls.length).to.equal(1); + }); + + it("findApiId for the rest api type failure", async () => { + const CloudFormationMock = mockClient(CloudFormationClient); + CloudFormationMock.on(DescribeStackResourceCommand).resolves({ + StackResourceDetail: { + LogicalResourceId: Globals.CFResourceIds[Globals.apiTypes.rest], + PhysicalResourceId: null, + ResourceType: "", + LastUpdatedTimestamp: null, + ResourceStatus: ResourceStatus.CREATE_COMPLETE + } }); - it("findApiId for the rest api type via Fn::ImportValue not found", async () => { - const fnImportValue = "test-value"; - const CloudFormationMock = mockClient(CloudFormationClient); - CloudFormationMock.on(ListExportsCommand).resolves({ - Exports: [ - {Name: "test-name", Value: fnImportValue}, - {Name: "dummy-name", Value: "dummy-value"}, - ] - }); - - Globals.serverless.service.provider.apiGateway.restApiId = { - [Globals.CFFuncNames.fnImport]: "not-existing-name" - }; - - let errored = false; - try { - await new CloudFormationWrapper().findApiId(Globals.apiTypes.rest); - } catch (err) { - errored = true; - expect(err.message).to.contains("Failed to find a stack"); + let errored = false; + try { + await new CloudFormationWrapper().findApiId(Globals.apiTypes.rest); + } catch (err) { + errored = true; + expect(err.message).to.contains("No ApiId associated with CloudFormation stack"); + } + expect(errored).to.equal(true); + }); + + it("findApiId for the rest api type with nested stacks", async () => { + const physicalResourceId = "test_rest_api_id"; + const nestedStackName = "custom-stage-name-NestedStackTwo-U89W84TQIHJK"; + const CloudFormationMock = mockClient(CloudFormationClient); + CloudFormationMock.on(DescribeStackResourceCommand).rejectsOnce() + .resolves({ + StackResourceDetail: { + LogicalResourceId: Globals.CFResourceIds[Globals.apiTypes.rest], + PhysicalResourceId: physicalResourceId, + ResourceType: "", + LastUpdatedTimestamp: null, + ResourceStatus: ResourceStatus.CREATE_COMPLETE } - expect(errored).to.equal(true); - - const expectedParams = {}; - const commandCalls = CloudFormationMock.commandCalls(ListExportsCommand, expectedParams, true); - expect(commandCalls.length).to.equal(1); - }); - - it("findApiId for the rest api type via Ref", async () => { - const physicalResourceId = "test_rest_api_id"; - const fnRefName = "test-name"; - const CloudFormationMock = mockClient(CloudFormationClient); - CloudFormationMock.on(DescribeStackResourceCommand).resolves({ - StackResourceDetail: { - LogicalResourceId: fnRefName, - PhysicalResourceId: physicalResourceId, - ResourceType: "", - LastUpdatedTimestamp: null, - ResourceStatus: ResourceStatus.CREATE_COMPLETE, - }, - }); - - const cloudFormationWrapper = new CloudFormationWrapper(); - Globals.serverless.service.provider.apiGateway.restApiId = { - [Globals.CFFuncNames.ref]: fnRefName - }; - - const actualResult = await cloudFormationWrapper.findApiId(Globals.apiTypes.rest) - expect(actualResult).to.equal(physicalResourceId); - - const expectedParams = { - LogicalResourceId: fnRefName, - StackName: Globals.serverless.service.provider.stackName, - }; - const commandCalls = CloudFormationMock.commandCalls(DescribeStackResourceCommand, expectedParams, true); - expect(commandCalls.length).to.equal(1); - }); - - it("findApiId for the rest api type via Ref not found", async () => { - const fnRefName = "not-existing-name"; - const CloudFormationMock = mockClient(CloudFormationClient); - CloudFormationMock.on(DescribeStackResourceCommand).resolves(null); - - Globals.serverless.service.provider.apiGateway.restApiId = { - [Globals.CFFuncNames.ref]: fnRefName - }; - - let errored = false; - try { - await new CloudFormationWrapper().findApiId(Globals.apiTypes.rest); - } catch (err) { - errored = true; - expect(err.message).to.contains("Failed to find a stack"); + }); + CloudFormationMock.on(DescribeStacksCommand).resolves({ + Stacks: [ + { + StackName: "custom-stage-name-NestedStackOne-U89W84TQIHJK", + RootId: "arn:aws:cloudformation:us-east-1:000000000000:stack/dummy-name/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + CreationTime: null, + StackStatus: StackStatus.CREATE_COMPLETE + }, + { + StackName: nestedStackName, + RootId: `arn:aws:cloudformation:us-east-1:000000000000:stack/${Globals.serverless.service.provider.stackName}/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`, + CreationTime: null, + StackStatus: StackStatus.CREATE_COMPLETE + }, + { + StackName: "outside-stack-NestedStackZERO-U89W84TQIHJK", + RootId: null, + CreationTime: null, + StackStatus: StackStatus.CREATE_COMPLETE } - expect(errored).to.equal(true); - - const expectedParams = { - LogicalResourceId: fnRefName, - StackName: Globals.serverless.service.provider.stackName, - }; - const commandCalls = CloudFormationMock.commandCalls(DescribeStackResourceCommand, expectedParams, true); - expect(commandCalls.length).to.equal(1); + ] }); - it("findApiId for the rest api type via not supported func", async () => { - const CloudFormationMock = mockClient(CloudFormationClient); - CloudFormationMock.on(DescribeStackResourceCommand).resolves(null); - - Globals.serverless.service.provider.apiGateway.restApiId = { - "unsupported-func-name": "test-value" - }; - - let errored = false; - try { - await new CloudFormationWrapper().findApiId(Globals.apiTypes.rest); - } catch (err) { - errored = true; - expect(err.message).to.contains("Failed to find a stack"); + const actualResult = await new CloudFormationWrapper().findApiId(Globals.apiTypes.rest); + expect(actualResult).to.equal(physicalResourceId); + + const expectedParams = { + LogicalResourceId: Globals.CFResourceIds[Globals.apiTypes.rest], + StackName: nestedStackName + }; + + const commandCalls = CloudFormationMock.commandCalls(DescribeStackResourceCommand, expectedParams, true); + expect(commandCalls.length).to.equal(1); + + const allCommandCalls = CloudFormationMock.commandCalls(DescribeStackResourceCommand); + expect(allCommandCalls.length).to.equal(2); + }); + + it("findApiId for the rest api type with paginated nested stacks", async () => { + const physicalResourceId = "test_rest_api_id"; + const nestedStackName = "custom-stage-name-NestedStackTwo-U89W84TQIHJK"; + const CloudFormationMock = mockClient(CloudFormationClient); + CloudFormationMock.on(DescribeStackResourceCommand).rejectsOnce() + .resolves({ + StackResourceDetail: { + LogicalResourceId: Globals.CFResourceIds[Globals.apiTypes.rest], + PhysicalResourceId: physicalResourceId, + ResourceType: "", + LastUpdatedTimestamp: undefined, + ResourceStatus: ResourceStatus.CREATE_COMPLETE } - expect(errored).to.equal(true); - expect(consoleOutput[0]).to.contains("Unsupported apiGateway"); - - const expectedParams = { - LogicalResourceId: Globals.CFResourceIds[Globals.apiTypes.rest], - StackName: Globals.serverless.service.provider.stackName, - }; - const commandCalls = CloudFormationMock.commandCalls(DescribeStackResourceCommand, expectedParams, true); - expect(commandCalls.length).to.equal(1); - }); - - it("findApiId for the rest api type", async () => { - const physicalResourceId = "test_rest_api_id"; - const CloudFormationMock = mockClient(CloudFormationClient); - CloudFormationMock.on(DescribeStackResourceCommand).resolves({ - StackResourceDetail: { - LogicalResourceId: "ApiGatewayRestApi", - PhysicalResourceId: physicalResourceId, - ResourceType: "", - LastUpdatedTimestamp: null, - ResourceStatus: ResourceStatus.CREATE_COMPLETE, - }, - }); - - const cloudFormationWrapper = new CloudFormationWrapper(); - const actualResult = await cloudFormationWrapper.findApiId(Globals.apiTypes.rest) - expect(actualResult).to.equal(physicalResourceId); - - const expectedParams = { - LogicalResourceId: Globals.CFResourceIds[Globals.apiTypes.rest], - StackName: Globals.serverless.service.provider.stackName, - }; - const commandCalls = CloudFormationMock.commandCalls(DescribeStackResourceCommand, expectedParams, true); - expect(commandCalls.length).to.equal(1); - }); - - it("findApiId for the rest api type failure", async () => { - const CloudFormationMock = mockClient(CloudFormationClient); - CloudFormationMock.on(DescribeStackResourceCommand).resolves({ - StackResourceDetail: { - LogicalResourceId: Globals.CFResourceIds[Globals.apiTypes.rest], - PhysicalResourceId: null, - ResourceType: "", - LastUpdatedTimestamp: null, - ResourceStatus: ResourceStatus.CREATE_COMPLETE, - }, - }); - - let errored = false; - try { - await new CloudFormationWrapper().findApiId(Globals.apiTypes.rest); - } catch (err) { - errored = true; - expect(err.message).to.contains("No ApiId associated with CloudFormation stack"); + }); + CloudFormationMock.on(DescribeStacksCommand).resolvesOnce({ + Stacks: [ + { + StackName: "custom-stage-name-NestedStackOne-U89W84TQIHJK", + RootId: "arn:aws:cloudformation:us-east-1:000000000000:stack/dummy-name/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + CreationTime: null, + StackStatus: StackStatus.CREATE_COMPLETE + }, + { + StackName: nestedStackName, + RootId: `arn:aws:cloudformation:us-east-1:000000000000:stack/${Globals.serverless.service.provider.stackName}/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`, + CreationTime: null, + StackStatus: StackStatus.CREATE_COMPLETE + }, + { + StackName: "outside-stack-NestedStackZERO-U89W84TQIHJK", + RootId: null, + CreationTime: null, + StackStatus: StackStatus.CREATE_COMPLETE } - expect(errored).to.equal(true); - }); - - it("findApiId for the rest api type with nested stacks", async () => { - const physicalResourceId = "test_rest_api_id"; - const nestedStackName = "custom-stage-name-NestedStackTwo-U89W84TQIHJK"; - const CloudFormationMock = mockClient(CloudFormationClient); - CloudFormationMock.on(DescribeStackResourceCommand).rejectsOnce() - .resolves({ - StackResourceDetail: { - LogicalResourceId: Globals.CFResourceIds[Globals.apiTypes.rest], - PhysicalResourceId: physicalResourceId, - ResourceType: "", - LastUpdatedTimestamp: null, - ResourceStatus: ResourceStatus.CREATE_COMPLETE, - }, - }); - CloudFormationMock.on(DescribeStacksCommand).resolves({ - Stacks: [ - { - StackName: "custom-stage-name-NestedStackOne-U89W84TQIHJK", - RootId: "arn:aws:cloudformation:us-east-1:000000000000:stack/dummy-name/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - CreationTime: null, - StackStatus: StackStatus.CREATE_COMPLETE - }, - { - StackName: nestedStackName, - RootId: `arn:aws:cloudformation:us-east-1:000000000000:stack/${Globals.serverless.service.provider.stackName}/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`, - CreationTime: null, - StackStatus: StackStatus.CREATE_COMPLETE - }, - { - StackName: "outside-stack-NestedStackZERO-U89W84TQIHJK", - RootId: null, - CreationTime: null, - StackStatus: StackStatus.CREATE_COMPLETE - }, - ], - }); - - const actualResult = await new CloudFormationWrapper().findApiId(Globals.apiTypes.rest) - expect(actualResult).to.equal(physicalResourceId); - - const expectedParams = { - LogicalResourceId: Globals.CFResourceIds[Globals.apiTypes.rest], - StackName: nestedStackName, - }; - - const commandCalls = CloudFormationMock.commandCalls(DescribeStackResourceCommand, expectedParams, true); - expect(commandCalls.length).to.equal(1); - - const allCommandCalls = CloudFormationMock.commandCalls(DescribeStackResourceCommand); - expect(allCommandCalls.length).to.equal(2); - }); - - it("findApiId for the rest api type with paginated nested stacks", async () => { - const physicalResourceId = "test_rest_api_id"; - const nestedStackName = "custom-stage-name-NestedStackTwo-U89W84TQIHJK"; - const CloudFormationMock = mockClient(CloudFormationClient); - CloudFormationMock.on(DescribeStackResourceCommand).rejectsOnce() - .resolves({ - StackResourceDetail: { - LogicalResourceId: Globals.CFResourceIds[Globals.apiTypes.rest], - PhysicalResourceId: physicalResourceId, - ResourceType: "", - LastUpdatedTimestamp: undefined, - ResourceStatus: ResourceStatus.CREATE_COMPLETE, - }, - }); - CloudFormationMock.on(DescribeStacksCommand).resolvesOnce({ - Stacks: [ - { - StackName: "custom-stage-name-NestedStackOne-U89W84TQIHJK", - RootId: "arn:aws:cloudformation:us-east-1:000000000000:stack/dummy-name/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - CreationTime: null, - StackStatus: StackStatus.CREATE_COMPLETE - }, - { - StackName: nestedStackName, - RootId: `arn:aws:cloudformation:us-east-1:000000000000:stack/${Globals.serverless.service.provider.stackName}/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`, - CreationTime: null, - StackStatus: StackStatus.CREATE_COMPLETE - }, - { - StackName: "outside-stack-NestedStackZERO-U89W84TQIHJK", - RootId: null, - CreationTime: null, - StackStatus: StackStatus.CREATE_COMPLETE - }, - ], - NextToken: "NextToken" - }) - .resolves({ - Stacks: [ - { - StackName: "custom-stage-name-NestedStackOne-U89W84TQ1235", - RootId: "arn:aws:cloudformation:us-east-1:000000000000:stack/dummy-name2/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - CreationTime: null, - StackStatus: StackStatus.CREATE_COMPLETE - } - ], - }); - - const actualResult = await new CloudFormationWrapper().findApiId(Globals.apiTypes.rest) - expect(actualResult).to.equal(physicalResourceId); - - const expectedParams = { - LogicalResourceId: Globals.CFResourceIds[Globals.apiTypes.rest], - StackName: nestedStackName, - }; - - const commandCalls = CloudFormationMock.commandCalls(DescribeStackResourceCommand, expectedParams, true); - expect(commandCalls.length).to.equal(1); - - const allCommandCalls = CloudFormationMock.commandCalls(DescribeStackResourceCommand); - expect(allCommandCalls.length).to.equal(2); - - const describeStacksCommandCalls = CloudFormationMock.commandCalls(DescribeStacksCommand); - expect(describeStacksCommandCalls.length).to.equal(2); - }); - - it("findApiId for the rest api type with nested stacks failure", async () => { - const nestedStackName = "custom-stage-name-NestedStackTwo-U89W84TQIHJK"; - const CloudFormationMock = mockClient(CloudFormationClient); - CloudFormationMock.on(DescribeStackResourceCommand).rejects(); - CloudFormationMock.on(DescribeStacksCommand).resolves({ - Stacks: [ - { - StackName: "custom-stage-name-NestedStackOne-U89W84TQIHJK", - RootId: "arn:aws:cloudformation:us-east-1:000000000000:stack/dummy-name/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - CreationTime: null, - StackStatus: StackStatus.CREATE_COMPLETE - }, - { - StackName: nestedStackName, - RootId: `arn:aws:cloudformation:us-east-1:000000000000:stack/${Globals.serverless.service.provider.stackName}/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`, - CreationTime: null, - StackStatus: StackStatus.CREATE_COMPLETE - } - ], - }); - - let errored = false; - try { - await new CloudFormationWrapper().findApiId(Globals.apiTypes.rest); - } catch (err) { - errored = true; - expect(err.message).to.contains("Failed to find a stack"); + ], + NextToken: "NextToken" + }) + .resolves({ + Stacks: [ + { + StackName: "custom-stage-name-NestedStackOne-U89W84TQ1235", + RootId: "arn:aws:cloudformation:us-east-1:000000000000:stack/dummy-name2/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + CreationTime: null, + StackStatus: StackStatus.CREATE_COMPLETE + } + ] + }); + + const actualResult = await new CloudFormationWrapper().findApiId(Globals.apiTypes.rest); + expect(actualResult).to.equal(physicalResourceId); + + const expectedParams = { + LogicalResourceId: Globals.CFResourceIds[Globals.apiTypes.rest], + StackName: nestedStackName + }; + + const commandCalls = CloudFormationMock.commandCalls(DescribeStackResourceCommand, expectedParams, true); + expect(commandCalls.length).to.equal(1); + + const allCommandCalls = CloudFormationMock.commandCalls(DescribeStackResourceCommand); + expect(allCommandCalls.length).to.equal(2); + + const describeStacksCommandCalls = CloudFormationMock.commandCalls(DescribeStacksCommand); + expect(describeStacksCommandCalls.length).to.equal(2); + }); + + it("findApiId for the rest api type with nested stacks failure", async () => { + const nestedStackName = "custom-stage-name-NestedStackTwo-U89W84TQIHJK"; + const CloudFormationMock = mockClient(CloudFormationClient); + CloudFormationMock.on(DescribeStackResourceCommand).rejects(); + CloudFormationMock.on(DescribeStacksCommand).resolves({ + Stacks: [ + { + StackName: "custom-stage-name-NestedStackOne-U89W84TQIHJK", + RootId: "arn:aws:cloudformation:us-east-1:000000000000:stack/dummy-name/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + CreationTime: null, + StackStatus: StackStatus.CREATE_COMPLETE + }, + { + StackName: nestedStackName, + RootId: `arn:aws:cloudformation:us-east-1:000000000000:stack/${Globals.serverless.service.provider.stackName}/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`, + CreationTime: null, + StackStatus: StackStatus.CREATE_COMPLETE } - expect(errored).to.equal(true); - expect(consoleOutput[0]).to.contains("[WARNING] Failed to find CloudFormation resources with an error"); + ] }); + + let errored = false; + try { + await new CloudFormationWrapper().findApiId(Globals.apiTypes.rest); + } catch (err) { + errored = true; + expect(err.message).to.contains("Failed to find a stack"); + } + expect(errored).to.equal(true); + expect(consoleOutput[0]).to.contains("[WARNING] Failed to find CloudFormation resources with an error"); + }); }); diff --git a/test/unit-tests/aws/route53-wrapper.test.ts b/test/unit-tests/aws/route53-wrapper.test.ts index 0f6268d8..14fa0262 100644 --- a/test/unit-tests/aws/route53-wrapper.test.ts +++ b/test/unit-tests/aws/route53-wrapper.test.ts @@ -38,7 +38,7 @@ describe("Route53 wrapper checks", () => { }); it("get route53 hosted zone id", async () => { - const testId = "test_host_id" + const testId = "test_host_id"; const Route53Mock = mockClient(Route53Client); Route53Mock.on(ListHostedZonesCommand).resolves({ HostedZones: [ @@ -46,17 +46,17 @@ describe("Route53 wrapper checks", () => { CallerReference: "1", Config: { PrivateZone: false }, Id: testId, - Name: "test_domain", + Name: "test_domain" }, { CallerReference: "2", Config: { PrivateZone: false }, Id: testId, - Name: "dummy_test_domain", + Name: "dummy_test_domain" }, { CallerReference: "3", Config: { PrivateZone: false }, Id: testId, - Name: "domain", + Name: "domain" } ] }); @@ -71,13 +71,13 @@ describe("Route53 wrapper checks", () => { const commandCalls = Route53Mock.commandCalls(ListHostedZonesCommand, {}); expect(commandCalls.length).to.equal(1); - dc.hostedZoneId = "test_id" + dc.hostedZoneId = "test_id"; actualId = await new Route53Wrapper().getRoute53HostedZoneId(dc); expect(actualId).to.equal(dc.hostedZoneId); }); it("get route53 hosted zone id paginated", async () => { - const testId = "test_host_id" + const testId = "test_host_id"; const Route53Mock = mockClient(Route53Client); Route53Mock.on(ListHostedZonesCommand).resolvesOnce({ HostedZones: [ @@ -85,17 +85,17 @@ describe("Route53 wrapper checks", () => { CallerReference: "1", Config: { PrivateZone: false }, Id: testId, - Name: "test_domain", + Name: "test_domain" }, { CallerReference: "2", Config: { PrivateZone: false }, Id: testId, - Name: "dummy_test_domain", + Name: "dummy_test_domain" }, { CallerReference: "3", Config: { PrivateZone: false }, Id: testId, - Name: "domain", + Name: "domain" } ], NextMarker: "NextMarker" @@ -106,17 +106,17 @@ describe("Route53 wrapper checks", () => { CallerReference: "4", Config: { PrivateZone: false }, Id: testId, - Name: "test_domain2", + Name: "test_domain2" }, { CallerReference: "5", Config: { PrivateZone: false }, Id: testId, - Name: "dummy_test_domain2", + Name: "dummy_test_domain2" }, { CallerReference: "6", Config: { PrivateZone: false }, Id: testId, - Name: "domain2", + Name: "domain2" } ], NextMarker: "NextMarker" @@ -127,17 +127,17 @@ describe("Route53 wrapper checks", () => { CallerReference: "7", Config: { PrivateZone: false }, Id: testId, - Name: "test_domain3", + Name: "test_domain3" }, { CallerReference: "8", Config: { PrivateZone: false }, Id: testId, - Name: "dummy_test_domain3", + Name: "dummy_test_domain3" }, { CallerReference: "9", Config: { PrivateZone: false }, Id: testId, - Name: "domain3", + Name: "domain3" } ] }); @@ -152,13 +152,13 @@ describe("Route53 wrapper checks", () => { const commandCalls = Route53Mock.commandCalls(ListHostedZonesCommand, {}); expect(commandCalls.length).to.equal(3); - dc.hostedZoneId = "test_id" + dc.hostedZoneId = "test_id"; actualId = await new Route53Wrapper().getRoute53HostedZoneId(dc); expect(actualId).to.equal(dc.hostedZoneId); }); it("get route53 hosted zone id public", async () => { - const testId = "test_host_id" + const testId = "test_host_id"; const Route53Mock = mockClient(Route53Client); Route53Mock.on(ListHostedZonesCommand).resolves({ HostedZones: [ @@ -166,17 +166,17 @@ describe("Route53 wrapper checks", () => { CallerReference: "", Config: { PrivateZone: false }, Id: "no_valid", - Name: "api.test_domain", + Name: "api.test_domain" }, { CallerReference: "", Config: { PrivateZone: false }, Id: testId, - Name: "devapi.test_domain", + Name: "devapi.test_domain" }, { CallerReference: "", Config: { PrivateZone: false }, Id: "dummy_host_id", - Name: "test_domain", + Name: "test_domain" } ] }); @@ -193,7 +193,7 @@ describe("Route53 wrapper checks", () => { }); it("get route53 hosted zone id private", async () => { - const testId = "test_host_id" + const testId = "test_host_id"; const Route53Mock = mockClient(Route53Client); Route53Mock.on(ListHostedZonesCommand).resolves({ HostedZones: [ @@ -201,12 +201,12 @@ describe("Route53 wrapper checks", () => { CallerReference: "", Config: { PrivateZone: false }, Id: "dummy_host_id", - Name: "test_domain", + Name: "test_domain" }, { CallerReference: "", Config: { PrivateZone: true }, Id: testId, - Name: "test_domain", + Name: "test_domain" } ] }); @@ -248,7 +248,7 @@ describe("Route53 wrapper checks", () => { CallerReference: "1", Config: { PrivateZone: false }, Id: "test_host_id", - Name: "test_domain", + Name: "test_domain" } ] }); @@ -284,8 +284,8 @@ describe("Route53 wrapper checks", () => { CallerReference: "", Config: { PrivateZone: false }, Id: "test_host_id", - Name: "test_domain", - }], + Name: "test_domain" + }] }); Route53Mock.on(ChangeResourceRecordSetsCommand).resolves(null); @@ -304,11 +304,11 @@ describe("Route53 wrapper checks", () => { AliasTarget: { DNSName: "test_domain", EvaluateTargetHealth: false, - HostedZoneId: "test_host_id", + HostedZoneId: "test_host_id" }, Name: "test_domain", - Type: RRType.A, - }, + Type: RRType.A + } }, { Action: ChangeAction.UPSERT, @@ -316,16 +316,16 @@ describe("Route53 wrapper checks", () => { AliasTarget: { DNSName: "test_domain", EvaluateTargetHealth: false, - HostedZoneId: "test_host_id", + HostedZoneId: "test_host_id" }, Name: "test_domain", - Type: RRType.AAAA, - }, - }, + Type: RRType.AAAA + } + } ], Comment: `Record created by "${Globals.pluginName}"` }, - HostedZoneId: "test_host_id", + HostedZoneId: "test_host_id" }; const commandCalls = Route53Mock.commandCalls(ChangeResourceRecordSetsCommand, expectedParams); expect(commandCalls.length).to.equal(1); @@ -338,8 +338,8 @@ describe("Route53 wrapper checks", () => { CallerReference: "", Config: { PrivateZone: false }, Id: "test_host_id", - Name: "test_domain", - }], + Name: "test_domain" + }] }); Route53Mock.on(ChangeResourceRecordSetsCommand).resolves(null); @@ -362,13 +362,13 @@ describe("Route53 wrapper checks", () => { AliasTarget: { DNSName: "test_domain", EvaluateTargetHealth: false, - HostedZoneId: "test_host_id", + HostedZoneId: "test_host_id" }, Name: "test_domain", Type: RRType.A, Region: ResourceRecordSetRegion.us_east_1, - SetIdentifier: 'test_domain' - }, + SetIdentifier: "test_domain" + } }, { Action: ChangeAction.UPSERT, @@ -376,18 +376,18 @@ describe("Route53 wrapper checks", () => { AliasTarget: { DNSName: "test_domain", EvaluateTargetHealth: false, - HostedZoneId: "test_host_id", + HostedZoneId: "test_host_id" }, Name: "test_domain", Type: RRType.AAAA, Region: ResourceRecordSetRegion.us_east_1, - SetIdentifier: 'test_domain' - }, - }, + SetIdentifier: "test_domain" + } + } ], Comment: `Record created by "${Globals.pluginName}"` }, - HostedZoneId: "test_host_id", + HostedZoneId: "test_host_id" }; const commandCalls = Route53Mock.commandCalls(ChangeResourceRecordSetsCommand, expectedParams); expect(commandCalls.length).to.equal(1); @@ -400,8 +400,8 @@ describe("Route53 wrapper checks", () => { CallerReference: "", Config: { PrivateZone: false }, Id: "test_host_id", - Name: "test_domain", - }], + Name: "test_domain" + }] }); Route53Mock.on(ChangeResourceRecordSetsCommand).resolves(null); @@ -425,13 +425,13 @@ describe("Route53 wrapper checks", () => { AliasTarget: { DNSName: "test_domain", EvaluateTargetHealth: false, - HostedZoneId: "test_host_id", + HostedZoneId: "test_host_id" }, Name: "test_domain", Type: RRType.A, Weight: 1, - SetIdentifier: 'test_domain' - }, + SetIdentifier: "test_domain" + } }, { Action: ChangeAction.UPSERT, @@ -439,18 +439,18 @@ describe("Route53 wrapper checks", () => { AliasTarget: { DNSName: "test_domain", EvaluateTargetHealth: false, - HostedZoneId: "test_host_id", + HostedZoneId: "test_host_id" }, Name: "test_domain", Type: RRType.AAAA, Weight: 1, - SetIdentifier: 'test_domain' - }, - }, + SetIdentifier: "test_domain" + } + } ], Comment: `Record created by "${Globals.pluginName}"` }, - HostedZoneId: "test_host_id", + HostedZoneId: "test_host_id" }; const commandCalls = Route53Mock.commandCalls(ChangeResourceRecordSetsCommand, expectedParams); expect(commandCalls.length).to.equal(1); @@ -465,12 +465,12 @@ describe("Route53 wrapper checks", () => { CallerReference: "", Config: { PrivateZone: false }, Id: publicZone, - Name: "test_domain", + Name: "test_domain" }, { CallerReference: "", Config: { PrivateZone: true }, Id: privateZone, - Name: "test_domain", + Name: "test_domain" } ] }); @@ -493,11 +493,11 @@ describe("Route53 wrapper checks", () => { AliasTarget: { DNSName: "test_domain", EvaluateTargetHealth: false, - HostedZoneId: publicZone, + HostedZoneId: publicZone }, Name: "test_domain", - Type: RRType.A, - }, + Type: RRType.A + } }, { Action: ChangeAction.UPSERT, @@ -505,16 +505,16 @@ describe("Route53 wrapper checks", () => { AliasTarget: { DNSName: "test_domain", EvaluateTargetHealth: false, - HostedZoneId: publicZone, + HostedZoneId: publicZone }, Name: "test_domain", - Type: RRType.AAAA, - }, - }, + Type: RRType.AAAA + } + } ], Comment: `Record created by "${Globals.pluginName}"` }, - HostedZoneId: publicZone, + HostedZoneId: publicZone }; const commandCalls1 = Route53Mock.commandCalls(ChangeResourceRecordSetsCommand, expectedParams1, true); expect(commandCalls1.length).to.equal(1); @@ -528,11 +528,11 @@ describe("Route53 wrapper checks", () => { AliasTarget: { DNSName: "test_domain", EvaluateTargetHealth: false, - HostedZoneId: publicZone, + HostedZoneId: publicZone }, Name: "test_domain", Type: RRType.A - }, + } }, { Action: ChangeAction.UPSERT, @@ -540,16 +540,16 @@ describe("Route53 wrapper checks", () => { AliasTarget: { DNSName: "test_domain", EvaluateTargetHealth: false, - HostedZoneId: publicZone, + HostedZoneId: publicZone }, Name: "test_domain", - Type: RRType.AAAA, - }, - }, + Type: RRType.AAAA + } + } ], Comment: `Record created by "${Globals.pluginName}"` }, - HostedZoneId: privateZone, + HostedZoneId: privateZone }; const commandCalls2 = Route53Mock.commandCalls(ChangeResourceRecordSetsCommand, expectedParams2, true); expect(commandCalls2.length).to.equal(1); @@ -564,12 +564,12 @@ describe("Route53 wrapper checks", () => { CallerReference: "", Config: { PrivateZone: false }, Id: publicZone, - Name: "test_domain", + Name: "test_domain" }, { CallerReference: "", Config: { PrivateZone: true }, Id: privateZone, - Name: "test_domain", + Name: "test_domain" }] }); Route53Mock.on(ChangeResourceRecordSetsCommand).rejects(null); diff --git a/test/unit-tests/aws/s3-wrapper.test.ts b/test/unit-tests/aws/s3-wrapper.test.ts index 98ddcfe9..78faeb88 100644 --- a/test/unit-tests/aws/s3-wrapper.test.ts +++ b/test/unit-tests/aws/s3-wrapper.test.ts @@ -1,88 +1,88 @@ -import {consoleOutput, expect, getDomainConfig} from "../base"; +import { consoleOutput, expect, getDomainConfig } from "../base"; import Globals from "../../../src/globals"; import S3Wrapper = require("../../../src/aws/s3-wrapper"); -import {mockClient} from "aws-sdk-client-mock"; -import {HeadObjectCommand, S3Client} from "@aws-sdk/client-s3"; +import { mockClient } from "aws-sdk-client-mock"; +import { HeadObjectCommand, S3Client } from "@aws-sdk/client-s3"; import DomainConfig = require("../../../src/models/domain-config"); describe("S3 wrapper checks", () => { - beforeEach(() => { - consoleOutput.length = 0; - }); + beforeEach(() => { + consoleOutput.length = 0; + }); - it("Initialization", () => { - const s3Wrapper = new S3Wrapper(); - const actualResult = s3Wrapper.s3.config[0].region; - expect(actualResult).to.equal(Globals.currentRegion); - }); + it("Initialization", () => { + const s3Wrapper = new S3Wrapper(); + const actualResult = s3Wrapper.s3.config[0].region; + expect(actualResult).to.equal(Globals.currentRegion); + }); - it("Assert TlS cert object exists", async () => { - const S3Mock = mockClient(S3Client); - S3Mock.on(HeadObjectCommand).resolves(null); + it("Assert TlS cert object exists", async () => { + const S3Mock = mockClient(S3Client); + S3Mock.on(HeadObjectCommand).resolves(null); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - endpointType: "regional", - tlsTruststoreUri: 's3://test_bucket/test_key', - tlsTruststoreVersion: "test-version" - })); - await new S3Wrapper().assertTlsCertObjectExists(dc); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain", + endpointType: "regional", + tlsTruststoreUri: "s3://test_bucket/test_key", + tlsTruststoreVersion: "test-version" + })); + await new S3Wrapper().assertTlsCertObjectExists(dc); - const expectedParams = { - Bucket: 'test_bucket', - Key: 'test_key' - } - const commandCalls = S3Mock.commandCalls(HeadObjectCommand, expectedParams); - expect(commandCalls.length).to.equal(1); - }); + const expectedParams = { + Bucket: "test_bucket", + Key: "test_key" + }; + const commandCalls = S3Mock.commandCalls(HeadObjectCommand, expectedParams); + expect(commandCalls.length).to.equal(1); + }); - it("Assert TlS cert object exists failure", async () => { - const S3Mock = mockClient(S3Client); - S3Mock.on(HeadObjectCommand).rejects(); + it("Assert TlS cert object exists failure", async () => { + const S3Mock = mockClient(S3Client); + S3Mock.on(HeadObjectCommand).rejects(); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - })); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain" + })); - let errored = false; - try { - await new S3Wrapper().assertTlsCertObjectExists(dc); - } catch (err) { - errored = true; - expect(err.message).to.contains("Invalid URL"); - } - expect(errored).to.equal(true); + let errored = false; + try { + await new S3Wrapper().assertTlsCertObjectExists(dc); + } catch (err) { + errored = true; + expect(err.message).to.contains("Invalid URL"); + } + expect(errored).to.equal(true); - dc.tlsTruststoreUri = "s3://test_bucket/test_key" - errored = false; - try { - await new S3Wrapper().assertTlsCertObjectExists(dc); - } catch (err) { - errored = true; - expect(err.message).to.contains("Could not head S3 object at"); - } - expect(errored).to.equal(true); - }); + dc.tlsTruststoreUri = "s3://test_bucket/test_key"; + errored = false; + try { + await new S3Wrapper().assertTlsCertObjectExists(dc); + } catch (err) { + errored = true; + expect(err.message).to.contains("Could not head S3 object at"); + } + expect(errored).to.equal(true); + }); - it("Assert TlS cert object exists forbidden", async () => { - const S3Mock = mockClient(S3Client); - S3Mock.on(HeadObjectCommand).rejects({ - "$metadata": {httpStatusCode: 403} - }); + it("Assert TlS cert object exists forbidden", async () => { + const S3Mock = mockClient(S3Client); + S3Mock.on(HeadObjectCommand).rejects({ + $metadata: { httpStatusCode: 403 } + }); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain", - endpointType: Globals.endpointTypes.regional, - tlsTruststoreUri: 's3://test_bucket/test_key', - })); + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain", + endpointType: Globals.endpointTypes.regional, + tlsTruststoreUri: "s3://test_bucket/test_key" + })); - let errored = false; - try { - await new S3Wrapper().assertTlsCertObjectExists(dc); - } catch (err) { - errored = true; - } - expect(errored).to.equal(false); - expect(consoleOutput[0]).to.contains("Forbidden to check the existence of the S3 object"); - }); + let errored = false; + try { + await new S3Wrapper().assertTlsCertObjectExists(dc); + } catch (err) { + errored = true; + } + expect(errored).to.equal(false); + expect(consoleOutput[0]).to.contains("Forbidden to check the existence of the S3 object"); + }); }); diff --git a/test/unit-tests/base.ts b/test/unit-tests/base.ts index 963e15e5..f976b948 100644 --- a/test/unit-tests/base.ts +++ b/test/unit-tests/base.ts @@ -2,9 +2,9 @@ import "mocha"; import chai = require("chai"); import spies = require("chai-spies"); import Globals from "../../src/globals"; -import {ServerlessOptions, ServerlessUtils} from "../../src/types"; +import { ServerlessOptions, ServerlessUtils } from "../../src/types"; import ServerlessCustomDomain = require("../../src"); -import {ResourceRecordSetRegion} from "@aws-sdk/client-route-53"; +import { ResourceRecordSetRegion } from "@aws-sdk/client-route-53"; chai.use(spies); @@ -12,108 +12,108 @@ const expect = chai.expect; const chaiSpy = chai.spy; const consoleOutput = []; const getDomainConfig = (customDomainOptions) => { - return { - allowPathMatching: customDomainOptions.allowPathMatching, - apiType: customDomainOptions.apiType, - autoDomain: customDomainOptions.autoDomain, - autoDomainWaitFor: customDomainOptions.autoDomainWaitFor, - basePath: customDomainOptions.basePath, - certificateArn: customDomainOptions.certificateArn, - certificateName: customDomainOptions.certificateName, - createRoute53Record: customDomainOptions.createRoute53Record, - createRoute53IPv6Record: customDomainOptions.createRoute53IPv6Record, - domainName: customDomainOptions.domainName, - enabled: customDomainOptions.enabled, - endpointType: customDomainOptions.endpointType, - tlsTruststoreUri: customDomainOptions.tlsTruststoreUri, - tlsTruststoreVersion: customDomainOptions.tlsTruststoreVersion, - hostedZoneId: customDomainOptions.hostedZoneId, - hostedZonePrivate: customDomainOptions.hostedZonePrivate, - splitHorizonDns: customDomainOptions.splitHorizonDns, - route53Profile: customDomainOptions.route53Profile, - route53Region: customDomainOptions.route53Region, - preserveExternalPathMappings: customDomainOptions.preserveExternalPathMappings, - securityPolicy: customDomainOptions.securityPolicy, - stage: customDomainOptions.stage, - route53Params: customDomainOptions.route53Params - } -} + return { + allowPathMatching: customDomainOptions.allowPathMatching, + apiType: customDomainOptions.apiType, + autoDomain: customDomainOptions.autoDomain, + autoDomainWaitFor: customDomainOptions.autoDomainWaitFor, + basePath: customDomainOptions.basePath, + certificateArn: customDomainOptions.certificateArn, + certificateName: customDomainOptions.certificateName, + createRoute53Record: customDomainOptions.createRoute53Record, + createRoute53IPv6Record: customDomainOptions.createRoute53IPv6Record, + domainName: customDomainOptions.domainName, + enabled: customDomainOptions.enabled, + endpointType: customDomainOptions.endpointType, + tlsTruststoreUri: customDomainOptions.tlsTruststoreUri, + tlsTruststoreVersion: customDomainOptions.tlsTruststoreVersion, + hostedZoneId: customDomainOptions.hostedZoneId, + hostedZonePrivate: customDomainOptions.hostedZonePrivate, + splitHorizonDns: customDomainOptions.splitHorizonDns, + route53Profile: customDomainOptions.route53Profile, + route53Region: customDomainOptions.route53Region, + preserveExternalPathMappings: customDomainOptions.preserveExternalPathMappings, + securityPolicy: customDomainOptions.securityPolicy, + stage: customDomainOptions.stage, + route53Params: customDomainOptions.route53Params + }; +}; const constructPlugin = (domainConfig, options?: ServerlessOptions, v3Utils?: ServerlessUtils) => { - const isMultiple = Array.isArray(domainConfig); - const serverless = { - cli: { - log(str: string) { - consoleOutput.push(str); - } + const isMultiple = Array.isArray(domainConfig); + const serverless = { + cli: { + log (str: string) { + consoleOutput.push(str); + } + }, + providers: { + aws: { + getCredentials: () => null + } + }, + service: { + custom: { + customDomain: isMultiple ? undefined : domainConfig, + customDomains: isMultiple ? domainConfig : undefined + }, + provider: { + apiGateway: { + restApiId: null, + websocketApiId: null + }, + compiledCloudFormationTemplate: { + Outputs: null }, - providers: { - aws: { - getCredentials: () => null - } + stackName: "custom-stage-name", + stage: null, + stackTags: { + test: "test" }, - service: { - custom: { - customDomain: isMultiple ? undefined : domainConfig, - customDomains: isMultiple ? domainConfig : undefined, - }, - provider: { - apiGateway: { - restApiId: null, - websocketApiId: null, - }, - compiledCloudFormationTemplate: { - Outputs: null, - }, - stackName: "custom-stage-name", - stage: null, - stackTags: { - test: "test" - }, - tags: { - test2: "test2" - } - }, - service: "test", + tags: { + test2: "test2" } - }; - const defaultOptions = { - stage: "test", - }; - return new ServerlessCustomDomain(serverless, options || defaultOptions, v3Utils); + }, + service: "test" + } + }; + const defaultOptions = { + stage: "test" + }; + return new ServerlessCustomDomain(serverless, options || defaultOptions, v3Utils); }; const getV3Utils = () => { - return { - writeText: (message: string) => { - consoleOutput.push(message); - }, - log: { - error(message: string) { - consoleOutput.push("V3 [Error] " + message); - }, - verbose(message: string) { - consoleOutput.push("V3 [Info] " + message); - }, - warning(message: string) { - consoleOutput.push("V3 [WARNING] " + message); - } - }, - progress: null - } -} + return { + writeText: (message: string) => { + consoleOutput.push(message); + }, + log: { + error (message: string) { + consoleOutput.push("V3 [Error] " + message); + }, + verbose (message: string) { + consoleOutput.push("V3 [Info] " + message); + }, + warning (message: string) { + consoleOutput.push("V3 [WARNING] " + message); + } + }, + progress: null + }; +}; Globals.currentRegion = ResourceRecordSetRegion.us_east_1; Globals.options = { - stage: "test" + stage: "test" }; // this is needed for running an individual test constructPlugin(getDomainConfig({})); export { - expect, - chaiSpy, - consoleOutput, - getDomainConfig, - getV3Utils, - constructPlugin, -} + expect, + chaiSpy, + consoleOutput, + getDomainConfig, + getV3Utils, + constructPlugin +}; diff --git a/test/unit-tests/index.test.ts b/test/unit-tests/index.test.ts index 10caa2e9..bddad42b 100644 --- a/test/unit-tests/index.test.ts +++ b/test/unit-tests/index.test.ts @@ -1,637 +1,636 @@ import Globals from "../../src/globals"; -import {chaiSpy, consoleOutput, constructPlugin, expect, getDomainConfig, getV3Utils} from "./base"; +import { chaiSpy, consoleOutput, constructPlugin, expect, getDomainConfig, getV3Utils } from "./base"; import Logging from "../../src/logging"; import DomainConfig = require("../../src/models/domain-config"); import APIGatewayV1Wrapper = require("../../src/aws/api-gateway-v1-wrapper"); import APIGatewayV2Wrapper = require("../../src/aws/api-gateway-v2-wrapper"); -import {mockClient} from "aws-sdk-client-mock"; +import { mockClient } from "aws-sdk-client-mock"; import { - APIGatewayClient, CreateBasePathMappingCommand, CreateDomainNameCommand, DeleteBasePathMappingCommand, - DeleteDomainNameCommand, - GetBasePathMappingsCommand, - GetDomainNameCommand, UpdateBasePathMappingCommand + APIGatewayClient, CreateBasePathMappingCommand, CreateDomainNameCommand, DeleteBasePathMappingCommand, + DeleteDomainNameCommand, + GetBasePathMappingsCommand, + GetDomainNameCommand, UpdateBasePathMappingCommand } from "@aws-sdk/client-api-gateway"; -import {ACMClient, ListCertificatesCommand} from "@aws-sdk/client-acm"; -import {ChangeResourceRecordSetsCommand, ListHostedZonesCommand, Route53Client} from "@aws-sdk/client-route-53"; -import {CloudFormationClient, DescribeStackResourceCommand, ResourceStatus} from "@aws-sdk/client-cloudformation"; - +import { ACMClient, ListCertificatesCommand } from "@aws-sdk/client-acm"; +import { ChangeResourceRecordSetsCommand, ListHostedZonesCommand, Route53Client } from "@aws-sdk/client-route-53"; +import { CloudFormationClient, DescribeStackResourceCommand, ResourceStatus } from "@aws-sdk/client-cloudformation"; describe("Custom Domain Plugin", () => { - beforeEach(() => { - consoleOutput.length = 0; - Globals.v3Utils = null; - }) - - describe("Initialization", () => { - it("with v3Utils", () => { - const testMessage = "test message"; - const v3Utils = getV3Utils(); - const domainConfig = getDomainConfig({}); + beforeEach(() => { + consoleOutput.length = 0; + Globals.v3Utils = null; + }); - constructPlugin(domainConfig, null, v3Utils); - Logging.logInfo(testMessage); + describe("Initialization", () => { + it("with v3Utils", () => { + const testMessage = "test message"; + const v3Utils = getV3Utils(); + const domainConfig = getDomainConfig({}); - expect(consoleOutput[0]).to.equal("V3 [Info] " + testMessage); - }); + constructPlugin(domainConfig, null, v3Utils); + Logging.logInfo(testMessage); - it("init AWS resources", () => { - const domainConfig = getDomainConfig({}); - const plugin = constructPlugin(domainConfig); - - let errored = false; - try { - plugin.initializeVariables(); - plugin.validateDomainConfigs(); - plugin.initAWSResources(); - } catch (err) { - errored = true; - } - expect(errored).to.equal(false); - }); + expect(consoleOutput[0]).to.equal("V3 [Info] " + testMessage); + }); - it("Unsupported endpoint types throw exception", () => { - const domainConfig = getDomainConfig({endpointType: "notSupported"}); - const plugin = constructPlugin(domainConfig); - - let errored = false; - try { - plugin.initializeVariables(); - } catch (err) { - errored = true; - expect(err.message).to.equal("notSupported is not supported endpointType, use EDGE or REGIONAL."); - } - expect(errored).to.equal(true); - }); + it("init AWS resources", () => { + const domainConfig = getDomainConfig({}); + const plugin = constructPlugin(domainConfig); + + let errored = false; + try { + plugin.initializeVariables(); + plugin.validateDomainConfigs(); + plugin.initAWSResources(); + } catch (err) { + errored = true; + } + expect(errored).to.equal(false); + }); - it("Unsupported api type throw exception", () => { - const domainConfig = getDomainConfig({apiType: "notSupported"}); - const plugin = constructPlugin(domainConfig); - - let errored = false; - try { - plugin.initializeVariables(); - } catch (err) { - errored = true; - expect(err.message).to.equal("notSupported is not supported api type, use REST, HTTP or WEBSOCKET."); - } - expect(errored).to.equal(true); - }); + it("Unsupported endpoint types throw exception", () => { + const domainConfig = getDomainConfig({ endpointType: "notSupported" }); + const plugin = constructPlugin(domainConfig); + + let errored = false; + try { + plugin.initializeVariables(); + } catch (err) { + errored = true; + expect(err.message).to.equal("notSupported is not supported endpointType, use EDGE or REGIONAL."); + } + expect(errored).to.equal(true); + }); - it("Get ApiGateway V1", () => { - const plugin = constructPlugin({}); - plugin.initAWSResources(); + it("Unsupported api type throw exception", () => { + const domainConfig = getDomainConfig({ apiType: "notSupported" }); + const plugin = constructPlugin(domainConfig); + + let errored = false; + try { + plugin.initializeVariables(); + } catch (err) { + errored = true; + expect(err.message).to.equal("notSupported is not supported api type, use REST, HTTP or WEBSOCKET."); + } + expect(errored).to.equal(true); + }); - const dc = new DomainConfig(getDomainConfig({ - apiType: Globals.apiTypes.rest - })); - const apiGateway = plugin.getApiGateway(dc); + it("Get ApiGateway V1", () => { + const plugin = constructPlugin({}); + plugin.initAWSResources(); - expect(apiGateway instanceof APIGatewayV1Wrapper).to.equal(true); - }); + const dc = new DomainConfig(getDomainConfig({ + apiType: Globals.apiTypes.rest + })); + const apiGateway = plugin.getApiGateway(dc); - it("Get ApiGateway V2", () => { - const plugin = constructPlugin({}); - plugin.initAWSResources(); - - // for the http API type should be APIGatewayV2Wrapper - let dc = new DomainConfig(getDomainConfig({ - apiType: Globals.apiTypes.http - })); - expect(plugin.getApiGateway(dc) instanceof APIGatewayV2Wrapper).to.equal(true); - - // for the websocket API type should be APIGatewayV2Wrapper - dc = new DomainConfig(getDomainConfig({ - apiType: Globals.apiTypes.websocket - })); - expect(plugin.getApiGateway(dc) instanceof APIGatewayV2Wrapper).to.equal(true); - - // for the multi-level base path and rest API type should be APIGatewayV2Wrapper - dc = new DomainConfig(getDomainConfig({ - basePath: "api/test", - apiType: Globals.apiTypes.rest, - })); - expect(plugin.getApiGateway(dc) instanceof APIGatewayV2Wrapper).to.equal(true); - }); + expect(apiGateway instanceof APIGatewayV1Wrapper).to.equal(true); }); - describe("Validate plugin configuration", () => { - it("Should thrown an Error when plugin customDomain configuration object is missing", () => { - const plugin = constructPlugin(getDomainConfig(getDomainConfig({}))); - delete plugin.serverless.service.custom.customDomain; - - let errored = false; - try { - plugin.validateConfigExists(); - } catch (err) { - errored = true; - expect(err.message).to.equal(`${Globals.pluginName}: Plugin configuration is missing.`); - } - expect(errored).to.equal(true); - }); + it("Get ApiGateway V2", () => { + const plugin = constructPlugin({}); + plugin.initAWSResources(); + + // for the http API type should be APIGatewayV2Wrapper + let dc = new DomainConfig(getDomainConfig({ + apiType: Globals.apiTypes.http + })); + expect(plugin.getApiGateway(dc) instanceof APIGatewayV2Wrapper).to.equal(true); + + // for the websocket API type should be APIGatewayV2Wrapper + dc = new DomainConfig(getDomainConfig({ + apiType: Globals.apiTypes.websocket + })); + expect(plugin.getApiGateway(dc) instanceof APIGatewayV2Wrapper).to.equal(true); + + // for the multi-level base path and rest API type should be APIGatewayV2Wrapper + dc = new DomainConfig(getDomainConfig({ + basePath: "api/test", + apiType: Globals.apiTypes.rest + })); + expect(plugin.getApiGateway(dc) instanceof APIGatewayV2Wrapper).to.equal(true); + }); + }); + + describe("Validate plugin configuration", () => { + it("Should thrown an Error when plugin customDomain configuration object is missing", () => { + const plugin = constructPlugin(getDomainConfig(getDomainConfig({}))); + delete plugin.serverless.service.custom.customDomain; + + let errored = false; + try { + plugin.validateConfigExists(); + } catch (err) { + errored = true; + expect(err.message).to.equal(`${Globals.pluginName}: Plugin configuration is missing.`); + } + expect(errored).to.equal(true); + }); - it("Should thrown an Error when Serverless custom configuration object is missing for multiple domains", () => { - const plugin = constructPlugin(getDomainConfig({}), null, null); - delete plugin.serverless.service.custom.customDomain; - delete plugin.serverless.service.custom.customDomains; - - let errored = false; - try { - plugin.validateConfigExists(); - } catch (err) { - errored = true; - expect(err.message).to.equal(`${Globals.pluginName}: Plugin configuration is missing.`); - } - expect(errored).to.equal(true); - }); + it("Should thrown an Error when Serverless custom configuration object is missing for multiple domains", () => { + const plugin = constructPlugin(getDomainConfig({}), null, null); + delete plugin.serverless.service.custom.customDomain; + delete plugin.serverless.service.custom.customDomains; + + let errored = false; + try { + plugin.validateConfigExists(); + } catch (err) { + errored = true; + expect(err.message).to.equal(`${Globals.pluginName}: Plugin configuration is missing.`); + } + expect(errored).to.equal(true); + }); - it("Should thrown an Error when Serverless custom configuration object is missing", () => { - const plugin = constructPlugin(getDomainConfig({})); - delete plugin.serverless.service.custom; - - let errored = false; - try { - plugin.validateConfigExists(); - } catch (err) { - errored = true; - expect(err.message).to.equal(`${Globals.pluginName}: Plugin configuration is missing.`); - } - expect(errored).to.equal(true); - }); + it("Should thrown an Error when Serverless custom configuration object is missing", () => { + const plugin = constructPlugin(getDomainConfig({})); + delete plugin.serverless.service.custom; + + let errored = false; + try { + plugin.validateConfigExists(); + } catch (err) { + errored = true; + expect(err.message).to.equal(`${Globals.pluginName}: Plugin configuration is missing.`); + } + expect(errored).to.equal(true); + }); - it("Unsupported HTTP EDGE endpoint configuration", () => { - const domainOptions = getDomainConfig({apiType: "http"}); - const plugin = constructPlugin(domainOptions); - - let errored = false; - try { - plugin.initializeVariables(); - plugin.validateDomainConfigs(); - } catch (err) { - errored = true; - expect(err.message).to.contains("'EDGE' endpointType is not compatible with HTTP APIs"); - } - expect(errored).to.equal(true); - }); + it("Unsupported HTTP EDGE endpoint configuration", () => { + const domainOptions = getDomainConfig({ apiType: "http" }); + const plugin = constructPlugin(domainOptions); + + let errored = false; + try { + plugin.initializeVariables(); + plugin.validateDomainConfigs(); + } catch (err) { + errored = true; + expect(err.message).to.contains("'EDGE' endpointType is not compatible with HTTP APIs"); + } + expect(errored).to.equal(true); + }); - it("Unsupported WS EDGE endpoint configuration", () => { - const domainOptions = getDomainConfig({apiType: "websocket"}); - const plugin = constructPlugin(domainOptions); - - let errored = false; - try { - plugin.initializeVariables(); - plugin.validateDomainConfigs(); - } catch (err) { - errored = true; - expect(err.message).to.equal("'EDGE' endpointType is not compatible with WebSocket APIs"); - } - expect(errored).to.equal(true); - }); + it("Unsupported WS EDGE endpoint configuration", () => { + const domainOptions = getDomainConfig({ apiType: "websocket" }); + const plugin = constructPlugin(domainOptions); + + let errored = false; + try { + plugin.initializeVariables(); + plugin.validateDomainConfigs(); + } catch (err) { + errored = true; + expect(err.message).to.equal("'EDGE' endpointType is not compatible with WebSocket APIs"); + } + expect(errored).to.equal(true); + }); - it("Lowercase edge endpoint type without errors", () => { - const domainOptions = getDomainConfig({endpointType: "edge"}); - const plugin = constructPlugin(domainOptions); - - let errored = false; - try { - plugin.initializeVariables(); - plugin.validateDomainConfigs(); - } catch (err) { - errored = true; - } - expect(errored).to.equal(false); - }); + it("Lowercase edge endpoint type without errors", () => { + const domainOptions = getDomainConfig({ endpointType: "edge" }); + const plugin = constructPlugin(domainOptions); + + let errored = false; + try { + plugin.initializeVariables(); + plugin.validateDomainConfigs(); + } catch (err) { + errored = true; + } + expect(errored).to.equal(false); + }); - it("Nested api type configuration", () => { - const domainOptions = getDomainConfig({}); - const plugin = constructPlugin({rest: domainOptions}); + it("Nested api type configuration", () => { + const domainOptions = getDomainConfig({}); + const plugin = constructPlugin({ rest: domainOptions }); - plugin.initializeVariables(); - plugin.validateDomainConfigs(); + plugin.initializeVariables(); + plugin.validateDomainConfigs(); - expect(plugin.domains.length).to.equal(1); - }); + expect(plugin.domains.length).to.equal(1); + }); - it("allowPathMatching", () => { - const domainOptions = getDomainConfig({allowPathMatching: true}); - const plugin = constructPlugin(domainOptions); + it("allowPathMatching", () => { + const domainOptions = getDomainConfig({ allowPathMatching: true }); + const plugin = constructPlugin(domainOptions); - plugin.initializeVariables(); - plugin.validateDomainConfigs(); + plugin.initializeVariables(); + plugin.validateDomainConfigs(); - expect(consoleOutput[0]).to.contains("This should only be used when migrating a path to a different API type. e.g. REST to HTTP."); - }); + expect(consoleOutput[0]).to.contains("This should only be used when migrating a path to a different API type. e.g. REST to HTTP."); + }); - it("Should enable the plugin by default", () => { - const plugin = constructPlugin(getDomainConfig({})); + it("Should enable the plugin by default", () => { + const plugin = constructPlugin(getDomainConfig({})); - plugin.initializeVariables(); - plugin.validateDomainConfigs(); + plugin.initializeVariables(); + plugin.validateDomainConfigs(); - expect(plugin.domains).length.to.be.greaterThan(0); - for (const domain of plugin.domains) { - expect(domain.enabled).to.equal(true); - } - }); + expect(plugin.domains).length.to.be.greaterThan(0); + for (const domain of plugin.domains) { + expect(domain.enabled).to.equal(true); + } + }); - it("Should enable the plugin when passing a true parameter with type boolean", () => { - const plugin = constructPlugin(getDomainConfig({enabled: true})); + it("Should enable the plugin when passing a true parameter with type boolean", () => { + const plugin = constructPlugin(getDomainConfig({ enabled: true })); - plugin.initializeVariables(); - plugin.validateDomainConfigs(); + plugin.initializeVariables(); + plugin.validateDomainConfigs(); - expect(plugin.domains).length.to.be.greaterThan(0); - for (const domain of plugin.domains) { - expect(domain.enabled).to.equal(true); - } - }); + expect(plugin.domains).length.to.be.greaterThan(0); + for (const domain of plugin.domains) { + expect(domain.enabled).to.equal(true); + } + }); - it("Should enable the plugin when passing a true parameter with type string", () => { - const plugin = constructPlugin(getDomainConfig({enabled: "true"})); + it("Should enable the plugin when passing a true parameter with type string", () => { + const plugin = constructPlugin(getDomainConfig({ enabled: "true" })); - plugin.initializeVariables(); - plugin.validateDomainConfigs(); + plugin.initializeVariables(); + plugin.validateDomainConfigs(); - expect(plugin.domains).length.to.be.greaterThan(0); - for (const domain of plugin.domains) { - expect(domain.enabled).to.equal(true); - } - }); + expect(plugin.domains).length.to.be.greaterThan(0); + for (const domain of plugin.domains) { + expect(domain.enabled).to.equal(true); + } + }); - it("Should disable the plugin when passing a false parameter with type boolean", () => { - const plugin = constructPlugin(getDomainConfig({enabled: false})); + it("Should disable the plugin when passing a false parameter with type boolean", () => { + const plugin = constructPlugin(getDomainConfig({ enabled: false })); - plugin.initializeVariables(); - plugin.validateDomainConfigs(); + plugin.initializeVariables(); + plugin.validateDomainConfigs(); - expect(plugin.domains.length).to.equal(0); - }); + expect(plugin.domains.length).to.equal(0); + }); - it("Should disable the plugin when passing a false parameter with type string", () => { - const plugin = constructPlugin(getDomainConfig({enabled: "false"})); + it("Should disable the plugin when passing a false parameter with type string", () => { + const plugin = constructPlugin(getDomainConfig({ enabled: "false" })); - plugin.initializeVariables(); - plugin.validateDomainConfigs(); + plugin.initializeVariables(); + plugin.validateDomainConfigs(); - expect(plugin.domains.length).to.equal(0); - }); + expect(plugin.domains.length).to.equal(0); + }); - it("Should throw an Error when passing a parameter that is not boolean", async () => { - const plugin = constructPlugin(getDomainConfig({enabled: "11"})); - - let errored = false; - try { - await plugin.hookWrapper(null); - } catch (err) { - errored = true; - expect(err.message).to.equal(`${Globals.pluginName}: Ambiguous boolean config: \"11\"`); - } - expect(errored).to.equal(true); - }); + it("Should throw an Error when passing a parameter that is not boolean", async () => { + const plugin = constructPlugin(getDomainConfig({ enabled: "11" })); + + let errored = false; + try { + await plugin.hookWrapper(null); + } catch (err) { + errored = true; + expect(err.message).to.equal(`${Globals.pluginName}: Ambiguous boolean config: "11"`); + } + expect(errored).to.equal(true); + }); - it("Should throw an Error when passing a parameter that cannot be converted to boolean", async () => { - const plugin = constructPlugin(getDomainConfig({enabled: "yes"})); - - let errored = false; - try { - await plugin.hookWrapper(null); - } catch (err) { - errored = true; - expect(err.message).to.equal(`${Globals.pluginName}: Ambiguous boolean config: \"yes\"`); - } - expect(errored).to.equal(true); - }); + it("Should throw an Error when passing a parameter that cannot be converted to boolean", async () => { + const plugin = constructPlugin(getDomainConfig({ enabled: "yes" })); + + let errored = false; + try { + await plugin.hookWrapper(null); + } catch (err) { + errored = true; + expect(err.message).to.equal(`${Globals.pluginName}: Ambiguous boolean config: "yes"`); + } + expect(errored).to.equal(true); + }); - it("Should throw an Error when mutual TLS is enabled for edge APIs", async () => { - const plugin = constructPlugin(getDomainConfig({ - endpointType: "edge", - tlsTruststoreUri: "s3://bucket-name/key-name" - })); - - let errored = false; - try { - await plugin.hookWrapper(null); - } catch (err) { - errored = true; - expect(err.message).to.equal(`EDGE APIs do not support mutual TLS, remove tlsTruststoreUri or change to a regional API.`); - } - expect(errored).to.equal(true); - }); + it("Should throw an Error when mutual TLS is enabled for edge APIs", async () => { + const plugin = constructPlugin(getDomainConfig({ + endpointType: "edge", + tlsTruststoreUri: "s3://bucket-name/key-name" + })); + + let errored = false; + try { + await plugin.hookWrapper(null); + } catch (err) { + errored = true; + expect(err.message).to.equal("EDGE APIs do not support mutual TLS, remove tlsTruststoreUri or change to a regional API."); + } + expect(errored).to.equal(true); + }); - it("Should throw an Error when mutual TLS uri is not an S3 uri", async () => { - const plugin = constructPlugin(getDomainConfig({ - endpointType: "regional", - tlsTruststoreUri: "http://example.com" - })); - - let errored = false; - try { - await plugin.hookWrapper(null); - } catch (err) { - errored = true; - expect(err.message).to.equal(`http://example.com is not a valid s3 uri, try something like s3://bucket-name/key-name.`); - } - expect(errored).to.equal(true); - }); + it("Should throw an Error when mutual TLS uri is not an S3 uri", async () => { + const plugin = constructPlugin(getDomainConfig({ + endpointType: "regional", + tlsTruststoreUri: "http://example.com" + })); + + let errored = false; + try { + await plugin.hookWrapper(null); + } catch (err) { + errored = true; + expect(err.message).to.equal("http://example.com is not a valid s3 uri, try something like s3://bucket-name/key-name."); + } + expect(errored).to.equal(true); + }); + }); + + describe("Hooks checks", () => { + it("after:deploy:deploy with the createBasePathMapping", async () => { + const APIGatewayMock = mockClient(APIGatewayClient); + APIGatewayMock.on(GetDomainNameCommand).resolves({ + domainName: "dummy_domain", + regionalHostedZoneId: "test_id" + }); + APIGatewayMock.on(GetBasePathMappingsCommand).resolves({ items: [] }); + APIGatewayMock.on(CreateBasePathMappingCommand).resolves(null); + const CloudFormationMock = mockClient(CloudFormationClient); + CloudFormationMock.on(DescribeStackResourceCommand).resolves({ + StackResourceDetail: { + LogicalResourceId: Globals.CFResourceIds[Globals.apiTypes.rest], + PhysicalResourceId: "test_rest_api_id", + ResourceType: "", + LastUpdatedTimestamp: null, + ResourceStatus: ResourceStatus.CREATE_COMPLETE + } + }); + + const domainOptions = getDomainConfig({ domainName: "test_domain" }); + const plugin = constructPlugin(domainOptions); + plugin.initAWSRegion = async () => null; + + await plugin.hooks["after:deploy:deploy"](); + + const commandCalls = APIGatewayMock.commandCalls(CreateBasePathMappingCommand); + expect(commandCalls.length).to.equal(1); }); - describe("Hooks checks", () => { - it("after:deploy:deploy with the createBasePathMapping", async () => { - const APIGatewayMock = mockClient(APIGatewayClient); - APIGatewayMock.on(GetDomainNameCommand).resolves({ - domainName: "dummy_domain", - regionalHostedZoneId: "test_id" - }); - APIGatewayMock.on(GetBasePathMappingsCommand).resolves({items: []}); - APIGatewayMock.on(CreateBasePathMappingCommand).resolves(null); - const CloudFormationMock = mockClient(CloudFormationClient); - CloudFormationMock.on(DescribeStackResourceCommand).resolves({ - StackResourceDetail: { - LogicalResourceId: Globals.CFResourceIds[Globals.apiTypes.rest], - PhysicalResourceId: "test_rest_api_id", - ResourceType: "", - LastUpdatedTimestamp: null, - ResourceStatus: ResourceStatus.CREATE_COMPLETE, - }, - }); - - const domainOptions = getDomainConfig({domainName: "test_domain"}); - const plugin = constructPlugin(domainOptions); - plugin.initAWSRegion = async () => null; - - await plugin.hooks["after:deploy:deploy"](); - - const commandCalls = APIGatewayMock.commandCalls(CreateBasePathMappingCommand); - expect(commandCalls.length).to.equal(1); - }); + it("after:deploy:deploy with the updateBasePathMapping", async () => { + const APIGatewayMock = mockClient(APIGatewayClient); + APIGatewayMock.on(GetDomainNameCommand).resolves({ + domainName: "dummy_domain", + regionalHostedZoneId: "test_id" + }); + APIGatewayMock.on(GetBasePathMappingsCommand).resolves({ + items: [{ + restApiId: "test_rest_api_id", + basePath: "test", + stage: "test" + }] + }); + APIGatewayMock.on(UpdateBasePathMappingCommand).resolves(null); + const CloudFormationMock = mockClient(CloudFormationClient); + CloudFormationMock.on(DescribeStackResourceCommand).resolves({ + StackResourceDetail: { + LogicalResourceId: Globals.CFResourceIds[Globals.apiTypes.rest], + PhysicalResourceId: "test_rest_api_id", + ResourceType: "", + LastUpdatedTimestamp: null, + ResourceStatus: ResourceStatus.CREATE_COMPLETE + } + }); + + const domainOptions = getDomainConfig({ domainName: "test_domain" }); + const plugin = constructPlugin(domainOptions); + plugin.initAWSRegion = async () => null; + + await plugin.hooks["after:deploy:deploy"](); + + const commandCalls = APIGatewayMock.commandCalls(UpdateBasePathMappingCommand); + expect(commandCalls.length).to.equal(1); + }); - it("after:deploy:deploy with the updateBasePathMapping", async () => { - const APIGatewayMock = mockClient(APIGatewayClient); - APIGatewayMock.on(GetDomainNameCommand).resolves({ - domainName: "dummy_domain", - regionalHostedZoneId: "test_id" - }); - APIGatewayMock.on(GetBasePathMappingsCommand).resolves({ - items: [{ - restApiId: "test_rest_api_id", - basePath: "test", - stage: "test" - }] - }); - APIGatewayMock.on(UpdateBasePathMappingCommand).resolves(null); - const CloudFormationMock = mockClient(CloudFormationClient); - CloudFormationMock.on(DescribeStackResourceCommand).resolves({ - StackResourceDetail: { - LogicalResourceId: Globals.CFResourceIds[Globals.apiTypes.rest], - PhysicalResourceId: "test_rest_api_id", - ResourceType: "", - LastUpdatedTimestamp: null, - ResourceStatus: ResourceStatus.CREATE_COMPLETE, - }, - }); - - const domainOptions = getDomainConfig({domainName: "test_domain"}); - const plugin = constructPlugin(domainOptions); - plugin.initAWSRegion = async () => null; - - await plugin.hooks["after:deploy:deploy"](); - - const commandCalls = APIGatewayMock.commandCalls(UpdateBasePathMappingCommand); - expect(commandCalls.length).to.equal(1); - }); + it("after:info:info", async () => { + const APIGatewayMock = mockClient(APIGatewayClient); + APIGatewayMock.on(GetDomainNameCommand).resolves({ + domainName: "dummy_domain", + regionalHostedZoneId: "test_id" + }); - it("after:info:info", async () => { - const APIGatewayMock = mockClient(APIGatewayClient); - APIGatewayMock.on(GetDomainNameCommand).resolves({ - domainName: "dummy_domain", - regionalHostedZoneId: "test_id" - }); + const plugin = constructPlugin(getDomainConfig({ domainName: "test_domain" })); + plugin.initAWSRegion = async () => null; - const plugin = constructPlugin(getDomainConfig({domainName: "test_domain"})); - plugin.initAWSRegion = async () => null; + await plugin.hooks["after:info:info"](); - await plugin.hooks["after:info:info"](); + expect(consoleOutput[0]).to.equal("[Summary] Distribution Domain Name"); + expect(consoleOutput[1]).to.equal(" Domain Name: test_domain"); + expect(consoleOutput[2]).to.equal(" Target Domain: dummy_domain"); + expect(consoleOutput[3]).to.equal(" Hosted Zone Id: test_id"); + }); - expect(consoleOutput[0]).to.equal("[Summary] Distribution Domain Name"); - expect(consoleOutput[1]).to.equal(" Domain Name: test_domain"); - expect(consoleOutput[2]).to.equal(" Target Domain: dummy_domain"); - expect(consoleOutput[3]).to.equal(" Hosted Zone Id: test_id"); + it("before:deploy:deploy with autoDomain false", async () => { + const APIGatewayMock = mockClient(APIGatewayClient); + APIGatewayMock.on(GetDomainNameCommand).resolvesOnce({}) + .resolves({ + domainName: "dummy_domain", + regionalHostedZoneId: "test_id" }); - it("before:deploy:deploy with autoDomain false", async () => { - const APIGatewayMock = mockClient(APIGatewayClient); - APIGatewayMock.on(GetDomainNameCommand).resolvesOnce({}) - .resolves({ - domainName: "dummy_domain", - regionalHostedZoneId: "test_id" - }); + const domainOptions = getDomainConfig({ domainName: "test_domain" }); + const plugin = constructPlugin(domainOptions); + plugin.initAWSRegion = async () => null; - const domainOptions = getDomainConfig({domainName: "test_domain"}); - const plugin = constructPlugin(domainOptions); - plugin.initAWSRegion = async () => null; + const createDomainSpy = chaiSpy.on(plugin, "createDomain"); + await plugin.hooks["before:deploy:deploy"](); - const createDomainSpy = chaiSpy.on(plugin, "createDomain"); - await plugin.hooks["before:deploy:deploy"](); + expect(createDomainSpy).to.not.have.been.called(); + }); - expect(createDomainSpy).to.not.have.been.called(); + it("before:deploy:deploy with autoDomain true", async () => { + const APIGatewayMock = mockClient(APIGatewayClient); + APIGatewayMock.on(GetDomainNameCommand) + .rejectsOnce({ $metadata: { httpStatusCode: 404 } }) + .resolves({ + domainName: "dummy_domain", + regionalHostedZoneId: "test_id" }); - it("before:deploy:deploy with autoDomain true", async () => { - const APIGatewayMock = mockClient(APIGatewayClient); - APIGatewayMock.on(GetDomainNameCommand) - .rejectsOnce({"$metadata": {httpStatusCode: 404}}) - .resolves({ - domainName: "dummy_domain", - regionalHostedZoneId: "test_id" - }); - - const plugin = constructPlugin(getDomainConfig({ - domainName: "test_domain", - autoDomain: true - })); - plugin.createDomain = async () => null; - plugin.initAWSRegion = async () => null; - - const createDomainSpy = chaiSpy.on(plugin, "createDomain"); - await plugin.hooks["before:deploy:deploy"](); - - expect(createDomainSpy).to.have.been.called(); - }); + const plugin = constructPlugin(getDomainConfig({ + domainName: "test_domain", + autoDomain: true + })); + plugin.createDomain = async () => null; + plugin.initAWSRegion = async () => null; - it("before:remove:remove with autoDomain true", async () => { - const APIGatewayMock = mockClient(APIGatewayClient); - APIGatewayMock.on(GetDomainNameCommand).resolves({ - domainName: "dummy_domain", - regionalHostedZoneId: "test_id" - }); - APIGatewayMock.on(GetBasePathMappingsCommand).resolves({ - items: [{ - restApiId: "test_rest_api_id", - basePath: "test", - stage: "test" - }] - }); - APIGatewayMock.on(DeleteBasePathMappingCommand).resolves(null); - const CloudFormationMock = mockClient(CloudFormationClient); - CloudFormationMock.on(DescribeStackResourceCommand).resolves({ - StackResourceDetail: { - LogicalResourceId: Globals.CFResourceIds[Globals.apiTypes.rest], - PhysicalResourceId: "test_rest_api_id", - ResourceType: "", - LastUpdatedTimestamp: null, - ResourceStatus: ResourceStatus.CREATE_COMPLETE, - }, - }); - - const domainOptions = getDomainConfig({domainName: "test_domain"}); - const plugin = constructPlugin(domainOptions); - plugin.initAWSRegion = async () => null; - - await plugin.hooks["before:remove:remove"](); - - const commandCalls = APIGatewayMock.commandCalls(DeleteBasePathMappingCommand); - expect(commandCalls.length).to.equal(1); - }); + const createDomainSpy = chaiSpy.on(plugin, "createDomain"); + await plugin.hooks["before:deploy:deploy"](); - it("before:remove:remove with autoDomain false", async () => { - const APIGatewayMock = mockClient(APIGatewayClient); - APIGatewayMock.on(GetDomainNameCommand).resolves({ - domainName: "dummy_domain", - regionalHostedZoneId: "test_id" - }); - APIGatewayMock.on(GetBasePathMappingsCommand).resolves({ - items: [{ - restApiId: "test_rest_api_id", - basePath: "test", - stage: "test" - }] - }); - APIGatewayMock.on(DeleteBasePathMappingCommand).resolves(null); - const CloudFormationMock = mockClient(CloudFormationClient); - CloudFormationMock.on(DescribeStackResourceCommand).resolves({ - StackResourceDetail: { - LogicalResourceId: Globals.CFResourceIds[Globals.apiTypes.rest], - PhysicalResourceId: "test_rest_api_id", - ResourceType: "", - LastUpdatedTimestamp: null, - ResourceStatus: ResourceStatus.CREATE_COMPLETE, - }, - }); - - const domainOptions = getDomainConfig({ - domainName: "test_domain", - autoDomain: true - }); - const plugin = constructPlugin(domainOptions); - plugin.initAWSRegion = async () => null; - plugin.deleteDomain = async () => null; - - const deleteDomainSpy = chaiSpy.on(plugin, "deleteDomain"); - await plugin.hooks["before:remove:remove"](); - - const commandCalls = APIGatewayMock.commandCalls(DeleteBasePathMappingCommand); - expect(commandCalls.length).to.equal(1); - expect(deleteDomainSpy).to.have.been.called(); - }); + expect(createDomainSpy).to.have.been.called(); + }); - it("create_domain:create", async () => { - const APIGatewayMock = mockClient(APIGatewayClient); - APIGatewayMock.on(GetDomainNameCommand).resolves({ - domainName: "dummy_domain", - regionalHostedZoneId: "test_id" - }); - const Route53Mock = mockClient(Route53Client); - Route53Mock.on(ListHostedZonesCommand).resolves({ - HostedZones: [{ - CallerReference: "1", - Config: {PrivateZone: true}, - Id: "public_host_id", - Name: "test_domain", - }] - }); - Route53Mock.on(ChangeResourceRecordSetsCommand).resolves(null); - - const plugin = constructPlugin(getDomainConfig({domainName: "test_domain"})); - plugin.initAWSRegion = async () => null; - plugin.deleteDomain = async () => null; - - const deleteDomainSpy = chaiSpy.on(plugin, "deleteDomain"); - await plugin.hooks["create_domain:create"](); - - const commandCalls = Route53Mock.commandCalls(ChangeResourceRecordSetsCommand); - expect(commandCalls.length).to.equal(1); - expect(deleteDomainSpy).to.not.have.been.called(); - }); + it("before:remove:remove with autoDomain true", async () => { + const APIGatewayMock = mockClient(APIGatewayClient); + APIGatewayMock.on(GetDomainNameCommand).resolves({ + domainName: "dummy_domain", + regionalHostedZoneId: "test_id" + }); + APIGatewayMock.on(GetBasePathMappingsCommand).resolves({ + items: [{ + restApiId: "test_rest_api_id", + basePath: "test", + stage: "test" + }] + }); + APIGatewayMock.on(DeleteBasePathMappingCommand).resolves(null); + const CloudFormationMock = mockClient(CloudFormationClient); + CloudFormationMock.on(DescribeStackResourceCommand).resolves({ + StackResourceDetail: { + LogicalResourceId: Globals.CFResourceIds[Globals.apiTypes.rest], + PhysicalResourceId: "test_rest_api_id", + ResourceType: "", + LastUpdatedTimestamp: null, + ResourceStatus: ResourceStatus.CREATE_COMPLETE + } + }); + + const domainOptions = getDomainConfig({ domainName: "test_domain" }); + const plugin = constructPlugin(domainOptions); + plugin.initAWSRegion = async () => null; + + await plugin.hooks["before:remove:remove"](); + + const commandCalls = APIGatewayMock.commandCalls(DeleteBasePathMappingCommand); + expect(commandCalls.length).to.equal(1); + }); - it("create_domain:create with no domain info", async () => { - const APIGatewayMock = mockClient(APIGatewayClient); - APIGatewayMock.on(GetDomainNameCommand).rejects({"$metadata": {httpStatusCode: 404}}) - APIGatewayMock.on(CreateDomainNameCommand).resolves({ - distributionDomainName: "foo", - securityPolicy: "TLS_1_0" - }); - const ACMCMock = mockClient(ACMClient); - ACMCMock.on(ListCertificatesCommand).resolves({ - CertificateSummaryList: [{ - CertificateArn: "test_certificate_arn", - DomainName: "test_domain", - }] - }); - const Route53Mock = mockClient(Route53Client); - Route53Mock.on(ListHostedZonesCommand).resolves({ - HostedZones: [{ - CallerReference: "1", - Config: {PrivateZone: true}, - Id: "public_host_id", - Name: "test_domain", - }] - }); - Route53Mock.on(ChangeResourceRecordSetsCommand).resolves(null); - - const plugin = constructPlugin(getDomainConfig({domainName: "test_domain"})); - plugin.initAWSRegion = async () => null; - plugin.deleteDomain = async () => null; - - const deleteDomainSpy = chaiSpy.on(plugin, "deleteDomain"); - await plugin.hooks["create_domain:create"](); - - const commandCalls = Route53Mock.commandCalls(ChangeResourceRecordSetsCommand); - expect(commandCalls.length).to.equal(1); - expect(deleteDomainSpy).to.not.have.been.called(); - }); + it("before:remove:remove with autoDomain false", async () => { + const APIGatewayMock = mockClient(APIGatewayClient); + APIGatewayMock.on(GetDomainNameCommand).resolves({ + domainName: "dummy_domain", + regionalHostedZoneId: "test_id" + }); + APIGatewayMock.on(GetBasePathMappingsCommand).resolves({ + items: [{ + restApiId: "test_rest_api_id", + basePath: "test", + stage: "test" + }] + }); + APIGatewayMock.on(DeleteBasePathMappingCommand).resolves(null); + const CloudFormationMock = mockClient(CloudFormationClient); + CloudFormationMock.on(DescribeStackResourceCommand).resolves({ + StackResourceDetail: { + LogicalResourceId: Globals.CFResourceIds[Globals.apiTypes.rest], + PhysicalResourceId: "test_rest_api_id", + ResourceType: "", + LastUpdatedTimestamp: null, + ResourceStatus: ResourceStatus.CREATE_COMPLETE + } + }); + + const domainOptions = getDomainConfig({ + domainName: "test_domain", + autoDomain: true + }); + const plugin = constructPlugin(domainOptions); + plugin.initAWSRegion = async () => null; + plugin.deleteDomain = async () => null; + + const deleteDomainSpy = chaiSpy.on(plugin, "deleteDomain"); + await plugin.hooks["before:remove:remove"](); + + const commandCalls = APIGatewayMock.commandCalls(DeleteBasePathMappingCommand); + expect(commandCalls.length).to.equal(1); + expect(deleteDomainSpy).to.have.been.called(); + }); - it("delete_domain:delete", async () => { - const APIGatewayMock = mockClient(APIGatewayClient); - APIGatewayMock.on(GetDomainNameCommand).resolves({ - domainName: "dummy_domain", - regionalHostedZoneId: "test_id" - }); - APIGatewayMock.on(DeleteDomainNameCommand).resolves(null); - const ACMCMock = mockClient(ACMClient); - ACMCMock.on(ListCertificatesCommand).resolves({ - CertificateSummaryList: [{ - CertificateArn: "test_certificate_arn", - DomainName: "test_domain", - }] - }); - const Route53Mock = mockClient(Route53Client); - Route53Mock.on(ListHostedZonesCommand).resolves({ - HostedZones: [{ - CallerReference: "1", - Config: {PrivateZone: true}, - Id: "public_host_id", - Name: "test_domain", - }] - }); - Route53Mock.on(ChangeResourceRecordSetsCommand).resolves(null); - - const plugin = constructPlugin(getDomainConfig({domainName: "test_domain"})); - plugin.initAWSRegion = async () => null; - - await plugin.hooks["delete_domain:delete"](); - - const commandCalls = Route53Mock.commandCalls(ChangeResourceRecordSetsCommand); - expect(commandCalls.length).to.equal(1); - }); + it("create_domain:create", async () => { + const APIGatewayMock = mockClient(APIGatewayClient); + APIGatewayMock.on(GetDomainNameCommand).resolves({ + domainName: "dummy_domain", + regionalHostedZoneId: "test_id" + }); + const Route53Mock = mockClient(Route53Client); + Route53Mock.on(ListHostedZonesCommand).resolves({ + HostedZones: [{ + CallerReference: "1", + Config: { PrivateZone: true }, + Id: "public_host_id", + Name: "test_domain" + }] + }); + Route53Mock.on(ChangeResourceRecordSetsCommand).resolves(null); + + const plugin = constructPlugin(getDomainConfig({ domainName: "test_domain" })); + plugin.initAWSRegion = async () => null; + plugin.deleteDomain = async () => null; + + const deleteDomainSpy = chaiSpy.on(plugin, "deleteDomain"); + await plugin.hooks["create_domain:create"](); + + const commandCalls = Route53Mock.commandCalls(ChangeResourceRecordSetsCommand); + expect(commandCalls.length).to.equal(1); + expect(deleteDomainSpy).to.not.have.been.called(); + }); + + it("create_domain:create with no domain info", async () => { + const APIGatewayMock = mockClient(APIGatewayClient); + APIGatewayMock.on(GetDomainNameCommand).rejects({ $metadata: { httpStatusCode: 404 } }); + APIGatewayMock.on(CreateDomainNameCommand).resolves({ + distributionDomainName: "foo", + securityPolicy: "TLS_1_0" + }); + const ACMCMock = mockClient(ACMClient); + ACMCMock.on(ListCertificatesCommand).resolves({ + CertificateSummaryList: [{ + CertificateArn: "test_certificate_arn", + DomainName: "test_domain" + }] + }); + const Route53Mock = mockClient(Route53Client); + Route53Mock.on(ListHostedZonesCommand).resolves({ + HostedZones: [{ + CallerReference: "1", + Config: { PrivateZone: true }, + Id: "public_host_id", + Name: "test_domain" + }] + }); + Route53Mock.on(ChangeResourceRecordSetsCommand).resolves(null); + + const plugin = constructPlugin(getDomainConfig({ domainName: "test_domain" })); + plugin.initAWSRegion = async () => null; + plugin.deleteDomain = async () => null; + + const deleteDomainSpy = chaiSpy.on(plugin, "deleteDomain"); + await plugin.hooks["create_domain:create"](); + + const commandCalls = Route53Mock.commandCalls(ChangeResourceRecordSetsCommand); + expect(commandCalls.length).to.equal(1); + expect(deleteDomainSpy).to.not.have.been.called(); + }); + + it("delete_domain:delete", async () => { + const APIGatewayMock = mockClient(APIGatewayClient); + APIGatewayMock.on(GetDomainNameCommand).resolves({ + domainName: "dummy_domain", + regionalHostedZoneId: "test_id" + }); + APIGatewayMock.on(DeleteDomainNameCommand).resolves(null); + const ACMCMock = mockClient(ACMClient); + ACMCMock.on(ListCertificatesCommand).resolves({ + CertificateSummaryList: [{ + CertificateArn: "test_certificate_arn", + DomainName: "test_domain" + }] + }); + const Route53Mock = mockClient(Route53Client); + Route53Mock.on(ListHostedZonesCommand).resolves({ + HostedZones: [{ + CallerReference: "1", + Config: { PrivateZone: true }, + Id: "public_host_id", + Name: "test_domain" + }] + }); + Route53Mock.on(ChangeResourceRecordSetsCommand).resolves(null); + + const plugin = constructPlugin(getDomainConfig({ domainName: "test_domain" })); + plugin.initAWSRegion = async () => null; + + await plugin.hooks["delete_domain:delete"](); + + const commandCalls = Route53Mock.commandCalls(ChangeResourceRecordSetsCommand); + expect(commandCalls.length).to.equal(1); }); + }); }); diff --git a/test/unit-tests/logging.test.ts b/test/unit-tests/logging.test.ts index df4ad882..82131890 100644 --- a/test/unit-tests/logging.test.ts +++ b/test/unit-tests/logging.test.ts @@ -1,87 +1,87 @@ -import {consoleOutput, expect, getDomainConfig} from "./base"; +import { consoleOutput, expect, getDomainConfig } from "./base"; import Logging from "../../src/logging"; import Globals from "../../src/globals"; import DomainConfig = require("../../src/models/domain-config"); import DomainInfo = require("../../src/models/domain-info"); describe("Logging checks", () => { - beforeEach(() => { - consoleOutput.length = 0; - }); + beforeEach(() => { + consoleOutput.length = 0; + }); - it("cliLog", () => { - Logging.cliLog("test", "message"); - expect(consoleOutput[0]).to.equal("test message"); - }); + it("cliLog", () => { + Logging.cliLog("test", "message"); + expect(consoleOutput[0]).to.equal("test message"); + }); - describe("V2 logging", () => { - it("logging test", () => { - Logging.logError("message"); - expect(consoleOutput[0]).to.equal("[Error] message"); - Logging.logInfo("message"); - expect(consoleOutput[1]).to.equal("[Info] message"); - Logging.logWarning("message"); - expect(consoleOutput[2]).to.equal("[WARNING] message"); + describe("V2 logging", () => { + it("logging test", () => { + Logging.logError("message"); + expect(consoleOutput[0]).to.equal("[Error] message"); + Logging.logInfo("message"); + expect(consoleOutput[1]).to.equal("[Info] message"); + Logging.logWarning("message"); + expect(consoleOutput[2]).to.equal("[WARNING] message"); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain" - })); - dc.domainInfo = new DomainInfo({ - domainName: "dummy_domain", - hostedZoneId: "test_hosted_zone" - }) + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain" + })); + dc.domainInfo = new DomainInfo({ + domainName: "dummy_domain", + hostedZoneId: "test_hosted_zone" + }); - Logging.printDomainSummary([dc]); + Logging.printDomainSummary([dc]); - expect(consoleOutput[3]).to.equal("[Summary] Distribution Domain Name"); - }); + expect(consoleOutput[3]).to.equal("[Summary] Distribution Domain Name"); }); + }); - describe("V3 logging", () => { - before(() => { - Globals.v3Utils = { - writeText: (message: string) => { - consoleOutput.push(message); - }, - log: { - error(message: string) { - consoleOutput.push("V3 [Error] " + message); - }, - verbose(message: string) { - consoleOutput.push("V3 [Info] " + message); - }, - warning(message: string) { - consoleOutput.push("V3 [WARNING] " + message); - } - }, - progress: null - } - Globals.serverless.addServiceOutputSection = (name: string, data: string[]) => { - consoleOutput.push(name); + describe("V3 logging", () => { + before(() => { + Globals.v3Utils = { + writeText: (message: string) => { + consoleOutput.push(message); + }, + log: { + error (message: string) { + consoleOutput.push("V3 [Error] " + message); + }, + verbose (message: string) { + consoleOutput.push("V3 [Info] " + message); + }, + warning (message: string) { + consoleOutput.push("V3 [WARNING] " + message); + } + }, + progress: null + }; + Globals.serverless.addServiceOutputSection = (name: string, data: string[]) => { + consoleOutput.push(name); - data.map((item) => { - consoleOutput.push(item); - }) - } + data.forEach((item) => { + consoleOutput.push(item); }); - it("logging test", () => { - Logging.logError("message"); - expect(consoleOutput[0]).to.equal("V3 [Error] message"); - Logging.logInfo("message"); - expect(consoleOutput[1]).to.equal("V3 [Info] message"); - Logging.logWarning("message"); - expect(consoleOutput[2]).to.equal("V3 [WARNING] message"); + }; + }); + it("logging test", () => { + Logging.logError("message"); + expect(consoleOutput[0]).to.equal("V3 [Error] message"); + Logging.logInfo("message"); + expect(consoleOutput[1]).to.equal("V3 [Info] message"); + Logging.logWarning("message"); + expect(consoleOutput[2]).to.equal("V3 [WARNING] message"); - const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain" - })); - dc.domainInfo = new DomainInfo({ - domainName: "dummy_domain", - hostedZoneId: "test_hosted_zone" - }) + const dc = new DomainConfig(getDomainConfig({ + domainName: "test_domain" + })); + dc.domainInfo = new DomainInfo({ + domainName: "dummy_domain", + hostedZoneId: "test_hosted_zone" + }); - Logging.printDomainSummary([dc]); - expect(consoleOutput[3]).to.equal("Serverless Domain Manager"); - }); + Logging.printDomainSummary([dc]); + expect(consoleOutput[3]).to.equal("Serverless Domain Manager"); }); + }); }); From 5fde4cd45804f6d3e660a7416245d469da4fe2a9 Mon Sep 17 00:00:00 2001 From: rddimon Date: Thu, 7 Dec 2023 15:37:07 +0200 Subject: [PATCH 06/10] Update version --- CHANGELOG.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09aeef16..00ca2f61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## [7.3.2] - 2023-12-07 +## [7.4.0] - 2023-12-07 ### Fixed - Bugfix for the hosted zone filtering. diff --git a/package.json b/package.json index 38bf2aa6..0e7370d4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serverless-domain-manager", - "version": "7.3.2", + "version": "7.4.0", "engines": { "node": ">=14" }, From 7188b33d99817e208114113fdfa1120689157e20 Mon Sep 17 00:00:00 2001 From: rddimon Date: Thu, 7 Dec 2023 15:38:53 +0200 Subject: [PATCH 07/10] Fix test --- test/unit-tests/index.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit-tests/index.test.ts b/test/unit-tests/index.test.ts index bddad42b..398ec0fc 100644 --- a/test/unit-tests/index.test.ts +++ b/test/unit-tests/index.test.ts @@ -320,7 +320,7 @@ describe("Custom Domain Plugin", () => { it("Should throw an Error when mutual TLS uri is not an S3 uri", async () => { const plugin = constructPlugin(getDomainConfig({ endpointType: "regional", - tlsTruststoreUri: "http://example.com" + tlsTruststoreUri: "https://example.com" })); let errored = false; @@ -328,7 +328,7 @@ describe("Custom Domain Plugin", () => { await plugin.hookWrapper(null); } catch (err) { errored = true; - expect(err.message).to.equal("http://example.com is not a valid s3 uri, try something like s3://bucket-name/key-name."); + expect(err.message).to.equal("https://example.com is not a valid s3 uri, try something like s3://bucket-name/key-name."); } expect(errored).to.equal(true); }); From 5e833768ac0e3b2e298f17c8eaf46f3fbfc55fb5 Mon Sep 17 00:00:00 2001 From: rddimon Date: Thu, 7 Dec 2023 15:43:58 +0200 Subject: [PATCH 08/10] Fix getAWSPagedResults --- src/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index 5bbad1ba..cfdb450e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -58,7 +58,7 @@ async function getAWSPagedResults Date: Thu, 7 Dec 2023 15:52:48 +0200 Subject: [PATCH 09/10] Revert AWS clients changes --- src/aws/acm-wrapper.ts | 4 ++-- src/aws/api-gateway-v1-wrapper.ts | 4 ++-- src/aws/api-gateway-v2-wrapper.ts | 4 ++-- src/aws/cloud-formation-wrapper.ts | 4 ++-- src/aws/route53-wrapper.ts | 8 ++++---- src/aws/s3-wrapper.ts | 4 ++-- test/unit-tests/aws/acm-wrapper.test.ts | 8 ++++---- test/unit-tests/aws/api-gateway-v1-wrapper.test.ts | 4 ++-- test/unit-tests/aws/api-gateway-v2-wrapper.test.ts | 4 ++-- test/unit-tests/aws/cloud-formation-wrapper.test.ts | 4 ++-- test/unit-tests/aws/route53-wrapper.test.ts | 10 +++++----- test/unit-tests/aws/s3-wrapper.test.ts | 4 ++-- 12 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/aws/acm-wrapper.ts b/src/aws/acm-wrapper.ts index 64127a54..7d8ea72f 100644 --- a/src/aws/acm-wrapper.ts +++ b/src/aws/acm-wrapper.ts @@ -22,12 +22,12 @@ class ACMWrapper { constructor (credentials: any, endpointType: string) { const isEdge = endpointType === Globals.endpointTypes.edge; - this.acm = new ACMClient([{ + this.acm = new ACMClient({ credentials, region: isEdge ? Globals.defaultRegion : Globals.getRegion(), retryStrategy: Globals.getRetryStrategy(), requestHandler: Globals.getRequestHandler() - }]); + }); } public async getCertArn (domain: DomainConfig): Promise { diff --git a/src/aws/api-gateway-v1-wrapper.ts b/src/aws/api-gateway-v1-wrapper.ts index d936b50a..1b509f2b 100644 --- a/src/aws/api-gateway-v1-wrapper.ts +++ b/src/aws/api-gateway-v1-wrapper.ts @@ -29,12 +29,12 @@ class APIGatewayV1Wrapper extends APIGatewayBase { constructor (credentials?: any) { super(); - this.apiGateway = new APIGatewayClient([{ + this.apiGateway = new APIGatewayClient({ credentials, region: Globals.getRegion(), retryStrategy: Globals.getRetryStrategy(), requestHandler: Globals.getRequestHandler() - }]); + }); } public async createCustomDomain (domain: DomainConfig): Promise { diff --git a/src/aws/api-gateway-v2-wrapper.ts b/src/aws/api-gateway-v2-wrapper.ts index 1c42e4ce..fa761b64 100644 --- a/src/aws/api-gateway-v2-wrapper.ts +++ b/src/aws/api-gateway-v2-wrapper.ts @@ -29,12 +29,12 @@ class APIGatewayV2Wrapper extends APIGatewayBase { constructor (credentials?: any) { super(); - this.apiGateway = new ApiGatewayV2Client([{ + this.apiGateway = new ApiGatewayV2Client({ credentials, region: Globals.getRegion(), retryStrategy: Globals.getRetryStrategy(), requestHandler: Globals.getRequestHandler() - }]); + }); } /** diff --git a/src/aws/cloud-formation-wrapper.ts b/src/aws/cloud-formation-wrapper.ts index 216f22f8..57292a49 100644 --- a/src/aws/cloud-formation-wrapper.ts +++ b/src/aws/cloud-formation-wrapper.ts @@ -27,12 +27,12 @@ class CloudFormationWrapper { // for the CloudFormation stack we should use the `base` stage not the plugin custom stage const defaultStackName = Globals.serverless.service.service + "-" + Globals.getBaseStage(); this.stackName = Globals.serverless.service.provider.stackName || defaultStackName; - this.cloudFormation = new CloudFormationClient([{ + this.cloudFormation = new CloudFormationClient({ credentials, region: Globals.getRegion(), retryStrategy: Globals.getRetryStrategy(), requestHandler: Globals.getRequestHandler() - }]); + }); } /** diff --git a/src/aws/route53-wrapper.ts b/src/aws/route53-wrapper.ts index 9863c7cd..bf33e772 100644 --- a/src/aws/route53-wrapper.ts +++ b/src/aws/route53-wrapper.ts @@ -19,19 +19,19 @@ class Route53Wrapper { // not null and not undefined if (credentials) { this.region = region || Globals.getRegion(); - this.route53 = new Route53Client([{ + this.route53 = new Route53Client({ credentials, region: this.region, retryStrategy: Globals.getRetryStrategy(), requestHandler: Globals.getRequestHandler() - }]); + }); } else { this.region = Globals.getRegion(); - this.route53 = new Route53Client([{ + this.route53 = new Route53Client({ region: this.region, retryStrategy: Globals.getRetryStrategy(), requestHandler: Globals.getRequestHandler() - }]); + }); } } diff --git a/src/aws/s3-wrapper.ts b/src/aws/s3-wrapper.ts index dd4e5e40..11ec7ccb 100644 --- a/src/aws/s3-wrapper.ts +++ b/src/aws/s3-wrapper.ts @@ -7,11 +7,11 @@ class S3Wrapper { public s3: S3Client; constructor (credentials?: any) { - this.s3 = new S3Client([{ + this.s3 = new S3Client({ credentials, region: Globals.getRegion(), requestHandler: Globals.getRequestHandler() - }]); + }); } /** diff --git a/test/unit-tests/aws/acm-wrapper.test.ts b/test/unit-tests/aws/acm-wrapper.test.ts index 8f34080e..18b8ab42 100644 --- a/test/unit-tests/aws/acm-wrapper.test.ts +++ b/test/unit-tests/aws/acm-wrapper.test.ts @@ -23,15 +23,15 @@ const certTestData = { }; describe("ACM Wrapper checks", () => { - it("Initialization edge", () => { + it("Initialization edge", async () => { const acmWrapper = new ACMWrapper(null, Globals.endpointTypes.edge); - const actualResult = acmWrapper.acm.config[0].region; + const actualResult = await acmWrapper.acm.config.region(); expect(actualResult).to.equal(Globals.defaultRegion); }); - it("Initialization regional", () => { + it("Initialization regional", async () => { const acmWrapper = new ACMWrapper(null, Globals.endpointTypes.regional); - const actualResult = acmWrapper.acm.config[0].region; + const actualResult = await acmWrapper.acm.config.region(); expect(actualResult).to.equal(Globals.currentRegion); }); diff --git a/test/unit-tests/aws/api-gateway-v1-wrapper.test.ts b/test/unit-tests/aws/api-gateway-v1-wrapper.test.ts index 80cb8e2b..0826f4be 100644 --- a/test/unit-tests/aws/api-gateway-v1-wrapper.test.ts +++ b/test/unit-tests/aws/api-gateway-v1-wrapper.test.ts @@ -18,9 +18,9 @@ describe("API Gateway V1 wrapper checks", () => { consoleOutput.length = 0; }); - it("Initialization", () => { + it("Initialization", async () => { const apiGatewayV1Wrapper = new APIGatewayV1Wrapper(); - const actualResult = apiGatewayV1Wrapper.apiGateway.config[0].region; + const actualResult = await apiGatewayV1Wrapper.apiGateway.config.region(); expect(actualResult).to.equal(Globals.currentRegion); }); diff --git a/test/unit-tests/aws/api-gateway-v2-wrapper.test.ts b/test/unit-tests/aws/api-gateway-v2-wrapper.test.ts index 68adbdfd..f2f63bbc 100644 --- a/test/unit-tests/aws/api-gateway-v2-wrapper.test.ts +++ b/test/unit-tests/aws/api-gateway-v2-wrapper.test.ts @@ -17,9 +17,9 @@ describe("API Gateway V2 wrapper checks", () => { consoleOutput.length = 0; }); - it("Initialization", () => { + it("Initialization", async () => { const apiGatewayV2Wrapper = new APIGatewayV2Wrapper(); - const actualResult = apiGatewayV2Wrapper.apiGateway.config[0].region; + const actualResult = await apiGatewayV2Wrapper.apiGateway.config.region(); expect(actualResult).to.equal(Globals.currentRegion); }); diff --git a/test/unit-tests/aws/cloud-formation-wrapper.test.ts b/test/unit-tests/aws/cloud-formation-wrapper.test.ts index 7d542e3b..1c47b01f 100644 --- a/test/unit-tests/aws/cloud-formation-wrapper.test.ts +++ b/test/unit-tests/aws/cloud-formation-wrapper.test.ts @@ -18,9 +18,9 @@ describe("Cloud Formation wrapper checks", () => { Globals.serverless.service.provider.apiGateway.websocketApiId = null; }); - it("Initialization", () => { + it("Initialization", async () => { const cloudFormationWrapper = new CloudFormationWrapper(); - const actualResult = cloudFormationWrapper.cloudFormation.config[0].region; + const actualResult = await cloudFormationWrapper.cloudFormation.config.region(); expect(actualResult).to.equal(Globals.currentRegion); expect(cloudFormationWrapper.stackName).to.equal(Globals.serverless.service.provider.stackName); }); diff --git a/test/unit-tests/aws/route53-wrapper.test.ts b/test/unit-tests/aws/route53-wrapper.test.ts index 14fa0262..3ce6c88a 100644 --- a/test/unit-tests/aws/route53-wrapper.test.ts +++ b/test/unit-tests/aws/route53-wrapper.test.ts @@ -15,13 +15,13 @@ describe("Route53 wrapper checks", () => { consoleOutput.length = 0; }); - it("Initialization", () => { + it("Initialization", async() => { const route53Wrapper = new Route53Wrapper(); - const actualResult = route53Wrapper.route53.config[0].region; + const actualResult = await route53Wrapper.route53.config.region(); expect(actualResult).to.equal(Globals.currentRegion); }); - it("Initialization profile", () => { + it("Initialization profile", async () => { const credentials = { accessKeyId: "test_key_id", secretAccessKey: "test_access_key", @@ -30,10 +30,10 @@ describe("Route53 wrapper checks", () => { const regionName = "test-region"; const route53Wrapper = new Route53Wrapper(credentials, regionName); - const actualRegion = route53Wrapper.route53.config[0].region; + const actualRegion = await route53Wrapper.route53.config.region(); expect(actualRegion).to.equal(regionName); - const actualCredentials = route53Wrapper.route53.config[0].credentials; + const actualCredentials = await route53Wrapper.route53.config.credentials(); expect(actualCredentials).to.equal(credentials); }); diff --git a/test/unit-tests/aws/s3-wrapper.test.ts b/test/unit-tests/aws/s3-wrapper.test.ts index 78faeb88..03d445b6 100644 --- a/test/unit-tests/aws/s3-wrapper.test.ts +++ b/test/unit-tests/aws/s3-wrapper.test.ts @@ -10,9 +10,9 @@ describe("S3 wrapper checks", () => { consoleOutput.length = 0; }); - it("Initialization", () => { + it("Initialization", async () => { const s3Wrapper = new S3Wrapper(); - const actualResult = s3Wrapper.s3.config[0].region; + const actualResult = await s3Wrapper.s3.config.region(); expect(actualResult).to.equal(Globals.currentRegion); }); From 17e34c710ea0f7ceae47f6464fa5d9a80b73ec31 Mon Sep 17 00:00:00 2001 From: rddimon Date: Thu, 7 Dec 2023 16:59:20 +0200 Subject: [PATCH 10/10] Update Rote53 tests --- test/unit-tests/aws/route53-wrapper.test.ts | 48 ++++++++++----------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/test/unit-tests/aws/route53-wrapper.test.ts b/test/unit-tests/aws/route53-wrapper.test.ts index 3ce6c88a..8ffa7f08 100644 --- a/test/unit-tests/aws/route53-wrapper.test.ts +++ b/test/unit-tests/aws/route53-wrapper.test.ts @@ -15,7 +15,7 @@ describe("Route53 wrapper checks", () => { consoleOutput.length = 0; }); - it("Initialization", async() => { + it("Initialization", async () => { const route53Wrapper = new Route53Wrapper(); const actualResult = await route53Wrapper.route53.config.region(); expect(actualResult).to.equal(Globals.currentRegion); @@ -50,19 +50,19 @@ describe("Route53 wrapper checks", () => { }, { CallerReference: "2", Config: { PrivateZone: false }, - Id: testId, - Name: "dummy_test_domain" + Id: "dummy_id", + Name: "devapi.test_domain" }, { CallerReference: "3", Config: { PrivateZone: false }, - Id: testId, + Id: "not_valid", Name: "domain" } ] }); const dc = new DomainConfig(getDomainConfig({ - domainName: "test_domain" + domainName: "api.test_domain" })); let actualId = await new Route53Wrapper().getRoute53HostedZoneId(dc); @@ -77,24 +77,23 @@ describe("Route53 wrapper checks", () => { }); it("get route53 hosted zone id paginated", async () => { - const testId = "test_host_id"; const Route53Mock = mockClient(Route53Client); Route53Mock.on(ListHostedZonesCommand).resolvesOnce({ HostedZones: [ { CallerReference: "1", Config: { PrivateZone: false }, - Id: testId, + Id: "1", Name: "test_domain" }, { CallerReference: "2", Config: { PrivateZone: false }, - Id: testId, + Id: "2", Name: "dummy_test_domain" }, { CallerReference: "3", Config: { PrivateZone: false }, - Id: testId, + Id: "3", Name: "domain" } ], @@ -105,17 +104,17 @@ describe("Route53 wrapper checks", () => { { CallerReference: "4", Config: { PrivateZone: false }, - Id: testId, + Id: "4", Name: "test_domain2" }, { CallerReference: "5", Config: { PrivateZone: false }, - Id: testId, + Id: "5", Name: "dummy_test_domain2" }, { CallerReference: "6", Config: { PrivateZone: false }, - Id: testId, + Id: "6", Name: "domain2" } ], @@ -126,17 +125,17 @@ describe("Route53 wrapper checks", () => { { CallerReference: "7", Config: { PrivateZone: false }, - Id: testId, + Id: "7", Name: "test_domain3" }, { CallerReference: "8", Config: { PrivateZone: false }, - Id: testId, + Id: "8", Name: "dummy_test_domain3" }, { CallerReference: "9", Config: { PrivateZone: false }, - Id: testId, + Id: "9", Name: "domain3" } ] @@ -147,7 +146,7 @@ describe("Route53 wrapper checks", () => { })); let actualId = await new Route53Wrapper().getRoute53HostedZoneId(dc); - expect(actualId).to.equal(testId); + expect(actualId).to.equal("1"); const commandCalls = Route53Mock.commandCalls(ListHostedZonesCommand, {}); expect(commandCalls.length).to.equal(3); @@ -158,38 +157,39 @@ describe("Route53 wrapper checks", () => { }); it("get route53 hosted zone id public", async () => { - const testId = "test_host_id"; const Route53Mock = mockClient(Route53Client); Route53Mock.on(ListHostedZonesCommand).resolves({ HostedZones: [ { CallerReference: "", Config: { PrivateZone: false }, - Id: "no_valid", + Id: "1", Name: "api.test_domain" }, { CallerReference: "", Config: { PrivateZone: false }, - Id: testId, + Id: "2", Name: "devapi.test_domain" }, { CallerReference: "", Config: { PrivateZone: false }, - Id: "dummy_host_id", + Id: "3", Name: "test_domain" } ] }); const dc = new DomainConfig(getDomainConfig({ - domainName: "devapi.test_domain" + domainName: "api.test_domain" })); + const routeWrap = new Route53Wrapper(); + expect(await routeWrap.getRoute53HostedZoneId(dc, false)).to.equal("1"); - const actualId = await new Route53Wrapper().getRoute53HostedZoneId(dc, false); - expect(actualId).to.equal(testId); + dc.givenDomainName = "testapi.test_domain"; + expect(await routeWrap.getRoute53HostedZoneId(dc, false)).to.equal("3"); const commandCalls = Route53Mock.commandCalls(ListHostedZonesCommand, {}); - expect(commandCalls.length).to.equal(1); + expect(commandCalls.length).to.equal(2); }); it("get route53 hosted zone id private", async () => {