Repository: elastic/synthetics
Pull request #649 (2022-11-08 06:24:17)

feat: use new kibana api for pushing monitors
+ Use the new Kibana API - https://github.com/elastic/kibana/issues/142418
+ This PR adds the support for the new Bulk API endpoints while maintaining the support for the previous API endpoints that is part of 8.5.x stack. 
+ Support for older and new versions is determined by hitting at the Kibana API endpoint and pushing the monitors. 
+ Monitors are created and deleted in chunks of 100, Might need further tuning later down the line as pushing 100's of monitor could be error prone due to the size of the bundled journey and request payload might be affected when there is a proxy layer in front of Kibana.  
+ The CLI progress has changed to keep it similar for both the legacy and the bulk API endpoints. 

<img width="612" alt="Screen Shot 2022-11-30 at 5 24 25 PM" src="https://user-images.githubusercontent.com/3902525/204943407-82238498-09dd-48f0-bfab-c82749894114.png">


## Testing
+ Build the project - npm run build
+ Create a brand new synthetics project using `npm init <dir>` command. 
+ Link the local synthetics package using `npm link`. 
+ Push a synthetics project - SYNTHETICS_API_KEY=<> npm run push
+ Try on both older versions and also on latest snapshots 8.6.0. 

apmmachine: 
## :broken_heart: Tests Failed


<!-- BUILD BADGES-->
> _the below badges are clickable and redirect to their specific view in the CI or DOCS_
[![Pipeline View](https://img.shields.io/badge/pipeline-pipeline%20-green)](https://apm-ci.elastic.co/blue/organizations/jenkins/apm-agent-rum%2Felastic-synthetics%2FPR-649/detail/PR-649/1//pipeline) [![Test View](https://img.shields.io/badge/test-test-green)](https://apm-ci.elastic.co/blue/organizations/jenkins/apm-agent-rum%2Felastic-synthetics%2FPR-649/detail/PR-649/1//tests) [![Changes](https://img.shields.io/badge/changes-changes-green)](https://apm-ci.elastic.co/blue/organizations/jenkins/apm-agent-rum%2Felastic-synthetics%2FPR-649/detail/PR-649/1//changes) [![Artifacts](https://img.shields.io/badge/artifacts-artifacts-yellow)](https://apm-ci.elastic.co/blue/organizations/jenkins/apm-agent-rum%2Felastic-synthetics%2FPR-649/detail/PR-649/1//artifacts) [![preview](https://img.shields.io/badge/docs-preview-yellowgreen)](http://synthetics_649.docs-preview.app.elstc.co/diff)  [![preview](https://img.shields.io/badge/elastic-observability-blue)](https://ci-stats.elastic.co/app/apm/services/apm-ci/transactions/view?rangeFrom=2022-11-08T06:14:34.329Z&rangeTo=2022-11-08T06:34:34.329Z&transactionName=BUILD+apm-agent-rum%2Felastic-synthetics%2FPR-%7Bnumber%7D&transactionType=job&latencyAggregationType=avg&traceId=8dfdd6a62bdf6435c404c7cbda604c17&transactionId=67afb4c4912fbe42)

<!-- BUILD SUMMARY-->
<details><summary>Expand to view the summary</summary>
<p>

#### Build stats




* Start Time: 2022-11-08T06:24:34.329+0000


* Duration: 8 min 2 sec




#### Test stats :test_tube:

| Test         | Results                         |
| ------------ | :-----------------------------: |
| Failed       | 8  |
| Passed       | 191  |
| Skipped      | 2 |
| Total        | 201   |


</p>
</details>

<!-- TEST RESULTS IF ANY-->

  ### Test errors [![8](https://img.shields.io/badge/8%20-red)](https://apm-ci.elastic.co/blue/organizations/jenkins/apm-agent-rum%2Felastic-synthetics%2FPR-649/detail/PR-649/1//tests)
<details><summary>Expand to view the tests failures</summary><p>
    
    
##### `Test / Monitors build browser monitor schema – Monitors build browser monitor schema`


<ul>
<details><summary>Expand to view the error details</summary><p>

```
 null 
 ```
</p></details>
<details><summary>Expand to view the stacktrace</summary><p>

```
 Error: expect(received).toEqual(expected) // deep equality

- Expected  - 0
+ Received  + 1

@@ -2,10 +2,11 @@
    "content": Any<String>,
    "enabled": true,
    "filter": Object {
      "match": "test",
    },
+   "hash": "I+bYcE74C35IaKpHeN04wBfinO3qEiqlcaksKFAmkBg=",
    "id": "test-monitor",
    "locations": Array [
      "europe-west2-a",
      "australia-southeast1-a",
    ],
    at Object.<anonymous> (/var/lib/jenkins/workspace/nt-rum_elastic-synthetics_PR-649/src/github.com/elastic/synthetics/__tests__/push/monitor.test.ts:68:23) 
 ```
</p></details>
</ul>
    
##### `Test / Monitors build lightweight monitor schema – Monitors build lightweight monitor schema`


<ul>
<details><summary>Expand to view the error details</summary><p>

```
 null 
 ```
</p></details>
<details><summary>Expand to view the stacktrace</summary><p>

```
 Error: expect(received).toEqual(expected) // deep equality

- Expected  - 0
+ Received  + 1

@@ -1,7 +1,8 @@
  Object {
    "enabled": true,
+   "hash": "fS9hbiqcopwk3gMeDrqMgyp0mEpeyFWy6F/YFSnfWPE=",
    "id": "test-monitor",
    "locations": Array [
      "europe-west2-a",
      "australia-southeast1-a",
    ],
    at Object.<anonymous> (/var/lib/jenkins/workspace/nt-rum_elastic-synthetics_PR-649/src/github.com/elastic/synthetics/__tests__/push/monitor.test.ts:55:23) 
 ```
</p></details>
</ul>
    
##### `Test / Push API handle streamed response – Push API handle streamed response`


<ul>
<details><summary>Expand to view the error details</summary><p>

```
 null 
 ```
</p></details>
<details><summary>Expand to view the stacktrace</summary><p>

```
 Error: expect(received).toMatchSnapshot()

Snapshot name: `Push API handle streamed response 1`

- Snapshot  - 6
+ Received  + 1

- > preparing all monitors
- > creating all monitors
- > journey 1
- > journey 2
- > deleting all stale monitors
- ✓ Pushed: http://localhost:54455/stream/app/uptime/manage-monitors/all
+ ✖ Please check your kibana url and try again - 404:Not found
  ↵
    at Object.<anonymous> (/var/lib/jenkins/workspace/nt-rum_elastic-synthetics_PR-649/src/github.com/elastic/synthetics/__tests__/push/index.test.ts:305:22)
    at processTicksAndRejections (node:internal/process/task_queues:95:5) 
 ```
</p></details>
</ul>
    
##### `Test / Push API handle sync response – Push API handle sync response`


<ul>
<details><summary>Expand to view the error details</summary><p>

```
 null 
 ```
</p></details>
<details><summary>Expand to view the stacktrace</summary><p>

```
 Error: expect(received).toMatchSnapshot()

Snapshot name: `Push API handle sync response 1`

- Snapshot  - 4
+ Received  + 1

- > preparing all monitors
- > creating all monitors
- > deleting all stale monitors
- ✓ Pushed: http://localhost:54455/sync/app/uptime/manage-monitors/all
+ ✖ Please check your kibana url and try again - 404:Not found
  ↵
    at Object.<anonymous> (/var/lib/jenkins/workspace/nt-rum_elastic-synthetics_PR-649/src/github.com/elastic/synthetics/__tests__/push/index.test.ts:297:22)
    at processTicksAndRejections (node:internal/process/task_queues:95:5) 
 ```
</p></details>
</ul>
    
##### `Test / Push abort when delete is skipped – Push abort when delete is skipped`


<ul>
<details><summary>Expand to view the error details</summary><p>

```
 null 
 ```
</p></details>
<details><summary>Expand to view the stacktrace</summary><p>

```
 Error: expect(received).toContain(expected) // indexOf

Expected substring: "Push command Aborted"
Received string:    "TypeError: Cannot read properties of undefined (reading 'replace')
    at removeTrailingSlash (/var/lib/jenkins/workspace/nt-rum_elastic-synthetics_PR-649/src/github.com/elastic/synthetics/src/helpers.ts:367:14)
    at generateURL (/var/lib/jenkins/workspace/nt-rum_elastic-synthetics_PR-649/src/github.com/elastic/synthetics/src/push/curd.ts:50:28)
    at getAllMonitors (/var/lib/jenkins/workspace/nt-rum_elastic-synthetics_PR-649/src/github.com/elastic/synthetics/src/push/curd.ts:63:10)
    at push (/var/lib/jenkins/workspace/nt-rum_elastic-synthetics_PR-649/src/github.com/elastic/synthetics/src/push/index.ts:67:40)
    at Command.<anonymous> (/var/lib/jenkins/workspace/nt-rum_elastic-synthetics_PR-649/src/github.com/elastic/synthetics/src/cli.ts:208:17)
"
    at Object.<anonymous> (/var/lib/jenkins/workspace/nt-rum_elastic-synthetics_PR-649/src/github.com/elastic/synthetics/__tests__/push/index.test.ts:225:20)
    at processTicksAndRejections (node:internal/process/task_queues:95:5) 
 ```
</p></details>
</ul>
    
##### `Test / Push delete entire project with --yes flag – Push delete entire project with --yes flag`


<ul>
<details><summary>Expand to view the error details</summary><p>

```
 null 
 ```
</p></details>
<details><summary>Expand to view the stacktrace</summary><p>

```
 Error: expect(received).toContain(expected) // indexOf

Expected substring: "deleting all stale monitors"
Received string:    "Error: connect ECONNREFUSED 127.0.0.1:8080
    at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1300:16) {
  errno: -111,
  code: 'ECONNREFUSED',
  syscall: 'connect',
  address: '127.0.0.1',
  port: 8080
}
"
    at Object.<anonymous> (/var/lib/jenkins/workspace/nt-rum_elastic-synthetics_PR-649/src/github.com/elastic/synthetics/__tests__/push/index.test.ts:234:20)
    at processTicksAndRejections (node:internal/process/task_queues:95:5) 
 ```
</p></details>
</ul>
    
##### `Test / Push delete entire project with overrides – Push delete entire project with overrides`


<ul>
<details><summary>Expand to view the error details</summary><p>

```
 null 
 ```
</p></details>
<details><summary>Expand to view the stacktrace</summary><p>

```
 Error: expect(received).toContain(expected) // indexOf

Expected substring: "deleting all stale monitors"
Received string:    "Error: connect ECONNREFUSED 127.0.0.1:8080
    at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1300:16) {
  errno: -111,
  code: 'ECONNREFUSED',
  syscall: 'connect',
  address: '127.0.0.1',
  port: 8080
}
"
    at Object.<anonymous> (/var/lib/jenkins/workspace/nt-rum_elastic-synthetics_PR-649/src/github.com/elastic/synthetics/__tests__/push/index.test.ts:245:20)
    at processTicksAndRejections (node:internal/process/task_queues:95:5) 
 ```
</p></details>
</ul>
    
##### `Test / Push push with different id when overriden – Push push with different id when overriden`


<ul>
<details><summary>Expand to view the error details</summary><p>

```
 null 
 ```
</p></details>
<details><summary>Expand to view the stacktrace</summary><p>

```
 Error: expect(received).toContain(expected) // indexOf

Expected substring: "preparing all monitors"
Received string:    "
Error: connect ECONNREFUSED 127.0.0.1:8080
    at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1300:16) {
  errno: -111,
  code: 'ECONNREFUSED',
  syscall: 'connect',
  address: '127.0.0.1',
  port: 8080
}
"
    at Object.<anonymous> (/var/lib/jenkins/workspace/nt-rum_elastic-synthetics_PR-649/src/github.com/elastic/synthetics/__tests__/push/index.test.ts:150:20)
    at processTicksAndRejections (node:internal/process/task_queues:95:5) 
 ```
</p></details>
</ul>
    
</p></details>


<!-- STEPS ERRORS IF ANY -->














### Steps errors [![1](https://img.shields.io/badge/1%20-red)](https://apm-ci.elastic.co/blue/organizations/jenkins/apm-agent-rum%2Felastic-synthetics%2FPR-649/detail/PR-649/1//pipeline)
  <details><summary>Expand to view the steps failures</summary>
  <p>


  
  
  
  
##### `Runs the tests`
<ul>
<li>Took 2 min 59 sec . View more details <a href="https://apm-ci.elastic.co//blue/rest/organizations/jenkins/pipelines/apm-agent-rum/pipelines/elastic-synthetics/pipelines/PR-649/runs/1/steps/346/log/?start=0">here</a></li>
<li>Description: <code>./node_modules/.bin/jest --ci --reporters=default --reporters=jest-junit</code></l1>
</ul>
  
  </p>
  </details>



## :robot: GitHub comments

<details><summary>Expand to view the GitHub comments</summary>
<p>

To re-run your PR in the CI, just comment with:



- `/test` : Re-trigger the build.




</p>
</details>

<!--COMMENT_GENERATED_WITH_ID_comment.id-->

Patch:
diff --git a/package-lock.json b/package-lock.json
index d7f7fc2..3abe5a1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,13 +15,14 @@
         "commander": "^9.4.1",
         "deepmerge": "^4.2.2",
         "enquirer": "^2.3.6",
-        "esbuild": "^0.15.10",
+        "esbuild": "^0.15.13",
         "expect": "^28.1.3",
         "http-proxy": "^1.18.1",
         "kleur": "^4.1.5",
         "micromatch": "^4.0.5",
         "playwright-chromium": "=1.27.1",
         "playwright-core": "=1.27.1",
+        "semver": "^7.3.8",
         "sharp": "^0.31.1",
         "snakecase-keys": "^4.0.1",
         "sonic-boom": "^3.2.0",
@@ -674,9 +675,9 @@
       }
     },
     "node_modules/@esbuild/android-arm": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.10.tgz",
