Repository: amplify-education/serverless-domain-manager
Pull request #478 (2022-01-25 13:08:51)

Preserve path mapping deletion
<!--- Information about referencing Github Issues: https://help.github.com/articles/basic-writing-and-formatting-syntax/#referencing-issues-and-pull-requests --->
Fixes #389 

**Description of Issue Fixed**
Refactoring of #389 

All integration tests passed:
![Screenshot 2022-01-26 at 12 24 36](https://user-images.githubusercontent.com/4427554/151147519-5ddbfd9d-7ad5-42d2-89c5-01ae0a3fb70a.png)

![Screenshot 2022-01-26 at 12 24 26](https://user-images.githubusercontent.com/4427554/151147511-88a008db-eef2-43e9-ba17-11b8b333482e.png)


<!--- Please remember to allow edits from maintainers: https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork --->



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5ef642d..e4fbf52 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,9 @@ 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).
 
+## [5.6.0] - 2021-01-26
+- Added config option to avoid automatically deleting an APIGW domain when other base path mappings exist. Thank you @straticJeff ([389](https://github.com/amplify-education/serverless-domain-manager/pull/389))  
+
 ## [5.5.0] - 2021-01-24
 - Added proxy support. Thank you @mscharp ([405](https://github.com/amplify-education/serverless-domain-manager/pull/405))
 - Fixed issue with disabling createRoute53Record. Thank you @albinlundmark ([476](https://github.com/amplify-education/serverless-domain-manager/pull/476))
diff --git a/README.md b/README.md
index 950a819..93aae2d 100644
--- a/README.md
+++ b/README.md
@@ -160,6 +160,8 @@ allowPathMatching | false | When updating an existing api mapping this will matc
 | route53Params:<br/>&nbsp; weight | `200` | Sets the weight for weighted routing. Ignored for `simple` and `latency` routing. |
 | route53Params:<br/>&nbsp; setIdentifier |  | A unique identifier for records in a set of Route 53 records with the same domain name. Only relevant for `latency` and `weighted` routing. Defaults to the regional endpoint if not provided. |
 | route53Params:<br/>&nbsp; healthCheckId |  | An optional id for a Route 53 health check. If it is failing, Route 53 will stop routing to it. Only relevant for `latency` and `weighted` routing. If it is not provided, no health check will be associated with the record. |
+| preserveExternalPathMappings | `false` | When `autoDomain` is set to true, and a deployment is removed, setting this to `true` checks for additional API Gateway base path mappings before automatically deleting the domain, and avoids doing so if they exist. |
+
 
 
 ## Running
diff --git a/package-lock.json b/package-lock.json
index c0b58c2..51577a7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,7 +14,7 @@
       },
       "devDependencies": {
         "@types/mocha": "^9.1.0",
-        "@types/node": "^17.0.10",
+        "@types/node": "^17.0.12",
         "aws-sdk-mock": "^5.6.0",
         "chai": "^4.3.4",
         "chai-spies": "^1.0.0",
@@ -24,7 +24,7 @@
         "randomstring": "^1.2.2",
         "request": "^2.88.2",
         "request-promise-native": "^1.0.9",
-        "serverless": "^2.72.1",
+        "serverless": "^2.72.2",
         "serverless-plugin-split-stacks": "^1.11.3",
         "shelljs": "^0.8.5",
         "ts-node": "^10.4.0",
@@ -1373,9 +1373,9 @@
       }
     },
     "node_modules/@sindresorhus/is": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.3.0.tgz",
-      "integrity": "sha512-wwOvh0eO3PiTEivGJWiZ+b946SlMSb4pe+y+Ur/4S87cwo09pYi+FWHHnbrM3W9W7cBYKDqQXcrFYjYUCOJUEQ==",
+      "version": "4.4.0",
+      "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.4.0.tgz",
+      "integrity": "sha512-QppPM/8l3Mawvh4rn9CNEYIU9bxpXUCRMaX9yUpvBk1nMKusLKpfXGDEKExKaPhLzcn3lzil7pR6rnJ11HgeRQ==",
       "dev": true,
       "engines": {
         "node": ">=10"
@@ -1525,9 +1525,9 @@
       "dev": true
     },
     "node_modules/@types/node": {
-      "version": "17.0.10",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.10.tgz",
-      "integrity": "sha512-S/3xB4KzyFxYGCppyDt68yzBU9ysL88lSdIah4D6cptdcltc4NCPCAMc0+PCpg/lLIyC7IPvj2Z52OJWeIUkog==",
+      "version": "17.0.12",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.12.tgz",
+      "integrity": "sha512-4YpbAsnJXWYK/fpTVFlMIcUIho2AYCi4wg5aNPrG1ng7fn/1/RZfCIpRCiBX+12RVa34RluilnvCqD+g3KiSiA==",
       "dev": true
     },
     "node_modules/@types/request": {
@@ -3019,13 +3019,13 @@
       "dev": true
     },
     "node_modules/crc-32": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz",
-      "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==",
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.1.tgz",
+      "integrity": "sha512-Dn/xm/1vFFgs3nfrpEVScHoIslO9NZRITWGz/1E/St6u4xw99vfZzVkW0OSnzx2h9egej9xwMCEut6sqwokM/w==",
       "dev": true,
       "dependencies": {
         "exit-on-epipe": "~1.0.1",
-        "printj": "~1.1.0"
+        "printj": "~1.3.1"
       },
       "bin": {
         "crc32": "bin/crc32.njs"
@@ -3591,9 +3591,9 @@
       }
     },
     "node_modules/electron-to-chromium": {
-      "version": "1.4.51",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.51.tgz",
-      "integrity": "sha512-JNEmcYl3mk1tGQmy0EvL5eik/CKSBuzAyGP0QFdG6LIgxQe3II0BL1m2zKc2MZMf3uGqHWE1TFddJML0RpjSHQ==",
+      "version": "1.4.52",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.52.tgz",
+      "integrity": "sha512-JGkh8HEh5PnVrhU4HbpyyO0O791dVY6k7AdqfDeqbcRMeoGxtNHWT77deR2nhvbLe4dKpxjlDEvdEwrvRLGu2Q==",
       "dev": true
     },
     "node_modules/emoji-regex": {
@@ -5971,15 +5971,16 @@
       "dev": true
     },
     "node_modules/ncjsm": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/ncjsm/-/ncjsm-4.2.0.tgz",
-      "integrity": "sha512-L2Qij4PTy7Bs4TB24zs7FLIAYJTaR5JPvSig5hIcO059LnMCNgy6MfHHNyg8s/aekPKrTqKX90gBGt3NNGvhdw==",
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ncjsm/-/ncjsm-4.3.0.tgz",
+      "integrity": "sha512-oah6YGwb4Ern2alojiMFcjPhE4wvQBw1Ur/kUr2P0ovKdzaF5pCIsGjs0f2y+iZeej0/5Y6OOhQ8j30cTDMEGw==",
       "dev": true,
       "dependencies": {
         "builtin-modules": "^3.2.0",
         "deferred": "^0.7.11",
         "es5-ext": "^0.10.53",
         "es6-set": "^0.1.5",
+        "ext": "^1.6.0",
         "find-requires": "^1.0.0",
         "fs2": "^0.3.9",
         "type": "^2.5.0"
@@ -7049,9 +7050,9 @@
       "dev": true
     },
     "node_modules/printj": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz",
-      "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==",
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/printj/-/printj-1.3.1.tgz",
+      "integrity": "sha512-GA3TdL8szPK4AQ2YnOe/b+Y1jUFwmmGMMK/qbY7VcE3Z7FU8JstbKiKRzO6CIiAKPhTO8m01NoQ0V5f3jc4OGg==",
       "dev": true,
       "bin": {
         "printj": "bin/printj.njs"
@@ -7682,9 +7683,9 @@
       }
     },
     "node_modules/serverless": {
-      "version": "2.72.1",
-      "resolved": "https://registry.npmjs.org/serverless/-/serverless-2.72.1.tgz",
-      "integrity": "sha512-SxmxyBgWQvcKvEXdP0fR3Y+nljOQ+nWCPDZXnhic//w+k0kNQ/bHcd3S1VZQqU3m7nZZwMsdC5lxB26EpUFELA==",
+      "version": "2.72.2",
+      "resolved": "https://registry.npmjs.org/serverless/-/serverless-2.72.2.tgz",
+      "integrity": "sha512-xlxaWyq4b58DIX3prwIikBmXj0z4R+YI9zcDPYgQc78mKJ0qTgCK7BMoy6dlVuHXnfhBkhT3uT/EhFvjKIdk6g==",
       "dev": true,
       "hasInstallScript": true,
       "dependencies": {
@@ -7696,7 +7697,7 @@
         "ajv": "^6.12.6",
         "ajv-keywords": "^3.5.2",
         "archiver": "^5.3.0",
-        "aws-sdk": "^2.1061.0",
+        "aws-sdk": "^2.1062.0",
         "bluebird": "^3.7.2",
         "boxen": "^5.1.2",
         "cachedir": "^2.3.0",
@@ -7726,7 +7727,7 @@
         "lodash": "^4.17.21",
         "memoizee": "^0.4.15",
         "micromatch": "^4.0.4",
-        "ncjsm": "^4.2.0",
+        "ncjsm": "^4.3.0",
         "node-fetch": "^2.6.7",
         "open": "^7.4.2",
         "path2": "^0.1.0",
@@ -11007,9 +11008,9 @@
       }
     },
     "@sindresorhus/is": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.3.0.tgz",
-      "integrity": "sha512-wwOvh0eO3PiTEivGJWiZ+b946SlMSb4pe+y+Ur/4S87cwo09pYi+FWHHnbrM3W9W7cBYKDqQXcrFYjYUCOJUEQ==",
+      "version": "4.4.0",
+      "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.4.0.tgz",
+      "integrity": "sha512-QppPM/8l3Mawvh4rn9CNEYIU9bxpXUCRMaX9yUpvBk1nMKusLKpfXGDEKExKaPhLzcn3lzil7pR6rnJ11HgeRQ==",
       "dev": true
     },
     "@sinonjs/commons": {
@@ -11150,9 +11151,9 @@
       "dev": true
     },
     "@types/node": {
-      "version": "17.0.10",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.10.tgz",
-      "integrity": "sha512-S/3xB4KzyFxYGCppyDt68yzBU9ysL88lSdIah4D6cptdcltc4NCPCAMc0+PCpg/lLIyC7IPvj2Z52OJWeIUkog==",
+      "version": "17.0.12",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.12.tgz",
+      "integrity": "sha512-4YpbAsnJXWYK/fpTVFlMIcUIho2AYCi4wg5aNPrG1ng7fn/1/RZfCIpRCiBX+12RVa34RluilnvCqD+g3KiSiA==",
       "dev": true
     },
     "@types/request": {
@@ -12360,13 +12361,13 @@
       "dev": true
     },
     "crc-32": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz",
-      "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==",
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.1.tgz",
+      "integrity": "sha512-Dn/xm/1vFFgs3nfrpEVScHoIslO9NZRITWGz/1E/St6u4xw99vfZzVkW0OSnzx2h9egej9xwMCEut6sqwokM/w==",
       "dev": true,
       "requires": {
         "exit-on-epipe": "~1.0.1",
-        "printj": "~1.1.0"
+        "printj": "~1.3.1"
       }
     },
     "crc32-stream": {
@@ -12828,9 +12829,9 @@
       }
     },
     "electron-to-chromium": {
-      "version": "1.4.51",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.51.tgz",
-      "integrity": "sha512-JNEmcYl3mk1tGQmy0EvL5eik/CKSBuzAyGP0QFdG6LIgxQe3II0BL1m2zKc2MZMf3uGqHWE1TFddJML0RpjSHQ==",
+      "version": "1.4.52",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.52.tgz",
+      "integrity": "sha512-JGkh8HEh5PnVrhU4HbpyyO0O791dVY6k7AdqfDeqbcRMeoGxtNHWT77deR2nhvbLe4dKpxjlDEvdEwrvRLGu2Q==",
       "dev": true
     },
     "emoji-regex": {
@@ -14728,15 +14729,16 @@
       "dev": true
     },
     "ncjsm": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/ncjsm/-/ncjsm-4.2.0.tgz",