-      "integrity": "sha512-FNONeQPy/ox+5NBkcSbYJxoXj9GWu8gVGJTVmUyoOCKQFDTrHVKgNSzChdNt0I8Aj/iKcsDf2r9BFwv+FSNUXg==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.13.tgz",
+      "integrity": "sha512-RY2fVI8O0iFUNvZirXaQ1vMvK0xhCcl0gqRj74Z6yEiO1zAUa7hbsdwZM1kzqbxHK7LFyMizipfXT3JME+12Hw==",
       "cpu": [
         "arm"
       ],
@@ -689,9 +690,9 @@
       }
     },
     "node_modules/@esbuild/linux-loong64": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.10.tgz",
-      "integrity": "sha512-w0Ou3Z83LOYEkwaui2M8VwIp+nLi/NA60lBLMvaJ+vXVMcsARYdEzLNE7RSm4+lSg4zq4d7fAVuzk7PNQ5JFgg==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.13.tgz",
+      "integrity": "sha512-+BoyIm4I8uJmH/QDIH0fu7MG0AEx9OXEDXnqptXCwKOlOqZiS4iraH1Nr7/ObLMokW3sOCeBNyD68ATcV9b9Ag==",
       "cpu": [
         "loong64"
       ],
@@ -2687,9 +2688,9 @@
       }
     },
     "node_modules/esbuild": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.10.tgz",
-      "integrity": "sha512-N7wBhfJ/E5fzn/SpNgX+oW2RLRjwaL8Y0ezqNqhjD6w0H2p0rDuEz2FKZqpqLnO8DCaWumKe8dsC/ljvVSSxng==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.13.tgz",
+      "integrity": "sha512-Cu3SC84oyzzhrK/YyN4iEVy2jZu5t2fz66HEOShHURcjSkOSAVL8C/gfUT+lDJxkVHpg8GZ10DD0rMHRPqMFaQ==",
       "hasInstallScript": true,
       "bin": {
         "esbuild": "bin/esbuild"
@@ -2698,34 +2699,34 @@
         "node": ">=12"
       },
       "optionalDependencies": {
-        "@esbuild/android-arm": "0.15.10",
-        "@esbuild/linux-loong64": "0.15.10",
-        "esbuild-android-64": "0.15.10",
-        "esbuild-android-arm64": "0.15.10",
-        "esbuild-darwin-64": "0.15.10",
-        "esbuild-darwin-arm64": "0.15.10",
-        "esbuild-freebsd-64": "0.15.10",
-        "esbuild-freebsd-arm64": "0.15.10",
-        "esbuild-linux-32": "0.15.10",
-        "esbuild-linux-64": "0.15.10",
-        "esbuild-linux-arm": "0.15.10",
-        "esbuild-linux-arm64": "0.15.10",
-        "esbuild-linux-mips64le": "0.15.10",
-        "esbuild-linux-ppc64le": "0.15.10",
-        "esbuild-linux-riscv64": "0.15.10",
-        "esbuild-linux-s390x": "0.15.10",
-        "esbuild-netbsd-64": "0.15.10",
-        "esbuild-openbsd-64": "0.15.10",
-        "esbuild-sunos-64": "0.15.10",
-        "esbuild-windows-32": "0.15.10",
-        "esbuild-windows-64": "0.15.10",
-        "esbuild-windows-arm64": "0.15.10"
+        "@esbuild/android-arm": "0.15.13",
+        "@esbuild/linux-loong64": "0.15.13",
+        "esbuild-android-64": "0.15.13",
+        "esbuild-android-arm64": "0.15.13",
+        "esbuild-darwin-64": "0.15.13",
+        "esbuild-darwin-arm64": "0.15.13",
+        "esbuild-freebsd-64": "0.15.13",
+        "esbuild-freebsd-arm64": "0.15.13",
+        "esbuild-linux-32": "0.15.13",
+        "esbuild-linux-64": "0.15.13",
+        "esbuild-linux-arm": "0.15.13",
+        "esbuild-linux-arm64": "0.15.13",
+        "esbuild-linux-mips64le": "0.15.13",
+        "esbuild-linux-ppc64le": "0.15.13",
+        "esbuild-linux-riscv64": "0.15.13",
+        "esbuild-linux-s390x": "0.15.13",
+        "esbuild-netbsd-64": "0.15.13",
+        "esbuild-openbsd-64": "0.15.13",
+        "esbuild-sunos-64": "0.15.13",
+        "esbuild-windows-32": "0.15.13",
+        "esbuild-windows-64": "0.15.13",
+        "esbuild-windows-arm64": "0.15.13"
       }
     },
     "node_modules/esbuild-android-64": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.10.tgz",
-      "integrity": "sha512-UI7krF8OYO1N7JYTgLT9ML5j4+45ra3amLZKx7LO3lmLt1Ibn8t3aZbX5Pu4BjWiqDuJ3m/hsvhPhK/5Y/YpnA==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.13.tgz",
+      "integrity": "sha512-yRorukXBlokwTip+Sy4MYskLhJsO0Kn0/Fj43s1krVblfwP+hMD37a4Wmg139GEsMLl+vh8WXp2mq/cTA9J97g==",
       "cpu": [
         "x64"
       ],
@@ -2738,9 +2739,9 @@
       }
     },
     "node_modules/esbuild-android-arm64": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.10.tgz",
-      "integrity": "sha512-EOt55D6xBk5O05AK8brXUbZmoFj4chM8u3riGflLa6ziEoVvNjRdD7Cnp82NHQGfSHgYR06XsPI8/sMuA/cUwg==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.13.tgz",
+      "integrity": "sha512-TKzyymLD6PiVeyYa4c5wdPw87BeAiTXNtK6amWUcXZxkV51gOk5u5qzmDaYSwiWeecSNHamFsaFjLoi32QR5/w==",
       "cpu": [
         "arm64"
       ],
@@ -2753,9 +2754,9 @@
       }
     },
     "node_modules/esbuild-darwin-64": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.10.tgz",
-      "integrity": "sha512-hbDJugTicqIm+WKZgp208d7FcXcaK8j2c0l+fqSJ3d2AzQAfjEYDRM3Z2oMeqSJ9uFxyj/muSACLdix7oTstRA==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.13.tgz",
+      "integrity": "sha512-WAx7c2DaOS6CrRcoYCgXgkXDliLnFv3pQLV6GeW1YcGEZq2Gnl8s9Pg7ahValZkpOa0iE/ojRVQ87sbUhF1Cbg==",
       "cpu": [
         "x64"
       ],
@@ -2768,9 +2769,9 @@
       }
     },
     "node_modules/esbuild-darwin-arm64": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.10.tgz",
-      "integrity": "sha512-M1t5+Kj4IgSbYmunf2BB6EKLkWUq+XlqaFRiGOk8bmBapu9bCDrxjf4kUnWn59Dka3I27EiuHBKd1rSO4osLFQ==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.13.tgz",
+      "integrity": "sha512-U6jFsPfSSxC3V1CLiQqwvDuj3GGrtQNB3P3nNC3+q99EKf94UGpsG9l4CQ83zBs1NHrk1rtCSYT0+KfK5LsD8A==",
       "cpu": [
         "arm64"
       ],
@@ -2783,9 +2784,9 @@
       }
     },
     "node_modules/esbuild-freebsd-64": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.10.tgz",
-      "integrity": "sha512-KMBFMa7C8oc97nqDdoZwtDBX7gfpolkk6Bcmj6YFMrtCMVgoU/x2DI1p74DmYl7CSS6Ppa3xgemrLrr5IjIn0w==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.13.tgz",
+      "integrity": "sha512-whItJgDiOXaDG/idy75qqevIpZjnReZkMGCgQaBWZuKHoElDJC1rh7MpoUgupMcdfOd+PgdEwNQW9DAE6i8wyA==",
       "cpu": [
         "x64"
       ],
@@ -2798,9 +2799,9 @@
       }
     },
     "node_modules/esbuild-freebsd-arm64": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.10.tgz",
-      "integrity": "sha512-m2KNbuCX13yQqLlbSojFMHpewbn8wW5uDS6DxRpmaZKzyq8Dbsku6hHvh2U+BcLwWY4mpgXzFUoENEf7IcioGg==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.13.tgz",
+      "integrity": "sha512-6pCSWt8mLUbPtygv7cufV0sZLeylaMwS5Fznj6Rsx9G2AJJsAjQ9ifA+0rQEIg7DwJmi9it+WjzNTEAzzdoM3Q==",
       "cpu": [
         "arm64"
       ],
@@ -2813,9 +2814,9 @@
       }
     },
     "node_modules/esbuild-linux-32": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.10.tgz",
-      "integrity": "sha512-guXrwSYFAvNkuQ39FNeV4sNkNms1bLlA5vF1H0cazZBOLdLFIny6BhT+TUbK/hdByMQhtWQ5jI9VAmPKbVPu1w==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.13.tgz",
+      "integrity": "sha512-VbZdWOEdrJiYApm2kkxoTOgsoCO1krBZ3quHdYk3g3ivWaMwNIVPIfEE0f0XQQ0u5pJtBsnk2/7OPiCFIPOe/w==",
       "cpu": [
         "ia32"
       ],
@@ -2828,9 +2829,9 @@
       }
     },
     "node_modules/esbuild-linux-64": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.10.tgz",
-      "integrity": "sha512-jd8XfaSJeucMpD63YNMO1JCrdJhckHWcMv6O233bL4l6ogQKQOxBYSRP/XLWP+6kVTu0obXovuckJDcA0DKtQA==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.13.tgz",
+      "integrity": "sha512-rXmnArVNio6yANSqDQlIO4WiP+Cv7+9EuAHNnag7rByAqFVuRusLbGi2697A5dFPNXoO//IiogVwi3AdcfPC6A==",
       "cpu": [
         "x64"
       ],
@@ -2843,9 +2844,9 @@
       }
     },
     "node_modules/esbuild-linux-arm": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.10.tgz",
-      "integrity": "sha512-6N8vThLL/Lysy9y4Ex8XoLQAlbZKUyExCWyayGi2KgTBelKpPgj6RZnUaKri0dHNPGgReJriKVU6+KDGQwn10A==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.13.tgz",
+      "integrity": "sha512-Ac6LpfmJO8WhCMQmO253xX2IU2B3wPDbl4IvR0hnqcPrdfCaUa2j/lLMGTjmQ4W5JsJIdHEdW12dG8lFS0MbxQ==",
       "cpu": [
         "arm"
       ],
@@ -2858,9 +2859,9 @@
       }
     },
     "node_modules/esbuild-linux-arm64": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.10.tgz",
-      "integrity": "sha512-GByBi4fgkvZFTHFDYNftu1DQ1GzR23jws0oWyCfhnI7eMOe+wgwWrc78dbNk709Ivdr/evefm2PJiUBMiusS1A==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.13.tgz",
+      "integrity": "sha512-alEMGU4Z+d17U7KQQw2IV8tQycO6T+rOrgW8OS22Ua25x6kHxoG6Ngry6Aq6uranC+pNWNMB6aHFPh7aTQdORQ==",
       "cpu": [
         "arm64"
       ],
@@ -2873,9 +2874,9 @@
       }
     },
     "node_modules/esbuild-linux-mips64le": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.10.tgz",
-      "integrity": "sha512-BxP+LbaGVGIdQNJUNF7qpYjEGWb0YyHVSKqYKrn+pTwH/SiHUxFyJYSP3pqkku61olQiSBnSmWZ+YUpj78Tw7Q==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.13.tgz",
+      "integrity": "sha512-47PgmyYEu+yN5rD/MbwS6DxP2FSGPo4Uxg5LwIdxTiyGC2XKwHhHyW7YYEDlSuXLQXEdTO7mYe8zQ74czP7W8A==",
       "cpu": [
         "mips64el"
       ],
@@ -2888,9 +2889,9 @@
       }
     },
     "node_modules/esbuild-linux-ppc64le": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.10.tgz",
-      "integrity": "sha512-LoSQCd6498PmninNgqd/BR7z3Bsk/mabImBWuQ4wQgmQEeanzWd5BQU2aNi9mBURCLgyheuZS6Xhrw5luw3OkQ==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.13.tgz",
+      "integrity": "sha512-z6n28h2+PC1Ayle9DjKoBRcx/4cxHoOa2e689e2aDJSaKug3jXcQw7mM+GLg+9ydYoNzj8QxNL8ihOv/OnezhA==",
       "cpu": [
         "ppc64"
       ],
@@ -2903,9 +2904,9 @@
       }
     },
     "node_modules/esbuild-linux-riscv64": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.10.tgz",
-      "integrity": "sha512-Lrl9Cr2YROvPV4wmZ1/g48httE8z/5SCiXIyebiB5N8VT7pX3t6meI7TQVHw/wQpqP/AF4SksDuFImPTM7Z32Q==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.13.tgz",
+      "integrity": "sha512-+Lu4zuuXuQhgLUGyZloWCqTslcCAjMZH1k3Xc9MSEJEpEFdpsSU0sRDXAnk18FKOfEjhu4YMGaykx9xjtpA6ow==",
       "cpu": [
         "riscv64"
       ],
@@ -2918,9 +2919,9 @@
       }
     },
     "node_modules/esbuild-linux-s390x": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.10.tgz",
-      "integrity": "sha512-ReP+6q3eLVVP2lpRrvl5EodKX7EZ1bS1/z5j6hsluAlZP5aHhk6ghT6Cq3IANvvDdscMMCB4QEbI+AjtvoOFpA==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.13.tgz",
+      "integrity": "sha512-BMeXRljruf7J0TMxD5CIXS65y7puiZkAh+s4XFV9qy16SxOuMhxhVIXYLnbdfLrsYGFzx7U9mcdpFWkkvy/Uag==",
       "cpu": [
         "s390x"
       ],
@@ -2933,9 +2934,9 @@
       }
     },
     "node_modules/esbuild-netbsd-64": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.10.tgz",
-      "integrity": "sha512-iGDYtJCMCqldMskQ4eIV+QSS/CuT7xyy9i2/FjpKvxAuCzrESZXiA1L64YNj6/afuzfBe9i8m/uDkFHy257hTw==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.13.tgz",
+      "integrity": "sha512-EHj9QZOTel581JPj7UO3xYbltFTYnHy+SIqJVq6yd3KkCrsHRbapiPb0Lx3EOOtybBEE9EyqbmfW1NlSDsSzvQ==",
       "cpu": [
         "x64"
       ],
@@ -2948,9 +2949,9 @@
       }
     },
     "node_modules/esbuild-openbsd-64": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.10.tgz",
-      "integrity": "sha512-ftMMIwHWrnrYnvuJQRJs/Smlcb28F9ICGde/P3FUTCgDDM0N7WA0o9uOR38f5Xe2/OhNCgkjNeb7QeaE3cyWkQ==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.13.tgz",
+      "integrity": "sha512-nkuDlIjF/sfUhfx8SKq0+U+Fgx5K9JcPq1mUodnxI0x4kBdCv46rOGWbuJ6eof2n3wdoCLccOoJAbg9ba/bT2w==",
       "cpu": [
         "x64"
       ],
@@ -2963,9 +2964,9 @@
       }
     },
     "node_modules/esbuild-sunos-64": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.10.tgz",
-      "integrity": "sha512-mf7hBL9Uo2gcy2r3rUFMjVpTaGpFJJE5QTDDqUFf1632FxteYANffDZmKbqX0PfeQ2XjUDE604IcE7OJeoHiyg==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.13.tgz",
+      "integrity": "sha512-jVeu2GfxZQ++6lRdY43CS0Tm/r4WuQQ0Pdsrxbw+aOrHQPHV0+LNOLnvbN28M7BSUGnJnHkHm2HozGgNGyeIRw==",
       "cpu": [
         "x64"
       ],
@@ -2978,9 +2979,9 @@
       }
     },
     "node_modules/esbuild-windows-32": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.10.tgz",
-      "integrity": "sha512-ttFVo+Cg8b5+qHmZHbEc8Vl17kCleHhLzgT8X04y8zudEApo0PxPg9Mz8Z2cKH1bCYlve1XL8LkyXGFjtUYeGg==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.13.tgz",
+      "integrity": "sha512-XoF2iBf0wnqo16SDq+aDGi/+QbaLFpkiRarPVssMh9KYbFNCqPLlGAWwDvxEVz+ywX6Si37J2AKm+AXq1kC0JA==",
       "cpu": [
         "ia32"
       ],
@@ -2993,9 +2994,9 @@
       }
     },
     "node_modules/esbuild-windows-64": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.10.tgz",
-      "integrity": "sha512-2H0gdsyHi5x+8lbng3hLbxDWR7mKHWh5BXZGKVG830KUmXOOWFE2YKJ4tHRkejRduOGDrBvHBriYsGtmTv3ntA==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.13.tgz",
+      "integrity": "sha512-Et6htEfGycjDrtqb2ng6nT+baesZPYQIW+HUEHK4D1ncggNrDNk3yoboYQ5KtiVrw/JaDMNttz8rrPubV/fvPQ==",
       "cpu": [
         "x64"
       ],
@@ -3008,9 +3009,9 @@
       }
     },
     "node_modules/esbuild-windows-arm64": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.10.tgz",
-      "integrity": "sha512-S+th4F+F8VLsHLR0zrUcG+Et4hx0RKgK1eyHc08kztmLOES8BWwMiaGdoW9hiXuzznXQ0I/Fg904MNbr11Nktw==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.13.tgz",
+      "integrity": "sha512-3bv7tqntThQC9SWLRouMDmZnlOukBhOCTlkzNqzGCmrkCJI7io5LLjwJBOVY6kOUlIvdxbooNZwjtBvj+7uuVg==",
       "cpu": [
         "arm64"
       ],
@@ -5886,9 +5887,9 @@
       "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
     },
     "node_modules/semver": {
-      "version": "7.3.7",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
-      "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
+      "version": "7.3.8",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
+      "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
       "dependencies": {
         "lru-cache": "^6.0.0"
       },
@@ -7274,15 +7275,15 @@
       }
     },
     "@esbuild/android-arm": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.10.tgz",
-      "integrity": "sha512-FNONeQPy/ox+5NBkcSbYJxoXj9GWu8gVGJTVmUyoOCKQFDTrHVKgNSzChdNt0I8Aj/iKcsDf2r9BFwv+FSNUXg==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.13.tgz",
+      "integrity": "sha512-RY2fVI8O0iFUNvZirXaQ1vMvK0xhCcl0gqRj74Z6yEiO1zAUa7hbsdwZM1kzqbxHK7LFyMizipfXT3JME+12Hw==",
       "optional": true
     },
     "@esbuild/linux-loong64": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.10.tgz",
-      "integrity": "sha512-w0Ou3Z83LOYEkwaui2M8VwIp+nLi/NA60lBLMvaJ+vXVMcsARYdEzLNE7RSm4+lSg4zq4d7fAVuzk7PNQ5JFgg==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.13.tgz",
+      "integrity": "sha512-+BoyIm4I8uJmH/QDIH0fu7MG0AEx9OXEDXnqptXCwKOlOqZiS4iraH1Nr7/ObLMokW3sOCeBNyD68ATcV9b9Ag==",
       "optional": true
     },
     "@eslint/eslintrc": {
@@ -8787,152 +8788,152 @@
       }
     },
     "esbuild": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.10.tgz",