-      "integrity": "sha512-L2Qij4PTy7Bs4TB24zs7FLIAYJTaR5JPvSig5hIcO059LnMCNgy6MfHHNyg8s/aekPKrTqKX90gBGt3NNGvhdw==",
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ncjsm/-/ncjsm-4.3.0.tgz",
+      "integrity": "sha512-oah6YGwb4Ern2alojiMFcjPhE4wvQBw1Ur/kUr2P0ovKdzaF5pCIsGjs0f2y+iZeej0/5Y6OOhQ8j30cTDMEGw==",
       "dev": true,
       "requires": {
         "builtin-modules": "^3.2.0",
         "deferred": "^0.7.11",
         "es5-ext": "^0.10.53",
         "es6-set": "^0.1.5",
+        "ext": "^1.6.0",
         "find-requires": "^1.0.0",
         "fs2": "^0.3.9",
         "type": "^2.5.0"
@@ -15575,9 +15577,9 @@
       }
     },
     "printj": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz",
-      "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==",
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/printj/-/printj-1.3.1.tgz",
+      "integrity": "sha512-GA3TdL8szPK4AQ2YnOe/b+Y1jUFwmmGMMK/qbY7VcE3Z7FU8JstbKiKRzO6CIiAKPhTO8m01NoQ0V5f3jc4OGg==",
       "dev": true
     },
     "process-nextick-args": {
@@ -16040,9 +16042,9 @@
       }
     },
     "serverless": {
-      "version": "2.72.1",
-      "resolved": "https://registry.npmjs.org/serverless/-/serverless-2.72.1.tgz",
-      "integrity": "sha512-SxmxyBgWQvcKvEXdP0fR3Y+nljOQ+nWCPDZXnhic//w+k0kNQ/bHcd3S1VZQqU3m7nZZwMsdC5lxB26EpUFELA==",
+      "version": "2.72.2",
+      "resolved": "https://registry.npmjs.org/serverless/-/serverless-2.72.2.tgz",
+      "integrity": "sha512-xlxaWyq4b58DIX3prwIikBmXj0z4R+YI9zcDPYgQc78mKJ0qTgCK7BMoy6dlVuHXnfhBkhT3uT/EhFvjKIdk6g==",
       "dev": true,
       "requires": {
         "@serverless/cli": "^1.6.0",
@@ -16053,7 +16055,7 @@
         "ajv": "^6.12.6",
         "ajv-keywords": "^3.5.2",
         "archiver": "^5.3.0",
-        "aws-sdk": "^2.1061.0",
+        "aws-sdk": "^2.1062.0",
         "bluebird": "^3.7.2",
         "boxen": "^5.1.2",
         "cachedir": "^2.3.0",
@@ -16083,7 +16085,7 @@
         "lodash": "^4.17.21",
         "memoizee": "^0.4.15",
         "micromatch": "^4.0.4",
-        "ncjsm": "^4.2.0",
+        "ncjsm": "^4.3.0",
         "node-fetch": "^2.6.7",
         "open": "^7.4.2",
         "path2": "^0.1.0",
diff --git a/package.json b/package.json
index 0fd1679..2cb299d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "serverless-domain-manager",
-  "version": "5.5.0",
+  "version": "5.6.0",
   "engines": {
     "node": ">=12"
   },
@@ -47,7 +47,7 @@
   },
   "devDependencies": {
     "@types/mocha": "^9.1.0",
-    "@types/node": "^17.0.10",
+    "@types/node": "^17.0.12",
     "aws-sdk-mock": "^5.6.0",
     "chai": "^4.3.4",
     "chai-spies": "^1.0.0",
@@ -57,7 +57,7 @@
     "randomstring": "^1.2.2",
     "request": "^2.88.2",
     "request-promise-native": "^1.0.9",
-    "serverless": "^2.72.1",
+    "serverless": "^2.72.2",
     "serverless-plugin-split-stacks": "^1.11.3",
     "shelljs": "^0.8.5",
     "ts-node": "^10.4.0",
diff --git a/src/aws/api-gateway-wrapper.ts b/src/aws/api-gateway-wrapper.ts
index a0ed66f..a9b9cbe 100644
--- a/src/aws/api-gateway-wrapper.ts
+++ b/src/aws/api-gateway-wrapper.ts
@@ -148,12 +148,9 @@ class APIGatewayWrapper {
         }
     }
 
-    /**
-     * Get basepath mapping
-     */
-    public async getBasePathMapping(domain: DomainConfig): Promise<ApiGatewayV2.GetApiMappingResponse> {
+    public async getApiMappings(domain: DomainConfig): Promise<ApiGatewayV2.GetApiMappingResponse[]> {
         try {
-            const mappings = await getAWSPagedResults(
+            return await getAWSPagedResults(
                 this.apiGatewayV2,
                 "getApiMappings",
                 "Items",
@@ -161,12 +158,6 @@ class APIGatewayWrapper {
                 "NextToken",
                 {DomainName: domain.givenDomainName},
             );
-            for (const mapping of mappings) {
-                if (mapping.ApiId === domain.apiId
-                    || (mapping.ApiMappingKey === domain.basePath && domain.allowPathMatching)) {
-                    return mapping;
-                }
-            }
         } catch (err) {
             Globals.logError(err, domain.givenDomainName);
             throw new Error(`Unable to get API Mappings for ${domain.givenDomainName}`);
diff --git a/src/domain-config.ts b/src/domain-config.ts
index 071daf1..4187af1 100644
--- a/src/domain-config.ts
+++ b/src/domain-config.ts
@@ -28,6 +28,7 @@ class DomainConfig {
     public autoDomain: boolean | undefined;
     public autoDomainWaitFor: string | undefined;
     public route53Params: Route53Params;
+    public preserveExternalPathMappings: boolean | undefined;
 
     public domainInfo: DomainInfo | undefined;
     public apiId: string | undefined;
@@ -50,6 +51,7 @@ class DomainConfig {
         this.allowPathMatching = config.allowPathMatching;
         this.autoDomain = config.autoDomain;
         this.autoDomainWaitFor = config.autoDomainWaitFor;
+        this.preserveExternalPathMappings = this.evaluateBoolean(config.preserveExternalPathMappings, false);
 
         let basePath = config.basePath;
         if (basePath == null || basePath.trim() === "") {
diff --git a/src/index.ts b/src/index.ts
index 7083105..2ef904b 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -301,7 +301,13 @@ class ServerlessCustomDomain {
         await Promise.all(this.domains.map(async (domain) => {
             try {
                 domain.apiId = await this.getApiId(domain);
-                domain.apiMapping = await this.apiGatewayWrapper.getBasePathMapping(domain);
+                const mappings = await this.apiGatewayWrapper.getApiMappings(domain);
+                const filteredMappings = mappings.filter((mapping) => {
+                    return mapping.ApiId === domain.apiId || (
+                        mapping.ApiMappingKey === domain.basePath && domain.allowPathMatching
+                    )
+                });
+                domain.apiMapping = filteredMappings ? filteredMappings[0] : null;
                 domain.domainInfo = await this.apiGatewayWrapper.getCustomDomainInfo(domain);
 
                 if (!domain.apiMapping) {
@@ -328,6 +334,7 @@ class ServerlessCustomDomain {
      */
     public async removeBasePathMappings(): Promise<void> {
         await Promise.all(this.domains.map(async (domain) => {
+            let externalBasePathExists = false;
             try {
                 domain.apiId = await this.getApiId(domain);
 
@@ -336,7 +343,17 @@ class ServerlessCustomDomain {
                     Globals.logInfo(`Unable to find corresponding API for ${domain.givenDomainName},
                         API Mappings may need to be manually removed.`);
                 } else {
-                    domain.apiMapping = await this.apiGatewayWrapper.getBasePathMapping(domain);
+                    const mappings = await this.apiGatewayWrapper.getApiMappings(domain);
+                    const filteredMappings = mappings.filter((mapping) => {
+                        return mapping.ApiId === domain.apiId || (
+                            mapping.ApiMappingKey === domain.basePath && domain.allowPathMatching
+                        )
+                    });
+                    if (domain.preserveExternalPathMappings) {
+                        externalBasePathExists = mappings.length > filteredMappings.length;
+                    }
+
+                    domain.apiMapping = filteredMappings ? filteredMappings[0] : null;
                     await this.apiGatewayWrapper.deleteBasePathMapping(domain);
                 }
             } catch (err) {
@@ -351,8 +368,7 @@ class ServerlessCustomDomain {
                 }
             }
 
-            const autoDomain = domain.autoDomain;
-            if (autoDomain === true) {
+            if (domain.autoDomain === true && !externalBasePathExists) {
                 Globals.logInfo("Deleting domain name after removing base path mapping.");
                 await this.deleteDomain(domain);
             }
diff --git a/src/types.ts b/src/types.ts
index d93fafd..8b5457d 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -19,6 +19,7 @@ export interface CustomDomain { // tslint:disable-line
     autoDomainWaitFor: string | undefined;
     allowPathMatching: boolean | undefined;
     route53Params: Route53Params | undefined
+    preserveExternalPathMappings: boolean | undefined;
 }
 
 export interface ServerlessInstance { // tslint:disable-line


Test patch:
diff --git a/test/integration-tests/basic.test.ts b/test/integration-tests/basic.test.ts
index 7fe8fdf..c749cad 100644
--- a/test/integration-tests/basic.test.ts
+++ b/test/integration-tests/basic.test.ts
@@ -94,7 +94,10 @@ describe("Integration Tests", function () {
             expect(basePath).to.equal("hello-world");
         } finally {
             // should destroy the last created config folder ( import config )
-            await utilities.destroyResources(`${testExportName} & ${testImportName}`);
+            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);
         }
     });
 
diff --git a/test/unit-tests/index.test.ts b/test/unit-tests/index.test.ts
index 25896e6..bc4df78 100644
--- a/test/unit-tests/index.test.ts
+++ b/test/unit-tests/index.test.ts
@@ -55,6 +55,7 @@ const constructPlugin = (customDomainOptions, multiple: boolean = false) => {
         hostedZonePrivate: customDomainOptions.hostedZonePrivate,
         route53Profile: customDomainOptions.route53Profile,
         route53Region: customDomainOptions.route53Region,
+        preserveExternalPathMappings: customDomainOptions.preserveExternalPathMappings,
         securityPolicy: customDomainOptions.securityPolicy,
         stage: customDomainOptions.stage,
         route53Params: customDomainOptions.route53Params
@@ -731,35 +732,6 @@ describe("Custom Domain Plugin", () => {
     });
 
     describe("Gets existing basepath mappings correctly", () => {
-        it("Returns undefined if no basepaths map to current api", async () => {
-            AWS.mock("ApiGatewayV2", "getApiMappings", (params, callback) => {
-                // @ts-ignore
-                callback(null, {
-                    Items: [
-                        {
-                            ApiId: "someother_api_id",
-                            ApiMappingId: "test_rest_api_id_one",
-                            MappingKey: "test",
-                            Stage: "test",
-                        },
-                    ],
-                });
-            });
-
-            const plugin = constructPlugin({
-                domainName: "test_domain",
-            });
-
-            const dc: DomainConfig = new DomainConfig(plugin.serverless.service.custom.customDomain);
-            dc.apiMapping = {ApiMappingId: "api_id"};
-
-            plugin.initializeVariables();
-            plugin.initAWSResources();
-
-            const result = await plugin.apiGatewayWrapper.getBasePathMapping(dc);
-            expect(result).to.equal(undefined);
-        });
-
         it("Returns current api mapping", async () => {
             AWS.mock("ApiGatewayV2", "getApiMappings", (params, callback) => {
                 callback(null, {
@@ -781,8 +753,8 @@ describe("Custom Domain Plugin", () => {
             plugin.initializeVariables();
             plugin.initAWSResources();
 
-            const result = await plugin.apiGatewayWrapper.getBasePathMapping(dc);
-            expect(result).to.eql({
+            const result = await plugin.apiGatewayWrapper.getApiMappings(dc);
+            expect(result[0]).to.eql({
                 ApiId: "test_rest_api_id",
                 ApiMappingId: "fake_id",
                 ApiMappingKey: "api",
@@ -1938,7 +1910,118 @@ describe("Custom Domain Plugin", () => {
             expect(spy).to.have.not.been.called();
         });
 
+        it("removeBasePathMapping should not call deleteDomain when preserveExternalPathMappings is true and " +
+          "external mappings exist", async () => {
+            AWS.mock("CloudFormation", "describeStackResource", (params, callback) => {
+                // @ts-ignore
+                callback(null, {
+                    StackResourceDetail:
+                      {
+                          LogicalResourceId: "ApiGatewayRestApi",
+                          PhysicalResourceId: "test_rest_api_id",
+                      },
+                });
+            });
+            AWS.mock("ApiGatewayV2", "getApiMappings", (params, callback) => {
+                // @ts-ignore
+                callback(null, {
+                    Items: [
+                        {ApiId: "test_rest_api_id", MappingKey: "test", ApiMappingId: "test_mapping_id", Stage: "test"},
+                        {
+                            ApiId: "test_rest_api_id_2",
+                            ApiMappingId: "test_mapping_id",
+                            MappingKey: "test",
+                            Stage: "test",
+                        },
+                    ],
+                });
+            });
+            AWS.mock("ApiGatewayV2", "deleteApiMapping", (params, callback) => {
+                callback(null, params);
+            });
+            AWS.mock("ApiGatewayV2", "deleteDomainName", (params, callback) => {
+                callback(null, params);
+            });
+            AWS.mock("ApiGatewayV2", "getDomainName", (params, callback) => {
+                callback(null, params);
+            });
+
+            const plugin = constructPlugin({
+                autoDomain: true,
+                basePath: "test_basepath",
+                createRoute53Record: false,
+                domainName: "test_domain",
+                preserveExternalPathMappings: true,
+                restApiId: "test_rest_api_id",
+            });
+            plugin.initializeVariables();
+            plugin.initAWSResources();
+
+            plugin.domains[0].apiMapping = {ApiMappingId: "test_mapping_id"};
+
+            const spy = chai.spy.on(plugin.apiGatewayWrapper.apiGatewayV2, "deleteDomainName");
+
+            await plugin.removeBasePathMappings();
+
+            expect(plugin.serverless.service.custom.customDomain.autoDomain).to.equal(true);
+            expect(plugin.serverless.service.custom.customDomain.preserveExternalPathMappings).to.equal(true);
+            expect(spy).to.have.not.been.called();
+        });
+
+        it("removeBasePathMapping should call deleteDomain when preserveExternalPathMappings is true and " +
+          "external mappings don't exist", async () => {
+            AWS.mock("CloudFormation", "describeStackResource", (params, callback) => {
+                // @ts-ignore
+                callback(null, {
+                    StackResourceDetail:
+                      {
+                          LogicalResourceId: "ApiGatewayRestApi",
+                          PhysicalResourceId: "test_rest_api_id",
+                      },
+                });
+            });
+            AWS.mock("ApiGatewayV2", "getApiMappings", (params, callback) => {
+                // @ts-ignore
+                callback(null, {
+                    Items: [
+                        {ApiId: "test_rest_api_id", MappingKey: "test", ApiMappingId: "test_mapping_id", Stage: "test"},
+                    ],
+                });
+            });
+            AWS.mock("ApiGatewayV2", "deleteApiMapping", (params, callback) => {
+                callback(null, params);
+            });
+            AWS.mock("ApiGatewayV2", "deleteDomainName", (params, callback) => {
+                callback(null, params);
+            });
+            AWS.mock("ApiGatewayV2", "getDomainName", (params, callback) => {
+                callback(null, params);
+            });
+
+            const plugin = constructPlugin({
+                autoDomain: true,
+                basePath: "test_basepath",
+                createRoute53Record: false,
+                domainName: "test_domain",
+                preserveExternalPathMappings: true,
+                restApiId: "test_rest_api_id",
+            });
+            plugin.initializeVariables();
+            plugin.initAWSResources();
+
+            plugin.domains[0].apiMapping = {ApiMappingId: "test_mapping_id"};
+
+            const spy = chai.spy.on(plugin.apiGatewayWrapper.apiGatewayV2, "deleteDomainName");
+
+            await plugin.removeBasePathMappings();
+
+            expect(plugin.serverless.service.custom.customDomain.autoDomain).to.equal(true);
+            expect(plugin.serverless.service.custom.customDomain.preserveExternalPathMappings).to.equal(true);
+            expect(spy).to.have.been.called();
+        });
+
         afterEach(() => {
+            AWS.restore();
             consoleOutput = [];
         });
     });