-      "integrity": "sha512-N7wBhfJ/E5fzn/SpNgX+oW2RLRjwaL8Y0ezqNqhjD6w0H2p0rDuEz2FKZqpqLnO8DCaWumKe8dsC/ljvVSSxng==",
-      "requires": {
-        "@esbuild/android-arm": "0.15.10",
-        "@esbuild/linux-loong64": "0.15.10",
-        "esbuild-android-64": "0.15.10",
-        "esbuild-android-arm64": "0.15.10",
-        "esbuild-darwin-64": "0.15.10",
-        "esbuild-darwin-arm64": "0.15.10",
-        "esbuild-freebsd-64": "0.15.10",
-        "esbuild-freebsd-arm64": "0.15.10",
-        "esbuild-linux-32": "0.15.10",
-        "esbuild-linux-64": "0.15.10",
-        "esbuild-linux-arm": "0.15.10",
-        "esbuild-linux-arm64": "0.15.10",
-        "esbuild-linux-mips64le": "0.15.10",
-        "esbuild-linux-ppc64le": "0.15.10",
-        "esbuild-linux-riscv64": "0.15.10",
-        "esbuild-linux-s390x": "0.15.10",
-        "esbuild-netbsd-64": "0.15.10",
-        "esbuild-openbsd-64": "0.15.10",
-        "esbuild-sunos-64": "0.15.10",
-        "esbuild-windows-32": "0.15.10",
-        "esbuild-windows-64": "0.15.10",
-        "esbuild-windows-arm64": "0.15.10"
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.13.tgz",
+      "integrity": "sha512-Cu3SC84oyzzhrK/YyN4iEVy2jZu5t2fz66HEOShHURcjSkOSAVL8C/gfUT+lDJxkVHpg8GZ10DD0rMHRPqMFaQ==",
+      "requires": {
+        "@esbuild/android-arm": "0.15.13",
+        "@esbuild/linux-loong64": "0.15.13",
+        "esbuild-android-64": "0.15.13",
+        "esbuild-android-arm64": "0.15.13",
+        "esbuild-darwin-64": "0.15.13",
+        "esbuild-darwin-arm64": "0.15.13",
+        "esbuild-freebsd-64": "0.15.13",
+        "esbuild-freebsd-arm64": "0.15.13",
+        "esbuild-linux-32": "0.15.13",
+        "esbuild-linux-64": "0.15.13",
+        "esbuild-linux-arm": "0.15.13",
+        "esbuild-linux-arm64": "0.15.13",
+        "esbuild-linux-mips64le": "0.15.13",
+        "esbuild-linux-ppc64le": "0.15.13",
+        "esbuild-linux-riscv64": "0.15.13",
+        "esbuild-linux-s390x": "0.15.13",
+        "esbuild-netbsd-64": "0.15.13",
+        "esbuild-openbsd-64": "0.15.13",
+        "esbuild-sunos-64": "0.15.13",
+        "esbuild-windows-32": "0.15.13",
+        "esbuild-windows-64": "0.15.13",
+        "esbuild-windows-arm64": "0.15.13"
       }
     },
     "esbuild-android-64": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.10.tgz",
-      "integrity": "sha512-UI7krF8OYO1N7JYTgLT9ML5j4+45ra3amLZKx7LO3lmLt1Ibn8t3aZbX5Pu4BjWiqDuJ3m/hsvhPhK/5Y/YpnA==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.13.tgz",
+      "integrity": "sha512-yRorukXBlokwTip+Sy4MYskLhJsO0Kn0/Fj43s1krVblfwP+hMD37a4Wmg139GEsMLl+vh8WXp2mq/cTA9J97g==",
       "optional": true
     },
     "esbuild-android-arm64": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.10.tgz",
-      "integrity": "sha512-EOt55D6xBk5O05AK8brXUbZmoFj4chM8u3riGflLa6ziEoVvNjRdD7Cnp82NHQGfSHgYR06XsPI8/sMuA/cUwg==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.13.tgz",
+      "integrity": "sha512-TKzyymLD6PiVeyYa4c5wdPw87BeAiTXNtK6amWUcXZxkV51gOk5u5qzmDaYSwiWeecSNHamFsaFjLoi32QR5/w==",
       "optional": true
     },
     "esbuild-darwin-64": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.10.tgz",
-      "integrity": "sha512-hbDJugTicqIm+WKZgp208d7FcXcaK8j2c0l+fqSJ3d2AzQAfjEYDRM3Z2oMeqSJ9uFxyj/muSACLdix7oTstRA==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.13.tgz",
+      "integrity": "sha512-WAx7c2DaOS6CrRcoYCgXgkXDliLnFv3pQLV6GeW1YcGEZq2Gnl8s9Pg7ahValZkpOa0iE/ojRVQ87sbUhF1Cbg==",
       "optional": true
     },
     "esbuild-darwin-arm64": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.10.tgz",
-      "integrity": "sha512-M1t5+Kj4IgSbYmunf2BB6EKLkWUq+XlqaFRiGOk8bmBapu9bCDrxjf4kUnWn59Dka3I27EiuHBKd1rSO4osLFQ==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.13.tgz",
+      "integrity": "sha512-U6jFsPfSSxC3V1CLiQqwvDuj3GGrtQNB3P3nNC3+q99EKf94UGpsG9l4CQ83zBs1NHrk1rtCSYT0+KfK5LsD8A==",
       "optional": true
     },
     "esbuild-freebsd-64": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.10.tgz",
-      "integrity": "sha512-KMBFMa7C8oc97nqDdoZwtDBX7gfpolkk6Bcmj6YFMrtCMVgoU/x2DI1p74DmYl7CSS6Ppa3xgemrLrr5IjIn0w==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.13.tgz",
+      "integrity": "sha512-whItJgDiOXaDG/idy75qqevIpZjnReZkMGCgQaBWZuKHoElDJC1rh7MpoUgupMcdfOd+PgdEwNQW9DAE6i8wyA==",
       "optional": true
     },
     "esbuild-freebsd-arm64": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.10.tgz",
-      "integrity": "sha512-m2KNbuCX13yQqLlbSojFMHpewbn8wW5uDS6DxRpmaZKzyq8Dbsku6hHvh2U+BcLwWY4mpgXzFUoENEf7IcioGg==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.13.tgz",
+      "integrity": "sha512-6pCSWt8mLUbPtygv7cufV0sZLeylaMwS5Fznj6Rsx9G2AJJsAjQ9ifA+0rQEIg7DwJmi9it+WjzNTEAzzdoM3Q==",
       "optional": true
     },
     "esbuild-linux-32": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.10.tgz",
-      "integrity": "sha512-guXrwSYFAvNkuQ39FNeV4sNkNms1bLlA5vF1H0cazZBOLdLFIny6BhT+TUbK/hdByMQhtWQ5jI9VAmPKbVPu1w==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.13.tgz",
+      "integrity": "sha512-VbZdWOEdrJiYApm2kkxoTOgsoCO1krBZ3quHdYk3g3ivWaMwNIVPIfEE0f0XQQ0u5pJtBsnk2/7OPiCFIPOe/w==",
       "optional": true
     },
     "esbuild-linux-64": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.10.tgz",
-      "integrity": "sha512-jd8XfaSJeucMpD63YNMO1JCrdJhckHWcMv6O233bL4l6ogQKQOxBYSRP/XLWP+6kVTu0obXovuckJDcA0DKtQA==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.13.tgz",
+      "integrity": "sha512-rXmnArVNio6yANSqDQlIO4WiP+Cv7+9EuAHNnag7rByAqFVuRusLbGi2697A5dFPNXoO//IiogVwi3AdcfPC6A==",
       "optional": true
     },
     "esbuild-linux-arm": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.10.tgz",
-      "integrity": "sha512-6N8vThLL/Lysy9y4Ex8XoLQAlbZKUyExCWyayGi2KgTBelKpPgj6RZnUaKri0dHNPGgReJriKVU6+KDGQwn10A==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.13.tgz",
+      "integrity": "sha512-Ac6LpfmJO8WhCMQmO253xX2IU2B3wPDbl4IvR0hnqcPrdfCaUa2j/lLMGTjmQ4W5JsJIdHEdW12dG8lFS0MbxQ==",
       "optional": true
     },
     "esbuild-linux-arm64": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.10.tgz",
-      "integrity": "sha512-GByBi4fgkvZFTHFDYNftu1DQ1GzR23jws0oWyCfhnI7eMOe+wgwWrc78dbNk709Ivdr/evefm2PJiUBMiusS1A==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.13.tgz",
+      "integrity": "sha512-alEMGU4Z+d17U7KQQw2IV8tQycO6T+rOrgW8OS22Ua25x6kHxoG6Ngry6Aq6uranC+pNWNMB6aHFPh7aTQdORQ==",
       "optional": true
     },
     "esbuild-linux-mips64le": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.10.tgz",
-      "integrity": "sha512-BxP+LbaGVGIdQNJUNF7qpYjEGWb0YyHVSKqYKrn+pTwH/SiHUxFyJYSP3pqkku61olQiSBnSmWZ+YUpj78Tw7Q==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.13.tgz",
+      "integrity": "sha512-47PgmyYEu+yN5rD/MbwS6DxP2FSGPo4Uxg5LwIdxTiyGC2XKwHhHyW7YYEDlSuXLQXEdTO7mYe8zQ74czP7W8A==",
       "optional": true
     },
     "esbuild-linux-ppc64le": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.10.tgz",
-      "integrity": "sha512-LoSQCd6498PmninNgqd/BR7z3Bsk/mabImBWuQ4wQgmQEeanzWd5BQU2aNi9mBURCLgyheuZS6Xhrw5luw3OkQ==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.13.tgz",
+      "integrity": "sha512-z6n28h2+PC1Ayle9DjKoBRcx/4cxHoOa2e689e2aDJSaKug3jXcQw7mM+GLg+9ydYoNzj8QxNL8ihOv/OnezhA==",
       "optional": true
     },
     "esbuild-linux-riscv64": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.10.tgz",
-      "integrity": "sha512-Lrl9Cr2YROvPV4wmZ1/g48httE8z/5SCiXIyebiB5N8VT7pX3t6meI7TQVHw/wQpqP/AF4SksDuFImPTM7Z32Q==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.13.tgz",
+      "integrity": "sha512-+Lu4zuuXuQhgLUGyZloWCqTslcCAjMZH1k3Xc9MSEJEpEFdpsSU0sRDXAnk18FKOfEjhu4YMGaykx9xjtpA6ow==",
       "optional": true
     },
     "esbuild-linux-s390x": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.10.tgz",
-      "integrity": "sha512-ReP+6q3eLVVP2lpRrvl5EodKX7EZ1bS1/z5j6hsluAlZP5aHhk6ghT6Cq3IANvvDdscMMCB4QEbI+AjtvoOFpA==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.13.tgz",
+      "integrity": "sha512-BMeXRljruf7J0TMxD5CIXS65y7puiZkAh+s4XFV9qy16SxOuMhxhVIXYLnbdfLrsYGFzx7U9mcdpFWkkvy/Uag==",
       "optional": true
     },
     "esbuild-netbsd-64": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.10.tgz",
-      "integrity": "sha512-iGDYtJCMCqldMskQ4eIV+QSS/CuT7xyy9i2/FjpKvxAuCzrESZXiA1L64YNj6/afuzfBe9i8m/uDkFHy257hTw==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.13.tgz",
+      "integrity": "sha512-EHj9QZOTel581JPj7UO3xYbltFTYnHy+SIqJVq6yd3KkCrsHRbapiPb0Lx3EOOtybBEE9EyqbmfW1NlSDsSzvQ==",
       "optional": true
     },
     "esbuild-openbsd-64": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.10.tgz",
-      "integrity": "sha512-ftMMIwHWrnrYnvuJQRJs/Smlcb28F9ICGde/P3FUTCgDDM0N7WA0o9uOR38f5Xe2/OhNCgkjNeb7QeaE3cyWkQ==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.13.tgz",
+      "integrity": "sha512-nkuDlIjF/sfUhfx8SKq0+U+Fgx5K9JcPq1mUodnxI0x4kBdCv46rOGWbuJ6eof2n3wdoCLccOoJAbg9ba/bT2w==",
       "optional": true
     },
     "esbuild-sunos-64": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.10.tgz",
-      "integrity": "sha512-mf7hBL9Uo2gcy2r3rUFMjVpTaGpFJJE5QTDDqUFf1632FxteYANffDZmKbqX0PfeQ2XjUDE604IcE7OJeoHiyg==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.13.tgz",
+      "integrity": "sha512-jVeu2GfxZQ++6lRdY43CS0Tm/r4WuQQ0Pdsrxbw+aOrHQPHV0+LNOLnvbN28M7BSUGnJnHkHm2HozGgNGyeIRw==",
       "optional": true
     },
     "esbuild-windows-32": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.10.tgz",
-      "integrity": "sha512-ttFVo+Cg8b5+qHmZHbEc8Vl17kCleHhLzgT8X04y8zudEApo0PxPg9Mz8Z2cKH1bCYlve1XL8LkyXGFjtUYeGg==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.13.tgz",
+      "integrity": "sha512-XoF2iBf0wnqo16SDq+aDGi/+QbaLFpkiRarPVssMh9KYbFNCqPLlGAWwDvxEVz+ywX6Si37J2AKm+AXq1kC0JA==",
       "optional": true
     },
     "esbuild-windows-64": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.10.tgz",
-      "integrity": "sha512-2H0gdsyHi5x+8lbng3hLbxDWR7mKHWh5BXZGKVG830KUmXOOWFE2YKJ4tHRkejRduOGDrBvHBriYsGtmTv3ntA==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.13.tgz",
+      "integrity": "sha512-Et6htEfGycjDrtqb2ng6nT+baesZPYQIW+HUEHK4D1ncggNrDNk3yoboYQ5KtiVrw/JaDMNttz8rrPubV/fvPQ==",
       "optional": true
     },
     "esbuild-windows-arm64": {
-      "version": "0.15.10",
-      "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.10.tgz",
-      "integrity": "sha512-S+th4F+F8VLsHLR0zrUcG+Et4hx0RKgK1eyHc08kztmLOES8BWwMiaGdoW9hiXuzznXQ0I/Fg904MNbr11Nktw==",
+      "version": "0.15.13",
+      "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.13.tgz",
+      "integrity": "sha512-3bv7tqntThQC9SWLRouMDmZnlOukBhOCTlkzNqzGCmrkCJI7io5LLjwJBOVY6kOUlIvdxbooNZwjtBvj+7uuVg==",
       "optional": true
     },
     "escalade": {
@@ -11043,9 +11044,9 @@
       "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
     },
     "semver": {
-      "version": "7.3.7",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
-      "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
+      "version": "7.3.8",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
+      "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
       "requires": {
         "lru-cache": "^6.0.0"
       }
diff --git a/package.json b/package.json
index 57c41a8..614a8f1 100644
--- a/package.json
+++ b/package.json
@@ -46,13 +46,14 @@
     "commander": "^9.4.1",
     "deepmerge": "^4.2.2",
     "enquirer": "^2.3.6",
-    "esbuild": "^0.15.10",
+    "esbuild": "^0.15.13",
     "expect": "^28.1.3",
     "http-proxy": "^1.18.1",
     "kleur": "^4.1.5",
     "micromatch": "^4.0.5",
     "playwright-chromium": "=1.27.1",
     "playwright-core": "=1.27.1",
+    "semver": "^7.3.8",
     "sharp": "^0.31.1",
     "snakecase-keys": "^4.0.1",
     "sonic-boom": "^3.2.0",
diff --git a/src/core/runner.ts b/src/core/runner.ts
index 770ced0..082c645 100644
--- a/src/core/runner.ts
+++ b/src/core/runner.ts
@@ -403,9 +403,7 @@ export default class Runner {
       if (!journey.isMatch(match, tags)) {
         continue;
       }
-
       this.#currentJourney = journey;
-
       /**
        * Execute dummy callback to get all monitor specific
        * configurations for the current journey
diff --git a/src/dsl/monitor.ts b/src/dsl/monitor.ts
index 510b017..bfe19dc 100644
--- a/src/dsl/monitor.ts
+++ b/src/dsl/monitor.ts
@@ -23,6 +23,7 @@
  *
  */
 
+import { createHash } from 'crypto';
 import merge from 'deepmerge';
 import { bold, red } from 'kleur/colors';
 import {
@@ -94,6 +95,11 @@ export class Monitor {
     this.filter = filter;
   }
 
+  hash(): string {
+    const hash = createHash('sha256');
+    return hash.update(JSON.stringify(this.config)).digest('base64');
+  }
+
   validate() {
     const schedule = this.config.schedule;
     if (ALLOWED_SCHEDULES.includes(schedule)) {
diff --git a/src/helpers.ts b/src/helpers.ts
index c4d4dfc..e33193d 100644
--- a/src/helpers.ts
+++ b/src/helpers.ts
@@ -339,16 +339,27 @@ export function safeNDJSONParse(data: string | string[]) {
 }
 
 // Console helpers
-export function write(message: string) {
-  process.stderr.write(message + '\n');
+export function write(message: string, live?: boolean) {
+  process.stderr.write(message + (live ? '\r' : '\n'));
 }
 
 export function progress(message: string) {
   write(cyan(bold(`${symbols.progress} ${message}`)));
 }
 
-export function apiProgress(message: string) {
-  write(grey(`> ${message}`));
+export async function liveProgress(promise: Promise<any>, message: string) {
+  const start = now();
+  const interval = setInterval(() => {
+    apiProgress(`${message} (${Math.trunc(now() - start)}ms)`, true);
+  }, 500);
+  promise.finally(() => clearInterval(interval));
+  const result = await promise;
+  apiProgress(`${message} (${Math.trunc(now() - start)}ms)`);
+  return result;
+}
+
+export function apiProgress(message: string, live = false) {
+  write(grey(`> ${message}`), live);
 }
 
 export function error(message: string) {
@@ -370,11 +381,3 @@ export function removeTrailingSlash(url: string) {
 export function getMonitorManagementURL(url) {
   return removeTrailingSlash(url) + '/app/uptime/manage-monitors/all';
 }
-
-export function getArrayChunks<T>(arr: T[], chunkSize: number): T[][] {
-  const chunks = [];
-  for (let i = 0; i < arr.length; i += chunkSize) {
-    chunks.push(arr.slice(i, i + chunkSize));
-  }
-  return chunks;
-}
diff --git a/src/locations/index.ts b/src/locations/index.ts
index a0296b7..1972e25 100644
--- a/src/locations/index.ts
+++ b/src/locations/index.ts
@@ -23,13 +23,9 @@
  *
  */
 
-import {
-  formatAPIError,
-  formatNotFoundError,
-  sendRequest,
-  ok,
-} from '../push/request';
-import { indent, removeTrailingSlash, write } from '../helpers';
+import { sendReqAndHandleError } from '../push/request';
+import { indent, write } from '../helpers';
+import { generateURL } from '../push/utils';
 
 export type LocationCmdOptions = {
   auth: string;
@@ -49,21 +45,12 @@ export type LocationAPIResponse = {
 const PRIVATE_KEYWORD = '(private)';
 
 export async function getLocations(options: LocationCmdOptions) {
-  const url =
-    removeTrailingSlash(options.url) + '/internal/uptime/service/locations';
-  const { body, statusCode } = await sendRequest({
-    url,
+  const resp = await sendReqAndHandleError<LocationAPIResponse>({
+    url: generateURL(options, 'location'),
     method: 'GET',
     auth: options.auth,
   });
-  if (statusCode === 404) {
-    throw formatNotFoundError(await body.text());
-  }
-  if (!ok(statusCode)) {
-    const { error, message } = await body.json();
-    throw formatAPIError(statusCode, error, message);
-  }
-  return ((await body.json()) as LocationAPIResponse).locations;
+  return resp.locations;
 }
 
 export function formatLocations(locations: Array<LocationMetadata>) {
diff --git a/src/push/index.ts b/src/push/index.ts
index 5e143c7..93736ee 100644
--- a/src/push/index.ts
+++ b/src/push/index.ts
@@ -22,118 +22,107 @@
  * THE SOFTWARE.
  *
  */
-
+import semver from 'semver';
 import { readFile, writeFile } from 'fs/promises';
 import { prompt } from 'enquirer';
 import { bold, grey } from 'kleur/colors';
 import {
-  ok,
-  formatAPIError,
-  formatFailedMonitors,
-  formatNotFoundError,
-  formatStaleMonitors,
-} from './request';
-import { buildMonitorSchema, createMonitors, MonitorSchema } from './monitor';
+  getLocalMonitors,
+  buildMonitorSchema,
+  diffMonitors as diffMonitorHashIDs,
+  MonitorSchema,
+} from './monitor';
 import { ALLOWED_SCHEDULES, Monitor } from '../dsl/monitor';
 import {
   progress,
-  apiProgress,
+  liveProgress,
   write,
   error,
   warn,
   indent,
-  safeNDJSONParse,
   done,
   getMonitorManagementURL,
-  getArrayChunks,
 } from '../helpers';
 import type { PushOptions, ProjectSettings } from '../common_types';
 import { findSyntheticsConfig, readConfig } from '../config';
+import {
+  bulkDeleteMonitors,
+  bulkGetMonitors,
+  bulkPutMonitors,
+  getVersion,
+  createMonitorsLegacy,
+  CHUNK_SIZE,
+} from './kibana_api';
+import { getChunks, logDiff } from './utils';
 
 export async function push(monitors: Monitor[], options: PushOptions) {
-  let schemas: MonitorSchema[] = [];
-  if (monitors.length > 0) {
-    const duplicates = trackDuplicates(monitors);
-    if (duplicates.size > 0) {
-      throw error(formatDuplicateError(duplicates));
-    }
+  const duplicates = trackDuplicates(monitors);
+  if (duplicates.size > 0) {
+    throw error(formatDuplicateError(duplicates));
+  }
+  progress(`Pushing monitors for project: ${options.id}`);
 
-    progress(`preparing all monitors`);
-    schemas = await buildMonitorSchema(monitors);
+  const stackVersion = await getVersion(options);
+  const isV2 = semver.satisfies(stackVersion, '>=8.6.0');
+  if (!isV2) {
+    return await pushLegacy(monitors, options, stackVersion);
+  }
+
+  const local = getLocalMonitors(monitors);
+  const { monitors: remote } = await bulkGetMonitors(options);
+  const { newIDs, changedIDs, removedIDs, unchangedIDs } = diffMonitorHashIDs(
+    local,
+    remote
+  );
+  logDiff(newIDs, changedIDs, removedIDs, unchangedIDs);
 
-    progress(`creating all monitors`);
-    const chunks = getArrayChunks(schemas, 10);
+  const updatedMonitors = new Set<string>([...changedIDs, ...newIDs]);
+  if (updatedMonitors.size > 0) {
+    const toBundle = monitors.filter(m => updatedMonitors.has(m.config.id));
+    progress(`bundling ${toBundle.length} monitors`);
+    const schemas = await buildMonitorSchema(toBundle, true);
+    const chunks = getChunks(schemas, CHUNK_SIZE);
     for (const chunk of chunks) {
-      await pushMonitors({ schemas: chunk, keepStale: true, options });
+      await liveProgress(
+        bulkPutMonitors(options, chunk),
+        `creating or updating ${chunk.length} monitors`
+      );
     }
-  } else {
-    write('');
-    const { deleteAll } = await prompt<{ deleteAll: boolean }>({
-      type: 'confirm',
-      skip() {
-        if (options.yes) {
-          this.initial = process.env.TEST_OVERRIDE ?? true;
-          return true;
-        }
-        return false;
-      },
-      name: 'deleteAll',
-      message: `Pushing without any monitors will delete all monitors associated with the project.\n Do you want to continue?`,
-      initial: false,
-    });
-    if (!deleteAll) {
-      throw warn('Push command Aborted');
+  }
+
+  if (removedIDs.size > 0) {
+    if (updatedMonitors.size === 0 && unchangedIDs.size === 0) {
+      await promptConfirmDeleteAll(options);
+    }
+    const chunks = getChunks(Array.from(removedIDs), CHUNK_SIZE);
+    for (const chunk of chunks) {
+      await liveProgress(
+        bulkDeleteMonitors(options, chunk),
+        `deleting ${chunk.length} monitors`
+      );
     }
   }
-  progress(`deleting all stale monitors`);
-  await pushMonitors({ schemas, keepStale: false, options });
 
   done(`Pushed: ${grey(getMonitorManagementURL(options.url))}`);
 }
 
-export async function pushMonitors({
-  schemas,
-  keepStale,
-  options,
-}: {
-  schemas: MonitorSchema[];
-  keepStale: boolean;
-  options: PushOptions;
-}) {
-  const { body, statusCode } = await createMonitors(
-    schemas,
-    options,
-    keepStale
-  );
-  if (statusCode === 404) {
-    throw formatNotFoundError(await body.text());
-  }
-  if (!ok(statusCode)) {
-    const { error, message } = await body.json();
-    throw formatAPIError(statusCode, error, message);
-  }
-  const allchunks = [];
-  for await (const data of body) {
-    allchunks.push(Buffer.from(data));
-  }
-  const chunks = safeNDJSONParse(Buffer.concat(allchunks).toString('utf-8'));
-  // Its kind of hacky for now where Kibana streams the response by
-  // writing the data as NDJSON events (data can be interleaved), we
-  // distinguish the final data by checking if the event was a progress vs complete event
-  for (const chunk of chunks) {
-    if (typeof chunk === 'string') {
-      // TODO: add progress back for all states once we get the fix
-      // on kibana side
-      keepStale && apiProgress(chunk);
-      continue;
-    }
-    const { failedMonitors, failedStaleMonitors } = chunk;
-    if (failedMonitors && failedMonitors.length > 0) {
-      throw formatFailedMonitors(failedMonitors);
-    }
-    if (failedStaleMonitors.length > 0) {
-      throw formatStaleMonitors(failedStaleMonitors);
-    }
+async function promptConfirmDeleteAll(options: PushOptions) {
+  write('');
+  const { deleteAll } = await prompt<{ deleteAll: boolean }>({
+    type: 'confirm',
+    skip() {
+      if (options.yes) {
+        this.initial = process.env.TEST_OVERRIDE ?? true;
+        return true;
+      }
+      return false;
+    },
+    name: 'deleteAll',
+    message: `Pushing without any monitors will delete all monitors associated with the project.\n Do you want to continue?`,
+    initial: false,
+  });
+  if (!deleteAll) {
+    throw warn('Push command Aborted');
   }
 }
 
@@ -174,7 +163,7 @@ export async function loadSettings() {
     }
     return config.project || ({} as ProjectSettings);
   } catch (e) {
-    throw error(`Aborted (missing synthetics config file), Project not set up corrrectly.
+    throw error(`Aborted (missing synthetics config file), Project not set up correctly.
 
 ${INSTALLATION_HELP}`);
   }
@@ -254,3 +243,40 @@ export async function catchIncorrectSettings(
     await overrideSettings(settings.id, options.id);
   }
 }
+
+export async function pushLegacy(
+  monitors: Monitor[],
+  options: PushOptions,
+  version: number
+) {
+  const noLightWeightSupport = semver.satisfies(version, '<8.5.0');
+  if (
+    noLightWeightSupport &&
+    monitors.some(monitor => monitor.type !== 'browser')
+  ) {
+    throw error(
+      `Aborted: Lightweight monitors are not supported in ${version}. Please upgrade to 8.5.0 or above.`
+    );
+  }
+
+  let schemas: MonitorSchema[] = [];
+  if (monitors.length > 0) {
+    progress(`bundling ${monitors.length} monitors`);
+    schemas = await buildMonitorSchema(monitors, false);
+    const chunks = getChunks(schemas, 10);
+    for (const chunk of chunks) {
+      await liveProgress(
+        createMonitorsLegacy({ schemas: chunk, keepStale: true, options }),
+        `creating or updating ${chunk.length} monitors`
+      );
+    }
+  } else {
+    await promptConfirmDeleteAll(options);
+  }
+  await liveProgress(
+    createMonitorsLegacy({ schemas, keepStale: false, options }),
+    `deleting all stale monitors`
+  );
+
+  done(`Pushed: ${grey(getMonitorManagementURL(options.url))}`);
+}
diff --git a/src/push/kibana_api.ts b/src/push/kibana_api.ts
new file mode 100644
index 0000000..8102a56
--- /dev/null
+++ b/src/push/kibana_api.ts
@@ -0,0 +1,180 @@
+/**
+ * MIT License
+ *
+ * Copyright (c) 2020-present, Elastic NV
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+import { PushOptions } from '../common_types';
+import { safeNDJSONParse } from '../helpers';
+import { MonitorHashID, MonitorSchema } from './monitor';
+import {
+  formatFailedMonitors,
+  formatStaleMonitors,
+  handleError,
+  sendReqAndHandleError,
+  sendRequest,
+  APIMonitorError,
+} from './request';
+import { generateURL } from './utils';
+
+// Default chunk size for bulk put / delete
+export const CHUNK_SIZE = 100;
+
+export type PutResponse = {
+  createdMonitors: string[];
+  updatedMonitors: string[];
+  failedMonitors: APIMonitorError[];
+};
+
+export async function bulkPutMonitors(
+  options: PushOptions,
+  schemas: MonitorSchema[]
+) {
+  const resp = await sendReqAndHandleError<PutResponse>({
+    url: generateURL(options, 'bulk_update') + '/_bulk_update',
+    method: 'PUT',
+    auth: options.auth,
+    body: JSON.stringify({ monitors: schemas }),
+  });
+  const { failedMonitors } = resp;
+  if (failedMonitors && failedMonitors.length > 0) {
+    throw formatFailedMonitors(failedMonitors);
+  }
+  return resp;
+}
+
+export type GetResponse = {
+  total: number;
+  monitors: MonitorHashID[];
+  after_key?: string;
+};
+
+export async function bulkGetMonitors(
+  options: PushOptions
+): Promise<GetResponse> {
+  let afterKey = null;
+  let total = 0;
+  const monitors: MonitorHashID[] = [];
+  let url = generateURL(options, 'bulk_get');
+  do {
+    if (afterKey) {
+      url += `?search_after=${afterKey}`;
+    }
+    const resp = await sendReqAndHandleError<GetResponse>({
+      url,
+      method: 'GET',
+      auth: options.auth,
+    });
+    afterKey = resp.after_key;
+
+    // The first page gives the total number of monitors
+    if (total == 0) {
+      total = resp.total;
+    }
+    monitors.push(...resp.monitors);
+  } while (afterKey);
+
+  return { total, monitors };
+}
+
+export type DeleteResponse = {
+  deleted_monitors: string[];
+};
+
+export async function bulkDeleteMonitors(
+  options: PushOptions,
+  monitorIDs: string[]
+) {
+  return await sendReqAndHandleError<DeleteResponse>({
+    url: generateURL(options, 'bulk_delete') + '/_bulk_delete',
+    method: 'DELETE',
+    auth: options.auth,
+    body: JSON.stringify({ monitors: monitorIDs }),
+  });
+}
+
+type StatusResponse = {
+  version: {
+    number: number;
+  };
+};
+
+export async function getVersion(options: PushOptions) {
+  const data = await sendReqAndHandleError<StatusResponse>({
+    url: generateURL(options, 'status'),
+    method: 'GET',
+    auth: options.auth,
+  });
+  return data.version.number;
+}
+
+export type LegacyAPISchema = {
+  project: string;
+  keep_stale: boolean;
+  monitors: MonitorSchema[];
+};
+
+export async function createMonitorsLegacy({
+  schemas,
+  keepStale,
+  options,
+}: {
+  schemas: MonitorSchema[];
+  keepStale: boolean;
+  options: PushOptions;
+}) {
+  const schema: LegacyAPISchema = {
+    project: options.id,
+    keep_stale: keepStale,
+    monitors: schemas,
+  };
+  const url = generateURL(options, 'legacy');
+  const { body, statusCode } = await sendRequest({
+    url,
+    method: 'PUT',
+    auth: options.auth,
+    body: JSON.stringify(schema),
+  });
+
+  const resBody = await handleError(statusCode, url, body);
+  const allchunks = [];
+  for await (const data of resBody) {
+    allchunks.push(Buffer.from(data));
+  }
+  const chunks = safeNDJSONParse(Buffer.concat(allchunks).toString('utf-8'));
+  // Its kind of hacky for now where Kibana streams the response by
+  // writing the data as NDJSON events (data can be interleaved), we
+  // distinguish the final data by checking if the event was a progress vs complete event
+  for (const chunk of chunks) {
+    if (typeof chunk === 'string') {
+      // Ignore the progress from Kibana as we chunk the requests
+      continue;
+    }
+    const { failedMonitors, failedStaleMonitors } = chunk;
+    if (failedMonitors && failedMonitors.length > 0) {
+      throw formatFailedMonitors(failedMonitors);
+    }
+    if (failedStaleMonitors.length > 0) {
+      throw formatStaleMonitors(failedStaleMonitors);
+    }
+  }
+}
diff --git a/src/push/monitor.ts b/src/push/monitor.ts
index 64155a0..750bd6f 100644
--- a/src/push/monitor.ts
+++ b/src/push/monitor.ts
@@ -28,14 +28,7 @@ import { join } from 'path';
 import { LineCounter, parseDocument, YAMLSeq, YAMLMap } from 'yaml';
 import { bold, red } from 'kleur/colors';
 import { Bundler } from './bundler';
-import { sendRequest } from './request';
-import {
-  removeTrailingSlash,
-  SYNTHETICS_PATH,
-  totalist,
-  indent,
-  warn,
-} from '../helpers';
+import { SYNTHETICS_PATH, totalist, indent, warn } from '../helpers';
 import { LocationsMap } from '../locations/public-locations';
 import { ALLOWED_SCHEDULES, Monitor, MonitorConfig } from '../dsl/monitor';
 import { PushOptions } from '../common_types';
@@ -44,12 +37,14 @@ export type MonitorSchema = Omit<MonitorConfig, 'locations'> & {
   locations: string[];
   content?: string;
   filter?: Monitor['filter'];
+  hash?: string;
 };
 
-export type APISchema = {
-  project: string;
-  keep_stale: boolean;
-  monitors: MonitorSchema[];
+// Abbreviated monitor info, as often returned by the API,
+// just the journey ID and hash
+export type MonitorHashID = {
+  journey_id?: string;
+  hash?: string;
 };
 
 function translateLocation(locations?: MonitorConfig['locations']) {
@@ -57,7 +52,67 @@ function translateLocation(locations?: MonitorConfig['locations']) {
   return locations.map(loc => LocationsMap[loc] || loc).filter(Boolean);
 }
 
-export async function buildMonitorSchema(monitors: Monitor[]) {
+class RemoteDiffResult {
+  // The set of monitor IDs that have been added
+  newIDs = new Set<string>();
+  // Monitor IDs that are different locally than remotely
+  changedIDs = new Set<string>();
+  // Monitor IDs that are no longer present locally
+  removedIDs = new Set<string>();
+  // Monitor IDs that are identical on the remote server
+  unchangedIDs = new Set<string>();
+}
+
+export function diffMonitors(
+  local: MonitorHashID[],
+  remote: MonitorHashID[]
+): RemoteDiffResult {
+  const result = new RemoteDiffResult();
+  const localMonitorsIDToHash = new Map<string, string>();
+  for (const hashID of local) {
+    localMonitorsIDToHash.set(hashID.journey_id, hashID.hash);
+  }
+  const remoteMonitorsIDToHash = new Map<string, string>();
+  for (const hashID of remote) {
+    remoteMonitorsIDToHash.set(hashID.journey_id, hashID.hash);
+  }
+
+  // Compare local to remote
+  for (const [localID, localHash] of localMonitorsIDToHash) {
+    // Hash is reset to '' when a monitor is edited on the UI
+    if (!remoteMonitorsIDToHash.has(localID)) {
+      result.newIDs.add(localID);
+    } else {
+      const remoteHash = remoteMonitorsIDToHash.get(localID);
+      if (remoteHash != localHash) {
+        result.changedIDs.add(localID);
+      } else if (remoteHash === localHash) {
+        result.unchangedIDs.add(localID);
+      }
+    }
+    // We no longer need to process this ID, removing it here
+    // reduces the numbers considered in the next phase
+    remoteMonitorsIDToHash.delete(localID);
+  }
+
+  for (const [id] of remoteMonitorsIDToHash) {
+    result.removedIDs.add(id);
+  }
+  return result;
+}
+
+export function getLocalMonitors(monitors: Monitor[]) {
+  const data: MonitorHashID[] = [];
+  for (const monitor of monitors) {
+    data.push({
+      journey_id: monitor.config.id,
+      hash: monitor.hash(),
+    });
+  }
+  return data;
+}
+
+export async function buildMonitorSchema(monitors: Monitor[], isV2: boolean) {
   /**
    * Set up the bundle artifacts path which can be used to
    * create the bundles required for uploading journeys
@@ -69,10 +124,13 @@ export async function buildMonitorSchema(monitors: Monitor[]) {
 
   for (const monitor of monitors) {
     const { source, config, filter, type } = monitor;
-    const schema = {
+    const schema: MonitorSchema = {
       ...config,
       locations: translateLocation(config.locations),
     };
+    if (isV2) {
+      schema.hash = monitor.hash();
+    }
     if (type === 'browser') {
       const outPath = join(bundlePath, config.name + '.zip');
       const content = await bundler.build(source.file, outPath);
@@ -216,24 +274,3 @@ export function nearestSchedule(schedule: number) {
   }
   return ALLOWED_SCHEDULES[end];
 }
-
-export async function createMonitors(
-  monitors: MonitorSchema[],
-  options: PushOptions,
-  keepStale: boolean
-) {
-  const schema: APISchema = {
-    project: options.id,
-    keep_stale: keepStale,
-    monitors,
-  };
-
-  return await sendRequest({
-    url:
-      removeTrailingSlash(options.url) +
-      `/s/${options.space}/api/synthetics/service/project/monitors`,
-    method: 'PUT',
-    auth: options.auth,
-    body: JSON.stringify(schema),
-  });
-}
diff --git a/src/push/request.ts b/src/push/request.ts
index 864b617..db77c0d 100644
--- a/src/push/request.ts
+++ b/src/push/request.ts
@@ -24,7 +24,8 @@
  */
 
 import { bold, red, yellow } from 'kleur/colors';
-import { Dispatcher, request } from 'undici';
+import type { Dispatcher } from 'undici';
+import { request } from 'undici';
 import { indent, symbols } from '../helpers';
 
 /* eslint-disable @typescript-eslint/no-var-requires */
@@ -51,6 +52,40 @@ export async function sendRequest(options: APIRequestOptions) {
   });
 }
 
+export async function sendReqAndHandleError<T>(
+  options: APIRequestOptions
+): Promise<T> {
+  const { statusCode, body } = await sendRequest(options);
+  return await (await handleError(statusCode, options.url, body)).json();
+}
+
+// Handle bad status code errors from Kibana API and format the
+// error message to be displayed to the user.
+// returns the response stream if no error is found
+export async function handleError(
+  statusCode: number,
+  url: string,
+  body: Dispatcher.ResponseData['body']
+): Promise<Dispatcher.ResponseData['body']> {
+  if (statusCode === 404) {
+    throw formatNotFoundError(url, await body.text());
+  } else if (!ok(statusCode)) {
+    let parsed: { error: string; message: string };
+    try {
+      parsed = await body.json();
+    } catch (e) {
+      throw formatAPIError(
+        statusCode,
+        'unexpected non-JSON error',
+        await body.text()
+      );
+    }
+    throw formatAPIError(statusCode, parsed.error, parsed.message);
+  }
+
+  return body;
+}
+
 export function ok(statusCode: number) {
   return statusCode >= 200 && statusCode <= 299;
 }
@@ -61,10 +96,10 @@ export type APIMonitorError = {
   details: string;
 };
 
-export function formatNotFoundError(message: string) {
+export function formatNotFoundError(url: string, message: string) {
   return red(
     bold(
-      `${symbols['failed']} Please check your kibana url and try again - 404:${message}`
+      `${symbols['failed']} Please check your kibana url: ${url} and try again - 404:${message}`
     )
   );
 }
diff --git a/src/push/utils.ts b/src/push/utils.ts
new file mode 100644
index 0000000..a9d4f59
--- /dev/null
+++ b/src/push/utils.ts
@@ -0,0 +1,77 @@
+/**
+ * MIT License
+ *
+ * Copyright (c) 2020-present, Elastic NV
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+import { progress, removeTrailingSlash } from '../helpers';
+import { green, red, grey, yellow } from 'kleur/colors';
+import { PushOptions } from '../common_types';
+
+export function logDiff<T extends Set<string>>(
+  newIDs: T,
+  changedIDs: T,
+  removedIDs: T,
+  unchangedIDs: T
+) {
+  progress(
+    'Monitor Diff: ' +
+      green(`Added(${newIDs.size}) `) +
+      yellow(`Updated(${changedIDs.size}) `) +
+      red(`Removed(${removedIDs.size}) `) +
+      grey(`Unchanged(${unchangedIDs.size})`)
+  );
+}
+
+export function getChunks(arr: any[], size: number) {
+  const chunks = [];
+  for (let i = 0; i < arr.length; i += size) {
+    chunks.push(arr.slice(i, i + size));
+  }
+  return chunks;
+}
+
+type Operation =
+  | 'status'
+  | 'bulk_get'
+  | 'bulk_update'
+  | 'bulk_delete'
+  | 'legacy'
+  | 'location';
+
+export function generateURL(options: PushOptions, operation: Operation) {
+  const url = removeTrailingSlash(options.url);
+  switch (operation) {
+    case 'status':
+      return `${url}/s/${options.space}/api/status`;
+    case 'bulk_get':
+    case 'bulk_update':
+    case 'bulk_delete':
+      return `${url}/s/${options.space}/api/synthetics/project/${options.id}/monitors`;
+    case 'legacy':
+      return `${url}/s/${options.space}/api/synthetics/service/project/monitors`;
+    case 'location':
+      return `${url}/internal/uptime/service/locations`;
+    default:
+      throw new Error('Invalid operation');
+  }
+}


Test patch:
diff --git a/__tests__/push/__snapshots__/index.test.ts.snap b/__tests__/push/__snapshots__/index.test.ts.snap
index 727591a..25c1512 100644
--- a/__tests__/push/__snapshots__/index.test.ts.snap
+++ b/__tests__/push/__snapshots__/index.test.ts.snap
@@ -1,23 +1,5 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`Push API handle streamed response 1`] = `
-"> preparing all monitors
-> creating all monitors
-> 2 monitors created successfully
-> journey 2 monitor updated successfully
-> deleting all stale monitors
-✓ Pushed: http://localhost:54455/stream/app/uptime/manage-monitors/all
-"
-`;
-
-exports[`Push API handle sync response 1`] = `
-"> preparing all monitors
-> creating all monitors
-> deleting all stale monitors
-✓ Pushed: http://localhost:54455/sync/app/uptime/manage-monitors/all
-"
-`;
-
 exports[`Push error on empty project id 1`] = `
 "Aborted. Invalid synthetics project settings.
 
diff --git a/__tests__/push/__snapshots__/request.test.ts.snap b/__tests__/push/__snapshots__/request.test.ts.snap
index 0064451..d21aa3d 100644
--- a/__tests__/push/__snapshots__/request.test.ts.snap
+++ b/__tests__/push/__snapshots__/request.test.ts.snap
@@ -1,6 +1,6 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`Push api request format 404 error 1`] = `"✖ Please check your kibana url and try again - 404:Not Found"`;
+exports[`Push api request format 404 error 1`] = `"✖ Please check your kibana url: http://foo and try again - 404:Not Found"`;
 
 exports[`Push api request format api error 1`] = `
 "✖ Error
diff --git a/__tests__/push/index.test.ts b/__tests__/push/index.test.ts
index 85787b6..17f400d 100644
--- a/__tests__/push/index.test.ts
+++ b/__tests__/push/index.test.ts
@@ -28,7 +28,7 @@ import { mkdir, rm, writeFile } from 'fs/promises';
 import { join } from 'path';
 import { Monitor } from '../../src/dsl/monitor';
 import { formatDuplicateError } from '../../src/push';
-import { APISchema } from '../../src/push/monitor';
+import { createKibanaTestServer } from '../utils/kibana-test-server';
 import { Server } from '../utils/server';
 import { CLIMock } from '../utils/test-config';
 
@@ -69,7 +69,7 @@ describe('Push', () => {
   it('error when project is not setup', async () => {
     const output = await runPush();
     expect(output).toContain(
-      'Aborted (missing synthetics config file), Project not set up corrrectly.'
+      'Aborted (missing synthetics config file), Project not set up correctly.'
     );
   });
 
@@ -130,27 +130,6 @@ journey('journey 1', () => monitor.use({ id: 'j1', schedule: 8 }));`
     await rm(testJourney, { force: true });
   });
 
-  it('push with different id when overriden', async () => {
-    await fakeProjectSetup(
-      { id: 'test-project', space: 'dummy', url: 'http://localhost:8080' },
-      { locations: ['test-loc'], schedule: 3 }
-    );
-    const testJourney = join(PROJECT_DIR, 'test.journey.ts');
-    await writeFile(
-      testJourney,
-      `import {journey, monitor} from '../../../src';
-journey('journey 1', () => monitor.use({ id: 'j1' }));`
-    );
-    const output = await runPush(
-      [...DEFAULT_ARGS, '-y', '--id', 'new-project'],
-      {
-        TEST_OVERRIDE: true,
-      }
-    );
-    expect(output).toContain('preparing all monitors');
-    await rm(testJourney, { force: true });
-  });
-
   it('errors on duplicate browser monitors', async () => {
     await fakeProjectSetup(
       { id: 'test-project' },
@@ -214,110 +193,59 @@ heartbeat.monitors:
     expect(formatDuplicateError(duplicates as Set<Monitor>)).toMatchSnapshot();
   });
 
-  it('abort when delete is skipped', async () => {
-    await fakeProjectSetup(
-      { id: 'test-project' },
-      { locations: ['test-loc'], schedule: 3 }
-    );
-    const output = await runPush([...DEFAULT_ARGS, '-y'], {
-      TEST_OVERRIDE: false,
-    });
-    expect(output).toContain('Push command Aborted');
-  });
-
-  it('delete entire project with --yes flag', async () => {
-    await fakeProjectSetup(
-      { id: 'test-project', space: 'dummy', url: 'http://localhost:8080' },
-      { locations: ['test-loc'], schedule: 3 }
-    );
-    const output = await runPush([...DEFAULT_ARGS, '-y']);
-    expect(output).toContain('deleting all stale monitors');
-  });
-
-  it('delete entire project with overrides', async () => {
-    await fakeProjectSetup(
-      { id: 'test-project', space: 'dummy', url: 'http://localhost:8080' },
-      { locations: ['test-loc'], schedule: 3 }
-    );
-    const output = await runPush([...DEFAULT_ARGS, '-y'], {
-      TEST_OVERRIDE: true,
-    });
-    expect(output).toContain('deleting all stale monitors');
-  });
-
-  describe('API', () => {
-    let server: Server;
-    beforeAll(async () => {
-      server = await Server.create({ port: 54455 });
-      const apiRes = { failedMonitors: [], failedStaleMonitors: [] };
-      server.route(
-        '/sync/s/dummy/api/synthetics/service/project/monitors',
-        (req, res) => {
-          res.end(JSON.stringify(apiRes));
-        }
-      );
-      server.route(
-        '/stream/s/dummy/api/synthetics/service/project/monitors',
-        async (req, res) => {
-          await new Promise(r => setTimeout(r, 20));
-          req.on('data', chunks => {
-            const schema = JSON.parse(chunks.toString()) as APISchema;
-            res.write(
-              JSON.stringify(
-                schema.monitors.length + ' monitors created successfully'
-              ) + '\n'
-            );
-            res.write(
-              JSON.stringify(
-                `${schema.monitors[1].name} monitor updated successfully`
-              ) + '\n'
-            );
-            if (!schema.keep_stale) {
-              // write more than the stream buffer to check the broken NDJSON data
-              res.write(
-                JSON.stringify(Buffer.from('a'.repeat(70000)).toString()) + '\n'
-              );
-            }
-          });
-          req.on('end', () => {
-            res.end(JSON.stringify(apiRes));
-          });
-        }
-      );
-      await fakeProjectSetup(
-        {
-          id: 'test-project',
-          space: 'dummy',
-        },
-        { locations: ['test-loc'], schedule: 3 }
-      );
-
-      await writeFile(
-        join(PROJECT_DIR, 'test.journey.ts'),
-        `import {journey, monitor} from '../../../src/index';
-journey('journey 1', () => monitor.use({ id: 'j1' }));
-journey('journey 2', () => monitor.use({ id: 'j2' }));`
-      );
-    });
-    afterAll(async () => {
-      await server.close();
-    });
-
-    it('handle sync response', async () => {
-      const output = await runPush([
-        '--url',
-        server.PREFIX + '/sync',
-        ...DEFAULT_ARGS,
-      ]);
-      expect(output).toMatchSnapshot();
-    });
-    it('handle streamed response', async () => {
-      const output = await runPush([
-        '--url',
-        server.PREFIX + '/stream',
-        ...DEFAULT_ARGS,
-      ]);
-      expect(output).toMatchSnapshot();
+  ['8.5.0', '8.6.0'].forEach(version => {
+    describe('API: ' + version, () => {
+      let server: Server;
+      const deleteProgress =
+        '8.5.0' === version
+          ? 'deleting all stale monitors'
+          : 'deleting 2 monitors';
+      beforeAll(async () => {
+        server = await createKibanaTestServer(version);
+        await fakeProjectSetup(
+          { id: 'test-project', space: 'dummy', url: server.PREFIX },
+          { locations: ['test-loc'], schedule: 3 }
+        );
+      });
+      afterAll(async () => {
+        await server.close();
+      });
+
+      it('abort when delete is skipped', async () => {
+        const output = await runPush([...DEFAULT_ARGS, '-y'], {
+          TEST_OVERRIDE: false,
+        });
+        expect(output).toContain('Push command Aborted');
+      });
+
+      it('delete entire project with --yes flag', async () => {
+        const output = await runPush([...DEFAULT_ARGS, '-y']);
+        expect(output).toContain(deleteProgress);
+      });
+
+      it('delete entire project with prompt override', async () => {
+        const output = await runPush([...DEFAULT_ARGS, '-y'], {
+          TEST_OVERRIDE: true,
+        });
+        expect(output).toContain(deleteProgress);
+      });
+
+      it('push journeys', async () => {
+        const testJourney = join(PROJECT_DIR, 'test.journey.ts');
+        await writeFile(
+          testJourney,
+          `import {journey, monitor} from '../../../src/index';
+        journey('journey 1', () => monitor.use({ id: 'j1' }));
+        journey('journey 2', () => monitor.use({ id: 'j2' }));`
+        );
+        const output = await runPush();
+        expect(output).toContain('Pushing monitors for project: test-project');
+        expect(output).toContain('bundling 2 monitors');
+        expect(output).toContain('creating or updating 2 monitors');
+        expect(output).toContain(deleteProgress);
+        expect(output).toContain('✓ Pushed:');
+        await rm(testJourney, { force: true });
+      });
     });
   });
 });
diff --git a/__tests__/push/monitor.test.ts b/__tests__/push/monitor.test.ts
index 5f23143..5e57bf4 100644
--- a/__tests__/push/monitor.test.ts
+++ b/__tests__/push/monitor.test.ts
@@ -28,15 +28,14 @@ import { join } from 'path';
 import { generateTempPath } from '../../src/helpers';
 import {
   buildMonitorSchema,
-  createMonitors,
   createLightweightMonitors,
+  diffMonitors,
   parseSchedule,
 } from '../../src/push/monitor';
 import { Server } from '../utils/server';
 import { createTestMonitor } from '../utils/test-config';
 
 describe('Monitors', () => {
-  const monitor = createTestMonitor('example.journey.ts');
   let server: Server;
   beforeAll(async () => {
     server = await Server.create();
@@ -48,29 +47,55 @@ describe('Monitors', () => {
     process.env.NO_COLOR = '';
   });
 
+  it('diff monitors', () => {
+    const local = [
+      { journey_id: 'j1', hash: 'hash1' },
+      { journey_id: 'j2', hash: 'hash2' },
+      { journey_id: 'j3', hash: 'hash3' },
+      { journey_id: 'j4', hash: 'hash4' },
+    ];
+    const remote = [
+      { journey_id: 'j1', hash: 'hash1' },
+      { journey_id: 'j2', hash: 'hash2-changed' },
+      { journey_id: 'j4', hash: '' }, // Hash reset in UI
+      { journey_id: 'j5', hash: 'hash5' },
+    ];
+    const result = diffMonitors(local, remote);
+    expect(Array.from(result.newIDs)).toEqual(['j3']);
+    expect(Array.from(result.changedIDs)).toEqual(['j2', 'j4']);
+    expect(Array.from(result.removedIDs)).toEqual(['j5']);
+    expect(Array.from(result.unchangedIDs)).toEqual(['j1']);
+  });
+
   it('build lightweight monitor schema', async () => {
-    const schema = await buildMonitorSchema([
-      createTestMonitor('heartbeat.yml', 'http'),
-    ]);
+    const schema = await buildMonitorSchema(
+      [createTestMonitor('heartbeat.yml', 'http')],
+      true
+    );
     expect(schema[0]).toEqual({
       id: 'test-monitor',
       name: 'test',
       schedule: 10,
       type: 'http',
       enabled: true,
+      hash: 'fS9hbiqcopwk3gMeDrqMgyp0mEpeyFWy6F/YFSnfWPE=',
       locations: ['europe-west2-a', 'australia-southeast1-a'],
       privateLocations: ['germany'],
     });
   });
 
   it('build browser monitor schema', async () => {
-    const schema = await buildMonitorSchema([monitor]);
+    const schema = await buildMonitorSchema(
+      [createTestMonitor('example.journey.ts')],
+      true
+    );
     expect(schema[0]).toEqual({
       id: 'test-monitor',
       name: 'test',
       schedule: 10,
       type: 'browser',
       enabled: true,
+      hash: 'I+bYcE74C35IaKpHeN04wBfinO3qEiqlcaksKFAmkBg=',
       locations: ['europe-west2-a', 'australia-southeast1-a'],
       privateLocations: ['germany'],
       content: expect.any(String),
@@ -97,40 +122,6 @@ describe('Monitors', () => {
     expect(parseSchedule('@every 10h2m10s')).toBe(60);
   });
 
-  it('api schema', async () => {
-    server.route(
-      '/s/dummy/api/synthetics/service/project/monitors',
-      (req, res) => {
-        let data = '';
-        req.on('data', chunks => {
-          data += chunks;
-        });
-        req.on('end', () => {
-          // Write the post data back
-          res.end(data.toString());
-        });
-      }
-    );
-    const schema = await buildMonitorSchema([monitor]);
-    const { statusCode, body } = await createMonitors(
-      schema,
-      {
-        url: `${server.PREFIX}`,
-        auth: 'apiKey',
-        id: 'blah',
-        space: 'dummy',
-      },
-      false
-    );
-
-    expect(statusCode).toBe(200);
-    expect(await body.json()).toEqual({
-      project: 'blah',
-      keep_stale: false,
-      monitors: schema,
-    });
-  });
-
   describe('Lightweight monitors', () => {
     const PROJECT_DIR = generateTempPath();
     const HB_SOURCE = join(PROJECT_DIR, 'heartbeat.yml');
diff --git a/__tests__/push/request.test.ts b/__tests__/push/request.test.ts
index 53305c9..27c844f 100644
--- a/__tests__/push/request.test.ts
+++ b/__tests__/push/request.test.ts
@@ -47,7 +47,7 @@ describe('Push api request', () => {
 
   it('format 404 error', () => {
     const message = 'Not Found';
-    expect(formatNotFoundError(message)).toMatchSnapshot();
+    expect(formatNotFoundError('http://foo', message)).toMatchSnapshot();
   });
 
   it('format failed monitors', () => {
diff --git a/__tests__/utils/kibana-test-server.ts b/__tests__/utils/kibana-test-server.ts
new file mode 100644
index 0000000..cb32340
--- /dev/null
+++ b/__tests__/utils/kibana-test-server.ts
@@ -0,0 +1,87 @@
+/**
+ * MIT License
+ *
+ * Copyright (c) 2020-present, Elastic NV
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+import { Server } from './server';
+import {
+  GetResponse,
+  LegacyAPISchema,
+  PutResponse,
+} from '../../src/push/kibana_api';
+
+export const createKibanaTestServer = async (kibanaVersion: string) => {
+  const server = await Server.create({ port: 54455 });
+  server.route('/s/dummy/api/status', (req, res) =>
+    res.end(JSON.stringify({ version: { number: kibanaVersion } }))
+  );
+  // Legacy
+  server.route(
+    '/s/dummy/api/synthetics/service/project/monitors',
+    async (req, res) => {
+      await new Promise(r => setTimeout(r, 20));
+      req.on('data', chunks => {
+        const schema = JSON.parse(chunks.toString()) as LegacyAPISchema;
+        res.write(
+          JSON.stringify(
+            schema.monitors.length + ' monitors created successfully'
+          ) + '\n'
+        );
+        if (!schema.keep_stale) {
+          // write more than the stream buffer to check the broken NDJSON data
+          res.write(
+            JSON.stringify(Buffer.from('a'.repeat(70000)).toString()) + '\n'
+          );
+        }
+      });
+      req.on('end', () => {
+        const apiRes = { failedMonitors: [], failedStaleMonitors: [] };
+        res.end(JSON.stringify(apiRes));
+      });
+    }
+  );
+
+  // Post 8.6
+  const basePath = '/s/dummy/api/synthetics/project/test-project/monitors';
+  server.route(basePath, (req, res) => {
+    const getResp = {
+      total: 2,
+      monitors: [
+        { journey_id: 'j3', hash: 'hash1' },
+        { journey_id: 'j4', hash: 'hash2' },
+      ],
+    } as GetResponse;
+    res.end(JSON.stringify(getResp));
+  });
+  server.route(basePath + '/_bulk_update', (req, res) => {
+    const updateResponse = {
+      createdMonitors: ['j1', 'j2'],
+      updatedMonitors: [],
+      failedMonitors: [],
+    } as PutResponse;
+    res.end(JSON.stringify(updateResponse));
+  });
+  server.route(basePath + '/_bulk_delete', (req, res) => {
+    res.end(JSON.stringify({ deleted_monitors: ['j3', 'j4'] }));
+  });
+  return server;
+};